create-request 1.6.0 → 1.6.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/dist/library/BaseRequest.d.ts +27 -27
- package/dist/library/BodyRequest.d.ts +6 -7
- package/dist/library/RequestError.d.ts +11 -1
- package/dist/library/ResponseWrapper.d.ts +19 -8
- package/dist/library/enums.d.ts +67 -58
- package/dist/library/index.cjs +429 -595
- package/dist/library/index.cjs.map +1 -1
- package/dist/library/index.esm.js +421 -595
- package/dist/library/index.esm.js.map +1 -1
- package/dist/library/index.esm.min.js +1 -1
- package/dist/library/index.esm.min.js.map +1 -1
- package/dist/library/index.min.cjs +1 -1
- package/dist/library/index.min.cjs.map +1 -1
- package/dist/library/requestMethods.d.ts +7 -14
- package/dist/library/utils/Config.d.ts +11 -11
- package/package.json +1 -1
|
@@ -1,98 +1,99 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Enum for HTTP methods
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
})(HttpMethod || (HttpMethod = {}));
|
|
4
|
+
const HttpMethod = {
|
|
5
|
+
GET: "GET",
|
|
6
|
+
PUT: "PUT",
|
|
7
|
+
POST: "POST",
|
|
8
|
+
HEAD: "HEAD",
|
|
9
|
+
PATCH: "PATCH",
|
|
10
|
+
DELETE: "DELETE",
|
|
11
|
+
OPTIONS: "OPTIONS",
|
|
12
|
+
};
|
|
14
13
|
/**
|
|
15
14
|
* Enum for request priorities
|
|
16
15
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
})(RequestPriority || (RequestPriority = {}));
|
|
16
|
+
const RequestPriority = {
|
|
17
|
+
LOW: "low",
|
|
18
|
+
HIGH: "high",
|
|
19
|
+
AUTO: "auto",
|
|
20
|
+
};
|
|
23
21
|
/**
|
|
24
22
|
* Enum for credentials policies
|
|
25
23
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
})(CredentialsPolicy || (CredentialsPolicy = {}));
|
|
24
|
+
const CredentialsPolicy = {
|
|
25
|
+
OMIT: "omit",
|
|
26
|
+
INCLUDE: "include",
|
|
27
|
+
SAME_ORIGIN: "same-origin",
|
|
28
|
+
};
|
|
32
29
|
/**
|
|
33
30
|
* Enum for request modes
|
|
34
31
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
})(RequestMode || (RequestMode = {}));
|
|
32
|
+
const RequestMode = {
|
|
33
|
+
CORS: "cors",
|
|
34
|
+
NO_CORS: "no-cors",
|
|
35
|
+
SAME_ORIGIN: "same-origin",
|
|
36
|
+
NAVIGATE: "navigate",
|
|
37
|
+
};
|
|
42
38
|
/**
|
|
43
39
|
* Enum for redirect modes
|
|
44
40
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
})(RedirectMode || (RedirectMode = {}));
|
|
41
|
+
const RedirectMode = {
|
|
42
|
+
ERROR: "error",
|
|
43
|
+
FOLLOW: "follow",
|
|
44
|
+
MANUAL: "manual",
|
|
45
|
+
};
|
|
51
46
|
/**
|
|
52
47
|
* Enum for cookie SameSite policies
|
|
53
48
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
})(SameSitePolicy || (SameSitePolicy = {}));
|
|
49
|
+
const SameSitePolicy = {
|
|
50
|
+
LAX: "Lax",
|
|
51
|
+
NONE: "None",
|
|
52
|
+
STRICT: "Strict",
|
|
53
|
+
};
|
|
60
54
|
/**
|
|
61
55
|
* Enum for body types
|
|
62
56
|
*/
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
})(BodyType || (BodyType = {}));
|
|
57
|
+
const BodyType = {
|
|
58
|
+
JSON: "json",
|
|
59
|
+
STRING: "string",
|
|
60
|
+
BINARY: "binary",
|
|
61
|
+
};
|
|
69
62
|
/**
|
|
70
63
|
* Referrer policies for fetch requests
|
|
71
64
|
*/
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
})(ReferrerPolicy || (ReferrerPolicy = {}));
|
|
65
|
+
const ReferrerPolicy = {
|
|
66
|
+
ORIGIN: "origin",
|
|
67
|
+
UNSAFE_URL: "unsafe-url",
|
|
68
|
+
SAME_ORIGIN: "same-origin",
|
|
69
|
+
NO_REFERRER: "no-referrer",
|
|
70
|
+
STRICT_ORIGIN: "strict-origin",
|
|
71
|
+
ORIGIN_WHEN_CROSS_ORIGIN: "origin-when-cross-origin",
|
|
72
|
+
NO_REFERRER_WHEN_DOWNGRADE: "no-referrer-when-downgrade",
|
|
73
|
+
STRICT_ORIGIN_WHEN_CROSS_ORIGIN: "strict-origin-when-cross-origin",
|
|
74
|
+
};
|
|
83
75
|
/**
|
|
84
76
|
* Cache modes for fetch requests
|
|
85
77
|
*/
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
})(CacheMode || (CacheMode = {}));
|
|
78
|
+
const CacheMode = {
|
|
79
|
+
RELOAD: "reload",
|
|
80
|
+
DEFAULT: "default",
|
|
81
|
+
NO_CACHE: "no-cache",
|
|
82
|
+
NO_STORE: "no-store",
|
|
83
|
+
FORCE_CACHE: "force-cache",
|
|
84
|
+
ONLY_IF_CACHED: "only-if-cached",
|
|
85
|
+
};
|
|
95
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Extract a message from an unknown thrown value
|
|
89
|
+
* @internal
|
|
90
|
+
*/
|
|
91
|
+
const errorMessage = (e) => (e instanceof Error ? e.message : String(e));
|
|
92
|
+
/**
|
|
93
|
+
* Coerce an unknown thrown value to an Error
|
|
94
|
+
* @internal
|
|
95
|
+
*/
|
|
96
|
+
const toError = (e) => (e instanceof Error ? e : new Error(String(e)));
|
|
96
97
|
/**
|
|
97
98
|
* Error class for HTTP request failures.
|
|
98
99
|
* Extends the standard Error class with additional context about the failed request.
|
|
@@ -133,7 +134,7 @@ class RequestError extends Error {
|
|
|
133
134
|
/** Whether the request was aborted (cancelled) */
|
|
134
135
|
isAborted;
|
|
135
136
|
/** Cached result of parsing `body` as JSON (lazily populated by getJson) */
|
|
136
|
-
|
|
137
|
+
_parsed;
|
|
137
138
|
/**
|
|
138
139
|
* Creates a new RequestError instance.
|
|
139
140
|
*
|
|
@@ -186,15 +187,15 @@ class RequestError extends Error {
|
|
|
186
187
|
* ```
|
|
187
188
|
*/
|
|
188
189
|
getJson() {
|
|
189
|
-
if (this.
|
|
190
|
+
if (this._parsed === undefined && this.body) {
|
|
190
191
|
try {
|
|
191
|
-
this.
|
|
192
|
+
this._parsed = JSON.parse(this.body);
|
|
192
193
|
}
|
|
193
194
|
catch {
|
|
194
195
|
// Body is not valid JSON - leave parsedBody undefined
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
|
-
return this.
|
|
198
|
+
return this._parsed;
|
|
198
199
|
}
|
|
199
200
|
/**
|
|
200
201
|
* Safely reads the body of a Response as text without consuming it.
|
|
@@ -290,55 +291,29 @@ class RequestError extends Error {
|
|
|
290
291
|
let message = originalError.message;
|
|
291
292
|
// Check for Node.js error codes (e.g., from undici/dns errors)
|
|
292
293
|
const errorCode = originalError.code;
|
|
293
|
-
const errorName = originalError.name;
|
|
294
294
|
const stack = originalError.stack || "";
|
|
295
|
-
const errorMessageLower = message.toLowerCase();
|
|
296
295
|
// Check for timeout errors (Node.js/undici TimeoutError)
|
|
297
296
|
// Note: While explicit timeouts set via withTimeout() are handled in BaseRequest,
|
|
298
|
-
// this detection serves as a safety net for
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const isTimeoutError = errorName === "TimeoutError" ||
|
|
303
|
-
errorMessageLower.includes("timeout") ||
|
|
304
|
-
errorMessageLower.includes("aborted due to timeout") ||
|
|
297
|
+
// this detection serves as a safety net for timeout errors from external
|
|
298
|
+
// AbortControllers, other runtimes, and network-level timeouts (ETIMEDOUT).
|
|
299
|
+
const isTimeoutError = originalError.name === "TimeoutError" ||
|
|
300
|
+
message.toLowerCase().includes("timeout") ||
|
|
305
301
|
errorCode === "ETIMEDOUT" ||
|
|
306
302
|
stack.includes("TimeoutError") ||
|
|
307
303
|
stack.includes("timeout");
|
|
308
304
|
// If the error message is generic "fetch failed", provide more context
|
|
309
305
|
if (message === "fetch failed" || message === "Failed to fetch") {
|
|
310
306
|
// Check for DNS resolution errors
|
|
311
|
-
const isDnsError = errorCode === "ENOTFOUND" ||
|
|
312
|
-
errorCode === "EAI_AGAIN" ||
|
|
313
|
-
errorCode === "EAI_NODATA" ||
|
|
314
|
-
stack.includes("getaddrinfo") ||
|
|
315
|
-
stack.includes("ENOTFOUND") ||
|
|
316
|
-
stack.includes("EAI_AGAIN");
|
|
307
|
+
const isDnsError = errorCode === "ENOTFOUND" || errorCode === "EAI_AGAIN" || errorCode === "EAI_NODATA" || /getaddrinfo|ENOTFOUND|EAI_AGAIN/.test(stack);
|
|
317
308
|
// Check for connection errors (but not timeout errors)
|
|
318
309
|
const isConnectionError = !isTimeoutError && (errorCode === "ECONNREFUSED" || errorCode === "ECONNRESET" || stack.includes("ECONNREFUSED") || stack.includes("connect"));
|
|
319
|
-
|
|
320
|
-
message = `Timeout:${url}`;
|
|
321
|
-
}
|
|
322
|
-
else if (isDnsError) {
|
|
323
|
-
message = `DNS:${url}`;
|
|
324
|
-
}
|
|
325
|
-
else if (isConnectionError) {
|
|
326
|
-
message = `Conn:${url}`;
|
|
327
|
-
}
|
|
328
|
-
else {
|
|
329
|
-
message = `Net:${url}`;
|
|
330
|
-
}
|
|
310
|
+
message = (isTimeoutError ? "Timeout:" : isDnsError ? "DNS:" : isConnectionError ? "Conn:" : "Net:") + url;
|
|
331
311
|
}
|
|
332
|
-
const error = new RequestError(message, url, method, {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
// Create a proper RequestError stack trace, but append the original stack for debugging
|
|
336
|
-
// This way Node.js will show "RequestError: ..." instead of "TypeError: ..."
|
|
312
|
+
const error = new RequestError(message, url, method, isTimeoutError ? { isTimeout: true } : {});
|
|
313
|
+
// Create a proper RequestError stack trace, but append the original stack for
|
|
314
|
+
// debugging context, so Node.js shows "RequestError: ..." instead of "TypeError: ..."
|
|
337
315
|
if (originalError.stack) {
|
|
338
|
-
|
|
339
|
-
const currentStack = error.stack || "";
|
|
340
|
-
// Append the original error's stack as "Caused by:" for debugging context
|
|
341
|
-
error.stack = `${currentStack}\n\nCaused by: ${originalError.stack}`;
|
|
316
|
+
error.stack = `${error.stack || ""}\n\nCaused by: ${originalError.stack}`;
|
|
342
317
|
}
|
|
343
318
|
return error;
|
|
344
319
|
}
|
|
@@ -382,19 +357,19 @@ class ResponseWrapper {
|
|
|
382
357
|
url;
|
|
383
358
|
/** The HTTP method that was used (if available) */
|
|
384
359
|
method;
|
|
385
|
-
|
|
386
|
-
|
|
360
|
+
_res;
|
|
361
|
+
_gqlOpts;
|
|
387
362
|
// Cache the body as the last used method
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
363
|
+
_blob;
|
|
364
|
+
_text;
|
|
365
|
+
_json;
|
|
366
|
+
_buf;
|
|
392
367
|
constructor(response, url, method, graphQLOptions) {
|
|
393
|
-
this.
|
|
368
|
+
this._res = response;
|
|
394
369
|
this.url = url;
|
|
395
370
|
this.method = method;
|
|
396
371
|
if (graphQLOptions) {
|
|
397
|
-
this.
|
|
372
|
+
this._gqlOpts = {
|
|
398
373
|
throwOnError: graphQLOptions.throwOnError,
|
|
399
374
|
};
|
|
400
375
|
}
|
|
@@ -403,43 +378,65 @@ class ResponseWrapper {
|
|
|
403
378
|
* HTTP status code (e.g., 200, 404, 500)
|
|
404
379
|
*/
|
|
405
380
|
get status() {
|
|
406
|
-
return this.
|
|
381
|
+
return this._res.status;
|
|
407
382
|
}
|
|
408
383
|
/**
|
|
409
384
|
* HTTP status text (e.g., "OK", "Not Found", "Internal Server Error")
|
|
410
385
|
*/
|
|
411
386
|
get statusText() {
|
|
412
|
-
return this.
|
|
387
|
+
return this._res.statusText;
|
|
413
388
|
}
|
|
414
389
|
/**
|
|
415
390
|
* Response headers as a Headers object
|
|
416
391
|
*/
|
|
417
392
|
get headers() {
|
|
418
|
-
return this.
|
|
393
|
+
return this._res.headers;
|
|
419
394
|
}
|
|
420
395
|
/**
|
|
421
396
|
* Whether the response status is in the 200-299 range (successful)
|
|
422
397
|
*/
|
|
423
398
|
get ok() {
|
|
424
|
-
return this.
|
|
399
|
+
return this._res.ok;
|
|
425
400
|
}
|
|
426
401
|
/**
|
|
427
402
|
* The raw Response object from the fetch API.
|
|
428
403
|
* Use this if you need direct access to the underlying Response.
|
|
429
404
|
*/
|
|
430
405
|
get raw() {
|
|
431
|
-
return this.
|
|
406
|
+
return this._res;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Create a RequestError carrying this response's context
|
|
410
|
+
* @param message - The error message
|
|
411
|
+
* @param withBody - Whether to attach the cached body text to the error
|
|
412
|
+
*/
|
|
413
|
+
_err(message, withBody) {
|
|
414
|
+
return new RequestError(message, this.url || "", this.method || "", {
|
|
415
|
+
status: this._res.status,
|
|
416
|
+
response: this._res,
|
|
417
|
+
body: withBody ? this._text : undefined,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Read the response body via the given reader, wrapping failures in a RequestError
|
|
422
|
+
* @throws RequestError if the body has already been consumed or reading fails
|
|
423
|
+
*/
|
|
424
|
+
async _read(reader) {
|
|
425
|
+
this._checkUsed();
|
|
426
|
+
try {
|
|
427
|
+
return await reader();
|
|
428
|
+
}
|
|
429
|
+
catch (e) {
|
|
430
|
+
throw this._err(`Read: ${errorMessage(e)}`);
|
|
431
|
+
}
|
|
432
432
|
}
|
|
433
433
|
/**
|
|
434
434
|
* Check if the response body has already been consumed and throw an error if so
|
|
435
435
|
* @throws RequestError if the body has already been consumed
|
|
436
436
|
*/
|
|
437
|
-
|
|
438
|
-
if (this.
|
|
439
|
-
throw
|
|
440
|
-
status: this.response.status,
|
|
441
|
-
response: this.response,
|
|
442
|
-
});
|
|
437
|
+
_checkUsed() {
|
|
438
|
+
if (this._res.bodyUsed) {
|
|
439
|
+
throw this._err("Body used");
|
|
443
440
|
}
|
|
444
441
|
}
|
|
445
442
|
/**
|
|
@@ -447,8 +444,8 @@ class ResponseWrapper {
|
|
|
447
444
|
* @param data - The parsed JSON data
|
|
448
445
|
* @throws RequestError if GraphQL response contains errors and throwOnError is enabled
|
|
449
446
|
*/
|
|
450
|
-
|
|
451
|
-
if (!this.
|
|
447
|
+
_checkGql(data) {
|
|
448
|
+
if (!this._gqlOpts?.throwOnError || typeof data !== "object" || data === null)
|
|
452
449
|
return;
|
|
453
450
|
const responseData = data;
|
|
454
451
|
if (!Array.isArray(responseData.errors) || responseData.errors.length === 0)
|
|
@@ -477,12 +474,7 @@ class ResponseWrapper {
|
|
|
477
474
|
}
|
|
478
475
|
return String(x);
|
|
479
476
|
});
|
|
480
|
-
|
|
481
|
-
throw new RequestError(`GQL: ${errorMessage}`, this.url || "", this.method || "", {
|
|
482
|
-
status: this.response.status,
|
|
483
|
-
response: this.response,
|
|
484
|
-
body: this.cachedText,
|
|
485
|
-
});
|
|
477
|
+
throw this._err(`GQL: ${errorMessages.join(", ")}`, true);
|
|
486
478
|
}
|
|
487
479
|
/**
|
|
488
480
|
* Parse the response body as JSON
|
|
@@ -511,39 +503,35 @@ class ResponseWrapper {
|
|
|
511
503
|
* }
|
|
512
504
|
*/
|
|
513
505
|
async getJson() {
|
|
514
|
-
if (this.
|
|
515
|
-
return this.
|
|
506
|
+
if (this._json !== undefined)
|
|
507
|
+
return this._json;
|
|
516
508
|
// Handle empty responses: 204 No Content or content-length: 0
|
|
517
|
-
const contentLength = this.
|
|
518
|
-
if (this.
|
|
519
|
-
this.
|
|
509
|
+
const contentLength = this._res.headers.get("content-length");
|
|
510
|
+
if (this._res.status === 204 || contentLength === "0") {
|
|
511
|
+
this._json = null;
|
|
520
512
|
return null;
|
|
521
513
|
}
|
|
522
|
-
this.
|
|
514
|
+
this._checkUsed();
|
|
523
515
|
try {
|
|
524
516
|
// Read as text first to handle empty bodies and cache for getText()
|
|
525
|
-
const text = await this.
|
|
526
|
-
this.
|
|
517
|
+
const text = await this._res.text();
|
|
518
|
+
this._text = text;
|
|
527
519
|
// Handle empty or whitespace-only responses
|
|
528
520
|
if (!text || text.trim() === "") {
|
|
529
|
-
this.
|
|
521
|
+
this._json = null;
|
|
530
522
|
return null;
|
|
531
523
|
}
|
|
532
524
|
// Parse the text as JSON
|
|
533
525
|
const parsed = JSON.parse(text);
|
|
534
|
-
this.
|
|
535
|
-
this.
|
|
526
|
+
this._json = parsed;
|
|
527
|
+
this._checkGql(parsed);
|
|
536
528
|
return parsed;
|
|
537
529
|
}
|
|
538
530
|
catch (error) {
|
|
539
531
|
if (error instanceof RequestError) {
|
|
540
532
|
throw error;
|
|
541
533
|
}
|
|
542
|
-
throw
|
|
543
|
-
status: this.response.status,
|
|
544
|
-
response: this.response,
|
|
545
|
-
body: this.cachedText,
|
|
546
|
-
});
|
|
534
|
+
throw this._err(`Bad JSON: ${errorMessage(error)}`, true);
|
|
547
535
|
}
|
|
548
536
|
}
|
|
549
537
|
/**
|
|
@@ -560,20 +548,9 @@ class ResponseWrapper {
|
|
|
560
548
|
* ```
|
|
561
549
|
*/
|
|
562
550
|
async getText() {
|
|
563
|
-
if (this.
|
|
564
|
-
return this.
|
|
565
|
-
this.
|
|
566
|
-
try {
|
|
567
|
-
const text = await this.response.text();
|
|
568
|
-
this.cachedText = text;
|
|
569
|
-
return text;
|
|
570
|
-
}
|
|
571
|
-
catch (e) {
|
|
572
|
-
throw new RequestError(`Read: ${e instanceof Error ? e.message : String(e)}`, this.url || "", this.method || "", {
|
|
573
|
-
status: this.response.status,
|
|
574
|
-
response: this.response,
|
|
575
|
-
});
|
|
576
|
-
}
|
|
551
|
+
if (this._text !== undefined)
|
|
552
|
+
return this._text;
|
|
553
|
+
return (this._text = await this._read(() => this._res.text()));
|
|
577
554
|
}
|
|
578
555
|
/**
|
|
579
556
|
* Get the response body as a Blob.
|
|
@@ -591,20 +568,9 @@ class ResponseWrapper {
|
|
|
591
568
|
* ```
|
|
592
569
|
*/
|
|
593
570
|
async getBlob() {
|
|
594
|
-
if (this.
|
|
595
|
-
return this.
|
|
596
|
-
this.
|
|
597
|
-
try {
|
|
598
|
-
const blob = await this.response.blob();
|
|
599
|
-
this.cachedBlob = blob;
|
|
600
|
-
return blob;
|
|
601
|
-
}
|
|
602
|
-
catch (e) {
|
|
603
|
-
throw new RequestError(`Read: ${e instanceof Error ? e.message : String(e)}`, this.url || "", this.method || "", {
|
|
604
|
-
status: this.response.status,
|
|
605
|
-
response: this.response,
|
|
606
|
-
});
|
|
607
|
-
}
|
|
571
|
+
if (this._blob !== undefined)
|
|
572
|
+
return this._blob;
|
|
573
|
+
return (this._blob = await this._read(() => this._res.blob()));
|
|
608
574
|
}
|
|
609
575
|
/**
|
|
610
576
|
* Get the response body as an ArrayBuffer.
|
|
@@ -622,21 +588,10 @@ class ResponseWrapper {
|
|
|
622
588
|
* ```
|
|
623
589
|
*/
|
|
624
590
|
async getArrayBuffer() {
|
|
625
|
-
if (this.
|
|
626
|
-
return this.
|
|
627
|
-
}
|
|
628
|
-
this.checkBodyNotConsumed();
|
|
629
|
-
try {
|
|
630
|
-
const arrayBuffer = await this.response.arrayBuffer();
|
|
631
|
-
this.cachedArrayBuffer = arrayBuffer;
|
|
632
|
-
return arrayBuffer;
|
|
633
|
-
}
|
|
634
|
-
catch (e) {
|
|
635
|
-
throw new RequestError(`Read: ${e instanceof Error ? e.message : String(e)}`, this.url || "", this.method || "", {
|
|
636
|
-
status: this.response.status,
|
|
637
|
-
response: this.response,
|
|
638
|
-
});
|
|
591
|
+
if (this._buf !== undefined) {
|
|
592
|
+
return this._buf;
|
|
639
593
|
}
|
|
594
|
+
return (this._buf = await this._read(() => this._res.arrayBuffer()));
|
|
640
595
|
}
|
|
641
596
|
/**
|
|
642
597
|
* Get the raw response body as a ReadableStream
|
|
@@ -654,8 +609,8 @@ class ResponseWrapper {
|
|
|
654
609
|
* }
|
|
655
610
|
*/
|
|
656
611
|
getBody() {
|
|
657
|
-
this.
|
|
658
|
-
return this.
|
|
612
|
+
this._checkUsed();
|
|
613
|
+
return this._res.body;
|
|
659
614
|
}
|
|
660
615
|
/**
|
|
661
616
|
* Extract specific data using a selector function
|
|
@@ -705,16 +660,11 @@ class ResponseWrapper {
|
|
|
705
660
|
}
|
|
706
661
|
// Enhance selector errors with context
|
|
707
662
|
if (selector) {
|
|
708
|
-
throw
|
|
709
|
-
status: this.response.status,
|
|
710
|
-
response: this.response,
|
|
711
|
-
body: this.cachedText,
|
|
712
|
-
});
|
|
663
|
+
throw this._err(`Selector: ${errorMessage(error)}`, true);
|
|
713
664
|
}
|
|
714
665
|
// If we get here and it's not a RequestError, wrap it
|
|
715
666
|
// This should rarely happen as getJson() should throw RequestError
|
|
716
|
-
|
|
717
|
-
throw RequestError.networkError(this.url || "", this.method || "", errorObj);
|
|
667
|
+
throw RequestError.networkError(this.url || "", this.method || "", toError(error));
|
|
718
668
|
}
|
|
719
669
|
}
|
|
720
670
|
}
|
|
@@ -726,20 +676,9 @@ class CookieUtils {
|
|
|
726
676
|
* @returns Formatted cookie string for the Cookie header
|
|
727
677
|
*/
|
|
728
678
|
static formatRequestCookies(cookies) {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
if (typeof valueOrOptions === "string") {
|
|
733
|
-
value = valueOrOptions;
|
|
734
|
-
}
|
|
735
|
-
else {
|
|
736
|
-
// Extract value from options object without validation
|
|
737
|
-
value = valueOrOptions.value;
|
|
738
|
-
}
|
|
739
|
-
// Add the cookie to the request
|
|
740
|
-
cookiePairs.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
|
|
741
|
-
});
|
|
742
|
-
return cookiePairs.join("; ");
|
|
679
|
+
return Object.entries(cookies)
|
|
680
|
+
.map(([name, valueOrOptions]) => `${encodeURIComponent(name)}=${encodeURIComponent(typeof valueOrOptions === "string" ? valueOrOptions : valueOrOptions.value)}`)
|
|
681
|
+
.join("; ");
|
|
743
682
|
}
|
|
744
683
|
}
|
|
745
684
|
|
|
@@ -793,17 +732,11 @@ class CsrfUtils {
|
|
|
793
732
|
// If token is longer than 10 chars, perform additional security checks
|
|
794
733
|
if (token.length > 10) {
|
|
795
734
|
// Check for valid character set (alphanumeric & common token symbols)
|
|
796
|
-
|
|
797
|
-
if (!validTokenRegex.test(token)) {
|
|
735
|
+
if (!/^[A-Za-z0-9\-_=+/.]+$/.test(token)) {
|
|
798
736
|
return false;
|
|
799
737
|
}
|
|
800
738
|
// For longer tokens, check for sufficient entropy (at least 2 character types)
|
|
801
|
-
|
|
802
|
-
const hasLowerCase = /[a-z]/.test(token);
|
|
803
|
-
const hasNumbers = /[0-9]/.test(token);
|
|
804
|
-
const hasSpecials = /[-_=+/.]/.test(token);
|
|
805
|
-
const characterTypesCount = [hasUpperCase, hasLowerCase, hasNumbers, hasSpecials].filter(Boolean).length;
|
|
806
|
-
return characterTypesCount >= 2;
|
|
739
|
+
return [/[A-Z]/, /[a-z]/, /[0-9]/, /[-_=+/.]/].filter(re => re.test(token)).length >= 2;
|
|
807
740
|
}
|
|
808
741
|
// For shorter tokens (8-10 chars), just return true if we reached here
|
|
809
742
|
return true;
|
|
@@ -814,19 +747,19 @@ class CsrfUtils {
|
|
|
814
747
|
* Global configuration for create-request
|
|
815
748
|
*/
|
|
816
749
|
class Config {
|
|
817
|
-
static
|
|
750
|
+
static _instance;
|
|
818
751
|
// CSRF configuration
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
752
|
+
_csrfHeader = "X-CSRF-Token";
|
|
753
|
+
_xsrfCookie = "XSRF-TOKEN";
|
|
754
|
+
_xsrfHeader = "X-XSRF-TOKEN";
|
|
755
|
+
_csrfToken = null;
|
|
756
|
+
_autoXsrf = true;
|
|
757
|
+
_antiCsrf = true; // X-Requested-With header
|
|
825
758
|
// Interceptor configuration
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
759
|
+
_reqI = [];
|
|
760
|
+
_resI = [];
|
|
761
|
+
_errI = [];
|
|
762
|
+
_nextId = 1;
|
|
830
763
|
constructor() { }
|
|
831
764
|
/**
|
|
832
765
|
* Get the singleton instance of the Config class
|
|
@@ -838,10 +771,10 @@ class Config {
|
|
|
838
771
|
* config.setCsrfToken('token123');
|
|
839
772
|
*/
|
|
840
773
|
static getInstance() {
|
|
841
|
-
if (!Config.
|
|
842
|
-
Config.
|
|
774
|
+
if (!Config._instance) {
|
|
775
|
+
Config._instance = new Config();
|
|
843
776
|
}
|
|
844
|
-
return Config.
|
|
777
|
+
return Config._instance;
|
|
845
778
|
}
|
|
846
779
|
/**
|
|
847
780
|
* Set a global CSRF token to be used for all requests
|
|
@@ -853,7 +786,7 @@ class Config {
|
|
|
853
786
|
* Config.getInstance().setCsrfToken('myToken123');
|
|
854
787
|
*/
|
|
855
788
|
setCsrfToken(token) {
|
|
856
|
-
this.
|
|
789
|
+
this._csrfToken = token;
|
|
857
790
|
return this;
|
|
858
791
|
}
|
|
859
792
|
/**
|
|
@@ -862,7 +795,7 @@ class Config {
|
|
|
862
795
|
* @returns The current CSRF token or null if not set
|
|
863
796
|
*/
|
|
864
797
|
getCsrfToken() {
|
|
865
|
-
return this.
|
|
798
|
+
return this._csrfToken;
|
|
866
799
|
}
|
|
867
800
|
/**
|
|
868
801
|
* Set the CSRF header name used when sending the token
|
|
@@ -874,7 +807,7 @@ class Config {
|
|
|
874
807
|
* Config.getInstance().setCsrfHeaderName('X-My-CSRF-Token');
|
|
875
808
|
*/
|
|
876
809
|
setCsrfHeaderName(name) {
|
|
877
|
-
this.
|
|
810
|
+
this._csrfHeader = name;
|
|
878
811
|
return this;
|
|
879
812
|
}
|
|
880
813
|
/**
|
|
@@ -883,7 +816,7 @@ class Config {
|
|
|
883
816
|
* @returns The current CSRF header name
|
|
884
817
|
*/
|
|
885
818
|
getCsrfHeaderName() {
|
|
886
|
-
return this.
|
|
819
|
+
return this._csrfHeader;
|
|
887
820
|
}
|
|
888
821
|
/**
|
|
889
822
|
* Set the XSRF cookie name to look for when extracting tokens from cookies
|
|
@@ -895,7 +828,7 @@ class Config {
|
|
|
895
828
|
* Config.getInstance().setXsrfCookieName('MY-XSRF-COOKIE');
|
|
896
829
|
*/
|
|
897
830
|
setXsrfCookieName(name) {
|
|
898
|
-
this.
|
|
831
|
+
this._xsrfCookie = name;
|
|
899
832
|
return this;
|
|
900
833
|
}
|
|
901
834
|
/**
|
|
@@ -904,7 +837,7 @@ class Config {
|
|
|
904
837
|
* @returns The current XSRF cookie name
|
|
905
838
|
*/
|
|
906
839
|
getXsrfCookieName() {
|
|
907
|
-
return this.
|
|
840
|
+
return this._xsrfCookie;
|
|
908
841
|
}
|
|
909
842
|
/**
|
|
910
843
|
* Set the XSRF header name for sending tokens extracted from cookies
|
|
@@ -913,7 +846,7 @@ class Config {
|
|
|
913
846
|
* @returns The config instance for chaining
|
|
914
847
|
*/
|
|
915
848
|
setXsrfHeaderName(name) {
|
|
916
|
-
this.
|
|
849
|
+
this._xsrfHeader = name;
|
|
917
850
|
return this;
|
|
918
851
|
}
|
|
919
852
|
/**
|
|
@@ -922,7 +855,7 @@ class Config {
|
|
|
922
855
|
* @returns The current XSRF header name
|
|
923
856
|
*/
|
|
924
857
|
getXsrfHeaderName() {
|
|
925
|
-
return this.
|
|
858
|
+
return this._xsrfHeader;
|
|
926
859
|
}
|
|
927
860
|
/**
|
|
928
861
|
* Enable or disable automatic extraction of XSRF tokens from cookies
|
|
@@ -936,7 +869,7 @@ class Config {
|
|
|
936
869
|
* Config.getInstance().setEnableAutoXsrf(false); // Disable XSRF extraction
|
|
937
870
|
*/
|
|
938
871
|
setEnableAutoXsrf(enable) {
|
|
939
|
-
this.
|
|
872
|
+
this._autoXsrf = enable;
|
|
940
873
|
return this;
|
|
941
874
|
}
|
|
942
875
|
/**
|
|
@@ -945,7 +878,7 @@ class Config {
|
|
|
945
878
|
* @returns True if automatic XSRF is enabled
|
|
946
879
|
*/
|
|
947
880
|
isAutoXsrfEnabled() {
|
|
948
|
-
return this.
|
|
881
|
+
return this._autoXsrf;
|
|
949
882
|
}
|
|
950
883
|
/**
|
|
951
884
|
* Enable or disable automatic addition of anti-CSRF headers
|
|
@@ -955,7 +888,7 @@ class Config {
|
|
|
955
888
|
* @returns The config instance for chaining
|
|
956
889
|
*/
|
|
957
890
|
setEnableAntiCsrf(enable) {
|
|
958
|
-
this.
|
|
891
|
+
this._antiCsrf = enable;
|
|
959
892
|
return this;
|
|
960
893
|
}
|
|
961
894
|
/**
|
|
@@ -964,7 +897,7 @@ class Config {
|
|
|
964
897
|
* @returns True if anti-CSRF protection is enabled
|
|
965
898
|
*/
|
|
966
899
|
isAntiCsrfEnabled() {
|
|
967
|
-
return this.
|
|
900
|
+
return this._antiCsrf;
|
|
968
901
|
}
|
|
969
902
|
/**
|
|
970
903
|
* Add a global request interceptor
|
|
@@ -980,8 +913,8 @@ class Config {
|
|
|
980
913
|
* });
|
|
981
914
|
*/
|
|
982
915
|
addRequestInterceptor(interceptor) {
|
|
983
|
-
const id = this.
|
|
984
|
-
this.
|
|
916
|
+
const id = this._nextId++;
|
|
917
|
+
this._reqI.push([id, interceptor]);
|
|
985
918
|
return id;
|
|
986
919
|
}
|
|
987
920
|
/**
|
|
@@ -998,8 +931,8 @@ class Config {
|
|
|
998
931
|
* });
|
|
999
932
|
*/
|
|
1000
933
|
addResponseInterceptor(interceptor) {
|
|
1001
|
-
const id = this.
|
|
1002
|
-
this.
|
|
934
|
+
const id = this._nextId++;
|
|
935
|
+
this._resI.push([id, interceptor]);
|
|
1003
936
|
return id;
|
|
1004
937
|
}
|
|
1005
938
|
/**
|
|
@@ -1016,8 +949,8 @@ class Config {
|
|
|
1016
949
|
* });
|
|
1017
950
|
*/
|
|
1018
951
|
addErrorInterceptor(interceptor) {
|
|
1019
|
-
const id = this.
|
|
1020
|
-
this.
|
|
952
|
+
const id = this._nextId++;
|
|
953
|
+
this._errI.push([id, interceptor]);
|
|
1021
954
|
return id;
|
|
1022
955
|
}
|
|
1023
956
|
/**
|
|
@@ -1029,7 +962,7 @@ class Config {
|
|
|
1029
962
|
* Config.getInstance().removeRequestInterceptor(id);
|
|
1030
963
|
*/
|
|
1031
964
|
removeRequestInterceptor(id) {
|
|
1032
|
-
this.
|
|
965
|
+
this._reqI = this._reqI.filter(item => item[0] !== id);
|
|
1033
966
|
}
|
|
1034
967
|
/**
|
|
1035
968
|
* Remove a response interceptor by its ID
|
|
@@ -1040,7 +973,7 @@ class Config {
|
|
|
1040
973
|
* Config.getInstance().removeResponseInterceptor(id);
|
|
1041
974
|
*/
|
|
1042
975
|
removeResponseInterceptor(id) {
|
|
1043
|
-
this.
|
|
976
|
+
this._resI = this._resI.filter(item => item[0] !== id);
|
|
1044
977
|
}
|
|
1045
978
|
/**
|
|
1046
979
|
* Remove an error interceptor by its ID
|
|
@@ -1051,7 +984,7 @@ class Config {
|
|
|
1051
984
|
* Config.getInstance().removeErrorInterceptor(id);
|
|
1052
985
|
*/
|
|
1053
986
|
removeErrorInterceptor(id) {
|
|
1054
|
-
this.
|
|
987
|
+
this._errI = this._errI.filter(item => item[0] !== id);
|
|
1055
988
|
}
|
|
1056
989
|
/**
|
|
1057
990
|
* Clear all interceptors (request, response, and error)
|
|
@@ -1060,30 +993,30 @@ class Config {
|
|
|
1060
993
|
* Config.getInstance().clearInterceptors();
|
|
1061
994
|
*/
|
|
1062
995
|
clearInterceptors() {
|
|
1063
|
-
this.
|
|
1064
|
-
this.
|
|
1065
|
-
this.
|
|
996
|
+
this._reqI = [];
|
|
997
|
+
this._resI = [];
|
|
998
|
+
this._errI = [];
|
|
1066
999
|
}
|
|
1067
1000
|
/**
|
|
1068
1001
|
* Get all global request interceptors (in registration order)
|
|
1069
1002
|
* @internal
|
|
1070
1003
|
*/
|
|
1071
1004
|
getRequestInterceptors() {
|
|
1072
|
-
return this.
|
|
1005
|
+
return this._reqI.map(item => item[1]);
|
|
1073
1006
|
}
|
|
1074
1007
|
/**
|
|
1075
1008
|
* Get all global response interceptors (in registration order)
|
|
1076
1009
|
* @internal
|
|
1077
1010
|
*/
|
|
1078
1011
|
getResponseInterceptors() {
|
|
1079
|
-
return this.
|
|
1012
|
+
return this._resI.map(item => item[1]);
|
|
1080
1013
|
}
|
|
1081
1014
|
/**
|
|
1082
1015
|
* Get all global error interceptors (in registration order)
|
|
1083
1016
|
* @internal
|
|
1084
1017
|
*/
|
|
1085
1018
|
getErrorInterceptors() {
|
|
1086
|
-
return this.
|
|
1019
|
+
return this._errI.map(item => item[1]);
|
|
1087
1020
|
}
|
|
1088
1021
|
/**
|
|
1089
1022
|
* Reset all configuration options to their default values
|
|
@@ -1094,12 +1027,12 @@ class Config {
|
|
|
1094
1027
|
* Config.getInstance().reset();
|
|
1095
1028
|
*/
|
|
1096
1029
|
reset() {
|
|
1097
|
-
this.
|
|
1098
|
-
this.
|
|
1099
|
-
this.
|
|
1100
|
-
this.
|
|
1101
|
-
this.
|
|
1102
|
-
this.
|
|
1030
|
+
this._csrfToken = null;
|
|
1031
|
+
this._csrfHeader = "X-CSRF-Token";
|
|
1032
|
+
this._antiCsrf = true;
|
|
1033
|
+
this._xsrfCookie = "XSRF-TOKEN";
|
|
1034
|
+
this._xsrfHeader = "X-XSRF-TOKEN";
|
|
1035
|
+
this._autoXsrf = true;
|
|
1103
1036
|
this.clearInterceptors();
|
|
1104
1037
|
return this;
|
|
1105
1038
|
}
|
|
@@ -1110,54 +1043,54 @@ class Config {
|
|
|
1110
1043
|
* Provides the core request building and execution capabilities.
|
|
1111
1044
|
*/
|
|
1112
1045
|
class BaseRequest {
|
|
1113
|
-
|
|
1114
|
-
|
|
1046
|
+
_url;
|
|
1047
|
+
_opts = {
|
|
1115
1048
|
headers: {},
|
|
1116
1049
|
};
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1050
|
+
_ctrl;
|
|
1051
|
+
_fetch;
|
|
1052
|
+
_query = new URLSearchParams();
|
|
1053
|
+
_autoCsrf = true;
|
|
1121
1054
|
// Per-request interceptors
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1055
|
+
_reqI = [];
|
|
1056
|
+
_resI = [];
|
|
1057
|
+
_errI = [];
|
|
1125
1058
|
constructor(url) {
|
|
1126
|
-
this.
|
|
1059
|
+
this._url = url;
|
|
1127
1060
|
}
|
|
1128
1061
|
/**
|
|
1129
1062
|
* Get GraphQL options if set (only for BodyRequest subclasses)
|
|
1130
1063
|
* @returns GraphQL options or undefined
|
|
1131
1064
|
*/
|
|
1132
|
-
|
|
1065
|
+
_gql() {
|
|
1133
1066
|
return undefined;
|
|
1134
1067
|
}
|
|
1135
1068
|
/**
|
|
1136
1069
|
* Creates a fluent API for setting enum-based options
|
|
1137
1070
|
* Combines direct setter with convenience methods
|
|
1138
1071
|
*/
|
|
1139
|
-
|
|
1072
|
+
_fluent(optionName, options) {
|
|
1140
1073
|
const fluent = {};
|
|
1141
1074
|
// Create convenience methods for each enum value
|
|
1142
1075
|
Object.entries(options).forEach(([key, value]) => {
|
|
1143
1076
|
fluent[key] = () => {
|
|
1144
|
-
this.
|
|
1077
|
+
this._opts[optionName] = value;
|
|
1145
1078
|
return this;
|
|
1146
1079
|
};
|
|
1147
1080
|
});
|
|
1148
1081
|
// Create the callable setter
|
|
1149
1082
|
const callable = (value) => {
|
|
1150
|
-
this.
|
|
1083
|
+
this._opts[optionName] = value;
|
|
1151
1084
|
return this;
|
|
1152
1085
|
};
|
|
1153
1086
|
return Object.assign(callable, fluent);
|
|
1154
1087
|
}
|
|
1155
|
-
|
|
1088
|
+
_validateUrl(url) {
|
|
1156
1089
|
const errorMessage = "Bad URL";
|
|
1157
1090
|
if (!url?.trim())
|
|
1158
|
-
throw new RequestError(errorMessage, url, this.
|
|
1091
|
+
throw new RequestError(errorMessage, url, this._method);
|
|
1159
1092
|
if (url.includes("\0") || url.includes("\r") || url.includes("\n")) {
|
|
1160
|
-
throw new RequestError(errorMessage, url, this.
|
|
1093
|
+
throw new RequestError(errorMessage, url, this._method);
|
|
1161
1094
|
}
|
|
1162
1095
|
const trimmed = url.trim();
|
|
1163
1096
|
if (/^https?:\/\//.test(trimmed)) {
|
|
@@ -1165,7 +1098,7 @@ class BaseRequest {
|
|
|
1165
1098
|
new URL(trimmed);
|
|
1166
1099
|
}
|
|
1167
1100
|
catch {
|
|
1168
|
-
throw new RequestError(errorMessage, trimmed, this.
|
|
1101
|
+
throw new RequestError(errorMessage, trimmed, this._method);
|
|
1169
1102
|
}
|
|
1170
1103
|
}
|
|
1171
1104
|
}
|
|
@@ -1183,16 +1116,12 @@ class BaseRequest {
|
|
|
1183
1116
|
*/
|
|
1184
1117
|
withHeaders(headers) {
|
|
1185
1118
|
// Filter out null and undefined values
|
|
1186
|
-
const
|
|
1187
|
-
|
|
1188
|
-
if (value
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
this.requestOptions.headers = {
|
|
1193
|
-
...this.getHeadersRecord(),
|
|
1194
|
-
...filteredHeaders,
|
|
1195
|
-
};
|
|
1119
|
+
const merged = { ...this._headers() };
|
|
1120
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1121
|
+
if (value != null)
|
|
1122
|
+
merged[key] = value;
|
|
1123
|
+
}
|
|
1124
|
+
this._opts.headers = merged;
|
|
1196
1125
|
return this;
|
|
1197
1126
|
}
|
|
1198
1127
|
/**
|
|
@@ -1221,8 +1150,8 @@ class BaseRequest {
|
|
|
1221
1150
|
*/
|
|
1222
1151
|
withTimeout(timeout) {
|
|
1223
1152
|
if (!Number.isFinite(timeout) || timeout <= 0)
|
|
1224
|
-
throw new RequestError("Bad timeout", this.
|
|
1225
|
-
this.
|
|
1153
|
+
throw new RequestError("Bad timeout", this._url, this._method);
|
|
1154
|
+
this._opts.timeout = timeout;
|
|
1226
1155
|
return this;
|
|
1227
1156
|
}
|
|
1228
1157
|
/**
|
|
@@ -1258,30 +1187,22 @@ class BaseRequest {
|
|
|
1258
1187
|
* });
|
|
1259
1188
|
*/
|
|
1260
1189
|
withRetries(retries) {
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1265
|
-
this.requestOptions.retries = retries;
|
|
1190
|
+
const isNumber = typeof retries === "number";
|
|
1191
|
+
const attempts = isNumber ? retries : retries.attempts;
|
|
1192
|
+
if (!Number.isInteger(attempts) || attempts < 0) {
|
|
1193
|
+
throw new RequestError(`Bad ${isNumber ? "retries" : "attempts"}: ${attempts}`, this._url, this._method);
|
|
1266
1194
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
if (
|
|
1270
|
-
|
|
1195
|
+
if (!isNumber && retries.delay !== undefined) {
|
|
1196
|
+
const delay = retries.delay;
|
|
1197
|
+
if (typeof delay === "number") {
|
|
1198
|
+
if (!Number.isFinite(delay) || delay < 0)
|
|
1199
|
+
throw new RequestError(`Bad delay: ${delay}`, this._url, this._method);
|
|
1271
1200
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
if (typeof retries.delay === "number") {
|
|
1275
|
-
if (!Number.isFinite(retries.delay) || retries.delay < 0) {
|
|
1276
|
-
throw new RequestError(`Bad delay: ${retries.delay}`, this.url, this.method);
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
else if (typeof retries.delay !== "function") {
|
|
1280
|
-
throw new RequestError(`Bad delay: ${typeof retries.delay}`, this.url, this.method);
|
|
1281
|
-
}
|
|
1201
|
+
else if (typeof delay !== "function") {
|
|
1202
|
+
throw new RequestError(`Bad delay: ${typeof delay}`, this._url, this._method);
|
|
1282
1203
|
}
|
|
1283
|
-
this.requestOptions.retries = retries;
|
|
1284
1204
|
}
|
|
1205
|
+
this._opts.retries = retries;
|
|
1285
1206
|
return this;
|
|
1286
1207
|
}
|
|
1287
1208
|
/**
|
|
@@ -1298,7 +1219,7 @@ class BaseRequest {
|
|
|
1298
1219
|
* });
|
|
1299
1220
|
*/
|
|
1300
1221
|
onRetry(callback) {
|
|
1301
|
-
this.
|
|
1222
|
+
this._opts.onRetry = callback;
|
|
1302
1223
|
return this;
|
|
1303
1224
|
}
|
|
1304
1225
|
/**
|
|
@@ -1325,11 +1246,7 @@ class BaseRequest {
|
|
|
1325
1246
|
* request.withCredentials.INCLUDE()
|
|
1326
1247
|
*/
|
|
1327
1248
|
get withCredentials() {
|
|
1328
|
-
return this.
|
|
1329
|
-
INCLUDE: CredentialsPolicy.INCLUDE,
|
|
1330
|
-
OMIT: CredentialsPolicy.OMIT,
|
|
1331
|
-
SAME_ORIGIN: CredentialsPolicy.SAME_ORIGIN,
|
|
1332
|
-
});
|
|
1249
|
+
return this._fluent("credentials", CredentialsPolicy);
|
|
1333
1250
|
}
|
|
1334
1251
|
/**
|
|
1335
1252
|
* Allows providing an external AbortController to cancel the request.
|
|
@@ -1358,7 +1275,7 @@ class BaseRequest {
|
|
|
1358
1275
|
* controller.abort();
|
|
1359
1276
|
*/
|
|
1360
1277
|
withAbortController(controller) {
|
|
1361
|
-
this.
|
|
1278
|
+
this._ctrl = controller;
|
|
1362
1279
|
return this;
|
|
1363
1280
|
}
|
|
1364
1281
|
/**
|
|
@@ -1392,8 +1309,8 @@ class BaseRequest {
|
|
|
1392
1309
|
*/
|
|
1393
1310
|
withFetch(fetchFn) {
|
|
1394
1311
|
if (typeof fetchFn !== "function")
|
|
1395
|
-
throw new RequestError("Bad fetch", this.
|
|
1396
|
-
this.
|
|
1312
|
+
throw new RequestError("Bad fetch", this._url, this._method);
|
|
1313
|
+
this._fetch = fetchFn;
|
|
1397
1314
|
return this;
|
|
1398
1315
|
}
|
|
1399
1316
|
/**
|
|
@@ -1415,7 +1332,7 @@ class BaseRequest {
|
|
|
1415
1332
|
* request.withReferrer("")
|
|
1416
1333
|
*/
|
|
1417
1334
|
withReferrer(referrer) {
|
|
1418
|
-
this.
|
|
1335
|
+
this._opts.referrer = referrer;
|
|
1419
1336
|
return this;
|
|
1420
1337
|
}
|
|
1421
1338
|
/**
|
|
@@ -1447,16 +1364,7 @@ class BaseRequest {
|
|
|
1447
1364
|
* request.withReferrerPolicy.NO_REFERRER()
|
|
1448
1365
|
*/
|
|
1449
1366
|
get withReferrerPolicy() {
|
|
1450
|
-
return this.
|
|
1451
|
-
ORIGIN: ReferrerPolicy.ORIGIN,
|
|
1452
|
-
UNSAFE_URL: ReferrerPolicy.UNSAFE_URL,
|
|
1453
|
-
SAME_ORIGIN: ReferrerPolicy.SAME_ORIGIN,
|
|
1454
|
-
NO_REFERRER: ReferrerPolicy.NO_REFERRER,
|
|
1455
|
-
STRICT_ORIGIN: ReferrerPolicy.STRICT_ORIGIN,
|
|
1456
|
-
ORIGIN_WHEN_CROSS_ORIGIN: ReferrerPolicy.ORIGIN_WHEN_CROSS_ORIGIN,
|
|
1457
|
-
NO_REFERRER_WHEN_DOWNGRADE: ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE,
|
|
1458
|
-
STRICT_ORIGIN_WHEN_CROSS_ORIGIN: ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
|
|
1459
|
-
});
|
|
1367
|
+
return this._fluent("referrerPolicy", ReferrerPolicy);
|
|
1460
1368
|
}
|
|
1461
1369
|
/**
|
|
1462
1370
|
* Sets how the request handles HTTP redirects (3xx status codes).
|
|
@@ -1485,11 +1393,7 @@ class BaseRequest {
|
|
|
1485
1393
|
* request.withRedirect.ERROR()
|
|
1486
1394
|
*/
|
|
1487
1395
|
get withRedirect() {
|
|
1488
|
-
return this.
|
|
1489
|
-
FOLLOW: RedirectMode.FOLLOW,
|
|
1490
|
-
ERROR: RedirectMode.ERROR,
|
|
1491
|
-
MANUAL: RedirectMode.MANUAL,
|
|
1492
|
-
});
|
|
1396
|
+
return this._fluent("redirect", RedirectMode);
|
|
1493
1397
|
}
|
|
1494
1398
|
/**
|
|
1495
1399
|
* Sets the keepalive flag for the request. When enabled, the request can continue
|
|
@@ -1507,7 +1411,7 @@ class BaseRequest {
|
|
|
1507
1411
|
* request.withKeepAlive(true)
|
|
1508
1412
|
*/
|
|
1509
1413
|
withKeepAlive(keepalive) {
|
|
1510
|
-
this.
|
|
1414
|
+
this._opts.keepalive = keepalive;
|
|
1511
1415
|
return this;
|
|
1512
1416
|
}
|
|
1513
1417
|
/**
|
|
@@ -1538,11 +1442,7 @@ class BaseRequest {
|
|
|
1538
1442
|
* request.withPriority.LOW()
|
|
1539
1443
|
*/
|
|
1540
1444
|
get withPriority() {
|
|
1541
|
-
return this.
|
|
1542
|
-
HIGH: RequestPriority.HIGH,
|
|
1543
|
-
LOW: RequestPriority.LOW,
|
|
1544
|
-
AUTO: RequestPriority.AUTO,
|
|
1545
|
-
});
|
|
1445
|
+
return this._fluent("priority", RequestPriority);
|
|
1546
1446
|
}
|
|
1547
1447
|
/**
|
|
1548
1448
|
* Sets the integrity hash for Subresource Integrity (SRI) verification.
|
|
@@ -1565,7 +1465,7 @@ class BaseRequest {
|
|
|
1565
1465
|
* request.withIntegrity("sha256-... sha384-...")
|
|
1566
1466
|
*/
|
|
1567
1467
|
withIntegrity(integrity) {
|
|
1568
|
-
this.
|
|
1468
|
+
this._opts.integrity = integrity;
|
|
1569
1469
|
return this;
|
|
1570
1470
|
}
|
|
1571
1471
|
/**
|
|
@@ -1603,15 +1503,7 @@ class BaseRequest {
|
|
|
1603
1503
|
* request.withCache.ONLY_IF_CACHED()
|
|
1604
1504
|
*/
|
|
1605
1505
|
get withCache() {
|
|
1606
|
-
|
|
1607
|
-
DEFAULT: "default",
|
|
1608
|
-
NO_STORE: "no-store",
|
|
1609
|
-
RELOAD: "reload",
|
|
1610
|
-
NO_CACHE: "no-cache",
|
|
1611
|
-
FORCE_CACHE: "force-cache",
|
|
1612
|
-
ONLY_IF_CACHED: "only-if-cached",
|
|
1613
|
-
};
|
|
1614
|
-
return this.createFluentSetter("cache", cacheOptions);
|
|
1506
|
+
return this._fluent("cache", CacheMode);
|
|
1615
1507
|
}
|
|
1616
1508
|
/**
|
|
1617
1509
|
* Adds query parameters to the request URL.
|
|
@@ -1650,10 +1542,10 @@ class BaseRequest {
|
|
|
1650
1542
|
}
|
|
1651
1543
|
if (Array.isArray(value)) {
|
|
1652
1544
|
// Handle array values - add multiple entries with the same key
|
|
1653
|
-
value.forEach(v => this.
|
|
1545
|
+
value.forEach(v => this._query.append(key, String(v)));
|
|
1654
1546
|
}
|
|
1655
1547
|
else {
|
|
1656
|
-
this.
|
|
1548
|
+
this._query.append(key, String(value));
|
|
1657
1549
|
}
|
|
1658
1550
|
});
|
|
1659
1551
|
return this;
|
|
@@ -1711,12 +1603,7 @@ class BaseRequest {
|
|
|
1711
1603
|
* request.withMode.SAME_ORIGIN()
|
|
1712
1604
|
*/
|
|
1713
1605
|
get withMode() {
|
|
1714
|
-
return this.
|
|
1715
|
-
CORS: RequestMode.CORS,
|
|
1716
|
-
NO_CORS: RequestMode.NO_CORS,
|
|
1717
|
-
SAME_ORIGIN: RequestMode.SAME_ORIGIN,
|
|
1718
|
-
NAVIGATE: RequestMode.NAVIGATE,
|
|
1719
|
-
});
|
|
1606
|
+
return this._fluent("mode", RequestMode);
|
|
1720
1607
|
}
|
|
1721
1608
|
/**
|
|
1722
1609
|
* Sets the Content-Type header for the request.
|
|
@@ -1774,28 +1661,26 @@ class BaseRequest {
|
|
|
1774
1661
|
* ```
|
|
1775
1662
|
*/
|
|
1776
1663
|
withBasicAuth(username, password) {
|
|
1777
|
-
const credentials = this.
|
|
1664
|
+
const credentials = this._b64(`${username}:${password}`);
|
|
1778
1665
|
return this.withAuthorization(`Basic ${credentials}`);
|
|
1779
1666
|
}
|
|
1780
1667
|
/**
|
|
1781
1668
|
* Cross-environment base64 encoding
|
|
1782
1669
|
* Works in both browser and Node.js environments
|
|
1783
1670
|
*/
|
|
1784
|
-
|
|
1671
|
+
_b64(str) {
|
|
1785
1672
|
// Modern approach using TextEncoder (available in both modern browsers and Node.js)
|
|
1786
|
-
if (typeof
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
}
|
|
1791
|
-
// Browser environment
|
|
1792
|
-
if (typeof btoa === "function")
|
|
1673
|
+
if (typeof btoa === "function") {
|
|
1674
|
+
if (typeof TextEncoder !== "undefined")
|
|
1675
|
+
return btoa(String.fromCharCode(...new TextEncoder().encode(str)));
|
|
1676
|
+
// Browser environment without TextEncoder
|
|
1793
1677
|
return btoa(str);
|
|
1678
|
+
}
|
|
1794
1679
|
// Node.js environment
|
|
1795
1680
|
if (typeof Buffer !== "undefined")
|
|
1796
1681
|
return Buffer.from(str).toString("base64");
|
|
1797
1682
|
// Fallback (should never happen in modern environments)
|
|
1798
|
-
throw new RequestError("No encoder", this.
|
|
1683
|
+
throw new RequestError("No encoder", this._url, this._method);
|
|
1799
1684
|
}
|
|
1800
1685
|
/**
|
|
1801
1686
|
* Sets a Bearer token for authentication.
|
|
@@ -1817,9 +1702,9 @@ class BaseRequest {
|
|
|
1817
1702
|
* Safely get headers as a Record<string, string>
|
|
1818
1703
|
* @returns The headers object
|
|
1819
1704
|
*/
|
|
1820
|
-
|
|
1821
|
-
if (typeof this.
|
|
1822
|
-
return this.
|
|
1705
|
+
_headers() {
|
|
1706
|
+
if (typeof this._opts.headers === "object" && this._opts.headers !== null) {
|
|
1707
|
+
return this._opts.headers;
|
|
1823
1708
|
}
|
|
1824
1709
|
return {};
|
|
1825
1710
|
}
|
|
@@ -1828,8 +1713,8 @@ class BaseRequest {
|
|
|
1828
1713
|
* @param headerName Header name to check
|
|
1829
1714
|
* @returns Boolean indicating if the header exists (case-insensitive)
|
|
1830
1715
|
*/
|
|
1831
|
-
|
|
1832
|
-
const headers = this.
|
|
1716
|
+
_hasHeader(headerName) {
|
|
1717
|
+
const headers = this._headers();
|
|
1833
1718
|
return Object.keys(headers).some(key => key.toLowerCase() === headerName.toLowerCase());
|
|
1834
1719
|
}
|
|
1835
1720
|
/**
|
|
@@ -1858,37 +1743,26 @@ class BaseRequest {
|
|
|
1858
1743
|
* ```
|
|
1859
1744
|
*/
|
|
1860
1745
|
withCookies(cookies) {
|
|
1861
|
-
|
|
1862
|
-
if (cookieEntries.length === 0) {
|
|
1746
|
+
if (Object.keys(cookies || {}).length === 0)
|
|
1863
1747
|
return this;
|
|
1864
|
-
|
|
1865
|
-
const
|
|
1866
|
-
// Get all existing cookie headers with different cases
|
|
1748
|
+
// Collect existing cookie header values (any casing), preserving the first header's case
|
|
1749
|
+
const newHeaders = { ...this._headers() };
|
|
1867
1750
|
const cookieValues = [];
|
|
1868
|
-
|
|
1869
|
-
Object.keys(
|
|
1751
|
+
let headerName = "";
|
|
1752
|
+
for (const key of Object.keys(newHeaders)) {
|
|
1870
1753
|
if (key.toLowerCase() === "cookie") {
|
|
1871
1754
|
// Don't add empty cookie values
|
|
1872
|
-
if (
|
|
1873
|
-
cookieValues.push(
|
|
1874
|
-
|
|
1755
|
+
if (newHeaders[key])
|
|
1756
|
+
cookieValues.push(newHeaders[key]);
|
|
1757
|
+
headerName ||= key;
|
|
1758
|
+
delete newHeaders[key];
|
|
1875
1759
|
}
|
|
1876
|
-
}
|
|
1877
|
-
//
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
const headerName = cookieHeaderKeys.length > 0 ? cookieHeaderKeys[0] : "Cookie";
|
|
1881
|
-
// Combine all existing cookie values with the new ones
|
|
1882
|
-
const combinedCookieValue = [...cookieValues, cookieString].filter(Boolean).join("; ");
|
|
1883
|
-
// Create a new headers object without any cookie headers
|
|
1884
|
-
const newHeaders = { ...currentHeaders };
|
|
1885
|
-
cookieHeaderKeys.forEach(key => {
|
|
1886
|
-
delete newHeaders[key];
|
|
1887
|
-
});
|
|
1888
|
-
// Set the new combined cookie header
|
|
1889
|
-
this.requestOptions.headers = {
|
|
1760
|
+
}
|
|
1761
|
+
// Combine all existing cookie values with the newly formatted ones
|
|
1762
|
+
cookieValues.push(CookieUtils.formatRequestCookies(cookies));
|
|
1763
|
+
this._opts.headers = {
|
|
1890
1764
|
...newHeaders,
|
|
1891
|
-
[headerName]:
|
|
1765
|
+
[headerName || "Cookie"]: cookieValues.filter(Boolean).join("; "),
|
|
1892
1766
|
};
|
|
1893
1767
|
return this;
|
|
1894
1768
|
}
|
|
@@ -1942,7 +1816,7 @@ class BaseRequest {
|
|
|
1942
1816
|
* @returns The instance for chaining
|
|
1943
1817
|
*/
|
|
1944
1818
|
withoutCsrfProtection() {
|
|
1945
|
-
this.
|
|
1819
|
+
this._autoCsrf = false;
|
|
1946
1820
|
return this;
|
|
1947
1821
|
}
|
|
1948
1822
|
/**
|
|
@@ -1966,7 +1840,7 @@ class BaseRequest {
|
|
|
1966
1840
|
* });
|
|
1967
1841
|
*/
|
|
1968
1842
|
withRequestInterceptor(interceptor) {
|
|
1969
|
-
this.
|
|
1843
|
+
this._reqI.push(interceptor);
|
|
1970
1844
|
return this;
|
|
1971
1845
|
}
|
|
1972
1846
|
/**
|
|
@@ -1983,7 +1857,7 @@ class BaseRequest {
|
|
|
1983
1857
|
* });
|
|
1984
1858
|
*/
|
|
1985
1859
|
withResponseInterceptor(interceptor) {
|
|
1986
|
-
this.
|
|
1860
|
+
this._resI.push(interceptor);
|
|
1987
1861
|
return this;
|
|
1988
1862
|
}
|
|
1989
1863
|
/**
|
|
@@ -2000,7 +1874,7 @@ class BaseRequest {
|
|
|
2000
1874
|
* });
|
|
2001
1875
|
*/
|
|
2002
1876
|
withErrorInterceptor(interceptor) {
|
|
2003
|
-
this.
|
|
1877
|
+
this._errI.push(interceptor);
|
|
2004
1878
|
return this;
|
|
2005
1879
|
}
|
|
2006
1880
|
/**
|
|
@@ -2014,13 +1888,13 @@ class BaseRequest {
|
|
|
2014
1888
|
* console.log(response.status);
|
|
2015
1889
|
*/
|
|
2016
1890
|
async getResponse() {
|
|
2017
|
-
const url = this.
|
|
2018
|
-
this.
|
|
1891
|
+
const url = this._fullUrl(this._url);
|
|
1892
|
+
this._applyCsrf();
|
|
2019
1893
|
const fetchOptions = {
|
|
2020
|
-
...this.
|
|
2021
|
-
method: this.
|
|
1894
|
+
...this._opts,
|
|
1895
|
+
method: this._method,
|
|
2022
1896
|
};
|
|
2023
|
-
return !this.
|
|
1897
|
+
return !this._opts.retries ? this._run(url, fetchOptions) : this._retry(url, fetchOptions);
|
|
2024
1898
|
}
|
|
2025
1899
|
/**
|
|
2026
1900
|
* Execute the request and parse the response as JSON
|
|
@@ -2161,8 +2035,8 @@ class BaseRequest {
|
|
|
2161
2035
|
/**
|
|
2162
2036
|
* Apply CSRF protection headers based on configuration
|
|
2163
2037
|
*/
|
|
2164
|
-
|
|
2165
|
-
if (!this.
|
|
2038
|
+
_applyCsrf() {
|
|
2039
|
+
if (!this._autoCsrf)
|
|
2166
2040
|
return;
|
|
2167
2041
|
const config = Config.getInstance();
|
|
2168
2042
|
// Apply anti-CSRF headers if enabled
|
|
@@ -2173,7 +2047,7 @@ class BaseRequest {
|
|
|
2173
2047
|
const globalToken = config.getCsrfToken();
|
|
2174
2048
|
if (globalToken) {
|
|
2175
2049
|
const csrfHeaderName = config.getCsrfHeaderName();
|
|
2176
|
-
const hasLocalToken = this.
|
|
2050
|
+
const hasLocalToken = this._hasHeader("X-CSRF-Token") || this._hasHeader(csrfHeaderName);
|
|
2177
2051
|
if (!hasLocalToken) {
|
|
2178
2052
|
this.withHeader(csrfHeaderName, globalToken);
|
|
2179
2053
|
}
|
|
@@ -2183,7 +2057,7 @@ class BaseRequest {
|
|
|
2183
2057
|
const xsrfToken = CsrfUtils.getTokenFromCookie(config.getXsrfCookieName());
|
|
2184
2058
|
if (xsrfToken && CsrfUtils.isValidToken(xsrfToken)) {
|
|
2185
2059
|
const xsrfHeaderName = config.getXsrfHeaderName();
|
|
2186
|
-
const hasLocalToken = this.
|
|
2060
|
+
const hasLocalToken = this._hasHeader("X-XSRF-TOKEN") || this._hasHeader(xsrfHeaderName);
|
|
2187
2061
|
if (!hasLocalToken) {
|
|
2188
2062
|
this.withHeader(xsrfHeaderName, xsrfToken);
|
|
2189
2063
|
}
|
|
@@ -2195,8 +2069,8 @@ class BaseRequest {
|
|
|
2195
2069
|
* @param url The base URL
|
|
2196
2070
|
* @returns The URL with query parameters appended
|
|
2197
2071
|
*/
|
|
2198
|
-
|
|
2199
|
-
const queryString = this.
|
|
2072
|
+
_fullUrl(url) {
|
|
2073
|
+
const queryString = this._query.toString();
|
|
2200
2074
|
if (!queryString) {
|
|
2201
2075
|
return url;
|
|
2202
2076
|
}
|
|
@@ -2204,7 +2078,7 @@ class BaseRequest {
|
|
|
2204
2078
|
// Try to use the URL constructor (works for absolute URLs)
|
|
2205
2079
|
const urlObj = new URL(url);
|
|
2206
2080
|
// Merge our query params with any that might be in the URL already
|
|
2207
|
-
this.
|
|
2081
|
+
this._query.forEach((value, key) => {
|
|
2208
2082
|
urlObj.searchParams.append(key, value);
|
|
2209
2083
|
});
|
|
2210
2084
|
return urlObj.toString();
|
|
@@ -2223,21 +2097,21 @@ class BaseRequest {
|
|
|
2223
2097
|
* @returns A wrapped response object
|
|
2224
2098
|
* @throws RequestError if the request fails after all retries
|
|
2225
2099
|
*/
|
|
2226
|
-
async
|
|
2227
|
-
const retriesConfig = this.
|
|
2100
|
+
async _retry(url, fetchOptions) {
|
|
2101
|
+
const retriesConfig = this._opts.retries;
|
|
2228
2102
|
const maxRetries = typeof retriesConfig === "number" ? retriesConfig : retriesConfig?.attempts || 0;
|
|
2229
2103
|
const method = typeof fetchOptions.method === "string" ? fetchOptions.method : "GET";
|
|
2230
2104
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
2231
2105
|
try {
|
|
2232
|
-
return await this.
|
|
2106
|
+
return await this._run(url, fetchOptions);
|
|
2233
2107
|
}
|
|
2234
2108
|
catch (error) {
|
|
2235
|
-
const requestError = error instanceof RequestError ? error : RequestError.networkError(url, method,
|
|
2109
|
+
const requestError = error instanceof RequestError ? error : RequestError.networkError(url, method, toError(error));
|
|
2236
2110
|
if (attempt >= maxRetries)
|
|
2237
2111
|
throw requestError;
|
|
2238
2112
|
// Call onRetry callback if provided
|
|
2239
|
-
if (this.
|
|
2240
|
-
await this.
|
|
2113
|
+
if (this._opts.onRetry) {
|
|
2114
|
+
await this._opts.onRetry({ attempt: attempt + 1, error: requestError });
|
|
2241
2115
|
}
|
|
2242
2116
|
// Apply delay if configured
|
|
2243
2117
|
if (typeof retriesConfig === "object" && retriesConfig.delay !== undefined) {
|
|
@@ -2261,9 +2135,9 @@ class BaseRequest {
|
|
|
2261
2135
|
* @param configParam - The request configuration
|
|
2262
2136
|
* @returns Modified config or a Response to short-circuit
|
|
2263
2137
|
*/
|
|
2264
|
-
async
|
|
2138
|
+
async _runReqI(configParam) {
|
|
2265
2139
|
const globalConfig = Config.getInstance();
|
|
2266
|
-
const allInterceptors = [...globalConfig.getRequestInterceptors(), ...this.
|
|
2140
|
+
const allInterceptors = [...globalConfig.getRequestInterceptors(), ...this._reqI];
|
|
2267
2141
|
let currentConfig = configParam;
|
|
2268
2142
|
for (let i = 0; i < allInterceptors.length; i++) {
|
|
2269
2143
|
try {
|
|
@@ -2275,7 +2149,7 @@ class BaseRequest {
|
|
|
2275
2149
|
currentConfig = result;
|
|
2276
2150
|
}
|
|
2277
2151
|
catch (error) {
|
|
2278
|
-
throw new RequestError(`ReqI: ${
|
|
2152
|
+
throw new RequestError(`ReqI: ${errorMessage(error)}`, currentConfig.url, currentConfig.method);
|
|
2279
2153
|
}
|
|
2280
2154
|
}
|
|
2281
2155
|
return currentConfig;
|
|
@@ -2285,18 +2159,18 @@ class BaseRequest {
|
|
|
2285
2159
|
* @param response - The response wrapper
|
|
2286
2160
|
* @returns Modified response wrapper
|
|
2287
2161
|
*/
|
|
2288
|
-
async
|
|
2162
|
+
async _runResI(response) {
|
|
2289
2163
|
const globalConfig = Config.getInstance();
|
|
2290
2164
|
const globalInterceptors = globalConfig.getResponseInterceptors();
|
|
2291
2165
|
// Per-request in order, then global in reverse
|
|
2292
|
-
const allInterceptors = [...this.
|
|
2166
|
+
const allInterceptors = [...this._resI, ...[...globalInterceptors].reverse()];
|
|
2293
2167
|
let currentResponse = response;
|
|
2294
2168
|
for (let i = 0; i < allInterceptors.length; i++) {
|
|
2295
2169
|
try {
|
|
2296
2170
|
currentResponse = await allInterceptors[i](currentResponse);
|
|
2297
2171
|
}
|
|
2298
2172
|
catch (error) {
|
|
2299
|
-
throw new RequestError(`ResI: ${
|
|
2173
|
+
throw new RequestError(`ResI: ${errorMessage(error)}`, currentResponse.url || "", currentResponse.method || "");
|
|
2300
2174
|
}
|
|
2301
2175
|
}
|
|
2302
2176
|
return currentResponse;
|
|
@@ -2306,11 +2180,11 @@ class BaseRequest {
|
|
|
2306
2180
|
* @param error - The error that occurred
|
|
2307
2181
|
* @returns Modified error or a ResponseWrapper to recover
|
|
2308
2182
|
*/
|
|
2309
|
-
async
|
|
2183
|
+
async _runErrI(error) {
|
|
2310
2184
|
const globalConfig = Config.getInstance();
|
|
2311
2185
|
const globalInterceptors = globalConfig.getErrorInterceptors();
|
|
2312
2186
|
// Per-request in order, then global in reverse
|
|
2313
|
-
const allInterceptors = [...this.
|
|
2187
|
+
const allInterceptors = [...this._errI, ...[...globalInterceptors].reverse()];
|
|
2314
2188
|
let currentError = error;
|
|
2315
2189
|
for (let i = 0; i < allInterceptors.length; i++) {
|
|
2316
2190
|
try {
|
|
@@ -2326,24 +2200,20 @@ class BaseRequest {
|
|
|
2326
2200
|
if (interceptorError instanceof RequestError) {
|
|
2327
2201
|
currentError = interceptorError;
|
|
2328
2202
|
}
|
|
2329
|
-
else {
|
|
2330
|
-
const em = interceptorError instanceof Error ? interceptorError.message : String(interceptorError);
|
|
2203
|
+
else if (currentError instanceof RequestError) {
|
|
2331
2204
|
// Always wrap in RequestError when we have context
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
currentError = RequestError.networkError(error.url, error.method, errorObj);
|
|
2345
|
-
/* c8 ignore end */
|
|
2346
|
-
}
|
|
2205
|
+
currentError = new RequestError(`ErrI${i + 1}: ${errorMessage(interceptorError)}`, currentError.url, currentError.method, {
|
|
2206
|
+
status: currentError.status,
|
|
2207
|
+
response: currentError.response,
|
|
2208
|
+
body: currentError.body,
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
2211
|
+
else {
|
|
2212
|
+
/* c8 ignore start */
|
|
2213
|
+
// Last resort: if we have no context at all, use the original error's context
|
|
2214
|
+
// This shouldn't happen in practice, but handle it gracefully
|
|
2215
|
+
currentError = RequestError.networkError(error.url, error.method, toError(interceptorError));
|
|
2216
|
+
/* c8 ignore end */
|
|
2347
2217
|
}
|
|
2348
2218
|
}
|
|
2349
2219
|
}
|
|
@@ -2353,7 +2223,7 @@ class BaseRequest {
|
|
|
2353
2223
|
* Helper to create an abort signal with timeout support
|
|
2354
2224
|
* Handles various AbortSignal API levels gracefully
|
|
2355
2225
|
*/
|
|
2356
|
-
|
|
2226
|
+
_signal(timeoutMs, externalController) {
|
|
2357
2227
|
let timeoutId;
|
|
2358
2228
|
let timeoutController;
|
|
2359
2229
|
let isTimeout = false;
|
|
@@ -2389,7 +2259,7 @@ class BaseRequest {
|
|
|
2389
2259
|
const finalSignal = externalController && hasAbortSignalAny
|
|
2390
2260
|
? AbortSignal.any([externalController.signal, timeoutSignal])
|
|
2391
2261
|
: externalController
|
|
2392
|
-
? this.
|
|
2262
|
+
? this._combine(externalController.signal, timeoutSignal)
|
|
2393
2263
|
: timeoutSignal;
|
|
2394
2264
|
return {
|
|
2395
2265
|
signal: finalSignal,
|
|
@@ -2404,7 +2274,7 @@ class BaseRequest {
|
|
|
2404
2274
|
* Manually combine two abort signals for older environments
|
|
2405
2275
|
* Returns the first signal and listens to the second
|
|
2406
2276
|
*/
|
|
2407
|
-
|
|
2277
|
+
_combine(signal1, signal2) {
|
|
2408
2278
|
// If either is already aborted, use that one
|
|
2409
2279
|
if (signal1.aborted)
|
|
2410
2280
|
return signal1;
|
|
@@ -2420,9 +2290,9 @@ class BaseRequest {
|
|
|
2420
2290
|
/**
|
|
2421
2291
|
* Convert fetchOptions to RequestConfig with proper typing
|
|
2422
2292
|
*/
|
|
2423
|
-
|
|
2293
|
+
_config(url, fetchOptions) {
|
|
2424
2294
|
const method = typeof fetchOptions.method === "string" ? fetchOptions.method : "GET";
|
|
2425
|
-
const headers = this.
|
|
2295
|
+
const headers = this._headers();
|
|
2426
2296
|
const extendedOptions = fetchOptions;
|
|
2427
2297
|
return {
|
|
2428
2298
|
url,
|
|
@@ -2444,7 +2314,7 @@ class BaseRequest {
|
|
|
2444
2314
|
/**
|
|
2445
2315
|
* Apply interceptor results back to fetchOptions
|
|
2446
2316
|
*/
|
|
2447
|
-
|
|
2317
|
+
_applyConfig(config, fetchOptions) {
|
|
2448
2318
|
fetchOptions.headers = config.headers;
|
|
2449
2319
|
if (config.body !== undefined) {
|
|
2450
2320
|
fetchOptions.body = config.body;
|
|
@@ -2456,54 +2326,54 @@ class BaseRequest {
|
|
|
2456
2326
|
fetchOptions.cache = config.cache;
|
|
2457
2327
|
}
|
|
2458
2328
|
}
|
|
2459
|
-
async
|
|
2329
|
+
async _run(url, fetchOptions) {
|
|
2460
2330
|
const method = typeof fetchOptions.method === "string" ? fetchOptions.method : "GET";
|
|
2461
2331
|
// Setup abort signal with timeout
|
|
2462
|
-
const abortSignal = this.
|
|
2332
|
+
const abortSignal = this._signal(this._opts.timeout, this._ctrl);
|
|
2463
2333
|
try {
|
|
2464
2334
|
// Run request interceptors before making the request
|
|
2465
|
-
const requestConfig = this.
|
|
2466
|
-
const interceptorResult = await this.
|
|
2335
|
+
const requestConfig = this._config(url, fetchOptions);
|
|
2336
|
+
const interceptorResult = await this._runReqI(requestConfig);
|
|
2467
2337
|
// If interceptor returned a Response, short-circuit and wrap it
|
|
2468
2338
|
if (interceptorResult instanceof Response) {
|
|
2469
|
-
const graphQLOptions = this.
|
|
2470
|
-
const wrappedResponse = new ResponseWrapper(interceptorResult, this.
|
|
2471
|
-
return await this.
|
|
2339
|
+
const graphQLOptions = this._gql();
|
|
2340
|
+
const wrappedResponse = new ResponseWrapper(interceptorResult, this._url, this._method, graphQLOptions);
|
|
2341
|
+
return await this._runResI(wrappedResponse);
|
|
2472
2342
|
}
|
|
2473
2343
|
// Update fetchOptions with interceptor modifications
|
|
2474
2344
|
url = interceptorResult.url;
|
|
2475
|
-
this.
|
|
2476
|
-
this.
|
|
2345
|
+
this._validateUrl(url);
|
|
2346
|
+
this._applyConfig(interceptorResult, fetchOptions);
|
|
2477
2347
|
// Set the combined abort signal
|
|
2478
2348
|
if (abortSignal.signal) {
|
|
2479
2349
|
fetchOptions.signal = abortSignal.signal;
|
|
2480
2350
|
}
|
|
2481
2351
|
// Execute fetch (custom implementation if provided, global fetch otherwise)
|
|
2482
|
-
const fetchFn = this.
|
|
2352
|
+
const fetchFn = this._fetch ?? globalThis.fetch;
|
|
2483
2353
|
let response;
|
|
2484
2354
|
try {
|
|
2485
2355
|
response = await fetchFn(url, fetchOptions);
|
|
2486
2356
|
}
|
|
2487
2357
|
catch (error) {
|
|
2488
|
-
const errorObj =
|
|
2358
|
+
const errorObj = toError(error);
|
|
2489
2359
|
const errorName = errorObj.name;
|
|
2490
|
-
const
|
|
2360
|
+
const lowerMessage = errorObj.message.toLowerCase();
|
|
2491
2361
|
// Check if this is a timeout error from our internal timeout
|
|
2492
2362
|
const isOurTimeout = abortSignal.wasTimeout();
|
|
2493
2363
|
// Check if it's an abort error (DOMException in browsers, or AbortSignal abort)
|
|
2494
2364
|
if (error instanceof DOMException && error.name === "AbortError") {
|
|
2495
2365
|
// If it was our timeout that caused the abort, throw timeout error
|
|
2496
|
-
if (isOurTimeout && this.
|
|
2497
|
-
throw RequestError.timeout(url, method, this.
|
|
2366
|
+
if (isOurTimeout && this._opts.timeout) {
|
|
2367
|
+
throw RequestError.timeout(url, method, this._opts.timeout);
|
|
2498
2368
|
}
|
|
2499
2369
|
// Otherwise it's a manual abort
|
|
2500
2370
|
throw RequestError.abortError(url, method);
|
|
2501
2371
|
}
|
|
2502
2372
|
// Check for Node.js/undici TimeoutError or other timeout indicators
|
|
2503
2373
|
// This catches timeout errors that aren't thrown as AbortError
|
|
2504
|
-
const isTimeoutError = isOurTimeout || errorName === "TimeoutError" ||
|
|
2505
|
-
if (isTimeoutError && this.
|
|
2506
|
-
throw RequestError.timeout(url, method, this.
|
|
2374
|
+
const isTimeoutError = isOurTimeout || errorName === "TimeoutError" || lowerMessage.includes("timeout");
|
|
2375
|
+
if (isTimeoutError && this._opts.timeout) {
|
|
2376
|
+
throw RequestError.timeout(url, method, this._opts.timeout);
|
|
2507
2377
|
}
|
|
2508
2378
|
// For other network errors, let RequestError.networkError handle them
|
|
2509
2379
|
// It will check for timeout patterns as a safety net (useful for external AbortControllers)
|
|
@@ -2519,22 +2389,15 @@ class BaseRequest {
|
|
|
2519
2389
|
// (reads from a clone, so error.response remains readable)
|
|
2520
2390
|
throw RequestError.fromResponse(response, url, method, await RequestError.captureBody(response));
|
|
2521
2391
|
}
|
|
2522
|
-
const graphQLOptions = this.
|
|
2392
|
+
const graphQLOptions = this._gql();
|
|
2523
2393
|
const wrappedResponse = new ResponseWrapper(response, url, method, graphQLOptions);
|
|
2524
|
-
return await this.
|
|
2394
|
+
return await this._runResI(wrappedResponse);
|
|
2525
2395
|
}
|
|
2526
2396
|
catch (error) {
|
|
2527
2397
|
// Convert to RequestError if needed
|
|
2528
|
-
|
|
2529
|
-
if (error instanceof RequestError) {
|
|
2530
|
-
requestError = error;
|
|
2531
|
-
}
|
|
2532
|
-
else {
|
|
2533
|
-
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
2534
|
-
requestError = RequestError.networkError(url, method, errorObj);
|
|
2535
|
-
}
|
|
2398
|
+
const requestError = error instanceof RequestError ? error : RequestError.networkError(url, method, toError(error));
|
|
2536
2399
|
// Run error interceptors
|
|
2537
|
-
const interceptorResult = await this.
|
|
2400
|
+
const interceptorResult = await this._runErrI(requestError);
|
|
2538
2401
|
// If error interceptor returned a ResponseWrapper, recover from error
|
|
2539
2402
|
if (interceptorResult instanceof ResponseWrapper) {
|
|
2540
2403
|
return interceptorResult;
|
|
@@ -2551,12 +2414,9 @@ class BaseRequest {
|
|
|
2551
2414
|
* Base class for requests that can have a body (POST, PUT, PATCH)
|
|
2552
2415
|
*/
|
|
2553
2416
|
class BodyRequest extends BaseRequest {
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
constructor(url) {
|
|
2558
|
-
super(url);
|
|
2559
|
-
}
|
|
2417
|
+
_body;
|
|
2418
|
+
_bodyType;
|
|
2419
|
+
_gqlOpts = undefined;
|
|
2560
2420
|
/**
|
|
2561
2421
|
* Sets the request body. Automatically detects the body type and sets appropriate Content-Type header.
|
|
2562
2422
|
* Supports JSON objects/arrays, strings, FormData, Blob, ArrayBuffer, URLSearchParams, and ReadableStream.
|
|
@@ -2593,11 +2453,11 @@ class BodyRequest extends BaseRequest {
|
|
|
2593
2453
|
* ```
|
|
2594
2454
|
*/
|
|
2595
2455
|
withBody(body) {
|
|
2596
|
-
this.
|
|
2456
|
+
this._body = body;
|
|
2597
2457
|
// Set body type and validate
|
|
2598
2458
|
if (typeof body === "string") {
|
|
2599
|
-
this.
|
|
2600
|
-
this.
|
|
2459
|
+
this._bodyType = BodyType.STRING;
|
|
2460
|
+
this._setCT("text/plain");
|
|
2601
2461
|
}
|
|
2602
2462
|
else if (body !== null &&
|
|
2603
2463
|
typeof body === "object" &&
|
|
@@ -2608,18 +2468,18 @@ class BodyRequest extends BaseRequest {
|
|
|
2608
2468
|
ArrayBuffer.isView(body) || // Handles TypedArray and DataView
|
|
2609
2469
|
body instanceof URLSearchParams ||
|
|
2610
2470
|
body instanceof ReadableStream)) {
|
|
2611
|
-
this.
|
|
2612
|
-
this.
|
|
2471
|
+
this._bodyType = BodyType.JSON;
|
|
2472
|
+
this._setCT("application/json");
|
|
2613
2473
|
// Validate JSON is stringifiable early
|
|
2614
2474
|
try {
|
|
2615
2475
|
JSON.stringify(body);
|
|
2616
2476
|
}
|
|
2617
2477
|
catch (error) {
|
|
2618
|
-
throw new RequestError(`Bad JSON: ${
|
|
2478
|
+
throw new RequestError(`Bad JSON: ${errorMessage(error)}`, this._url, this._method);
|
|
2619
2479
|
}
|
|
2620
2480
|
}
|
|
2621
2481
|
else {
|
|
2622
|
-
this.
|
|
2482
|
+
this._bodyType = BodyType.BINARY;
|
|
2623
2483
|
}
|
|
2624
2484
|
return this;
|
|
2625
2485
|
}
|
|
@@ -2662,25 +2522,25 @@ class BodyRequest extends BaseRequest {
|
|
|
2662
2522
|
*/
|
|
2663
2523
|
withGraphQL(query, variables, options) {
|
|
2664
2524
|
if (typeof query !== "string" || query.length === 0) {
|
|
2665
|
-
throw new RequestError("Bad query", this.
|
|
2525
|
+
throw new RequestError("Bad query", this._url, this._method);
|
|
2666
2526
|
}
|
|
2667
2527
|
const graphQLBody = {
|
|
2668
2528
|
query: query,
|
|
2669
2529
|
};
|
|
2670
2530
|
if (variables !== undefined) {
|
|
2671
2531
|
if (typeof variables !== "object" || variables === null || Array.isArray(variables)) {
|
|
2672
|
-
throw new RequestError("Bad vars", this.
|
|
2532
|
+
throw new RequestError("Bad vars", this._url, this._method);
|
|
2673
2533
|
}
|
|
2674
2534
|
graphQLBody.variables = variables;
|
|
2675
2535
|
}
|
|
2676
2536
|
// Store GraphQL options if provided
|
|
2677
2537
|
if (options !== undefined) {
|
|
2678
2538
|
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
2679
|
-
throw new RequestError("Bad opts", this.
|
|
2539
|
+
throw new RequestError("Bad opts", this._url, this._method);
|
|
2680
2540
|
}
|
|
2681
2541
|
// Store only the known GraphQL options properties
|
|
2682
2542
|
const opts = options;
|
|
2683
|
-
this.
|
|
2543
|
+
this._gqlOpts = {
|
|
2684
2544
|
throwOnError: typeof opts.throwOnError === "boolean" ? opts.throwOnError : undefined,
|
|
2685
2545
|
};
|
|
2686
2546
|
}
|
|
@@ -2689,26 +2549,26 @@ class BodyRequest extends BaseRequest {
|
|
|
2689
2549
|
JSON.stringify(graphQLBody);
|
|
2690
2550
|
}
|
|
2691
2551
|
catch (error) {
|
|
2692
|
-
throw new RequestError(`Bad JSON: ${
|
|
2552
|
+
throw new RequestError(`Bad JSON: ${errorMessage(error)}`, this._url, this._method);
|
|
2693
2553
|
}
|
|
2694
|
-
this.
|
|
2695
|
-
this.
|
|
2696
|
-
this.
|
|
2554
|
+
this._body = graphQLBody;
|
|
2555
|
+
this._bodyType = BodyType.JSON;
|
|
2556
|
+
this._setCT("application/json");
|
|
2697
2557
|
return this;
|
|
2698
2558
|
}
|
|
2699
2559
|
/**
|
|
2700
2560
|
* Check if Content-Type header is already set (case-insensitive)
|
|
2701
2561
|
*/
|
|
2702
|
-
|
|
2703
|
-
const headers = this.
|
|
2562
|
+
_hasCT() {
|
|
2563
|
+
const headers = this._opts.headers;
|
|
2704
2564
|
if (typeof headers === "object" && headers !== null) {
|
|
2705
2565
|
const headersObj = headers;
|
|
2706
2566
|
return Object.keys(headersObj).some(header => header.toLowerCase() === "content-type");
|
|
2707
2567
|
}
|
|
2708
2568
|
return false;
|
|
2709
2569
|
}
|
|
2710
|
-
|
|
2711
|
-
if (!this.
|
|
2570
|
+
_setCT(contentType) {
|
|
2571
|
+
if (!this._hasCT()) {
|
|
2712
2572
|
this.withContentType(contentType);
|
|
2713
2573
|
}
|
|
2714
2574
|
}
|
|
@@ -2716,24 +2576,24 @@ class BodyRequest extends BaseRequest {
|
|
|
2716
2576
|
* Get the GraphQL options if set
|
|
2717
2577
|
* @returns The GraphQL options or undefined
|
|
2718
2578
|
*/
|
|
2719
|
-
|
|
2720
|
-
return this.
|
|
2579
|
+
_gql() {
|
|
2580
|
+
return this._gqlOpts;
|
|
2721
2581
|
}
|
|
2722
2582
|
/**
|
|
2723
2583
|
* Execute the request and return the ResponseWrapper
|
|
2724
2584
|
* Overrides the base implementation to add body handling
|
|
2725
2585
|
*/
|
|
2726
2586
|
async getResponse() {
|
|
2727
|
-
if (this.
|
|
2587
|
+
if (this._body !== undefined) {
|
|
2728
2588
|
// Remove previous body if it exists
|
|
2729
|
-
if (this.
|
|
2730
|
-
delete this.
|
|
2589
|
+
if (this._opts.body)
|
|
2590
|
+
delete this._opts.body;
|
|
2731
2591
|
// Process the body based on its type
|
|
2732
|
-
if (this.
|
|
2733
|
-
this.
|
|
2592
|
+
if (this._bodyType === BodyType.JSON) {
|
|
2593
|
+
this._opts.body = JSON.stringify(this._body);
|
|
2734
2594
|
}
|
|
2735
2595
|
else {
|
|
2736
|
-
this.
|
|
2596
|
+
this._opts.body = this._body;
|
|
2737
2597
|
}
|
|
2738
2598
|
}
|
|
2739
2599
|
return super.getResponse();
|
|
@@ -2750,10 +2610,7 @@ class BodyRequest extends BaseRequest {
|
|
|
2750
2610
|
* const data = await request.getData();
|
|
2751
2611
|
*/
|
|
2752
2612
|
class GetRequest extends BaseRequest {
|
|
2753
|
-
|
|
2754
|
-
constructor(url) {
|
|
2755
|
-
super(url);
|
|
2756
|
-
}
|
|
2613
|
+
_method = HttpMethod.GET;
|
|
2757
2614
|
}
|
|
2758
2615
|
/**
|
|
2759
2616
|
* HTTP HEAD request implementation
|
|
@@ -2764,10 +2621,7 @@ class GetRequest extends BaseRequest {
|
|
|
2764
2621
|
* const response = await request.getResponse();
|
|
2765
2622
|
*/
|
|
2766
2623
|
class HeadRequest extends BaseRequest {
|
|
2767
|
-
|
|
2768
|
-
constructor(url) {
|
|
2769
|
-
super(url);
|
|
2770
|
-
}
|
|
2624
|
+
_method = HttpMethod.HEAD;
|
|
2771
2625
|
}
|
|
2772
2626
|
/**
|
|
2773
2627
|
* HTTP OPTIONS request implementation
|
|
@@ -2778,10 +2632,7 @@ class HeadRequest extends BaseRequest {
|
|
|
2778
2632
|
* const response = await request.getResponse();
|
|
2779
2633
|
*/
|
|
2780
2634
|
class OptionsRequest extends BaseRequest {
|
|
2781
|
-
|
|
2782
|
-
constructor(url) {
|
|
2783
|
-
super(url);
|
|
2784
|
-
}
|
|
2635
|
+
_method = HttpMethod.OPTIONS;
|
|
2785
2636
|
}
|
|
2786
2637
|
/**
|
|
2787
2638
|
* HTTP DELETE request implementation
|
|
@@ -2792,10 +2643,7 @@ class OptionsRequest extends BaseRequest {
|
|
|
2792
2643
|
* await request.getData();
|
|
2793
2644
|
*/
|
|
2794
2645
|
class DeleteRequest extends BaseRequest {
|
|
2795
|
-
|
|
2796
|
-
constructor(url) {
|
|
2797
|
-
super(url);
|
|
2798
|
-
}
|
|
2646
|
+
_method = HttpMethod.DELETE;
|
|
2799
2647
|
}
|
|
2800
2648
|
/**
|
|
2801
2649
|
* HTTP POST request implementation
|
|
@@ -2807,10 +2655,7 @@ class DeleteRequest extends BaseRequest {
|
|
|
2807
2655
|
* const data = await request.getData();
|
|
2808
2656
|
*/
|
|
2809
2657
|
class PostRequest extends BodyRequest {
|
|
2810
|
-
|
|
2811
|
-
constructor(url) {
|
|
2812
|
-
super(url);
|
|
2813
|
-
}
|
|
2658
|
+
_method = HttpMethod.POST;
|
|
2814
2659
|
}
|
|
2815
2660
|
/**
|
|
2816
2661
|
* HTTP PUT request implementation
|
|
@@ -2822,10 +2667,7 @@ class PostRequest extends BodyRequest {
|
|
|
2822
2667
|
* const data = await request.getData();
|
|
2823
2668
|
*/
|
|
2824
2669
|
class PutRequest extends BodyRequest {
|
|
2825
|
-
|
|
2826
|
-
constructor(url) {
|
|
2827
|
-
super(url);
|
|
2828
|
-
}
|
|
2670
|
+
_method = HttpMethod.PUT;
|
|
2829
2671
|
}
|
|
2830
2672
|
/**
|
|
2831
2673
|
* HTTP PATCH request implementation
|
|
@@ -2837,10 +2679,7 @@ class PutRequest extends BodyRequest {
|
|
|
2837
2679
|
* const data = await request.getData();
|
|
2838
2680
|
*/
|
|
2839
2681
|
class PatchRequest extends BodyRequest {
|
|
2840
|
-
|
|
2841
|
-
constructor(url) {
|
|
2842
|
-
super(url);
|
|
2843
|
-
}
|
|
2682
|
+
_method = HttpMethod.PATCH;
|
|
2844
2683
|
}
|
|
2845
2684
|
|
|
2846
2685
|
/**
|
|
@@ -2952,73 +2791,60 @@ function options(url) {
|
|
|
2952
2791
|
* Internal API builder implementation.
|
|
2953
2792
|
*/
|
|
2954
2793
|
class ApiBuilderImpl {
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2794
|
+
_baseURL;
|
|
2795
|
+
_mods = [];
|
|
2796
|
+
_proxy;
|
|
2958
2797
|
withBaseURL(baseURL) {
|
|
2959
|
-
this.
|
|
2960
|
-
return this.
|
|
2798
|
+
this._baseURL = baseURL;
|
|
2799
|
+
return this._getProxy();
|
|
2961
2800
|
}
|
|
2962
|
-
|
|
2801
|
+
_resolve(url) {
|
|
2963
2802
|
if (!url)
|
|
2964
|
-
return this.
|
|
2803
|
+
return this._baseURL || "";
|
|
2965
2804
|
if (/^https?:\/\//.test(url))
|
|
2966
2805
|
return url;
|
|
2967
|
-
if (!this.
|
|
2806
|
+
if (!this._baseURL)
|
|
2968
2807
|
return url;
|
|
2969
|
-
return this.
|
|
2808
|
+
return this._baseURL.replace(/\/$/, "") + (url[0] === "/" ? url : "/" + url);
|
|
2970
2809
|
}
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2810
|
+
_new(Ctor, url) {
|
|
2811
|
+
const request = new Ctor(this._resolve(url));
|
|
2812
|
+
for (const modifier of this._mods)
|
|
2813
|
+
modifier(request);
|
|
2814
|
+
return request;
|
|
2975
2815
|
}
|
|
2976
2816
|
get(url) {
|
|
2977
|
-
|
|
2978
|
-
this.applyModifiers(request);
|
|
2979
|
-
return request;
|
|
2817
|
+
return this._new(GetRequest, url);
|
|
2980
2818
|
}
|
|
2981
2819
|
post(url) {
|
|
2982
|
-
|
|
2983
|
-
this.applyModifiers(request);
|
|
2984
|
-
return request;
|
|
2820
|
+
return this._new(PostRequest, url);
|
|
2985
2821
|
}
|
|
2986
2822
|
put(url) {
|
|
2987
|
-
|
|
2988
|
-
this.applyModifiers(request);
|
|
2989
|
-
return request;
|
|
2823
|
+
return this._new(PutRequest, url);
|
|
2990
2824
|
}
|
|
2991
2825
|
del(url) {
|
|
2992
|
-
|
|
2993
|
-
this.applyModifiers(request);
|
|
2994
|
-
return request;
|
|
2826
|
+
return this._new(DeleteRequest, url);
|
|
2995
2827
|
}
|
|
2996
2828
|
patch(url) {
|
|
2997
|
-
|
|
2998
|
-
this.applyModifiers(request);
|
|
2999
|
-
return request;
|
|
2829
|
+
return this._new(PatchRequest, url);
|
|
3000
2830
|
}
|
|
3001
2831
|
head(url) {
|
|
3002
|
-
|
|
3003
|
-
this.applyModifiers(request);
|
|
3004
|
-
return request;
|
|
2832
|
+
return this._new(HeadRequest, url);
|
|
3005
2833
|
}
|
|
3006
2834
|
options(url) {
|
|
3007
|
-
|
|
3008
|
-
this.applyModifiers(request);
|
|
3009
|
-
return request;
|
|
2835
|
+
return this._new(OptionsRequest, url);
|
|
3010
2836
|
}
|
|
3011
|
-
|
|
3012
|
-
this.
|
|
3013
|
-
return this.
|
|
2837
|
+
_add(modifier) {
|
|
2838
|
+
this._mods.push(modifier);
|
|
2839
|
+
return this._getProxy();
|
|
3014
2840
|
}
|
|
3015
|
-
|
|
3016
|
-
if (!this.
|
|
3017
|
-
this.
|
|
2841
|
+
_getProxy() {
|
|
2842
|
+
if (!this._proxy) {
|
|
2843
|
+
this._proxy = this._mkProxy();
|
|
3018
2844
|
}
|
|
3019
|
-
return this.
|
|
2845
|
+
return this._proxy;
|
|
3020
2846
|
}
|
|
3021
|
-
|
|
2847
|
+
_mkProxy() {
|
|
3022
2848
|
const disallowedMethods = new Set(["withBody", "withGraphQL", "withAbortController"]);
|
|
3023
2849
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
3024
2850
|
return new Proxy(this, {
|
|
@@ -3040,7 +2866,7 @@ class ApiBuilderImpl {
|
|
|
3040
2866
|
// If it's a 'with...' method or other chainable method, create a modifier for it
|
|
3041
2867
|
if (typeof prop === "string" && (prop.startsWith("with") || prop === "onRetry")) {
|
|
3042
2868
|
return (...args) => {
|
|
3043
|
-
return implTarget.
|
|
2869
|
+
return implTarget._add((request) => {
|
|
3044
2870
|
const method = request[prop];
|
|
3045
2871
|
if (typeof method === "function") {
|
|
3046
2872
|
method.apply(request, args);
|
|
@@ -3055,7 +2881,7 @@ class ApiBuilderImpl {
|
|
|
3055
2881
|
});
|
|
3056
2882
|
}
|
|
3057
2883
|
static create() {
|
|
3058
|
-
return new ApiBuilderImpl().
|
|
2884
|
+
return new ApiBuilderImpl()._getProxy();
|
|
3059
2885
|
}
|
|
3060
2886
|
}
|
|
3061
2887
|
/**
|