rehive 4.2.1 → 4.2.2
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/dist/admin.d.mts +2 -2
- package/dist/admin.d.ts +2 -2
- package/dist/auth.d.mts +2 -2
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +1 -1
- package/dist/auth.mjs +1 -1
- package/dist/chunk-647XG2KN.js +1 -0
- package/dist/chunk-BAUHEOL5.mjs +1 -0
- package/dist/{create-api-client-CgvKBlQ_.d.ts → create-api-client-DzUaFuXN.d.ts} +1 -1
- package/dist/{create-api-client-Bkkri-AM.d.mts → create-api-client-JpNvOfh9.d.mts} +1 -1
- package/dist/{create-auth-ChOASbvo.d.mts → create-auth-D-3te_Jw.d.mts} +46 -1
- package/dist/{create-auth-ChOASbvo.d.ts → create-auth-D-3te_Jw.d.ts} +46 -1
- package/dist/extensions/alchemy.d.mts +2 -2
- package/dist/extensions/alchemy.d.ts +2 -2
- package/dist/extensions/app.d.mts +2 -2
- package/dist/extensions/app.d.ts +2 -2
- package/dist/extensions/billing.d.mts +2 -2
- package/dist/extensions/billing.d.ts +2 -2
- package/dist/extensions/bridge.d.mts +2 -2
- package/dist/extensions/bridge.d.ts +2 -2
- package/dist/extensions/builder.d.mts +2 -2
- package/dist/extensions/builder.d.ts +2 -2
- package/dist/extensions/business.d.mts +2 -2
- package/dist/extensions/business.d.ts +2 -2
- package/dist/extensions/conversion.d.mts +2 -2
- package/dist/extensions/conversion.d.ts +2 -2
- package/dist/extensions/mass-send.d.mts +2 -2
- package/dist/extensions/mass-send.d.ts +2 -2
- package/dist/extensions/notifications.d.mts +2 -2
- package/dist/extensions/notifications.d.ts +2 -2
- package/dist/extensions/payment-requests.d.mts +2 -2
- package/dist/extensions/payment-requests.d.ts +2 -2
- package/dist/extensions/products.d.mts +2 -2
- package/dist/extensions/products.d.ts +2 -2
- package/dist/extensions/rain.d.mts +2 -2
- package/dist/extensions/rain.d.ts +2 -2
- package/dist/extensions/rewards.d.mts +2 -2
- package/dist/extensions/rewards.d.ts +2 -2
- package/dist/extensions/stellar-testnet.d.mts +2 -2
- package/dist/extensions/stellar-testnet.d.ts +2 -2
- package/dist/extensions/stellar.d.mts +2 -2
- package/dist/extensions/stellar.d.ts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react.d.mts +11 -3
- package/dist/react.d.ts +11 -3
- package/dist/react.js +1 -1
- package/dist/react.mjs +1 -1
- package/dist/user.d.mts +3 -3
- package/dist/user.d.ts +3 -3
- package/package.json +3 -3
- package/src/auth/create-auth.ts +774 -170
- package/src/auth/index.ts +25 -2
- package/src/auth/types/index.ts +48 -0
- package/dist/chunk-OV77OD2G.js +0 -1
- package/dist/chunk-RO2QGTSG.mjs +0 -1
package/src/auth/create-auth.ts
CHANGED
|
@@ -14,10 +14,16 @@ import type {
|
|
|
14
14
|
import { WebStorageAdapter, MemoryStorageAdapter } from './core/storage-adapters.js';
|
|
15
15
|
import { ApiError, normalizeFetch } from '../shared/api-utils.js';
|
|
16
16
|
import type {
|
|
17
|
+
AuthEvent,
|
|
18
|
+
AuthEventListener,
|
|
19
|
+
AuthRecoveryState,
|
|
17
20
|
AuthSession,
|
|
21
|
+
AuthSnapshot,
|
|
18
22
|
AuthState,
|
|
19
|
-
|
|
23
|
+
AuthStateListener,
|
|
24
|
+
AuthStatus,
|
|
20
25
|
ErrorListener,
|
|
26
|
+
SessionListener,
|
|
21
27
|
StorageAdapter,
|
|
22
28
|
} from './types/index.js';
|
|
23
29
|
|
|
@@ -43,6 +49,21 @@ export type RegisterParams = {
|
|
|
43
49
|
|
|
44
50
|
export type RegisterCompanyParams = RegisterCompanyRequestWritable;
|
|
45
51
|
|
|
52
|
+
export interface ImportTokenOptions {
|
|
53
|
+
company?: string;
|
|
54
|
+
expires?: number;
|
|
55
|
+
sessionDuration?: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ValidateSessionOptions {
|
|
59
|
+
retryCount?: number;
|
|
60
|
+
retryDelayMs?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type SessionPatch =
|
|
64
|
+
| Partial<AuthSession>
|
|
65
|
+
| ((session: AuthSession) => AuthSession);
|
|
66
|
+
|
|
46
67
|
export interface AuthConfig {
|
|
47
68
|
baseUrl?: string;
|
|
48
69
|
storage?: 'local' | 'memory' | StorageAdapter;
|
|
@@ -61,11 +82,25 @@ export interface Auth {
|
|
|
61
82
|
getActiveSession(): AuthSession | null;
|
|
62
83
|
getSessions(): AuthSession[];
|
|
63
84
|
getSessionsByCompany(company: string): AuthSession[];
|
|
85
|
+
getState(): AuthSnapshot;
|
|
86
|
+
getStatus(): AuthStatus;
|
|
87
|
+
getRecoveryState(): AuthRecoveryState;
|
|
64
88
|
switchToSession(userId: string, company?: string): Promise<AuthSession | null>;
|
|
65
89
|
clearAllSessions(): Promise<void>;
|
|
66
90
|
deleteChallenge(challengeId: string): Promise<void>;
|
|
91
|
+
importToken(token: string, options?: ImportTokenOptions): Promise<AuthSession>;
|
|
92
|
+
validateActiveSession(options?: ValidateSessionOptions): Promise<boolean>;
|
|
93
|
+
syncActiveSessionUser(): Promise<AuthSession | null>;
|
|
94
|
+
updateSession(
|
|
95
|
+
userId: string,
|
|
96
|
+
company: string | undefined,
|
|
97
|
+
patch: SessionPatch,
|
|
98
|
+
): Promise<AuthSession | null>;
|
|
99
|
+
expireActiveSession(): Promise<AuthRecoveryState>;
|
|
67
100
|
subscribe(listener: SessionListener): () => void;
|
|
68
101
|
subscribeToErrors(listener: ErrorListener): () => void;
|
|
102
|
+
subscribeToState(listener: AuthStateListener): () => void;
|
|
103
|
+
subscribeToEvents(listener: AuthEventListener): () => void;
|
|
69
104
|
readonly baseUrl: string;
|
|
70
105
|
}
|
|
71
106
|
|
|
@@ -106,46 +141,195 @@ function errorHandlingFetch(baseFetch: typeof fetch): typeof fetch {
|
|
|
106
141
|
};
|
|
107
142
|
}
|
|
108
143
|
|
|
144
|
+
function sleep(ms: number): Promise<void> {
|
|
145
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getSessionCompany(session: AuthSession): string | undefined {
|
|
149
|
+
return session.company ?? session.user?.company;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function cloneSessions(sessions: AuthSession[]): AuthSession[] {
|
|
153
|
+
return [...sessions];
|
|
154
|
+
}
|
|
155
|
+
|
|
109
156
|
export function createAuth(config: AuthConfig = {}): Auth {
|
|
110
157
|
const baseUrl = config.baseUrl || 'https://api.rehive.com';
|
|
111
158
|
const storage = resolveStorage(config.storage);
|
|
112
159
|
const permanentToken = config.token;
|
|
113
160
|
const enableCrossTabSync = config.enableCrossTabSync ?? true;
|
|
114
161
|
const storageKey = 'rehive_auth_state';
|
|
162
|
+
const baseFetch = normalizeFetch(globalThis.fetch);
|
|
115
163
|
|
|
116
164
|
const client = createClient({
|
|
117
165
|
baseUrl,
|
|
118
166
|
responseStyle: 'data' as const,
|
|
119
|
-
fetch: errorHandlingFetch(
|
|
167
|
+
fetch: errorHandlingFetch(baseFetch),
|
|
120
168
|
});
|
|
121
169
|
|
|
122
170
|
let sessions: AuthSession[] = [];
|
|
123
171
|
let activeSessionIndex = -1;
|
|
124
172
|
let sessionListeners: SessionListener[] = [];
|
|
125
173
|
let errorListeners: ErrorListener[] = [];
|
|
174
|
+
let stateListeners: AuthStateListener[] = [];
|
|
175
|
+
let eventListeners: AuthEventListener[] = [];
|
|
126
176
|
let refreshPromise: Promise<void> | null = null;
|
|
127
177
|
let isRefreshing = false;
|
|
128
178
|
let loadAuthStatePromise: Promise<AuthState> | null = null;
|
|
129
179
|
let initialized = false;
|
|
180
|
+
let initializePromise: Promise<void> | null = null;
|
|
181
|
+
let cachedSnapshot: AuthSnapshot | null = null;
|
|
182
|
+
let lastError: Error | null = null;
|
|
183
|
+
let lastExpiredSession: AuthSession | null = null;
|
|
130
184
|
|
|
131
185
|
function isTokenExpired(expires: number): boolean {
|
|
132
186
|
return Date.now() >= expires - 30 * 1000;
|
|
133
187
|
}
|
|
134
188
|
|
|
135
|
-
function
|
|
136
|
-
if (
|
|
137
|
-
|
|
189
|
+
function getActiveSessionFromState(state: AuthState): AuthSession | null {
|
|
190
|
+
if (
|
|
191
|
+
state.activeSessionIndex >= 0 &&
|
|
192
|
+
state.activeSessionIndex < state.sessions.length
|
|
193
|
+
) {
|
|
194
|
+
return state.sessions[state.activeSessionIndex];
|
|
138
195
|
}
|
|
139
196
|
return null;
|
|
140
197
|
}
|
|
141
198
|
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
|
|
199
|
+
function getCurrentState(): AuthState {
|
|
200
|
+
return {
|
|
201
|
+
sessions: cloneSessions(sessions),
|
|
202
|
+
activeSessionIndex,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getActiveSession(): AuthSession | null {
|
|
207
|
+
return getActiveSessionFromState(getCurrentState());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function buildRecoveryState(state: AuthState = getCurrentState()): AuthRecoveryState {
|
|
211
|
+
const session = getActiveSessionFromState(state);
|
|
212
|
+
const pending = !permanentToken && !session && state.sessions.length > 0;
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
pending,
|
|
216
|
+
expiredSession: pending ? lastExpiredSession : null,
|
|
217
|
+
remainingSessions: pending ? cloneSessions(state.sessions) : [],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getStatus(state: AuthState = getCurrentState()): AuthStatus {
|
|
222
|
+
if (!initialized && !permanentToken) {
|
|
223
|
+
return 'loading';
|
|
224
|
+
}
|
|
225
|
+
if (isRefreshing) {
|
|
226
|
+
return 'refreshing';
|
|
227
|
+
}
|
|
228
|
+
if (permanentToken) {
|
|
229
|
+
return 'authenticated';
|
|
230
|
+
}
|
|
231
|
+
if (getActiveSessionFromState(state)) {
|
|
232
|
+
return 'authenticated';
|
|
233
|
+
}
|
|
234
|
+
if (state.sessions.length > 0) {
|
|
235
|
+
return 'recoverable';
|
|
236
|
+
}
|
|
237
|
+
return 'unauthenticated';
|
|
145
238
|
}
|
|
146
239
|
|
|
147
|
-
function
|
|
148
|
-
|
|
240
|
+
function getState(): AuthSnapshot {
|
|
241
|
+
if (cachedSnapshot) {
|
|
242
|
+
return cachedSnapshot;
|
|
243
|
+
}
|
|
244
|
+
const state = getCurrentState();
|
|
245
|
+
cachedSnapshot = {
|
|
246
|
+
status: getStatus(state),
|
|
247
|
+
session: getActiveSessionFromState(state),
|
|
248
|
+
sessions: state.sessions,
|
|
249
|
+
activeSessionIndex: state.activeSessionIndex,
|
|
250
|
+
isRefreshing,
|
|
251
|
+
initialized: initialized || !!permanentToken,
|
|
252
|
+
error: lastError,
|
|
253
|
+
recovery: buildRecoveryState(state),
|
|
254
|
+
};
|
|
255
|
+
return cachedSnapshot;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function invalidateSnapshot(): void {
|
|
259
|
+
cachedSnapshot = null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function emit(event: Omit<AuthEvent, 'snapshot'>): void {
|
|
263
|
+
const snapshot = getState();
|
|
264
|
+
const payload: AuthEvent = {
|
|
265
|
+
...event,
|
|
266
|
+
snapshot,
|
|
267
|
+
};
|
|
268
|
+
eventListeners.forEach((listener) => listener(payload));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function notifyAll(event?: Omit<AuthEvent, 'snapshot'>): void {
|
|
272
|
+
invalidateSnapshot();
|
|
273
|
+
const snapshot = getState();
|
|
274
|
+
sessionListeners.forEach((listener) => listener(snapshot.session));
|
|
275
|
+
errorListeners.forEach((listener) => listener(snapshot.error));
|
|
276
|
+
stateListeners.forEach((listener) => listener(snapshot));
|
|
277
|
+
|
|
278
|
+
if (event) {
|
|
279
|
+
emit(event);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function setError(error: Error | null): void {
|
|
284
|
+
lastError = error;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function persistState(state: AuthState): Promise<void> {
|
|
288
|
+
if (permanentToken) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
await storage.setItem(storageKey, JSON.stringify(state));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function applyState(
|
|
296
|
+
state: AuthState,
|
|
297
|
+
options: {
|
|
298
|
+
clearExpiredSession?: boolean;
|
|
299
|
+
expiredSession?: AuthSession | null;
|
|
300
|
+
error?: Error | null;
|
|
301
|
+
event?: Omit<AuthEvent, 'snapshot'>;
|
|
302
|
+
} = {},
|
|
303
|
+
): Promise<void> {
|
|
304
|
+
sessions = cloneSessions(state.sessions);
|
|
305
|
+
activeSessionIndex =
|
|
306
|
+
state.activeSessionIndex >= 0 && state.activeSessionIndex < sessions.length
|
|
307
|
+
? state.activeSessionIndex
|
|
308
|
+
: -1;
|
|
309
|
+
|
|
310
|
+
if (options.clearExpiredSession || activeSessionIndex >= 0 || sessions.length === 0) {
|
|
311
|
+
lastExpiredSession = null;
|
|
312
|
+
}
|
|
313
|
+
if (Object.prototype.hasOwnProperty.call(options, 'expiredSession')) {
|
|
314
|
+
lastExpiredSession = options.expiredSession ?? null;
|
|
315
|
+
}
|
|
316
|
+
if (Object.prototype.hasOwnProperty.call(options, 'error')) {
|
|
317
|
+
setError(options.error ?? null);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
await persistState({
|
|
322
|
+
sessions,
|
|
323
|
+
activeSessionIndex,
|
|
324
|
+
});
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.error('Failed to save auth state:', error);
|
|
327
|
+
if (!lastError && error instanceof Error) {
|
|
328
|
+
setError(error);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
notifyAll(options.event);
|
|
149
333
|
}
|
|
150
334
|
|
|
151
335
|
async function loadAuthState(): Promise<AuthState> {
|
|
@@ -168,9 +352,17 @@ export function createAuth(config: AuthConfig = {}): Auth {
|
|
|
168
352
|
} catch (error) {
|
|
169
353
|
console.error('Failed to load auth state:', error);
|
|
170
354
|
}
|
|
355
|
+
|
|
171
356
|
sessions = state.sessions;
|
|
172
|
-
activeSessionIndex =
|
|
173
|
-
|
|
357
|
+
activeSessionIndex =
|
|
358
|
+
state.activeSessionIndex >= 0 && state.activeSessionIndex < state.sessions.length
|
|
359
|
+
? state.activeSessionIndex
|
|
360
|
+
: -1;
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
sessions: cloneSessions(sessions),
|
|
364
|
+
activeSessionIndex,
|
|
365
|
+
};
|
|
174
366
|
})();
|
|
175
367
|
|
|
176
368
|
try {
|
|
@@ -180,51 +372,133 @@ export function createAuth(config: AuthConfig = {}): Auth {
|
|
|
180
372
|
}
|
|
181
373
|
}
|
|
182
374
|
|
|
183
|
-
async function saveAuthState(state: AuthState): Promise<void> {
|
|
184
|
-
try {
|
|
185
|
-
await storage.setItem(storageKey, JSON.stringify(state));
|
|
186
|
-
sessions = state.sessions;
|
|
187
|
-
activeSessionIndex = state.activeSessionIndex;
|
|
188
|
-
notifySessionListeners();
|
|
189
|
-
} catch (error) {
|
|
190
|
-
console.error('Failed to save auth state:', error);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
375
|
function setupCrossTabSync(): void {
|
|
195
376
|
if (typeof window !== 'undefined') {
|
|
196
377
|
window.addEventListener('storage', (event: StorageEvent) => {
|
|
197
|
-
if (event.key
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
378
|
+
if (event.key !== storageKey) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (!event.newValue) {
|
|
383
|
+
void applyState(
|
|
384
|
+
{ sessions: [], activeSessionIndex: -1 },
|
|
385
|
+
{ clearExpiredSession: true },
|
|
386
|
+
);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
const newState = JSON.parse(event.newValue);
|
|
392
|
+
void applyState(
|
|
393
|
+
{
|
|
394
|
+
sessions: Array.isArray(newState.sessions) ? newState.sessions : [],
|
|
395
|
+
activeSessionIndex:
|
|
396
|
+
typeof newState.activeSessionIndex === 'number'
|
|
397
|
+
? newState.activeSessionIndex
|
|
398
|
+
: -1,
|
|
399
|
+
},
|
|
400
|
+
{},
|
|
401
|
+
);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
console.error('Failed to sync auth state from storage event:', error);
|
|
209
404
|
}
|
|
210
405
|
});
|
|
211
406
|
}
|
|
212
407
|
}
|
|
213
408
|
|
|
214
409
|
async function initialize(): Promise<void> {
|
|
215
|
-
if (initialized)
|
|
216
|
-
|
|
410
|
+
if (initialized) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (initializePromise) {
|
|
414
|
+
return initializePromise;
|
|
415
|
+
}
|
|
217
416
|
|
|
218
|
-
|
|
219
|
-
if (
|
|
220
|
-
|
|
417
|
+
initializePromise = (async () => {
|
|
418
|
+
if (!permanentToken) {
|
|
419
|
+
if (enableCrossTabSync) {
|
|
420
|
+
setupCrossTabSync();
|
|
421
|
+
}
|
|
422
|
+
await loadAuthState();
|
|
221
423
|
}
|
|
222
|
-
|
|
223
|
-
|
|
424
|
+
initialized = true;
|
|
425
|
+
notifyAll({ type: 'initialized' });
|
|
426
|
+
})();
|
|
427
|
+
|
|
428
|
+
return initializePromise;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async function fetchUserForToken(token: string): Promise<any> {
|
|
432
|
+
const response = await baseFetch(`${baseUrl.replace(/\/$/, '')}/3/user/`, {
|
|
433
|
+
headers: {
|
|
434
|
+
Authorization: `Token ${token}`,
|
|
435
|
+
'Content-Type': 'application/json',
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
if (!response.ok) {
|
|
440
|
+
const errorText = await response.text();
|
|
441
|
+
let errorJson: any = null;
|
|
442
|
+
try {
|
|
443
|
+
errorJson = JSON.parse(errorText);
|
|
444
|
+
} catch {
|
|
445
|
+
// not JSON
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
throw new ApiError({
|
|
449
|
+
status: response.status,
|
|
450
|
+
error: errorJson || errorText,
|
|
451
|
+
message:
|
|
452
|
+
errorJson?.error ||
|
|
453
|
+
errorJson?.message ||
|
|
454
|
+
'A server error occurred. HTTPStatus: ' + response.status,
|
|
455
|
+
});
|
|
224
456
|
}
|
|
457
|
+
|
|
458
|
+
const payload = await response.json();
|
|
459
|
+
return payload?.data ?? payload;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function upsertSession(
|
|
463
|
+
newSession: AuthSession,
|
|
464
|
+
eventType: AuthEvent['type'],
|
|
465
|
+
): Promise<AuthSession> {
|
|
466
|
+
await initialize();
|
|
467
|
+
const currentState = await loadAuthState();
|
|
468
|
+
const sessionCompany = getSessionCompany(newSession);
|
|
469
|
+
const existingIdx = currentState.sessions.findIndex(
|
|
470
|
+
(session) =>
|
|
471
|
+
session.user.id === newSession.user.id &&
|
|
472
|
+
getSessionCompany(session) === sessionCompany,
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
let nextState: AuthState;
|
|
476
|
+
if (existingIdx >= 0) {
|
|
477
|
+
const updatedSessions = cloneSessions(currentState.sessions);
|
|
478
|
+
updatedSessions[existingIdx] = newSession;
|
|
479
|
+
nextState = {
|
|
480
|
+
sessions: updatedSessions,
|
|
481
|
+
activeSessionIndex: existingIdx,
|
|
482
|
+
};
|
|
483
|
+
} else {
|
|
484
|
+
nextState = {
|
|
485
|
+
sessions: [...currentState.sessions, newSession],
|
|
486
|
+
activeSessionIndex: currentState.sessions.length,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
await applyState(nextState, {
|
|
491
|
+
clearExpiredSession: true,
|
|
492
|
+
error: null,
|
|
493
|
+
event: {
|
|
494
|
+
type: eventType,
|
|
495
|
+
session: newSession,
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
return newSession;
|
|
225
500
|
}
|
|
226
501
|
|
|
227
|
-
// Kick off initialization (non-blocking)
|
|
228
502
|
if (!permanentToken) {
|
|
229
503
|
initialize().catch(console.error);
|
|
230
504
|
}
|
|
@@ -247,40 +521,25 @@ export function createAuth(config: AuthConfig = {}): Auth {
|
|
|
247
521
|
const result: any = await authLogin({ client, body, throwOnError: true });
|
|
248
522
|
const data = result?.data ?? result;
|
|
249
523
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const existingIdx = currentState.sessions.findIndex(
|
|
262
|
-
(s: AuthSession) =>
|
|
263
|
-
s.user.id === newSession.user.id && s.company === newSession.company,
|
|
524
|
+
return await upsertSession(
|
|
525
|
+
{
|
|
526
|
+
user: data.user,
|
|
527
|
+
token: data.token,
|
|
528
|
+
refresh_token: data.refresh_token,
|
|
529
|
+
challenges: data.challenges,
|
|
530
|
+
expires: data.expires,
|
|
531
|
+
session_duration: sessionDuration ?? 900,
|
|
532
|
+
company: params.company,
|
|
533
|
+
},
|
|
534
|
+
'login',
|
|
264
535
|
);
|
|
265
|
-
|
|
266
|
-
let newState: AuthState;
|
|
267
|
-
if (existingIdx !== -1) {
|
|
268
|
-
const updated = [...currentState.sessions];
|
|
269
|
-
updated[existingIdx] = newSession;
|
|
270
|
-
newState = { ...currentState, sessions: updated, activeSessionIndex: existingIdx };
|
|
271
|
-
} else {
|
|
272
|
-
newState = {
|
|
273
|
-
sessions: [...currentState.sessions, newSession],
|
|
274
|
-
activeSessionIndex: currentState.sessions.length,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
await saveAuthState(newState);
|
|
279
|
-
notifyErrorListeners(null);
|
|
280
|
-
return newSession;
|
|
281
536
|
} catch (e) {
|
|
282
537
|
const error = e instanceof ApiError ? e : new Error('Login failed');
|
|
283
|
-
|
|
538
|
+
setError(error);
|
|
539
|
+
notifyAll({
|
|
540
|
+
type: 'error',
|
|
541
|
+
error,
|
|
542
|
+
});
|
|
284
543
|
throw e;
|
|
285
544
|
}
|
|
286
545
|
}
|
|
@@ -309,28 +568,25 @@ export function createAuth(config: AuthConfig = {}): Auth {
|
|
|
309
568
|
const result: any = await authRegister({ client, body, throwOnError: true });
|
|
310
569
|
const data = result?.data ?? result;
|
|
311
570
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
sessions: [...currentState.sessions, newSession],
|
|
325
|
-
activeSessionIndex: currentState.sessions.length,
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
await saveAuthState(newState);
|
|
329
|
-
notifyErrorListeners(null);
|
|
330
|
-
return newSession;
|
|
571
|
+
return await upsertSession(
|
|
572
|
+
{
|
|
573
|
+
user: data.user,
|
|
574
|
+
token: data.token,
|
|
575
|
+
refresh_token: data.refresh_token,
|
|
576
|
+
challenges: data.challenges,
|
|
577
|
+
expires: data.expires,
|
|
578
|
+
session_duration: sessionDuration ?? 900,
|
|
579
|
+
company: params.company,
|
|
580
|
+
},
|
|
581
|
+
'register',
|
|
582
|
+
);
|
|
331
583
|
} catch (e) {
|
|
332
584
|
const error = e instanceof ApiError ? e : new Error('Registration failed');
|
|
333
|
-
|
|
585
|
+
setError(error);
|
|
586
|
+
notifyAll({
|
|
587
|
+
type: 'error',
|
|
588
|
+
error,
|
|
589
|
+
});
|
|
334
590
|
throw e;
|
|
335
591
|
}
|
|
336
592
|
}
|
|
@@ -338,47 +594,45 @@ export function createAuth(config: AuthConfig = {}): Auth {
|
|
|
338
594
|
async function registerCompanyFn(params: RegisterCompanyParams): Promise<AuthSession> {
|
|
339
595
|
await initialize();
|
|
340
596
|
try {
|
|
341
|
-
const
|
|
342
|
-
|
|
597
|
+
const result: any = await authRegisterCompany({
|
|
598
|
+
client,
|
|
599
|
+
body: params,
|
|
600
|
+
throwOnError: true,
|
|
601
|
+
});
|
|
343
602
|
const data = result?.data ?? result;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
await saveAuthState(newState);
|
|
362
|
-
notifyErrorListeners(null);
|
|
363
|
-
return newSession;
|
|
603
|
+
const company =
|
|
604
|
+
typeof params.company === 'string'
|
|
605
|
+
? params.company
|
|
606
|
+
: (params.company as any)?.id;
|
|
607
|
+
|
|
608
|
+
return await upsertSession(
|
|
609
|
+
{
|
|
610
|
+
user: data.user,
|
|
611
|
+
token: data.token,
|
|
612
|
+
refresh_token: data.refresh_token,
|
|
613
|
+
challenges: data.challenges,
|
|
614
|
+
expires: data.expires,
|
|
615
|
+
session_duration: 900,
|
|
616
|
+
company,
|
|
617
|
+
},
|
|
618
|
+
'register-company',
|
|
619
|
+
);
|
|
364
620
|
} catch (e) {
|
|
365
|
-
const error =
|
|
366
|
-
|
|
621
|
+
const error =
|
|
622
|
+
e instanceof ApiError ? e : new Error('Company registration failed');
|
|
623
|
+
setError(error);
|
|
624
|
+
notifyAll({
|
|
625
|
+
type: 'error',
|
|
626
|
+
error,
|
|
627
|
+
});
|
|
367
628
|
throw e;
|
|
368
629
|
}
|
|
369
630
|
}
|
|
370
631
|
|
|
371
632
|
async function logout(): Promise<void> {
|
|
633
|
+
await initialize();
|
|
372
634
|
const currentState = await loadAuthState();
|
|
373
|
-
const session = currentState
|
|
374
|
-
|
|
375
|
-
const newState: AuthState = {
|
|
376
|
-
...currentState,
|
|
377
|
-
sessions: currentState.sessions.filter(
|
|
378
|
-
(_: AuthSession, i: number) => i !== currentState.activeSessionIndex,
|
|
379
|
-
),
|
|
380
|
-
activeSessionIndex: -1,
|
|
381
|
-
};
|
|
635
|
+
const session = getActiveSessionFromState(currentState);
|
|
382
636
|
|
|
383
637
|
if (session?.token) {
|
|
384
638
|
try {
|
|
@@ -389,19 +643,34 @@ export function createAuth(config: AuthConfig = {}): Auth {
|
|
|
389
643
|
throwOnError: true,
|
|
390
644
|
});
|
|
391
645
|
} catch {
|
|
392
|
-
// Continue with local logout even if API call fails
|
|
646
|
+
// Continue with local logout even if API call fails.
|
|
393
647
|
}
|
|
394
648
|
}
|
|
395
649
|
|
|
396
|
-
await
|
|
397
|
-
|
|
650
|
+
await applyState(
|
|
651
|
+
{
|
|
652
|
+
sessions: currentState.sessions.filter(
|
|
653
|
+
(_session, index) => index !== currentState.activeSessionIndex,
|
|
654
|
+
),
|
|
655
|
+
activeSessionIndex: -1,
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
clearExpiredSession: true,
|
|
659
|
+
error: null,
|
|
660
|
+
event: {
|
|
661
|
+
type: 'logout',
|
|
662
|
+
session,
|
|
663
|
+
},
|
|
664
|
+
},
|
|
665
|
+
);
|
|
398
666
|
}
|
|
399
667
|
|
|
400
668
|
async function logoutAll(): Promise<void> {
|
|
669
|
+
await initialize();
|
|
401
670
|
const currentState = await loadAuthState();
|
|
402
671
|
|
|
403
672
|
await Promise.all(
|
|
404
|
-
currentState.sessions.map(async (session
|
|
673
|
+
currentState.sessions.map(async (session) => {
|
|
405
674
|
try {
|
|
406
675
|
await authLogout({
|
|
407
676
|
client,
|
|
@@ -410,26 +679,44 @@ export function createAuth(config: AuthConfig = {}): Auth {
|
|
|
410
679
|
throwOnError: true,
|
|
411
680
|
});
|
|
412
681
|
} catch {
|
|
413
|
-
//
|
|
682
|
+
// Continue clearing local sessions even if API logout fails.
|
|
414
683
|
}
|
|
415
684
|
}),
|
|
416
685
|
);
|
|
417
686
|
|
|
418
|
-
await
|
|
419
|
-
|
|
687
|
+
await applyState(
|
|
688
|
+
{
|
|
689
|
+
sessions: [],
|
|
690
|
+
activeSessionIndex: -1,
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
clearExpiredSession: true,
|
|
694
|
+
error: null,
|
|
695
|
+
event: {
|
|
696
|
+
type: 'logout-all',
|
|
697
|
+
},
|
|
698
|
+
},
|
|
699
|
+
);
|
|
420
700
|
}
|
|
421
701
|
|
|
422
702
|
async function refresh(): Promise<void> {
|
|
423
|
-
if (refreshPromise)
|
|
703
|
+
if (refreshPromise) {
|
|
704
|
+
return refreshPromise;
|
|
705
|
+
}
|
|
424
706
|
|
|
425
707
|
refreshPromise = (async () => {
|
|
426
708
|
isRefreshing = true;
|
|
709
|
+
notifyAll();
|
|
710
|
+
|
|
711
|
+
let sessionBeforeRefresh: AuthSession | null = null;
|
|
712
|
+
|
|
427
713
|
try {
|
|
428
714
|
const currentState = await loadAuthState();
|
|
429
715
|
const idx = currentState.activeSessionIndex;
|
|
430
|
-
const session = currentState
|
|
716
|
+
const session = getActiveSessionFromState(currentState);
|
|
717
|
+
sessionBeforeRefresh = session;
|
|
431
718
|
|
|
432
|
-
if (!session?.token || !session
|
|
719
|
+
if (idx < 0 || !session?.token || !session.refresh_token) {
|
|
433
720
|
throw new Error('No active session, token, or refresh token found');
|
|
434
721
|
}
|
|
435
722
|
|
|
@@ -442,33 +729,60 @@ export function createAuth(config: AuthConfig = {}): Auth {
|
|
|
442
729
|
});
|
|
443
730
|
const data = result?.data ?? result;
|
|
444
731
|
|
|
445
|
-
const updatedSessions =
|
|
732
|
+
const updatedSessions = cloneSessions(currentState.sessions);
|
|
446
733
|
updatedSessions[idx] = {
|
|
447
734
|
...session,
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
735
|
+
token: data.token ?? session.token,
|
|
736
|
+
refresh_token: data.refresh_token ?? session.refresh_token,
|
|
737
|
+
expires: data.expires ?? session.expires,
|
|
738
|
+
session_duration: session.session_duration ?? 900,
|
|
739
|
+
company: getSessionCompany(session),
|
|
452
740
|
};
|
|
453
741
|
|
|
454
|
-
await
|
|
455
|
-
|
|
742
|
+
await applyState(
|
|
743
|
+
{
|
|
744
|
+
sessions: updatedSessions,
|
|
745
|
+
activeSessionIndex: idx,
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
clearExpiredSession: true,
|
|
749
|
+
error: null,
|
|
750
|
+
event: {
|
|
751
|
+
type: 'refresh',
|
|
752
|
+
session: updatedSessions[idx],
|
|
753
|
+
},
|
|
754
|
+
},
|
|
755
|
+
);
|
|
456
756
|
} catch (e) {
|
|
457
757
|
const error = e instanceof ApiError ? e : new Error('Refresh failed');
|
|
458
|
-
notifyErrorListeners(error);
|
|
459
|
-
|
|
460
758
|
const currentState = await loadAuthState();
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
),
|
|
466
|
-
|
|
467
|
-
|
|
759
|
+
const expiredSession =
|
|
760
|
+
sessionBeforeRefresh ??
|
|
761
|
+
getActiveSessionFromState(currentState);
|
|
762
|
+
const nextSessions = currentState.sessions.filter(
|
|
763
|
+
(_session, index) => index !== currentState.activeSessionIndex,
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
await applyState(
|
|
767
|
+
{
|
|
768
|
+
sessions: nextSessions,
|
|
769
|
+
activeSessionIndex: -1,
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
expiredSession: expiredSession ?? null,
|
|
773
|
+
error,
|
|
774
|
+
event: {
|
|
775
|
+
type: 'session-expired',
|
|
776
|
+
session: expiredSession,
|
|
777
|
+
error,
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
);
|
|
468
781
|
throw e;
|
|
469
782
|
} finally {
|
|
470
783
|
isRefreshing = false;
|
|
471
784
|
refreshPromise = null;
|
|
785
|
+
notifyAll();
|
|
472
786
|
}
|
|
473
787
|
})();
|
|
474
788
|
|
|
@@ -476,11 +790,15 @@ export function createAuth(config: AuthConfig = {}): Auth {
|
|
|
476
790
|
}
|
|
477
791
|
|
|
478
792
|
async function getToken(): Promise<string | undefined> {
|
|
479
|
-
if (permanentToken)
|
|
793
|
+
if (permanentToken) {
|
|
794
|
+
return permanentToken;
|
|
795
|
+
}
|
|
480
796
|
|
|
481
797
|
await initialize();
|
|
482
798
|
const session = getActiveSession();
|
|
483
|
-
if (!session)
|
|
799
|
+
if (!session) {
|
|
800
|
+
return undefined;
|
|
801
|
+
}
|
|
484
802
|
|
|
485
803
|
if (session.expires && isTokenExpired(session.expires)) {
|
|
486
804
|
try {
|
|
@@ -498,17 +816,34 @@ export function createAuth(config: AuthConfig = {}): Auth {
|
|
|
498
816
|
userId: string,
|
|
499
817
|
company?: string,
|
|
500
818
|
): Promise<AuthSession | null> {
|
|
819
|
+
await initialize();
|
|
501
820
|
const currentState = await loadAuthState();
|
|
502
821
|
const idx = currentState.sessions.findIndex(
|
|
503
|
-
(
|
|
822
|
+
(session) =>
|
|
823
|
+
session.user.id === userId && getSessionCompany(session) === company,
|
|
504
824
|
);
|
|
505
825
|
|
|
506
|
-
if (idx === -1)
|
|
826
|
+
if (idx === -1) {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
507
829
|
|
|
508
|
-
|
|
509
|
-
|
|
830
|
+
await applyState(
|
|
831
|
+
{
|
|
832
|
+
sessions: currentState.sessions,
|
|
833
|
+
activeSessionIndex: idx,
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
clearExpiredSession: true,
|
|
837
|
+
error: null,
|
|
838
|
+
event: {
|
|
839
|
+
type: 'session-switched',
|
|
840
|
+
session: currentState.sessions[idx],
|
|
841
|
+
},
|
|
842
|
+
},
|
|
843
|
+
);
|
|
510
844
|
|
|
511
|
-
|
|
845
|
+
const session = getActiveSession();
|
|
846
|
+
if (session?.expires && isTokenExpired(session.expires)) {
|
|
512
847
|
await refresh();
|
|
513
848
|
return getActiveSession();
|
|
514
849
|
}
|
|
@@ -517,23 +852,272 @@ export function createAuth(config: AuthConfig = {}): Auth {
|
|
|
517
852
|
}
|
|
518
853
|
|
|
519
854
|
async function clearAllSessions(): Promise<void> {
|
|
520
|
-
await
|
|
521
|
-
|
|
855
|
+
await initialize();
|
|
856
|
+
await applyState(
|
|
857
|
+
{
|
|
858
|
+
sessions: [],
|
|
859
|
+
activeSessionIndex: -1,
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
clearExpiredSession: true,
|
|
863
|
+
error: null,
|
|
864
|
+
event: {
|
|
865
|
+
type: 'session-cleared',
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
);
|
|
522
869
|
}
|
|
523
870
|
|
|
524
871
|
async function deleteChallenge(challengeId: string): Promise<void> {
|
|
872
|
+
await initialize();
|
|
525
873
|
const currentState = await loadAuthState();
|
|
526
874
|
const idx = currentState.activeSessionIndex;
|
|
527
|
-
|
|
875
|
+
|
|
876
|
+
if (idx < 0 || idx >= currentState.sessions.length) {
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
528
879
|
|
|
529
880
|
const session = currentState.sessions[idx];
|
|
530
|
-
const updatedSessions =
|
|
881
|
+
const updatedSessions = cloneSessions(currentState.sessions);
|
|
531
882
|
updatedSessions[idx] = {
|
|
532
883
|
...session,
|
|
533
|
-
challenges:
|
|
884
|
+
challenges:
|
|
885
|
+
session.challenges?.filter((challenge: any) => challenge.id !== challengeId) ||
|
|
886
|
+
[],
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
await applyState(
|
|
890
|
+
{
|
|
891
|
+
sessions: updatedSessions,
|
|
892
|
+
activeSessionIndex: idx,
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
error: null,
|
|
896
|
+
},
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
async function importToken(
|
|
901
|
+
token: string,
|
|
902
|
+
options: ImportTokenOptions = {},
|
|
903
|
+
): Promise<AuthSession> {
|
|
904
|
+
await initialize();
|
|
905
|
+
const user = await fetchUserForToken(token);
|
|
906
|
+
const sessionDuration = options.sessionDuration ?? 900;
|
|
907
|
+
|
|
908
|
+
return upsertSession(
|
|
909
|
+
{
|
|
910
|
+
user,
|
|
911
|
+
token,
|
|
912
|
+
refresh_token: '',
|
|
913
|
+
challenges: [],
|
|
914
|
+
expires: options.expires ?? Date.now() + 30 * 24 * 60 * 60 * 1000,
|
|
915
|
+
session_duration: sessionDuration,
|
|
916
|
+
company: options.company ?? user?.company,
|
|
917
|
+
},
|
|
918
|
+
'session-imported',
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
async function validateActiveSession(
|
|
923
|
+
options: ValidateSessionOptions = {},
|
|
924
|
+
): Promise<boolean> {
|
|
925
|
+
await initialize();
|
|
926
|
+
if (!getActiveSession()?.token) {
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const retryCount = options.retryCount ?? 1;
|
|
931
|
+
const retryDelayMs = options.retryDelayMs ?? 400;
|
|
932
|
+
let refreshAttempted = false;
|
|
933
|
+
|
|
934
|
+
for (let attempt = 0; attempt <= retryCount; attempt += 1) {
|
|
935
|
+
const activeSession = getActiveSession();
|
|
936
|
+
|
|
937
|
+
if (!activeSession?.token) {
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
try {
|
|
942
|
+
await fetchUserForToken(activeSession.token);
|
|
943
|
+
return true;
|
|
944
|
+
} catch (error) {
|
|
945
|
+
const status =
|
|
946
|
+
error instanceof ApiError
|
|
947
|
+
? error.status
|
|
948
|
+
: typeof error === 'object' &&
|
|
949
|
+
error &&
|
|
950
|
+
'status' in error &&
|
|
951
|
+
typeof (error as any).status === 'number'
|
|
952
|
+
? (error as any).status
|
|
953
|
+
: undefined;
|
|
954
|
+
|
|
955
|
+
if (status === 401 || status === 403) {
|
|
956
|
+
if (!refreshAttempted && activeSession.refresh_token) {
|
|
957
|
+
refreshAttempted = true;
|
|
958
|
+
|
|
959
|
+
try {
|
|
960
|
+
await refresh();
|
|
961
|
+
} catch {
|
|
962
|
+
await expireActiveSession();
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const refreshedSession = getActiveSession();
|
|
967
|
+
if (!refreshedSession?.token) {
|
|
968
|
+
return false;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
try {
|
|
972
|
+
await fetchUserForToken(refreshedSession.token);
|
|
973
|
+
return true;
|
|
974
|
+
} catch {
|
|
975
|
+
await expireActiveSession();
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (attempt < retryCount) {
|
|
981
|
+
await sleep(retryDelayMs);
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
await expireActiveSession();
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if (attempt < retryCount) {
|
|
989
|
+
await sleep(retryDelayMs);
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return false;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
async function syncActiveSessionUser(): Promise<AuthSession | null> {
|
|
1001
|
+
await initialize();
|
|
1002
|
+
const currentState = await loadAuthState();
|
|
1003
|
+
const idx = currentState.activeSessionIndex;
|
|
1004
|
+
const session = getActiveSessionFromState(currentState);
|
|
1005
|
+
|
|
1006
|
+
if (idx < 0 || !session?.token) {
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const user = await fetchUserForToken(session.token);
|
|
1011
|
+
const updatedSession: AuthSession = {
|
|
1012
|
+
...session,
|
|
1013
|
+
user: {
|
|
1014
|
+
...session.user,
|
|
1015
|
+
...user,
|
|
1016
|
+
},
|
|
1017
|
+
company: user?.company ?? getSessionCompany(session),
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
const updatedSessions = cloneSessions(currentState.sessions);
|
|
1021
|
+
updatedSessions[idx] = updatedSession;
|
|
1022
|
+
|
|
1023
|
+
await applyState(
|
|
1024
|
+
{
|
|
1025
|
+
sessions: updatedSessions,
|
|
1026
|
+
activeSessionIndex: idx,
|
|
1027
|
+
},
|
|
1028
|
+
{
|
|
1029
|
+
error: null,
|
|
1030
|
+
event: {
|
|
1031
|
+
type: 'session-updated',
|
|
1032
|
+
session: updatedSession,
|
|
1033
|
+
},
|
|
1034
|
+
},
|
|
1035
|
+
);
|
|
1036
|
+
|
|
1037
|
+
return updatedSession;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
async function updateSession(
|
|
1041
|
+
userId: string,
|
|
1042
|
+
company: string | undefined,
|
|
1043
|
+
patch: SessionPatch,
|
|
1044
|
+
): Promise<AuthSession | null> {
|
|
1045
|
+
await initialize();
|
|
1046
|
+
const currentState = await loadAuthState();
|
|
1047
|
+
const idx = currentState.sessions.findIndex(
|
|
1048
|
+
(session) =>
|
|
1049
|
+
session.user.id === userId && getSessionCompany(session) === company,
|
|
1050
|
+
);
|
|
1051
|
+
|
|
1052
|
+
if (idx === -1) {
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
const currentSession = currentState.sessions[idx];
|
|
1057
|
+
const updatedSession =
|
|
1058
|
+
typeof patch === 'function'
|
|
1059
|
+
? patch(currentSession)
|
|
1060
|
+
: {
|
|
1061
|
+
...currentSession,
|
|
1062
|
+
...patch,
|
|
1063
|
+
user: patch.user
|
|
1064
|
+
? {
|
|
1065
|
+
...currentSession.user,
|
|
1066
|
+
...patch.user,
|
|
1067
|
+
}
|
|
1068
|
+
: currentSession.user,
|
|
1069
|
+
company:
|
|
1070
|
+
patch.company ??
|
|
1071
|
+
patch.user?.company ??
|
|
1072
|
+
getSessionCompany(currentSession),
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
const updatedSessions = cloneSessions(currentState.sessions);
|
|
1076
|
+
updatedSessions[idx] = updatedSession;
|
|
1077
|
+
|
|
1078
|
+
await applyState(
|
|
1079
|
+
{
|
|
1080
|
+
sessions: updatedSessions,
|
|
1081
|
+
activeSessionIndex: currentState.activeSessionIndex,
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
error: null,
|
|
1085
|
+
event: {
|
|
1086
|
+
type: 'session-updated',
|
|
1087
|
+
session: updatedSession,
|
|
1088
|
+
},
|
|
1089
|
+
},
|
|
1090
|
+
);
|
|
1091
|
+
|
|
1092
|
+
return updatedSession;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
async function expireActiveSession(): Promise<AuthRecoveryState> {
|
|
1096
|
+
await initialize();
|
|
1097
|
+
const currentState = await loadAuthState();
|
|
1098
|
+
const session = getActiveSessionFromState(currentState);
|
|
1099
|
+
|
|
1100
|
+
if (!session) {
|
|
1101
|
+
return buildRecoveryState(currentState);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const nextState: AuthState = {
|
|
1105
|
+
sessions: currentState.sessions.filter(
|
|
1106
|
+
(_entry, index) => index !== currentState.activeSessionIndex,
|
|
1107
|
+
),
|
|
1108
|
+
activeSessionIndex: -1,
|
|
534
1109
|
};
|
|
535
1110
|
|
|
536
|
-
await
|
|
1111
|
+
await applyState(nextState, {
|
|
1112
|
+
expiredSession: session,
|
|
1113
|
+
error: null,
|
|
1114
|
+
event: {
|
|
1115
|
+
type: 'session-expired',
|
|
1116
|
+
session,
|
|
1117
|
+
},
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
return buildRecoveryState();
|
|
537
1121
|
}
|
|
538
1122
|
|
|
539
1123
|
return {
|
|
@@ -545,22 +1129,42 @@ export function createAuth(config: AuthConfig = {}): Auth {
|
|
|
545
1129
|
refresh,
|
|
546
1130
|
getToken,
|
|
547
1131
|
getActiveSession,
|
|
548
|
-
getSessions: () =>
|
|
1132
|
+
getSessions: () => cloneSessions(sessions),
|
|
549
1133
|
getSessionsByCompany: (company: string) =>
|
|
550
|
-
sessions.filter((
|
|
1134
|
+
sessions.filter((session) => getSessionCompany(session) === company),
|
|
1135
|
+
getState,
|
|
1136
|
+
getStatus: () => getStatus(),
|
|
1137
|
+
getRecoveryState: () => buildRecoveryState(),
|
|
551
1138
|
switchToSession,
|
|
552
1139
|
clearAllSessions,
|
|
553
1140
|
deleteChallenge,
|
|
1141
|
+
importToken,
|
|
1142
|
+
validateActiveSession,
|
|
1143
|
+
syncActiveSessionUser,
|
|
1144
|
+
updateSession,
|
|
1145
|
+
expireActiveSession,
|
|
554
1146
|
subscribe(listener: SessionListener): () => void {
|
|
555
1147
|
sessionListeners.push(listener);
|
|
556
1148
|
return () => {
|
|
557
|
-
sessionListeners = sessionListeners.filter((
|
|
1149
|
+
sessionListeners = sessionListeners.filter((entry) => entry !== listener);
|
|
558
1150
|
};
|
|
559
1151
|
},
|
|
560
1152
|
subscribeToErrors(listener: ErrorListener): () => void {
|
|
561
1153
|
errorListeners.push(listener);
|
|
562
1154
|
return () => {
|
|
563
|
-
errorListeners = errorListeners.filter((
|
|
1155
|
+
errorListeners = errorListeners.filter((entry) => entry !== listener);
|
|
1156
|
+
};
|
|
1157
|
+
},
|
|
1158
|
+
subscribeToState(listener: AuthStateListener): () => void {
|
|
1159
|
+
stateListeners.push(listener);
|
|
1160
|
+
return () => {
|
|
1161
|
+
stateListeners = stateListeners.filter((entry) => entry !== listener);
|
|
1162
|
+
};
|
|
1163
|
+
},
|
|
1164
|
+
subscribeToEvents(listener: AuthEventListener): () => void {
|
|
1165
|
+
eventListeners.push(listener);
|
|
1166
|
+
return () => {
|
|
1167
|
+
eventListeners = eventListeners.filter((entry) => entry !== listener);
|
|
564
1168
|
};
|
|
565
1169
|
},
|
|
566
1170
|
get baseUrl() {
|