@forward-software/react-auth 1.0.2 → 2.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 +2 -2
- package/README.md +91 -44
- package/dist/auth.d.ts +237 -0
- package/dist/auth.js +215 -0
- package/dist/index.d.ts +2 -21
- package/dist/index.js +1 -8
- package/dist/{types/eventEmitter.d.ts → utils.d.ts} +17 -10
- package/dist/utils.js +30 -0
- package/package.json +28 -63
- package/src/auth.tsx +546 -0
- package/src/index.ts +2 -0
- package/src/{types/eventEmitter.ts → utils.ts} +25 -2
- package/dist/react-auth.cjs.development.js +0 -1267
- package/dist/react-auth.cjs.development.js.map +0 -1
- package/dist/react-auth.cjs.production.min.js +0 -2
- package/dist/react-auth.cjs.production.min.js.map +0 -1
- package/dist/react-auth.esm.js +0 -1259
- package/dist/react-auth.esm.js.map +0 -1
- package/dist/types/index.d.ts +0 -54
- package/src/index.tsx +0 -98
- package/src/types/index.ts +0 -278
package/src/auth.tsx
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
2
|
+
import type { PropsWithChildren } from 'react';
|
|
3
|
+
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
|
4
|
+
|
|
5
|
+
import { createEventEmitter, Deferred, EventReceiver } from "./utils";
|
|
6
|
+
import type { EventKey } from "./utils";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Represents authentication tokens used for API authorization
|
|
10
|
+
*/
|
|
11
|
+
type AuthTokens = {};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Represents user credentials used for authentication
|
|
15
|
+
*/
|
|
16
|
+
type AuthCredentials = {};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Maps authentication events to their corresponding payload types
|
|
20
|
+
* @template E - The error type used throughout the authentication flow
|
|
21
|
+
*/
|
|
22
|
+
type AuthEventsMap<E extends Error> = {
|
|
23
|
+
initSuccess: undefined;
|
|
24
|
+
|
|
25
|
+
initFailed: E;
|
|
26
|
+
|
|
27
|
+
loginStarted: undefined;
|
|
28
|
+
|
|
29
|
+
loginSuccess: undefined;
|
|
30
|
+
|
|
31
|
+
loginFailed: E;
|
|
32
|
+
|
|
33
|
+
refreshStarted: undefined;
|
|
34
|
+
|
|
35
|
+
refreshSuccess: undefined;
|
|
36
|
+
|
|
37
|
+
refreshFailed: E;
|
|
38
|
+
|
|
39
|
+
logoutStarted: undefined;
|
|
40
|
+
|
|
41
|
+
logoutSuccess: undefined;
|
|
42
|
+
|
|
43
|
+
logoutFailed: E;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Function type for subscription callbacks
|
|
48
|
+
*/
|
|
49
|
+
type SubscribeFn = () => void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Function type for unsubscribing from events
|
|
53
|
+
* @returns {boolean} - Returns true if the subscription was successfully removed
|
|
54
|
+
*/
|
|
55
|
+
type UnsubscribeFn = () => boolean;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Interface defining the core authentication client functionality
|
|
59
|
+
* @template T - The type of authentication tokens
|
|
60
|
+
* @template C - The type of authentication credentials
|
|
61
|
+
*/
|
|
62
|
+
export interface AuthClient<T = AuthTokens, C = AuthCredentials> {
|
|
63
|
+
/**
|
|
64
|
+
* Optional initialization hook called before authentication
|
|
65
|
+
* @returns {Promise<T | null>} - Returns authentication tokens if available
|
|
66
|
+
*/
|
|
67
|
+
onInit?(): Promise<T | null>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Optional post-initialization hook
|
|
71
|
+
*/
|
|
72
|
+
onPostInit?(): Promise<void>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Optional pre-login hook
|
|
76
|
+
*/
|
|
77
|
+
onPreLogin?(): Promise<void>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Handles the login process
|
|
81
|
+
* @param {C} [credentials] - Optional credentials for authentication
|
|
82
|
+
* @returns {Promise<T>} - Returns authentication tokens upon successful login
|
|
83
|
+
*/
|
|
84
|
+
onLogin(credentials?: C): Promise<T>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Optional post-login hook
|
|
88
|
+
* @param {boolean} isSuccess - Indicates whether the login was successful
|
|
89
|
+
*/
|
|
90
|
+
onPostLogin?(isSuccess: boolean): Promise<void>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Optional pre-refresh hook
|
|
94
|
+
*/
|
|
95
|
+
onPreRefresh?(): Promise<void>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Optional token refresh handler.
|
|
99
|
+
* Implement this method to handle token refresh logic.
|
|
100
|
+
* @param {T} currentTokens - The current authentication tokens.
|
|
101
|
+
* @param {number} [minValidity] - Optional minimum token validity period in seconds.
|
|
102
|
+
* @returns {Promise<T>} - A promise that resolves with the refreshed authentication tokens.
|
|
103
|
+
*/
|
|
104
|
+
onRefresh?(currentTokens: T, minValidity?: number): Promise<T>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Optional post-refresh hook
|
|
108
|
+
* @param {boolean} isSuccess - Indicates whether the token refresh was successful
|
|
109
|
+
*/
|
|
110
|
+
onPostRefresh?(isSuccess: boolean): Promise<void>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Optional pre-logout hook
|
|
114
|
+
*/
|
|
115
|
+
onPreLogout?(): Promise<void>;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Optional logout handler
|
|
119
|
+
*/
|
|
120
|
+
onLogout?(): Promise<void>;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Optional post-logout hook
|
|
124
|
+
* @param {boolean} isSuccess - Indicates whether the logout was successful
|
|
125
|
+
*/
|
|
126
|
+
onPostLogout?(isSuccess: boolean): Promise<void>;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Extracts token type from an AuthClient implementation
|
|
131
|
+
* @template AC - The AuthClient implementation type
|
|
132
|
+
*/
|
|
133
|
+
type AuthClientTokens<AC extends AuthClient> = Partial<Awaited<ReturnType<AC["onLogin"]>>>;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Extracts credentials type from an AuthClient implementation
|
|
137
|
+
* @template AC - The AuthClient implementation type
|
|
138
|
+
*/
|
|
139
|
+
type AuthClientCredentials<AC extends AuthClient> = Parameters<AC["onLogin"]>;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Represents the current state of an AuthClient
|
|
143
|
+
* @template AC - The AuthClient implementation type
|
|
144
|
+
*/
|
|
145
|
+
type AuthClientState<AC extends AuthClient> = {
|
|
146
|
+
isAuthenticated: boolean;
|
|
147
|
+
|
|
148
|
+
isInitialized: boolean;
|
|
149
|
+
|
|
150
|
+
tokens: AuthClientTokens<AC>;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class AuthClientEnhancements<AC extends AuthClient, E extends Error> {
|
|
155
|
+
|
|
156
|
+
private _state: Readonly<AuthClientState<AC>> = {
|
|
157
|
+
isAuthenticated: false,
|
|
158
|
+
isInitialized: false,
|
|
159
|
+
tokens: {},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// refresh queue - used to avoid concurrency issue during Token refresh
|
|
163
|
+
private refreshQ: Array<Deferred<boolean>> = [];
|
|
164
|
+
|
|
165
|
+
private eventEmitter = createEventEmitter<AuthEventsMap<E>>();
|
|
166
|
+
|
|
167
|
+
private subscribers: Set<SubscribeFn> = new Set();
|
|
168
|
+
|
|
169
|
+
private _authClient: AC;
|
|
170
|
+
|
|
171
|
+
constructor(authClient: AC) {
|
|
172
|
+
this._authClient = authClient;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
//
|
|
176
|
+
// Getters
|
|
177
|
+
//
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Indicates whether the authentication client has been initialized
|
|
181
|
+
* @readonly
|
|
182
|
+
*/
|
|
183
|
+
public get isInitialized() {
|
|
184
|
+
return this._state.isInitialized;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Indicates whether the user is currently authenticated
|
|
189
|
+
* @readonly
|
|
190
|
+
*/
|
|
191
|
+
public get isAuthenticated() {
|
|
192
|
+
return this._state.isAuthenticated;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Current authentication tokens
|
|
197
|
+
* @readonly
|
|
198
|
+
*/
|
|
199
|
+
public get tokens() {
|
|
200
|
+
return this._state.tokens;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Initializes the authentication client
|
|
205
|
+
* @returns {Promise<boolean>} - Returns true if initialization was successful
|
|
206
|
+
*/
|
|
207
|
+
public async init(): Promise<boolean> {
|
|
208
|
+
try {
|
|
209
|
+
const prevTokens = await this._authClient.onInit?.();
|
|
210
|
+
|
|
211
|
+
this.setState({
|
|
212
|
+
isInitialized: true,
|
|
213
|
+
isAuthenticated: !!prevTokens,
|
|
214
|
+
tokens: prevTokens || {},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
this.emit("initSuccess", undefined);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
this.setState({
|
|
220
|
+
isInitialized: false,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
this.emit("initFailed", error as E);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
await this._authClient.onPostInit?.();
|
|
227
|
+
|
|
228
|
+
return this.isInitialized;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Attempts to authenticate the user with provided credentials
|
|
233
|
+
* @param {...AuthClientCredentials<AC>} params - Authentication credentials
|
|
234
|
+
* @returns {Promise<boolean>} - Returns true if login was successful
|
|
235
|
+
*/
|
|
236
|
+
public async login(...params: AuthClientCredentials<AC>): Promise<boolean> {
|
|
237
|
+
this.emit("loginStarted", undefined);
|
|
238
|
+
|
|
239
|
+
await this._authClient.onPreLogin?.();
|
|
240
|
+
|
|
241
|
+
let isSuccess: boolean = false;
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const tokens = await this._authClient.onLogin(...params);
|
|
245
|
+
|
|
246
|
+
this.setState({
|
|
247
|
+
isAuthenticated: !!tokens,
|
|
248
|
+
tokens,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
this.emit("loginSuccess", undefined);
|
|
252
|
+
|
|
253
|
+
isSuccess = true;
|
|
254
|
+
} catch (err) {
|
|
255
|
+
this.setState({
|
|
256
|
+
isAuthenticated: false,
|
|
257
|
+
tokens: {},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
this.emit("loginFailed", err as E);
|
|
261
|
+
|
|
262
|
+
isSuccess = false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
await this._authClient.onPostLogin?.(isSuccess);
|
|
266
|
+
|
|
267
|
+
return this.isAuthenticated;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Refreshes the authentication tokens
|
|
272
|
+
* @param {number} [minValidity] - Minimum token validity period in seconds
|
|
273
|
+
* @returns {Promise<boolean>} - Returns true if token refresh was successful
|
|
274
|
+
*/
|
|
275
|
+
public async refresh(minValidity?: number): Promise<boolean> {
|
|
276
|
+
const deferred = new Deferred<boolean>();
|
|
277
|
+
|
|
278
|
+
this.runRefresh(deferred, minValidity);
|
|
279
|
+
|
|
280
|
+
return deferred.getPromise();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Logs out the current user
|
|
285
|
+
* @returns {Promise<void>}
|
|
286
|
+
*/
|
|
287
|
+
public async logout(): Promise<void> {
|
|
288
|
+
this.emit("logoutStarted", undefined);
|
|
289
|
+
|
|
290
|
+
await this._authClient.onPreLogout?.();
|
|
291
|
+
|
|
292
|
+
let isSuccess: boolean = false;
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
await this._authClient.onLogout?.();
|
|
296
|
+
|
|
297
|
+
this.setState({
|
|
298
|
+
isAuthenticated: false,
|
|
299
|
+
tokens: {},
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
this.emit("logoutSuccess", undefined);
|
|
303
|
+
|
|
304
|
+
isSuccess = true;
|
|
305
|
+
} catch (err) {
|
|
306
|
+
this.emit("logoutFailed", err as E);
|
|
307
|
+
|
|
308
|
+
isSuccess = false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
await this._authClient.onPostLogout?.(isSuccess);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Registers an event listener for authentication events
|
|
316
|
+
* @template K - The event key type
|
|
317
|
+
* @param {K} eventName - The name of the event to listen for
|
|
318
|
+
* @param {EventReceiver<AuthEventsMap<E>[K]>} listener - The event handler function
|
|
319
|
+
*/
|
|
320
|
+
public on<K extends EventKey<AuthEventsMap<E>>>(eventName: K, listener: EventReceiver<AuthEventsMap<E>[K]>): void {
|
|
321
|
+
this.eventEmitter.on(eventName, listener);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Removes an event listener for authentication events
|
|
326
|
+
* @template K - The event key type
|
|
327
|
+
* @param {K} eventName - The name of the event to stop listening for
|
|
328
|
+
* @param {EventReceiver<AuthEventsMap<E>[K]>} listener - The event handler function to remove
|
|
329
|
+
*/
|
|
330
|
+
public off<K extends EventKey<AuthEventsMap<E>>>(eventName: K, listener: EventReceiver<AuthEventsMap<E>[K]>): void {
|
|
331
|
+
this.eventEmitter.off(eventName, listener);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Subscribes to authentication state changes
|
|
336
|
+
* @param {SubscribeFn} subscription - The callback function to be called on state changes
|
|
337
|
+
* @returns {UnsubscribeFn} - A function to unsubscribe from state changes
|
|
338
|
+
*/
|
|
339
|
+
// Should be declared like this to avoid binding issues when used by useSyncExternalStore
|
|
340
|
+
public subscribe = (subscription: SubscribeFn): UnsubscribeFn => {
|
|
341
|
+
this.subscribers.add(subscription);
|
|
342
|
+
|
|
343
|
+
return () => this.subscribers.delete(subscription);
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Gets the current authentication state
|
|
349
|
+
* @returns {AuthClientState<AC>} - The current authentication state
|
|
350
|
+
*/
|
|
351
|
+
// Should be declared like this to avoid binding issues when used by useSyncExternalStore
|
|
352
|
+
public getSnapshot = (): AuthClientState<AC> => {
|
|
353
|
+
return this._state;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
//
|
|
357
|
+
// Private methods
|
|
358
|
+
//
|
|
359
|
+
|
|
360
|
+
private setState(stateUpdate: Partial<AuthClientState<AC>>): void {
|
|
361
|
+
this._state = {
|
|
362
|
+
...this._state,
|
|
363
|
+
...stateUpdate,
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
this.notifySubscribers();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private async runRefresh(deferred: Deferred<boolean>, minValidity?: number): Promise<void> {
|
|
370
|
+
// Add deferred Promise to refresh queue
|
|
371
|
+
this.refreshQ.push(deferred);
|
|
372
|
+
|
|
373
|
+
// If refresh queue already has promises enqueued do not attempt a new refresh - one is already in progress
|
|
374
|
+
if (this.refreshQ.length !== 1) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
this.emit("refreshStarted", undefined);
|
|
379
|
+
|
|
380
|
+
await this._authClient.onPreRefresh?.();
|
|
381
|
+
|
|
382
|
+
let isAuthenticated: boolean = false;
|
|
383
|
+
let tokens: AuthClientTokens<AC> = {};
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
tokens = (await this._authClient.onRefresh?.(this.tokens, minValidity)) ?? {};
|
|
387
|
+
isAuthenticated = true;
|
|
388
|
+
|
|
389
|
+
this.emit("refreshSuccess", undefined);
|
|
390
|
+
} catch (err) {
|
|
391
|
+
isAuthenticated = false;
|
|
392
|
+
|
|
393
|
+
this.emit("refreshFailed", err as E);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
this.setState({
|
|
397
|
+
isAuthenticated,
|
|
398
|
+
tokens,
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
await this._authClient.onPostRefresh?.(isAuthenticated);
|
|
402
|
+
|
|
403
|
+
for (let p = this.refreshQ.pop(); p != null; p = this.refreshQ.pop()) {
|
|
404
|
+
p.resolve(isAuthenticated);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private emit<K extends EventKey<AuthEventsMap<E>>>(eventName: K, error: AuthEventsMap<E>[K]): void {
|
|
409
|
+
this.eventEmitter.emit(eventName, error);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private notifySubscribers() {
|
|
413
|
+
this.subscribers.forEach((s) => {
|
|
414
|
+
try {
|
|
415
|
+
s();
|
|
416
|
+
} catch { }
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Enhanced authentication client with additional functionality and state management
|
|
423
|
+
* @template AC - The AuthClient implementation type
|
|
424
|
+
* @template E - The error type used throughout the authentication flow
|
|
425
|
+
*/
|
|
426
|
+
export type EnhancedAuthClient<AC extends AuthClient, E extends Error> = AC & AuthClientEnhancements<AC, E>;
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Wraps a basic AuthClient implementation with enhanced functionality
|
|
430
|
+
* @template AC - The AuthClient implementation type
|
|
431
|
+
* @template E - The error type used throughout the authentication flow
|
|
432
|
+
* @param {AC} authClient - The base authentication client to enhance
|
|
433
|
+
* @returns {EnhancedAuthClient<AC, E>} - An enhanced authentication client with additional features
|
|
434
|
+
*/
|
|
435
|
+
export function wrapAuthClient<AC extends AuthClient, E extends Error = Error>(authClient: AC): EnhancedAuthClient<AC, E> {
|
|
436
|
+
Object.setPrototypeOf(AuthClientEnhancements.prototype, authClient);
|
|
437
|
+
|
|
438
|
+
return new AuthClientEnhancements<AC, E>(authClient) as unknown as EnhancedAuthClient<AC, E>;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Represents the current state of the authentication provider
|
|
443
|
+
*/
|
|
444
|
+
type AuthProviderState = {
|
|
445
|
+
isAuthenticated: boolean;
|
|
446
|
+
isInitialized: boolean;
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* The authentication context containing both the state and the enhanced auth client
|
|
451
|
+
* @template AC - The AuthClient implementation type
|
|
452
|
+
* @template E - The error type used throughout the authentication flow
|
|
453
|
+
*/
|
|
454
|
+
type AuthContext<AC extends AuthClient, E extends Error> = AuthProviderState & {
|
|
455
|
+
authClient: EnhancedAuthClient<AC, E>;
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Props that can be passed to AuthProvider
|
|
460
|
+
*/
|
|
461
|
+
export type AuthProviderProps = PropsWithChildren<{
|
|
462
|
+
/**
|
|
463
|
+
* An optional component to display if AuthClient initialization failed.
|
|
464
|
+
*/
|
|
465
|
+
ErrorComponent?: React.ReactNode;
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* An optional component to display while AuthClient instance is being initialized.
|
|
469
|
+
*/
|
|
470
|
+
LoadingComponent?: React.ReactNode;
|
|
471
|
+
}>;
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Creates an authentication context and provider for a React application.
|
|
475
|
+
* It wraps the provided `authClient` with enhanced state management and event handling.
|
|
476
|
+
*
|
|
477
|
+
* @template AC - The type of the base `AuthClient` implementation.
|
|
478
|
+
* @template E - The type of error expected during authentication flows. Defaults to `Error`.
|
|
479
|
+
* @param {AC} authClient - The base authentication client instance to use.
|
|
480
|
+
* @returns An object containing:
|
|
481
|
+
* - `AuthProvider`: A React component to wrap the application or parts of it.
|
|
482
|
+
* - `authClient`: The enhanced authentication client instance.
|
|
483
|
+
* - `useAuthClient`: A hook to access the enhanced `authClient` within the `AuthProvider`.
|
|
484
|
+
*/
|
|
485
|
+
export function createAuth<AC extends AuthClient, E extends Error = Error>(authClient: AC) {
|
|
486
|
+
// Create a React context containing an AuthClient instance.
|
|
487
|
+
const authContext = createContext<AuthContext<AC, E> | null>(null);
|
|
488
|
+
|
|
489
|
+
const enhancedAuthClient = wrapAuthClient<AC, E>(authClient);
|
|
490
|
+
|
|
491
|
+
// Create the React Context Provider for the AuthClient instance.
|
|
492
|
+
const AuthProvider: React.FC<AuthProviderProps> = ({ children, ErrorComponent, LoadingComponent }) => {
|
|
493
|
+
const [isInitFailed, setInitFailed] = useState(false);
|
|
494
|
+
const { isAuthenticated, isInitialized } = useSyncExternalStore(enhancedAuthClient.subscribe, enhancedAuthClient.getSnapshot);
|
|
495
|
+
|
|
496
|
+
useEffect(() => {
|
|
497
|
+
async function initAuthClient() {
|
|
498
|
+
// Call init function
|
|
499
|
+
const initSuccess = await enhancedAuthClient.init();
|
|
500
|
+
setInitFailed(!initSuccess);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Init AuthClient
|
|
504
|
+
initAuthClient();
|
|
505
|
+
}, []);
|
|
506
|
+
|
|
507
|
+
if (!!ErrorComponent && isInitFailed) {
|
|
508
|
+
return ErrorComponent;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (!!LoadingComponent && !isInitialized) {
|
|
512
|
+
return LoadingComponent;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return (
|
|
516
|
+
<authContext.Provider
|
|
517
|
+
value={{
|
|
518
|
+
authClient: enhancedAuthClient,
|
|
519
|
+
isAuthenticated,
|
|
520
|
+
isInitialized,
|
|
521
|
+
}}
|
|
522
|
+
>
|
|
523
|
+
{children}
|
|
524
|
+
</authContext.Provider>
|
|
525
|
+
);
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Hook to access the authentication client within the AuthProvider
|
|
530
|
+
* @throws Error if used outside of an AuthProvider
|
|
531
|
+
*/
|
|
532
|
+
const useAuthClient = function (): EnhancedAuthClient<AC, E> {
|
|
533
|
+
const ctx = useContext(authContext);
|
|
534
|
+
if (!ctx) {
|
|
535
|
+
throw new Error('useAuthClient hook should be used inside AuthProvider');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return ctx.authClient;
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
AuthProvider,
|
|
543
|
+
authClient: enhancedAuthClient,
|
|
544
|
+
useAuthClient,
|
|
545
|
+
};
|
|
546
|
+
}
|
package/src/index.ts
ADDED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
// DEFERRED
|
|
2
|
+
|
|
3
|
+
export class Deferred<T> {
|
|
4
|
+
private promise: Promise<T>;
|
|
5
|
+
|
|
6
|
+
public resolve!: (value: T | PromiseLike<T>) => void;
|
|
7
|
+
|
|
8
|
+
public reject!: (reason?: any) => void;
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
this.promise = new Promise<T>((resolve, reject) => {
|
|
12
|
+
this.reject = reject;
|
|
13
|
+
this.resolve = resolve;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public getPromise(): Promise<T> {
|
|
18
|
+
return this.promise;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// EVENT EMITTER
|
|
23
|
+
|
|
1
24
|
type EventsMap = Record<string, any>;
|
|
2
25
|
|
|
3
26
|
export type EventKey<T extends EventsMap> = string & keyof T;
|
|
@@ -21,10 +44,10 @@ export function createEventEmitter<T extends EventsMap>(): Emitter<T> {
|
|
|
21
44
|
listeners[key] = (listeners[key] || []).concat(fn);
|
|
22
45
|
},
|
|
23
46
|
off(key, fn) {
|
|
24
|
-
listeners[key] = (listeners[key] || []).filter(f => f !== fn);
|
|
47
|
+
listeners[key] = (listeners[key] || []).filter((f) => f !== fn);
|
|
25
48
|
},
|
|
26
49
|
emit(key, data) {
|
|
27
|
-
(listeners[key] || []).forEach(function(fn) {
|
|
50
|
+
(listeners[key] || []).forEach(function (fn) {
|
|
28
51
|
try {
|
|
29
52
|
fn(data);
|
|
30
53
|
} catch {}
|