httpcloak 1.5.7 → 1.5.9

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/lib/index.d.ts CHANGED
@@ -59,6 +59,127 @@ export class Response {
59
59
  raiseForStatus(): void;
60
60
  }
61
61
 
62
+ /**
63
+ * High-performance HTTP Response with zero-copy buffer transfer.
64
+ *
65
+ * Use session.getFast() or session.postFast() for maximum download performance.
66
+ * Call release() when done to return buffers to the pool.
67
+ */
68
+ export class FastResponse {
69
+ /** HTTP status code */
70
+ statusCode: number;
71
+ /** Response headers */
72
+ headers: Record<string, string>;
73
+ /** Raw response body as Buffer */
74
+ body: Buffer;
75
+ /** Response body as Buffer (alias for body) */
76
+ content: Buffer;
77
+ /** Response body as string */
78
+ text: string;
79
+ /** Final URL after redirects */
80
+ finalUrl: string;
81
+ /** Final URL after redirects (alias for finalUrl) */
82
+ url: string;
83
+ /** Protocol used (http/1.1, h2, h3) */
84
+ protocol: string;
85
+ /** Elapsed time in milliseconds */
86
+ elapsed: number;
87
+ /** Cookies set by this response */
88
+ cookies: Cookie[];
89
+ /** Redirect history */
90
+ history: RedirectInfo[];
91
+ /** True if status code < 400 */
92
+ ok: boolean;
93
+ /** HTTP status reason phrase (e.g., 'OK', 'Not Found') */
94
+ reason: string;
95
+ /** Response encoding from Content-Type header */
96
+ encoding: string | null;
97
+
98
+ /** Parse response body as JSON */
99
+ json<T = any>(): T;
100
+
101
+ /** Raise error if status >= 400 */
102
+ raiseForStatus(): void;
103
+
104
+ /**
105
+ * Release the underlying buffer back to the pool.
106
+ * Call this when done with the response to enable buffer reuse.
107
+ * After calling release(), the body buffer should not be used.
108
+ */
109
+ release(): void;
110
+ }
111
+
112
+ /**
113
+ * Streaming HTTP Response for downloading large files.
114
+ *
115
+ * Use session.getStream() or session.postStream() for streaming downloads.
116
+ * Supports async iteration with for-await-of loops.
117
+ *
118
+ * @example
119
+ * const stream = session.getStream(url);
120
+ * for await (const chunk of stream) {
121
+ * file.write(chunk);
122
+ * }
123
+ * stream.close();
124
+ */
125
+ export class StreamResponse {
126
+ /** HTTP status code */
127
+ statusCode: number;
128
+ /** Response headers */
129
+ headers: Record<string, string>;
130
+ /** Final URL after redirects */
131
+ finalUrl: string;
132
+ /** Final URL after redirects (alias for finalUrl) */
133
+ url: string;
134
+ /** Protocol used (http/1.1, h2, h3) */
135
+ protocol: string;
136
+ /** Content-Length header value, or -1 if unknown */
137
+ contentLength: number;
138
+ /** Cookies set by this response */
139
+ cookies: Cookie[];
140
+ /** True if status code < 400 */
141
+ ok: boolean;
142
+ /** HTTP status reason phrase (e.g., 'OK', 'Not Found') */
143
+ reason: string;
144
+
145
+ /**
146
+ * Read a chunk of data from the stream.
147
+ * @param chunkSize Maximum bytes to read (default: 8192)
148
+ * @returns Chunk of data or null if EOF
149
+ */
150
+ readChunk(chunkSize?: number): Buffer | null;
151
+
152
+ /**
153
+ * Read the entire response body as Buffer.
154
+ * Warning: This defeats the purpose of streaming for large files.
155
+ */
156
+ readAll(): Buffer;
157
+
158
+ /**
159
+ * Async generator for iterating over chunks.
160
+ * @param chunkSize Size of each chunk (default: 8192)
161
+ */
162
+ iterate(chunkSize?: number): AsyncGenerator<Buffer, void, unknown>;
163
+
164
+ /** Async iterator for for-await-of loops */
165
+ [Symbol.asyncIterator](): AsyncIterator<Buffer>;
166
+
167
+ /** Read the entire response body as string */
168
+ readonly text: string;
169
+
170
+ /** Read the entire response body as Buffer */
171
+ readonly body: Buffer;
172
+
173
+ /** Parse the response body as JSON */
174
+ json<T = any>(): T;
175
+
176
+ /** Close the stream and release resources */
177
+ close(): void;
178
+
179
+ /** Raise error if status >= 400 */
180
+ raiseForStatus(): void;
181
+ }
182
+
62
183
  export interface SessionOptions {
63
184
  /** Browser preset to use (default: "chrome-143") */
64
185
  preset?: string;
@@ -82,6 +203,10 @@ export interface SessionOptions {
82
203
  retry?: number;
83
204
  /** Status codes to retry on (default: [429, 500, 502, 503, 504]) */
84
205
  retryOnStatus?: number[];
206
+ /** Minimum wait time between retries in milliseconds (default: 500) */
207
+ retryWaitMin?: number;
208
+ /** Maximum wait time between retries in milliseconds (default: 10000) */
209
+ retryWaitMax?: number;
85
210
  /** Prefer IPv4 addresses over IPv6 (default: false) */
86
211
  preferIpv4?: boolean;
87
212
  /** Default basic auth [username, password] */
@@ -90,6 +215,10 @@ export interface SessionOptions {
90
215
  connectTo?: Record<string, string>;
91
216
  /** Domain to fetch ECH config from (e.g., "cloudflare-ech.com" for any Cloudflare domain) */
92
217
  echConfigDomain?: string;
218
+ /** TLS-only mode: skip preset HTTP headers, only apply TLS fingerprint (default: false) */
219
+ tlsOnly?: boolean;
220
+ /** QUIC idle timeout in seconds (default: 30). Set higher for long-lived HTTP/3 connections. */
221
+ quicIdleTimeout?: number;
93
222
  }
94
223
 
95
224
  export interface RequestOptions {
@@ -178,6 +307,368 @@ export class Session {
178
307
 
179
308
  /** Get cookies as a property */
180
309
  readonly cookies: Record<string, string>;
310
+
311
+ // Proxy management
312
+
313
+ /**
314
+ * Change both TCP and UDP proxies for the session.
315
+ * This closes all existing connections and creates new ones through the new proxy.
316
+ * @param proxyUrl - Proxy URL (e.g., "http://user:pass@host:port", "socks5://host:port"). Empty string for direct.
317
+ */
318
+ setProxy(proxyUrl: string): void;
319
+
320
+ /**
321
+ * Change only the TCP proxy (for HTTP/1.1 and HTTP/2).
322
+ * @param proxyUrl - Proxy URL for TCP traffic
323
+ */
324
+ setTcpProxy(proxyUrl: string): void;
325
+
326
+ /**
327
+ * Change only the UDP proxy (for HTTP/3 via SOCKS5 or MASQUE).
328
+ * @param proxyUrl - Proxy URL for UDP traffic
329
+ */
330
+ setUdpProxy(proxyUrl: string): void;
331
+
332
+ /**
333
+ * Get the current proxy URL.
334
+ * @returns Current proxy URL, or empty string if using direct connection
335
+ */
336
+ getProxy(): string;
337
+
338
+ /**
339
+ * Get the current TCP proxy URL.
340
+ * @returns Current TCP proxy URL, or empty string if using direct connection
341
+ */
342
+ getTcpProxy(): string;
343
+
344
+ /**
345
+ * Get the current UDP proxy URL.
346
+ * @returns Current UDP proxy URL, or empty string if using direct connection
347
+ */
348
+ getUdpProxy(): string;
349
+
350
+ /**
351
+ * Set a custom header order for all requests.
352
+ * @param order - Array of header names in desired order (lowercase). Pass empty array to reset to preset's default.
353
+ * @example
354
+ * session.setHeaderOrder(["accept-language", "sec-ch-ua", "accept", "sec-fetch-site"]);
355
+ */
356
+ setHeaderOrder(order: string[]): void;
357
+
358
+ /**
359
+ * Get the current header order.
360
+ * @returns Array of header names in current order, or preset's default order
361
+ */
362
+ getHeaderOrder(): string[];
363
+
364
+ /**
365
+ * Set a session identifier for TLS cache key isolation.
366
+ * This is used when the session is registered with a LocalProxy to ensure
367
+ * TLS sessions are isolated per proxy/session configuration in distributed caches.
368
+ * @param sessionId - Unique identifier for this session. Pass empty string to clear.
369
+ */
370
+ setSessionIdentifier(sessionId: string): void;
371
+
372
+ /** Get/set the current proxy as a property */
373
+ proxy: string;
374
+
375
+ // ===========================================================================
376
+ // Session Persistence
377
+ // ===========================================================================
378
+
379
+ /**
380
+ * Save session state to a file.
381
+ *
382
+ * This saves cookies, TLS session tickets, and ECH configs.
383
+ * Use Session.load() to restore the session later.
384
+ *
385
+ * @param path - Path to save the session file
386
+ * @throws {HTTPCloakError} If the file cannot be written
387
+ *
388
+ * @example
389
+ * session.save("session.json");
390
+ * // Later...
391
+ * const session = Session.load("session.json");
392
+ */
393
+ save(path: string): void;
394
+
395
+ /**
396
+ * Export session state to a JSON string.
397
+ *
398
+ * Use Session.unmarshal() to restore the session from the string.
399
+ * Useful for storing session state in databases or caches.
400
+ *
401
+ * @returns JSON string containing session state
402
+ * @throws {HTTPCloakError} If marshaling fails
403
+ *
404
+ * @example
405
+ * const sessionData = session.marshal();
406
+ * await redis.set("session:user1", sessionData);
407
+ */
408
+ marshal(): string;
409
+
410
+ /**
411
+ * Load a session from a file.
412
+ *
413
+ * Restores session state including cookies, TLS session tickets, and ECH configs.
414
+ * The session uses the same preset that was used when it was saved.
415
+ *
416
+ * @param path - Path to the session file
417
+ * @returns Restored Session object
418
+ * @throws {HTTPCloakError} If the file cannot be read or is invalid
419
+ *
420
+ * @example
421
+ * const session = Session.load("session.json");
422
+ * const r = await session.get("https://example.com");
423
+ */
424
+ static load(path: string): Session;
425
+
426
+ /**
427
+ * Restore a session from a JSON string.
428
+ *
429
+ * @param data - JSON string containing session state (from marshal())
430
+ * @returns Restored Session object
431
+ * @throws {HTTPCloakError} If the data is invalid
432
+ *
433
+ * @example
434
+ * const sessionData = await redis.get("session:user1");
435
+ * const session = Session.unmarshal(sessionData);
436
+ */
437
+ static unmarshal(data: string): Session;
438
+
439
+ // ===========================================================================
440
+ // Streaming Methods
441
+ // ===========================================================================
442
+
443
+ /**
444
+ * Perform a streaming GET request.
445
+ *
446
+ * Returns a StreamResponse that can be iterated to read chunks.
447
+ * Use this for downloading large files without loading them into memory.
448
+ *
449
+ * @param url - Request URL
450
+ * @param options - Request options
451
+ * @returns StreamResponse for chunked reading
452
+ *
453
+ * @example
454
+ * const stream = session.getStream("https://example.com/large-file.zip");
455
+ * for await (const chunk of stream) {
456
+ * file.write(chunk);
457
+ * }
458
+ * stream.close();
459
+ */
460
+ getStream(url: string, options?: RequestOptions): StreamResponse;
461
+
462
+ /**
463
+ * Perform a streaming POST request.
464
+ *
465
+ * @param url - Request URL
466
+ * @param options - Request options
467
+ * @returns StreamResponse for chunked reading
468
+ */
469
+ postStream(url: string, options?: RequestOptions): StreamResponse;
470
+
471
+ /**
472
+ * Perform a streaming request with any HTTP method.
473
+ *
474
+ * @param method - HTTP method (GET, POST, PUT, etc.)
475
+ * @param url - Request URL
476
+ * @param options - Request options
477
+ * @returns StreamResponse for chunked reading
478
+ */
479
+ requestStream(method: string, url: string, options?: RequestOptions): StreamResponse;
480
+
481
+ // ===========================================================================
482
+ // Fast-path Methods (Zero-copy for maximum performance)
483
+ // ===========================================================================
484
+
485
+ /**
486
+ * Perform a fast GET request with zero-copy buffer transfer.
487
+ *
488
+ * This method bypasses JSON serialization and base64 encoding for the response body,
489
+ * copying data directly from Go's memory to a Node.js Buffer.
490
+ *
491
+ * Use this method for downloading large files when you need maximum throughput.
492
+ * Call response.release() when done to return buffers to the pool.
493
+ *
494
+ * @param url - Request URL
495
+ * @param options - Request options
496
+ * @returns FastResponse with Buffer body
497
+ *
498
+ * @example
499
+ * const response = session.getFast("https://example.com/large-file.zip");
500
+ * fs.writeFileSync("file.zip", response.body);
501
+ * response.release();
502
+ */
503
+ getFast(url: string, options?: RequestOptions): FastResponse;
504
+
505
+ /**
506
+ * Perform a fast POST request with zero-copy buffer transfer.
507
+ *
508
+ * Use this method for uploading large files when you need maximum throughput.
509
+ * Call response.release() when done to return buffers to the pool.
510
+ *
511
+ * @param url - Request URL
512
+ * @param options - Request options (body must be Buffer or string)
513
+ * @returns FastResponse with Buffer body
514
+ *
515
+ * @example
516
+ * const data = fs.readFileSync("large-file.zip");
517
+ * const response = session.postFast("https://example.com/upload", { body: data });
518
+ * console.log(`Uploaded, status: ${response.statusCode}`);
519
+ * response.release();
520
+ */
521
+ postFast(url: string, options?: RequestOptions): FastResponse;
522
+
523
+ /**
524
+ * Perform a fast generic HTTP request with zero-copy buffer transfer.
525
+ *
526
+ * Use this method for any HTTP method when you need maximum throughput.
527
+ * Call response.release() when done to return buffers to the pool.
528
+ *
529
+ * @param method - HTTP method (GET, POST, PUT, DELETE, PATCH, etc.)
530
+ * @param url - Request URL
531
+ * @param options - Request options (body must be Buffer or string)
532
+ * @returns FastResponse with Buffer body
533
+ *
534
+ * @example
535
+ * const response = session.requestFast("PUT", "https://api.example.com/resource", {
536
+ * body: JSON.stringify({ key: "value" }),
537
+ * headers: { "Content-Type": "application/json" }
538
+ * });
539
+ * console.log(`Status: ${response.statusCode}`);
540
+ * response.release();
541
+ */
542
+ requestFast(method: string, url: string, options?: RequestOptions): FastResponse;
543
+
544
+ /**
545
+ * Perform a fast PUT request with zero-copy buffer transfer.
546
+ *
547
+ * @param url - Request URL
548
+ * @param options - Request options (body, headers, params, cookies, auth, timeout)
549
+ * @returns FastResponse with Buffer body
550
+ */
551
+ putFast(url: string, options?: RequestOptions): FastResponse;
552
+
553
+ /**
554
+ * Perform a fast DELETE request with zero-copy buffer transfer.
555
+ *
556
+ * @param url - Request URL
557
+ * @param options - Request options (headers, params, cookies, auth, timeout)
558
+ * @returns FastResponse with Buffer body
559
+ */
560
+ deleteFast(url: string, options?: RequestOptions): FastResponse;
561
+
562
+ /**
563
+ * Perform a fast PATCH request with zero-copy buffer transfer.
564
+ *
565
+ * @param url - Request URL
566
+ * @param options - Request options (body, headers, params, cookies, auth, timeout)
567
+ * @returns FastResponse with Buffer body
568
+ */
569
+ patchFast(url: string, options?: RequestOptions): FastResponse;
570
+ }
571
+
572
+ export interface LocalProxyOptions {
573
+ /** Port to listen on (default: 0 for auto-assign) */
574
+ port?: number;
575
+ /** Browser preset to use (default: "chrome-143") */
576
+ preset?: string;
577
+ /** Request timeout in seconds (default: 30) */
578
+ timeout?: number;
579
+ /** Maximum concurrent connections (default: 1000) */
580
+ maxConnections?: number;
581
+ /** Proxy URL for TCP protocols (HTTP/1.1, HTTP/2) */
582
+ tcpProxy?: string;
583
+ /** Proxy URL for UDP protocols (HTTP/3 via MASQUE) */
584
+ udpProxy?: string;
585
+ /** TLS-only mode: skip preset HTTP headers, only apply TLS fingerprint (default: false) */
586
+ tlsOnly?: boolean;
587
+ }
588
+
589
+ export interface LocalProxyStats {
590
+ /** Total number of requests processed */
591
+ totalRequests: number;
592
+ /** Number of active connections */
593
+ activeConnections: number;
594
+ /** Number of failed requests */
595
+ failedRequests: number;
596
+ /** Bytes sent */
597
+ bytesSent: number;
598
+ /** Bytes received */
599
+ bytesReceived: number;
600
+ }
601
+
602
+ export class LocalProxy {
603
+ /**
604
+ * Create a new LocalProxy instance.
605
+ * The proxy starts automatically when constructed.
606
+ * @param options - LocalProxy configuration options
607
+ */
608
+ constructor(options?: LocalProxyOptions);
609
+
610
+ /** Get the port the proxy is listening on */
611
+ readonly port: number;
612
+
613
+ /** Check if the proxy is currently running */
614
+ readonly isRunning: boolean;
615
+
616
+ /** Get the proxy URL (e.g., "http://localhost:8888") */
617
+ readonly proxyUrl: string;
618
+
619
+ /**
620
+ * Get proxy statistics.
621
+ * @returns Statistics object with request counts, bytes transferred, etc.
622
+ */
623
+ getStats(): LocalProxyStats;
624
+
625
+ /**
626
+ * Register a session with an ID for use with X-HTTPCloak-Session header.
627
+ * This allows per-request session routing through the proxy.
628
+ *
629
+ * When a request is made through the proxy with the `X-HTTPCloak-Session: <sessionId>` header,
630
+ * the proxy will use the registered session for that request, applying its TLS fingerprint
631
+ * and cookies.
632
+ *
633
+ * @param sessionId - Unique identifier for the session
634
+ * @param session - The session to register
635
+ * @throws {HTTPCloakError} If registration fails (e.g., invalid session or proxy not running)
636
+ *
637
+ * @example
638
+ * ```typescript
639
+ * const proxy = new LocalProxy({ port: 8888 });
640
+ * const session = new Session({ preset: 'chrome-143' });
641
+ *
642
+ * // Register session with ID
643
+ * proxy.registerSession('user-1', session);
644
+ *
645
+ * // Now requests with X-HTTPCloak-Session: user-1 header will use this session
646
+ * ```
647
+ */
648
+ registerSession(sessionId: string, session: Session): void;
649
+
650
+ /**
651
+ * Unregister a session by ID.
652
+ * After unregistering, the session ID can no longer be used with X-HTTPCloak-Session header.
653
+ *
654
+ * @param sessionId - The session ID to unregister
655
+ * @returns True if the session was found and unregistered, false otherwise
656
+ *
657
+ * @example
658
+ * ```typescript
659
+ * const wasUnregistered = proxy.unregisterSession('user-1');
660
+ * if (wasUnregistered) {
661
+ * console.log('Session unregistered');
662
+ * }
663
+ * ```
664
+ */
665
+ unregisterSession(sessionId: string): boolean;
666
+
667
+ /**
668
+ * Stop and close the proxy.
669
+ * After closing, the LocalProxy instance cannot be reused.
670
+ */
671
+ close(): void;
181
672
  }
182
673
 
183
674
  /** Get the httpcloak library version */
@@ -186,6 +677,24 @@ export function version(): string;
186
677
  /** Get list of available browser presets */
187
678
  export function availablePresets(): string[];
188
679
 
680
+ /**
681
+ * Configure the DNS servers used for ECH (Encrypted Client Hello) config queries.
682
+ *
683
+ * By default, ECH queries use Google (8.8.8.8), Cloudflare (1.1.1.1), and Quad9 (9.9.9.9).
684
+ * This is a global setting that affects all sessions.
685
+ *
686
+ * @param servers - Array of DNS server addresses in "host:port" format. Pass null or empty array to reset to defaults.
687
+ * @throws {HTTPCloakError} If the servers list is invalid.
688
+ */
689
+ export function setEchDnsServers(servers: string[] | null): void;
690
+
691
+ /**
692
+ * Get the current DNS servers used for ECH (Encrypted Client Hello) config queries.
693
+ *
694
+ * @returns Array of DNS server addresses in "host:port" format.
695
+ */
696
+ export function getEchDnsServers(): string[];
697
+
189
698
  export interface ConfigureOptions extends SessionOptions {
190
699
  /** Default headers for all requests */
191
700
  headers?: Record<string, string>;
@@ -228,6 +737,8 @@ export const Preset: {
228
737
  CHROME_143_WINDOWS: string;
229
738
  CHROME_143_LINUX: string;
230
739
  CHROME_143_MACOS: string;
740
+ CHROME_141: string;
741
+ CHROME_133: string;
231
742
  CHROME_131: string;
232
743
  CHROME_131_WINDOWS: string;
233
744
  CHROME_131_LINUX: string;
@@ -239,3 +750,90 @@ export const Preset: {
239
750
  IOS_SAFARI_17: string;
240
751
  all(): string[];
241
752
  };
753
+
754
+ // ============================================================================
755
+ // Distributed Session Cache
756
+ // ============================================================================
757
+
758
+ export interface SessionCacheOptions {
759
+ /**
760
+ * Function to get session data from cache.
761
+ * Returns JSON string with session data, or null if not found.
762
+ * Note: Must be synchronous - async callbacks are not supported.
763
+ */
764
+ get?: (key: string) => string | null;
765
+
766
+ /**
767
+ * Function to store session data in cache.
768
+ * Returns 0 on success, non-zero on error.
769
+ * Note: Must be synchronous - async callbacks are not supported.
770
+ */
771
+ put?: (key: string, value: string, ttlSeconds: number) => number;
772
+
773
+ /**
774
+ * Function to delete session data from cache.
775
+ * Returns 0 on success, non-zero on error.
776
+ * Note: Must be synchronous - async callbacks are not supported.
777
+ */
778
+ delete?: (key: string) => number;
779
+
780
+ /**
781
+ * Function to get ECH config from cache.
782
+ * Returns base64-encoded config, or null if not found.
783
+ * Note: Must be synchronous - async callbacks are not supported.
784
+ */
785
+ getEch?: (key: string) => string | null;
786
+
787
+ /**
788
+ * Function to store ECH config in cache.
789
+ * Returns 0 on success, non-zero on error.
790
+ * Note: Must be synchronous - async callbacks are not supported.
791
+ */
792
+ putEch?: (key: string, value: string, ttlSeconds: number) => number;
793
+
794
+ /**
795
+ * Error callback for cache operations.
796
+ */
797
+ onError?: (operation: string, key: string, error: string) => void;
798
+ }
799
+
800
+ /**
801
+ * Distributed TLS session cache backend for sharing sessions across instances.
802
+ *
803
+ * Enables TLS session resumption across distributed httpcloak instances
804
+ * by storing session tickets in an external cache like Redis or Memcached.
805
+ *
806
+ * Cache key formats:
807
+ * - TLS sessions: httpcloak:sessions:{preset}:{protocol}:{host}:{port}
808
+ * - ECH configs: httpcloak:ech:{preset}:{host}:{port}
809
+ */
810
+ export class SessionCacheBackend {
811
+ constructor(options?: SessionCacheOptions);
812
+
813
+ /**
814
+ * Register this cache backend globally.
815
+ * After registration, all new Session and LocalProxy instances will use
816
+ * this cache for TLS session storage.
817
+ */
818
+ register(): void;
819
+
820
+ /**
821
+ * Unregister this cache backend.
822
+ * After unregistration, new sessions will not use distributed caching.
823
+ */
824
+ unregister(): void;
825
+ }
826
+
827
+ /**
828
+ * Configure a distributed session cache backend.
829
+ *
830
+ * @param options Cache configuration
831
+ * @returns The registered SessionCacheBackend instance
832
+ */
833
+ export function configureSessionCache(options: SessionCacheOptions): SessionCacheBackend;
834
+
835
+ /**
836
+ * Clear the distributed session cache backend.
837
+ * After calling this, new sessions will not use distributed caching.
838
+ */
839
+ export function clearSessionCache(): void;