@anonympins/fingerprint 0.0.9 → 0.1.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.
- package/README.md +494 -477
- package/fingerprint.client.js +63 -3
- package/fingerprint.js +2267 -1882
- package/library.js +136 -5
- package/package.json +3 -2
- package/pow.solver.js +214 -0
package/library.js
CHANGED
|
@@ -498,8 +498,8 @@ Optimization.geneticAlgorithmMultiObjective = function (
|
|
|
498
498
|
options = {},
|
|
499
499
|
) {
|
|
500
500
|
const {
|
|
501
|
-
generations =
|
|
502
|
-
populationSize =
|
|
501
|
+
generations = 150, // Augmenté pour une meilleure convergence
|
|
502
|
+
populationSize = 60, // Augmenté pour plus de diversité
|
|
503
503
|
mutationRate = 0.1,
|
|
504
504
|
} = options;
|
|
505
505
|
|
|
@@ -1152,8 +1152,8 @@ const BASE_CLICK_COST = 1;
|
|
|
1152
1152
|
* @param {number} config.estimatedImpressions - Le nombre d'impressions quotidiennes estimées pour ce contexte.
|
|
1153
1153
|
*/
|
|
1154
1154
|
Optimization.Operators.createOptimalCPCEvaluator = (context) => {
|
|
1155
|
-
const { optimalBaseCommission, optimalBonusFactor } = platformParams;
|
|
1156
|
-
const websiteQualityScore = (website?.relevanceScore || 50) / 100;
|
|
1155
|
+
const { optimalBaseCommission, optimalBonusFactor } = context.platformParams;
|
|
1156
|
+
const websiteQualityScore = (context.website?.relevanceScore || 50) / 100;
|
|
1157
1157
|
|
|
1158
1158
|
// Le taux de commission effectif pour ce site
|
|
1159
1159
|
const effectiveCommissionRate = Math.max(
|
|
@@ -1164,7 +1164,7 @@ Optimization.Operators.createOptimalCPCEvaluator = (context) => {
|
|
|
1164
1164
|
// Modèle de la demande : combien de clics l'annonceur peut-il s'offrir ?
|
|
1165
1165
|
const advertiserDemand = (cpc) => {
|
|
1166
1166
|
if (cpc <= 0) return Infinity;
|
|
1167
|
-
return (advertiser.credits || 0) / cpc;
|
|
1167
|
+
return (context.advertiser.credits || 0) / cpc;
|
|
1168
1168
|
};
|
|
1169
1169
|
|
|
1170
1170
|
// --- SOLUTION : Utiliser l'offre réelle et la concurrence ---
|
|
@@ -1277,6 +1277,38 @@ Optimization.Operators.solveOptimalCPC = (context, options = {}) => {
|
|
|
1277
1277
|
});
|
|
1278
1278
|
};
|
|
1279
1279
|
|
|
1280
|
+
/**
|
|
1281
|
+
* Crée un évaluateur multi-objectifs pour trouver le TTL (Time-To-Live) optimal pour un ticket de sécurité.
|
|
1282
|
+
* @param {object} context - L'objet de configuration.
|
|
1283
|
+
* @param {number} context.suspicionScore - Le score de suspicion de l'utilisateur (0-100).
|
|
1284
|
+
* @returns {function(number): number[]} Une fonction de fitness qui prend un TTL (en ms) et retourne les scores des objectifs [risque, friction].
|
|
1285
|
+
*/
|
|
1286
|
+
Optimization.Operators.createOptimalTtlEvaluator = ({ suspicionScore }) => {
|
|
1287
|
+
// On normalise le score pour qu'il soit plus impactant dans le calcul du risque.
|
|
1288
|
+
const normalizedScore = Math.max(1, suspicionScore);
|
|
1289
|
+
|
|
1290
|
+
return function ttlFitness(ttl) {
|
|
1291
|
+
// Contraintes : un TTL doit être dans une plage raisonnable (ex: 5min à 24h)
|
|
1292
|
+
if (ttl < 300000 || ttl > 86400000) return [Infinity, Infinity];
|
|
1293
|
+
|
|
1294
|
+
// Objectif 1 : Minimiser le Risque.
|
|
1295
|
+
// Le risque est le produit du score et de la durée de la session.
|
|
1296
|
+
// Pour un score élevé, l'algo doit choisir un TTL faible pour minimiser ce produit.
|
|
1297
|
+
const risk = normalizedScore * ttl;
|
|
1298
|
+
|
|
1299
|
+
// Objectif 2 : Minimiser la Friction UX.
|
|
1300
|
+
// La friction est l'inverse du TTL. On la pénalise d'autant plus que le score est FAIBLE.
|
|
1301
|
+
// (101 - score) assure que pour un score de 1, la pénalité d'un TTL court est maximale.
|
|
1302
|
+
// Pour un score de 100, cette pénalité est quasi nulle.
|
|
1303
|
+
const friction = (1 / ttl) * (101 - normalizedScore);
|
|
1304
|
+
|
|
1305
|
+
// On retourne 2 objectifs avec des facteurs de mise à l'échelle pour les équilibrer.
|
|
1306
|
+
return [risk / 1e7, friction * 1e9];
|
|
1307
|
+
};
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
|
|
1311
|
+
|
|
1280
1312
|
/**
|
|
1281
1313
|
* Crée un évaluateur multi-objectifs pour trouver les seuils de détection de fraude optimaux.
|
|
1282
1314
|
* @param {object} config - L'objet de configuration.
|
|
@@ -1443,4 +1475,103 @@ Optimization.Operators.solveFraudDetection = (context, options = {}) => {
|
|
|
1443
1475
|
);
|
|
1444
1476
|
};
|
|
1445
1477
|
|
|
1478
|
+
/**
|
|
1479
|
+
* Crée un évaluateur multi-objectifs pour l'auto-tuning complet de la configuration de sécurité.
|
|
1480
|
+
* Optimise à la fois les seuils, les poids de suspicion et les paramètres de détection de patterns.
|
|
1481
|
+
* @param {object} config - L'objet de configuration.
|
|
1482
|
+
* @param {Array<object>} config.trafficData - Données de trafic collectées.
|
|
1483
|
+
* @returns {function(object): number[]} Une fonction de fitness qui prend une configuration complète et retourne les scores [taux de faux positifs, taux de faux négatifs].
|
|
1484
|
+
*/
|
|
1485
|
+
Optimization.Operators.createFullSecurityConfigEvaluator = ({ trafficData }) => {
|
|
1486
|
+
// Un "individu" est un objet de configuration complet
|
|
1487
|
+
// { thresholds: { low, medium, high }, weights: { historyScore, ... }, patterns: { velocityThreshold, ... } }
|
|
1488
|
+
|
|
1489
|
+
return function fullConfigFitness(config) {
|
|
1490
|
+
let falsePositives = 0; // Humains légitimes challengés ou bloqués
|
|
1491
|
+
let falseNegatives = 0; // Bots qui sont passés
|
|
1492
|
+
let totalHumans = 0;
|
|
1493
|
+
let totalBots = 0;
|
|
1494
|
+
|
|
1495
|
+
// Simule le calcul du score pour chaque point de données avec la configuration testée
|
|
1496
|
+
const calculateScore = (log) => {
|
|
1497
|
+
// Cette fonction interne devrait répliquer la logique de `getSuspicionVector`
|
|
1498
|
+
// en utilisant les poids et les paramètres de `config`.
|
|
1499
|
+
// Pour cet exemple, nous utilisons une version simplifiée.
|
|
1500
|
+
let score = 0;
|
|
1501
|
+
for (const key in config.weights) {
|
|
1502
|
+
score += (log.vector[key] || 0) * config.weights[key];
|
|
1503
|
+
}
|
|
1504
|
+
return score;
|
|
1505
|
+
};
|
|
1506
|
+
|
|
1507
|
+
for (const log of trafficData) {
|
|
1508
|
+
// On se base sur le comportement observé pour déterminer la nature "réelle" de la requête
|
|
1509
|
+
const isLikelyBot = log.type === 'challenge_issued' || log.type === 'request_blocked';
|
|
1510
|
+
const isLikelyHuman = log.type === 'request_passed' || log.type === 'challenge_solved';
|
|
1511
|
+
|
|
1512
|
+
if (isLikelyBot) {
|
|
1513
|
+
totalBots++;
|
|
1514
|
+
const score = calculateScore(log);
|
|
1515
|
+
// Faux négatif : un bot qui aurait dû être challengé mais ne l'a pas été
|
|
1516
|
+
if (score < config.thresholds.low) {
|
|
1517
|
+
falseNegatives++;
|
|
1518
|
+
}
|
|
1519
|
+
} else if (isLikelyHuman) {
|
|
1520
|
+
totalHumans++;
|
|
1521
|
+
const score = calculateScore(log);
|
|
1522
|
+
// Faux positif : un humain qui a été challengé inutilement
|
|
1523
|
+
if (score >= config.thresholds.low) {
|
|
1524
|
+
falsePositives++;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
const falsePositiveRate = totalHumans > 0 ? falsePositives / totalHumans : 0;
|
|
1530
|
+
const falseNegativeRate = totalBots > 0 ? falseNegatives / totalBots : 0;
|
|
1531
|
+
|
|
1532
|
+
// L'algorithme doit minimiser ces deux objectifs
|
|
1533
|
+
return [falsePositiveRate, falseNegativeRate];
|
|
1534
|
+
};
|
|
1535
|
+
};
|
|
1536
|
+
|
|
1537
|
+
/**
|
|
1538
|
+
* Résout le problème de l'auto-tuning complet de la configuration de sécurité.
|
|
1539
|
+
* @param {object} context - Le contexte contenant les données de trafic.
|
|
1540
|
+
* @param {object} [options] - Options pour l'algorithme génétique.
|
|
1541
|
+
* @returns {Array<{solution: object, objectives: number[]}>} Le front de Pareto des configurations optimales.
|
|
1542
|
+
*/
|
|
1543
|
+
Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
|
|
1544
|
+
const fitnessFunction = Optimization.Operators.createFullSecurityConfigEvaluator(context);
|
|
1545
|
+
|
|
1546
|
+
// Un "individu" est un objet de configuration complet
|
|
1547
|
+
const createIndividual = () => ({
|
|
1548
|
+
thresholds: {
|
|
1549
|
+
low: 10 + Math.random() * 30, // 10-40
|
|
1550
|
+
medium: 40 + Math.random() * 30, // 40-70
|
|
1551
|
+
high: 70 + Math.random() * 25, // 70-95
|
|
1552
|
+
},
|
|
1553
|
+
weights: {
|
|
1554
|
+
historyScore: Math.random(),
|
|
1555
|
+
rotationScore: Math.random(),
|
|
1556
|
+
headerAnomalyScore: Math.random(),
|
|
1557
|
+
requestPatternScore: Math.random(),
|
|
1558
|
+
inconsistencyScore: Math.random(),
|
|
1559
|
+
honeypotScore: 1.0, // Garder le honeypot à 1.0 est une bonne pratique
|
|
1560
|
+
},
|
|
1561
|
+
// On pourrait aussi faire muter les `patterns` ici
|
|
1562
|
+
});
|
|
1563
|
+
|
|
1564
|
+
// Le crossover et la mutation doivent maintenant opérer sur des objets complexes
|
|
1565
|
+
const crossover = (c1, c2) => { /* ... logique de croisement pour les objets de config ... */ return c1; };
|
|
1566
|
+
const mutate = (c) => { /* ... logique de mutation pour les objets de config ... */ return c; };
|
|
1567
|
+
|
|
1568
|
+
return Optimization.geneticAlgorithmMultiObjective(
|
|
1569
|
+
createIndividual,
|
|
1570
|
+
fitnessFunction,
|
|
1571
|
+
crossover,
|
|
1572
|
+
mutate,
|
|
1573
|
+
{ generations: 50, populationSize: 50, ...options }
|
|
1574
|
+
);
|
|
1575
|
+
};
|
|
1576
|
+
|
|
1446
1577
|
export { Optimization };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anonympins/fingerprint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "An HTTP(S) client mitigation and anti-bot protection library for Node.js/Express, based on digital fingerprinting and dynamic Proof-of-Work (PoW) challenges.",
|
|
5
5
|
"main": "fingerprint.js",
|
|
6
6
|
"type": "module",
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
"node": ">=20.0.0"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "vitest run"
|
|
11
|
+
"test": "vitest run --reporter=verbose"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"fingerprint.js",
|
|
15
15
|
"fingerprint.client.js",
|
|
16
16
|
"fingerprint.builder.js",
|
|
17
|
+
"pow.solver.js",
|
|
17
18
|
"library.js",
|
|
18
19
|
"redis-store.js",
|
|
19
20
|
"mongodb-store.js",
|
package/pow.solver.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file @/pow.solver.js
|
|
3
|
+
* @description Contient les fonctions côté client pour résoudre les différents types de challenges Proof-of-Work.
|
|
4
|
+
* Fichier compatible à la fois avec l'import de modules ES6 et l'injection directe dans un script HTML.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Résout un challenge CPU basé sur une cible (version inline pour compatibilité HTML).
|
|
11
|
+
* @param {string} clientIp - L'adresse IP du client.
|
|
12
|
+
* @param {string} nonce - Le nonce du challenge.
|
|
13
|
+
* @param {bigint} target - La cible à atteindre.
|
|
14
|
+
* @param {string} clientSecret - Le secret client (optionnel).
|
|
15
|
+
* @param {Function} progressCallback - Callback pour les mises à jour de progression.
|
|
16
|
+
* @returns {Promise<number>} La solution (un nombre entier).
|
|
17
|
+
*/
|
|
18
|
+
export async function solveCpuTargetInline(clientIp, nonce, target, clientSecret = null, progressCallback) {
|
|
19
|
+
const cpuTarget = BigInt(target);
|
|
20
|
+
let cpuSolution = 0;
|
|
21
|
+
while (true) {
|
|
22
|
+
const msg = clientSecret
|
|
23
|
+
? `${clientIp}:${nonce}:${cpuSolution}:${clientSecret}`
|
|
24
|
+
: `${clientIp}:${nonce}:${cpuSolution}`;
|
|
25
|
+
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(msg));
|
|
26
|
+
const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
27
|
+
if (BigInt('0x' + hashHex) < cpuTarget) break;
|
|
28
|
+
cpuSolution++;
|
|
29
|
+
if (cpuSolution % 100000 === 0) {
|
|
30
|
+
await new Promise(r => setTimeout(r, 0));
|
|
31
|
+
if (progressCallback) progressCallback(cpuSolution);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return cpuSolution;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Résout un challenge CPU basé sur une cible (version Web Worker).
|
|
39
|
+
* @param {string} message - Le message à hasher (ex: `ip:nonce:solution:secret`).
|
|
40
|
+
* @param {bigint} target - La cible à atteindre.
|
|
41
|
+
* @returns {Promise<number>} La solution (un nombre entier).
|
|
42
|
+
*/
|
|
43
|
+
export async function solveCpuTarget(message, target) {
|
|
44
|
+
// Vérifie si les Web Workers sont supportés par le navigateur.
|
|
45
|
+
if (typeof(Worker) === "undefined") {
|
|
46
|
+
console.warn("Web Workers not supported. Falling back to main thread calculation (UI may freeze).");
|
|
47
|
+
// Ici, on pourrait remettre l'ancienne implémentation comme solution de secours.
|
|
48
|
+
// Pour la clarté, nous supposons que les workers sont disponibles.
|
|
49
|
+
throw new Error("Web Worker support is required for CPU challenges.");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
// Crée un worker à partir du script dédié. Le chemin doit être accessible publiquement.
|
|
54
|
+
// Assurez-vous que `pow.worker.js` est servi par votre serveur statique.
|
|
55
|
+
const worker = new Worker('./pow.worker.js');
|
|
56
|
+
|
|
57
|
+
worker.onmessage = (event) => {
|
|
58
|
+
resolve(event.data.solution);
|
|
59
|
+
worker.terminate(); // Nettoie le worker une fois le travail terminé.
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
worker.onerror = (error) => {
|
|
63
|
+
reject(error);
|
|
64
|
+
worker.terminate();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Envoie les données du challenge au worker pour qu'il commence le calcul.
|
|
68
|
+
worker.postMessage({ message, target });
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Résout un challenge basé sur la mémoire.
|
|
74
|
+
* @param {string} seed - La graine pour l'initialisation de la mémoire.
|
|
75
|
+
* @param {number} difficulty - La difficulté (en Mo).
|
|
76
|
+
* @returns {Promise<number>} La solution (nombre entier).
|
|
77
|
+
*/
|
|
78
|
+
export async function solveMemory(seed, difficulty) {
|
|
79
|
+
const size = difficulty * 1024 * 1024;
|
|
80
|
+
const buffer = new Uint32Array(size / 4);
|
|
81
|
+
let h = new TextEncoder().encode(seed).reduce((acc, v) => acc + v, 0);
|
|
82
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
83
|
+
buffer[i] = (h = Math.imul(h ^ i, 1597334677));
|
|
84
|
+
}
|
|
85
|
+
let solution = 0;
|
|
86
|
+
const iterations = size / 16;
|
|
87
|
+
let addr = buffer.length > 0 ? buffer[0] % buffer.length : 0;
|
|
88
|
+
for (let i = 0; i < iterations; i++) {
|
|
89
|
+
addr = buffer[addr] % buffer.length;
|
|
90
|
+
solution ^= addr;
|
|
91
|
+
}
|
|
92
|
+
return solution;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Résout un challenge de type "Problème du Voyageur de Commerce" (TSP).
|
|
97
|
+
* NOTE: Ceci est une implémentation simple (heuristique du plus proche voisin) et n'est pas garantie
|
|
98
|
+
* de trouver la solution optimale, mais elle est suffisante pour un challenge.
|
|
99
|
+
* @param {Array<{x: number, y: number}>} cities - Les coordonnées des villes.
|
|
100
|
+
* @param {number} targetMaxDistance - La distance maximale acceptable.
|
|
101
|
+
* @returns {Promise<{path: number[], distance: number}>} Le chemin et la distance.
|
|
102
|
+
*/
|
|
103
|
+
export async function solveTsp(cities, targetMaxDistance) {
|
|
104
|
+
// Utility function to calculate the distance between two cities
|
|
105
|
+
function distance(city1, city2) {
|
|
106
|
+
return Math.sqrt(Math.pow(city1.x - city2.x, 2) + Math.pow(city1.y - city2.y, 2));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Utility function to evaluate the total distance of a path
|
|
110
|
+
function evaluatePathDistance(cities, path) {
|
|
111
|
+
let totalDistance = 0;
|
|
112
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
113
|
+
totalDistance += distance(cities[path[i]], cities[path[i + 1]]);
|
|
114
|
+
}
|
|
115
|
+
totalDistance += distance(cities[path[path.length - 1]], cities[path[0]]); // Return to start
|
|
116
|
+
return totalDistance;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Solveur simple du TSP (heuristique du plus proche voisin)
|
|
120
|
+
function solveTspNearestNeighbor(cities) {
|
|
121
|
+
const numCities = cities.length;
|
|
122
|
+
if (numCities === 0) return [];
|
|
123
|
+
|
|
124
|
+
let currentPath = [];
|
|
125
|
+
let visited = new Array(numCities).fill(false);
|
|
126
|
+
|
|
127
|
+
let currentCityIndex = 0; // Always start with the first city for reproducibility
|
|
128
|
+
currentPath.push(currentCityIndex);
|
|
129
|
+
visited[currentCityIndex] = true;
|
|
130
|
+
|
|
131
|
+
for (let i = 1; i < numCities; i++) {
|
|
132
|
+
let nearestCityIndex = -1;
|
|
133
|
+
let minDistance = Infinity;
|
|
134
|
+
|
|
135
|
+
for (let j = 0; j < numCities; j++) {
|
|
136
|
+
if (!visited[j]) {
|
|
137
|
+
const dist = distance(cities[currentCityIndex], cities[j]);
|
|
138
|
+
if (dist < minDistance) {
|
|
139
|
+
minDistance = dist;
|
|
140
|
+
nearestCityIndex = j;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
currentCityIndex = nearestCityIndex;
|
|
145
|
+
currentPath.push(currentCityIndex);
|
|
146
|
+
visited[currentCityIndex] = true;
|
|
147
|
+
}
|
|
148
|
+
return currentPath;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// To avoid freezing the browser, yield the thread from time to time
|
|
152
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
153
|
+
const solutionPath = solveTspNearestNeighbor(cities);
|
|
154
|
+
const solutionDistance = evaluatePathDistance(cities, solutionPath);
|
|
155
|
+
|
|
156
|
+
return { path: solutionPath, distance: solutionDistance };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Fonction principale qui reçoit un objet challenge et le résout.
|
|
161
|
+
* @param {object} challenge - L'objet challenge reçu du serveur.
|
|
162
|
+
* @returns {Promise<object>} Un objet contenant la ou les solutions.
|
|
163
|
+
*/
|
|
164
|
+
export async function solveChallenge(challenge) {
|
|
165
|
+
const { type, nonce, clientSecret, target, memDifficulty, cities, clientIp, targetMaxDistance } = challenge;
|
|
166
|
+
const solutions = {};
|
|
167
|
+
|
|
168
|
+
switch (type) {
|
|
169
|
+
case 'cpu_target':
|
|
170
|
+
const baseMessageCpu = `:${nonce}`; // L'IP est gérée côté serveur
|
|
171
|
+
solutions.cpu = await solveCpuTarget(baseMessageCpu, BigInt('0x' + target));
|
|
172
|
+
break;
|
|
173
|
+
case 'cpu_mem':
|
|
174
|
+
// Pour les appels API, le client IP n'est pas connu, on ne le met pas dans le message
|
|
175
|
+
const baseMessageCombined = `${nonce}:${clientSecret}`;
|
|
176
|
+
const memSeed = `:${nonce}:${clientSecret}`;
|
|
177
|
+
const [cpuSol, memSol] = await Promise.all([
|
|
178
|
+
// On utilise une version de solveCpuTarget qui n'a pas besoin de l'IP
|
|
179
|
+
solveCpuTargetInline(null, nonce, target, clientSecret),
|
|
180
|
+
solveMemory(memSeed, memDifficulty)
|
|
181
|
+
]);
|
|
182
|
+
solutions.cpu = cpuSol;
|
|
183
|
+
solutions.mem = memSol;
|
|
184
|
+
break;
|
|
185
|
+
case 'cpu_mem_inline':
|
|
186
|
+
// Version inline pour compatibilité HTML avec IP incluse
|
|
187
|
+
const memSeedInline = `${nonce}:${clientSecret}`;
|
|
188
|
+
const [cpuSolInline, memSolInline] = await Promise.all([
|
|
189
|
+
solveCpuTargetInline(clientIp, nonce, target, clientSecret),
|
|
190
|
+
solveMemory(memSeedInline, memDifficulty)
|
|
191
|
+
]);
|
|
192
|
+
solutions.cpu = cpuSolInline;
|
|
193
|
+
solutions.mem = memSolInline;
|
|
194
|
+
break;
|
|
195
|
+
case 'tsp':
|
|
196
|
+
const tspResult = await solveTsp(cities, targetMaxDistance);
|
|
197
|
+
solutions.tsp = tspResult.path;
|
|
198
|
+
solutions.distance = tspResult.distance;
|
|
199
|
+
break;
|
|
200
|
+
default:
|
|
201
|
+
throw new Error(`Unknown challenge type: ${type}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return solutions;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// --- Compatibilité pour l'injection directe dans le HTML ---
|
|
208
|
+
// Si le script est chargé dans un navigateur (window existe), on attache les fonctions nécessaires à window.
|
|
209
|
+
if (typeof window !== 'undefined') {
|
|
210
|
+
window.solveCpuChallengeInline = solveCpuTargetInline;
|
|
211
|
+
window.solveMemoryChallenge = solveMemory;
|
|
212
|
+
window.solveTspChallenge = solveTsp;
|
|
213
|
+
window.solveChallenge = solveChallenge;
|
|
214
|
+
}
|