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

@ianmenethil/zp-devicefp

v0.2.0

Published

Async-first browser and device fingerprinting library with explainable signals and anomaly scoring.

Readme

Browser Fingerprint Library

Async-first TypeScript browser and device fingerprinting library. Collects 21 tiered browser signals, computes per-component SHA-256 hashes, returns a composite thumbprint, and includes an anti-spoof anomaly report with confidence scoring.

  • Zero runtime dependencies — no CDN loads, no npm deps at runtime.
  • ESM, CJS, and IIFE — single TypeScript source tree, three distribution formats.
  • 92 tests across 24 files, 94.9% line coverage, 82.3% function coverage.

Install

bun add @ianmenethil/zp-devicefp
import { createFingerprintClient } from '@ianmenethil/zp-devicefp';

Quick start

import { createFingerprintClient } from '@ianmenethil/zp-devicefp';

const client = await createFingerprintClient({ timeoutMs: 1500 });

// Async collection — all signals, parallel with timeouts
const result = await client.collect();

console.log(result.thumbprint); // "a1b2c3d4..." (64-char SHA-256 hex)
console.log(result.confidence); // 0.87
console.log(result.antiSpoof.score); // 0.94

Named exports

npm / ESM / CJS (@ianmenethil/zp-devicefp)

Functions

| Export | Signature | Description | | ------------------------- | ---------------------------------------------------------- | ----------------------------------------------- | | createFingerprintClient | (options?: CollectOptions) => Promise<FingerprintClient> | Factory — creates a configured client instance. |

Constants

| Export | Type | Description | | ------------------------- | --------------------------------------------- | ---------------------------------------------------------------- | | LIBRARY_VERSION | string | Semver string of the installed library (e.g. "0.1.1"). | | SCHEMA_VERSION | number | Integer schema version; bumped on breaking result-shape changes. | | CORE_SIGNALS | SignalName[] | The 10 signals collected by default. | | EXTENDED_SIGNALS | SignalName[] | The 11 signals requiring extended: true. | | SYNC_SIGNALS | SignalName[] | The 13 signals that support collectSync(). | | DEFAULT_TIMEOUT_MS | number | Default per-signal timeout: 1500. | | DEFAULT_FONT_LIST | string[] | The 70 font faces probed during font detection (Fingerprint2 / Cardinal Commerce set). | | DEFAULT_PERMISSION_NAMES| readonly [...] | The 4 permission names queried by the permissions signal. | | DEFAULT_UA_HINTS | readonly [...] | The 8 high-entropy UA-CH hints requested. |

TypeScript types — all declared in the package root and importable directly:

import type {
  // Client
  FingerprintClient,

  // Core result
  FingerprintResult,
  SignalResult,      // generic: SignalResult<T = unknown>
  SignalName,
  SignalStatus,
  AntiSpoofReport,
  CollectorDiagnostics,

  // Options
  CollectOptions,

  // Events
  EventPayloadMap,
  ProgressEvent,
  WarningEvent,
  CompleteEvent,
} from '@ianmenethil/zp-devicefp';

CDN / IIFE (window.DeviceFP)

The IIFE bundle exposes only the three runtime values — TypeScript types are compile-time only and are not present on the global:

| Property | Type | Description | | ------------------------- | ---------------------------------------------------------- | ----------------------------------------------- | | createFingerprintClient | (options?: CollectOptions) => Promise<FingerprintClient> | Factory — creates a configured client instance. | | LIBRARY_VERSION | string | Semver string of the running bundle. | | SCHEMA_VERSION | number | Integer schema version of the result shape. |


API overview

| Method | Returns | Description | | -------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------- | | client.collect(options?) | Promise<FingerprintResult> | Async collection. All signals run in parallel with per-signal timeouts. Emits events. | | client.collectSync(options?) | FingerprintResult | Sync collection. Only 13 of 21 signals support sync mode. No timeout/abort. | | client.on(event, callback) | () => void | Subscribe to progress, warning, or complete events during async collection. |

Events

client.on('progress', e => {
  // { completed: 3, total: 10, signal: 'canvas', result: SignalResult }
  console.log(`${e.completed}/${e.total}: ${e.signal}`);
});

client.on('warning', e => {
  // { signal?: 'webrtc', message: 'RTCPeerConnection not available' }
  console.warn(e.message);
});

client.on('complete', e => {
  // { result: FingerprintResult }
  console.log('Done', e.result.thumbprint);
});

Aborting

const controller = new AbortController();

const resultPromise = client.collect({ abortSignal: controller.signal });

// Cancel mid-flight
controller.abort();

