@anonympins/fingerprint 0.6.7 → 0.7.1

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 (63) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +3 -7
  3. package/composer.json +8 -1
  4. package/package.json +1 -1
  5. package/public/fingerprint-wordpress.zip +0 -0
  6. package/public/fp.wasm +0 -0
  7. package/src/js/build-client.js +9 -11
  8. package/src/js/dynamic-wasm.js +7 -7
  9. package/src/js/fingerprint.builder.js +37 -38
  10. package/src/js/fingerprint.client.js +236 -152
  11. package/src/js/fingerprint.client.obfuscated.js +1 -1
  12. package/src/js/fingerprint.js +517 -101
  13. package/src/js/fingerprint.utils.js +213 -213
  14. package/src/js/library.js +355 -384
  15. package/src/js/mongodb-store.js +1 -1
  16. package/src/js/optimization.worker.js +27 -27
  17. package/src/js/pow.solver.inline.js +206 -62
  18. package/src/js/pow.solver.js +235 -101
  19. package/src/js/pow.worker.js +48 -26
  20. package/src/js/redis-store.js +1 -1
  21. package/src/js/tests/fingerprint.client.init.test.js +146 -143
  22. package/src/js/tests/fingerprint.test.js +101 -40
  23. package/src/js/tests/ip-reputation.test.js +1 -0
  24. package/src/js/upow-model-task.js +14 -15
  25. package/src/php/AutoTuner.php +34 -34
  26. package/src/php/Challenge/ChallengeUtils.php +204 -32
  27. package/src/php/Config/SecurityProfiles.php +21 -0
  28. package/src/php/DirectFingerprint.php +190 -145
  29. package/src/php/FingerprintBuilder.php +29 -30
  30. package/src/php/FingerprintClient.php +24 -25
  31. package/src/php/FingerprintEngine.php +1836 -1880
  32. package/src/php/Ja3AnomalyDetector.php +258 -227
  33. package/src/php/Optimization/FunctionRegistry.php +7 -7
  34. package/src/php/Optimization/Optimization.php +24 -24
  35. package/src/php/Optimization/OptimizationOperators.php +28 -27
  36. package/src/php/Optimization/ProblemInitializers.php +52 -52
  37. package/src/php/ProblemManager.php +31 -34
  38. package/src/php/RequestContext.php +8 -1
  39. package/src/php/Store/IStore.php +7 -8
  40. package/src/php/Store/InMemoryStore.php +66 -66
  41. package/src/php/Store/MongoDbStore.php +5 -5
  42. package/src/php/Store/RedisStore.php +5 -5
  43. package/src/php/Store/StoreManager.php +35 -35
  44. package/src/php/Tests/ChallengeUtilsTest.php +453 -453
  45. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  46. package/src/php/Tests/FingerprintEngineTest.php +662 -693
  47. package/src/php/Tests/IpReputationTest.php +10 -0
  48. package/src/php/Tests/MaliciousPatternsTest.php +103 -103
  49. package/src/php/Tests/MetricsTest.php +97 -97
  50. package/src/php/Tests/ProblemManagerTest.php +376 -376
  51. package/src/php/Tests/QuicFingerprintTest.php +53 -53
  52. package/src/php/Tests/RequestUtilsTest.php +471 -471
  53. package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
  54. package/src/php/Tests/config/ed25519_key.json +3 -3
  55. package/src/php/Utils/Env.php +49 -49
  56. package/src/php/Utils/MaliciousPatterns.php +74 -74
  57. package/src/php/Utils/MetricsManager.php +209 -209
  58. package/src/php/Utils/RequestUtils.php +236 -19
  59. package/src/php/Utils/TLSClientHelloParser.php +369 -212
  60. package/src/php/WordPress/WpDbStore.php +155 -0
  61. package/src/php/WordPress/fingerprint-wordpress.php +217 -0
  62. package/src/php/WordPress/package.php +123 -0
  63. package/src/php/bin/auto-tune.php +118 -118
@@ -2,16 +2,16 @@ import {cyrb53 as jsCyrb53, FingerprintBuilder} from './fingerprint.builder.js';
2
2
  import {solveChallenge} from './pow.solver.js';
3
3
 
4
4
  function secureRandom() {
5
- if (typeof window !== 'undefined' && (window.crypto || window.msCrypto)) {
5
+ if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.getRandomValues) {
6
6
  const array = new Uint32Array(1);
7
- (window.crypto || window.msCrypto).getRandomValues(array);
8
- return array[0] / 0x100000000;
7
+ globalThis.crypto.getRandomValues(array);
8
+ return array[0] / 0xffffffff;
9
9
  }
10
10
  return Math.random();
11
11
  }
12
12
 
13
- // Variable pour stocker la fonction de hachage active.
14
- // Par défaut, c'est l'implémentation JavaScript.
13
+ // Variable storing the active hash function.
14
+ // Defaults to the JavaScript implementation.
15
15
  let activeCyrb53 = jsCyrb53;
16
16
  let derivedKey = null;
17
17
 
@@ -37,7 +37,13 @@ async function negotiateSessionKey() {
37
37
  body: JSON.stringify({ fp: ClientLibrary.getDeviceFingerprint() })
38
38
  });
39
39
 
40
- const serverPubKeyHex = response.headers.get('x-server-ephemeral-key');
40
+ if (!response || !response.headers) return;
41
+ let serverPubKeyHex = null;
42
+ if (typeof response.headers.get === 'function') {
43
+ serverPubKeyHex = response.headers.get('x-server-ephemeral-key');
44
+ } else if (typeof response.headers === 'object') {
45
+ serverPubKeyHex = response.headers['x-server-ephemeral-key'] || response.headers['X-Server-Ephemeral-Key'];
46
+ }
41
47
  if (serverPubKeyHex) {
42
48
  const serverPubKeyBuffer = new Uint8Array(
43
49
  serverPubKeyHex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))
@@ -144,7 +150,7 @@ function cacheWasm(url, data) {
144
150
  }
145
151
 
