httpcloak 1.5.1 → 1.5.3

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 CHANGED
@@ -48,6 +48,19 @@ async function main() {
48
48
  main();
49
49
  ```
50
50
 
51
+ ### ES Modules
52
+
53
+ ```javascript
54
+ import { Session } from "httpcloak";
55
+
56
+ const session = new Session({ preset: "chrome-143" });
57
+
58
+ const response = await session.get("https://example.com");
59
+ console.log(response.text);
60
+
61
+ session.close();
62
+ ```
63
+
51
64
  ### Synchronous Usage
52
65
 
53
66
  ```javascript
@@ -99,13 +112,209 @@ session.postCb(
99
112
  );
100
113
  ```
101
114
 
102
- ### With Proxy
115
+ ### Streaming Downloads
116
+
117
+ For large downloads, use streaming to avoid loading entire response into memory:
118
+
119
+ ```javascript
120
+ const { Session } = require("httpcloak");
121
+ const fs = require("fs");
122
+
123
+ async function downloadFile() {
124
+ const session = new Session({ preset: "chrome-143" });
125
+
126
+ try {
127
+ // Start streaming request
128
+ const stream = session.getStream("https://example.com/large-file.zip");
129
+
130
+ console.log(`Status: ${stream.statusCode}`);
131
+ console.log(`Content-Length: ${stream.contentLength}`);
132
+ console.log(`Protocol: ${stream.protocol}`);
133
+
134
+ // Read in chunks
135
+ const file = fs.createWriteStream("downloaded-file.zip");
136
+ let totalBytes = 0;
137
+ let chunk;
138
+
139
+ while ((chunk = stream.readChunk(65536)) !== null) {
140
+ file.write(chunk);
141
+ totalBytes += chunk.length;
142
+ console.log(`Downloaded ${totalBytes} bytes...`);
143
+ }
144
+
145
+ file.end();
146
+ stream.close();
147
+ console.log(`Download complete: ${totalBytes} bytes`);
148
+ } finally {
149
+ session.close();
150
+ }
151
+ }
152
+
153
+ downloadFile();
154
+ ```
155
+
156
+ ### Streaming with All Methods
103
157
 
104
158
  ```javascript
159
+ const { Session } = require("httpcloak");
160
+
161
+ const session = new Session({ preset: "chrome-143" });
162
+
163
+ // Stream GET
164
+ const getStream = session.getStream("https://example.com/data");
165
+
166
+ // Stream POST
167
+ const postStream = session.postStream("https://example.com/upload", "data");
168
+
169
+ // Stream with custom options
170
+ const customStream = session.requestStream({
171
+ method: "PUT",
172
+ url: "https://example.com/resource",
173
+ headers: { "Content-Type": "application/json" },
174
+ body: JSON.stringify({ key: "value" }),
175
+ });
176
+
177
+ // Read response
178
+ let chunk;
179
+ while ((chunk = customStream.readChunk(65536)) !== null) {
180
+ console.log(`Received ${chunk.length} bytes`);
181
+ }
182
+ customStream.close();
183
+
184
+ session.close();
185
+ ```
186
+
187
+ ## Proxy Support
188
+
189
+ HTTPCloak supports HTTP, SOCKS5, and HTTP/3 (MASQUE) proxies with full fingerprint preservation.
190
+
191
+ ### HTTP Proxy
192
+
193
+ ```javascript
194
+ const { Session } = require("httpcloak");
195
+
196
+ // Basic HTTP proxy
105
197
  const session = new Session({
198
+ preset: "chrome-143",
199
+ proxy: "http://host:port",
200
+ });
201
+
202
+ // With authentication
203
+ const sessionAuth = new Session({
106
204
  preset: "chrome-143",
107
205
  proxy: "http://user:pass@host:port",
108
206
  });
207
+
208
+ // HTTPS proxy
209
+ const sessionHttps = new Session({
210
+ preset: "chrome-143",
211
+ proxy: "https://user:pass@host:port",
212
+ });
213
+ ```
214
+
215
+ ### SOCKS5 Proxy
216
+
217
+ ```javascript
218
+ const { Session } = require("httpcloak");
219
+
220
+ // SOCKS5 proxy (with DNS resolution on proxy)
221
+ const session = new Session({
222
+ preset: "chrome-143",
223
+ proxy: "socks5h://host:port",
224
+ });
225
+
226
+ // With authentication
227
+ const sessionAuth = new Session({
228
+ preset: "chrome-143",
229
+ proxy: "socks5h://user:pass@host:port",
230
+ });
231
+
232
+ const response = await session.get("https://www.cloudflare.com/cdn-cgi/trace");
233
+ console.log(response.protocol); // h3 (HTTP/3 through SOCKS5!)
234
+ ```
235
+
236
+ ### HTTP/3 MASQUE Proxy
237
+
238
+ MASQUE (RFC 9484) enables HTTP/3 connections through compatible proxies:
239
+
240
+ ```javascript
241
+ const { Session } = require("httpcloak");
242
+
243
+ // MASQUE proxy (auto-detected for known providers like Bright Data)
244
+ const session = new Session({
245
+ preset: "chrome-143",
246
+ proxy: "https://user:pass@brd.superproxy.io:10001",
247
+ });
248
+
249
+ const response = await session.get("https://www.cloudflare.com/cdn-cgi/trace");
250
+ console.log(response.protocol); // h3
251
+ ```
252
+
253
+ ### Split Proxy Configuration
254
+
255
+ Use different proxies for TCP (HTTP/1.1, HTTP/2) and UDP (HTTP/3) traffic:
256
+
257
+ ```javascript
258
+ const { Session } = require("httpcloak");
259
+
260
+ const session = new Session({
261
+ preset: "chrome-143",
262
+ tcpProxy: "http://tcp-proxy:port", // For HTTP/1.1, HTTP/2
263
+ udpProxy: "https://masque-proxy:port", // For HTTP/3
264
+ });
265
+ ```
266
+
267
+ ## Advanced Features
268
+
269
+ ### Encrypted Client Hello (ECH)
270
+
271
+ ECH encrypts the SNI (Server Name Indication) to prevent traffic analysis. Works with all Cloudflare domains:
272
+
273
+ ```javascript
274
+ const { Session } = require("httpcloak");
275
+
276
+ // Enable ECH for Cloudflare domains
277
+ const session = new Session({
278
+ preset: "chrome-143",
279
+ echConfigDomain: "cloudflare-ech.com",
280
+ });
281
+
282
+ const response = await session.get("https://www.cloudflare.com/cdn-cgi/trace");
283
+ console.log(response.text);
284
+ // Output includes: sni=encrypted, http=http/3
285
+ ```
286
+
287
+ ### Domain Fronting (Connect-To)
288
+
289
+ Connect to one server while requesting a different domain:
290
+
291
+ ```javascript
292
+ const { Session } = require("httpcloak");
293
+
294
+ // Connect to claude.ai's IP but request www.cloudflare.com
295
+ const session = new Session({
296
+ preset: "chrome-143",
297
+ connectTo: { "www.cloudflare.com": "claude.ai" },
298
+ });
299
+
300
+ const response = await session.get("https://www.cloudflare.com/cdn-cgi/trace");
301
+ ```
302
+
303
+ ### Combined: SOCKS5 + ECH
304
+
305
+ Get HTTP/3 with encrypted SNI through a SOCKS5 proxy:
306
+
307
+ ```javascript
308
+ const { Session } = require("httpcloak");
309
+
310
+ const session = new Session({
311
+ preset: "chrome-143",
312
+ proxy: "socks5h://user:pass@host:port",
313
+ echConfigDomain: "cloudflare-ech.com",
314
+ });
315
+
316
+ const response = await session.get("https://www.cloudflare.com/cdn-cgi/trace");
317
+ // Response shows: http=http/3, sni=encrypted
109
318
  ```
110
319
 
111
320
  ## Cookie Management
@@ -115,19 +324,47 @@ const { Session } = require("httpcloak");
115
324
 
116
325
  const session = new Session();
117
326
 
327
+ // Set a cookie
328
+ session.setCookie("session_id", "abc123");
329
+
118
330
  // Get all cookies
119
331
  const cookies = session.getCookies();
120
332
  console.log(cookies);
121
333
 
122
- // Set a cookie
123
- session.setCookie("session_id", "abc123");
124
-
125
334
  // Access cookies as property
126
335
  console.log(session.cookies);
127
336
 
337
+ // Clear a cookie
338
+ session.clearCookie("session_id");
339
+
340
+ // Clear all cookies
341
+ session.clearCookies();
342
+
128
343
  session.close();
129
344
  ```
130
345
 
346
+ ## Session Configuration
347
+
348
+ ```javascript
349
+ const { Session } = require("httpcloak");
350
+
351
+ const session = new Session({
352
+ preset: "chrome-143", // Browser fingerprint preset
353
+ proxy: null, // Proxy URL
354
+ tcpProxy: null, // Separate TCP proxy
355
+ udpProxy: null, // Separate UDP proxy (MASQUE)
356
+ timeout: 30, // Request timeout in seconds
357
+ httpVersion: "auto", // "auto", "h1", "h2", "h3"
358
+ verify: true, // SSL certificate verification
359
+ allowRedirects: true, // Follow redirects
360
+ maxRedirects: 10, // Maximum redirect count
361
+ retry: 3, // Retry count on failure
362
+ preferIpv4: false, // Prefer IPv4 over IPv6
363
+ connectTo: null, // Domain fronting map
364
+ echConfigDomain: null, // ECH config domain
365
+ });
366
+ ```
367
+
131
368
  ## Available Presets
132
369
 
133
370
  ```javascript
@@ -140,28 +377,90 @@ console.log(availablePresets());
140
377
 
141
378
  ## Response Object
142
379
 
380
+ ### Standard Response
381
+
143
382
  ```javascript
144
383
  const response = await session.get("https://example.com");
145
384
 
146
- response.statusCode; // number: HTTP status code
147
- response.headers; // object: Response headers
148
- response.body; // Buffer: Raw response body
149
- response.text; // string: Response body as text
150
- response.finalUrl; // string: Final URL after redirects
151
- response.protocol; // string: Protocol used (http/1.1, h2, h3)
152
- response.json(); // Parse response body as JSON
385
+ response.statusCode; // number: HTTP status code
386
+ response.headers; // object: Response headers (values are arrays)
387
+ response.body; // Buffer: Raw response body
388
+ response.text; // string: Response body as text
389
+ response.finalUrl; // string: Final URL after redirects
390
+ response.protocol; // string: Protocol used (http/1.1, h2, h3)
391
+ response.ok; // boolean: True if status < 400
392
+ response.cookies; // array: Cookies from response
393
+ response.history; // array: Redirect history
394
+
395
+ // Get specific header
396
+ const contentType = response.getHeader("Content-Type");
397
+ const allCookies = response.getHeaders("Set-Cookie");
398
+
399
+ // Parse JSON
400
+ const data = response.json();
153
401
  ```
154
402
 
155
- ## Custom Requests
403
+ ### Streaming Response
156
404
 
157
405
  ```javascript
158
- const response = await session.request({
406
+ const stream = session.getStream("https://example.com");
407
+
408
+ stream.statusCode; // number: HTTP status code
409
+ stream.headers; // object: Response headers (values are arrays)
410
+ stream.contentLength; // number: Content length (-1 if unknown)
411
+ stream.finalUrl; // string: Final URL after redirects
412
+ stream.protocol; // string: Protocol used
413
+
414
+ // Read all bytes
415
+ const data = stream.readAll();
416
+
417
+ // Read in chunks (memory efficient)
418
+ let chunk;
419
+ while ((chunk = stream.readChunk(65536)) !== null) {
420
+ process(chunk);
421
+ }
422
+
423
+ stream.close();
424
+ ```
425
+
426
+ ## HTTP Methods
427
+
428
+ ```javascript
429
+ const { Session } = require("httpcloak");
430
+
431
+ const session = new Session({ preset: "chrome-143" });
432
+
433
+ // GET
434
+ const response = await session.get("https://example.com");
435
+
436
+ // POST
437
+ const postResponse = await session.post("https://example.com", { key: "value" });
438
+
439
+ // PUT
440
+ const putResponse = await session.put("https://example.com", { key: "value" });
441
+
442
+ // PATCH
443
+ const patchResponse = await session.patch("https://example.com", { key: "value" });
444
+
445
+ // DELETE
446
+ const deleteResponse = await session.delete("https://example.com");
447
+
448
+ // HEAD
449
+ const headResponse = await session.head("https://example.com");
450
+
451
+ // OPTIONS
452
+ const optionsResponse = await session.options("https://example.com");
453
+
454
+ // Custom request
455
+ const customResponse = await session.request({
159
456
  method: "PUT",
160
457
  url: "https://api.example.com/resource",
161
458
  headers: { "X-Custom": "value" },
162
459
  body: { data: "value" },
163
460
  timeout: 60,
164
461
  });
462
+
463
+ session.close();
165
464
  ```
166
465
 
167
466
  ## Error Handling
@@ -189,13 +488,22 @@ session.close();
189
488
  HTTPCloak includes TypeScript definitions out of the box:
190
489
 
191
490
  ```typescript
192
- import { Session, Response, HTTPCloakError } from "httpcloak";
491
+ import { Session, Response, StreamResponse, HTTPCloakError } from "httpcloak";
193
492
 
194
493
  const session = new Session({ preset: "chrome-143" });
195
494
 
196
495
  async function fetchData(): Promise<Response> {
197
496
  return session.get("https://example.com");
198
497
  }
498
+
499
+ async function downloadLargeFile(): Promise<void> {
500
+ const stream: StreamResponse = session.getStream("https://example.com/file");
501
+ let chunk: Buffer | null;
502
+ while ((chunk = stream.readChunk(65536)) !== null) {
503
+ // Process chunk
504
+ }
505
+ stream.close();
506
+ }
199
507
  ```
200
508
 
201
509
  ## Platform Support
@@ -203,6 +511,7 @@ async function fetchData(): Promise<Response> {
203
511
  - Linux (x64, arm64)
204
512
  - macOS (x64, arm64)
205
513
  - Windows (x64, arm64)
514
+ - Node.js 16+
206
515
 
207
516
  ## License
208
517
 
package/lib/index.d.ts CHANGED
@@ -6,6 +6,22 @@ export class HTTPCloakError extends Error {
6
6
  name: "HTTPCloakError";
7
7
  }
8
8
 
9
+ export class Cookie {
10
+ /** Cookie name */
11
+ name: string;
12
+ /** Cookie value */
13
+ value: string;
14
+ }
15
+
16
+ export class RedirectInfo {
17
+ /** HTTP status code */
18
+ statusCode: number;
19
+ /** Request URL */
20
+ url: string;
21
+ /** Response headers */
22
+ headers: Record<string, string>;
23
+ }
24
+
9
25
  export class Response {
10
26
  /** HTTP status code */
11
27
  statusCode: number;
@@ -13,22 +29,45 @@ export class Response {
13
29
  headers: Record<string, string>;
14
30
  /** Raw response body as Buffer */
15
31
  body: Buffer;
32
+ /** Response body as Buffer (alias for body) */
33
+ content: Buffer;
16
34
  /** Response body as string */
17
35
  text: string;
18
36
  /** Final URL after redirects */
19
37
  finalUrl: string;
38
+ /** Final URL after redirects (alias for finalUrl) */
39
+ url: string;
20
40
  /** Protocol used (http/1.1, h2, h3) */
21
41
  protocol: string;
42
+ /** Elapsed time in milliseconds */
43
+ elapsed: number;
44
+ /** Cookies set by this response */
45
+ cookies: Cookie[];
46
+ /** Redirect history */
47
+ history: RedirectInfo[];
48
+ /** True if status code < 400 */
49
+ ok: boolean;
50
+ /** HTTP status reason phrase (e.g., 'OK', 'Not Found') */
51
+ reason: string;
52
+ /** Response encoding from Content-Type header */
53
+ encoding: string | null;
22
54
 
23
55
  /** Parse response body as JSON */
24
56
  json<T = any>(): T;
57
+
58
+ /** Raise error if status >= 400 */
59
+ raiseForStatus(): void;
25
60
  }
26
61
 
27
62
  export interface SessionOptions {
28
63
  /** Browser preset to use (default: "chrome-143") */
29
64
  preset?: string;
30
- /** Proxy URL (e.g., "http://user:pass@host:port") */
65
+ /** Proxy URL (e.g., "http://user:pass@host:port" or "socks5://host:port") */
31
66
  proxy?: string;
67
+ /** Proxy URL for TCP protocols (HTTP/1.1, HTTP/2) - use with udpProxy for split config */
68
+ tcpProxy?: string;
69
+ /** Proxy URL for UDP protocols (HTTP/3 via MASQUE) - use with tcpProxy for split config */
70
+ udpProxy?: string;
32
71
  /** Request timeout in seconds (default: 30) */
33
72
  timeout?: number;
34
73
  /** HTTP version: "auto", "h1", "h2", "h3" (default: "auto") */
@@ -43,17 +82,33 @@ export interface SessionOptions {
43
82
  retry?: number;
44
83
  /** Status codes to retry on (default: [429, 500, 502, 503, 504]) */
45
84
  retryOnStatus?: number[];
85
+ /** Prefer IPv4 addresses over IPv6 (default: false) */
86
+ preferIpv4?: boolean;
87
+ /** Default basic auth [username, password] */
88
+ auth?: [string, string];
89
+ /** Domain fronting map {requestHost: connectHost} - DNS resolves connectHost but SNI/Host uses requestHost */
90
+ connectTo?: Record<string, string>;
91
+ /** Domain to fetch ECH config from (e.g., "cloudflare-ech.com" for any Cloudflare domain) */
92
+ echConfigDomain?: string;
46
93
  }
47
94
 
48
95
  export interface RequestOptions {
49
- /** HTTP method */
50
- method: string;
51
- /** Request URL */
52
- url: string;
53
96
  /** Optional custom headers */
54
97
  headers?: Record<string, string>;
55
- /** Optional request body */
98
+ /** Optional request body (for POST, PUT, PATCH) */
56
99
  body?: string | Buffer | Record<string, any>;
100
+ /** JSON body (will be serialized) */
101
+ json?: Record<string, any>;
102
+ /** Form data (will be URL encoded) */
103
+ data?: Record<string, any>;
104
+ /** Files to upload as multipart/form-data */
105
+ files?: Record<string, Buffer | { filename: string; content: Buffer; contentType?: string }>;
106
+ /** Query parameters */
107
+ params?: Record<string, string | number | boolean>;
108
+ /** Cookies to send with this request */
109
+ cookies?: Record<string, string>;
110
+ /** Basic auth [username, password] */
111
+ auth?: [string, string];
57
112
  /** Optional request timeout in seconds */
58
113
  timeout?: number;
59
114
  }
@@ -61,67 +116,66 @@ export interface RequestOptions {
61
116
  export class Session {
62
117
  constructor(options?: SessionOptions);
63
118
 
119
+ /** Default headers for all requests */
120
+ headers: Record<string, string>;
121
+
122
+ /** Default auth for all requests [username, password] */
123
+ auth: [string, string] | null;
124
+
64
125
  /** Close the session and release resources */
65
126
  close(): void;
66
127
 
67
128
  // Synchronous methods
68
129
  /** Perform a synchronous GET request */
69
- getSync(url: string, headers?: Record<string, string>): Response;
130
+ getSync(url: string, options?: RequestOptions): Response;
70
131
 
71
132
  /** Perform a synchronous POST request */
72
- postSync(
73
- url: string,
74
- body?: string | Buffer | Record<string, any>,
75
- headers?: Record<string, string>
76
- ): Response;
133
+ postSync(url: string, options?: RequestOptions): Response;
77
134
 
78
135
  /** Perform a synchronous custom HTTP request */
79
- requestSync(options: RequestOptions): Response;
136
+ requestSync(method: string, url: string, options?: RequestOptions): Response;
80
137
 
81
138
  // Promise-based methods
82
139
  /** Perform an async GET request */
83
- get(url: string, headers?: Record<string, string>): Promise<Response>;
140
+ get(url: string, options?: RequestOptions): Promise<Response>;
84
141
 
85
142
  /** Perform an async POST request */
86
- post(
87
- url: string,
88
- body?: string | Buffer | Record<string, any>,
89
- headers?: Record<string, string>
90
- ): Promise<Response>;
143
+ post(url: string, options?: RequestOptions): Promise<Response>;
91
144
 
92
145
  /** Perform an async custom HTTP request */
93
- request(options: RequestOptions): Promise<Response>;
146
+ request(method: string, url: string, options?: RequestOptions): Promise<Response>;
94
147
 
95
148
  /** Perform an async PUT request */
96
- put(
97
- url: string,
98
- body?: string | Buffer | Record<string, any>,
99
- headers?: Record<string, string>
100
- ): Promise<Response>;
149
+ put(url: string, options?: RequestOptions): Promise<Response>;
101
150
 
102
151
  /** Perform an async DELETE request */
103
- delete(url: string, headers?: Record<string, string>): Promise<Response>;
152
+ delete(url: string, options?: RequestOptions): Promise<Response>;
104
153
 
105
154
  /** Perform an async PATCH request */
106
- patch(
107
- url: string,
108
- body?: string | Buffer | Record<string, any>,
109
- headers?: Record<string, string>
110
- ): Promise<Response>;
155
+ patch(url: string, options?: RequestOptions): Promise<Response>;
111
156
 
112
157
  /** Perform an async HEAD request */
113
- head(url: string, headers?: Record<string, string>): Promise<Response>;
158
+ head(url: string, options?: RequestOptions): Promise<Response>;
114
159
 
115
160
  /** Perform an async OPTIONS request */
116
- options(url: string, headers?: Record<string, string>): Promise<Response>;
161
+ options(url: string, options?: RequestOptions): Promise<Response>;
117
162
 
118
163
  // Cookie management
119
164
  /** Get all cookies from the session */
120
165
  getCookies(): Record<string, string>;
121
166
 
167
+ /** Get a specific cookie by name */
168
+ getCookie(name: string): string | null;
169
+
122
170
  /** Set a cookie in the session */
123
171
  setCookie(name: string, value: string): void;
124
172
 
173
+ /** Delete a specific cookie by name */
174
+ deleteCookie(name: string): void;
175
+
176
+ /** Clear all cookies from the session */
177
+ clearCookies(): void;
178
+
125
179
  /** Get cookies as a property */
126
180
  readonly cookies: Record<string, string>;
127
181
  }
@@ -162,7 +216,8 @@ export function patch(url: string, options?: RequestOptions): Promise<Response>;
162
216
  export function head(url: string, options?: RequestOptions): Promise<Response>;
163
217
 
164
218
  /** Perform an OPTIONS request */
165
- export function options(url: string, options?: RequestOptions): Promise<Response>;
219
+ declare function opts(url: string, options?: RequestOptions): Promise<Response>;
220
+ export { opts as options };
166
221
 
167
222
  /** Perform a custom HTTP request */
168
223
  export function request(method: string, url: string, options?: RequestOptions): Promise<Response>;