@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.
Files changed (2) hide show
  1. package/package.json +7 -7
  2. 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",
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.3",
29
- "@ossiana/node-libcurl-darwin-x64": "1.9.3",
30
- "@ossiana/node-libcurl-linux-arm64-gnu": "1.9.3",
31
- "@ossiana/node-libcurl-linux-x64-gnu": "1.9.3",
32
- "@ossiana/node-libcurl-win32-x64-msvc": "1.9.3"
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.25"
48
+ "artifacts-version": "v1.0.28"
49
49
  }
package/readme.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # node-libcurl
2
2
 
3
- ## Different with Nodejs fetch api
4
- * The fingerprint can be customized to look like chrome or firefox ,it modified the BoringSSL extension, set the custom cipher suite with Libcurl
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
- ## Use Sample
26
+ The package exports four APIs:
25
27
 
26
- ```javascript
27
- import { LibCurl, fetch, requests } from '@ossiana/node-libcurl'
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
- ```javascript
32
- // nonstandard
33
- fetch("https://www.google.com").then(e => e.json())
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
- ```javascript
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
- verbose: true,
61
- timeout: 15, // 15 seconds
62
- // interface: "eth0",
63
- ja3: "auto",
64
- akamai: "auto",
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.post("https://www.google.com", {
76
- params: {
77
- a: "b",
78
- },
79
- headers: {
80
- "Content-Type": "application/octet-stream",
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
- data: new Uint8Array([1, 2, 3]),
83
- h2config: {
84
- weight: 220,
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
- // overwrite `autoSortRequestHeaders` [[Once]]
88
- headersOrder: [
89
- "referer",
90
- "Content-Type",
91
- ...
92
- ],
93
- // overwrite `requestType` [[Once]]
94
- requestType: "XMLHttpRequest"
95
- })
96
- .then(e => console.log(e.text));
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.