@anonympins/fingerprint 0.7.3 → 0.7.5
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 +41 -0
- package/README.md +1 -1
- package/package.json +109 -107
- package/public/fingerprint-anti-bot.zip +0 -0
- package/src/js/dynamic-wasm.js +218 -218
- package/src/js/fingerprint.client.js +329 -88
- package/src/js/fingerprint.client.obfuscated.js +1 -0
- package/src/js/fingerprint.js +486 -231
- package/src/js/fingerprint.worker.js +81 -0
- package/src/js/library.js +6 -2
- package/src/js/pow.solver.js +2 -3
- package/src/js/problem-manager.js +113 -113
- package/src/js/tests/dynamic-wasm.test.js +77 -77
- package/src/js/tests/fingerprint.client.init.test.js +108 -0
- package/src/js/tests/fingerprint.test.js +153 -3
- package/src/js/tests/threatIntel.test.js +40 -1
- package/src/php/Challenge/ChallengeUtils.php +25 -16
- package/src/php/Config/SecurityProfiles.php +5 -0
- package/src/php/FingerprintEngine.php +186 -37
- package/src/php/Tests/ChallengeUtilsTest.php +469 -453
- package/src/php/Tests/RequestUtilsTest.php +1 -1
- package/src/php/Tests/ThreatIntelTest.php +34 -0
- package/src/php/Utils/RequestUtils.php +257 -127
- package/src/php/WordPress/fingerprint-anti-bot.php +1 -1
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +1 -1
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +1 -1
- package/src/php/WordPress/readme.txt +1 -1
|
@@ -159,9 +159,14 @@ const ClientLibrary = {
|
|
|
159
159
|
* @param {object} [detail={}] - The data to include in the event's detail property.
|
|
160
160
|
*/
|
|
161
161
|
_dispatchEvent(eventName, detail = {}) {
|
|
162
|
-
if (typeof window === 'undefined'
|
|
163
|
-
const
|
|
164
|
-
|
|
162
|
+
if (typeof window === 'undefined') return;
|
|
163
|
+
const CustomEventCtor = window.CustomEvent || (typeof CustomEvent !== 'undefined' ? CustomEvent : null);
|
|
164
|
+
if (!CustomEventCtor) return;
|
|
165
|
+
try {
|
|
166
|
+
const event = new CustomEventCtor(`fingerprint:${eventName}`, { detail });
|
|
167
|
+
window.dispatchEvent(event);
|
|
168
|
+
} catch (e) {
|
|
169
|
+
}
|
|
165
170
|
},
|
|
166
171
|
|
|
167
172
|
/**
|
|
@@ -190,10 +195,20 @@ const ClientLibrary = {
|
|
|
190
195
|
const canvas2d = typeof CanvasRenderingContext2D !== 'undefined' ? CanvasRenderingContext2D : win.CanvasRenderingContext2D;
|
|
191
196
|
const webgl = typeof WebGLRenderingContext !== 'undefined' ? WebGLRenderingContext : win.WebGLRenderingContext;
|
|
192
197
|
|
|
198
|
+
const isFetchTampered = () => {
|
|
199
|
+
if (ClientLibrary._isFetchPatched) {
|
|
200
|
+
if (ClientLibrary._originalFetch) {
|
|
201
|
+
return !Function.prototype.toString.call(ClientLibrary._originalFetch).includes('[native code]');
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return checkNative(win, 'fetch');
|
|
206
|
+
};
|
|
207
|
+
|
|
193
208
|
return checkNative(htmlCanvas?.prototype, 'toDataURL') ||
|
|
194
209
|
checkNative(canvas2d?.prototype, 'getImageData') ||
|
|
195
210
|
checkNative(webgl?.prototype, 'getParameter') ||
|
|
196
|
-
|
|
211
|
+
isFetchTampered();
|
|
197
212
|
},
|
|
198
213
|
|
|
199
214
|
/**
|
|
@@ -397,54 +412,80 @@ const ClientLibrary = {
|
|
|
397
412
|
},
|
|
398
413
|
|
|
399
414
|
/**
|
|
400
|
-
* Injects invisible phantom interactive elements to trap bots (focus/hover).
|
|
415
|
+
* Injects invisible phantom interactive elements and forms within a closed Shadow DOM container to trap bots (focus/hover/click).
|
|
401
416
|
*/
|
|
402
417
|
injectPhantomTraps() {
|
|
403
|
-
if (typeof document === 'undefined') return;
|
|
418
|
+
if (typeof document === 'undefined' || !document.body) return;
|
|
419
|
+
|
|
420
|
+
const host = document.createElement('div');
|
|
421
|
+
host.setAttribute('aria-hidden', 'true');
|
|
422
|
+
host.style.position = 'absolute';
|
|
423
|
+
host.style.left = '-9999px';
|
|
424
|
+
host.style.top = '-9999px';
|
|
425
|
+
host.style.width = '0';
|
|
426
|
+
host.style.height = '0';
|
|
427
|
+
host.style.overflow = 'hidden';
|
|
428
|
+
|
|
429
|
+
const shadow = typeof host.attachShadow === 'function'
|
|
430
|
+
? host.attachShadow({ mode: 'closed' })
|
|
431
|
+
: host;
|
|
432
|
+
|
|
433
|
+
const style = document.createElement('style');
|
|
434
|
+
style.textContent = `
|
|
435
|
+
:host {
|
|
436
|
+
position: absolute;
|
|
437
|
+
left: -9999px;
|
|
438
|
+
top: -9999px;
|
|
439
|
+
width: 0;
|
|
440
|
+
height: 0;
|
|
441
|
+
overflow: hidden;
|
|
442
|
+
}
|
|
443
|
+
a, input, button, form {
|
|
444
|
+
opacity: 0.001;
|
|
445
|
+
pointer-events: auto;
|
|
446
|
+
}
|
|
447
|
+
`;
|
|
448
|
+
shadow.appendChild(style);
|
|
404
449
|
|
|
405
|
-
|
|
450
|
+
const triggerTrap = () => {
|
|
451
|
+
this.onHoneypotTrigger();
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// 1. Phantom interactive link
|
|
406
455
|
const phantom = document.createElement('a');
|
|
407
456
|
phantom.href = '#';
|
|
408
457
|
// Random deceptive name to attract automated link/form parsers
|
|
409
458
|
const phantomNames = ['sys-session-recovery', 'auth-token-refresh', 'debug-console-login', 'admin-portal-access', 'security-bypass-bypass', 'recovery-key-session', 'api-key-test', 'client-secrets-access'];
|
|
410
459
|
phantom.id = phantomNames[Math.floor(secureRandom() * phantomNames.length)] + '-' + genRandStr(6);
|
|
411
460
|
phantom.className = genRandStr(8);
|
|
412
|
-
phantom.tabIndex = 0; // In natural tab flow
|
|
413
|
-
phantom.setAttribute('aria-hidden', 'true'); // Hidden from legitimate screen readers
|
|
414
|
-
|
|
415
|
-
// Invisible yet interactive style (1px x 1px, nearly transparent)
|
|
416
|
-
phantom.style.position = 'fixed';
|
|
417
|
-
phantom.style.top = '1px';
|
|
418
|
-
phantom.style.left = '1px';
|
|
419
|
-
phantom.style.width = '1px';
|
|
420
|
-
phantom.style.height = '1px';
|
|
421
|
-
phantom.style.opacity = '0.001';
|
|
422
|
-
phantom.style.zIndex = '99999';
|
|
423
|
-
phantom.style.overflow = 'hidden';
|
|
424
|
-
phantom.style.pointerEvents = 'auto';
|
|
425
|
-
|
|
426
|
-
const triggerTrap = () => {
|
|
427
|
-
this.onHoneypotTrigger();
|
|
428
|
-
};
|
|
429
|
-
|
|
461
|
+
phantom.tabIndex = 0; // In natural tab flow for crawler parsers
|
|
430
462
|
phantom.addEventListener('focus', triggerTrap, { passive: true });
|
|
431
463
|
phantom.addEventListener('mouseover', triggerTrap, { passive: true });
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
464
|
+
phantom.addEventListener('click', (e) => {
|
|
465
|
+
if (e && typeof e.preventDefault === 'function') e.preventDefault();
|
|
466
|
+
triggerTrap();
|
|
467
|
+
}, { passive: false });
|
|
468
|
+
|
|
469
|
+
// 2. Deceptive hidden form with bait input
|
|
470
|
+
const phantomForm = document.createElement('form');
|
|
471
|
+
phantomForm.action = '#';
|
|
472
|
+
phantomForm.tabIndex = -1;
|
|
473
|
+
const inputNames = ['session_token', 'admin_pwd', 'secret_key', 'auth_bypass'];
|
|
474
|
+
const phantomInput = document.createElement('input');
|
|
475
|
+
phantomInput.type = 'text';
|
|
476
|
+
phantomInput.name = inputNames[Math.floor(secureRandom() * inputNames.length)] + '_' + genRandStr(4);
|
|
477
|
+
phantomInput.tabIndex = 0;
|
|
478
|
+
phantomInput.autocomplete = 'off';
|
|
479
|
+
phantomInput.addEventListener('focus', triggerTrap, { passive: true });
|
|
480
|
+
phantomInput.addEventListener('input', triggerTrap, { passive: true });
|
|
481
|
+
phantomInput.addEventListener('click', triggerTrap, { passive: true });
|
|
482
|
+
phantomInput.addEventListener('change', triggerTrap, { passive: true });
|
|
483
|
+
|
|
484
|
+
phantomForm.appendChild(phantomInput);
|
|
485
|
+
shadow.appendChild(phantom);
|
|
486
|
+
shadow.appendChild(phantomForm);
|
|
487
|
+
|
|
488
|
+
document.body.appendChild(host);
|
|
448
489
|
},
|
|
449
490
|
|
|
450
491
|
/**
|
|
@@ -785,7 +826,7 @@ const ClientLibrary = {
|
|
|
785
826
|
});
|
|
786
827
|
|
|
787
828
|
// 3. Generate trap inputs inside a closed Shadow DOM
|
|
788
|
-
if (typeof document !== 'undefined' && honeypotFieldNames.length > 0) {
|
|
829
|
+
if (typeof document !== 'undefined' && honeypotFieldNames.length > 0 && document.body) {
|
|
789
830
|
const host = document.createElement('div');
|
|
790
831
|
host.setAttribute('aria-hidden', 'true');
|
|
791
832
|
host.style.position = 'absolute';
|
|
@@ -793,7 +834,9 @@ const ClientLibrary = {
|
|
|
793
834
|
host.style.height = '0';
|
|
794
835
|
host.style.overflow = 'hidden';
|
|
795
836
|
|
|
796
|
-
const shadow = host.attachShadow
|
|
837
|
+
const shadow = typeof host.attachShadow === 'function'
|
|
838
|
+
? host.attachShadow({ mode: 'closed' })
|
|
839
|
+
: host;
|
|
797
840
|
|
|
798
841
|
// Randomize classes, CSS variables, and layout tags
|
|
799
842
|
const wrapperClass = genRandStr(10);
|
|
@@ -843,6 +886,7 @@ const ClientLibrary = {
|
|
|
843
886
|
input.addEventListener('input', trigger, { passive: true });
|
|
844
887
|
input.addEventListener('change', trigger, { passive: true });
|
|
845
888
|
input.addEventListener('focus', trigger, { passive: true });
|
|
889
|
+
input.addEventListener('click', trigger, { passive: true });
|
|
846
890
|
|
|
847
891
|
// Polymorphic nesting of label and input
|
|
848
892
|
const nestingType = Math.floor(secureRandom() * 3);
|
|
@@ -949,38 +993,118 @@ const ClientLibrary = {
|
|
|
949
993
|
},
|
|
950
994
|
|
|
951
995
|
/**
|
|
952
|
-
*
|
|
953
|
-
*
|
|
954
|
-
* @returns {
|
|
996
|
+
* Évalue le comportement côté client en local sans transmettre de données brutes.
|
|
997
|
+
* Retourne 1 si le comportement est humain/légitime, 0 en cas de suspicion/bot.
|
|
998
|
+
* @returns {number} 1 ou 0
|
|
955
999
|
*/
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
metrics.
|
|
1000
|
+
evaluateBehavior() {
|
|
1001
|
+
let score = 0;
|
|
1002
|
+
if (metrics.honeypotInteraction) return 100;
|
|
1003
|
+
if (ClientLibrary.detectTamperedPrototypes()) score += 80;
|
|
1004
|
+
|
|
1005
|
+
// Vérification du rendu graphique
|
|
1006
|
+
if (metrics.rendering) {
|
|
1007
|
+
if (metrics.rendering.offscreenAnom) return 0;
|
|
1008
|
+
const fps = parseFloat(metrics.rendering.fps || 0);
|
|
1009
|
+
const jitter = parseFloat(metrics.rendering.jitter || 0);
|
|
1010
|
+
if (fps > 250 || (fps > 0 && fps < 15)) score += 50;
|
|
1011
|
+
if (jitter > 6.0) score += Math.min(80, (jitter - 6.0) * 10);
|
|
1012
|
+
}
|
|
959
1013
|
|
|
960
|
-
//
|
|
961
|
-
|
|
962
|
-
|
|
1014
|
+
// Mouvements de souris robotiques
|
|
1015
|
+
if (mouseMovementsHistory.length >= 3) {
|
|
1016
|
+
let totalDist = 0;
|
|
1017
|
+
let pauses = 0;
|
|
1018
|
+
const segments = [];
|
|
1019
|
+
for (let i = 1; i < mouseMovementsHistory.length; i++) {
|
|
1020
|
+
const p1 = mouseMovementsHistory[i - 1];
|
|
1021
|
+
const p2 = mouseMovementsHistory[i];
|
|
1022
|
+
const dx = p2.x - p1.x;
|
|
1023
|
+
const dy = p2.y - p1.y;
|
|
1024
|
+
const dt = p2.t - p1.t;
|
|
1025
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1026
|
+
if (dt > 0) segments.push(dist / dt);
|
|
1027
|
+
totalDist += dist;
|
|
1028
|
+
if (dt > 100 && dist < 5) pauses++;
|
|
1029
|
+
}
|
|
1030
|
+
if (segments.length >= 2) {
|
|
1031
|
+
const totalTime = mouseMovementsHistory[mouseMovementsHistory.length - 1].t - mouseMovementsHistory[0].t;
|
|
1032
|
+
const avgSpeed = totalTime > 0 ? totalDist / totalTime : 0;
|
|
1033
|
+
const pStart = mouseMovementsHistory[0];
|
|
1034
|
+
const pEnd = mouseMovementsHistory[mouseMovementsHistory.length - 1];
|
|
1035
|
+
const straightDist = Math.sqrt(Math.pow(pEnd.x - pStart.x, 2) + Math.pow(pEnd.y - pStart.y, 2));
|
|
1036
|
+
const straightness = totalDist > 0 ? straightDist / totalDist : 1;
|
|
1037
|
+
if (avgSpeed > 3.0) score += 25;
|
|
1038
|
+
if (straightness > 0.95) score += 30;
|
|
1039
|
+
if (pauses === 0 && segments.length > 20) score += 15;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
963
1042
|
|
|
964
|
-
|
|
965
|
-
if (
|
|
966
|
-
|
|
1043
|
+
// Analyse des événements tactiles
|
|
1044
|
+
if (touchMovementsHistory.length >= 3) {
|
|
1045
|
+
let totalDist = 0;
|
|
1046
|
+
let totalPressure = 0;
|
|
1047
|
+
let totalRadius = 0;
|
|
1048
|
+
for (const pt of touchMovementsHistory) {
|
|
1049
|
+
totalPressure += pt.p || 0;
|
|
1050
|
+
totalRadius += pt.r || 0;
|
|
1051
|
+
}
|
|
1052
|
+
const avgPressure = totalPressure / touchMovementsHistory.length;
|
|
1053
|
+
const avgRadius = totalRadius / touchMovementsHistory.length;
|
|
1054
|
+
let sqDiffPressure = 0;
|
|
1055
|
+
let sqDiffRadius = 0;
|
|
1056
|
+
for (const pt of touchMovementsHistory) {
|
|
1057
|
+
sqDiffPressure += Math.pow((pt.p || 0) - avgPressure, 2);
|
|
1058
|
+
sqDiffRadius += Math.pow((pt.r || 0) - avgRadius, 2);
|
|
1059
|
+
}
|
|
1060
|
+
if (avgPressure > 0 && (sqDiffPressure / touchMovementsHistory.length) === 0) {
|
|
1061
|
+
score += 30;
|
|
1062
|
+
}
|
|
1063
|
+
if (avgRadius > 0 && (sqDiffRadius / touchMovementsHistory.length) === 0) {
|
|
1064
|
+
score += 30;
|
|
1065
|
+
}
|
|
967
1066
|
}
|
|
968
|
-
|
|
969
|
-
//
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
// NOUVEAU: Keystroke dynamics metrics (dwell and flight times)
|
|
973
|
-
metrics.keystrokeDwellTimes = keystrokeDwellTimes;
|
|
974
|
-
metrics.keystrokeFlightTimes = keystrokeFlightTimes;
|
|
975
|
-
|
|
976
|
-
// Calculate mean keystroke latency
|
|
977
|
-
if (keystrokeLatencies.length > 0) {
|
|
978
|
-
const sum = keystrokeLatencies.reduce((a, b) => a + b, 0);
|
|
979
|
-
metrics.keystrokeLatency = sum / keystrokeLatencies.length;
|
|
980
|
-
} else {
|
|
981
|
-
metrics.keystrokeLatency = 0;
|
|
1067
|
+
|
|
1068
|
+
// Détection de rack / ferme de téléphones mobiles immobiles
|
|
1069
|
+
if (typeof window !== 'undefined' && (window.navigator?.userAgent || '').includes('Mobile')) {
|
|
1070
|
+
if (metrics.motionVariance === 0) return 0;
|
|
982
1071
|
}
|
|
983
|
-
|
|
1072
|
+
|
|
1073
|
+
// Variance des clics ultra-précise (bot de clic)
|
|
1074
|
+
if (clicksHistory.length >= 3) {
|
|
1075
|
+
const targets = {};
|
|
1076
|
+
for (const c of clicksHistory) {
|
|
1077
|
+
if (!c.targetId) continue;
|
|
1078
|
+
if (!targets[c.targetId]) targets[c.targetId] = [];
|
|
1079
|
+
targets[c.targetId].push(c);
|
|
1080
|
+
}
|
|
1081
|
+
for (const id in targets) {
|
|
1082
|
+
const clks = targets[id];
|
|
1083
|
+
if (clks.length >= 3) {
|
|
1084
|
+
const mx = clks.reduce((s, c) => s + c.x, 0) / clks.length;
|
|
1085
|
+
const my = clks.reduce((s, c) => s + c.y, 0) / clks.length;
|
|
1086
|
+
const v = clks.reduce((s, c) => s + Math.pow(c.x - mx, 2) + Math.pow(c.y - my, 2), 0) / clks.length;
|
|
1087
|
+
if (v < 1.0) return 0;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// Dynamique des touches de clavier robotique
|
|
1093
|
+
if (keystrokeDwellTimes.length >= 5) {
|
|
1094
|
+
const mean = keystrokeDwellTimes.reduce((a, b) => a + b, 0) / keystrokeDwellTimes.length;
|
|
1095
|
+
const variance = keystrokeDwellTimes.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / keystrokeDwellTimes.length;
|
|
1096
|
+
if (Math.sqrt(variance) < 2.0 || mean < 15.0) return 0;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
return 1;
|
|
1100
|
+
},
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* Récupère le résultat comportemental sous forme binaire (1 = valide/humain, 0 = suspect).
|
|
1104
|
+
* @returns {number} 1 ou 0
|
|
1105
|
+
*/
|
|
1106
|
+
getClientBehaviorMetrics() {
|
|
1107
|
+
return this.evaluateBehavior();
|
|
984
1108
|
},
|
|
985
1109
|
|
|
986
1110
|
/**
|
|
@@ -991,14 +1115,13 @@ const ClientLibrary = {
|
|
|
991
1115
|
*/
|
|
992
1116
|
async protectedFetch(resource, options = {}) {
|
|
993
1117
|
const fp = this.getDeviceFingerprint();
|
|
994
|
-
const rawBehavior = this.getClientBehaviorMetrics();
|
|
995
1118
|
const zkpProof = await this.generateZkpProof(fp); // Generate ZKP proof
|
|
996
|
-
const behavior =
|
|
1119
|
+
const behavior = this.getClientBehaviorMetrics();
|
|
997
1120
|
|
|
998
1121
|
const headers = new Headers(options.headers || {});
|
|
999
1122
|
headers.set('X-ZKP-Proof', zkpProof); // Add ZKP proof to headers
|
|
1000
1123
|
headers.set('X-Device-Fingerprint', fp);
|
|
1001
|
-
headers.set('X-Behavior-Metrics',
|
|
1124
|
+
headers.set('X-Behavior-Metrics', String(behavior));
|
|
1002
1125
|
|
|
1003
1126
|
options.headers = headers;
|
|
1004
1127
|
return fetch(resource, options);
|
|
@@ -1008,6 +1131,8 @@ const ClientLibrary = {
|
|
|
1008
1131
|
_isFetchPatched: false,
|
|
1009
1132
|
_interceptorChain: [],
|
|
1010
1133
|
// Store original fetch bound to window to prevent illegal invocation errors
|
|
1134
|
+
_targetDomains: [],
|
|
1135
|
+
_swRegistration: null,
|
|
1011
1136
|
_originalFetch: (typeof window !== 'undefined') ? window.fetch.bind(window) : null,
|
|
1012
1137
|
|
|
1013
1138
|
/**
|
|
@@ -1045,10 +1170,78 @@ const ClientLibrary = {
|
|
|
1045
1170
|
*/
|
|
1046
1171
|
onHoneypotTrigger() {
|
|
1047
1172
|
metrics.honeypotInteraction = true;
|
|
1173
|
+
if (this.workerPath) {
|
|
1174
|
+
this.syncServiceWorkerCredentials();
|
|
1175
|
+
}
|
|
1048
1176
|
// Dispatch event allowing host applications to react
|
|
1049
1177
|
this._dispatchEvent('honeypotTriggered');
|
|
1050
1178
|
},
|
|
1051
1179
|
|
|
1180
|
+
/**
|
|
1181
|
+
* Registers and manages the Service Worker for transparent HTTPS request decoration.
|
|
1182
|
+
* @param {string} workerPath - Path to the worker script.
|
|
1183
|
+
* @param {string[]} [targetDomains=[]] - Domains to intercept.
|
|
1184
|
+
* @param {string} [scope] - Service Worker registration scope.
|
|
1185
|
+
* @returns {Promise<ServiceWorkerRegistration|null>}
|
|
1186
|
+
*/
|
|
1187
|
+
async initServiceWorker(workerPath, targetDomains = [], scope = undefined) {
|
|
1188
|
+
if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) {
|
|
1189
|
+
console.warn('[Fingerprint] Service Worker is not supported in this browser.');
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
try {
|
|
1194
|
+
const registerOptions = scope ? { scope } : {};
|
|
1195
|
+
const registration = await navigator.serviceWorker.register(workerPath, registerOptions);
|
|
1196
|
+
this._swRegistration = registration;
|
|
1197
|
+
|
|
1198
|
+
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
1199
|
+
this.syncServiceWorkerCredentials(targetDomains);
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
await navigator.serviceWorker.ready;
|
|
1203
|
+
this.syncServiceWorkerCredentials(targetDomains);
|
|
1204
|
+
|
|
1205
|
+
if (typeof window !== 'undefined') {
|
|
1206
|
+
window.addEventListener('focus', () => this.syncServiceWorkerCredentials(targetDomains), { passive: true });
|
|
1207
|
+
document.addEventListener('visibilitychange', () => {
|
|
1208
|
+
if (document.visibilityState === 'visible') {
|
|
1209
|
+
this.syncServiceWorkerCredentials(targetDomains);
|
|
1210
|
+
}
|
|
1211
|
+
}, { passive: true });
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
console.log('[Fingerprint] Network Service Worker active and synchronized.');
|
|
1215
|
+
return registration;
|
|
1216
|
+
} catch (error) {
|
|
1217
|
+
console.warn('[Fingerprint] Failed to register Service Worker:', error);
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
},
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* Synchronizes device fingerprint and behavior metrics with the active Service Worker.
|
|
1224
|
+
* @param {string[]} [targetDomains=[]]
|
|
1225
|
+
*/
|
|
1226
|
+
syncServiceWorkerCredentials(targetDomains = []) {
|
|
1227
|
+
if (typeof navigator === 'undefined' || !navigator.serviceWorker) return;
|
|
1228
|
+
const domains = (Array.isArray(targetDomains) && targetDomains.length > 0)
|
|
1229
|
+
? targetDomains
|
|
1230
|
+
: (this._targetDomains || []);
|
|
1231
|
+
const payload = {
|
|
1232
|
+
type: 'UPDATE_CREDENTIALS',
|
|
1233
|
+
fp: this.getDeviceFingerprint(),
|
|
1234
|
+
behavior: this.getClientBehaviorMetrics(),
|
|
1235
|
+
targetDomains: domains
|
|
1236
|
+
};
|
|
1237
|
+
|
|
1238
|
+
if (navigator.serviceWorker.controller) {
|
|
1239
|
+
navigator.serviceWorker.controller.postMessage(payload);
|
|
1240
|
+
} else if (this._swRegistration && this._swRegistration.active) {
|
|
1241
|
+
this._swRegistration.active.postMessage(payload);
|
|
1242
|
+
}
|
|
1243
|
+
},
|
|
1244
|
+
|
|
1052
1245
|
/**
|
|
1053
1246
|
* Initializes the fingerprinting fetch interceptor.
|
|
1054
1247
|
* Appends to interceptor chain without clobbering existing handlers.
|
|
@@ -1071,11 +1264,10 @@ const ClientLibrary = {
|
|
|
1071
1264
|
|
|
1072
1265
|
if (shouldProtect) {
|
|
1073
1266
|
const fp = this.getDeviceFingerprint();
|
|
1074
|
-
const
|
|
1075
|
-
const behavior = await signMetrics(rawBehavior);
|
|
1267
|
+
const behavior = this.getClientBehaviorMetrics();
|
|
1076
1268
|
const headers = new Headers(options.headers || {});
|
|
1077
1269
|
headers.set('X-Device-Fingerprint', fp);
|
|
1078
|
-
headers.set('X-Behavior-Metrics',
|
|
1270
|
+
headers.set('X-Behavior-Metrics', String(behavior));
|
|
1079
1271
|
options.headers = headers;
|
|
1080
1272
|
}
|
|
1081
1273
|
|
|
@@ -1092,7 +1284,7 @@ const ClientLibrary = {
|
|
|
1092
1284
|
* @private
|
|
1093
1285
|
*/
|
|
1094
1286
|
injectTrapLinks(urls) {
|
|
1095
|
-
if (!urls || urls.length === 0 || typeof document === 'undefined') {
|
|
1287
|
+
if (!urls || urls.length === 0 || typeof document === 'undefined' || !document.body) {
|
|
1096
1288
|
return;
|
|
1097
1289
|
}
|
|
1098
1290
|
|
|
@@ -1103,7 +1295,9 @@ const ClientLibrary = {
|
|
|
1103
1295
|
host.style.height = '0';
|
|
1104
1296
|
host.style.overflow = 'hidden';
|
|
1105
1297
|
|
|
1106
|
-
const shadow = host.attachShadow
|
|
1298
|
+
const shadow = typeof host.attachShadow === 'function'
|
|
1299
|
+
? host.attachShadow({ mode: 'closed' })
|
|
1300
|
+
: host;
|
|
1107
1301
|
|
|
1108
1302
|
// Randomize classes, CSS variables, and layout tags
|
|
1109
1303
|
const wrapperClass = genRandStr(10);
|
|
@@ -1167,10 +1361,13 @@ const ClientLibrary = {
|
|
|
1167
1361
|
link.innerHTML = `<span>> ${i + 1}</span>`;
|
|
1168
1362
|
}
|
|
1169
1363
|
|
|
1170
|
-
const trigger = () => {
|
|
1364
|
+
const trigger = (e) => {
|
|
1365
|
+
if (e && typeof e.preventDefault === 'function') {
|
|
1366
|
+
e.preventDefault();
|
|
1367
|
+
}
|
|
1171
1368
|
this.onHoneypotTrigger();
|
|
1172
1369
|
};
|
|
1173
|
-
link.addEventListener('click', trigger, { passive:
|
|
1370
|
+
link.addEventListener('click', trigger, { passive: false });
|
|
1174
1371
|
link.addEventListener('focus', trigger, { passive: true });
|
|
1175
1372
|
link.addEventListener('mouseover', trigger, { passive: true });
|
|
1176
1373
|
|
|
@@ -1193,10 +1390,20 @@ const ClientLibrary = {
|
|
|
1193
1390
|
* @param {Response} response - Initial response (status 404/challenge).
|
|
1194
1391
|
* @param {RequestInfo} resource - Original request resource.
|
|
1195
1392
|
* @param {RequestInit} options - Original request options.
|
|
1393
|
+
* @param {number} [maxRetries=3] - Maximum allowed challenge retry attempts.
|
|
1196
1394
|
* @returns {Promise<Response>} - Retried response.
|
|
1197
1395
|
* @private
|
|
1198
1396
|
*/
|
|
1199
|
-
async solveChallengeAndRetry(response, resource, options) {
|
|
1397
|
+
async solveChallengeAndRetry(response, resource, options = {}, maxRetries = 3) {
|
|
1398
|
+
const effectiveMaxRetries = (options && typeof options._powMaxRetries === 'number')
|
|
1399
|
+
? options._powMaxRetries
|
|
1400
|
+
: (typeof maxRetries === 'number' ? maxRetries : 3);
|
|
1401
|
+
const currentRetries = (options && options._powRetryCount) || (resource && resource._powRetryCount) || 0;
|
|
1402
|
+
if (currentRetries >= effectiveMaxRetries) {
|
|
1403
|
+
console.warn(`[Fingerprint] Challenge retry limit reached (${effectiveMaxRetries}). Aborting challenge retry loop.`);
|
|
1404
|
+
return response;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1200
1407
|
if (!response || response.status !== 404 || !response.headers?.get?.('content-type')?.includes('application/json') || response.bodyUsed) {
|
|
1201
1408
|
return response;
|
|
1202
1409
|
}
|
|
@@ -1223,8 +1430,16 @@ const ClientLibrary = {
|
|
|
1223
1430
|
// Attach solver fingerprint to retry request
|
|
1224
1431
|
url.searchParams.set('pow_fp', solverFp);
|
|
1225
1432
|
|
|
1226
|
-
// Use fetch to retry with
|
|
1227
|
-
|
|
1433
|
+
// Use fetch to retry with incremented retry count to prevent infinite challenge loops
|
|
1434
|
+
const retryOptions = {
|
|
1435
|
+
...options,
|
|
1436
|
+
_powRetryCount: currentRetries + 1,
|
|
1437
|
+
_powMaxRetries: effectiveMaxRetries
|
|
1438
|
+
};
|
|
1439
|
+
const fetchFn = (typeof window !== 'undefined' && typeof window.fetch === 'function')
|
|
1440
|
+
? window.fetch
|
|
1441
|
+
: fetch;
|
|
1442
|
+
return fetchFn(url.toString(), retryOptions);
|
|
1228
1443
|
} catch (e) {
|
|
1229
1444
|
console.error('[Fingerprint] Failed to solve or retry challenge:', e);
|
|
1230
1445
|
return response; // Retourne la réponse 429 originale en cas d'échec
|
|
@@ -1249,6 +1464,7 @@ const ClientLibrary = {
|
|
|
1249
1464
|
honeypots = [],
|
|
1250
1465
|
trapUrls = [], // Trap URLs
|
|
1251
1466
|
wasmPath,
|
|
1467
|
+
worker = false,
|
|
1252
1468
|
workerPath, // NOUVEAU
|
|
1253
1469
|
fetch: fetchConfig = {}
|
|
1254
1470
|
} = config;
|
|
@@ -1262,8 +1478,30 @@ const ClientLibrary = {
|
|
|
1262
1478
|
if (mouse) {
|
|
1263
1479
|
this.startMouseEntropyTracker();
|
|
1264
1480
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1481
|
+
|
|
1482
|
+
// Instantiation du Service Worker via initializeClient
|
|
1483
|
+
let resolvedWorkerPath = null;
|
|
1484
|
+
let workerScope = undefined;
|
|
1485
|
+
let workerTargetDomains = fetchConfig.targetDomains || [];
|
|
1486
|
+
|
|
1487
|
+
if (typeof worker === 'string') {
|
|
1488
|
+
resolvedWorkerPath = worker;
|
|
1489
|
+
} else if (typeof worker === 'object' && worker !== null) {
|
|
1490
|
+
resolvedWorkerPath = worker.path || '/fingerprint.worker.js';
|
|
1491
|
+
workerScope = worker.scope;
|
|
1492
|
+
if (Array.isArray(worker.targetDomains)) {
|
|
1493
|
+
workerTargetDomains = worker.targetDomains;
|
|
1494
|
+
}
|
|
1495
|
+
} else if (worker === true) {
|
|
1496
|
+
resolvedWorkerPath = '/fingerprint.worker.js';
|
|
1497
|
+
} else if (workerPath) {
|
|
1498
|
+
resolvedWorkerPath = workerPath;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
if (resolvedWorkerPath) {
|
|
1502
|
+
this.workerPath = resolvedWorkerPath;
|
|
1503
|
+
this._targetDomains = workerTargetDomains;
|
|
1504
|
+
this.initServiceWorker(resolvedWorkerPath, workerTargetDomains, workerScope);
|
|
1267
1505
|
}
|
|
1268
1506
|
if (keystrokes) {
|
|
1269
1507
|
this.startKeystrokeDynamicsTracker();
|
|
@@ -1294,16 +1532,17 @@ const ClientLibrary = {
|
|
|
1294
1532
|
if (trapUrls.length > 0) {
|
|
1295
1533
|
this.injectTrapLinks(trapUrls);
|
|
1296
1534
|
}
|
|
1297
|
-
// Enable fetch
|
|
1298
|
-
if (config.fetch) {
|
|
1535
|
+
// Enable fetch monkey-patching only if explicitly configured and no Service Worker is used
|
|
1536
|
+
if (config.fetch && !resolvedWorkerPath) {
|
|
1299
1537
|
this.initializeFetch(fetchConfig.targetDomains);
|
|
1300
1538
|
|
|
1301
1539
|
// Add challenge resolution interceptor
|
|
1302
1540
|
if (fetchConfig.handleChallenges !== false) {
|
|
1541
|
+
const maxRetries = typeof fetchConfig.maxRetries === 'number' ? fetchConfig.maxRetries : 3;
|
|
1303
1542
|
this.addFetchInterceptor(async (resource, options, next) => {
|
|
1304
1543
|
const originalResponse = await next(resource, options);
|
|
1305
1544
|
// Clone response to prevent draining the body stream for original caller
|
|
1306
|
-
return this.solveChallengeAndRetry(originalResponse.clone(), resource, options);
|
|
1545
|
+
return this.solveChallengeAndRetry(originalResponse.clone(), resource, options, maxRetries);
|
|
1307
1546
|
});
|
|
1308
1547
|
}
|
|
1309
1548
|
}
|
|
@@ -1521,6 +1760,8 @@ export const generateZkpProof = ClientLibrary.generateZkpProof.bind(ClientLibrar
|
|
|
1521
1760
|
export const initializeSpace = ClientLibrary.initializeSpace.bind(ClientLibrary);
|
|
1522
1761
|
export const readSpaceBlock = ClientLibrary.readSpaceBlock.bind(ClientLibrary);
|
|
1523
1762
|
export const solveSpaceChallenge = ClientLibrary.solveSpaceChallenge.bind(ClientLibrary);
|
|
1763
|
+
export const initServiceWorker = ClientLibrary.initServiceWorker.bind(ClientLibrary);
|
|
1764
|
+
export const syncServiceWorkerCredentials = ClientLibrary.syncServiceWorkerCredentials.bind(ClientLibrary);
|
|
1524
1765
|
|
|
1525
1766
|
// Export the internal object for testing purposes
|
|
1526
1767
|
export default ClientLibrary;
|