wreq-js 0.1.0 → 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 +110 -182
- package/dist/test/helpers/local-test-server.d.ts +7 -0
- package/dist/test/helpers/local-test-server.d.ts.map +1 -0
- package/dist/test/helpers/local-test-server.js +302 -0
- package/dist/test/helpers/local-test-server.js.map +1 -0
- package/dist/test/http.spec.js +135 -37
- package/dist/test/http.spec.js.map +1 -1
- package/dist/test/run-with-local-server.d.ts +2 -0
- package/dist/test/run-with-local-server.d.ts.map +1 -0
- package/dist/test/run-with-local-server.js +61 -0
- package/dist/test/run-with-local-server.js.map +1 -0
- package/dist/test/websocket.spec.js +47 -50
- package/dist/test/websocket.spec.js.map +1 -1
- package/dist/types.d.ts +273 -30
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +16 -1
- package/dist/types.js.map +1 -1
- package/dist/wreq-js.d.ts +82 -55
- package/dist/wreq-js.d.ts.map +1 -1
- package/dist/wreq-js.js +595 -82
- package/dist/wreq-js.js.map +1 -1
- package/package.json +20 -22
- package/rust/wreq-js.darwin-arm64.node +0 -0
- package/rust/wreq-js.darwin-x64.node +0 -0
- package/rust/wreq-js.linux-arm64-gnu.node +0 -0
- package/rust/wreq-js.linux-x64-gnu.node +0 -0
- package/rust/wreq-js.win32-x64-msvc.node +0 -0
package/dist/wreq-js.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WebSocket = void 0;
|
|
3
|
+
exports.RequestError = exports.WebSocket = exports.Session = exports.Response = exports.Headers = void 0;
|
|
4
|
+
exports.fetch = fetch;
|
|
5
|
+
exports.createSession = createSession;
|
|
6
|
+
exports.withSession = withSession;
|
|
4
7
|
exports.request = request;
|
|
5
8
|
exports.getProfiles = getProfiles;
|
|
6
9
|
exports.get = get;
|
|
7
10
|
exports.post = post;
|
|
8
11
|
exports.websocket = websocket;
|
|
12
|
+
const node_crypto_1 = require("node:crypto");
|
|
13
|
+
const node_http_1 = require("node:http");
|
|
9
14
|
const types_1 = require("./types");
|
|
15
|
+
Object.defineProperty(exports, "RequestError", { enumerable: true, get: function () { return types_1.RequestError; } });
|
|
10
16
|
let nativeBinding;
|
|
17
|
+
let cachedProfiles;
|
|
11
18
|
function loadNativeBinding() {
|
|
12
19
|
const platform = process.platform;
|
|
13
20
|
const arch = process.arch;
|
|
@@ -15,15 +22,15 @@ function loadNativeBinding() {
|
|
|
15
22
|
// napi-rs creates files like: wreq-js.linux-x64-gnu.node
|
|
16
23
|
const platformArchMap = {
|
|
17
24
|
darwin: {
|
|
18
|
-
x64:
|
|
19
|
-
arm64:
|
|
25
|
+
x64: "darwin-x64",
|
|
26
|
+
arm64: "darwin-arm64",
|
|
20
27
|
},
|
|
21
28
|
linux: {
|
|
22
|
-
x64:
|
|
23
|
-
arm64:
|
|
29
|
+
x64: "linux-x64-gnu",
|
|
30
|
+
arm64: "linux-arm64-gnu",
|
|
24
31
|
},
|
|
25
32
|
win32: {
|
|
26
|
-
x64:
|
|
33
|
+
x64: "win32-x64-msvc",
|
|
27
34
|
},
|
|
28
35
|
};
|
|
29
36
|
const platformArch = platformArchMap[platform]?.[arch];
|
|
@@ -36,62 +43,568 @@ function loadNativeBinding() {
|
|
|
36
43
|
try {
|
|
37
44
|
return require(`../rust/${binaryName}`);
|
|
38
45
|
}
|
|
39
|
-
catch
|
|
46
|
+
catch {
|
|
40
47
|
// Fallback to wreq-js.node (for local development)
|
|
41
48
|
try {
|
|
42
|
-
return require(
|
|
49
|
+
return require("../rust/wreq-js.node");
|
|
43
50
|
}
|
|
44
|
-
catch
|
|
51
|
+
catch {
|
|
45
52
|
throw new Error(`Failed to load native module for ${platform}-${arch}. ` +
|
|
46
53
|
`Tried: ../rust/${binaryName} and ../rust/wreq-js.node. ` +
|
|
47
54
|
`Make sure the package is installed correctly and the native module is built for your platform.`);
|
|
48
55
|
}
|
|
49
56
|
}
|
|
50
57
|
}
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
nativeBinding = loadNativeBinding();
|
|
59
|
+
const websocketFinalizer = typeof FinalizationRegistry === "function"
|
|
60
|
+
? new FinalizationRegistry((connection) => {
|
|
61
|
+
void nativeBinding.websocketClose(connection).catch(() => undefined);
|
|
62
|
+
})
|
|
63
|
+
: undefined;
|
|
64
|
+
const DEFAULT_BROWSER = "chrome_142";
|
|
65
|
+
function generateSessionId() {
|
|
66
|
+
const cryptoGlobal = globalThis.crypto;
|
|
67
|
+
if (cryptoGlobal?.randomUUID) {
|
|
68
|
+
return cryptoGlobal.randomUUID();
|
|
69
|
+
}
|
|
70
|
+
return (0, node_crypto_1.randomBytes)(16).toString("hex");
|
|
71
|
+
}
|
|
72
|
+
function normalizeSessionOptions(options) {
|
|
73
|
+
const sessionId = options?.sessionId ?? generateSessionId();
|
|
74
|
+
const defaults = {
|
|
75
|
+
browser: options?.browser ?? DEFAULT_BROWSER,
|
|
76
|
+
};
|
|
77
|
+
if (options?.proxy !== undefined) {
|
|
78
|
+
defaults.proxy = options.proxy;
|
|
79
|
+
}
|
|
80
|
+
if (options?.timeout !== undefined) {
|
|
81
|
+
defaults.timeout = options.timeout;
|
|
82
|
+
}
|
|
83
|
+
return { sessionId, defaults };
|
|
84
|
+
}
|
|
85
|
+
function isIterable(value) {
|
|
86
|
+
return Boolean(value) && typeof value[Symbol.iterator] === "function";
|
|
87
|
+
}
|
|
88
|
+
function isPlainObject(value) {
|
|
89
|
+
if (typeof value !== "object" || value === null) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const proto = Object.getPrototypeOf(value);
|
|
93
|
+
return proto === Object.prototype || proto === null;
|
|
94
|
+
}
|
|
95
|
+
function coerceHeaderValue(value) {
|
|
96
|
+
return String(value);
|
|
97
|
+
}
|
|
98
|
+
class Headers {
|
|
99
|
+
constructor(init) {
|
|
100
|
+
this.store = new Map();
|
|
101
|
+
if (init) {
|
|
102
|
+
this.applyInit(init);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
applyInit(init) {
|
|
106
|
+
if (init instanceof Headers) {
|
|
107
|
+
for (const [name, value] of init) {
|
|
108
|
+
this.append(name, value);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (Array.isArray(init) || isIterable(init)) {
|
|
113
|
+
for (const tuple of init) {
|
|
114
|
+
if (!tuple) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const [name, value] = tuple;
|
|
118
|
+
this.append(name, value);
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (isPlainObject(init)) {
|
|
123
|
+
for (const [name, value] of Object.entries(init)) {
|
|
124
|
+
if (value === undefined || value === null) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
this.set(name, coerceHeaderValue(value));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
normalizeName(name) {
|
|
132
|
+
if (typeof name !== "string") {
|
|
133
|
+
throw new TypeError("Header name must be a string");
|
|
134
|
+
}
|
|
135
|
+
const trimmed = name.trim();
|
|
136
|
+
if (!trimmed) {
|
|
137
|
+
throw new TypeError("Header name must not be empty");
|
|
138
|
+
}
|
|
139
|
+
return { key: trimmed.toLowerCase(), display: trimmed };
|
|
140
|
+
}
|
|
141
|
+
assertValue(value) {
|
|
142
|
+
if (value === undefined || value === null) {
|
|
143
|
+
throw new TypeError("Header value must not be null or undefined");
|
|
144
|
+
}
|
|
145
|
+
return coerceHeaderValue(value);
|
|
146
|
+
}
|
|
147
|
+
append(name, value) {
|
|
148
|
+
const normalized = this.normalizeName(name);
|
|
149
|
+
const existing = this.store.get(normalized.key);
|
|
150
|
+
const coercedValue = this.assertValue(value);
|
|
151
|
+
if (existing) {
|
|
152
|
+
existing.values.push(coercedValue);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
this.store.set(normalized.key, {
|
|
156
|
+
name: normalized.display,
|
|
157
|
+
values: [coercedValue],
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
set(name, value) {
|
|
161
|
+
const normalized = this.normalizeName(name);
|
|
162
|
+
const coercedValue = this.assertValue(value);
|
|
163
|
+
this.store.set(normalized.key, {
|
|
164
|
+
name: normalized.display,
|
|
165
|
+
values: [coercedValue],
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
get(name) {
|
|
169
|
+
const normalized = this.normalizeName(name);
|
|
170
|
+
const entry = this.store.get(normalized.key);
|
|
171
|
+
return entry ? entry.values.join(", ") : null;
|
|
172
|
+
}
|
|
173
|
+
has(name) {
|
|
174
|
+
const normalized = this.normalizeName(name);
|
|
175
|
+
return this.store.has(normalized.key);
|
|
176
|
+
}
|
|
177
|
+
delete(name) {
|
|
178
|
+
const normalized = this.normalizeName(name);
|
|
179
|
+
this.store.delete(normalized.key);
|
|
180
|
+
}
|
|
181
|
+
entries() {
|
|
182
|
+
return this[Symbol.iterator]();
|
|
183
|
+
}
|
|
184
|
+
*keys() {
|
|
185
|
+
for (const [name] of this) {
|
|
186
|
+
yield name;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
*values() {
|
|
190
|
+
for (const [, value] of this) {
|
|
191
|
+
yield value;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
forEach(callback, thisArg) {
|
|
195
|
+
for (const [name, value] of this) {
|
|
196
|
+
callback.call(thisArg, value, name, this);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
[Symbol.iterator]() {
|
|
200
|
+
const generator = function* (store) {
|
|
201
|
+
for (const entry of store.values()) {
|
|
202
|
+
yield [entry.name, entry.values.join(", ")];
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
return generator(this.store);
|
|
206
|
+
}
|
|
207
|
+
toObject() {
|
|
208
|
+
const result = {};
|
|
209
|
+
for (const [name, value] of this) {
|
|
210
|
+
result[name] = value;
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.Headers = Headers;
|
|
216
|
+
function cloneNativeResponse(payload) {
|
|
217
|
+
return {
|
|
218
|
+
status: payload.status,
|
|
219
|
+
headers: { ...payload.headers },
|
|
220
|
+
body: payload.body,
|
|
221
|
+
cookies: { ...payload.cookies },
|
|
222
|
+
url: payload.url,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
class Response {
|
|
226
|
+
constructor(payload, requestUrl) {
|
|
227
|
+
this.type = "basic";
|
|
228
|
+
this.bodyUsed = false;
|
|
229
|
+
this.payload = cloneNativeResponse(payload);
|
|
230
|
+
this.requestUrl = requestUrl;
|
|
231
|
+
this.status = payload.status;
|
|
232
|
+
this.statusText = node_http_1.STATUS_CODES[payload.status] ?? "";
|
|
233
|
+
this.ok = this.status >= 200 && this.status < 300;
|
|
234
|
+
this.headers = new Headers(payload.headers);
|
|
235
|
+
this.url = payload.url;
|
|
236
|
+
this.redirected = this.url !== requestUrl;
|
|
237
|
+
this.cookies = { ...payload.cookies };
|
|
238
|
+
this.body = payload.body;
|
|
239
|
+
}
|
|
240
|
+
async json() {
|
|
241
|
+
const text = await this.text();
|
|
242
|
+
return JSON.parse(text);
|
|
243
|
+
}
|
|
244
|
+
async text() {
|
|
245
|
+
this.assertBodyAvailable();
|
|
246
|
+
this.bodyUsed = true;
|
|
247
|
+
return this.body;
|
|
248
|
+
}
|
|
249
|
+
clone() {
|
|
250
|
+
if (this.bodyUsed) {
|
|
251
|
+
throw new TypeError("Cannot clone a Response whose body is already used");
|
|
252
|
+
}
|
|
253
|
+
return new Response(cloneNativeResponse(this.payload), this.requestUrl);
|
|
254
|
+
}
|
|
255
|
+
assertBodyAvailable() {
|
|
256
|
+
if (this.bodyUsed) {
|
|
257
|
+
throw new TypeError("Response body is already used");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
exports.Response = Response;
|
|
262
|
+
class Session {
|
|
263
|
+
constructor(id, defaults) {
|
|
264
|
+
this.disposed = false;
|
|
265
|
+
this.id = id;
|
|
266
|
+
this.defaults = defaults;
|
|
267
|
+
}
|
|
268
|
+
get closed() {
|
|
269
|
+
return this.disposed;
|
|
270
|
+
}
|
|
271
|
+
ensureActive() {
|
|
272
|
+
if (this.disposed) {
|
|
273
|
+
throw new types_1.RequestError("Session has been closed");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
enforceBrowser(browser) {
|
|
277
|
+
const resolved = browser ?? this.defaults.browser;
|
|
278
|
+
if (resolved !== this.defaults.browser) {
|
|
279
|
+
throw new types_1.RequestError("Session browser cannot be changed after creation");
|
|
280
|
+
}
|
|
281
|
+
return resolved;
|
|
282
|
+
}
|
|
283
|
+
enforceProxy(proxy) {
|
|
284
|
+
if (proxy === undefined) {
|
|
285
|
+
return this.defaults.proxy;
|
|
286
|
+
}
|
|
287
|
+
if ((this.defaults.proxy ?? null) !== (proxy ?? null)) {
|
|
288
|
+
throw new types_1.RequestError("Session proxy cannot be changed after creation");
|
|
289
|
+
}
|
|
290
|
+
return proxy;
|
|
291
|
+
}
|
|
292
|
+
async fetch(input, init) {
|
|
293
|
+
this.ensureActive();
|
|
294
|
+
const config = {
|
|
295
|
+
...(init ?? {}),
|
|
296
|
+
session: this,
|
|
297
|
+
cookieMode: "session",
|
|
298
|
+
};
|
|
299
|
+
config.browser = this.enforceBrowser(config.browser);
|
|
300
|
+
const proxy = this.enforceProxy(config.proxy);
|
|
301
|
+
if (proxy !== undefined || config.proxy !== undefined) {
|
|
302
|
+
if (proxy === undefined) {
|
|
303
|
+
delete config.proxy;
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
config.proxy = proxy;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (config.timeout === undefined && this.defaults.timeout !== undefined) {
|
|
310
|
+
config.timeout = this.defaults.timeout;
|
|
311
|
+
}
|
|
312
|
+
return fetch(input, config);
|
|
313
|
+
}
|
|
314
|
+
async clearCookies() {
|
|
315
|
+
this.ensureActive();
|
|
316
|
+
try {
|
|
317
|
+
nativeBinding.clearSession(this.id);
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
throw new types_1.RequestError(String(error));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async close() {
|
|
324
|
+
if (this.disposed) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
this.disposed = true;
|
|
328
|
+
try {
|
|
329
|
+
nativeBinding.dropSession(this.id);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
throw new types_1.RequestError(String(error));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
exports.Session = Session;
|
|
337
|
+
function resolveSessionContext(config) {
|
|
338
|
+
const requestedMode = config.cookieMode ?? "ephemeral";
|
|
339
|
+
const sessionCandidate = config.session;
|
|
340
|
+
const providedSessionId = typeof config.sessionId === "string" ? config.sessionId.trim() : undefined;
|
|
341
|
+
if (sessionCandidate && providedSessionId) {
|
|
342
|
+
throw new types_1.RequestError("Provide either `session` or `sessionId`, not both.");
|
|
343
|
+
}
|
|
344
|
+
if (sessionCandidate) {
|
|
345
|
+
if (!(sessionCandidate instanceof Session)) {
|
|
346
|
+
throw new types_1.RequestError("`session` must be created via createSession()");
|
|
347
|
+
}
|
|
348
|
+
if (sessionCandidate.closed) {
|
|
349
|
+
throw new types_1.RequestError("Session has been closed");
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
sessionId: sessionCandidate.id,
|
|
353
|
+
cookieMode: "session",
|
|
354
|
+
dropAfterRequest: false,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
if (providedSessionId) {
|
|
358
|
+
if (!providedSessionId) {
|
|
359
|
+
throw new types_1.RequestError("sessionId must not be empty");
|
|
360
|
+
}
|
|
361
|
+
if (requestedMode === "ephemeral") {
|
|
362
|
+
throw new types_1.RequestError("cookieMode 'ephemeral' cannot be combined with sessionId");
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
sessionId: providedSessionId,
|
|
366
|
+
cookieMode: "session",
|
|
367
|
+
dropAfterRequest: false,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if (requestedMode === "session") {
|
|
371
|
+
throw new types_1.RequestError("cookieMode 'session' requires a session or sessionId");
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
sessionId: generateSessionId(),
|
|
375
|
+
cookieMode: "ephemeral",
|
|
376
|
+
dropAfterRequest: true,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function createAbortError(reason) {
|
|
380
|
+
const fallbackMessage = typeof reason === "string" ? reason : "The operation was aborted";
|
|
381
|
+
if (typeof DOMException !== "undefined" && reason instanceof DOMException) {
|
|
382
|
+
return reason.name === "AbortError" ? reason : new DOMException(reason.message || fallbackMessage, "AbortError");
|
|
383
|
+
}
|
|
384
|
+
if (reason instanceof Error) {
|
|
385
|
+
reason.name = "AbortError";
|
|
386
|
+
return reason;
|
|
387
|
+
}
|
|
388
|
+
if (typeof DOMException !== "undefined") {
|
|
389
|
+
return new DOMException(fallbackMessage, "AbortError");
|
|
390
|
+
}
|
|
391
|
+
const error = new Error(fallbackMessage);
|
|
392
|
+
error.name = "AbortError";
|
|
393
|
+
return error;
|
|
394
|
+
}
|
|
395
|
+
function isAbortError(error) {
|
|
396
|
+
return Boolean(error) && typeof error.name === "string" && error.name === "AbortError";
|
|
397
|
+
}
|
|
398
|
+
function setupAbort(signal) {
|
|
399
|
+
if (!signal) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
if (signal.aborted) {
|
|
403
|
+
throw createAbortError(signal.reason);
|
|
404
|
+
}
|
|
405
|
+
let onAbort;
|
|
406
|
+
const promise = new Promise((_, reject) => {
|
|
407
|
+
onAbort = () => {
|
|
408
|
+
reject(createAbortError(signal.reason));
|
|
409
|
+
};
|
|
410
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
411
|
+
});
|
|
412
|
+
const cleanup = () => {
|
|
413
|
+
if (onAbort) {
|
|
414
|
+
signal.removeEventListener("abort", onAbort);
|
|
415
|
+
onAbort = undefined;
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
return { promise, cleanup };
|
|
419
|
+
}
|
|
420
|
+
function normalizeUrlInput(input) {
|
|
421
|
+
const value = typeof input === "string" ? input : input.toString();
|
|
422
|
+
if (!value) {
|
|
423
|
+
throw new types_1.RequestError("URL is required");
|
|
424
|
+
}
|
|
425
|
+
try {
|
|
426
|
+
return new URL(value).toString();
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
throw new types_1.RequestError(`Invalid URL: ${value}`);
|
|
430
|
+
}
|
|
53
431
|
}
|
|
54
|
-
|
|
55
|
-
|
|
432
|
+
function validateRedirectMode(mode) {
|
|
433
|
+
if (!mode || mode === "follow") {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
throw new types_1.RequestError(`Redirect mode '${mode}' is not supported`);
|
|
437
|
+
}
|
|
438
|
+
function serializeBody(body) {
|
|
439
|
+
if (body === null || body === undefined) {
|
|
440
|
+
return undefined;
|
|
441
|
+
}
|
|
442
|
+
if (typeof body === "string") {
|
|
443
|
+
return body;
|
|
444
|
+
}
|
|
445
|
+
if (Buffer.isBuffer(body)) {
|
|
446
|
+
return body.toString();
|
|
447
|
+
}
|
|
448
|
+
if (body instanceof URLSearchParams) {
|
|
449
|
+
return body.toString();
|
|
450
|
+
}
|
|
451
|
+
if (body instanceof ArrayBuffer) {
|
|
452
|
+
return Buffer.from(body).toString();
|
|
453
|
+
}
|
|
454
|
+
if (ArrayBuffer.isView(body)) {
|
|
455
|
+
return Buffer.from(body.buffer, body.byteOffset, body.byteLength).toString();
|
|
456
|
+
}
|
|
457
|
+
throw new TypeError("Unsupported body type; expected string, Buffer, ArrayBuffer, or URLSearchParams");
|
|
458
|
+
}
|
|
459
|
+
const SUPPORTED_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"];
|
|
460
|
+
function ensureMethod(method) {
|
|
461
|
+
const normalized = method?.trim().toUpperCase();
|
|
462
|
+
return normalized && normalized.length > 0 ? normalized : "GET";
|
|
463
|
+
}
|
|
464
|
+
function assertSupportedMethod(method) {
|
|
465
|
+
if (!SUPPORTED_METHODS.includes(method)) {
|
|
466
|
+
throw new types_1.RequestError(`Unsupported HTTP method: ${method}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function ensureBodyAllowed(method, body) {
|
|
470
|
+
if (!body) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (method === "GET" || method === "HEAD") {
|
|
474
|
+
throw new types_1.RequestError(`Request with ${method} method cannot have a body`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
function validateBrowserProfile(browser) {
|
|
478
|
+
if (!browser) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const profiles = getProfiles();
|
|
482
|
+
if (!profiles.includes(browser)) {
|
|
483
|
+
throw new types_1.RequestError(`Invalid browser profile: ${browser}. Available profiles: ${profiles.join(", ")}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async function dispatchRequest(options, requestUrl, signal) {
|
|
487
|
+
const abortHandler = setupAbort(signal);
|
|
488
|
+
const nativePromise = nativeBinding.request(options);
|
|
489
|
+
const pending = abortHandler ? Promise.race([nativePromise, abortHandler.promise]) : nativePromise;
|
|
490
|
+
let payload;
|
|
491
|
+
try {
|
|
492
|
+
payload = (await pending);
|
|
493
|
+
}
|
|
494
|
+
catch (error) {
|
|
495
|
+
if (isAbortError(error)) {
|
|
496
|
+
throw error;
|
|
497
|
+
}
|
|
498
|
+
if (error instanceof types_1.RequestError) {
|
|
499
|
+
throw error;
|
|
500
|
+
}
|
|
501
|
+
throw new types_1.RequestError(String(error));
|
|
502
|
+
}
|
|
503
|
+
finally {
|
|
504
|
+
abortHandler?.cleanup();
|
|
505
|
+
}
|
|
506
|
+
return new Response(payload, requestUrl);
|
|
56
507
|
}
|
|
57
508
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* @param options - Request options
|
|
61
|
-
* @returns Promise that resolves to the response
|
|
62
|
-
*
|
|
63
|
-
* @example
|
|
64
|
-
* ```typescript
|
|
65
|
-
* import { request } from 'wreq-js';
|
|
66
|
-
*
|
|
67
|
-
* const response = await request({
|
|
68
|
-
* url: 'https://example.com/api',
|
|
69
|
-
* browser: 'chrome_137',
|
|
70
|
-
* headers: {
|
|
71
|
-
* 'Custom-Header': 'value'
|
|
72
|
-
* }
|
|
73
|
-
* });
|
|
509
|
+
* Fetch-compatible entry point that adds browser impersonation controls.
|
|
74
510
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* ```
|
|
511
|
+
* @param input - Request URL (string or URL instance)
|
|
512
|
+
* @param init - Fetch-compatible init options
|
|
78
513
|
*/
|
|
79
|
-
async function
|
|
80
|
-
|
|
81
|
-
|
|
514
|
+
async function fetch(input, init) {
|
|
515
|
+
const url = normalizeUrlInput(input);
|
|
516
|
+
const config = init ?? {};
|
|
517
|
+
const sessionContext = resolveSessionContext(config);
|
|
518
|
+
validateRedirectMode(config.redirect);
|
|
519
|
+
validateBrowserProfile(config.browser);
|
|
520
|
+
const headers = new Headers(config.headers);
|
|
521
|
+
const method = ensureMethod(config.method);
|
|
522
|
+
assertSupportedMethod(method);
|
|
523
|
+
const body = serializeBody(config.body ?? null);
|
|
524
|
+
ensureBodyAllowed(method, body);
|
|
525
|
+
const headerRecord = headers.toObject();
|
|
526
|
+
const hasHeaders = Object.keys(headerRecord).length > 0;
|
|
527
|
+
const requestOptions = {
|
|
528
|
+
url,
|
|
529
|
+
method,
|
|
530
|
+
...(config.browser && { browser: config.browser }),
|
|
531
|
+
...(hasHeaders && { headers: headerRecord }),
|
|
532
|
+
...(body !== undefined && { body }),
|
|
533
|
+
...(config.proxy !== undefined && { proxy: config.proxy }),
|
|
534
|
+
...(config.timeout !== undefined && { timeout: config.timeout }),
|
|
535
|
+
sessionId: sessionContext.sessionId,
|
|
536
|
+
ephemeral: sessionContext.dropAfterRequest,
|
|
537
|
+
};
|
|
538
|
+
try {
|
|
539
|
+
return await dispatchRequest(requestOptions, url, config.signal ?? null);
|
|
82
540
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
541
|
+
finally {
|
|
542
|
+
if (sessionContext.dropAfterRequest) {
|
|
543
|
+
try {
|
|
544
|
+
nativeBinding.dropSession(sessionContext.sessionId);
|
|
545
|
+
}
|
|
546
|
+
catch {
|
|
547
|
+
// ignore cleanup errors for ephemeral sessions
|
|
548
|
+
}
|
|
87
549
|
}
|
|
88
550
|
}
|
|
551
|
+
}
|
|
552
|
+
async function createSession(options) {
|
|
553
|
+
const { sessionId, defaults } = normalizeSessionOptions(options);
|
|
554
|
+
validateBrowserProfile(defaults.browser);
|
|
555
|
+
let createdId;
|
|
89
556
|
try {
|
|
90
|
-
|
|
557
|
+
createdId = nativeBinding.createSession({
|
|
558
|
+
sessionId,
|
|
559
|
+
browser: defaults.browser,
|
|
560
|
+
...(defaults.proxy !== undefined && { proxy: defaults.proxy }),
|
|
561
|
+
});
|
|
91
562
|
}
|
|
92
563
|
catch (error) {
|
|
93
564
|
throw new types_1.RequestError(String(error));
|
|
94
565
|
}
|
|
566
|
+
return new Session(createdId, defaults);
|
|
567
|
+
}
|
|
568
|
+
async function withSession(fn, options) {
|
|
569
|
+
const session = await createSession(options);
|
|
570
|
+
try {
|
|
571
|
+
return await fn(session);
|
|
572
|
+
}
|
|
573
|
+
finally {
|
|
574
|
+
await session.close();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* @deprecated Use {@link fetch} instead.
|
|
579
|
+
*/
|
|
580
|
+
async function request(options) {
|
|
581
|
+
if (!options.url) {
|
|
582
|
+
throw new types_1.RequestError("URL is required");
|
|
583
|
+
}
|
|
584
|
+
const { url, ...rest } = options;
|
|
585
|
+
const init = {};
|
|
586
|
+
if (rest.method !== undefined) {
|
|
587
|
+
init.method = rest.method;
|
|
588
|
+
}
|
|
589
|
+
if (rest.headers !== undefined) {
|
|
590
|
+
init.headers = rest.headers;
|
|
591
|
+
}
|
|
592
|
+
if (rest.body !== undefined) {
|
|
593
|
+
init.body = rest.body;
|
|
594
|
+
}
|
|
595
|
+
if (rest.browser !== undefined) {
|
|
596
|
+
init.browser = rest.browser;
|
|
597
|
+
}
|
|
598
|
+
if (rest.proxy !== undefined) {
|
|
599
|
+
init.proxy = rest.proxy;
|
|
600
|
+
}
|
|
601
|
+
if (rest.timeout !== undefined) {
|
|
602
|
+
init.timeout = rest.timeout;
|
|
603
|
+
}
|
|
604
|
+
if (rest.sessionId !== undefined) {
|
|
605
|
+
init.sessionId = rest.sessionId;
|
|
606
|
+
}
|
|
607
|
+
return fetch(url, init);
|
|
95
608
|
}
|
|
96
609
|
/**
|
|
97
610
|
* Get list of available browser profiles
|
|
@@ -107,46 +620,27 @@ async function request(options) {
|
|
|
107
620
|
* ```
|
|
108
621
|
*/
|
|
109
622
|
function getProfiles() {
|
|
110
|
-
|
|
623
|
+
if (!cachedProfiles) {
|
|
624
|
+
cachedProfiles = nativeBinding.getProfiles();
|
|
625
|
+
}
|
|
626
|
+
return cachedProfiles;
|
|
111
627
|
}
|
|
112
628
|
/**
|
|
113
|
-
* Convenience
|
|
114
|
-
*
|
|
115
|
-
* @param url - URL to request
|
|
116
|
-
* @param options - Additional request options
|
|
117
|
-
* @returns Promise that resolves to the response
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* ```typescript
|
|
121
|
-
* import { get } from 'wreq-js';
|
|
122
|
-
*
|
|
123
|
-
* const response = await get('https://example.com/api');
|
|
124
|
-
* ```
|
|
629
|
+
* Convenience helper for GET requests using {@link fetch}.
|
|
125
630
|
*/
|
|
126
|
-
async function get(url,
|
|
127
|
-
return
|
|
631
|
+
async function get(url, init) {
|
|
632
|
+
return fetch(url, { ...(init ?? {}), method: "GET" });
|
|
128
633
|
}
|
|
129
634
|
/**
|
|
130
|
-
* Convenience
|
|
131
|
-
*
|
|
132
|
-
* @param url - URL to request
|
|
133
|
-
* @param body - Request body
|
|
134
|
-
* @param options - Additional request options
|
|
135
|
-
* @returns Promise that resolves to the response
|
|
136
|
-
*
|
|
137
|
-
* @example
|
|
138
|
-
* ```typescript
|
|
139
|
-
* import { post } from 'wreq-js';
|
|
140
|
-
*
|
|
141
|
-
* const response = await post(
|
|
142
|
-
* 'https://example.com/api',
|
|
143
|
-
* JSON.stringify({ foo: 'bar' }),
|
|
144
|
-
* { headers: { 'Content-Type': 'application/json' } }
|
|
145
|
-
* );
|
|
146
|
-
* ```
|
|
635
|
+
* Convenience helper for POST requests using {@link fetch}.
|
|
147
636
|
*/
|
|
148
|
-
async function post(url, body,
|
|
149
|
-
|
|
637
|
+
async function post(url, body, init) {
|
|
638
|
+
const config = {
|
|
639
|
+
...(init ?? {}),
|
|
640
|
+
method: "POST",
|
|
641
|
+
...(body !== undefined ? { body } : {}),
|
|
642
|
+
};
|
|
643
|
+
return fetch(url, config);
|
|
150
644
|
}
|
|
151
645
|
/**
|
|
152
646
|
* WebSocket connection class
|
|
@@ -157,7 +651,7 @@ async function post(url, body, options) {
|
|
|
157
651
|
*
|
|
158
652
|
* const ws = await websocket({
|
|
159
653
|
* url: 'wss://echo.websocket.org',
|
|
160
|
-
* browser: '
|
|
654
|
+
* browser: 'chrome_142',
|
|
161
655
|
* onMessage: (data) => {
|
|
162
656
|
* console.log('Received:', data);
|
|
163
657
|
* },
|
|
@@ -181,7 +675,12 @@ async function post(url, body, options) {
|
|
|
181
675
|
*/
|
|
182
676
|
class WebSocket {
|
|
183
677
|
constructor(connection) {
|
|
678
|
+
this._closed = false;
|
|
184
679
|
this._connection = connection;
|
|
680
|
+
if (websocketFinalizer) {
|
|
681
|
+
this._finalizerToken = connection;
|
|
682
|
+
websocketFinalizer.register(this, connection, connection);
|
|
683
|
+
}
|
|
185
684
|
}
|
|
186
685
|
/**
|
|
187
686
|
* Send a message (text or binary)
|
|
@@ -198,6 +697,14 @@ class WebSocket {
|
|
|
198
697
|
* Close the WebSocket connection
|
|
199
698
|
*/
|
|
200
699
|
async close() {
|
|
700
|
+
if (this._closed) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
this._closed = true;
|
|
704
|
+
if (this._finalizerToken && websocketFinalizer) {
|
|
705
|
+
websocketFinalizer.unregister(this._finalizerToken);
|
|
706
|
+
this._finalizerToken = undefined;
|
|
707
|
+
}
|
|
201
708
|
try {
|
|
202
709
|
await nativeBinding.websocketClose(this._connection);
|
|
203
710
|
}
|
|
@@ -215,26 +722,26 @@ exports.WebSocket = WebSocket;
|
|
|
215
722
|
*/
|
|
216
723
|
async function websocket(options) {
|
|
217
724
|
if (!options.url) {
|
|
218
|
-
throw new types_1.RequestError(
|
|
725
|
+
throw new types_1.RequestError("URL is required");
|
|
219
726
|
}
|
|
220
727
|
if (!options.onMessage) {
|
|
221
|
-
throw new types_1.RequestError(
|
|
728
|
+
throw new types_1.RequestError("onMessage callback is required");
|
|
222
729
|
}
|
|
223
730
|
if (options.browser) {
|
|
224
731
|
const profiles = getProfiles();
|
|
225
732
|
if (!profiles.includes(options.browser)) {
|
|
226
|
-
throw new types_1.RequestError(`Invalid browser profile: ${options.browser}. Available profiles: ${profiles.join(
|
|
733
|
+
throw new types_1.RequestError(`Invalid browser profile: ${options.browser}. Available profiles: ${profiles.join(", ")}`);
|
|
227
734
|
}
|
|
228
735
|
}
|
|
229
736
|
try {
|
|
230
737
|
const connection = await nativeBinding.websocketConnect({
|
|
231
738
|
url: options.url,
|
|
232
|
-
browser: options.browser ||
|
|
739
|
+
browser: options.browser || DEFAULT_BROWSER,
|
|
233
740
|
headers: options.headers || {},
|
|
234
|
-
proxy: options.proxy,
|
|
741
|
+
...(options.proxy !== undefined && { proxy: options.proxy }),
|
|
235
742
|
onMessage: options.onMessage,
|
|
236
|
-
onClose: options.onClose,
|
|
237
|
-
onError: options.onError,
|
|
743
|
+
...(options.onClose !== undefined && { onClose: options.onClose }),
|
|
744
|
+
...(options.onError !== undefined && { onError: options.onError }),
|
|
238
745
|
});
|
|
239
746
|
return new WebSocket(connection);
|
|
240
747
|
}
|
|
@@ -243,11 +750,17 @@ async function websocket(options) {
|
|
|
243
750
|
}
|
|
244
751
|
}
|
|
245
752
|
exports.default = {
|
|
753
|
+
fetch,
|
|
246
754
|
request,
|
|
247
755
|
get,
|
|
248
756
|
post,
|
|
249
757
|
getProfiles,
|
|
758
|
+
createSession,
|
|
759
|
+
withSession,
|
|
250
760
|
websocket,
|
|
251
761
|
WebSocket,
|
|
762
|
+
Headers,
|
|
763
|
+
Response,
|
|
764
|
+
Session,
|
|
252
765
|
};
|
|
253
766
|
//# sourceMappingURL=wreq-js.js.map
|