@anonympins/fingerprint 0.7.2 → 0.7.4

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 +36 -9
  2. package/README.md +1 -1
  3. package/package.json +109 -107
  4. package/public/fingerprint-anti-bot.zip +0 -0
  5. package/src/js/fingerprint.client.js +252 -45
  6. package/src/js/fingerprint.client.obfuscated.js +1 -1
  7. package/src/js/fingerprint.js +265 -185
  8. package/src/js/fingerprint.utils.js +32 -5
  9. package/src/js/fingerprint.worker.js +81 -0
  10. package/src/js/library.js +6 -2
  11. package/src/js/pow.solver.js +2 -3
  12. package/src/js/problem-manager.js +113 -113
  13. package/src/js/tests/fingerprint.client.init.test.js +30 -0
  14. package/src/js/tests/fingerprint.test.js +136 -4
  15. package/src/js/tests/threatIntel.test.js +76 -0
  16. package/src/php/AutoTuner.php +36 -18
  17. package/src/php/Challenge/ChallengeUtils.php +121 -240
  18. package/src/php/DirectFingerprint.php +16 -4
  19. package/src/php/FingerprintClient.php +17 -19
  20. package/src/php/FingerprintEngine.php +77 -39
  21. package/src/php/Ja3AnomalyDetector.php +20 -12
  22. package/src/php/Optimization/Optimization.php +5 -1
  23. package/src/php/Optimization/OptimizationOperators.php +5 -1
  24. package/src/php/Optimization/ProblemInitializers.php +16 -4
  25. package/src/php/ProblemManager.php +20 -14
  26. package/src/php/Tests/ChallengeUtilsTest.php +469 -453
  27. package/src/php/Tests/FingerprintEngineTest.php +6 -1
  28. package/src/php/Tests/RequestUtilsTest.php +1 -1
  29. package/src/php/Tests/ThreatIntelTest.php +64 -0
  30. package/src/php/Utils/Env.php +51 -49
  31. package/src/php/Utils/MaliciousPatterns.php +74 -74
  32. package/src/php/Utils/RequestUtils.php +231 -132
  33. package/src/php/WordPress/WpDbStore.php +4 -0
  34. package/src/php/WordPress/{fingerprint-wordpress.php → fingerprint-anti-bot.php} +131 -126
  35. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
  36. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +1 -1
  37. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
  38. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +1 -1
  39. package/src/php/WordPress/package.php +6 -3
  40. package/src/php/WordPress/readme.txt +36 -0
  41. package/public/fingerprint-wordpress.zip +0 -0
@@ -8,22 +8,22 @@ const __dirname = dirname(__filename);
8
8
 
9
9
  /**
10
10
  * @namespace FunctionRegistry
11
- * @description Registre pour exposer de manière contrôlée les fonctions de la bibliothèque.
12
- * Permet de les appeler dynamiquement depuis la configuration des problèmes.
13
- * Utilise la notation par points pour accéder aux fonctions imbriquées (ex: 'tsp.calculateEnergy').
11
+ * @description Registry to expose library functions in a controlled manner.
12
+ * Allows dynamic invocation from problem configurations.
13
+ * Uses dot notation to access nested functions (e.g. 'tsp.calculateEnergy').
14
14
  */
15
15
  const FunctionRegistry = {};
16
16
 
17
- // --- Fonctions de "Scoring" (évaluation d'une solution) ---
18
- // Ces fonctions sont des adaptateurs pour utiliser les utilitaires de la bibliothèque
19
- // avec la structure attendue par le ProblemManager.
20
- FunctionRegistry['cpc.solve'] = Optimization.Operators.solveOptimalCPC; // NOUVEAU: Enregistrement du solveur CPC
17
+ // --- Scoring functions (solution evaluation) ---
18
+ // These functions are adapters to use library utilities
19
+ // with the structure expected by the ProblemManager.
20
+ FunctionRegistry['cpc.solve'] = Optimization.Operators.solveOptimalCPC; // Register CPC solver
21
21
 
22
22
  /**
23
- * Évalue le coût total pour le problème de placement d'infrastructures.
24
- * @param {Array<{x: number, y: number}>} facilities - Les installations.
25
- * @param {object} payload - Le payload contenant les clients et les options.
26
- * @returns {number} Le coût total.
23
+ * Evaluates total cost for the facility location problem.
24
+ * @param {Array<{x: number, y: number}>} facilities - Facilities.
25
+ * @param {object} payload - Payload containing customers and options.
26
+ * @returns {number} Total cost.
27
27
  */
