httpcloak 1.5.2 → 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,6 +112,78 @@ session.postCb(
99
112
  );
100
113
  ```
101
114
 
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
157
+
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
+
102
187
  ## Proxy Support
103
188
 
104
189
  HTTPCloak supports HTTP, SOCKS5, and HTTP/3 (MASQUE) proxies with full fingerprint preservation.
@@ -165,6 +250,20 @@ const response = await session.get("https://www.cloudflare.com/cdn-cgi/trace");
165
250
  console.log(response.protocol); // h3
166
251
  ```
167
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
+
168
267
  ## Advanced Features
169
268
 
170
269
  ### Encrypted Client Hello (ECH)
@@ -225,19 +324,47 @@ const { Session } = require("httpcloak");
225
324
 
226
325
  const session = new Session();
227
326
 
327
+ // Set a cookie
328
+ session.setCookie("session_id", "abc123");
329
+
228
330
  // Get all cookies
229
331
  const cookies = session.getCookies();
230
332
  console.log(cookies);
231
333
 
232
- // Set a cookie
233
- session.setCookie("session_id", "abc123");
234
-
235
334
  // Access cookies as property
236
335
  console.log(session.cookies);
237
336
 
337
+ // Clear a cookie
338
+ session.clearCookie("session_id");
339
+
340
+ // Clear all cookies
341
+ session.clearCookies();
342
+
238
343
  session.close();
239
344
  ```
240
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
+
241
368
  ## Available Presets
242
369
 
243
370
  ```javascript
@@ -250,28 +377,90 @@ console.log(availablePresets());
250
377
 
251
378
  ## Response Object
252
379
 
380
+ ### Standard Response
381
+
253
382
  ```javascript
254
383
  const response = await session.get("https://example.com");
255
384
 
256
- response.statusCode; // number: HTTP status code
257
- response.headers; // object: Response headers
258
- response.body; // Buffer: Raw response body
259
- response.text; // string: Response body as text
260
- response.finalUrl; // string: Final URL after redirects
261
- response.protocol; // string: Protocol used (http/1.1, h2, h3)
262
- 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();
401
+ ```
402
+
403
+ ### Streaming Response
404
+
405
+ ```javascript
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();
263
424
  ```
264
425
 
265
- ## Custom Requests
426
+ ## HTTP Methods
266
427
 
267
428
  ```javascript
268
- const response = await session.request({
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({
269
456
  method: "PUT",
270
457
  url: "https://api.example.com/resource",
271
458
  headers: { "X-Custom": "value" },
272
459
  body: { data: "value" },
273
460
  timeout: 60,
274
461
  });
462
+
463
+ session.close();
275
464
  ```
276
465
 
277
466
  ## Error Handling
@@ -299,13 +488,22 @@ session.close();
299
488
  HTTPCloak includes TypeScript definitions out of the box:
300
489
 
301
490
  ```typescript
302
- import { Session, Response, HTTPCloakError } from "httpcloak";
491
+ import { Session, Response, StreamResponse, HTTPCloakError } from "httpcloak";
303
492
 
304
493
  const session = new Session({ preset: "chrome-143" });
305
494
 
306
495
  async function fetchData(): Promise<Response> {
307
496
  return session.get("https://example.com");
308
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
+ }
309
507
  ```
310
508
 
311
509
  ## Platform Support
