httpcloak 1.0.0 → 1.0.1

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.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * HTTPCloak Node.js Client
3
3
  *
4
- * Provides HTTP client with browser fingerprint emulation.
4
+ * A fetch/axios-compatible HTTP client with browser fingerprint emulation.
5
+ * Provides TLS fingerprinting for HTTP requests.
5
6
  */
6
7
 
7
8
  const koffi = require("koffi");
@@ -26,17 +27,51 @@ class Response {
26
27
  constructor(data) {
27
28
  this.statusCode = data.status_code || 0;
28
29
  this.headers = data.headers || {};
29
- this.body = Buffer.from(data.body || "", "utf8");
30
- this.text = data.body || "";
30
+ this._body = Buffer.from(data.body || "", "utf8");
31
+ this._text = data.body || "";
31
32
  this.finalUrl = data.final_url || "";
32
33
  this.protocol = data.protocol || "";
33
34
  }
34
35
 
36
+ /** Response body as string */
37
+ get text() {
38
+ return this._text;
39
+ }
40
+
41
+ /** Response body as Buffer (requests compatibility) */
42
+ get body() {
43
+ return this._body;
44
+ }
45
+
46
+ /** Response body as Buffer (requests compatibility alias) */
47
+ get content() {
48
+ return this._body;
49
+ }
50
+
51
+ /** Final URL after redirects (requests compatibility alias) */
52
+ get url() {
53
+ return this.finalUrl;
54
+ }
55
+
56
+ /** True if status code < 400 (requests compatibility) */
57
+ get ok() {
58
+ return this.statusCode < 400;
59
+ }
60
+
35
61
  /**
36
62
  * Parse response body as JSON
37
63
  */
38
64
  json() {
39
- return JSON.parse(this.text);
65
+ return JSON.parse(this._text);
66
+ }
67
+
68
+ /**
69
+ * Raise error if status >= 400 (requests compatibility)
70
+ */
71
+ raiseForStatus() {
72
+ if (!this.ok) {
73
+ throw new HTTPCloakError(`HTTP ${this.statusCode}`);
74
+ }
40
75
  }
41
76
  }
42
77
 
