@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.
@@ -0,0 +1,383 @@
1
+ # Signal Reference
2
+
3
+ Every signal collected by the library, including its name, tier, sync support, description, and the shape of its `value` when `status` is `'ok'`.
4
+
5
+ ---
6
+
7
+ ## Signal overview
8
+
9
+ | # | Signal | Tier | Sync | Description |
10
+ |---|--------|------|------|-------------|
11
+ | 1 | `ua` | core | yes | Legacy `navigator.userAgent` and related properties |
12
+ | 2 | `uaHints` | core | no | User-Agent Client Hints (brands, platform, high-entropy values) |
13
+ | 3 | `locale` | core | yes | Language, locale, calendar, time zone, number/date formatting |
14
+ | 4 | `screen` | core | yes | Screen dimensions, color depth, orientation, DPR, media queries |
15
+ | 5 | `hardware` | core | yes | Hardware concurrency, device memory, platform |
16
+ | 6 | `storage` | core | yes | localStorage, sessionStorage, IndexedDB, Web SQL availability |
17
+ | 7 | `fonts` | core | yes | Installed font detection via DOM measurement |
18
+ | 8 | `canvas` | core | yes | Canvas 2D rendering fingerprint (GPU+driver signature) |
19
+ | 9 | `webgl` | core | yes | WebGL renderer, vendor, extensions, GPU capabilities |
20
+ | 10 | `audio` | core | no | OfflineAudioContext oscillator+compressor signature |
21
+ | 11 | `mediaDevices` | extended | no | Media device kind enumeration (counts only, no labels) |
22
+ | 12 | `permissions` | extended | no | Permissions API state for geolocation, notifications, camera, microphone |
23
+ | 13 | `webrtc` | extended | no | RTCPeerConnection codec and extmap capability hash |
24
+ | 14 | `frameInfo` | extended | yes | Iframe context: count, domains, top-level check |
25
+ | 15 | `networkInfo` | extended | yes | NetworkInformation API: effective type, downlink, RTT, saveData |
26
+ | 16 | `paymentSupport` | extended | yes | PaymentRequest API availability |
27
+ | 17 | `referrerInfo` | extended | yes | `document.referrer` |
28
+ | 18 | `navigationInfo` | extended | yes | PerformanceNavigationTiming type (navigate/reload/back_forward/prerender) |
29
+ | 19 | `riskSignals` | extended | no | Bot/headless detection: 9 heuristics |
30
+
31
+ ---
32
+
33
+ ## Core signals
34
+
35
+ ### `ua` — User-Agent string
36
+
37
+ **Tier:** core | **Sync:** yes | **APIs used:** `navigator`
38
+
39
+ ```ts
40
+ // value shape:
41
+ {
42
+ userAgent: string; // navigator.userAgent
43
+ appVersion: string; // navigator.appVersion
44
+ vendor: string; // navigator.vendor
45
+ platform: string; // navigator.platform
46
+ webdriver: boolean; // navigator.webdriver
47
+ maxTouchPoints: number; // navigator.maxTouchPoints
48
+ cookieEnabled: boolean; // navigator.cookieEnabled
49
+ vendorSub: string; // navigator.vendorSub
50
+ }
51
+ ```
52
+
53
+ Returns `'unsupported'` if `navigator` is not available.
54
+
55
+ ### `uaHints` — User-Agent Client Hints
56
+
57
+ **Tier:** core | **Sync:** no | **APIs used:** `navigator.userAgentData`
58
+
59
+ ```ts
60
+ // value shape:
61
+ {
62
+ brands: { brand: string; version: string }[]; // low-entropy brands
63
+ mobile: boolean; // userAgentData.mobile
64
+ platform: string; // userAgentData.platform
65
+ architecture?: string; // high-entropy: CPU architecture
66
+ bitness?: string; // high-entropy: 32/64
67
+ formFactors?: string[]; // high-entropy: device form factors
68
+ fullVersionList?: { brand: string; version: string }[]; // high-entropy: full versions
69
+ model?: string; // high-entropy: device model
70
+ platformVersion?: string; // high-entropy: OS version
71
+ wow64?: boolean; // high-entropy: 32-bit on 64-bit
72
+ }
73
+ ```
74
+
75
+ Returns `'unsupported'` if `navigator.userAgentData` is not available. Returns `'blocked'` if `getHighEntropyValues` throws.
76
+
77
+ ### `locale` — Language and internationalization
78
+
79
+ **Tier:** core | **Sync:** yes | **APIs used:** `navigator`, `Intl`
80
+
81
+ ```ts
82
+ // value shape:
83
+ {
84
+ language: string; // navigator.language
85
+ languages: readonly string[]; // navigator.languages
86
+ locale: string; // Intl.DateTimeFormat resolved locale
87
+ calendar: string; // e.g. "gregory", "iso8601"
88
+ numberingSystem: string; // e.g. "latn", "arab"
89
+ timeZone: string; // IANA timezone e.g. "America/New_York"
90
+ hourCycle: string; // e.g. "h12", "h23"
91
+ timeZoneOffsetMinutes: number; // UTC offset in minutes
92
+ formattedNumber: string; // Intl.NumberFormat sample: "123,456.789"
93
+ formattedDate: string; // Intl.DateTimeFormat sample for Jan 2, 2024
94
+ formattedRelativeDay: string | undefined; // Intl.RelativeTimeFormat: "yesterday" etc.
95
+ }
96
+ ```
97
+
98
+ Returns `'unsupported'` if `navigator` or `Intl` is not available.
99
+
100
+ ### `screen` — Screen and display
101
+
102
+ **Tier:** core | **Sync:** yes | **APIs used:** `screen`, `navigator`, `matchMedia`, `devicePixelRatio`
103
+
104
+ ```ts
105
+ // value shape:
106
+ {
107
+ width: number; // screen.width
108
+ height: number; // screen.height
109
+ availWidth: number; // screen.availWidth
110
+ availHeight: number; // screen.availHeight
111
+ colorDepth: number; // screen.colorDepth
112
+ pixelDepth: number; // screen.pixelDepth
113
+ orientationType: string; // screen.orientation.type
114
+ orientationAngle: number; // screen.orientation.angle
115
+ maxTouchPoints: number; // navigator.maxTouchPoints (0 if missing)
116
+ devicePixelRatio: number | undefined; // window.devicePixelRatio
117
+ colorGamutP3: boolean | 'unsupported'; // (color-gamut: p3) media query
118
+ prefersReducedMotion: boolean | 'unsupported'; // (prefers-reduced-motion: reduce)
119
+ prefersContrastMore: boolean | 'unsupported'; // (prefers-contrast: more)
120
+ forcedColorsActive: boolean | 'unsupported'; // (forced-colors: active)
121
+ }
122
+ ```
123
+
124
+ Returns `'unsupported'` if `screen` is not available.
125
+
126
+ ### `hardware` — Hardware properties
127
+
128
+ **Tier:** core | **Sync:** yes | **APIs used:** `navigator`
129
+
130
+ ```ts
131
+ // value shape:
132
+ {
133
+ hardwareConcurrency: number; // navigator.hardwareConcurrency (logical CPU cores)
134
+ deviceMemory: number | undefined; // navigator.deviceMemory (GB, may be undefined)
135
+ platform: string; // navigator.platform
136
+ maxTouchPoints: number; // navigator.maxTouchPoints
137
+ }
138
+ ```
139
+
140
+ Returns `'unsupported'` if `navigator` is not available.
141
+
142
+ ### `storage` — Storage API availability
143
+
144
+ **Tier:** core | **Sync:** yes | **APIs used:** `localStorage`, `sessionStorage`, `indexedDB`, `openDatabase`, `navigator`
145
+
146
+ ```ts
147
+ // value shape:
148
+ {
149
+ cookiesEnabled: boolean; // navigator.cookieEnabled
150
+ localStorage: 'available' | 'blocked' | 'unsupported'; // write-read-delete probe
151
+ sessionStorage: 'available' | 'blocked' | 'unsupported'; // write-read-delete probe
152
+ indexedDb: 'available' | 'unsupported'; // typeof indexedDB check
153
+ openDatabase: 'available' | 'unsupported'; // typeof openDatabase check (Web SQL)
154
+ pdfViewerEnabled: boolean; // navigator.pdfViewerEnabled
155
+ }
156
+ ```
157
+
158
+ ### `fonts` — Font detection
159
+
160
+ **Tier:** core | **Sync:** yes | **APIs used:** DOM (`document.createElement`, `offsetWidth`)
161
+
162
+ ```ts
163
+ // value shape:
164
+ {
165
+ detectedFonts: string[]; // sorted list of detected font family names
166
+ fontCount: number; // count of detected fonts
167
+ }
168
+ ```
169
+
170
+ Tests 10 fonts against 3 generic families (monospace, sans-serif, serif) by rendering hidden `<span>` elements and measuring width differences. Returns `'unsupported'` if the DOM is not available.
171
+
172
+ **Fonts probed:** `Arial`, `Helvetica Neue`, `Times New Roman`, `Georgia`, `Courier New`, `Trebuchet MS`, `Verdana`, `Tahoma`, `Impact`, `Comic Sans MS`
173
+
174
+ ### `canvas` — Canvas 2D fingerprint
175
+
176
+ **Tier:** core | **Sync:** yes | **APIs used:** Canvas 2D (`getContext('2d')`)
177
+
178
+ ```ts
179
+ // value shape:
180
+ {
181
+ dataUrl: string; // canvas.toDataURL() — base64 PNG of rendered pattern
182
+ winding: boolean; // ctx.isPointInPath(1, 1, 'evenodd')
183
+ }
184
+ ```
185
+
186
+ Renders a 280×80 canvas with a filled rectangle (#f60), text ("Browser FP"), and an arc. Differences in GPU, driver, and browser anti-aliasing produce distinct fingerprints.
187
+
188
+ Returns `'unsupported'` if the canvas 2D context is unavailable. Returns `'error'` if rendering throws.
189
+
190
+ ### `webgl` — WebGL fingerprint
191
+
192
+ **Tier:** core | **Sync:** yes | **APIs used:** WebGL (`getContext('webgl')`, `WEBGL_debug_renderer_info`)
193
+
194
+ ```ts
195
+ // value shape:
196
+ {
197
+ vendor: string; // UNMASKED_VENDOR_WEBGL or VENDOR
198
+ renderer: string; // UNMASKED_RENDERER_WEBGL or RENDERER
199
+ version: string; // WebGL version string
200
+ shadingLanguageVersion: string; // GLSL version string
201
+ maxTextureSize: number; // MAX_TEXTURE_SIZE
202
+ maxViewportDims: Int32Array; // MAX_VIEWPORT_DIMS
203
+ extensions: string[]; // getSupportedExtensions() sorted
204
+ }
205
+ ```
206
+
207
+ Attempts `webgl` context first, falls back to `experimental-webgl`. Uses `WEBGL_debug_renderer_info` for unmasked GPU strings when available.
208
+
209
+ Returns `'unsupported'` if canvas/WebGL is not available.
210
+
211
+ ### `audio` — AudioContext fingerprint
212
+
213
+ **Tier:** core | **Sync:** no | **APIs used:** `OfflineAudioContext`, `OscillatorNode`, `DynamicsCompressorNode`
214
+
215
+ ```ts
216
+ // value shape:
217
+ {
218
+ sampleRate: number; // rendered.sampleRate
219
+ length: number; // rendered.length (number of sample frames)
220
+ signalValue: number; // sum of absolute channel data values (6 decimal places)
221
+ }
222
+ ```
223
+
224
+ Creates an OfflineAudioContext (1 channel, 5000 samples, 44100 Hz), plays a 10 kHz triangle wave through a DynamicsCompressor with tuned parameters, then sums the absolute values of the rendered output. Different audio hardware/drivers produce distinct values.
225
+
226
+ Returns `'unsupported'` if `OfflineAudioContext` (or `webkitOfflineAudioContext`) is not available. Returns `'blocked'` if rendering throws.
227
+
228
+ ---
229
+
230
+ ## Extended signals
231
+
232
+ ### `mediaDevices` — Media device enumeration
233
+
234
+ **Tier:** extended | **Sync:** no | **APIs used:** `navigator.mediaDevices.enumerateDevices`
235
+
236
+ ```ts
237
+ // value shape — an object keyed by MediaDeviceKind:
238
+ {
239
+ audioinput: number; // count of audio input devices
240
+ audiooutput: number; // count of audio output devices
241
+ videoinput: number; // count of video input devices
242
+ }
243
+ ```
244
+
245
+ Only device counts and kinds are collected — no device labels or IDs. Requires a secure context (HTTPS) and a visible document.
246
+
247
+ Returns `'blocked'` if not in a secure context, if document is not visible, or if `enumerateDevices` throws. Returns `'unsupported'` if `mediaDevices` is not available.
248
+
249
+ ### `permissions` — Permissions API state
250
+
251
+ **Tier:** extended | **Sync:** no | **APIs used:** `navigator.permissions.query`
252
+
253
+ ```ts
254
+ // value shape:
255
+ {
256
+ geolocation: 'granted' | 'denied' | 'prompt' | 'unsupported';
257
+ notifications: 'granted' | 'denied' | 'prompt' | 'unsupported';
258
+ camera: 'granted' | 'denied' | 'prompt' | 'unsupported';
259
+ microphone: 'granted' | 'denied' | 'prompt' | 'unsupported';
260
+ }
261
+ ```
262
+
263
+ Queries 4 permission names in parallel. Each individual query that fails returns `'unsupported'` for that key.
264
+
265
+ Returns `'unsupported'` if `navigator.permissions` is not available. Returns `'blocked'` if all queries fail together.
266
+
267
+ ### `webrtc` — WebRTC codec capability
268
+
269
+ **Tier:** extended | **Sync:** no | **APIs used:** `RTCPeerConnection`
270
+
271
+ ```ts
272
+ // value shape:
273
+ {
274
+ codecs: string[]; // sorted audio/video codec names from SDP rtpmap lines
275
+ extmaps: string[]; // sorted extmap IDs from SDP
276
+ }
277
+ ```
278
+
279
+ Creates an RTCPeerConnection, opens a data channel, generates an SDP offer, and parses supported codecs and extmap entries. The peer connection is closed after collection.
280
+
281
+ Returns `'unsupported'` if `RTCPeerConnection` (or `webkitRTCPeerConnection`) is not available. Returns `'blocked'` if SDP negotiation throws.
282
+
283
+ ### `frameInfo` — Iframe context
284
+
285
+ **Tier:** extended | **Sync:** yes | **APIs used:** DOM, `window.top`
286
+
287
+ ```ts
288
+ // value shape:
289
+ {
290
+ iframesCount: number; // count of <iframe> elements in the document
291
+ isTopLevel: boolean; // whether window === window.top
292
+ iframeDomains: string[]; // hostnames extracted from iframe src URLs
293
+ }
294
+ ```
295
+
296
+ Returns `'unsupported'` if `document` is not available. Returns `'error'` if enumeration throws.
297
+
298
+ ### `networkInfo` — Network Information
299
+
300
+ **Tier:** extended | **Sync:** yes | **APIs used:** `navigator.connection` (vendor-prefixed)
301
+
302
+ ```ts
303
+ // value shape:
304
+ {
305
+ effectiveType: string | null; // 'slow-2g' | '2g' | '3g' | '4g' | null
306
+ downlink: number | null; // estimated downlink speed in Mbps
307
+ rtt: number | null; // estimated round-trip time in ms
308
+ saveData: boolean | null; // data-saver mode
309
+ }
310
+ ```
311
+
312
+ Checks `navigator.connection`, `navigator.mozConnection`, and `navigator.webkitConnection` for the NetworkInformation API (Chrome, Firefox, older WebKit).
313
+
314
+ Returns `'unsupported'` if `navigator` or the connection API is not available.
315
+
316
+ ### `paymentSupport` — PaymentRequest API
317
+
318
+ **Tier:** extended | **Sync:** yes | **APIs used:** `window.PaymentRequest`
319
+
320
+ ```ts
321
+ // value shape:
322
+ {
323
+ paymentRequest: boolean; // whether PaymentRequest constructor exists
324
+ }
325
+ ```
326
+
327
+ ### `referrerInfo` — Document referrer
328
+
329
+ **Tier:** extended | **Sync:** yes | **APIs used:** `document.referrer`
330
+
331
+ ```ts
332
+ // value shape:
333
+ {
334
+ referrer: string; // document.referrer (empty string if no referrer)
335
+ }
336
+ ```
337
+
338
+ Returns `'unsupported'` if `document` is not available.
339
+
340
+ ### `navigationInfo` — Navigation type
341
+
342
+ **Tier:** extended | **Sync:** yes | **APIs used:** `PerformanceNavigationTiming`
343
+
344
+ ```ts
345
+ // value shape:
346
+ {
347
+ navigationType: string | number | null;
348
+ // 'navigate' | 'reload' | 'back_forward' | 'prerender' (modern)
349
+ // 0 | 1 | 2 | 255 (legacy performance.navigation.type)
350
+ }
351
+ ```
352
+
353
+ Prefers `PerformanceNavigationTiming.type`. Falls back to deprecated `performance.navigation.type` in older browsers.
354
+
355
+ Returns `'unsupported'` if `performance` is not available. Returns `'error'` if probing throws.
356
+
357
+ ### `riskSignals` — Bot and headless detection
358
+
359
+ **Tier:** extended | **Sync:** no | **APIs used:** `navigator`, `Notification`, `Permissions`, `Performance`
360
+
361
+ ```ts
362
+ // value shape:
363
+ {
364
+ webdriver: boolean | null; // navigator.webdriver flag
365
+ headlessHints: string[]; // detected headless indicators
366
+ uaConsistency: boolean | null; // UA platform vs UA-CH consistency
367
+ notificationPermission: string | null; // Notification.permission
368
+ permissionsHeadlessHint: boolean | null; // denied+default mismatch
369
+ timeToCaptureMs: number | null; // ms since navigation start
370
+ }
371
+ ```
372
+
373
+ #### Headless hints checked
374
+
375
+ | Hint code | Trigger |
376
+ |-----------|---------|
377
+ | `headless-ua` | UA contains `"HeadlessChrome"` |
378
+ | `missing-chrome-object` | Chrome UA but `window.chrome` is not an object |
379
+ | `zero-outer-dimensions` | `outerWidth === 0` or `outerHeight === 0` |
380
+ | `no-plugins-on-desktop-chrome` | Chrome UA (non-mobile) but `navigator.plugins` is empty |
381
+ | `empty-languages` | `navigator.languages` is an empty array |
382
+
383
+ Returns `'unsupported'` if `navigator` is not available. Returns `'blocked'` if collection throws.
package/package.json CHANGED
@@ -1,75 +1,78 @@
1
1
  {
2
- "name": "@ianmenethil/zp-devicefp",
3
- "version": "0.1.0-alpha.0",
4
- "description": "Async-first browser and device fingerprinting library with explainable signals and anomaly scoring.",
5
- "type": "module",
6
- "main": "./dist/npm/index.cjs",
7
- "module": "./dist/npm/index.mjs",
8
- "types": "./dist/npm/index.d.ts",
9
- "sideEffects": false,
10
- "exports": {
11
- ".": {
12
- "types": "./dist/npm/index.d.ts",
13
- "import": "./dist/npm/index.mjs",
14
- "require": "./dist/npm/index.cjs",
15
- "default": "./dist/npm/index.mjs"
16
- }
17
- },
18
- "files": [
19
- "dist",
20
- "README.md",
21
- "LICENSE",
22
- "docs"
23
- ],
24
- "packageManager": "bun@1.3.9",
25
- "engines": {
26
- "bun": ">=1.3.0",
27
- "node": ">=22"
28
- },
29
- "prepublishOnly": "bun run build",
30
- "scripts": {
31
- "build": "bun run check && bun run test:coverage && bun -e \"await import('./.scripts/build.ts')\"",
32
- "test": "bun test",
33
- "test:coverage": "bun test --coverage",
34
- "release": "sh -c 'V=$(npm version \"${1:-minor}\" -m \"chore: release %s\" | tail -1 | sed s/v//) && bun publish --access public && mkdir -p \"F:/_ZP-Main/apps/CDN-server/public/devicefp/$V\" && cp -r dist/cdn/* \"F:/_ZP-Main/apps/CDN-server/public/devicefp/$V/\" && mkdir -p \"F:/_ZP-Main/apps/CDN-server/public/devicefp/latest\" && cp -r dist/cdn/* \"F:/_ZP-Main/apps/CDN-server/public/devicefp/latest/\" && git push --follow-tags' --",
35
- "smoke": "bun run scripts/smoke.mjs",
36
- "check": "bun run lint && bun run typecheck && bun run knip && bun run jscpd",
37
- "lint": "eslint . --config eslint.config.js",
38
- "typecheck": "tsc -p tsconfig.json --noEmit",
39
- "knip": "knip --config knip.json",
40
- "jscpd": "jscpd --config .jscpd.json",
41
- "format": "prettier --write .",
42
- "format:check": "prettier --check ."
43
- },
44
- "keywords": [
45
- "browser",
46
- "device",
47
- "fingerprint",
48
- "typescript",
49
- "fraud",
50
- "risk"
51
- ],
52
- "author": "Ian / Zenith Payments",
53
- "license": "MIT",
54
- "homepage": "https://github.com/ianmenethil/zp-devicefp#readme",
55
- "repository": {
56
- "type": "git",
57
- "url": "git+https://github.com/ianmenethil/zp-devicefp.git"
58
- },
59
- "bugs": {
60
- "url": "https://github.com/ianmenethil/zp-devicefp/issues"
61
- },
62
- "devDependencies": {
63
- "@eslint/js": "^10.0.1",
64
- "esbuild": "^0.28.0",
65
- "eslint": "^10.3.0",
66
- "eslint-plugin-tsdoc": "^0.5.2",
67
- "javascript-obfuscator": "^5.4.2",
68
- "jscpd": "^4.1.1",
69
- "knip": "^6.13.1",
70
- "lefthook": "^2.1.6",
71
- "prettier": "^3.8.3",
72
- "typescript": "~6.0.3",
73
- "typescript-eslint": "^8.59.3"
2
+ "name": "@ianmenethil/zp-devicefp",
3
+ "version": "0.1.0",
4
+ "description": "Async-first browser and device fingerprinting library with explainable signals and anomaly scoring.",
5
+ "type": "module",
6
+ "main": "./dist/npm/index.cjs",
7
+ "module": "./dist/npm/index.mjs",
8
+ "types": "./dist/npm/index.d.ts",
9
+ "sideEffects": false,
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/npm/index.d.ts",
13
+ "import": "./dist/npm/index.mjs",
14
+ "require": "./dist/npm/index.cjs",
15
+ "default": "./dist/npm/index.mjs"
74
16
  }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "LICENSE",
22
+ "docs"
23
+ ],
24
+ "packageManager": "bun@1.3.9",
25
+ "engines": {
26
+ "bun": ">=1.3.0",
27
+ "node": ">=22"
28
+ },
29
+ "prepublishOnly": "bun run build",
30
+ "scripts": {
31
+ "build": "bun run check && bun run test:coverage && bun -e \"await import('./.scripts/build.ts')\"",
32
+ "test": "bun test",
33
+ "test:coverage": "bun test --coverage",
34
+ "release": "sh -c 'V=$(npm version \"${1:-minor}\" -m \"chore: release %s\" | tail -1 | sed s/v//) && bun publish --access public && rm -rf \"F:/_ZP-Main/apps/CDN-server/public/devicefp\" && mkdir -p \"F:/_ZP-Main/apps/CDN-server/public/devicefp/$V\" && cp -r dist/cdn/* \"F:/_ZP-Main/apps/CDN-server/public/devicefp/$V/\" && mkdir -p \"F:/_ZP-Main/apps/CDN-server/public/devicefp/latest\" && cp -r dist/cdn/* \"F:/_ZP-Main/apps/CDN-server/public/devicefp/latest/\" && git push --follow-tags' --",
35
+ "release:minor": "bun run release minor",
36
+ "release:patch": "bun run release patch",
37
+ "release:major": "bun run release major",
38
+ "smoke": "bun run scripts/smoke.mjs",
39
+ "check": "bun run lint && bun run typecheck && bun run knip && bun run jscpd",
40
+ "lint": "eslint . --config eslint.config.js",
41
+ "typecheck": "tsc -p tsconfig.json --noEmit",
42
+ "knip": "knip --config knip.json",
43
+ "jscpd": "jscpd --config .jscpd.json",
44
+ "format": "prettier --write .",
45
+ "format:check": "prettier --check ."
46
+ },
47
+ "keywords": [
48
+ "browser",
49
+ "device",
50
+ "fingerprint",
51
+ "typescript",
52
+ "fraud",
53
+ "risk"
54
+ ],
55
+ "author": "Ian / Zenith Payments",
56
+ "license": "MIT",
57
+ "homepage": "https://github.com/ianmenethil/zp-devicefp#readme",
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "git+https://github.com/ianmenethil/zp-devicefp.git"
61
+ },
62
+ "bugs": {
63
+ "url": "https://github.com/ianmenethil/zp-devicefp/issues"
64
+ },
65
+ "devDependencies": {
66
+ "@eslint/js": "^10.0.1",
67
+ "esbuild": "^0.28.0",
68
+ "eslint": "^10.3.0",
69
+ "eslint-plugin-tsdoc": "^0.5.2",
70
+ "javascript-obfuscator": "^5.4.2",
71
+ "jscpd": "^4.2.0",
72
+ "knip": "^6.13.1",
73
+ "lefthook": "^2.1.6",
74
+ "prettier": "^3.8.3",
75
+ "typescript": "~6.0.3",
76
+ "typescript-eslint": "^8.59.3"
77
+ }
75
78
  }