const result = await resultPromise;
// Aborted signals appear as status: 'error' with error: 'Collection aborted.'

Options

interface CollectOptions {
  include?: SignalName[]; // whitelist: only collect these signals
  exclude?: SignalName[]; // blacklist: skip these signals
  timeoutMs?: number; // per-signal timeout (default: 1500)
  abortSignal?: AbortSignal; // cancel in-flight collection
  extended?: boolean; // include extended-tier signals (default: false)
  debug?: boolean; // enable debug logging (default: false)
}

Result shape

interface FingerprintResult {
  schemaVersion: number; // schema version (integer, bumped on breaking changes)
  libraryVersion: string; // library semver string
  thumbprint: string; // SHA-256 of all component hashes combined
  confidence: number; // 0–1 quality score
  componentHashes: Partial<Record<SignalName, string>>; // per-signal SHA-256 digests
  signals: Partial<Record<SignalName, SignalResult>>; // raw signal results
  antiSpoof: AntiSpoofReport; // coherence analysis and automation hints
  warnings: string[]; // non-fatal collection warnings
  diagnostics: CollectorDiagnostics; // run metadata
}

AntiSpoofReport

{
	score: number;             // 0 = automated/bot, 1 = genuine user
	anomalies: string[];       // signal inconsistencies (6 anomaly types)
	automationHints: string[]; // headless/bot indicators (2 hint types)
	fakedOS: boolean;          // UA's OS contradicts navigator.platform
	fakedBrowser: boolean;     // UA claims Chrome but window.chrome is absent
}

SignalResult

{
	status: 'ok' | 'unsupported' | 'blocked' | 'timeout' | 'error';
	value?: unknown;     // signal-specific data (only when status === 'ok')
	durationMs: number;  // wall-clock collection time
	error?: string;      // error description (when status !== 'ok')
}

Full API reference with all types, methods, events, options, confidence scoring, anti-spoof heuristics, and constants: docs/api-reference.md


Signals (21 total)

Core tier (10 signals, enabled by default)

| Signal | Sync | Description | | ---------- | ---- | ----------------------------------------------------------------------------------------------------------------------- | | ua | yes | navigator.userAgent, platform, webdriver, maxTouchPoints, cookieEnabled, vendor, doNotTrack (normalized), plugins (MIME format) | | uaHints | no | User-Agent Client Hints: brands, platform, high-entropy values (architecture, bitness, model, etc.) | | locale | yes | Language, languages, locale, calendar, numbering system, timeZone, formatted samples | | screen | yes | Screen width/height, colorDepth, orientation, DPR, fakedResolution (FP2 has_lied_resolution), ccaScreenSize (Cardinal bucket), media queries | | hardware | yes | navigator.hardwareConcurrency, deviceMemory, platform, maxTouchPoints, touchEventCreationSuccessful, onTouchStartAvailable | | storage | yes | localStorage, sessionStorage, IndexedDB, Web SQL availability (write-read-delete probe) | | fonts | yes | Installed font detection via hidden DOM span measurement (70 fonts, 3 generic families) | | canvas | yes | Canvas 2D rendering pattern → toDataURL() (GPU+driver fingerprint) | | webgl | yes | WebGL renderer/vendor (unmasked), version, extensions, max texture/viewport | | audio | no | OfflineAudioContext oscillator+compressor numeric signature |

Extended tier (11 signals, requires extended: true)

| Signal | Sync | Description | | ---------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | mediaDevices | no | Media device counts by kind (audioinput, audiooutput, videoinput) — no labels/IDs | | permissions | no | Permissions API state for geolocation, notifications, camera, microphone | | webrtc | no | RTCPeerConnection SDP codec/extmap capability hash | | frameInfo | yes | Iframe count, source domains, top-level window check | | networkInfo | yes | NetworkInformation API: effectiveType, downlink, RTT, saveData | | paymentSupport | yes | window.PaymentRequest availability | | referrerInfo | yes | document.referrer | | navigationInfo | yes | PerformanceNavigationTiming.type (navigate/reload/back_forward/prerender) | | riskSignals | no | Bot/headless detection: webdriver, headless UA, missing chrome object, zero dimensions, empty plugins, empty languages, UA-CH consistency, notification permission, permissions API headless hint, time to capture | | adblock | no | Ad blocker detection — appends div.adsbox to body, checks offsetHeight === 0 after 100 ms | | geolocation | no | Geographic coordinates (latitude, longitude, accuracy) via Geolocation API — prompts user permission |

Complete per-signal documentation with value shapes, APIs used, and edge cases: docs/signals.md