@@ -313,6 +511,7 @@ async function fetchData(): Promise<Response> {
313
511
  - Linux (x64, arm64)
314
512
  - macOS (x64, arm64)
315
513
  - Windows (x64, arm64)
514
+ - Node.js 16+
316
515
 
317
516
  ## License
318
517
 
package/lib/index.d.ts CHANGED
@@ -64,6 +64,10 @@ export interface SessionOptions {
64
64
  preset?: string;
65
65
  /** Proxy URL (e.g., "http://user:pass@host:port" or "socks5://host:port") */
66
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;
67
71
  /** Request timeout in seconds (default: 30) */
68
72
  timeout?: number;
69
73
  /** HTTP version: "auto", "h1", "h2", "h3" (default: "auto") */
package/lib/index.js CHANGED
@@ -235,6 +235,364 @@ class Response {
235
235
  }
236
236
  }
237
237
 
238
+ /**
239
+ * High-performance buffer pool using SharedArrayBuffer for zero-allocation copies.
240
+ * Pre-allocates a large buffer once and reuses it across requests.
241
+ */
242
+ class FastBufferPool {
243
+ constructor() {
244
+ // Pre-allocate 256MB SharedArrayBuffer for maximum performance
245
+ this._sharedBuffer = new SharedArrayBuffer(256 * 1024 * 1024);
246
+ this._bufferView = Buffer.from(this._sharedBuffer);
247
+ this._inUse = false;
248
+
249
+ // Fallback pool for concurrent requests or very large files
250
+ this._fallbackPools = new Map();
251
+ this._tiers = [1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864, 134217728];
252
+ }
253
+
254
+ /**
255
+ * Get a buffer of at least the requested size
256
+ * @param {number} size - Minimum buffer size needed
257
+ * @returns {Buffer} - A buffer (may be larger than requested)
258
+ */
259
+ acquire(size) {
260
+ // Use pre-allocated SharedArrayBuffer if available and large enough
261
+ if (!this._inUse && size <= this._sharedBuffer.byteLength) {
262
+ this._inUse = true;
263
+ return this._bufferView;
264
+ }
265
+
266
+ // Fallback to regular buffer pool for concurrent requests
267
+ let tier = this._tiers[this._tiers.length - 1];
268
+ for (const t of this._tiers) {
269
+ if (t >= size) {
270
+ tier = t;
271
+ break;
272
+ }
273
+ }
274
+
275
+ const pool = this._fallbackPools.get(tier);
276
+ if (pool && pool.length > 0) {
277
+ return pool.pop();
278
+ }
279
+
280
+ return Buffer.allocUnsafe(Math.max(tier, size));
281
+ }
282
+
283
+ /**
284
+ * Return a buffer to the pool for reuse
285
+ * @param {Buffer} buffer - Buffer to return
286
+ */
287
+ release(buffer) {
288
+ // Check if this is our shared buffer
289
+ if (buffer.buffer === this._sharedBuffer) {
290
+ this._inUse = false;
291
+ return;
292
+ }
293
+
294
+ // Otherwise add to fallback pool
295
+ const size = buffer.length;
296
+ if (!this._tiers.includes(size)) {
297
+ return;
298
+ }
299
+
300
+ let pool = this._fallbackPools.get(size);
301
+ if (!pool) {
302
+ pool = [];
303
+ this._fallbackPools.set(size, pool);
304
+ }
305
+
306
+ if (pool.length < 2) {
307
+ pool.push(buffer);
308
+ }
309
+ }
310
+ }
311
+
312
+ // Global buffer pool instance
313
+ const _bufferPool = new FastBufferPool();
314
+
315
+ /**
316
+ * Fast response object with zero-copy buffer transfer.
317
+ *
318
+ * This response type avoids JSON serialization and base64 encoding for the body,
319
+ * copying data directly from Go's memory to a Node.js Buffer.
320
+ *
321
+ * Use session.getFast() for maximum download performance.
322
+ */
323
+ class FastResponse {
324
+ /**
325
+ * @param {Object} metadata - Response metadata from native library
326
+ * @param {Buffer} body - Response body as Buffer (view of pooled buffer)
327
+ * @param {number} [elapsed=0] - Elapsed time in milliseconds
328
+ * @param {Buffer} [pooledBuffer=null] - The underlying pooled buffer for release
329
+ */
330
+ constructor(metadata, body, elapsed = 0, pooledBuffer = null) {
331
+ this.statusCode = metadata.status_code || 0;
332
+ this.headers = metadata.headers || {};
333
+ this._body = body;
334
+ this._pooledBuffer = pooledBuffer;
335
+ this.finalUrl = metadata.final_url || "";
336
+ this.protocol = metadata.protocol || "";
337
+ this.elapsed = elapsed;
338
+
339
+ // Parse cookies from response
340
+ this._cookies = (metadata.cookies || []).map(c => new Cookie(c.name || "", c.value || ""));
341
+
342
+ // Parse redirect history
343
+ this._history = (metadata.history || []).map(h => new RedirectInfo(
344
+ h.status_code || 0,
345
+ h.url || "",
346
+ h.headers || {}
347
+ ));
348
+ }
349
+
350
+ /**
351
+ * Release the underlying buffer back to the pool.
352
+ * Call this when done with the response to enable buffer reuse.
353
+ * After calling release(), the body buffer should not be used.
354
+ */
355
+ release() {
356
+ if (this._pooledBuffer) {
357
+ _bufferPool.release(this._pooledBuffer);
358
+ this._pooledBuffer = null;
359
+ this._body = null;
360
+ }
361
+ }
362
+
363
+ /** Cookies set by this response */
364
+ get cookies() {
365
+ return this._cookies;
366
+ }
367
+
368
+ /** Redirect history (list of RedirectInfo objects) */
369
+ get history() {
370
+ return this._history;
371
+ }
372
+
373
+ /** Response body as string */
374
+ get text() {
375
+ return this._body.toString("utf8");
376
+ }
377
+
378
+ /** Response body as Buffer */
379
+ get body() {
380
+ return this._body;
381
+ }
382
+
383
+ /** Response body as Buffer (requests compatibility alias) */
384
+ get content() {
385
+ return this._body;
386
+ }
387
+
388
+ /** Final URL after redirects (requests compatibility alias) */
389
+ get url() {
390
+ return this.finalUrl;
391
+ }
392
+
393
+ /** True if status code < 400 (requests compatibility) */
394
+ get ok() {
395
+ return this.statusCode < 400;
396
+ }
397
+
398
+ /** HTTP status reason phrase (e.g., 'OK', 'Not Found') */
399
+ get reason() {
400
+ return HTTP_STATUS_PHRASES[this.statusCode] || "Unknown";
401
+ }
402
+
403
+ /**
404
+ * Response encoding from Content-Type header.
405
+ * Returns null if not specified.
406
+ */
407
+ get encoding() {
408
+ let contentType = this.headers["content-type"] || this.headers["Content-Type"] || "";
409
+ if (contentType.includes("charset=")) {
410
+ const parts = contentType.split(";");
411
+ for (const part of parts) {
412
+ const trimmed = part.trim();
413
+ if (trimmed.toLowerCase().startsWith("charset=")) {
414
+ return trimmed.split("=")[1].trim().replace(/['"]/g, "");
415
+ }
416
+ }
417
+ }
418
+ return null;
419
+ }
420
+
421
+ /**
422
+ * Parse response body as JSON
423
+ */
424
+ json() {
425
+ return JSON.parse(this._body.toString("utf8"));
426
+ }
427
+
428
+ /**
429
+ * Raise error if status >= 400 (requests compatibility)
430
+ */
431
+ raiseForStatus() {
432
+ if (!this.ok) {
433
+ throw new HTTPCloakError(`HTTP ${this.statusCode}: ${this.reason}`);
434
+ }
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Streaming HTTP Response for downloading large files.
440
+ *
441
+ * Example:
442
+ * const stream = session.getStream(url);
443
+ * for await (const chunk of stream) {
444
+ * file.write(chunk);
445
+ * }
446
+ * stream.close();
447
+ */
448
+ class StreamResponse {
449
+ /**
450
+ * @param {number} streamHandle - Native stream handle
451
+ * @param {Object} lib - Native library
452
+ * @param {Object} metadata - Stream metadata
453
+ */
454
+ constructor(streamHandle, lib, metadata) {
455
+ this._handle = streamHandle;
456
+ this._lib = lib;
457
+ this.statusCode = metadata.status_code || 0;
458
+ this.headers = metadata.headers || {};
459
+ this.finalUrl = metadata.final_url || "";
460
+ this.protocol = metadata.protocol || "";
461
+ this.contentLength = metadata.content_length || -1;
462
+ this._cookies = (metadata.cookies || []).map(c => new Cookie(c.name || "", c.value || ""));
463
+ this._closed = false;
464
+ }
465
+
466
+ /** Cookies set by this response */
467
+ get cookies() {
468
+ return this._cookies;
469
+ }
470
+
471
+ /** Final URL after redirects */
472
+ get url() {
473
+ return this.finalUrl;
474
+ }
475
+
476
+ /** True if status code < 400 */
477
+ get ok() {
478
+ return this.statusCode < 400;
479
+ }
480
+
481
+ /** HTTP status reason phrase */
482
+ get reason() {
483
+ return HTTP_STATUS_PHRASES[this.statusCode] || "Unknown";
484
+ }
485
+
486
+ /**
487
+ * Read a chunk of data from the stream.
488
+ * @param {number} [chunkSize=8192] - Maximum bytes to read
489
+ * @returns {Buffer|null} - Chunk of data or null if EOF
490
+ */
491
+ readChunk(chunkSize = 8192) {
492
+ if (this._closed) {
493
+ throw new HTTPCloakError("Stream is closed");
494
+ }
495
+
496
+ const result = this._lib.httpcloak_stream_read(this._handle, chunkSize);
497
+ if (!result || result === "") {
498
+ return null; // EOF
499
+ }
500
+
501
+ // Decode base64 to Buffer
502
+ return Buffer.from(result, "base64");
503
+ }
504
+
505
+ /**
506
+ * Async generator for iterating over chunks.
507
+ * @param {number} [chunkSize=8192] - Size of each chunk
508
+ * @yields {Buffer} - Chunks of response content
509
+ *
510
+ * Example:
511
+ * for await (const chunk of stream.iterate()) {
512
+ * file.write(chunk);
513
+ * }
514
+ */
515
+ async *iterate(chunkSize = 8192) {
516
+ while (true) {
517
+ const chunk = this.readChunk(chunkSize);
518
+ if (!chunk) {
519
+ break;
520
+ }
521
+ yield chunk;
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Symbol.asyncIterator for for-await-of loops.
527
+ * @yields {Buffer} - Chunks of response content
528
+ *
529
+ * Example:
530
+ * for await (const chunk of stream) {
531
+ * file.write(chunk);
532
+ * }
533
+ */
534
+ [Symbol.asyncIterator]() {
535
+ return this.iterate();
536
+ }
537
+
538
+ /**
539
+ * Read the entire response body as Buffer.
540
+ * Warning: This defeats the purpose of streaming for large files.
541
+ * @returns {Buffer}
542
+ */
543
+ readAll() {
544
+ const chunks = [];
545
+ let chunk;
546
+ while ((chunk = this.readChunk()) !== null) {
547
+ chunks.push(chunk);
548
+ }
549
+ return Buffer.concat(chunks);
550
+ }
551
+
552
+ /**
553
+ * Read the entire response body as string.
554
+ * @returns {string}
555
+ */
556
+ get text() {
557
+ return this.readAll().toString("utf8");
558
+ }
559
+
560
+ /**
561
+ * Read the entire response body as Buffer.
562
+ * @returns {Buffer}
563
+ */
564
+ get body() {
565
+ return this.readAll();
566
+ }
567
+
568
+ /**
569
+ * Parse the response body as JSON.
570
+ * @returns {any}
571
+ */
572
+ json() {
573
+ return JSON.parse(this.text);
574
+ }
575
+
576
+ /**
577
+ * Close the stream and release resources.
578
+ */
579
+ close() {
580
+ if (!this._closed) {
581
+ this._lib.httpcloak_stream_close(this._handle);
582
+ this._closed = true;
583
+ }
584
+ }
585
+
586
+ /**
587
+ * Raise error if status >= 400
588
+ */
589
+ raiseForStatus() {
590
+ if (!this.ok) {
591
+ throw new HTTPCloakError(`HTTP ${this.statusCode}: ${this.reason}`);
592
+ }
593
+ }
594
+ }
595
+
238
596
  /**
239
597
  * Get the platform package name for the current platform
240
598
  */
@@ -357,6 +715,22 @@ function getLib() {
357
715
  httpcloak_get_async: nativeLibHandle.func("httpcloak_get_async", "void", ["int64", "str", "str", "int64"]),
358
716
  httpcloak_post_async: nativeLibHandle.func("httpcloak_post_async", "void", ["int64", "str", "str", "str", "int64"]),
359
717
  httpcloak_request_async: nativeLibHandle.func("httpcloak_request_async", "void", ["int64", "str", "int64"]),
718
+ // Streaming functions
719
+ httpcloak_stream_get: nativeLibHandle.func("httpcloak_stream_get", "int64", ["int64", "str", "str"]),
720
+ httpcloak_stream_post: nativeLibHandle.func("httpcloak_stream_post", "int64", ["int64", "str", "str", "str"]),
721
+ httpcloak_stream_request: nativeLibHandle.func("httpcloak_stream_request", "int64", ["int64", "str"]),
722
+ httpcloak_stream_get_metadata: nativeLibHandle.func("httpcloak_stream_get_metadata", "str", ["int64"]),
723
+ httpcloak_stream_read: nativeLibHandle.func("httpcloak_stream_read", "str", ["int64", "int64"]),
724
+ httpcloak_stream_close: nativeLibHandle.func("httpcloak_stream_close", "void", ["int64"]),
725
+ // Raw response functions for fast-path (zero-copy)
726
+ httpcloak_get_raw: nativeLibHandle.func("httpcloak_get_raw", "int64", ["int64", "str", "str"]),
727
+ httpcloak_post_raw: nativeLibHandle.func("httpcloak_post_raw", "int64", ["int64", "str", "void*", "int", "str"]),
728
+ httpcloak_response_get_metadata: nativeLibHandle.func("httpcloak_response_get_metadata", "str", ["int64"]),
729
+ httpcloak_response_get_body_len: nativeLibHandle.func("httpcloak_response_get_body_len", "int", ["int64"]),
730
+ httpcloak_response_copy_body_to: nativeLibHandle.func("httpcloak_response_copy_body_to", "int", ["int64", "void*", "int"]),
731
+ httpcloak_response_free: nativeLibHandle.func("httpcloak_response_free", "void", ["int64"]),
732
+ // Combined finalize function (copy + metadata + free in one call)
733
+ httpcloak_response_finalize: nativeLibHandle.func("httpcloak_response_finalize", "str", ["int64", "void*", "int"]),
360
734
  };
361
735
  }
362
736
  return lib;
@@ -675,6 +1049,8 @@ class Session {
675
1049
  * @param {Object} options - Session options
676
1050
  * @param {string} [options.preset="chrome-143"] - Browser preset to use
677
1051
  * @param {string} [options.proxy] - Proxy URL (e.g., "http://user:pass@host:port" or "socks5://host:port")
1052
+ * @param {string} [options.tcpProxy] - Proxy URL for TCP protocols (HTTP/1.1, HTTP/2) - use with udpProxy for split config
1053
+ * @param {string} [options.udpProxy] - Proxy URL for UDP protocols (HTTP/3 via MASQUE) - use with tcpProxy for split config
678
1054
  * @param {number} [options.timeout=30] - Request timeout in seconds
679
1055
  * @param {string} [options.httpVersion="auto"] - HTTP version: "auto", "h1", "h2", "h3"
680
1056
  * @param {boolean} [options.verify=true] - SSL certificate verification
@@ -690,6 +1066,8 @@ class Session {
690
1066
  const {
691
1067
  preset = "chrome-143",
692
1068
  proxy = null,
1069
+ tcpProxy = null,
1070
+ udpProxy = null,
693
1071
  timeout = 30,
694
1072
  httpVersion = "auto",
695
1073
  verify = true,
@@ -715,6 +1093,12 @@ class Session {
715
1093
  if (proxy) {
716
1094
  config.proxy = proxy;
717
1095
  }
1096
+ if (tcpProxy) {
1097
+ config.tcp_proxy = tcpProxy;
1098
+ }
1099
+ if (udpProxy) {
1100
+ config.udp_proxy = udpProxy;
1101
+ }
718
1102
  if (!verify) {
719
1103
  config.verify = false;
720
1104
  }
@@ -814,9 +1198,15 @@ class Session {
814
1198
  mergedHeaders = applyAuth(mergedHeaders, effectiveAuth);
815
1199
  mergedHeaders = this._applyCookies(mergedHeaders, cookies);
816
1200
 
817
- const headersJson = mergedHeaders ? JSON.stringify(mergedHeaders) : null;
1201
+ // Build request options JSON with headers wrapper (clib expects {"headers": {...}})
1202
+ const reqOptions = {};
1203
+ if (mergedHeaders) {
1204
+ reqOptions.headers = mergedHeaders;
1205
+ }
1206
+ const optionsJson = Object.keys(reqOptions).length > 0 ? JSON.stringify(reqOptions) : null;
1207
+
818
1208
  const startTime = Date.now();
819
- const result = this._lib.httpcloak_get(this._handle, url, headersJson);
1209
+ const result = this._lib.httpcloak_get(this._handle, url, optionsJson);
820
1210
  const elapsed = Date.now() - startTime;
821
1211
  return parseResponse(result, elapsed);
822
1212
  }
@@ -878,9 +1268,15 @@ class Session {
878
1268
  mergedHeaders = applyAuth(mergedHeaders, effectiveAuth);
879
1269
  mergedHeaders = this._applyCookies(mergedHeaders, cookies);
880
1270
 
881
- const headersJson = mergedHeaders ? JSON.stringify(mergedHeaders) : null;
1271
+ // Build request options JSON with headers wrapper (clib expects {"headers": {...}})
1272
+ const reqOptions = {};
1273
+ if (mergedHeaders) {
1274
+ reqOptions.headers = mergedHeaders;
1275
+ }
1276
+ const optionsJson = Object.keys(reqOptions).length > 0 ? JSON.stringify(reqOptions) : null;
1277
+
882
1278
  const startTime = Date.now();
883
- const result = this._lib.httpcloak_post(this._handle, url, body, headersJson);
1279
+ const result = this._lib.httpcloak_post(this._handle, url, body, optionsJson);
884
1280
  const elapsed = Date.now() - startTime;
885
1281
  return parseResponse(result, elapsed);
886
1282
  }
@@ -972,14 +1368,19 @@ class Session {
972
1368
  mergedHeaders = applyAuth(mergedHeaders, effectiveAuth);
973
1369
  mergedHeaders = this._applyCookies(mergedHeaders, cookies);
974
1370
 
975
- const headersJson = mergedHeaders ? JSON.stringify(mergedHeaders) : null;
1371
+ // Build request options JSON with headers wrapper (clib expects {"headers": {...}})
1372
+ const reqOptions = {};
1373
+ if (mergedHeaders) {
1374
+ reqOptions.headers = mergedHeaders;
1375
+ }
1376
+ const optionsJson = Object.keys(reqOptions).length > 0 ? JSON.stringify(reqOptions) : null;
976
1377
 
977
1378
  // Register async request with callback manager
978
1379
  const manager = getAsyncManager();
979
1380
  const { callbackId, promise } = manager.registerRequest(this._lib);
980
1381
 
981
1382
  // Start async request
982
- this._lib.httpcloak_get_async(this._handle, url, headersJson, callbackId);
1383
+ this._lib.httpcloak_get_async(this._handle, url, optionsJson, callbackId);
983
1384
 
984
1385
  return promise;
985
1386
  }
@@ -1031,14 +1432,19 @@ class Session {
1031
1432
  mergedHeaders = applyAuth(mergedHeaders, effectiveAuth);
1032
1433
  mergedHeaders = this._applyCookies(mergedHeaders, cookies);
1033
1434
 
1034
- const headersJson = mergedHeaders ? JSON.stringify(mergedHeaders) : null;
1435
+ // Build request options JSON with headers wrapper (clib expects {"headers": {...}})
1436
+ const reqOptions = {};
1437
+ if (mergedHeaders) {
1438
+ reqOptions.headers = mergedHeaders;
1439
+ }
1440
+ const optionsJson = Object.keys(reqOptions).length > 0 ? JSON.stringify(reqOptions) : null;
1035
1441
 
1036
1442
  // Register async request with callback manager
1037
1443
  const manager = getAsyncManager();
1038
1444
  const { callbackId, promise } = manager.registerRequest(this._lib);
1039
1445
 
1040
1446
  // Start async request
1041
- this._lib.httpcloak_post_async(this._handle, url, body, headersJson, callbackId);
1447
+ this._lib.httpcloak_post_async(this._handle, url, body, optionsJson, callbackId);
1042
1448
 
1043
1449
  return promise;
1044
1450
  }
@@ -1205,6 +1611,394 @@ class Session {
1205
1611
  get cookies() {
1206
1612
  return this.getCookies();
1207
1613
  }
1614
+
1615
+ // ===========================================================================
1616
+ // Streaming Methods
1617
+ // ===========================================================================
1618
+
1619
+ /**
1620
+ * Perform a streaming GET request.
1621
+ *
1622
+ * @param {string} url - Request URL
1623
+ * @param {Object} [options] - Request options
1624
+ * @param {Object} [options.params] - URL query parameters
1625
+ * @param {Object} [options.headers] - Request headers
1626
+ * @param {Object} [options.cookies] - Cookies to send
1627
+ * @param {number} [options.timeout] - Request timeout in milliseconds
1628
+ * @returns {StreamResponse} - Streaming response for chunked reading
1629
+ *
1630
+ * Example:
1631
+ * const stream = session.getStream("https://example.com/large-file.zip");
1632
+ * for await (const chunk of stream) {
1633
+ * file.write(chunk);
1634
+ * }
1635
+ * stream.close();
1636
+ */
1637
+ getStream(url, options = {}) {
1638
+ const { params, headers, cookies, timeout } = options;
1639
+
1640
+ // Add params to URL
1641
+ if (params) {
1642
+ url = addParamsToUrl(url, params);
1643
+ }
1644
+
1645
+ // Merge headers
1646
+ let mergedHeaders = { ...this.headers };
1647
+ if (headers) {
1648
+ mergedHeaders = { ...mergedHeaders, ...headers };
1649
+ }
1650
+ if (cookies) {
1651
+ const cookieStr = Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join("; ");
1652
+ mergedHeaders["Cookie"] = mergedHeaders["Cookie"]
1653
+ ? `${mergedHeaders["Cookie"]}; ${cookieStr}`
1654
+ : cookieStr;
1655
+ }
1656
+
1657
+ // Build options JSON
1658
+ const reqOptions = {};
1659
+ if (Object.keys(mergedHeaders).length > 0) {
1660
+ reqOptions.headers = mergedHeaders;
1661
+ }
1662
+ if (timeout) {
1663
+ reqOptions.timeout = timeout;
1664
+ }
1665
+ const optionsJson = Object.keys(reqOptions).length > 0 ? JSON.stringify(reqOptions) : null;
1666
+
1667
+ // Start stream
1668
+ const streamHandle = this._lib.httpcloak_stream_get(this._handle, url, optionsJson);
1669
+ if (streamHandle < 0) {
1670
+ throw new HTTPCloakError("Failed to start streaming request");
1671
+ }
1672
+
1673
+ // Get metadata
1674
+ const metadataStr = this._lib.httpcloak_stream_get_metadata(streamHandle);
1675
+ if (!metadataStr) {
1676
+ this._lib.httpcloak_stream_close(streamHandle);
1677
+ throw new HTTPCloakError("Failed to get stream metadata");
1678
+ }
1679
+
1680
+ const metadata = JSON.parse(metadataStr);
1681
+ if (metadata.error) {
1682
+ this._lib.httpcloak_stream_close(streamHandle);
1683
+ throw new HTTPCloakError(metadata.error);
1684
+ }
1685
+
1686
+ return new StreamResponse(streamHandle, this._lib, metadata);
1687
+ }
1688
+
1689
+ /**
1690
+ * Perform a streaming POST request.
1691
+ *
1692
+ * @param {string} url - Request URL
1693
+ * @param {Object} [options] - Request options
1694
+ * @param {string|Buffer|Object} [options.body] - Request body
1695
+ * @param {Object} [options.json] - JSON body (will be serialized)
1696
+ * @param {Object} [options.form] - Form data (will be URL-encoded)
1697
+ * @param {Object} [options.params] - URL query parameters
1698
+ * @param {Object} [options.headers] - Request headers
1699
+ * @param {Object} [options.cookies] - Cookies to send
1700
+ * @param {number} [options.timeout] - Request timeout in milliseconds
1701
+ * @returns {StreamResponse} - Streaming response for chunked reading
1702
+ */
1703
+ postStream(url, options = {}) {
1704
+ const { body: bodyOpt, json: jsonBody, form, params, headers, cookies, timeout } = options;
1705
+
1706
+ // Add params to URL
1707
+ if (params) {
1708
+ url = addParamsToUrl(url, params);
1709
+ }
1710
+
1711
+ // Merge headers
1712
+ let mergedHeaders = { ...this.headers };
1713
+ if (headers) {
1714
+ mergedHeaders = { ...mergedHeaders, ...headers };
1715
+ }
1716
+
1717
+ // Process body
1718
+ let body = null;
1719
+ if (jsonBody) {
1720
+ body = JSON.stringify(jsonBody);
1721
+ mergedHeaders["Content-Type"] = mergedHeaders["Content-Type"] || "application/json";
1722
+ } else if (form) {
1723
+ body = Object.entries(form).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
1724
+ mergedHeaders["Content-Type"] = mergedHeaders["Content-Type"] || "application/x-www-form-urlencoded";
1725
+ } else if (bodyOpt) {
1726
+ body = typeof bodyOpt === "string" ? bodyOpt : bodyOpt.toString();
1727
+ }
1728
+
1729
+ if (cookies) {
1730
+ const cookieStr = Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join("; ");
1731
+ mergedHeaders["Cookie"] = mergedHeaders["Cookie"]
1732
+ ? `${mergedHeaders["Cookie"]}; ${cookieStr}`
1733
+ : cookieStr;
1734
+ }
1735
+
1736
+ // Build options JSON
1737
+ const reqOptions = {};
1738
+ if (Object.keys(mergedHeaders).length > 0) {
1739
+ reqOptions.headers = mergedHeaders;
1740
+ }
1741
+ if (timeout) {
1742
+ reqOptions.timeout = timeout;
1743
+ }
1744
+ const optionsJson = Object.keys(reqOptions).length > 0 ? JSON.stringify(reqOptions) : null;
1745
+
1746
+ // Start stream
1747
+ const streamHandle = this._lib.httpcloak_stream_post(this._handle, url, body, optionsJson);
1748
+ if (streamHandle < 0) {
1749
+ throw new HTTPCloakError("Failed to start streaming request");
1750
+ }
1751
+
1752
+ // Get metadata
1753
+ const metadataStr = this._lib.httpcloak_stream_get_metadata(streamHandle);
1754
+ if (!metadataStr) {
1755
+ this._lib.httpcloak_stream_close(streamHandle);
1756
+ throw new HTTPCloakError("Failed to get stream metadata");
1757
+ }
1758
+
1759
+ const metadata = JSON.parse(metadataStr);
1760
+ if (metadata.error) {
1761
+ this._lib.httpcloak_stream_close(streamHandle);
1762
+ throw new HTTPCloakError(metadata.error);
1763
+ }
1764
+
1765
+ return new StreamResponse(streamHandle, this._lib, metadata);
1766
+ }
1767
+
1768
+ /**
1769
+ * Perform a streaming request with any HTTP method.
1770
+ *
1771
+ * @param {string} method - HTTP method
1772
+ * @param {string} url - Request URL
1773
+ * @param {Object} [options] - Request options
1774
+ * @param {string|Buffer} [options.body] - Request body
1775
+ * @param {Object} [options.params] - URL query parameters
1776
+ * @param {Object} [options.headers] - Request headers
1777
+ * @param {Object} [options.cookies] - Cookies to send
1778
+ * @param {number} [options.timeout] - Request timeout in seconds
1779
+ * @returns {StreamResponse} - Streaming response for chunked reading
1780
+ */
1781
+ requestStream(method, url, options = {}) {
1782
+ const { body, params, headers, cookies, timeout } = options;
1783
+
1784
+ // Add params to URL
1785
+ if (params) {
1786
+ url = addParamsToUrl(url, params);
1787
+ }
1788
+
1789
+ // Merge headers
1790
+ let mergedHeaders = { ...this.headers };
1791
+ if (headers) {
1792
+ mergedHeaders = { ...mergedHeaders, ...headers };
1793
+ }
1794
+ if (cookies) {
1795
+ const cookieStr = Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join("; ");
1796
+ mergedHeaders["Cookie"] = mergedHeaders["Cookie"]
1797
+ ? `${mergedHeaders["Cookie"]}; ${cookieStr}`
1798
+ : cookieStr;
1799
+ }
1800
+
1801
+ // Build request config
1802
+ const requestConfig = {
1803
+ method: method.toUpperCase(),
1804
+ url,
1805
+ };
1806
+ if (Object.keys(mergedHeaders).length > 0) {
1807
+ requestConfig.headers = mergedHeaders;
1808
+ }
1809
+ if (body) {
1810
+ requestConfig.body = typeof body === "string" ? body : body.toString();
1811
+ }
1812
+ if (timeout) {
1813
+ requestConfig.timeout = timeout;
1814
+ }
1815
+
1816
+ // Start stream
1817
+ const streamHandle = this._lib.httpcloak_stream_request(this._handle, JSON.stringify(requestConfig));
1818
+ if (streamHandle < 0) {
1819
+ throw new HTTPCloakError("Failed to start streaming request");
1820
+ }
1821
+
1822
+ // Get metadata
1823
+ const metadataStr = this._lib.httpcloak_stream_get_metadata(streamHandle);
1824
+ if (!metadataStr) {
1825
+ this._lib.httpcloak_stream_close(streamHandle);
1826
+ throw new HTTPCloakError("Failed to get stream metadata");
1827
+ }
1828
+
1829
+ const metadata = JSON.parse(metadataStr);
1830
+ if (metadata.error) {
1831
+ this._lib.httpcloak_stream_close(streamHandle);
1832
+ throw new HTTPCloakError(metadata.error);
1833
+ }
1834
+
1835
+ return new StreamResponse(streamHandle, this._lib, metadata);
1836
+ }
1837
+
1838
+ // ===========================================================================
1839
+ // Fast-path Methods (Zero-copy for maximum performance)
1840
+ // ===========================================================================
1841
+
1842
+ /**
1843
+ * Perform a fast GET request with zero-copy buffer transfer.
1844
+ *
1845
+ * This method bypasses JSON serialization and base64 encoding for the response body,
1846
+ * copying data directly from Go's memory to a Node.js Buffer.
1847
+ *
1848
+ * Use this method for downloading large files when you need maximum throughput.
1849
+ *
1850
+ * @param {string} url - Request URL
1851
+ * @param {Object} [options] - Request options
1852
+ * @param {Object} [options.headers] - Custom headers
1853
+ * @param {Object} [options.params] - Query parameters
1854
+ * @param {Object} [options.cookies] - Cookies to send with this request
1855
+ * @param {Array} [options.auth] - Basic auth [username, password]
1856
+ * @returns {FastResponse} Fast response object with Buffer body
1857
+ *
1858
+ * Example:
1859
+ * const response = session.getFast("https://example.com/large-file.zip");
1860
+ * console.log(`Downloaded ${response.body.length} bytes`);
1861
+ * fs.writeFileSync("file.zip", response.body);
1862
+ */
1863
+ getFast(url, options = {}) {
1864
+ const { headers = null, params = null, cookies = null, auth = null } = options;
1865
+
1866
+ url = addParamsToUrl(url, params);
1867
+ let mergedHeaders = this._mergeHeaders(headers);
1868
+ // Use request auth if provided, otherwise fall back to session auth
1869
+ const effectiveAuth = auth !== null ? auth : this.auth;
1870
+ mergedHeaders = applyAuth(mergedHeaders, effectiveAuth);
1871
+ mergedHeaders = this._applyCookies(mergedHeaders, cookies);
1872
+
1873
+ // Build request options JSON with headers wrapper
1874
+ const reqOptions = {};
1875
+ if (mergedHeaders) {
1876
+ reqOptions.headers = mergedHeaders;
1877
+ }
1878
+ const optionsJson = Object.keys(reqOptions).length > 0 ? JSON.stringify(reqOptions) : null;
1879
+
1880
+ const startTime = Date.now();
1881
+
1882
+ // Get raw response handle
1883
+ const responseHandle = this._lib.httpcloak_get_raw(this._handle, url, optionsJson);
1884
+ if (responseHandle === 0 || responseHandle === 0n) {
1885
+ throw new HTTPCloakError("Failed to make request");
1886
+ }
1887
+
1888
+ // Get body length first (1 FFI call)
1889
+ const bodyLen = this._lib.httpcloak_response_get_body_len(responseHandle);
1890
+ if (bodyLen < 0) {
1891
+ this._lib.httpcloak_response_free(responseHandle);
1892
+ throw new HTTPCloakError("Failed to get response body length");
1893
+ }
1894
+
1895
+ // Acquire pooled buffer
1896
+ const pooledBuffer = _bufferPool.acquire(bodyLen);
1897
+
1898
+ // Finalize: copy body + get metadata + free handle (1 FFI call instead of 3)
1899
+ const metadataStr = this._lib.httpcloak_response_finalize(responseHandle, pooledBuffer, bodyLen);
1900
+ if (!metadataStr) {
1901
+ _bufferPool.release(pooledBuffer);
1902
+ throw new HTTPCloakError("Failed to finalize response");
1903
+ }
1904
+
1905
+ const metadata = JSON.parse(metadataStr);
1906
+ if (metadata.error) {
1907
+ _bufferPool.release(pooledBuffer);
1908
+ throw new HTTPCloakError(metadata.error);
1909
+ }
1910
+
1911
+ // Create a view of just the used portion
1912
+ const buffer = pooledBuffer.subarray(0, bodyLen);
1913
+
1914
+ const elapsed = Date.now() - startTime;
1915
+ return new FastResponse(metadata, buffer, elapsed, pooledBuffer);
1916
+ }
1917
+
1918
+ /**
1919
+ * High-performance POST request optimized for large uploads.
1920
+ *
1921
+ * Uses binary buffer passing and response pooling for maximum throughput.
1922
+ * Call response.release() when done to return buffers to pool.
1923
+ *
1924
+ * @param {string} url - Request URL
1925
+ * @param {Object} [options] - Request options
1926
+ * @param {Buffer} [options.body] - Request body as Buffer
1927
+ * @param {Object} [options.headers] - Request headers
1928
+ * @param {Object} [options.params] - Query parameters
1929
+ * @param {Object} [options.cookies] - Cookies to send with this request
1930
+ * @param {Array} [options.auth] - Basic auth [username, password]
1931
+ * @returns {FastResponse} Fast response object with Buffer body
1932
+ *
1933
+ * Example:
1934
+ * const data = Buffer.alloc(10 * 1024 * 1024); // 10MB
1935
+ * const response = session.postFast("https://example.com/upload", { body: data });
1936
+ * console.log(`Uploaded, response: ${response.statusCode}`);
1937
+ * response.release();
1938
+ */
1939
+ postFast(url, options = {}) {
1940
+ let { body = null, headers = null, params = null, cookies = null, auth = null } = options;
1941
+
1942
+ url = addParamsToUrl(url, params);
1943
+ let mergedHeaders = this._mergeHeaders(headers);
1944
+ // Use request auth if provided, otherwise fall back to session auth
1945
+ const effectiveAuth = auth !== null ? auth : this.auth;
1946
+ mergedHeaders = applyAuth(mergedHeaders, effectiveAuth);
1947
+ mergedHeaders = this._applyCookies(mergedHeaders, cookies);
1948
+
1949
+ // Ensure body is a Buffer
1950
+ if (body === null) {
1951
+ body = Buffer.alloc(0);
1952
+ } else if (typeof body === "string") {
1953
+ body = Buffer.from(body, "utf8");
1954
+ } else if (!Buffer.isBuffer(body)) {
1955
+ throw new HTTPCloakError("postFast body must be a Buffer or string");
1956
+ }
1957
+
1958
+ // Build request options JSON with headers wrapper
1959
+ const reqOptions = {};
1960
+ if (mergedHeaders) {
1961
+ reqOptions.headers = mergedHeaders;
1962
+ }
1963
+ const optionsJson = Object.keys(reqOptions).length > 0 ? JSON.stringify(reqOptions) : null;
1964
+
1965
+ const startTime = Date.now();
1966
+
1967
+ // Use httpcloak_post_raw with binary buffer (no string conversion!)
1968
+ const responseHandle = this._lib.httpcloak_post_raw(this._handle, url, body, body.length, optionsJson);
1969
+ if (responseHandle === 0 || responseHandle === 0n || responseHandle < 0) {
1970
+ throw new HTTPCloakError("Failed to make POST request");
1971
+ }
1972
+
1973
+ // Get body length first (1 FFI call)
1974
+ const bodyLen = this._lib.httpcloak_response_get_body_len(responseHandle);
1975
+ if (bodyLen < 0) {
1976
+ this._lib.httpcloak_response_free(responseHandle);
1977
+ throw new HTTPCloakError("Failed to get response body length");
1978
+ }
1979
+
1980
+ // Acquire pooled buffer for response
1981
+ const pooledBuffer = _bufferPool.acquire(bodyLen);
1982
+
1983
+ // Finalize: copy body + get metadata + free handle (1 FFI call instead of 3)
1984
+ const metadataStr = this._lib.httpcloak_response_finalize(responseHandle, pooledBuffer, bodyLen);
1985
+ if (!metadataStr) {
1986
+ _bufferPool.release(pooledBuffer);
1987
+ throw new HTTPCloakError("Failed to finalize response");
1988
+ }
1989
+
1990
+ const metadata = JSON.parse(metadataStr);
1991
+ if (metadata.error) {
1992
+ _bufferPool.release(pooledBuffer);
1993
+ throw new HTTPCloakError(metadata.error);
1994
+ }
1995
+
1996
+ // Create a view of just the used portion
1997
+ const responseBuffer = pooledBuffer.subarray(0, bodyLen);
1998
+
1999
+ const elapsed = Date.now() - startTime;
2000
+ return new FastResponse(metadata, responseBuffer, elapsed, pooledBuffer);
2001
+ }
1208
2002
  }
1209
2003
 
1210
2004
  // =============================================================================
@@ -1384,6 +2178,8 @@ module.exports = {
1384
2178
  // Classes
1385
2179
  Session,
1386
2180
  Response,
2181
+ FastResponse,
2182
+ StreamResponse,
1387
2183
  Cookie,
1388
2184
  RedirectInfo,
1389
2185
  HTTPCloakError,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@httpcloak/darwin-arm64",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "HTTPCloak native binary for darwin arm64",
5
5
  "os": [
6
6
  "darwin"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@httpcloak/darwin-x64",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "HTTPCloak native binary for darwin x64",
5
5
  "os": [
6
6
  "darwin"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@httpcloak/linux-arm64",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "HTTPCloak native binary for linux arm64",
5
5
  "os": [
6
6
  "linux"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@httpcloak/linux-x64",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "HTTPCloak native binary for linux x64",
5
5
  "os": [
6
6
  "linux"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@httpcloak/win32-arm64",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "HTTPCloak native binary for win32 arm64",
5
5
  "os": [
6
6
  "win32"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@httpcloak/win32-x64",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "HTTPCloak native binary for win32 x64",
5
5
  "os": [
6
6
  "win32"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "httpcloak",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "Browser fingerprint emulation HTTP client with HTTP/1.1, HTTP/2, and HTTP/3 support",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/index.mjs",
@@ -49,11 +49,11 @@
49
49
  "koffi": "^2.9.0"
50
50
  },
51
51
  "optionalDependencies": {
52
- "@httpcloak/linux-x64": "1.5.1",
53
- "@httpcloak/linux-arm64": "1.5.1",
54
- "@httpcloak/darwin-x64": "1.5.1",
55
- "@httpcloak/darwin-arm64": "1.5.1",
56
- "@httpcloak/win32-x64": "1.5.1",
57
- "@httpcloak/win32-arm64": "1.5.1"
52
+ "@httpcloak/linux-x64": "1.5.3",
53
+ "@httpcloak/linux-arm64": "1.5.3",
54
+ "@httpcloak/darwin-x64": "1.5.3",
55
+ "@httpcloak/darwin-arm64": "1.5.3",
56
+ "@httpcloak/win32-x64": "1.5.3",
57
+ "@httpcloak/win32-arm64": "1.5.3"
58
58
  }
59
59
  }