rehive 4.1.4 → 4.2.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.
Files changed (162) hide show
  1. package/dist/admin.d.mts +43 -106
  2. package/dist/admin.d.ts +43 -106
  3. package/dist/admin.js +1 -1
  4. package/dist/admin.mjs +1 -1
  5. package/dist/auth.d.mts +2 -2
  6. package/dist/auth.d.ts +2 -2
  7. package/dist/auth.js +1 -1
  8. package/dist/auth.mjs +1 -1
  9. package/dist/{chunk-AVPKHYGS.js → chunk-7USVOK77.js} +5 -5
  10. package/dist/{chunk-X6YDNXYV.js → chunk-ERD2XKSM.js} +1 -1
  11. package/dist/chunk-G3M5QMRX.js +1 -0
  12. package/dist/chunk-HSPTEN45.mjs +1 -0
  13. package/dist/chunk-HWXE5WBF.js +7 -0
  14. package/dist/chunk-MUN3POSM.mjs +7 -0
  15. package/dist/{chunk-4WSHB6U6.mjs → chunk-PCR54D6A.mjs} +1 -1
  16. package/dist/chunk-ZUZHOLAX.mjs +7 -0
  17. package/dist/{create-api-client-ebyTfhcm.d.ts → create-api-client-ADSlg8HX.d.ts} +1 -1
  18. package/dist/{create-api-client-C-LEZku2.d.mts → create-api-client-B4Q01gHp.d.mts} +1 -1
  19. package/dist/{create-auth-DsnHpEf0.d.mts → create-auth-CT_IFt3T.d.mts} +53 -47
  20. package/dist/{create-auth-DsnHpEf0.d.ts → create-auth-CT_IFt3T.d.ts} +53 -47
  21. package/dist/extensions/alchemy.d.mts +3 -3
  22. package/dist/extensions/alchemy.d.ts +3 -3
  23. package/dist/extensions/alchemy.js +5 -5
  24. package/dist/extensions/alchemy.mjs +5 -5
  25. package/dist/extensions/app.d.mts +3 -3
  26. package/dist/extensions/app.d.ts +3 -3
  27. package/dist/extensions/app.js +5 -5
  28. package/dist/extensions/app.mjs +5 -5
  29. package/dist/extensions/billing.d.mts +3 -3
  30. package/dist/extensions/billing.d.ts +3 -3
  31. package/dist/extensions/billing.js +5 -5
  32. package/dist/extensions/billing.mjs +5 -5
  33. package/dist/extensions/bridge.d.mts +16 -190
  34. package/dist/extensions/bridge.d.ts +16 -190
  35. package/dist/extensions/bridge.js +5 -5
  36. package/dist/extensions/bridge.mjs +5 -5
  37. package/dist/extensions/builder.d.mts +3 -3
  38. package/dist/extensions/builder.d.ts +3 -3
  39. package/dist/extensions/builder.js +5 -5
  40. package/dist/extensions/builder.mjs +5 -5
  41. package/dist/extensions/business.d.mts +47 -82
  42. package/dist/extensions/business.d.ts +47 -82
  43. package/dist/extensions/business.js +5 -5
  44. package/dist/extensions/business.mjs +5 -5
  45. package/dist/extensions/conversion.d.mts +3 -3
  46. package/dist/extensions/conversion.d.ts +3 -3
  47. package/dist/extensions/conversion.js +5 -5
  48. package/dist/extensions/conversion.mjs +5 -5
  49. package/dist/extensions/mass-send.d.mts +3 -3
  50. package/dist/extensions/mass-send.d.ts +3 -3
  51. package/dist/extensions/mass-send.js +5 -5
  52. package/dist/extensions/mass-send.mjs +5 -5
  53. package/dist/extensions/notifications.d.mts +3 -3
  54. package/dist/extensions/notifications.d.ts +3 -3
  55. package/dist/extensions/notifications.js +5 -5
  56. package/dist/extensions/notifications.mjs +5 -5
  57. package/dist/extensions/payment-requests.d.mts +3 -7
  58. package/dist/extensions/payment-requests.d.ts +3 -7
  59. package/dist/extensions/payment-requests.js +5 -5
  60. package/dist/extensions/payment-requests.mjs +5 -5
  61. package/dist/extensions/products.d.mts +3 -3
  62. package/dist/extensions/products.d.ts +3 -3
  63. package/dist/extensions/products.js +5 -5
  64. package/dist/extensions/products.mjs +5 -5
  65. package/dist/extensions/rain.d.mts +3 -3
  66. package/dist/extensions/rain.d.ts +3 -3
  67. package/dist/extensions/rain.js +5 -5
  68. package/dist/extensions/rain.mjs +5 -5
  69. package/dist/extensions/rewards.d.mts +3 -3
  70. package/dist/extensions/rewards.d.ts +3 -3
  71. package/dist/extensions/rewards.js +5 -5
  72. package/dist/extensions/rewards.mjs +5 -5
  73. package/dist/extensions/stellar-testnet.d.mts +3 -3
  74. package/dist/extensions/stellar-testnet.d.ts +3 -3
  75. package/dist/extensions/stellar-testnet.js +5 -5
  76. package/dist/extensions/stellar-testnet.mjs +5 -5
  77. package/dist/extensions/stellar.d.mts +3 -3
  78. package/dist/extensions/stellar.d.ts +3 -3
  79. package/dist/extensions/stellar.js +5 -5
  80. package/dist/extensions/stellar.mjs +5 -5
  81. package/dist/index.d.mts +2 -2
  82. package/dist/index.d.ts +2 -2
  83. package/dist/index.js +1 -1
  84. package/dist/index.mjs +1 -1
  85. package/dist/react.d.mts +11 -3
  86. package/dist/react.d.ts +11 -3
  87. package/dist/react.js +1 -1
  88. package/dist/react.mjs +1 -1
  89. package/dist/user.d.mts +6 -16
  90. package/dist/user.d.ts +6 -16
  91. package/dist/user.js +1 -1
  92. package/dist/user.mjs +1 -1
  93. package/package.json +1 -1
  94. package/src/auth/create-auth.ts +773 -169
  95. package/src/auth/index.ts +25 -2
  96. package/src/auth/types/index.ts +48 -0
  97. package/src/extensions/alchemy/openapi-ts/client/client.gen.ts +3 -5
  98. package/src/extensions/alchemy/openapi-ts/core/bodySerializer.gen.ts +8 -6
  99. package/src/extensions/alchemy/openapi-ts/core/params.gen.ts +1 -1
  100. package/src/extensions/app/openapi-ts/client/client.gen.ts +3 -5
  101. package/src/extensions/app/openapi-ts/core/bodySerializer.gen.ts +8 -6
  102. package/src/extensions/app/openapi-ts/core/params.gen.ts +1 -1
  103. package/src/extensions/billing/openapi-ts/client/client.gen.ts +3 -5
  104. package/src/extensions/billing/openapi-ts/core/bodySerializer.gen.ts +8 -6
  105. package/src/extensions/billing/openapi-ts/core/params.gen.ts +1 -1
  106. package/src/extensions/bridge/openapi-ts/client/client.gen.ts +3 -5
  107. package/src/extensions/bridge/openapi-ts/core/bodySerializer.gen.ts +8 -6
  108. package/src/extensions/bridge/openapi-ts/core/params.gen.ts +1 -1
  109. package/src/extensions/bridge/openapi-ts/index.ts +2 -2
  110. package/src/extensions/bridge/openapi-ts/sdk.gen.ts +1 -43
  111. package/src/extensions/bridge/openapi-ts/types.gen.ts +11 -201
  112. package/src/extensions/builder/openapi-ts/client/client.gen.ts +3 -5
  113. package/src/extensions/builder/openapi-ts/core/bodySerializer.gen.ts +8 -6
  114. package/src/extensions/builder/openapi-ts/core/params.gen.ts +1 -1
  115. package/src/extensions/business/openapi-ts/client/client.gen.ts +3 -5
  116. package/src/extensions/business/openapi-ts/core/bodySerializer.gen.ts +8 -6
  117. package/src/extensions/business/openapi-ts/core/params.gen.ts +1 -1
  118. package/src/extensions/business/openapi-ts/types.gen.ts +44 -79
  119. package/src/extensions/conversion/openapi-ts/client/client.gen.ts +3 -5
  120. package/src/extensions/conversion/openapi-ts/core/bodySerializer.gen.ts +8 -6
  121. package/src/extensions/conversion/openapi-ts/core/params.gen.ts +1 -1
  122. package/src/extensions/mass-send/openapi-ts/client/client.gen.ts +3 -5
  123. package/src/extensions/mass-send/openapi-ts/core/bodySerializer.gen.ts +8 -6
  124. package/src/extensions/mass-send/openapi-ts/core/params.gen.ts +1 -1
  125. package/src/extensions/notifications/openapi-ts/client/client.gen.ts +3 -5
  126. package/src/extensions/notifications/openapi-ts/core/bodySerializer.gen.ts +8 -6
  127. package/src/extensions/notifications/openapi-ts/core/params.gen.ts +1 -1
  128. package/src/extensions/payment-requests/openapi-ts/client/client.gen.ts +3 -5
  129. package/src/extensions/payment-requests/openapi-ts/core/bodySerializer.gen.ts +8 -6
  130. package/src/extensions/payment-requests/openapi-ts/core/params.gen.ts +1 -1
  131. package/src/extensions/payment-requests/openapi-ts/types.gen.ts +0 -4
  132. package/src/extensions/products/openapi-ts/client/client.gen.ts +3 -5
  133. package/src/extensions/products/openapi-ts/core/bodySerializer.gen.ts +8 -6
  134. package/src/extensions/products/openapi-ts/core/params.gen.ts +1 -1
  135. package/src/extensions/rain/openapi-ts/client/client.gen.ts +3 -5
  136. package/src/extensions/rain/openapi-ts/core/bodySerializer.gen.ts +8 -6
  137. package/src/extensions/rain/openapi-ts/core/params.gen.ts +1 -1
  138. package/src/extensions/rewards/openapi-ts/client/client.gen.ts +3 -5
  139. package/src/extensions/rewards/openapi-ts/core/bodySerializer.gen.ts +8 -6
  140. package/src/extensions/rewards/openapi-ts/core/params.gen.ts +1 -1
  141. package/src/extensions/stellar/openapi-ts/client/client.gen.ts +3 -5
  142. package/src/extensions/stellar/openapi-ts/core/bodySerializer.gen.ts +8 -6
  143. package/src/extensions/stellar/openapi-ts/core/params.gen.ts +1 -1
  144. package/src/extensions/stellar-testnet/openapi-ts/client/client.gen.ts +3 -5
  145. package/src/extensions/stellar-testnet/openapi-ts/core/bodySerializer.gen.ts +8 -6
  146. package/src/extensions/stellar-testnet/openapi-ts/core/params.gen.ts +1 -1
  147. package/src/platform/admin/openapi-ts/client/client.gen.ts +3 -5
  148. package/src/platform/admin/openapi-ts/core/bodySerializer.gen.ts +8 -6
  149. package/src/platform/admin/openapi-ts/core/params.gen.ts +1 -1
  150. package/src/platform/admin/openapi-ts/sdk.gen.ts +10 -10
  151. package/src/platform/admin/openapi-ts/types.gen.ts +40 -103
  152. package/src/platform/user/openapi-ts/client/client.gen.ts +3 -5
  153. package/src/platform/user/openapi-ts/core/bodySerializer.gen.ts +8 -6
  154. package/src/platform/user/openapi-ts/core/params.gen.ts +1 -1
  155. package/src/platform/user/openapi-ts/index.ts +2 -2
  156. package/src/platform/user/openapi-ts/sdk.gen.ts +3 -29
  157. package/src/platform/user/openapi-ts/types.gen.ts +7 -54
  158. package/dist/chunk-KUT5NSX7.mjs +0 -7
  159. package/dist/chunk-KZWBOQHZ.mjs +0 -7
  160. package/dist/chunk-PE6PG7ZE.js +0 -1
  161. package/dist/chunk-UYYU5OJ4.js +0 -7
  162. package/dist/chunk-Z7BUNKND.mjs +0 -1
