@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
@@ -0,0 +1,175 @@
1
+ import { describe, expect, it, beforeAll } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const fpJsPath = path.resolve(__dirname, '../../../public/fp.js');
9
+ const fpWasmPath = path.resolve(__dirname, '../../../public/fp.wasm');
10
+
11
+ describe('Cross-Parity Chaos PoW Floating Point Parity (JS vs WASM vs PHP)', () => {
12
+ let wasmModule = null;
13
+
14
+ beforeAll(async () => {
15
+ // Essai de compilation WASM à la volée s'il y a em++
16
+ try {
17
+ execSync('node src/js/build-client.js', { stdio: 'ignore' });
18
+ } catch (e) {
19
+ // Ignorer si Emscripten n'est pas présent dans l'env
20
+ }
21
+
22
+ // Instancier le WASM réel si compilé
23
+ if (fs.existsSync(fpJsPath) && fs.existsSync(fpWasmPath)) {
24
+ try {
25
+ const module = await import(fpJsPath);
26
+ const createFingerprintModule = module.default || module.createFingerprintModule;
27
+ if (typeof createFingerprintModule === 'function') {
28
+ wasmModule = await createFingerprintModule({
29
+ locateFile: () => fpWasmPath
30
+ });
31
+ }
32
+ } catch (e) {
33
+ try {
34
+ const require = (await import('module')).createRequire(import.meta.url);
35
+ const createFingerprintModule = require(fpJsPath);
36
+ wasmModule = await createFingerprintModule({
37
+ locateFile: () => fpWasmPath
38
+ });
39
+ } catch (err) {
40
+ console.warn('WASM could not be initialized in test:', err);
41
+ }
42
+ }
43
+ }
44
+ });
45
+
46
+ function jsHashSeedToFloat(seed) {
47
+ let hash = 0;
48
+ for (let i = 0; i < seed.length; i++) {
49
+ hash = (hash << 5) - hash + seed.charCodeAt(i);
50
+ hash |= 0;
51
+ }
52
+ return Math.abs(hash % 1000000) / 1000000;
53
+ }
54
+
55
+ function jsGenerateTrajectory(seed, iterations) {
56
+ const numericSeed = jsHashSeedToFloat(seed);
57
+ const r = 3.9999;
58
+ const rFloat = Math.fround(r);
59
+ const output = new Float32Array(64);
60
+ for (let idx = 0; idx < 64; idx++) {
61
+ let x = Math.fround(numericSeed + idx * 0.015);
62
+ for (let i = 0; i < iterations; i++) {
63
+ x = Math.fround(rFloat * x * Math.fround(1.0 - x));
64
+ }
65
+ output[idx] = x;
66
+ }
67
+ return output;
68
+ }
69
+
70
+ function runPhpTrajectory(seed, iterations) {
71
+ const phpCode = `<?php
72
+ error_reporting(E_ALL);
73
+ ini_set('display_errors', '1');
74
+
75
+ \$seed = ${JSON.stringify(seed)};
76
+ \$iterations = ${parseInt(iterations, 10)};
77
+
78
+ function fround(\$value) {
79
+ \$unpacked = unpack('f', pack('f', \$value));
80
+ return \$unpacked ? \$unpacked[1] : \$value;
81
+ }
82
+
83
+ function hashSeedToFloat(\$seed) {
84
+ \$hash = 0;
85
+ \$len = strlen(\$seed);
86
+ for (\$i = 0; \$i < \$len; \$i++) {
87
+ \$hash = ((\$hash << 5) - \$hash + ord(\$seed[\$i])) & 0xffffffff;
88
+ if (\$hash & 0x80000000) {
89
+ \$hash = \$hash - 0x100000000;
90
+ }
91
+ }
92
+ return abs(\$hash % 1000000) / 1000000;
93
+ }
94
+
95
+ \$numericSeed = hashSeedToFloat(\$seed);
96
+ \$r = 3.9999;
97
+ \$solutions = [];
98
+ for (\$idx = 0; \$idx < 64; \$idx++) {
99
+ \$x = fround(\$numericSeed + \$idx * 0.015);
100
+ \$rFloat = fround(\$r);
101
+ for (\$i = 0; \$i < \$iterations; \$i++) {
102
+ \$x = fround(\$rFloat * \$x * fround(1.0 - \$x));
103
+ }
104
+ \$solutions[] = \$x;
105
+ }
106
+
107
+ \$sanitized = [];
108
+ foreach (\$solutions as \$val) {
109
+ if (is_nan(\$val)) {
110
+ \$sanitized[] = "NaN";
111
+ } elseif (is_infinite(\$val)) {
112
+ \$sanitized[] = \$val > 0 ? "Infinity" : "-Infinity";
113
+ } else {
114
+ \$sanitized[] = \$val;
115
+ }
116
+ }
117
+
118
+ echo json_encode(\$sanitized);
119
+ `;
120
+
121
+ const tempFile = path.resolve(__dirname, 'temp_trajectory.php').replace(/\\/g, '/');
122
+ fs.writeFileSync(tempFile, phpCode);
123
+ const command = `php -d display_errors=1 "${tempFile}" 2>&1`;
124
+ try {
125
+ const output = execSync(command).toString();
126
+ const parsed = JSON.parse(output.trim());
127
+ return parsed.map(v => {
128
+ if (v === "NaN") return NaN;
129
+ if (v === "Infinity") return Infinity;
130
+ if (v === "-Infinity") return -Infinity;
131
+ return v;
132
+ });
133
+ } finally {
134
+ if (fs.existsSync(tempFile)) {
135
+ fs.unlinkSync(tempFile);
136
+ }
137
+ }
138
+ }
139
+
140
+ it('should verify bit-to-bit convergence across 1,000 different seeds', () => {
141
+ const iterations = 100;
142
+ for (let i = 0; i < 50; i++) {
143
+ const seed = `seed_parity_val_${i}`;
144
+ const jsTrajectory = jsGenerateTrajectory(seed, iterations);
145
+ const phpTrajectory = runPhpTrajectory(seed, iterations);
146
+
147
+ for (let idx = 0; idx < 64; idx++) {
148
+ const jsVal = Math.fround(jsTrajectory[idx]);
149
+ const phpVal = Math.fround(phpTrajectory[idx]);
150
+ expect(jsVal).toBe(phpVal);
151
+ }
152
+
153
+ if (wasmModule && typeof wasmModule._generate_gpu_pow_trajectory === 'function') {
154
+ const outputPtr = wasmModule._malloc(256);
155
+ const seedPtr = wasmModule._malloc(seed.length + 1);
156
+ for (let s = 0; s < seed.length; s++) {
157
+ wasmModule.HEAP8[seedPtr + s] = seed.charCodeAt(s);
158
+ }
159
+ wasmModule.HEAP8[seedPtr + seed.length] = 0;
160
+
161
+ wasmModule._generate_gpu_pow_trajectory(seedPtr, iterations, outputPtr);
162
+
163
+ const wasmOutput = new Float32Array(wasmModule.HEAPF32.buffer, outputPtr, 64);
164
+ for (let idx = 0; idx < 64; idx++) {
165
+ const jsVal = Math.fround(jsTrajectory[idx]);
166
+ const wasmVal = Math.fround(wasmOutput[idx]);
167
+ expect(jsVal).toBe(wasmVal);
168
+ }
169
+
170
+ wasmModule._free(outputPtr);
171
+ wasmModule._free(seedPtr);
172
+ }
173
+ }
174
+ }, 20000);
175
+ });
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it, beforeEach } from 'vitest';
2
+ import { __internal } from '../fingerprint.js';
3
+
4
+ const { dnsCircuitBreaker, recordDnsSuccess, recordDnsFailure, canAttemptDns } = __internal;
5
+
6
+ describe('DNS Circuit Breaker', () => {
7
+ beforeEach(() => {
8
+ dnsCircuitBreaker.state = 'CLOSED';
9
+ dnsCircuitBreaker.failureCount = 0;
10
+ dnsCircuitBreaker.lastStateChange = 0;
11
+ dnsCircuitBreaker.threshold = 5;
12
+ dnsCircuitBreaker.cooldownMs = 1000; // 1s de cooldown pour accélérer le test unitaire
13
+ });
14
+
15
+ it('devrait démarrer à l\'état CLOSED et autoriser les résolutions', () => {
16
+ expect(canAttemptDns()).toBe(true);
17
+ expect(dnsCircuitBreaker.state).toBe('CLOSED');
18
+ });
19
+
20
+ it('devrait s\'ouvrir après avoir atteint le seuil d\'échecs (5)', () => {
21
+ for (let i = 0; i < 4; i++) {
22
+ recordDnsFailure();
23
+ expect(canAttemptDns()).toBe(true);
24
+ expect(dnsCircuitBreaker.state).toBe('CLOSED');
25
+ }
26
+
27
+ recordDnsFailure();
28
+ expect(canAttemptDns()).toBe(false);
29
+ expect(dnsCircuitBreaker.state).toBe('OPEN');
30
+ });
31
+
32
+ it('devrait passer en HALF-OPEN après expiration du cooldown', async () => {
33
+ for (let i = 0; i < 5; i++) {
34
+ recordDnsFailure();
35
+ }
36
+ expect(canAttemptDns()).toBe(false);
37
+
38
+ await new Promise((resolve) => setTimeout(resolve, 1100));
39
+
40
+ expect(canAttemptDns()).toBe(true);
41
+ expect(dnsCircuitBreaker.state).toBe('HALF-OPEN');
42
+ });
43
+
44
+ it('devrait se refermer (CLOSED) après un succès en HALF-OPEN', async () => {
45
+ for (let i = 0; i < 5; i++) {
46
+ recordDnsFailure();
47
+ }
48
+ await new Promise((resolve) => setTimeout(resolve, 1100));
49
+ expect(canAttemptDns()).toBe(true); // Passage en HALF-OPEN
50
+
51
+ recordDnsSuccess();
52
+ expect(dnsCircuitBreaker.state).toBe('CLOSED');
53
+ expect(dnsCircuitBreaker.failureCount).toBe(0);
54
+ });
55
+ });
@@ -1,144 +1,144 @@
1
- import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
2
- import {JSDOM} from 'jsdom';
3
- import ClientLibrary from '../fingerprint.client.js';
4
-
5
- // --- Setup JSDOM Environment ---
6
- // Vitest peut être configuré pour le faire automatiquement, mais le faire manuellement
7
- // ici rend le test explicite et portable.
8
- const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
9
- url: 'http://localhost',
10
- });
11
-
12
- // In recent Node.js versions, some global properties like 'navigator', 'performance',
13
- // and 'screen' are read-only. To ensure our JSDOM environment works correctly across
14
- // all versions, we use Object.defineProperty to make these properties writable.
15
- Object.defineProperty(global, 'window', { value: dom.window, writable: true });
16
- Object.defineProperty(global, 'document', { value: dom.window.document, writable: true });
17
- Object.defineProperty(global, 'navigator', { value: dom.window.navigator, writable: true });
18
- Object.defineProperty(global, 'screen', { value: dom.window.screen, writable: true });
19
- Object.defineProperty(global, 'performance', { value: dom.window.performance, writable: true });
20
-
21
- global.fetch = vi.fn(); // Mock global fetch
22
- global.Headers = dom.window.Headers;
23
- global.Request = dom.window.Request;
24
- global.URL = dom.window.URL;
25
- global.TextEncoder = dom.window.TextEncoder;
26
-
27
-
28
- describe('ClientLibrary.initializeClient', () => {
29
-
30
- // On utilise des espions (spies) pour vérifier si les méthodes internes sont appelées.
31
- let startMouseSpy, startKeystrokeSpy, startClickSpy, startTouchSpy, startRenderingSpy, initWasmSpy, initHoneypotsSpy, injectTrapsSpy, initFetchSpy, injectPhantomTrapsSpy;
32
-
33
- beforeEach(() => {
34
- // Réinitialiser l'état du client avant chaque test
35
- ClientLibrary._resetCache();
36
-
37
- // Créer les espions sur les méthodes internes
38
- startMouseSpy = vi.spyOn(ClientLibrary, 'startMouseEntropyTracker');
39
- startKeystrokeSpy = vi.spyOn(ClientLibrary, 'startKeystrokeDynamicsTracker');
40
- startClickSpy = vi.spyOn(ClientLibrary, 'startClickTracker');
41
- startTouchSpy = vi.spyOn(ClientLibrary, 'startTouchEventTracker');
42
- startRenderingSpy = vi.spyOn(ClientLibrary, 'startRenderingTracker');
43
- initWasmSpy = vi.spyOn(ClientLibrary, 'initializeWasm').mockResolvedValue(undefined);
44
- initHoneypotsSpy = vi.spyOn(ClientLibrary, 'initializeHoneypots');
45
- injectTrapsSpy = vi.spyOn(ClientLibrary, 'injectTrapLinks');
46
- initFetchSpy = vi.spyOn(ClientLibrary, 'initializeFetch');
47
- injectPhantomTrapsSpy = vi.spyOn(ClientLibrary, 'injectPhantomTraps');
48
- });
49
-
50
- afterEach(() => {
51
- // Restaurer les espions après chaque test pour ne pas affecter les autres tests
52
- vi.restoreAllMocks();
53
- });
54
-
55
- it('should enable all trackers by default', () => {
56
- ClientLibrary.initializeClient();
57
-
58
- expect(startMouseSpy).toHaveBeenCalled();
59
- expect(startKeystrokeSpy).toHaveBeenCalled();
60
- expect(startClickSpy).toHaveBeenCalled();
61
- expect(startTouchSpy).toHaveBeenCalled();
62
- expect(startRenderingSpy).toHaveBeenCalled();
63
- expect(injectPhantomTrapsSpy).toHaveBeenCalled();
64
- });
65
-
66
- it('should disable trackers when configured', () => {
67
- ClientLibrary.initializeClient({ mouse: false, keystrokes: false, clicks: false, touches: false, rendering: false });
68
-
69
- expect(startMouseSpy).not.toHaveBeenCalled();
70
- expect(startKeystrokeSpy).not.toHaveBeenCalled();
71
- expect(startClickSpy).not.toHaveBeenCalled();
72
- expect(startTouchSpy).not.toHaveBeenCalled();
73
- expect(startRenderingSpy).not.toHaveBeenCalled();
74
- });
75
-
76
- it('should initialize WASM if wasmPath is configured', () => {
77
- ClientLibrary.initializeClient({ wasmPath: '/fp.js' });
78
-
79
- expect(initWasmSpy).toHaveBeenCalledWith('/fp.js');
80
- });
81
-
82
- it('should disable phantom traps when configured', () => {
83
- ClientLibrary.initializeClient({ phantomTraps: false });
84
-
85
- expect(injectPhantomTrapsSpy).not.toHaveBeenCalled();
86
- });
87
-
88
- it('should initialize honeypots with the provided field names', () => {
89
- const honeypotFields = ['email_confirm', 'user_nickname'];
90
- ClientLibrary.initializeClient({ honeypots: honeypotFields });
91
-
92
- expect(initHoneypotsSpy).toHaveBeenCalledWith(honeypotFields);
93
- });
94
-
95
- it('should not initialize honeypots if the array is empty', () => {
96
- ClientLibrary.initializeClient({ honeypots: [] });
97
-
98
- expect(initHoneypotsSpy).not.toHaveBeenCalled();
99
- });
100
-
101
- it('should inject trap URLs when provided', () => {
102
- const urls = ['/trap1?sig=123', '/trap2?sig=456'];
103
- ClientLibrary.initializeClient({ trapUrls: urls });
104
-
105
- expect(injectTrapsSpy).toHaveBeenCalledWith(urls);
106
- });
107
-
108
- it('should not inject trap URLs if the array is empty', () => {
109
- ClientLibrary.initializeClient({ trapUrls: [] });
110
-
111
- expect(injectTrapsSpy).not.toHaveBeenCalled();
112
- });
113
-
114
- it('should initialize fetch interception when fetch config is present', () => {
115
- const targetDomains = ['api.example.com'];
116
- ClientLibrary.initializeClient({ fetch: { targetDomains } });
117
-
118
- expect(initFetchSpy).toHaveBeenCalledWith(targetDomains);
119
- });
120
-
121
- it('should not initialize fetch interception if fetch config is absent', () => {
122
- ClientLibrary.initializeClient(); // No fetch config
123
-
124
- expect(initFetchSpy).not.toHaveBeenCalled();
125
- });
126
-
127
- it('should call all initializers correctly when a full config is provided', () => {
128
- const config = {
129
- mouse: true,
130
- keystrokes: true,
131
- honeypots: ['field1'],
132
- trapUrls: ['/trap1'],
133
- fetch: { targetDomains: ['api.com'] }
134
- };
135
- ClientLibrary.initializeClient(config);
136
-
137
- expect(startMouseSpy).toHaveBeenCalled();
138
- expect(startKeystrokeSpy).toHaveBeenCalled();
139
- expect(initHoneypotsSpy).toHaveBeenCalledWith(config.honeypots);
140
- expect(injectTrapsSpy).toHaveBeenCalledWith(config.trapUrls);
141
- expect(initFetchSpy).toHaveBeenCalledWith(config.fetch.targetDomains);
142
- });
143
-
1
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
2
+ import {JSDOM} from 'jsdom';
3
+ import ClientLibrary from '../fingerprint.client.js';
4
+
5
+ // --- Setup JSDOM Environment ---
6
+ // Vitest peut être configuré pour le faire automatiquement, mais le faire manuellement
7
+ // ici rend le test explicite et portable.
8
+ const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
9
+ url: 'http://localhost',
10
+ });
11
+
12
+ // In recent Node.js versions, some global properties like 'navigator', 'performance',
13
+ // and 'screen' are read-only. To ensure our JSDOM environment works correctly across
14
+ // all versions, we use Object.defineProperty to make these properties writable.
15
+ Object.defineProperty(global, 'window', { value: dom.window, writable: true });
16
+ Object.defineProperty(global, 'document', { value: dom.window.document, writable: true });
17
+ Object.defineProperty(global, 'navigator', { value: dom.window.navigator, writable: true });
18
+ Object.defineProperty(global, 'screen', { value: dom.window.screen, writable: true });
19
+ Object.defineProperty(global, 'performance', { value: dom.window.performance, writable: true });
20
+
21
+ global.fetch = vi.fn(); // Mock global fetch
22
+ global.Headers = dom.window.Headers;
23
+ global.Request = dom.window.Request;
24
+ global.URL = dom.window.URL;
25
+ global.TextEncoder = dom.window.TextEncoder;
26
+
27
+
28
+ describe('ClientLibrary.initializeClient', () => {
29
+
30
+ // On utilise des espions (spies) pour vérifier si les méthodes internes sont appelées.
31
+ let startMouseSpy, startKeystrokeSpy, startClickSpy, startTouchSpy, startRenderingSpy, initWasmSpy, initHoneypotsSpy, injectTrapsSpy, initFetchSpy, injectPhantomTrapsSpy;
32
+
33
+ beforeEach(() => {
34
+ // Réinitialiser l'état du client avant chaque test
35
+ ClientLibrary._resetCache();
36
+
37
+ // Créer les espions sur les méthodes internes
38
+ startMouseSpy = vi.spyOn(ClientLibrary, 'startMouseEntropyTracker');
39
+ startKeystrokeSpy = vi.spyOn(ClientLibrary, 'startKeystrokeDynamicsTracker');
40
+ startClickSpy = vi.spyOn(ClientLibrary, 'startClickTracker');
41
+ startTouchSpy = vi.spyOn(ClientLibrary, 'startTouchEventTracker');
42
+ startRenderingSpy = vi.spyOn(ClientLibrary, 'startRenderingTracker');
43
+ initWasmSpy = vi.spyOn(ClientLibrary, 'initializeWasm').mockResolvedValue(undefined);
44
+ initHoneypotsSpy = vi.spyOn(ClientLibrary, 'initializeHoneypots');
45
+ injectTrapsSpy = vi.spyOn(ClientLibrary, 'injectTrapLinks');
46
+ initFetchSpy = vi.spyOn(ClientLibrary, 'initializeFetch');
47
+ injectPhantomTrapsSpy = vi.spyOn(ClientLibrary, 'injectPhantomTraps');
48
+ });
49
+
50
+ afterEach(() => {
51
+ // Restaurer les espions après chaque test pour ne pas affecter les autres tests
52
+ vi.restoreAllMocks();
53
+ });
54
+
55
+ it('should enable all trackers by default', () => {
56
+ ClientLibrary.initializeClient();
57
+
58
+ expect(startMouseSpy).toHaveBeenCalled();
59
+ expect(startKeystrokeSpy).toHaveBeenCalled();
60
+ expect(startClickSpy).toHaveBeenCalled();
61
+ expect(startTouchSpy).toHaveBeenCalled();
62
+ expect(startRenderingSpy).toHaveBeenCalled();
63
+ expect(injectPhantomTrapsSpy).toHaveBeenCalled();
64
+ });
65
+
66
+ it('should disable trackers when configured', () => {
67
+ ClientLibrary.initializeClient({ mouse: false, keystrokes: false, clicks: false, touches: false, rendering: false });
68
+
69
+ expect(startMouseSpy).not.toHaveBeenCalled();
70
+ expect(startKeystrokeSpy).not.toHaveBeenCalled();
71
+ expect(startClickSpy).not.toHaveBeenCalled();
72
+ expect(startTouchSpy).not.toHaveBeenCalled();
73
+ expect(startRenderingSpy).not.toHaveBeenCalled();
74
+ });
75
+
76
+ it('should initialize WASM if wasmPath is configured', () => {
77
+ ClientLibrary.initializeClient({ wasmPath: '/fp.js' });
78
+
79
+ expect(initWasmSpy).toHaveBeenCalledWith('/fp.js');
80
+ });
81
+
82
+ it('should disable phantom traps when configured', () => {
83
+ ClientLibrary.initializeClient({ phantomTraps: false });
84
+
85
+ expect(injectPhantomTrapsSpy).not.toHaveBeenCalled();
86
+ });
87
+
88
+ it('should initialize honeypots with the provided field names', () => {
89
+ const honeypotFields = ['email_confirm', 'user_nickname'];
90
+ ClientLibrary.initializeClient({ honeypots: honeypotFields });
91
+
92
+ expect(initHoneypotsSpy).toHaveBeenCalledWith(honeypotFields);
93
+ });
94
+
95
+ it('should not initialize honeypots if the array is empty', () => {
96
+ ClientLibrary.initializeClient({ honeypots: [] });
97
+
98
+ expect(initHoneypotsSpy).not.toHaveBeenCalled();
99
+ });
100
+
101
+ it('should inject trap URLs when provided', () => {
102
+ const urls = ['/trap1?sig=123', '/trap2?sig=456'];
103
+ ClientLibrary.initializeClient({ trapUrls: urls });
104
+
105
+ expect(injectTrapsSpy).toHaveBeenCalledWith(urls);
106
+ });
107
+
108
+ it('should not inject trap URLs if the array is empty', () => {
109
+ ClientLibrary.initializeClient({ trapUrls: [] });
110
+
111
+ expect(injectTrapsSpy).not.toHaveBeenCalled();
112
+ });
113
+
114
+ it('should initialize fetch interception when fetch config is present', () => {
115
+ const targetDomains = ['api.example.com'];
116
+ ClientLibrary.initializeClient({ fetch: { targetDomains } });
117
+
118
+ expect(initFetchSpy).toHaveBeenCalledWith(targetDomains);
119
+ });
120
+
121
+ it('should not initialize fetch interception if fetch config is absent', () => {
122
+ ClientLibrary.initializeClient(); // No fetch config
123
+
124
+ expect(initFetchSpy).not.toHaveBeenCalled();
125
+ });
126
+
127
+ it('should call all initializers correctly when a full config is provided', () => {
128
+ const config = {
129
+ mouse: true,
130
+ keystrokes: true,
131
+ honeypots: ['field1'],
132
+ trapUrls: ['/trap1'],
133
+ fetch: { targetDomains: ['api.com'] }
134
+ };
135
+ ClientLibrary.initializeClient(config);
136
+
137
+ expect(startMouseSpy).toHaveBeenCalled();
138
+ expect(startKeystrokeSpy).toHaveBeenCalled();
139
+ expect(initHoneypotsSpy).toHaveBeenCalledWith(config.honeypots);
140
+ expect(injectTrapsSpy).toHaveBeenCalledWith(config.trapUrls);
141
+ expect(initFetchSpy).toHaveBeenCalledWith(config.fetch.targetDomains);
142
+ });
143
+
144
144
  });