@ianmenethil/zp-devicefp 0.1.0-alpha.0 → 0.1.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 +235 -80
- package/dist/cdn/zp.dfp.manifest.json +8 -8
- package/dist/cdn/zp.dfp.obf.js +1 -1
- package/docs/api-reference.md +472 -0
- package/docs/signals.md +383 -0
- package/package.json +75 -72
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Browser Fingerprint Library
|
|
2
2
|
|
|
3
|
-
Async-first TypeScript browser and device fingerprinting library
|
|
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.
|
|
4
|
+
|
|
5
|
+
- **Zero runtime dependencies** — no CDN loads, no npm deps at runtime.
|
|
6
|
+
- **ESM, CJS, and IIFE** — single TypeScript source tree, three distribution formats.
|
|
7
|
+
- **84 tests, 95% line coverage.**
|
|
8
|
+
|
|
9
|
+
---
|
|
4
10
|
|
|
5
11
|
## Install
|
|
6
12
|
|
|
@@ -12,97 +18,236 @@ bun add @ianmenethil/zp-devicefp
|
|
|
12
18
|
import { createFingerprintClient } from '@ianmenethil/zp-devicefp';
|
|
13
19
|
```
|
|
14
20
|
|
|
15
|
-
##
|
|
21
|
+
## Quick start
|
|
16
22
|
|
|
17
|
-
```
|
|
18
|
-
@ianmenethil/zp-devicefp
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
│ ├── zp.dfp.esm.js ← ESM for import()
|
|
29
|
-
│ └── zp.dfp.manifest.json ← SRI integrity hashes
|
|
30
|
-
├── README.md
|
|
31
|
-
├── LICENSE
|
|
32
|
-
└── docs/
|
|
23
|
+
```ts
|
|
24
|
+
import { createFingerprintClient } from '@ianmenethil/zp-devicefp';
|
|
25
|
+
|
|
26
|
+
const client = await createFingerprintClient({ timeoutMs: 1500 });
|
|
27
|
+
|
|
28
|
+
// Async collection — all signals, parallel with timeouts
|
|
29
|
+
const result = await client.collect();
|
|
30
|
+
|
|
31
|
+
console.log(result.thumbprint); // "a1b2c3d4..." (64-char SHA-256 hex)
|
|
32
|
+
console.log(result.confidence); // 0.87
|
|
33
|
+
console.log(result.antiSpoof.score); // 0.94
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
---
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
## API overview
|
|
38
39
|
|
|
39
|
-
|
|
|
40
|
-
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
| Manifest | `@ianmenethil/zp-devicefp/dist/cdn/zp.dfp.manifest.json` | `devicefp/latest/zp.dfp.manifest.json` |
|
|
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. |
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
- **Self-hosted base:** `https://cdn.zenithpayments.support/` — `latest/` always points to the most recent build; versioned folders (`2.1.1/`) are created on `bun run release`.
|
|
47
|
+
### Events
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
```ts
|
|
50
|
+
client.on('progress', (e) => {
|
|
51
|
+
// { completed: 3, total: 10, signal: 'canvas', result: SignalResult }
|
|
52
|
+
console.log(`${e.completed}/${e.total}: ${e.signal}`);
|
|
53
|
+
});
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
client.on('warning', (e) => {
|
|
56
|
+
// { signal?: 'webrtc', message: 'RTCPeerConnection not available' }
|
|
57
|
+
console.warn(e.message);
|
|
58
|
+
});
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
client.on('complete', (e) => {
|
|
61
|
+
// { result: FingerprintResult }
|
|
62
|
+
console.log('Done', e.result.thumbprint);
|
|
63
|
+
});
|
|
64
|
+
```
|
|
58
65
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
+
});
|
|
63
74
|
```
|
|
64
75
|
|
|
65
|
-
|
|
76
|
+
### Aborting
|
|
66
77
|
|
|
67
78
|
```ts
|
|
68
|
-
|
|
79
|
+
const controller = new AbortController();
|
|
69
80
|
|
|
70
|
-
const
|
|
71
|
-
timeoutMs: 1500,
|
|
72
|
-
extended: false,
|
|
73
|
-
});
|
|
81
|
+
const resultPromise = client.collect({ abortSignal: controller.signal });
|
|
74
82
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
// Cancel mid-flight
|
|
84
|
+
controller.abort();
|
|
85
|
+
|
|
86
|
+
const result = await resultPromise;
|
|
87
|
+
// Aborted signals appear as status: 'error' with error: 'Collection aborted.'
|
|
78
88
|
```
|
|
79
89
|
|
|
80
|
-
|
|
90
|
+
---
|
|
81
91
|
|
|
82
|
-
|
|
92
|
+
## Options
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
```ts
|
|
95
|
+
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)
|
|
102
|
+
}
|
|
103
|
+
```
|
|
88
104
|
|
|
89
|
-
|
|
105
|
+
---
|
|
90
106
|
|
|
91
|
-
|
|
107
|
+
## Result shape
|
|
92
108
|
|
|
93
|
-
|
|
109
|
+
```ts
|
|
110
|
+
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
|
|
120
|
+
}
|
|
121
|
+
```
|
|
94
122
|
|
|
95
|
-
|
|
123
|
+
### `AntiSpoofReport`
|
|
96
124
|
|
|
97
|
-
|
|
125
|
+
```ts
|
|
126
|
+
{
|
|
127
|
+
score: number; // 0 = automated/bot, 1 = genuine user
|
|
128
|
+
anomalies: string[]; // signal inconsistencies (6 anomaly types)
|
|
129
|
+
automationHints: string[]; // headless/bot indicators (2 hint types)
|
|
130
|
+
}
|
|
131
|
+
```
|
|
98
132
|
|
|
99
|
-
|
|
133
|
+
### `SignalResult`
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
{
|
|
137
|
+
status: 'ok' | 'unsupported' | 'blocked' | 'timeout' | 'error';
|
|
138
|
+
value?: unknown; // signal-specific data (only when status === 'ok')
|
|
139
|
+
durationMs: number; // wall-clock collection time
|
|
140
|
+
error?: string; // error description (when status !== 'ok')
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Full API reference with all types, methods, events, options, confidence scoring, anti-spoof heuristics, and constants: **[docs/api-reference.md](docs/api-reference.md)**
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Signals (19 total)
|
|
149
|
+
|
|
150
|
+
### Core tier (10 signals, enabled by default)
|
|
100
151
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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 |
|
|
178
|
+
|
|
179
|
+
Complete per-signal documentation with value shapes, APIs used, and edge cases: **[docs/signals.md](docs/signals.md)**
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Anti-spoof heuristics (6 anomalies + 2 automation hints)
|
|
184
|
+
|
|
185
|
+
| Code | What it detects |
|
|
186
|
+
|------|----------------|
|
|
187
|
+
| `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 |
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Confidence scoring
|
|
199
|
+
|
|
200
|
+
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
|
+
|
|
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).
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## CDN
|
|
207
|
+
|
|
208
|
+
CDN files are available from jsDelivr (automatic from npm publish) and self-hosted.
|
|
209
|
+
|
|
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 |
|
|
217
|
+
|
|
218
|
+
```html
|
|
219
|
+
<!-- jsDelivr -->
|
|
220
|
+
<script src="https://cdn.jsdelivr.net/npm/@ianmenethil/zp-devicefp/dist/cdn/zp.dfp.min.js"></script>
|
|
221
|
+
|
|
222
|
+
<!-- Self-hosted -->
|
|
223
|
+
<script src="https://cdn.zenithpayments.support/devicefp/latest/zp.dfp.min.js"></script>
|
|
224
|
+
|
|
225
|
+
<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"
|
|
229
|
+
</script>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Package layout
|
|
235
|
+
|
|
236
|
+
```text
|
|
237
|
+
dist/
|
|
238
|
+
├── npm/
|
|
239
|
+
│ ├── index.mjs # ESM entry (browser, ES2022)
|
|
240
|
+
│ ├── index.cjs # CJS entry (Node 22+)
|
|
241
|
+
│ └── index.d.ts # public types
|
|
242
|
+
└── cdn/
|
|
243
|
+
├── zp.dfp.js # IIFE (unminified)
|
|
244
|
+
├── zp.dfp.min.js # IIFE (minified)
|
|
245
|
+
├── zp.dfp.obf.js # IIFE (minified + obfuscated)
|
|
246
|
+
├── zp.dfp.esm.js # ESM for import()
|
|
247
|
+
└── zp.dfp.manifest.json
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
106
251
|
|
|
107
252
|
## Build and test
|
|
108
253
|
|
|
@@ -113,22 +258,32 @@ bun test # 84 tests, 95% line coverage
|
|
|
113
258
|
bun run smoke # validates all dist artifacts
|
|
114
259
|
```
|
|
115
260
|
|
|
116
|
-
|
|
261
|
+
---
|
|
117
262
|
|
|
118
|
-
##
|
|
263
|
+
## Docs
|
|
119
264
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
-
|
|
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 |
|
|
123
272
|
|
|
124
|
-
|
|
273
|
+
---
|
|
125
274
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
275
|
+
## Design choices
|
|
276
|
+
|
|
277
|
+
- **Async first** — high-value collectors (audio, UA hints, media devices, permissions, WebRTC, risk signals) run in `collect()`.
|
|
278
|
+
- **Explainable output** — every signal includes a status and duration. No silent failures.
|
|
279
|
+
- **No dangerous defaults** — no battery, sensors, arbitrary installed-app probes, or local IP harvesting.
|
|
280
|
+
- **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
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Limitations
|
|
286
|
+
|
|
287
|
+
- Browser-side fingerprints are probabilistic, not identity proofs.
|
|
288
|
+
- Font detection is bounded to 10 common fonts and is intentionally conservative.
|
|
289
|
+
- The anti-spoof report is heuristic — combine with server-side context for fraud or login-risk decisions.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ianmenethil/zp-devicefp",
|
|
3
|
-
"version": "
|
|
4
|
-
"tag": "@ianmenethil/zp-devicefp@
|
|
5
|
-
"npmIdentifier": "ianmenethil-zp-devicefp-
|
|
6
|
-
"manifestPath": "ianmenethil-zp-devicefp-
|
|
7
|
-
"buildTimestamp": "2026-05-
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"tag": "@ianmenethil/zp-devicefp@0.1.0-alpha.0",
|
|
5
|
+
"npmIdentifier": "ianmenethil-zp-devicefp-0.1.0-alpha.0",
|
|
6
|
+
"manifestPath": "ianmenethil-zp-devicefp-0.1.0-alpha.0-manifest.json",
|
|
7
|
+
"buildTimestamp": "2026-05-14T13:29:16.204Z",
|
|
8
8
|
"files": [
|
|
9
9
|
{
|
|
10
10
|
"file": "zp.dfp.js",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"file": "zp.dfp.obf.js",
|
|
23
|
-
"hash": "
|
|
24
|
-
"hashSri": "sha384-
|
|
25
|
-
"size":
|
|
23
|
+
"hash": "f4307cffbeaafa52f46a56c3ad63d07789468181d80350d2e585520f04d4b456da4c913d809f6155de79bcb00260e90e",
|
|
24
|
+
"hashSri": "sha384-9DB8/76q+lL0albDrWPQd4lGgYHYA1DS5YVSDwTUtFbaTJE9gJ9hVd55vLACYOkO",
|
|
25
|
+
"size": 229236
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"file": "zp.dfp.esm.js",
|