@anonympins/fingerprint 0.3.8 → 0.4.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +60 -53
  3. package/composer.json +38 -38
  4. package/index.js +4 -4
  5. package/package.json +103 -103
  6. package/phpunit.xml +20 -20
  7. package/public/fp.js +1 -1
  8. package/public/fp.wasm +0 -0
  9. package/src/js/build-client.js +1 -1
  10. package/src/js/fingerprint.client.js +2 -0
  11. package/src/js/fingerprint.js +4429 -4381
  12. package/src/js/mongodb-store.js +79 -79
  13. package/src/js/pow.solver.inline.js +31 -0
  14. package/src/js/pow.solver.js +31 -0
  15. package/src/js/tests/fingerprint.builder.test.js +79 -0
  16. package/src/js/tests/fingerprint.client.init.test.js +120 -0
  17. package/src/js/tests/fingerprint.client.test.js +105 -0
  18. package/src/js/tests/fingerprint.engine.test.js +371 -0
  19. package/src/js/tests/fingerprint.isMalicious.test.js +117 -0
  20. package/src/js/tests/fingerprint.test.js +2319 -0
  21. package/src/js/tests/ip-reputation.test.js +132 -0
  22. package/src/js/tests/ja3AnomalyDetector.test.js +135 -0
  23. package/src/js/tests/library.test.js +96 -0
  24. package/src/js/tests/metrics.test.js +104 -0
  25. package/src/js/tests/pow.solver.test.js +198 -0
  26. package/src/js/tests/problem-manager.test.js +323 -0
  27. package/src/js/tests/stores.test.js +118 -0
  28. package/src/php/Challenge/ChallengeUtils.php +361 -361
  29. package/src/php/Config/SecurityProfiles.php +271 -266
  30. package/src/php/FingerprintBuilder.php +185 -185
  31. package/src/php/FingerprintClient.php +131 -131
  32. package/src/php/FingerprintEngine.php +1006 -1006
  33. package/src/php/Ja3AnomalyDetector.php +227 -227
  34. package/src/php/Optimization/FunctionRegistry.php +62 -62
  35. package/src/php/Optimization/Optimization.php +255 -255
  36. package/src/php/Optimization/OptimizationOperators.php +304 -304
  37. package/src/php/Store/InMemoryStore.php +66 -66
  38. package/src/php/Store/MongoDbStore.php +104 -104
  39. package/src/php/Store/RedisStore.php +53 -53
  40. package/src/php/Tests/ChallengeUtilsTest.php +81 -81
  41. package/src/php/Tests/FingerprintBuilderTest.php +57 -57
  42. package/src/php/Tests/FingerprintClientTest.php +71 -0
  43. package/src/php/Tests/FingerprintEngineTest.php +299 -299
  44. package/src/php/Tests/IpReputationTest.php +156 -156
  45. package/src/php/Tests/Ja3AnomalyDetectorTest.php +179 -179
  46. package/src/php/Tests/MetricsTest.php +45 -45
  47. package/src/php/Tests/PowTest.php +39 -39
  48. package/src/php/Tests/ProblemManagerTest.php +296 -296
  49. package/src/php/Tests/RequestUtilsTest.php +253 -145
  50. package/src/php/Tests/TLSClientHelloParserTest.php +118 -0
  51. package/src/php/Tests/problems.config.json +8 -8
  52. package/src/php/Utils/BigInt.php +144 -144
  53. package/src/php/Utils/Logger.php +29 -29
  54. package/src/php/Utils/MaliciousPatterns.php +58 -58
  55. package/src/php/Utils/MetricsManager.php +166 -166
  56. package/src/php/Utils/RequestUtils.php +1169 -1169
  57. package/src/php/Utils/TLSClientHelloParser.php +117 -0
  58. package/src/php/bin/auto-tune.php +117 -117