28
28
  FunctionRegistry['facility.calculateEnergy'] = (facilities, payload) => {
29
29
  const customers = payload.customers || [];
@@ -44,50 +44,50 @@ FunctionRegistry['facility.calculateEnergy'] = (facilities, payload) => {
44
44
  };
45
45
 
46
46
  /**
47
- * Évalue la distance totale d'un chemin pour le problème du voyageur de commerce (TSP).
48
- * @param {Array<{x: number, y: number}>} path - Un tableau de points représentant le chemin.
49
- * @returns {number} La distance totale du chemin.
47
+ * Evaluates total distance of a path for the traveling salesperson problem (TSP).
48
+ * @param {Array<{x: number, y: number}>} path - Array of points representing the path.
49
+ * @returns {number} Total path distance.
50
50
  */
51
51
  FunctionRegistry['tsp.calculateEnergy'] = (path, payload) => {
52
52
  const cities = payload?.cities || payload?.points || [];
53
53
  if (path && typeof path[0] === 'number') {
54
54
  return Optimization.Utils.evaluatePathDistance(cities, path);
55
55
  }
56
- // Fallback: Crée un tableau d'indices [0, 1, 2, ...] pour la fonction evaluatePathDistance.
56
+ // Fallback: Creates an array of indices [0, 1, 2, ...] for evaluatePathDistance.
57
57
  const indices = Array.from({ length: path.length }, (_, i) => i);
58
58
  return Optimization.Utils.evaluatePathDistance(path, indices);
59
59
  };
60
60
 
61
61
  /**
62
- * Évalue les métriques d'un portefeuille (rendement et volatilité).
63
- * Pour l'instant, retourne le rendement négatif pour correspondre à l'objectif de minimisation
64
- * de l'algorithme génétique de la bibliothèque.
65
- * @param {Array<number>} weights - Les poids des actifs dans le portefeuille.
66
- * @param {object} payload - Le payload du problème, contenant les actifs.
67
- * @returns {number} Le rendement négatif du portefeuille.
62
+ * Evaluates portfolio metrics (return and volatility).
63
+ * Currently returns negative return to match the minimization objective
64
+ * of the library's genetic algorithm.
65
+ * @param {Array<number>} weights - Asset weights in the portfolio.
66
+ * @param {object} payload - Problem payload, containing assets.
67
+ * @returns {number} Negative return of the portfolio.
68
68
  */
69
69
  FunctionRegistry['portfolio.calculateMetrics'] = (weights, payload) => {
70
70
  const { assets, maxVolatility } = payload;
71
- // On utilise l'opérateur de la bibliothèque pour créer la fonction de fitness
72
- // et on l'appelle immédiatement.
71
+ // Use the library operator to create the fitness function
72
+ // and invoke it immediately.
73
73
  const fitnessFunction = Optimization.Operators.createPortfolioAllocator({
74
74
  assets,
75
75
  maxVolatility,
76
76
  });
77
- // La fonction de fitness retourne le rendement négatif, ce qui est ce que nous voulons
78
- // stocker comme "énergie" ou score.
77
+ // Fitness function returns negative return, which is what we want
78
+ // to store as "energy" or score.
79
79
  return fitnessFunction(weights);
80
80
  };
81
81
 
82
- // --- Fonctions de "Résolution" (algorithmes complets) ---
83
- // Utiles pour les workers qui exécutent une tâche de bout en bout.
82
+ // --- Solving functions (complete algorithms) ---
83
+ // Useful for workers running end-to-end tasks.
84
84
  FunctionRegistry['tsp.solve'] = Optimization.Operators.solveTSP;
85
85
  FunctionRegistry['portfolio.solve'] = Optimization.Operators.solvePortfolio;
86
- FunctionRegistry['fraud.solve'] = Optimization.Operators.solveFraudDetection; // NOUVEAU: Enregistrement du solveur de fraude
86
+ FunctionRegistry['fraud.solve'] = Optimization.Operators.solveFraudDetection; // Register fraud solver
87
87
  FunctionRegistry['facility.solve'] = Optimization.Operators.solveFacilityLocation;
88
88
  FunctionRegistry['security.tune'] = Optimization.Operators.solveFullSecurityTuning;
89
89
 
90
- // --- Fonctions "Utilitaires" ---
90
+ // --- Utility functions ---
91
91
  FunctionRegistry['utils.evaluatePathDistance'] = Optimization.Utils.evaluatePathDistance;
92
92
 
93
93
  const yieldToEventLoop = () => new Promise(resolve => {
@@ -100,14 +100,14 @@ const yieldToEventLoop = () => new Promise(resolve => {
100
100
 
101
101
  /**
102
102
  * @namespace ProblemInitializers
103
- * @description Fonctions pour générer dynamiquement les données d'un problème.
103
+ * @description Functions to dynamically generate problem data.
104
104
  */
105
105
  const ProblemInitializers = {
106
106
  /**
107
- * Génère un ensemble de points aléatoires pour un problème de TSP.
108
- * @param {object} params - Les paramètres de génération.
109
- * @param {number} params.count - Le nombre de points à générer.
110
- * @param {{x: number, y: number}} [params.bounds={x: 1000, y: 1000}] - Les limites spatiales.
107
+ * Generates a set of random points for a TSP problem.
108
+ * @param {object} params - Generation parameters.
109
+ * @param {number} params.count - Number of points to generate.
110
+ * @param {{x: number, y: number}} [params.bounds={x: 1000, y: 1000}] - Spatial bounds.
111
111
  * @returns {Promise<Array<{x: number, y: number}>>}
112
112
  */
113
113
  'generate:randomPoints': async (params) => {
@@ -125,9 +125,9 @@ const ProblemInitializers = {
125
125
  },
126
126
 
127
127
  /**
128
- * Génère un ensemble d'actifs financiers aléatoires pour un problème de portefeuille.
129
- * @param {object} params - Les paramètres de génération.
130
- * @param {number} params.count - Le nombre d'actifs à générer.
128
+ * Generates a set of random financial assets for a portfolio problem.
129
+ * @param {object} params - Generation parameters.
130
+ * @param {number} params.count - Number of assets to generate.
131
131
  * @returns {Promise<Array<{expectedReturn: number, volatility: number}>>}
132
132
  */
133
133
  'generate:randomAssets': async (params) => {
@@ -148,18 +148,18 @@ const ProblemInitializers = {
148
148
  },
149
149
 
150
150
  /**
151
- * Crée une fonction qui génère des arguments pour chaque worker de `runMultipleParallel`.
152
- * Permet de faire varier les paramètres (ex: solution initiale) pour chaque cycle.
153
- * @param {object} params - Les paramètres de configuration.
154
- * @param {Array<any>} params.baseArgs - Les arguments de base, communs à tous les workers.
155
- * @param {object} params.variations - Décrit comment faire varier un argument.
156
- * @returns {function(number): Array<any>} La fonction `workerDataGenerator`.
151
+ * Creates a function that generates arguments for each worker in `runMultipleParallel`.
152
+ * Allows varying parameters (e.g. initial solution) for each cycle.
153
+ * @param {object} params - Configuration parameters.
154
+ * @param {Array<any>} params.baseArgs - Base arguments common to all workers.
155
+ * @param {object} params.variations - Describes how to vary an argument.
156
+ * @returns {function(number): Array<any>} The `workerDataGenerator` function.
157
157
  */
158
158
  'generate:parallelArgs': (params) => {
159
159
  const { baseArgs, variations } = params || {};
160
160
  return (cycleIndex) => {
161
161
  const cycleArgs = baseArgs ? [...baseArgs] : [];
162
- // Pour l'instant, on gère la variation de la solution initiale pour le TSP
162
+ // For now, handle initial solution variation for TSP
163
163
  if (variations?.initialSolution === 'random') {
164
164
  cycleArgs[0] = cycleArgs[0]?.sort(() => Math.random() - 0.5);
165
165
  }
@@ -171,11 +171,11 @@ const ProblemInitializers = {
171
171
  class ProblemManager {
172
172
  /**
173
173
  * @private
174
- * Le constructeur est privé. Utilisez la méthode de fabrique asynchrone `create()`.
175
- * @param {object} options - Les options d'initialisation.
176
- * @param {string} [options.configPath] - Le chemin vers le fichier de configuration.
177
- * @param {object} [options.config] - L'objet de configuration des problèmes.
178
- * @param {Array<object>} problems - Les problèmes pré-chargés.
174
+ * The constructor is private. Use the asynchronous factory method `create()`.
175
+ * @param {object} options - Initialization options.
176
+ * @param {string} [options.configPath] - Path to the configuration file.
177
+ * @param {object} [options.config] - Problem configuration object.
178
+ * @param {Array<object>} problems - Pre-loaded problems.
179
179
  * @param {IStore} store - The datastore for synchronization.
180
180
  */
181
181
  constructor(options, problems, store) {
@@ -187,8 +187,8 @@ class ProblemManager {
187
187
  }
188
188
 
189
189
  /**
190
- * Méthode de fabrique asynchrone pour créer et initialiser une instance de ProblemManager.
191
- * @param {object} options - Les options d'initialisation.
190
+ * Asynchronous factory method to create and initialize a ProblemManager instance.
191
+ * @param {object} options - Initialization options.
192
192
  * @returns {Promise<ProblemManager>}
193
193
  */
194
194
  static async create(options, store) {
@@ -233,22 +233,22 @@ class ProblemManager {
233
233
  problem.state = storedState;
234
234
  return problem;
235
235
  }));
236
- // Initialisation dynamique des problèmes
236
+ // Dynamic problem initialization
237
237
  for (const problem of problems) {
238
- // Résolution des fonctions via le registre
238
+ // Resolve functions via registry
239
239
  if (problem.workUnit && problem.workUnit.scoreFunction) {
240
240
  problem.workUnit.scoreFunction = FunctionRegistry[problem.workUnit.scoreFunction] || null;
241
241
  }
242
242
  if (problem.payload) {
243
243
  for (const key in problem.payload) {
244
244
  const value = problem.payload[key];
245
- // On cherche une instruction d'initialisation (ex: { "$init": "generate:randomPoints", ... })
245
+ // Look for an initialization instruction (e.g. { "$init": "generate:randomPoints", ... })
246
246
  if (typeof value === 'object' && value !== null && value.$init) {
247
247
  const initializer = ProblemInitializers[value.$init];
248
- // On cherche une instruction de fonction (ex: { "$func": "tsp.calculateEnergy" })
249
- // Note: Actuellement non utilisé, mais prêt pour une future extension.
248
+ // Look for a function instruction (e.g. { "$func": "tsp.calculateEnergy" })
249
+ // Note: Currently unused, but ready for future extension.
250
250
  if (initializer) {
251
- // On remplace l'objet d'instruction par les données générées de manière asynchrone.
251
+ // Replace instruction object with dynamically generated data.
252
252
  problem.payload[key] = await initializer(value.params || {});
253
253
  }
254
254
  }
@@ -263,8 +263,8 @@ class ProblemManager {
263
263
  }
264
264
 
265
265
  /**
266
- * Sélectionne un problème et génère une unité de travail.
267
- * @param {number} suspicionFactor - Le facteur de suspicion pour ajuster la difficulté.
266
+ * Selects a problem and generates a work unit.
267
+ * @param {number} suspicionFactor - Suspicion factor to adjust difficulty.
268
268
  * @returns {{problemId: string, task: object}|null}
269
269
  */
270
270
  dispatchWork(suspicionFactor) {
@@ -278,7 +278,7 @@ class ProblemManager {
278
278
 
279
279
  switch (problem.workUnit.type) {
280
280
  case 'simulated_annealing_iterations':
281
- // Assurer une difficulté minimale pour que le challenge soit significatif
281
+ // Ensure a minimum difficulty so the challenge is meaningful
282
282
  const baseIterations = Math.max(15000, problem.workUnit.baseIterations || 0);
283
283
  task.iterations = scalingFactor
284
284
  ? Math.floor(baseIterations * Math.pow(scalingFactor, suspicionFactor))
@@ -288,7 +288,7 @@ class ProblemManager {
288
288
  break;
289
289
 
290
290
  case 'genetic_algorithm_generations':
291
- // Assurer une difficulté minimale pour que le challenge soit significatif
291
+ // Ensure a minimum difficulty so the challenge is meaningful
292
292
  const baseGenerations = Math.max(50, problem.workUnit.baseGenerations || 0);
293
293
  task.generations = scalingFactor
294
294
  ? Math.floor(baseGenerations * Math.pow(scalingFactor, suspicionFactor))
@@ -300,7 +300,7 @@ class ProblemManager {
300
300
  case 'run_multiple_parallel':
301
301
  task.solverName = problem.workUnit.solverName;
302
302
  task.numCycles = problem.workUnit.numCycles;
303
- // Les arguments et le générateur sont dans le payload pour plus de flexibilité
303
+ // Arguments and generator are in payload for added flexibility
304
304
  task.baseSolverArgs = problem.payload.baseSolverArgs;
305
305
  task.workerDataGenerator = problem.payload.workerDataGenerator;
306
306
  task.logProgress = problem.payload.logProgress || false;
@@ -308,15 +308,15 @@ class ProblemManager {
308
308
  break;
309
309
 
310
310
  case 'multi_objective_genetic_algorithm':
311
- // La difficulté s'applique au nombre de générations
311
+ // Difficulty applies to the number of generations
312
312
  const baseGenerationsMulti = Math.max(30, problem.workUnit.baseGenerations || 0);
313
313
  task.generations = scalingFactor
314
314
  ? Math.floor(baseGenerationsMulti * Math.pow(scalingFactor, suspicionFactor))
315
315
  : Math.floor(baseGenerationsMulti * (0.5 + suspicionFactor));
316
316
  task.payload = problem.payload;
317
- // L'état initial est le front de Pareto actuel, que le client peut utiliser pour l'élitisme
317
+ // Initial state is current Pareto front, which client can use for elitism
318
318
  task.initialFront = problem.state.paretoFront;
319
- task.solverName = problem.workUnit.solverName; // Le nom du solveur à utiliser (ex: 'cpc.solve')
319
+ task.solverName = problem.workUnit.solverName; // Name of solver to use (e.g. 'cpc.solve')
320
320
  break;
321
321
 
322
322
  case 'tfjs_learning':
@@ -330,9 +330,9 @@ class ProblemManager {
330
330
  }
331
331
 
332
332
  /**
333
- * Intègre la solution d'un client dans l'état du problème.
334
- * @param {string} problemId - L'ID du problème.
335
- * @param {object} solutionData - La solution renvoyée par le client.
333
+ * Integrates a client's solution into the problem state.
334
+ * @param {string} problemId - Problem ID.
335
+ * @param {object} solutionData - Solution returned by client.
336
336
  */
337
337
  async integrateSolution(problemId, solutionData) {
338
338
  const problem = this.problems.find(p => p.id === problemId);
@@ -363,7 +363,7 @@ class ProblemManager {
363
363
  return;
364
364
  }
365
365
  } catch (e) {}
366
- // 1. Ne JAMAIS faire confiance au score du client. Recalculer systématiquement.
366
+ // 1. NEVER trust client score. Always recalculate.
367
367
  const scoreFunction = problem.workUnit.scoreFunction;
368
368
  if (!scoreFunction) {
369
369
  console.error(`[ProblemManager] Aucune fonction de score définie pour ${problemId}. Impossible de vérifier la solution.`);
@@ -372,25 +372,25 @@ class ProblemManager {
372
372
  const recalculatedEnergy = scoreFunction(solutionData.solution, problem.payload);
373
373
 
374
374
  const currentBest = parseFloat(problem.state.bestEnergy) || Infinity;
375
- // 2. Comparer le score recalculé, pas celui du client.
375
+ // 2. Compare recalculated score, not client score.
376
376
  const isBetter = recalculatedEnergy < currentBest;
377
377
 
378
378
  if (isBetter) {
379
379
  problem.state.bestSolution = solutionData.solution;
380
- problem.state.bestEnergy = recalculatedEnergy; // 3. Stocker le score vérifié.
380
+ problem.state.bestEnergy = recalculatedEnergy; // 3. Store verified score.
381
381
  problem.state.lastUpdate = new Date().toISOString();
382
382
  console.log(`[ProblemManager] Nouvelle meilleure solution pour ${problemId}: ${recalculatedEnergy.toFixed(2)}`);
383
383
  }
384
384
  break;
385
385
  case 'genetic_algorithm_generations':
386
- // VÉRIFICATION PAR ÉCHANTILLONNAGE pour équilibrer sécurité et performance.
387
- const fitnessFunction = FunctionRegistry['portfolio.calculateMetrics']; // Ou une fonction plus générique
386
+ // SAMPLING VERIFICATION to balance security and performance.
387
+ const fitnessFunction = FunctionRegistry['portfolio.calculateMetrics']; // Or a more generic function
388
388
  if (!solutionData || !Array.isArray(solutionData.population)) {
389
389
  console.error(`[ProblemManager] Propriété 'population' manquante ou invalide dans solutionData pour ${problemId}.`);
390
390
  return;
391
391
  }
392
392
  if (solutionData.population.length === 0) {
393
- return; // Retour gracieux si la population est vide (ex: fallback de redirection)
393
+ return; // Graceful return if population is empty (e.g. redirect fallback)
394
394
  }
395
395
  if (!fitnessFunction) {
396
396
  console.error(`[ProblemManager] Impossible de vérifier la population pour ${problemId} (fonction de fitness manquante).`);
@@ -407,14 +407,14 @@ class ProblemManager {
407
407
  return;
408
408
  }
409
409
  }
410
- // 1. On choisit un petit échantillon aléatoire de la population soumise.
410
+ // 1. Choose a small random sample of the submitted population.
411
411
  const sampleSize = Math.min(5, solutionData.population.length);
412
412
  const sampleIndices = new Set();
413
413
  while (sampleIndices.size < sampleSize) {
414
414
  sampleIndices.add(Math.floor(Math.random() * solutionData.population.length));
415
415
  }
416
416
 
417
- // 2. On recalcule le score pour cet échantillon.
417
+ // 2. Recalculate score for this sample.
418
418
  let totalRecalculatedFitness = 0;
419
419
  for (const index of sampleIndices) {
420
420
  const individual = solutionData.population[index];
@@ -426,14 +426,14 @@ class ProblemManager {
426
426
  if (individual.fitness !== undefined && individual.fitness !== -1) {
427
427
  if (Math.abs(individual.fitness - recalculated) > 1e-4) {
428
428
  console.error(`[ProblemManager] Triche détectée pour ${problemId}! Fitness déclaré: ${individual.fitness}, recalculé: ${recalculated}`);
429
- return; // Rejeter immédiatement toute la population
429
+ return; // Immediately reject entire population
430
430
  }
431
431
  }
432
- individual.fitness = recalculated; // Forcer la valeur exacte recalculée
432
+ individual.fitness = recalculated; // Enforce exact recalculated value
433
433
  totalRecalculatedFitness += recalculated;
434
434
  }
435
435
 
436
- problem.state.population = solutionData.population; // On accepte la population
436
+ problem.state.population = solutionData.population; // Accept population
437
437
  console.log(`[ProblemManager] Population mise à jour pour ${problemId}. Fitness moyen de l'échantillon: ${(totalRecalculatedFitness / sampleSize).toFixed(4)}`);
438
438
  break;
439
439
 
@@ -442,7 +442,7 @@ class ProblemManager {
442
442
  console.error(`[ProblemManager] Propriété 'paretoFront' manquante ou invalide dans solutionData pour ${problemId}.`);
443
443
  return;
444
444
  }
445
- // Pour le multi-objectifs, on fusionne le front de Pareto existant avec celui du client.
445
+ // For multi-objective, merge existing Pareto front with client's.
446
446
  await this._integrateParetoFront(problem, solutionData.paretoFront);
447
447
  break;
448
448
  }
@@ -454,31 +454,31 @@ class ProblemManager {
454
454
  }
455
455
 
456
456
  /**
457
- * S'assure qu'un problème a une solution initiale. Si non, en génère une.
458
- * @param {object} problem - L'objet problème.
457
+ * Ensures a problem has an initial solution. If not, generates one.
458
+ * @param {object} problem - Problem object.
459
459
  * @private
460
460
  */
461
461
  async _ensureInitialSolution(problem) {
462
462
  if (problem.state.bestSolution) {
463
- return; // Une solution existe déjà
463
+ return; // A solution already exists
464
464
  }
465
465
 
466
466
  console.log(`[ProblemManager] Génération d'une solution initiale pour le problème ${problem.id}...`);
467
467
 
468
- // On utilise la fonction de score définie dans la config
468
+ // Use score function defined in config
469
469
  const scoreFunction = problem.workUnit.scoreFunction;
470
- // On suppose que la source de la solution initiale est définie dans la config
470
+ // Assume initial solution source is defined in config
471
471
  const initialSolutionSource = problem.payload[problem.workUnit.initialSolutionSource];
472
472
 
473
473
  if (scoreFunction && initialSolutionSource && Array.isArray(initialSolutionSource)) {
474
474
  const initialSolution = initialSolutionSource;
475
- // On calcule le score (énergie, fitness, etc.) de cette solution initiale.
476
- // La fonction de scoring peut nécessiter des arguments supplémentaires du payload.
475
+ // Calculate score (energy, fitness, etc.) of this initial solution.
476
+ // Scoring function may require additional arguments from payload.
477
477
  const score = scoreFunction(initialSolution, problem.payload);
478
478
 
479
479
  problem.state.bestSolution = initialSolution;
480
- // Le nom de la propriété du score dépend du type de problème
481
- problem.state.bestEnergy = score; // Pourrait être généralisé si besoin
480
+ // Score property name depends on problem type
481
+ problem.state.bestEnergy = score; // Could be generalized if needed
482
482
  problem.state.lastUpdate = new Date().toISOString();
483
483
 
484
484
  console.log(`[ProblemManager] Solution initiale pour ${problem.id} générée avec un score de ${score.toFixed(2)}.`);
@@ -488,9 +488,9 @@ class ProblemManager {
488
488
  }
489
489
 
490
490
  /**
491
- * Intègre un nouveau front de Pareto dans l'état du problème.
492
- * @param {object} problem - L'objet problème.
493
- * @param {Array<object>} newFront - Le front de Pareto renvoyé par un client.
491
+ * Integrates a new Pareto front into problem state.
492
+ * @param {object} problem - Problem object.
493
+ * @param {Array<object>} newFront - Pareto front returned by client.
494
494
  * @private
495
495
  */
496
496
  async _integrateParetoFront(problem, newFront) {
@@ -499,13 +499,13 @@ class ProblemManager {
499
499
  const currentFront = problem.state.paretoFront || []; // eslint-disable-line no-unused-vars
500
500
  const combined = [...currentFront, ...newFront];
501
501
 
502
- // --- Logique de tri non-dominé pour trouver le nouveau meilleur front ---
502
+ // --- Non-dominated sorting logic to find new best front ---
503
503
  const paretoDominates = (a, b) => {
504
504
  let aIsBetterInOne = false;
505
- // On suppose que les objectifs sont à minimiser
505
+ // Assume objectives are to be minimized
506
506
  for (let i = 0; i < a.objectives.length; i++) {
507
- if (a.objectives[i] > b.objectives[i]) return false; // A est pire sur au moins un objectif
508
- if (a.objectives[i] < b.objectives[i]) aIsBetterInOne = true; // A est strictement meilleur sur au moins un
507
+ if (a.objectives[i] > b.objectives[i]) return false; // A is worse on at least one objective
508
+ if (a.objectives[i] < b.objectives[i]) aIsBetterInOne = true; // A is strictly better on at least one
509
509
  }
510
510
  return aIsBetterInOne;
511
511
  };
@@ -543,33 +543,33 @@ class ProblemManager {
543
543
  }
544
544
 
545
545
  /**
546
- * Récupère la meilleure solution actuellement connue pour un ou plusieurs problèmes.
547
- * @param {string} [problemId] - L'ID optionnel du problème à consulter.
548
- * Si non fourni, retourne les meilleures solutions pour tous les problèmes.
546
+ * Retrieves currently known best solution for one or more problems.
547
+ * @param {string} [problemId] - Optional problem ID to inspect.
548
+ * If omitted, returns best solutions for all problems.
549
549
  * @returns {object|Array<object>|null}
550
- * - Si un `problemId` est fourni, retourne un objet `{ id, solution, score }` ou `null` si non trouvé.
551
- * - Si aucun `problemId` n'est fourni, retourne un tableau de ces objets.
550
+ * - If `problemId` is provided, returns `{ id, solution, score }` or `null` if not found.
551
+ * - If no `problemId` is provided, returns array of these objects.
552
552
  */
553
553
  async getBestSolutions(problemId) {
554
554
  const problemsToProcess = problemId
555
555
  ? this.problems.filter(p => p.id === problemId)
556
556
  : this.problems;
557
557
 
558
- // On ne génère une solution initiale que pour les problèmes mono-objectif
558
+ // Only generate an initial solution for single-objective problems
559
559
  for (const p of problemsToProcess.filter(p => p.workUnit.type !== 'multi_objective_genetic_algorithm')) {
560
560
  await this._ensureInitialSolution(p);
561
561
  }
562
562
 
563
563
  const formatSolution = (p) => {
564
- // Après _ensureInitialSolution, on peut supposer que p.state existe.
564
+ // After _ensureInitialSolution, p.state can be assumed to exist.
565
565
  if (!p || !p.state) return null;
566
566
 
567
- // Cas spécial pour les problèmes multi-objectifs
567
+ // Special case for multi-objective problems
568
568
  if (p.workUnit.type === 'multi_objective_genetic_algorithm') {
569
569
  return {
570
570
  id: p.id,
571
- solution: p.state.paretoFront, // La "solution" est l'ensemble du front
572
- score: p.state.paretoFront?.length || 0, // Le "score" est le nombre de points sur le front
571
+ solution: p.state.paretoFront, // "solution" is the entire front
572
+ score: p.state.paretoFront?.length || 0, // "score" is number of points on front
573
573
  lastUpdate: p.state.lastUpdate,
574
574
  };
575
575
  }
@@ -584,18 +584,18 @@ class ProblemManager {
584
584
 
585
585
  if (problemId) {
586
586
  const problem = this.problems.find(p => p.id === problemId);
587
- return problem ? formatSolution(problem) : null; // Le filtrage initial a déjà fait le travail
587
+ return problem ? formatSolution(problem) : null; // Initial filtering already done
588
588
  }
589
589
 
590
- // Retourne un aperçu pour tous les problèmes
590
+ // Return overview for all problems
591
591
  return this.problems.map(formatSolution).filter(s => s && s.solution);
592
592
  }
593
593
 
594
594
  /**
595
- * Met à jour le payload d'un problème spécifique par son ID.
596
- * @param {string} problemId - L'ID du problème à mettre à jour.
597
- * @param {object} newPayload - Le nouvel objet payload qui remplacera l'ancien.
598
- * @returns {boolean} - True si la mise à jour a réussi, false sinon.
595
+ * Updates payload of a specific problem by its ID.
596
+ * @param {string} problemId - ID of problem to update.
597
+ * @param {object} newPayload - New payload object to replace old one.
598
+ * @returns {boolean} - True if update succeeded, false otherwise.
599
599
  */
600
600
  async updateProblemPayload(problemId, newPayload) {
601
601
  const problem = this.problems.find(p => p.id === problemId);
@@ -607,7 +607,7 @@ class ProblemManager {
607
607
  console.log(`[ProblemManager] Mise à jour du payload pour le problème '${problemId}'.`);
608
608
  problem.payload = newPayload;
609
609
 
610
- // Invalider l'état actuel car le problème a changé
610
+ // Invalidate current state because problem has changed
611
611
  problem.state.bestSolution = null;
612
612
  problem.state.bestEnergy = "Infinity";
613
613
 
@@ -17,8 +17,13 @@ Object.defineProperty(global, 'document', { value: dom.window.document, writable
17
17
  Object.defineProperty(global, 'navigator', { value: dom.window.navigator, writable: true });
18
18
  Object.defineProperty(global, 'screen', { value: dom.window.screen, writable: true });
19
19
  Object.defineProperty(global, 'performance', { value: dom.window.performance, writable: true });
20
+ if (typeof globalThis.crypto !== 'undefined') {
21
+ Object.defineProperty(dom.window, 'crypto', { value: globalThis.crypto, writable: true, configurable: true });
22
+ }
23
+ Object.defineProperty(global, 'CustomEvent', { value: dom.window.CustomEvent, writable: true, configurable: true });
20
24
 
21
25
  global.fetch = vi.fn(); // Mock global fetch
26
+ window.fetch = (...args) => global.fetch(...args);
22
27
  global.Headers = dom.window.Headers;
23
28
  global.Request = dom.window.Request;
24
29
  global.URL = dom.window.URL;
@@ -45,6 +50,7 @@ describe('ClientLibrary.initializeClient', () => {
45
50
  initHoneypotsSpy = vi.spyOn(ClientLibrary, 'initializeHoneypots');
46
51
  injectTrapsSpy = vi.spyOn(ClientLibrary, 'injectTrapLinks');
47
52
  initFetchSpy = vi.spyOn(ClientLibrary, 'initializeFetch');
53
+ window.fetch = (...args) => global.fetch(...args);
48
54
  injectPhantomTrapsSpy = vi.spyOn(ClientLibrary, 'injectPhantomTraps');
49
55
  });
50
56
 
@@ -144,4 +150,28 @@ describe('ClientLibrary.initializeClient', () => {
144
150
  expect(initFetchSpy).toHaveBeenCalledWith(config.fetch.targetDomains);
145
151
  });
146
152
 
153
+ it('should abort challenge retry loop when maxRetries is reached', async () => {
154
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
155
+ const mockResponse = {
156
+ status: 404,
157
+ headers: new Headers({ 'content-type': 'application/json' }),
158
+ bodyUsed: false,
159
+ json: vi.fn().mockResolvedValue({
160
+ challenge: { type: 'cpu_target', nonce: 'nonce-1', cpuTarget: '0000ffff' }
161
+ }),
162
+ clone() { return this; }
163
+ };
164
+
165
+ const result = await ClientLibrary.solveChallengeAndRetry(
166
+ mockResponse,
167
+ 'http://localhost/api/data',
168
+ { _powRetryCount: 3 },
169
+ 3
170
+ );
171
+
172
+ expect(result).toBe(mockResponse);
173
+ expect(mockResponse.json).not.toHaveBeenCalled();
174
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Challenge retry limit reached (3)'));
175
+ });
176
+
147
177
  });