@@ -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
- SessionListener,
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(normalizeFetch(globalThis.fetch)),
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 getActiveSession(): AuthSession | null {
136
- if (activeSessionIndex >= 0 && activeSessionIndex < sessions.length) {
137
- return sessions[activeSessionIndex];
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 notifySessionListeners(): void {
143
- const session = getActiveSession();
144
- sessionListeners.forEach((listener) => listener(session));
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
+ };
145
219
  }
146
220
 
147
- function notifyErrorListeners(error: Error | null): void {
148
- errorListeners.forEach((listener) => listener(error));
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';
238
+ }
239
+
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 = state.activeSessionIndex;
173
- return state;
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 === storageKey && event.newValue) {
198
- try {
199
- const newState = JSON.parse(event.newValue);
200
- sessions = Array.isArray(newState.sessions) ? newState.sessions : [];
201
- activeSessionIndex =
202
- typeof newState.activeSessionIndex === 'number'
203
- ? newState.activeSessionIndex
204
- : -1;
205
- notifySessionListeners();
206
- } catch (error) {
207
- console.error('Failed to sync auth state from storage event:', error);
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) return;
216
- initialized = true;
410
+ if (initialized) {
411
+ return;
412
+ }
413
+ if (initializePromise) {
414
+ return initializePromise;
415
+ }
416
+
417
+ initializePromise = (async () => {
418
+ if (!permanentToken) {
419
+ if (enableCrossTabSync) {
420
+ setupCrossTabSync();
421
+ }
422
+ await loadAuthState();
423
+ }
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
+ });
217
438
 
218
- if (!permanentToken) {
219
- if (enableCrossTabSync) {
220
- setupCrossTabSync();
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
221
446
  }
222
- await loadAuthState();
223
- notifySessionListeners();
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
  }
@@ -244,40 +518,25 @@ export function createAuth(config: AuthConfig = {}): Auth {
244
518
  const result: any = await authLogin({ client, body, throwOnError: true });
245
519
  const data = result?.data ?? result;
246
520
 
247
- const newSession: AuthSession = {
248
- user: data.user,
249
- token: data.token,
250
- refresh_token: data.refresh_token,
251
- challenges: data.challenges,
252
- expires: data.expires,
253
- session_duration: sessionDuration,
254
- company: params.company,
255
- };
256
-
257
- const currentState = await loadAuthState();
258
- const existingIdx = currentState.sessions.findIndex(
259
- (s: AuthSession) =>
260
- s.user.id === newSession.user.id && s.company === newSession.company,
521
+ return await upsertSession(
522
+ {
523
+ user: data.user,
524
+ token: data.token,
525
+ refresh_token: data.refresh_token,
526
+ challenges: data.challenges,
527
+ expires: data.expires,
528
+ session_duration: sessionDuration,
529
+ company: params.company,
530
+ },
531
+ 'login',
261
532
  );
262
-
263
- let newState: AuthState;
264
- if (existingIdx !== -1) {
265
- const updated = [...currentState.sessions];
266
- updated[existingIdx] = newSession;
267
- newState = { ...currentState, sessions: updated, activeSessionIndex: existingIdx };
268
- } else {
269
- newState = {
270
- sessions: [...currentState.sessions, newSession],
271
- activeSessionIndex: currentState.sessions.length,
272
- };
273
- }
274
-
275
- await saveAuthState(newState);
276
- notifyErrorListeners(null);
277
- return newSession;
278
533
  } catch (e) {
279
534
  const error = e instanceof ApiError ? e : new Error('Login failed');
280
- notifyErrorListeners(error);
535
+ setError(error);
536
+ notifyAll({
537
+ type: 'error',
538
+ error,
539
+ });
281
540
  throw e;
282
541
  }
283
542
  }
@@ -303,28 +562,25 @@ export function createAuth(config: AuthConfig = {}): Auth {
303
562
  const result: any = await authRegister({ client, body, throwOnError: true });
304
563
  const data = result?.data ?? result;
305
564
 
306
- const newSession: AuthSession = {
307
- user: data.user,
308
- token: data.token,
309
- refresh_token: data.refresh_token,
310
- challenges: data.challenges,
311
- expires: data.expires,
312
- session_duration: sessionDuration,
313
- company: params.company,
314
- };
315
-
316
- const currentState = await loadAuthState();
317
- const newState: AuthState = {
318
- sessions: [...currentState.sessions, newSession],
319
- activeSessionIndex: currentState.sessions.length,
320
- };
321
-
322
- await saveAuthState(newState);
323
- notifyErrorListeners(null);
324
- return newSession;
565
+ return await upsertSession(
566
+ {
567
+ user: data.user,
568
+ token: data.token,
569
+ refresh_token: data.refresh_token,
570
+ challenges: data.challenges,
571
+ expires: data.expires,
572
+ session_duration: sessionDuration,
573
+ company: params.company,
574
+ },
575
+ 'register',
576
+ );
325
577
  } catch (e) {
326
578
  const error = e instanceof ApiError ? e : new Error('Registration failed');
327
- notifyErrorListeners(error);
579
+ setError(error);
580
+ notifyAll({
581
+ type: 'error',
582
+ error,
583
+ });
328
584
  throw e;
329
585
  }
330
586
  }
