create-request 0.0.1-beta.11 → 0.0.1-beta.13

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.
@@ -1,1907 +1 @@
1
- /**
2
- * Enum for HTTP methods
3
- */
4
- var HttpMethod;
5
- (function (HttpMethod) {
6
- HttpMethod["GET"] = "GET";
7
- HttpMethod["PUT"] = "PUT";
8
- HttpMethod["POST"] = "POST";
9
- HttpMethod["HEAD"] = "HEAD";
10
- HttpMethod["PATCH"] = "PATCH";
11
- HttpMethod["DELETE"] = "DELETE";
12
- HttpMethod["OPTIONS"] = "OPTIONS";
13
- })(HttpMethod || (HttpMethod = {}));
14
- /**
15
- * Enum for request priorities
16
- */
17
- var RequestPriority;
18
- (function (RequestPriority) {
19
- RequestPriority["LOW"] = "low";
20
- RequestPriority["HIGH"] = "high";
21
- RequestPriority["AUTO"] = "auto";
22
- })(RequestPriority || (RequestPriority = {}));
23
- /**
24
- * Enum for credentials policies
25
- */
26
- var CredentialsPolicy;
27
- (function (CredentialsPolicy) {
28
- CredentialsPolicy["OMIT"] = "omit";
29
- CredentialsPolicy["INCLUDE"] = "include";
30
- CredentialsPolicy["SAME_ORIGIN"] = "same-origin";
31
- })(CredentialsPolicy || (CredentialsPolicy = {}));
32
- /**
33
- * Enum for request modes
34
- */
35
- var RequestMode;
36
- (function (RequestMode) {
37
- RequestMode["CORS"] = "cors";
38
- RequestMode["NO_CORS"] = "no-cors";
39
- RequestMode["SAME_ORIGIN"] = "same-origin";
40
- RequestMode["NAVIGATE"] = "navigate";
41
- })(RequestMode || (RequestMode = {}));
42
- /**
43
- * Enum for redirect modes
44
- */
45
- var RedirectMode;
46
- (function (RedirectMode) {
47
- RedirectMode["ERROR"] = "error";
48
- RedirectMode["FOLLOW"] = "follow";
49
- RedirectMode["MANUAL"] = "manual";
50
- })(RedirectMode || (RedirectMode = {}));
51
- /**
52
- * Enum for cache modes
53
- */
54
- var CacheMode;
55
- (function (CacheMode) {
56
- CacheMode["DEFAULT"] = "default";
57
- CacheMode["RELOAD"] = "reload";
58
- CacheMode["NO_STORE"] = "no-store";
59
- CacheMode["NO_CACHE"] = "no-cache";
60
- CacheMode["FORCE_CACHE"] = "force-cache";
61
- CacheMode["ONLY_IF_CACHED"] = "only-if-cached";
62
- })(CacheMode || (CacheMode = {}));
63
- /**
64
- * Enum for cookie SameSite policies
65
- */
66
- var SameSitePolicy;
67
- (function (SameSitePolicy) {
68
- SameSitePolicy["LAX"] = "Lax";
69
- SameSitePolicy["NONE"] = "None";
70
- SameSitePolicy["STRICT"] = "Strict";
71
- })(SameSitePolicy || (SameSitePolicy = {}));
72
- /**
73
- * Enum for body types
74
- */
75
- var BodyType;
76
- (function (BodyType) {
77
- BodyType["JSON"] = "json";
78
- BodyType["STRING"] = "string";
79
- BodyType["BINARY"] = "binary";
80
- })(BodyType || (BodyType = {}));
81
- /**
82
- * Referrer policies for fetch requests
83
- */
84
- var ReferrerPolicy;
85
- (function (ReferrerPolicy) {
86
- ReferrerPolicy["ORIGIN"] = "origin";
87
- ReferrerPolicy["UNSAFE_URL"] = "unsafe-url";
88
- ReferrerPolicy["SAME_ORIGIN"] = "same-origin";
89
- ReferrerPolicy["NO_REFERRER"] = "no-referrer";
90
- ReferrerPolicy["STRICT_ORIGIN"] = "strict-origin";
91
- ReferrerPolicy["ORIGIN_WHEN_CROSS_ORIGIN"] = "origin-when-cross-origin";
92
- ReferrerPolicy["NO_REFERRER_WHEN_DOWNGRADE"] = "no-referrer-when-downgrade";
93
- ReferrerPolicy["STRICT_ORIGIN_WHEN_CROSS_ORIGIN"] = "strict-origin-when-cross-origin";
94
- })(ReferrerPolicy || (ReferrerPolicy = {}));
95
-
96
- class RequestError extends Error {
97
- constructor(message, url, method, options = {}) {
98
- super(message);
99
- this.name = "RequestError";
100
- this.url = url;
101
- this.method = method;
102
- this.status = options.status;
103
- this.response = options.response;
104
- this.timeoutError = options.timeoutError;
105
- // For better stack traces in modern environments
106
- if (Error.captureStackTrace) {
107
- Error.captureStackTrace(this, RequestError);
108
- }
109
- // Maintains proper prototype chain for instanceof checks
110
- Object.setPrototypeOf(this, RequestError.prototype);
111
- }
112
- /**
113
- * Static methods for creating specific types of RequestError
114
- */
115
- static timeout(url, method, timeoutMs) {
116
- return new RequestError(`Request timed out after ${timeoutMs}ms`, url, method, {
117
- timeoutError: true,
118
- });
119
- }
120
- static fromResponse(response, url, method) {
121
- return new RequestError(`Request failed with status ${response.status}`, url, method, {
122
- status: response.status,
123
- response,
124
- });
125
- }
126
- static networkError(url, method, originalError) {
127
- const error = new RequestError(`Network error: ${originalError.message}`, url, method);
128
- error.stack = originalError.stack;
129
- return error;
130
- }
131
- }
132
-
133
- /**
134
- * Wrapper for HTTP responses with methods to transform the response data
135
- * Provides caching and conversion between different response formats.
136
- */
137
- class ResponseWrapper {
138
- constructor(response) {
139
- this.bodyUsed = false;
140
- this.response = response;
141
- }
142
- // Wrapper properties
143
- get status() {
144
- return this.response.status;
145
- }
146
- get statusText() {
147
- return this.response.statusText;
148
- }
149
- get headers() {
150
- return this.response.headers;
151
- }
152
- get ok() {
153
- return this.response.ok;
154
- }
155
- get raw() {
156
- return this.response;
157
- }
158
- /**
159
- * Parse the response body as JSON
160
- * Caches the result for subsequent calls.
161
- *
162
- * @returns The parsed JSON data
163
- * @throws Error if the response has already been consumed in a different format
164
- *
165
- * @example
166
- * const data = await response.getJson();
167
- * console.log(data.items);
168
- */
169
- async getJson() {
170
- if (this.jsonCache !== undefined) {
171
- return this.jsonCache;
172
- }
173
- if (this.bodyUsed) {
174
- // If we already have text, try to parse it as JSON
175
- if (this.textCache !== undefined) {
176
- try {
177
- // Fix unsafe assignment by explicitly casting to unknown first
178
- this.jsonCache = JSON.parse(this.textCache);
179
- return this.jsonCache;
180
- }
181
- catch (e) {
182
- throw new Error("Cannot parse cached text as JSON");
183
- }
184
- }
185
- throw new Error("Response body has already been consumed in a different format");
186
- }
187
- this.bodyUsed = true;
188
- this.jsonCache = (await this.response.json());
189
- return this.jsonCache;
190
- }
191
- /**
192
- * Get the response body as text
193
- * Caches the result for subsequent calls. Attempts to convert from JSON or Blob
194
- * if the body has already been consumed in those formats.
195
- *
196
- * @returns The response text
197
- * @throws Error if the response has already been consumed in an incompatible format
198
- *
199
- * @example
200
- * const text = await response.getText();
201
- */
202
- async getText() {
203
- if (this.textCache !== undefined) {
204
- return this.textCache;
205
- }
206
- if (this.bodyUsed) {
207
- // If we already have JSON, convert it to text
208
- if (this.jsonCache !== undefined) {
209
- this.textCache = JSON.stringify(this.jsonCache);
210
- return this.textCache;
211
- }
212
- // If we have blob, convert to text
213
- if (this.blobCache !== undefined) {
214
- this.textCache = await this.blobCache.text();
215
- return this.textCache;
216
- }
217
- throw new Error("Response body has already been consumed in a format that cannot be converted to text");
218
- }
219
- this.bodyUsed = true;
220
- this.textCache = await this.response.text();
221
- return this.textCache;
222
- }
223
- /**
224
- * Get the response body as a Blob
225
- * Caches the result for subsequent calls. Attempts to convert from text
226
- * if the body has already been consumed as text.
227
- *
228
- * @returns The response as a Blob
229
- * @throws Error if the response has already been consumed in an incompatible format
230
- *
231
- * @example
232
- * const blob = await response.getBlob();
233
- * const url = URL.createObjectURL(blob);
234
- */
235
- async getBlob() {
236
- if (this.blobCache !== undefined) {
237
- return this.blobCache;
238
- }
239
- if (this.bodyUsed) {
240
- // If we have text, convert to Blob
241
- if (this.textCache !== undefined) {
242
- this.blobCache = new Blob([this.textCache], { type: "text/plain" });
243
- return this.blobCache;
244
- }
245
- throw new Error("Response body has already been consumed in a format that cannot be converted to Blob");
246
- }
247
- this.bodyUsed = true;
248
- this.blobCache = await this.response.blob();
249
- return this.blobCache;
250
- }
251
- /**
252
- * Get the response body as an ArrayBuffer
253
- * Caches the result for subsequent calls. Attempts to convert from text or Blob
254
- * if the body has already been consumed in those formats.
255
- *
256
- * @returns The response as an ArrayBuffer
257
- * @throws Error if the response has already been consumed in an incompatible format
258
- *
259
- * @example
260
- * const buffer = await response.getArrayBuffer();
261
- */
262
- async getArrayBuffer() {
263
- if (this.arrayBufferCache !== undefined) {
264
- return this.arrayBufferCache;
265
- }
266
- if (this.bodyUsed) {
267
- // If we have text, convert to ArrayBuffer
268
- if (this.textCache !== undefined) {
269
- const encoder = new TextEncoder();
270
- // Fix ArrayBufferLike by explicitly casting to ArrayBuffer
271
- const uint8Array = encoder.encode(this.textCache);
272
- this.arrayBufferCache = uint8Array.buffer;
273
- return this.arrayBufferCache;
274
- }
275
- // If we have blob, convert to ArrayBuffer
276
- if (this.blobCache !== undefined) {
277
- this.arrayBufferCache = await this.blobCache.arrayBuffer();
278
- return this.arrayBufferCache;
279
- }
280
- throw new Error("Response body has already been consumed in a format that cannot be converted to ArrayBuffer");
281
- }
282
- this.bodyUsed = true;
283
- this.arrayBufferCache = await this.response.arrayBuffer();
284
- return this.arrayBufferCache;
285
- }
286
- /**
287
- * Get the raw response body as a ReadableStream
288
- * This method consumes the body and should only be called once.
289
- *
290
- * @returns The response body as a ReadableStream or null
291
- * @throws Error if the response body has already been consumed
292
- *
293
- * @example
294
- * const stream = response.getBody();
295
- * if (stream) {
296
- * const reader = stream.getReader();
297
- * // Process the stream
298
- * }
299
- */
300
- getBody() {
301
- if (this.bodyUsed) {
302
- throw new Error("Response body has already been consumed");
303
- }
304
- this.bodyUsed = true;
305
- return this.response.body;
306
- }
307
- }
308
-
309
- /**
310
- * Parse size strings like "10MB" into bytes
311
- * Supports KB, MB, and GB units. If no unit is specified, assumes bytes.
312
- *
313
- * @param size - Size as number (bytes) or string with units (e.g., "5MB")
314
- * @returns Size in bytes
315
- *
316
- * @example
317
- * parseSize("10MB") // returns 10485760
318
- * parseSize(1024) // returns 1024
319
- */
320
- function parseSize(size) {
321
- var _a;
322
- if (typeof size === "number")
323
- return size;
324
- const match = size.match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB)?$/i);
325
- if (!match)
326
- return 0;
327
- const num = parseFloat(match[1]);
328
- const unit = (_a = match[2]) === null || _a === void 0 ? void 0 : _a.toUpperCase();
329
- switch (unit) {
330
- case "KB":
331
- return num * 1024;
332
- case "MB":
333
- return num * 1024 * 1024;
334
- case "GB":
335
- return num * 1024 * 1024 * 1024;
336
- default:
337
- return num;
338
- }
339
- }
340
- /**
341
- * Create a memory storage provider using Map
342
- * This provider stores data in memory and is cleared when the application restarts.
343
- *
344
- * @returns A storage provider using an in-memory Map
345
- *
346
- * @example
347
- * const storage = createMemoryStorage();
348
- * storage.set("key", "value");
349
- */
350
- function createMemoryStorage() {
351
- const store = new Map();
352
- return {
353
- get(key) {
354
- return store.get(key) || null;
355
- },
356
- set(key, value) {
357
- store.set(key, value);
358
- },
359
- has(key) {
360
- return store.has(key);
361
- },
362
- delete(key) {
363
- store.delete(key);
364
- },
365
- clear() {
366
- store.clear();
367
- },
368
- };
369
- }
370
- /**
371
- * Create a localStorage-based storage provider
372
- * This provider persists data between browser sessions.
373
- *
374
- * @returns A storage provider using localStorage
375
- * @throws Console warnings if localStorage is not available or storage quota is exceeded
376
- *
377
- * @example
378
- * const storage = createLocalStorageStorage();
379
- * storage.set("key", "value");
380
- */
381
- function createLocalStorageStorage() {
382
- return {
383
- get(key) {
384
- if (typeof localStorage === "undefined")
385
- return null;
386
- return localStorage.getItem(key);
387
- },
388
- set(key, value) {
389
- if (typeof localStorage === "undefined")
390
- return;
391
- try {
392
- localStorage.setItem(key, value);
393
- }
394
- catch (e) {
395
- // Handle quota exceeded errors
396
- console.warn("Cache storage quota exceeded, clearing cache");
397
- void this.clear();
398
- try {
399
- localStorage.setItem(key, value);
400
- }
401
- catch (e) {
402
- console.error("Failed to set cache item even after clearing cache");
403
- }
404
- }
405
- },
406
- has(key) {
407
- if (typeof localStorage === "undefined")
408
- return false;
409
- return localStorage.getItem(key) !== null;
410
- },
411
- delete(key) {
412
- if (typeof localStorage === "undefined")
413
- return;
414
- localStorage.removeItem(key);
415
- },
416
- clear() {
417
- if (typeof localStorage === "undefined")
418
- return;
419
- localStorage.clear();
420
- },
421
- };
422
- }
423
- /**
424
- * Create a sessionStorage-based storage provider
425
- * This provider persists data only for the current browser session.
426
- *
427
- * @returns A storage provider using sessionStorage
428
- * @throws Console warnings if sessionStorage is not available or storage quota is exceeded
429
- *
430
- * @example
431
- * const storage = createSessionStorageStorage();
432
- * storage.set("key", "value");
433
- */
434
- function createSessionStorageStorage() {
435
- return {
436
- get(key) {
437
- if (typeof sessionStorage === "undefined")
438
- return null;
439
- return sessionStorage.getItem(key);
440
- },
441
- set(key, value) {
442
- if (typeof sessionStorage === "undefined")
443
- return;
444
- try {
445
- sessionStorage.setItem(key, value);
446
- }
447
- catch (e) {
448
- // Handle quota exceeded errors
449
- console.warn("Cache storage quota exceeded, clearing cache");
450
- void this.clear();
451
- try {
452
- sessionStorage.setItem(key, value);
453
- }
454
- catch (e) {
455
- console.error("Failed to set cache item even after clearing cache");
456
- }
457
- }
458
- },
459
- has(key) {
460
- if (typeof sessionStorage === "undefined")
461
- return false;
462
- return sessionStorage.getItem(key) !== null;
463
- },
464
- delete(key) {
465
- if (typeof sessionStorage === "undefined")
466
- return;
467
- sessionStorage.removeItem(key);
468
- },
469
- clear() {
470
- if (typeof sessionStorage === "undefined")
471
- return;
472
- sessionStorage.clear();
473
- },
474
- };
475
- }
476
- /**
477
- * Create a storage provider from cache options
478
- * Selects the appropriate storage implementation based on the provided options.
479
- * Falls back to memory storage if no suitable storage is found.
480
- *
481
- * @param options - Cache configuration options
482
- * @returns A storage provider based on the options
483
- *
484
- * @example
485
- * const storage = createStorageProvider({ storage: localStorage });
486
- */
487
- function createStorageProvider(options) {
488
- const { storage } = options || {};
489
- if (!storage) {
490
- // Default to memory storage
491
- return createMemoryStorage();
492
- }
493
- // Check if storage is already a StorageProvider
494
- if (typeof storage.get === "function" && typeof storage.set === "function") {
495
- return storage;
496
- }
497
- // Check if it's localStorage or sessionStorage
498
- if (storage === localStorage) {
499
- return createLocalStorageStorage();
500
- }
501
- if (storage === sessionStorage) {
502
- return createSessionStorageStorage();
503
- }
504
- // Check if it's a Map
505
- if (storage instanceof Map) {
506
- return {
507
- get(key) {
508
- return storage.get(key) || null;
509
- },
510
- set(key, value, _ttl) {
511
- storage.set(key, value);
512
- },
513
- has(key) {
514
- return storage.has(key);
515
- },
516
- delete(key) {
517
- storage.delete(key);
518
- },
519
- clear() {
520
- storage.clear();
521
- },
522
- };
523
- }
524
- // Default to memory storage
525
- console.warn("Unsupported storage type, falling back to memory storage");
526
- return createMemoryStorage();
527
- }
528
-
529
- /**
530
- * Cache manager that handles storage operations
531
- * Provides methods for storing, retrieving, and managing cached API responses.
532
- */
533
- class CacheManager {
534
- constructor(options = {}) {
535
- this.entryCount = 0;
536
- this.totalSize = 0;
537
- this.options = options;
538
- this.storage = createStorageProvider(options);
539
- }
540
- /**
541
- * Generate a cache key based on request details
542
- * Keys can vary based on URL, method, headers, and request body.
543
- *
544
- * @param url - The request URL
545
- * @param method - The HTTP method
546
- * @param headers - Request headers that might affect caching
547
- * @param body - Request body for non-GET requests
548
- * @returns A unique cache key string
549
- *
550
- * @example
551
- * const key = cacheManager.generateKey("/api/users", "GET", { "Accept": "application/json" });
552
- */
553
- generateKey(url, method, headers, body) {
554
- // Use custom key generator if provided
555
- if (this.options.keyGenerator) {
556
- const customKey = this.options.keyGenerator(url, method, headers, body);
557
- return this.options.keyPrefix ? `${this.options.keyPrefix}:${customKey}` : customKey;
558
- }
559
- // Default key generation
560
- let key = `${method}:${url}`;
561
- // Add vary headers to key if specified
562
- if (this.options.varyByHeaders && headers) {
563
- const varyHeaders = {};
564
- this.options.varyByHeaders.forEach(header => {
565
- const normalizedHeader = header.toLowerCase();
566
- // Find the header in case-insensitive way
567
- Object.keys(headers).forEach(h => {
568
- if (h.toLowerCase() === normalizedHeader) {
569
- varyHeaders[normalizedHeader] = headers[h];
570
- }
571
- });
572
- });
573
- if (Object.keys(varyHeaders).length > 0) {
574
- key += `:${JSON.stringify(varyHeaders)}`;
575
- }
576
- }
577
- // Include body in cache key for non-GET requests if it's a simple value
578
- if (method !== "GET" && body !== undefined && body !== null) {
579
- try {
580
- if (typeof body === "string" || typeof body === "number" || typeof body === "boolean") {
581
- key += `:${String(body)}`;
582
- }
583
- else if (typeof body === "object") {
584
- key += `:${JSON.stringify(body)}`;
585
- }
586
- }
587
- catch (e) {
588
- console.warn("Failed to stringify request body for cache key");
589
- }
590
- }
591
- // Add prefix if specified
592
- return this.options.keyPrefix ? `${this.options.keyPrefix}:${key}` : key;
593
- }
594
- /**
595
- * Get a cached value by key
596
- * Checks if the entry exists and isn't expired before returning it.
597
- *
598
- * @param key - The cache key to retrieve
599
- * @returns The cached entry or null if not found or expired
600
- *
601
- * @example
602
- * const entry = await cacheManager.get("GET:/api/users");
603
- */
604
- async get(key) {
605
- const value = await Promise.resolve(this.storage.get(key));
606
- if (!value)
607
- return null;
608
- try {
609
- const entry = JSON.parse(value);
610
- // Check if the entry has expired
611
- if (entry.expiry && entry.expiry < Date.now()) {
612
- await Promise.resolve(this.storage.delete(key));
613
- return null;
614
- }
615
- return entry;
616
- }
617
- catch (e) {
618
- // If we can't parse the cached value, remove it
619
- await Promise.resolve(this.storage.delete(key));
620
- return null;
621
- }
622
- }
623
- /**
624
- * Store a value in the cache
625
- * Enforces size and entry count limits if configured.
626
- *
627
- * @param key - The cache key
628
- * @param value - The value to cache
629
- * @param headers - Response headers to store with the cached value
630
- * @returns Promise that resolves when caching is complete
631
- *
632
- * @example
633
- * await cacheManager.set("GET:/api/users", userData, { "Content-Type": "application/json" });
634
- */
635
- async set(key, value, headers) {
636
- const now = Date.now();
637
- const ttl = this.options.ttl;
638
- const entry = {
639
- value,
640
- timestamp: now,
641
- expiry: ttl ? now + ttl : null,
642
- headers,
643
- };
644
- const serialized = JSON.stringify(entry);
645
- // Check size constraints if applicable
646
- if (this.options.maxSize) {
647
- const maxBytes = parseSize(this.options.maxSize);
648
- const entrySize = new TextEncoder().encode(serialized).length;
649
- // If a single entry exceeds the max size, reject it without clearing the cache
650
- if (entrySize > maxBytes) {
651
- console.warn(`Cache entry size (${entrySize} bytes) exceeds maximum cache size (${maxBytes} bytes)`);
652
- return;
653
- }
654
- // Only clear if adding this entry would exceed the total limit
655
- if (this.totalSize + entrySize > maxBytes) {
656
- // Simple strategy: clear everything
657
- // A more sophisticated strategy would remove oldest entries first
658
- await Promise.resolve(this.storage.clear());
659
- this.totalSize = 0;
660
- this.entryCount = 0;
661
- }
662
- this.totalSize += entrySize;
663
- }
664
- // Check max entries constraint
665
- if (this.options.maxEntries) {
666
- this.entryCount++;
667
- if (this.entryCount > this.options.maxEntries) {
668
- // Simple strategy: clear everything
669
- // A more sophisticated strategy would use LRU cache
670
- await Promise.resolve(this.storage.clear());
671
- this.entryCount = 1;
672
- }
673
- }
674
- // Pass ttl to storage provider if available
675
- await Promise.resolve(this.storage.set(key, serialized, ttl));
676
- }
677
- /**
678
- * Check if a key exists in the cache
679
- *
680
- * @param key - The cache key to check
681
- * @returns Promise that resolves to true if the key exists, false otherwise
682
- *
683
- * @example
684
- * if (await cacheManager.has("GET:/api/users")) {
685
- * // Use cached value
686
- * }
687
- */
688
- async has(key) {
689
- return Promise.resolve(this.storage.has(key));
690
- }
691
- /**
692
- * Delete a specific key from the cache
693
- *
694
- * @param key - The cache key to delete
695
- * @returns Promise that resolves when deletion is complete
696
- *
697
- * @example
698
- * await cacheManager.delete("GET:/api/users");
699
- */
700
- async delete(key) {
701
- return Promise.resolve(this.storage.delete(key));
702
- }
703
- /**
704
- * Clear the entire cache
705
- * Resets internal counters for entry count and size.
706
- *
707
- * @returns Promise that resolves when the cache is cleared
708
- *
709
- * @example
710
- * await cacheManager.clear();
711
- */
712
- async clear() {
713
- this.entryCount = 0;
714
- this.totalSize = 0;
715
- return Promise.resolve(this.storage.clear());
716
- }
717
- }
718
-
719
- /**
720
- * Global configuration for create-request
721
- */
722
- class Config {
723
- constructor() {
724
- // CSRF configuration
725
- this.csrfHeaderName = "X-CSRF-Token";
726
- this.xsrfCookieName = "XSRF-TOKEN";
727
- this.xsrfHeaderName = "X-XSRF-TOKEN";
728
- this.csrfToken = null;
729
- this.enableAutoXsrf = true;
730
- this.enableAntiCsrf = true; // X-Requested-With header
731
- }
732
- /**
733
- * Get the singleton instance of the Config class
734
- *
735
- * @returns The global configuration instance
736
- *
737
- * @example
738
- * const config = Config.getInstance();
739
- * config.setCsrfToken('token123');
740
- */
741
- static getInstance() {
742
- if (!Config.instance) {
743
- Config.instance = new Config();
744
- }
745
- return Config.instance;
746
- }
747
- /**
748
- * Set a global CSRF token to be used for all requests
749
- *
750
- * @param token - The CSRF token value
751
- * @returns The config instance for chaining
752
- *
753
- * @example
754
- * Config.getInstance().setCsrfToken('myToken123');
755
- */
756
- setCsrfToken(token) {
757
- this.csrfToken = token;
758
- return this;
759
- }
760
- /**
761
- * Get the global CSRF token that will be automatically applied to requests
762
- *
763
- * @returns The current CSRF token or null if not set
764
- */
765
- getCsrfToken() {
766
- return this.csrfToken;
767
- }
768
- /**
769
- * Set the CSRF header name used when sending the token
770
- *
771
- * @param name - The header name to use
772
- * @returns The config instance for chaining
773
- *
774
- * @example
775
- * Config.getInstance().setCsrfHeaderName('X-My-CSRF-Token');
776
- */
777
- setCsrfHeaderName(name) {
778
- this.csrfHeaderName = name;
779
- return this;
780
- }
781
- /**
782
- * Get the configured CSRF header name
783
- *
784
- * @returns The current CSRF header name
785
- */
786
- getCsrfHeaderName() {
787
- return this.csrfHeaderName;
788
- }
789
- /**
790
- * Set the XSRF cookie name to look for when extracting tokens from cookies
791
- *
792
- * @param name - The cookie name to look for
793
- * @returns The config instance for chaining
794
- *
795
- * @example
796
- * Config.getInstance().setXsrfCookieName('MY-XSRF-COOKIE');
797
- */
798
- setXsrfCookieName(name) {
799
- this.xsrfCookieName = name;
800
- return this;
801
- }
802
- /**
803
- * Get the configured XSRF cookie name
804
- *
805
- * @returns The current XSRF cookie name
806
- */
807
- getXsrfCookieName() {
808
- return this.xsrfCookieName;
809
- }
810
- /**
811
- * Set the XSRF header name for sending tokens extracted from cookies
812
- *
813
- * @param name - The header name to use
814
- * @returns The config instance for chaining
815
- */
816
- setXsrfHeaderName(name) {
817
- this.xsrfHeaderName = name;
818
- return this;
819
- }
820
- /**
821
- * Get the configured XSRF header name
822
- *
823
- * @returns The current XSRF header name
824
- */
825
- getXsrfHeaderName() {
826
- return this.xsrfHeaderName;
827
- }
828
- /**
829
- * Enable or disable automatic extraction of XSRF tokens from cookies
830
- * When enabled, the library will look for XSRF tokens in cookies and
831
- * automatically add them to request headers.
832
- *
833
- * @param enable - Whether to enable this feature
834
- * @returns The config instance for chaining
835
- *
836
- * @example
837
- * Config.getInstance().setEnableAutoXsrf(false); // Disable XSRF extraction
838
- */
839
- setEnableAutoXsrf(enable) {
840
- this.enableAutoXsrf = enable;
841
- return this;
842
- }
843
- /**
844
- * Check if automatic XSRF token extraction is enabled
845
- *
846
- * @returns True if automatic XSRF is enabled
847
- */
848
- isAutoXsrfEnabled() {
849
- return this.enableAutoXsrf;
850
- }
851
- /**
852
- * Enable or disable automatic addition of anti-CSRF headers
853
- * When enabled, X-Requested-With: XMLHttpRequest will be added to all requests.
854
- *
855
- * @param enable - Whether to enable this feature
856
- * @returns The config instance for chaining
857
- */
858
- setEnableAntiCsrf(enable) {
859
- this.enableAntiCsrf = enable;
860
- return this;
861
- }
862
- /**
863
- * Check if anti-CSRF protection is enabled
864
- *
865
- * @returns True if anti-CSRF protection is enabled
866
- */
867
- isAntiCsrfEnabled() {
868
- return this.enableAntiCsrf;
869
- }
870
- /**
871
- * Reset all configuration options to their default values
872
- *
873
- * @returns The config instance for chaining
874
- *
875
- * @example
876
- * Config.getInstance().reset();
877
- */
878
- reset() {
879
- this.csrfToken = null;
880
- this.csrfHeaderName = "X-CSRF-Token";
881
- this.enableAntiCsrf = true;
882
- this.xsrfCookieName = "XSRF-TOKEN";
883
- this.xsrfHeaderName = "X-XSRF-TOKEN";
884
- this.enableAutoXsrf = true;
885
- return this;
886
- }
887
- }
888
-
889
- class CookieUtils {
890
- /**
891
- * Formats cookies for a request
892
- * @param cookies Object containing cookie name-value pairs or cookie options
893
- * @returns Formatted cookie string for the Cookie header
894
- */
895
- static formatRequestCookies(cookies) {
896
- const cookiePairs = [];
897
- Object.entries(cookies).forEach(([name, valueOrOptions]) => {
898
- let value;
899
- if (typeof valueOrOptions === "string") {
900
- value = valueOrOptions;
901
- }
902
- else {
903
- // Extract value from options object without validation
904
- value = valueOrOptions.value;
905
- }
906
- // Add the cookie to the request
907
- cookiePairs.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
908
- });
909
- return cookiePairs.join("; ");
910
- }
911
- }
912
-
913
- /**
914
- * Utility class for CSRF token management
915
- */
916
- class CsrfUtils {
917
- /**
918
- * Extracts CSRF token from a meta tag in the document head
919
- * @param metaName The name attribute of the meta tag (default: "csrf-token")
920
- * @returns The CSRF token or null if not found
921
- */
922
- static getTokenFromMeta(metaName = "csrf-token") {
923
- if (typeof document === "undefined") {
924
- return null;
925
- }
926
- const meta = document.querySelector(`meta[name="${metaName}"]`);
927
- return (meta === null || meta === void 0 ? void 0 : meta.getAttribute("content")) || null;
928
- }
929
- /**
930
- * Extracts CSRF token from a cookie
931
- * @param cookieName The name of the cookie containing the CSRF token
932
- * @returns The CSRF token or null if not found
933
- */
934
- static getTokenFromCookie(cookieName = "csrf-token") {
935
- if (typeof document === "undefined") {
936
- return null;
937
- }
938
- const cookies = document.cookie.split(";");
939
- for (const cookie of cookies) {
940
- const [name, value] = cookie.trim().split("=");
941
- if (name === cookieName) {
942
- return decodeURIComponent(value);
943
- }
944
- }
945
- return null;
946
- }
947
- /**
948
- * Validates if the provided string is a potential CSRF token
949
- * Checks if the token meets security requirements
950
- * @param token The token to validate
951
- * @returns Whether the token is valid
952
- */
953
- static isValidToken(token) {
954
- // Basic checks for null/undefined and type
955
- if (typeof token !== "string") {
956
- return false;
957
- }
958
- if (token.length < 8)
959
- return false;
960
- // If token is longer than 10 chars, perform additional security checks
961
- if (token.length > 10) {
962
- // Check for valid character set (alphanumeric & common token symbols)
963
- const validTokenRegex = /^[A-Za-z0-9\-_=+/.]+$/;
964
- if (!validTokenRegex.test(token)) {
965
- return false;
966
- }
967
- // For longer tokens, check for sufficient entropy (at least 2 character types)
968
- const hasUpperCase = /[A-Z]/.test(token);
969
- const hasLowerCase = /[a-z]/.test(token);
970
- const hasNumbers = /[0-9]/.test(token);
971
- const hasSpecials = /[-_=+/.]/.test(token);
972
- const characterTypesCount = [hasUpperCase, hasLowerCase, hasNumbers, hasSpecials].filter(Boolean).length;
973
- return characterTypesCount >= 2;
974
- }
975
- // For shorter tokens (8-10 chars), just return true if we reached here
976
- return true;
977
- }
978
- }
979
-
980
- /**
981
- * Base class with common functionality for all request types
982
- * Provides the core request building and execution capabilities.
983
- */
984
- class BaseRequest {
985
- constructor() {
986
- this.requestOptions = {
987
- headers: {},
988
- };
989
- this.queryParams = new URLSearchParams();
990
- this.autoApplyCsrfProtection = true;
991
- this.cacheEnabled = false;
992
- }
993
- /**
994
- * Add multiple HTTP headers to the request
995
- *
996
- * @param headers - Key-value pairs of header names and values
997
- * @returns The request instance for chaining
998
- *
999
- * @example
1000
- * request.withHeaders({
1001
- * 'Accept': 'application/json',
1002
- * 'X-Custom-Header': 'value'
1003
- * });
1004
- */
1005
- withHeaders(headers) {
1006
- this.requestOptions.headers = {
1007
- ...this.requestOptions.headers,
1008
- ...headers,
1009
- };
1010
- return this;
1011
- }
1012
- /**
1013
- * Add a single HTTP header to the request
1014
- *
1015
- * @param key - The header name
1016
- * @param value - The header value
1017
- * @returns The request instance for chaining
1018
- *
1019
- * @example
1020
- * request.withHeader('Accept', 'application/json');
1021
- */
1022
- withHeader(key, value) {
1023
- return this.withHeaders({ [key]: value });
1024
- }
1025
- /**
1026
- * Set a timeout for the request
1027
- * If the request takes longer than the specified timeout, it will be aborted.
1028
- *
1029
- * @param timeout - The timeout in milliseconds
1030
- * @returns The request instance for chaining
1031
- * @throws Error if timeout is not a positive number
1032
- *
1033
- * @example
1034
- * request.withTimeout(5000); // 5 seconds timeout
1035
- */
1036
- withTimeout(timeout) {
1037
- if (!Number.isFinite(timeout) || timeout <= 0)
1038
- throw new Error("Timeout must be a positive number");
1039
- this.requestOptions.timeout = timeout;
1040
- return this;
1041
- }
1042
- /**
1043
- * Configure automatic retry behavior for failed requests
1044
- *
1045
- * @param retries - Number of retry attempts before failing
1046
- * @returns The request instance for chaining
1047
- * @throws Error if retries is not a non-negative integer
1048
- *
1049
- * @example
1050
- * request.withRetries(3); // Retry up to 3 times
1051
- */
1052
- withRetries(retries) {
1053
- if (!Number.isInteger(retries) || retries < 0)
1054
- throw new Error("Retry count must be a non-negative integer");
1055
- this.requestOptions.retries = retries;
1056
- return this;
1057
- }
1058
- /**
1059
- * Set a callback to be invoked before each retry attempt
1060
- * Useful for implementing backoff strategies or logging retry attempts.
1061
- *
1062
- * @param callback - Function to call before retrying
1063
- * @returns The request instance for chaining
1064
- *
1065
- * @example
1066
- * request.onRetry(({ attempt, error }) => {
1067
- * console.log(`Retry attempt ${attempt} after error: ${error.message}`);
1068
- * return new Promise(resolve => setTimeout(resolve, attempt * 1000));
1069
- * });
1070
- */
1071
- onRetry(callback) {
1072
- this.requestOptions.onRetry = callback;
1073
- return this;
1074
- }
1075
- /**
1076
- * Sets the credentials policy for the request
1077
- * @param credentialsPolicy - Can be 'omit', 'same-origin', or 'include'
1078
- * @returns The instance for chaining
1079
- */
1080
- withCredentials(credentialsPolicy = CredentialsPolicy.SAME_ORIGIN) {
1081
- this.requestOptions.credentials = credentialsPolicy;
1082
- return this;
1083
- }
1084
- /**
1085
- * Allows providing an external AbortController to cancel the request
1086
- * @param controller - The AbortController to use for this request
1087
- * @returns The instance for chaining
1088
- */
1089
- withAbortController(controller) {
1090
- this.abortController = controller;
1091
- return this;
1092
- }
1093
- /**
1094
- * Sets the referrer for the request
1095
- * @param referrer - The referrer URL or policy
1096
- * @returns The instance for chaining
1097
- */
1098
- withReferrer(referrer) {
1099
- this.requestOptions.referrer = referrer;
1100
- return this;
1101
- }
1102
- /**
1103
- * Sets the referrer policy for the request
1104
- * @param policy - The referrer policy to use
1105
- * @returns The instance for chaining
1106
- */
1107
- withReferrerPolicy(policy) {
1108
- this.requestOptions.referrerPolicy = policy;
1109
- return this;
1110
- }
1111
- /**
1112
- * Sets the redirect policy for the request
1113
- * @param redirect - Can be 'follow', 'error', or 'manual'
1114
- * @returns The instance for chaining
1115
- */
1116
- withRedirect(redirect) {
1117
- this.requestOptions.redirect = redirect;
1118
- return this;
1119
- }
1120
- /**
1121
- * Sets the keepalive flag for the request
1122
- * @param keepalive - Whether to allow the request to outlive the page
1123
- * @returns The instance for chaining
1124
- */
1125
- withKeepAlive(keepalive) {
1126
- this.requestOptions.keepalive = keepalive;
1127
- return this;
1128
- }
1129
- /**
1130
- * Sets the priority of the request
1131
- * @param priority - Can be 'high', 'low', or 'auto'
1132
- * @returns The instance for chaining
1133
- */
1134
- withPriority(priority) {
1135
- this.requestOptions.priority = priority;
1136
- return this;
1137
- }
1138
- /**
1139
- * Adds query parameters to the request URL
1140
- * @param params - An object containing the query parameters
1141
- * @returns The instance for chaining
1142
- */
1143
- withQueryParams(params) {
1144
- Object.entries(params).forEach(([key, value]) => {
1145
- if (value === null || value === undefined) {
1146
- return;
1147
- }
1148
- if (Array.isArray(value)) {
1149
- // Handle array values - add multiple entries with the same key
1150
- value.forEach(v => this.queryParams.append(key, String(v)));
1151
- }
1152
- else {
1153
- this.queryParams.append(key, String(value));
1154
- }
1155
- });
1156
- return this;
1157
- }
1158
- /**
1159
- * Adds a single query parameter
1160
- * @param key - The parameter name
1161
- * @param value - The parameter value, can be a single value or array of values
1162
- * @returns The instance for chaining
1163
- */
1164
- withQueryParam(key, value) {
1165
- if (value === null || value === undefined)
1166
- return this;
1167
- if (Array.isArray(value))
1168
- value.forEach(v => this.queryParams.append(key, String(v)));
1169
- else
1170
- this.queryParams.append(key, String(value));
1171
- return this;
1172
- }
1173
- /**
1174
- * Sets the mode of the request
1175
- * @param mode - The mode for the request (cors, no-cors, same-origin, navigate)
1176
- * @returns The instance for chaining
1177
- */
1178
- withMode(mode) {
1179
- this.requestOptions.mode = mode;
1180
- return this;
1181
- }
1182
- /**
1183
- * Shorthand for setting the Content-Type header
1184
- * @param contentType - The content type
1185
- * @returns The instance for chaining
1186
- */
1187
- withContentType(contentType) {
1188
- return this.withHeaders({
1189
- "Content-Type": contentType,
1190
- });
1191
- }
1192
- /**
1193
- * Shorthand for setting the Authorization header
1194
- * @param authValue - The authorization value
1195
- * @returns The instance for chaining
1196
- */
1197
- withAuthorization(authValue) {
1198
- return this.withHeaders({
1199
- Authorization: authValue,
1200
- });
1201
- }
1202
- /**
1203
- * Shorthand for setting up Basic authentication
1204
- * @param username - The username
1205
- * @param password - The password
1206
- * @returns The instance for chaining
1207
- */
1208
- withBasicAuth(username, password) {
1209
- const credentials = this.encodeBase64(`${username}:${password}`);
1210
- return this.withAuthorization(`Basic ${credentials}`);
1211
- }
1212
- /**
1213
- * Cross-environment base64 encoding
1214
- * Works in both browser and Node.js environments
1215
- */
1216
- encodeBase64(str) {
1217
- // Modern approach using TextEncoder (available in both modern browsers and Node.js)
1218
- if (typeof TextEncoder !== "undefined" && typeof btoa === "function") {
1219
- const encoder = new TextEncoder();
1220
- const bytes = encoder.encode(str);
1221
- return btoa(String.fromCharCode.apply(null, [...new Uint8Array(bytes)]));
1222
- }
1223
- // Browser environment
1224
- if (typeof btoa === "function")
1225
- return btoa(str);
1226
- // Node.js environment
1227
- if (typeof Buffer !== "undefined")
1228
- return Buffer.from(str).toString("base64");
1229
- // Fallback (should never happen in modern environments)
1230
- throw new Error("Base64 encoding is not supported in this environment");
1231
- }
1232
- /**
1233
- * Sets the bearer token for authentication
1234
- * @param token - The bearer token
1235
- * @returns The instance for chaining
1236
- */
1237
- withBearerToken(token) {
1238
- return this.withAuthorization(`Bearer ${token}`);
1239
- }
1240
- /**
1241
- * Helper function to check for header presence in a case-insensitive way
1242
- * @param headers Headers object
1243
- * @param headerName Header name to check
1244
- * @returns Boolean indicating if the header exists (case-insensitive)
1245
- */
1246
- hasHeader(headers, headerName) {
1247
- return Object.keys(headers).some(key => key.toLowerCase() === headerName.toLowerCase());
1248
- }
1249
- /**
1250
- * Sets cookies for the request
1251
- * @param cookies Object containing cookie name-value pairs or cookie options
1252
- * @returns The instance for chaining
1253
- */
1254
- withCookies(cookies) {
1255
- const cookieEntries = Object.entries(cookies);
1256
- if (cookieEntries.length === 0) {
1257
- return this;
1258
- }
1259
- // Get current headers or initialize empty object
1260
- const currentHeaders = this.requestOptions.headers || {};
1261
- // Get all existing cookie headers with different cases
1262
- const cookieValues = [];
1263
- const cookieHeaderKeys = [];
1264
- Object.keys(currentHeaders).forEach(key => {
1265
- if (key.toLowerCase() === "cookie") {
1266
- // Don't add empty cookie values
1267
- if (currentHeaders[key])
1268
- cookieValues.push(currentHeaders[key]);
1269
- cookieHeaderKeys.push(key);
1270
- }
1271
- });
1272
- // Format the new cookies
1273
- const cookieString = CookieUtils.formatRequestCookies(cookies);
1274
- // Choose which header name to use - preserve existing case if possible
1275
- const headerName = cookieHeaderKeys.length > 0 ? cookieHeaderKeys[0] : "Cookie";
1276
- // Combine all existing cookie values with the new ones
1277
- const combinedCookieValue = [...cookieValues, cookieString].filter(Boolean).join("; ");
1278
- // Create a new headers object without any cookie headers
1279
- const newHeaders = { ...currentHeaders };
1280
- cookieHeaderKeys.forEach(key => {
1281
- delete newHeaders[key];
1282
- });
1283
- // Set the new combined cookie header
1284
- this.requestOptions.headers = {
1285
- ...newHeaders,
1286
- [headerName]: combinedCookieValue,
1287
- };
1288
- return this;
1289
- }
1290
- /**
1291
- * Set a single cookie
1292
- * @param name Cookie name
1293
- * @param value Cookie value or options object
1294
- * @returns The instance for chaining
1295
- */
1296
- withCookie(name, value) {
1297
- return this.withCookies({ [name]: value });
1298
- }
1299
- /**
1300
- * Sets a CSRF token in the request headers
1301
- * @param token The CSRF token
1302
- * @param headerName The name of the header to use (default: X-CSRF-Token)
1303
- * @returns The instance for chaining
1304
- */
1305
- withCsrfToken(token, headerName = "X-CSRF-Token") {
1306
- return this.withHeaders({
1307
- [headerName]: token,
1308
- });
1309
- }
1310
- /**
1311
- * Disables automatic anti-CSRF protection.
1312
- * By default, X-Requested-With: XMLHttpRequest header is sent with all requests.
1313
- * @returns The instance for chaining
1314
- */
1315
- withoutCsrfProtection() {
1316
- this.autoApplyCsrfProtection = false;
1317
- return this;
1318
- }
1319
- /**
1320
- * Sets common security headers to help prevent CSRF attacks
1321
- * @returns The instance for chaining
1322
- */
1323
- withAntiCsrfHeaders() {
1324
- return this.withHeaders({
1325
- "X-Requested-With": "XMLHttpRequest",
1326
- });
1327
- }
1328
- /**
1329
- * Configure request caching behavior
1330
- * When enabled, responses will be cached and reused for subsequent identical requests.
1331
- *
1332
- * @param options - Cache configuration options
1333
- * @returns The request instance for chaining
1334
- *
1335
- * @example
1336
- * request.withCache({
1337
- * ttl: 60000, // Cache for 1 minute
1338
- * maxEntries: 100,
1339
- * storage: localStorage
1340
- * });
1341
- */
1342
- withCache(options = {}) {
1343
- this.cacheEnabled = true;
1344
- this.cacheManager = new CacheManager(options);
1345
- return this;
1346
- }
1347
- /**
1348
- * Send the request to the specified URL
1349
- * This is the final method in the chain that executes the request.
1350
- *
1351
- * @param url - The URL to send the request to
1352
- * @returns A promise with additional methods for processing the response
1353
- *
1354
- * @example
1355
- * const response = await request.sendTo('/api/users');
1356
- * const data = await response.getJson();
1357
- */
1358
- sendTo(url) {
1359
- // Format the URL with query parameters
1360
- url = this.formatUrlWithQueryParams(url);
1361
- // Apply automatic CSRF protection if enabled
1362
- if (this.autoApplyCsrfProtection) {
1363
- const config = Config.getInstance();
1364
- // Apply anti-CSRF headers if enabled
1365
- if (config.isAntiCsrfEnabled()) {
1366
- this.withHeaders({
1367
- "X-Requested-With": "XMLHttpRequest",
1368
- });
1369
- }
1370
- // Apply global CSRF token if set
1371
- const globalToken = config.getCsrfToken();
1372
- if (globalToken) {
1373
- // Check if local token exists using case-insensitive comparison
1374
- const headers = this.requestOptions.headers;
1375
- const csrfHeaderName = config.getCsrfHeaderName();
1376
- const hasLocalToken = this.hasHeader(headers, "X-CSRF-Token") || this.hasHeader(headers, csrfHeaderName);
1377
- if (!hasLocalToken) {
1378
- this.withHeaders({
1379
- [csrfHeaderName]: globalToken,
1380
- });
1381
- }
1382
- }
1383
- // check for XSRF token in cookies and send it as a header
1384
- if (config.isAutoXsrfEnabled() && typeof document !== "undefined") {
1385
- const xsrfToken = CsrfUtils.getTokenFromCookie(config.getXsrfCookieName());
1386
- if (xsrfToken && CsrfUtils.isValidToken(xsrfToken)) {
1387
- const headers = this.requestOptions.headers;
1388
- const xsrfHeaderName = config.getXsrfHeaderName();
1389
- const hasLocalToken = this.hasHeader(headers, "X-XSRF-TOKEN") || this.hasHeader(headers, xsrfHeaderName);
1390
- if (!hasLocalToken) {
1391
- this.withHeaders({
1392
- [xsrfHeaderName]: xsrfToken,
1393
- });
1394
- }
1395
- }
1396
- }
1397
- }
1398
- // Cast the request options to RequestInit to ensure compatibility
1399
- const fetchOptions = {
1400
- ...this.requestOptions,
1401
- method: this.method,
1402
- };
1403
- // Create the base promise - handle caching if enabled
1404
- let basePromise;
1405
- if (this.cacheEnabled && this.cacheManager) {
1406
- const cacheKey = this.cacheManager.generateKey(url, this.method, this.requestOptions.headers, this.requestOptions.body);
1407
- basePromise = this.executeWithCache(url, fetchOptions, cacheKey);
1408
- }
1409
- else {
1410
- basePromise = !this.requestOptions.retries ? this.executeRequest(url, fetchOptions) : this.executeWithRetries(url, fetchOptions);
1411
- }
1412
- const responsePromise = basePromise;
1413
- // Attach convenience methods that begin processing immediately
1414
- responsePromise.getJson = async () => {
1415
- return basePromise.then(response => response.getJson());
1416
- };
1417
- responsePromise.getText = async () => {
1418
- return basePromise.then(response => response.getText());
1419
- };
1420
- responsePromise.getBlob = async () => {
1421
- return basePromise.then(response => response.getBlob());
1422
- };
1423
- responsePromise.getArrayBuffer = async () => {
1424
- return basePromise.then(response => response.getArrayBuffer());
1425
- };
1426
- responsePromise.getBody = async () => {
1427
- return basePromise.then(response => response.getBody());
1428
- };
1429
- responsePromise.getData = async (selector) => {
1430
- try {
1431
- const data = await basePromise.then(response => response.getJson());
1432
- // If no selector is provided, return the raw JSON data
1433
- if (!selector)
1434
- return data;
1435
- // Apply the selector if provided
1436
- return selector(data);
1437
- }
1438
- catch (error) {
1439
- // Always enhance the error with the original data
1440
- const errorMessage = error instanceof Error ? error.message : String(error);
1441
- const enhancedError = new Error(`Data selector failed: ${errorMessage}. Original data: ${JSON.stringify(await basePromise.then(response => response.getJson()), null, 2)}`);
1442
- // Preserve the stack trace if available
1443
- if (error instanceof Error && error.stack) {
1444
- enhancedError.stack = error.stack;
1445
- }
1446
- throw enhancedError;
1447
- }
1448
- };
1449
- return responsePromise;
1450
- }
1451
- /**
1452
- * Formats the URL with any query parameters
1453
- * @param url The base URL
1454
- * @returns The URL with query parameters appended
1455
- */
1456
- formatUrlWithQueryParams(url) {
1457
- if (!this.queryParams.toString()) {
1458
- return url;
1459
- }
1460
- try {
1461
- // Try to use the URL constructor (works for absolute URLs)
1462
- const urlObj = new URL(url);
1463
- // Merge our query params with any that might be in the URL already
1464
- this.queryParams.forEach((value, key) => {
1465
- urlObj.searchParams.append(key, value);
1466
- });
1467
- return urlObj.toString();
1468
- }
1469
- catch (error) {
1470
- // Handle relative URLs
1471
- const hasExistingParams = url.includes("?");
1472
- const separator = hasExistingParams ? "&" : "?";
1473
- return `${url}${separator}${this.queryParams.toString()}`;
1474
- }
1475
- }
1476
- /**
1477
- * Executes a request with configured retry logic
1478
- * @param url The formatted URL to send the request to
1479
- * @param fetchOptions The fetch options to use
1480
- * @returns A wrapped response object
1481
- */
1482
- async executeWithRetries(url, fetchOptions) {
1483
- let attempt = 0;
1484
- const maxRetries = this.requestOptions.retries || 0;
1485
- do {
1486
- try {
1487
- return await this.executeRequest(url, fetchOptions);
1488
- }
1489
- catch (error) {
1490
- const requestError = error instanceof RequestError ? error : RequestError.networkError(url, fetchOptions.method, error);
1491
- // Don't retry on timeout errors or if we've reached the max retries
1492
- if (attempt >= maxRetries || requestError.timeoutError)
1493
- throw requestError;
1494
- attempt++;
1495
- if (this.requestOptions.onRetry) {
1496
- await this.requestOptions.onRetry({ attempt, error: requestError });
1497
- }
1498
- }
1499
- } while (attempt <= maxRetries);
1500
- // If we somehow get here, we throw an error to avoid undefined behavior
1501
- throw new RequestError(`Max retries reached (${maxRetries}) but request still failed`, url, fetchOptions.method);
1502
- }
1503
- async executeRequest(url, fetchOptions) {
1504
- // Create a request controller for timeout if needed, or use the provided one
1505
- const controller = this.abortController || new AbortController();
1506
- const { signal } = controller;
1507
- // Use the controller signal for the fetch request, but don't overwrite an existing signal
1508
- fetchOptions.signal = fetchOptions.signal || signal;
1509
- let timeoutId;
1510
- // Flag to track if abort was caused by our timeout
1511
- let abortedByTimeout = false;
1512
- // Handle timeout regardless of whether an external controller is provided
1513
- if (this.requestOptions.timeout) {
1514
- if (this.abortController) {
1515
- // If we have an external controller, create a separate timeout controller
1516
- // that won't interfere with the external one
1517
- // Use AbortSignal.timeout if available (modern browsers)
1518
- if (typeof AbortSignal.timeout === "function") {
1519
- const timeoutSignal = AbortSignal.timeout(this.requestOptions.timeout);
1520
- // Add event listener to propagate the abort to the main controller
1521
- timeoutSignal.addEventListener("abort", () => {
1522
- if (!controller.signal.aborted) {
1523
- abortedByTimeout = true;
1524
- controller.abort();
1525
- }
1526
- });
1527
- }
1528
- else {
1529
- // Fallback for browsers without AbortSignal.timeout
1530
- timeoutId = setTimeout(() => {
1531
- if (!controller.signal.aborted) {
1532
- abortedByTimeout = true;
1533
- controller.abort();
1534
- }
1535
- }, this.requestOptions.timeout);
1536
- }
1537
- }
1538
- else {
1539
- timeoutId = setTimeout(() => {
1540
- abortedByTimeout = true;
1541
- controller.abort();
1542
- }, this.requestOptions.timeout);
1543
- }
1544
- }
1545
- try {
1546
- let response;
1547
- try {
1548
- response = await fetch(url, fetchOptions);
1549
- }
1550
- catch (error) {
1551
- // Check if this is an abort error that was specifically caused by our timeout
1552
- if (error instanceof DOMException && error.name === "AbortError" && abortedByTimeout) {
1553
- throw RequestError.timeout(url, fetchOptions.method, this.requestOptions.timeout);
1554
- }
1555
- // Otherwise it's either a user-triggered abort or another network error
1556
- throw RequestError.networkError(url, fetchOptions.method, error);
1557
- }
1558
- if (!response.ok) {
1559
- throw RequestError.fromResponse(response, url, fetchOptions.method);
1560
- }
1561
- // Return a wrapped response instead of parsing it
1562
- return new ResponseWrapper(response);
1563
- }
1564
- finally {
1565
- if (timeoutId) {
1566
- clearTimeout(timeoutId);
1567
- }
1568
- }
1569
- }
1570
- /**
1571
- * Executes a request using cache when possible
1572
- * @param url The formatted URL to send the request to
1573
- * @param fetchOptions The fetch options to use
1574
- * @param cacheKey The cache key for this request
1575
- */
1576
- async executeWithCache(url, fetchOptions, cacheKey) {
1577
- // Try to get from cache first
1578
- if (this.cacheManager) {
1579
- const cachedEntry = await this.cacheManager.get(cacheKey);
1580
- if (cachedEntry) {
1581
- // Create a synthetic Response object from cached data
1582
- const cachedResponse = new Response(JSON.stringify(cachedEntry.value), {
1583
- status: 200,
1584
- headers: new Headers(cachedEntry.headers || {}),
1585
- });
1586
- return new ResponseWrapper(cachedResponse);
1587
- }
1588
- }
1589
- // Not in cache, make the actual request
1590
- const response = await (!this.requestOptions.retries ? this.executeRequest(url, fetchOptions) : this.executeWithRetries(url, fetchOptions));
1591
- // Cache the successful response if caching is enabled
1592
- // Only cache successful responses
1593
- if (response.ok && this.cacheManager) {
1594
- // Clone the response so we don't consume it
1595
- try {
1596
- const responseData = await response.getJson();
1597
- // Extract headers to store in cache
1598
- const headers = {};
1599
- response.headers.forEach((value, key) => {
1600
- headers[key] = value;
1601
- });
1602
- // Store in cache
1603
- await this.cacheManager.set(cacheKey, responseData, headers);
1604
- }
1605
- catch (error) {
1606
- // Failed to cache, but we can still return the response
1607
- console.warn("Failed to cache response:", error);
1608
- }
1609
- }
1610
- return response;
1611
- }
1612
- }
1613
-
1614
- /**
1615
- * Base class for requests that can have a body (POST, PUT, PATCH)
1616
- */
1617
- class BodyRequest extends BaseRequest {
1618
- /**
1619
- * Sets the body of the request
1620
- * @param body - The request body
1621
- */
1622
- withBody(body) {
1623
- this.body = body;
1624
- // Set body type and validate
1625
- if (typeof body === "string") {
1626
- this.bodyType = BodyType.STRING;
1627
- this.setContentTypeIfNeeded("text/plain");
1628
- }
1629
- else if (body !== null &&
1630
- typeof body === "object" &&
1631
- !(body instanceof FormData || body instanceof Blob || body instanceof ArrayBuffer || body instanceof URLSearchParams || body instanceof ReadableStream)) {
1632
- this.bodyType = BodyType.JSON;
1633
- this.setContentTypeIfNeeded("application/json");
1634
- // Validate JSON is stringifiable early
1635
- try {
1636
- JSON.stringify(body);
1637
- }
1638
- catch (error) {
1639
- throw new Error(`Failed to stringify request body: ${String(error)}`);
1640
- }
1641
- }
1642
- else {
1643
- this.bodyType = BodyType.BINARY;
1644
- }
1645
- return this;
1646
- }
1647
- setContentTypeIfNeeded(contentType) {
1648
- // Only set if not already set
1649
- const headers = this.requestOptions.headers;
1650
- if (!Object.keys(headers).some(header => header.toLowerCase() === "content-type")) {
1651
- this.withContentType(contentType);
1652
- }
1653
- }
1654
- /**
1655
- * Send the request to the specified URL
1656
- * Overrides the base implementation to add body handling
1657
- */
1658
- sendTo(url) {
1659
- if (this.body !== undefined) {
1660
- // Remove previous body if it exists
1661
- if (this.requestOptions.body)
1662
- delete this.requestOptions.body;
1663
- // Process the body based on its type
1664
- if (this.bodyType === BodyType.JSON) {
1665
- this.requestOptions.body = JSON.stringify(this.body);
1666
- }
1667
- else {
1668
- this.requestOptions.body = this.body;
1669
- }
1670
- }
1671
- return super.sendTo(url);
1672
- }
1673
- }
1674
-
1675
- /**
1676
- * HTTP GET request implementation
1677
- * Used for retrieving data from an API endpoint.
1678
- *
1679
- * @example
1680
- * const request = new GetRequest()
1681
- * .withQueryParams({ id: '123' })
1682
- * .sendTo('/api/users');
1683
- */
1684
- class GetRequest extends BaseRequest {
1685
- constructor() {
1686
- super(...arguments);
1687
- this.method = HttpMethod.GET;
1688
- }
1689
- }
1690
- /**
1691
- * HTTP HEAD request implementation
1692
- * Similar to GET but returns only headers without a response body.
1693
- *
1694
- * @example
1695
- * const request = new HeadRequest()
1696
- * .sendTo('/api/users/123');
1697
- */
1698
- class HeadRequest extends BaseRequest {
1699
- constructor() {
1700
- super(...arguments);
1701
- this.method = HttpMethod.HEAD;
1702
- }
1703
- }
1704
- /**
1705
- * HTTP OPTIONS request implementation
1706
- * Used to describe the communication options for the target resource.
1707
- *
1708
- * @example
1709
- * const request = new OptionsRequest()
1710
- * .sendTo('/api/users');
1711
- */
1712
- class OptionsRequest extends BaseRequest {
1713
- constructor() {
1714
- super(...arguments);
1715
- this.method = HttpMethod.OPTIONS;
1716
- }
1717
- }
1718
- /**
1719
- * HTTP DELETE request implementation
1720
- * Used to delete a resource on the server.
1721
- *
1722
- * @example
1723
- * const request = new DeleteRequest()
1724
- * .sendTo('/api/users/123');
1725
- */
1726
- class DeleteRequest extends BaseRequest {
1727
- constructor() {
1728
- super(...arguments);
1729
- this.method = HttpMethod.DELETE;
1730
- }
1731
- }
1732
- /**
1733
- * HTTP POST request implementation
1734
- * Used to create a new resource or submit data for processing.
1735
- *
1736
- * @example
1737
- * const request = new PostRequest()
1738
- * .withJson({ name: 'John', email: 'john@example.com' })
1739
- * .sendTo('/api/users');
1740
- */
1741
- class PostRequest extends BodyRequest {
1742
- constructor() {
1743
- super(...arguments);
1744
- this.method = HttpMethod.POST;
1745
- }
1746
- }
1747
- /**
1748
- * HTTP PUT request implementation
1749
- * Used to replace or update an existing resource.
1750
- *
1751
- * @example
1752
- * const request = new PutRequest()
1753
- * .withJson({ id: '123', name: 'John', email: 'john@example.com' })
1754
- * .sendTo('/api/users/123');
1755
- */
1756
- class PutRequest extends BodyRequest {
1757
- constructor() {
1758
- super(...arguments);
1759
- this.method = HttpMethod.PUT;
1760
- }
1761
- }
1762
- /**
1763
- * HTTP PATCH request implementation
1764
- * Used to apply partial modifications to a resource.
1765
- *
1766
- * @example
1767
- * const request = new PatchRequest()
1768
- * .withJson({ email: 'new.email@example.com' })
1769
- * .sendTo('/api/users/123');
1770
- */
1771
- class PatchRequest extends BodyRequest {
1772
- constructor() {
1773
- super(...arguments);
1774
- this.method = HttpMethod.PATCH;
1775
- }
1776
- }
1777
-
1778
- /**
1779
- * Create a GET request
1780
- * Used for retrieving resources from the server.
1781
- *
1782
- * @returns A new GET request instance
1783
- *
1784
- * @example
1785
- * const users = await get()
1786
- * .withQueryParams({ limit: 10 })
1787
- * .sendTo('/api/users')
1788
- * .getJson();
1789
- */
1790
- function get() {
1791
- return new GetRequest();
1792
- }
1793
- /**
1794
- * Create a POST request
1795
- * Used for creating new resources or submitting data.
1796
- *
1797
- * @returns A new POST request instance
1798
- *
1799
- * @example
1800
- * const newUser = await post()
1801
- * .withJson({ name: 'John', email: 'john@example.com' })
1802
- * .sendTo('/api/users')
1803
- * .getJson();
1804
- */
1805
- function post() {
1806
- return new PostRequest();
1807
- }
1808
- /**
1809
- * Create a PUT request
1810
- * Used for replacing or updating an existing resource.
1811
- *
1812
- * @returns A new PUT request instance
1813
- *
1814
- * @example
1815
- * const updatedUser = await put()
1816
- * .withJson({ name: 'John Updated', email: 'john@example.com' })
1817
- * .sendTo('/api/users/123')
1818
- * .getJson();
1819
- */
1820
- function put() {
1821
- return new PutRequest();
1822
- }
1823
- /**
1824
- * Create a DELETE request
1825
- * Used for removing resources from the server.
1826
- *
1827
- * @returns A new DELETE request instance
1828
- *
1829
- * @example
1830
- * await del()
1831
- * .sendTo('/api/users/123')
1832
- * .getJson();
1833
- */
1834
- function del() {
1835
- return new DeleteRequest();
1836
- }
1837
- /**
1838
- * Create a PATCH request
1839
- * Used for applying partial modifications to a resource.
1840
- *
1841
- * @returns A new PATCH request instance
1842
- *
1843
- * @example
1844
- * const patchedUser = await patch()
1845
- * .withJson({ status: 'active' })
1846
- * .sendTo('/api/users/123')
1847
- * .getJson();
1848
- */
1849
- function patch() {
1850
- return new PatchRequest();
1851
- }
1852
- /**
1853
- * Create a HEAD request
1854
- * Similar to GET but returns only headers without a body.
1855
- *
1856
- * @returns A new HEAD request instance
1857
- *
1858
- * @example
1859
- * const response = await head()
1860
- * .sendTo('/api/users/123');
1861
- * console.log(response.headers.get('Last-Modified'));
1862
- */
1863
- function head() {
1864
- return new HeadRequest();
1865
- }
1866
- /**
1867
- * Create an OPTIONS request
1868
- * Used to describe the communication options for a resource.
1869
- *
1870
- * @returns A new OPTIONS request instance
1871
- *
1872
- * @example
1873
- * const response = await options()
1874
- * .sendTo('/api/users');
1875
- * console.log(response.headers.get('Allow'));
1876
- */
1877
- function options() {
1878
- return new OptionsRequest();
1879
- }
1880
-
1881
- /**
1882
- * Main API object for creating HTTP requests
1883
- * Provides factory methods for all HTTP methods and access to global configuration.
1884
- *
1885
- * @example
1886
- * // Making a GET request
1887
- * const users = await create.get()
1888
- * .withQueryParams({ limit: 10 })
1889
- * .sendTo('/api/users')
1890
- * .getJson();
1891
- *
1892
- * // Configure global settings
1893
- * create.config.setCsrfToken('token123');
1894
- */
1895
- const create = {
1896
- get,
1897
- post,
1898
- put,
1899
- del,
1900
- patch,
1901
- head,
1902
- options,
1903
- // Add configuration access
1904
- config: Config.getInstance(),
1905
- };
1906
-
1907
- export { CacheManager, CacheMode, CookieUtils, CredentialsPolicy, DeleteRequest, GetRequest, HeadRequest, HttpMethod, OptionsRequest, PatchRequest, PostRequest, PutRequest, RedirectMode, ReferrerPolicy, RequestError, RequestMode, RequestPriority, ResponseWrapper, SameSitePolicy, createLocalStorageStorage, createMemoryStorage, createSessionStorageStorage, create as default };
1
+ var e,t,s,r,o,i,n,a,h;!function(e){e.GET="GET",e.PUT="PUT",e.POST="POST",e.HEAD="HEAD",e.PATCH="PATCH",e.DELETE="DELETE",e.OPTIONS="OPTIONS"}(e||(e={})),function(e){e.LOW="low",e.HIGH="high",e.AUTO="auto"}(t||(t={})),function(e){e.OMIT="omit",e.INCLUDE="include",e.SAME_ORIGIN="same-origin"}(s||(s={})),function(e){e.CORS="cors",e.NO_CORS="no-cors",e.SAME_ORIGIN="same-origin",e.NAVIGATE="navigate"}(r||(r={})),function(e){e.ERROR="error",e.FOLLOW="follow",e.MANUAL="manual"}(o||(o={})),function(e){e.DEFAULT="default",e.RELOAD="reload",e.NO_STORE="no-store",e.NO_CACHE="no-cache",e.FORCE_CACHE="force-cache",e.ONLY_IF_CACHED="only-if-cached"}(i||(i={})),function(e){e.LAX="Lax",e.NONE="None",e.STRICT="Strict"}(n||(n={})),function(e){e.JSON="json",e.STRING="string",e.BINARY="binary"}(a||(a={})),function(e){e.ORIGIN="origin",e.UNSAFE_URL="unsafe-url",e.SAME_ORIGIN="same-origin",e.NO_REFERRER="no-referrer",e.STRICT_ORIGIN="strict-origin",e.ORIGIN_WHEN_CROSS_ORIGIN="origin-when-cross-origin",e.NO_REFERRER_WHEN_DOWNGRADE="no-referrer-when-downgrade",e.STRICT_ORIGIN_WHEN_CROSS_ORIGIN="strict-origin-when-cross-origin"}(h||(h={}));class c extends Error{constructor(e,t,s,r={}){super(e),this.name="RequestError",this.url=t,this.method=s,this.status=r.status,this.response=r.response,this.timeoutError=r.timeoutError,Error.captureStackTrace&&Error.captureStackTrace(this,c),Object.setPrototypeOf(this,c.prototype)}static timeout(e,t,s){return new c(`Request timed out after ${s}ms`,e,t,{timeoutError:!0})}static fromResponse(e,t,s){return new c("Request failed with status "+e.status,t,s,{status:e.status,response:e})}static networkError(e,t,s){const r=new c("Network error: "+s.message,e,t);return r.stack=s.stack,r}}class u{constructor(e){this.bodyUsed=!1,this.response=e}get status(){return this.response.status}get statusText(){return this.response.statusText}get headers(){return this.response.headers}get ok(){return this.response.ok}get raw(){return this.response}async getJson(){if(void 0!==this.jsonCache)return this.jsonCache;if(this.bodyUsed){if(void 0!==this.textCache)try{return this.jsonCache=JSON.parse(this.textCache),this.jsonCache}catch(e){throw Error("Cannot parse cached text as JSON")}throw Error("Response body has already been consumed in a different format")}return this.bodyUsed=!0,this.jsonCache=await this.response.json(),this.jsonCache}async getText(){if(void 0!==this.textCache)return this.textCache;if(this.bodyUsed){if(void 0!==this.jsonCache)return this.textCache=JSON.stringify(this.jsonCache),this.textCache;if(void 0!==this.blobCache)return this.textCache=await this.blobCache.text(),this.textCache;throw Error("Response body has already been consumed in a format that cannot be converted to text")}return this.bodyUsed=!0,this.textCache=await this.response.text(),this.textCache}async getBlob(){if(void 0!==this.blobCache)return this.blobCache;if(this.bodyUsed){if(void 0!==this.textCache)return this.blobCache=new Blob([this.textCache],{type:"text/plain"}),this.blobCache;throw Error("Response body has already been consumed in a format that cannot be converted to Blob")}return this.bodyUsed=!0,this.blobCache=await this.response.blob(),this.blobCache}async getArrayBuffer(){if(void 0!==this.arrayBufferCache)return this.arrayBufferCache;if(this.bodyUsed){if(void 0!==this.textCache){const e=(new TextEncoder).encode(this.textCache);return this.arrayBufferCache=e.buffer,this.arrayBufferCache}if(void 0!==this.blobCache)return this.arrayBufferCache=await this.blobCache.arrayBuffer(),this.arrayBufferCache;throw Error("Response body has already been consumed in a format that cannot be converted to ArrayBuffer")}return this.bodyUsed=!0,this.arrayBufferCache=await this.response.arrayBuffer(),this.arrayBufferCache}getBody(){if(this.bodyUsed)throw Error("Response body has already been consumed");return this.bodyUsed=!0,this.response.body}}function f(){const e=new Map;return{get:t=>e.get(t)||null,set(t,s){e.set(t,s)},has:t=>e.has(t),delete(t){e.delete(t)},clear(){e.clear()}}}function d(){return{get:e=>"undefined"==typeof localStorage?null:localStorage.getItem(e),set(e,t){if("undefined"!=typeof localStorage)try{localStorage.setItem(e,t)}catch(s){this.clear();try{localStorage.setItem(e,t)}catch(e){}}},has:e=>"undefined"!=typeof localStorage&&null!==localStorage.getItem(e),delete(e){"undefined"!=typeof localStorage&&localStorage.removeItem(e)},clear(){"undefined"!=typeof localStorage&&localStorage.clear()}}}function l(){return{get:e=>"undefined"==typeof sessionStorage?null:sessionStorage.getItem(e),set(e,t){if("undefined"!=typeof sessionStorage)try{sessionStorage.setItem(e,t)}catch(s){this.clear();try{sessionStorage.setItem(e,t)}catch(e){}}},has:e=>"undefined"!=typeof sessionStorage&&null!==sessionStorage.getItem(e),delete(e){"undefined"!=typeof sessionStorage&&sessionStorage.removeItem(e)},clear(){"undefined"!=typeof sessionStorage&&sessionStorage.clear()}}}class y{constructor(e={}){this.entryCount=0,this.totalSize=0,this.options=e,this.storage=function(e){const{storage:t}=e||{};return t?"function"==typeof t.get&&"function"==typeof t.set?t:t===localStorage?d():t===sessionStorage?l():t instanceof Map?{get:e=>t.get(e)||null,set(e,s,r){t.set(e,s)},has:e=>t.has(e),delete(e){t.delete(e)},clear(){t.clear()}}:f():f()}(e)}generateKey(e,t,s,r){if(this.options.keyGenerator){const o=this.options.keyGenerator(e,t,s,r);return this.options.keyPrefix?`${this.options.keyPrefix}:${o}`:o}let o=`${t}:${e}`;if(this.options.varyByHeaders&&s){const e={};this.options.varyByHeaders.forEach((t=>{const r=t.toLowerCase();Object.keys(s).forEach((t=>{t.toLowerCase()===r&&(e[r]=s[t])}))})),Object.keys(e).length>0&&(o+=":"+JSON.stringify(e))}if("GET"!==t&&null!=r)try{"string"==typeof r||"number"==typeof r||"boolean"==typeof r?o+=":"+r:"object"==typeof r&&(o+=":"+JSON.stringify(r))}catch(e){}return this.options.keyPrefix?`${this.options.keyPrefix}:${o}`:o}async get(e){const t=await Promise.resolve(this.storage.get(e));if(!t)return null;try{const s=JSON.parse(t);return s.expiry&&s.expiry<Date.now()?(await Promise.resolve(this.storage.delete(e)),null):s}catch(t){return await Promise.resolve(this.storage.delete(e)),null}}async set(e,t,s){const r=Date.now(),o=this.options.ttl,i=JSON.stringify({value:t,timestamp:r,expiry:o?r+o:null,headers:s});if(this.options.maxSize){const e=function(e){var t;if("number"==typeof e)return e;const s=e.match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB)?$/i);if(!s)return 0;const r=parseFloat(s[1]);switch(null===(t=s[2])||void 0===t?void 0:t.toUpperCase()){case"KB":return 1024*r;case"MB":return 1048576*r;case"GB":return 1073741824*r;default:return r}}(this.options.maxSize),t=(new TextEncoder).encode(i).length;if(t>e)return;this.totalSize+t>e&&(await Promise.resolve(this.storage.clear()),this.totalSize=0,this.entryCount=0),this.totalSize+=t}this.options.maxEntries&&(this.entryCount++,this.entryCount>this.options.maxEntries&&(await Promise.resolve(this.storage.clear()),this.entryCount=1)),await Promise.resolve(this.storage.set(e,i,o))}async has(e){return Promise.resolve(this.storage.has(e))}async delete(e){return Promise.resolve(this.storage.delete(e))}async clear(){return this.entryCount=0,this.totalSize=0,Promise.resolve(this.storage.clear())}}class p{constructor(){this.csrfHeaderName="X-CSRF-Token",this.xsrfCookieName="XSRF-TOKEN",this.xsrfHeaderName="X-XSRF-TOKEN",this.csrfToken=null,this.enableAutoXsrf=!0,this.enableAntiCsrf=!0}static getInstance(){return p.instance||(p.instance=new p),p.instance}setCsrfToken(e){return this.csrfToken=e,this}getCsrfToken(){return this.csrfToken}setCsrfHeaderName(e){return this.csrfHeaderName=e,this}getCsrfHeaderName(){return this.csrfHeaderName}setXsrfCookieName(e){return this.xsrfCookieName=e,this}getXsrfCookieName(){return this.xsrfCookieName}setXsrfHeaderName(e){return this.xsrfHeaderName=e,this}getXsrfHeaderName(){return this.xsrfHeaderName}setEnableAutoXsrf(e){return this.enableAutoXsrf=e,this}isAutoXsrfEnabled(){return this.enableAutoXsrf}setEnableAntiCsrf(e){return this.enableAntiCsrf=e,this}isAntiCsrfEnabled(){return this.enableAntiCsrf}reset(){return this.csrfToken=null,this.csrfHeaderName="X-CSRF-Token",this.enableAntiCsrf=!0,this.xsrfCookieName="XSRF-TOKEN",this.xsrfHeaderName="X-XSRF-TOKEN",this.enableAutoXsrf=!0,this}}class m{static formatRequestCookies(e){const t=[];return Object.entries(e).forEach((([e,s])=>{let r;r="string"==typeof s?s:s.value,t.push(`${encodeURIComponent(e)}=${encodeURIComponent(r)}`)})),t.join("; ")}}class g{static getTokenFromMeta(e="csrf-token"){if("undefined"==typeof document)return null;const t=document.querySelector(`meta[name="${e}"]`);return(null==t?void 0:t.getAttribute("content"))||null}static getTokenFromCookie(e="csrf-token"){if("undefined"==typeof document)return null;const t=document.cookie.split(";");for(const s of t){const[t,r]=s.trim().split("=");if(t===e)return decodeURIComponent(r)}return null}static isValidToken(e){return!("string"!=typeof e||8>e.length||e.length>10&&(!/^[A-Za-z0-9\-_=+/.]+$/.test(e)||2>[/[A-Z]/.test(e),/[a-z]/.test(e),/[0-9]/.test(e),/[-_=+/.]/.test(e)].filter(Boolean).length))}}class C{constructor(){this.requestOptions={headers:{}},this.queryParams=new URLSearchParams,this.autoApplyCsrfProtection=!0,this.cacheEnabled=!1}withHeaders(e){return this.requestOptions.headers={...this.requestOptions.headers,...e},this}withHeader(e,t){return this.withHeaders({[e]:t})}withTimeout(e){if(!Number.isFinite(e)||0>=e)throw Error("Timeout must be a positive number");return this.requestOptions.timeout=e,this}withRetries(e){if(!Number.isInteger(e)||0>e)throw Error("Retry count must be a non-negative integer");return this.requestOptions.retries=e,this}onRetry(e){return this.requestOptions.onRetry=e,this}withCredentials(e=s.SAME_ORIGIN){return this.requestOptions.credentials=e,this}withAbortController(e){return this.abortController=e,this}withReferrer(e){return this.requestOptions.referrer=e,this}withReferrerPolicy(e){return this.requestOptions.referrerPolicy=e,this}withRedirect(e){return this.requestOptions.redirect=e,this}withKeepAlive(e){return this.requestOptions.keepalive=e,this}withPriority(e){return this.requestOptions.priority=e,this}withQueryParams(e){return Object.entries(e).forEach((([e,t])=>{null!=t&&(Array.isArray(t)?t.forEach((t=>this.queryParams.append(e,t+""))):this.queryParams.append(e,t+""))})),this}withQueryParam(e,t){return null==t||(Array.isArray(t)?t.forEach((t=>this.queryParams.append(e,t+""))):this.queryParams.append(e,t+"")),this}withMode(e){return this.requestOptions.mode=e,this}withContentType(e){return this.withHeaders({"Content-Type":e})}withAuthorization(e){return this.withHeaders({Authorization:e})}withBasicAuth(e,t){const s=this.encodeBase64(`${e}:${t}`);return this.withAuthorization("Basic "+s)}encodeBase64(e){if("undefined"!=typeof TextEncoder&&"function"==typeof btoa){const t=(new TextEncoder).encode(e);return btoa(String.fromCharCode.call(null,...new Uint8Array(t)))}if("function"==typeof btoa)return btoa(e);if("undefined"!=typeof Buffer)return Buffer.from(e).toString("base64");throw Error("Base64 encoding is not supported in this environment")}withBearerToken(e){return this.withAuthorization("Bearer "+e)}hasHeader(e,t){return Object.keys(e).some((e=>e.toLowerCase()===t.toLowerCase()))}withCookies(e){if(0===Object.entries(e).length)return this;const t=this.requestOptions.headers||{},s=[],r=[];Object.keys(t).forEach((e=>{"cookie"===e.toLowerCase()&&(t[e]&&s.push(t[e]),r.push(e))}));const o=m.formatRequestCookies(e),i=r.length>0?r[0]:"Cookie",n=[...s,o].filter(Boolean).join("; "),a={...t};return r.forEach((e=>{delete a[e]})),this.requestOptions.headers={...a,[i]:n},this}withCookie(e,t){return this.withCookies({[e]:t})}withCsrfToken(e,t="X-CSRF-Token"){return this.withHeaders({[t]:e})}withoutCsrfProtection(){return this.autoApplyCsrfProtection=!1,this}withAntiCsrfHeaders(){return this.withHeaders({"X-Requested-With":"XMLHttpRequest"})}withCache(e={}){return this.cacheEnabled=!0,this.cacheManager=new y(e),this}sendTo(e){if(e=this.formatUrlWithQueryParams(e),this.autoApplyCsrfProtection){const e=p.getInstance();e.isAntiCsrfEnabled()&&this.withHeaders({"X-Requested-With":"XMLHttpRequest"});const t=e.getCsrfToken();if(t){const s=this.requestOptions.headers,r=e.getCsrfHeaderName();this.hasHeader(s,"X-CSRF-Token")||this.hasHeader(s,r)||this.withHeaders({[r]:t})}if(e.isAutoXsrfEnabled()&&"undefined"!=typeof document){const t=g.getTokenFromCookie(e.getXsrfCookieName());if(t&&g.isValidToken(t)){const s=this.requestOptions.headers,r=e.getXsrfHeaderName();this.hasHeader(s,"X-XSRF-TOKEN")||this.hasHeader(s,r)||this.withHeaders({[r]:t})}}}const t={...this.requestOptions,method:this.method};let s;if(this.cacheEnabled&&this.cacheManager){const r=this.cacheManager.generateKey(e,this.method,this.requestOptions.headers,this.requestOptions.body);s=this.executeWithCache(e,t,r)}else s=this.requestOptions.retries?this.executeWithRetries(e,t):this.executeRequest(e,t);const r=s;return r.getJson=async()=>s.then((e=>e.getJson())),r.getText=async()=>s.then((e=>e.getText())),r.getBlob=async()=>s.then((e=>e.getBlob())),r.getArrayBuffer=async()=>s.then((e=>e.getArrayBuffer())),r.getBody=async()=>s.then((e=>e.getBody())),r.getData=async e=>{try{const t=await s.then((e=>e.getJson()));return e?e(t):t}catch(e){const t=Error(`Data selector failed: ${e instanceof Error?e.message:e+""}. Original data: ${JSON.stringify(await s.then((e=>e.getJson())),null,2)}`);throw e instanceof Error&&e.stack&&(t.stack=e.stack),t}},r}formatUrlWithQueryParams(e){if(!(""+this.queryParams))return e;try{const t=new URL(e);return this.queryParams.forEach(((e,s)=>{t.searchParams.append(s,e)})),""+t}catch(t){const s=e.includes("?");return`${e}${s?"&":"?"}${""+this.queryParams}`}}async executeWithRetries(e,t){let s=0;const r=this.requestOptions.retries||0;do{try{return await this.executeRequest(e,t)}catch(o){const i=o instanceof c?o:c.networkError(e,t.method,o);if(s>=r||i.timeoutError)throw i;s++,this.requestOptions.onRetry&&await this.requestOptions.onRetry({attempt:s,error:i})}}while(r>=s);throw new c(`Max retries reached (${r}) but request still failed`,e,t.method)}async executeRequest(e,t){const s=this.abortController||new AbortController,{signal:r}=s;let o;t.signal=t.signal||r;let i=!1;this.requestOptions.timeout&&(this.abortController?"function"==typeof AbortSignal.timeout?AbortSignal.timeout(this.requestOptions.timeout).addEventListener("abort",(()=>{s.signal.aborted||(i=!0,s.abort())})):o=setTimeout((()=>{s.signal.aborted||(i=!0,s.abort())}),this.requestOptions.timeout):o=setTimeout((()=>{i=!0,s.abort()}),this.requestOptions.timeout));try{let s;try{s=await fetch(e,t)}catch(s){if(s instanceof DOMException&&"AbortError"===s.name&&i)throw c.timeout(e,t.method,this.requestOptions.timeout);throw c.networkError(e,t.method,s)}if(!s.ok)throw c.fromResponse(s,e,t.method);return new u(s)}finally{o&&clearTimeout(o)}}async executeWithCache(e,t,s){if(this.cacheManager){const e=await this.cacheManager.get(s);if(e){const t=new Response(JSON.stringify(e.value),{status:200,headers:new Headers(e.headers||{})});return new u(t)}}const r=await(this.requestOptions.retries?this.executeWithRetries(e,t):this.executeRequest(e,t));if(r.ok&&this.cacheManager)try{const e=await r.getJson(),t={};r.headers.forEach(((e,s)=>{t[s]=e})),await this.cacheManager.set(s,e,t)}catch(e){}return r}}class b extends C{withBody(e){if(this.body=e,"string"==typeof e)this.bodyType=a.STRING,this.setContentTypeIfNeeded("text/plain");else if(null===e||"object"!=typeof e||e instanceof FormData||e instanceof Blob||e instanceof ArrayBuffer||e instanceof URLSearchParams||e instanceof ReadableStream)this.bodyType=a.BINARY;else{this.bodyType=a.JSON,this.setContentTypeIfNeeded("application/json");try{JSON.stringify(e)}catch(e){throw Error("Failed to stringify request body: "+e)}}return this}setContentTypeIfNeeded(e){Object.keys(this.requestOptions.headers).some((e=>"content-type"===e.toLowerCase()))||this.withContentType(e)}sendTo(e){return void 0!==this.body&&(this.requestOptions.body&&delete this.requestOptions.body,this.requestOptions.body=this.bodyType===a.JSON?JSON.stringify(this.body):this.body),super.sendTo(e)}}class w extends C{constructor(){super(...arguments),this.method=e.GET}}class O extends C{constructor(){super(...arguments),this.method=e.HEAD}}class E extends C{constructor(){super(...arguments),this.method=e.OPTIONS}}class S extends C{constructor(){super(...arguments),this.method=e.DELETE}}class R extends b{constructor(){super(...arguments),this.method=e.POST}}class T extends b{constructor(){super(...arguments),this.method=e.PUT}}class N extends b{constructor(){super(...arguments),this.method=e.PATCH}}const x={get:function(){return new w},post:function(){return new R},put:function(){return new T},del:function(){return new S},patch:function(){return new N},head:function(){return new O},options:function(){return new E},config:p.getInstance()};export{y as CacheManager,i as CacheMode,m as CookieUtils,s as CredentialsPolicy,S as DeleteRequest,w as GetRequest,O as HeadRequest,e as HttpMethod,E as OptionsRequest,N as PatchRequest,R as PostRequest,T as PutRequest,o as RedirectMode,h as ReferrerPolicy,c as RequestError,r as RequestMode,t as RequestPriority,u as ResponseWrapper,n as SameSitePolicy,d as createLocalStorageStorage,f as createMemoryStorage,l as createSessionStorageStorage,x as default};//# sourceMappingURL=index.esm.js.map