146
152
  const ClientLibrary = {
147
- // Cache pour éviter de recalculer les constantes (Hardware, etc.)
153
+ // Cache to avoid recalculating hardware constants
148
154
  _cachedBuilder: null,
149
155
  /**
150
156
  * @private
@@ -159,16 +165,48 @@ const ClientLibrary = {
159
165
  },
160
166
 
161
167
  /**
162
- * Wrapper interne pour la fonction de hachage.
168
+ * Checks if native JavaScript prototypes have been tampered with or hooked (Frida, Puppeteer Stealth).
169
+ */
170
+ detectTamperedPrototypes() {
171
+ const checkNative = (obj, method) => {
172
+ try {
173
+ if (!obj) return false;
174
+ const fn = obj[method];
175
+ if (!fn) return false;
176
+ const str = Function.prototype.toString.call(fn);
177
+ if (!str.includes('[native code]')) return true; // Standard JS hook
178
+ const desc = Object.getOwnPropertyDescriptor(obj, method);
179
+ if (desc && (!desc.writable && !desc.configurable && desc.value)) return false;
180
+ return false;
181
+ } catch (e) {
182
+ return true;
183
+ }
184
+ };
185
+
186
+ const win = typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : null);
187
+ if (!win) return false;
188
+
189
+ const htmlCanvas = typeof HTMLCanvasElement !== 'undefined' ? HTMLCanvasElement : win.HTMLCanvasElement;
190
+ const canvas2d = typeof CanvasRenderingContext2D !== 'undefined' ? CanvasRenderingContext2D : win.CanvasRenderingContext2D;
191
+ const webgl = typeof WebGLRenderingContext !== 'undefined' ? WebGLRenderingContext : win.WebGLRenderingContext;
192
+
193
+ return checkNative(htmlCanvas?.prototype, 'toDataURL') ||
194
+ checkNative(canvas2d?.prototype, 'getImageData') ||
195
+ checkNative(webgl?.prototype, 'getParameter') ||
196
+ checkNative(win, 'fetch');
197
+ },
198
+
199
+ /**
200
+ * Internal wrapper for the active hash function.
163
201
  * @private
164
202
  */
165
203
  _hasher: (str, seed) => activeCyrb53(str, seed),
166
204
 
167
205
  /**
168
- * Génère une preuve de connaissance à divulgation nulle (ZKP) de Schnorr pour l'empreinte de l'appareil.
169
- * Rend le tout stable et compatible avec les vérifications JS, PHP et Python.
170
- * @param {string} fingerprint - L'empreinte de l'appareil.
171
- * @returns {Promise<string>} La preuve sous format "y:t:s" en hexadécimal.
206
+ * Generates a Schnorr Zero-Knowledge Proof (ZKP) of the device fingerprint.
207
+ * Ensures compatibility across JS, PHP, and Java verification engines.
208
+ * @param {string} fingerprint - Device fingerprint string.
209
+ * @returns {Promise<string>} Proof in hex format "y:t:s".
172
210
  */