@@ -332,47 +588,45 @@ export function createAuth(config: AuthConfig = {}): Auth {
332
588
  async function registerCompanyFn(params: RegisterCompanyParams): Promise<AuthSession> {
333
589
  await initialize();
334
590
  try {
335
- const body: RegisterCompanyRequestWritable = params;
336
- const result: any = await authRegisterCompany({ client, body, throwOnError: true });
591
+ const result: any = await authRegisterCompany({
592
+ client,
593
+ body: params,
594
+ throwOnError: true,
595
+ });
337
596
  const data = result?.data ?? result;
338
-
339
- const newSession: AuthSession = {
340
- user: data.user,
341
- token: data.token,
342
- refresh_token: data.refresh_token,
343
- challenges: data.challenges,
344
- expires: data.expires,
345
- session_duration: 900,
346
- company: typeof params.company === 'string' ? params.company : (params.company as any)?.id,
347
- };
348
-
349
- const currentState = await loadAuthState();
350
- const newState: AuthState = {
351
- sessions: [...currentState.sessions, newSession],
352
- activeSessionIndex: currentState.sessions.length,
353
- };
354
-
355
- await saveAuthState(newState);
356
- notifyErrorListeners(null);
357
- return newSession;
597
+ const company =
598
+ typeof params.company === 'string'
599
+ ? params.company
600
+ : (params.company as any)?.id;
601
+
602
+ return await upsertSession(
603
+ {
604
+ user: data.user,
605
+ token: data.token,
606
+ refresh_token: data.refresh_token,
607
+ challenges: data.challenges,
608
+ expires: data.expires,
609
+ session_duration: 900,
610
+ company,
611
+ },
612
+ 'register-company',
613
+ );
358
614
  } catch (e) {
359
- const error = e instanceof ApiError ? e : new Error('Company registration failed');
360
- notifyErrorListeners(error);
615
+ const error =
616
+ e instanceof ApiError ? e : new Error('Company registration failed');
617
+ setError(error);
618
+ notifyAll({
619
+ type: 'error',
620
+ error,
621
+ });
361
622
  throw e;
362
623
  }
363
624
  }
364
625
 
365
626
  async function logout(): Promise<void> {
627
+ await initialize();
366
628
  const currentState = await loadAuthState();
367
- const session = currentState.sessions[currentState.activeSessionIndex];
368
-
369
- const newState: AuthState = {
370
- ...currentState,
371
- sessions: currentState.sessions.filter(
372
- (_: AuthSession, i: number) => i !== currentState.activeSessionIndex,
373
- ),
374
- activeSessionIndex: -1,
375
- };
629
+ const session = getActiveSessionFromState(currentState);
376
630
 
377
631
  if (session?.token) {
378
632
  try {
@@ -383,19 +637,34 @@ export function createAuth(config: AuthConfig = {}): Auth {
383
637
  throwOnError: true,
384
638
  });
385
639
  } catch {
386
- // Continue with local logout even if API call fails
640
+ // Continue with local logout even if API call fails.
387
641
  }
388
642
  }
389
643
 
390
- await saveAuthState(newState);
391
- notifyErrorListeners(null);
644
+ await applyState(
645
+ {
646
+ sessions: currentState.sessions.filter(
647
+ (_session, index) => index !== currentState.activeSessionIndex,
648
+ ),
649
+ activeSessionIndex: -1,
650
+ },
651
+ {
652
+ clearExpiredSession: true,
653
+ error: null,
654
+ event: {
655
+ type: 'logout',
656
+ session,
657
+ },
658
+ },
659
+ );
392
660
  }
