npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kaidn/angular

v2.0.0

Published

Angular bindings for Kaidn device intelligence. A DI service over @kaidn/fp, built from runtime API only so no Angular compiler is involved.

Readme

@kaidn/angular

Angular bindings for Kaidn device intelligence. A DI service over @kaidn/fp, built from runtime API only so no Angular compiler is involved.

npm install @kaidn/angular
// app.config.ts
import { provideKaidn } from "@kaidn/angular";

bootstrapApplication(AppComponent, {
  providers: [provideKaidn({ publishableKey: "pk_live_…" })],
});
import { Component, inject } from "@angular/core";
import { KaidnService } from "@kaidn/angular";

@Component({ /* … */ })
export class SignupComponent {
  private readonly kaidn = inject(KaidnService);

  async submit() {
    const deviceId = await this.kaidn.getDeviceId();
    await fetch("/api/signup", {
      method: "POST",
      body: JSON.stringify({ email: this.email, deviceId }),
    });
  }
}

Why this exists

Angular decorators have to be processed by Angular's own compiler. That is why a library using them ships partial-Ivy output from ng-packagr and gets relinked against every consumer's Angular version, and why such libraries break on majors.

This package contains no Angular decorators. It is built from runtime API only: InjectionToken, inject, signal, and a factory provider. Plain tsc compiles it, there is nothing to relink, and there is no version of Angular that can break the build.

The whole cost is the provideKaidn(…) line you were writing anyway.

What it gives you, and what it deliberately does not

You get a device id. No score, and no verdict, because the browser is an untrusted place to make a decision: anything you render there, a determined visitor can edit. Send the id to your server, score it with @kaidn/sdk or any of the backend clients, and act on the verdict where nobody can reach it.

Reactive state, as signals

readonly deviceId = this.kaidn.deviceId;     // Signal<string | null>
readonly isLoading = this.kaidn.isLoading;   // Signal<boolean>
<button (click)="submit()" [disabled]="isLoading()">Create account</button>

They are read-only signals. A consumer that could .set() them would be writing a device id nothing collected.

SSR, and why there is no isPlatformBrowser check to write

getDeviceId() resolves to null when there is no window, so the same call is safe in a component that renders on the server and the client. Nothing collects during server rendering and there is no platform check for you to remember.

Collection happens when you ask

Nothing runs when the service is constructed. That is on purpose: collecting on demand puts the work at the moment somebody acts, which is when the signal is freshest, when consent is least ambiguous, and it keeps fingerprinting off every route of your app that has no signup on it.

It never throws

A blocked script, an ad blocker, an offline visitor, a browser that refuses the APIs: all of it resolves to null.

const deviceId = await this.kaidn.getDeviceId();   // string | null, never a throw

Fail open. A fraud tool that can take down your signup form is a worse problem than the fraud it was meant to stop, so a missing id means your server scores the event with one signal fewer rather than your form breaking.

Watching a signed-in session

Some signals only exist across time, because they are a change rather than a value: a VPN that drops mid-session and leaks the real home IP, a session that starts masking partway through, one device seen from many addresses. None of them can be read from a single page load.

export class AccountLayoutComponent implements OnInit {
  private readonly kaidn = inject(KaidnService);
  private readonly destroyRef = inject(DestroyRef);

  ngOnInit() {
    this.kaidn.watchSession({ destroyRef: this.destroyRef });
  }
}

Pass the DestroyRef and the observation ends when the component does. It is asked for explicitly rather than injected inside the service, because inject() only works in an injection context and ngOnInit is not one — a method that silently required a context you could not see would fail at runtime complaining about the wrong thing.

The beacon is free and you are billed per scored decision, so this adds signal without adding events.

Consent

provideKaidn({ publishableKey: "pk_live_…", consent: hasConsent ? "granted" : "denied" })

With consent: "denied" nothing collects anything, and watchSession() hands back an inert handle rather than nothing, so no caller has to branch on whether it got one. Fingerprinting reads properties of a visitor's device, which in several jurisdictions needs a lawful basis before it happens rather than a note in a policy afterwards.

Keys

The pk_live_… publishable key is meant to be visible in page source: it is domain-locked and can only send fingerprints. Your kdn_live_… secret key is the opposite, and passing one to provideKaidn throws where you wrote it rather than leaking into your bundle.

Get a publishable key from the dashboard under Fraud Scoring API → Device trackers.

API

| | | | --- | --- | | provideKaidn({ publishableKey, endpoint?, consent }) | Provider[] for your app config. Validates the key immediately | | KaidnService.getDeviceId({ timeoutMs? }) | Promise<string \| null>, never throws | | KaidnService.deviceId / isLoading / error | read-only signals | | KaidnService.watchSession({ intervalMs?, destroyRef? }) | { stop }. Re-beacons while a session is open | | KAIDN_CONFIG | the injection token, if you need the resolved config yourself |

Requires Angular 16 or newer, for signals.

Links

MIT