@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 +173 -103
- package/dist/cdn/zp.dfp.esm.js +294 -84
- package/dist/cdn/zp.dfp.js +287 -84
- package/dist/cdn/zp.dfp.manifest.json +17 -17
- package/dist/cdn/zp.dfp.min.js +1 -1
- package/dist/cdn/zp.dfp.obf.js +1 -1
- package/dist/npm/index.cjs +301 -84
- package/dist/npm/index.d.ts +21 -8
- package/dist/npm/index.mjs +294 -84
- package/docs/api-reference.md +520 -472
- package/docs/architecture.md +171 -40
- package/docs/privacy.md +20 -20
- package/docs/signals.md +425 -383
- package/docs/testing.md +54 -32
- package/package.json +3 -3
package/docs/architecture.md
CHANGED
|
@@ -1,40 +1,171 @@
|
|
|
1
|
-
# Architecture
|
|
2
|
-
|
|
3
|
-
##
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## Module structure
|
|
4
|
+
|
|
5
|
+
```mermaid
|
|
6
|
+
graph TD
|
|
7
|
+
subgraph "Public API"
|
|
8
|
+
index["src/index.ts<br/>Barrel: createFingerprintClient,<br/>LIBRARY_VERSION, SCHEMA_VERSION"]
|
|
9
|
+
cdn["src/cdn.ts<br/>IIFE entry: attaches<br/>window.DeviceFP"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
subgraph "Client Orchestration"
|
|
13
|
+
client["src/client.ts<br/>Factory + collect / collectSync / on"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
subgraph "Core Modules"
|
|
17
|
+
options["core/options.ts<br/>Default resolution,<br/>signal set filtering"]
|
|
18
|
+
canonicalize["core/canonicalize.ts<br/>Deterministic JSON<br/>for hashing"]
|
|
19
|
+
hash["core/hash.ts<br/>Zero-dependency SHA-256"]
|
|
20
|
+
antiSpoof["core/antiSpoof.ts<br/>Cross-signal coherence<br/>6 anomaly + 2 automation checks"]
|
|
21
|
+
confidence["core/confidence.ts<br/>Weighted signal scoring,<br/>anti-spoof blend (65/35)"]
|
|
22
|
+
events["core/events.ts<br/>Typed FingerprintEmitter<br/>(progress/warning/complete)"]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
subgraph "Runtime"
|
|
26
|
+
browser["runtime/browser.ts<br/>getBrowserEnv, now,<br/>hasDom, hasCanvas"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
subgraph "Signal Collectors (21)"
|
|
30
|
+
signalsIdx["signals/index.ts<br/>signalCollectors array +<br/>collectorMap (Map)"]
|
|
31
|
+
coreSigs["Core (10): ua, uaHints, locale,<br/>screen, hardware, storage, fonts,<br/>canvas, webgl, audio"]
|
|
32
|
+
extSigs["Extended (11): mediaDevices,<br/>permissions, webrtc, frameInfo,<br/>networkInfo, paymentSupport,<br/>referrerInfo, navigationInfo,<br/>riskSignals, adblock, geolocation"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
subgraph "Types"
|
|
36
|
+
publicT["types/public.ts<br/>All public interfaces:<br/>FingerprintResult, CollectOptions, etc."]
|
|
37
|
+
internalT["types/internal.ts<br/>BrowserEnv, SignalCollector,<br/>CollectorContext"]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
subgraph "Constants"
|
|
41
|
+
consts["constants.ts<br/>CORE_SIGNALS, EXTENDED_SIGNALS,<br/>SYNC_SIGNALS, DEFAULT_TIMEOUT_MS,<br/>DEFAULT_FONT_LIST, etc."]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
index --> client
|
|
45
|
+
cdn --> index
|
|
46
|
+
client --> options
|
|
47
|
+
client --> canonicalize
|
|
48
|
+
client --> hash
|
|
49
|
+
client --> antiSpoof
|
|
50
|
+
client --> confidence
|
|
51
|
+
client --> events
|
|
52
|
+
client --> browser
|
|
53
|
+
client --> signalsIdx
|
|
54
|
+
client --> consts
|
|
55
|
+
signalsIdx --> coreSigs
|
|
56
|
+
signalsIdx --> extSigs
|
|
57
|
+
signalsIdx --> internalT
|
|
58
|
+
coreSigs --> browser
|
|
59
|
+
extSigs --> browser
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Main runtime flow (async `collect()`)
|
|
63
|
+
|
|
64
|
+
```mermaid
|
|
65
|
+
sequenceDiagram
|
|
66
|
+
participant User
|
|
67
|
+
participant Client
|
|
68
|
+
participant Options
|
|
69
|
+
participant Signals
|
|
70
|
+
participant Core
|
|
71
|
+
participant Emitter
|
|
72
|
+
|
|
73
|
+
User->>Client: collect(options?)
|
|
74
|
+
Client->>Options: withDefaultOptions(options)
|
|
75
|
+
Options-->>Client: resolved options
|
|
76
|
+
Client->>Options: resolveSignalSet(resolved, 'async')
|
|
77
|
+
Options-->>Client: signalNames[]
|
|
78
|
+
Client->>Client: getBrowserEnv()
|
|
79
|
+
|
|
80
|
+
par Parallel signal collection
|
|
81
|
+
Client->>Signals: collector.collect(ctx) + timeout race
|
|
82
|
+
Signals-->>Client: SignalResult (per signal)
|
|
83
|
+
Client->>Emitter: emit('progress', { completed, total, signal, result })
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
loop For each ok signal
|
|
87
|
+
Client->>Core: canonicalizeToString(value)
|
|
88
|
+
Core-->>Client: deterministic JSON
|
|
89
|
+
Client->>Core: sha256Hex(json)
|
|
90
|
+
Core-->>Client: component hash
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
Client->>Core: sha256Hex(sorted component hashes)
|
|
94
|
+
Core-->>Client: thumbprint
|
|
95
|
+
|
|
96
|
+
Client->>Core: analyseSignals(signals)
|
|
97
|
+
Core-->>Client: AntiSpoofReport
|
|
98
|
+
|
|
99
|
+
Client->>Core: computeConfidence(signals, antiSpoof)
|
|
100
|
+
Core-->>Client: confidence score
|
|
101
|
+
|
|
102
|
+
Client->>Emitter: emit('complete', { result })
|
|
103
|
+
Client-->>User: FingerprintResult
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Data flow: signal → hash → thumbprint
|
|
107
|
+
|
|
108
|
+
```mermaid
|
|
109
|
+
flowchart LR
|
|
110
|
+
A["Signal value<br/>(raw JS object)"] --> B["canonicalizeToString()<br/>sorted keys, 6-decimal numbers,<br/>ISO dates, undefined → 'undefined'"]
|
|
111
|
+
B --> C["sha256Hex()<br/>64-char hex digest"]
|
|
112
|
+
C --> D["componentHashes<br/>Partial<Record<SignalName, string>>"]
|
|
113
|
+
D --> E["Sort by signal name,<br/>serialize as [[name, hash], ...]"]
|
|
114
|
+
E --> F["sha256Hex()"]
|
|
115
|
+
F --> G["thumbprint<br/>(composite 64-char hex)"]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Signal tiers
|
|
119
|
+
|
|
120
|
+
The library splits collectors into two tiers:
|
|
121
|
+
|
|
122
|
+
| Tier | Count | Signals | Description |
|
|
123
|
+
|------|-------|---------|-------------|
|
|
124
|
+
| **Core** | 10 | `ua`, `uaHints`, `locale`, `screen`, `hardware`, `storage`, `fonts`, `canvas`, `webgl`, `audio` | Safe defaults — no permission prompts, broad browser/device context |
|
|
125
|
+
| **Extended** | 11 | `mediaDevices`, `permissions`, `webrtc`, `frameInfo`, `networkInfo`, `paymentSupport`, `referrerInfo`, `navigationInfo`, `riskSignals`, `adblock`, `geolocation` | Slower or more privacy-sensitive probes — requires `extended: true` |
|
|
126
|
+
|
|
127
|
+
## Sync vs async support
|
|
128
|
+
|
|
129
|
+
| Type | Count | Signals |
|
|
130
|
+
|------|-------|---------|
|
|
131
|
+
| **Sync-capable** | 13 | `ua`, `locale`, `screen`, `hardware`, `storage`, `fonts`, `canvas`, `webgl`, `frameInfo`, `networkInfo`, `paymentSupport`, `referrerInfo`, `navigationInfo` |
|
|
132
|
+
| **Async-only** | 8 | `uaHints`, `audio`, `mediaDevices`, `permissions`, `webrtc`, `riskSignals`, `adblock`, `geolocation` |
|
|
133
|
+
|
|
134
|
+
When `collectSync()` is called, only the 13 sync-capable signals run. The 8 async-only signals are silently skipped (with a warning emitted).
|
|
135
|
+
|
|
136
|
+
## Confidence scoring
|
|
137
|
+
|
|
138
|
+
```mermaid
|
|
139
|
+
flowchart TD
|
|
140
|
+
subgraph "Per-signal points"
|
|
141
|
+
W["Signal weights (0.2–1.0)"] --> E
|
|
142
|
+
S["Signal status"] --> E["Earn = weight × multiplier"]
|
|
143
|
+
E --> E2["ok → 1.0× | unsupported/blocked → 0.35×<br/>timeout → 0.15× | error → 0×"]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
E2 --> BASE["base = earned / available"]
|
|
147
|
+
|
|
148
|
+
subgraph "Anti-spoof blend"
|
|
149
|
+
AS["antiSpoof.score<br/>(0 = bot, 1 = genuine)"] --> BLEND
|
|
150
|
+
BASE --> BLEND["final = base × (0.65 + antiSpoof × 0.35)"]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
BLEND --> FINAL["confidence<br/>(0–1, 3 decimal places)"]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Anti-spoof strategy
|
|
157
|
+
|
|
158
|
+
The library does not use "secret" client tricks. It scores coherence through cross-signal consistency checks:
|
|
159
|
+
|
|
160
|
+
- **Anomalies (6 checks, -0.12 each):** platform mismatches between UA and UA-CH, touch claims without touch points, mobile UA with desktop screen, Windows UA with macOS platform, implausible device memory, tiny screen geometry.
|
|
161
|
+
- **Automation hints (2 checks, -0.18 each):** `navigator.webdriver` flag, headless user-agent string.
|
|
162
|
+
|
|
163
|
+
This report is meant to enrich downstream server-side risk logic, not replace it.
|
|
164
|
+
|
|
165
|
+
## Key design decisions
|
|
166
|
+
|
|
167
|
+
- **Async first** — high-value collectors (audio, UA hints, media devices, permissions, WebRTC, risk signals) run async.
|
|
168
|
+
- **Explainable output** — every signal includes a status and duration. No silent failures.
|
|
169
|
+
- **No dangerous defaults** — no battery, sensors, arbitrary installed-app probes, or local IP harvesting.
|
|
170
|
+
- **Anti-spoofing** focuses on coherence checks and automation hints, not "secret" client tricks.
|
|
171
|
+
- **Zero runtime dependencies** — no CDN loads, no npm deps at runtime. SHA-256 is implemented in-house.
|
package/docs/privacy.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
# Privacy Notes
|
|
2
|
-
|
|
3
|
-
This implementation follows the brief's privacy-by-design direction:
|
|
4
|
-
|
|
5
|
-
- The default collector avoids permission prompts.
|
|
6
|
-
- It does not attempt raw local IP discovery.
|
|
7
|
-
- It does not probe battery state, arbitrary sensors, or installed apps.
|
|
8
|
-
- Media devices are summarized by counts and kinds only.
|
|
9
|
-
- WebRTC collection uses capability signatures derived from SDP, not network-address harvesting.
|
|
10
|
-
|
|
11
|
-
## Recommended operational posture
|
|
12
|
-
|
|
13
|
-
- Treat the output as pseudonymous device intelligence, not anonymous data.
|
|
14
|
-
- Retain raw signal payloads for a shorter window than derived hashes or risk scores.
|
|
15
|
-
- Scope identifiers per product or tenant if cross-context correlation is not required.
|
|
16
|
-
- Provide notice and an account- or tenant-level suppression path where appropriate.
|
|
17
|
-
|
|
18
|
-
## Jurisdiction reminder
|
|
19
|
-
|
|
20
|
-
The code can support legitimate security and fraud-prevention use cases, but the legal position still depends on purpose, notice, retention, and jurisdiction.
|
|
1
|
+
# Privacy Notes
|
|
2
|
+
|
|
3
|
+
This implementation follows the brief's privacy-by-design direction:
|
|
4
|
+
|
|
5
|
+
- The default collector avoids permission prompts.
|
|
6
|
+
- It does not attempt raw local IP discovery.
|
|
7
|
+
- It does not probe battery state, arbitrary sensors, or installed apps.
|
|
8
|
+
- Media devices are summarized by counts and kinds only.
|
|
9
|
+
- WebRTC collection uses capability signatures derived from SDP, not network-address harvesting.
|
|
10
|
+
|
|
11
|
+
## Recommended operational posture
|
|
12
|
+
|
|
13
|
+
- Treat the output as pseudonymous device intelligence, not anonymous data.
|
|
14
|
+
- Retain raw signal payloads for a shorter window than derived hashes or risk scores.
|
|
15
|
+
- Scope identifiers per product or tenant if cross-context correlation is not required.
|
|
16
|
+
- Provide notice and an account- or tenant-level suppression path where appropriate.
|
|
17
|
+
|
|
18
|
+
## Jurisdiction reminder
|
|
19
|
+
|
|
20
|
+
The code can support legitimate security and fraud-prevention use cases, but the legal position still depends on purpose, notice, retention, and jurisdiction. Backend handling can enforce local policy on data retention and processing.
|