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.
- package/README.md +716 -0
- package/curl-cffi-node.darwin-arm64.node +0 -0
- package/curl-cffi-node.darwin-x64.node +0 -0
- package/curl-cffi-node.linux-arm64-gnu.node +0 -0
- package/curl-cffi-node.linux-x64-gnu.node +0 -0
- package/curl-cffi-node.win32-x64-msvc.node +0 -0
- package/dist/binding.d.ts +2 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +90 -0
- package/dist/binding.js.map +1 -0
- package/dist/enums.d.ts +87 -0
- package/dist/enums.d.ts.map +1 -0
- package/dist/enums.js +101 -0
- package/dist/enums.js.map +1 -0
- package/dist/errors.d.ts +61 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +116 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/response.d.ts +86 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/response.js +137 -0
- package/dist/response.js.map +1 -0
- package/dist/session.d.ts +93 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +228 -0
- package/dist/session.js.map +1 -0
- package/dist/websocket.d.ts +68 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +176 -0
- package/dist/websocket.js.map +1 -0
- package/npm/darwin-arm64/curl-cffi-node.darwin-arm64.node +0 -0
- package/npm/darwin-arm64/package.json +19 -0
- package/npm/darwin-x64/curl-cffi-node.darwin-x64.node +0 -0
- package/npm/darwin-x64/package.json +19 -0
- package/npm/linux-arm64-gnu/curl-cffi-node.linux-arm64-gnu.node +0 -0
- package/npm/linux-arm64-gnu/package.json +22 -0
- package/npm/linux-x64-gnu/curl-cffi-node.linux-x64-gnu.node +0 -0
- package/npm/linux-x64-gnu/package.json +22 -0
- package/npm/linux-x64-musl/package.json +14 -0
- package/npm/win32-x64-msvc/curl-cffi-node.win32-x64-msvc.node +0 -0
- package/npm/win32-x64-msvc/package.json +19 -0
- 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
|
+
[](https://www.npmjs.com/package/curl-cffi-node)
|
|
6
|
+
[](https://www.npmjs.com/package/curl-cffi-node)
|
|
7
|
+
[](https://github.com/meodemsao/curl-cffi-node/actions/workflows/publish.yml)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://nodejs.org/)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
11
|
+
[](#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
|