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

@ingeniacode/fingerprint

v0.1.0

Published

A standalone, zero-dependency TypeScript library for browser fingerprinting. Collects signals from five independent browser APIs, hashes them with MurmurHash3 (32-bit), and returns a stable `visitorId` — no cookies, no storage, no server round-trips.

Readme

@ingeniacode/fingerprint

A standalone, zero-dependency TypeScript library for browser fingerprinting. Collects signals from five independent browser APIs, hashes them with MurmurHash3 (32-bit), and returns a stable visitorId — no cookies, no storage, no server round-trips.

Features

  • Zero runtime dependencies — pure TypeScript, native Web APIs only
  • Five signal collectors — Canvas, WebGL, Audio, Hardware, Fonts
  • Error boundaries — one blocked API never breaks the fingerprint
  • SSR-safe — returns a placeholder when window is unavailable
  • Consistent — same browser + hardware = same visitorId across calls
  • Lightweight — single async function, tree-shakeable ESM output

Installation

npm install @ingeniacode/fingerprint

API

import { getFingerprint } from '@ingeniacode/fingerprint';

const { visitorId, components } = await getFingerprint();

getFingerprint(): Promise<FingerprintResult>

Collects all signals concurrently and returns:

| Field | Type | Description | |---|---|---| | visitorId | string | 8-character lowercase hex hash (e.g. "a3f2b1c4") | | components | Record<string, string> | Raw output from each collector, keyed by name |

Component keys

| Key | Source | |---|---| | canvas | PNG data URL from a hidden 2D canvas | | webgl | GPU vendor, renderer, and hardware parameters | | audio | OfflineAudioContext DSP signature | | hardware | OS, screen, memory, concurrency, timezone | | fonts | List of detected system fonts |

SSR behaviour

When called outside a browser (Next.js SSR, Node.js, Cloudflare Workers), getFingerprint() returns immediately without accessing any browser API:

{ visitorId: 'ssr-placeholder', components: {} }

React

Hook

// hooks/useFingerprint.ts
import { useState, useEffect } from 'react';
import { getFingerprint, type FingerprintResult } from '@ingeniacode/fingerprint';

interface UseFingerprintResult {
  visitorId: string | null;
  components: FingerprintResult['components'];
  loading: boolean;
  error: Error | null;
}

export function useFingerprint(): UseFingerprintResult {
  const [visitorId, setVisitorId] = useState<string | null>(null);
  const [components, setComponents] = useState<FingerprintResult['components']>({});
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    let cancelled = false;

    getFingerprint()
      .then((result) => {
        if (!cancelled) {
          setVisitorId(result.visitorId);
          setComponents(result.components);
        }
      })
      .catch((err: unknown) => {
        if (!cancelled) {
          setError(err instanceof Error ? err : new Error(String(err)));
        }
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });

    return () => {
      cancelled = true;
    };
  }, []);

  return { visitorId, components, loading, error };
}

Component usage

// components/VisitorBadge.tsx
import { useFingerprint } from '../hooks/useFingerprint';

export function VisitorBadge() {
  const { visitorId, loading, error } = useFingerprint();

  if (loading) return <span>Identifying…</span>;
  if (error)   return <span>Fingerprint unavailable</span>;

  return (
    <div className="visitor-badge">
      <span className="label">Visitor ID</span>
      <code>{visitorId}</code>
    </div>
  );
}

Next.js (App Router)

Because getFingerprint() requires browser APIs, call it only in Client Components:

// app/components/FingerprintProvider.tsx
'use client';

import { createContext, useContext } from 'react';
import { useFingerprint } from '../hooks/useFingerprint';
import type { FingerprintResult } from '@ingeniacode/fingerprint';

interface FingerprintContextValue {
  visitorId: string | null;
  components: FingerprintResult['components'];
  loading: boolean;
}

const FingerprintContext = createContext<FingerprintContextValue>({
  visitorId: null,
  components: {},
  loading: true,
});

export function FingerprintProvider({ children }: { children: React.ReactNode }) {
  const { visitorId, components, loading } = useFingerprint();

  return (
    <FingerprintContext.Provider value={{ visitorId, components, loading }}>
      {children}
    </FingerprintContext.Provider>
  );
}

export const useVisitorId = () => useContext(FingerprintContext);
// app/layout.tsx
import { FingerprintProvider } from './components/FingerprintProvider';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <FingerprintProvider>
          {children}
        </FingerprintProvider>
      </body>
    </html>
  );
}
// app/page.tsx  (Server Component — reads from context via a Client Component)
'use client';

