@ianmenethil/zp-devicefp 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Browser Fingerprint Library
2
2
 
3
- 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.
3
+ 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.
4
4
 
5
5
  - **Zero runtime dependencies** — no CDN loads, no npm deps at runtime.
6
6
  - **ESM, CJS, and IIFE** — single TypeScript source tree, three distribution formats.
7
- - **84 tests, 95% line coverage.**
7
+ - **92 tests across 24 files, 94.9% line coverage, 82.3% function coverage.**
8
8
 
9
9
  ---
10
10
 
@@ -28,50 +28,102 @@ const client = await createFingerprintClient({ timeoutMs: 1500 });
28
28
  // Async collection — all signals, parallel with timeouts
29
29
  const result = await client.collect();
30
30
 
31
- console.log(result.thumbprint); // "a1b2c3d4..." (64-char SHA-256 hex)
32
- console.log(result.confidence); // 0.87
31
+ console.log(result.thumbprint); // "a1b2c3d4..." (64-char SHA-256 hex)
32
+ console.log(result.confidence); // 0.87
33
33
  console.log(result.antiSpoof.score); // 0.94
34
34
  ```
35
35
 
36
36
  ---
37
37
 
38
+ ## Named exports
39
+
40
+ ### npm / ESM / CJS (`@ianmenethil/zp-devicefp`)
41
+
42
+ **Functions**
43
+
44
+ | Export | Signature | Description |
45
+ | ------------------------- | ---------------------------------------------------------- | ----------------------------------------------- |
46
+ | `createFingerprintClient` | `(options?: CollectOptions) => Promise<FingerprintClient>` | Factory — creates a configured client instance. |
47
+
48
+ **Constants**
49
+
50
+ | Export | Type | Description |
51
+ | ------------------------- | --------------------------------------------- | ---------------------------------------------------------------- |
52
+ | `LIBRARY_VERSION` | `string` | Semver string of the installed library (e.g. `"0.1.1"`). |
53
+ | `SCHEMA_VERSION` | `number` | Integer schema version; bumped on breaking result-shape changes. |
54
+ | `CORE_SIGNALS` | `SignalName[]` | The 10 signals collected by default. |
55
+ | `EXTENDED_SIGNALS` | `SignalName[]` | The 11 signals requiring `extended: true`. |
56
+ | `SYNC_SIGNALS` | `SignalName[]` | The 13 signals that support `collectSync()`. |
57
+ | `DEFAULT_TIMEOUT_MS` | `number` | Default per-signal timeout: `1500`. |
58
+ | `DEFAULT_FONT_LIST` | `string[]` | The 70 font faces probed during font detection (Fingerprint2 / Cardinal Commerce set). |
59
+ | `DEFAULT_PERMISSION_NAMES`| `readonly [...]` | The 4 permission names queried by the permissions signal. |
60
+ | `DEFAULT_UA_HINTS` | `readonly [...]` | The 8 high-entropy UA-CH hints requested. |
61
+
62
+ **TypeScript types** — all declared in the package root and importable directly:
63
+
64
+ ```ts
65
+ import type {
66
+ // Client
67
+ FingerprintClient,
68
+
69
+ // Core result
70
+ FingerprintResult,
71
+ SignalResult, // generic: SignalResult<T = unknown>
72
+ SignalName,
73
+ SignalStatus,
74
+ AntiSpoofReport,
75
+ CollectorDiagnostics,
76
+
77
+ // Options
78
+ CollectOptions,
79
+
80
+ // Events
81
+ EventPayloadMap,
82
+ ProgressEvent,
83
+ WarningEvent,
84
+ CompleteEvent,
85
+ } from '@ianmenethil/zp-devicefp';
86
+ ```
87
+
88
+ ### CDN / IIFE (`window.DeviceFP`)
89
+
90
+ The IIFE bundle exposes only the three runtime values — TypeScript types are compile-time only and are not present on the global:
91
+
92
+ | Property | Type | Description |
93
+ | ------------------------- | ---------------------------------------------------------- | ----------------------------------------------- |
94
+ | `createFingerprintClient` | `(options?: CollectOptions) => Promise<FingerprintClient>` | Factory — creates a configured client instance. |
95
+ | `LIBRARY_VERSION` | `string` | Semver string of the running bundle. |
96
+ | `SCHEMA_VERSION` | `number` | Integer schema version of the result shape. |
97
+
98
+ ---
99
+
38
100
  ## API overview
39
101
 
40
- | Method | Returns | Description |
41
- |--------|---------|-------------|
42
- | `client.collect(options?)` | `Promise<FingerprintResult>` | Async collection. All signals run in parallel with per-signal timeouts. Emits events. |
43
- | `client.collectSync(options?)` | `FingerprintResult` | Sync collection. Only 13 of 19 signals support sync mode. No timeout/abort. |
44
- | `client.upload(result, options)` | `Promise<Response>` | POST the fingerprint to a backend endpoint. |
45
- | `client.on(event, callback)` | `() => void` | Subscribe to `progress`, `warning`, or `complete` events during async collection. |
102
+ | Method | Returns | Description |
103
+ | -------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------- |
104
+ | `client.collect(options?)` | `Promise<FingerprintResult>` | Async collection. All signals run in parallel with per-signal timeouts. Emits events. |
105
+ | `client.collectSync(options?)` | `FingerprintResult` | Sync collection. Only 13 of 21 signals support sync mode. No timeout/abort. |
106
+ | `client.on(event, callback)` | `() => void` | Subscribe to `progress`, `warning`, or `complete` events during async collection. |
46
107
 
47
108
  ### Events
48
109
 
49
110
  ```ts
