@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/dist/npm/index.cjs
CHANGED
|
@@ -20,8 +20,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
CORE_SIGNALS: () => CORE_SIGNALS,
|
|
24
|
+
DEFAULT_FONT_LIST: () => DEFAULT_FONT_LIST,
|
|
25
|
+
DEFAULT_PERMISSION_NAMES: () => DEFAULT_PERMISSION_NAMES,
|
|
26
|
+
DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
|
|
27
|
+
DEFAULT_UA_HINTS: () => DEFAULT_UA_HINTS,
|
|
28
|
+
EXTENDED_SIGNALS: () => EXTENDED_SIGNALS,
|
|
23
29
|
LIBRARY_VERSION: () => LIBRARY_VERSION,
|
|
24
30
|
SCHEMA_VERSION: () => SCHEMA_VERSION,
|
|
31
|
+
SYNC_SIGNALS: () => SYNC_SIGNALS,
|
|
25
32
|
createFingerprintClient: () => createFingerprintClient
|
|
26
33
|
});
|
|
27
34
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -50,6 +57,35 @@ function hasCanvas(env) {
|
|
|
50
57
|
return Boolean(env.document?.createElement?.("canvas").getContext);
|
|
51
58
|
}
|
|
52
59
|
|
|
60
|
+
// src/signals/adblock.ts
|
|
61
|
+
var adblockCollector = {
|
|
62
|
+
name: "adblock",
|
|
63
|
+
tier: "extended",
|
|
64
|
+
supportsSync: false,
|
|
65
|
+
async collect(context) {
|
|
66
|
+
const started = now(context.env);
|
|
67
|
+
const doc = context.env.document;
|
|
68
|
+
if (!doc || !hasDom(context.env)) {
|
|
69
|
+
return { status: "unsupported", durationMs: now(context.env) - started };
|
|
70
|
+
}
|
|
71
|
+
return new Promise((resolve) => {
|
|
72
|
+
const bait = doc.createElement("div");
|
|
73
|
+
bait.innerHTML = "\xA0";
|
|
74
|
+
bait.className = "adsbox";
|
|
75
|
+
doc.body.appendChild(bait);
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
const adblockEnabled = bait.offsetHeight === 0;
|
|
78
|
+
doc.body.removeChild(bait);
|
|
79
|
+
resolve({
|
|
80
|
+
status: "ok",
|
|
81
|
+
durationMs: now(context.env) - started,
|
|
82
|
+
value: { adblockEnabled }
|
|
83
|
+
});
|
|
84
|
+
}, 100);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
53
89
|
// src/signals/audio.ts
|
|
54
90
|
function getOfflineAudioContext(env) {
|
|
55
91
|
const g = env;
|
|
@@ -168,7 +204,9 @@ var EXTENDED_SIGNALS = [
|
|
|
168
204
|
"paymentSupport",
|
|
169
205
|
"referrerInfo",
|
|
170
206
|
"navigationInfo",
|
|
171
|
-
"riskSignals"
|
|
207
|
+
"riskSignals",
|
|
208
|
+
"adblock",
|
|
209
|
+
"geolocation"
|
|
172
210
|
];
|
|
173
211
|
var SYNC_SIGNALS = [
|
|
174
212
|
"ua",
|
|
@@ -187,16 +225,76 @@ var SYNC_SIGNALS = [
|
|
|
187
225
|
];
|
|
188
226
|
var DEFAULT_TIMEOUT_MS = 1500;
|
|
189
227
|
var DEFAULT_FONT_LIST = [
|
|
228
|
+
"Andale Mono",
|
|
229
|
+
"American Typewriter",
|
|
230
|
+
"Apple Chancery",
|
|
190
231
|
"Arial",
|
|
191
|
-
"
|
|
192
|
-
"
|
|
193
|
-
"
|
|
232
|
+
"Arial Black",
|
|
233
|
+
"Arial Narrow",
|
|
234
|
+
"Arial Rounded MT Bold",
|
|
235
|
+
"Baskerville",
|
|
236
|
+
"Book Antiqua",
|
|
237
|
+
"Bookman Old Style",
|
|
238
|
+
"Bradley Hand ITC",
|
|
239
|
+
"Calibri",
|
|
240
|
+
"Cambria",
|
|
241
|
+
"Candara",
|
|
242
|
+
"Century",
|
|
243
|
+
"Century Gothic",
|
|
244
|
+
"Century Schoolbook",
|
|
245
|
+
"Chalkboard",
|
|
246
|
+
"Charcoal",
|
|
247
|
+
"Cochin",
|
|
248
|
+
"Comic Sans MS",
|
|
249
|
+
"Consolas",
|
|
250
|
+
"Constantia",
|
|
251
|
+
"Corbel",
|
|
252
|
+
"Courier",
|
|
194
253
|
"Courier New",
|
|
254
|
+
"Franklin Gothic Medium",
|
|
255
|
+
"Futura",
|
|
256
|
+
"Garamond",
|
|
257
|
+
"Geneva",
|
|
258
|
+
"Georgia",
|
|
259
|
+
"Gill Sans",
|
|
260
|
+
"Gill Sans MT",
|
|
261
|
+
"Helvetica",
|
|
262
|
+
"Hoefler Text",
|
|
263
|
+
"Impact",
|
|
264
|
+
"Lucida Bright",
|
|
265
|
+
"Lucida Console",
|
|
266
|
+
"Lucida Grande",
|
|
267
|
+
"Lucida Sans",
|
|
268
|
+
"MS Gothic",
|
|
269
|
+
"MS PGothic",
|
|
270
|
+
"MS Serif",
|
|
271
|
+
"MS UI Gothic",
|
|
272
|
+
"Meiryo",
|
|
273
|
+
"Meiryo UI",
|
|
274
|
+
"Monaco",
|
|
275
|
+
"Monotype Corsiva",
|
|
276
|
+
"New York",
|
|
277
|
+
"Optima",
|
|
278
|
+
"Palatino",
|
|
279
|
+
"Palatino Linotype",
|
|
280
|
+
"Papyrus",
|
|
281
|
+
"Plantagenet Cherokee",
|
|
282
|
+
"Segoe Print",
|
|
283
|
+
"Segoe Script",
|
|
284
|
+
"Segoe UI",
|
|
285
|
+
"Segoe UI Light",
|
|
286
|
+
"Segoe UI Semibold",
|
|
287
|
+
"Segoe UI Symbol",
|
|
288
|
+
"Symbol",
|
|
289
|
+
"Tahoma",
|
|
290
|
+
"Times",
|
|
291
|
+
"Times New Roman",
|
|
195
292
|
"Trebuchet MS",
|
|
196
293
|
"Verdana",
|
|
197
|
-
"
|
|
198
|
-
"
|
|
199
|
-
"
|
|
294
|
+
"Wingdings",
|
|
295
|
+
"Wingdings 2",
|
|
296
|
+
"Wingdings 3",
|
|
297
|
+
"Zapfino"
|
|
200
298
|
];
|
|
201
299
|
var DEFAULT_PERMISSION_NAMES = ["geolocation", "notifications", "camera", "microphone"];
|
|
202
300
|
var DEFAULT_UA_HINTS = [
|
|
@@ -324,6 +422,44 @@ var frameInfoCollector = {
|
|
|
324
422
|
}
|
|
325
423
|
};
|
|
326
424
|
|
|
425
|
+
// src/signals/geolocation.ts
|
|
426
|
+
var geolocationCollector = {
|
|
427
|
+
name: "geolocation",
|
|
428
|
+
tier: "extended",
|
|
429
|
+
supportsSync: false,
|
|
430
|
+
async collect(context) {
|
|
431
|
+
const started = now(context.env);
|
|
432
|
+
const nav = context.env.navigator;
|
|
433
|
+
if (!nav?.geolocation) {
|
|
434
|
+
return { status: "unsupported", durationMs: now(context.env) - started };
|
|
435
|
+
}
|
|
436
|
+
return new Promise((resolve) => {
|
|
437
|
+
nav.geolocation.getCurrentPosition(
|
|
438
|
+
(pos) => {
|
|
439
|
+
resolve({
|
|
440
|
+
status: "ok",
|
|
441
|
+
durationMs: now(context.env) - started,
|
|
442
|
+
value: {
|
|
443
|
+
latitude: pos.coords.latitude,
|
|
444
|
+
longitude: pos.coords.longitude,
|
|
445
|
+
accuracy: pos.coords.accuracy
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
},
|
|
449
|
+
(err) => {
|
|
450
|
+
resolve({
|
|
451
|
+
// PERMISSION_DENIED = 1
|
|
452
|
+
status: err.code === 1 ? "blocked" : "error",
|
|
453
|
+
durationMs: now(context.env) - started,
|
|
454
|
+
error: err.message
|
|
455
|
+
});
|
|
456
|
+
},
|
|
457
|
+
{ timeout: context.options.timeoutMs, maximumAge: 6e4 }
|
|
458
|
+
);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
327
463
|
// src/signals/hardware.ts
|
|
328
464
|
var hardwareCollector = {
|
|
329
465
|
name: "hardware",
|
|
@@ -339,6 +475,17 @@ var hardwareCollector = {
|
|
|
339
475
|
if (!nav) {
|
|
340
476
|
return { status: "unsupported", durationMs: now(context.env) - started };
|
|
341
477
|
}
|
|
478
|
+
const doc = context.env.document;
|
|
479
|
+
const g = context.env.global;
|
|
480
|
+
let touchEventCreationSuccessful = false;
|
|
481
|
+
try {
|
|
482
|
+
if (doc) {
|
|
483
|
+
doc.createEvent("TouchEvent");
|
|
484
|
+
touchEventCreationSuccessful = true;
|
|
485
|
+
}
|
|
486
|
+
} catch {
|
|
487
|
+
}
|
|
488
|
+
const onTouchStartAvailable = g != null && "ontouchstart" in g;
|
|
342
489
|
return {
|
|
343
490
|
status: "ok",
|
|
344
491
|
durationMs: now(context.env) - started,
|
|
@@ -346,7 +493,9 @@ var hardwareCollector = {
|
|
|
346
493
|
hardwareConcurrency: nav.hardwareConcurrency,
|
|
347
494
|
deviceMemory: nav.deviceMemory,
|
|
348
495
|
platform: nav.platform,
|
|
349
|
-
maxTouchPoints: nav.maxTouchPoints
|
|
496
|
+
maxTouchPoints: nav.maxTouchPoints,
|
|
497
|
+
touchEventCreationSuccessful,
|
|
498
|
+
onTouchStartAvailable
|
|
350
499
|
}
|
|
351
500
|
};
|
|
352
501
|
}
|
|
@@ -368,28 +517,36 @@ var localeCollector = {
|
|
|
368
517
|
if (!nav || !intl) {
|
|
369
518
|
return { status: "unsupported", durationMs: now(context.env) - started };
|
|
370
519
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
520
|
+
try {
|
|
521
|
+
const formatter = new intl.DateTimeFormat();
|
|
522
|
+
const options = formatter.resolvedOptions();
|
|
523
|
+
const numberFormatter = new intl.NumberFormat(nav.language);
|
|
524
|
+
const relativeTimeFormatter = typeof intl.RelativeTimeFormat === "function" ? new intl.RelativeTimeFormat(nav.language, { numeric: "auto" }) : void 0;
|
|
525
|
+
const sampleDate = new Date(Date.UTC(2024, 0, 2, 3, 4, 5));
|
|
526
|
+
return {
|
|
527
|
+
status: "ok",
|
|
528
|
+
durationMs: now(context.env) - started,
|
|
529
|
+
value: {
|
|
530
|
+
language: nav.language,
|
|
531
|
+
languages: nav.languages,
|
|
532
|
+
locale: options.locale,
|
|
533
|
+
calendar: options.calendar,
|
|
534
|
+
numberingSystem: options.numberingSystem,
|
|
535
|
+
timeZone: options.timeZone,
|
|
536
|
+
hourCycle: options.hourCycle,
|
|
537
|
+
timeZoneOffsetMinutes: sampleDate.getTimezoneOffset(),
|
|
538
|
+
formattedNumber: numberFormatter.format(123456.789),
|
|
539
|
+
formattedDate: formatter.format(sampleDate),
|
|
540
|
+
formattedRelativeDay: relativeTimeFormatter?.format(-1, "day")
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
} catch (error) {
|
|
544
|
+
return {
|
|
545
|
+
status: "error",
|
|
546
|
+
durationMs: now(context.env) - started,
|
|
547
|
+
error: error instanceof Error ? error.message : String(error)
|
|
548
|
+
};
|
|
549
|
+
}
|
|
393
550
|
}
|
|
394
551
|
};
|
|
395
552
|
|
|
@@ -700,6 +857,7 @@ var screenCollector = {
|
|
|
700
857
|
if (!screen2) {
|
|
701
858
|
return { status: "unsupported", durationMs: now(context.env) - started };
|
|
702
859
|
}
|
|
860
|
+
const orientation = screen2.orientation;
|
|
703
861
|
return {
|
|
704
862
|
status: "ok",
|
|
705
863
|
durationMs: now(context.env) - started,
|
|
@@ -710,10 +868,24 @@ var screenCollector = {
|
|
|
710
868
|
availHeight: screen2.availHeight,
|
|
711
869
|
colorDepth: screen2.colorDepth,
|
|
712
870
|
pixelDepth: screen2.pixelDepth,
|
|
713
|
-
|
|
714
|
-
orientationAngle:
|
|
871
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- orientation is optional at runtime despite the Screen intersection
|
|
872
|
+
...orientation ? { orientationType: orientation.type, orientationAngle: orientation.angle } : {},
|
|
715
873
|
maxTouchPoints: nav?.maxTouchPoints ?? 0,
|
|
874
|
+
resolution: `${String(screen2.width)}x${String(screen2.height)}`,
|
|
875
|
+
ratio: screen2.height > 0 ? Math.round(screen2.width / screen2.height * 1e10) / 1e10 : 0,
|
|
716
876
|
devicePixelRatio: typeof runtime.devicePixelRatio === "number" ? runtime.devicePixelRatio : void 0,
|
|
877
|
+
// Fingerprint2 / Cardinal Commerce has_lied_resolution: availWidth never exceeds width on a real device
|
|
878
|
+
fakedResolution: screen2.availWidth > screen2.width || screen2.availHeight > screen2.height,
|
|
879
|
+
// Cardinal Commerce CCAScreenSize — outerWidth/outerHeight bucket code
|
|
880
|
+
ccaScreenSize: (() => {
|
|
881
|
+
const w = typeof runtime.outerWidth === "number" ? runtime.outerWidth : 0;
|
|
882
|
+
const h = typeof runtime.outerHeight === "number" ? runtime.outerHeight : 0;
|
|
883
|
+
if (w < 390) return "01";
|
|
884
|
+
if (w < 500) return "02";
|
|
885
|
+
if (w < 600) return "03";
|
|
886
|
+
if (h < 600) return "04";
|
|
887
|
+
return "02";
|
|
888
|
+
})(),
|
|
717
889
|
colorGamutP3: getMediaPreference(runtime, "(color-gamut: p3)"),
|
|
718
890
|
prefersReducedMotion: getMediaPreference(runtime, "(prefers-reduced-motion: reduce)"),
|
|
719
891
|
prefersContrastMore: getMediaPreference(runtime, "(prefers-contrast: more)"),
|
|
@@ -780,6 +952,16 @@ var uaCollector = {
|
|
|
780
952
|
if (!nav) {
|
|
781
953
|
return { status: "unsupported", durationMs: now(context.env) - started };
|
|
782
954
|
}
|
|
955
|
+
let plugins = [];
|
|
956
|
+
try {
|
|
957
|
+
plugins = Array.from(nav.plugins).map((p) => {
|
|
958
|
+
const mimes = Array.from({ length: p.length }, (_, i) => p.item(i)).filter((m) => m !== null).map((m) => `${m.type}~${m.suffixes}`).join(",");
|
|
959
|
+
return `${p.name}::${p.description}::${mimes}`;
|
|
960
|
+
});
|
|
961
|
+
} catch {
|
|
962
|
+
}
|
|
963
|
+
const rawDnt = nav.doNotTrack ?? context.env.global.doNotTrack ?? null;
|
|
964
|
+
const doNotTrack = rawDnt === "1" ? "enabled" : rawDnt === "0" ? "disabled" : rawDnt ?? "not_set";
|
|
783
965
|
return {
|
|
784
966
|
status: "ok",
|
|
785
967
|
durationMs: now(context.env) - started,
|
|
@@ -791,7 +973,9 @@ var uaCollector = {
|
|
|
791
973
|
webdriver: nav.webdriver,
|
|
792
974
|
maxTouchPoints: nav.maxTouchPoints,
|
|
793
975
|
cookieEnabled: nav.cookieEnabled,
|
|
794
|
-
vendorSub: nav.vendorSub
|
|
976
|
+
vendorSub: nav.vendorSub,
|
|
977
|
+
doNotTrack,
|
|
978
|
+
plugins
|
|
795
979
|
}
|
|
796
980
|
};
|
|
797
981
|
}
|
|
@@ -948,7 +1132,9 @@ var signalCollectors = [
|
|
|
948
1132
|
paymentSupportCollector,
|
|
949
1133
|
referrerInfoCollector,
|
|
950
1134
|
navigationInfoCollector,
|
|
951
|
-
riskSignalsCollector
|
|
1135
|
+
riskSignalsCollector,
|
|
1136
|
+
adblockCollector,
|
|
1137
|
+
geolocationCollector
|
|
952
1138
|
];
|
|
953
1139
|
var collectorMap = new Map(signalCollectors.map((collector) => [collector.name, collector]));
|
|
954
1140
|
|
|
@@ -962,6 +1148,13 @@ function asString(value) {
|
|
|
962
1148
|
function asNumber(value) {
|
|
963
1149
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
964
1150
|
}
|
|
1151
|
+
function canonicalizeLegacyPlatform(legacyPlatform) {
|
|
1152
|
+
if (legacyPlatform.startsWith("win")) return "windows";
|
|
1153
|
+
if (legacyPlatform.startsWith("mac")) return "macos";
|
|
1154
|
+
if (/cros|chrome/.test(legacyPlatform)) return "chrome os";
|
|
1155
|
+
if (/linux|x11/.test(legacyPlatform)) return "linux";
|
|
1156
|
+
return legacyPlatform;
|
|
1157
|
+
}
|
|
965
1158
|
function analyseSignals(signals) {
|
|
966
1159
|
const anomalies = [];
|
|
967
1160
|
const automationHints = [];
|
|
@@ -983,8 +1176,12 @@ function analyseSignals(signals) {
|
|
|
983
1176
|
if (userAgent.includes("headless")) {
|
|
984
1177
|
automationHints.push("headless_user_agent");
|
|
985
1178
|
}
|
|
986
|
-
if (platform && hintPlatform
|
|
987
|
-
|
|
1179
|
+
if (platform && hintPlatform) {
|
|
1180
|
+
const canonicalLegacy = canonicalizeLegacyPlatform(platform);
|
|
1181
|
+
const linuxFamilyOk = canonicalLegacy === "linux" && (hintPlatform === "linux" || hintPlatform === "android" || hintPlatform === "chrome os");
|
|
1182
|
+
if (!linuxFamilyOk && canonicalLegacy !== hintPlatform) {
|
|
1183
|
+
anomalies.push("ua_platform_mismatch");
|
|
1184
|
+
}
|
|
988
1185
|
}
|
|
989
1186
|
if (userAgent.includes("iphone") && maxTouchPoints === 0) {
|
|
990
1187
|
anomalies.push("touch_claim_without_touch_points");
|
|
@@ -1004,10 +1201,16 @@ function analyseSignals(signals) {
|
|
|
1004
1201
|
const anomalyPenalty = anomalies.length * 0.12;
|
|
1005
1202
|
const automationPenalty = automationHints.length * 0.18;
|
|
1006
1203
|
const score = Math.max(0, Math.min(1, Number((1 - anomalyPenalty - automationPenalty).toFixed(3))));
|
|
1204
|
+
const fakedOS = anomalies.includes("ua_platform_mismatch");
|
|
1205
|
+
const riskVal = asRecord(signals.riskSignals?.value);
|
|
1206
|
+
const headlessHints = Array.isArray(riskVal?.headlessHints) ? riskVal.headlessHints : [];
|
|
1207
|
+
const fakedBrowser = headlessHints.includes("missing-chrome-object");
|
|
1007
1208
|
return {
|
|
1008
1209
|
score,
|
|
1009
1210
|
anomalies,
|
|
1010
|
-
automationHints
|
|
1211
|
+
automationHints,
|
|
1212
|
+
fakedOS,
|
|
1213
|
+
fakedBrowser
|
|
1011
1214
|
};
|
|
1012
1215
|
}
|
|
1013
1216
|
|
|
@@ -1077,7 +1280,9 @@ var SIGNAL_WEIGHTS = {
|
|
|
1077
1280
|
paymentSupport: 0.2,
|
|
1078
1281
|
referrerInfo: 0.3,
|
|
1079
1282
|
navigationInfo: 0.4,
|
|
1080
|
-
riskSignals: 0.7
|
|
1283
|
+
riskSignals: 0.7,
|
|
1284
|
+
adblock: 0.3,
|
|
1285
|
+
geolocation: 0.5
|
|
1081
1286
|
};
|
|
1082
1287
|
function computeConfidence(signals, antiSpoof) {
|
|
1083
1288
|
let earned = 0;
|
|
@@ -1299,28 +1504,9 @@ function resolveSignalSet(options, mode = "async") {
|
|
|
1299
1504
|
return included.filter((signal) => !excluded.has(signal));
|
|
1300
1505
|
}
|
|
1301
1506
|
|
|
1302
|
-
// src/core/upload.ts
|
|
1303
|
-
async function uploadFingerprint(env, result, options) {
|
|
1304
|
-
const fetchImpl = options.endpoint && env.fetch;
|
|
1305
|
-
if (!fetchImpl) {
|
|
1306
|
-
throw new Error("Fetch is not available in this runtime.");
|
|
1307
|
-
}
|
|
1308
|
-
return fetchImpl(options.endpoint, {
|
|
1309
|
-
method: "POST",
|
|
1310
|
-
headers: {
|
|
1311
|
-
"content-type": "application/json",
|
|
1312
|
-
...options.headers
|
|
1313
|
-
},
|
|
1314
|
-
body: JSON.stringify({
|
|
1315
|
-
fingerprint: result,
|
|
1316
|
-
...options.bodyExtras
|
|
1317
|
-
})
|
|
1318
|
-
});
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
1507
|
// src/version.ts
|
|
1322
|
-
var LIBRARY_VERSION = "0.
|
|
1323
|
-
var SCHEMA_VERSION =
|
|
1508
|
+
var LIBRARY_VERSION = true ? "0.2.0" : "0.0.0-dev";
|
|
1509
|
+
var SCHEMA_VERSION = 2;
|
|
1324
1510
|
|
|
1325
1511
|
// src/client.ts
|
|
1326
1512
|
function toTimedOutResult(durationMs) {
|
|
@@ -1330,6 +1516,18 @@ function toTimedOutResult(durationMs) {
|
|
|
1330
1516
|
error: "Signal collection timed out."
|
|
1331
1517
|
};
|
|
1332
1518
|
}
|
|
1519
|
+
function toErrorResult(durationMs, error) {
|
|
1520
|
+
return {
|
|
1521
|
+
status: "error",
|
|
1522
|
+
durationMs,
|
|
1523
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
function debugLog(resolved, message) {
|
|
1527
|
+
if (resolved.debug) {
|
|
1528
|
+
console.debug(`[zp-devicefp] ${message}`);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1333
1531
|
async function runCollectorWithTimeout(collector, env, options, warn) {
|
|
1334
1532
|
const started = env.performance?.now?.() ?? Date.now();
|
|
1335
1533
|
if (options.abortSignal?.aborted) {
|
|
@@ -1341,31 +1539,36 @@ async function runCollectorWithTimeout(collector, env, options, warn) {
|
|
|
1341
1539
|
}
|
|
1342
1540
|
let clearTimeoutHandle = () => {
|
|
1343
1541
|
};
|
|
1542
|
+
let removeAbortListener = () => {
|
|
1543
|
+
};
|
|
1344
1544
|
const timeoutPromise = new Promise((resolve) => {
|
|
1345
1545
|
const timeout = setTimeout(() => {
|
|
1346
1546
|
resolve(toTimedOutResult((env.performance?.now?.() ?? Date.now()) - started));
|
|
1347
1547
|
}, options.timeoutMs);
|
|
1348
1548
|
clearTimeoutHandle = () => clearTimeout(timeout);
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
(
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
{ once: true }
|
|
1360
|
-
);
|
|
1361
|
-
});
|
|
1362
|
-
const resultPromise = collector.collect({
|
|
1363
|
-
env,
|
|
1364
|
-
options,
|
|
1365
|
-
warn
|
|
1549
|
+
const onAbort = () => {
|
|
1550
|
+
clearTimeoutHandle();
|
|
1551
|
+
resolve({
|
|
1552
|
+
status: "error",
|
|
1553
|
+
durationMs: (env.performance?.now?.() ?? Date.now()) - started,
|
|
1554
|
+
error: "Collection aborted."
|
|
1555
|
+
});
|
|
1556
|
+
};
|
|
1557
|
+
options.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1558
|
+
removeAbortListener = () => options.abortSignal?.removeEventListener("abort", onAbort);
|
|
1366
1559
|
});
|
|
1560
|
+
const resultPromise = (async () => {
|
|
1561
|
+
try {
|
|
1562
|
+
return await collector.collect({ env, options, warn });
|
|
1563
|
+
} catch (error) {
|
|
1564
|
+
const result = toErrorResult((env.performance?.now?.() ?? Date.now()) - started, error);
|
|
1565
|
+
warn(`Signal "${collector.name}" threw: ${result.error ?? "unknown error"}`, collector.name);
|
|
1566
|
+
return result;
|
|
1567
|
+
}
|
|
1568
|
+
})();
|
|
1367
1569
|
return Promise.race([resultPromise, timeoutPromise]).finally(() => {
|
|
1368
1570
|
clearTimeoutHandle();
|
|
1571
|
+
removeAbortListener();
|
|
1369
1572
|
});
|
|
1370
1573
|
}
|
|
1371
1574
|
function buildResult(signals, warnings, requestedSignals, elapsedMs) {
|
|
@@ -1427,6 +1630,7 @@ async function createFingerprintClient(options) {
|
|
|
1427
1630
|
}
|
|
1428
1631
|
const result = await runCollectorWithTimeout(collector, env, resolved, warn);
|
|
1429
1632
|
collected[name] = result;
|
|
1633
|
+
debugLog(resolved, `${name}: ${result.status} (${String(result.durationMs)}ms)`);
|
|
1430
1634
|
completed += 1;
|
|
1431
1635
|
emitter.emit("progress", {
|
|
1432
1636
|
completed,
|
|
@@ -1457,22 +1661,35 @@ async function createFingerprintClient(options) {
|
|
|
1457
1661
|
warn(`Signal "${name}" is not available in synchronous mode.`, name);
|
|
1458
1662
|
continue;
|
|
1459
1663
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1664
|
+
const signalStarted = env.performance?.now?.() ?? Date.now();
|
|
1665
|
+
let result;
|
|
1666
|
+
try {
|
|
1667
|
+
result = collector.collectSync({
|
|
1668
|
+
env,
|
|
1669
|
+
options: resolved,
|
|
1670
|
+
warn
|
|
1671
|
+
});
|
|
1672
|
+
} catch (error) {
|
|
1673
|
+
result = toErrorResult((env.performance?.now?.() ?? Date.now()) - signalStarted, error);
|
|
1674
|
+
warn(`Signal "${name}" threw: ${result.error ?? "unknown error"}`, name);
|
|
1675
|
+
}
|
|
1676
|
+
collected[name] = result;
|
|
1677
|
+
debugLog(resolved, `${name}: ${result.status} (${String(result.durationMs)}ms)`);
|
|
1465
1678
|
}
|
|
1466
1679
|
return buildResult(collected, warnings, signalNames, (env.performance?.now?.() ?? Date.now()) - started);
|
|
1467
|
-
},
|
|
1468
|
-
upload(result, uploadOptions) {
|
|
1469
|
-
return uploadFingerprint(getBrowserEnv(), result, uploadOptions);
|
|
1470
1680
|
}
|
|
1471
1681
|
};
|
|
1472
1682
|
}
|
|
1473
1683
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1474
1684
|
0 && (module.exports = {
|
|
1685
|
+
CORE_SIGNALS,
|
|
1686
|
+
DEFAULT_FONT_LIST,
|
|
1687
|
+
DEFAULT_PERMISSION_NAMES,
|
|
1688
|
+
DEFAULT_TIMEOUT_MS,
|
|
1689
|
+
DEFAULT_UA_HINTS,
|
|
1690
|
+
EXTENDED_SIGNALS,
|
|
1475
1691
|
LIBRARY_VERSION,
|
|
1476
1692
|
SCHEMA_VERSION,
|
|
1693
|
+
SYNC_SIGNALS,
|
|
1477
1694
|
createFingerprintClient
|
|
1478
1695
|
});
|
package/dist/npm/index.d.ts
CHANGED
|
@@ -17,7 +17,9 @@ export type SignalName =
|
|
|
17
17
|
| 'paymentSupport'
|
|
18
18
|
| 'referrerInfo'
|
|
19
19
|
| 'navigationInfo'
|
|
20
|
-
| 'riskSignals'
|
|
20
|
+
| 'riskSignals'
|
|
21
|
+
| 'adblock'
|
|
22
|
+
| 'geolocation';
|
|
21
23
|
|
|
22
24
|
export type SignalStatus = 'ok' | 'unsupported' | 'blocked' | 'timeout' | 'error';
|
|
23
25
|
|
|
@@ -32,6 +34,8 @@ export interface AntiSpoofReport {
|
|
|
32
34
|
score: number;
|
|
33
35
|
anomalies: string[];
|
|
34
36
|
automationHints: string[];
|
|
37
|
+
fakedOS: boolean;
|
|
38
|
+
fakedBrowser: boolean;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export interface FingerprintResult {
|
|
@@ -61,12 +65,6 @@ export interface CollectorDiagnostics {
|
|
|
61
65
|
elapsedMs: number;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
export interface UploadOptions {
|
|
65
|
-
endpoint: string;
|
|
66
|
-
headers?: Record<string, string>;
|
|
67
|
-
bodyExtras?: Record<string, unknown>;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
68
|
export interface ProgressEvent {
|
|
71
69
|
completed: number;
|
|
72
70
|
total: number;
|
|
@@ -92,10 +90,25 @@ export interface EventPayloadMap {
|
|
|
92
90
|
export interface FingerprintClient {
|
|
93
91
|
collect(options?: CollectOptions): Promise<FingerprintResult>;
|
|
94
92
|
collectSync(options?: Omit<CollectOptions, 'timeoutMs' | 'abortSignal'>): FingerprintResult;
|
|
95
|
-
upload(result: FingerprintResult, options: UploadOptions): Promise<Response>;
|
|
96
93
|
on<K extends keyof EventPayloadMap>(event: K, cb: (payload: EventPayloadMap[K]) => void): () => void;
|
|
97
94
|
}
|
|
98
95
|
|
|
99
96
|
export declare const LIBRARY_VERSION: string;
|
|
100
97
|
export declare const SCHEMA_VERSION: number;
|
|
98
|
+
export declare const CORE_SIGNALS: SignalName[];
|
|
99
|
+
export declare const EXTENDED_SIGNALS: SignalName[];
|
|
100
|
+
export declare const SYNC_SIGNALS: SignalName[];
|
|
101
|
+
export declare const DEFAULT_TIMEOUT_MS: number;
|
|
102
|
+
export declare const DEFAULT_FONT_LIST: string[];
|
|
103
|
+
export declare const DEFAULT_PERMISSION_NAMES: readonly ['geolocation', 'notifications', 'camera', 'microphone'];
|
|
104
|
+
export declare const DEFAULT_UA_HINTS: readonly [
|
|
105
|
+
'architecture',
|
|
106
|
+
'bitness',
|
|
107
|
+
'formFactors',
|
|
108
|
+
'fullVersionList',
|
|
109
|
+
'model',
|
|
110
|
+
'platform',
|
|
111
|
+
'platformVersion',
|
|
112
|
+
'wow64',
|
|
113
|
+
];
|
|
101
114
|
export declare function createFingerprintClient(options?: CollectOptions): Promise<FingerprintClient>;
|