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