import { useVisitorId } from './components/FingerprintProvider';

export default function Page() {
  const { visitorId, loading } = useVisitorId();

  return (
    <main>
      <p>Visitor ID: {loading ? 'Loading…' : visitorId}</p>
    </main>
  );
}

Angular

Service

// fingerprint.service.ts
import { Injectable, signal } from '@angular/core';
import { getFingerprint, type FingerprintResult } from '@ingeniacode/fingerprint';

@Injectable({ providedIn: 'root' })
export class FingerprintService {
  readonly visitorId   = signal<string | null>(null);
  readonly components  = signal<FingerprintResult['components']>({});
  readonly loading     = signal(true);
  readonly error       = signal<Error | null>(null);

  constructor() {
    this.collect();
  }

  private collect(): void {
    getFingerprint()
      .then((result) => {
        this.visitorId.set(result.visitorId);
        this.components.set(result.components);
      })
      .catch((err: unknown) => {
        this.error.set(err instanceof Error ? err : new Error(String(err)));
      })
      .finally(() => {
        this.loading.set(false);
      });
  }
}

Component

// visitor-badge.component.ts
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FingerprintService } from './fingerprint.service';

@Component({
  selector: 'app-visitor-badge',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="visitor-badge">
      @if (fp.loading()) {
        <span>Identifying…</span>
      } @else if (fp.error()) {
        <span>Fingerprint unavailable</span>
      } @else {
        <span class="label">Visitor ID</span>
        <code>{{ fp.visitorId() }}</code>
      }
    </div>
  `,
})
export class VisitorBadgeComponent {
  protected fp = inject(FingerprintService);
}

Usage in a standalone app

// app.config.ts
import { ApplicationConfig } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    // FingerprintService is providedIn: 'root' — no extra registration needed
  ],
};
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';

bootstrapApplication(AppComponent, appConfig).catch(console.error);
// app.component.ts
import { Component } from '@angular/core';
import { VisitorBadgeComponent } from './visitor-badge.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [VisitorBadgeComponent],
  template: `<app-visitor-badge />`,
})
export class AppComponent {}

Angular Universal (SSR)

The library is SSR-safe out of the box. When Angular Universal renders on the server, getFingerprint() returns { visitorId: 'ssr-placeholder', components: {} } without touching any browser API. The real fingerprint is computed on the client after hydration.


How it works

getFingerprint()
│
├── canvas    → hidden <canvas> 2D draw → toDataURL()
├── webgl     → WebGLRenderingContext → GPU vendor/renderer + hw params
├── audio     → OfflineAudioContext oscillator → sum(|samples|)
├── hardware  → navigator + screen + Intl → JSON
└── fonts     → canvas measureText width comparison → detected[]
│
└── hashComponents([canvas, webgl, audio, hardware, fonts])
        │
        └── MurmurHash3 (32-bit x86) → 8-char hex → visitorId

Each collector runs concurrently. If any Web API is blocked or unavailable, that collector returns an empty string and the rest continue normally.

Collectors in detail

| Collector | Signal source | Varies by | |---|---|---| | Canvas | 2D rendering of text + shapes | GPU driver, OS font renderer, browser version | | WebGL | GPU parameters + unmasked vendor/renderer | GPU model, driver version | | Audio | OfflineAudioContext DSP output | CPU FPU, browser DSP implementation | | Hardware | navigator, screen, Intl, devicePixelRatio | OS, device, display, locale | | Fonts | Canvas text width per font family | Installed system fonts |

Hashing

Signals are joined with | and hashed with MurmurHash3 (32-bit x86), implemented in pure TypeScript using Math.imul() for correct 32-bit arithmetic. The output is an 8-character zero-padded lowercase hex string.

murmurhash3_x86_32("hello", 0) === 0x248bfa47
murmurhash3_x86_32("test",  0) === 0xba6bd213

Privacy considerations

  • No data is sent to any server by this library.
  • No cookies or storage (localStorage, sessionStorage, IndexedDB) are written.
  • All computation happens locally in the browser.
  • The visitorId is deterministic — the same device produces the same ID on every call.
  • Users who reset browser settings, clear site data, or switch browsers will receive a different visitorId.

Browser support

| Browser | Version | |---|---| | Chrome | 80+ | | Firefox | 75+ | | Safari | 14+ | | Edge | 80+ |

Requires: OfflineAudioContext, WebGLRenderingContext, HTMLCanvasElement, Intl.DateTimeFormat. All available in any modern browser released after 2020.

License

Apache 2.0 — see LICENSE.