@anonympins/fingerprint 0.5.2 → 0.5.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/package.json +1 -1
  3. package/src/js/build-client.js +67 -67
  4. package/src/js/fingerprint.client.js +280 -38
  5. package/src/js/fingerprint.client.obfuscated.js +1 -1
  6. package/src/js/fingerprint.js +474 -163
  7. package/src/js/fingerprint.utils.js +213 -213
  8. package/src/js/library.js +24 -8
  9. package/src/js/mongodb-store.js +165 -79
  10. package/src/js/obfuscation.worker.js +23 -0
  11. package/src/js/pow.solver.inline.js +509 -14
  12. package/src/js/pow.solver.js +350 -32
  13. package/src/js/problem-manager.js +1 -1
  14. package/src/js/redis-store.js +114 -42
  15. package/src/js/tests/cross_parity.test.js +175 -0
  16. package/src/js/tests/dns-circuit-breaker.test.js +55 -0
  17. package/src/js/tests/fingerprint.client.init.test.js +143 -143
  18. package/src/js/tests/fingerprint.client.test.js +128 -128
  19. package/src/js/tests/fingerprint.test.js +57 -2
  20. package/src/js/tests/ip-reputation.test.js +1 -1
  21. package/src/js/tests/obfuscation.test.js +48 -0
  22. package/src/js/tests/pow.solver.test.js +5 -2
  23. package/src/js/tests/usefulWorkSolver.test.js +162 -0
  24. package/src/php/AutoTuner.php +42 -0
  25. package/src/php/Challenge/ChallengeUtils.php +21 -8
  26. package/src/php/FingerprintClient.php +22 -5
  27. package/src/php/FingerprintEngine.php +233 -57
  28. package/src/php/Optimization/Optimization.php +23 -16
  29. package/src/php/Optimization/OptimizationOperators.php +27 -0
  30. package/src/php/ProblemManager.php +1 -1
  31. package/src/php/Store/MongoDbStore.php +184 -104
  32. package/src/php/Store/RedisStore.php +91 -11
  33. package/src/php/Tests/ChallengeUtilsTest.php +285 -285
  34. package/src/php/Tests/DnsCircuitBreakerTest.php +106 -0
  35. package/src/php/Tests/IpReputationTest.php +1 -1
  36. package/src/php/Tests/PowTest.php +1 -1
  37. package/src/php/Tests/RequestUtilsTest.php +29 -0
  38. package/src/php/Tests/TLSClientHelloParserTest.php +177 -117
  39. package/src/php/Utils/BigInt.php +202 -202
  40. package/src/php/Utils/RequestUtils.php +74 -21
  41. package/src/php/Utils/TLSClientHelloParser.php +212 -116
@@ -9,6 +9,15 @@
9
9
 
10
10
  'use strict';
11
11
 
