@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.
Files changed (107) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +716 -205
  4. package/demo/index.html +285 -0
  5. package/dist/collectors/audio.d.ts +3 -0
  6. package/dist/collectors/audio.d.ts.map +1 -0
  7. package/dist/collectors/canvas.d.ts +3 -0
  8. package/dist/collectors/canvas.d.ts.map +1 -0
  9. package/dist/collectors/fonts.d.ts +3 -0
  10. package/dist/collectors/fonts.d.ts.map +1 -0
  11. package/dist/collectors/locale.d.ts +3 -0
  12. package/dist/collectors/locale.d.ts.map +1 -0
  13. package/dist/collectors/navigator.d.ts +3 -0
  14. package/dist/collectors/navigator.d.ts.map +1 -0
  15. package/dist/collectors/permissions.d.ts +3 -0
  16. package/dist/collectors/permissions.d.ts.map +1 -0
  17. package/dist/collectors/screen.d.ts +3 -0
  18. package/dist/collectors/screen.d.ts.map +1 -0
  19. package/dist/collectors/storage.d.ts +3 -0
  20. package/dist/collectors/storage.d.ts.map +1 -0
  21. package/dist/collectors/webgl.d.ts +3 -0
  22. package/dist/collectors/webgl.d.ts.map +1 -0
  23. package/dist/core/collector.d.ts +20 -0
  24. package/dist/core/collector.d.ts.map +1 -0
  25. package/dist/core/runCollectors.d.ts +9 -0
  26. package/dist/core/runCollectors.d.ts.map +1 -0
  27. package/dist/core/serializer.d.ts +3 -0
  28. package/dist/core/serializer.d.ts.map +1 -0
  29. package/dist/hash/hash.d.ts +3 -0
  30. package/dist/hash/hash.d.ts.map +1 -0
  31. package/dist/hash/syncHash.d.ts +2 -0
  32. package/dist/hash/syncHash.d.ts.map +1 -0
  33. package/dist/index.cjs +2245 -1457
  34. package/dist/index.cjs.map +1 -0
  35. package/dist/index.d.ts +23 -172
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +2240 -1426
  38. package/dist/index.js.map +1 -0
  39. package/dist/index.umd.js +2299 -0
  40. package/dist/index.umd.js.map +1 -0
  41. package/dist/normalizers/canonicalize.d.ts +2 -0
  42. package/dist/normalizers/canonicalize.d.ts.map +1 -0
  43. package/dist/risk/anomaly.d.ts +3 -0
  44. package/dist/risk/anomaly.d.ts.map +1 -0
  45. package/dist/risk/scoring.d.ts +6 -0
  46. package/dist/risk/scoring.d.ts.map +1 -0
  47. package/dist/types/public.d.ts +70 -0
  48. package/dist/types/public.d.ts.map +1 -0
  49. package/dist/utils/env.d.ts +4 -0
  50. package/dist/utils/env.d.ts.map +1 -0
  51. package/dist/utils/timeout.d.ts +3 -0
  52. package/dist/utils/timeout.d.ts.map +1 -0
  53. package/dist/v2/browser.d.ts +14 -0
  54. package/dist/v2/browser.d.ts.map +1 -0
  55. package/dist/v2/core.d.ts +8 -0
  56. package/dist/v2/core.d.ts.map +1 -0
  57. package/dist/v2/index.d.ts +5 -0
  58. package/dist/v2/index.d.ts.map +1 -0
  59. package/dist/v2/server.d.ts +24 -0
  60. package/dist/v2/server.d.ts.map +1 -0
  61. package/dist/v2/types.d.ts +162 -0
  62. package/dist/v2/types.d.ts.map +1 -0
  63. package/dist/vnext/artifacts.d.ts +11 -0
  64. package/dist/vnext/artifacts.d.ts.map +1 -0
  65. package/dist/vnext/calibration.d.ts +14 -0
  66. package/dist/vnext/calibration.d.ts.map +1 -0
  67. package/dist/vnext/contracts.d.ts +36 -0
  68. package/dist/vnext/contracts.d.ts.map +1 -0
  69. package/dist/vnext/dataset.d.ts +16 -0
  70. package/dist/vnext/dataset.d.ts.map +1 -0
  71. package/dist/vnext/features.d.ts +3 -0
  72. package/dist/vnext/features.d.ts.map +1 -0
  73. package/dist/vnext/index.d.ts +18 -0
  74. package/dist/vnext/index.d.ts.map +1 -0
  75. package/dist/vnext/inference.d.ts +5 -0
  76. package/dist/vnext/inference.d.ts.map +1 -0
  77. package/dist/vnext/labeling.d.ts +13 -0
  78. package/dist/vnext/labeling.d.ts.map +1 -0
  79. package/dist/vnext/logging.d.ts +53 -0
  80. package/dist/vnext/logging.d.ts.map +1 -0
  81. package/dist/vnext/models.d.ts +44 -0
  82. package/dist/vnext/models.d.ts.map +1 -0
  83. package/dist/vnext/monitoring.d.ts +10 -0
  84. package/dist/vnext/monitoring.d.ts.map +1 -0
  85. package/dist/vnext/quality.d.ts +9 -0
  86. package/dist/vnext/quality.d.ts.map +1 -0
  87. package/dist/vnext/retrieval.d.ts +32 -0
  88. package/dist/vnext/retrieval.d.ts.map +1 -0
  89. package/dist/vnext/runtime.d.ts +44 -0
  90. package/dist/vnext/runtime.d.ts.map +1 -0
  91. package/dist/vnext/storage.d.ts +49 -0
  92. package/dist/vnext/storage.d.ts.map +1 -0
  93. package/dist/vnext/types.d.ts +103 -0
  94. package/dist/vnext/types.d.ts.map +1 -0
  95. package/docs/API.md +26 -0
  96. package/docs/ARCHITECTURE.md +48 -0
  97. package/docs/BROWSER_SDK_V2.md +18 -0
  98. package/docs/PAYMENT_FLOW_MAPPING_V2.md +20 -0
  99. package/docs/SECURITY_PRIVACY.md +21 -0
  100. package/docs/SERVER_SDK_V2.md +25 -0
  101. package/docs/TESTING.md +33 -0
  102. package/docs/UPGRADE_V2.md +21 -0
  103. package/docs/VNEXT_PHASE2.md +65 -0
  104. package/docs/rollout-checklist.md +54 -0
  105. package/docs/threshold-policy.md +50 -0
  106. package/package.json +60 -85
  107. package/dist/index.d.cts +0 -172
