@forward-software/react-auth 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,278 @@
1
+ import { createEventEmitter, EventKey, EventReceiver } from './eventEmitter';
2
+
3
+ type AuthTokens = {};
4
+
5
+ type AuthCredentials = {};
6
+
7
+ type AuthEventsMap<E extends Error> = {
8
+ initSuccess: undefined;
9
+
10
+ initFailed: E;
11
+
12
+ loginStarted: undefined;
13
+
14
+ loginSuccess: undefined;
15
+
16
+ loginFailed: E;
17
+
18
+ refreshStarted: undefined;
19
+
20
+ refreshSuccess: undefined;
21
+
22
+ refreshFailed: E;
23
+
24
+ logoutStarted: undefined;
25
+
26
+ logoutSuccess: undefined;
27
+
28
+ logoutFailed: E;
29
+ };
30
+
31
+ type SubscribeFn = () => void;
32
+
33
+ type UnsubscribeFn = () => boolean;
34
+
35
+ type AuthClientState<T> = {
36
+ isAuthenticated: boolean;
37
+
38
+ isInitialized: boolean;
39
+
40
+ tokens: Partial<T>;
41
+ };
42
+
43
+ export abstract class BaseAuthClient<
44
+ T = AuthTokens,
45
+ C = AuthCredentials,
46
+ E extends Error = Error
47
+ > {
48
+ private _state: Readonly<AuthClientState<T>> = {
49
+ isAuthenticated: false,
50
+ isInitialized: false,
51
+ tokens: {},
52
+ };
53
+
54
+ private eventEmitter = createEventEmitter<AuthEventsMap<E>>();
55
+
56
+ private subscribers: Set<SubscribeFn> = new Set();
57
+
58
+ //
59
+ // Getters
60
+ //
61
+
62
+ public get isInitialized() {
63
+ return this._state.isInitialized;
64
+ }
65
+
66
+ public get isAuthenticated() {
67
+ return this._state.isAuthenticated;
68
+ }
69
+
70
+ public get tokens() {
71
+ return this._state.tokens;
72
+ }
73
+
74
+ //
75
+ // Public methods
76
+ //
77
+
78
+ public async init(): Promise<boolean> {
79
+ try {
80
+ await this.onInit();
81
+
82
+ this.setState({
83
+ isInitialized: true,
84
+ });
85
+
86
+ this.emit('initSuccess', undefined);
87
+ } catch (error) {
88
+ this.setState({
89
+ isInitialized: false,
90
+ });
91
+
92
+ this.emit('initFailed', error as E);
93
+ }
94
+
95
+ await this.onPostInit?.();
96
+
97
+ return this.isInitialized;
98
+ }
99
+
100
+ public async login(credentials?: C): Promise<boolean> {
101
+ this.emit('loginStarted', undefined);
102
+
103
+ await this.onPreLogin?.();
104
+
105
+ let isSuccess: boolean = false;
106
+
107
+ try {
108
+ const tokens = await this.onLogin(credentials);
109
+
110
+ this.setState({
111
+ isAuthenticated: true,
112
+ tokens,
113
+ });
114
+
115
+ this.emit('loginSuccess', undefined);
116
+
117
+ isSuccess = true;
118
+ } catch (err) {
119
+ this.setState({
120
+ isAuthenticated: false,
121
+ tokens: {},
122
+ });
123
+
124
+ this.emit('loginFailed', err as E);
125
+
126
+ isSuccess = false;
127
+ }
128
+
129
+ await this.onPostLogin?.(isSuccess);
130
+
131
+ return this.isAuthenticated;
132
+ }
133
+
134
+ public async refresh(minValidity?: number): Promise<boolean> {
135
+ this.emit('refreshStarted', undefined);
136
+
137
+ await this.onPreRefresh?.();
138
+
139
+ let isSuccess: boolean = false;
140
+
141
+ try {
142
+ const tokens = await this.onRefresh(minValidity);
143
+
144
+ this.setState({
145
+ isAuthenticated: true,
146
+ tokens,
147
+ });
148
+
149
+ this.emit('refreshSuccess', undefined);
150
+
151
+ isSuccess = true;
152
+ } catch (err) {
153
+ this.setState({
154
+ isAuthenticated: false,
155
+ tokens: {},
156
+ });
157
+
158
+ this.emit('refreshFailed', err as E);
159
+
160
+ isSuccess = false;
161
+ }
162
+
163
+ await this.onPostRefresh?.(isSuccess);
164
+
165
+ return this.isAuthenticated;
166
+ }
167
+
168
+ public async logout(): Promise<void> {
169
+ this.emit('logoutStarted', undefined);
170
+
171
+ await this.onPreLogout?.();
172
+
173
+ let isSuccess: boolean = false;
174
+
175
+ try {
176
+ await this.onLogout();
177
+
178
+ this.setState({
179
+ isAuthenticated: false,
180
+ tokens: {},
181
+ });
182
+
183
+ this.emit('logoutSuccess', undefined);
184
+
185
+ isSuccess = true;
186
+ } catch (err) {
187
+ this.emit('logoutFailed', err as E);
188
+
189
+ isSuccess = false;
190
+ }
191
+
192
+ await this.onPostLogout?.(isSuccess);
193
+ }
194
+
195
+ public on<K extends EventKey<AuthEventsMap<E>>>(
196
+ eventName: K,
197
+ listener: EventReceiver<AuthEventsMap<E>[K]>
198
+ ): void {
199
+ this.eventEmitter.on(eventName, listener);
200
+ }
201
+
202
+ public off<K extends EventKey<AuthEventsMap<E>>>(
203
+ eventName: K,
204
+ listener: EventReceiver<AuthEventsMap<E>[K]>
205
+ ): void {
206
+ this.eventEmitter.off(eventName, listener);
207
+ }
208
+
209
+ // Should be declared like this to avoid binding issues when used by useSyncExternalStore
210
+ public subscribe = (subscription: SubscribeFn): UnsubscribeFn => {
211
+ this.subscribers.add(subscription);
212
+
213
+ return () => this.subscribers.delete(subscription);
214
+ };
215
+
216
+ // Should be declared like this to avoid binding issues when used by useSyncExternalStore
217
+ public getSnapshot = (): AuthClientState<T> => {
218
+ return this._state;
219
+ };
220
+
221
+ //
222
+ // Protected methods
223
+ //
224
+
225
+ protected setState(stateUpdate: Partial<AuthClientState<T>>): void {
226
+ this._state = {
227
+ ...this._state,
228
+ ...stateUpdate,
229
+ };
230
+
231
+ this.notifySubscribers();
232
+ }
233
+
234
+ //
235
+ // Private methods
236
+ //
237
+
238
+ private emit<K extends EventKey<AuthEventsMap<E>>>(
239
+ eventName: K,
240
+ error: AuthEventsMap<E>[K]
241
+ ): void {
242
+ this.eventEmitter.emit(eventName, error);
243
+ }
244
+
245
+ private notifySubscribers() {
246
+ this.subscribers.forEach(s => {
247
+ try {
248
+ s();
249
+ } catch {}
250
+ });
251
+ }
252
+
253
+ //
254
+ // Abstract methods
255
+ //
256
+
257
+ protected abstract onInit(): Promise<void>;
258
+
259
+ protected onPostInit?(): Promise<void>;
260
+
261
+ protected onPreLogin?(): Promise<void>;
262
+
263
+ protected abstract onLogin(credentials?: C): Promise<T>;
264
+
265
+ protected onPostLogin?(isSuccess: boolean): Promise<void>;
266
+
267
+ protected onPreRefresh?(): Promise<void>;
268
+
269
+ protected abstract onRefresh(minValidity?: number): Promise<T>;
270
+
271
+ protected onPostRefresh?(isSuccess: boolean): Promise<void>;
272
+
273
+ protected onPreLogout?(): Promise<void>;
274
+
275
+ protected abstract onLogout(): Promise<void>;
276
+
277
+ protected onPostLogout?(isSuccess: boolean): Promise<void>;
278
+ }