50
- client.on('progress', (e) => {
51
- // { completed: 3, total: 10, signal: 'canvas', result: SignalResult }
52
- console.log(`${e.completed}/${e.total}: ${e.signal}`);
111
+ client.on('progress', e => {
112
+ // { completed: 3, total: 10, signal: 'canvas', result: SignalResult }
113
+ console.log(`${e.completed}/${e.total}: ${e.signal}`);
53
114
  });
54
115
 
55
- client.on('warning', (e) => {
56
- // { signal?: 'webrtc', message: 'RTCPeerConnection not available' }
57
- console.warn(e.message);
116
+ client.on('warning', e => {
117
+ // { signal?: 'webrtc', message: 'RTCPeerConnection not available' }
118
+ console.warn(e.message);
58
119
  });
59
120
 
60
- client.on('complete', (e) => {
61
- // { result: FingerprintResult }
62
- console.log('Done', e.result.thumbprint);
121
+ client.on('complete', e => {
122
+ // { result: FingerprintResult }
123
+ console.log('Done', e.result.thumbprint);
63
124
  });
64
125
  ```
65
126
 
66
- ### Upload
67
-
68
- ```ts
69
- await client.upload(result, {
70
- endpoint: 'https://api.example.com/fingerprint',
71
- headers: { 'Authorization': 'Bearer token' },
72
- bodyExtras: { sessionId: 'abc123' },
73
- });
74
- ```
75
127
 
76
128
  ### Aborting
77
129
 
@@ -93,12 +145,12 @@ const result = await resultPromise;
93
145
 
94
146
  ```ts
95
147
  interface CollectOptions {
96
- include?: SignalName[]; // whitelist: only collect these signals
97
- exclude?: SignalName[]; // blacklist: skip these signals
98
- timeoutMs?: number; // per-signal timeout (default: 1500)
99
- abortSignal?: AbortSignal; // cancel in-flight collection
100
- extended?: boolean; // include extended-tier signals (default: false)
101
- debug?: boolean; // enable debug logging (default: false)
148
+ include?: SignalName[]; // whitelist: only collect these signals
149
+ exclude?: SignalName[]; // blacklist: skip these signals
150
+ timeoutMs?: number; // per-signal timeout (default: 1500)
151
+ abortSignal?: AbortSignal; // cancel in-flight collection
152
+ extended?: boolean; // include extended-tier signals (default: false)
153
+ debug?: boolean; // enable debug logging (default: false)
102
154
  }
103
155
  ```
104
156
 
@@ -108,15 +160,15 @@ interface CollectOptions {
108
160
 
109
161
  ```ts
110
162
  interface FingerprintResult {
111
- schemaVersion: number; // schema version (integer, bumped on breaking changes)
112
- libraryVersion: string; // library semver string
113
- thumbprint: string; // SHA-256 of all component hashes combined
114
- confidence: number; // 0–1 quality score
115
- componentHashes: Partial<Record<SignalName, string>>; // per-signal SHA-256 digests
116
- signals: Partial<Record<SignalName, SignalResult>>; // raw signal results
117
- antiSpoof: AntiSpoofReport; // coherence analysis and automation hints
118
- warnings: string[]; // non-fatal collection warnings
119
- diagnostics: CollectorDiagnostics; // run metadata
163
+ schemaVersion: number; // schema version (integer, bumped on breaking changes)
164
+ libraryVersion: string; // library semver string
165
+ thumbprint: string; // SHA-256 of all component hashes combined
166
+ confidence: number; // 0–1 quality score
167
+ componentHashes: Partial<Record<SignalName, string>>; // per-signal SHA-256 digests
168
+ signals: Partial<Record<SignalName, SignalResult>>; // raw signal results
169
+ antiSpoof: AntiSpoofReport; // coherence analysis and automation hints
170
+ warnings: string[]; // non-fatal collection warnings
171
+ diagnostics: CollectorDiagnostics; // run metadata
120
172
  }
121
173
  ```
122
174
 
@@ -124,9 +176,11 @@ interface FingerprintResult {
124
176
 
125
177
  ```ts
126
178
  {
127
- score: number; // 0 = automated/bot, 1 = genuine user
128
- anomalies: string[]; // signal inconsistencies (6 anomaly types)
179
+ score: number; // 0 = automated/bot, 1 = genuine user
180
+ anomalies: string[]; // signal inconsistencies (6 anomaly types)
129
181
  automationHints: string[]; // headless/bot indicators (2 hint types)
182
+ fakedOS: boolean; // UA's OS contradicts navigator.platform
183
+ fakedBrowser: boolean; // UA claims Chrome but window.chrome is absent
130
184
  }
131
185
  ```
132
186
 
@@ -145,36 +199,38 @@ Full API reference with all types, methods, events, options, confidence scoring,
145
199
 
146
200
  ---
147
201
 
148
- ## Signals (19 total)
202
+ ## Signals (21 total)
149
203
 
150
204
  ### Core tier (10 signals, enabled by default)
151
205
 
152
- | Signal | Sync | Description |
153
- |--------|------|-------------|
154
- | `ua` | yes | `navigator.userAgent`, platform, webdriver, maxTouchPoints, cookieEnabled, vendor |
155
- | `uaHints` | no | User-Agent Client Hints: brands, platform, high-entropy values (architecture, bitness, model, etc.) |
156
- | `locale` | yes | Language, languages, locale, calendar, numbering system, timeZone, formatted samples |
157
- | `screen` | yes | Screen width/height, colorDepth, orientation, DPR, media queries (color-gamut, reduced-motion, contrast, forced-colors) |
158
- | `hardware` | yes | `navigator.hardwareConcurrency`, `deviceMemory`, platform, maxTouchPoints |
159
- | `storage` | yes | localStorage, sessionStorage, IndexedDB, Web SQL availability (write-read-delete probe) |
160
- | `fonts` | yes | Installed font detection via hidden DOM span measurement (10 fonts, 3 generic families) |
161
- | `canvas` | yes | Canvas 2D rendering pattern → `toDataURL()` (GPU+driver fingerprint) |
162
- | `webgl` | yes | WebGL renderer/vendor (unmasked), version, extensions, max texture/viewport |
163
- | `audio` | no | OfflineAudioContext oscillator+compressor numeric signature |
164
-
165
- ### Extended tier (9 signals, requires `extended: true`)
166
-
167
- | Signal | Sync | Description |
168
- |--------|------|-------------|
169
- | `mediaDevices` | no | Media device counts by kind (audioinput, audiooutput, videoinput) — no labels/IDs |
170
- | `permissions` | no | Permissions API state for geolocation, notifications, camera, microphone |
171
- | `webrtc` | no | RTCPeerConnection SDP codec/extmap capability hash |
172
- | `frameInfo` | yes | Iframe count, source domains, top-level window check |
173
- | `networkInfo` | yes | NetworkInformation API: effectiveType, downlink, RTT, saveData |
174
- | `paymentSupport` | yes | `window.PaymentRequest` availability |
175
- | `referrerInfo` | yes | `document.referrer` |
176
- | `navigationInfo` | yes | PerformanceNavigationTiming.type (navigate/reload/back_forward/prerender) |
177
- | `riskSignals` | no | Bot/headless detection: webdriver, headless UA, missing chrome object, zero dimensions, empty plugins, UA-CH consistency, notification permission, time to capture |
206
+ | Signal | Sync | Description |
207
+ | ---------- | ---- | ----------------------------------------------------------------------------------------------------------------------- |
208
+ | `ua` | yes | `navigator.userAgent`, platform, webdriver, maxTouchPoints, cookieEnabled, vendor, doNotTrack (normalized), plugins (MIME format) |
209
+ | `uaHints` | no | User-Agent Client Hints: brands, platform, high-entropy values (architecture, bitness, model, etc.) |
210
+ | `locale` | yes | Language, languages, locale, calendar, numbering system, timeZone, formatted samples |
211
+ | `screen` | yes | Screen width/height, colorDepth, orientation, DPR, fakedResolution (FP2 has_lied_resolution), ccaScreenSize (Cardinal bucket), media queries |
212
+ | `hardware` | yes | `navigator.hardwareConcurrency`, `deviceMemory`, platform, maxTouchPoints, touchEventCreationSuccessful, onTouchStartAvailable |
213
+ | `storage` | yes | localStorage, sessionStorage, IndexedDB, Web SQL availability (write-read-delete probe) |
214
+ | `fonts` | yes | Installed font detection via hidden DOM span measurement (70 fonts, 3 generic families) |
215
+ | `canvas` | yes | Canvas 2D rendering pattern → `toDataURL()` (GPU+driver fingerprint) |
216
+ | `webgl` | yes | WebGL renderer/vendor (unmasked), version, extensions, max texture/viewport |
217
+ | `audio` | no | OfflineAudioContext oscillator+compressor numeric signature |
218
+
219
+ ### Extended tier (11 signals, requires `extended: true`)
220
+
221
+ | Signal | Sync | Description |
222
+ | ---------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
223
+ | `mediaDevices` | no | Media device counts by kind (audioinput, audiooutput, videoinput) — no labels/IDs |
224
+ | `permissions` | no | Permissions API state for geolocation, notifications, camera, microphone |
225
+ | `webrtc` | no | RTCPeerConnection SDP codec/extmap capability hash |
226
+ | `frameInfo` | yes | Iframe count, source domains, top-level window check |
227
+ | `networkInfo` | yes | NetworkInformation API: effectiveType, downlink, RTT, saveData |
228
+ | `paymentSupport` | yes | `window.PaymentRequest` availability |
229
+ | `referrerInfo` | yes | `document.referrer` |
230
+ | `navigationInfo` | yes | PerformanceNavigationTiming.type (navigate/reload/back_forward/prerender) |
231
+ | `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 |
232
+ | `adblock` | no | Ad blocker detection — appends `div.adsbox` to body, checks `offsetHeight === 0` after 100 ms |
233
+ | `geolocation` | no | Geographic coordinates (latitude, longitude, accuracy) via Geolocation API — prompts user permission |
178
234
 
179
235
  Complete per-signal documentation with value shapes, APIs used, and edge cases: **[docs/signals.md](docs/signals.md)**
180
236
 
@@ -182,16 +238,30 @@ Complete per-signal documentation with value shapes, APIs used, and edge cases:
182
238
 
183
239
  ## Anti-spoof heuristics (6 anomalies + 2 automation hints)
184
240
 
185
- | Code | What it detects |
186
- |------|----------------|
241
+ ### Anomalies (coherence violations, –0.12 each)
242
+
243
+ | Code | What it detects |
244
+ | ---------------------------------- | ----------------------------------------------- |
245
+ | `ua_platform_mismatch` | `navigator.platform` contradicts UA-CH platform |
246
+ | `touch_claim_without_touch_points` | iPhone UA but `maxTouchPoints` is 0 |
247
+ | `mobile_ua_desktop_screen` | Android UA with screen width >= 1600px |
248
+ | `windows_ua_mac_platform` | Windows UA but platform reports macOS |
249
+ | `implausible_device_memory` | `deviceMemory` < 0.25 or > 64 GB |
250
+ | `tiny_screen_geometry` | Screen width and height both < 200px |
251
+
252
+ ### Automation hints (bot/headless indicators, –0.18 each)
253
+
254
+ | Code | What it detects |
255
+ | --------------------- | ------------------------------- |
187
256
  | `navigator_webdriver` | `navigator.webdriver` is truthy |
188
- | `headless_user_agent` | UA contains `"headless"` |
189
- | `ua_platform_mismatch` | `navigator.platform` contradicts UA-CH platform |
190
- | `touch_claim_without_touch_points` | iPhone UA but `maxTouchPoints` is 0 |
191
- | `mobile_ua_desktop_screen` | Android UA with screen width >= 1600px |
192
- | `windows_ua_mac_platform` | Windows UA but platform reports macOS |
193
- | `implausible_device_memory` | `deviceMemory` < 0.25 or > 64 GB |
194
- | `tiny_screen_geometry` | Screen width and height both < 200px |
257
+ | `headless_user_agent` | UA contains `"headless"` |
258
+
259
+ ### Named booleans on `AntiSpoofReport`
260
+
261
+ | Field | Derived from | Meaning |
262
+ | ------------- | ----------------------------- | ---------------------------------------------------- |
263
+ | `fakedOS` | `ua_platform_mismatch` anomaly | UA's OS contradicts `navigator.platform` |
264
+ | `fakedBrowser`| `missing-chrome-object` hint | UA claims Chrome but `window.chrome` is absent |
195
265
 
196
266
  ---
197
267
 
@@ -199,7 +269,7 @@ Complete per-signal documentation with value shapes, APIs used, and edge cases:
199
269
 
200
270
  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.
201
271
 
202
- The base ratio is blended with the anti-spoof score (65% base / 35% anti-spoof). Full details in [docs/api-reference.md](docs/api-reference.md).
272
+ 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](docs/api-reference.md).
203
273
 
204
274
  ---
205
275
 
@@ -207,25 +277,26 @@ The base ratio is blended with the anti-spoof score (65% base / 35% anti-spoof).
207
277
 
208
278
  CDN files are available from jsDelivr (automatic from npm publish) and self-hosted.
209
279
 
210
- | File | Format |
211
- |------|--------|
212
- | `zp.dfp.js` | IIFE (unminified) |
213
- | `zp.dfp.min.js` | IIFE (minified) |
214
- | `zp.dfp.obf.js` | IIFE (minified + obfuscated) |
215
- | `zp.dfp.esm.js` | ESM for `import()` |
216
- | `zp.dfp.manifest.json` | SRI integrity hashes |
280
+ | File | Format |
281
+ | ---------------------- | ---------------------------- |
282
+ | `zp.dfp.js` | IIFE (unminified) |
283
+ | `zp.dfp.min.js` | IIFE (minified) |
284
+ | `zp.dfp.obf.js` | IIFE (minified + obfuscated) |
285
+ | `zp.dfp.esm.js` | ESM for `import()` |
286
+ | `zp.dfp.manifest.json` | SRI integrity hashes |
217
287
 
218
288
  ```html
219
289
  <!-- jsDelivr -->
220
290
  <script src="https://cdn.jsdelivr.net/npm/@ianmenethil/zp-devicefp/dist/cdn/zp.dfp.min.js"></script>
221
291
 
222
292
  <!-- Self-hosted -->
223
- <script src="https://cdn.zenithpayments.support/devicefp/latest/zp.dfp.min.js"></script>
293
+ <script src="https://cdn.zenithpayments.support/devicefp/zp.dfp.min.js"></script>
224
294
 
225
295
  <script>
226
- const client = await window.DeviceFP.createFingerprintClient({ timeoutMs: 1500 });
227
- const result = await client.collect();
228
- console.log(window.DeviceFP.LIBRARY_VERSION); // "0.1.0"
296
+ const client = await window.DeviceFP.createFingerprintClient({ timeoutMs: 1500 });
297
+ const result = await client.collect();
298
+ console.log(window.DeviceFP.LIBRARY_VERSION); // "0.1.1"
299
+ console.log(window.DeviceFP.SCHEMA_VERSION); // 2
229
300
  </script>
230
301
  ```
231
302
 
@@ -254,7 +325,7 @@ dist/
254
325
  ```bash
255
326
  bun install
256
327
  bun run build # lint → typecheck → knip → jscpd → tests + coverage → esbuild → CDN deploy
257
- bun test # 84 tests, 95% line coverage
328
+ bun test # 92 tests, 94.9% line coverage
258
329
  bun run smoke # validates all dist artifacts
259
330
  ```
260
331
 
@@ -262,13 +333,13 @@ bun run smoke # validates all dist artifacts
262
333
 
263
334
  ## Docs
264
335
 
265
- | Document | Covers |
266
- |----------|--------|
267
- | [docs/api-reference.md](docs/api-reference.md) | Complete API: types, methods, events, options, constants, confidence, anti-spoof, upload, CDN |
268
- | [docs/signals.md](docs/signals.md) | All 19 signals: value shapes, APIs used, sync support, edge cases |
269
- | [docs/architecture.md](docs/architecture.md) | High-level flow and module map |
270
- | [docs/privacy.md](docs/privacy.md) | Privacy-by-design notes |
271
- | [docs/testing.md](docs/testing.md) | Test strategy and coverage intent |
336
+ | Document | Covers |
337
+ | ---------------------------------------------- | ----------------------------------------------------------------------------------------- |
338
+ | [docs/api-reference.md](docs/api-reference.md) | Complete API: types, methods, events, options, constants, confidence, anti-spoof, CDN |
339
+ | [docs/signals.md](docs/signals.md) | All 21 signals: value shapes, APIs used, sync support, edge cases |
340
+ | [docs/architecture.md](docs/architecture.md) | Architecture, module map, Mermaid diagrams, data flow, confidence scoring |
341
+ | [docs/privacy.md](docs/privacy.md) | Privacy-by-design notes |
342
+ | [docs/testing.md](docs/testing.md) | Test strategy and coverage intent |
272
343
 
273
344
  ---
274
345
 
@@ -278,12 +349,11 @@ bun run smoke # validates all dist artifacts
278
349
  - **Explainable output** — every signal includes a status and duration. No silent failures.
279
350
  - **No dangerous defaults** — no battery, sensors, arbitrary installed-app probes, or local IP harvesting.
280
351
  - **Anti-spoofing** focuses on coherence checks and automation hints, not "secret" client tricks.
281
- - **Upload is backend-neutral and optional** — you control where (if anywhere) fingerprints are sent.
282
352
 
283
353
  ---
284
354
 
285
355
  ## Limitations
286
356
 
287
357
  - Browser-side fingerprints are probabilistic, not identity proofs.
288
- - Font detection is bounded to 10 common fonts and is intentionally conservative.
358
+ - Font detection probes 70 common fonts (Fingerprint2 / Cardinal Commerce set) and relies on span-measurement heuristics.
289
359
  - The anti-spoof report is heuristic — combine with server-side context for fraud or login-risk decisions.