393
661
 
394
662
  async function logoutAll(): Promise<void> {
663
+ await initialize();
395
664
  const currentState = await loadAuthState();
396
665
 
397
666
  await Promise.all(
398
- currentState.sessions.map(async (session: AuthSession) => {
667
+ currentState.sessions.map(async (session) => {
399
668
  try {
400
669
  await authLogout({
401
670
  client,
@@ -404,26 +673,44 @@ export function createAuth(config: AuthConfig = {}): Auth {
404
673
  throwOnError: true,
405
674
  });
406
675
  } catch {
407
- // Log but continue
676
+ // Continue clearing local sessions even if API logout fails.
408
677
  }
409
678
  }),
410
679
  );
411
680
 
412
- await saveAuthState({ sessions: [], activeSessionIndex: -1 });
413
- notifyErrorListeners(null);
681
+ await applyState(
682
+ {
683
+ sessions: [],
684
+ activeSessionIndex: -1,
685
+ },
686
+ {
687
+ clearExpiredSession: true,
688
+ error: null,
689
+ event: {
690
+ type: 'logout-all',
691
+ },
692
+ },
693
+ );
414
694
  }
415
695
 
416
696
  async function refresh(): Promise<void> {
417
- if (refreshPromise) return refreshPromise;
697
+ if (refreshPromise) {
698
+ return refreshPromise;
699
+ }
418
700
 
419
701
  refreshPromise = (async () => {
420
702
  isRefreshing = true;
703
+ notifyAll();
704
+
705
+ let sessionBeforeRefresh: AuthSession | null = null;
706
+
421
707
  try {
422
708
  const currentState = await loadAuthState();
423
709
  const idx = currentState.activeSessionIndex;
424
- const session = currentState.sessions[idx];
710
+ const session = getActiveSessionFromState(currentState);
711
+ sessionBeforeRefresh = session;
425
712
 
426
- if (!session?.token || !session?.refresh_token) {
713
+ if (idx < 0 || !session?.token || !session.refresh_token) {
427
714
  throw new Error('No active session, token, or refresh token found');
428
715
  }
429
716
 
@@ -435,33 +722,60 @@ export function createAuth(config: AuthConfig = {}): Auth {
435
722
  });
436
723
  const data = result?.data ?? result;
437
724
 
438
- const updatedSessions = [...currentState.sessions];
725
+ const updatedSessions = cloneSessions(currentState.sessions);
439
726
  updatedSessions[idx] = {
440
727
  ...session,
441
- refresh_token: data.refresh_token,
442
- expires: data.expires,
728
+ token: data.token ?? session.token,
729
+ refresh_token: data.refresh_token ?? session.refresh_token,
730
+ expires: data.expires ?? session.expires,
443
731
  session_duration: session.session_duration ?? 900,
444
- company: session.company,
732
+ company: getSessionCompany(session),
445
733
  };
446
734
 
447
- await saveAuthState({ ...currentState, sessions: updatedSessions });
448
- notifyErrorListeners(null);
735
+ await applyState(
736
+ {
737
+ sessions: updatedSessions,
738
+ activeSessionIndex: idx,
739
+ },
740
+ {
741
+ clearExpiredSession: true,
742
+ error: null,
743
+ event: {
744
+ type: 'refresh',
745
+ session: updatedSessions[idx],
746
+ },
747
+ },
748
+ );
449
749
  } catch (e) {
450
750
  const error = e instanceof ApiError ? e : new Error('Refresh failed');
451
- notifyErrorListeners(error);
452
-
453
751
  const currentState = await loadAuthState();
454
- await saveAuthState({
455
- ...currentState,
456
- sessions: currentState.sessions.filter(
457
- (_: AuthSession, i: number) => i !== currentState.activeSessionIndex,
458
- ),
459
- activeSessionIndex: -1,
460
- });
752
+ const expiredSession =
753
+ sessionBeforeRefresh ??
754
+ getActiveSessionFromState(currentState);
755
+ const nextSessions = currentState.sessions.filter(
756
+ (_session, index) => index !== currentState.activeSessionIndex,
757
+ );
758
+
759
+ await applyState(
760
+ {
761
+ sessions: nextSessions,
762
+ activeSessionIndex: -1,
763
+ },
764
+ {
765
+ expiredSession: expiredSession ?? null,
766
+ error,
767
+ event: {
768
+ type: 'session-expired',
769
+ session: expiredSession,
770
+ error,
771
+ },
772
+ },
773
+ );
461
774
  throw e;
462
775
  } finally {
463
776
  isRefreshing = false;
464
777
  refreshPromise = null;
778
+ notifyAll();
465
779
  }
466
780
  })();
467
781
 
@@ -469,11 +783,15 @@ export function createAuth(config: AuthConfig = {}): Auth {
469
783
  }
470
784
 
471
785
  async function getToken(): Promise<string | undefined> {
472
- if (permanentToken) return permanentToken;
786
+ if (permanentToken) {
787
+ return permanentToken;
788
+ }
473
789
 
474
790
  await initialize();
475
791
  const session = getActiveSession();
476
- if (!session) return undefined;
792
+ if (!session) {
793
+ return undefined;
794
+ }
477
795
 
478
796
  if (session.expires && isTokenExpired(session.expires)) {
479
797
  try {
@@ -491,17 +809,34 @@ export function createAuth(config: AuthConfig = {}): Auth {
491
809
  userId: string,
492
810
  company?: string,
493
811
  ): Promise<AuthSession | null> {
812
+ await initialize();
494
813
  const currentState = await loadAuthState();
495
814
  const idx = currentState.sessions.findIndex(
496
- (s: AuthSession) => s.user.id === userId && s.company === company,
815
+ (session) =>
816
+ session.user.id === userId && getSessionCompany(session) === company,
497
817
  );
498
818
 
499
- if (idx === -1) return null;
819
+ if (idx === -1) {
820
+ return null;
821
+ }
500
822
 
501
- const session = currentState.sessions[idx];
502
- await saveAuthState({ ...currentState, activeSessionIndex: idx });
823
+ await applyState(
824
+ {
825
+ sessions: currentState.sessions,
826
+ activeSessionIndex: idx,
827
+ },
828
+ {
829
+ clearExpiredSession: true,
830
+ error: null,
831
+ event: {
832
+ type: 'session-switched',
833
+ session: currentState.sessions[idx],
834
+ },
835
+ },
836
+ );
503
837
 
504
- if (session.expires && isTokenExpired(session.expires)) {
838
+ const session = getActiveSession();
839
+ if (session?.expires && isTokenExpired(session.expires)) {
505
840
  await refresh();
506
841
  return getActiveSession();
507
842
  }
@@ -510,23 +845,272 @@ export function createAuth(config: AuthConfig = {}): Auth {
510
845
  }
511
846
 
512
847
  async function clearAllSessions(): Promise<void> {
513
- await saveAuthState({ sessions: [], activeSessionIndex: -1 });
514
- notifyErrorListeners(null);
848
+ await initialize();
849
+ await applyState(
850
+ {
851
+ sessions: [],
852
+ activeSessionIndex: -1,
853
+ },
854
+ {
855
+ clearExpiredSession: true,
856
+ error: null,
857
+ event: {
858
+ type: 'session-cleared',
859
+ },
860
+ },
861
+ );
515
862
  }
516
863
 
517
864
  async function deleteChallenge(challengeId: string): Promise<void> {
865
+ await initialize();
518
866
  const currentState = await loadAuthState();
519
867
  const idx = currentState.activeSessionIndex;
520
- if (idx < 0 || idx >= currentState.sessions.length) return;
868
+
869
+ if (idx < 0 || idx >= currentState.sessions.length) {
870
+ return;
871
+ }
521
872
 
522
873
  const session = currentState.sessions[idx];
523
- const updatedSessions = [...currentState.sessions];
874
+ const updatedSessions = cloneSessions(currentState.sessions);
524
875
  updatedSessions[idx] = {
525
876
  ...session,
526
- challenges: session.challenges?.filter((c: any) => c.id !== challengeId) || [],
877
+ challenges:
878
+ session.challenges?.filter((challenge: any) => challenge.id !== challengeId) ||
879
+ [],
527
880
  };
528
881
 
529
- await saveAuthState({ ...currentState, sessions: updatedSessions });
882
+ await applyState(
883
+ {
884
+ sessions: updatedSessions,
885
+ activeSessionIndex: idx,
886
+ },
887
+ {
888
+ error: null,
889
+ },
890
+ );
891
+ }
892
+
893
+ async function importToken(
894
+ token: string,
895
+ options: ImportTokenOptions = {},
896
+ ): Promise<AuthSession> {
897
+ await initialize();
898
+ const user = await fetchUserForToken(token);
899
+ const sessionDuration = options.sessionDuration ?? 900;
900
+
901
+ return upsertSession(
902
+ {
903
+ user,
904
+ token,
905
+ refresh_token: '',
906
+ challenges: [],
907
+ expires: options.expires ?? Date.now() + 30 * 24 * 60 * 60 * 1000,
908
+ session_duration: sessionDuration,
909
+ company: options.company ?? user?.company,
910
+ },
911
+ 'session-imported',
912
+ );
913
+ }
914
+
915
+ async function validateActiveSession(
916
+ options: ValidateSessionOptions = {},
917
+ ): Promise<boolean> {
918
+ await initialize();
919
+ if (!getActiveSession()?.token) {
920
+ return false;
921
+ }
922
+
923
+ const retryCount = options.retryCount ?? 1;
924
+ const retryDelayMs = options.retryDelayMs ?? 400;
925
+ let refreshAttempted = false;
926
+
927
+ for (let attempt = 0; attempt <= retryCount; attempt += 1) {
928
+ const activeSession = getActiveSession();
929
+
930
+ if (!activeSession?.token) {
931
+ return false;
932
+ }
933
+
934
+ try {
935
+ await fetchUserForToken(activeSession.token);
936
+ return true;
937
+ } catch (error) {
938
+ const status =
939
+ error instanceof ApiError
940
+ ? error.status
941
+ : typeof error === 'object' &&
942
+ error &&
943
+ 'status' in error &&
944
+ typeof (error as any).status === 'number'
945
+ ? (error as any).status
946
+ : undefined;
947
+
948
+ if (status === 401 || status === 403) {
949
+ if (!refreshAttempted && activeSession.refresh_token) {
950
+ refreshAttempted = true;
951
+
952
+ try {
953
+ await refresh();
954
+ } catch {
955
+ await expireActiveSession();
956
+ return false;
957
+ }
958
+
959
+ const refreshedSession = getActiveSession();
960
+ if (!refreshedSession?.token) {
961
+ return false;
962
+ }
963
+
964
+ try {
965
+ await fetchUserForToken(refreshedSession.token);
966
+ return true;
967
+ } catch {
968
+ await expireActiveSession();
969
+ return false;
970
+ }
971
+ }
972
+
973
+ if (attempt < retryCount) {
974
+ await sleep(retryDelayMs);
975
+ continue;
976
+ }
977
+ await expireActiveSession();
978
+ return false;
979
+ }
980
+
981
+ if (attempt < retryCount) {
982
+ await sleep(retryDelayMs);
983
+ continue;
984
+ }
985
+
986
+ return false;
987
+ }
988
+ }
989
+
990
+ return false;
991
+ }
992
+
993
+ async function syncActiveSessionUser(): Promise<AuthSession | null> {
994
+ await initialize();
995
+ const currentState = await loadAuthState();
996
+ const idx = currentState.activeSessionIndex;
997
+ const session = getActiveSessionFromState(currentState);
998
+
999
+ if (idx < 0 || !session?.token) {
1000
+ return null;
1001
+ }
1002
+
1003
+ const user = await fetchUserForToken(session.token);
1004
+ const updatedSession: AuthSession = {
1005
+ ...session,
1006
+ user: {
1007
+ ...session.user,
1008
+ ...user,
1009
+ },
1010
+ company: user?.company ?? getSessionCompany(session),
1011
+ };
1012
+
1013
+ const updatedSessions = cloneSessions(currentState.sessions);
1014
+ updatedSessions[idx] = updatedSession;
1015
+
1016
+ await applyState(
1017
+ {
1018
+ sessions: updatedSessions,
1019
+ activeSessionIndex: idx,
1020
+ },
1021
+ {
1022
+ error: null,
1023
+ event: {
1024
+ type: 'session-updated',
1025
+ session: updatedSession,
1026
+ },
1027
+ },
1028
+ );
1029
+
1030
+ return updatedSession;
1031
+ }
1032
+
1033
+ async function updateSession(
1034
+ userId: string,
1035
+ company: string | undefined,
1036
+ patch: SessionPatch,
1037
+ ): Promise<AuthSession | null> {
1038
+ await initialize();
1039
+ const currentState = await loadAuthState();
1040
+ const idx = currentState.sessions.findIndex(
1041
+ (session) =>
1042
+ session.user.id === userId && getSessionCompany(session) === company,
1043
+ );
1044
+
1045
+ if (idx === -1) {
1046
+ return null;
1047
+ }
1048
+
1049
+ const currentSession = currentState.sessions[idx];
1050
+ const updatedSession =
1051
+ typeof patch === 'function'
1052
+ ? patch(currentSession)
1053
+ : {
1054
+ ...currentSession,
1055
+ ...patch,
1056
+ user: patch.user
1057
+ ? {
1058
+ ...currentSession.user,
1059
+ ...patch.user,
1060
+ }
1061
+ : currentSession.user,
1062
+ company:
1063
+ patch.company ??
1064
+ patch.user?.company ??
1065
+ getSessionCompany(currentSession),
1066
+ };
1067
+
1068
+ const updatedSessions = cloneSessions(currentState.sessions);
1069
+ updatedSessions[idx] = updatedSession;
1070
+
1071
+ await applyState(
1072
+ {
1073
+ sessions: updatedSessions,
1074
+ activeSessionIndex: currentState.activeSessionIndex,
1075
+ },
1076
+ {
1077
+ error: null,
1078
+ event: {
1079
+ type: 'session-updated',
1080
+ session: updatedSession,
1081
+ },
1082
+ },
1083
+ );
1084
+
1085
+ return updatedSession;
1086
+ }
1087
+
1088
+ async function expireActiveSession(): Promise<AuthRecoveryState> {
1089
+ await initialize();
1090
+ const currentState = await loadAuthState();
1091
+ const session = getActiveSessionFromState(currentState);
1092
+
1093
+ if (!session) {
1094
+ return buildRecoveryState(currentState);
1095
+ }
1096
+
1097
+ const nextState: AuthState = {
1098
+ sessions: currentState.sessions.filter(
1099
+ (_entry, index) => index !== currentState.activeSessionIndex,
1100
+ ),
1101
+ activeSessionIndex: -1,
1102
+ };
1103
+
1104
+ await applyState(nextState, {
1105
+ expiredSession: session,
1106
+ error: null,
1107
+ event: {
1108
+ type: 'session-expired',
1109
+ session,
1110
+ },
1111
+ });
1112
+
1113
+ return buildRecoveryState();
530
1114
  }
531
1115
 
532
1116
  return {
@@ -538,22 +1122,42 @@ export function createAuth(config: AuthConfig = {}): Auth {
538
1122
  refresh,
539
1123
  getToken,
540
1124
  getActiveSession,
541
- getSessions: () => [...sessions],
1125
+ getSessions: () => cloneSessions(sessions),
542
1126
  getSessionsByCompany: (company: string) =>
543
- sessions.filter((s) => s.company === company),
1127
+ sessions.filter((session) => getSessionCompany(session) === company),
1128
+ getState,
1129
+ getStatus: () => getStatus(),
1130
+ getRecoveryState: () => buildRecoveryState(),
544
1131
  switchToSession,
545
1132
  clearAllSessions,
546
1133
  deleteChallenge,
1134
+ importToken,
1135
+ validateActiveSession,
1136
+ syncActiveSessionUser,
1137
+ updateSession,
1138
+ expireActiveSession,
547
1139
  subscribe(listener: SessionListener): () => void {
548
1140
  sessionListeners.push(listener);
549
1141
  return () => {
550
- sessionListeners = sessionListeners.filter((l) => l !== listener);
1142
+ sessionListeners = sessionListeners.filter((entry) => entry !== listener);
551
1143
  };
552
1144
  },
553
1145
  subscribeToErrors(listener: ErrorListener): () => void {
554
1146
  errorListeners.push(listener);
555
1147
  return () => {
556
- errorListeners = errorListeners.filter((l) => l !== listener);
1148
+ errorListeners = errorListeners.filter((entry) => entry !== listener);
1149
+ };
1150
+ },
1151
+ subscribeToState(listener: AuthStateListener): () => void {
1152
+ stateListeners.push(listener);
1153
+ return () => {
1154
+ stateListeners = stateListeners.filter((entry) => entry !== listener);
1155
+ };
1156
+ },
1157
+ subscribeToEvents(listener: AuthEventListener): () => void {
1158
+ eventListeners.push(listener);
1159
+ return () => {
1160
+ eventListeners = eventListeners.filter((entry) => entry !== listener);
557
1161
  };
558
1162
  },
559
1163
  get baseUrl() {