fetchguard 1.6.3 → 2.1.0
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 +268 -66
- package/dist/index.d.ts +594 -31
- package/dist/index.js +544 -86
- package/dist/index.js.map +1 -1
- package/dist/worker.js +177 -74
- package/dist/worker.js.map +1 -1
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -18,7 +18,8 @@ var MSG = Object.freeze({
|
|
|
18
18
|
AUTH_STATE_CHANGED: "AUTH_STATE_CHANGED",
|
|
19
19
|
AUTH_CALL_RESULT: "AUTH_CALL_RESULT",
|
|
20
20
|
FETCH_RESULT: "FETCH_RESULT",
|
|
21
|
-
FETCH_ERROR: "FETCH_ERROR"
|
|
21
|
+
FETCH_ERROR: "FETCH_ERROR",
|
|
22
|
+
TOKEN_REFRESHED: "TOKEN_REFRESHED"
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
// src/constants.ts
|
|
@@ -26,65 +27,101 @@ var DEFAULT_REFRESH_EARLY_MS = 6e4;
|
|
|
26
27
|
|
|
27
28
|
// src/errors.ts
|
|
28
29
|
import { defineError, defineErrorAdvanced } from "ts-micro-result";
|
|
30
|
+
|
|
31
|
+
// src/error-codes.ts
|
|
32
|
+
var ERROR_CODES = {
|
|
33
|
+
// General
|
|
34
|
+
UNEXPECTED: "UNEXPECTED",
|
|
35
|
+
UNKNOWN_MESSAGE: "UNKNOWN_MESSAGE",
|
|
36
|
+
RESULT_PARSE_ERROR: "RESULT_PARSE_ERROR",
|
|
37
|
+
// Init
|
|
38
|
+
INIT_ERROR: "INIT_ERROR",
|
|
39
|
+
PROVIDER_INIT_FAILED: "PROVIDER_INIT_FAILED",
|
|
40
|
+
INIT_FAILED: "INIT_FAILED",
|
|
41
|
+
// Auth
|
|
42
|
+
TOKEN_REFRESH_FAILED: "TOKEN_REFRESH_FAILED",
|
|
43
|
+
TOKEN_EXCHANGE_FAILED: "TOKEN_EXCHANGE_FAILED",
|
|
44
|
+
LOGIN_FAILED: "LOGIN_FAILED",
|
|
45
|
+
LOGOUT_FAILED: "LOGOUT_FAILED",
|
|
46
|
+
NOT_AUTHENTICATED: "NOT_AUTHENTICATED",
|
|
47
|
+
// Domain
|
|
48
|
+
DOMAIN_NOT_ALLOWED: "DOMAIN_NOT_ALLOWED",
|
|
49
|
+
// Request
|
|
50
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
51
|
+
REQUEST_CANCELLED: "REQUEST_CANCELLED",
|
|
52
|
+
HTTP_ERROR: "HTTP_ERROR",
|
|
53
|
+
RESPONSE_PARSE_FAILED: "RESPONSE_PARSE_FAILED",
|
|
54
|
+
QUEUE_FULL: "QUEUE_FULL",
|
|
55
|
+
REQUEST_TIMEOUT: "REQUEST_TIMEOUT"
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/errors.ts
|
|
29
59
|
var GeneralErrors = {
|
|
30
|
-
Unexpected: defineError(
|
|
31
|
-
UnknownMessage: defineError(
|
|
32
|
-
ResultParse: defineError(
|
|
60
|
+
Unexpected: defineError(ERROR_CODES.UNEXPECTED, "Unexpected error"),
|
|
61
|
+
UnknownMessage: defineError(ERROR_CODES.UNKNOWN_MESSAGE, "Unknown message type"),
|
|
62
|
+
ResultParse: defineError(ERROR_CODES.RESULT_PARSE_ERROR, "Failed to parse result")
|
|
33
63
|
};
|
|
34
64
|
var InitErrors = {
|
|
35
|
-
NotInitialized: defineError(
|
|
36
|
-
ProviderInitFailed: defineError(
|
|
37
|
-
InitFailed: defineError(
|
|
65
|
+
NotInitialized: defineError(ERROR_CODES.INIT_ERROR, "Worker not initialized"),
|
|
66
|
+
ProviderInitFailed: defineError(ERROR_CODES.PROVIDER_INIT_FAILED, "Failed to initialize provider"),
|
|
67
|
+
InitFailed: defineError(ERROR_CODES.INIT_FAILED, "Initialization failed")
|
|
38
68
|
};
|
|
39
69
|
var AuthErrors = {
|
|
40
|
-
TokenRefreshFailed: defineError(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
70
|
+
TokenRefreshFailed: defineError(ERROR_CODES.TOKEN_REFRESH_FAILED, "Token refresh failed"),
|
|
71
|
+
TokenExchangeFailed: defineError(ERROR_CODES.TOKEN_EXCHANGE_FAILED, "Token exchange failed"),
|
|
72
|
+
LoginFailed: defineError(ERROR_CODES.LOGIN_FAILED, "Login failed"),
|
|
73
|
+
LogoutFailed: defineError(ERROR_CODES.LOGOUT_FAILED, "Logout failed"),
|
|
74
|
+
NotAuthenticated: defineError(ERROR_CODES.NOT_AUTHENTICATED, "User is not authenticated")
|
|
44
75
|
};
|
|
45
76
|
var DomainErrors = {
|
|
46
|
-
NotAllowed: defineErrorAdvanced(
|
|
77
|
+
NotAllowed: defineErrorAdvanced(ERROR_CODES.DOMAIN_NOT_ALLOWED, "Domain not allowed: {url}")
|
|
47
78
|
};
|
|
48
79
|
var RequestErrors = {
|
|
49
80
|
// Network errors (connection failed, no response)
|
|
50
|
-
NetworkError: defineError(
|
|
51
|
-
Cancelled: defineError(
|
|
81
|
+
NetworkError: defineError(ERROR_CODES.NETWORK_ERROR, "Network error"),
|
|
82
|
+
Cancelled: defineError(ERROR_CODES.REQUEST_CANCELLED, "Request was cancelled"),
|
|
52
83
|
// HTTP errors (server responded with error status)
|
|
53
|
-
HttpError: defineErrorAdvanced(
|
|
84
|
+
HttpError: defineErrorAdvanced(ERROR_CODES.HTTP_ERROR, "HTTP {status} error"),
|
|
54
85
|
// Response parsing errors
|
|
55
|
-
ResponseParseFailed: defineError(
|
|
86
|
+
ResponseParseFailed: defineError(ERROR_CODES.RESPONSE_PARSE_FAILED, "Failed to parse response body"),
|
|
87
|
+
// Queue errors
|
|
88
|
+
QueueFull: defineErrorAdvanced(ERROR_CODES.QUEUE_FULL, "Request queue full ({size}/{maxSize})"),
|
|
89
|
+
// Timeout errors
|
|
90
|
+
Timeout: defineError(ERROR_CODES.REQUEST_TIMEOUT, "Request timed out")
|
|
56
91
|
};
|
|
57
92
|
|
|
58
93
|
// src/utils/formdata.ts
|
|
59
94
|
async function serializeFormData(formData) {
|
|
60
95
|
const entries = [];
|
|
96
|
+
const transferables = [];
|
|
97
|
+
const orderedEntries = [];
|
|
98
|
+
let index = 0;
|
|
61
99
|
formData.forEach((value, key) => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
entries.push([key, String(value)]);
|
|
65
|
-
}
|
|
100
|
+
orderedEntries.push({ index, key, value });
|
|
101
|
+
index++;
|
|
66
102
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const arrayBuffer = await value.arrayBuffer();
|
|
72
|
-
const uint8Array = new Uint8Array(arrayBuffer);
|
|
103
|
+
await Promise.all(
|
|
104
|
+
orderedEntries.map(async ({ index: idx, key, value }) => {
|
|
105
|
+
if (value instanceof File) {
|
|
106
|
+
const buffer = await value.arrayBuffer();
|
|
73
107
|
const serializedFile = {
|
|
74
108
|
name: value.name,
|
|
75
109
|
type: value.type,
|
|
76
|
-
|
|
77
|
-
// Convert to number array
|
|
110
|
+
buffer
|
|
78
111
|
};
|
|
79
|
-
entries
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
112
|
+
entries[idx] = [key, serializedFile];
|
|
113
|
+
transferables.push(buffer);
|
|
114
|
+
} else {
|
|
115
|
+
entries[idx] = [key, String(value)];
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
);
|
|
85
119
|
return {
|
|
86
|
-
|
|
87
|
-
|
|
120
|
+
data: {
|
|
121
|
+
_type: "FormData",
|
|
122
|
+
entries
|
|
123
|
+
},
|
|
124
|
+
transferables
|
|
88
125
|
};
|
|
89
126
|
}
|
|
90
127
|
function deserializeFormData(serialized) {
|
|
@@ -93,8 +130,7 @@ function deserializeFormData(serialized) {
|
|
|
93
130
|
if (typeof value === "string") {
|
|
94
131
|
formData.append(key, value);
|
|
95
132
|
} else {
|
|
96
|
-
const
|
|
97
|
-
const file = new File([uint8Array], value.name, { type: value.type });
|
|
133
|
+
const file = new File([value.buffer], value.name, { type: value.type });
|
|
98
134
|
formData.append(key, file);
|
|
99
135
|
}
|
|
100
136
|
}
|
|
@@ -108,22 +144,46 @@ function isSerializedFormData(body) {
|
|
|
108
144
|
}
|
|
109
145
|
|
|
110
146
|
// src/client.ts
|
|
147
|
+
var DEFAULT_MAX_CONCURRENT = 6;
|
|
148
|
+
var DEFAULT_MAX_QUEUE_SIZE = 1e3;
|
|
149
|
+
var DEFAULT_SETUP_TIMEOUT = 1e4;
|
|
150
|
+
var DEFAULT_REQUEST_TIMEOUT = 3e4;
|
|
111
151
|
var FetchGuardClient = class {
|
|
112
152
|
worker;
|
|
113
153
|
messageId = 0;
|
|
114
154
|
// Using unknown because different messages have different response types
|
|
115
|
-
// (
|
|
155
|
+
// (FetchEnvelope for FETCH, AuthResult for AUTH_CALL, etc.)
|
|
116
156
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
157
|
+
/** Track request URLs for debug hooks */
|
|
158
|
+
requestUrls = /* @__PURE__ */ new Map();
|
|
159
|
+
/** Track request timing for metrics */
|
|
160
|
+
requestTimings = /* @__PURE__ */ new Map();
|
|
117
161
|
authListeners = /* @__PURE__ */ new Set();
|
|
118
162
|
readyListeners = /* @__PURE__ */ new Set();
|
|
119
163
|
isReady = false;
|
|
120
164
|
requestQueue = [];
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
165
|
+
activeRequests = 0;
|
|
166
|
+
maxConcurrent;
|
|
167
|
+
maxQueueSize;
|
|
168
|
+
setupTimeout;
|
|
169
|
+
requestTimeout;
|
|
124
170
|
setupResolve;
|
|
125
171
|
setupReject;
|
|
172
|
+
debug;
|
|
173
|
+
retry;
|
|
174
|
+
dedupe;
|
|
175
|
+
/** In-flight requests for deduplication */
|
|
176
|
+
inFlightRequests = /* @__PURE__ */ new Map();
|
|
177
|
+
/** Recent completed requests for time-window deduplication */
|
|
178
|
+
recentResults = /* @__PURE__ */ new Map();
|
|
126
179
|
constructor(options) {
|
|
180
|
+
this.maxConcurrent = options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
|
|
181
|
+
this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
182
|
+
this.setupTimeout = options.setupTimeout ?? DEFAULT_SETUP_TIMEOUT;
|
|
183
|
+
this.requestTimeout = options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
184
|
+
this.debug = options.debug;
|
|
185
|
+
this.retry = options.retry;
|
|
186
|
+
this.dedupe = options.dedupe;
|
|
127
187
|
this.worker = new Worker(new URL("./worker.js", import.meta.url), {
|
|
128
188
|
type: "module"
|
|
129
189
|
});
|
|
@@ -168,7 +228,7 @@ var FetchGuardClient = class {
|
|
|
168
228
|
this.setupResolve = void 0;
|
|
169
229
|
this.setupReject = void 0;
|
|
170
230
|
}
|
|
171
|
-
},
|
|
231
|
+
}, this.setupTimeout);
|
|
172
232
|
});
|
|
173
233
|
}
|
|
174
234
|
/**
|
|
@@ -179,31 +239,35 @@ var FetchGuardClient = class {
|
|
|
179
239
|
if (type === MSG.FETCH_RESULT) {
|
|
180
240
|
const request = this.pendingRequests.get(id);
|
|
181
241
|
if (!request) return;
|
|
242
|
+
const url = this.requestUrls.get(id);
|
|
243
|
+
const timing = this.requestTimings.get(id);
|
|
182
244
|
this.pendingRequests.delete(id);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
{
|
|
190
|
-
body: String(payload?.body ?? ""),
|
|
191
|
-
headers: payload?.headers ?? {}
|
|
192
|
-
},
|
|
193
|
-
payload?.status
|
|
194
|
-
));
|
|
245
|
+
this.requestUrls.delete(id);
|
|
246
|
+
this.requestTimings.delete(id);
|
|
247
|
+
this.onRequestComplete();
|
|
248
|
+
const metrics = this.calculateMetrics(timing);
|
|
249
|
+
if (this.debug?.onResponse && url) {
|
|
250
|
+
this.debug.onResponse(url, payload, metrics);
|
|
195
251
|
}
|
|
252
|
+
request.resolve(ok(payload));
|
|
196
253
|
return;
|
|
197
254
|
}
|
|
198
255
|
if (type === MSG.FETCH_ERROR) {
|
|
199
256
|
const request = this.pendingRequests.get(id);
|
|
200
257
|
if (!request) return;
|
|
258
|
+
const url = this.requestUrls.get(id);
|
|
259
|
+
const timing = this.requestTimings.get(id);
|
|
201
260
|
this.pendingRequests.delete(id);
|
|
202
|
-
|
|
261
|
+
this.requestUrls.delete(id);
|
|
262
|
+
this.requestTimings.delete(id);
|
|
263
|
+
this.onRequestComplete();
|
|
264
|
+
const errorMessage = String(payload?.error || "Network error");
|
|
265
|
+
const metrics = this.calculateMetrics(timing);
|
|
266
|
+
if (this.debug?.onError && url) {
|
|
267
|
+
this.debug.onError(url, { code: "NETWORK_ERROR", message: errorMessage }, metrics);
|
|
268
|
+
}
|
|
203
269
|
request.resolve(err(
|
|
204
|
-
RequestErrors.NetworkError({ message:
|
|
205
|
-
void 0,
|
|
206
|
-
status
|
|
270
|
+
RequestErrors.NetworkError({ message: errorMessage })
|
|
207
271
|
));
|
|
208
272
|
return;
|
|
209
273
|
}
|
|
@@ -211,7 +275,8 @@ var FetchGuardClient = class {
|
|
|
211
275
|
const request = this.pendingRequests.get(id);
|
|
212
276
|
if (!request) return;
|
|
213
277
|
this.pendingRequests.delete(id);
|
|
214
|
-
|
|
278
|
+
this.onRequestComplete();
|
|
279
|
+
request.resolve(err(payload.errors, payload.meta));
|
|
215
280
|
return;
|
|
216
281
|
}
|
|
217
282
|
if (type === MSG.SETUP_ERROR) {
|
|
@@ -224,6 +289,7 @@ var FetchGuardClient = class {
|
|
|
224
289
|
}
|
|
225
290
|
if (type === MSG.READY) {
|
|
226
291
|
this.isReady = true;
|
|
292
|
+
this.debug?.onWorkerReady?.();
|
|
227
293
|
for (const listener of this.readyListeners) {
|
|
228
294
|
listener();
|
|
229
295
|
}
|
|
@@ -238,6 +304,7 @@ var FetchGuardClient = class {
|
|
|
238
304
|
const request = this.pendingRequests.get(id);
|
|
239
305
|
if (request) {
|
|
240
306
|
this.pendingRequests.delete(id);
|
|
307
|
+
this.onRequestComplete();
|
|
241
308
|
request.resolve(ok({ timestamp: payload?.timestamp }));
|
|
242
309
|
}
|
|
243
310
|
return;
|
|
@@ -250,20 +317,28 @@ var FetchGuardClient = class {
|
|
|
250
317
|
const request = this.pendingRequests.get(id);
|
|
251
318
|
if (request) {
|
|
252
319
|
this.pendingRequests.delete(id);
|
|
320
|
+
this.onRequestComplete();
|
|
253
321
|
request.resolve(ok(payload));
|
|
254
322
|
}
|
|
255
323
|
return;
|
|
256
324
|
}
|
|
325
|
+
if (type === MSG.TOKEN_REFRESHED) {
|
|
326
|
+
this.debug?.onRefresh?.(payload?.reason);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
257
329
|
}
|
|
258
330
|
/**
|
|
259
331
|
* Handle worker errors
|
|
260
332
|
*/
|
|
261
333
|
handleWorkerError(error) {
|
|
262
334
|
console.error("Worker error:", error);
|
|
335
|
+
this.debug?.onWorkerError?.(error);
|
|
263
336
|
for (const [id, request] of this.pendingRequests) {
|
|
264
337
|
request.reject(new Error(`Worker error: ${error.message}`));
|
|
265
338
|
}
|
|
266
339
|
this.pendingRequests.clear();
|
|
340
|
+
this.requestUrls.clear();
|
|
341
|
+
this.requestTimings.clear();
|
|
267
342
|
}
|
|
268
343
|
/**
|
|
269
344
|
* Generate unique message ID
|
|
@@ -272,11 +347,207 @@ var FetchGuardClient = class {
|
|
|
272
347
|
return `msg_${++this.messageId}_${Date.now()}`;
|
|
273
348
|
}
|
|
274
349
|
/**
|
|
275
|
-
* Make API request
|
|
350
|
+
* Make API request with optional deduplication, retry, and AbortSignal support
|
|
351
|
+
*
|
|
352
|
+
* @param url - Full URL to fetch
|
|
353
|
+
* @param options - Request options including optional AbortSignal
|
|
354
|
+
* @returns Result with FetchEnvelope on success, error on failure
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* // With AbortSignal
|
|
358
|
+
* const controller = new AbortController()
|
|
359
|
+
* setTimeout(() => controller.abort(), 5000)
|
|
360
|
+
* const result = await api.fetch('/slow', { signal: controller.signal })
|
|
276
361
|
*/
|
|
277
362
|
async fetch(url, options = {}) {
|
|
278
|
-
const {
|
|
279
|
-
|
|
363
|
+
const { signal, ...restOptions } = options;
|
|
364
|
+
if (signal?.aborted) {
|
|
365
|
+
return err(RequestErrors.Cancelled());
|
|
366
|
+
}
|
|
367
|
+
const dedupeKey = this.getDedupeKey(url, restOptions);
|
|
368
|
+
if (dedupeKey) {
|
|
369
|
+
const inFlight = this.inFlightRequests.get(dedupeKey);
|
|
370
|
+
if (inFlight) {
|
|
371
|
+
if (signal) {
|
|
372
|
+
return this.wrapWithAbortSignal(inFlight, signal, null);
|
|
373
|
+
}
|
|
374
|
+
return inFlight;
|
|
375
|
+
}
|
|
376
|
+
const window = this.dedupe?.window ?? 0;
|
|
377
|
+
if (window > 0) {
|
|
378
|
+
const recent = this.recentResults.get(dedupeKey);
|
|
379
|
+
if (recent && Date.now() - recent.timestamp < window) {
|
|
380
|
+
return recent.result;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const promise = this.fetchWithRetryAndSignal(url, restOptions, signal ?? void 0);
|
|
384
|
+
this.inFlightRequests.set(dedupeKey, promise);
|
|
385
|
+
try {
|
|
386
|
+
const result = await promise;
|
|
387
|
+
if (window > 0) {
|
|
388
|
+
this.recentResults.set(dedupeKey, { result, timestamp: Date.now() });
|
|
389
|
+
setTimeout(() => this.recentResults.delete(dedupeKey), window);
|
|
390
|
+
}
|
|
391
|
+
return result;
|
|
392
|
+
} finally {
|
|
393
|
+
this.inFlightRequests.delete(dedupeKey);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return this.fetchWithRetryAndSignal(url, restOptions, signal ?? void 0);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Wrap a promise with AbortSignal support
|
|
400
|
+
*/
|
|
401
|
+
wrapWithAbortSignal(promise, signal, requestId) {
|
|
402
|
+
return new Promise((resolve) => {
|
|
403
|
+
const abortHandler = () => {
|
|
404
|
+
if (requestId) {
|
|
405
|
+
this.cancel(requestId);
|
|
406
|
+
}
|
|
407
|
+
resolve(err(RequestErrors.Cancelled()));
|
|
408
|
+
};
|
|
409
|
+
if (signal.aborted) {
|
|
410
|
+
abortHandler();
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
414
|
+
promise.then((result) => {
|
|
415
|
+
signal.removeEventListener("abort", abortHandler);
|
|
416
|
+
resolve(result);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Fetch with retry logic and AbortSignal support (internal)
|
|
422
|
+
*/
|
|
423
|
+
async fetchWithRetryAndSignal(url, options, signal) {
|
|
424
|
+
const maxAttempts = this.retry?.maxAttempts ?? 0;
|
|
425
|
+
const delay = this.retry?.delay ?? 1e3;
|
|
426
|
+
const backoff = this.retry?.backoff ?? 1;
|
|
427
|
+
const maxDelay = this.retry?.maxDelay ?? 3e4;
|
|
428
|
+
const jitter = this.retry?.jitter ?? 0;
|
|
429
|
+
const shouldRetry = this.retry?.shouldRetry ?? this.defaultShouldRetry;
|
|
430
|
+
let lastResult = null;
|
|
431
|
+
let currentDelay = delay;
|
|
432
|
+
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
433
|
+
if (signal?.aborted) {
|
|
434
|
+
return err(RequestErrors.Cancelled());
|
|
435
|
+
}
|
|
436
|
+
const { id, result } = this.fetchWithId(url, options);
|
|
437
|
+
if (signal) {
|
|
438
|
+
lastResult = await this.wrapWithAbortSignal(result, signal, id);
|
|
439
|
+
} else {
|
|
440
|
+
lastResult = await result;
|
|
441
|
+
}
|
|
442
|
+
if (lastResult.ok) {
|
|
443
|
+
return lastResult;
|
|
444
|
+
}
|
|
445
|
+
if (lastResult.errors[0]?.code === "REQUEST_CANCELLED") {
|
|
446
|
+
return lastResult;
|
|
447
|
+
}
|
|
448
|
+
const error = lastResult.errors[0];
|
|
449
|
+
const errorDetail = {
|
|
450
|
+
code: error?.code ?? "NETWORK_ERROR",
|
|
451
|
+
message: error?.message ?? "Unknown error"
|
|
452
|
+
};
|
|
453
|
+
if (attempt >= maxAttempts || !shouldRetry(errorDetail)) {
|
|
454
|
+
return lastResult;
|
|
455
|
+
}
|
|
456
|
+
const cappedDelay = Math.min(currentDelay, maxDelay);
|
|
457
|
+
const jitteredDelay = this.applyJitter(cappedDelay, jitter);
|
|
458
|
+
if (signal) {
|
|
459
|
+
const aborted = await this.sleepWithAbort(jitteredDelay, signal);
|
|
460
|
+
if (aborted) {
|
|
461
|
+
return err(RequestErrors.Cancelled());
|
|
462
|
+
}
|
|
463
|
+
} else {
|
|
464
|
+
await this.sleep(jitteredDelay);
|
|
465
|
+
}
|
|
466
|
+
currentDelay = currentDelay * backoff;
|
|
467
|
+
}
|
|
468
|
+
return lastResult;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Generate deduplication key for request
|
|
472
|
+
* Returns null if request should not be deduplicated
|
|
473
|
+
*/
|
|
474
|
+
getDedupeKey(url, options) {
|
|
475
|
+
if (!this.dedupe?.enabled) {
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
if (this.dedupe.keyGenerator) {
|
|
479
|
+
return this.dedupe.keyGenerator(url, options);
|
|
480
|
+
}
|
|
481
|
+
const method = (options.method ?? "GET").toUpperCase();
|
|
482
|
+
if (method !== "GET") {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
return `GET:${url}`;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Apply jitter to a delay value
|
|
489
|
+
* Jitter adds ±(jitter * delay) randomness to prevent thundering herd
|
|
490
|
+
* @param delay - Base delay in milliseconds
|
|
491
|
+
* @param jitter - Jitter factor (0-1)
|
|
492
|
+
* @returns Jittered delay
|
|
493
|
+
*/
|
|
494
|
+
applyJitter(delay, jitter) {
|
|
495
|
+
if (jitter <= 0) return delay;
|
|
496
|
+
const clampedJitter = Math.min(Math.max(jitter, 0), 1);
|
|
497
|
+
const randomFactor = Math.random() * 2 - 1;
|
|
498
|
+
return Math.max(0, delay + delay * clampedJitter * randomFactor);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Default retry condition - only retry on NETWORK_ERROR
|
|
502
|
+
*/
|
|
503
|
+
defaultShouldRetry(error) {
|
|
504
|
+
return error.code === "NETWORK_ERROR";
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Calculate request metrics from timing data
|
|
508
|
+
*/
|
|
509
|
+
calculateMetrics(timing) {
|
|
510
|
+
if (!timing) return void 0;
|
|
511
|
+
const endTime = Date.now();
|
|
512
|
+
const startTime = timing.createdAt;
|
|
513
|
+
const sentAt = timing.sentAt ?? startTime;
|
|
514
|
+
const duration = endTime - startTime;
|
|
515
|
+
const queueTime = sentAt - startTime;
|
|
516
|
+
const ipcTime = duration - queueTime;
|
|
517
|
+
return {
|
|
518
|
+
startTime,
|
|
519
|
+
endTime,
|
|
520
|
+
duration,
|
|
521
|
+
queueTime,
|
|
522
|
+
ipcTime
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Sleep helper for retry delay
|
|
527
|
+
*/
|
|
528
|
+
sleep(ms) {
|
|
529
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Sleep with abort signal support
|
|
533
|
+
* Returns true if aborted, false if completed normally
|
|
534
|
+
*/
|
|
535
|
+
sleepWithAbort(ms, signal) {
|
|
536
|
+
return new Promise((resolve) => {
|
|
537
|
+
if (signal.aborted) {
|
|
538
|
+
resolve(true);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const timer = setTimeout(() => {
|
|
542
|
+
signal.removeEventListener("abort", abortHandler);
|
|
543
|
+
resolve(false);
|
|
544
|
+
}, ms);
|
|
545
|
+
const abortHandler = () => {
|
|
546
|
+
clearTimeout(timer);
|
|
547
|
+
resolve(true);
|
|
548
|
+
};
|
|
549
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
550
|
+
});
|
|
280
551
|
}
|
|
281
552
|
/**
|
|
282
553
|
* Fetch with id for external cancellation
|
|
@@ -290,11 +561,18 @@ var FetchGuardClient = class {
|
|
|
290
561
|
resolve: (response) => resolve(response),
|
|
291
562
|
reject: (error) => reject(error)
|
|
292
563
|
});
|
|
564
|
+
this.requestUrls.set(id, url);
|
|
565
|
+
this.requestTimings.set(id, { createdAt: Date.now() });
|
|
566
|
+
this.debug?.onRequest?.(url, options);
|
|
293
567
|
try {
|
|
294
568
|
let serializedOptions = { ...options };
|
|
569
|
+
let transferables;
|
|
295
570
|
if (options.body && isFormData(options.body)) {
|
|
296
|
-
const
|
|
297
|
-
serializedOptions.body =
|
|
571
|
+
const { data, transferables: formDataTransferables } = await serializeFormData(options.body);
|
|
572
|
+
serializedOptions.body = data;
|
|
573
|
+
if (formDataTransferables.length > 0) {
|
|
574
|
+
transferables = formDataTransferables;
|
|
575
|
+
}
|
|
298
576
|
}
|
|
299
577
|
if (options.headers) {
|
|
300
578
|
if (options.headers instanceof Headers) {
|
|
@@ -306,11 +584,13 @@ var FetchGuardClient = class {
|
|
|
306
584
|
}
|
|
307
585
|
}
|
|
308
586
|
const message = { id, type: MSG.FETCH, payload: { url, options: serializedOptions } };
|
|
309
|
-
await this.sendMessageQueued(message, 3e4);
|
|
587
|
+
await this.sendMessageQueued(message, 3e4, transferables);
|
|
310
588
|
} catch (error) {
|
|
311
589
|
const request = this.pendingRequests.get(id);
|
|
312
590
|
if (request) {
|
|
313
591
|
this.pendingRequests.delete(id);
|
|
592
|
+
this.requestUrls.delete(id);
|
|
593
|
+
this.requestTimings.delete(id);
|
|
314
594
|
request.reject(error instanceof Error ? error : new Error(String(error)));
|
|
315
595
|
}
|
|
316
596
|
}
|
|
@@ -324,8 +604,16 @@ var FetchGuardClient = class {
|
|
|
324
604
|
cancel(id) {
|
|
325
605
|
const request = this.pendingRequests.get(id);
|
|
326
606
|
if (request) {
|
|
607
|
+
const url = this.requestUrls.get(id);
|
|
608
|
+
const timing = this.requestTimings.get(id);
|
|
327
609
|
this.pendingRequests.delete(id);
|
|
610
|
+
this.requestUrls.delete(id);
|
|
611
|
+
this.requestTimings.delete(id);
|
|
328
612
|
this.worker.postMessage({ id, type: MSG.CANCEL });
|
|
613
|
+
const metrics = this.calculateMetrics(timing);
|
|
614
|
+
if (this.debug?.onError && url) {
|
|
615
|
+
this.debug.onError(url, { code: "REQUEST_CANCELLED", message: "Request cancelled" }, metrics);
|
|
616
|
+
}
|
|
329
617
|
request.reject(new Error("Request cancelled"));
|
|
330
618
|
}
|
|
331
619
|
}
|
|
@@ -454,6 +742,37 @@ var FetchGuardClient = class {
|
|
|
454
742
|
async refreshToken(emitEvent = true) {
|
|
455
743
|
return this.call("refreshToken", emitEvent);
|
|
456
744
|
}
|
|
745
|
+
/**
|
|
746
|
+
* Exchange current token for a new one with different context
|
|
747
|
+
*
|
|
748
|
+
* Useful for:
|
|
749
|
+
* - Switching tenants in multi-tenant apps
|
|
750
|
+
* - Changing authorization scope
|
|
751
|
+
* - Impersonating users (admin feature)
|
|
752
|
+
*
|
|
753
|
+
* @param url - URL to call for token exchange
|
|
754
|
+
* @param options - Exchange options (method, payload)
|
|
755
|
+
* @param emitEvent - Whether to emit AUTH_STATE_CHANGED event (default: true)
|
|
756
|
+
*
|
|
757
|
+
* @example
|
|
758
|
+
* // Switch tenant
|
|
759
|
+
* await api.exchangeToken('https://auth.example.com/auth/select-tenant', {
|
|
760
|
+
* payload: { tenantId: 'tenant_123' }
|
|
761
|
+
* })
|
|
762
|
+
*
|
|
763
|
+
* // Change scope with PUT method
|
|
764
|
+
* await api.exchangeToken('https://auth.example.com/auth/switch-context', {
|
|
765
|
+
* method: 'PUT',
|
|
766
|
+
* payload: { scope: 'admin' }
|
|
767
|
+
* })
|
|
768
|
+
*/
|
|
769
|
+
async exchangeToken(url, options, emitEvent = true) {
|
|
770
|
+
const args = [url];
|
|
771
|
+
if (options) {
|
|
772
|
+
args.push(options);
|
|
773
|
+
}
|
|
774
|
+
return this.call("exchangeToken", emitEvent, ...args);
|
|
775
|
+
}
|
|
457
776
|
/**
|
|
458
777
|
* Check if worker is ready
|
|
459
778
|
*/
|
|
@@ -511,20 +830,28 @@ var FetchGuardClient = class {
|
|
|
511
830
|
/**
|
|
512
831
|
* Send message through queue system
|
|
513
832
|
* All messages go through queue for sequential processing
|
|
833
|
+
* @param transferables - Optional Transferable objects for zero-copy postMessage
|
|
514
834
|
*/
|
|
515
|
-
sendMessageQueued(message, timeoutMs = this.
|
|
835
|
+
sendMessageQueued(message, timeoutMs = this.requestTimeout, transferables) {
|
|
516
836
|
return new Promise((resolve, reject) => {
|
|
837
|
+
if (this.requestQueue.length >= this.maxQueueSize) {
|
|
838
|
+
reject(err(RequestErrors.QueueFull({ size: this.requestQueue.length, maxSize: this.maxQueueSize })));
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
517
841
|
const timeout = setTimeout(() => {
|
|
518
842
|
const index = this.requestQueue.findIndex((item) => item.id === message.id);
|
|
519
843
|
if (index !== -1) {
|
|
520
844
|
this.requestQueue.splice(index, 1);
|
|
521
845
|
}
|
|
522
846
|
this.pendingRequests.delete(message.id);
|
|
523
|
-
|
|
847
|
+
this.requestUrls.delete(message.id);
|
|
848
|
+
this.requestTimings.delete(message.id);
|
|
849
|
+
reject(err(RequestErrors.Timeout()));
|
|
524
850
|
}, timeoutMs);
|
|
525
851
|
const queueItem = {
|
|
526
852
|
id: message.id,
|
|
527
853
|
message,
|
|
854
|
+
transferables,
|
|
528
855
|
resolve,
|
|
529
856
|
reject,
|
|
530
857
|
timeout
|
|
@@ -534,29 +861,44 @@ var FetchGuardClient = class {
|
|
|
534
861
|
});
|
|
535
862
|
}
|
|
536
863
|
/**
|
|
537
|
-
* Process message queue
|
|
864
|
+
* Process message queue with concurrency limit
|
|
865
|
+
*
|
|
866
|
+
* Uses semaphore pattern to allow N concurrent requests.
|
|
538
867
|
* Benefits:
|
|
539
|
-
* -
|
|
868
|
+
* - Higher throughput than sequential processing
|
|
869
|
+
* - Backpressure via maxConcurrent limit
|
|
540
870
|
* - Better error isolation (one failure doesn't affect others)
|
|
541
|
-
* - 50ms delay between requests for backpressure
|
|
542
871
|
*/
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
this.isProcessingQueue = true;
|
|
548
|
-
while (this.requestQueue.length > 0) {
|
|
872
|
+
processQueue() {
|
|
873
|
+
while (this.requestQueue.length > 0 && this.activeRequests < this.maxConcurrent) {
|
|
549
874
|
const item = this.requestQueue.shift();
|
|
550
875
|
if (!item) continue;
|
|
876
|
+
this.activeRequests++;
|
|
877
|
+
const timing = this.requestTimings.get(item.id);
|
|
878
|
+
if (timing) {
|
|
879
|
+
timing.sentAt = Date.now();
|
|
880
|
+
}
|
|
551
881
|
try {
|
|
552
|
-
|
|
553
|
-
|
|
882
|
+
if (item.transferables && item.transferables.length > 0) {
|
|
883
|
+
this.worker.postMessage(item.message, item.transferables);
|
|
884
|
+
} else {
|
|
885
|
+
this.worker.postMessage(item.message);
|
|
886
|
+
}
|
|
554
887
|
} catch (error) {
|
|
888
|
+
this.activeRequests--;
|
|
555
889
|
clearTimeout(item.timeout);
|
|
556
890
|
item.reject(error instanceof Error ? error : new Error(String(error)));
|
|
891
|
+
this.processQueue();
|
|
557
892
|
}
|
|
558
893
|
}
|
|
559
|
-
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Called when a request completes (success or error)
|
|
897
|
+
* Decrements active count and processes next items in queue
|
|
898
|
+
*/
|
|
899
|
+
onRequestComplete() {
|
|
900
|
+
this.activeRequests--;
|
|
901
|
+
this.processQueue();
|
|
560
902
|
}
|
|
561
903
|
/**
|
|
562
904
|
* Cleanup - terminate worker
|
|
@@ -564,6 +906,8 @@ var FetchGuardClient = class {
|
|
|
564
906
|
destroy() {
|
|
565
907
|
this.worker.terminate();
|
|
566
908
|
this.pendingRequests.clear();
|
|
909
|
+
this.requestUrls.clear();
|
|
910
|
+
this.requestTimings.clear();
|
|
567
911
|
for (const item of this.requestQueue) {
|
|
568
912
|
clearTimeout(item.timeout);
|
|
569
913
|
item.reject(new Error("Client destroyed"));
|
|
@@ -619,7 +963,7 @@ function createProvider(config) {
|
|
|
619
963
|
const response = await config.strategy.refresh(currentRefreshToken);
|
|
620
964
|
if (!response.ok) {
|
|
621
965
|
const body = await response.text().catch(() => "");
|
|
622
|
-
return err2(AuthErrors.TokenRefreshFailed(), { body
|
|
966
|
+
return err2(AuthErrors.TokenRefreshFailed(), { params: { body, status: response.status } });
|
|
623
967
|
}
|
|
624
968
|
const tokenInfo = await config.parser.parse(response);
|
|
625
969
|
if (!tokenInfo.token) {
|
|
@@ -638,7 +982,7 @@ function createProvider(config) {
|
|
|
638
982
|
const response = await config.strategy.login(payload, url);
|
|
639
983
|
if (!response.ok) {
|
|
640
984
|
const body = await response.text().catch(() => "");
|
|
641
|
-
return err2(AuthErrors.LoginFailed(), { body
|
|
985
|
+
return err2(AuthErrors.LoginFailed(), { params: { body, status: response.status } });
|
|
642
986
|
}
|
|
643
987
|
const tokenInfo = await config.parser.parse(response);
|
|
644
988
|
if (!tokenInfo.token) {
|
|
@@ -657,7 +1001,7 @@ function createProvider(config) {
|
|
|
657
1001
|
const response = await config.strategy.logout(payload);
|
|
658
1002
|
if (!response.ok) {
|
|
659
1003
|
const body = await response.text().catch(() => "");
|
|
660
|
-
return err2(AuthErrors.LogoutFailed(), { body
|
|
1004
|
+
return err2(AuthErrors.LogoutFailed(), { params: { body, status: response.status } });
|
|
661
1005
|
}
|
|
662
1006
|
if (config.refreshStorage) {
|
|
663
1007
|
await config.refreshStorage.set(null);
|
|
@@ -672,6 +1016,37 @@ function createProvider(config) {
|
|
|
672
1016
|
} catch (error) {
|
|
673
1017
|
return err2(RequestErrors.NetworkError({ message: String(error) }));
|
|
674
1018
|
}
|
|
1019
|
+
},
|
|
1020
|
+
async exchangeToken(accessToken, url, options = {}) {
|
|
1021
|
+
const { method = "POST", payload } = options;
|
|
1022
|
+
if (!accessToken) {
|
|
1023
|
+
return err2(AuthErrors.NotAuthenticated());
|
|
1024
|
+
}
|
|
1025
|
+
try {
|
|
1026
|
+
const response = await fetch(url, {
|
|
1027
|
+
method,
|
|
1028
|
+
headers: {
|
|
1029
|
+
"Content-Type": "application/json",
|
|
1030
|
+
"Authorization": `Bearer ${accessToken}`
|
|
1031
|
+
},
|
|
1032
|
+
body: payload ? JSON.stringify(payload) : void 0,
|
|
1033
|
+
credentials: "include"
|
|
1034
|
+
});
|
|
1035
|
+
if (!response.ok) {
|
|
1036
|
+
const body = await response.text().catch(() => "");
|
|
1037
|
+
return err2(AuthErrors.TokenExchangeFailed(), { params: { body, status: response.status } });
|
|
1038
|
+
}
|
|
1039
|
+
const tokenInfo = await config.parser.parse(response);
|
|
1040
|
+
if (!tokenInfo.token) {
|
|
1041
|
+
return err2(AuthErrors.TokenExchangeFailed({ message: "No access token in response" }));
|
|
1042
|
+
}
|
|
1043
|
+
if (config.refreshStorage && tokenInfo.refreshToken) {
|
|
1044
|
+
await config.refreshStorage.set(tokenInfo.refreshToken);
|
|
1045
|
+
}
|
|
1046
|
+
return ok2(tokenInfo);
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
return err2(RequestErrors.NetworkError({ message: String(error) }));
|
|
1049
|
+
}
|
|
675
1050
|
}
|
|
676
1051
|
};
|
|
677
1052
|
if (config.customMethods) {
|
|
@@ -684,7 +1059,13 @@ function createProvider(config) {
|
|
|
684
1059
|
}
|
|
685
1060
|
|
|
686
1061
|
// src/provider/storage/indexeddb.ts
|
|
687
|
-
function createIndexedDBStorage(
|
|
1062
|
+
function createIndexedDBStorage(options = "FetchGuardDB", legacyRefreshTokenKey) {
|
|
1063
|
+
const config = typeof options === "string" ? { dbName: options, refreshTokenKey: legacyRefreshTokenKey ?? "refreshToken", onError: void 0 } : {
|
|
1064
|
+
dbName: options.dbName ?? "FetchGuardDB",
|
|
1065
|
+
refreshTokenKey: options.refreshTokenKey ?? "refreshToken",
|
|
1066
|
+
onError: options.onError
|
|
1067
|
+
};
|
|
1068
|
+
const { dbName, refreshTokenKey, onError } = config;
|
|
688
1069
|
const storeName = "tokens";
|
|
689
1070
|
const openDB = () => {
|
|
690
1071
|
return new Promise((resolve, reject) => {
|
|
@@ -715,7 +1096,7 @@ function createIndexedDBStorage(dbName = "FetchGuardDB", refreshTokenKey = "refr
|
|
|
715
1096
|
const result = await promisifyRequest(store.get(refreshTokenKey));
|
|
716
1097
|
return result?.value || null;
|
|
717
1098
|
} catch (error) {
|
|
718
|
-
|
|
1099
|
+
onError?.(error, "get");
|
|
719
1100
|
return null;
|
|
720
1101
|
}
|
|
721
1102
|
},
|
|
@@ -730,7 +1111,7 @@ function createIndexedDBStorage(dbName = "FetchGuardDB", refreshTokenKey = "refr
|
|
|
730
1111
|
await promisifyRequest(store.delete(refreshTokenKey));
|
|
731
1112
|
}
|
|
732
1113
|
} catch (error) {
|
|
733
|
-
|
|
1114
|
+
onError?.(error, token ? "set" : "delete");
|
|
734
1115
|
}
|
|
735
1116
|
}
|
|
736
1117
|
};
|
|
@@ -891,9 +1272,76 @@ function isBinaryContentType(contentType) {
|
|
|
891
1272
|
if (normalized.includes("html")) return false;
|
|
892
1273
|
return true;
|
|
893
1274
|
}
|
|
1275
|
+
|
|
1276
|
+
// src/helpers.ts
|
|
1277
|
+
function isNetworkError(result) {
|
|
1278
|
+
return !result.ok;
|
|
1279
|
+
}
|
|
1280
|
+
function isSuccess(result) {
|
|
1281
|
+
return result.ok && result.data.status >= 200 && result.data.status < 300;
|
|
1282
|
+
}
|
|
1283
|
+
function isClientError(result) {
|
|
1284
|
+
return result.ok && result.data.status >= 400 && result.data.status < 500;
|
|
1285
|
+
}
|
|
1286
|
+
function isServerError(result) {
|
|
1287
|
+
return result.ok && result.data.status >= 500;
|
|
1288
|
+
}
|
|
1289
|
+
function parseJson(result) {
|
|
1290
|
+
if (!result.ok) return null;
|
|
1291
|
+
try {
|
|
1292
|
+
return JSON.parse(result.data.body);
|
|
1293
|
+
} catch {
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
function getErrorMessage(result) {
|
|
1298
|
+
if (result.ok) {
|
|
1299
|
+
try {
|
|
1300
|
+
const body = JSON.parse(result.data.body);
|
|
1301
|
+
return body.message || body.error || `HTTP ${result.data.status}`;
|
|
1302
|
+
} catch {
|
|
1303
|
+
return `HTTP ${result.data.status}`;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
return result.errors[0]?.message || "Unknown error";
|
|
1307
|
+
}
|
|
1308
|
+
function getErrorBody(result) {
|
|
1309
|
+
if (!result.ok) return null;
|
|
1310
|
+
if (result.data.status >= 400) {
|
|
1311
|
+
try {
|
|
1312
|
+
return JSON.parse(result.data.body);
|
|
1313
|
+
} catch {
|
|
1314
|
+
return null;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
return null;
|
|
1318
|
+
}
|
|
1319
|
+
function getStatus(result) {
|
|
1320
|
+
return result.ok ? result.data.status : null;
|
|
1321
|
+
}
|
|
1322
|
+
function hasStatus(result, status) {
|
|
1323
|
+
return result.ok && result.data.status === status;
|
|
1324
|
+
}
|
|
1325
|
+
function matchResult(result, handlers) {
|
|
1326
|
+
if (!result.ok) {
|
|
1327
|
+
return handlers.networkError?.(result.errors);
|
|
1328
|
+
}
|
|
1329
|
+
const status = result.data.status;
|
|
1330
|
+
if (status >= 200 && status < 300) {
|
|
1331
|
+
return handlers.success?.(result.data);
|
|
1332
|
+
}
|
|
1333
|
+
if (status >= 400 && status < 500) {
|
|
1334
|
+
return handlers.clientError?.(result.data);
|
|
1335
|
+
}
|
|
1336
|
+
if (status >= 500) {
|
|
1337
|
+
return handlers.serverError?.(result.data);
|
|
1338
|
+
}
|
|
1339
|
+
return void 0;
|
|
1340
|
+
}
|
|
894
1341
|
export {
|
|
895
1342
|
AuthErrors,
|
|
896
1343
|
DomainErrors,
|
|
1344
|
+
ERROR_CODES,
|
|
897
1345
|
FetchGuardClient,
|
|
898
1346
|
GeneralErrors,
|
|
899
1347
|
InitErrors,
|
|
@@ -913,12 +1361,22 @@ export {
|
|
|
913
1361
|
createIndexedDBStorage,
|
|
914
1362
|
createProvider,
|
|
915
1363
|
deserializeFormData,
|
|
1364
|
+
getErrorBody,
|
|
1365
|
+
getErrorMessage,
|
|
916
1366
|
getProvider,
|
|
1367
|
+
getStatus,
|
|
917
1368
|
hasProvider,
|
|
1369
|
+
hasStatus,
|
|
918
1370
|
isBinaryContentType,
|
|
1371
|
+
isClientError,
|
|
919
1372
|
isFormData,
|
|
1373
|
+
isNetworkError,
|
|
920
1374
|
isSerializedFormData,
|
|
1375
|
+
isServerError,
|
|
1376
|
+
isSuccess,
|
|
921
1377
|
listProviders,
|
|
1378
|
+
matchResult,
|
|
1379
|
+
parseJson,
|
|
922
1380
|
registerProvider,
|
|
923
1381
|
serializeFormData,
|
|
924
1382
|
unregisterProvider
|