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.
@@ -0,0 +1,551 @@
1
+ import * as ts_micro_result from 'ts-micro-result';
2
+ import { Result, SerializedResult } from 'ts-micro-result';
3
+ import * as ts_micro_result_factories_errors_advanced from 'ts-micro-result/factories/errors-advanced';
4
+
5
+ /**
6
+ * FetchGuard Business Types
7
+ *
8
+ * Contains domain logic types: Provider, Config, API Response, etc.
9
+ * For message protocol types, see messages.ts
10
+ */
11
+ /**
12
+ * Token info returned from provider
13
+ */
14
+ interface TokenInfo {
15
+ token: string;
16
+ expiresAt?: number;
17
+ refreshToken?: string;
18
+ user?: unknown;
19
+ }
20
+ /**
21
+ * Interface for token provider
22
+ *
23
+ * Provider has 3 required methods:
24
+ * - refreshToken: Refresh access token when expired
25
+ * - login: Login with credentials
26
+ * - logout: Logout (clear tokens)
27
+ *
28
+ * User can add custom auth methods (loginWithPhone, loginWithGoogle, etc.)
29
+ * All custom methods must return Result<TokenInfo> for token retrieval
30
+ */
31
+ interface TokenProvider {
32
+ /**
33
+ * Refresh tokens (required)
34
+ * @param refreshToken - Current refresh token (from worker memory, null if not available)
35
+ * @returns Result<TokenInfo> with new tokens
36
+ */
37
+ refreshToken(refreshToken: string | null): Promise<Result<TokenInfo>>;
38
+ /**
39
+ * Login with credentials (required)
40
+ * @param payload - Login credentials (email/password, etc.)
41
+ * @returns Result<TokenInfo> with tokens
42
+ */
43
+ login(payload: unknown): Promise<Result<TokenInfo>>;
44
+ /**
45
+ * Logout - clear tokens (required)
46
+ * @param payload - Optional logout payload
47
+ * @returns Result<TokenInfo> with all fields reset (token = '', refreshToken = undefined, user = undefined)
48
+ */
49
+ logout(payload?: unknown): Promise<Result<TokenInfo>>;
50
+ /**
51
+ * Custom auth methods (optional)
52
+ * Examples: loginWithPhone, loginWithGoogle, loginWithFacebook, etc.
53
+ * All must return Result<TokenInfo> for token retrieval
54
+ */
55
+ [key: string]: (...args: any[]) => Promise<Result<TokenInfo>>;
56
+ }
57
+ /**
58
+ * Interface for refresh token storage - only stores refresh token
59
+ *
60
+ * Access token is always stored in worker memory.
61
+ * Refresh token storage is OPTIONAL:
62
+ * - If available (IndexedDB): persist refresh token for reuse after reload
63
+ * - If not (undefined): cookie-based auth (httpOnly cookie)
64
+ */
65
+ interface RefreshTokenStorage {
66
+ get(): Promise<string | null>;
67
+ set(token: string | null): Promise<void>;
68
+ }
69
+ /**
70
+ * Interface for token parser - parse token from backend response
71
+ * Parser returns complete TokenInfo (including user data)
72
+ */
73
+ interface TokenParser {
74
+ parse(response: Response): Promise<TokenInfo>;
75
+ }
76
+ /**
77
+ * Interface for auth strategy - defines how to call auth APIs
78
+ *
79
+ * Strategy focuses only on API calls, returns Response
80
+ * Provider handles parsing and storage
81
+ *
82
+ * All methods are required
83
+ */
84
+ interface AuthStrategy {
85
+ /** Refresh access token */
86
+ refresh(refreshToken: string | null): Promise<Response>;
87
+ /** Login with credentials */
88
+ login(payload: unknown): Promise<Response>;
89
+ /** Logout */
90
+ logout(payload?: unknown): Promise<Response>;
91
+ }
92
+ /**
93
+ * Provider preset configuration for built-in auth strategies
94
+ */
95
+ interface ProviderPresetConfig {
96
+ type: 'cookie-auth' | 'body-auth';
97
+ refreshUrl: string;
98
+ loginUrl: string;
99
+ logoutUrl: string;
100
+ refreshTokenKey?: string;
101
+ }
102
+ /**
103
+ * Configuration for FetchGuard client
104
+ */
105
+ interface FetchGuardOptions {
106
+ /**
107
+ * Token provider - 3 options:
108
+ * 1. TokenProvider instance (for custom providers)
109
+ * 2. ProviderPresetConfig object (for built-in presets)
110
+ * 3. string (for registry lookup - advanced usage)
111
+ */
112
+ provider: TokenProvider | ProviderPresetConfig | string;
113
+ /** List of allowed domains (wildcard supported) */
114
+ allowedDomains?: string[];
115
+ /** Debug mode */
116
+ debug?: boolean;
117
+ /** Early refresh time for tokens (ms) */
118
+ refreshEarlyMs?: number;
119
+ /** Default timeout for requests (ms) */
120
+ defaultTimeoutMs?: number;
121
+ /** Default retry count */
122
+ retryCount?: number;
123
+ /** Delay between retries (ms) */
124
+ retryDelayMs?: number;
125
+ }
126
+ /**
127
+ * Internal worker configuration
128
+ */
129
+ interface WorkerConfig {
130
+ allowedDomains: string[];
131
+ debug: boolean;
132
+ refreshEarlyMs: number;
133
+ defaultTimeoutMs: number;
134
+ retryCount: number;
135
+ retryDelayMs: number;
136
+ }
137
+ /**
138
+ * Extended RequestInit with FetchGuard-specific options
139
+ */
140
+ interface FetchGuardRequestInit extends RequestInit {
141
+ /** Whether this request requires authentication. Default: true */
142
+ requiresAuth?: boolean;
143
+ /** Include response headers in result metadata and FETCH_RESULT payload */
144
+ includeHeaders?: boolean;
145
+ }
146
+ /**
147
+ * API response wrapper
148
+ */
149
+ interface ApiResponse<T = unknown> {
150
+ data: T;
151
+ status: number;
152
+ headers: Record<string, string>;
153
+ }
154
+
155
+ /**
156
+ * FetchGuard Client - main interface cho việc gọi API thông qua Web Worker
157
+ */
158
+ declare class FetchGuardClient {
159
+ private worker;
160
+ private messageId;
161
+ private pendingRequests;
162
+ private authListeners;
163
+ private requestQueue;
164
+ private isProcessingQueue;
165
+ private queueTimeout;
166
+ private setupResolve?;
167
+ private setupReject?;
168
+ constructor(options: FetchGuardOptions);
169
+ /**
170
+ * Initialize worker with config and provider
171
+ */
172
+ private initializeWorker;
173
+ /**
174
+ * Handle worker messages
175
+ */
176
+ private handleWorkerMessage;
177
+ /**
178
+ * Handle worker errors
179
+ */
180
+ private handleWorkerError;
181
+ /**
182
+ * Generate unique message ID
183
+ */
184
+ private generateMessageId;
185
+ /**
186
+ * Make API request
187
+ */
188
+ fetch(url: string, options?: FetchGuardRequestInit): Promise<Result<ApiResponse>>;
189
+ /**
190
+ * Fetch with id for external cancellation
191
+ * Returns { id, result, cancel }
192
+ * Now uses queue system for sequential processing
193
+ */
194
+ fetchWithId(url: string, options?: FetchGuardRequestInit): {
195
+ id: string;
196
+ result: Promise<Result<ApiResponse>>;
197
+ cancel: () => void;
198
+ };
199
+ /**
200
+ * Cancel a pending request by ID
201
+ */
202
+ cancel(id: string): void;
203
+ /**
204
+ * Convenience methods
205
+ */
206
+ get(url: string, options?: Omit<FetchGuardRequestInit, 'method' | 'body'>): Promise<Result<ApiResponse>>;
207
+ post(url: string, body?: any, options?: Omit<FetchGuardRequestInit, 'method' | 'body'>): Promise<Result<ApiResponse>>;
208
+ put(url: string, body?: any, options?: Omit<FetchGuardRequestInit, 'method' | 'body'>): Promise<Result<ApiResponse>>;
209
+ delete(url: string, options?: Omit<FetchGuardRequestInit, 'method' | 'body'>): Promise<Result<ApiResponse>>;
210
+ patch(url: string, body?: any, options?: Omit<FetchGuardRequestInit, 'method' | 'body'>): Promise<Result<ApiResponse>>;
211
+ /**
212
+ * Generic method to call any auth method on provider
213
+ * @param method - Method name (login, logout, loginWithPhone, etc.)
214
+ * @param args - Arguments to pass to the method
215
+ * @returns Result with success (auth state changes emitted via AUTH_STATE_CHANGED event)
216
+ */
217
+ call(method: string, ...args: unknown[]): Promise<Result<void>>;
218
+ /**
219
+ * Convenience wrapper for login
220
+ * Note: Auth state changes are emitted via onAuthStateChanged event
221
+ */
222
+ login(payload?: unknown): Promise<Result<void>>;
223
+ /**
224
+ * Convenience wrapper for logout
225
+ * Note: Auth state changes are emitted via onAuthStateChanged event
226
+ */
227
+ logout(payload?: unknown): Promise<Result<void>>;
228
+ onAuthStateChanged(cb: (state: {
229
+ authenticated: boolean;
230
+ expiresAt?: number | null;
231
+ user?: unknown;
232
+ }) => void): () => void;
233
+ /** Send PING and await PONG */
234
+ ping(): Promise<Result<{
235
+ timestamp: number;
236
+ }>>;
237
+ /**
238
+ * Send message through queue system
239
+ * All messages go through queue for sequential processing
240
+ */
241
+ private sendMessageQueued;
242
+ /**
243
+ * Process message queue sequentially
244
+ * Benefits:
245
+ * - Sequential processing prevents worker overload
246
+ * - Better error isolation (one failure doesn't affect others)
247
+ * - 50ms delay between requests for backpressure
248
+ */
249
+ private processQueue;
250
+ /**
251
+ * Cleanup - terminate worker
252
+ */
253
+ destroy(): void;
254
+ }
255
+ /**
256
+ * Factory function to create FetchGuard client
257
+ */
258
+ declare function createClient(options: FetchGuardOptions): FetchGuardClient;
259
+
260
+ /**
261
+ * MESSAGE PAYLOADS - SINGLE SOURCE OF TRUTH
262
+ *
263
+ * Define all message payloads here. Type unions and MSG constants are auto-generated.
264
+ *
265
+ * USAGE:
266
+ * - To add a new message: Just add one line to the appropriate interface
267
+ * - Payload types are automatically inferred
268
+ * - MSG constants are automatically generated
269
+ *
270
+ * EXAMPLE:
271
+ * ```typescript
272
+ * interface MainPayloads {
273
+ * NEW_MESSAGE: { foo: string } // Add this line
274
+ * }
275
+ * // => Automatically get MainToWorkerMessage union with NEW_MESSAGE
276
+ * // => Automatically get MSG.NEW_MESSAGE = 'NEW_MESSAGE'
277
+ * ```
278
+ */
279
+ /**
280
+ * Payloads for messages sent from Main thread → Worker thread
281
+ */
282
+ interface MainPayloads {
283
+ SETUP: {
284
+ config: WorkerConfig;
285
+ providerConfig: ProviderPresetConfig | string | null;
286
+ };
287
+ FETCH: {
288
+ url: string;
289
+ options?: FetchGuardRequestInit;
290
+ };
291
+ AUTH_CALL: {
292
+ method: string;
293
+ args: unknown[];
294
+ };
295
+ CANCEL: undefined;
296
+ PING: {
297
+ timestamp: number;
298
+ };
299
+ }
300
+ /**
301
+ * Payloads for messages sent from Worker thread → Main thread
302
+ */
303
+ interface WorkerPayloads {
304
+ RESULT: {
305
+ result: SerializedResult | object;
306
+ };
307
+ READY: undefined;
308
+ PONG: {
309
+ timestamp: number;
310
+ };
311
+ LOG: {
312
+ level: 'info' | 'warn' | 'error';
313
+ message: string;
314
+ };
315
+ AUTH_STATE_CHANGED: {
316
+ authenticated: boolean;
317
+ expiresAt?: number | null;
318
+ user?: unknown;
319
+ };
320
+ FETCH_RESULT: {
321
+ status: number;
322
+ headers?: Record<string, string>;
323
+ body: string;
324
+ };
325
+ FETCH_ERROR: {
326
+ error: string;
327
+ status?: number;
328
+ };
329
+ }
330
+ /**
331
+ * Generate message type from payload definition
332
+ * Handles optional payloads (undefined) gracefully
333
+ */
334
+ type MessageFromPayloads<P> = {
335
+ [K in keyof P]: {
336
+ id: string;
337
+ type: K;
338
+ } & (P[K] extends undefined ? {} : {
339
+ payload: P[K];
340
+ });
341
+ }[keyof P];
342
+ /**
343
+ * Message type unions - auto-generated from payload interfaces
344
+ */
345
+ type MainToWorkerMessage = MessageFromPayloads<MainPayloads>;
346
+ type WorkerToMainMessage = MessageFromPayloads<WorkerPayloads>;
347
+ /**
348
+ * Message type unions for compile-time type checking
349
+ */
350
+ type MainType = keyof MainPayloads;
351
+ type WorkerType = keyof WorkerPayloads;
352
+ type MessageType = MainType | WorkerType;
353
+ /**
354
+ * MSG constants object - auto-generated from payload keys
355
+ * Usage: MSG.SETUP, MSG.FETCH, etc.
356
+ */
357
+ declare const MSG: { readonly [K in MessageType]: K; };
358
+
359
+ /**
360
+ * Error definitions organized by domain
361
+ * Using ts-micro-result's defineError for consistency
362
+ */
363
+ /**
364
+ * General errors
365
+ */
366
+ declare const GeneralErrors: {
367
+ readonly Unexpected: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
368
+ readonly UnknownMessage: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
369
+ readonly ResultParse: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
370
+ };
371
+ /**
372
+ * Initialization errors
373
+ */
374
+ declare const InitErrors: {
375
+ readonly NotInitialized: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
376
+ readonly ProviderInitFailed: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
377
+ readonly InitFailed: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
378
+ };
379
+ /**
380
+ * Authentication & Token errors
381
+ */
382
+ declare const AuthErrors: {
383
+ readonly TokenRefreshFailed: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
384
+ readonly LoginFailed: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
385
+ readonly LogoutFailed: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
386
+ readonly NotAuthenticated: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
387
+ };
388
+ /**
389
+ * Domain validation errors
390
+ */
391
+ declare const DomainErrors: {
392
+ readonly NotAllowed: (params?: ({
393
+ url: any;
394
+ } & Partial<ts_micro_result_factories_errors_advanced.BaseErrorParams>) | undefined) => ts_micro_result.ErrorDetail;
395
+ };
396
+ /**
397
+ * Network & HTTP errors
398
+ */
399
+ declare const NetworkErrors: {
400
+ readonly NetworkError: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
401
+ readonly HttpError: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
402
+ readonly FetchError: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
403
+ };
404
+ /**
405
+ * Request errors
406
+ */
407
+ declare const RequestErrors: {
408
+ readonly Cancelled: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
409
+ readonly Timeout: (params?: ts_micro_result.BaseErrorParams) => ts_micro_result.ErrorDetail;
410
+ };
411
+
412
+ /**
413
+ * Register a token provider with name
414
+ */
415
+ declare function registerProvider(name: string, provider: TokenProvider): void;
416
+ /**
417
+ * Get provider by name
418
+ */
419
+ declare function getProvider(name: string): TokenProvider;
420
+ /**
421
+ * Check if provider exists
422
+ */
423
+ declare function hasProvider(name: string): boolean;
424
+ /**
425
+ * Get list of all provider names
426
+ */
427
+ declare function listProviders(): string[];
428
+ /**
429
+ * Remove provider
430
+ */
431
+ declare function unregisterProvider(name: string): boolean;
432
+ /**
433
+ * Remove all providers
434
+ */
435
+ declare function clearProviders(): void;
436
+
437
+ /**
438
+ * Custom auth method type
439
+ */
440
+ type CustomAuthMethod = (...args: any[]) => Promise<Result<TokenInfo>>;
441
+ /**
442
+ * Configuration for creating provider
443
+ *
444
+ * refreshStorage: OPTIONAL - to load refresh token initially when worker starts
445
+ * - undefined: cookie-based auth (httpOnly cookie, no need to load)
446
+ * - RefreshTokenStorage: body-based auth (load from IndexedDB on startup)
447
+ *
448
+ * strategy: AuthStrategy with refresh (required), login/logout (required)
449
+ *
450
+ * customMethods: OPTIONAL - custom auth methods (loginWithPhone, loginWithGoogle, etc.)
451
+ */
452
+ interface ProviderConfig {
453
+ refreshStorage?: RefreshTokenStorage;
454
+ parser: TokenParser;
455
+ strategy: AuthStrategy;
456
+ customMethods?: Record<string, CustomAuthMethod>;
457
+ }
458
+ /**
459
+ * Factory function to create TokenProvider from modular components
460
+ *
461
+ * Provider automatically handles refresh token:
462
+ * - If refreshToken is null and storage exists → load from storage initially
463
+ * - If refreshToken exists → use token from worker memory
464
+ * - Cookie-based (no storage) → always null
465
+ *
466
+ * Custom methods:
467
+ * - User can add custom auth methods (loginWithPhone, loginWithGoogle, etc.)
468
+ * - Custom methods will be spread into provider object
469
+ */
470
+ declare function createProvider(config: ProviderConfig): TokenProvider;
471
+
472
+ /**
473
+ * IndexedDB storage - only stores refresh token in IndexedDB
474
+ * Suitable for body-based refresh strategy
475
+ * Persists refresh token for reuse after reload
476
+ */
477
+ declare function createIndexedDBStorage(dbName?: string, refreshTokenKey?: string): RefreshTokenStorage;
478
+
479
+ /**
480
+ * Body parser - parse token from response body (JSON)
481
+ * Expects response format: { data: { accessToken, refreshToken, expiresAt?, user? } }
482
+ */
483
+ declare const bodyParser: TokenParser;
484
+
485
+ /**
486
+ * Cookie parser - parse access token from response body
487
+ * Expects response format: { data: { accessToken, expiresAt?, user? } }
488
+ * Refresh token is automatically set by backend into httpOnly cookie
489
+ */
490
+ declare const cookieParser: TokenParser;
491
+
492
+ /**
493
+ * Cookie auth strategy - all auth operations via httpOnly cookies
494
+ * Suitable for SSR and cross-domain authentication
495
+ *
496
+ * Refresh token is sent automatically via httpOnly cookie
497
+ * Credentials are sent in request body
498
+ */
499
+ declare function createCookieStrategy(config: {
500
+ refreshUrl: string;
501
+ loginUrl: string;
502
+ logoutUrl: string;
503
+ }): AuthStrategy;
504
+ /**
505
+ * Standard cookie strategy
506
+ */
507
+ declare const cookieStrategy: AuthStrategy;
508
+
509
+ /**
510
+ * Body auth strategy - all auth operations via request body
511
+ * Suitable for SPA applications
512
+ *
513
+ * All tokens/credentials are sent in request body
514
+ */
515
+ declare function createBodyStrategy(config: {
516
+ refreshUrl: string;
517
+ loginUrl: string;
518
+ logoutUrl: string;
519
+ }): AuthStrategy;
520
+ /**
521
+ * Standard body strategy
522
+ */
523
+ declare const bodyStrategy: AuthStrategy;
524
+
525
+ /**
526
+ * Cookie Provider - uses httpOnly cookies
527
+ * Suitable for SSR and cross-domain authentication
528
+ *
529
+ * Access token: Worker memory
530
+ * Refresh token: httpOnly cookie (managed by backend)
531
+ */
532
+ declare function createCookieProvider(config: {
533
+ refreshUrl: string;
534
+ loginUrl: string;
535
+ logoutUrl: string;
536
+ }): TokenProvider;
537
+ /**
538
+ * Body Provider - refresh token in response body, persisted to IndexedDB
539
+ * Suitable for SPA applications
540
+ *
541
+ * Access token: Worker memory
542
+ * Refresh token: IndexedDB (persists across reload)
543
+ */
544
+ declare function createBodyProvider(config: {
545
+ refreshUrl: string;
546
+ loginUrl: string;
547
+ logoutUrl: string;
548
+ refreshTokenKey?: string;
549
+ }): TokenProvider;
550
+
551
+ export { type ApiResponse, AuthErrors, type AuthStrategy, DomainErrors, FetchGuardClient, type FetchGuardOptions, type FetchGuardRequestInit, GeneralErrors, InitErrors, MSG, type MainToWorkerMessage, type MessageType, NetworkErrors, type ProviderConfig, type ProviderPresetConfig, type RefreshTokenStorage, RequestErrors, type TokenInfo, type TokenParser, type TokenProvider, type WorkerConfig, type WorkerToMainMessage, bodyParser, bodyStrategy, clearProviders, cookieParser, cookieStrategy, createBodyProvider, createBodyStrategy, createClient, createCookieProvider, createCookieStrategy, createIndexedDBStorage, createProvider, getProvider, hasProvider, listProviders, registerProvider, unregisterProvider };