httpcloak 1.0.0

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 ADDED
@@ -0,0 +1,209 @@
1
+ # HTTPCloak Node.js
2
+
3
+ Browser fingerprint emulation HTTP client with HTTP/1.1, HTTP/2, and HTTP/3 support.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install httpcloak
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### Promise-based Usage (Recommended)
14
+
15
+ ```javascript
16
+ const { Session } = require("httpcloak");
17
+
18
+ async function main() {
19
+ const session = new Session({ preset: "chrome-143" });
20
+
21
+ try {
22
+ // GET request
23
+ const response = await session.get("https://www.cloudflare.com/cdn-cgi/trace");
24
+ console.log(response.statusCode);
25
+ console.log(response.text);
26
+
27
+ // POST request with JSON body
28
+ const postResponse = await session.post("https://api.example.com/data", {
29
+ key: "value",
30
+ });
31
+
32
+ // Custom headers
33
+ const customResponse = await session.get("https://example.com", {
34
+ "X-Custom": "value",
35
+ });
36
+
37
+ // Concurrent requests
38
+ const responses = await Promise.all([
39
+ session.get("https://example.com/1"),
40
+ session.get("https://example.com/2"),
41
+ session.get("https://example.com/3"),
42
+ ]);
43
+ } finally {
44
+ session.close();
45
+ }
46
+ }
47
+
48
+ main();
49
+ ```
50
+
51
+ ### Synchronous Usage
52
+
53
+ ```javascript
54
+ const { Session } = require("httpcloak");
55
+
56
+ const session = new Session({ preset: "chrome-143" });
57
+
58
+ // Sync GET
59
+ const response = session.getSync("https://example.com");
60
+ console.log(response.statusCode);
61
+ console.log(response.text);
62
+
63
+ // Sync POST
64
+ const postResponse = session.postSync("https://api.example.com/data", {
65
+ key: "value",
66
+ });
67
+
68
+ session.close();
69
+ ```
70
+
71
+ ### Callback-based Usage
72
+
73
+ ```javascript
74
+ const { Session } = require("httpcloak");
75
+
76
+ const session = new Session({ preset: "chrome-143" });
77
+
78
+ // GET with callback
79
+ session.getCb("https://example.com", (err, response) => {
80
+ if (err) {
81
+ console.error("Error:", err.message);
82
+ return;
83
+ }
84
+ console.log(response.statusCode);
85
+ console.log(response.text);
86
+ });
87
+
88
+ // POST with callback
89
+ session.postCb(
90
+ "https://api.example.com/data",
91
+ { key: "value" },
92
+ (err, response) => {
93
+ if (err) {
94
+ console.error("Error:", err.message);
95
+ return;
96
+ }
97
+ console.log(response.statusCode);
98
+ }
99
+ );
100
+ ```
101
+
102
+ ### With Proxy
103
+
104
+ ```javascript
105
+ const session = new Session({
106
+ preset: "chrome-143",
107
+ proxy: "http://user:pass@host:port",
108
+ });
109
+ ```
110
+
111
+ ## Cookie Management
112
+
113
+ ```javascript
114
+ const { Session } = require("httpcloak");
115
+
116
+ const session = new Session();
117
+
118
+ // Get all cookies
119
+ const cookies = session.getCookies();
120
+ console.log(cookies);
121
+
122
+ // Set a cookie
123
+ session.setCookie("session_id", "abc123");
124
+
125
+ // Access cookies as property
126
+ console.log(session.cookies);
127
+
128
+ session.close();
129
+ ```
130
+
131
+ ## Available Presets
132
+
133
+ ```javascript
134
+ const { availablePresets } = require("httpcloak");
135
+
136
+ console.log(availablePresets());
137
+ // ['chrome-143', 'chrome-143-windows', 'chrome-143-linux', 'chrome-143-macos',
138
+ // 'chrome-131', 'firefox-133', 'safari-18', ...]
139
+ ```
140
+
141
+ ## Response Object
142
+
143
+ ```javascript
144
+ const response = await session.get("https://example.com");
145
+
146
+ response.statusCode; // number: HTTP status code
147
+ response.headers; // object: Response headers
148
+ response.body; // Buffer: Raw response body
149
+ response.text; // string: Response body as text
150
+ response.finalUrl; // string: Final URL after redirects
151
+ response.protocol; // string: Protocol used (http/1.1, h2, h3)
152
+ response.json(); // Parse response body as JSON
153
+ ```
154
+
155
+ ## Custom Requests
156
+
157
+ ```javascript
158
+ const response = await session.request({
159
+ method: "PUT",
160
+ url: "https://api.example.com/resource",
161
+ headers: { "X-Custom": "value" },
162
+ body: { data: "value" },
163
+ timeout: 60,
164
+ });
165
+ ```
166
+
167
+ ## Error Handling
168
+
169
+ ```javascript
170
+ const { Session, HTTPCloakError } = require("httpcloak");
171
+
172
+ const session = new Session();
173
+
174
+ try {
175
+ const response = await session.get("https://example.com");
176
+ } catch (err) {
177
+ if (err instanceof HTTPCloakError) {
178
+ console.error("HTTPCloak error:", err.message);
179
+ } else {
180
+ console.error("Unknown error:", err);
181
+ }
182
+ }
183
+
184
+ session.close();
185
+ ```
186
+
187
+ ## TypeScript Support
188
+
189
+ HTTPCloak includes TypeScript definitions out of the box:
190
+
191
+ ```typescript
192
+ import { Session, Response, HTTPCloakError } from "httpcloak";
193
+
194
+ const session = new Session({ preset: "chrome-143" });
195
+
196
+ async function fetchData(): Promise<Response> {
197
+ return session.get("https://example.com");
198
+ }
199
+ ```
200
+
201
+ ## Platform Support
202
+
203
+ - Linux (x64, arm64)
204
+ - macOS (x64, arm64)
205
+ - Windows (x64, arm64)
206
+
207
+ ## License
208
+
209
+ MIT
package/lib/index.d.ts ADDED
@@ -0,0 +1,152 @@
1
+ /**
2
+ * HTTPCloak Node.js TypeScript Definitions
3
+ */
4
+
5
+ export class HTTPCloakError extends Error {
6
+ name: "HTTPCloakError";
7
+ }
8
+
9
+ export class Response {
10
+ /** HTTP status code */
11
+ statusCode: number;
12
+ /** Response headers */
13
+ headers: Record<string, string>;
14
+ /** Raw response body as Buffer */
15
+ body: Buffer;
16
+ /** Response body as string */
17
+ text: string;
18
+ /** Final URL after redirects */
19
+ finalUrl: string;
20
+ /** Protocol used (http/1.1, h2, h3) */
21
+ protocol: string;
22
+
23
+ /** Parse response body as JSON */
24
+ json<T = any>(): T;
25
+ }
26
+
27
+ export interface SessionOptions {
28
+ /** Browser preset to use (default: "chrome-143") */
29
+ preset?: string;
30
+ /** Proxy URL (e.g., "http://user:pass@host:port") */
31
+ proxy?: string;
32
+ /** Request timeout in seconds (default: 30) */
33
+ timeout?: number;
34
+ }
35
+
36
+ export interface RequestOptions {
37
+ /** HTTP method */
38
+ method: string;
39
+ /** Request URL */
40
+ url: string;
41
+ /** Optional custom headers */
42
+ headers?: Record<string, string>;
43
+ /** Optional request body */
44
+ body?: string | Buffer | Record<string, any>;
45
+ /** Optional request timeout in seconds */
46
+ timeout?: number;
47
+ }
48
+
49
+ export type RequestCallback = (
50
+ error: HTTPCloakError | null,
51
+ response: Response | null
52
+ ) => void;
53
+
54
+ export class Session {
55
+ constructor(options?: SessionOptions);
56
+
57
+ /** Close the session and release resources */
58
+ close(): void;
59
+
60
+ // Synchronous methods
61
+ /** Perform a synchronous GET request */
62
+ getSync(url: string, headers?: Record<string, string>): Response;
63
+
64
+ /** Perform a synchronous POST request */
65
+ postSync(
66
+ url: string,
67
+ body?: string | Buffer | Record<string, any>,
68
+ headers?: Record<string, string>
69
+ ): Response;
70
+
71
+ /** Perform a synchronous custom HTTP request */
72
+ requestSync(options: RequestOptions): Response;
73
+
74
+ // Promise-based methods
75
+ /** Perform an async GET request */
76
+ get(url: string, headers?: Record<string, string>): Promise<Response>;
77
+
78
+ /** Perform an async POST request */
79
+ post(
80
+ url: string,
81
+ body?: string | Buffer | Record<string, any>,
82
+ headers?: Record<string, string>
83
+ ): Promise<Response>;
84
+
85
+ /** Perform an async custom HTTP request */
86
+ request(options: RequestOptions): Promise<Response>;
87
+
88
+ /** Perform an async PUT request */
89
+ put(
90
+ url: string,
91
+ body?: string | Buffer | Record<string, any>,
92
+ headers?: Record<string, string>
93
+ ): Promise<Response>;
94
+
95
+ /** Perform an async DELETE request */
96
+ delete(url: string, headers?: Record<string, string>): Promise<Response>;
97
+
98
+ /** Perform an async PATCH request */
99
+ patch(
100
+ url: string,
101
+ body?: string | Buffer | Record<string, any>,
102
+ headers?: Record<string, string>
103
+ ): Promise<Response>;
104
+
105
+ /** Perform an async HEAD request */
106
+ head(url: string, headers?: Record<string, string>): Promise<Response>;
107
+
108
+ /** Perform an async OPTIONS request */
109
+ options(url: string, headers?: Record<string, string>): Promise<Response>;
110
+
111
+ // Callback-based methods
112
+ /** Perform a GET request with callback */
113
+ getCb(url: string, callback: RequestCallback): void;
114
+ getCb(
115
+ url: string,
116
+ headers: Record<string, string>,
117
+ callback: RequestCallback
118
+ ): void;
119
+
120
+ /** Perform a POST request with callback */
121
+ postCb(url: string, callback: RequestCallback): void;
122
+ postCb(
123
+ url: string,
124
+ body: string | Buffer | Record<string, any>,
125
+ callback: RequestCallback
126
+ ): void;
127
+ postCb(
128
+ url: string,
129
+ body: string | Buffer | Record<string, any>,
130
+ headers: Record<string, string>,
131
+ callback: RequestCallback
132
+ ): void;
133
+
134
+ /** Perform a custom request with callback */
135
+ requestCb(options: RequestOptions, callback: RequestCallback): void;
136
+
137
+ // Cookie management
138
+ /** Get all cookies from the session */
139
+ getCookies(): Record<string, string>;
140
+
141
+ /** Set a cookie in the session */
142
+ setCookie(name: string, value: string): void;
143
+
144
+ /** Get cookies as a property */
145
+ readonly cookies: Record<string, string>;
146
+ }
147
+
148
+ /** Get the httpcloak library version */
149
+ export function version(): string;
150
+
151
+ /** Get list of available browser presets */
152
+ export function availablePresets(): string[];
package/lib/index.js ADDED
@@ -0,0 +1,551 @@
1
+ /**
2
+ * HTTPCloak Node.js Client
3
+ *
4
+ * Provides HTTP client with browser fingerprint emulation.
5
+ */
6
+
7
+ const koffi = require("koffi");
8
+ const path = require("path");
9
+ const os = require("os");
10
+ const fs = require("fs");
11
+
12
+ /**
13
+ * Custom error class for HTTPCloak errors
14
+ */
15
+ class HTTPCloakError extends Error {
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = "HTTPCloakError";
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Response object returned from HTTP requests
24
+ */
25
+ class Response {
26
+ constructor(data) {
27
+ this.statusCode = data.status_code || 0;
28
+ this.headers = data.headers || {};
29
+ this.body = Buffer.from(data.body || "", "utf8");
30
+ this.text = data.body || "";
31
+ this.finalUrl = data.final_url || "";
32
+ this.protocol = data.protocol || "";
33
+ }
34
+
35
+ /**
36
+ * Parse response body as JSON
37
+ */
38
+ json() {
39
+ return JSON.parse(this.text);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Get the platform package name for the current platform
45
+ */
46
+ function getPlatformPackageName() {
47
+ const platform = os.platform();
48
+ const arch = os.arch();
49
+
50
+ // Map to npm platform names
51
+ let platName;
52
+ if (platform === "darwin") {
53
+ platName = "darwin";
54
+ } else if (platform === "win32") {
55
+ platName = "win32";
56
+ } else {
57
+ platName = "linux";
58
+ }
59
+
60
+ let archName;
61
+ if (arch === "x64" || arch === "amd64") {
62
+ archName = "x64";
63
+ } else if (arch === "arm64" || arch === "aarch64") {
64
+ archName = "arm64";
65
+ } else {
66
+ archName = arch;
67
+ }
68
+
69
+ return `@httpcloak/${platName}-${archName}`;
70
+ }
71
+
72
+ /**
73
+ * Get the path to the native library
74
+ */
75
+ function getLibPath() {
76
+ const platform = os.platform();
77
+ const arch = os.arch();
78
+
79
+ // Check environment variable first
80
+ const envPath = process.env.HTTPCLOAK_LIB_PATH;
81
+ if (envPath && fs.existsSync(envPath)) {
82
+ return envPath;
83
+ }
84
+
85
+ // Try to load from platform-specific optional dependency
86
+ const packageName = getPlatformPackageName();
87
+ try {
88
+ const libPath = require(packageName);
89
+ if (fs.existsSync(libPath)) {
90
+ return libPath;
91
+ }
92
+ } catch (e) {
93
+ // Optional dependency not installed, fall back to local search
94
+ }
95
+
96
+ // Normalize architecture for library name
97
+ let archName;
98
+ if (arch === "x64" || arch === "amd64") {
99
+ archName = "amd64";
100
+ } else if (arch === "arm64" || arch === "aarch64") {
101
+ archName = "arm64";
102
+ } else {
103
+ archName = arch;
104
+ }
105
+
106
+ // Determine OS name and extension
107
+ let osName, ext;
108
+ if (platform === "darwin") {
109
+ osName = "darwin";
110
+ ext = ".dylib";
111
+ } else if (platform === "win32") {
112
+ osName = "windows";
113
+ ext = ".dll";
114
+ } else {
115
+ osName = "linux";
116
+ ext = ".so";
117
+ }
118
+
119
+ const libName = `libhttpcloak-${osName}-${archName}${ext}`;
120
+
121
+ // Search paths (fallback for local development)
122
+ const searchPaths = [
123
+ path.join(__dirname, libName),
124
+ path.join(__dirname, "..", libName),
125
+ path.join(__dirname, "..", "lib", libName),
126
+ ];
127
+
128
+ for (const searchPath of searchPaths) {
129
+ if (fs.existsSync(searchPath)) {
130
+ return searchPath;
131
+ }
132
+ }
133
+
134
+ throw new HTTPCloakError(
135
+ `Could not find httpcloak library (${libName}). ` +
136
+ `Try: npm install ${packageName}`
137
+ );
138
+ }
139
+
140
+ // Load the native library
141
+ let lib = null;
142
+
143
+ function getLib() {
144
+ if (lib === null) {
145
+ const libPath = getLibPath();
146
+ const nativeLib = koffi.load(libPath);
147
+
148
+ lib = {
149
+ httpcloak_session_new: nativeLib.func("httpcloak_session_new", "int64", ["str"]),
150
+ httpcloak_session_free: nativeLib.func("httpcloak_session_free", "void", ["int64"]),
151
+ httpcloak_get: nativeLib.func("httpcloak_get", "str", ["int64", "str", "str"]),
152
+ httpcloak_post: nativeLib.func("httpcloak_post", "str", ["int64", "str", "str", "str"]),
153
+ httpcloak_request: nativeLib.func("httpcloak_request", "str", ["int64", "str"]),
154
+ httpcloak_get_cookies: nativeLib.func("httpcloak_get_cookies", "str", ["int64"]),
155
+ httpcloak_set_cookie: nativeLib.func("httpcloak_set_cookie", "void", ["int64", "str", "str"]),
156
+ httpcloak_free_string: nativeLib.func("httpcloak_free_string", "void", ["str"]),
157
+ httpcloak_version: nativeLib.func("httpcloak_version", "str", []),
158
+ httpcloak_available_presets: nativeLib.func("httpcloak_available_presets", "str", []),
159
+ };
160
+ }
161
+ return lib;
162
+ }
163
+
164
+ /**
165
+ * Parse response from the native library
166
+ */
167
+ function parseResponse(result) {
168
+ if (!result) {
169
+ throw new HTTPCloakError("No response received");
170
+ }
171
+
172
+ const data = JSON.parse(result);
173
+
174
+ if (data.error) {
175
+ throw new HTTPCloakError(data.error);
176
+ }
177
+
178
+ return new Response(data);
179
+ }
180
+
181
+ /**
182
+ * Get the httpcloak library version
183
+ */
184
+ function version() {
185
+ const nativeLib = getLib();
186
+ return nativeLib.httpcloak_version() || "unknown";
187
+ }
188
+
189
+ /**
190
+ * Get list of available browser presets
191
+ */
192
+ function availablePresets() {
193
+ const nativeLib = getLib();
194
+ const result = nativeLib.httpcloak_available_presets();
195
+ if (result) {
196
+ return JSON.parse(result);
197
+ }
198
+ return [];
199
+ }
200
+
201
+ /**
202
+ * HTTP Session with browser fingerprint emulation
203
+ */
204
+ class Session {
205
+ /**
206
+ * Create a new session
207
+ * @param {Object} options - Session options
208
+ * @param {string} [options.preset="chrome-143"] - Browser preset to use
209
+ * @param {string} [options.proxy] - Proxy URL (e.g., "http://user:pass@host:port")
210
+ * @param {number} [options.timeout=30] - Request timeout in seconds
211
+ */
212
+ constructor(options = {}) {
213
+ const { preset = "chrome-143", proxy = null, timeout = 30 } = options;
214
+
215
+ this._lib = getLib();
216
+
217
+ const config = {
218
+ preset,
219
+ timeout,
220
+ };
221
+ if (proxy) {
222
+ config.proxy = proxy;
223
+ }
224
+
225
+ this._handle = this._lib.httpcloak_session_new(JSON.stringify(config));
226
+
227
+ if (this._handle === 0n || this._handle === 0) {
228
+ throw new HTTPCloakError("Failed to create session");
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Close the session and release resources
234
+ */
235
+ close() {
236
+ if (this._handle) {
237
+ this._lib.httpcloak_session_free(this._handle);
238
+ this._handle = 0n;
239
+ }
240
+ }
241
+
242
+ // ===========================================================================
243
+ // Synchronous Methods
244
+ // ===========================================================================
245
+
246
+ /**
247
+ * Perform a synchronous GET request
248
+ * @param {string} url - Request URL
249
+ * @param {Object} [headers] - Optional custom headers
250
+ * @returns {Response} Response object
251
+ */
252
+ getSync(url, headers = null) {
253
+ const headersJson = headers ? JSON.stringify(headers) : null;
254
+ const result = this._lib.httpcloak_get(this._handle, url, headersJson);
255
+ return parseResponse(result);
256
+ }
257
+
258
+ /**
259
+ * Perform a synchronous POST request
260
+ * @param {string} url - Request URL
261
+ * @param {string|Buffer|Object} [body] - Request body
262
+ * @param {Object} [headers] - Optional custom headers
263
+ * @returns {Response} Response object
264
+ */
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";
271
+ }
272
+ }
273
+
274
+ if (Buffer.isBuffer(body)) {
275
+ body = body.toString("utf8");
276
+ }
277
+
278
+ const headersJson = headers ? JSON.stringify(headers) : null;
279
+ const result = this._lib.httpcloak_post(this._handle, url, body, headersJson);
280
+ return parseResponse(result);
281
+ }
282
+
283
+ /**
284
+ * 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
291
+ * @returns {Response} Response object
292
+ */
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";
301
+ }
302
+ }
303
+
304
+ if (Buffer.isBuffer(body)) {
305
+ body = body.toString("utf8");
306
+ }
307
+
308
+ const requestConfig = {
309
+ method: method.toUpperCase(),
310
+ url,
311
+ };
312
+ if (headers) requestConfig.headers = headers;
313
+ if (body) requestConfig.body = body;
314
+ if (timeout) requestConfig.timeout = timeout;
315
+
316
+ const result = this._lib.httpcloak_request(
317
+ this._handle,
318
+ JSON.stringify(requestConfig)
319
+ );
320
+ return parseResponse(result);
321
+ }
322
+
323
+ // ===========================================================================
324
+ // Promise-based Methods
325
+ // ===========================================================================
326
+
327
+ /**
328
+ * Perform an async GET request
329
+ * @param {string} url - Request URL
330
+ * @param {Object} [headers] - Optional custom headers
331
+ * @returns {Promise<Response>} Response object
332
+ */
333
+ get(url, headers = null) {
334
+ return new Promise((resolve, reject) => {
335
+ setImmediate(() => {
336
+ try {
337
+ resolve(this.getSync(url, headers));
338
+ } catch (err) {
339
+ reject(err);
340
+ }
341
+ });
342
+ });
343
+ }
344
+
345
+ /**
346
+ * Perform an async POST request
347
+ * @param {string} url - Request URL
348
+ * @param {string|Buffer|Object} [body] - Request body
349
+ * @param {Object} [headers] - Optional custom headers
350
+ * @returns {Promise<Response>} Response object
351
+ */
352
+ post(url, body = null, headers = null) {
353
+ return new Promise((resolve, reject) => {
354
+ setImmediate(() => {
355
+ try {
356
+ resolve(this.postSync(url, body, headers));
357
+ } catch (err) {
358
+ reject(err);
359
+ }
360
+ });
361
+ });
362
+ }
363
+
364
+ /**
365
+ * Perform an async custom HTTP request
366
+ * @param {Object} options - Request options
367
+ * @returns {Promise<Response>} Response object
368
+ */
369
+ request(options) {
370
+ return new Promise((resolve, reject) => {
371
+ setImmediate(() => {
372
+ try {
373
+ resolve(this.requestSync(options));
374
+ } catch (err) {
375
+ reject(err);
376
+ }
377
+ });
378
+ });
379
+ }
380
+
381
+ /**
382
+ * 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
+ */
388
+ put(url, body = null, headers = null) {
389
+ return this.request({ method: "PUT", url, body, headers });
390
+ }
391
+
392
+ /**
393
+ * 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
+ */
398
+ delete(url, headers = null) {
399
+ return this.request({ method: "DELETE", url, headers });
400
+ }
401
+
402
+ /**
403
+ * 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
+ */
409
+ patch(url, body = null, headers = null) {
410
+ return this.request({ method: "PATCH", url, body, headers });
411
+ }
412
+
413
+ /**
414
+ * 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
+ */
419
+ head(url, headers = null) {
420
+ return this.request({ method: "HEAD", url, headers });
421
+ }
422
+
423
+ /**
424
+ * 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
+ */
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
+ });
510
+ }
511
+
512
+ // ===========================================================================
513
+ // Cookie Management
514
+ // ===========================================================================
515
+
516
+ /**
517
+ * Get all cookies from the session
518
+ * @returns {Object} Cookies as key-value pairs
519
+ */
520
+ getCookies() {
521
+ const result = this._lib.httpcloak_get_cookies(this._handle);
522
+ if (result) {
523
+ return JSON.parse(result);
524
+ }
525
+ return {};
526
+ }
527
+
528
+ /**
529
+ * Set a cookie in the session
530
+ * @param {string} name - Cookie name
531
+ * @param {string} value - Cookie value
532
+ */
533
+ setCookie(name, value) {
534
+ this._lib.httpcloak_set_cookie(this._handle, name, value);
535
+ }
536
+
537
+ /**
538
+ * Get cookies as a property
539
+ */
540
+ get cookies() {
541
+ return this.getCookies();
542
+ }
543
+ }
544
+
545
+ module.exports = {
546
+ Session,
547
+ Response,
548
+ HTTPCloakError,
549
+ version,
550
+ availablePresets,
551
+ };
@@ -0,0 +1,3 @@
1
+ // Auto-generated - exports path to native library
2
+ const path = require("path");
3
+ module.exports = path.join(__dirname, "libhttpcloak-darwin-arm64.dylib");
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@httpcloak/darwin-arm64",
3
+ "version": "1.0.0",
4
+ "description": "HTTPCloak native binary for darwin arm64",
5
+ "os": [
6
+ "darwin"
7
+ ],
8
+ "cpu": [
9
+ "arm64"
10
+ ],
11
+ "main": "lib.js",
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/sardanioss/httpcloak"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ }
20
+ }
@@ -0,0 +1,3 @@
1
+ // Auto-generated - exports path to native library
2
+ const path = require("path");
3
+ module.exports = path.join(__dirname, "libhttpcloak-darwin-amd64.dylib");
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@httpcloak/darwin-x64",
3
+ "version": "1.0.0",
4
+ "description": "HTTPCloak native binary for darwin x64",
5
+ "os": [
6
+ "darwin"
7
+ ],
8
+ "cpu": [
9
+ "x64"
10
+ ],
11
+ "main": "lib.js",
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/sardanioss/httpcloak"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ }
20
+ }
@@ -0,0 +1,3 @@
1
+ // Auto-generated - exports path to native library
2
+ const path = require("path");
3
+ module.exports = path.join(__dirname, "libhttpcloak-linux-arm64.so");
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@httpcloak/linux-arm64",
3
+ "version": "1.0.0",
4
+ "description": "HTTPCloak native binary for linux arm64",
5
+ "os": [
6
+ "linux"
7
+ ],
8
+ "cpu": [
9
+ "arm64"
10
+ ],
11
+ "main": "lib.js",
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/sardanioss/httpcloak"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ }
20
+ }
@@ -0,0 +1,3 @@
1
+ // Auto-generated - exports path to native library
2
+ const path = require("path");
3
+ module.exports = path.join(__dirname, "libhttpcloak-linux-amd64.so");
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@httpcloak/linux-x64",
3
+ "version": "1.0.0",
4
+ "description": "HTTPCloak native binary for linux x64",
5
+ "os": [
6
+ "linux"
7
+ ],
8
+ "cpu": [
9
+ "x64"
10
+ ],
11
+ "main": "lib.js",
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/sardanioss/httpcloak"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ }
20
+ }
@@ -0,0 +1,3 @@
1
+ // Auto-generated - exports path to native library
2
+ const path = require("path");
3
+ module.exports = path.join(__dirname, "libhttpcloak-windows-arm64.dll");
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@httpcloak/win32-arm64",
3
+ "version": "1.0.0",
4
+ "description": "HTTPCloak native binary for win32 arm64",
5
+ "os": [
6
+ "win32"
7
+ ],
8
+ "cpu": [
9
+ "arm64"
10
+ ],
11
+ "main": "lib.js",
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/sardanioss/httpcloak"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ }
20
+ }
@@ -0,0 +1,3 @@
1
+ // Auto-generated - exports path to native library
2
+ const path = require("path");
3
+ module.exports = path.join(__dirname, "libhttpcloak-windows-amd64.dll");
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@httpcloak/win32-x64",
3
+ "version": "1.0.0",
4
+ "description": "HTTPCloak native binary for win32 x64",
5
+ "os": [
6
+ "win32"
7
+ ],
8
+ "cpu": [
9
+ "x64"
10
+ ],
11
+ "main": "lib.js",
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/sardanioss/httpcloak"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ }
20
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "httpcloak",
3
+ "version": "1.0.0",
4
+ "description": "Browser fingerprint emulation HTTP client with HTTP/1.1, HTTP/2, and HTTP/3 support",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "scripts": {
8
+ "test": "node test.js",
9
+ "setup-packages": "node scripts/setup-npm-packages.js"
10
+ },
11
+ "keywords": [
12
+ "http",
13
+ "http2",
14
+ "http3",
15
+ "quic",
16
+ "tls",
17
+ "fingerprint",
18
+ "browser",
19
+ "scraping",
20
+ "bot-detection"
21
+ ],
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/sardanioss/httpcloak"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/sardanioss/httpcloak/issues"
29
+ },
30
+ "homepage": "https://github.com/sardanioss/httpcloak#readme",
31
+ "engines": {
32
+ "node": ">=14.0.0"
33
+ },
34
+ "dependencies": {
35
+ "koffi": "^2.9.0"
36
+ },
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"
44
+ }
45
+ }
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate platform-specific npm packages for httpcloak
4
+ */
5
+
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+
9
+ const VERSION = "1.4.0";
10
+
11
+ const PLATFORMS = [
12
+ { name: "linux-x64", os: "linux", cpu: "x64", libName: "libhttpcloak-linux-amd64.so" },
13
+ { name: "linux-arm64", os: "linux", cpu: "arm64", libName: "libhttpcloak-linux-arm64.so" },
14
+ { name: "darwin-x64", os: "darwin", cpu: "x64", libName: "libhttpcloak-darwin-amd64.dylib" },
15
+ { name: "darwin-arm64", os: "darwin", cpu: "arm64", libName: "libhttpcloak-darwin-arm64.dylib" },
16
+ { name: "win32-x64", os: "win32", cpu: "x64", libName: "libhttpcloak-windows-amd64.dll" },
17
+ { name: "win32-arm64", os: "win32", cpu: "arm64", libName: "libhttpcloak-windows-arm64.dll" },
18
+ ];
19
+
20
+ const npmDir = path.join(__dirname, "..", "npm");
21
+
22
+ // Create each platform package
23
+ for (const platform of PLATFORMS) {
24
+ const pkgDir = path.join(npmDir, platform.name);
25
+
26
+ // Create directory
27
+ fs.mkdirSync(pkgDir, { recursive: true });
28
+
29
+ // Create package.json
30
+ const packageJson = {
31
+ name: `@httpcloak/${platform.name}`,
32
+ version: VERSION,
33
+ description: `HTTPCloak native binary for ${platform.os} ${platform.cpu}`,
34
+ os: [platform.os],
35
+ cpu: [platform.cpu],
36
+ main: "lib.js",
37
+ license: "MIT",
38
+ repository: {
39
+ type: "git",
40
+ url: "https://github.com/sardanioss/httpcloak",
41
+ },
42
+ publishConfig: {
43
+ access: "public",
44
+ },
45
+ };
46
+
47
+ fs.writeFileSync(
48
+ path.join(pkgDir, "package.json"),
49
+ JSON.stringify(packageJson, null, 2) + "\n"
50
+ );
51
+
52
+ // Create lib.js that exports the library path
53
+ const libJs = `// Auto-generated - exports path to native library
54
+ const path = require("path");
55
+ module.exports = path.join(__dirname, "${platform.libName}");
56
+ `;
57
+ fs.writeFileSync(path.join(pkgDir, "lib.js"), libJs);
58
+
59
+ console.log(`Created: @httpcloak/${platform.name}`);
60
+ }
61
+
62
+ // Create optionalDependencies for main package
63
+ const optionalDeps = {};
64
+ for (const platform of PLATFORMS) {
65
+ optionalDeps[`@httpcloak/${platform.name}`] = VERSION;
66
+ }
67
+
68
+ console.log("\nAdd to main package.json optionalDependencies:");
69
+ console.log(JSON.stringify(optionalDeps, null, 2));
package/test.js ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * HTTPCloak Node.js Test
3
+ */
4
+
5
+ const { Session, version, availablePresets, HTTPCloakError } = require("./lib");
6
+
7
+ async function main() {
8
+ console.log("=== HTTPCloak Node.js Test ===\n");
9
+
10
+ // Test version
11
+ console.log("Version:", version());
12
+
13
+ // Test presets
14
+ const presets = availablePresets();
15
+ console.log("Presets:", presets.slice(0, 5).join(", "), "...");
16
+ console.log("");
17
+
18
+ // Create session
19
+ const session = new Session({ preset: "chrome-143" });
20
+
21
+ try {
22
+ // Test sync GET
23
+ console.log("--- Sync GET ---");
24
+ const syncResponse = session.getSync(
25
+ "https://www.cloudflare.com/cdn-cgi/trace"
26
+ );
27
+ console.log("Status:", syncResponse.statusCode);
28
+ console.log("Protocol:", syncResponse.protocol);
29
+ console.log("");
30
+
31
+ // Test async GET
32
+ console.log("--- Async GET (Promise) ---");
33
+ const asyncResponse = await session.get(
34
+ "https://www.cloudflare.com/cdn-cgi/trace"
35
+ );
36
+ console.log("Status:", asyncResponse.statusCode);
37
+ console.log("Protocol:", asyncResponse.protocol);
38
+ console.log("");
39
+
40
+ // Test callback GET
41
+ console.log("--- Callback GET ---");
42
+ await new Promise((resolve, reject) => {
43
+ session.getCb(
44
+ "https://www.cloudflare.com/cdn-cgi/trace",
45
+ (err, response) => {
46
+ if (err) {
47
+ console.error("Error:", err.message);
48
+ reject(err);
49
+ return;
50
+ }
51
+ console.log("Status:", response.statusCode);
52
+ console.log("Protocol:", response.protocol);
53
+ resolve();
54
+ }
55
+ );
56
+ });
57
+ console.log("");
58
+
59
+ // Test concurrent requests
60
+ console.log("--- Concurrent Requests ---");
61
+ const startTime = Date.now();
62
+ const responses = await Promise.all([
63
+ session.get("https://www.cloudflare.com/cdn-cgi/trace"),
64
+ session.get("https://www.cloudflare.com/cdn-cgi/trace"),
65
+ ]);
66
+ const elapsed = Date.now() - startTime;
67
+ console.log("Concurrent requests:", responses.length);
68
+ console.log("Time:", elapsed, "ms");
69
+ console.log("");
70
+
71
+ // Test cookies
72
+ console.log("--- Cookies ---");
73
+ session.setCookie("test_cookie", "test_value");
74
+ const cookies = session.getCookies();
75
+ console.log("Cookies:", JSON.stringify(cookies));
76
+ console.log("");
77
+
78
+ console.log("=== All tests passed! ===");
79
+ } catch (err) {
80
+ console.error("Test failed:", err);
81
+ process.exit(1);
82
+ } finally {
83
+ session.close();
84
+ }
85
+ }
86
+
87
+ main();