create-request 0.0.1-beta.2 → 0.0.1-beta.4
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 +188 -451
- package/dist/library/BaseRequest.d.ts +29 -3
- package/dist/library/BodyRequest.d.ts +14 -1
- package/dist/library/ResponseWrapper.d.ts +22 -11
- package/dist/library/enums.d.ts +24 -8
- package/dist/library/index.d.ts +5 -2
- package/dist/library/index.esm.js +390 -79
- package/dist/library/index.js +390 -78
- package/dist/library/types.d.ts +14 -3
- package/dist/library/utils/Config.d.ts +69 -0
- package/dist/library/utils/CookieUtils.d.ts +9 -0
- package/dist/library/utils/CsrfUtils.d.ts +24 -0
- package/package.json +6 -2
package/dist/library/index.js
CHANGED
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
exports.HttpMethod = void 0;
|
|
11
11
|
(function (HttpMethod) {
|
|
12
12
|
HttpMethod["GET"] = "GET";
|
|
13
|
-
HttpMethod["POST"] = "POST";
|
|
14
13
|
HttpMethod["PUT"] = "PUT";
|
|
15
|
-
HttpMethod["
|
|
16
|
-
HttpMethod["PATCH"] = "PATCH";
|
|
14
|
+
HttpMethod["POST"] = "POST";
|
|
17
15
|
HttpMethod["HEAD"] = "HEAD";
|
|
16
|
+
HttpMethod["PATCH"] = "PATCH";
|
|
17
|
+
HttpMethod["DELETE"] = "DELETE";
|
|
18
18
|
HttpMethod["OPTIONS"] = "OPTIONS";
|
|
19
19
|
})(exports.HttpMethod || (exports.HttpMethod = {}));
|
|
20
20
|
/**
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
*/
|
|
23
23
|
exports.RequestPriority = void 0;
|
|
24
24
|
(function (RequestPriority) {
|
|
25
|
-
RequestPriority["HIGH"] = "high";
|
|
26
25
|
RequestPriority["LOW"] = "low";
|
|
26
|
+
RequestPriority["HIGH"] = "high";
|
|
27
27
|
RequestPriority["AUTO"] = "auto";
|
|
28
28
|
})(exports.RequestPriority || (exports.RequestPriority = {}));
|
|
29
29
|
/**
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
*/
|
|
32
32
|
exports.CredentialsPolicy = void 0;
|
|
33
33
|
(function (CredentialsPolicy) {
|
|
34
|
+
CredentialsPolicy["OMIT"] = "omit";
|
|
34
35
|
CredentialsPolicy["INCLUDE"] = "include";
|
|
35
36
|
CredentialsPolicy["SAME_ORIGIN"] = "same-origin";
|
|
36
|
-
CredentialsPolicy["OMIT"] = "omit";
|
|
37
37
|
})(exports.CredentialsPolicy || (exports.CredentialsPolicy = {}));
|
|
38
38
|
/**
|
|
39
39
|
* Enum for request modes
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
*/
|
|
51
51
|
exports.RedirectMode = void 0;
|
|
52
52
|
(function (RedirectMode) {
|
|
53
|
-
RedirectMode["FOLLOW"] = "follow";
|
|
54
53
|
RedirectMode["ERROR"] = "error";
|
|
54
|
+
RedirectMode["FOLLOW"] = "follow";
|
|
55
55
|
RedirectMode["MANUAL"] = "manual";
|
|
56
56
|
})(exports.RedirectMode || (exports.RedirectMode = {}));
|
|
57
57
|
/**
|
|
@@ -60,12 +60,30 @@
|
|
|
60
60
|
exports.CacheMode = void 0;
|
|
61
61
|
(function (CacheMode) {
|
|
62
62
|
CacheMode["DEFAULT"] = "default";
|
|
63
|
-
CacheMode["NO_STORE"] = "no-store";
|
|
64
63
|
CacheMode["RELOAD"] = "reload";
|
|
64
|
+
CacheMode["NO_STORE"] = "no-store";
|
|
65
65
|
CacheMode["NO_CACHE"] = "no-cache";
|
|
66
66
|
CacheMode["FORCE_CACHE"] = "force-cache";
|
|
67
67
|
CacheMode["ONLY_IF_CACHED"] = "only-if-cached";
|
|
68
68
|
})(exports.CacheMode || (exports.CacheMode = {}));
|
|
69
|
+
/**
|
|
70
|
+
* Enum for cookie SameSite policies
|
|
71
|
+
*/
|
|
72
|
+
exports.SameSitePolicy = void 0;
|
|
73
|
+
(function (SameSitePolicy) {
|
|
74
|
+
SameSitePolicy["LAX"] = "Lax";
|
|
75
|
+
SameSitePolicy["NONE"] = "None";
|
|
76
|
+
SameSitePolicy["STRICT"] = "Strict";
|
|
77
|
+
})(exports.SameSitePolicy || (exports.SameSitePolicy = {}));
|
|
78
|
+
/**
|
|
79
|
+
* Enum for body types
|
|
80
|
+
*/
|
|
81
|
+
var BodyType;
|
|
82
|
+
(function (BodyType) {
|
|
83
|
+
BodyType["JSON"] = "json";
|
|
84
|
+
BodyType["STRING"] = "string";
|
|
85
|
+
BodyType["BINARY"] = "binary";
|
|
86
|
+
})(BodyType || (BodyType = {}));
|
|
69
87
|
|
|
70
88
|
class RequestError extends Error {
|
|
71
89
|
constructor(message, url, method, options = {}) {
|
|
@@ -139,44 +157,241 @@
|
|
|
139
157
|
/**
|
|
140
158
|
* Parse response as JSON
|
|
141
159
|
*/
|
|
142
|
-
async
|
|
160
|
+
async getJson() {
|
|
143
161
|
return this.response.json();
|
|
144
162
|
}
|
|
145
163
|
/**
|
|
146
164
|
* Get response as text
|
|
147
165
|
*/
|
|
148
|
-
async
|
|
166
|
+
async getText() {
|
|
149
167
|
return this.response.text();
|
|
150
168
|
}
|
|
151
169
|
/**
|
|
152
170
|
* Get response as Blob
|
|
153
171
|
*/
|
|
154
|
-
async
|
|
172
|
+
async getBlob() {
|
|
155
173
|
return this.response.blob();
|
|
156
174
|
}
|
|
157
175
|
/**
|
|
158
176
|
* Get response as ArrayBuffer
|
|
159
177
|
*/
|
|
160
|
-
async
|
|
178
|
+
async getArrayBuffer() {
|
|
161
179
|
return this.response.arrayBuffer();
|
|
162
180
|
}
|
|
163
181
|
/**
|
|
164
|
-
* Get response as ReadableStream
|
|
182
|
+
* Get response as ReadableStream for true streaming consumption.
|
|
183
|
+
* This allows you to process data incrementally as it arrives.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* const stream = await request.sendTo('https://example.com/large-file').getBody();
|
|
187
|
+
* const reader = stream.getReader();
|
|
188
|
+
*
|
|
189
|
+
* while (true) {
|
|
190
|
+
* const { done, value } = await reader.read();
|
|
191
|
+
* if (done) break;
|
|
192
|
+
* // Process chunk of data in 'value'
|
|
193
|
+
* }
|
|
165
194
|
*/
|
|
166
|
-
|
|
195
|
+
getBody() {
|
|
167
196
|
return this.response.body;
|
|
168
197
|
}
|
|
169
198
|
}
|
|
170
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Global configuration for create-request
|
|
202
|
+
*/
|
|
203
|
+
class Config {
|
|
204
|
+
constructor() {
|
|
205
|
+
// CSRF configuration
|
|
206
|
+
this.csrfHeaderName = "X-CSRF-Token";
|
|
207
|
+
this.xsrfCookieName = "XSRF-TOKEN";
|
|
208
|
+
this.xsrfHeaderName = "X-XSRF-TOKEN";
|
|
209
|
+
this.csrfToken = null;
|
|
210
|
+
this.enableAutoXsrf = true;
|
|
211
|
+
this.enableAntiCsrf = true; // X-Requested-With header
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get the singleton instance
|
|
215
|
+
*/
|
|
216
|
+
static getInstance() {
|
|
217
|
+
if (!Config.instance) {
|
|
218
|
+
Config.instance = new Config();
|
|
219
|
+
}
|
|
220
|
+
return Config.instance;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Set a global CSRF token to be used for all requests
|
|
224
|
+
*/
|
|
225
|
+
setCsrfToken(token) {
|
|
226
|
+
this.csrfToken = token;
|
|
227
|
+
return this;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get the global CSRF token
|
|
231
|
+
*/
|
|
232
|
+
getCsrfToken() {
|
|
233
|
+
return this.csrfToken;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Set the CSRF header name (default is X-CSRF-Token)
|
|
237
|
+
*/
|
|
238
|
+
setCsrfHeaderName(name) {
|
|
239
|
+
this.csrfHeaderName = name;
|
|
240
|
+
return this;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get the CSRF header name
|
|
244
|
+
*/
|
|
245
|
+
getCsrfHeaderName() {
|
|
246
|
+
return this.csrfHeaderName;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Set the XSRF cookie name to look for (default is XSRF-TOKEN)
|
|
250
|
+
*/
|
|
251
|
+
setXsrfCookieName(name) {
|
|
252
|
+
this.xsrfCookieName = name;
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Get the XSRF cookie name
|
|
257
|
+
*/
|
|
258
|
+
getXsrfCookieName() {
|
|
259
|
+
return this.xsrfCookieName;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Set the XSRF header name to send the token in (default is X-XSRF-TOKEN)
|
|
263
|
+
*/
|
|
264
|
+
setXsrfHeaderName(name) {
|
|
265
|
+
this.xsrfHeaderName = name;
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Get the XSRF header name
|
|
270
|
+
*/
|
|
271
|
+
getXsrfHeaderName() {
|
|
272
|
+
return this.xsrfHeaderName;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Enable or disable automatic XSRF token extraction from cookies
|
|
276
|
+
*/
|
|
277
|
+
setEnableAutoXsrf(enable) {
|
|
278
|
+
this.enableAutoXsrf = enable;
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Check if automatic XSRF is enabled
|
|
283
|
+
*/
|
|
284
|
+
isAutoXsrfEnabled() {
|
|
285
|
+
return this.enableAutoXsrf;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Enable or disable automatic addition of anti-CSRF headers
|
|
289
|
+
*/
|
|
290
|
+
setEnableAntiCsrf(enable) {
|
|
291
|
+
this.enableAntiCsrf = enable;
|
|
292
|
+
return this;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Check if anti-CSRF is enabled
|
|
296
|
+
*/
|
|
297
|
+
isAntiCsrfEnabled() {
|
|
298
|
+
return this.enableAntiCsrf;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Reset configuration to defaults
|
|
302
|
+
*/
|
|
303
|
+
reset() {
|
|
304
|
+
this.csrfToken = null;
|
|
305
|
+
this.csrfHeaderName = "X-CSRF-Token";
|
|
306
|
+
this.xsrfCookieName = "XSRF-TOKEN";
|
|
307
|
+
this.xsrfHeaderName = "X-XSRF-TOKEN";
|
|
308
|
+
this.enableAutoXsrf = true;
|
|
309
|
+
this.enableAntiCsrf = true;
|
|
310
|
+
return this;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
class CookieUtils {
|
|
315
|
+
/**
|
|
316
|
+
* Formats cookies for a request
|
|
317
|
+
* @param cookies Object containing cookie name-value pairs or cookie options
|
|
318
|
+
* @returns Formatted cookie string for the Cookie header
|
|
319
|
+
*/
|
|
320
|
+
static formatRequestCookies(cookies) {
|
|
321
|
+
const cookiePairs = [];
|
|
322
|
+
Object.entries(cookies).forEach(([name, valueOrOptions]) => {
|
|
323
|
+
let value;
|
|
324
|
+
if (typeof valueOrOptions === "string") {
|
|
325
|
+
value = valueOrOptions;
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
// Extract value from options object without validation
|
|
329
|
+
value = valueOrOptions.value;
|
|
330
|
+
}
|
|
331
|
+
// Add the cookie to the request
|
|
332
|
+
cookiePairs.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
|
|
333
|
+
});
|
|
334
|
+
return cookiePairs.join("; ");
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Utility class for CSRF token management
|
|
340
|
+
*/
|
|
341
|
+
class CsrfUtils {
|
|
342
|
+
/**
|
|
343
|
+
* Extracts CSRF token from a meta tag in the document head
|
|
344
|
+
* @param metaName The name attribute of the meta tag (default: "csrf-token")
|
|
345
|
+
* @returns The CSRF token or null if not found
|
|
346
|
+
*/
|
|
347
|
+
static getTokenFromMeta(metaName = "csrf-token") {
|
|
348
|
+
if (typeof document === "undefined") {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const meta = document.querySelector(`meta[name="${metaName}"]`);
|
|
352
|
+
return (meta === null || meta === void 0 ? void 0 : meta.getAttribute("content")) || null;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Extracts CSRF token from a cookie
|
|
356
|
+
* @param cookieName The name of the cookie containing the CSRF token
|
|
357
|
+
* @returns The CSRF token or null if not found
|
|
358
|
+
*/
|
|
359
|
+
static getTokenFromCookie(cookieName = "csrf-token") {
|
|
360
|
+
if (typeof document === "undefined") {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
const cookies = document.cookie.split(";");
|
|
364
|
+
for (const cookie of cookies) {
|
|
365
|
+
const [name, value] = cookie.trim().split("=");
|
|
366
|
+
if (name === cookieName) {
|
|
367
|
+
return decodeURIComponent(value);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Validates if the provided string is a potential CSRF token
|
|
374
|
+
* Checks if the token meets minimum security requirements (non-empty, not too short)
|
|
375
|
+
* @param token The token to validate
|
|
376
|
+
* @returns Whether the token is potentially valid
|
|
377
|
+
*/
|
|
378
|
+
static isValidToken(token) {
|
|
379
|
+
return typeof token === "string" && token.length >= 8;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
171
383
|
// Base class with common functionality for all request types
|
|
172
384
|
class BaseRequest {
|
|
173
385
|
constructor() {
|
|
174
|
-
this.requestOptions = {
|
|
386
|
+
this.requestOptions = {
|
|
387
|
+
headers: {},
|
|
388
|
+
};
|
|
175
389
|
this.queryParams = new URLSearchParams();
|
|
390
|
+
this.autoApplyCsrfProtection = true;
|
|
176
391
|
}
|
|
177
392
|
withHeaders(headers) {
|
|
178
393
|
this.requestOptions.headers = {
|
|
179
|
-
...
|
|
394
|
+
...this.requestOptions.headers,
|
|
180
395
|
...headers,
|
|
181
396
|
};
|
|
182
397
|
return this;
|
|
@@ -328,7 +543,7 @@
|
|
|
328
543
|
}
|
|
329
544
|
/**
|
|
330
545
|
* Sets cookies for the request
|
|
331
|
-
* @param cookies
|
|
546
|
+
* @param cookies Object containing cookie name-value pairs or cookie options
|
|
332
547
|
* @returns The instance for chaining
|
|
333
548
|
*/
|
|
334
549
|
withCookies(cookies) {
|
|
@@ -336,18 +551,58 @@
|
|
|
336
551
|
if (cookieEntries.length === 0) {
|
|
337
552
|
return this;
|
|
338
553
|
}
|
|
339
|
-
// Format cookies as name=value pairs
|
|
340
|
-
const cookieString = cookieEntries
|
|
341
|
-
.map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`)
|
|
342
|
-
.join("; ");
|
|
343
554
|
// Get current headers or initialize empty object
|
|
344
555
|
const currentHeaders = this.requestOptions.headers || {};
|
|
345
|
-
//
|
|
346
|
-
|
|
556
|
+
// Get the existing cookie header in a case-insensitive way
|
|
557
|
+
let existingCookies = "";
|
|
558
|
+
const cookieHeaderName = Object.keys(currentHeaders).find(header => header.toLowerCase() === "cookie");
|
|
559
|
+
if (cookieHeaderName)
|
|
560
|
+
existingCookies = currentHeaders[cookieHeaderName];
|
|
561
|
+
const cookieString = CookieUtils.formatRequestCookies(cookies);
|
|
562
|
+
// Combine with existing cookies if present
|
|
347
563
|
const newCookieValue = existingCookies ? `${existingCookies}; ${cookieString}` : cookieString;
|
|
348
|
-
// Set the Cookie header
|
|
564
|
+
// Set the Cookie header, preserving original case if it exists
|
|
565
|
+
const headerName = cookieHeaderName || "Cookie";
|
|
349
566
|
return this.withHeaders({
|
|
350
|
-
|
|
567
|
+
[headerName]: newCookieValue,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Set a single cookie
|
|
572
|
+
* @param name Cookie name
|
|
573
|
+
* @param value Cookie value or options object
|
|
574
|
+
* @returns The instance for chaining
|
|
575
|
+
*/
|
|
576
|
+
withCookie(name, value) {
|
|
577
|
+
return this.withCookies({ [name]: value });
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Sets a CSRF token in the request headers
|
|
581
|
+
* @param token The CSRF token
|
|
582
|
+
* @param headerName The name of the header to use (default: X-CSRF-Token)
|
|
583
|
+
* @returns The instance for chaining
|
|
584
|
+
*/
|
|
585
|
+
withCsrfToken(token, headerName = "X-CSRF-Token") {
|
|
586
|
+
return this.withHeaders({
|
|
587
|
+
[headerName]: token,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Disables automatic anti-CSRF protection.
|
|
592
|
+
* By default, X-Requested-With: XMLHttpRequest header is sent with all requests.
|
|
593
|
+
* @returns The instance for chaining
|
|
594
|
+
*/
|
|
595
|
+
withoutCsrfProtection() {
|
|
596
|
+
this.autoApplyCsrfProtection = false;
|
|
597
|
+
return this;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Sets common security headers to help prevent CSRF attacks
|
|
601
|
+
* @returns The instance for chaining
|
|
602
|
+
*/
|
|
603
|
+
withAntiCsrfHeaders() {
|
|
604
|
+
return this.withHeaders({
|
|
605
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
351
606
|
});
|
|
352
607
|
}
|
|
353
608
|
/**
|
|
@@ -357,6 +612,41 @@
|
|
|
357
612
|
sendTo(url) {
|
|
358
613
|
// Format the URL with query parameters
|
|
359
614
|
url = this.formatUrlWithQueryParams(url);
|
|
615
|
+
// Apply automatic CSRF protection if enabled
|
|
616
|
+
if (this.autoApplyCsrfProtection) {
|
|
617
|
+
const config = Config.getInstance();
|
|
618
|
+
// Apply anti-CSRF headers if enabled
|
|
619
|
+
if (config.isAntiCsrfEnabled()) {
|
|
620
|
+
this.withHeaders({
|
|
621
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
// Apply global CSRF token if set
|
|
625
|
+
const globalToken = config.getCsrfToken();
|
|
626
|
+
if (globalToken) {
|
|
627
|
+
// Check if local token exists
|
|
628
|
+
const headers = this.requestOptions.headers;
|
|
629
|
+
const hasLocalToken = Object.keys(headers).some(key => key === "X-CSRF-Token" || key === config.getCsrfHeaderName());
|
|
630
|
+
if (!hasLocalToken) {
|
|
631
|
+
this.withHeaders({
|
|
632
|
+
[config.getCsrfHeaderName()]: globalToken,
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
// check for XSRF token in cookies and send it as a header
|
|
637
|
+
if (config.isAutoXsrfEnabled() && typeof document !== "undefined") {
|
|
638
|
+
const xsrfToken = CsrfUtils.getTokenFromCookie(config.getXsrfCookieName());
|
|
639
|
+
if (xsrfToken && CsrfUtils.isValidToken(xsrfToken)) {
|
|
640
|
+
const headers = this.requestOptions.headers;
|
|
641
|
+
const hasLocalToken = Object.keys(headers).some(key => key === "X-XSRF-TOKEN" || key === config.getXsrfHeaderName());
|
|
642
|
+
if (!hasLocalToken) {
|
|
643
|
+
this.withHeaders({
|
|
644
|
+
[config.getXsrfHeaderName()]: xsrfToken,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
360
650
|
// Cast the request options to RequestInit to ensure compatibility
|
|
361
651
|
const fetchOptions = {
|
|
362
652
|
...this.requestOptions,
|
|
@@ -366,28 +656,22 @@
|
|
|
366
656
|
const basePromise = !this.requestOptions.retries
|
|
367
657
|
? this.executeRequest(url, fetchOptions)
|
|
368
658
|
: this.executeWithRetries(url, fetchOptions);
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const response = await basePromise;
|
|
374
|
-
return response.json();
|
|
659
|
+
const responsePromise = basePromise;
|
|
660
|
+
// Attach convenience methods that begin processing immediately
|
|
661
|
+
responsePromise.getJson = async () => {
|
|
662
|
+
return basePromise.then(response => response.getJson());
|
|
375
663
|
};
|
|
376
|
-
responsePromise.
|
|
377
|
-
|
|
378
|
-
return response.text();
|
|
664
|
+
responsePromise.getText = async () => {
|
|
665
|
+
return basePromise.then(response => response.getText());
|
|
379
666
|
};
|
|
380
|
-
responsePromise.
|
|
381
|
-
|
|
382
|
-
return response.blob();
|
|
667
|
+
responsePromise.getBlob = async () => {
|
|
668
|
+
return basePromise.then(response => response.getBlob());
|
|
383
669
|
};
|
|
384
|
-
responsePromise.
|
|
385
|
-
|
|
386
|
-
return response.arrayBuffer();
|
|
670
|
+
responsePromise.getArrayBuffer = async () => {
|
|
671
|
+
return basePromise.then(response => response.getArrayBuffer());
|
|
387
672
|
};
|
|
388
|
-
responsePromise.
|
|
389
|
-
|
|
390
|
-
return response.body();
|
|
673
|
+
responsePromise.getBody = async () => {
|
|
674
|
+
return basePromise.then(response => response.getBody());
|
|
391
675
|
};
|
|
392
676
|
return responsePromise;
|
|
393
677
|
}
|
|
@@ -414,7 +698,7 @@
|
|
|
414
698
|
* @returns A wrapped response object
|
|
415
699
|
*/
|
|
416
700
|
async executeWithRetries(url, fetchOptions) {
|
|
417
|
-
let
|
|
701
|
+
let attempt = 0;
|
|
418
702
|
const maxRetries = this.requestOptions.retries || 0;
|
|
419
703
|
// eslint-disable-next-line no-constant-condition
|
|
420
704
|
while (true) {
|
|
@@ -425,12 +709,12 @@
|
|
|
425
709
|
const requestError = error instanceof RequestError
|
|
426
710
|
? error
|
|
427
711
|
: RequestError.networkError(url, fetchOptions.method, error);
|
|
428
|
-
if (
|
|
712
|
+
if (attempt >= maxRetries || requestError.timeoutError) {
|
|
429
713
|
throw requestError;
|
|
430
714
|
}
|
|
431
|
-
|
|
715
|
+
attempt++;
|
|
432
716
|
if (this.requestOptions.onRetry) {
|
|
433
|
-
await this.requestOptions.onRetry({
|
|
717
|
+
await this.requestOptions.onRetry({ attempt, error: requestError });
|
|
434
718
|
}
|
|
435
719
|
}
|
|
436
720
|
}
|
|
@@ -476,47 +760,72 @@
|
|
|
476
760
|
}
|
|
477
761
|
}
|
|
478
762
|
|
|
479
|
-
|
|
763
|
+
/**
|
|
764
|
+
* Base class for requests that can have a body (POST, PUT, PATCH)
|
|
765
|
+
*/
|
|
480
766
|
class BodyRequest extends BaseRequest {
|
|
767
|
+
constructor() {
|
|
768
|
+
super(...arguments);
|
|
769
|
+
this.bodyProcessed = false;
|
|
770
|
+
}
|
|
481
771
|
/**
|
|
482
|
-
* Sets the
|
|
772
|
+
* Sets the body of the request
|
|
773
|
+
* @param body - The request body
|
|
483
774
|
*/
|
|
484
775
|
withBody(body) {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
489
|
-
// Handle special body types that don't need transformation
|
|
490
|
-
if (body instanceof Blob ||
|
|
491
|
-
body instanceof FormData ||
|
|
492
|
-
body instanceof URLSearchParams ||
|
|
493
|
-
body instanceof ArrayBuffer ||
|
|
494
|
-
body instanceof ReadableStream) {
|
|
495
|
-
this.requestOptions.body = body;
|
|
496
|
-
// For FormData, make sure to NOT explicitly set Content-Type
|
|
497
|
-
// The browser will set it automatically with the correct boundary
|
|
498
|
-
// If we set it manually, it would be missing the boundary parameter
|
|
499
|
-
return this;
|
|
500
|
-
}
|
|
501
|
-
// Handle strings
|
|
776
|
+
this.body = body;
|
|
777
|
+
this.bodyProcessed = false;
|
|
778
|
+
// Set body type and validate
|
|
502
779
|
if (typeof body === "string") {
|
|
503
|
-
this.
|
|
504
|
-
|
|
780
|
+
this.bodyType = BodyType.STRING;
|
|
781
|
+
this.setContentTypeIfNeeded("text/plain");
|
|
505
782
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
783
|
+
else if (body !== null &&
|
|
784
|
+
typeof body === "object" &&
|
|
785
|
+
!(body instanceof FormData ||
|
|
786
|
+
body instanceof Blob ||
|
|
787
|
+
body instanceof ArrayBuffer ||
|
|
788
|
+
body instanceof URLSearchParams ||
|
|
789
|
+
body instanceof ReadableStream)) {
|
|
790
|
+
this.bodyType = BodyType.JSON;
|
|
791
|
+
this.setContentTypeIfNeeded("application/json");
|
|
792
|
+
// Validate JSON is stringifiable early
|
|
793
|
+
try {
|
|
794
|
+
JSON.stringify(body);
|
|
795
|
+
}
|
|
796
|
+
catch (error) {
|
|
797
|
+
throw new Error(`Failed to stringify request body: ${String(error)}`);
|
|
798
|
+
}
|
|
513
799
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
throw new Error(`Failed to stringify request body: ${errorMessage}`);
|
|
800
|
+
else {
|
|
801
|
+
this.bodyType = BodyType.BINARY;
|
|
517
802
|
}
|
|
518
803
|
return this;
|
|
519
804
|
}
|
|
805
|
+
setContentTypeIfNeeded(contentType) {
|
|
806
|
+
// Only set if not already set
|
|
807
|
+
const headers = this.requestOptions.headers;
|
|
808
|
+
if (!Object.keys(headers).some(header => header.toLowerCase() === "content-type")) {
|
|
809
|
+
this.withContentType(contentType);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Send the request to the specified URL
|
|
814
|
+
* Overrides the base implementation to add body handling
|
|
815
|
+
*/
|
|
816
|
+
sendTo(url) {
|
|
817
|
+
// Process body only once
|
|
818
|
+
if (!this.bodyProcessed && this.body !== undefined && !this.requestOptions.body) {
|
|
819
|
+
if (this.bodyType === BodyType.JSON) {
|
|
820
|
+
this.requestOptions.body = JSON.stringify(this.body);
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
this.requestOptions.body = this.body;
|
|
824
|
+
}
|
|
825
|
+
this.bodyProcessed = true;
|
|
826
|
+
}
|
|
827
|
+
return super.sendTo(url);
|
|
828
|
+
}
|
|
520
829
|
}
|
|
521
830
|
|
|
522
831
|
// Methods without body
|
|
@@ -608,7 +917,7 @@
|
|
|
608
917
|
return new OptionsRequest();
|
|
609
918
|
}
|
|
610
919
|
|
|
611
|
-
// Create the main API object
|
|
920
|
+
// Create the main API object with configuration
|
|
612
921
|
const create = {
|
|
613
922
|
get,
|
|
614
923
|
post,
|
|
@@ -617,8 +926,11 @@
|
|
|
617
926
|
patch,
|
|
618
927
|
head,
|
|
619
928
|
options,
|
|
929
|
+
// Add configuration access
|
|
930
|
+
config: Config.getInstance(),
|
|
620
931
|
};
|
|
621
932
|
|
|
933
|
+
exports.CookieUtils = CookieUtils;
|
|
622
934
|
exports.DeleteRequest = DeleteRequest;
|
|
623
935
|
exports.GetRequest = GetRequest;
|
|
624
936
|
exports.HeadRequest = HeadRequest;
|
package/dist/library/types.d.ts
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
|
-
import { HttpMethod, RequestPriority, CredentialsPolicy, RequestMode, RedirectMode, CacheMode } from "./enums";
|
|
1
|
+
import { HttpMethod, RequestPriority, CredentialsPolicy, RequestMode, RedirectMode, CacheMode, SameSitePolicy } from "./enums";
|
|
2
2
|
import type { RequestError } from "./RequestError";
|
|
3
3
|
export type Body = null | Blob | string | object | FormData | ArrayBuffer | URLSearchParams | ReadableStream;
|
|
4
4
|
export type RetryCallback = (options: {
|
|
5
|
-
|
|
5
|
+
attempt: number;
|
|
6
6
|
error: RequestError;
|
|
7
7
|
}) => void | Promise<void>;
|
|
8
|
-
export
|
|
8
|
+
export interface CookieOptions {
|
|
9
|
+
value: string;
|
|
10
|
+
secure?: boolean;
|
|
11
|
+
httpOnly?: boolean;
|
|
12
|
+
sameSite?: SameSitePolicy;
|
|
13
|
+
expires?: Date;
|
|
14
|
+
path?: string;
|
|
15
|
+
domain?: string;
|
|
16
|
+
maxAge?: number;
|
|
17
|
+
}
|
|
18
|
+
export type CookiesRecord = Record<string, string | CookieOptions>;
|
|
19
|
+
export { CacheMode, HttpMethod, RequestMode, RedirectMode, SameSitePolicy, RequestPriority, CredentialsPolicy, };
|
|
9
20
|
export interface RequestOptions extends Omit<RequestInit, "signal" | "body" | "method" | "credentials" | "mode" | "redirect" | "cache" | "priority"> {
|
|
10
21
|
timeout?: number;
|
|
11
22
|
retries?: number;
|