@anonympins/fingerprint 0.5.1 → 0.5.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 (42) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +118 -115
  3. package/package.json +1 -1
  4. package/src/js/dynamic-wasm.js +52 -13
  5. package/src/js/fingerprint.builder.js +169 -168
  6. package/src/js/fingerprint.client.js +1271 -1189
  7. package/src/js/fingerprint.client.obfuscated.js +1 -1
  8. package/src/js/fingerprint.js +436 -162
  9. package/src/js/fingerprint.utils.js +30 -0
  10. package/src/js/gpu_pow.solver.js +96 -1
  11. package/src/js/library.js +40 -21
  12. package/src/js/mongodb-store.js +165 -79
  13. package/src/js/pow.solver.inline.js +627 -30
  14. package/src/js/pow.solver.js +453 -31
  15. package/src/js/problem-manager.js +1 -1
  16. package/src/js/redis-store.js +114 -42
  17. package/src/js/tests/dns-circuit-breaker.test.js +55 -0
  18. package/src/js/tests/fingerprint.client.init.test.js +143 -143
  19. package/src/js/tests/fingerprint.client.test.js +128 -128
  20. package/src/js/tests/fingerprint.engine.test.js +370 -370
  21. package/src/js/tests/fingerprint.test.js +119 -5
  22. package/src/js/tests/gpu_pow.test.js +81 -0
  23. package/src/js/tests/ip-reputation.test.js +1 -1
  24. package/src/js/tests/pow.solver.test.js +9 -6
  25. package/src/js/tests/usefulWorkSolver.test.js +162 -0
  26. package/src/php/Challenge/ChallengeUtils.php +137 -35
  27. package/src/php/FingerprintClient.php +22 -5
  28. package/src/php/FingerprintEngine.php +335 -58
  29. package/src/php/Optimization/Optimization.php +23 -16
  30. package/src/php/Optimization/OptimizationOperators.php +27 -0
  31. package/src/php/ProblemManager.php +1 -1
  32. package/src/php/Store/MongoDbStore.php +184 -104
  33. package/src/php/Store/RedisStore.php +91 -11
  34. package/src/php/Tests/ChallengeUtilsTest.php +285 -285
  35. package/src/php/Tests/DnsCircuitBreakerTest.php +106 -0
  36. package/src/php/Tests/IpReputationTest.php +1 -1
  37. package/src/php/Tests/PowTest.php +1 -1
  38. package/src/php/Tests/RequestUtilsTest.php +29 -0
  39. package/src/php/Tests/TLSClientHelloParserTest.php +177 -117
  40. package/src/php/Utils/BigInt.php +202 -202
  41. package/src/php/Utils/RequestUtils.php +74 -21
  42. package/src/php/Utils/TLSClientHelloParser.php +212 -116
@@ -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
  }
@@ -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
  });