package/README.md CHANGED
@@ -1,252 +1,763 @@
1
- # @mack1ch/fingerprint-js
1
+ # @1payment/fingerprint
2
2
 
3
- Библиотека браузерного фингерпринта для antifraud-задач:
3
+ TypeScript SDK для device/browser fingerprinting в антифрод-сценариях.
4
4
 
5
- - собирает device/browser/network сигналы,
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 @mack1ch/fingerprint-js
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 { getFingerprint } from '@mack1ch/fingerprint-js';
31
+ import { createFingerprint } from "@1payment/fingerprint";
20
32
 
21
- const profile = await getFingerprint({
22
- required: {
23
- merchantId: 'merchant-1',
24
- userId: 'u-42',
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(profile.coreHash); // Устойчивый идентификатор
35
- console.log(profile.fullHash); // Полный отпечаток с шумными сигналами
36
- console.log(profile.meta.confidenceScore);
39
+ console.log(fp);
37
40
  ```
38
41
 
39
- ## Публичный API
42
+ Вы получаете:
40
43
 
41
- - `getFingerprint(options): Promise<FingerprintResult>`
42
- - полный сбор: sync + async сигналы.
43
- - `getFingerprintQuick(options): Promise<FingerprintResult>`
44
- - быстрый режим: только sync сигналы.
45
- - `compareFingerprints(current, reference): FingerprintComparison`
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
- ```ts
59
- type FingerprintOptions = {
60
- required: Record<string, string | number | boolean | null | undefined>;
61
- optional?: Record<string, string | number | boolean | null | undefined>;
62
- collect?: Partial<{
63
- network: boolean;
64
- webrtc: boolean;
65
- permissions: boolean;
66
- mediaDevices: boolean;
67
- battery: boolean;
68
- uaHints: boolean;
69
- keyboard: boolean;
70
- adBlock: boolean;
71
- screenDetails: boolean;
72
- storageEstimate: boolean;
73
- }>;
74
- performance?: {
75
- mode?: 'fast' | 'balanced' | 'accurate';
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
- type FingerprintResult = {
100
- coreHash: string; // устойчивый hash
101
- fullHash: string; // полный hash
102
- hash: string; // compatibility alias = fullHash
103
- hashVersion: 'fp_v1';
104
- components: FingerprintComponent[];
105
- requiredInputs: Record<string, string>;
106
- optionalInputs: Record<string, string>;
107
- deviceSignals: Record<string, string>;
108
- raw: UserDataItem[];
109
- meta: {
110
- mode: 'fast' | 'balanced' | 'accurate';
111
- digestAlgorithm: 'SHA-256' | 'MD5';
112
- elapsedMs: number;
113
- timedOutCollectors: string[];
114
- failedCollectors: Array<{ collector: string; reason: string }>;
115
- cacheHit: boolean;
116
- confidenceScore: number; // 0..1
117
- };
118
- };
119
- ```
120
-
121
- ## Интерпретация результатов
122
-
123
- - `coreHash` стабилен между сессиями (если стабильные сигналы и входы).
124
- - `fullHash` может меняться чаще, это нормально.
125
- - `confidenceScore`:
126
- - `>= 0.85`: высокая полнота сигнала,
127
- - `0.6..0.85`: средняя,
128
- - `< 0.6`: ограниченная полнота.
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 current = await getFingerprint({ required: { merchantId: 'm1', userId: 'u1' } });
140
- const reference = await getFingerprint({ required: { merchantId: 'm1', userId: 'u1' } });
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
- const oneToOne = compareFingerprints(current, reference);
143
- // oneToOne.verdict: same_device | likely_same | suspicious | different_device
125
+ ### Server (v2)
144
126
 
145
- const historyResult = compareFingerprintAgainstHistory(current, [reference]);
146
- // historyResult.best - лучший матч из истории
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
- `FingerprintComparison` содержит:
136
+ ---
137
+
138
+ ## 6. Namespace vNext (matching platform)
150
139
 
151
- - `similarityScore` (0..1),
152
- - `riskScore` (0..1),
153
- - `verdict`,
154
- - `reasons[]`,
155
- - `changedStableKeys[]`, `changedVolatileKeys[]`.
140
+ `vNext` — это production-контур для candidate retrieval + pairwise matching.
156
141
 
157
- ## Практика для antifraud
142
+ Что доступно:
158
143
 
159
- - Используйте `coreHash` как identity ключ.
160
- - Используйте `compareFingerprintAgainstHistory` для решений, а не только strict hash equality.
161
- - Храните историю последних профилей на пользователя/устройство.
162
- - Разделяйте действие по порогам:
163
- - низкий риск + высокая похожесть -> allow,
164
- - средние значения -> challenge,
165
- - высокий риск/низкая похожесть -> review/deny.
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
- ## Payment Form Integration (рекомендуемый сценарий)
154
+ ### Пример vNext runtime
168
155
 
169
156
  ```ts
170
- import {
171
- assessPaymentRisk,
172
- compareFingerprintAgainstHistory,
173
- compareFingerprints,
174
- startPaymentFingerprintSession,
175
- } from '@mack1ch/fingerprint-js';
176
-
177
- const fpSession = startPaymentFingerprintSession({
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
- ### Рекомендуемый backend pipeline для оплаты
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
- 1. На открытии формы принять `quick` профиль.
215
- 2. До финального authorize принять `full` профиль (он стартует в фоне в idle, чтобы не мешать UI).
216
- 3. Сравнить с историей профилей клиента:
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
- ### Минимальные поля для решения antifraud
484
+ ### Browser smoke (демо)
227
485
 
228
- - `coreHash`, `fullHash`
229
- - `meta.confidenceScore`
230
- - `comparison.best.similarityScore`
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
- - Библиотека не гарантирует 100% идентификацию в вебе.
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
- npm install
245
- npm run build:lib
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 pack
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`