@mack1ch/fingerprint-js 0.1.4 → 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/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +716 -205
- package/demo/index.html +285 -0
- package/dist/collectors/audio.d.ts +3 -0
- package/dist/collectors/audio.d.ts.map +1 -0
- package/dist/collectors/canvas.d.ts +3 -0
- package/dist/collectors/canvas.d.ts.map +1 -0
- package/dist/collectors/fonts.d.ts +3 -0
- package/dist/collectors/fonts.d.ts.map +1 -0
- package/dist/collectors/locale.d.ts +3 -0
- package/dist/collectors/locale.d.ts.map +1 -0
- package/dist/collectors/navigator.d.ts +3 -0
- package/dist/collectors/navigator.d.ts.map +1 -0
- package/dist/collectors/permissions.d.ts +3 -0
- package/dist/collectors/permissions.d.ts.map +1 -0
- package/dist/collectors/screen.d.ts +3 -0
- package/dist/collectors/screen.d.ts.map +1 -0
- package/dist/collectors/storage.d.ts +3 -0
- package/dist/collectors/storage.d.ts.map +1 -0
- package/dist/collectors/webgl.d.ts +3 -0
- package/dist/collectors/webgl.d.ts.map +1 -0
- package/dist/core/collector.d.ts +20 -0
- package/dist/core/collector.d.ts.map +1 -0
- package/dist/core/runCollectors.d.ts +9 -0
- package/dist/core/runCollectors.d.ts.map +1 -0
- package/dist/core/serializer.d.ts +3 -0
- package/dist/core/serializer.d.ts.map +1 -0
- package/dist/hash/hash.d.ts +3 -0
- package/dist/hash/hash.d.ts.map +1 -0
- package/dist/hash/syncHash.d.ts +2 -0
- package/dist/hash/syncHash.d.ts.map +1 -0
- package/dist/index.cjs +2245 -1457
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +23 -172
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2240 -1426
- package/dist/index.js.map +1 -0
- package/dist/index.umd.js +2299 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/normalizers/canonicalize.d.ts +2 -0
- package/dist/normalizers/canonicalize.d.ts.map +1 -0
- package/dist/risk/anomaly.d.ts +3 -0
- package/dist/risk/anomaly.d.ts.map +1 -0
- package/dist/risk/scoring.d.ts +6 -0
- package/dist/risk/scoring.d.ts.map +1 -0
- package/dist/types/public.d.ts +70 -0
- package/dist/types/public.d.ts.map +1 -0
- package/dist/utils/env.d.ts +4 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/timeout.d.ts +3 -0
- package/dist/utils/timeout.d.ts.map +1 -0
- package/dist/v2/browser.d.ts +14 -0
- package/dist/v2/browser.d.ts.map +1 -0
- package/dist/v2/core.d.ts +8 -0
- package/dist/v2/core.d.ts.map +1 -0
- package/dist/v2/index.d.ts +5 -0
- package/dist/v2/index.d.ts.map +1 -0
- package/dist/v2/server.d.ts +24 -0
- package/dist/v2/server.d.ts.map +1 -0
- package/dist/v2/types.d.ts +162 -0
- package/dist/v2/types.d.ts.map +1 -0
- package/dist/vnext/artifacts.d.ts +11 -0
- package/dist/vnext/artifacts.d.ts.map +1 -0
- package/dist/vnext/calibration.d.ts +14 -0
- package/dist/vnext/calibration.d.ts.map +1 -0
- package/dist/vnext/contracts.d.ts +36 -0
- package/dist/vnext/contracts.d.ts.map +1 -0
- package/dist/vnext/dataset.d.ts +16 -0
- package/dist/vnext/dataset.d.ts.map +1 -0
- package/dist/vnext/features.d.ts +3 -0
- package/dist/vnext/features.d.ts.map +1 -0
- package/dist/vnext/index.d.ts +18 -0
- package/dist/vnext/index.d.ts.map +1 -0
- package/dist/vnext/inference.d.ts +5 -0
- package/dist/vnext/inference.d.ts.map +1 -0
- package/dist/vnext/labeling.d.ts +13 -0
- package/dist/vnext/labeling.d.ts.map +1 -0
- package/dist/vnext/logging.d.ts +53 -0
- package/dist/vnext/logging.d.ts.map +1 -0
- package/dist/vnext/models.d.ts +44 -0
- package/dist/vnext/models.d.ts.map +1 -0
- package/dist/vnext/monitoring.d.ts +10 -0
- package/dist/vnext/monitoring.d.ts.map +1 -0
- package/dist/vnext/quality.d.ts +9 -0
- package/dist/vnext/quality.d.ts.map +1 -0
- package/dist/vnext/retrieval.d.ts +32 -0
- package/dist/vnext/retrieval.d.ts.map +1 -0
- package/dist/vnext/runtime.d.ts +44 -0
- package/dist/vnext/runtime.d.ts.map +1 -0
- package/dist/vnext/storage.d.ts +49 -0
- package/dist/vnext/storage.d.ts.map +1 -0
- package/dist/vnext/types.d.ts +103 -0
- package/dist/vnext/types.d.ts.map +1 -0
- package/docs/API.md +26 -0
- package/docs/ARCHITECTURE.md +48 -0
- package/docs/BROWSER_SDK_V2.md +18 -0
- package/docs/PAYMENT_FLOW_MAPPING_V2.md +20 -0
- package/docs/SECURITY_PRIVACY.md +21 -0
- package/docs/SERVER_SDK_V2.md +25 -0
- package/docs/TESTING.md +33 -0
- package/docs/UPGRADE_V2.md +21 -0
- package/docs/VNEXT_PHASE2.md +65 -0
- package/docs/rollout-checklist.md +54 -0
- package/docs/threshold-policy.md +50 -0
- package/package.json +60 -85
- package/dist/index.d.cts +0 -172
package/README.md
CHANGED
|
@@ -1,252 +1,763 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @1payment/fingerprint
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK для device/browser fingerprinting в антифрод-сценариях.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- строит устойчивый `coreHash` и детальный `fullHash`,
|
|
7
|
-
- считает метрики качества (`confidenceScore`),
|
|
8
|
-
- сравнивает профили (`similarityScore`, `riskScore`, `verdict`).
|
|
5
|
+
Пакет помогает:
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
- собирать браузерные сигналы,
|
|
8
|
+
- вычислять устойчивые идентификаторы среды,
|
|
9
|
+
- сравнивать fingerprint-снимки,
|
|
10
|
+
- строить вероятностный matching-контур (vNext),
|
|
11
|
+
- безопасно раскатывать решения через shadow/compare/assist/enforcement.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Установка
|
|
11
16
|
|
|
12
17
|
```bash
|
|
13
|
-
npm i @
|
|
18
|
+
npm i @1payment/fingerprint
|
|
14
19
|
```
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
Требования:
|
|
22
|
+
|
|
23
|
+
- Node.js `>= 18`
|
|
24
|
+
- для браузерной части нужен реальный browser context
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 2. Быстрый старт (Browser)
|
|
17
29
|
|
|
18
30
|
```ts
|
|
19
|
-
import {
|
|
31
|
+
import { createFingerprint } from "@1payment/fingerprint";
|
|
20
32
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
26
|
-
optional: {
|
|
27
|
-
accountAgeDays: 180,
|
|
28
|
-
abBucket: 'A',
|
|
29
|
-
},
|
|
30
|
-
performance: { mode: 'balanced' },
|
|
31
|
-
privacy: { includeRaw: true, maskSensitive: true },
|
|
33
|
+
const fp = await createFingerprint({
|
|
34
|
+
mode: "balanced",
|
|
35
|
+
includeRaw: false,
|
|
36
|
+
includeDebug: false
|
|
32
37
|
});
|
|
33
38
|
|
|
34
|
-
console.log(
|
|
35
|
-
console.log(profile.fullHash); // Полный отпечаток с шумными сигналами
|
|
36
|
-
console.log(profile.meta.confidenceScore);
|
|
39
|
+
console.log(fp);
|
|
37
40
|
```
|
|
38
41
|
|
|
39
|
-
|
|
42
|
+
Вы получаете:
|
|
40
43
|
|
|
41
|
-
- `
|
|
42
|
-
|
|
43
|
-
- `
|
|
44
|
-
|
|
45
|
-
- `
|
|
46
|
-
|
|
47
|
-
- `compareFingerprintAgainstHistory(current, history): FingerprintHistoryComparison`
|
|
48
|
-
- сравнение с набором исторических профилей и выбор лучшего матча.
|
|
49
|
-
- `buildIdentityId(profile): string`
|
|
50
|
-
- возвращает стабильный identity ID (сейчас это `coreHash`).
|
|
51
|
-
- `startPaymentFingerprintSession(options): PaymentFingerprintSession`
|
|
52
|
-
- двухфазный сбор для формы оплаты: быстрый профиль сразу + полный профиль в фоне.
|
|
53
|
-
- `assessPaymentRisk(input): PaymentRiskAssessment`
|
|
54
|
-
- готовая оценка риска для payment flow с `allow/challenge/manual_review/deny`.
|
|
55
|
-
|
|
56
|
-
## Опции `getFingerprint`
|
|
44
|
+
- `fingerprintId` — полный fingerprint,
|
|
45
|
+
- `stableId` — более устойчивый идентификатор,
|
|
46
|
+
- `environmentId` — идентификатор среды,
|
|
47
|
+
- `confidenceScore` — качество сигналов,
|
|
48
|
+
- `riskScore` — эвристический риск,
|
|
49
|
+
- `flags` — признаки нестабильности/анома
|
|
57
50
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
timeoutMs?: number;
|
|
77
|
-
maxConcurrency?: number;
|
|
78
|
-
cacheTtlMs?: number;
|
|
79
|
-
};
|
|
80
|
-
privacy?: {
|
|
81
|
-
includeRaw?: boolean;
|
|
82
|
-
maskSensitive?: boolean;
|
|
83
|
-
};
|
|
84
|
-
hash?: {
|
|
85
|
-
algorithm?: 'SHA-256' | 'MD5';
|
|
86
|
-
};
|
|
87
|
-
};
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Рекомендации по входам
|
|
91
|
-
|
|
92
|
-
- В `required` передавайте стабильные бизнес-идентификаторы (`merchantId`, `userId`).
|
|
93
|
-
- Не передавайте в `required` постоянно меняющиеся значения (`sessionId`, nonce), иначе устойчивость снизится.
|
|
94
|
-
- Если нужно использовать `sessionId`, лучше класть в `optional`.
|
|
95
|
-
|
|
96
|
-
## Формат результата `FingerprintResult`
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 3. Основной публичный API
|
|
54
|
+
|
|
55
|
+
### Базовые функции
|
|
56
|
+
|
|
57
|
+
- `createFingerprint(options?) => Promise<FingerprintResult>`
|
|
58
|
+
- `getFingerprint(options?) => Promise<FingerprintResult>` (алиас)
|
|
59
|
+
- `hashComponents(components, salt?) => string`
|
|
60
|
+
- `compareFingerprints(a, b, options?) => FingerprintComparison`
|
|
61
|
+
- `toFingerprintPayload(result, requestId?) => FingerprintPayload`
|
|
62
|
+
|
|
63
|
+
### Стратегии сравнения
|
|
64
|
+
|
|
65
|
+
- `strict` — более жесткое сравнение
|
|
66
|
+
- `resilient` — устойчивое сравнение при частичных изменениях сигналов
|
|
67
|
+
|
|
68
|
+
Пример:
|
|
97
69
|
|
|
98
70
|
```ts
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
- `
|
|
124
|
-
- `
|
|
125
|
-
- `
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
71
|
+
import { compareFingerprints } from "@1payment/fingerprint";
|
|
72
|
+
|
|
73
|
+
const result = compareFingerprints(fpA, fpB, { strategy: "resilient" });
|
|
74
|
+
console.log(result.similarity, result.confidenceLabel);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 4. Опции createFingerprint
|
|
80
|
+
|
|
81
|
+
Ключевые опции:
|
|
82
|
+
|
|
83
|
+
- `mode`: `fast | balanced | strict`
|
|
84
|
+
- `timeoutMs`: таймаут на коллектор
|
|
85
|
+
- `includeRaw`: включать сырые компоненты
|
|
86
|
+
- `includeDebug`: отладочная информация по коллекторам
|
|
87
|
+
- `includePerformance`: тайминги коллекторов
|
|
88
|
+
- `collectPermissions`, `collectCanvas`, `collectWebGL`, `collectAudio`, `collectFonts`, `collectStorageSignals`
|
|
89
|
+
- `salt`: соль для хэширования
|
|
90
|
+
- `componentWeights`: веса компонент
|
|
91
|
+
- `customComponents`: свои асинхронные коллекторы
|
|
92
|
+
|
|
93
|
+
Рекомендация по режимам:
|
|
94
|
+
|
|
95
|
+
- `fast` — для раннего этапа страницы,
|
|
96
|
+
- `balanced` — рабочий дефолт,
|
|
97
|
+
- `strict` — для high-risk сценариев.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 5. Namespace v2 (browser + server flow)
|
|
102
|
+
|
|
103
|
+
В пакете есть namespace `v2`:
|
|
104
|
+
|
|
105
|
+
- `v2.BrowserFingerprintSession`
|
|
106
|
+
- `v2.FingerprintServerSDK`
|
|
107
|
+
- `v2.migrateV1PayloadToV2`
|
|
108
|
+
- `v2.validateEnvelopeV2`
|
|
109
|
+
- `v2.hashPII`
|
|
110
|
+
- `v2.similarityScore`
|
|
111
|
+
|
|
112
|
+
### Browser (v2)
|
|
131
113
|
|
|
132
114
|
```ts
|
|
133
|
-
import {
|
|
134
|
-
getFingerprint,
|
|
135
|
-
compareFingerprints,
|
|
136
|
-
compareFingerprintAgainstHistory,
|
|
137
|
-
} from '@mack1ch/fingerprint-js';
|
|
115
|
+
import { v2 } from "@1payment/fingerprint";
|
|
138
116
|
|
|
139
|
-
const
|
|
140
|
-
const
|
|
117
|
+
const session = new v2.BrowserFingerprintSession();
|
|
118
|
+
const envelope = await session.collectStage({
|
|
119
|
+
requestId: "req-1",
|
|
120
|
+
stage: "pre_submit",
|
|
121
|
+
mode: "balanced"
|
|
122
|
+
});
|
|
123
|
+
```
|
|
141
124
|
|
|
142
|
-
|
|
143
|
-
// oneToOne.verdict: same_device | likely_same | suspicious | different_device
|
|
125
|
+
### Server (v2)
|
|
144
126
|
|
|
145
|
-
|
|
146
|
-
|
|
127
|
+
```ts
|
|
128
|
+
import { v2 } from "@1payment/fingerprint";
|
|
129
|
+
|
|
130
|
+
const sdk = new v2.FingerprintServerSDK({ shadowMode: true });
|
|
131
|
+
const ingest = await sdk.ingestEnvelope(envelope, { country: "RU", asn: "AS12345" });
|
|
132
|
+
const match = await sdk.matchProfiles(ingest.eventId);
|
|
133
|
+
const risk = await sdk.calculateRisk(ingest.eventId);
|
|
147
134
|
```
|
|
148
135
|
|
|
149
|
-
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 6. Namespace vNext (matching platform)
|
|
150
139
|
|
|
151
|
-
|
|
152
|
-
- `riskScore` (0..1),
|
|
153
|
-
- `verdict`,
|
|
154
|
-
- `reasons[]`,
|
|
155
|
-
- `changedStableKeys[]`, `changedVolatileKeys[]`.
|
|
140
|
+
`vNext` — это production-контур для candidate retrieval + pairwise matching.
|
|
156
141
|
|
|
157
|
-
|
|
142
|
+
Что доступно:
|
|
158
143
|
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
144
|
+
- versioned contracts и compatibility validation,
|
|
145
|
+
- retrieval и `recall@K`,
|
|
146
|
+
- pairwise feature builder,
|
|
147
|
+
- baseline/prod модели,
|
|
148
|
+
- calibration layer,
|
|
149
|
+
- decision output (`same_device | likely_same | review | different`),
|
|
150
|
+
- rollout runtime (`shadow | compare | assist | enforcement`),
|
|
151
|
+
- logging/storage/labeling helpers,
|
|
152
|
+
- мониторинг (cohorts, drift).
|
|
166
153
|
|
|
167
|
-
|
|
154
|
+
### Пример vNext runtime
|
|
168
155
|
|
|
169
156
|
```ts
|
|
170
|
-
import {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
required: {
|
|
179
|
-
merchantId: 'm-1',
|
|
180
|
-
paymentId: 'pay-100500',
|
|
181
|
-
customerId: 'c-42',
|
|
182
|
-
},
|
|
183
|
-
optional: {
|
|
184
|
-
emailHash: '...',
|
|
185
|
-
accountAgeDays: 220,
|
|
186
|
-
},
|
|
187
|
-
performance: {
|
|
188
|
-
fullMode: 'balanced',
|
|
189
|
-
startFullOnIdle: true,
|
|
190
|
-
},
|
|
191
|
-
hooks: {
|
|
192
|
-
onQuick: (quickProfile) => {
|
|
193
|
-
// Отправить быстрый профиль сразу после открытия формы.
|
|
194
|
-
void fetch('/api/fingerprint/stage', {
|
|
195
|
-
method: 'POST',
|
|
196
|
-
headers: { 'content-type': 'application/json' },
|
|
197
|
-
body: JSON.stringify(fpSession.buildStagePayload('quick', quickProfile)),
|
|
198
|
-
});
|
|
199
|
-
},
|
|
200
|
-
onFull: (fullProfile) => {
|
|
201
|
-
// Отправить полный профиль до сабмита или вместе с сабмитом.
|
|
202
|
-
void fetch('/api/fingerprint/stage', {
|
|
203
|
-
method: 'POST',
|
|
204
|
-
headers: { 'content-type': 'application/json' },
|
|
205
|
-
body: JSON.stringify(fpSession.buildStagePayload('full', fullProfile)),
|
|
206
|
-
});
|
|
207
|
-
},
|
|
208
|
-
},
|
|
157
|
+
import { vNext } from "@1payment/fingerprint";
|
|
158
|
+
|
|
159
|
+
const versions = vNext.buildVersionSet({
|
|
160
|
+
collectorVersion: "collector-v1",
|
|
161
|
+
featureSchemaVersion: "feature-v1",
|
|
162
|
+
modelVersion: "model-v1",
|
|
163
|
+
calibrationVersion: "cal-v1",
|
|
164
|
+
thresholdPolicyVersion: "thr-v1"
|
|
209
165
|
});
|
|
210
166
|
```
|
|
211
167
|
|
|
212
|
-
|
|
168
|
+
Подробности:
|
|
169
|
+
|
|
170
|
+
- `docs/VNEXT_PHASE2.md`
|
|
171
|
+
- `docs/threshold-policy.md`
|
|
172
|
+
- `docs/rollout-checklist.md`
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 7. Offline ML (опционально)
|
|
177
|
+
|
|
178
|
+
Если вы используете оффлайн обучение:
|
|
179
|
+
|
|
180
|
+
- скрипты в `offline_ml/`
|
|
181
|
+
- baseline: Logistic Regression
|
|
182
|
+
- production candidate: HistGradientBoosting/XGBoost
|
|
183
|
+
- калибровка: sigmoid/isotonic
|
|
184
|
+
- экспорт model/calibration/threshold artifacts
|
|
185
|
+
|
|
186
|
+
Запуск:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
python -m venv .venv
|
|
190
|
+
source .venv/bin/activate
|
|
191
|
+
pip install -r offline_ml/requirements.txt
|
|
192
|
+
python offline_ml/train.py --input "/path/to/pairwise.jsonl" --output-dir offline_ml/out --model-kind both --calibration sigmoid
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Подробный процесс:
|
|
196
|
+
|
|
197
|
+
- `offline_ml/README.md`
|
|
198
|
+
- `offline_ml/runbook.md`
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 8. Демо для ручного тестирования
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
npm run demo:serve
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Откройте:
|
|
209
|
+
|
|
210
|
+
- `http://localhost:4173/demo/`
|
|
211
|
+
|
|
212
|
+
В demo есть русскоязычный test harness:
|
|
213
|
+
|
|
214
|
+
- сценарные прогоны (`Balanced x5`, `Strict x5`, `Mixed x6`),
|
|
215
|
+
- быстрый compare последних результатов,
|
|
216
|
+
- ручное сравнение JSON A/B,
|
|
217
|
+
- экспорт истории прогонов.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 9. Проверка качества перед релизом
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
npm run typecheck
|
|
225
|
+
npm run lint
|
|
226
|
+
npm test
|
|
227
|
+
npm run build
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 10. Ограничения и важные замечания
|
|
233
|
+
|
|
234
|
+
- Fingerprinting — вероятностный сигнал, не абсолютная идентификация.
|
|
235
|
+
- Не принимайте финальные антифрод-решения только по одному hash/id.
|
|
236
|
+
- Для прод-решений используйте:
|
|
237
|
+
- candidate retrieval,
|
|
238
|
+
- pairwise scoring,
|
|
239
|
+
- calibration,
|
|
240
|
+
- threshold policy,
|
|
241
|
+
- мониторинг cohort/drift.
|
|
242
|
+
- Synthetic-only данные подходят для bootstrap/sanity, но не для финальных порогов production.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 11. Документация
|
|
247
|
+
|
|
248
|
+
- `docs/API.md`
|
|
249
|
+
- `docs/ARCHITECTURE.md`
|
|
250
|
+
- `docs/SECURITY_PRIVACY.md`
|
|
251
|
+
- `docs/TESTING.md`
|
|
252
|
+
- `docs/UPGRADE_V2.md`
|
|
253
|
+
- `docs/BROWSER_SDK_V2.md`
|
|
254
|
+
- `docs/SERVER_SDK_V2.md`
|
|
255
|
+
- `docs/PAYMENT_FLOW_MAPPING_V2.md`
|
|
256
|
+
- `docs/VNEXT_PHASE2.md`
|
|
257
|
+
- `docs/threshold-policy.md`
|
|
258
|
+
- `docs/rollout-checklist.md`
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## 12. Лицензия
|
|
263
|
+
|
|
264
|
+
MIT
|
|
265
|
+
# @1payment/fingerprint
|
|
266
|
+
|
|
267
|
+
TypeScript SDK для device/browser fingerprinting в платежных сценариях 1PAYMENT.
|
|
268
|
+
|
|
269
|
+
SDK ориентирован на antifraud/risk scoring и состоит из browser + server контуров:
|
|
270
|
+
- сбор клиентских сигналов,
|
|
271
|
+
- probabilistic matching,
|
|
272
|
+
- profile linking (device/payer/instrument),
|
|
273
|
+
- explainable risk.
|
|
274
|
+
|
|
275
|
+
## Важные ограничения
|
|
276
|
+
|
|
277
|
+
- Библиотека **не обещает** 100% идентификацию пользователя и идеальную склейку между браузерами.
|
|
278
|
+
- Результат нужно использовать как вероятностный сигнал для antifraud/risk систем.
|
|
279
|
+
- По умолчанию библиотека **не делает сетевых запросов**.
|
|
280
|
+
- Библиотека **не вызывает permission prompts**.
|
|
281
|
+
|
|
282
|
+
## Установка
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
npm i @1payment/fingerprint
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Быстрый старт (Browser)
|
|
289
|
+
|
|
290
|
+
```html
|
|
291
|
+
<script type="module">
|
|
292
|
+
import { createFingerprint } from "https://esm.sh/@1payment/fingerprint";
|
|
293
|
+
|
|
294
|
+
const fp = await createFingerprint({
|
|
295
|
+
mode: "fast",
|
|
296
|
+
includeRaw: false,
|
|
297
|
+
includeDebug: false
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
console.log(fp);
|
|
301
|
+
</script>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Что SDK возвращает
|
|
305
|
+
|
|
306
|
+
- `fingerprintId` — полный session/browser fingerprint.
|
|
307
|
+
- `stableId` — более устойчивый fingerprint (device-centric subset).
|
|
308
|
+
- `environmentId` — fingerprint среды (браузер/платформа/рендер).
|
|
309
|
+
- `confidenceScore` — качество доступных сигналов.
|
|
310
|
+
- `riskScore` — риск по клиентским эвристикам.
|
|
311
|
+
- `flags` — tamper/anomaly/privacy-индикаторы.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Публичный API
|
|
316
|
+
|
|
317
|
+
### v1/v2 base API
|
|
318
|
+
|
|
319
|
+
- `createFingerprint(options?) => Promise<FingerprintResult>`
|
|
320
|
+
- `getFingerprint(options?) => Promise<FingerprintResult>`
|
|
321
|
+
- `hashComponents(components, salt?) => string`
|
|
322
|
+
- `compareFingerprints(a, b, options?) => FingerprintComparison`
|
|
323
|
+
- `toFingerprintPayload(result, requestId?) => FingerprintPayload`
|
|
324
|
+
|
|
325
|
+
## V2 APIs
|
|
326
|
+
|
|
327
|
+
В `@1payment/fingerprint` доступен namespace `v2`:
|
|
328
|
+
|
|
329
|
+
- `v2.BrowserFingerprintSession`
|
|
330
|
+
- `v2.FingerprintServerSDK`
|
|
331
|
+
- `v2.migrateV1PayloadToV2`
|
|
332
|
+
- `v2.validateEnvelopeV2`
|
|
333
|
+
- `v2.hashPII`
|
|
334
|
+
- `v2.similarityScore`
|
|
335
|
+
|
|
336
|
+
## vNext APIs
|
|
337
|
+
|
|
338
|
+
В `@1payment/fingerprint` доступен namespace `vNext`:
|
|
339
|
+
|
|
340
|
+
- retrieval + pairwise feature builder
|
|
341
|
+
- baseline/production model interfaces
|
|
342
|
+
- calibration layer
|
|
343
|
+
- rollout runtime (`shadow/compare/assist/enforcement`)
|
|
344
|
+
- versioned artifact compatibility validation
|
|
345
|
+
- labeling and monitoring helpers
|
|
346
|
+
|
|
347
|
+
Production contracts описаны в `docs/VNEXT_PHASE2.md`.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Как теперь работает vNext (Phase 2)
|
|
352
|
+
|
|
353
|
+
`vNext` теперь разделен на runtime matching-контур и offline ML lifecycle.
|
|
354
|
+
|
|
355
|
+
### 1) Browser snapshot (сбор сигналов)
|
|
356
|
+
|
|
357
|
+
Browser SDK собирает multi-signal fingerprint (navigator/locale/screen/canvas/webgl/audio/fonts/storage/permissions), а также:
|
|
358
|
+
|
|
359
|
+
- `fingerprintId`, `stableId`, `environmentId`
|
|
360
|
+
- component quality metadata (present/missing/unstable/privacy_reduced/error)
|
|
361
|
+
- top-level flags (webdriver/automation/incognito/spoofing hints)
|
|
362
|
+
|
|
363
|
+
Важно: `fingerprintId/stableId/environmentId` больше не трактуются как финальное решение, а используются как retrieval/diagnostic keys.
|
|
364
|
+
|
|
365
|
+
### 2) Runtime matching pipeline (server-side)
|
|
366
|
+
|
|
367
|
+
`vNext` runtime работает как двухстадийный matching:
|
|
368
|
+
|
|
369
|
+
1. candidate retrieval / blocking
|
|
370
|
+
2. pairwise scoring + calibration + threshold decision
|
|
371
|
+
|
|
372
|
+
Пайплайн:
|
|
373
|
+
|
|
374
|
+
- retrieval находит кандидатов (stable/environment/buckets/context);
|
|
375
|
+
- pairwise feature builder строит comparison vector;
|
|
376
|
+
- model выдает `rawScore`;
|
|
377
|
+
- calibration превращает его в `calibratedProbability`;
|
|
378
|
+
- threshold policy выдает решение:
|
|
379
|
+
- `same_device`
|
|
380
|
+
- `likely_same`
|
|
381
|
+
- `review`
|
|
382
|
+
- `different`
|
|
383
|
+
|
|
384
|
+
Decision содержит:
|
|
385
|
+
|
|
386
|
+
- `rawScore`
|
|
387
|
+
- `calibratedProbability`
|
|
388
|
+
- `decision`
|
|
389
|
+
- `reasons`
|
|
390
|
+
- `candidateSnapshotId`
|
|
391
|
+
- `modelVersion`
|
|
392
|
+
- `calibrationVersion`
|
|
393
|
+
- `thresholdPolicyVersion`
|
|
394
|
+
|
|
395
|
+
### 3) Versioned contracts и артефакты
|
|
396
|
+
|
|
397
|
+
Контракты versioned и валидируются до инференса:
|
|
398
|
+
|
|
399
|
+
- `collector_version`
|
|
400
|
+
- `feature_schema_version`
|
|
401
|
+
- `model_version`
|
|
402
|
+
- `calibration_version`
|
|
403
|
+
- `threshold_policy_version`
|
|
404
|
+
|
|
405
|
+
Артефакты загружаются независимо:
|
|
406
|
+
|
|
407
|
+
- model artifact
|
|
408
|
+
- calibration artifact
|
|
409
|
+
- threshold artifact
|
|
410
|
+
|
|
411
|
+
Если версии несовместимы — runtime отклоняет запуск инференса.
|
|
412
|
+
|
|
413
|
+
### 4) Logging и storage для обучения
|
|
414
|
+
|
|
415
|
+
Runtime логирует:
|
|
416
|
+
|
|
417
|
+
- snapshot-level события
|
|
418
|
+
- retrieval-level события
|
|
419
|
+
- pairwise-level события
|
|
420
|
+
|
|
421
|
+
Логи содержат versions + business anchors и используются для offline dataset/label enrichment.
|
|
422
|
+
|
|
423
|
+
Минимальный storage-модуль хранит:
|
|
424
|
+
|
|
425
|
+
- snapshots
|
|
426
|
+
- retrieval logs
|
|
427
|
+
- pairwise logs
|
|
428
|
+
- match decisions
|
|
429
|
+
- device profile history
|
|
430
|
+
|
|
431
|
+
### 5) Labeling pipeline
|
|
432
|
+
|
|
433
|
+
Pair labels строятся offline job’ом из production-like логов и anchors:
|
|
434
|
+
|
|
435
|
+
- positives: confirmed/verified same-entity anchors
|
|
436
|
+
- negatives: easy/medium/hard
|
|
437
|
+
- hard negatives: near-miss/high-score label=0 и конфликтные сценарии
|
|
438
|
+
|
|
439
|
+
### 6) Offline training lifecycle
|
|
440
|
+
|
|
441
|
+
Offline stack находится в `offline_ml/` и не зависит от runtime:
|
|
442
|
+
|
|
443
|
+
- baseline: Logistic Regression
|
|
444
|
+
- production candidate: XGBoost (или HistGradientBoosting fallback)
|
|
445
|
+
- calibration: sigmoid/isotonic
|
|
446
|
+
- leak-safe split policy
|
|
447
|
+
- evaluation reports + artifact export
|
|
448
|
+
|
|
449
|
+
### 7) Rollout modes
|
|
450
|
+
|
|
451
|
+
Поддерживаются безопасные режимы:
|
|
452
|
+
|
|
453
|
+
- `shadow` — считается, но не влияет на финальный action
|
|
454
|
+
- `compare` — сравнение с предыдущим контуром
|
|
455
|
+
- `assist` — сигнал для review/admin
|
|
456
|
+
- `enforcement` — участие в финальном decisioning
|
|
457
|
+
|
|
458
|
+
### 8) Monitoring и drift
|
|
459
|
+
|
|
460
|
+
Доступны helpers для:
|
|
461
|
+
|
|
462
|
+
- PR/ROC/LogLoss/Brier
|
|
463
|
+
- cohort breakdown
|
|
464
|
+
- retrieval recall@K
|
|
465
|
+
- distribution drift (PSI-like)
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## Как потестировать vNext
|
|
470
|
+
|
|
471
|
+
### Полная проверка (рекомендуется)
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
npm run typecheck
|
|
475
|
+
npm test
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Только vNext тесты
|
|
213
479
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
- `compareFingerprintAgainstHistory(current, history)`
|
|
218
|
-
4. Для лучшего матча получить risk-классификацию:
|
|
219
|
-
- `assessPaymentRisk(...)`
|
|
220
|
-
5. Принять решение:
|
|
221
|
-
- `allow` -> платеж без friction,
|
|
222
|
-
- `challenge` -> 3DS / step-up,
|
|
223
|
-
- `manual_review` -> ручная верификация,
|
|
224
|
-
- `deny` -> отклонение.
|
|
480
|
+
```bash
|
|
481
|
+
npm run test -- test/vnext.pipeline.test.ts test/vnext.retrieval.test.ts test/vnext.phase2.test.ts
|
|
482
|
+
```
|
|
225
483
|
|
|
226
|
-
###
|
|
484
|
+
### Browser smoke (демо)
|
|
227
485
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
- `comparison.best.riskScore`
|
|
232
|
-
- `comparison.best.verdict`
|
|
233
|
-
- `assessment.decision`, `assessment.score`, `assessment.reasons`
|
|
486
|
+
```bash
|
|
487
|
+
npm run demo:serve
|
|
488
|
+
```
|
|
234
489
|
|
|
235
|
-
|
|
490
|
+
Откройте `http://localhost:4173/demo/` и сравните несколько сборов/режимов.
|
|
236
491
|
|
|
237
|
-
|
|
238
|
-
- Некоторые сигналы зависят от браузера, разрешений и сетевой среды.
|
|
239
|
-
- При частичной недоступности API часть сигналов деградирует, это отражается в `meta`.
|
|
492
|
+
Быстрый ручной чеклист в demo:
|
|
240
493
|
|
|
241
|
-
|
|
494
|
+
1. Нажмите `Run Balanced x5`:
|
|
495
|
+
- проверьте `stable transitions` (должен быть высоким);
|
|
496
|
+
- посмотрите `last similarity` между соседними запусками.
|
|
497
|
+
2. Нажмите `Run Mixed x6`:
|
|
498
|
+
- оцените, как меняется similarity между `fast/balanced/strict`;
|
|
499
|
+
- сравните `Compare Last Two`.
|
|
500
|
+
3. Используйте `Use Last as A/B` + `Compare JSONs` и `Compare Strict`:
|
|
501
|
+
- `resilient` обычно устойчивее при частичных изменениях;
|
|
502
|
+
- `strict` полезен для same-browser проверки.
|
|
503
|
+
4. Нажмите `Download History`:
|
|
504
|
+
- сохраните историю прогона в JSON для последующего анализа/репортов.
|
|
505
|
+
5. Нажмите `Clear History` и повторите сценарий в incognito/private режиме.
|
|
506
|
+
|
|
507
|
+
### Offline trainer
|
|
242
508
|
|
|
243
509
|
```bash
|
|
244
|
-
|
|
245
|
-
|
|
510
|
+
python -m venv .venv
|
|
511
|
+
source .venv/bin/activate
|
|
512
|
+
pip install -r offline_ml/requirements.txt
|
|
513
|
+
python offline_ml/train.py --input pairwise_training_rows.jsonl --output-dir offline_ml/out --model-kind both --calibration sigmoid
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Проверьте в `offline_ml/out`:
|
|
517
|
+
|
|
518
|
+
- `model-*.json`
|
|
519
|
+
- `calibration-*.json`
|
|
520
|
+
- `threshold-*.json`
|
|
521
|
+
- `evaluation-report.json`
|
|
522
|
+
- `evaluation-report.md`
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## Режимы сбора (Browser)
|
|
527
|
+
|
|
528
|
+
- `fast` — минимальная задержка для initial render.
|
|
529
|
+
- `balanced` — режим по умолчанию для pre-submit.
|
|
530
|
+
- `strict` — максимальный сбор для high-risk сценариев.
|
|
531
|
+
|
|
532
|
+
Рекомендуемый lifecycle:
|
|
533
|
+
- `page_open` -> `fast`
|
|
534
|
+
- `pre_submit` -> `balanced`
|
|
535
|
+
- `post_init`/подозрительные кейсы -> `strict`
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## Сравнение отпечатков
|
|
540
|
+
|
|
541
|
+
`compareFingerprints()` поддерживает стратегии:
|
|
542
|
+
- `strict` — жесткое сравнение (подходит для same-browser проверок).
|
|
543
|
+
- `resilient` — устойчивое сравнение (лучше для cross-browser и частичных изменений).
|
|
544
|
+
|
|
545
|
+
Пример:
|
|
546
|
+
|
|
547
|
+
```ts
|
|
548
|
+
import { compareFingerprints } from "@1payment/fingerprint";
|
|
549
|
+
|
|
550
|
+
const cmp = compareFingerprints(a, b, { strategy: "resilient" });
|
|
551
|
+
console.log(cmp.similarity, cmp.confidenceLabel, cmp.matchedComponents);
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
## Browser SDK v2 (multi-stage)
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
import { v2 } from "@1payment/fingerprint";
|
|
560
|
+
|
|
561
|
+
const session = new v2.BrowserFingerprintSession();
|
|
562
|
+
|
|
563
|
+
const pageOpen = await session.collectStage({
|
|
564
|
+
requestId: "req-1",
|
|
565
|
+
stage: "page_open",
|
|
566
|
+
mode: "fast",
|
|
567
|
+
paymentContext: {
|
|
568
|
+
partnerId: "partner-1",
|
|
569
|
+
projectId: "project-1",
|
|
570
|
+
flowType: "card_form",
|
|
571
|
+
paymentType: "card",
|
|
572
|
+
userId: "u-123",
|
|
573
|
+
userData: "crm-42",
|
|
574
|
+
shopUrl: "https://merchant.example",
|
|
575
|
+
lang: "ru"
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
session.registerSubmitAttempt();
|
|
580
|
+
|
|
581
|
+
const preSubmit = await session.collectStage({
|
|
582
|
+
requestId: "req-1",
|
|
583
|
+
stage: "pre_submit",
|
|
584
|
+
mode: "balanced"
|
|
585
|
+
});
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
## Server SDK v2 (ingestion/enrichment/scoring)
|
|
591
|
+
|
|
592
|
+
```ts
|
|
593
|
+
import { v2 } from "@1payment/fingerprint";
|
|
594
|
+
|
|
595
|
+
const server = new v2.FingerprintServerSDK({ shadowMode: true });
|
|
596
|
+
|
|
597
|
+
const ingest = await server.ingestEnvelope(preSubmit, {
|
|
598
|
+
ip: "203.0.113.10",
|
|
599
|
+
asn: "AS12345",
|
|
600
|
+
country: "RU",
|
|
601
|
+
origin: "https://merchant.example"
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
await server.enrichWithPaymentInit({
|
|
605
|
+
requestId: "req-1",
|
|
606
|
+
orderId: "order-1001",
|
|
607
|
+
status: "init",
|
|
608
|
+
paymentType: "card"
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
await server.enrichWithCallback({
|
|
612
|
+
orderId: "order-1001",
|
|
613
|
+
status: "success",
|
|
614
|
+
token: "tok_abc",
|
|
615
|
+
accountMask: "4111****1111"
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
const match = await server.matchProfiles(ingest.eventId);
|
|
619
|
+
const risk = await server.calculateRisk(ingest.eventId);
|
|
620
|
+
const admin = await server.buildAdminView(ingest.eventId);
|
|
246
621
|
```
|
|
247
622
|
|
|
248
|
-
|
|
623
|
+
Что возвращает server-контур:
|
|
624
|
+
- match probabilities (`sameDeviceProbability`, `samePayerProbability`, `sameInstrumentProbability`)
|
|
625
|
+
- `profileDecision`
|
|
626
|
+
- explainability (`topRiskReasons`, `topTrustReasons`)
|
|
627
|
+
- admin-ready view model
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## Какие данные прокидывать для максимальной точности
|
|
632
|
+
|
|
633
|
+
Чтобы повысить качество идентификации, передавайте:
|
|
634
|
+
- `partnerId`, `projectId`, `flowType`, `paymentType`
|
|
635
|
+
- `userId`, `userData`
|
|
636
|
+
- `orderId` (когда появляется)
|
|
637
|
+
- `tokenHash`/`token` (для recurring/card linkage)
|
|
638
|
+
- `phoneHash`, `emailHash`, `nameHash` (где применимо)
|
|
639
|
+
- request metadata на сервере: `ip`, `asn`, `country`, `origin/referrer`
|
|
640
|
+
|
|
641
|
+
Важно: точность повышается не за счет одного “супер-хэша”, а за счет **мультисигнального match + server enrichment**.
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
## Как считается fingerprint (формула)
|
|
646
|
+
|
|
647
|
+
Browser SDK собирает набор компонент:
|
|
648
|
+
|
|
649
|
+
`components = { navigator, locale, screen, canvas, webgl, audio, fonts, storage, permissions?, ...customComponents }`
|
|
650
|
+
|
|
651
|
+
Дальше применяется канонизация (стабильный порядок ключей/значений):
|
|
652
|
+
|
|
653
|
+
`C = canonicalize(components)`
|
|
654
|
+
|
|
655
|
+
Если веса не заданы, базовый идентификатор считается как:
|
|
656
|
+
|
|
657
|
+
`fingerprintId = SHA-256(salt + ":" + JSON.stringify(C))`
|
|
658
|
+
|
|
659
|
+
Если заданы `componentWeights`, используется взвешенное хэширование:
|
|
660
|
+
|
|
661
|
+
`fingerprintId = SHA-256(salt + ":" + join("|", repeat(path=value, weight(path))))`
|
|
662
|
+
|
|
663
|
+
где:
|
|
664
|
+
- `path=value` — leaf-пара компоненты (например, `navigator.userAgent="..."`);
|
|
665
|
+
- `weight(path)` — вес для пути (`navigator.userAgent`) или его top-level ключа (`navigator`);
|
|
666
|
+
- веса нормализуются в диапазон `1..10`;
|
|
667
|
+
- вес `<= 0` исключает параметр из финального хэша.
|
|
668
|
+
|
|
669
|
+
Дополнительно считаются:
|
|
670
|
+
- `stableId = hash(weightedStableSubset(C))`
|
|
671
|
+
- `environmentId = hash(weightedEnvironmentSubset(C))`
|
|
672
|
+
- `confidenceScore`, `riskScore`, `flags` — на основе качества/аномалий сигналов.
|
|
673
|
+
|
|
674
|
+
---
|
|
675
|
+
|
|
676
|
+
## Что можно менять самостоятельно
|
|
677
|
+
|
|
678
|
+
Через `createFingerprint(options)` можно гибко управлять расчетом:
|
|
679
|
+
|
|
680
|
+
- включать/выключать коллекторы:
|
|
681
|
+
- `collectCanvas`, `collectWebGL`, `collectAudio`, `collectFonts`, `collectStorageSignals`, `collectPermissions`;
|
|
682
|
+
- задавать `mode` (`fast | balanced | strict`) и `timeoutMs`;
|
|
683
|
+
- добавлять свои сигналы через `customComponents`;
|
|
684
|
+
- менять вклад каждого сигнала через `componentWeights`;
|
|
685
|
+
- задавать `salt` для изоляции хэш-пространства.
|
|
686
|
+
|
|
687
|
+
Пример:
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
const fp = await createFingerprint({
|
|
691
|
+
mode: "balanced",
|
|
692
|
+
salt: "merchant-v1",
|
|
693
|
+
collectPermissions: true,
|
|
694
|
+
componentWeights: {
|
|
695
|
+
navigator: 2,
|
|
696
|
+
"navigator.userAgent": 4,
|
|
697
|
+
screen: 1,
|
|
698
|
+
"fonts.availableCount": 3,
|
|
699
|
+
"audio.baseLatency": 0
|
|
700
|
+
},
|
|
701
|
+
customComponents: {
|
|
702
|
+
appBuild: () => "web-2026.03.20"
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
Важно: изменение весов и состава компонент изменяет распределение `fingerprintId/stableId/environmentId`. Для production рекомендуем фиксировать конфиг по версии (`signalsVersion`/ваша версия профиля).
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
711
|
+
## Совместимость с v1
|
|
712
|
+
|
|
713
|
+
- Принимается старый payload через migration layer:
|
|
714
|
+
- `v2.migrateV1PayloadToV2(...)`
|
|
715
|
+
- В `ingestEnvelope` доступен флаг `migratedFromV1` и `missingFields`.
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
## Demo
|
|
720
|
+
|
|
721
|
+
Запуск:
|
|
249
722
|
|
|
250
723
|
```bash
|
|
251
|
-
npm
|
|
724
|
+
npm run demo:serve
|
|
252
725
|
```
|
|
726
|
+
|
|
727
|
+
Откройте:
|
|
728
|
+
- `http://localhost:4173/demo/`
|
|
729
|
+
|
|
730
|
+
В demo доступны:
|
|
731
|
+
- `Collect Fast/Balanced/Strict`
|
|
732
|
+
- сравнение двух JSON (Safari ↔ Chrome)
|
|
733
|
+
- `Compare JSONs` (resilient)
|
|
734
|
+
- `Compare Strict`
|
|
735
|
+
- `Copy Last JSON`, `Use Last as A/B`
|
|
736
|
+
|
|
737
|
+
---
|
|
738
|
+
|
|
739
|
+
## Скрипты
|
|
740
|
+
|
|
741
|
+
- `npm run lint`
|
|
742
|
+
- `npm run typecheck`
|
|
743
|
+
- `npm run test`
|
|
744
|
+
- `npm run test:browser`
|
|
745
|
+
- `npm run build`
|
|
746
|
+
- `npm run size`
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
## Документация
|
|
751
|
+
|
|
752
|
+
- `docs/API.md`
|
|
753
|
+
- `docs/ARCHITECTURE.md`
|
|
754
|
+
- `docs/SECURITY_PRIVACY.md`
|
|
755
|
+
- `docs/TESTING.md`
|
|
756
|
+
- `docs/UPGRADE_V2.md`
|
|
757
|
+
- `docs/BROWSER_SDK_V2.md`
|
|
758
|
+
- `docs/SERVER_SDK_V2.md`
|
|
759
|
+
- `docs/PAYMENT_FLOW_MAPPING_V2.md`
|
|
760
|
+
- `docs/VNEXT_PHASE2.md`
|
|
761
|
+
- `docs/threshold-policy.md`
|
|
762
|
+
- `docs/rollout-checklist.md`
|
|
763
|
+
- `offline_ml/runbook.md`
|