@ossiana/node-libcurl 1.9.3 → 1.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/readme.md +284 -65
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossiana/node-libcurl",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.4",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Ossian"
|
|
6
6
|
},
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"typescript": "^4.9.4"
|
|
26
26
|
},
|
|
27
27
|
"optionalDependencies": {
|
|
28
|
-
"@ossiana/node-libcurl-darwin-arm64": "1.9.
|
|
29
|
-
"@ossiana/node-libcurl-darwin-x64": "1.9.
|
|
30
|
-
"@ossiana/node-libcurl-linux-arm64-gnu": "1.9.
|
|
31
|
-
"@ossiana/node-libcurl-linux-x64-gnu": "1.9.
|
|
32
|
-
"@ossiana/node-libcurl-win32-x64-msvc": "1.9.
|
|
28
|
+
"@ossiana/node-libcurl-darwin-arm64": "1.9.4",
|
|
29
|
+
"@ossiana/node-libcurl-darwin-x64": "1.9.4",
|
|
30
|
+
"@ossiana/node-libcurl-linux-arm64-gnu": "1.9.4",
|
|
31
|
+
"@ossiana/node-libcurl-linux-x64-gnu": "1.9.4",
|
|
32
|
+
"@ossiana/node-libcurl-win32-x64-msvc": "1.9.4"
|
|
33
33
|
},
|
|
34
34
|
"main": "./dist/index.js",
|
|
35
35
|
"files": [
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"type": "git",
|
|
46
46
|
"url": "git@github.com/Ossianaa/node-libcurl.git"
|
|
47
47
|
},
|
|
48
|
-
"artifacts-version": "v1.0.
|
|
48
|
+
"artifacts-version": "v1.0.28"
|
|
49
49
|
}
|
package/readme.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# node-libcurl
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
A Node.js HTTP request library based on libcurl, with browser-grade TLS/HTTP2/HTTP3 fingerprint customization. It patches BoringSSL to customize the TLS client hello (cipher suites, extensions, order) and uses a custom HTTP/2 / HTTP/3 implementation, so the traffic can be made to look like real Chrome or Firefox — something the built-in Node.js `fetch` can't do.
|
|
4
|
+
|
|
5
5
|
------------
|
|
6
6
|
|
|
7
7
|
## Build Status
|
|
@@ -14,84 +14,303 @@
|
|
|
14
14
|
------------
|
|
15
15
|
|
|
16
16
|
## How to Install
|
|
17
|
+
|
|
17
18
|
> npm i -g pnpm
|
|
18
|
-
>
|
|
19
|
+
>
|
|
19
20
|
> pnpm i @ossiana/node-libcurl
|
|
20
|
-
------------
|
|
21
21
|
|
|
22
|
+
------------
|
|
22
23
|
|
|
24
|
+
## Usage
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
The package exports four APIs:
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
| Export | Description |
|
|
29
|
+
| :----- | :---------- |
|
|
30
|
+
| `requests` | High-level, axios/requests-style API. **Recommended.** |
|
|
31
|
+
| `fetch` | `fetch`-like API with a compatible response interface. |
|
|
32
|
+
| `LibCurl` | Low-level wrapper around the native libcurl binding. |
|
|
33
|
+
| `LibCurlWebSocket` | WebSocket client with fingerprint support. |
|
|
28
34
|
|
|
35
|
+
```ts
|
|
36
|
+
import { requests, fetch, LibCurl, LibCurlWebSocket } from "@ossiana/node-libcurl";
|
|
29
37
|
```
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
### 1. requests (axios-style)
|
|
40
|
+
|
|
41
|
+
#### Static requests (one-shot)
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { requests } from "@ossiana/node-libcurl";
|
|
45
|
+
|
|
46
|
+
const resp = await requests.get("https://httpbin.org/get", {
|
|
47
|
+
headers: {
|
|
48
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
|
|
49
|
+
},
|
|
50
|
+
params: { page: 1, size: 20 }, // appended to query string
|
|
51
|
+
});
|
|
52
|
+
console.log(resp.status); // 200
|
|
53
|
+
console.log(resp.text); // response body as string
|
|
54
|
+
console.log(resp.json); // response body parsed as JSON
|
|
34
55
|
```
|
|
35
56
|
|
|
36
|
-
|
|
57
|
+
Supported methods: `get` `post` `put` `patch` `delete` `head` `options` `trace`.
|
|
58
|
+
|
|
59
|
+
#### Session (persistent connection + cookies + retry)
|
|
60
|
+
|
|
61
|
+
A session reuses one `LibCurl` instance, so the TCP/TLS connection and cookies are kept between requests — pass it a shared `instance` to get the same behavior across sessions:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
37
64
|
const session = requests.session({
|
|
38
|
-
redirect: true,
|
|
39
|
-
cookies: {
|
|
40
|
-
value: "a=1",
|
|
41
|
-
url: "google.com"
|
|
42
|
-
},
|
|
43
|
-
proxy: "user:pwd@ip:port",
|
|
44
|
-
defaultRequestHeaders: [
|
|
45
|
-
["sec-ch-ua-platform", '"Windows"'],
|
|
46
|
-
["user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"],
|
|
47
|
-
["sec-ch-ua", '"Chromium";v="136", "Google Chrome";v="136", "Not.A/Brand";v="99"'],
|
|
48
|
-
["sec-ch-ua-mobile", "?0"],
|
|
49
|
-
["accept", "*/*"],
|
|
50
|
-
["sec-fetch-site", "same-origin"],
|
|
51
|
-
["sec-fetch-mode", "cors"],
|
|
52
|
-
["sec-fetch-dest", "empty"],
|
|
53
|
-
["sec-fetch-storage-access", "none"],
|
|
54
|
-
["referer", "https://www.google.com/search?q=1"],
|
|
55
|
-
["accept-encoding", "gzip, deflate, br, zstd"],
|
|
56
|
-
["accept-language", "en-US"],
|
|
57
|
-
["priority", "u=1, i"],
|
|
58
|
-
],
|
|
59
65
|
httpVersion: "http2",
|
|
60
|
-
|
|
61
|
-
timeout: 15, //
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
autoSortRequestHeaders: true,
|
|
66
|
-
requestType: "fetch",
|
|
67
|
-
// sslCert: {
|
|
68
|
-
// certBlob: ...;
|
|
69
|
-
// privateKeyBlob: ...;
|
|
70
|
-
// type: "PEM";
|
|
71
|
-
// password: ...;
|
|
72
|
-
// }
|
|
66
|
+
redirect: true,
|
|
67
|
+
timeout: 15, // seconds
|
|
68
|
+
// ja3: "chrome131",
|
|
69
|
+
// akamai: "auto",
|
|
70
|
+
// proxy: "http://user:pass@127.0.0.1:8888",
|
|
73
71
|
});
|
|
74
72
|
|
|
75
|
-
await session.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
const a = await session.get("https://example.com/login");
|
|
74
|
+
const b = await session.post("https://example.com/login", {
|
|
75
|
+
json: { username: "user", password: "pass" }, // sets Content-Type: application/json
|
|
76
|
+
// or use data for form-encoded bodies:
|
|
77
|
+
// data: { username: "user", password: "pass" },
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Session-only options:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const session = requests.session({
|
|
85
|
+
defaultRequestHeaders: { // merged into every request of this session
|
|
86
|
+
"User-Agent": "Mozilla/5.0 ... Chrome/150.0.0.0 Safari/537.36",
|
|
81
87
|
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
streamId: 13,
|
|
88
|
+
cookies: { // pre-set cookies for the given uri
|
|
89
|
+
value: { name: "a", value: "b" },
|
|
90
|
+
uri: "https://example.com",
|
|
86
91
|
},
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
autoSortRequestHeaders: "auto", // reorder headers like Chrome fetch
|
|
93
|
+
requestType: "fetch", // "fetch" | "XMLHttpRequest"
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Session methods (in addition to the HTTP verbs):
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
session.setDefaultRequestHeaders(headers);
|
|
101
|
+
session.setCookie("key", "value", ".example.com", "/"); // set cookie manually
|
|
102
|
+
session.getCookie("key", ".example.com"); // get one cookie value
|
|
103
|
+
session.getCookies(); // "a=b; c=d;"
|
|
104
|
+
session.getCookiesMap(); // Map<string, { domain, subDomain, path, secure, timestamp, value }>
|
|
105
|
+
session.deleteCookie("key", ".example.com");
|
|
106
|
+
session.retry(3); // returns a NEW session that retries up to 3 times
|
|
107
|
+
session.setProxy(proxy);
|
|
108
|
+
session.setTimeout(connectTime, sendTime); // seconds
|
|
109
|
+
session.setRedirect(true);
|
|
110
|
+
session.setHttpVersion("http2");
|
|
111
|
+
session.setInterface("eth0");
|
|
112
|
+
session.setJA3Fingerprint("chrome150");
|
|
113
|
+
session.setAkamaiFingerprint("auto");
|
|
114
|
+
session.setHttp3Fingerprint("auto");
|
|
115
|
+
session.getLastEffectiveUrl();
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`retry` accepts a condition callback:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const retrySession = session.retry(3, (resp, error) => {
|
|
122
|
+
// return true to stop retrying, false to retry again
|
|
123
|
+
if (error) return false;
|
|
124
|
+
return resp.status < 500;
|
|
125
|
+
});
|
|
97
126
|
```
|
|
127
|
+
|
|
128
|
+
#### Request options
|
|
129
|
+
|
|
130
|
+
| Option | Type | Description |
|
|
131
|
+
| :----- | :--- | :---------- |
|
|
132
|
+
| `headers` | `string \| object \| string[] \| [string, string][]` | Request headers. String form is `"Key: value\nKey2: value2"`. |
|
|
133
|
+
| `params` | `URLSearchParams \| string \| object` | Appended to the URL query string. |
|
|
134
|
+
| `json` | `object` | Sends the object as JSON body, sets `Content-Type: application/json`. |
|
|
135
|
+
| `data` | `string \| Uint8Array \| URLSearchParams \| object` | Sends a body. Objects are form-encoded (`a=1&b=2`), and `Content-Type` is set automatically. Cannot be combined with `json`. |
|
|
136
|
+
| `timeout` | `number` | Timeout in seconds. |
|
|
137
|
+
| `redirect` | `boolean` | Follow redirects (default `false`). |
|
|
138
|
+
| `proxy` | `string \| { proxy, username, password }` | Proxy, e.g. `"http://127.0.0.1:8888"`, `"socks5://user:pass@host:1080"`, or an account object. |
|
|
139
|
+
| `httpVersion` | `"http1.1" \| "http2" \| "http3" \| "http3_only"` | HTTP protocol version. |
|
|
140
|
+
| `interface` | `string` | Bind to a specific network interface. |
|
|
141
|
+
| `ja3` | see [Fingerprints](#3-fingerprints) | TLS (JA3) fingerprint. |
|
|
142
|
+
| `akamai` | see [Fingerprints](#3-fingerprints) | HTTP/2 Akamai fingerprint. |
|
|
143
|
+
| `http3Fingerprint` | see [Fingerprints](#3-fingerprints) | HTTP/3 (QUIC) fingerprint. |
|
|
144
|
+
| `autoSortRequestHeaders` | `"auto" \| "chrome130" \| "chrome131" \| boolean` | Auto-sort request headers like Chrome fetch. |
|
|
145
|
+
| `tlsVerifySigalgs` | `string \| (string \| number)[]` | Custom TLS signature algorithms. |
|
|
146
|
+
| `requestType` | `"fetch" \| "XMLHttpRequest"` | HTTP/2 pseudo-header ordering style. |
|
|
147
|
+
| `headersOrder` | `string[]` | Explicit order of request headers for the next request only. |
|
|
148
|
+
| `h2config` | `{ weight: number, streamId?: number }` | Customize the HTTP/2 stream weight / next stream id. |
|
|
149
|
+
| `sslCert` | `{ certBlob, privateKeyBlob?, type?, password? }` | Client certificate (`type`: `"PEM" \| "DER" \| "P12"`). |
|
|
150
|
+
| `sslVerify` | `{ caPath: string }` | Custom CA bundle path. |
|
|
151
|
+
|
|
152
|
+
#### Response
|
|
153
|
+
|
|
154
|
+
| Property | Type | Description |
|
|
155
|
+
| :------- | :--- | :---------- |
|
|
156
|
+
| `status` | `number` | HTTP status code. |
|
|
157
|
+
| `text` | `string` | Decoded response body. |
|
|
158
|
+
| `json` | `object` | `JSON.parse(text)`. |
|
|
159
|
+
| `buffer` | `Uint8Array` | Raw response body. |
|
|
160
|
+
| `headers` | `string` | Raw response headers. |
|
|
161
|
+
| `headersMap` | `Headers` | Response headers as a `Headers` object. |
|
|
162
|
+
| `contentLength` | `number` | Response body length. |
|
|
163
|
+
| `encodedBodySize` | `number` | Wire-level (pre-decompression) body size. |
|
|
164
|
+
|
|
165
|
+
### 2. fetch (fetch-style)
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { fetch } from "@ossiana/node-libcurl";
|
|
169
|
+
|
|
170
|
+
const resp = await fetch("https://httpbin.org/post", {
|
|
171
|
+
method: "POST",
|
|
172
|
+
headers: { "Content-Type": "application/json" },
|
|
173
|
+
body: { hello: "world" }, // object is JSON.stringify'd
|
|
174
|
+
redirect: true,
|
|
175
|
+
ja3: "chrome150",
|
|
176
|
+
akamai: "auto",
|
|
177
|
+
httpVersion: "http3",
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
await resp.text(); // Promise<string>
|
|
181
|
+
await resp.json(); // Promise<object>
|
|
182
|
+
await resp.arraybuffer(); // Promise<ArrayBuffer>
|
|
183
|
+
await resp.headers(); // Promise<Headers>
|
|
184
|
+
await resp.cookies(); // Promise<string>
|
|
185
|
+
await resp.cookiesMap(); // Promise<Map<string, {...}>>
|
|
186
|
+
await resp.lastEffectiveUrl(); // Promise<string>
|
|
187
|
+
resp.status(); // number (sync)
|
|
188
|
+
resp.contentLength();
|
|
189
|
+
resp.encodedBodySize();
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Options (all optional): `method` (default `"GET"`), `headers`, `body`, `redirect`, `cookies`, `httpVersion`, `verbose`, `proxy`, `timeout`, `interface`, `instance`, `ja3`, `akamai`, `autoSortRequestHeaders`, `sslCert`, `sslVerify`, `tlsVerifySigalgs`, `http3Fingerprint` (default `"auto"`).
|
|
193
|
+
|
|
194
|
+
Pass a shared `LibCurl` instance via `instance` to keep a persistent connection across calls:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
const curl = new LibCurl();
|
|
198
|
+
const r1 = await fetch("https://example.com/a", { instance: curl });
|
|
199
|
+
const r2 = await fetch("https://example.com/b", { instance: curl }); // reuses the connection
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 3. Fingerprints
|
|
203
|
+
|
|
204
|
+
This is the core feature — make your requests indistinguishable from a real browser.
|
|
205
|
+
|
|
206
|
+
#### JA3 (TLS fingerprint)
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
ja3: "auto" // pick by User-Agent Chrome version (default)
|
|
210
|
+
ja3: "chrome99" | "chrome101" | "chrome110" | "chrome124" | "chrome131" | "chrome133" | "chrome150"
|
|
211
|
+
ja3: "771,4865-4866-4867-...,0-23-65281-10-11-35-16-...,29-23-24,0" // custom JA3 string
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The built-in versions randomize the TLS extension order per request (like real Chrome), and `"auto"` selects a preset matching the `Chrome/x.y` version in your `User-Agent` header.
|
|
215
|
+
|
|
216
|
+
#### Akamai (HTTP/2 fingerprint)
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
akamai: "auto" // default
|
|
220
|
+
akamai: "chrome99" | "chrome107" | "chrome119"
|
|
221
|
+
akamai: "1:65536;3:1000;4:6291456;6:262144|15663105|0|m,a,s,p" // custom string
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### HTTP/3 fingerprint
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
http3Fingerprint: "auto" // default
|
|
228
|
+
http3Fingerprint: "chrome126" | "chrome150"
|
|
229
|
+
http3Fingerprint: { // fully custom config
|
|
230
|
+
scid: "scid=0",
|
|
231
|
+
settings: "1:65536;6:262144;7:100;51:1;GREASE",
|
|
232
|
+
transport_params: "12584:0x4f524947;9:103;1:30000;7:6291456;15:AUTO;4:15728640;GREASE;32:65536;3:1472;17:1@1,GREASE;8:100;6:6291456;12583:174718;5:6291456",
|
|
233
|
+
tls: "ciphers=1,2,3;alps=h3;grease=off;rand=on",
|
|
234
|
+
permutation: "0,15,19,23,9,1,14,21,17,4,7",
|
|
235
|
+
verify_sigalgs: "0x0403,0x0804,0x0401,0x0503,0x0805,0x0501,0x0806,0x0601,0x0201",
|
|
236
|
+
},
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### TLS signature algorithms (HTTP/1.1 HTTP/2)
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
tlsVerifySigalgs: [
|
|
243
|
+
0x0403, "ecdsa_secp256r1_sha256", "rsa_pss_rsae_sha256", "rsa_pkcs1_sha256", ...
|
|
244
|
+
]
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### Auto-sorted request headers
|
|
248
|
+
|
|
249
|
+
With `autoSortRequestHeaders: "auto"` (default), request headers are automatically re-ordered the same way Chrome's `fetch` does (prefix / client-hint / middle / suffix groups). Use `"chrome130"` / `"chrome131"` to pin a specific ordering version.
|
|
250
|
+
|
|
251
|
+
### 4. LibCurl (low-level)
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
import { LibCurl } from "@ossiana/node-libcurl";
|
|
255
|
+
|
|
256
|
+
const curl = new LibCurl();
|
|
257
|
+
curl.open("POST", "https://example.com/api");
|
|
258
|
+
curl.setRequestHeaders({ "Content-Type": "application/json" });
|
|
259
|
+
curl.setJA3Fingerprint("chrome150");
|
|
260
|
+
curl.setAkamaiFingerprint("auto");
|
|
261
|
+
curl.setHttp3Fingerprint("auto");
|
|
262
|
+
curl.setProxy("127.0.0.1:8888"); // or { proxy, username, password }
|
|
263
|
+
curl.setTimeout(10, 20); // connect / total, seconds
|
|
264
|
+
curl.setRedirect(true);
|
|
265
|
+
curl.setHttpVersion("http2");
|
|
266
|
+
curl.setInterface("eth0");
|
|
267
|
+
curl.setVerbose(true); // print curl internal logs
|
|
268
|
+
await curl.send({ hello: "world" }); // object is JSON.stringify'd
|
|
269
|
+
|
|
270
|
+
curl.getResponseStatus(); // number
|
|
271
|
+
curl.getResponseHeaders(); // string
|
|
272
|
+
curl.getResponseHeadersMap(); // Headers
|
|
273
|
+
curl.getResponseString(); // string
|
|
274
|
+
curl.getResponseBody(); // Uint8Array
|
|
275
|
+
curl.getResponseContentLength();
|
|
276
|
+
curl.getResponseEncodedBodySize();
|
|
277
|
+
curl.getCookies(); // "a=b; c=d;"
|
|
278
|
+
curl.getLastEffectiveUrl();
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Other setters: `setRequestHeader(key, value)`, `setCookie({name, value, domain, path})`, `getCookie({name, domain, path})`, `getCookiesMap()`, `deleteCookie()`, `setSSLVerify({caPath})`, `setSSLCert(certBlob, privateKeyBlob?, "PEM"|"DER"|"P12", password?)`, `setTLSVerifySigalgs()`, `setHttp2NextStreamId(streamId)`, `setHttp2StreamWeight(weight)`, `setAutoSortRequestHeaders()`, `setRequestType("fetch"|"XMLHttpRequest")`, `setNextRequestType()`, `setNextRequestHeadersOrder(order)`.
|
|
282
|
+
|
|
283
|
+
> **Note:** one `LibCurl` instance can only run one request at a time — calling `send()` while a request is in flight throws.
|
|
284
|
+
|
|
285
|
+
### 5. WebSocket
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
const ws = new LibCurlWebSocket("wss://echo.websocket.org", {
|
|
289
|
+
userAgent: "Mozilla/5.0 ... Chrome/150.0.0.0 Safari/537.36",
|
|
290
|
+
origin: "https://example.com",
|
|
291
|
+
cookie: "session=abc",
|
|
292
|
+
protocol: "chat",
|
|
293
|
+
timeout: 30,
|
|
294
|
+
ja3: "chrome150",
|
|
295
|
+
// instance: sharedCurl, // reuse an existing LibCurl
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
ws.onopen = () => {
|
|
299
|
+
ws.send("hello");
|
|
300
|
+
ws.send(new Uint8Array([1, 2, 3]));
|
|
301
|
+
};
|
|
302
|
+
ws.onmessage = (data: Uint8Array) => { /* receive */ };
|
|
303
|
+
ws.onclose = () => {};
|
|
304
|
+
ws.onerror = (message: string) => {};
|
|
305
|
+
|
|
306
|
+
ws.close();
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
------------
|
|
310
|
+
|
|
311
|
+
## Difference from Node.js `fetch`
|
|
312
|
+
|
|
313
|
+
* The TLS fingerprint (JA3), HTTP/2 (Akamai) and HTTP/3 fingerprints can be customized to look like Chrome or Firefox — node-libcurl patches BoringSSL and ships a custom HTTP/2/3 stack, so the handshake, header ordering and cipher suites match real browsers.
|
|
314
|
+
* Request headers can be auto-sorted exactly like Chrome's `fetch` (`autoSortRequestHeaders`).
|
|
315
|
+
* Persistent connections and cookie jars are first-class (session API).
|
|
316
|
+
* Client certificates, custom CA, per-interface binding, custom TLS signature algorithms, and HTTP/2 stream weight control are all supported.
|