package/CHANGELOG.md CHANGED
@@ -1,3 +1,97 @@
1
+ ## Version 0.4.1
2
+
3
+ ### 🐍 Full Python Engine Support & Middlewares
4
+ - **Introducing the Python Fingerprint Engine**: Ported the entire behavioral, cryptographic, and network analysis engine to Python 3.8+, ensuring complete cross-language parity with the Node.js and PHP versions.
5
+ - **JS-Compatible Hashing (`cyrb53` & `imul`)**: Implemented deterministic JS-compatible `cyrb53` hashing by emulating JavaScript's signed 32-bit integer multiplication (`Math.imul`) for consistent fingerprint generation.
6
+ - **Universal Middlewares (ASGI & WSGI)**:
7
+ - `ASGIFingerprintMiddleware`: Universal ASGI 3.0 middleware designed for FastAPI, Starlette, Quart, Sanic, and more.
8
+ - `WSGIFingerprintMiddleware`: Universal WSGI 1.0 middleware compatible with Flask, Django, and legacy python applications, featuring safe asynchronous bridges under the hood.
9
+ - `FastAPIFingerprintMiddleware`: Dedicated integration class utilizing Starlette's `BaseHTTPMiddleware` for seamless FastAPI injection.
10
+ - **Full Detection Suite Ported**:
11
+ - Real-time CPU and Memory Proof-of-Work (PoW) verification.
12
+ - Advanced TLS Spoofing Detection cross-referencing JA3/JA4 signatures against User-Agents.
13
+ - Client Hints and HTTP header anomaly scoring.
14
+ - Honeypot form fields & trap URLs condemnation mechanism.
15
+ - **Asynchronous Storage & State Management**: Ported the TTL-capable memory store to Python (`InMemoryStore`) supporting high-performance asynchronous lookups.
16
+ - **Robust Testing & CI Integration**: Added complete pytest coverage (`test_engine.py`) and integrated Python testing matrix (Python 3.8 to 3.12) in the GitHub Actions CI pipeline.
17
+
18
+ ---
19
+
20
+ ## Version 0.4.0
21
+
22
+ ### ⚡ WebAssembly (WASM) v2 Client-Side Solver & Autoloader
23
+ - **WASM by Default**: WebAssembly acceleration is now active by default (`wasm: true`). The client library automatically attempts to load the WASM modules from the public library path (`/fp.js` and `/fp.wasm`), falling back to pure JavaScript only if loading fails.
24
+ - **Full WASM v2 Solver**: Re-engineered the client-side solvers (`solveCpuTargetInline`, `solveMemory`) to execute fully inside WebAssembly, ensuring high-speed cryptographic evaluations and significantly increasing resistance to client-side reverse engineering and tampering.
25
+
26
+ ### 🧬 Native PHP TLS Fingerprinting & `TLSClientHelloParser`
27
+ - **Introducing TLSClientHelloParser**: Added native binary parser in PHP to inspect raw TLS Client Hello packets. This enables real-time extraction and computation of JA3 and JA4 TLS fingerprints directly inside PHP.
28
+ - **Asynchronous PHP Support**: Built-in support for event-driven PHP runtimes (Swoole, ReactPHP, Workerman). This allows intercepting the raw TCP socket stream, parsing the TLS handshake, and attaching the calculated TLS fingerprint before upgrading to SSL/TLS.
29
+ - **Architectural Documentation**: Added detailed guides on why standard PHP (PHP-FPM, Apache) cannot extract TLS fingerprints natively (due to TLS termination at Nginx/CDN layer) and how to bypass this using modern asynchronous PHP application servers.
30
+
31
+ ### 📊 Documentation & Monitoring Updates
32
+ - **Prometheus Metrics**: Updated `prometheus_metrics.md` to reflect newer indicators and score metrics.
33
+ - **Home Documentation**: Updated `home.md` with guidelines on WASM integrations and native PHP event-loop setups.
34
+
35
+ ---
36
+
37
+ ## Why can't PHP calculate TLS fingerprints natively?
38
+
39
+ Unlike Node.js, which often acts as a direct web server terminating TLS connections itself and exposing socket metadata (like `socket.clientHello`), standard PHP (PHP-FPM, Apache `mod_php`) runs behind a web server or a reverse proxy.
40
+
41
+ 1. **TLS Termination**: Your web server (Nginx, Apache) or CDN (Cloudflare) terminates the TLS connection. It performs the cryptographic handshake and decrypts the traffic.
42
+ 2. **FastCGI / SAPI Abstraction**: The web server forwards a clean, plain-text HTTP request to PHP. By the time PHP gets the request, the raw **TLS Client Hello** packet (which contains the cipher suites and extensions order needed to compute JA3/JA4) has already been processed and discarded.
43
+
44
+ ### Alternatives to compiling server modules:
45
+ If you cannot install custom modules like `ngx_http_ssl_ja3_module` on your server, you can use one of the following approaches:
46
+
47
+ * **Cloudflare**: Cloudflare automatically calculates the JA3 signature and forwards it in the `CF-JA3-Sig` header. You can map this header to `X-JA3-Hash` in your configuration.
48
+ * **AWS Cloudfront**: Cloudfront can be configured to forward TLS client handshakes headers.
49
+ * **PHP Application Servers (Swoole / ReactPHP / Workerman)**: By bypassing standard reverse proxies and handling sockets directly, you can use the built-in native `TLSClientHelloParser` to intercept the binary handshake directly inside PHP's Event Loop.
50
+
51
+ ### Native TLS Extraction inside PHP Event Loops (e.g., Workerman)
52
+
53
+ If you run a raw TCP worker, you can peek at the first incoming bytes of the stream connection (the raw **Client Hello** payload) before upgrading the connection stream to SSL/TLS.
54
+
55
+ Here is a concrete example using the native `TLSClientHelloParser` within a Workerman connection listener:
56
+
57
+ ```php
58
+ <?php
59
+
60
+ use Workerman\Worker;
61
+ use Workerman\Connection\TcpConnection;
62
+ use Anonympins\Fingerprint\Utils\TLSClientHelloParser;
63
+
64
+ $worker = new Worker('tcp://0.0.0.0:443');
65
+
66
+ $worker->onConnect = function(TcpConnection $connection) {
67
+ // Intercept the first chunk of data (the raw TLS handshake)
68
+ $connection->onMessage = function(TcpConnection $connection, $rawData) {
69
+ // Parse the binary payload to calculate JA3 natively in PHP
70
+ $tlsData = TLSClientHelloParser::parse($rawData);
71
+ if ($tlsData) {
72
+ // Attach the fingerprint directly to the connection context
73
+ $connection->ja3Hash = $tlsData['ja3_hash'];
74
+ }
75
+
76
+ // Clear the temporary plain-text parser callback
77
+ $connection->onMessage = null;
78
+
79
+ // Dynamically upgrade the socket transport layer to SSL (initiates cryptographic handshake)
80
+ $connection->transport = 'ssl';
81
+
82
+ // Bind your final application logic/HTTP router
83
+ $connection->onMessage = function($conn, $httpPayload) {
84
+ // $conn->ja3Hash is available here for real-time security score checks!
85
+ };
86
+
87
+ // Pipe back the buffered bytes so the SSL engine can consume the Client Hello
88
+ $connection->consumeFirstLocalBuffer($rawData);
89
+ };
90
+ };
91
+
92
+ Worker::runAll();
93
+ ```
94
+
1
95
  ## Version 0.3.8
