curl-cffi-node 0.1.7

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 (46) hide show
  1. package/README.md +716 -0
  2. package/curl-cffi-node.darwin-arm64.node +0 -0
  3. package/curl-cffi-node.darwin-x64.node +0 -0
  4. package/curl-cffi-node.linux-arm64-gnu.node +0 -0
  5. package/curl-cffi-node.linux-x64-gnu.node +0 -0
  6. package/curl-cffi-node.win32-x64-msvc.node +0 -0
  7. package/dist/binding.d.ts +2 -0
  8. package/dist/binding.d.ts.map +1 -0
  9. package/dist/binding.js +90 -0
  10. package/dist/binding.js.map +1 -0
  11. package/dist/enums.d.ts +87 -0
  12. package/dist/enums.d.ts.map +1 -0
  13. package/dist/enums.js +101 -0
  14. package/dist/enums.js.map +1 -0
  15. package/dist/errors.d.ts +61 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +116 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/index.d.ts +77 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +88 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/response.d.ts +86 -0
  24. package/dist/response.d.ts.map +1 -0
  25. package/dist/response.js +137 -0
  26. package/dist/response.js.map +1 -0
  27. package/dist/session.d.ts +93 -0
  28. package/dist/session.d.ts.map +1 -0
  29. package/dist/session.js +228 -0
  30. package/dist/session.js.map +1 -0
  31. package/dist/websocket.d.ts +68 -0
  32. package/dist/websocket.d.ts.map +1 -0
  33. package/dist/websocket.js +176 -0
  34. package/dist/websocket.js.map +1 -0
  35. package/npm/darwin-arm64/curl-cffi-node.darwin-arm64.node +0 -0
  36. package/npm/darwin-arm64/package.json +19 -0
  37. package/npm/darwin-x64/curl-cffi-node.darwin-x64.node +0 -0
  38. package/npm/darwin-x64/package.json +19 -0
  39. package/npm/linux-arm64-gnu/curl-cffi-node.linux-arm64-gnu.node +0 -0
  40. package/npm/linux-arm64-gnu/package.json +22 -0
  41. package/npm/linux-x64-gnu/curl-cffi-node.linux-x64-gnu.node +0 -0
  42. package/npm/linux-x64-gnu/package.json +22 -0
  43. package/npm/linux-x64-musl/package.json +14 -0
  44. package/npm/win32-x64-msvc/curl-cffi-node.win32-x64-msvc.node +0 -0
  45. package/npm/win32-x64-msvc/package.json +19 -0
  46. package/package.json +83 -0
