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 +516 -159
- package/npm/darwin-arm64/package.json +1 -1
- package/npm/darwin-x64/package.json +1 -1
- package/npm/linux-arm64/package.json +1 -1
- package/npm/linux-x64/package.json +1 -1
- package/npm/win32-arm64/package.json +1 -1
- package/npm/win32-x64/package.json +1 -1
- package/package.json +7 -7
package/lib/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTTPCloak Node.js Client
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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.
|
|
30
|
-
this.
|
|
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.
|
|
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
|
|
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 {
|
|
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} [
|
|
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,
|
|
253
|
-
const
|
|
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 {
|
|
262
|
-
* @param {Object} [
|
|
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,
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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 (
|
|
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
|
-
|
|
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 {
|
|
286
|
-
* @param {string}
|
|
287
|
-
* @param {
|
|
288
|
-
* @param {Object} [options.
|
|
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 {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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 (
|
|
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 (
|
|
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} [
|
|
596
|
+
* @param {Object} [options] - Request options
|
|
331
597
|
* @returns {Promise<Response>} Response object
|
|
332
598
|
*/
|
|
333
|
-
get(url,
|
|
599
|
+
get(url, options = {}) {
|
|
334
600
|
return new Promise((resolve, reject) => {
|
|
335
601
|
setImmediate(() => {
|
|
336
602
|
try {
|
|
337
|
-
resolve(this.getSync(url,
|
|
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 {
|
|
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,
|
|
617
|
+
post(url, options = {}) {
|
|
353
618
|
return new Promise((resolve, reject) => {
|
|
354
619
|
setImmediate(() => {
|
|
355
620
|
try {
|
|
356
|
-
resolve(this.postSync(url,
|
|
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 {
|
|
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,
|
|
389
|
-
return this.request(
|
|
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,
|
|
399
|
-
return this.request(
|
|
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,
|
|
410
|
-
return this.request(
|
|
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,
|
|
420
|
-
return this.request(
|
|
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
|
-
|
|
471
|
-
|
|
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
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "httpcloak",
|
|
3
|
-
"version": "1.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.
|
|
39
|
-
"@httpcloak/linux-arm64": "1.0.
|
|
40
|
-
"@httpcloak/darwin-x64": "1.0.
|
|
41
|
-
"@httpcloak/darwin-arm64": "1.0.
|
|
42
|
-
"@httpcloak/win32-x64": "1.0.
|
|
43
|
-
"@httpcloak/win32-arm64": "1.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
|
}
|