12
+ function secureRandom() {
13
+ if (typeof window !== 'undefined' && (window.crypto || window.msCrypto)) {
14
+ const array = new Uint32Array(1);
15
+ (window.crypto || window.msCrypto).getRandomValues(array);
16
+ return array[0] / 0x100000000;
17
+ }
18
+ return Math.random();
19
+ }
20
+
12
21
  function cyrb53(str, seed = 0) {
13
22
  const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
14
23
  let h1 = 0xdeadbeef ^ seed,
@@ -269,15 +278,17 @@ export async function solveCpuTarget(message, target) {
269
278
  */
270
279
  export async function solveMemory(seed, difficulty) {
271
280
  const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
281
+ let solution;
282
+ let useWasm = false;
272
283
  if (wasmModule && typeof wasmModule._solve_memory_challenge === 'function') {
273
284
  const encoder = new TextEncoder();
274
285
  const seedBytes = encoder.encode(seed);
275
286
  const ptr = wasmModule._malloc(seedBytes.length + 1);
276
287
  wasmModule.HEAPU8.set(seedBytes, ptr);
277
288
  wasmModule.HEAPU8[ptr + seedBytes.length] = 0; // Null-terminator
278
- const solution = wasmModule._solve_memory_challenge(ptr, difficulty);
289
+ solution = wasmModule._solve_memory_challenge(ptr, difficulty);
279
290
  wasmModule._free(ptr);
280
- return solution;
291
+ useWasm = true;
281
292
  }
282
293
 
283
294
  const size = difficulty * 1024 * 1024;
@@ -325,19 +336,21 @@ export async function solveMemory(seed, difficulty) {
325
336
  }
326
337
  const merkleRoot = tree[tree.length - 1][0];
327
338
 
328
- function readBuffer(blocks, addr) {
329
- const blockIdx = Math.floor(addr / 1024);
330
- const elementIdx = addr % 1024;
331
- return blocks[blockIdx][elementIdx];
332
- }
339
+ if (!useWasm) {
340
+ function readBuffer(blocks, addr) {
341
+ const blockIdx = Math.floor(addr / 1024);
342
+ const elementIdx = addr % 1024;
343
+ return blocks[blockIdx][elementIdx];
344
+ }
333
345
 
334
- const totalElements = numBlocks * 1024;
335
- let addr = totalElements > 0 ? readBuffer(blocks, 0) % totalElements : 0;
336
- let solution = 0;
337
- const iterations = 1024;
338
- for (let i = 0; i < iterations; i++) {
339
- addr = readBuffer(blocks, addr) % totalElements;
340
- solution ^= addr;
346
+ const totalElements = numBlocks * 1024;
347
+ let addr = totalElements > 0 ? readBuffer(blocks, 0) % totalElements : 0;
348
+ solution = 0;
349
+ const iterations = 1024;
350
+ for (let i = 0; i < iterations; i++) {
351
+ addr = readBuffer(blocks, addr) % totalElements;
352
+ solution ^= addr;
353
+ }
341
354
  }
342
355
 
343
356
  const challengedIndices = [];
@@ -430,6 +443,81 @@ export async function solveTsp(cities, targetMaxDistance) {
430
443
 
431
444
  // --- Fonctions d'optimisation copiées/adaptées de library.js pour le client ---
432
445
 
446
+ function paretoDominates(objectivesA, objectivesB) {
447
+ let aIsBetterInOne = false;
448
+ for (let i = 0; i < objectivesA.length; i++) {
449
+ if (objectivesA[i] > objectivesB[i]) {
450
+ return false;
451
+ }
452
+ if (objectivesA[i] < objectivesB[i]) {
453
+ aIsBetterInOne = true;
454
+ }
455
+ }
456
+ return aIsBetterInOne;
457
+ }
458
+
459
+ function nonDominatedSort(populationWithObjectives) {
460
+ const fronts = [[]];
461
+ for (const p1 of populationWithObjectives) {
462
+ p1.dominationCount = 0;
463
+ p1.dominatedSolutions = [];
464
+ for (const p2 of populationWithObjectives) {
465
+ if (p1 === p2) continue;
466
+ if (paretoDominates(p1.objectives, p2.objectives)) {
467
+ p1.dominatedSolutions.push(p2);
468
+ } else if (paretoDominates(p2.objectives, p1.objectives)) {
469
+ p1.dominationCount++;
470
+ }
471
+ }
472
+ if (p1.dominationCount === 0) {
473
+ p1.rank = 0;
474
+ fronts[0].push(p1);
475
+ }
476
+ }
477
+
478
+ let i = 0;
479
+ while (fronts[i] && fronts[i].length > 0) {
480
+ const nextFront = [];
481
+ for (const p1 of fronts[i]) {
482
+ for (const p2 of p1.dominatedSolutions) {
483
+ p2.dominationCount--;
484
+ if (p2.dominationCount === 0) {
485
+ p2.rank = i + 1;
486
+ nextFront.push(p2);
487
+ }
488
+ }
489
+ }
490
+ i++;
491
+ if (nextFront.length > 0) {
492
+ fronts[i] = nextFront;
493
+ }
494
+ }
495
+ return fronts;
496
+ }
497
+
498
+ function calculateCrowdingDistance(front) {
499
+ if (front.length === 0) return;
500
+ front.forEach((p) => (p.crowdingDistance = 0));
501
+ const numObjectives = front[0].objectives.length;
502
+
503
+ for (let i = 0; i < numObjectives; i++) {
504
+ front.sort((a, b) => a.objectives[i] - b.objectives[i]);
505
+ const minObj = front[0].objectives[i];
506
+ const maxObj = front[front.length - 1].objectives[i];
507
+
508
+ front[0].crowdingDistance = Infinity;
509
+ front[front.length - 1].crowdingDistance = Infinity;
510
+
511
+ if (maxObj === minObj) continue;
512
+
513
+ for (let j = 1; j < front.length - 1; j++) {
514
+ front[j].crowdingDistance +=
515
+ (front[j + 1].objectives[i] - front[j - 1].objectives[i]) /
516
+ (maxObj - minObj);
517
+ }
518
+ }
519
+ }
520
+
433
521
  const ClientOptimizers = {
434
522
  simulatedAnnealing(initialSolution, evaluator, neighbor, iterations, temp, cooling) {
435
523
  let currentSolution = initialSolution;
@@ -439,7 +527,7 @@ const ClientOptimizers = {
439
527
  for (let i = 0; i < iterations; i++) {
440
528
  const newSolution = neighbor(currentSolution);
441
529
  const newEnergy = evaluator(newSolution);
442
- if (newEnergy < currentEnergy || Math.random() < Math.exp((currentEnergy - newEnergy) / temperature)) {
530
+ if (newEnergy < currentEnergy || secureRandom() < Math.exp((currentEnergy - newEnergy) / temperature)) {
443
531
  currentSolution = newSolution;
444
532
  currentEnergy = newEnergy;
445
533
  }
@@ -458,15 +546,245 @@ const ClientOptimizers = {
458
546
  population.sort((a, b) => a.fitness - b.fitness);
459
547
  const newPopulation = [population[0]]; // Elitism
460
548
  while (newPopulation.length < popSize) {
461
- const p1 = population[Math.floor(Math.random() * (popSize / 2))];
462
- const p2 = population[Math.floor(Math.random() * (popSize / 2))];
549
+ const p1 = population[Math.floor(secureRandom() * (popSize / 2))];
550
+ const p2 = population[Math.floor(secureRandom() * (popSize / 2))];
463
551
  let offspring = crossover(p1.chromosome, p2.chromosome);
464
- if (Math.random() < 0.1) offspring = mutate(offspring);
552
+ if (secureRandom() < 0.1) offspring = mutate(offspring);
465
553
  newPopulation.push({ chromosome: offspring, fitness: fitness(offspring) });
466
554
  }
467
555
  population = newPopulation;
468
556
  }
469
557
  return population;
558
+ },
559
+
560
+ geneticAlgorithmMultiObjective(createIndividual, fitnessFunction, crossover, mutate, options = {}) {
561
+ const generations = options.generations || 50;
562
+ const populationSize = options.populationSize || 40;
563
+ const mutationRate = options.mutationRate !== undefined ? options.mutationRate : 0.1;
564
+
565
+ let population = Array.from({ length: populationSize }, () => ({
566
+ individual: createIndividual(),
567
+ }));
568
+ population.forEach((p) => (p.objectives = fitnessFunction(p.individual)));
569
+
570
+ for (let gen = 0; gen < generations; gen++) {
571
+ const offspring = [];
572
+ for (let i = 0; i < populationSize; i++) {
573
+ const parent1 = population[Math.floor(secureRandom() * population.length)];
574
+ const parent2 = population[Math.floor(secureRandom() * population.length)];
575
+ let childIndividual = crossover(parent1.individual, parent2.individual);
576
+ if (secureRandom() < mutationRate) {
577
+ childIndividual = mutate(childIndividual);
578
+ }
579
+ const child = { individual: childIndividual };
580
+ child.objectives = fitnessFunction(child.individual);
581
+ offspring.push(child);
582
+ }
583
+
584
+ const combinedPopulation = [...population, ...offspring];
585
+ const fronts = nonDominatedSort(combinedPopulation);
586
+ const newPopulation = [];
587
+ for (const front of fronts) {
588
+ if (newPopulation.length + front.length <= populationSize) {
589
+ newPopulation.push(...front);
590
+ } else {
591
+ calculateCrowdingDistance(front);
592
+ front.sort((a, b) => b.crowdingDistance - a.crowdingDistance);
593
+ const remaining = populationSize - newPopulation.length;
594
+ newPopulation.push(...front.slice(0, remaining));
595
+ break;
596
+ }
597
+ }
598
+ population = newPopulation;
599
+ }
600
+
601
+ const finalFronts = nonDominatedSort(population);
602
+ const bestFront = finalFronts.length > 0 ? finalFronts[0] : [];
603
+ const uniqueSolutionsMap = new Map();
604
+ for (const p of bestFront) {
605
+ const key = JSON.stringify(p.objectives);
606
+ if (!uniqueSolutionsMap.has(key)) {
607
+ uniqueSolutionsMap.set(key, {
608
+ solution: p.individual,
609
+ objectives: p.objectives,
610
+ });
611
+ }
612
+ }
613
+ return Array.from(uniqueSolutionsMap.values());
614
+ },
615
+
616
+ 'cpc.solve'(context, options = {}) {
617
+ const optimalBaseCommission = context.platformParams ? context.platformParams.optimalBaseCommission : 0.3;
618
+ const optimalBonusFactor = context.platformParams ? context.platformParams.optimalBonusFactor : 0.1;
619
+ const websiteQualityScore = ((context.website && context.website.relevanceScore) || 50) / 100;
620
+ const effectiveCommissionRate = Math.max(0, optimalBaseCommission - websiteQualityScore * optimalBonusFactor);
621
+
622
+ const advertiserDemand = (cpc) => {
623
+ if (cpc <= 0) return Infinity;
624
+ return (context.advertiser.credits || 0) / cpc;
625
+ };
626
+
627
+ const supply = context.estimatedImpressions || 1;
628
+ const competingAdsCount = context.competingAds ? context.competingAds.length : 0;
629
+ const competitionFactor = Math.min(2.5, 1 + competingAdsCount * 0.1);
630
+
631
+ const fitnessFunction = (cpcMultiplier) => {
632
+ const adjustedCPC = 1.0 * cpcMultiplier * competitionFactor;
633
+ if (adjustedCPC < 0.1) return [Infinity, Infinity, Infinity];
634
+
635
+ const demand = advertiserDemand(adjustedCPC);
636
+ const estimatedClicks = Math.min(demand, supply);
637
+ const platformRevenue = estimatedClicks * adjustedCPC * effectiveCommissionRate;
638
+ const advertiserValue = estimatedClicks;
639
+ const marketImbalance = Math.abs(demand - supply);
640
+
641
+ return [-platformRevenue, -advertiserValue, marketImbalance];
642
+ };
643
+
644
+ const createIndividual = () => 0.5 + secureRandom() * 4.5;
645
+ const crossover = (cpc1, cpc2) => (cpc1 + cpc2) / 2;
646
+ const mutate = (cpc) => Math.max(0.1, cpc + (secureRandom() - 0.5) * 0.5);
647
+
648
+ const gaOptions = {
649
+ generations: 50,
650
+ populationSize: 40,
651
+ ...options,
652
+ };
653
+
654
+ const paretoFront = ClientOptimizers.geneticAlgorithmMultiObjective(
655
+ createIndividual,
656
+ fitnessFunction,
657
+ crossover,
658
+ mutate,
659
+ gaOptions
660
+ );
661
+
662
+ return {
663
+ paretoFront: paretoFront.map((result) => {
664
+ const cpcMultiplier = result.solution;
665
+ const finalCpc = Math.max(0.1, 1.0 * cpcMultiplier * competitionFactor);
666
+ return {
667
+ ...result,
668
+ solution: finalCpc,
669
+ };
670
+ })
671
+ };
672
+ },
673
+
674
+ 'fraud.solve'(context, options = {}) {
675
+ const legitimateClicks = context.legitimateClicks || [];
676
+ const fraudulentClicks = context.fraudulentClicks || [];
677
+
678
+ const fitnessFunction = (solution) => {
679
+ const [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents] = solution;
680
+ if (
681
+ minTimeToClick < 100 ||
682
+ minTimeToClick > 5000 ||
683
+ maxClickVariance < 1 ||
684
+ maxClickVariance > 10000 ||
685
+ minMouseEntropy < 0 ||
686
+ minMouseEntropy > 1 ||
687
+ minScrollEvents < 0
688
+ ) {
689
+ return [Infinity, Infinity];
690
+ }
691
+
692
+ const calculateClickVariance = (clicks) => {
693
+ if (!clicks || clicks.length < 2) return 0;
694
+ const meanX = clicks.reduce((sum, c) => sum + c.clickX, 0) / clicks.length;
695
+ const meanY = clicks.reduce((sum, c) => sum + c.clickY, 0) / clicks.length;
696
+ return clicks.reduce((sum, c) => sum + Math.pow(c.clickX - meanX, 2) + Math.pow(c.clickY - meanY, 2), 0) / clicks.length;
697
+ };
698
+
699
+ const getClicksByFingerprint = (clickData) => {
700
+ const grouped = {};
701
+ for (const click of clickData) {
702
+ if (!grouped[click.fingerprint]) grouped[click.fingerprint] = [];
703
+ grouped[click.fingerprint].push(click);
704
+ }
705
+ return grouped;
706
+ };
707
+
708
+ const legitimateGroups = getClicksByFingerprint(legitimateClicks);
709
+ const fraudulentGroups = getClicksByFingerprint(fraudulentClicks);
710
+
711
+ let truePositives = 0;
712
+ let falsePositives = 0;
713
+
714
+ for (const fingerprint in fraudulentGroups) {
715
+ const clicks = fraudulentGroups[fingerprint];
716
+ if (!clicks) continue;
717
+ const variance = calculateClickVariance(clicks);
718
+ const isTooFast = clicks.some((c) => c.timeToClick < minTimeToClick);
719
+ const isTooUniform = variance < maxClickVariance;
720
+ const hasLowEntropy = clicks.some((c) => c.mouseEntropy < minMouseEntropy);
721
+ const hasFewScrolls = clicks.some((c) => c.scrollEvents < minScrollEvents);
722
+
723
+ if (isTooFast || isTooUniform || hasLowEntropy || hasFewScrolls) {
724
+ truePositives++;
725
+ }
726
+ }
727
+
728
+ for (const fingerprint in legitimateGroups) {
729
+ const clicks = legitimateGroups[fingerprint];
730
+ if (!clicks) continue;
731
+ const variance = calculateClickVariance(clicks);
732
+ if (
733
+ clicks.some((c) => c.timeToClick < minTimeToClick || c.mouseEntropy < minMouseEntropy || c.scrollEvents < minScrollEvents) ||
734
+ variance < maxClickVariance
735
+ ) {
736
+ falsePositives++;
737
+ }
738
+ }
739
+
740
+ const totalFraudulent = Object.keys(fraudulentGroups).length || 1;
741
+ const totalLegitimate = Object.keys(legitimateGroups).length || 1;
742
+
743
+ const objective1 = 1 - truePositives / totalFraudulent;
744
+ const objective2 = falsePositives / totalLegitimate;
745
+
746
+ return [objective1, objective2];
747
+ };
748
+
749
+ const createIndividual = () => [
750
+ 100 + secureRandom() * 4900,
751
+ 1 + secureRandom() * 9999,
752
+ secureRandom() * 0.5,
753
+ Math.floor(secureRandom() * 10)
754
+ ];
755
+
756
+ const crossover = (s1, s2) => [
757
+ (s1[0] + s2[0]) / 2,
758
+ (s1[1] + s2[1]) / 2,
759
+ (s1[2] + s2[2]) / 2,
760
+ (s1[3] + s2[3]) / 2,
761
+ ];
762
+
763
+ const mutate = (solution) => {
764
+ const newSolution = [...solution];
765
+ const i = Math.floor(Math.random() * 4);
766
+ const mutationFactors = [500, 1000, 0.1, 2];
767
+ newSolution[i] += (secureRandom() - 0.5) * mutationFactors[i];
768
+ return newSolution;
769
+ };
770
+
771
+ const gaOptions = {
772
+ generations: 80,
773
+ populationSize: 60,
774
+ ...options,
775
+ };
776
+
777
+ const paretoFront = ClientOptimizers.geneticAlgorithmMultiObjective(
778
+ createIndividual,
779
+ fitnessFunction,
780
+ crossover,
781
+ mutate,
782
+ gaOptions
783
+ );
784
+
785
+ return {
786
+ paretoFront
787
+ };
470
788
  }
471
789
  };
472
790
 
@@ -500,16 +818,16 @@ async function solveUsefulWorkTask(task) {
500
818
  };
501
819
  const neighbor = (facilities) => {
502
820
  const newFacilities = facilities.map((f) => ({ ...f }));
503
- const i = Math.floor(Math.random() * numFacilities);
504
- const moveX = (Math.random() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
505
- const moveY = (Math.random() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
821
+ const i = Math.floor(secureRandom() * numFacilities);
822
+ const moveX = (secureRandom() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
823
+ const moveY = (secureRandom() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
506
824
  newFacilities[i].x = Math.max(bounds.minX, Math.min(bounds.maxX, newFacilities[i].x + moveX));
507
825
  newFacilities[i].y = Math.max(bounds.minY, Math.min(bounds.maxY, newFacilities[i].y + moveY));
508
826
  return newFacilities;
509
827
  };
510
828
  const initialSolution = task.initialSolution || Array.from({ length: numFacilities }, () => ({
511
- x: bounds.minX + Math.random() * (bounds.maxX - bounds.minX),
512
- y: bounds.minY + Math.random() * (bounds.maxY - bounds.minY),
829
+ x: bounds.minX + secureRandom() * (bounds.maxX - bounds.minX),
830
+ y: bounds.minY + secureRandom() * (bounds.maxY - bounds.minY),
513
831
  }));
514
832
  return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
515
833
  }
@@ -524,11 +842,11 @@ async function solveUsefulWorkTask(task) {
524
842
  };
525
843
  const neighbor = (path) => {
526
844
  const newPath = [...path];
527
- const [i, j] = [Math.floor(Math.random() * path.length), Math.floor(Math.random() * path.length)];
845
+ const [i, j] = [Math.floor(secureRandom() * path.length), Math.floor(secureRandom() * path.length)];
528
846
  [newPath[i], newPath[j]] = [newPath[j], newPath[i]];
529
847
  return newPath;
530
848
  };
531
- const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - Math.random());
849
+ const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - secureRandom());
532
850
 
533
851
  return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
534
852
  }
@@ -544,9 +862,9 @@ async function solveUsefulWorkTask(task) {
544
862
  if (vol > maxVolatility) return 1000 + (vol - maxVolatility);
545
863
  return -ret;
546
864
  };
547
- const createIndividual = () => Array.from({ length: assets.length }, Math.random);
865
+ const createIndividual = () => Array.from({ length: assets.length }, secureRandom);
548
866
  const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
549
- const mutate = p => { const n = [...p], i = Math.floor(Math.random() * n.length); n[i] += (Math.random() - 0.5) * 0.2; return n.map(v => Math.max(0, v)); };
867
+ const mutate = p => { const n = [...p], i = Math.floor(secureRandom() * n.length); n[i] += (secureRandom() - 0.5) * 0.2; return n.map(v => Math.max(0, v)); };
550
868
 
551
869
  // Le client doit recréer la population si elle n'est pas fournie
552
870
  const initialPopulation = task.initialPopulation || Array.from({ length: task.payload.options.populationSize }, () => ({ chromosome: createIndividual(), fitness: 0 }));
@@ -598,8 +916,8 @@ export async function solveOptimizationTask(initialPopulation, generations) {
598
916
  const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
599
917
  const mutate = (p) => {
600
918
  const newP = [...p];
601
- const i = Math.floor(Math.random() * newP.length);
602
- newP[i] += (Math.random() - 0.5) * 0.2;
919
+ const i = Math.floor(secureRandom() * newP.length);
920
+ newP[i] += (secureRandom() - 0.5) * 0.2;
603
921
  return newP;
604
922
  };
605
923
 
@@ -611,10 +929,10 @@ export async function solveOptimizationTask(initialPopulation, generations) {
611
929
  const newPopulation = [...parents]; // Élitisme
612
930
 
613
931
  while (newPopulation.length < population.length) {
614
- const parent1 = parents[Math.floor(Math.random() * parents.length)];
615
- const parent2 = parents[Math.floor(Math.random() * parents.length)];
932
+ const parent1 = parents[Math.floor(secureRandom() * parents.length)];
933
+ const parent2 = parents[Math.floor(secureRandom() * parents.length)];
616
934
  let offspring = crossover(parent1.chromosome, parent2.chromosome);
617
- if (Math.random() < 0.1) offspring = mutate(offspring);
935
+ if (secureRandom() < 0.1) offspring = mutate(offspring);
618
936
  // La fitness sera recalculée côté serveur pour la vérification.
619
937
  newPopulation.push({ chromosome: offspring, fitness: -1 });
620
938
  }
@@ -586,7 +586,7 @@ let managerPromise = null;
586
586
  * @returns {Promise<ProblemManager>} The singleton instance.
587
587
  */
588
588
  export function getProblemManager(options = {}, store) {
589
- const defaultPath = join(__dirname, '..', '..', 'problems.config.json');
589
+ const defaultPath = join(__dirname, '..', '..', 'config', 'problems.config.json');
590
590
  const { configPath = defaultPath, config } = options;
591
591
 
592
592
  const hasConfigChanged = problemManagerInstance && (
@@ -1,43 +1,115 @@
1
- /**
2
- * @file Creates a store adapter for ioredis.
3
- * This adapter handles the serialization and deserialization of complex objects,
4
- * including the conversion of Set objects to arrays for storage in Redis.
5
- */
6
-
7
- /**
8
- * Creates a store adapter for an ioredis client.
9
- * @param {import('ioredis').Redis} redisClient - An instance of the ioredis client.
10
- * @returns {import('./fingerprint.js').IStore} An object that complies with the IStore interface.
11
- */
12
- export function createRedisStore(redisClient) {
13
- return {
14
- async get(key) {
15
- const value = await redisClient.get(key);
16
- if (!value) return null;
17
- // Use a reviver to convert arrays back to Sets for specific keys like 'ips'.
18
- return JSON.parse(value, (k, v) => {
19
- if (k === 'ips' && Array.isArray(v)) {
20
- return new Set(v);
21
- }
22
- return v;
23
- });
24
- },
25
- async set(key, value, ttl) {
26
- // Use a replacer to convert Set objects into arrays before serialization.
27
- const stringValue = JSON.stringify(value, (k, v) => {
28
- if (v instanceof Set) {
29
- return Array.from(v);
30
- }
31
- return v;
32
- });
33
-
34
- if (ttl && ttl > 0) {
35
- await redisClient.set(key, stringValue, 'EX', ttl);
36
- } else {
37
- await redisClient.set(key, stringValue);
38
- }
39
- },
40
- async has(key) { return (await redisClient.exists(key)) === 1; },
41
- async delete(key) { await redisClient.del(key); },
42
- };
1
+ /**
2
+ * @file Creates a store adapter for ioredis.
3
+ * This adapter handles the serialization and deserialization of complex objects,
4
+ * including the conversion of Set objects to arrays for storage in Redis.
5
+ */
6
+
7
+ /**
8
+ * Creates a store adapter for an ioredis client.
9
+ * @param {import('ioredis').Redis} redisClient - An instance of the ioredis client.
10
+ * @returns {import('./fingerprint.js').IStore} An object that complies with the IStore interface.
11
+ */
12
+ export function createRedisStore(redisClient) {
13
+ const replacer = (k, v) => (v instanceof Set ? Array.from(v) : v);
14
+ const reviver = (k, v) => (k === 'ips' && Array.isArray(v) ? new Set(v) : v);
15
+
16
+ const localStore = new Map();
17
+ const localTimeouts = new Map();
18
+ let isDown = false;
19
+ let reconnecting = false;
20
+
21
+ const triggerFallback = () => {
22
+ if (!isDown) {
23
+ isDown = true;
24
+ attemptReconnection();
25
+ }
26
+ };
27
+
28
+ const attemptReconnection = () => {
29
+ if (reconnecting) return;
30
+ reconnecting = true;
31
+ const interval = setInterval(async () => {
32
+ try {
33
+ await redisClient.ping();
34
+ isDown = false;
35
+ reconnecting = false;
36
+ clearInterval(interval);
37
+ } catch (err) {
38
+ // Continue de tenter la reconnexion toutes les 5 secondes
39
+ }
40
+ }, 5000);
41
+ if (interval.unref) interval.unref();
42
+ };
43
+
44
+ const setLocal = (key, value, ttl) => {
45
+ localStore.set(key, value);
46
+ if (localTimeouts.has(key)) {
47
+ clearTimeout(localTimeouts.get(key));
48
+ }
49
+ if (ttl && ttl > 0) {
50
+ const timeout = setTimeout(() => {
51
+ localStore.delete(key);
52
+ localTimeouts.delete(key);
53
+ }, ttl * 1000);
54
+ if (timeout.unref) timeout.unref();
55
+ localTimeouts.set(key, timeout);
56
+ }
57
+ };
58
+
59
+ return {
60
+ async get(key) {
61
+ if (isDown) {
62
+ return localStore.get(key) || null;
63
+ }
64
+ try {
65
+ const value = await redisClient.get(key);
66
+ if (!value) return null;
67
+ return JSON.parse(value, reviver);
68
+ } catch (e) {
69
+ triggerFallback();
70
+ return localStore.get(key) || null;
71
+ }
72
+ },
73
+ async set(key, value, ttl) {
74
+ if (isDown) {
75
+ setLocal(key, value, ttl);
76
+ return;
77
+ }
78
+ try {
79
+ const stringValue = JSON.stringify(value, replacer);
80
+ if (ttl && ttl > 0) {
81
+ await redisClient.set(key, stringValue, 'EX', ttl);
82
+ } else {
83
+ await redisClient.set(key, stringValue);
84
+ }
85
+ } catch (e) {
86
+ triggerFallback();
87
+ setLocal(key, value, ttl);
88
+ }
89
+ },
90
+ async has(key) {
91
+ if (isDown) {
92
+ return localStore.has(key);
93
+ }
94
+ try {
95
+ return (await redisClient.exists(key)) === 1;
96
+ } catch (e) {
97
+ triggerFallback();
98
+ return localStore.has(key);
99
+ }
100
+ },
101
+ async delete(key) {
102
+ if (localTimeouts.has(key)) {
103
+ clearTimeout(localTimeouts.get(key));
104
+ localTimeouts.delete(key);
105
+ }
106
+ localStore.delete(key);
107
+ if (isDown) return;
108
+ try {
109
+ await redisClient.del(key);
110
+ } catch (e) {
111
+ triggerFallback();
112
+ }
113
+ },
114
+ };
43
115
  }