Anti-spoof heuristics (6 anomalies + 2 automation hints)

Anomalies (coherence violations, –0.12 each)

| Code | What it detects | | ---------------------------------- | ----------------------------------------------- | | ua_platform_mismatch | navigator.platform contradicts UA-CH platform | | touch_claim_without_touch_points | iPhone UA but maxTouchPoints is 0 | | mobile_ua_desktop_screen | Android UA with screen width >= 1600px | | windows_ua_mac_platform | Windows UA but platform reports macOS | | implausible_device_memory | deviceMemory < 0.25 or > 64 GB | | tiny_screen_geometry | Screen width and height both < 200px |

Automation hints (bot/headless indicators, –0.18 each)

| Code | What it detects | | --------------------- | ------------------------------- | | navigator_webdriver | navigator.webdriver is truthy | | headless_user_agent | UA contains "headless" |

Named booleans on AntiSpoofReport

| Field | Derived from | Meaning | | ------------- | ----------------------------- | ---------------------------------------------------- | | fakedOS | ua_platform_mismatch anomaly | UA's OS contradicts navigator.platform | | fakedBrowser| missing-chrome-object hint | UA claims Chrome but window.chrome is absent |


Confidence scoring

Each signal has a fixed weight (0.2–1.0). Canvas, WebGL, fonts, and audio carry the highest weight. Status affects points earned: ok = full weight, unsupported/blocked = 35%, timeout = 15%, error = 0.

The base ratio is scaled by an anti-spoof factor: base × (0.65 + antiSpoofScore × 0.35). A perfect anti-spoof score (1.0) leaves the base unchanged; a zero score reduces it to 65%. Full details in docs/api-reference.md.


CDN

CDN files are available from jsDelivr (automatic from npm publish) and self-hosted.

| File | Format | | ---------------------- | ---------------------------- | | zp.dfp.js | IIFE (unminified) | | zp.dfp.min.js | IIFE (minified) | | zp.dfp.obf.js | IIFE (minified + obfuscated) | | zp.dfp.esm.js | ESM for import() | | zp.dfp.manifest.json | SRI integrity hashes |

<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/@ianmenethil/zp-devicefp/dist/cdn/zp.dfp.min.js"></script>

<!-- Self-hosted -->
<script src="https://cdn.zenithpayments.support/devicefp/zp.dfp.min.js"></script>

<script>
  const client = await window.DeviceFP.createFingerprintClient({ timeoutMs: 1500 });
  const result = await client.collect();
  console.log(window.DeviceFP.LIBRARY_VERSION); // "0.1.1"
  console.log(window.DeviceFP.SCHEMA_VERSION);  // 2
</script>

Package layout

dist/
├── npm/
│   ├── index.mjs       # ESM entry (browser, ES2022)
│   ├── index.cjs       # CJS entry (Node 22+)
│   └── index.d.ts      # public types
└── cdn/
    ├── zp.dfp.js       # IIFE (unminified)
    ├── zp.dfp.min.js   # IIFE (minified)
    ├── zp.dfp.obf.js   # IIFE (minified + obfuscated)
    ├── zp.dfp.esm.js   # ESM for import()
    └── zp.dfp.manifest.json

Build and test

bun install
bun run build       # lint → typecheck → knip → jscpd → tests + coverage → esbuild → CDN deploy
bun test            # 92 tests, 94.9% line coverage
bun run smoke       # validates all dist artifacts

Docs

| Document | Covers | | ---------------------------------------------- | ----------------------------------------------------------------------------------------- | | docs/api-reference.md | Complete API: types, methods, events, options, constants, confidence, anti-spoof, CDN | | docs/signals.md | All 21 signals: value shapes, APIs used, sync support, edge cases | | docs/architecture.md | Architecture, module map, Mermaid diagrams, data flow, confidence scoring | | docs/privacy.md | Privacy-by-design notes | | docs/testing.md | Test strategy and coverage intent |


Design choices

  • Async first — high-value collectors (audio, UA hints, media devices, permissions, WebRTC, risk signals) run in collect().
  • Explainable output — every signal includes a status and duration. No silent failures.
  • No dangerous defaults — no battery, sensors, arbitrary installed-app probes, or local IP harvesting.
  • Anti-spoofing focuses on coherence checks and automation hints, not "secret" client tricks.

Limitations

  • Browser-side fingerprints are probabilistic, not identity proofs.
  • Font detection probes 70 common fonts (Fingerprint2 / Cardinal Commerce set) and relies on span-measurement heuristics.
  • The anti-spoof report is heuristic — combine with server-side context for fraud or login-risk decisions.