fetchguard 1.0.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/LICENSE +21 -0
- package/README.md +429 -0
- package/dist/index.d.ts +551 -0
- package/dist/index.js +706 -0
- package/dist/index.js.map +1 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.js +587 -0
- package/dist/worker.js.map +1 -0
- package/package.json +69 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import { fromJSON, ok, err } from "ts-micro-result";
|
|
3
|
+
|
|
4
|
+
// src/messages.ts
|
|
5
|
+
var MSG = Object.freeze({
|
|
6
|
+
...Object.fromEntries(
|
|
7
|
+
Object.keys({}).map((k) => [k, k])
|
|
8
|
+
),
|
|
9
|
+
...Object.fromEntries(
|
|
10
|
+
Object.keys({}).map((k) => [k, k])
|
|
11
|
+
)
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// src/constants.ts
|
|
15
|
+
var DEFAULT_REFRESH_EARLY_MS = 6e4;
|
|
16
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
17
|
+
var DEFAULT_RETRY_COUNT = 3;
|
|
18
|
+
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
19
|
+
|
|
20
|
+
// src/errors.ts
|
|
21
|
+
import { defineError, defineErrorAdvanced } from "ts-micro-result";
|
|
22
|
+
var GeneralErrors = {
|
|
23
|
+
Unexpected: defineError("UNEXPECTED", "Unexpected error", 500),
|
|
24
|
+
UnknownMessage: defineError("UNKNOWN_MESSAGE", "Unknown message type", 400),
|
|
25
|
+
ResultParse: defineError("RESULT_PARSE_ERROR", "Failed to parse result", 500)
|
|
26
|
+
};
|
|
27
|
+
var InitErrors = {
|
|
28
|
+
NotInitialized: defineError("INIT_ERROR", "Worker not initialized", 500),
|
|
29
|
+
ProviderInitFailed: defineError("PROVIDER_INIT_FAILED", "Failed to initialize provider", 500),
|
|
30
|
+
InitFailed: defineError("INIT_FAILED", "Initialization failed", 500)
|
|
31
|
+
};
|
|
32
|
+
var AuthErrors = {
|
|
33
|
+
TokenRefreshFailed: defineError("TOKEN_REFRESH_FAILED", "Token refresh failed", 401),
|
|
34
|
+
LoginFailed: defineError("LOGIN_FAILED", "Login failed", 401),
|
|
35
|
+
LogoutFailed: defineError("LOGOUT_FAILED", "Logout failed", 500),
|
|
36
|
+
NotAuthenticated: defineError("NOT_AUTHENTICATED", "User is not authenticated", 401)
|
|
37
|
+
};
|
|
38
|
+
var DomainErrors = {
|
|
39
|
+
NotAllowed: defineErrorAdvanced("DOMAIN_NOT_ALLOWED", "Domain not allowed: {url}", 403)
|
|
40
|
+
};
|
|
41
|
+
var NetworkErrors = {
|
|
42
|
+
NetworkError: defineError("NETWORK_ERROR", "Network error", 500),
|
|
43
|
+
HttpError: defineError("HTTP_ERROR", "HTTP error", 500),
|
|
44
|
+
FetchError: defineError("FETCH_ERROR", "Fetch error", 500)
|
|
45
|
+
};
|
|
46
|
+
var RequestErrors = {
|
|
47
|
+
Cancelled: defineError("REQUEST_CANCELLED", "Request was cancelled", 499),
|
|
48
|
+
Timeout: defineError("REQUEST_TIMEOUT", "Request timeout", 408)
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/client.ts
|
|
52
|
+
var FetchGuardClient = class {
|
|
53
|
+
worker;
|
|
54
|
+
messageId = 0;
|
|
55
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
56
|
+
authListeners = /* @__PURE__ */ new Set();
|
|
57
|
+
requestQueue = [];
|
|
58
|
+
isProcessingQueue = false;
|
|
59
|
+
queueTimeout = 3e4;
|
|
60
|
+
// 30 seconds
|
|
61
|
+
setupResolve;
|
|
62
|
+
setupReject;
|
|
63
|
+
constructor(options) {
|
|
64
|
+
this.worker = new Worker(new URL("./worker.js", import.meta.url), {
|
|
65
|
+
type: "module"
|
|
66
|
+
});
|
|
67
|
+
this.worker.onmessage = this.handleWorkerMessage.bind(this);
|
|
68
|
+
this.worker.onerror = this.handleWorkerError.bind(this);
|
|
69
|
+
this.initializeWorker(options);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Initialize worker with config and provider
|
|
73
|
+
*/
|
|
74
|
+
async initializeWorker(options) {
|
|
75
|
+
const config = {
|
|
76
|
+
allowedDomains: options.allowedDomains || [],
|
|
77
|
+
debug: options.debug || false,
|
|
78
|
+
refreshEarlyMs: options.refreshEarlyMs ?? DEFAULT_REFRESH_EARLY_MS,
|
|
79
|
+
defaultTimeoutMs: options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
80
|
+
retryCount: options.retryCount ?? DEFAULT_RETRY_COUNT,
|
|
81
|
+
retryDelayMs: options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS
|
|
82
|
+
};
|
|
83
|
+
let providerConfig = null;
|
|
84
|
+
if (typeof options.provider === "string") {
|
|
85
|
+
providerConfig = options.provider;
|
|
86
|
+
} else if ("type" in options.provider && options.provider.type) {
|
|
87
|
+
providerConfig = options.provider;
|
|
88
|
+
} else {
|
|
89
|
+
throw new Error(
|
|
90
|
+
'Direct TokenProvider instance is not supported. Use ProviderPresetConfig instead:\n { type: "cookie-auth", refreshUrl: "...", loginUrl: "...", logoutUrl: "..." }\nOr for custom providers, register in worker code and use string name.'
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const message = {
|
|
94
|
+
id: this.generateMessageId(),
|
|
95
|
+
type: MSG.SETUP,
|
|
96
|
+
payload: {
|
|
97
|
+
config,
|
|
98
|
+
providerConfig
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
this.setupResolve = resolve;
|
|
103
|
+
this.setupReject = reject;
|
|
104
|
+
this.worker.postMessage(message);
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
if (this.setupReject) {
|
|
107
|
+
this.setupReject(new Error("Worker setup timeout"));
|
|
108
|
+
this.setupResolve = void 0;
|
|
109
|
+
this.setupReject = void 0;
|
|
110
|
+
}
|
|
111
|
+
}, 1e4);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Handle worker messages
|
|
116
|
+
*/
|
|
117
|
+
handleWorkerMessage(event) {
|
|
118
|
+
const { id, type, payload } = event.data;
|
|
119
|
+
if (type === MSG.FETCH_RESULT || type === MSG.FETCH_ERROR) {
|
|
120
|
+
const request = this.pendingRequests.get(id);
|
|
121
|
+
if (!request) return;
|
|
122
|
+
this.pendingRequests.delete(id);
|
|
123
|
+
if (type === MSG.FETCH_RESULT) {
|
|
124
|
+
const status = payload?.status ?? 200;
|
|
125
|
+
const headers = payload?.headers ?? {};
|
|
126
|
+
const body = String(payload?.body ?? "");
|
|
127
|
+
let data;
|
|
128
|
+
try {
|
|
129
|
+
data = JSON.parse(body);
|
|
130
|
+
} catch {
|
|
131
|
+
data = body;
|
|
132
|
+
}
|
|
133
|
+
request.resolve(ok({ data, status, headers }));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (type === MSG.FETCH_ERROR) {
|
|
137
|
+
const status = typeof payload?.status === "number" ? payload.status : void 0;
|
|
138
|
+
if (typeof status === "number") {
|
|
139
|
+
request.resolve(err(NetworkErrors.HttpError({ message: String(payload?.error || "HTTP error") }), void 0, status));
|
|
140
|
+
} else {
|
|
141
|
+
request.resolve(err(NetworkErrors.NetworkError({ message: String(payload?.error || "Network error") })));
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (type === MSG.RESULT) {
|
|
147
|
+
const request = this.pendingRequests.get(id);
|
|
148
|
+
if (!request) return;
|
|
149
|
+
this.pendingRequests.delete(id);
|
|
150
|
+
if (payload && payload.result) {
|
|
151
|
+
try {
|
|
152
|
+
const result = fromJSON(JSON.stringify(payload.result));
|
|
153
|
+
request.resolve(result);
|
|
154
|
+
} catch (e) {
|
|
155
|
+
request.resolve(err(GeneralErrors.ResultParse({ message: String(e) })));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (type === MSG.READY) {
|
|
161
|
+
if (this.setupResolve) {
|
|
162
|
+
this.setupResolve();
|
|
163
|
+
this.setupResolve = void 0;
|
|
164
|
+
this.setupReject = void 0;
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (type === MSG.PONG) {
|
|
169
|
+
const request = this.pendingRequests.get(id);
|
|
170
|
+
if (request) {
|
|
171
|
+
this.pendingRequests.delete(id);
|
|
172
|
+
request.resolve(ok({ timestamp: payload?.timestamp }));
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (type === MSG.AUTH_STATE_CHANGED) {
|
|
177
|
+
for (const cb of this.authListeners) cb(payload);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Handle worker errors
|
|
183
|
+
*/
|
|
184
|
+
handleWorkerError(error) {
|
|
185
|
+
console.error("Worker error:", error);
|
|
186
|
+
for (const [id, request] of this.pendingRequests) {
|
|
187
|
+
request.reject(new Error(`Worker error: ${error.message}`));
|
|
188
|
+
}
|
|
189
|
+
this.pendingRequests.clear();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Generate unique message ID
|
|
193
|
+
*/
|
|
194
|
+
generateMessageId() {
|
|
195
|
+
return `msg_${++this.messageId}_${Date.now()}`;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Make API request
|
|
199
|
+
*/
|
|
200
|
+
async fetch(url, options = {}) {
|
|
201
|
+
const { result } = this.fetchWithId(url, options);
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Fetch with id for external cancellation
|
|
206
|
+
* Returns { id, result, cancel }
|
|
207
|
+
* Now uses queue system for sequential processing
|
|
208
|
+
*/
|
|
209
|
+
fetchWithId(url, options = {}) {
|
|
210
|
+
const id = this.generateMessageId();
|
|
211
|
+
const message = { id, type: MSG.FETCH, payload: { url, options } };
|
|
212
|
+
const result = new Promise((resolve, reject) => {
|
|
213
|
+
this.pendingRequests.set(id, {
|
|
214
|
+
resolve: (response) => resolve(response),
|
|
215
|
+
reject: (error) => reject(error)
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
this.sendMessageQueued(message, 3e4).catch((error) => {
|
|
219
|
+
const request = this.pendingRequests.get(id);
|
|
220
|
+
if (request) {
|
|
221
|
+
this.pendingRequests.delete(id);
|
|
222
|
+
request.reject(error);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
const cancel = () => this.cancel(id);
|
|
226
|
+
return { id, result, cancel };
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Cancel a pending request by ID
|
|
230
|
+
*/
|
|
231
|
+
cancel(id) {
|
|
232
|
+
const request = this.pendingRequests.get(id);
|
|
233
|
+
if (request) {
|
|
234
|
+
this.pendingRequests.delete(id);
|
|
235
|
+
this.worker.postMessage({ id, type: MSG.CANCEL });
|
|
236
|
+
request.reject(new Error("Request cancelled"));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Convenience methods
|
|
241
|
+
*/
|
|
242
|
+
async get(url, options = {}) {
|
|
243
|
+
return this.fetch(url, { ...options, method: "GET" });
|
|
244
|
+
}
|
|
245
|
+
async post(url, body, options = {}) {
|
|
246
|
+
const headers = new Headers(options.headers);
|
|
247
|
+
if (body && !headers.has("Content-Type")) {
|
|
248
|
+
headers.set("Content-Type", "application/json");
|
|
249
|
+
}
|
|
250
|
+
return this.fetch(url, {
|
|
251
|
+
...options,
|
|
252
|
+
headers,
|
|
253
|
+
method: "POST",
|
|
254
|
+
body: body ? JSON.stringify(body) : void 0
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
async put(url, body, options = {}) {
|
|
258
|
+
const headers = new Headers(options.headers);
|
|
259
|
+
if (body && !headers.has("Content-Type")) {
|
|
260
|
+
headers.set("Content-Type", "application/json");
|
|
261
|
+
}
|
|
262
|
+
return this.fetch(url, {
|
|
263
|
+
...options,
|
|
264
|
+
headers,
|
|
265
|
+
method: "PUT",
|
|
266
|
+
body: body ? JSON.stringify(body) : void 0
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
async delete(url, options = {}) {
|
|
270
|
+
return this.fetch(url, { ...options, method: "DELETE" });
|
|
271
|
+
}
|
|
272
|
+
async patch(url, body, options = {}) {
|
|
273
|
+
const headers = new Headers(options.headers);
|
|
274
|
+
if (body && !headers.has("Content-Type")) {
|
|
275
|
+
headers.set("Content-Type", "application/json");
|
|
276
|
+
}
|
|
277
|
+
return this.fetch(url, {
|
|
278
|
+
...options,
|
|
279
|
+
headers,
|
|
280
|
+
method: "PATCH",
|
|
281
|
+
body: body ? JSON.stringify(body) : void 0
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Generic method to call any auth method on provider
|
|
286
|
+
* @param method - Method name (login, logout, loginWithPhone, etc.)
|
|
287
|
+
* @param args - Arguments to pass to the method
|
|
288
|
+
* @returns Result with success (auth state changes emitted via AUTH_STATE_CHANGED event)
|
|
289
|
+
*/
|
|
290
|
+
async call(method, ...args) {
|
|
291
|
+
const id = this.generateMessageId();
|
|
292
|
+
const message = { id, type: MSG.AUTH_CALL, payload: { method, args } };
|
|
293
|
+
return new Promise((resolve, reject) => {
|
|
294
|
+
this.pendingRequests.set(id, {
|
|
295
|
+
resolve: (r) => resolve(r),
|
|
296
|
+
reject: (e) => reject(e)
|
|
297
|
+
});
|
|
298
|
+
this.sendMessageQueued(message, 15e3).catch((error) => {
|
|
299
|
+
const request = this.pendingRequests.get(id);
|
|
300
|
+
if (request) {
|
|
301
|
+
this.pendingRequests.delete(id);
|
|
302
|
+
request.reject(error);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Convenience wrapper for login
|
|
309
|
+
* Note: Auth state changes are emitted via onAuthStateChanged event
|
|
310
|
+
*/
|
|
311
|
+
async login(payload) {
|
|
312
|
+
return this.call("login", payload);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Convenience wrapper for logout
|
|
316
|
+
* Note: Auth state changes are emitted via onAuthStateChanged event
|
|
317
|
+
*/
|
|
318
|
+
async logout(payload) {
|
|
319
|
+
return this.call("logout", payload);
|
|
320
|
+
}
|
|
321
|
+
onAuthStateChanged(cb) {
|
|
322
|
+
this.authListeners.add(cb);
|
|
323
|
+
return () => this.authListeners.delete(cb);
|
|
324
|
+
}
|
|
325
|
+
/** Send PING and await PONG */
|
|
326
|
+
async ping() {
|
|
327
|
+
const id = this.generateMessageId();
|
|
328
|
+
const message = { id, type: MSG.PING, payload: { timestamp: Date.now() } };
|
|
329
|
+
return new Promise((resolve, reject) => {
|
|
330
|
+
this.pendingRequests.set(id, {
|
|
331
|
+
resolve: (r) => resolve(r),
|
|
332
|
+
reject: (e) => reject(e)
|
|
333
|
+
});
|
|
334
|
+
this.sendMessageQueued(message, 5e3).catch((error) => {
|
|
335
|
+
const request = this.pendingRequests.get(id);
|
|
336
|
+
if (request) {
|
|
337
|
+
this.pendingRequests.delete(id);
|
|
338
|
+
request.reject(error);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Send message through queue system
|
|
345
|
+
* All messages go through queue for sequential processing
|
|
346
|
+
*/
|
|
347
|
+
sendMessageQueued(message, timeoutMs = this.queueTimeout) {
|
|
348
|
+
return new Promise((resolve, reject) => {
|
|
349
|
+
const timeout = setTimeout(() => {
|
|
350
|
+
const index = this.requestQueue.findIndex((item) => item.id === message.id);
|
|
351
|
+
if (index !== -1) {
|
|
352
|
+
this.requestQueue.splice(index, 1);
|
|
353
|
+
}
|
|
354
|
+
this.pendingRequests.delete(message.id);
|
|
355
|
+
reject(new Error("Request timeout"));
|
|
356
|
+
}, timeoutMs);
|
|
357
|
+
const queueItem = {
|
|
358
|
+
id: message.id,
|
|
359
|
+
message,
|
|
360
|
+
resolve,
|
|
361
|
+
reject,
|
|
362
|
+
timeout
|
|
363
|
+
};
|
|
364
|
+
this.requestQueue.push(queueItem);
|
|
365
|
+
this.processQueue();
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Process message queue sequentially
|
|
370
|
+
* Benefits:
|
|
371
|
+
* - Sequential processing prevents worker overload
|
|
372
|
+
* - Better error isolation (one failure doesn't affect others)
|
|
373
|
+
* - 50ms delay between requests for backpressure
|
|
374
|
+
*/
|
|
375
|
+
async processQueue() {
|
|
376
|
+
if (this.isProcessingQueue || this.requestQueue.length === 0) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
this.isProcessingQueue = true;
|
|
380
|
+
while (this.requestQueue.length > 0) {
|
|
381
|
+
const item = this.requestQueue.shift();
|
|
382
|
+
if (!item) continue;
|
|
383
|
+
try {
|
|
384
|
+
this.worker.postMessage(item.message);
|
|
385
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
386
|
+
} catch (error) {
|
|
387
|
+
clearTimeout(item.timeout);
|
|
388
|
+
item.reject(error instanceof Error ? error : new Error(String(error)));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
this.isProcessingQueue = false;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Cleanup - terminate worker
|
|
395
|
+
*/
|
|
396
|
+
destroy() {
|
|
397
|
+
this.worker.terminate();
|
|
398
|
+
this.pendingRequests.clear();
|
|
399
|
+
for (const item of this.requestQueue) {
|
|
400
|
+
clearTimeout(item.timeout);
|
|
401
|
+
item.reject(new Error("Client destroyed"));
|
|
402
|
+
}
|
|
403
|
+
this.requestQueue = [];
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
function createClient(options) {
|
|
407
|
+
return new FetchGuardClient(options);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// src/utils/registry.ts
|
|
411
|
+
var registry = /* @__PURE__ */ new Map();
|
|
412
|
+
function registerProvider(name, provider) {
|
|
413
|
+
if (typeof name !== "string" || !name.trim()) {
|
|
414
|
+
throw new Error("Provider name must be a non-empty string");
|
|
415
|
+
}
|
|
416
|
+
if (!provider || typeof provider.refreshToken !== "function") {
|
|
417
|
+
throw new Error("Provider must implement TokenProvider interface");
|
|
418
|
+
}
|
|
419
|
+
registry.set(name, provider);
|
|
420
|
+
}
|
|
421
|
+
function getProvider(name) {
|
|
422
|
+
const provider = registry.get(name);
|
|
423
|
+
if (!provider) {
|
|
424
|
+
throw new Error(`Provider '${name}' not found. Available providers: ${Array.from(registry.keys()).join(", ")}`);
|
|
425
|
+
}
|
|
426
|
+
return provider;
|
|
427
|
+
}
|
|
428
|
+
function hasProvider(name) {
|
|
429
|
+
return registry.has(name);
|
|
430
|
+
}
|
|
431
|
+
function listProviders() {
|
|
432
|
+
return Array.from(registry.keys());
|
|
433
|
+
}
|
|
434
|
+
function unregisterProvider(name) {
|
|
435
|
+
return registry.delete(name);
|
|
436
|
+
}
|
|
437
|
+
function clearProviders() {
|
|
438
|
+
registry.clear();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// src/provider/create-provider.ts
|
|
442
|
+
import { ok as ok2, err as err2 } from "ts-micro-result";
|
|
443
|
+
function createProvider(config) {
|
|
444
|
+
const baseProvider = {
|
|
445
|
+
async refreshToken(refreshToken) {
|
|
446
|
+
let currentRefreshToken = refreshToken;
|
|
447
|
+
if (currentRefreshToken === null && config.refreshStorage) {
|
|
448
|
+
currentRefreshToken = await config.refreshStorage.get();
|
|
449
|
+
}
|
|
450
|
+
try {
|
|
451
|
+
const response = await config.strategy.refresh(currentRefreshToken);
|
|
452
|
+
if (!response.ok) {
|
|
453
|
+
return err2(AuthErrors.TokenRefreshFailed({ message: `HTTP ${response.status}` }));
|
|
454
|
+
}
|
|
455
|
+
const tokenInfo = await config.parser.parse(response);
|
|
456
|
+
if (!tokenInfo.token) {
|
|
457
|
+
return err2(AuthErrors.TokenRefreshFailed({ message: "No access token in response" }));
|
|
458
|
+
}
|
|
459
|
+
if (config.refreshStorage && tokenInfo.refreshToken) {
|
|
460
|
+
await config.refreshStorage.set(tokenInfo.refreshToken);
|
|
461
|
+
}
|
|
462
|
+
return ok2(tokenInfo);
|
|
463
|
+
} catch (error) {
|
|
464
|
+
return err2(NetworkErrors.NetworkError({ message: String(error) }));
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
async login(payload) {
|
|
468
|
+
try {
|
|
469
|
+
const response = await config.strategy.login(payload);
|
|
470
|
+
if (!response.ok) {
|
|
471
|
+
return err2(AuthErrors.LoginFailed({ message: `HTTP ${response.status}` }));
|
|
472
|
+
}
|
|
473
|
+
const tokenInfo = await config.parser.parse(response);
|
|
474
|
+
if (!tokenInfo.token) {
|
|
475
|
+
return err2(AuthErrors.LoginFailed({ message: "No access token in response" }));
|
|
476
|
+
}
|
|
477
|
+
if (config.refreshStorage && tokenInfo.refreshToken) {
|
|
478
|
+
await config.refreshStorage.set(tokenInfo.refreshToken);
|
|
479
|
+
}
|
|
480
|
+
return ok2(tokenInfo);
|
|
481
|
+
} catch (error) {
|
|
482
|
+
return err2(NetworkErrors.NetworkError({ message: String(error) }));
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
async logout(payload) {
|
|
486
|
+
try {
|
|
487
|
+
const response = await config.strategy.logout(payload);
|
|
488
|
+
if (!response.ok) {
|
|
489
|
+
return err2(AuthErrors.LogoutFailed({ message: `HTTP ${response.status}` }));
|
|
490
|
+
}
|
|
491
|
+
if (config.refreshStorage) {
|
|
492
|
+
await config.refreshStorage.set(null);
|
|
493
|
+
}
|
|
494
|
+
return ok2({
|
|
495
|
+
token: "",
|
|
496
|
+
refreshToken: void 0,
|
|
497
|
+
expiresAt: void 0,
|
|
498
|
+
user: void 0
|
|
499
|
+
});
|
|
500
|
+
} catch (error) {
|
|
501
|
+
return err2(NetworkErrors.NetworkError({ message: String(error) }));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
if (config.customMethods) {
|
|
506
|
+
return {
|
|
507
|
+
...baseProvider,
|
|
508
|
+
...config.customMethods
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
return baseProvider;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// src/provider/storage/indexeddb.ts
|
|
515
|
+
function createIndexedDBStorage(dbName = "FetchGuardDB", refreshTokenKey = "refreshToken") {
|
|
516
|
+
const storeName = "tokens";
|
|
517
|
+
const openDB = () => {
|
|
518
|
+
return new Promise((resolve, reject) => {
|
|
519
|
+
const request = indexedDB.open(dbName, 1);
|
|
520
|
+
request.onerror = () => reject(request.error);
|
|
521
|
+
request.onsuccess = () => resolve(request.result);
|
|
522
|
+
request.onupgradeneeded = (event) => {
|
|
523
|
+
const db = event.target.result;
|
|
524
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
525
|
+
const store = db.createObjectStore(storeName, { keyPath: "key" });
|
|
526
|
+
store.createIndex("timestamp", "timestamp", { unique: false });
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
});
|
|
530
|
+
};
|
|
531
|
+
const promisifyRequest = (request) => {
|
|
532
|
+
return new Promise((resolve, reject) => {
|
|
533
|
+
request.onsuccess = () => resolve(request.result);
|
|
534
|
+
request.onerror = () => reject(request.error);
|
|
535
|
+
});
|
|
536
|
+
};
|
|
537
|
+
return {
|
|
538
|
+
async get() {
|
|
539
|
+
try {
|
|
540
|
+
const db = await openDB();
|
|
541
|
+
const transaction = db.transaction([storeName], "readonly");
|
|
542
|
+
const store = transaction.objectStore(storeName);
|
|
543
|
+
const result = await promisifyRequest(store.get(refreshTokenKey));
|
|
544
|
+
return result?.value || null;
|
|
545
|
+
} catch (error) {
|
|
546
|
+
console.warn("Failed to get refresh token from IndexedDB:", error);
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
async set(token) {
|
|
551
|
+
try {
|
|
552
|
+
const db = await openDB();
|
|
553
|
+
const transaction = db.transaction([storeName], "readwrite");
|
|
554
|
+
const store = transaction.objectStore(storeName);
|
|
555
|
+
if (token) {
|
|
556
|
+
await promisifyRequest(store.put({ key: refreshTokenKey, value: token, timestamp: Date.now() }));
|
|
557
|
+
} else {
|
|
558
|
+
await promisifyRequest(store.delete(refreshTokenKey));
|
|
559
|
+
}
|
|
560
|
+
} catch (error) {
|
|
561
|
+
console.warn("Failed to save refresh token to IndexedDB:", error);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/provider/parser/body.ts
|
|
568
|
+
var bodyParser = {
|
|
569
|
+
async parse(response) {
|
|
570
|
+
const json = await response.clone().json();
|
|
571
|
+
return {
|
|
572
|
+
token: json.data.accessToken,
|
|
573
|
+
refreshToken: json.data.refreshToken,
|
|
574
|
+
expiresAt: json.data.expiresAt,
|
|
575
|
+
user: json.data.user
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// src/provider/parser/cookie.ts
|
|
581
|
+
var cookieParser = {
|
|
582
|
+
async parse(response) {
|
|
583
|
+
const json = await response.clone().json();
|
|
584
|
+
return {
|
|
585
|
+
token: json.data.accessToken,
|
|
586
|
+
expiresAt: json.data.expiresAt,
|
|
587
|
+
user: json.data.user
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
// src/provider/strategy/cookie.ts
|
|
593
|
+
function createCookieStrategy(config) {
|
|
594
|
+
return {
|
|
595
|
+
async refresh() {
|
|
596
|
+
return fetch(config.refreshUrl, {
|
|
597
|
+
method: "POST",
|
|
598
|
+
headers: { "Content-Type": "application/json" },
|
|
599
|
+
credentials: "include"
|
|
600
|
+
});
|
|
601
|
+
},
|
|
602
|
+
async login(payload) {
|
|
603
|
+
return fetch(config.loginUrl, {
|
|
604
|
+
method: "POST",
|
|
605
|
+
headers: { "Content-Type": "application/json" },
|
|
606
|
+
body: JSON.stringify(payload),
|
|
607
|
+
credentials: "include"
|
|
608
|
+
});
|
|
609
|
+
},
|
|
610
|
+
async logout(payload) {
|
|
611
|
+
return fetch(config.logoutUrl, {
|
|
612
|
+
method: "POST",
|
|
613
|
+
headers: { "Content-Type": "application/json" },
|
|
614
|
+
body: payload ? JSON.stringify(payload) : void 0,
|
|
615
|
+
credentials: "include"
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
var cookieStrategy = createCookieStrategy({
|
|
621
|
+
refreshUrl: "/auth/refresh",
|
|
622
|
+
loginUrl: "/auth/login",
|
|
623
|
+
logoutUrl: "/auth/logout"
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// src/provider/strategy/body.ts
|
|
627
|
+
function createBodyStrategy(config) {
|
|
628
|
+
return {
|
|
629
|
+
async refresh(refreshToken) {
|
|
630
|
+
if (!refreshToken) {
|
|
631
|
+
throw new Error("No refresh token available");
|
|
632
|
+
}
|
|
633
|
+
return fetch(config.refreshUrl, {
|
|
634
|
+
method: "POST",
|
|
635
|
+
headers: { "Content-Type": "application/json" },
|
|
636
|
+
body: JSON.stringify({ refreshToken }),
|
|
637
|
+
credentials: "include"
|
|
638
|
+
});
|
|
639
|
+
},
|
|
640
|
+
async login(payload) {
|
|
641
|
+
return fetch(config.loginUrl, {
|
|
642
|
+
method: "POST",
|
|
643
|
+
headers: { "Content-Type": "application/json" },
|
|
644
|
+
body: JSON.stringify(payload),
|
|
645
|
+
credentials: "include"
|
|
646
|
+
});
|
|
647
|
+
},
|
|
648
|
+
async logout(payload) {
|
|
649
|
+
return fetch(config.logoutUrl, {
|
|
650
|
+
method: "POST",
|
|
651
|
+
headers: { "Content-Type": "application/json" },
|
|
652
|
+
body: payload ? JSON.stringify(payload) : void 0,
|
|
653
|
+
credentials: "include"
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
var bodyStrategy = createBodyStrategy({
|
|
659
|
+
refreshUrl: "/auth/refresh",
|
|
660
|
+
loginUrl: "/auth/login",
|
|
661
|
+
logoutUrl: "/auth/logout"
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// src/provider/presets.ts
|
|
665
|
+
function createCookieProvider(config) {
|
|
666
|
+
return createProvider({
|
|
667
|
+
refreshStorage: void 0,
|
|
668
|
+
parser: cookieParser,
|
|
669
|
+
strategy: createCookieStrategy(config)
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
function createBodyProvider(config) {
|
|
673
|
+
return createProvider({
|
|
674
|
+
refreshStorage: createIndexedDBStorage("FetchGuardDB", config.refreshTokenKey || "refreshToken"),
|
|
675
|
+
parser: bodyParser,
|
|
676
|
+
strategy: createBodyStrategy(config)
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
export {
|
|
680
|
+
AuthErrors,
|
|
681
|
+
DomainErrors,
|
|
682
|
+
FetchGuardClient,
|
|
683
|
+
GeneralErrors,
|
|
684
|
+
InitErrors,
|
|
685
|
+
MSG,
|
|
686
|
+
NetworkErrors,
|
|
687
|
+
RequestErrors,
|
|
688
|
+
bodyParser,
|
|
689
|
+
bodyStrategy,
|
|
690
|
+
clearProviders,
|
|
691
|
+
cookieParser,
|
|
692
|
+
cookieStrategy,
|
|
693
|
+
createBodyProvider,
|
|
694
|
+
createBodyStrategy,
|
|
695
|
+
createClient,
|
|
696
|
+
createCookieProvider,
|
|
697
|
+
createCookieStrategy,
|
|
698
|
+
createIndexedDBStorage,
|
|
699
|
+
createProvider,
|
|
700
|
+
getProvider,
|
|
701
|
+
hasProvider,
|
|
702
|
+
listProviders,
|
|
703
|
+
registerProvider,
|
|
704
|
+
unregisterProvider
|
|
705
|
+
};
|
|
706
|
+
//# sourceMappingURL=index.js.map
|