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