@mack1ch/fingerprint-js 0.1.0 → 0.1.2
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 +228 -73
- package/dist/index.cjs +1001 -13
- package/dist/index.d.cts +82 -1
- package/dist/index.d.ts +82 -1
- package/dist/index.js +1016 -15
- package/package.json +52 -2
package/README.md
CHANGED
|
@@ -1,97 +1,252 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @mack1ch/fingerprint-js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Библиотека браузерного фингерпринта для antifraud-задач:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- собирает device/browser/network сигналы,
|
|
6
|
+
- строит устойчивый `coreHash` и детальный `fullHash`,
|
|
7
|
+
- считает метрики качества (`confidenceScore`),
|
|
8
|
+
- сравнивает профили (`similarityScore`, `riskScore`, `verdict`).
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
# Сборка и запуск (порт 3000)
|
|
9
|
-
docker compose up --build
|
|
10
|
+
## Установка
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
docker run -p 3000:80 1pay_risk
|
|
12
|
+
```bash
|
|
13
|
+
npm i @mack1ch/fingerprint-js
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
## Быстрый старт
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
```ts
|
|
19
|
+
import { getFingerprint } from '@mack1ch/fingerprint-js';
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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 },
|
|
32
|
+
});
|
|
24
33
|
|
|
25
|
-
|
|
34
|
+
console.log(profile.coreHash); // Устойчивый идентификатор
|
|
35
|
+
console.log(profile.fullHash); // Полный отпечаток с шумными сигналами
|
|
36
|
+
console.log(profile.meta.confidenceScore);
|
|
37
|
+
```
|
|
26
38
|
|
|
27
|
-
|
|
39
|
+
## Публичный API
|
|
40
|
+
|
|
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`
|
|
57
|
+
|
|
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
|
+
```
|
|
28
89
|
|
|
29
|
-
|
|
90
|
+
### Рекомендации по входам
|
|
91
|
+
|
|
92
|
+
- В `required` передавайте стабильные бизнес-идентификаторы (`merchantId`, `userId`).
|
|
93
|
+
- Не передавайте в `required` постоянно меняющиеся значения (`sessionId`, nonce), иначе устойчивость снизится.
|
|
94
|
+
- Если нужно использовать `sessionId`, лучше класть в `optional`.
|
|
95
|
+
|
|
96
|
+
## Формат результата `FingerprintResult`
|
|
97
|
+
|
|
98
|
+
```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
|
+
```
|
|
30
120
|
|
|
31
|
-
|
|
32
|
-
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
121
|
+
## Интерпретация результатов
|
|
33
122
|
|
|
34
|
-
|
|
123
|
+
- `coreHash` стабилен между сессиями (если стабильные сигналы и входы).
|
|
124
|
+
- `fullHash` может меняться чаще, это нормально.
|
|
125
|
+
- `confidenceScore`:
|
|
126
|
+
- `>= 0.85`: высокая полнота сигнала,
|
|
127
|
+
- `0.6..0.85`: средняя,
|
|
128
|
+
- `< 0.6`: ограниченная полнота.
|
|
35
129
|
|
|
36
|
-
|
|
130
|
+
## Сравнение профилей
|
|
37
131
|
|
|
38
|
-
|
|
132
|
+
```ts
|
|
133
|
+
import {
|
|
134
|
+
getFingerprint,
|
|
135
|
+
compareFingerprints,
|
|
136
|
+
compareFingerprintAgainstHistory,
|
|
137
|
+
} from '@mack1ch/fingerprint-js';
|
|
39
138
|
|
|
40
|
-
|
|
139
|
+
const current = await getFingerprint({ required: { merchantId: 'm1', userId: 'u1' } });
|
|
140
|
+
const reference = await getFingerprint({ required: { merchantId: 'm1', userId: 'u1' } });
|
|
41
141
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
globalIgnores(['dist']),
|
|
45
|
-
{
|
|
46
|
-
files: ['**/*.{ts,tsx}'],
|
|
47
|
-
extends: [
|
|
48
|
-
// Other configs...
|
|
142
|
+
const oneToOne = compareFingerprints(current, reference);
|
|
143
|
+
// oneToOne.verdict: same_device | likely_same | suspicious | different_device
|
|
49
144
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
tseslint.configs.strictTypeChecked,
|
|
54
|
-
// Optionally, add this for stylistic rules
|
|
55
|
-
tseslint.configs.stylisticTypeChecked,
|
|
145
|
+
const historyResult = compareFingerprintAgainstHistory(current, [reference]);
|
|
146
|
+
// historyResult.best - лучший матч из истории
|
|
147
|
+
```
|
|
56
148
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
149
|
+
`FingerprintComparison` содержит:
|
|
150
|
+
|
|
151
|
+
- `similarityScore` (0..1),
|
|
152
|
+
- `riskScore` (0..1),
|
|
153
|
+
- `verdict`,
|
|
154
|
+
- `reasons[]`,
|
|
155
|
+
- `changedStableKeys[]`, `changedVolatileKeys[]`.
|
|
156
|
+
|
|
157
|
+
## Практика для antifraud
|
|
158
|
+
|
|
159
|
+
- Используйте `coreHash` как identity ключ.
|
|
160
|
+
- Используйте `compareFingerprintAgainstHistory` для решений, а не только strict hash equality.
|
|
161
|
+
- Храните историю последних профилей на пользователя/устройство.
|
|
162
|
+
- Разделяйте действие по порогам:
|
|
163
|
+
- низкий риск + высокая похожесть -> allow,
|
|
164
|
+
- средние значения -> challenge,
|
|
165
|
+
- высокий риск/низкая похожесть -> review/deny.
|
|
166
|
+
|
|
167
|
+
## Payment Form Integration (рекомендуемый сценарий)
|
|
168
|
+
|
|
169
|
+
```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
|
+
});
|
|
65
207
|
},
|
|
66
208
|
},
|
|
67
|
-
|
|
209
|
+
});
|
|
68
210
|
```
|
|
69
211
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
212
|
+
### Рекомендуемый backend pipeline для оплаты
|
|
213
|
+
|
|
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` -> отклонение.
|
|
225
|
+
|
|
226
|
+
### Минимальные поля для решения antifraud
|
|
227
|
+
|
|
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`
|
|
234
|
+
|
|
235
|
+
## Ограничения
|
|
236
|
+
|
|
237
|
+
- Библиотека не гарантирует 100% идентификацию в вебе.
|
|
238
|
+
- Некоторые сигналы зависят от браузера, разрешений и сетевой среды.
|
|
239
|
+
- При частичной недоступности API часть сигналов деградирует, это отражается в `meta`.
|
|
240
|
+
|
|
241
|
+
## Разработка библиотеки
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
npm install
|
|
245
|
+
npm run build:lib
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Проверка упаковки:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
npm pack
|
|
97
252
|
```
|