@@ -47,7 +82,6 @@ function getPlatformPackageName() {
47
82
  const platform = os.platform();
48
83
  const arch = os.arch();
49
84
 
50
- // Map to npm platform names
51
85
  let platName;
52
86
  if (platform === "darwin") {
53
87
  platName = "darwin";
@@ -76,13 +110,11 @@ function getLibPath() {
76
110
  const platform = os.platform();
77
111
  const arch = os.arch();
78
112
 
79
- // Check environment variable first
80
113
  const envPath = process.env.HTTPCLOAK_LIB_PATH;
81
114
  if (envPath && fs.existsSync(envPath)) {
82
115
  return envPath;
83
116
  }
84
117
 
85
- // Try to load from platform-specific optional dependency
86
118
  const packageName = getPlatformPackageName();
87
119
  try {
88
120
  const libPath = require(packageName);
@@ -90,10 +122,9 @@ function getLibPath() {
90
122
  return libPath;
91
123
  }
92
124
  } catch (e) {
93
- // Optional dependency not installed, fall back to local search
125
+ // Optional dependency not installed
94
126
  }
95
127
 
96
- // Normalize architecture for library name
97
128
  let archName;
98
129
  if (arch === "x64" || arch === "amd64") {
99
130
  archName = "amd64";
@@ -103,7 +134,6 @@ function getLibPath() {
103
134
  archName = arch;
104
135
  }
105
136
 
106
- // Determine OS name and extension
107
137
  let osName, ext;
108
138
  if (platform === "darwin") {
109
139
  osName = "darwin";
@@ -118,7 +148,6 @@ function getLibPath() {
118
148
 
119
149
  const libName = `libhttpcloak-${osName}-${archName}${ext}`;
120
150
 
121
- // Search paths (fallback for local development)
122
151
  const searchPaths = [
123
152
  path.join(__dirname, libName),
124
153
  path.join(__dirname, "..", libName),
@@ -178,6 +207,139 @@ function parseResponse(result) {
178
207
  return new Response(data);
179
208
  }
180
209
 
210
+ /**
211
+ * Add query parameters to URL
212
+ */
213
+ function addParamsToUrl(url, params) {
214
+ if (!params || Object.keys(params).length === 0) {
215
+ return url;
216
+ }
217
+
218
+ const urlObj = new URL(url);
219
+ for (const [key, value] of Object.entries(params)) {
220
+ urlObj.searchParams.append(key, String(value));
221
+ }
222
+ return urlObj.toString();
223
+ }
224
+
225
+ /**
226
+ * Apply basic auth to headers
227
+ */
228
+ function applyAuth(headers, auth) {
229
+ if (!auth) {
230
+ return headers;
231
+ }
232
+
233
+ const [username, password] = auth;
234
+ const credentials = Buffer.from(`${username}:${password}`).toString("base64");
235
+
236
+ headers = headers ? { ...headers } : {};
237
+ headers["Authorization"] = `Basic ${credentials}`;
238
+ return headers;
239
+ }
240
+
241
+ /**
242
+ * Detect MIME type from filename
243
+ */
244
+ function detectMimeType(filename) {
245
+ const ext = path.extname(filename).toLowerCase();
246
+ const mimeTypes = {
247
+ ".html": "text/html",
248
+ ".htm": "text/html",
249
+ ".css": "text/css",
250
+ ".js": "application/javascript",
251
+ ".json": "application/json",
252
+ ".xml": "application/xml",
253
+ ".txt": "text/plain",
254
+ ".csv": "text/csv",
255
+ ".jpg": "image/jpeg",
256
+ ".jpeg": "image/jpeg",
257
+ ".png": "image/png",
258
+ ".gif": "image/gif",
259
+ ".webp": "image/webp",
260
+ ".svg": "image/svg+xml",
261
+ ".ico": "image/x-icon",
262
+ ".bmp": "image/bmp",
263
+ ".mp3": "audio/mpeg",
264
+ ".wav": "audio/wav",
265
+ ".ogg": "audio/ogg",
266
+ ".mp4": "video/mp4",
267
+ ".webm": "video/webm",
268
+ ".pdf": "application/pdf",
269
+ ".zip": "application/zip",
270
+ ".gz": "application/gzip",
271
+ ".tar": "application/x-tar",
272
+ };
273
+ return mimeTypes[ext] || "application/octet-stream";
274
+ }
275
+
276
+ /**
277
+ * Encode multipart form data
278
+ * @param {Object} data - Form fields (key-value pairs)
279
+ * @param {Object} files - Files to upload
280
+ * Each key is the field name, value can be:
281
+ * - Buffer: raw file content
282
+ * - { filename, content, contentType? }: file with metadata
283
+ * @returns {{ body: Buffer, contentType: string }}
284
+ */
285
+ function encodeMultipart(data, files) {
286
+ const boundary = `----HTTPCloakBoundary${Date.now().toString(16)}${Math.random().toString(16).slice(2)}`;
287
+ const parts = [];
288
+
289
+ // Add form fields
290
+ if (data) {
291
+ for (const [key, value] of Object.entries(data)) {
292
+ parts.push(
293
+ `--${boundary}\r\n` +
294
+ `Content-Disposition: form-data; name="${key}"\r\n\r\n` +
295
+ `${value}\r\n`
296
+ );
297
+ }
298
+ }
299
+
300
+ // Add files
301
+ if (files) {
302
+ for (const [fieldName, fileValue] of Object.entries(files)) {
303
+ let filename, content, contentType;
304
+
305
+ if (Buffer.isBuffer(fileValue)) {
306
+ filename = fieldName;
307
+ content = fileValue;
308
+ contentType = "application/octet-stream";
309
+ } else if (typeof fileValue === "object" && fileValue !== null) {
310
+ filename = fileValue.filename || fieldName;
311
+ content = fileValue.content;
312
+ contentType = fileValue.contentType || detectMimeType(filename);
313
+
314
+ if (!Buffer.isBuffer(content)) {
315
+ content = Buffer.from(content);
316
+ }
317
+ } else {
318
+ throw new HTTPCloakError(`Invalid file value for field '${fieldName}'`);
319
+ }
320
+
321
+ parts.push(Buffer.from(
322
+ `--${boundary}\r\n` +
323
+ `Content-Disposition: form-data; name="${fieldName}"; filename="${filename}"\r\n` +
324
+ `Content-Type: ${contentType}\r\n\r\n`
325
+ ));
326
+ parts.push(content);
327
+ parts.push(Buffer.from("\r\n"));
328
+ }
329
+ }
330
+
331
+ parts.push(Buffer.from(`--${boundary}--\r\n`));
332
+
333
+ // Combine all parts
334
+ const bodyParts = parts.map(p => Buffer.isBuffer(p) ? p : Buffer.from(p));
335
+ const body = Buffer.concat(bodyParts);
336
+
337
+ return {
338
+ body,
339
+ contentType: `multipart/form-data; boundary=${boundary}`,
340
+ };
341
+ }
342
+
181
343
  /**
182
344
  * Get the httpcloak library version
183
345
  */
@@ -208,19 +370,51 @@ class Session {
208
370
  * @param {string} [options.preset="chrome-143"] - Browser preset to use
209
371
  * @param {string} [options.proxy] - Proxy URL (e.g., "http://user:pass@host:port")
210
372
  * @param {number} [options.timeout=30] - Request timeout in seconds
373
+ * @param {string} [options.httpVersion="auto"] - HTTP version: "auto", "h1", "h2", "h3"
374
+ * @param {boolean} [options.verify=true] - SSL certificate verification
375
+ * @param {boolean} [options.allowRedirects=true] - Follow redirects
376
+ * @param {number} [options.maxRedirects=10] - Maximum number of redirects to follow
377
+ * @param {number} [options.retry=0] - Number of retries on failure
378
+ * @param {number[]} [options.retryOnStatus] - Status codes to retry on
211
379
  */
212
380
  constructor(options = {}) {
213
- const { preset = "chrome-143", proxy = null, timeout = 30 } = options;
381
+ const {
382
+ preset = "chrome-143",
383
+ proxy = null,
384
+ timeout = 30,
385
+ httpVersion = "auto",
386
+ verify = true,
387
+ allowRedirects = true,
388
+ maxRedirects = 10,
389
+ retry = 0,
390
+ retryOnStatus = null,
391
+ } = options;
214
392
 
215
393
  this._lib = getLib();
394
+ this.headers = {}; // Default headers
216
395
 
217
396
  const config = {
218
397
  preset,
219
398
  timeout,
399
+ http_version: httpVersion,
220
400
  };
221
401
  if (proxy) {
222
402
  config.proxy = proxy;
223
403
  }
404
+ if (!verify) {
405
+ config.verify = false;
406
+ }
407
+ if (!allowRedirects) {
408
+ config.allow_redirects = false;
409
+ } else if (maxRedirects !== 10) {
410
+ config.max_redirects = maxRedirects;
411
+ }
412
+ if (retry > 0) {
413
+ config.retry = retry;
414
+ if (retryOnStatus) {
415
+ config.retry_on_status = retryOnStatus;
416
+ }
417
+ }
224
418
 
225
419
  this._handle = this._lib.httpcloak_session_new(JSON.stringify(config));
226
420
 
@@ -239,6 +433,16 @@ class Session {
239
433
  }
240
434
  }
241
435
 
436
+ /**
437
+ * Merge session headers with request headers
438
+ */
439
+ _mergeHeaders(headers) {
440
+ if (!this.headers || Object.keys(this.headers).length === 0) {
441
+ return headers;
442
+ }
443
+ return { ...this.headers, ...headers };
444
+ }
445
+
242
446
  // ===========================================================================
243
447
  // Synchronous Methods
244
448
  // ===========================================================================
@@ -246,11 +450,20 @@ class Session {
246
450
  /**
247
451
  * Perform a synchronous GET request
248
452
  * @param {string} url - Request URL
249
- * @param {Object} [headers] - Optional custom headers
453
+ * @param {Object} [options] - Request options
454
+ * @param {Object} [options.headers] - Custom headers
455
+ * @param {Object} [options.params] - Query parameters
456
+ * @param {Array} [options.auth] - Basic auth [username, password]
250
457
  * @returns {Response} Response object
251
458
  */
252
- getSync(url, headers = null) {
253
- const headersJson = headers ? JSON.stringify(headers) : null;
459
+ getSync(url, options = {}) {
460
+ const { headers = null, params = null, auth = null } = options;
461
+
462
+ url = addParamsToUrl(url, params);
463
+ let mergedHeaders = this._mergeHeaders(headers);
464
+ mergedHeaders = applyAuth(mergedHeaders, auth);
465
+
466
+ const headersJson = mergedHeaders ? JSON.stringify(mergedHeaders) : null;
254
467
  const result = this._lib.httpcloak_get(this._handle, url, headersJson);
255
468
  return parseResponse(result);
256
469
  }
@@ -258,58 +471,111 @@ class Session {
258
471
  /**
259
472
  * Perform a synchronous POST request
260
473
  * @param {string} url - Request URL
261
- * @param {string|Buffer|Object} [body] - Request body
262
- * @param {Object} [headers] - Optional custom headers
474
+ * @param {Object} [options] - Request options
475
+ * @param {string|Buffer|Object} [options.body] - Request body
476
+ * @param {Object} [options.json] - JSON body (will be serialized)
477
+ * @param {Object} [options.data] - Form data (will be URL encoded)
478
+ * @param {Object} [options.files] - Files to upload as multipart/form-data
479
+ * Each key is the field name, value can be:
480
+ * - Buffer: raw file content
481
+ * - { filename, content, contentType? }: file with metadata
482
+ * @param {Object} [options.headers] - Custom headers
483
+ * @param {Object} [options.params] - Query parameters
484
+ * @param {Array} [options.auth] - Basic auth [username, password]
263
485
  * @returns {Response} Response object
264
486
  */
265
- postSync(url, body = null, headers = null) {
266
- if (typeof body === "object" && body !== null && !Buffer.isBuffer(body)) {
267
- body = JSON.stringify(body);
268
- headers = headers || {};
269
- if (!headers["Content-Type"]) {
270
- headers["Content-Type"] = "application/json";
487
+ postSync(url, options = {}) {
488
+ let { body = null, json = null, data = null, files = null, headers = null, params = null, auth = null } = options;
489
+
490
+ url = addParamsToUrl(url, params);
491
+ let mergedHeaders = this._mergeHeaders(headers);
492
+
493
+ // Handle multipart file upload
494
+ if (files !== null) {
495
+ const formData = (data !== null && typeof data === "object") ? data : null;
496
+ const multipart = encodeMultipart(formData, files);
497
+ body = multipart.body.toString("latin1"); // Preserve binary data
498
+ mergedHeaders = mergedHeaders || {};
499
+ mergedHeaders["Content-Type"] = multipart.contentType;
500
+ }
501
+ // Handle JSON body
502
+ else if (json !== null) {
503
+ body = JSON.stringify(json);
504
+ mergedHeaders = mergedHeaders || {};
505
+ if (!mergedHeaders["Content-Type"]) {
506
+ mergedHeaders["Content-Type"] = "application/json";
271
507
  }
272
508
  }
273
-
274
- if (Buffer.isBuffer(body)) {
509
+ // Handle form data
510
+ else if (data !== null && typeof data === "object") {
511
+ body = new URLSearchParams(data).toString();
512
+ mergedHeaders = mergedHeaders || {};
513
+ if (!mergedHeaders["Content-Type"]) {
514
+ mergedHeaders["Content-Type"] = "application/x-www-form-urlencoded";
515
+ }
516
+ }
517
+ // Handle Buffer body
518
+ else if (Buffer.isBuffer(body)) {
275
519
  body = body.toString("utf8");
276
520
  }
277
521
 
278
- const headersJson = headers ? JSON.stringify(headers) : null;
522
+ mergedHeaders = applyAuth(mergedHeaders, auth);
523
+
524
+ const headersJson = mergedHeaders ? JSON.stringify(mergedHeaders) : null;
279
525
  const result = this._lib.httpcloak_post(this._handle, url, body, headersJson);
280
526
  return parseResponse(result);
281
527
  }
282
528
 
283
529
  /**
284
530
  * Perform a synchronous custom HTTP request
285
- * @param {Object} options - Request options
286
- * @param {string} options.method - HTTP method
287
- * @param {string} options.url - Request URL
288
- * @param {Object} [options.headers] - Optional custom headers
289
- * @param {string|Buffer|Object} [options.body] - Optional request body
290
- * @param {number} [options.timeout] - Optional request timeout
531
+ * @param {string} method - HTTP method
532
+ * @param {string} url - Request URL
533
+ * @param {Object} [options] - Request options
534
+ * @param {Object} [options.files] - Files to upload as multipart/form-data
291
535
  * @returns {Response} Response object
292
536
  */
293
- requestSync(options) {
294
- let { method, url, headers = null, body = null, timeout = null } = options;
295
-
296
- if (typeof body === "object" && body !== null && !Buffer.isBuffer(body)) {
297
- body = JSON.stringify(body);
298
- headers = headers || {};
299
- if (!headers["Content-Type"]) {
300
- headers["Content-Type"] = "application/json";
537
+ requestSync(method, url, options = {}) {
538
+ let { body = null, json = null, data = null, files = null, headers = null, params = null, auth = null, timeout = null } = options;
539
+
540
+ url = addParamsToUrl(url, params);
541
+ let mergedHeaders = this._mergeHeaders(headers);
542
+
543
+ // Handle multipart file upload
544
+ if (files !== null) {
545
+ const formData = (data !== null && typeof data === "object") ? data : null;
546
+ const multipart = encodeMultipart(formData, files);
547
+ body = multipart.body.toString("latin1"); // Preserve binary data
548
+ mergedHeaders = mergedHeaders || {};
549
+ mergedHeaders["Content-Type"] = multipart.contentType;
550
+ }
551
+ // Handle JSON body
552
+ else if (json !== null) {
553
+ body = JSON.stringify(json);
554
+ mergedHeaders = mergedHeaders || {};
555
+ if (!mergedHeaders["Content-Type"]) {
556
+ mergedHeaders["Content-Type"] = "application/json";
301
557
  }
302
558
  }
303
-
304
- if (Buffer.isBuffer(body)) {
559
+ // Handle form data
560
+ else if (data !== null && typeof data === "object") {
561
+ body = new URLSearchParams(data).toString();
562
+ mergedHeaders = mergedHeaders || {};
563
+ if (!mergedHeaders["Content-Type"]) {
564
+ mergedHeaders["Content-Type"] = "application/x-www-form-urlencoded";
565
+ }
566
+ }
567
+ // Handle Buffer body
568
+ else if (Buffer.isBuffer(body)) {
305
569
  body = body.toString("utf8");
306
570
  }
307
571
 
572
+ mergedHeaders = applyAuth(mergedHeaders, auth);
573
+
308
574
  const requestConfig = {
309
575
  method: method.toUpperCase(),
310
576
  url,
311
577
  };
312
- if (headers) requestConfig.headers = headers;
578
+ if (mergedHeaders) requestConfig.headers = mergedHeaders;
313
579
  if (body) requestConfig.body = body;
314
580
  if (timeout) requestConfig.timeout = timeout;
315
581
 
@@ -327,14 +593,14 @@ class Session {
327
593
  /**
328
594
  * Perform an async GET request
329
595
  * @param {string} url - Request URL
330
- * @param {Object} [headers] - Optional custom headers
596
+ * @param {Object} [options] - Request options
331
597
  * @returns {Promise<Response>} Response object
332
598
  */
333
- get(url, headers = null) {
599
+ get(url, options = {}) {
334
600
  return new Promise((resolve, reject) => {
335
601
  setImmediate(() => {
336
602
  try {
337
- resolve(this.getSync(url, headers));
603
+ resolve(this.getSync(url, options));
338
604
  } catch (err) {
339
605
  reject(err);
340
606
  }
@@ -345,15 +611,14 @@ class Session {
345
611
  /**
346
612
  * Perform an async POST request
347
613
  * @param {string} url - Request URL
348
- * @param {string|Buffer|Object} [body] - Request body
349
- * @param {Object} [headers] - Optional custom headers
614
+ * @param {Object} [options] - Request options
350
615
  * @returns {Promise<Response>} Response object
351
616
  */
352
- post(url, body = null, headers = null) {
617
+ post(url, options = {}) {
353
618
  return new Promise((resolve, reject) => {
354
619
  setImmediate(() => {
355
620
  try {
356
- resolve(this.postSync(url, body, headers));
621
+ resolve(this.postSync(url, options));
357
622
  } catch (err) {
358
623
  reject(err);
359
624
  }
@@ -363,14 +628,16 @@ class Session {
363
628
 
364
629
  /**
365
630
  * Perform an async custom HTTP request
366
- * @param {Object} options - Request options
631
+ * @param {string} method - HTTP method
632
+ * @param {string} url - Request URL
633
+ * @param {Object} [options] - Request options
367
634
  * @returns {Promise<Response>} Response object
368
635
  */
369
- request(options) {
636
+ request(method, url, options = {}) {
370
637
  return new Promise((resolve, reject) => {
371
638
  setImmediate(() => {
372
639
  try {
373
- resolve(this.requestSync(options));
640
+ resolve(this.requestSync(method, url, options));
374
641
  } catch (err) {
375
642
  reject(err);
376
643
  }
@@ -380,133 +647,37 @@ class Session {
380
647
 
381
648
  /**
382
649
  * Perform an async PUT request
383
- * @param {string} url - Request URL
384
- * @param {string|Buffer|Object} [body] - Request body
385
- * @param {Object} [headers] - Optional custom headers
386
- * @returns {Promise<Response>} Response object
387
650
  */
388
- put(url, body = null, headers = null) {
389
- return this.request({ method: "PUT", url, body, headers });
651
+ put(url, options = {}) {
652
+ return this.request("PUT", url, options);
390
653
  }
391
654
 
392
655
  /**
393
656
  * Perform an async DELETE request
394
- * @param {string} url - Request URL
395
- * @param {Object} [headers] - Optional custom headers
396
- * @returns {Promise<Response>} Response object
397
657
  */
398
- delete(url, headers = null) {
399
- return this.request({ method: "DELETE", url, headers });
658
+ delete(url, options = {}) {
659
+ return this.request("DELETE", url, options);
400
660
  }
401
661
 
402
662
  /**
403
663
  * Perform an async PATCH request
404
- * @param {string} url - Request URL
405
- * @param {string|Buffer|Object} [body] - Request body
406
- * @param {Object} [headers] - Optional custom headers
407
- * @returns {Promise<Response>} Response object
408
664
  */
409
- patch(url, body = null, headers = null) {
410
- return this.request({ method: "PATCH", url, body, headers });
665
+ patch(url, options = {}) {
666
+ return this.request("PATCH", url, options);
411
667
  }
412
668
 
413
669
  /**
414
670
  * Perform an async HEAD request
415
- * @param {string} url - Request URL
416
- * @param {Object} [headers] - Optional custom headers
417
- * @returns {Promise<Response>} Response object
418
671
  */
419
- head(url, headers = null) {
420
- return this.request({ method: "HEAD", url, headers });
672
+ head(url, options = {}) {
673
+ return this.request("HEAD", url, options);
421
674
  }
422
675
 
423
676
  /**
424
677
  * Perform an async OPTIONS request
425
- * @param {string} url - Request URL
426
- * @param {Object} [headers] - Optional custom headers
427
- * @returns {Promise<Response>} Response object
428
- */
429
- options(url, headers = null) {
430
- return this.request({ method: "OPTIONS", url, headers });
431
- }
432
-
433
- // ===========================================================================
434
- // Callback-based Methods
435
- // ===========================================================================
436
-
437
- /**
438
- * Perform a GET request with callback
439
- * @param {string} url - Request URL
440
- * @param {Object|Function} [headersOrCallback] - Headers or callback
441
- * @param {Function} [callback] - Callback function (err, response)
442
- */
443
- getCb(url, headersOrCallback, callback) {
444
- let headers = null;
445
- let cb = callback;
446
-
447
- if (typeof headersOrCallback === "function") {
448
- cb = headersOrCallback;
449
- } else {
450
- headers = headersOrCallback;
451
- }
452
-
453
- setImmediate(() => {
454
- try {
455
- const response = this.getSync(url, headers);
456
- cb(null, response);
457
- } catch (err) {
458
- cb(err, null);
459
- }
460
- });
461
- }
462
-
463
- /**
464
- * Perform a POST request with callback
465
- * @param {string} url - Request URL
466
- * @param {string|Buffer|Object} [body] - Request body
467
- * @param {Object|Function} [headersOrCallback] - Headers or callback
468
- * @param {Function} [callback] - Callback function (err, response)
469
678
  */
470
- postCb(url, body, headersOrCallback, callback) {
471
- let headers = null;
472
- let cb = callback;
473
-
474
- if (typeof headersOrCallback === "function") {
475
- cb = headersOrCallback;
476
- } else {
477
- headers = headersOrCallback;
478
- cb = callback;
479
- }
480
-
481
- if (typeof body === "function") {
482
- cb = body;
483
- body = null;
484
- }
485
-
486
- setImmediate(() => {
487
- try {
488
- const response = this.postSync(url, body, headers);
489
- cb(null, response);
490
- } catch (err) {
491
- cb(err, null);
492
- }
493
- });
494
- }
495
-
496
- /**
497
- * Perform a custom request with callback
498
- * @param {Object} options - Request options
499
- * @param {Function} callback - Callback function (err, response)
500
- */
501
- requestCb(options, callback) {
502
- setImmediate(() => {
503
- try {
504
- const response = this.requestSync(options);
505
- callback(null, response);
506
- } catch (err) {
507
- callback(err, null);
508
- }
509
- });
679
+ options(url, options = {}) {
680
+ return this.request("OPTIONS", url, options);
510
681
  }
511
682
 
512
683
  // ===========================================================================
@@ -542,10 +713,196 @@ class Session {
542
713
  }
543
714
  }
544
715
 
716
+ // =============================================================================
717
+ // Module-level convenience functions
718
+ // =============================================================================
719
+
720
+ let _defaultSession = null;
721
+ let _defaultConfig = {};
722
+
723
+ /**
724
+ * Configure defaults for module-level functions
725
+ * @param {Object} options - Configuration options
726
+ * @param {string} [options.preset="chrome-143"] - Browser preset
727
+ * @param {Object} [options.headers] - Default headers
728
+ * @param {Array} [options.auth] - Default basic auth [username, password]
729
+ * @param {string} [options.proxy] - Proxy URL
730
+ * @param {number} [options.timeout=30] - Default timeout in seconds
731
+ * @param {string} [options.httpVersion="auto"] - HTTP version: "auto", "h1", "h2", "h3"
732
+ * @param {boolean} [options.verify=true] - SSL certificate verification
733
+ * @param {boolean} [options.allowRedirects=true] - Follow redirects
734
+ * @param {number} [options.maxRedirects=10] - Maximum number of redirects to follow
735
+ * @param {number} [options.retry=0] - Number of retries on failure
736
+ * @param {number[]} [options.retryOnStatus] - Status codes to retry on
737
+ */
738
+ function configure(options = {}) {
739
+ const {
740
+ preset = "chrome-143",
741
+ headers = null,
742
+ auth = null,
743
+ proxy = null,
744
+ timeout = 30,
745
+ httpVersion = "auto",
746
+ verify = true,
747
+ allowRedirects = true,
748
+ maxRedirects = 10,
749
+ retry = 0,
750
+ retryOnStatus = null,
751
+ } = options;
752
+
753
+ // Close existing session
754
+ if (_defaultSession) {
755
+ _defaultSession.close();
756
+ _defaultSession = null;
757
+ }
758
+
759
+ // Apply auth to headers
760
+ let finalHeaders = applyAuth(headers, auth) || {};
761
+
762
+ // Store config
763
+ _defaultConfig = {
764
+ preset,
765
+ proxy,
766
+ timeout,
767
+ httpVersion,
768
+ verify,
769
+ allowRedirects,
770
+ maxRedirects,
771
+ retry,
772
+ retryOnStatus,
773
+ headers: finalHeaders,
774
+ };
775
+
776
+ // Create new session
777
+ _defaultSession = new Session({
778
+ preset,
779
+ proxy,
780
+ timeout,
781
+ httpVersion,
782
+ verify,
783
+ allowRedirects,
784
+ maxRedirects,
785
+ retry,
786
+ retryOnStatus,
787
+ });
788
+ if (Object.keys(finalHeaders).length > 0) {
789
+ Object.assign(_defaultSession.headers, finalHeaders);
790
+ }
791
+ }
792
+
793
+ /**
794
+ * Get or create the default session
795
+ */
796
+ function _getDefaultSession() {
797
+ if (!_defaultSession) {
798
+ const preset = _defaultConfig.preset || "chrome-143";
799
+ const proxy = _defaultConfig.proxy || null;
800
+ const timeout = _defaultConfig.timeout || 30;
801
+ const httpVersion = _defaultConfig.httpVersion || "auto";
802
+ const verify = _defaultConfig.verify !== undefined ? _defaultConfig.verify : true;
803
+ const allowRedirects = _defaultConfig.allowRedirects !== undefined ? _defaultConfig.allowRedirects : true;
804
+ const maxRedirects = _defaultConfig.maxRedirects || 10;
805
+ const retry = _defaultConfig.retry || 0;
806
+ const retryOnStatus = _defaultConfig.retryOnStatus || null;
807
+ const headers = _defaultConfig.headers || {};
808
+
809
+ _defaultSession = new Session({
810
+ preset,
811
+ proxy,
812
+ timeout,
813
+ httpVersion,
814
+ verify,
815
+ allowRedirects,
816
+ maxRedirects,
817
+ retry,
818
+ retryOnStatus,
819
+ });
820
+ if (Object.keys(headers).length > 0) {
821
+ Object.assign(_defaultSession.headers, headers);
822
+ }
823
+ }
824
+ return _defaultSession;
825
+ }
826
+
827
+ /**
828
+ * Perform a GET request
829
+ * @param {string} url - Request URL
830
+ * @param {Object} [options] - Request options
831
+ * @returns {Promise<Response>}
832
+ */
833
+ function get(url, options = {}) {
834
+ return _getDefaultSession().get(url, options);
835
+ }
836
+
837
+ /**
838
+ * Perform a POST request
839
+ * @param {string} url - Request URL
840
+ * @param {Object} [options] - Request options
841
+ * @returns {Promise<Response>}
842
+ */
843
+ function post(url, options = {}) {
844
+ return _getDefaultSession().post(url, options);
845
+ }
846
+
847
+ /**
848
+ * Perform a PUT request
849
+ */
850
+ function put(url, options = {}) {
851
+ return _getDefaultSession().put(url, options);
852
+ }
853
+
854
+ /**
855
+ * Perform a DELETE request
856
+ */
857
+ function del(url, options = {}) {
858
+ return _getDefaultSession().delete(url, options);
859
+ }
860
+
861
+ /**
862
+ * Perform a PATCH request
863
+ */
864
+ function patch(url, options = {}) {
865
+ return _getDefaultSession().patch(url, options);
866
+ }
867
+
868
+ /**
869
+ * Perform a HEAD request
870
+ */
871
+ function head(url, options = {}) {
872
+ return _getDefaultSession().head(url, options);
873
+ }
874
+
875
+ /**
876
+ * Perform an OPTIONS request
877
+ */
878
+ function options(url, opts = {}) {
879
+ return _getDefaultSession().options(url, opts);
880
+ }
881
+
882
+ /**
883
+ * Perform a custom HTTP request
884
+ */
885
+ function request(method, url, options = {}) {
886
+ return _getDefaultSession().request(method, url, options);
887
+ }
888
+
545
889
  module.exports = {
890
+ // Classes
546
891
  Session,
547
892
  Response,
548
893
  HTTPCloakError,
894
+ // Configuration
895
+ configure,
896
+ // Module-level functions
897
+ get,
898
+ post,
899
+ put,
900
+ delete: del,
901
+ patch,
902
+ head,
903
+ options,
904
+ request,
905
+ // Utility
549
906
  version,
550
907
  availablePresets,
551
908
  };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@httpcloak/darwin-arm64",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
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.0.0",
3
+ "version": "1.0.1",
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.0.0",
3
+ "version": "1.0.1",
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.0.0",
3
+ "version": "1.0.1",
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.0.0",
3
+ "version": "1.0.1",
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.0.0",
3
+ "version": "1.0.1",
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.0.0",
3
+ "version": "1.0.1",
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
  "types": "lib/index.d.ts",
@@ -35,11 +35,11 @@
35
35
  "koffi": "^2.9.0"
36
36
  },
37
37
  "optionalDependencies": {
38
- "@httpcloak/linux-x64": "1.0.0",
39
- "@httpcloak/linux-arm64": "1.0.0",
40
- "@httpcloak/darwin-x64": "1.0.0",
41
- "@httpcloak/darwin-arm64": "1.0.0",
42
- "@httpcloak/win32-x64": "1.0.0",
43
- "@httpcloak/win32-arm64": "1.0.0"
38
+ "@httpcloak/linux-x64": "1.0.1",
39
+ "@httpcloak/linux-arm64": "1.0.1",
40
+ "@httpcloak/darwin-x64": "1.0.1",
41
+ "@httpcloak/darwin-arm64": "1.0.1",
42
+ "@httpcloak/win32-x64": "1.0.1",
43
+ "@httpcloak/win32-arm64": "1.0.1"
44
44
  }
45
45
  }