173
211
  async generateZkpProof(fingerprint) {
174
212
  const ZKP_P = 115792089237316195423570985008687907853269984665640564039457584007908834671663n;
@@ -215,7 +253,7 @@ const ClientLibrary = {
215
253
  },
216
254
 
217
255
  /**
218
- * Génère l'empreinte de l'appareil actuel.
256
+ * Generates the current device fingerprint.
219
257
  */
220
258
  getDeviceFingerprint() {
221
259
  if (typeof window === "undefined") {
@@ -229,19 +267,19 @@ const ClientLibrary = {
229
267
 
230
268
  this._cachedBuilder = new FingerprintBuilder();
231
269
 
232
- // 1. Hardware (Très stable) : Cœurs, RAM, GPU (si dispo via canvas), Touch
270
+ // 1. Hardware (Very stable): Cores, RAM, GPU, Touch
233
271
  this._cachedBuilder.add(
234
- "hw", // Utilise maintenant le hasher actif
272
+ "hw",
235
273
  `${nav.hardwareConcurrency}_${nav.deviceMemory}_${nav.maxTouchPoints}`,
236
274
  );
237
275
 
238
- // 2. Geo/Locale (Stable sauf voyage/VPN) : Timezone, Langue
276
+ // 2. Geo/Locale (Stable unless traveling/VPN): Timezone, Language
239
277
  this._cachedBuilder.add(
240
278
  "geo",
241
279
  `${Intl.DateTimeFormat().resolvedOptions().timeZone}_${nav.language}_${new Date().getTimezoneOffset()}`,
242
280
  );
243
281
 
244
- // 3. Screen (Stable sauf changement moniteur/zoom) : Dimensions, ColorDepth
282
+ // 3. Screen (Stable unless monitor/zoom changes): Dimensions, ColorDepth
245
283
  this._cachedBuilder.add(
246
284
  "scr",
247
285
  `${screen.width}x${screen.height}_${screen.colorDepth}`,
@@ -288,8 +326,8 @@ const ClientLibrary = {
288
326
  } catch (e) {
289
327
  }
290
328
 
291
- // 7. Détection des artefacts du Chrome DevTools Protocol (CDP)
292
- // Ces variables sont souvent injectées par les outils d'automatisation.
329
+ // 7. Detection of Chrome DevTools Protocol (CDP) artifacts
330
+ // Injected by automation tools
293
331
  const cdpFootprints = [
294
332
  'cdc_adoQpoasnfa76pfcZLmcfl_Array',
295
333
  'cdc_adoQpoasnfa76pfcZLmcfl_Promise',
@@ -302,15 +340,18 @@ const ClientLibrary = {
302
340
  this._cachedBuilder.add("cdp", "true");
303
341
  }
304
342
 
305
- // 7. Bot Detection (Indication cachée)
343
+ // 8. Bot Detection (Hidden indicators)
306
344
  if (nav.webdriver) this._cachedBuilder.add("bot", "true");
345
+ if (ClientLibrary.detectTamperedPrototypes()) {
346
+ this._cachedBuilder.add("tampered", "true");
347
+ }
307
348
  }
308
349
 
309
350
  return this._cachedBuilder.toString();
310
351
  },
311
352
 
312
353
  /**
313
- * Génère une signature de requête incluant le contexte.
354
+ * Generates a request signature including context.
314
355
  * @param {object} payload
315
356
  */
316
357
  /**
@@ -328,10 +369,10 @@ const ClientLibrary = {
328
369
  },
329
370
 
330
371
  /**
331
- * Génère une signature HMAC-SHA256 en utilisant l'API Web Crypto.
332
- * @param {object} payload - Les données à signer.
333
- * @param {string} secret - La clé secrète partagée.
334
- * @returns {Promise<string>} La signature hexadécimale.
372
+ * Generates an HMAC-SHA256 signature using the Web Crypto API.
373
+ * @param {object} payload - Data payload to sign.
374
+ * @param {string} secret - Shared secret key.
375
+ * @returns {Promise<string>} Hexadecimal signature.
335
376
  */
336
377
  async generateClientSideSignature(payload, secret) {
337
378
  const sortedPayload = Object.keys(payload).sort().map((k) => `${k}=${payload[k]}`).join("&");
@@ -350,28 +391,28 @@ const ClientLibrary = {
350
391
  * Resets the cached fingerprint builder. Used for testing purposes.
351
392
  */
352
393
  _resetCache() {
353
- // Réinitialise le hasher à l'implémentation JS par défaut.
394
+ // Reset hasher to default JS implementation.
354
395
  activeCyrb53 = jsCyrb53;
355
396
  this._cachedBuilder = null;
356
397
  },
357
398
 
358
399
  /**
359
- * Injecte des éléments interactifs fantômes invisibles pour piéger les bots (focus/hover).
400
+ * Injects invisible phantom interactive elements to trap bots (focus/hover).
360
401
  */
361
402
  injectPhantomTraps() {
362
403
  if (typeof document === 'undefined') return;
363
404
 
364
- // Création d'un élément interactif fantôme
405
+ // Create a phantom interactive element
365
406
  const phantom = document.createElement('a');
366
407
  phantom.href = '#';
367
- // Nom trompeur aléatoire pour attirer les analyseurs automatiques de liens / formulaires
408
+ // Random deceptive name to attract automated link/form parsers
368
409
  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'];
369
410
  phantom.id = phantomNames[Math.floor(secureRandom() * phantomNames.length)] + '-' + genRandStr(6);
370
411
  phantom.className = genRandStr(8);
371
- phantom.tabIndex = 0; // Dans le flux naturel de tabulation
372
- phantom.setAttribute('aria-hidden', 'true'); // Masqué pour les screen readers légitimes
412
+ phantom.tabIndex = 0; // In natural tab flow
413
+ phantom.setAttribute('aria-hidden', 'true'); // Hidden from legitimate screen readers
373
414
 
374
- // Style invisible mais interactif (1px x 1px, presque transparent)
415
+ // Invisible yet interactive style (1px x 1px, nearly transparent)
375
416
  phantom.style.position = 'fixed';
376
417
  phantom.style.top = '1px';
377
418
  phantom.style.left = '1px';
@@ -389,7 +430,7 @@ const ClientLibrary = {
389
430
  phantom.addEventListener('focus', triggerTrap, { passive: true });
390
431
  phantom.addEventListener('mouseover', triggerTrap, { passive: true });
391
432
 
392
- // Imbrication polymorphique du piège fantôme dans le corps du document
433
+ // Polymorphic DOM insertion of the phantom trap
393
434
  const nestingOptions = [
394
435
  () => document.body.appendChild(phantom),
395
436
  () => {
@@ -407,7 +448,31 @@ const ClientLibrary = {
407
448
  },
408
449
 
409
450
  /**
410
- * Démarre le suivi des événements tactiles sur mobile/tablette.
451
+ * Measures gyroscope/accelerometer noise to detect phone farm racks.
452
+ */
453
+ startMotionTracker() {
454
+ if (this._motionTrackerAttached || typeof window === 'undefined') return;
455
+ this._motionTrackerAttached = true;
456
+ const motionSamples = [];
457
+ const handleMotion = (e) => {
458
+ const acc = e.accelerationIncludingGravity || e.acceleration;
459
+ if (!acc) return;
460
+ const mag = Math.sqrt((acc.x || 0)**2 + (acc.y || 0)**2 + (acc.z || 0)**2);
461
+ motionSamples.push(mag);
462
+ if (motionSamples.length > 20) motionSamples.shift();
463
+ if (motionSamples.length >= 10) {
464
+ const avg = motionSamples.reduce((a, b) => a + b, 0) / motionSamples.length;
465
+ const variance = motionSamples.reduce((a, b) => a + (b - avg)**2, 0) / motionSamples.length;
466
+ metrics.motionVariance = Math.round(variance * 10000) / 10000;
467
+ }
468
+ };
469
+ try {
470
+ window.addEventListener('devicemotion', handleMotion, { passive: true });
471
+ } catch (e) {}
472
+ },
473
+
474
+ /**
475
+ * Starts tracking touch events on mobile/tablet devices.
411
476
  */
412
477
  startTouchEventTracker() {
413
478
  if (this._touchTrackerAttached) return;
@@ -441,7 +506,7 @@ const ClientLibrary = {
441
506
  },
442
507
 
443
508
  /**
444
- * Démarre le suivi de la régularité d'affichage (V-Sync/rAF) pour détecter les framebuffers logiciels sans V-Sync.
509
+ * Starts tracking display regularity (V-Sync/rAF) to detect software framebuffers lacking V-Sync.
445
510
  */
446
511
  startRenderingTracker() {
447
512
  if (this._renderingTrackerAttached) return;
@@ -455,8 +520,9 @@ const ClientLibrary = {
455
520
 
456
521
  const checkOffscreenAnom = () => {
457
522
  try {
458
- if ('OffscreenCanvas' in window && HTMLCanvasElement.prototype.transferControlToOffscreen) {
459
- const nativeToString = Function.prototype.toString.call(HTMLCanvasElement.prototype.transferControlToOffscreen);
523
+ const htmlCanvas = typeof HTMLCanvasElement !== 'undefined' ? HTMLCanvasElement : window.HTMLCanvasElement;
524
+ if ('OffscreenCanvas' in window && htmlCanvas?.prototype?.transferControlToOffscreen) {
525
+ const nativeToString = Function.prototype.toString.call(htmlCanvas.prototype.transferControlToOffscreen);
460
526
  return !nativeToString.includes('[native code]');
461
527
  }
462
528
  } catch (e) {}
@@ -487,7 +553,7 @@ const ClientLibrary = {
487
553
  },
488
554
 
489
555
  /**
490
- * Initialise l'espace Proof-of-Space persistant dans l'IndexedDB locale.
556
+ * Initializes persistent Proof-of-Space allocation in local IndexedDB.
491
557
  */
492
558
  async initializeSpace(seed, sizeMb) {
493
559
  return new Promise((resolve, reject) => {
@@ -529,7 +595,7 @@ const ClientLibrary = {
529
595
  },
530
596
 
531
597
  /**
532
- * Lit un bloc spécifique de l'IndexedDB locale sous format hexadécimal.
598
+ * Reads a specific block from local IndexedDB formatted as hex.
533
599
  */
534
600
  async readSpaceBlock(blockIdx) {
535
601
  return new Promise((resolve, reject) => {
@@ -555,7 +621,7 @@ const ClientLibrary = {
555
621
  },
556
622
 
557
623
  /**
558
- * Résout le Proof-of-Space en combinant optionnellement le bloc du pair.
624
+ * Solves Proof-of-Space by optionally combining a peer's block.
559
625
  */
560
626
  async solveSpaceChallenge(seed, queries, nonce, clientSecret, peerBlock = '') {
561
627
  const blocks = [];
@@ -572,8 +638,8 @@ const ClientLibrary = {
572
638
  },
573
639
 
574
640
  /**
575
- * Démarre le suivi des mouvements de la souris pour calculer l'entropie.
576
- * À appeler une fois sur la page.
641
+ * Starts tracking mouse movements to evaluate entropy.
642
+ * Call once per page lifecycle.
577
643
  */
578
644
  startMouseEntropyTracker() {
579
645
  // Utiliser un drapeau pour éviter d'attacher l'écouteur plusieurs fois
@@ -581,9 +647,9 @@ const ClientLibrary = {
581
647
  this._mouseTrackerAttached = true;
582
648
 
583
649
  document.addEventListener('mousemove', (e) => {
584
- // NOUVEAU: Capturer une série de points {x, y, t}
650
+ // Capture trajectory points {x, y, t}
585
651
  if (mouseMovementsHistory.length >= MOUSE_HISTORY_MAX) {
586
- // Garder la taille de l'historique constante pour éviter une consommation mémoire excessive.
652
+ // Cap history size to prevent unbounded memory growth
587
653
  mouseMovementsHistory.shift();
588
654
  }
589
655
  mouseMovementsHistory.push({
@@ -595,8 +661,8 @@ const ClientLibrary = {
595
661
  },
596
662
 
597
663
  /**
598
- * Démarre le suivi de la dynamique de frappe pour calculer le dwell time et le flight time (digraphie/trigraphie).
599
- * À appeler une fois sur la page.
664
+ * Starts tracking keystroke dynamics to evaluate dwell and flight times.
665
+ * Call once per page lifecycle.
600
666
  */
601
667
  startKeystrokeDynamicsTracker() {
602
668
  // S'assurer de ne pas attacher l'écouteur plusieurs fois
@@ -694,18 +760,18 @@ const ClientLibrary = {
694
760
  }, { passive: true });
695
761
  },
696
762
  /**
697
- * Initialise ou réinitialise les honeypots côté client pour une détection immédiate.
698
- * Les anciens écouteurs sont supprimés avant d'en ajouter de nouveaux.
699
- * @param {string[]} honeypotFieldNames - Noms des champs de formulaire cachés.
763
+ * Initializes or resets client-side honeypots for immediate detection.
764
+ * Previous listeners are removed before attaching new ones.
765
+ * @param {string[]} honeypotFieldNames - Hidden form field names.
700
766
  */
701
767
  initializeHoneypots(honeypotFieldNames) {
702
- // 1. Nettoyer les anciens écouteurs
768
+ // 1. Clean up existing listeners
703
769
  activeHoneypotListeners.forEach((listener, field) => {
704
770
  field.removeEventListener('input', listener);
705
771
  });
706
772
  activeHoneypotListeners.clear();
707
773
 
708
- // 2. Ajouter les nouveaux écouteurs sur le DOM classique
774
+ // 2. Attach new listeners to existing DOM elements
709
775
  honeypotFieldNames.forEach(fieldName => {
710
776
  const field = document.querySelector(`[name="${fieldName}"]`);
711
777
  if (field) {
@@ -714,11 +780,11 @@ const ClientLibrary = {
714
780
  field.removeEventListener('input', listener);
715
781
  };
716
782
  field.addEventListener('input', listener);
717
- activeHoneypotListeners.set(field, listener); // On stocke la référence
783
+ activeHoneypotListeners.set(field, listener);
718
784
  }
719
785
  });
720
786
 
721
- // 3. Générer des champs d'input pièges masqués dans un Shadow DOM fermé
787
+ // 3. Generate trap inputs inside a closed Shadow DOM
722
788
  if (typeof document !== 'undefined' && honeypotFieldNames.length > 0) {
723
789
  const host = document.createElement('div');
724
790
  host.setAttribute('aria-hidden', 'true');
@@ -729,7 +795,7 @@ const ClientLibrary = {
729
795
 
730
796
  const shadow = host.attachShadow({ mode: 'closed' });
731
797
 
732
- // Aléatisation des classes, variables CSS et balises de structure
798
+ // Randomize classes, CSS variables, and layout tags
733
799
  const wrapperClass = genRandStr(10);
734
800
  const posStateVar = `--${genRandStr(8)}`;
735
801
  const offValVar = `--${genRandStr(8)}`;
@@ -778,7 +844,7 @@ const ClientLibrary = {
778
844
  input.addEventListener('change', trigger, { passive: true });
779
845
  input.addEventListener('focus', trigger, { passive: true });
780
846
 
781
- // Imbrication polymorphique du label et de l'input
847
+ // Polymorphic nesting of label and input
782
848
  const nestingType = Math.floor(secureRandom() * 3);
783
849
  if (nestingType === 1) {
784
850
  label.appendChild(input);
@@ -800,21 +866,22 @@ const ClientLibrary = {
800
866
  }
801
867
  },
802
868
  /**
803
- * Sollicite silencieusement l'attestation matérielle FIDO2 (WebAuthn)
804
- * pour ancrer l'identité physique de l'appareil via TPM / Secure Enclave.
869
+ * Silently requests FIDO2 hardware attestation (WebAuthn)
870
+ * to anchor physical device identity via TPM / Secure Enclave.
805
871
  * @returns {Promise<object|null>}
806
872
  */
807
873
  async getWebAuthnAnchor() {
808
- if (typeof window === 'undefined' || !window.PublicKeyCredential) return null;
874
+ const win = typeof window !== 'undefined' ? window : null;
875
+ if (!win || !win.PublicKeyCredential) return null;
809
876
  try {
810
- const isPlatformAvailable = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
877
+ const isPlatformAvailable = await win.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
811
878
  if (!isPlatformAvailable) return null;
812
879
 
813
880
  const storedCredIdBase64 = localStorage.getItem('fp_webauthn_cred_id');
814
881
  const challenge = new Uint8Array([109, 101, 116, 114, 105, 99, 115, 95, 97, 110, 99, 104, 111, 114, 95, 115, 101, 99, 117, 114, 101]); // Static stable challenge
815
882
 
816
883
  if (storedCredIdBase64) {
817
- // Tentative d'assertion silencieuse (Authentification)
884
+ // Silent assertion attempt (Authentication)
818
885
  const credentialId = Uint8Array.from(atob(storedCredIdBase64), c => c.charCodeAt(0));
819
886
  const assertion = await navigator.credentials.get({
820
887
  publicKey: {
@@ -837,7 +904,7 @@ const ClientLibrary = {
837
904
  };
838
905
  }
839
906
  } else {
840
- // Création silencieuse (Enregistrement initial)
907
+ // Silent creation attempt (Registration)
841
908
  const options = {
842
909
  publicKey: {
843
910
  challenge,
@@ -875,34 +942,38 @@ const ClientLibrary = {
875
942
  }
876
943
  }
877
944
  } catch (e) {
878
- // Ignore et bypass silencieusement pour garder l'invisibilité en cas de non-support
945
+ // Bypass silently when unsupported to remain non-intrusive
879
946
  console.log('[WebAuthn-Anchor] Silent attestation bypassed:', e.message);
880
947
  }
881
948
  return null;
882
949
  },
883
950
 
884
951
  /**
885
- * Récupère les métriques comportementales collectées.
886
- * À appeler avant d'envoyer une requête sensible.
952
+ * Retrieves collected client behavioral metrics.
953
+ * Call before submitting sensitive requests.
887
954
  * @returns {ClientBehaviorMetrics}
888
955
  */
889
956
  getClientBehaviorMetrics() {
890
957
  // Add history length as a behavioral signal.
891
958
  metrics.historyLength = window.history.length;
892
959
 
893
- // Ajoute un timestamp au moment de la collecte pour la détection de rejeu.
960
+ // Attach collection timestamp for replay detection
894
961
  metrics.clicksHistory = clicksHistory;
895
962
  metrics.clientTimestamp = Date.now();
896
963
 
897
964
  metrics.touchMovementsHistory = touchMovementsHistory;
898
- // NOUVEAU: Inclure l'historique des mouvements de la souris pour une analyse côté serveur.
965
+ if (ClientLibrary.detectTamperedPrototypes()) {
966
+ metrics.prototypeTampered = true;
967
+ }
968
+ metrics.motionVariance = metrics.motionVariance ?? -1;
969
+ // Include mouse trajectory history for server-side evaluation
899
970
  metrics.mouseMovementsHistory = mouseMovementsHistory;
900
971
 
901
972
  // NOUVEAU: Keystroke dynamics metrics (dwell and flight times)
902
973
  metrics.keystrokeDwellTimes = keystrokeDwellTimes;
903
974
  metrics.keystrokeFlightTimes = keystrokeFlightTimes;
904
975
 
905
- // Calcule la latence moyenne des frappes
976
+ // Calculate mean keystroke latency
906
977
  if (keystrokeLatencies.length > 0) {
907
978
  const sum = keystrokeLatencies.reduce((a, b) => a + b, 0);
908
979
  metrics.keystrokeLatency = sum / keystrokeLatencies.length;
@@ -913,7 +984,7 @@ const ClientLibrary = {
913
984
  },
914
985
 
915
986
  /**
916
- * Enrichit une requête fetch avec les en-têtes de fingerprinting et de comportement.
987
+ * Enriches a fetch request with fingerprinting and behavioral headers.
917
988
  * @param {RequestInfo} resource
918
989
  * @param {RequestInit} [options]
919
990
  * @returns {Promise<Response>}
@@ -933,18 +1004,16 @@ const ClientLibrary = {
933
1004
  return fetch(resource, options);
934
1005
  },
935
1006
 
936
- // --- Système d'interception de Fetch robuste et anti-conflit ---
937
-
1007
+ // --- Fetch Interception Chain ---
938
1008
  _isFetchPatched: false,
939
1009
  _interceptorChain: [],
940
- // On stocke la fonction fetch originale et on la lie à son contexte (window)
941
- // pour éviter les erreurs "Illegal invocation" si une autre lib la modifie.
1010
+ // Store original fetch bound to window to prevent illegal invocation errors
942
1011
  _originalFetch: (typeof window !== 'undefined') ? window.fetch.bind(window) : null,
943
1012
 
944
1013
  /**
945
1014
  * Adds an interceptor function to the `fetch` chain.
946
- * Chaque intercepteur reçoit `resource`, `options`, et une fonction `next`.
947
- * Il DOIT appeler `next(resource, options)` pour continuer la chaîne.
1015
+ * Each interceptor receives `resource`, `options`, and a `next` function.
1016
+ * Must call `next(resource, options)` to continue the chain.
948
1017
  * @param {function(RequestInfo, RequestInit, function): Promise<Response>} interceptor
949
1018
  */
950
1019
  addFetchInterceptor(interceptor) {
@@ -959,14 +1028,11 @@ const ClientLibrary = {
959
1028
 
960
1029
  this._isFetchPatched = true;
961
1030
  window.fetch = (resource, options) => {
962
- // Le "dispatcher" qui exécute la chaîne.
963
1031
  const dispatch = (index, res, opts) => {
964
1032
  if (index >= this._interceptorChain.length) {
965
- // Fin de la chaîne, on appelle le fetch original.
966
1033
  return this._originalFetch(res, opts);
967
1034
  }
968
1035
  const nextInterceptor = this._interceptorChain[index];
969
- // Appelle l'intercepteur actuel en lui passant la fonction pour appeler le suivant.
970
1036
  return nextInterceptor(res, opts, (nextRes, nextOpts) => dispatch(index + 1, nextRes, nextOpts));
971
1037
  };
972
1038
  return dispatch(0, resource, options || {});
@@ -974,20 +1040,19 @@ const ClientLibrary = {
974
1040
  },
975
1041
 
976
1042
  /**
977
- * La fonction qui est appelée lorsqu'un honeypot est déclenché.
1043
+ * Invoked when a honeypot trigger occurs.
978
1044
  * @private
979
1045
  */
980
1046
  onHoneypotTrigger() {
981
1047
  metrics.honeypotInteraction = true;
982
- // Émettre un événement pour que l'application puisse réagir.
1048
+ // Dispatch event allowing host applications to react
983
1049
  this._dispatchEvent('honeypotTriggered');
984
1050
  },
985
1051
 
986
1052
  /**
987
- * Initialise l'intercepteur de fingerprinting.
988
- * Il s'ajoute à la chaîne d'interception sans écraser les autres.
989
- * @param {string[]} [targetDomains] - Optionnel. Liste de domaines à protéger.
990
- * Si non fourni, protège les requêtes de même origine.
1053
+ * Initializes the fingerprinting fetch interceptor.
1054
+ * Appends to interceptor chain without clobbering existing handlers.
1055
+ * @param {string[]} [targetDomains] - Domains to protect. Defaults to same-origin.
991
1056
  */
992
1057
  initializeFetch(targetDomains = []) {
993
1058
  const fingerprintInterceptor = async (resource, options, next) => {
@@ -996,14 +1061,11 @@ const ClientLibrary = {
996
1061
 
997
1062
  try {
998
1063
  const url = new URL(requestUrl, window.location.origin);
999
- // Protéger si la liste de domaines est vide ET que la requête est de même origine,
1000
- // OU si le domaine de la requête est dans la liste fournie.
1064
+ // Protect if targetDomains is empty and same-origin, or host matches targetDomains
1001
1065
  shouldProtect = (targetDomains.length === 0 && url.origin === window.location.origin) ||
1002
1066
  (targetDomains.length > 0 && targetDomains.includes(url.hostname));
1003
1067
  } catch (e) {
1004
- // Si l'URL est relative (ex: '/api/data'), new URL() ne lèvera pas d'erreur.
1005
- // Ce bloc est une sécurité pour les cas où l'URL serait malformée.
1006
- // On protège par défaut si aucune liste de domaines n'est spécifiée.
1068
+ // Fallback for malformed URLs
1007
1069
  shouldProtect = targetDomains.length === 0;
1008
1070
  }
1009
1071
 
@@ -1017,12 +1079,12 @@ const ClientLibrary = {
1017
1079
  options.headers = headers;
1018
1080
  }
1019
1081
 
1020
- // Passe la main à l'intercepteur suivant dans la chaîne.
1082
+ // Forward to next interceptor in chain
1021
1083
  return next(resource, options);
1022
1084
  };
1023
1085
 
1024
1086
  this.addFetchInterceptor(fingerprintInterceptor);
1025
- }, // <-- VIRGULE AJOUTÉE ICI
1087
+ },
1026
1088
 
1027
1089
  /**
1028
1090
  * Injects visually hidden "honeypot" links into the DOM to trap bots.
@@ -1043,7 +1105,7 @@ const ClientLibrary = {
1043
1105
 
1044
1106
  const shadow = host.attachShadow({ mode: 'closed' });
1045
1107
 
1046
- // Aléatisation des classes, variables CSS et balises de structure
1108
+ // Randomize classes, CSS variables, and layout tags
1047
1109
  const wrapperClass = genRandStr(10);
1048
1110
  const layoutPosVar = `--${genRandStr(8)}`;
1049
1111
  const offsetValVar = `--${genRandStr(8)}`;
@@ -1089,7 +1151,7 @@ const ClientLibrary = {
1089
1151
  link.className = linkColorClass;
1090
1152
  link.id = genRandStr(8);
1091
1153
 
1092
- // Structure interne polymorphique du lien
1154
+ // Polymorphic link content structure
1093
1155
  const contentNestingType = Math.floor(secureRandom() * 3);
1094
1156
  if (contentNestingType === 1) {
1095
1157
  const span = document.createElement('span');
@@ -1112,7 +1174,7 @@ const ClientLibrary = {
1112
1174
  link.addEventListener('focus', trigger, { passive: true });
1113
1175
  link.addEventListener('mouseover', trigger, { passive: true });
1114
1176
 
1115
- // Imbrication du lien de manière polymorphique
1177
+ // Polymorphic link nesting
1116
1178
  if (secureRandom() > 0.5) {
1117
1179
  const itemContainer = document.createElement('span');
1118
1180
  itemContainer.className = genRandStr(5);
@@ -1127,73 +1189,72 @@ const ClientLibrary = {
1127
1189
  document.body.appendChild(host);
1128
1190
  },
1129
1191
  /**
1130
- * Intercepte une réponse de challenge JSON, le résout, et réessaie la requête.
1131
- * @param {Response} response - La réponse initiale (potentiellement 429).
1132
- * @param {RequestInfo} resource - La ressource de la requête originale.
1133
- * @param {RequestInit} options - Les options de la requête originale.
1134
- * @returns {Promise<Response>} - La réponse de la requête réessayée.
1192
+ * Intercepts a JSON challenge response, solves it, and retries the request.
1193
+ * @param {Response} response - Initial response (status 404/challenge).
1194
+ * @param {RequestInfo} resource - Original request resource.
1195
+ * @param {RequestInit} options - Original request options.
1196
+ * @returns {Promise<Response>} - Retried response.
1135
1197
  * @private
1136
1198
  */
1137
1199
  async solveChallengeAndRetry(response, resource, options) {
1138
- if (response.status !== 404 || !response.headers.get('content-type')?.includes('application/json') || response.bodyUsed) {
1200
+ if (!response || response.status !== 404 || !response.headers?.get?.('content-type')?.includes('application/json') || response.bodyUsed) {
1139
1201
  return response;
1140
1202
  }
1141
1203
 
1142
1204
  try {
1143
1205
  const challengeData = await response.json();
1144
1206
  if (!challengeData.challenge || !challengeData.challenge.type) {
1145
- return response; // Pas un challenge JSON valide
1207
+ return response; // Not a valid JSON challenge
1146
1208
  }
1147
1209
 
1148
1210
  console.log(`[Fingerprint] Received a '${challengeData.challenge.type}' challenge. Solving...`);
1149
1211
  this._dispatchEvent('challengeReceived', { challenge: challengeData.challenge });
1150
1212
 
1151
- // L'empreinte de l'appareil qui résout le challenge est cruciale.
1213
+ // The device fingerprint solving the challenge is crucial
1152
1214
  const solverFp = this.getDeviceFingerprint();
1153
1215
  const solutionWrapper = await solveChallenge(challengeData.challenge, solverFp);
1154
1216
  console.log('[Fingerprint] Challenge solved. Retrying original request.');
1155
1217
 
1156
1218
  this._dispatchEvent('challengeSolved', { solution: solutionWrapper.rawSolution });
1157
- // Ajouter la solution aux paramètres de la requête pour le nouvel essai
1219
+ // Append solution parameters to retry request URL
1158
1220
  const url = new URL((resource instanceof Request) ? resource.url : String(resource), window.location.origin);
1159
- // La logique de formatage est maintenant cachée dans la classe ChallengeSolution.
1160
1221
  solutionWrapper.applyToUrl(url);
1161
1222
 
1162
- // On ajoute l'empreinte du solveur à la requête de réessai.
1223
+ // Attach solver fingerprint to retry request
1163
1224
  url.searchParams.set('pow_fp', solverFp);
1164
1225
 
1165
- // On utilise la chaîne d'intercepteurs pour la requête réessayée,
1166
- // ce qui garantit que le fetch original est appelé avec le bon contexte.
1167
- // Cela évite de réintroduire l'erreur "Illegal invocation".
1226
+ // Use fetch to retry with appropriate execution context
1168
1227
  return window.fetch(url.toString(), options);
1169
1228
  } catch (e) {
1170
1229
  console.error('[Fingerprint] Failed to solve or retry challenge:', e);
1171
1230
  return response; // Retourne la réponse 429 originale en cas d'échec
1172
1231
  }
1173
- }, // <-- VIRGULE AJOUTÉE ICI
1232
+ },
1174
1233
 
1175
1234
  /**
1176
- * Initialise toutes les protections côté client en une seule fois.
1177
- * Tente également de charger le module WASM si `wasmPath` est fourni.
1178
- * C'est la méthode d'initialisation recommandée.
1179
- * @param {ClientConfig} [config={}] - L'objet de configuration.
1235
+ * Initializes all client-side protections in one call.
1236
+ * Also loads WASM module if `wasmPath` is provided.
1237
+ * Recommended initialization approach.
1238
+ * @param {ClientConfig} [config={}] - Client configuration options.
1180
1239
  */
1181
1240
  initializeClient(config = {}) {
1182
1241
  const {
1183
1242
  mouse = true,
1184
1243
  keystrokes = true,
1185
1244
  clicks = true, // Add new option
1186
- touches = true, // Nouveau paramètre tactiles
1245
+ touches = true, // Touch event tracking
1246
+ motion = true,
1187
1247
  rendering = true,
1188
- phantomTraps = true, // NOUVEAU
1248
+ phantomTraps = true,
1189
1249
  honeypots = [],
1190
- trapUrls = [], // Nouveau paramètre pour les URL pièges
1191
- wasmPath, // Nouveau paramètre
1250
+ trapUrls = [], // Trap URLs
1251
+ wasmPath,
1252
+ workerPath, // NOUVEAU
1192
1253
  fetch: fetchConfig = {}
1193
1254
  } = config;
1194
1255
 
1195
1256
  negotiateSessionKey();
1196
- // Tentative de chargement du WASM si le chemin est fourni
1257
+ // Load WASM module if path provided
1197
1258
  if (wasmPath) {
1198
1259
  this.initializeWasm(wasmPath);
1199
1260
  }
@@ -1201,6 +1262,9 @@ const ClientLibrary = {
1201
1262
  if (mouse) {
1202
1263
  this.startMouseEntropyTracker();
1203
1264
  }
1265
+ if (workerPath) {
1266
+ this.workerPath = workerPath;
1267
+ }
1204
1268
  if (keystrokes) {
1205
1269
  this.startKeystrokeDynamicsTracker();
1206
1270
  }
@@ -1209,6 +1273,12 @@ const ClientLibrary = {
1209
1273
  }
1210
1274
  if (touches) {
1211
1275
  this.startTouchEventTracker();
1276
+ }
1277
+ if (motion) {
1278
+ this.startMotionTracker();
1279
+ }
1280
+ if (ClientLibrary.detectTamperedPrototypes()) {
1281
+ metrics.prototypeTampered = true;
1212
1282
  }
1213
1283
  if (rendering) {
1214
1284
  this.startRenderingTracker();
@@ -1220,19 +1290,19 @@ const ClientLibrary = {
1220
1290
  this.initializeHoneypots(honeypots);
1221
1291
  }
1222
1292
 
1223
- // Injection dynamique des liens pièges au démarrage
1293
+ // Dynamically inject trap links on startup
1224
1294
  if (trapUrls.length > 0) {
1225
1295
  this.injectTrapLinks(trapUrls);
1226
1296
  }
1227
- // On active l'interception si `fetch` est configuré, même avec un objet vide.
1297
+ // Enable fetch interception if configured
1228
1298
  if (config.fetch) {
1229
1299
  this.initializeFetch(fetchConfig.targetDomains);
1230
1300
 
1231
- // Ajoute l'intercepteur pour la résolution de challenge
1301
+ // Add challenge resolution interceptor
1232
1302
  if (fetchConfig.handleChallenges !== false) {
1233
1303
  this.addFetchInterceptor(async (resource, options, next) => {
1234
1304
  const originalResponse = await next(resource, options);
1235
- // On clone la réponse pour que la lecture du corps par solveChallengeAndRetry ne la consomme pas pour l'appelant original.
1305
+ // Clone response to prevent draining the body stream for original caller
1236
1306
  return this.solveChallengeAndRetry(originalResponse.clone(), resource, options);
1237
1307
  });
1238
1308
  }
@@ -1240,18 +1310,30 @@ const ClientLibrary = {
1240
1310
  },
1241
1311
 
1242
1312
  /**
1243
- * Tente de charger et d'initialiser le module WebAssembly pour un hachage plus rapide.
1244
- * Si le chargement échoue, il se rabat silencieusement sur l'implémentation JS.
1245
- * @param {string} wasmPath - Le chemin vers le script de chargement du module WASM (ex: '/fp.js').
1313
+ * Attempts to load and initialize WebAssembly module for accelerated hashing.
1314
+ * Falls back to JavaScript implementation on error.
1315
+ * @param {string} wasmPath - Path to WASM loader script or binary.
1246
1316
  */
1247
1317
  async initializeWasm(wasmPath) {
1248
1318
  try {
1249
1319
  // Direct standalone polymorphic WebAssembly loading
1250
1320
  if (wasmPath.endsWith('.wasm')) {
1251
- const response = await fetch(wasmPath);
1252
- const arrayBuffer = await response.arrayBuffer();
1253
- const module = await WebAssembly.compile(arrayBuffer);
1254
- const instance = await WebAssembly.instantiate(module, {});
1321
+ let instance;
1322
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
1323
+ try {
1324
+ const response = await fetch(wasmPath);
1325
+ const result = await WebAssembly.instantiateStreaming(response, {});
1326
+ instance = result.instance;
1327
+ } catch (streamingError) {
1328
+ console.warn('[Fingerprint] WebAssembly.instantiateStreaming failed, falling back to compile:', streamingError);
1329
+ }
1330
+ }
1331
+ if (!instance) {
1332
+ const response = await fetch(wasmPath);
1333
+ const arrayBuffer = await response.arrayBuffer();
1334
+ const module = await WebAssembly.compile(arrayBuffer);
1335
+ instance = await WebAssembly.instantiate(module, {});
1336
+ }
1255
1337
  const exports = instance.exports;
1256
1338
  const memory = exports.memory;
1257
1339
  activeCyrb53 = (str) => {
@@ -1267,7 +1349,7 @@ const ClientLibrary = {
1267
1349
  }
1268
1350
  return;
1269
1351
  }
1270
- // 1. Injecter le script qui charge le module WASM
1352
+ // 1. Inject WASM loader script
1271
1353
  const script = document.createElement('script');
1272
1354
  script.src = wasmPath;
1273
1355
  await new Promise((resolve, reject) => {
@@ -1276,12 +1358,12 @@ const ClientLibrary = {
1276
1358
  document.head.appendChild(script);
1277
1359
  });
1278
1360
 
1279
- // 2. Attendre que la fonction globale `createFingerprintModule` soit disponible
1361
+ // 2. Await global module factory
1280
1362
  if (typeof window.createFingerprintModule !== 'function') {
1281
1363
  throw new Error('WASM loader script did not expose createFingerprintModule.');
1282
1364
  }
1283
1365
 
1284
- // 3. Initialiser le module
1366
+ // 3. Initialize module
1285
1367
  const wasmUrl = wasmPath.replace(/\.js$/, '.wasm');
1286
1368
  const wasmModule = await window.createFingerprintModule({
1287
1369
  instantiateWasm: (imports, successCallback) => {
@@ -1342,13 +1424,13 @@ const ClientLibrary = {
1342
1424
  window.wasmModule = wasmModule;
1343
1425
  ClientLibrary.wasmModule = wasmModule;
1344
1426
 
1345
- // Pooling de mémoire statique : pré-allocation d'un buffer réutilisable de 4 Ko
1346
- // Évite d'allouer/libérer de la mémoire sur le tas Emscripten lors des frappes clavier/mouvements souris rapides
1427
+ // Static memory pool: 4KB pre-allocated buffer
1428
+ // Avoids heap malloc/free churn on rapid mouse and keyboard tracking
1347
1429
  const staticBufferSize = 4096;
1348
1430
  const staticBufferPtr = wasmModule._malloc(staticBufferSize);
1349
1431
  const encoder = new TextEncoder();
1350
1432
 
1351
- // 4. Remplacer la fonction de hachage par la version WASM optimisée via le buffer statique
1433
+ // 4. Swap hash function with WASM implementation
1352
1434
  activeCyrb53 = (str) => {
1353
1435
  const bytes = encoder.encode(str);
1354
1436
  if (bytes.length < staticBufferSize) {
@@ -1356,12 +1438,12 @@ const ClientLibrary = {
1356
1438
  wasmModule.HEAPU8[staticBufferPtr + bytes.length] = 0; // null-terminator
1357
1439
  return wasmModule._hash_string(staticBufferPtr);
1358
1440
  }
1359
- // Fallback dynamique sécurisé si la chaîne dépasse la taille du buffer statique
1441
+ // Fallback if string exceeds static buffer size
1360
1442
  return wasmModule._hash_string(str);
1361
1443
  };
1362
1444
 
1363
1445
  console.log('[Fingerprint] WASM module loaded successfully. Using fast hashing.');
1364
- // NOUVEAU: Ajoute un indicateur à l'empreinte pour que le serveur sache que le WASM est actif.
1446
+ // Tag fingerprint with wasm marker
1365
1447
  if (this._cachedBuilder) {
1366
1448
  this._cachedBuilder.addRaw('wasm', 'true');
1367
1449
  }
@@ -1373,19 +1455,19 @@ const ClientLibrary = {
1373
1455
 
1374
1456
  /**
1375
1457
  * @typedef {object} ClientBehaviorMetrics
1376
- * @property {number} mouseEntropy - Entropie des mouvements de la souris.
1377
- * @property {Array<{x: number, y: number, t: number}>} mouseMovementsHistory - Historique des points de la souris.
1378
- * @property {number} keystrokeLatency - Latence moyenne entre les frappes.
1379
- * @property {boolean} honeypotInteraction - Vrai si un honeypot a été touché.
1380
- * @property {Array<{x: number, y: number, t: number, targetId: string}>} clicksHistory - Historique des clics.
1381
- * @property {Array<{x: number, y: number, t: number, p: number, r: number, num: number}>} touchMovementsHistory - Historique des glissements tactiles.
1382
- * @property {number} historyLength - La longueur de l'historique de session du navigateur (`window.history.length`).
1383
- * @property {number} clientTimestamp - Timestamp (Date.now()) de la collecte des métriques.
1384
- * @property {string[]} [trapUrls] - URLs pièges à injecter dynamiquement.
1458
+ * @property {number} mouseEntropy - Mouse entropy estimate.
1459
+ * @property {Array<{x: number, y: number, t: number}>} mouseMovementsHistory - Mouse movement coordinates.
1460
+ * @property {number} keystrokeLatency - Average latency between keystrokes.
1461
+ * @property {boolean} honeypotInteraction - True if honeypot was triggered.
1462
+ * @property {Array<{x: number, y: number, t: number, targetId: string}>} clicksHistory - Click interaction history.
1463
+ * @property {Array<{x: number, y: number, t: number, p: number, r: number, num: number}>} touchMovementsHistory - Touch movement trajectory.
1464
+ * @property {number} historyLength - Browser window.history.length.
1465
+ * @property {number} clientTimestamp - Metrics collection timestamp.
1466
+ * @property {string[]} [trapUrls] - Dynamically injected trap URLs.
1385
1467
  */
1386
1468
  /** @type {ClientBehaviorMetrics} */
1387
1469
  const metrics = {
1388
- mouseEntropy: 0, // Conservé pour la compatibilité, mais l'analyse se fait maintenant sur l'historique
1470
+ mouseEntropy: 0, // Retained for compatibility; analysis primarily evaluates trajectory history
1389
1471
  mouseMovementsHistory: [],
1390
1472
  touchMovementsHistory: [],
1391
1473
  clicksHistory: [],
@@ -1397,18 +1479,18 @@ const metrics = {
1397
1479
  };
1398
1480
 
1399
1481
  let lastMousePos = { x: 0, y: 0 };
1400
- let mouseMovementsHistory = []; // NOUVEAU: Historique des points de la souris
1401
- let touchMovementsHistory = []; // NOUVEAU: Historique des gestes tactiles
1482
+ let mouseMovementsHistory = []; // Mouse movement trajectory
1483
+ let touchMovementsHistory = []; // Touch movement trajectory
1402
1484
  const TOUCH_HISTORY_MAX = 100;
1403
- const MOUSE_HISTORY_MAX = 100; // Limite le nombre de points stockés
1485
+ const MOUSE_HISTORY_MAX = 100;
1404
1486
  let clicksHistory = [];
1405
1487
  const CLICKS_HISTORY_MAX = 50;
1406
- let activeHoneypotListeners = new Map(); // Garde une trace des écouteurs actifs
1488
+ let activeHoneypotListeners = new Map();
1407
1489
  let keystrokeTimestamps = [];
1408
- let keystrokeLatencies = []; // NOUVEAU: Tableau dédié pour les latences
1490
+ let keystrokeLatencies = [];
1409
1491
  let keystrokeDwellTimes = [];
1410
1492
  let keystrokeFlightTimes = [];
1411
- const KEYSTROKE_HISTORY_MAX = 20; // On garde l'historique des 20 dernières frappes
1493
+ const KEYSTROKE_HISTORY_MAX = 20; // Keep the last 20 keystroke events
1412
1494
 
1413
1495
 
1414
1496
 
@@ -1422,6 +1504,8 @@ export const startKeystrokeDynamicsTracker = ClientLibrary.startKeystrokeDynamic
1422
1504
  export const startClickTracker = ClientLibrary.startClickTracker.bind(ClientLibrary);
1423
1505
  export const startTouchEventTracker = ClientLibrary.startTouchEventTracker.bind(ClientLibrary);
1424
1506
  export const startRenderingTracker = ClientLibrary.startRenderingTracker.bind(ClientLibrary);
1507
+ export const startMotionTracker = ClientLibrary.startMotionTracker.bind(ClientLibrary);
1508
+ export const detectTamperedPrototypes = ClientLibrary.detectTamperedPrototypes.bind(ClientLibrary);
1425
1509
  export const initializeHoneypots = ClientLibrary.initializeHoneypots.bind(ClientLibrary);
1426
1510
  export const getClientBehaviorMetrics = ClientLibrary.getClientBehaviorMetrics.bind(ClientLibrary);
1427
1511
  export const protectedFetch = ClientLibrary.protectedFetch.bind(ClientLibrary);