2
96
 
3
97
  ### 🎫 Opaque Tickets Implementation (JS/PHP)
package/README.md CHANGED
@@ -1,54 +1,61 @@
1
- # Fingerprint anti-bot protection
2
-
3
- NodeJS tests : [![Test NodeJS](https://img.shields.io/github/actions/workflow/status/anonympins/fingerprint/ci-nodejs.yml)](https://github.com/anonympins/fingerprint/actions/workflows/ci.yml)
4
-
5
- PHP tests : [![PHP](https://img.shields.io/github/actions/workflow/status/anonympins/fingerprint/ci-php.yml)](https://github.com/anonympins/fingerprint/actions/workflows/ci.yml)
6
-
7
- [![Release](https://img.shields.io/github/v/release/anonympins/fingerprint)](https://github.com/anonympins/fingerprint/releases)
8
- [![License](https://img.shields.io/github/license/anonympins/fingerprint)](https://github.com/anonympins/fingerprint/blob/main/LICENSE)
9
- ![GitHub commit activity](https://img.shields.io/github/commit-activity/w/anonympins/fingerprint)
10
-
11
- A multi-layered behavioral, cryptographic, and network analysis engine designed to identify and mitigate malicious requests (bots, scrapers, session hijacking) in real-time. Supports both **Node.js** and **PHP** environments.
12
-
13
- ## Key Features
14
-
15
- - **Multi-Layered Detection**: Combines TLS/JA3/JA4 analysis, HTTP header consistency checks, IP reputation, and behavioral tracking (mouse movements, keystrokes).
16
- - **Adaptive Mitigation**: Imposes progressive cryptographic Proof-of-Work (PoW) challenges to block automated clients without impacting legitimate human users.
17
- - **Predefined Profiles**: Ready-to-use security profiles (`balanced`, `strict`, `api`, `blog`, `ecommerce`) tailored to your specific use case.
18
-
19
- ## Quick Start
20
-
21
- ### Node.js
22
-
23
- ```bash
24
- npm install @anonympins/fingerprint
25
- ```
26
-
27
- ### PHP
28
-
29
- ```bash
30
- composer require anonympins/fingerprint
31
- ```
32
-
33
- ## Documentation
34
-
35
- To prevent documentation drift, all detailed guides and reference materials are maintained in the `doc/` directory. Please refer to [these resources](home) to configure and integrate the engine:
36
-
37
- 1. **[Key Concepts & Suspicion Vectors](https://github.com/anonympins/fingerprint/wiki/concepts)**: Learn how the engine calculates suspicion scores across the 15 distinct vectors and manages the Proof-of-Work mitigation layers.
38
- 2. **[Node.js Integration Guide](https://github.com/anonympins/fingerprint/wiki/nodejs_integration)**: Step-by-step instructions for Express.js middleware and raw HTTP server integrations.
39
- 3. **[PHP Integration Guide](https://github.com/anonympins/fingerprint/wiki/php_integration)**: Configuration details for direct PHP integration, TLS fingerprinting forwarding via Nginx/Apache, and securing Prometheus metrics.
40
- 4. **[Full Configuration Options](https://github.com/anonympins/fingerprint/wiki/full_options)**: Complete parameter list for fine-tuning weights, custom honeypots, and security profile overrides.
41
- 5. **[API Reference](https://github.com/anonympins/fingerprint/wiki/api_reference)**: Public API signatures and guides on substituting the in-memory datastore with Redis or MongoDB.
42
-
43
- Start with the **[Documentation Portal](home)** for a complete index.
44
-
45
- ## Contributing
46
-
47
- We welcome community contributions! Please read our **[Contributing Guidelines](https://github.com/anonympins/fingerprint/blob/main/CONTRIBUTING.md)** for information on:
48
- - Setting up your local environment (Node.js and PHP).
49
- - Running the test suites (`Vitest` and `PHPUnit`).
50
- - Coding and pull request standards.
51
-
52
- ## License
53
-
1
+ # Fingerprint anti-bot protection
2
+
3
+ NodeJS tests : [![Test NodeJS](https://img.shields.io/github/actions/workflow/status/anonympins/fingerprint/ci-nodejs.yml)](https://github.com/anonympins/fingerprint/actions/workflows/ci.yml) / PHP tests : [![PHP](https://img.shields.io/github/actions/workflow/status/anonympins/fingerprint/ci-php.yml)](https://github.com/anonympins/fingerprint/actions/workflows/ci-php.yml) / Python tests : [![PHP](https://img.shields.io/github/actions/workflow/status/anonympins/fingerprint/ci-php.yml)](https://github.com/anonympins/fingerprint/actions/workflows/ci-python.yml)
4
+
5
+ [![Release](https://img.shields.io/github/v/release/anonympins/fingerprint)](https://github.com/anonympins/fingerprint/releases)
6
+ [![License](https://img.shields.io/github/license/anonympins/fingerprint)](https://github.com/anonympins/fingerprint/blob/main/LICENSE)
7
+ ![GitHub commit activity](https://img.shields.io/github/commit-activity/w/anonympins/fingerprint)
8
+
9
+ A multi-layered behavioral, cryptographic, and network analysis engine designed to identify and mitigate malicious requests (bots, scrapers, session hijacking) in real-time. Supports both **Node.js** and **PHP** environments.
10
+
11
+ ![illustration](https://i.ibb.co/fV1QT6Mf/image-c6e10859baae53bb595112ec08fc9e27.png)
12
+
13
+ ## Key Features
14
+
15
+ - **Multi-Layered Detection**: Combines TLS/JA3/JA4 analysis, HTTP header consistency checks, IP reputation, and behavioral tracking (mouse movements, keystrokes).
16
+ - **Adaptive Mitigation**: Imposes progressive cryptographic Proof-of-Work (PoW) challenges to block automated clients without impacting legitimate human users.
17
+ - **Predefined Profiles**: Ready-to-use security profiles (`balanced`, `strict`, `api`, `blog`, `ecommerce`) tailored to your specific use case.
18
+
19
+ ## Quick Start
20
+
21
+ ### Node.js
22
+
23
+ ```bash
24
+ npm install @anonympins/fingerprint
25
+ ```
26
+
27
+ ### PHP
28
+
29
+ ```bash
30
+ composer require anonympins/fingerprint
31
+ ```
32
+
33
+ ## Documentation
34
+
35
+ To prevent documentation drift, all detailed guides and reference materials are maintained in the `doc/` directory. Please refer to [these resources](https://github.com/anonympins/fingerprint/wiki/home) to configure and integrate the engine:
36
+
37
+ 1. **[Key Concepts & Suspicion Vectors](https://github.com/anonympins/fingerprint/wiki/concepts)**: Learn how the engine calculates suspicion scores across the 15 distinct vectors and manages the Proof-of-Work mitigation layers.
38
+ 2. **[Node.js Integration Guide](https://github.com/anonympins/fingerprint/wiki/nodejs_integration)**: Step-by-step instructions for Express.js middleware and raw HTTP server integrations.
39
+ 3. **[PHP Integration Guide](https://github.com/anonympins/fingerprint/wiki/php_integration)**: Configuration details for direct PHP integration, TLS fingerprinting forwarding via Nginx/Apache, and securing Prometheus metrics.
40
+ 4. **[Full Configuration Options](https://github.com/anonympins/fingerprint/wiki/full_options)**: Complete parameter list for fine-tuning weights, custom honeypots, and security profile overrides.
41
+ 5. **[API Reference](https://github.com/anonympins/fingerprint/wiki/api_reference)**: Public API signatures and guides on substituting the in-memory datastore with Redis or MongoDB.
42
+
43
+ Start with the **[Documentation Portal](https://github.com/anonympins/fingerprint/wiki/home)** for a complete index.
44
+
45
+ ## Contributing
46
+
47
+ We welcome community contributions! Please read our **[Contributing Guidelines](https://github.com/anonympins/fingerprint/blob/main/CONTRIBUTING.md)** for information on:
48
+ - Setting up your local environment (Node.js and PHP).
49
+ - Running the test suites (`Vitest` and `PHPUnit`).
50
+ - Coding and pull request standards.
51
+
52
+ Thanks to our contributors :
53
+ - [anonympins](https://github.com/anonympins)
54
+
55
+ ## Used actively on
56
+
57
+ - https://primals.net and sub-sites
58
+
59
+ ## License
60
+
54
61
  This project is licensed under the MIT License.
package/composer.json CHANGED
@@ -1,39 +1,39 @@
1
- {
2
- "name": "anonympins/fingerprint",
3
- "description": "Advanced anti-bot library for Node.js and PHP using multi-layer fingerprinting, behavioral analysis, and adaptive Proof-of-Work (PoW) challenges.",
4
- "type": "library",
5
- "license": "MIT",
6
- "authors": [
7
- {
8
- "name": "anonympins",
9
- "homepage": "https://github.com/anonympins"
10
- }
11
- ],
12
- "require": {
13
- "php": ">=8.0",
14
- "ext-json": "*",
15
- "ext-bcmath": "*"
16
- },
17
- "require-dev": {
18
- "phpunit/phpunit": "^9.5"
19
- },
20
- "autoload": {
21
- "psr-4": {
22
- "Anonympins\\Fingerprint\\": "src/php/"
23
- }
24
- },
25
- "autoload-dev": {
26
- "psr-4": {
27
- "Anonympins\\Fingerprint\\Tests\\": "src/php/Tests/"
28
- }
29
- },
30
- "suggest": {
31
- "ext-gmp": "Recommended for high-performance big integer arithmetic in cryptographic challenges.",
32
- "ext-redis": "Required to use the Redis store adapter.",
33
- "ext-pdo": "Required to use SQL store adapters (e.g., for PostgreSQL, MySQL)."
34
- },
35
- "config": {
36
- "sort-packages": true
37
- },
38
- "minimum-stability": "stable"
1
+ {
2
+ "name": "anonympins/fingerprint",
3
+ "description": "Advanced anti-bot library for Node.js and PHP using multi-layer fingerprinting, behavioral analysis, and adaptive Proof-of-Work (PoW) challenges.",
4
+ "type": "library",
5
+ "license": "MIT",
6
+ "authors": [
7
+ {
8
+ "name": "anonympins",
9
+ "homepage": "https://github.com/anonympins"
10
+ }
11
+ ],
12
+ "require": {
13
+ "php": ">=8.0",
14
+ "ext-json": "*",
15
+ "ext-bcmath": "*"
16
+ },
17
+ "require-dev": {
18
+ "phpunit/phpunit": "^9.5"
19
+ },
20
+ "autoload": {
21
+ "psr-4": {
22
+ "Anonympins\\Fingerprint\\": "src/php/"
23
+ }
24
+ },
25
+ "autoload-dev": {
26
+ "psr-4": {
27
+ "Anonympins\\Fingerprint\\Tests\\": "src/php/Tests/"
28
+ }
29
+ },
30
+ "suggest": {
31
+ "ext-gmp": "Recommended for high-performance big integer arithmetic in cryptographic challenges.",
32
+ "ext-redis": "Required to use the Redis store adapter.",
33
+ "ext-pdo": "Required to use SQL store adapters (e.g., for PostgreSQL, MySQL)."
34
+ },
35
+ "config": {
36
+ "sort-packages": true
37
+ },
38
+ "minimum-stability": "stable"
39
39
  }
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
- /**
2
- * Point d'entrée principal pour le package Node.js.
3
- * Ré-exporte toutes les fonctionnalités de la bibliothèque principale.
4
- */
1
+ /**
2
+ * Point d'entrée principal pour le package Node.js.
3
+ * Ré-exporte toutes les fonctionnalités de la bibliothèque principale.
4
+ */
5
5
  export * from './src/js/fingerprint.js';
package/package.json CHANGED
@@ -1,103 +1,103 @@
1
- {
2
- "name": "@anonympins/fingerprint",
3
- "version": "0.3.8",
4
- "description": "Advanced anti-bot library for Node.js using multi-layer fingerprinting (JA3, client-side, headers), behavioral analysis, and adaptive Proof-of-Work (PoW) challenges to mitigate scraping, scalping, and automated threats.",
5
- "main": "index.js",
6
- "type": "module",
7
- "engines": {
8
- "node": ">=20.0.0"
9
- },
10
- "scripts": {
11
- "test": "vitest run --reporter=verbose",
12
- "build": "node src/js/build-client.js"
13
- },
14
- "exports": {
15
- ".": "./index.js",
16
- "./client": {
17
- "import": "./src/js/fingerprint.client.obfuscated.js",
18
- "require": "./src/js/fingerprint.client.obfuscated.js",
19
- "browser": "./src/js/fingerprint.client.obfuscated.js"
20
- },
21
- "./client/src/*": "./src/js/*",
22
- "./fp.wasm": "./public/fp.wasm",
23
- "./fp.js": "./public/fp.js",
24
- "./package.json": "./package.json"
25
- },
26
- "files": [
27
- "index.js",
28
- "src/js/",
29
- "public/",
30
- "README.md",
31
- "LICENSE",
32
- "CHANGELOG.md",
33
- "src/php/",
34
- "composer.json",
35
- "phpunit.xml"
36
- ],
37
- "repository": {
38
- "type": "git",
39
- "url": "git+https://github.com/anonympins/fingerprint.git"
40
- },
41
- "keywords": [
42
- "anti-bot",
43
- "bot-detection",
44
- "security",
45
- "middleware",
46
- "express",
47
- "nodejs",
48
- "fingerprint",
49
- "device-fingerprinting",
50
- "ja3",
51
- "tls-fingerprinting",
52
- "proof-of-work",
53
- "pow",
54
- "useful-proof-of-work",
55
- "upow",
56
- "waf",
57
- "scraping",
58
- "scalping",
59
- "mitigation",
60
- "rate-limiting",
61
- "honeypot",
62
- "behavioral-analysis"
63
- ],
64
- "author": "anonympins",
65
- "license": "MIT",
66
- "bugs": {
67
- "url": "https://github.com/anonympins/fingerprint/issues"
68
- },
69
- "homepage": "https://github.com/anonympins/fingerprint#readme",
70
- "devDependencies": {
71
- "cookie-parser": "^1.4.6",
72
- "express": "^4.22.2",
73
- "javascript-obfuscator": "^4.1.0",
74
- "jsdom": "^24.0.0",
75
- "prom-client": "^15.1.2",
76
- "terser": "^5.30.3",
77
- "vitest": "^4.1.11"
78
- },
79
- "overrides": {
80
- "body-parser": "2.3.0",
81
- "qs": "6.16.0"
82
- },
83
- "peerDependencies": {
84
- "ioredis": "^5.3.2",
85
- "knex": ">=3.0.0",
86
- "mongodb": "^6.3.0",
87
- "sqlite3": "^5.1.7"
88
- },
89
- "peerDependenciesMeta": {
90
- "ioredis": {
91
- "optional": true
92
- },
93
- "mongodb": {
94
- "optional": true
95
- },
96
- "knex": {
97
- "optional": true
98
- },
99
- "sqlite3": {
100
- "optional": true
101
- }
102
- }
103
- }
1
+ {
2
+ "name": "@anonympins/fingerprint",
3
+ "version": "0.4.1",
4
+ "description": "Advanced anti-bot library for Node.js using multi-layer fingerprinting (JA3, client-side, headers), behavioral analysis, and adaptive Proof-of-Work (PoW) challenges to mitigate scraping, scalping, and automated threats.",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=20.0.0"
9
+ },
10
+ "scripts": {
11
+ "test": "vitest run --reporter=verbose",
12
+ "build": "node src/js/build-client.js"
13
+ },
14
+ "exports": {
15
+ ".": "./index.js",
16
+ "./client": {
17
+ "import": "./src/js/fingerprint.client.obfuscated.js",
18
+ "require": "./src/js/fingerprint.client.obfuscated.js",
19
+ "browser": "./src/js/fingerprint.client.obfuscated.js"
20
+ },
21
+ "./client/src/*": "./src/js/*",
22
+ "./fp.wasm": "./public/fp.wasm",
23
+ "./fp.js": "./public/fp.js",
24
+ "./package.json": "./package.json"
25
+ },
26
+ "files": [
27
+ "index.js",
28
+ "src/js/",
29
+ "public/",
30
+ "README.md",
31
+ "LICENSE",
32
+ "CHANGELOG.md",
33
+ "src/php/",
34
+ "composer.json",
35
+ "phpunit.xml"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/anonympins/fingerprint.git"
40
+ },
41
+ "keywords": [
42
+ "anti-bot",
43
+ "bot-detection",
44
+ "security",
45
+ "middleware",
46
+ "express",
47
+ "nodejs",
48
+ "fingerprint",
49
+ "device-fingerprinting",
50
+ "ja3",
51
+ "tls-fingerprinting",
52
+ "proof-of-work",
53
+ "pow",
54
+ "useful-proof-of-work",
55
+ "upow",
56
+ "waf",
57
+ "scraping",
58
+ "scalping",
59
+ "mitigation",
60
+ "rate-limiting",
61
+ "honeypot",
62
+ "behavioral-analysis"
63
+ ],
64
+ "author": "anonympins",
65
+ "license": "MIT",
66
+ "bugs": {
67
+ "url": "https://github.com/anonympins/fingerprint/issues"
68
+ },
69
+ "homepage": "https://github.com/anonympins/fingerprint#readme",
70
+ "devDependencies": {
71
+ "cookie-parser": "^1.4.6",
72
+ "express": "^4.22.2",
73
+ "javascript-obfuscator": "^4.1.0",
74
+ "jsdom": "^24.0.0",
75
+ "prom-client": "^15.1.2",
76
+ "terser": "^5.30.3",
77
+ "vitest": "^4.1.11"
78
+ },
79
+ "overrides": {
80
+ "body-parser": "2.3.0",
81
+ "qs": "6.16.0"
82
+ },
83
+ "peerDependencies": {
84
+ "ioredis": "^5.3.2",
85
+ "knex": ">=3.0.0",
86
+ "mongodb": "^6.3.0",
87
+ "sqlite3": "^5.1.7"
88
+ },
89
+ "peerDependenciesMeta": {
90
+ "ioredis": {
91
+ "optional": true
92
+ },
93
+ "mongodb": {
94
+ "optional": true
95
+ },
96
+ "knex": {
97
+ "optional": true
98
+ },
99
+ "sqlite3": {
100
+ "optional": true
101
+ }
102
+ }
103
+ }
package/phpunit.xml CHANGED
@@ -1,21 +1,21 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
- xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
4
- bootstrap="vendor/autoload.php"
5
- colors="true">
6
- <testsuites>
7
- <testsuite name="default">
8
- <directory suffix="Test.php">src/php/Tests</directory>
9
- </testsuite>
10
- </testsuites>
11
-
12
- <coverage>
13
- <include>
14
- <directory suffix=".php">./src/php</directory>
15
- </include>
16
- <exclude>
17
- <directory>./src/php/Tests/*</directory>
18
- <directory>./src/php/Tests</directory>
19
- </exclude>
20
- </coverage>
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
+ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
4
+ bootstrap="vendor/autoload.php"
5
+ colors="true">
6
+ <testsuites>
7
+ <testsuite name="default">
8
+ <directory suffix="Test.php">src/php/Tests</directory>
9
+ </testsuite>
10
+ </testsuites>
11
+
12
+ <coverage>
13
+ <include>
14
+ <directory suffix=".php">./src/php</directory>
15
+ </include>
16
+ <exclude>
17
+ <directory>./src/php/Tests/*</directory>
18
+ <directory>./src/php/Tests</directory>
19
+ </exclude>
20
+ </coverage>
21
21
  </phpunit>
package/public/fp.js CHANGED
@@ -1,2 +1,2 @@
1
- var createFingerprintModule=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var programArgs=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}programArgs=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var isFileURI=filename=>filename.startsWith("file://");class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAPU8=new Uint8Array(b);HEAPU32=new Uint32Array(b)}function preRun(){var preRun=Module["preRun"];if(preRun){if(typeof preRun=="function")preRun=[preRun];onPreRuns.push(...preRun)}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["e"]()}function postRun(){var postRun=Module["postRun"];if(postRun){if(typeof postRun=="function")postRun=[postRun];onPostRuns.push(...postRun)}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("fp.wasm")}function getBinarySync(file){if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();var instantiateWasm=Module["instantiateWasm"];if(instantiateWasm){return new Promise(resolve=>{instantiateWasm(info,inst=>resolve(receiveInstance(inst)))})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var onPreRuns=[];var noExitRuntime=true;var HEAP8;var HEAPU32;class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var uncaughtExceptionCount=0;var __Unwind_RaiseException=ex=>{abort()};var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);uncaughtExceptionCount++;__Unwind_RaiseException(ptr)};var __abort_js=()=>abort("");var abortOnCannotGrowMemory=requestedSize=>{abort("OOM")};var HEAPU8;var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;abortOnCannotGrowMemory(requestedSize)};{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["arguments"])programArgs=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var preInit=Module["preInit"];if(preInit){if(typeof preInit=="function")Module["preInit"]=preInit=[preInit];while(preInit.length>0){preInit.shift()()}}}var _hash_string,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_hash_string=Module["_hash_string"]=wasmExports["f"];memory=wasmMemory=wasmExports["d"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={c:___cxa_throw,a:__abort_js,b:_emscripten_resize_heap};async function run(){preRun();var setStatus=Module["setStatus"];if(setStatus){setStatus("Running...");await new Promise(resolve=>setTimeout(resolve,1));setTimeout(setStatus,1,"")}if(ABORT)return;initRuntime();Module["onRuntimeInitialized"]?.();postRun()}var wasmExports;wasmExports=await createWasm();await run();
1
+ var createFingerprintModule=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var programArgs=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}programArgs=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var isFileURI=filename=>filename.startsWith("file://");class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAPU8=new Uint8Array(b);HEAPU32=new Uint32Array(b)}function preRun(){var preRun=Module["preRun"];if(preRun){if(typeof preRun=="function")preRun=[preRun];onPreRuns.push(...preRun)}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["e"]()}function postRun(){var postRun=Module["postRun"];if(postRun){if(typeof postRun=="function")postRun=[postRun];onPostRuns.push(...postRun)}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("fp.wasm")}function getBinarySync(file){if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();var instantiateWasm=Module["instantiateWasm"];if(instantiateWasm){return new Promise(resolve=>{instantiateWasm(info,inst=>resolve(receiveInstance(inst)))})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var onPreRuns=[];var noExitRuntime=true;var HEAP8;var HEAPU32;class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var uncaughtExceptionCount=0;var __Unwind_RaiseException=ex=>{abort()};var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);uncaughtExceptionCount++;__Unwind_RaiseException(ptr)};var __abort_js=()=>abort("");var abortOnCannotGrowMemory=requestedSize=>{abort("OOM")};var HEAPU8;var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;abortOnCannotGrowMemory(requestedSize)};{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["arguments"])programArgs=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var preInit=Module["preInit"];if(preInit){if(typeof preInit=="function")Module["preInit"]=preInit=[preInit];while(preInit.length>0){preInit.shift()()}}}var _hash_string,_solve_cpu_target,_solve_memory_challenge,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_hash_string=Module["_hash_string"]=wasmExports["f"];_solve_cpu_target=Module["_solve_cpu_target"]=wasmExports["g"];_solve_memory_challenge=Module["_solve_memory_challenge"]=wasmExports["h"];memory=wasmMemory=wasmExports["d"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={c:___cxa_throw,a:__abort_js,b:_emscripten_resize_heap};async function run(){preRun();var setStatus=Module["setStatus"];if(setStatus){setStatus("Running...");await new Promise(resolve=>setTimeout(resolve,1));setTimeout(setStatus,1,"")}if(ABORT)return;initRuntime();Module["onRuntimeInitialized"]?.();postRun()}var wasmExports;wasmExports=await createWasm();await run();
2
2
  ;return Module}})();if(typeof exports==="object"&&typeof module==="object"){module.exports=createFingerprintModule;module.exports.default=createFingerprintModule}else if(typeof define==="function"&&define["amd"])define([],()=>createFingerprintModule);
package/public/fp.wasm CHANGED
Binary file
@@ -14,7 +14,7 @@ const __dirname = dirname(__filename);
14
14
  function buildWasm() {
15
15
  return new Promise((resolve) => {
16
16
  console.log('Attempting to build WASM module (optional)...');
17
- const command = "em++ src/cpp/main.cpp src/cpp/utils.cpp -o public/fp.js -s WASM=1 -s MODULARIZE=1 -s EXPORT_NAME='createFingerprintModule' -s \"EXPORTED_FUNCTIONS=['_hash_string']\" -O3 --no-entry";
17
+ const command = "em++ src/cpp/main.cpp src/cpp/utils.cpp -o public/fp.js -s WASM=1 -s MODULARIZE=1 -s EXPORT_NAME='createFingerprintModule' -s \"EXPORTED_FUNCTIONS=['_hash_string','_solve_cpu_target','_solve_memory_challenge']\" -O3 --no-entry";
18
18
 
19
19
  exec(command, (error, stdout, stderr) => {
20
20
  if (error) {
@@ -553,6 +553,8 @@ const ClientLibrary = {
553
553
  if (typeof wasmModule._hash_string !== 'function') {
554
554
  throw new Error('WASM module did not export _hash_string.');
555
555
  }
556
+ window.wasmModule = wasmModule;
557
+ ClientLibrary.wasmModule = wasmModule;
556
558
 
557
559
  // 4. Remplacer la fonction de hachage par la version WASM
558
560
  activeCyrb53 = (str) => {