create-request 0.0.1-beta.5 → 0.0.1-beta.7
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 +145 -0
- package/dist/library/BaseRequest.d.ts +31 -2
- package/dist/library/BodyRequest.d.ts +0 -1
- package/dist/library/ResponseWrapper.d.ts +8 -41
- package/dist/library/index.d.ts +6 -2
- package/dist/library/index.esm.js +633 -110
- package/dist/library/index.js +636 -109
- package/dist/library/types/cache.d.ts +62 -0
- package/dist/library/types.d.ts +1 -1
- package/dist/library/utils/CacheManager.d.ts +35 -0
- package/dist/library/utils/CacheStorage.d.ts +57 -0
- package/dist/library/utils/Config.d.ts +1 -1
- package/dist/library/utils/CsrfUtils.d.ts +2 -2
- package/dist/library/utils/StorageProvider.d.ts +33 -0
- package/package.json +1 -1
|
@@ -121,73 +121,440 @@ class RequestError extends Error {
|
|
|
121
121
|
*/
|
|
122
122
|
class ResponseWrapper {
|
|
123
123
|
constructor(response) {
|
|
124
|
+
this.bodyUsed = false;
|
|
124
125
|
this.response = response;
|
|
125
|
-
this.ok = response.status >= 200 && response.status < 300; // Standard Fetch API behavior
|
|
126
126
|
}
|
|
127
|
-
|
|
128
|
-
* Get the raw Response object
|
|
129
|
-
*/
|
|
130
|
-
get raw() {
|
|
131
|
-
return this.response;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Get the HTTP status code
|
|
135
|
-
*/
|
|
127
|
+
// Wrapper properties
|
|
136
128
|
get status() {
|
|
137
129
|
return this.response.status;
|
|
138
130
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Get the HTTP status text
|
|
141
|
-
*/
|
|
142
131
|
get statusText() {
|
|
143
132
|
return this.response.statusText;
|
|
144
133
|
}
|
|
145
|
-
/**
|
|
146
|
-
* Get response headers
|
|
147
|
-
*/
|
|
148
134
|
get headers() {
|
|
149
135
|
return this.response.headers;
|
|
150
136
|
}
|
|
137
|
+
get ok() {
|
|
138
|
+
return this.response.ok;
|
|
139
|
+
}
|
|
140
|
+
get raw() {
|
|
141
|
+
return this.response;
|
|
142
|
+
}
|
|
143
|
+
// Response body methods with caching
|
|
144
|
+
async getJson() {
|
|
145
|
+
if (this.jsonCache !== undefined) {
|
|
146
|
+
return this.jsonCache;
|
|
147
|
+
}
|
|
148
|
+
if (this.bodyUsed) {
|
|
149
|
+
// If we already have text, try to parse it as JSON
|
|
150
|
+
if (this.textCache !== undefined) {
|
|
151
|
+
try {
|
|
152
|
+
// Fix unsafe assignment by explicitly casting to unknown first
|
|
153
|
+
this.jsonCache = JSON.parse(this.textCache);
|
|
154
|
+
return this.jsonCache;
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
throw new Error("Cannot parse cached text as JSON");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
throw new Error("Response body has already been consumed in a different format");
|
|
161
|
+
}
|
|
162
|
+
this.bodyUsed = true;
|
|
163
|
+
this.jsonCache = (await this.response.json());
|
|
164
|
+
return this.jsonCache;
|
|
165
|
+
}
|
|
166
|
+
async getText() {
|
|
167
|
+
if (this.textCache !== undefined) {
|
|
168
|
+
return this.textCache;
|
|
169
|
+
}
|
|
170
|
+
if (this.bodyUsed) {
|
|
171
|
+
// If we already have JSON, convert it to text
|
|
172
|
+
if (this.jsonCache !== undefined) {
|
|
173
|
+
this.textCache = JSON.stringify(this.jsonCache);
|
|
174
|
+
return this.textCache;
|
|
175
|
+
}
|
|
176
|
+
// If we have blob, convert to text
|
|
177
|
+
if (this.blobCache !== undefined) {
|
|
178
|
+
this.textCache = await this.blobCache.text();
|
|
179
|
+
return this.textCache;
|
|
180
|
+
}
|
|
181
|
+
throw new Error("Response body has already been consumed in a format that cannot be converted to text");
|
|
182
|
+
}
|
|
183
|
+
this.bodyUsed = true;
|
|
184
|
+
this.textCache = await this.response.text();
|
|
185
|
+
return this.textCache;
|
|
186
|
+
}
|
|
187
|
+
async getBlob() {
|
|
188
|
+
if (this.blobCache !== undefined) {
|
|
189
|
+
return this.blobCache;
|
|
190
|
+
}
|
|
191
|
+
if (this.bodyUsed) {
|
|
192
|
+
// If we have text, convert to Blob
|
|
193
|
+
if (this.textCache !== undefined) {
|
|
194
|
+
this.blobCache = new Blob([this.textCache], { type: "text/plain" });
|
|
195
|
+
return this.blobCache;
|
|
196
|
+
}
|
|
197
|
+
throw new Error("Response body has already been consumed in a format that cannot be converted to Blob");
|
|
198
|
+
}
|
|
199
|
+
this.bodyUsed = true;
|
|
200
|
+
this.blobCache = await this.response.blob();
|
|
201
|
+
return this.blobCache;
|
|
202
|
+
}
|
|
203
|
+
async getArrayBuffer() {
|
|
204
|
+
if (this.arrayBufferCache !== undefined) {
|
|
205
|
+
return this.arrayBufferCache;
|
|
206
|
+
}
|
|
207
|
+
if (this.bodyUsed) {
|
|
208
|
+
// If we have text, convert to ArrayBuffer
|
|
209
|
+
if (this.textCache !== undefined) {
|
|
210
|
+
const encoder = new TextEncoder();
|
|
211
|
+
// Fix ArrayBufferLike by explicitly casting to ArrayBuffer
|
|
212
|
+
const uint8Array = encoder.encode(this.textCache);
|
|
213
|
+
this.arrayBufferCache = uint8Array.buffer;
|
|
214
|
+
return this.arrayBufferCache;
|
|
215
|
+
}
|
|
216
|
+
// If we have blob, convert to ArrayBuffer
|
|
217
|
+
if (this.blobCache !== undefined) {
|
|
218
|
+
this.arrayBufferCache = await this.blobCache.arrayBuffer();
|
|
219
|
+
return this.arrayBufferCache;
|
|
220
|
+
}
|
|
221
|
+
throw new Error("Response body has already been consumed in a format that cannot be converted to ArrayBuffer");
|
|
222
|
+
}
|
|
223
|
+
this.bodyUsed = true;
|
|
224
|
+
this.arrayBufferCache = await this.response.arrayBuffer();
|
|
225
|
+
return this.arrayBufferCache;
|
|
226
|
+
}
|
|
227
|
+
getBody() {
|
|
228
|
+
if (this.bodyUsed) {
|
|
229
|
+
throw new Error("Response body has already been consumed");
|
|
230
|
+
}
|
|
231
|
+
this.bodyUsed = true;
|
|
232
|
+
return this.response.body;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Parse size strings like "10MB" into bytes
|
|
238
|
+
* @param size Size as number or string with units
|
|
239
|
+
*/
|
|
240
|
+
function parseSize(size) {
|
|
241
|
+
var _a;
|
|
242
|
+
if (typeof size === "number")
|
|
243
|
+
return size;
|
|
244
|
+
const match = size.match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB)?$/i);
|
|
245
|
+
if (!match)
|
|
246
|
+
return 0;
|
|
247
|
+
const num = parseFloat(match[1]);
|
|
248
|
+
const unit = (_a = match[2]) === null || _a === void 0 ? void 0 : _a.toUpperCase();
|
|
249
|
+
switch (unit) {
|
|
250
|
+
case "KB":
|
|
251
|
+
return num * 1024;
|
|
252
|
+
case "MB":
|
|
253
|
+
return num * 1024 * 1024;
|
|
254
|
+
case "GB":
|
|
255
|
+
return num * 1024 * 1024 * 1024;
|
|
256
|
+
default:
|
|
257
|
+
return num;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Create a memory storage provider using Map
|
|
262
|
+
*/
|
|
263
|
+
function createMemoryStorage() {
|
|
264
|
+
const store = new Map();
|
|
265
|
+
return {
|
|
266
|
+
get(key) {
|
|
267
|
+
return store.get(key) || null;
|
|
268
|
+
},
|
|
269
|
+
set(key, value) {
|
|
270
|
+
store.set(key, value);
|
|
271
|
+
},
|
|
272
|
+
has(key) {
|
|
273
|
+
return store.has(key);
|
|
274
|
+
},
|
|
275
|
+
delete(key) {
|
|
276
|
+
store.delete(key);
|
|
277
|
+
},
|
|
278
|
+
clear() {
|
|
279
|
+
store.clear();
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Create a localStorage-based storage provider
|
|
285
|
+
*/
|
|
286
|
+
function createLocalStorageStorage() {
|
|
287
|
+
return {
|
|
288
|
+
get(key) {
|
|
289
|
+
if (typeof localStorage === "undefined")
|
|
290
|
+
return null;
|
|
291
|
+
return localStorage.getItem(key);
|
|
292
|
+
},
|
|
293
|
+
set(key, value) {
|
|
294
|
+
if (typeof localStorage === "undefined")
|
|
295
|
+
return;
|
|
296
|
+
try {
|
|
297
|
+
localStorage.setItem(key, value);
|
|
298
|
+
}
|
|
299
|
+
catch (e) {
|
|
300
|
+
// Handle quota exceeded errors
|
|
301
|
+
console.warn("Cache storage quota exceeded, clearing cache");
|
|
302
|
+
void this.clear();
|
|
303
|
+
try {
|
|
304
|
+
localStorage.setItem(key, value);
|
|
305
|
+
}
|
|
306
|
+
catch (e) {
|
|
307
|
+
console.error("Failed to set cache item even after clearing cache");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
has(key) {
|
|
312
|
+
if (typeof localStorage === "undefined")
|
|
313
|
+
return false;
|
|
314
|
+
return localStorage.getItem(key) !== null;
|
|
315
|
+
},
|
|
316
|
+
delete(key) {
|
|
317
|
+
if (typeof localStorage === "undefined")
|
|
318
|
+
return;
|
|
319
|
+
localStorage.removeItem(key);
|
|
320
|
+
},
|
|
321
|
+
clear() {
|
|
322
|
+
if (typeof localStorage === "undefined")
|
|
323
|
+
return;
|
|
324
|
+
localStorage.clear();
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Create a sessionStorage-based storage provider
|
|
330
|
+
*/
|
|
331
|
+
function createSessionStorageStorage() {
|
|
332
|
+
return {
|
|
333
|
+
get(key) {
|
|
334
|
+
if (typeof sessionStorage === "undefined")
|
|
335
|
+
return null;
|
|
336
|
+
return sessionStorage.getItem(key);
|
|
337
|
+
},
|
|
338
|
+
set(key, value) {
|
|
339
|
+
if (typeof sessionStorage === "undefined")
|
|
340
|
+
return;
|
|
341
|
+
try {
|
|
342
|
+
sessionStorage.setItem(key, value);
|
|
343
|
+
}
|
|
344
|
+
catch (e) {
|
|
345
|
+
// Handle quota exceeded errors
|
|
346
|
+
console.warn("Cache storage quota exceeded, clearing cache");
|
|
347
|
+
void this.clear();
|
|
348
|
+
try {
|
|
349
|
+
sessionStorage.setItem(key, value);
|
|
350
|
+
}
|
|
351
|
+
catch (e) {
|
|
352
|
+
console.error("Failed to set cache item even after clearing cache");
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
has(key) {
|
|
357
|
+
if (typeof sessionStorage === "undefined")
|
|
358
|
+
return false;
|
|
359
|
+
return sessionStorage.getItem(key) !== null;
|
|
360
|
+
},
|
|
361
|
+
delete(key) {
|
|
362
|
+
if (typeof sessionStorage === "undefined")
|
|
363
|
+
return;
|
|
364
|
+
sessionStorage.removeItem(key);
|
|
365
|
+
},
|
|
366
|
+
clear() {
|
|
367
|
+
if (typeof sessionStorage === "undefined")
|
|
368
|
+
return;
|
|
369
|
+
sessionStorage.clear();
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Create a storage provider from a cache options object
|
|
375
|
+
*/
|
|
376
|
+
function createStorageProvider(options) {
|
|
377
|
+
const { storage } = options || {};
|
|
378
|
+
if (!storage) {
|
|
379
|
+
// Default to memory storage
|
|
380
|
+
return createMemoryStorage();
|
|
381
|
+
}
|
|
382
|
+
// Check if storage is already a StorageProvider
|
|
383
|
+
if (typeof storage.get === "function" && typeof storage.set === "function") {
|
|
384
|
+
return storage;
|
|
385
|
+
}
|
|
386
|
+
// Check if it's localStorage or sessionStorage
|
|
387
|
+
if (storage === localStorage) {
|
|
388
|
+
return createLocalStorageStorage();
|
|
389
|
+
}
|
|
390
|
+
if (storage === sessionStorage) {
|
|
391
|
+
return createSessionStorageStorage();
|
|
392
|
+
}
|
|
393
|
+
// Check if it's a Map
|
|
394
|
+
if (storage instanceof Map) {
|
|
395
|
+
return {
|
|
396
|
+
get(key) {
|
|
397
|
+
return storage.get(key) || null;
|
|
398
|
+
},
|
|
399
|
+
set(key, value, _ttl) {
|
|
400
|
+
storage.set(key, value);
|
|
401
|
+
},
|
|
402
|
+
has(key) {
|
|
403
|
+
return storage.has(key);
|
|
404
|
+
},
|
|
405
|
+
delete(key) {
|
|
406
|
+
storage.delete(key);
|
|
407
|
+
},
|
|
408
|
+
clear() {
|
|
409
|
+
storage.clear();
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
// Default to memory storage
|
|
414
|
+
console.warn("Unsupported storage type, falling back to memory storage");
|
|
415
|
+
return createMemoryStorage();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Cache manager that handles storage operations
|
|
420
|
+
*/
|
|
421
|
+
class CacheManager {
|
|
422
|
+
constructor(options = {}) {
|
|
423
|
+
this.entryCount = 0;
|
|
424
|
+
this.totalSize = 0;
|
|
425
|
+
this.options = options;
|
|
426
|
+
this.storage = createStorageProvider(options);
|
|
427
|
+
}
|
|
151
428
|
/**
|
|
152
|
-
*
|
|
429
|
+
* Generate a cache key based on request details
|
|
153
430
|
*/
|
|
154
|
-
|
|
155
|
-
|
|
431
|
+
generateKey(url, method, headers, body) {
|
|
432
|
+
// Use custom key generator if provided
|
|
433
|
+
if (this.options.keyGenerator) {
|
|
434
|
+
const customKey = this.options.keyGenerator(url, method, headers, body);
|
|
435
|
+
return this.options.keyPrefix ? `${this.options.keyPrefix}:${customKey}` : customKey;
|
|
436
|
+
}
|
|
437
|
+
// Default key generation
|
|
438
|
+
let key = `${method}:${url}`;
|
|
439
|
+
// Add vary headers to key if specified
|
|
440
|
+
if (this.options.varyByHeaders && headers) {
|
|
441
|
+
const varyHeaders = {};
|
|
442
|
+
this.options.varyByHeaders.forEach(header => {
|
|
443
|
+
const normalizedHeader = header.toLowerCase();
|
|
444
|
+
// Find the header in case-insensitive way
|
|
445
|
+
Object.keys(headers).forEach(h => {
|
|
446
|
+
if (h.toLowerCase() === normalizedHeader) {
|
|
447
|
+
varyHeaders[normalizedHeader] = headers[h];
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
if (Object.keys(varyHeaders).length > 0) {
|
|
452
|
+
key += `:${JSON.stringify(varyHeaders)}`;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// Include body in cache key for non-GET requests if it's a simple value
|
|
456
|
+
if (method !== "GET" && body !== undefined && body !== null) {
|
|
457
|
+
try {
|
|
458
|
+
if (typeof body === "string" || typeof body === "number" || typeof body === "boolean") {
|
|
459
|
+
key += `:${String(body)}`;
|
|
460
|
+
}
|
|
461
|
+
else if (typeof body === "object") {
|
|
462
|
+
key += `:${JSON.stringify(body)}`;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch (e) {
|
|
466
|
+
console.warn("Failed to stringify request body for cache key");
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// Add prefix if specified
|
|
470
|
+
return this.options.keyPrefix ? `${this.options.keyPrefix}:${key}` : key;
|
|
156
471
|
}
|
|
157
472
|
/**
|
|
158
|
-
* Get
|
|
473
|
+
* Get a value from cache
|
|
159
474
|
*/
|
|
160
|
-
async
|
|
161
|
-
|
|
475
|
+
async get(key) {
|
|
476
|
+
const value = await Promise.resolve(this.storage.get(key));
|
|
477
|
+
if (!value)
|
|
478
|
+
return null;
|
|
479
|
+
try {
|
|
480
|
+
const entry = JSON.parse(value);
|
|
481
|
+
// Check if the entry has expired
|
|
482
|
+
if (entry.expiry && entry.expiry < Date.now()) {
|
|
483
|
+
await Promise.resolve(this.storage.delete(key));
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
return entry;
|
|
487
|
+
}
|
|
488
|
+
catch (e) {
|
|
489
|
+
// If we can't parse the cached value, remove it
|
|
490
|
+
await Promise.resolve(this.storage.delete(key));
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
162
493
|
}
|
|
163
494
|
/**
|
|
164
|
-
*
|
|
495
|
+
* Set a value in cache
|
|
165
496
|
*/
|
|
166
|
-
async
|
|
167
|
-
|
|
497
|
+
async set(key, value, headers) {
|
|
498
|
+
const now = Date.now();
|
|
499
|
+
const ttl = this.options.ttl;
|
|
500
|
+
const entry = {
|
|
501
|
+
value,
|
|
502
|
+
timestamp: now,
|
|
503
|
+
expiry: ttl ? now + ttl : null,
|
|
504
|
+
headers,
|
|
505
|
+
};
|
|
506
|
+
const serialized = JSON.stringify(entry);
|
|
507
|
+
// Check size constraints if applicable
|
|
508
|
+
if (this.options.maxSize) {
|
|
509
|
+
const maxBytes = parseSize(this.options.maxSize);
|
|
510
|
+
const entrySize = new TextEncoder().encode(serialized).length;
|
|
511
|
+
// If a single entry exceeds the max size, reject it without clearing the cache
|
|
512
|
+
if (entrySize > maxBytes) {
|
|
513
|
+
console.warn(`Cache entry size (${entrySize} bytes) exceeds maximum cache size (${maxBytes} bytes)`);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
// Only clear if adding this entry would exceed the total limit
|
|
517
|
+
if (this.totalSize + entrySize > maxBytes) {
|
|
518
|
+
// Simple strategy: clear everything
|
|
519
|
+
// A more sophisticated strategy would remove oldest entries first
|
|
520
|
+
await Promise.resolve(this.storage.clear());
|
|
521
|
+
this.totalSize = 0;
|
|
522
|
+
this.entryCount = 0;
|
|
523
|
+
}
|
|
524
|
+
this.totalSize += entrySize;
|
|
525
|
+
}
|
|
526
|
+
// Check max entries constraint
|
|
527
|
+
if (this.options.maxEntries) {
|
|
528
|
+
this.entryCount++;
|
|
529
|
+
if (this.entryCount > this.options.maxEntries) {
|
|
530
|
+
// Simple strategy: clear everything
|
|
531
|
+
// A more sophisticated strategy would use LRU cache
|
|
532
|
+
await Promise.resolve(this.storage.clear());
|
|
533
|
+
this.entryCount = 1;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// Pass ttl to storage provider if available
|
|
537
|
+
await Promise.resolve(this.storage.set(key, serialized, ttl));
|
|
168
538
|
}
|
|
169
539
|
/**
|
|
170
|
-
*
|
|
540
|
+
* Check if a key exists in the cache
|
|
171
541
|
*/
|
|
172
|
-
async
|
|
173
|
-
return this.
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
*
|
|
177
|
-
* This allows you to process data incrementally as it arrives.
|
|
178
|
-
*
|
|
179
|
-
* @example
|
|
180
|
-
* const stream = await request.sendTo('https://example.com/large-file').getBody();
|
|
181
|
-
* const reader = stream.getReader();
|
|
182
|
-
*
|
|
183
|
-
* while (true) {
|
|
184
|
-
* const { done, value } = await reader.read();
|
|
185
|
-
* if (done) break;
|
|
186
|
-
* // Process chunk of data in 'value'
|
|
187
|
-
* }
|
|
542
|
+
async has(key) {
|
|
543
|
+
return Promise.resolve(this.storage.has(key));
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Delete a key from the cache
|
|
188
547
|
*/
|
|
189
|
-
|
|
190
|
-
return this.
|
|
548
|
+
async delete(key) {
|
|
549
|
+
return Promise.resolve(this.storage.delete(key));
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Clear the entire cache
|
|
553
|
+
*/
|
|
554
|
+
async clear() {
|
|
555
|
+
this.entryCount = 0;
|
|
556
|
+
this.totalSize = 0;
|
|
557
|
+
return Promise.resolve(this.storage.clear());
|
|
191
558
|
}
|
|
192
559
|
}
|
|
193
560
|
|
|
@@ -292,15 +659,15 @@ class Config {
|
|
|
292
659
|
return this.enableAntiCsrf;
|
|
293
660
|
}
|
|
294
661
|
/**
|
|
295
|
-
* Reset configuration to defaults
|
|
662
|
+
* Reset all configuration options to their defaults
|
|
296
663
|
*/
|
|
297
664
|
reset() {
|
|
298
665
|
this.csrfToken = null;
|
|
299
666
|
this.csrfHeaderName = "X-CSRF-Token";
|
|
667
|
+
this.enableAntiCsrf = true;
|
|
300
668
|
this.xsrfCookieName = "XSRF-TOKEN";
|
|
301
669
|
this.xsrfHeaderName = "X-XSRF-TOKEN";
|
|
302
670
|
this.enableAutoXsrf = true;
|
|
303
|
-
this.enableAntiCsrf = true;
|
|
304
671
|
return this;
|
|
305
672
|
}
|
|
306
673
|
}
|
|
@@ -365,12 +732,34 @@ class CsrfUtils {
|
|
|
365
732
|
}
|
|
366
733
|
/**
|
|
367
734
|
* Validates if the provided string is a potential CSRF token
|
|
368
|
-
* Checks if the token meets
|
|
735
|
+
* Checks if the token meets security requirements
|
|
369
736
|
* @param token The token to validate
|
|
370
|
-
* @returns Whether the token is
|
|
737
|
+
* @returns Whether the token is valid
|
|
371
738
|
*/
|
|
372
739
|
static isValidToken(token) {
|
|
373
|
-
|
|
740
|
+
// Basic checks for null/undefined and type
|
|
741
|
+
if (typeof token !== "string") {
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
if (token.length < 8)
|
|
745
|
+
return false;
|
|
746
|
+
// If token is longer than 10 chars, perform additional security checks
|
|
747
|
+
if (token.length > 10) {
|
|
748
|
+
// Check for valid character set (alphanumeric & common token symbols)
|
|
749
|
+
const validTokenRegex = /^[A-Za-z0-9\-_=+/.]+$/;
|
|
750
|
+
if (!validTokenRegex.test(token)) {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
// For longer tokens, check for sufficient entropy (at least 2 character types)
|
|
754
|
+
const hasUpperCase = /[A-Z]/.test(token);
|
|
755
|
+
const hasLowerCase = /[a-z]/.test(token);
|
|
756
|
+
const hasNumbers = /[0-9]/.test(token);
|
|
757
|
+
const hasSpecials = /[-_=+/.]/.test(token);
|
|
758
|
+
const characterTypesCount = [hasUpperCase, hasLowerCase, hasNumbers, hasSpecials].filter(Boolean).length;
|
|
759
|
+
return characterTypesCount >= 2;
|
|
760
|
+
}
|
|
761
|
+
// For shorter tokens (8-10 chars), just return true if we reached here
|
|
762
|
+
return true;
|
|
374
763
|
}
|
|
375
764
|
}
|
|
376
765
|
|
|
@@ -382,6 +771,7 @@ class BaseRequest {
|
|
|
382
771
|
};
|
|
383
772
|
this.queryParams = new URLSearchParams();
|
|
384
773
|
this.autoApplyCsrfProtection = true;
|
|
774
|
+
this.cacheEnabled = false;
|
|
385
775
|
}
|
|
386
776
|
withHeaders(headers) {
|
|
387
777
|
this.requestOptions.headers = {
|
|
@@ -391,10 +781,14 @@ class BaseRequest {
|
|
|
391
781
|
return this;
|
|
392
782
|
}
|
|
393
783
|
withTimeout(timeout) {
|
|
784
|
+
if (!Number.isFinite(timeout) || timeout <= 0)
|
|
785
|
+
throw new Error("Timeout must be a positive number");
|
|
394
786
|
this.requestOptions.timeout = timeout;
|
|
395
787
|
return this;
|
|
396
788
|
}
|
|
397
789
|
withRetries(retries) {
|
|
790
|
+
if (!Number.isInteger(retries) || retries < 0)
|
|
791
|
+
throw new Error("Retry count must be a non-negative integer");
|
|
398
792
|
this.requestOptions.retries = retries;
|
|
399
793
|
return this;
|
|
400
794
|
}
|
|
@@ -479,13 +873,16 @@ class BaseRequest {
|
|
|
479
873
|
/**
|
|
480
874
|
* Adds a single query parameter
|
|
481
875
|
* @param key - The parameter name
|
|
482
|
-
* @param value - The parameter value
|
|
876
|
+
* @param value - The parameter value, can be a single value or array of values
|
|
483
877
|
* @returns The instance for chaining
|
|
484
878
|
*/
|
|
485
879
|
withQueryParam(key, value) {
|
|
486
|
-
if (value
|
|
880
|
+
if (value === null || value === undefined)
|
|
881
|
+
return this;
|
|
882
|
+
if (Array.isArray(value))
|
|
883
|
+
value.forEach(v => this.queryParams.append(key, String(v)));
|
|
884
|
+
else
|
|
487
885
|
this.queryParams.append(key, String(value));
|
|
488
|
-
}
|
|
489
886
|
return this;
|
|
490
887
|
}
|
|
491
888
|
/**
|
|
@@ -524,9 +921,23 @@ class BaseRequest {
|
|
|
524
921
|
* @returns The instance for chaining
|
|
525
922
|
*/
|
|
526
923
|
withBasicAuth(username, password) {
|
|
527
|
-
const credentials =
|
|
924
|
+
const credentials = this.encodeBase64(`${username}:${password}`);
|
|
528
925
|
return this.withAuthorization(`Basic ${credentials}`);
|
|
529
926
|
}
|
|
927
|
+
/**
|
|
928
|
+
* Cross-environment base64 encoding
|
|
929
|
+
* Works in both browser and Node.js environments
|
|
930
|
+
*/
|
|
931
|
+
encodeBase64(str) {
|
|
932
|
+
// Browser environment
|
|
933
|
+
if (typeof btoa === "function")
|
|
934
|
+
return btoa(str);
|
|
935
|
+
// Node.js environment
|
|
936
|
+
if (typeof Buffer !== "undefined")
|
|
937
|
+
return Buffer.from(str).toString("base64");
|
|
938
|
+
// Fallback (should never happen in modern environments)
|
|
939
|
+
throw new Error("Base64 encoding is not supported in this environment");
|
|
940
|
+
}
|
|
530
941
|
/**
|
|
531
942
|
* Sets the bearer token for authentication
|
|
532
943
|
* @param token - The bearer token
|
|
@@ -535,6 +946,15 @@ class BaseRequest {
|
|
|
535
946
|
withBearerToken(token) {
|
|
536
947
|
return this.withAuthorization(`Bearer ${token}`);
|
|
537
948
|
}
|
|
949
|
+
/**
|
|
950
|
+
* Helper function to check for header presence in a case-insensitive way
|
|
951
|
+
* @param headers Headers object
|
|
952
|
+
* @param headerName Header name to check
|
|
953
|
+
* @returns Boolean indicating if the header exists (case-insensitive)
|
|
954
|
+
*/
|
|
955
|
+
hasHeader(headers, headerName) {
|
|
956
|
+
return Object.keys(headers).some(key => key.toLowerCase() === headerName.toLowerCase());
|
|
957
|
+
}
|
|
538
958
|
/**
|
|
539
959
|
* Sets cookies for the request
|
|
540
960
|
* @param cookies Object containing cookie name-value pairs or cookie options
|
|
@@ -547,19 +967,34 @@ class BaseRequest {
|
|
|
547
967
|
}
|
|
548
968
|
// Get current headers or initialize empty object
|
|
549
969
|
const currentHeaders = this.requestOptions.headers || {};
|
|
550
|
-
// Get
|
|
551
|
-
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
970
|
+
// Get all existing cookie headers with different cases
|
|
971
|
+
const cookieValues = [];
|
|
972
|
+
const cookieHeaderKeys = [];
|
|
973
|
+
Object.keys(currentHeaders).forEach(key => {
|
|
974
|
+
if (key.toLowerCase() === "cookie") {
|
|
975
|
+
// Don't add empty cookie values
|
|
976
|
+
if (currentHeaders[key])
|
|
977
|
+
cookieValues.push(currentHeaders[key]);
|
|
978
|
+
cookieHeaderKeys.push(key);
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
// Format the new cookies
|
|
555
982
|
const cookieString = CookieUtils.formatRequestCookies(cookies);
|
|
556
|
-
//
|
|
557
|
-
const
|
|
558
|
-
//
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
983
|
+
// Choose which header name to use - preserve existing case if possible
|
|
984
|
+
const headerName = cookieHeaderKeys.length > 0 ? cookieHeaderKeys[0] : "Cookie";
|
|
985
|
+
// Combine all existing cookie values with the new ones
|
|
986
|
+
const combinedCookieValue = [...cookieValues, cookieString].filter(Boolean).join("; ");
|
|
987
|
+
// Create a new headers object without any cookie headers
|
|
988
|
+
const newHeaders = { ...currentHeaders };
|
|
989
|
+
cookieHeaderKeys.forEach(key => {
|
|
990
|
+
delete newHeaders[key];
|
|
562
991
|
});
|
|
992
|
+
// Set the new combined cookie header
|
|
993
|
+
this.requestOptions.headers = {
|
|
994
|
+
...newHeaders,
|
|
995
|
+
[headerName]: combinedCookieValue,
|
|
996
|
+
};
|
|
997
|
+
return this;
|
|
563
998
|
}
|
|
564
999
|
/**
|
|
565
1000
|
* Set a single cookie
|
|
@@ -599,6 +1034,16 @@ class BaseRequest {
|
|
|
599
1034
|
"X-Requested-With": "XMLHttpRequest",
|
|
600
1035
|
});
|
|
601
1036
|
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Enables caching for the request with the specified options
|
|
1039
|
+
* @param options The cache configuration options
|
|
1040
|
+
* @returns The instance for chaining
|
|
1041
|
+
*/
|
|
1042
|
+
withCache(options = {}) {
|
|
1043
|
+
this.cacheEnabled = true;
|
|
1044
|
+
this.cacheManager = new CacheManager(options);
|
|
1045
|
+
return this;
|
|
1046
|
+
}
|
|
602
1047
|
/**
|
|
603
1048
|
* Send the request to the specified URL
|
|
604
1049
|
* @param url The URL to send the request to
|
|
@@ -618,12 +1063,13 @@ class BaseRequest {
|
|
|
618
1063
|
// Apply global CSRF token if set
|
|
619
1064
|
const globalToken = config.getCsrfToken();
|
|
620
1065
|
if (globalToken) {
|
|
621
|
-
// Check if local token exists
|
|
1066
|
+
// Check if local token exists using case-insensitive comparison
|
|
622
1067
|
const headers = this.requestOptions.headers;
|
|
623
|
-
const
|
|
1068
|
+
const csrfHeaderName = config.getCsrfHeaderName();
|
|
1069
|
+
const hasLocalToken = this.hasHeader(headers, "X-CSRF-Token") || this.hasHeader(headers, csrfHeaderName);
|
|
624
1070
|
if (!hasLocalToken) {
|
|
625
1071
|
this.withHeaders({
|
|
626
|
-
[
|
|
1072
|
+
[csrfHeaderName]: globalToken,
|
|
627
1073
|
});
|
|
628
1074
|
}
|
|
629
1075
|
}
|
|
@@ -632,10 +1078,11 @@ class BaseRequest {
|
|
|
632
1078
|
const xsrfToken = CsrfUtils.getTokenFromCookie(config.getXsrfCookieName());
|
|
633
1079
|
if (xsrfToken && CsrfUtils.isValidToken(xsrfToken)) {
|
|
634
1080
|
const headers = this.requestOptions.headers;
|
|
635
|
-
const
|
|
1081
|
+
const xsrfHeaderName = config.getXsrfHeaderName();
|
|
1082
|
+
const hasLocalToken = this.hasHeader(headers, "X-XSRF-TOKEN") || this.hasHeader(headers, xsrfHeaderName);
|
|
636
1083
|
if (!hasLocalToken) {
|
|
637
1084
|
this.withHeaders({
|
|
638
|
-
[
|
|
1085
|
+
[xsrfHeaderName]: xsrfToken,
|
|
639
1086
|
});
|
|
640
1087
|
}
|
|
641
1088
|
}
|
|
@@ -646,10 +1093,15 @@ class BaseRequest {
|
|
|
646
1093
|
...this.requestOptions,
|
|
647
1094
|
method: this.method,
|
|
648
1095
|
};
|
|
649
|
-
// Create the base promise
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
1096
|
+
// Create the base promise - handle caching if enabled
|
|
1097
|
+
let basePromise;
|
|
1098
|
+
if (this.cacheEnabled && this.cacheManager) {
|
|
1099
|
+
const cacheKey = this.cacheManager.generateKey(url, this.method, this.requestOptions.headers, this.requestOptions.body);
|
|
1100
|
+
basePromise = this.executeWithCache(url, fetchOptions, cacheKey);
|
|
1101
|
+
}
|
|
1102
|
+
else {
|
|
1103
|
+
basePromise = !this.requestOptions.retries ? this.executeRequest(url, fetchOptions) : this.executeWithRetries(url, fetchOptions);
|
|
1104
|
+
}
|
|
653
1105
|
const responsePromise = basePromise;
|
|
654
1106
|
// Attach convenience methods that begin processing immediately
|
|
655
1107
|
responsePromise.getJson = async () => {
|
|
@@ -678,12 +1130,21 @@ class BaseRequest {
|
|
|
678
1130
|
if (!this.queryParams.toString()) {
|
|
679
1131
|
return url;
|
|
680
1132
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1133
|
+
try {
|
|
1134
|
+
// Try to use the URL constructor (works for absolute URLs)
|
|
1135
|
+
const urlObj = new URL(url);
|
|
1136
|
+
// Merge our query params with any that might be in the URL already
|
|
1137
|
+
this.queryParams.forEach((value, key) => {
|
|
1138
|
+
urlObj.searchParams.append(key, value);
|
|
1139
|
+
});
|
|
1140
|
+
return urlObj.toString();
|
|
1141
|
+
}
|
|
1142
|
+
catch (error) {
|
|
1143
|
+
// Handle relative URLs
|
|
1144
|
+
const hasExistingParams = url.includes("?");
|
|
1145
|
+
const separator = hasExistingParams ? "&" : "?";
|
|
1146
|
+
return `${url}${separator}${this.queryParams.toString()}`;
|
|
1147
|
+
}
|
|
687
1148
|
}
|
|
688
1149
|
/**
|
|
689
1150
|
* Executes a request with configured retry logic
|
|
@@ -694,38 +1155,65 @@ class BaseRequest {
|
|
|
694
1155
|
async executeWithRetries(url, fetchOptions) {
|
|
695
1156
|
let attempt = 0;
|
|
696
1157
|
const maxRetries = this.requestOptions.retries || 0;
|
|
697
|
-
|
|
698
|
-
while (true) {
|
|
1158
|
+
do {
|
|
699
1159
|
try {
|
|
700
1160
|
return await this.executeRequest(url, fetchOptions);
|
|
701
1161
|
}
|
|
702
1162
|
catch (error) {
|
|
703
|
-
const requestError = error instanceof RequestError
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
if (attempt >= maxRetries || requestError.timeoutError) {
|
|
1163
|
+
const requestError = error instanceof RequestError ? error : RequestError.networkError(url, fetchOptions.method, error);
|
|
1164
|
+
// Don't retry on timeout errors or if we've reached the max retries
|
|
1165
|
+
if (attempt >= maxRetries || requestError.timeoutError)
|
|
707
1166
|
throw requestError;
|
|
708
|
-
}
|
|
709
1167
|
attempt++;
|
|
710
1168
|
if (this.requestOptions.onRetry) {
|
|
711
1169
|
await this.requestOptions.onRetry({ attempt, error: requestError });
|
|
712
1170
|
}
|
|
713
1171
|
}
|
|
714
|
-
}
|
|
1172
|
+
} while (attempt <= maxRetries);
|
|
1173
|
+
// If we somehow get here, we throw an error to avoid undefined behavior
|
|
1174
|
+
throw new RequestError(`Max retries reached (${maxRetries}) but request still failed`, url, fetchOptions.method);
|
|
715
1175
|
}
|
|
716
1176
|
async executeRequest(url, fetchOptions) {
|
|
717
1177
|
// Create a request controller for timeout if needed, or use the provided one
|
|
718
1178
|
const controller = this.abortController || new AbortController();
|
|
719
1179
|
const { signal } = controller;
|
|
720
1180
|
// Use the controller signal for the fetch request, but don't overwrite an existing signal
|
|
721
|
-
// that might have been set in requestOptions if it exists
|
|
722
1181
|
fetchOptions.signal = fetchOptions.signal || signal;
|
|
723
1182
|
let timeoutId;
|
|
724
|
-
//
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1183
|
+
// Flag to track if abort was caused by our timeout
|
|
1184
|
+
let abortedByTimeout = false;
|
|
1185
|
+
// Handle timeout regardless of whether an external controller is provided
|
|
1186
|
+
if (this.requestOptions.timeout) {
|
|
1187
|
+
if (this.abortController) {
|
|
1188
|
+
// If we have an external controller, create a separate timeout controller
|
|
1189
|
+
// that won't interfere with the external one
|
|
1190
|
+
// Use AbortSignal.timeout if available (modern browsers)
|
|
1191
|
+
if (typeof AbortSignal.timeout === "function") {
|
|
1192
|
+
const timeoutSignal = AbortSignal.timeout(this.requestOptions.timeout);
|
|
1193
|
+
// Add event listener to propagate the abort to the main controller
|
|
1194
|
+
timeoutSignal.addEventListener("abort", () => {
|
|
1195
|
+
if (!controller.signal.aborted) {
|
|
1196
|
+
abortedByTimeout = true;
|
|
1197
|
+
controller.abort();
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
else {
|
|
1202
|
+
// Fallback for browsers without AbortSignal.timeout
|
|
1203
|
+
timeoutId = setTimeout(() => {
|
|
1204
|
+
if (!controller.signal.aborted) {
|
|
1205
|
+
abortedByTimeout = true;
|
|
1206
|
+
controller.abort();
|
|
1207
|
+
}
|
|
1208
|
+
}, this.requestOptions.timeout);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
else {
|
|
1212
|
+
timeoutId = setTimeout(() => {
|
|
1213
|
+
abortedByTimeout = true;
|
|
1214
|
+
controller.abort();
|
|
1215
|
+
}, this.requestOptions.timeout);
|
|
1216
|
+
}
|
|
729
1217
|
}
|
|
730
1218
|
try {
|
|
731
1219
|
let response;
|
|
@@ -733,11 +1221,11 @@ class BaseRequest {
|
|
|
733
1221
|
response = await fetch(url, fetchOptions);
|
|
734
1222
|
}
|
|
735
1223
|
catch (error) {
|
|
736
|
-
// Check if this is an abort error
|
|
737
|
-
if (error instanceof DOMException && error.name === "AbortError" &&
|
|
1224
|
+
// Check if this is an abort error that was specifically caused by our timeout
|
|
1225
|
+
if (error instanceof DOMException && error.name === "AbortError" && abortedByTimeout) {
|
|
738
1226
|
throw RequestError.timeout(url, fetchOptions.method, this.requestOptions.timeout);
|
|
739
1227
|
}
|
|
740
|
-
// Otherwise it's a network error
|
|
1228
|
+
// Otherwise it's either a user-triggered abort or another network error
|
|
741
1229
|
throw RequestError.networkError(url, fetchOptions.method, error);
|
|
742
1230
|
}
|
|
743
1231
|
if (!response.ok) {
|
|
@@ -752,23 +1240,60 @@ class BaseRequest {
|
|
|
752
1240
|
}
|
|
753
1241
|
}
|
|
754
1242
|
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Executes a request using cache when possible
|
|
1245
|
+
* @param url The formatted URL to send the request to
|
|
1246
|
+
* @param fetchOptions The fetch options to use
|
|
1247
|
+
* @param cacheKey The cache key for this request
|
|
1248
|
+
*/
|
|
1249
|
+
async executeWithCache(url, fetchOptions, cacheKey) {
|
|
1250
|
+
// Try to get from cache first
|
|
1251
|
+
if (this.cacheManager) {
|
|
1252
|
+
const cachedEntry = await this.cacheManager.get(cacheKey);
|
|
1253
|
+
if (cachedEntry) {
|
|
1254
|
+
// Create a synthetic Response object from cached data
|
|
1255
|
+
const cachedResponse = new Response(JSON.stringify(cachedEntry.value), {
|
|
1256
|
+
status: 200,
|
|
1257
|
+
headers: new Headers(cachedEntry.headers || {}),
|
|
1258
|
+
});
|
|
1259
|
+
return new ResponseWrapper(cachedResponse);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
// Not in cache, make the actual request
|
|
1263
|
+
const response = await (!this.requestOptions.retries ? this.executeRequest(url, fetchOptions) : this.executeWithRetries(url, fetchOptions));
|
|
1264
|
+
// Cache the successful response if caching is enabled
|
|
1265
|
+
// Only cache successful responses
|
|
1266
|
+
if (response.ok && this.cacheManager) {
|
|
1267
|
+
// Clone the response so we don't consume it
|
|
1268
|
+
try {
|
|
1269
|
+
const responseData = await response.getJson();
|
|
1270
|
+
// Extract headers to store in cache
|
|
1271
|
+
const headers = {};
|
|
1272
|
+
response.headers.forEach((value, key) => {
|
|
1273
|
+
headers[key] = value;
|
|
1274
|
+
});
|
|
1275
|
+
// Store in cache
|
|
1276
|
+
await this.cacheManager.set(cacheKey, responseData, headers);
|
|
1277
|
+
}
|
|
1278
|
+
catch (error) {
|
|
1279
|
+
// Failed to cache, but we can still return the response
|
|
1280
|
+
console.warn("Failed to cache response:", error);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
return response;
|
|
1284
|
+
}
|
|
755
1285
|
}
|
|
756
1286
|
|
|
757
1287
|
/**
|
|
758
1288
|
* Base class for requests that can have a body (POST, PUT, PATCH)
|
|
759
1289
|
*/
|
|
760
1290
|
class BodyRequest extends BaseRequest {
|
|
761
|
-
constructor() {
|
|
762
|
-
super(...arguments);
|
|
763
|
-
this.bodyProcessed = false;
|
|
764
|
-
}
|
|
765
1291
|
/**
|
|
766
1292
|
* Sets the body of the request
|
|
767
1293
|
* @param body - The request body
|
|
768
1294
|
*/
|
|
769
1295
|
withBody(body) {
|
|
770
1296
|
this.body = body;
|
|
771
|
-
this.bodyProcessed = false;
|
|
772
1297
|
// Set body type and validate
|
|
773
1298
|
if (typeof body === "string") {
|
|
774
1299
|
this.bodyType = BodyType.STRING;
|
|
@@ -776,11 +1301,7 @@ class BodyRequest extends BaseRequest {
|
|
|
776
1301
|
}
|
|
777
1302
|
else if (body !== null &&
|
|
778
1303
|
typeof body === "object" &&
|
|
779
|
-
!(body instanceof FormData ||
|
|
780
|
-
body instanceof Blob ||
|
|
781
|
-
body instanceof ArrayBuffer ||
|
|
782
|
-
body instanceof URLSearchParams ||
|
|
783
|
-
body instanceof ReadableStream)) {
|
|
1304
|
+
!(body instanceof FormData || body instanceof Blob || body instanceof ArrayBuffer || body instanceof URLSearchParams || body instanceof ReadableStream)) {
|
|
784
1305
|
this.bodyType = BodyType.JSON;
|
|
785
1306
|
this.setContentTypeIfNeeded("application/json");
|
|
786
1307
|
// Validate JSON is stringifiable early
|
|
@@ -808,15 +1329,17 @@ class BodyRequest extends BaseRequest {
|
|
|
808
1329
|
* Overrides the base implementation to add body handling
|
|
809
1330
|
*/
|
|
810
1331
|
sendTo(url) {
|
|
811
|
-
|
|
812
|
-
|
|
1332
|
+
if (this.body !== undefined) {
|
|
1333
|
+
// Remove previous body if it exists
|
|
1334
|
+
if (this.requestOptions.body)
|
|
1335
|
+
delete this.requestOptions.body;
|
|
1336
|
+
// Process the body based on its type
|
|
813
1337
|
if (this.bodyType === BodyType.JSON) {
|
|
814
1338
|
this.requestOptions.body = JSON.stringify(this.body);
|
|
815
1339
|
}
|
|
816
1340
|
else {
|
|
817
1341
|
this.requestOptions.body = this.body;
|
|
818
1342
|
}
|
|
819
|
-
this.bodyProcessed = true;
|
|
820
1343
|
}
|
|
821
1344
|
return super.sendTo(url);
|
|
822
1345
|
}
|
|
@@ -924,4 +1447,4 @@ const create = {
|
|
|
924
1447
|
config: Config.getInstance(),
|
|
925
1448
|
};
|
|
926
1449
|
|
|
927
|
-
export { CacheMode, CookieUtils, CredentialsPolicy, DeleteRequest, GetRequest, HeadRequest, HttpMethod, OptionsRequest, PatchRequest, PostRequest, PutRequest, RedirectMode, RequestError, RequestMode, RequestPriority, ResponseWrapper, SameSitePolicy, create as default };
|
|
1450
|
+
export { CacheManager, CacheMode, CookieUtils, CredentialsPolicy, DeleteRequest, GetRequest, HeadRequest, HttpMethod, OptionsRequest, PatchRequest, PostRequest, PutRequest, RedirectMode, RequestError, RequestMode, RequestPriority, ResponseWrapper, SameSitePolicy, createLocalStorageStorage, createMemoryStorage, createSessionStorageStorage, create as default };
|