package/README.md ADDED
@@ -0,0 +1,716 @@
1
+ # curl-cffi-node
2
+
3
+ > **Node.js HTTP client with browser TLS/JA3/HTTP2 fingerprint impersonation** — powered by [curl-impersonate](https://github.com/lexiforest/curl-impersonate) via [napi-rs](https://napi.rs).
4
+
5
+ [![npm version](https://img.shields.io/npm/v/curl-cffi-node.svg?color=cb3837&logo=npm)](https://www.npmjs.com/package/curl-cffi-node)
6
+ [![npm downloads](https://img.shields.io/npm/dm/curl-cffi-node.svg?color=blue&logo=npm)](https://www.npmjs.com/package/curl-cffi-node)
7
+ [![Publish](https://github.com/meodemsao/curl-cffi-node/actions/workflows/publish.yml/badge.svg)](https://github.com/meodemsao/curl-cffi-node/actions/workflows/publish.yml)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
9
+ [![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg?logo=node.js)](https://nodejs.org/)
10
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-3178c6.svg?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
11
+ [![Platform](https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-lightgrey.svg)](#supported-platforms)
12
+
13
+ Bypass TLS fingerprinting, JA3 detection, and HTTP/2 fingerprinting used by Cloudflare, Akamai, and other anti-bot systems. `curl-cffi-node` impersonates real browsers at the TLS and HTTP/2 protocol level — not just User-Agent strings.
14
+
15
+ ---
16
+
17
+ ## Table of Contents
18
+
19
+ - [Features](#features)
20
+ - [Why curl-cffi-node?](#why-curl-cffi-node)
21
+ - [Quick Start](#quick-start)
22
+ - [Installation](#installation)
23
+ - [Usage](#usage)
24
+ - [Simple Requests](#simple-requests)
25
+ - [Session with Browser Impersonation](#session-with-browser-impersonation)
26
+ - [POST with JSON Body](#post-with-json-body)
27
+ - [Custom Headers & Query Parameters](#custom-headers--query-parameters)
28
+ - [Cookie Management](#cookie-management)
29
+ - [Proxy Support](#proxy-support)
30
+ - [WebSocket with Impersonated TLS](#websocket-with-impersonated-tls)
31
+ - [Custom TLS Fingerprint](#custom-tls-fingerprint)
32
+ - [Streaming Response](#streaming-response)
33
+ - [Async Concurrent Requests](#async-concurrent-requests)
34
+ - [Low-Level Curl Handle](#low-level-curl-handle)
35
+ - [API Reference](#api-reference)
36
+ - [Session](#session)
37
+ - [Response](#response)
38
+ - [CurlWebSocket](#curlwebsocket)
39
+ - [Error Hierarchy](#error-hierarchy)
40
+ - [Shorthand Functions](#shorthand-functions)
41
+ - [Browser Impersonation Targets](#browser-impersonation-targets)
42
+ - [Supported Platforms](#supported-platforms)
43
+ - [Performance](#performance)
44
+ - [Building from Source](#building-from-source)
45
+ - [Testing](#testing)
46
+ - [Comparison](#comparison)
47
+ - [Contributing](#contributing)
48
+ - [License](#license)
49
+
50
+ ---
51
+
52
+ ## Features
53
+
54
+ - 🎭 **Browser Impersonation** — Chrome, Safari, Firefox, Edge TLS/JA3/HTTP2 fingerprints
55
+ - 🔒 **TLS Fingerprint Matching** — Exact JA3 hash reproduction for anti-bot bypass
56
+ - 🌐 **HTTP/2 Fingerprinting** — SETTINGS, WINDOW_UPDATE, PRIORITY frames match real browsers
57
+ - 🍪 **Cookie Persistence** — Automatic cookie jar with import/export (Netscape format)
58
+ - ♻️ **Connection Reuse** — Persistent handles for keep-alive and connection pooling
59
+ - 🔌 **WebSocket** — WebSocket connections with impersonated TLS handshakes
60
+ - ⚡ **Async I/O** — Non-blocking `performAsync()` via thread pool (no event loop blocking)
61
+ - 🌍 **Proxy Support** — HTTP, HTTPS, SOCKS4, SOCKS5 with authentication
62
+ - 📡 **Streaming** — Node.js `Readable` stream responses for large downloads
63
+ - 🛡️ **Typed Errors** — `TimeoutError`, `ConnectionError`, `TLSError`, `ProxyError`
64
+ - 📦 **Zero Dependencies** — Prebuilt native binaries, no runtime dependencies
65
+ - 🔄 **Dual Format** — ESM and CommonJS support with TypeScript declarations
66
+
67
+ ---
68
+
69
+ ## Why curl-cffi-node?
70
+
71
+ Traditional HTTP clients (`node-fetch`, `axios`, `undici`) create **identifiable TLS fingerprints** that anti-bot systems detect instantly. Changing the User-Agent header alone is not enough — modern bot detection analyzes:
72
+
73
+ | Detection Layer | What They Check | Regular HTTP Clients | curl-cffi-node |
74
+ |---|---|---|---|
75
+ | **TLS Fingerprint (JA3)** | Cipher suites, extensions, elliptic curves | ❌ Node.js default | ✅ Matches Chrome/Safari |
76
+ | **HTTP/2 Fingerprint** | SETTINGS, WINDOW_UPDATE, PRIORITY frames | ❌ Generic | ✅ Matches target browser |
77
+ | **Header Order** | Order of HTTP headers in request | ❌ Alphabetical | ✅ Matches browser order |
78
+ | **TLS Extensions** | ALPN, SNI, supported groups | ❌ Node.js/OpenSSL | ✅ Browser-identical |
79
+
80
+ ---
81
+
82
+ ## Quick Start
83
+
84
+ ```typescript
85
+ import { Session } from 'curl-cffi-node';
86
+
87
+ // Create a session impersonating Chrome 116
88
+ const session = new Session({ impersonate: 'chrome116' });
89
+
90
+ // Make a request — your TLS fingerprint matches a real Chrome browser
91
+ const response = await session.get('https://example.com');
92
+
93
+ console.log(response.status); // 200
94
+ console.log(response.json()); // { ... }
95
+ console.log(response.elapsed); // 142.5 (ms)
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Installation
101
+
102
+ ```bash
103
+ npm install curl-cffi-node
104
+ ```
105
+
106
+ Prebuilt native binaries are available for all [supported platforms](#supported-platforms). No compilation needed.
107
+
108
+ ### From Source
109
+
110
+ If you need to build from source (custom platform or modifications):
111
+
112
+ ```bash
113
+ git clone --recursive https://github.com/meodemsao/curl-cffi-node.git
114
+ cd curl-cffi-node
115
+ npm install
116
+ npm run build
117
+ ```
118
+
119
+ > Requires Rust toolchain (1.70+) and system dependencies for curl-impersonate. See [Building from Source](#building-from-source) for details.
120
+
121
+ ---
122
+
123
+ ## Usage
124
+
125
+ ### Simple Requests
126
+
127
+ Use shorthand functions for one-off requests:
128
+
129
+ ```typescript
130
+ import { get, post, del } from 'curl-cffi-node';
131
+
132
+ // GET
133
+ const r1 = await get('https://httpbin.org/get', { impersonate: 'chrome116' });
134
+ console.log(r1.json());
135
+
136
+ // POST with JSON
137
+ const r2 = await post('https://httpbin.org/post', {
138
+ data: { name: 'curl-cffi-node', version: '0.1.0' },
139
+ impersonate: 'chrome110',
140
+ });
141
+ console.log(r2.json());
142
+
143
+ // DELETE
144
+ const r3 = await del('https://httpbin.org/delete');
145
+ console.log(r3.status); // 200
146
+ ```
147
+
148
+ ### Session with Browser Impersonation
149
+
150
+ Sessions persist cookies and reuse connections across requests:
151
+
152
+ ```typescript
153
+ import { Session } from 'curl-cffi-node';
154
+
155
+ const session = new Session({
156
+ impersonate: 'chrome116', // TLS fingerprint target
157
+ headers: { 'X-Custom': 'value' }, // Default headers
158
+ timeout: 30, // Default timeout (seconds)
159
+ followRedirects: true, // Follow 3xx redirects
160
+ maxRedirects: 10, // Max redirect chain length
161
+ verify: true, // Verify SSL certificates
162
+ });
163
+
164
+ // All requests share cookies and connection
165
+ const r1 = await session.get('https://example.com/login');
166
+ const r2 = await session.post('https://example.com/api/data', {
167
+ data: { key: 'value' },
168
+ });
169
+ // r2 automatically sends cookies from r1
170
+ ```
171
+
172
+ ### POST with JSON Body
173
+
174
+ Pass an object as `data` — it's automatically serialized with `Content-Type: application/json`:
175
+
176
+ ```typescript
177
+ const session = new Session({ impersonate: 'chrome116' });
178
+
179
+ // Object → auto JSON serialization
180
+ const r = await session.post('https://httpbin.org/post', {
181
+ data: { username: 'admin', password: 'secret' },
182
+ });
183
+
184
+ // String body
185
+ const r2 = await session.post('https://httpbin.org/post', {
186
+ data: 'raw string body',
187
+ headers: { 'Content-Type': 'text/plain' },
188
+ });
189
+
190
+ // Buffer body
191
+ const r3 = await session.post('https://httpbin.org/post', {
192
+ data: Buffer.from('binary data'),
193
+ });
194
+ ```
195
+
196
+ ### Custom Headers & Query Parameters
197
+
198
+ ```typescript
199
+ const session = new Session({
200
+ headers: { 'Authorization': 'Bearer token123' }, // Session-level
201
+ });
202
+
203
+ const r = await session.get('https://httpbin.org/get', {
204
+ headers: { 'X-Request-ID': 'abc-123' }, // Request-level (merged)
205
+ params: { page: 1, limit: 50, search: 'nodejs' }, // → ?page=1&limit=50&search=nodejs
206
+ });
207
+
208
+ console.log(r.json().args);
209
+ // { page: '1', limit: '50', search: 'nodejs' }
210
+ ```
211
+
212
+ ### Cookie Management
213
+
214
+ ```typescript
215
+ const session = new Session({ impersonate: 'chrome110' });
216
+
217
+ // Cookies are automatically stored from Set-Cookie headers
218
+ await session.get('https://httpbin.org/cookies/set?session_id=abc123');
219
+
220
+ // Read current cookies
221
+ console.log(session.cookies);
222
+ // ['httpbin.org\tFALSE\t/\tFALSE\t0\tsession_id\tabc123']
223
+
224
+ // Export cookies (Netscape format) for persistence
225
+ const exported = session.exportCookies();
226
+ // Save to file, database, etc.
227
+
228
+ // Import cookies into a new session
229
+ const session2 = new Session({ cookies: exported });
230
+
231
+ // Or import later
232
+ session2.importCookies([
233
+ 'example.com\tFALSE\t/\tTRUE\t0\ttoken\txyz789'
234
+ ]);
235
+
236
+ // Clear all cookies
237
+ session.clearCookies();
238
+
239
+ // Clear cookies for specific domain
240
+ session.clearCookies('httpbin.org');
241
+ ```
242
+
243
+ ### Proxy Support
244
+
245
+ ```typescript
246
+ const session = new Session({
247
+ impersonate: 'chrome116',
248
+ proxy: 'http://user:pass@proxy.example.com:8080',
249
+ });
250
+
251
+ // All requests through proxy
252
+ const r = await session.get('https://httpbin.org/ip');
253
+ console.log(r.json().origin); // Proxy IP
254
+
255
+ // Per-request proxy override
256
+ const r2 = await session.get('https://httpbin.org/ip', {
257
+ proxy: 'socks5://localhost:1080',
258
+ });
259
+
260
+ // SOCKS5 with authentication
261
+ const r3 = await session.get('https://httpbin.org/ip', {
262
+ proxy: 'socks5://user:pass@socks-proxy.example.com:1080',
263
+ });
264
+ ```
265
+
266
+ ### WebSocket with Impersonated TLS
267
+
268
+ ```typescript
269
+ import { CurlWebSocket } from 'curl-cffi-node';
270
+
271
+ const ws = new CurlWebSocket('wss://echo.websocket.org', {
272
+ impersonate: 'chrome116',
273
+ headers: { 'Origin': 'https://example.com' },
274
+ });
275
+
276
+ ws.on('open', () => {
277
+ console.log('Connected with Chrome TLS fingerprint!');
278
+ ws.send('Hello from curl-cffi-node!');
279
+ });
280
+
281
+ ws.on('message', (data) => {
282
+ console.log('Received:', data);
283
+ ws.close();
284
+ });
285
+
286
+ ws.on('close', () => console.log('Disconnected'));
287
+ ws.on('error', (err) => console.error('Error:', err));
288
+
289
+ await ws.connect();
290
+ ```
291
+
292
+ ### Custom TLS Fingerprint
293
+
294
+ For advanced users who need specific cipher suites or TLS settings:
295
+
296
+ ```typescript
297
+ import { Curl, CurlOpt } from 'curl-cffi-node';
298
+
299
+ const curl = new Curl();
300
+ curl.setoptStr(CurlOpt.Url, 'https://tls.peet.ws/api/all');
301
+
302
+ // Custom cipher suite
303
+ curl.setoptStr(CurlOpt.SslCipherList,
304
+ 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256'
305
+ );
306
+
307
+ // Custom elliptic curves
308
+ curl.setoptStr(CurlOpt.SslEcCurves,
309
+ 'X25519:P-256:P-384'
310
+ );
311
+
312
+ // Custom HTTP/2 settings
313
+ curl.setoptStr(CurlOpt.Http2PseudoHeadersOrder, 'm,p,a,s');
314
+ curl.setoptStr(CurlOpt.Http2Settings,
315
+ '1:65536;3:1000;4:6291456;6:262144'
316
+ );
317
+
318
+ const result = await curl.performAsync();
319
+ const tls = JSON.parse(result.body.toString());
320
+ console.log('JA3:', tls.tls.ja3_hash);
321
+ ```
322
+
323
+ ### Streaming Response
324
+
325
+ For large downloads or server-sent events:
326
+
327
+ ```typescript
328
+ const session = new Session({ impersonate: 'chrome116' });
329
+ const response = await session.get('https://httpbin.org/bytes/1048576');
330
+
331
+ // Get a Node.js Readable stream
332
+ const stream = response.stream();
333
+
334
+ let totalBytes = 0;
335
+ stream.on('data', (chunk) => {
336
+ totalBytes += chunk.length;
337
+ });
338
+ stream.on('end', () => {
339
+ console.log(`Downloaded ${totalBytes} bytes`);
340
+ });
341
+
342
+ // Or pipe to a file
343
+ import { createWriteStream } from 'fs';
344
+ response.stream().pipe(createWriteStream('download.bin'));
345
+ ```
346
+
347
+ ### Async Concurrent Requests
348
+
349
+ `performAsync()` runs in a thread pool — the Node.js event loop stays free:
350
+
351
+ ```typescript
352
+ import { Curl, CurlOpt } from 'curl-cffi-node';
353
+
354
+ const urls = [
355
+ 'https://httpbin.org/get?id=1',
356
+ 'https://httpbin.org/get?id=2',
357
+ 'https://httpbin.org/get?id=3',
358
+ 'https://httpbin.org/get?id=4',
359
+ 'https://httpbin.org/get?id=5',
360
+ ];
361
+
362
+ // All 5 requests execute in parallel
363
+ const results = await Promise.all(
364
+ urls.map((url) => {
365
+ const curl = new Curl();
366
+ curl.setoptStr(CurlOpt.Url, url);
367
+ curl.setoptLong(CurlOpt.TimeoutMs, 10000);
368
+ return curl.performAsync();
369
+ })
370
+ );
371
+
372
+ results.forEach((r, i) => {
373
+ console.log(`Request ${i + 1}: ${r.statusCode}`);
374
+ });
375
+ ```
376
+
377
+ ### Low-Level Curl Handle
378
+
379
+ Full control over curl options for advanced use cases:
380
+
381
+ ```typescript
382
+ import { Curl, CurlOpt, CurlInfo, BrowserType } from 'curl-cffi-node';
383
+
384
+ const curl = new Curl();
385
+
386
+ // Impersonate Chrome 116
387
+ curl.impersonate(BrowserType.Chrome116);
388
+
389
+ // Or use string target
390
+ curl.impersonateStr('safari15_5');
391
+
392
+ // Set options
393
+ curl.setoptStr(CurlOpt.Url, 'https://httpbin.org/get');
394
+ curl.setoptLong(CurlOpt.FollowLocation, 1);
395
+ curl.setoptLong(CurlOpt.MaxRedirs, 10);
396
+ curl.setoptLong(CurlOpt.TimeoutMs, 30000);
397
+ curl.setoptList(CurlOpt.HttpHeader, [
398
+ 'Accept: application/json',
399
+ 'Accept-Language: en-US,en;q=0.9',
400
+ ]);
401
+
402
+ // Synchronous perform (blocks event loop)
403
+ const result = curl.perform();
404
+
405
+ // Or async perform (non-blocking)
406
+ const result2 = await curl.performAsync();
407
+
408
+ console.log('Status:', result.statusCode);
409
+ console.log('Body:', result.body.toString());
410
+ console.log('Headers:', result.headers);
411
+
412
+ // Get info after perform
413
+ console.log('Total time:', curl.getinfo(CurlInfo.TotalTime));
414
+ console.log('Effective URL:', curl.getinfo(CurlInfo.EffectiveUrl));
415
+
416
+ // Reuse handle
417
+ curl.reset();
418
+ curl.setoptStr(CurlOpt.Url, 'https://another-site.com');
419
+ const result3 = await curl.performAsync();
420
+ ```
421
+
422
+ ---
423
+
424
+ ## API Reference
425
+
426
+ ### Session
427
+
428
+ ```typescript
429
+ new Session(options?: SessionOptions)
430
+ ```
431
+
432
+ | Option | Type | Default | Description |
433
+ |---|---|---|---|
434
+ | `impersonate` | `string` | — | Browser target (e.g., `'chrome116'`, `'safari15_5'`) |
435
+ | `headers` | `Record<string, string>` | — | Default headers for all requests |
436
+ | `timeout` | `number` | `0` (no timeout) | Default timeout in seconds |
437
+ | `followRedirects` | `boolean` | `true` | Follow 3xx redirects |
438
+ | `maxRedirects` | `number` | `30` | Maximum redirect chain length |
439
+ | `proxy` | `string` | — | Proxy URL (`http://`, `socks5://`) |
440
+ | `verify` | `boolean` | `true` | Verify SSL certificates |
441
+ | `defaultHeaders` | `boolean` | `true` | Apply browser default headers |
442
+ | `cookies` | `string[]` | — | Pre-existing cookies (Netscape format) |
443
+
444
+ #### Methods
445
+
446
+ | Method | Return | Description |
447
+ |---|---|---|
448
+ | `get(url, options?)` | `Promise<Response>` | HTTP GET |
449
+ | `post(url, options?)` | `Promise<Response>` | HTTP POST |
450
+ | `put(url, options?)` | `Promise<Response>` | HTTP PUT |
451
+ | `delete(url, options?)` | `Promise<Response>` | HTTP DELETE |
452
+ | `head(url, options?)` | `Promise<Response>` | HTTP HEAD |
453
+ | `patch(url, options?)` | `Promise<Response>` | HTTP PATCH |
454
+ | `options(url, options?)` | `Promise<Response>` | HTTP OPTIONS |
455
+ | `cookies` | `string[]` | Get all cookies (Netscape format) |
456
+ | `exportCookies()` | `string[]` | Export cookies for persistence |
457
+ | `importCookies(cookies)` | `void` | Import Netscape-format cookies |
458
+ | `clearCookies(domain?)` | `void` | Clear all or domain-specific cookies |
459
+
460
+ #### RequestOptions
461
+
462
+ | Option | Type | Description |
463
+ |---|---|---|
464
+ | `headers` | `Record<string, string>` | Merged with session headers |
465
+ | `data` | `string \| Buffer \| object` | Request body (object → JSON) |
466
+ | `params` | `Record<string, string \| number \| boolean>` | URL query parameters |
467
+ | `timeout` | `number` | Override session timeout (seconds) |
468
+ | `followRedirects` | `boolean` | Override session redirect behavior |
469
+ | `proxy` | `string` | Override session proxy |
470
+ | `verify` | `boolean` | Override session SSL verification |
471
+ | `impersonate` | `string` | Override session impersonation target |
472
+
473
+ ### Response
474
+
475
+ | Property/Method | Type | Description |
476
+ |---|---|---|
477
+ | `status` | `number` | HTTP status code |
478
+ | `ok` | `boolean` | `true` if status is 200–299 |
479
+ | `url` | `string` | Effective URL (after redirects) |
480
+ | `headers` | `Headers` | Response headers (case-insensitive) |
481
+ | `elapsed` | `number` | Total request time in ms |
482
+ | `timing` | `Timing` | Detailed timing breakdown |
483
+ | `text()` | `string` | Body as UTF-8 string |
484
+ | `json()` | `any` | Body parsed as JSON |
485
+ | `buffer()` | `Buffer` | Raw body as Buffer |
486
+ | `stream()` | `Readable` | Body as Node.js Readable stream |
487
+
488
+ #### Timing Object
489
+
490
+ | Field | Type | Description |
491
+ |---|---|---|
492
+ | `dns` | `number` | DNS resolution time (ms) |
493
+ | `connect` | `number` | TCP connection time (ms) |
494
+ | `tls` | `number` | TLS handshake time (ms) |
495
+ | `total` | `number` | Total request time (ms) |
496
+
497
+ ### CurlWebSocket
498
+
499
+ ```typescript
500
+ new CurlWebSocket(url: string, options?: CurlWebSocketOptions)
501
+ ```
502
+
503
+ | Method | Description |
504
+ |---|---|
505
+ | `connect()` | Initiate WebSocket connection |
506
+ | `send(data)` | Send text or binary message |
507
+ | `close()` | Close the connection |
508
+ | `connected` | `boolean` — current connection state |
509
+
510
+ | Event | Payload | Description |
511
+ |---|---|---|
512
+ | `open` | — | Connection established |
513
+ | `message` | `string \| Buffer` | Message received |
514
+ | `close` | — | Connection closed |
515
+ | `error` | `Error` | Error occurred |
516
+
517
+ ### Error Hierarchy
518
+
519
+ ```
520
+ Error
521
+ └── CurlError (code, curlMessage)
522
+ ├── TimeoutError (code: 28)
523
+ ├── ConnectionError (code: 7)
524
+ ├── TLSError (code: 35, 51, 58, 59, 60)
525
+ └── ProxyError (code: 5, 97)
526
+ ```
527
+
528
+ ```typescript
529
+ import { Session, TimeoutError, ConnectionError, TLSError } from 'curl-cffi-node';
530
+
531
+ try {
532
+ const r = await session.get('https://example.com', { timeout: 5 });
533
+ } catch (err) {
534
+ if (err instanceof TimeoutError) {
535
+ console.log('Request timed out after', err.code, 'seconds');
536
+ } else if (err instanceof ConnectionError) {
537
+ console.log('Connection failed:', err.curlMessage);
538
+ } else if (err instanceof TLSError) {
539
+ console.log('TLS error:', err.curlMessage);
540
+ }
541
+ }
542
+ ```
543
+
544
+ ### Shorthand Functions
545
+
546
+ One-off requests without creating a Session:
547
+
548
+ ```typescript
549
+ import { get, post, put, del, head, patch } from 'curl-cffi-node';
550
+
551
+ const r = await get('https://httpbin.org/get', { impersonate: 'chrome116' });
552
+ ```
553
+
554
+ ---
555
+
556
+ ## Browser Impersonation Targets
557
+
558
+ | Target | Browser | JA3 Match | HTTP/2 Match |
559
+ |---|---|---|---|
560
+ | `chrome99` | Chrome 99 | ✅ | ✅ |
561
+ | `chrome100` | Chrome 100 | ✅ | ✅ |
562
+ | `chrome101` | Chrome 101 | ✅ | ✅ |
563
+ | `chrome104` | Chrome 104 | ✅ | ✅ |
564
+ | `chrome107` | Chrome 107 | ✅ | ✅ |
565
+ | `chrome110` | Chrome 110 | ✅ | ✅ |
566
+ | `chrome116` | Chrome 116 | ✅ | ✅ |
567
+ | `chrome119` | Chrome 119 | ✅ | ✅ |
568
+ | `chrome120` | Chrome 120 | ✅ | ✅ |
569
+ | `chrome123` | Chrome 123 | ✅ | ✅ |
570
+ | `chrome124` | Chrome 124 | ✅ | ✅ |
571
+ | `chrome131` | Chrome 131 | ✅ | ✅ |
572
+ | `edge99` | Edge 99 | ✅ | ✅ |
573
+ | `edge101` | Edge 101 | ✅ | ✅ |
574
+ | `safari15_3` | Safari 15.3 | ✅ | ✅ |
575
+ | `safari15_5` | Safari 15.5 | ✅ | ✅ |
576
+ | `safari17_0` | Safari 17.0 | ✅ | ✅ |
577
+ | `safari17_2_ios` | Safari 17.2 (iOS) | ✅ | ✅ |
578
+ | `safari18_0` | Safari 18.0 | ✅ | ✅ |
579
+
580
+ > Full list depends on the bundled curl-impersonate version. Use `curl.impersonateStr('target')` with any supported target string.
581
+
582
+ ---
583
+
584
+ ## Supported Platforms
585
+
586
+ | Platform | Architecture | Binary |
587
+ |---|---|---|
588
+ | Linux | x64 (glibc) | `@curl-cffi-node/linux-x64-gnu` |
589
+ | Linux | x64 (musl) | `@curl-cffi-node/linux-x64-musl` |
590
+ | Linux | ARM64 (glibc) | `@curl-cffi-node/linux-arm64-gnu` |
591
+ | macOS | x64 (Intel) | `@curl-cffi-node/darwin-x64` |
592
+ | macOS | ARM64 (M1/M2/M3) | `@curl-cffi-node/darwin-arm64` |
593
+ | Windows | x64 (GNU) | `@curl-cffi-node/win32-x64-gnu` |
594
+
595
+ Binaries are automatically selected at install time via `optionalDependencies`.
596
+
597
+ ---
598
+
599
+ ## Performance
600
+
601
+ `curl-cffi-node` uses native code (Rust + C) for maximum throughput:
602
+
603
+ | Metric | `curl-cffi-node` | `node-fetch` | `axios` |
604
+ |---|---|---|---|
605
+ | **TLS fingerprint** | Browser-identical | Node.js default | Node.js default |
606
+ | **Anti-bot bypass** | ✅ | ❌ | ❌ |
607
+ | **Connection reuse** | ✅ Built-in | Manual | Manual |
608
+ | **Async I/O** | Thread pool | libuv | libuv |
609
+ | **Cookie persistence** | ✅ Built-in | Manual | Manual |
610
+ | **WebSocket + impersonation** | ✅ | ❌ | ❌ |
611
+ | **Binary size** | ~5MB native | 0 (JS only) | 0 (JS only) |
612
+
613
+ ---
614
+
615
+ ## Building from Source
616
+
617
+ ### Prerequisites
618
+
619
+ - **Node.js** ≥ 18
620
+ - **Rust** ≥ 1.70 ([rustup.rs](https://rustup.rs/))
621
+ - **System dependencies** for curl-impersonate:
622
+ - Linux: `build-essential`, `pkg-config`, `libssl-dev`
623
+ - macOS: Xcode Command Line Tools
624
+ - Windows: Visual Studio Build Tools
625
+
626
+ ### Build Steps
627
+
628
+ ```bash
629
+ # Clone with submodules (includes curl-impersonate)
630
+ git clone --recursive https://github.com/meodemsao/curl-cffi-node.git
631
+ cd curl-cffi-node
632
+
633
+ # Install Node.js dependencies
634
+ npm install
635
+
636
+ # Build native module (Rust) + TypeScript
637
+ npm run build
638
+
639
+ # Verify
640
+ node -e "const m = require('.'); console.log(m.curlVersion())"
641
+ ```
642
+
643
+ ---
644
+
645
+ ## Testing
646
+
647
+ ```bash
648
+ # Unit tests only (no network, <200ms)
649
+ npm run test:unit
650
+
651
+ # Integration tests (requires network)
652
+ npm run test:integration
653
+
654
+ # All tests
655
+ npm run test:fast
656
+
657
+ # Rust native tests
658
+ npm run test:rust
659
+
660
+ # Watch mode
661
+ npm run test:watch
662
+ ```
663
+
664
+ | Suite | Tests | Time | Network |
665
+ |---|---|---|---|
666
+ | Unit | 54 | ~180ms | ❌ |
667
+ | Integration | 112 | ~30s | ✅ |
668
+ | **Total** | **166** | **~30s** | — |
669
+
670
+ ---
671
+
672
+ ## Comparison
673
+
674
+ | Feature | `curl-cffi-node` | `curl-cffi` (Python) | `got` | `undici` |
675
+ |---|---|---|---|---|
676
+ | Language | Node.js | Python | Node.js | Node.js |
677
+ | TLS Impersonation | ✅ | ✅ | ❌ | ❌ |
678
+ | HTTP/2 Fingerprint | ✅ | ✅ | ❌ | ❌ |
679
+ | WebSocket + TLS | ✅ | ✅ | ❌ | ❌ |
680
+ | Cookie Jar | ✅ | ✅ | ✅ | ❌ |
681
+ | Proxy Support | ✅ | ✅ | ✅ | ✅ |
682
+ | Streaming | ✅ | ✅ | ✅ | ✅ |
683
+ | TypeScript | ✅ Native | ❌ | ✅ | ✅ |
684
+ | Async | ✅ Thread pool | ✅ asyncio | ✅ | ✅ |
685
+
686
+ ---
687
+
688
+ ## Contributing
689
+
690
+ Contributions are welcome! Please:
691
+
692
+ 1. Fork the repository
693
+ 2. Create a feature branch (`git checkout -b feat/amazing-feature`)
694
+ 3. Run tests (`npm run test:fast`)
695
+ 4. Commit with [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `test:`, etc.)
696
+ 5. Open a Pull Request
697
+
698
+ ---
699
+
700
+ ## License
701
+
702
+ [MIT](LICENSE) — see [LICENSE](LICENSE) file for details.
703
+
704
+ ---
705
+
706
+ ## Acknowledgements
707
+
708
+ - [curl-impersonate](https://github.com/lwthiker/curl-impersonate) — Modified curl with browser TLS fingerprints
709
+ - [napi-rs](https://napi.rs) — Rust ↔ Node.js binding framework
710
+ - [curl-cffi](https://github.com/yifeikong/curl_cffi) — Python equivalent (inspiration)
711
+
712
+ ---
713
+
714
+ <p align="center">
715
+ <sub>Built with 🦀 Rust + 💚 Node.js — bypass anti-bot detection with real browser fingerprints</sub>
716
+ </p>
Binary file