@ianmenethil/zp-devicefp
v0.1.1
Published
Async-first browser and device fingerprinting library with explainable signals and anomaly scoring.
Downloads
885
Maintainers
Readme
Browser Fingerprint Library
Async-first TypeScript browser and device fingerprinting library. Collects 19 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.
- 84 tests, 95% line coverage.
Install
bun add @ianmenethil/zp-devicefpimport { 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.94API 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 19 signals support sync mode. No timeout/abort. |
| client.upload(result, options) | Promise<Response> | POST the fingerprint to a backend endpoint. |
| 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);
});Upload
await client.upload(result, {
endpoint: 'https://api.example.com/fingerprint',
headers: { 'Authorization': 'Bearer token' },
bodyExtras: { sessionId: 'abc123' },
});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)
}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 (19 total)
Core tier (10 signals, enabled by default)
| Signal | Sync | Description |
|--------|------|-------------|
| ua | yes | navigator.userAgent, platform, webdriver, maxTouchPoints, cookieEnabled, vendor |
| 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, media queries (color-gamut, reduced-motion, contrast, forced-colors) |
| hardware | yes | navigator.hardwareConcurrency, deviceMemory, platform, maxTouchPoints |
| storage | yes | localStorage, sessionStorage, IndexedDB, Web SQL availability (write-read-delete probe) |
| fonts | yes | Installed font detection via hidden DOM span measurement (10 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 (9 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, UA-CH consistency, notification permission, time to capture |
Complete per-signal documentation with value shapes, APIs used, and edge cases: docs/signals.md
Anti-spoof heuristics (6 anomalies + 2 automation hints)
| Code | What it detects |
|------|----------------|
| navigator_webdriver | navigator.webdriver is truthy |
| headless_user_agent | UA contains "headless" |
| 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 |
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 blended with the anti-spoof score (65% base / 35% anti-spoof). 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/latest/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.0"
</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.jsonBuild and test
bun install
bun run build # lint → typecheck → knip → jscpd → tests + coverage → esbuild → CDN deploy
bun test # 84 tests, 95% line coverage
bun run smoke # validates all dist artifactsDocs
| Document | Covers | |----------|--------| | docs/api-reference.md | Complete API: types, methods, events, options, constants, confidence, anti-spoof, upload, CDN | | docs/signals.md | All 19 signals: value shapes, APIs used, sync support, edge cases | | docs/architecture.md | High-level flow and module map | | 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.
- Upload is backend-neutral and optional — you control where (if anywhere) fingerprints are sent.
Limitations
- Browser-side fingerprints are probabilistic, not identity proofs.
- Font detection is bounded to 10 common fonts and is intentionally conservative.
- The anti-spoof report is heuristic — combine with server-side context for fraud or login-risk decisions.
