@oxyhq/services 5.2.2 → 5.2.4

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 (64) hide show
  1. package/lib/commonjs/core/index.js +117 -0
  2. package/lib/commonjs/core/index.js.map +1 -1
  3. package/lib/commonjs/models/secureSession.js +2 -0
  4. package/lib/commonjs/models/secureSession.js.map +1 -0
  5. package/lib/commonjs/ui/context/LegacyOxyContext.js +643 -0
  6. package/lib/commonjs/ui/context/LegacyOxyContext.js.map +1 -0
  7. package/lib/commonjs/ui/context/OxyContext.js +215 -450
  8. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  9. package/lib/commonjs/ui/context/SecureOxyContext.js +408 -0
  10. package/lib/commonjs/ui/context/SecureOxyContext.js.map +1 -0
  11. package/lib/commonjs/ui/screens/AccountCenterScreen.js +3 -3
  12. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  13. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +31 -30
  14. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  15. package/lib/commonjs/ui/screens/AppInfoScreen.js +4 -4
  16. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  17. package/lib/commonjs/ui/screens/SessionManagementScreen.js +34 -34
  18. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  19. package/lib/commonjs/ui/screens/SignInScreen.js +2 -2
  20. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  21. package/lib/module/core/index.js +117 -0
  22. package/lib/module/core/index.js.map +1 -1
  23. package/lib/module/models/secureSession.js +2 -0
  24. package/lib/module/models/secureSession.js.map +1 -0
  25. package/lib/module/ui/context/LegacyOxyContext.js +639 -0
  26. package/lib/module/ui/context/LegacyOxyContext.js.map +1 -0
  27. package/lib/module/ui/context/OxyContext.js +214 -450
  28. package/lib/module/ui/context/OxyContext.js.map +1 -1
  29. package/lib/module/ui/context/SecureOxyContext.js +403 -0
  30. package/lib/module/ui/context/SecureOxyContext.js.map +1 -0
  31. package/lib/module/ui/screens/AccountCenterScreen.js +3 -3
  32. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  33. package/lib/module/ui/screens/AccountSwitcherScreen.js +31 -30
  34. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  35. package/lib/module/ui/screens/AppInfoScreen.js +4 -4
  36. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  37. package/lib/module/ui/screens/SessionManagementScreen.js +34 -34
  38. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  39. package/lib/module/ui/screens/SignInScreen.js +2 -2
  40. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  41. package/lib/typescript/core/index.d.ts +51 -0
  42. package/lib/typescript/core/index.d.ts.map +1 -1
  43. package/lib/typescript/models/secureSession.d.ts +25 -0
  44. package/lib/typescript/models/secureSession.d.ts.map +1 -0
  45. package/lib/typescript/ui/context/LegacyOxyContext.d.ts +40 -0
  46. package/lib/typescript/ui/context/LegacyOxyContext.d.ts.map +1 -0
  47. package/lib/typescript/ui/context/OxyContext.d.ts +11 -12
  48. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  49. package/lib/typescript/ui/context/SecureOxyContext.d.ts +39 -0
  50. package/lib/typescript/ui/context/SecureOxyContext.d.ts.map +1 -0
  51. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  52. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/src/core/index.ts +117 -0
  55. package/src/index.ts +2 -2
  56. package/src/models/secureSession.ts +27 -0
  57. package/src/ui/context/LegacyOxyContext.tsx +735 -0
  58. package/src/ui/context/OxyContext.tsx +412 -674
  59. package/src/ui/context/SecureOxyContext.tsx +473 -0
  60. package/src/ui/screens/AccountCenterScreen.tsx +4 -4
  61. package/src/ui/screens/AccountSwitcherScreen.tsx +36 -34
  62. package/src/ui/screens/AppInfoScreen.tsx +3 -3
  63. package/src/ui/screens/SessionManagementScreen.tsx +31 -35
  64. package/src/ui/screens/SignInScreen.tsx +2 -2
@@ -0,0 +1,735 @@
1
+ import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode, useMemo } from 'react';
2
+ import { OxyServices } from '../../core';
3
+ import { User, LoginResponse } from '../../models/interfaces';
4
+
5
+ // Define authenticated user with tokens
6
+ export interface AuthenticatedUser extends User {
7
+ accessToken: string;
8
+ refreshToken?: string;
9
+ sessionId?: string;
10
+ }
11
+
12
+ // Define the legacy context shape
13
+ export interface LegacyOxyContextState {
14
+ // Multi-user authentication state
15
+ user: User | null; // Current active user
16
+ users: AuthenticatedUser[]; // All authenticated users
17
+ isAuthenticated: boolean;
18
+ isLoading: boolean;
19
+ error: string | null;
20
+
21
+ // Auth methods
22
+ login: (username: string, password: string) => Promise<User>;
23
+ logout: (userId?: string) => Promise<void>; // Optional userId for multi-user logout
24
+ logoutAll: () => Promise<void>; // Logout all users
25
+ signUp: (username: string, email: string, password: string) => Promise<User>;
26
+
27
+ // Multi-user methods
28
+ switchUser: (userId: string) => Promise<void>;
29
+ removeUser: (userId: string) => Promise<void>;
30
+ getUserSessions: (userId?: string) => Promise<any[]>; // Get sessions for user
31
+ logoutSession: (sessionId: string, userId?: string) => Promise<void>; // Logout specific session
32
+
33
+ // Access to services
34
+ oxyServices: OxyServices;
35
+ bottomSheetRef?: React.RefObject<any>;
36
+
37
+ // Methods to directly control the bottom sheet
38
+ showBottomSheet?: (screenOrConfig?: string | { screen: string; props?: Record<string, any> }) => void;
39
+ hideBottomSheet?: () => void;
40
+ }
41
+
42
+ // Create the context with default values
43
+ const LegacyOxyContext = createContext<LegacyOxyContextState | null>(null);
44
+
45
+ // Props for the LegacyOxyContextProvider
46
+ export interface LegacyOxyContextProviderProps {
47
+ children: ReactNode;
48
+ oxyServices: OxyServices;
49
+ storageKeyPrefix?: string;
50
+ onAuthStateChange?: (user: User | null) => void;
51
+ bottomSheetRef?: React.RefObject<any>;
52
+ }
53
+
54
+ // Platform storage implementation
55
+ interface StorageInterface {
56
+ getItem: (key: string) => Promise<string | null>;
57
+ setItem: (key: string, value: string) => Promise<void>;
58
+ removeItem: (key: string) => Promise<void>;
59
+ clear: () => Promise<void>;
60
+ }
61
+
62
+ // Web localStorage implementation
63
+ class WebStorage implements StorageInterface {
64
+ async getItem(key: string): Promise<string | null> {
65
+ return localStorage.getItem(key);
66
+ }
67
+
68
+ async setItem(key: string, value: string): Promise<void> {
69
+ localStorage.setItem(key, value);
70
+ }
71
+
72
+ async removeItem(key: string): Promise<void> {
73
+ localStorage.removeItem(key);
74
+ }
75
+
76
+ async clear(): Promise<void> {
77
+ localStorage.clear();
78
+ }
79
+ }
80
+
81
+ // React Native AsyncStorage implementation
82
+ // This will be dynamically imported only in React Native environment
83
+ let AsyncStorage: StorageInterface;
84
+
85
+ // Determine the platform and set up storage
86
+ const isReactNative = (): boolean => {
87
+ return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
88
+ };
89
+
90
+ // Get appropriate storage for the platform
91
+ const getStorage = async (): Promise<StorageInterface> => {
92
+ if (isReactNative()) {
93
+ // Dynamically import AsyncStorage only in React Native environment
94
+ if (!AsyncStorage) {
95
+ try {
96
+ const asyncStorageModule = await import('@react-native-async-storage/async-storage');
97
+ AsyncStorage = asyncStorageModule.default;
98
+ } catch (error) {
99
+ console.error('Failed to import AsyncStorage:', error);
100
+ throw new Error('AsyncStorage is required in React Native environment');
101
+ }
102
+ }
103
+ return AsyncStorage;
104
+ }
105
+
106
+ // Default to web storage
107
+ return new WebStorage();
108
+ };
109
+
110
+ // Storage keys
111
+ const getStorageKeys = (prefix = 'oxy') => ({
112
+ users: `${prefix}_users`, // Array of authenticated users with tokens
113
+ activeUserId: `${prefix}_active_user_id`, // ID of currently active user
114
+ // Legacy keys for migration
115
+ accessToken: `${prefix}_access_token`,
116
+ refreshToken: `${prefix}_refresh_token`,
117
+ user: `${prefix}_user`,
118
+ });
119
+
120
+ export const LegacyOxyContextProvider: React.FC<LegacyOxyContextProviderProps> = ({
121
+ children,
122
+ oxyServices,
123
+ storageKeyPrefix = 'oxy',
124
+ onAuthStateChange,
125
+ bottomSheetRef,
126
+ }) => {
127
+ // Authentication state
128
+ const [user, setUser] = useState<User | null>(null);
129
+ const [users, setUsers] = useState<AuthenticatedUser[]>([]);
130
+ const [isLoading, setIsLoading] = useState(true);
131
+ const [error, setError] = useState<string | null>(null);
132
+ const [storage, setStorage] = useState<StorageInterface | null>(null);
133
+
134
+ // Storage keys (memoized to prevent infinite loops)
135
+ const keys = useMemo(() => getStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
136
+
137
+ // Initialize storage
138
+ useEffect(() => {
139
+ const initStorage = async () => {
140
+ try {
141
+ const platformStorage = await getStorage();
142
+ setStorage(platformStorage);
143
+ } catch (error) {
144
+ console.error('Failed to initialize storage:', error);
145
+ setError('Failed to initialize storage');
146
+ }
147
+ };
148
+
149
+ initStorage();
150
+ }, []);
151
+
152
+ // Effect to initialize authentication state
153
+ useEffect(() => {
154
+ const initAuth = async () => {
155
+ if (!storage) return;
156
+
157
+ setIsLoading(true);
158
+ try {
159
+ // Check for multi-user data first
160
+ const usersData = await storage.getItem(keys.users);
161
+ const activeUserId = await storage.getItem(keys.activeUserId);
162
+
163
+ console.log('InitAuth - usersData:', usersData);
164
+ console.log('InitAuth - activeUserId:', activeUserId);
165
+
166
+ if (usersData) {
167
+ // Multi-user setup exists
168
+ const parsedUsers: AuthenticatedUser[] = JSON.parse(usersData);
169
+ console.log('InitAuth - parsedUsers:', parsedUsers);
170
+ setUsers(parsedUsers);
171
+
172
+ if (activeUserId && parsedUsers.length > 0) {
173
+ const activeUser = parsedUsers.find(u => u.id === activeUserId);
174
+ console.log('InitAuth - activeUser found:', activeUser);
175
+ if (activeUser) {
176
+ setUser(activeUser);
177
+ oxyServices.setTokens(activeUser.accessToken, activeUser.refreshToken || activeUser.accessToken);
178
+
179
+ // Validate the tokens
180
+ const isValid = await oxyServices.validate();
181
+ console.log('InitAuth - token validation result:', isValid);
182
+ if (!isValid) {
183
+ // Remove invalid user during initialization
184
+ console.log('InitAuth - removing invalid user due to failed validation');
185
+ const filteredUsers = parsedUsers.filter(u => u.id !== activeUser.id);
186
+ setUsers(filteredUsers);
187
+ await saveUsersToStorage(filteredUsers);
188
+
189
+ // If there are other users, switch to the first one
190
+ if (filteredUsers.length > 0) {
191
+ const newActiveUser = filteredUsers[0];
192
+ setUser(newActiveUser);
193
+ await saveActiveUserId(newActiveUser.id);
194
+ oxyServices.setTokens(newActiveUser.accessToken, newActiveUser.refreshToken || newActiveUser.accessToken);
195
+
196
+ if (onAuthStateChange) {
197
+ onAuthStateChange(newActiveUser);
198
+ }
199
+ } else {
200
+ // No valid users left
201
+ setUser(null);
202
+ await storage.removeItem(keys.activeUserId);
203
+ oxyServices.clearTokens();
204
+
205
+ if (onAuthStateChange) {
206
+ onAuthStateChange(null);
207
+ }
208
+ }
209
+ } else {
210
+ console.log('InitAuth - user validated successfully, setting auth state');
211
+ // Notify about auth state change
212
+ if (onAuthStateChange) {
213
+ onAuthStateChange(activeUser);
214
+ }
215
+ }
216
+ }
217
+ }
218
+ } else {
219
+ console.log('InitAuth - no users data, checking legacy auth');
220
+ // Check for legacy single-user data and migrate
221
+ await migrateLegacyAuth();
222
+ }
223
+ } catch (err) {
224
+ console.error('Auth initialization error:', err);
225
+ await clearAllStorage();
226
+ oxyServices.clearTokens();
227
+ } finally {
228
+ setIsLoading(false);
229
+ }
230
+ };
231
+
232
+ if (storage) {
233
+ initAuth();
234
+ }
235
+ }, [storage, oxyServices, keys, onAuthStateChange]);
236
+
237
+ // Migrate legacy single-user authentication to multi-user
238
+ const migrateLegacyAuth = useCallback(async (): Promise<void> => {
239
+ if (!storage) return;
240
+
241
+ try {
242
+ const accessToken = await storage.getItem(keys.accessToken);
243
+ const refreshToken = await storage.getItem(keys.refreshToken);
244
+ const storedUser = await storage.getItem(keys.user);
245
+
246
+ if (accessToken && storedUser) {
247
+ // Set tokens in OxyServices
248
+ oxyServices.setTokens(accessToken, refreshToken || accessToken);
249
+
250
+ // Validate the tokens
251
+ const isValid = await oxyServices.validate();
252
+
253
+ if (isValid) {
254
+ const parsedUser = JSON.parse(storedUser);
255
+ const authenticatedUser: AuthenticatedUser = {
256
+ ...parsedUser,
257
+ accessToken,
258
+ refreshToken: refreshToken || undefined,
259
+ };
260
+
261
+ // Store in new multi-user format
262
+ await storage.setItem(keys.users, JSON.stringify([authenticatedUser]));
263
+ await storage.setItem(keys.activeUserId, authenticatedUser.id);
264
+
265
+ // Set state
266
+ setUsers([authenticatedUser]);
267
+ setUser(authenticatedUser);
268
+
269
+ // Notify about auth state change
270
+ if (onAuthStateChange) {
271
+ onAuthStateChange(authenticatedUser);
272
+ }
273
+ }
274
+
275
+ // Clear legacy storage
276
+ await storage.removeItem(keys.accessToken);
277
+ await storage.removeItem(keys.refreshToken);
278
+ await storage.removeItem(keys.user);
279
+ }
280
+ } catch (err) {
281
+ console.error('Migration error:', err);
282
+ }
283
+ }, [storage, keys, oxyServices, onAuthStateChange]);
284
+
285
+ // Helper to clear legacy storage
286
+ const clearStorage = useCallback(async (): Promise<void> => {
287
+ if (!storage) return;
288
+
289
+ try {
290
+ await storage.removeItem(keys.accessToken);
291
+ await storage.removeItem(keys.refreshToken);
292
+ await storage.removeItem(keys.user);
293
+ } catch (err) {
294
+ console.error('Clear storage error:', err);
295
+ }
296
+ }, [storage, keys]);
297
+
298
+ // Helper to clear all storage (multi-user)
299
+ const clearAllStorage = useCallback(async (): Promise<void> => {
300
+ if (!storage) return;
301
+
302
+ try {
303
+ await storage.removeItem(keys.users);
304
+ await storage.removeItem(keys.activeUserId);
305
+ // Also clear legacy keys
306
+ await clearStorage();
307
+ } catch (err) {
308
+ console.error('Clear all storage error:', err);
309
+ }
310
+ }, [storage, keys, clearStorage]);
311
+
312
+ // Save users to storage
313
+ const saveUsersToStorage = useCallback(async (usersList: AuthenticatedUser[]): Promise<void> => {
314
+ if (!storage) return;
315
+ await storage.setItem(keys.users, JSON.stringify(usersList));
316
+ }, [storage, keys.users]);
317
+
318
+ // Save active user ID to storage
319
+ const saveActiveUserId = useCallback(async (userId: string): Promise<void> => {
320
+ if (!storage) return;
321
+ await storage.setItem(keys.activeUserId, userId);
322
+ }, [storage, keys.activeUserId]);
323
+
324
+ // Utility function to handle different token response formats
325
+ const storeTokens = useCallback(async (response: any) => {
326
+ // Store token and user data
327
+ if (response.accessToken) {
328
+ await storage?.setItem(keys.accessToken, response.accessToken);
329
+ if (response.refreshToken) {
330
+ await storage?.setItem(keys.refreshToken, response.refreshToken);
331
+ }
332
+ } else if (response.token) {
333
+ // Handle legacy API response
334
+ await storage?.setItem(keys.accessToken, response.token);
335
+ }
336
+ await storage?.setItem(keys.user, JSON.stringify(response.user));
337
+ }, [storage, keys]);
338
+
339
+ // Login method (updated for multi-user)
340
+ const login = async (username: string, password: string): Promise<User> => {
341
+ if (!storage) throw new Error('Storage not initialized');
342
+
343
+ setIsLoading(true);
344
+ setError(null);
345
+
346
+ try {
347
+ const response = await oxyServices.login(username, password);
348
+ const accessToken = response.accessToken || (response as any).token;
349
+
350
+ if (!accessToken) {
351
+ throw new Error('No access token received from login');
352
+ }
353
+
354
+ const newUser: AuthenticatedUser = {
355
+ ...response.user,
356
+ accessToken,
357
+ refreshToken: response.refreshToken,
358
+ // sessionId will be set by backend, but we don't get it in response yet
359
+ };
360
+
361
+ // Check if user already exists
362
+ const existingUserIndex = users.findIndex(u => u.id === newUser.id);
363
+ let updatedUsers: AuthenticatedUser[];
364
+
365
+ if (existingUserIndex >= 0) {
366
+ // Update existing user
367
+ updatedUsers = [...users];
368
+ updatedUsers[existingUserIndex] = newUser;
369
+ } else {
370
+ // Add new user
371
+ updatedUsers = [...users, newUser];
372
+ }
373
+
374
+ // Update state
375
+ setUsers(updatedUsers);
376
+ setUser(newUser);
377
+
378
+ // Save to storage
379
+ await saveUsersToStorage(updatedUsers);
380
+ await saveActiveUserId(newUser.id);
381
+
382
+ // Notify about auth state change
383
+ if (onAuthStateChange) {
384
+ onAuthStateChange(newUser);
385
+ }
386
+
387
+ return newUser;
388
+ } catch (err: any) {
389
+ setError(err.message || 'Login failed');
390
+ throw err;
391
+ } finally {
392
+ setIsLoading(false);
393
+ }
394
+ };
395
+
396
+ // Logout method (supports multi-user)
397
+ const logout = async (userId?: string): Promise<void> => {
398
+ if (!storage) throw new Error('Storage not initialized');
399
+
400
+ setIsLoading(true);
401
+ setError(null);
402
+
403
+ try {
404
+ const targetUserId = userId || user?.id;
405
+ if (!targetUserId) return;
406
+
407
+ const targetUser = users.find(u => u.id === targetUserId);
408
+ if (targetUser) {
409
+ // Set the target user's tokens to logout
410
+ oxyServices.setTokens(targetUser.accessToken, targetUser.refreshToken || targetUser.accessToken);
411
+
412
+ try {
413
+ await oxyServices.logout();
414
+ } catch (logoutError) {
415
+ console.warn('Logout API call failed:', logoutError);
416
+ }
417
+
418
+ // Remove user from list
419
+ const updatedUsers = users.filter(u => u.id !== targetUserId);
420
+ setUsers(updatedUsers);
421
+
422
+ // If logging out current user, switch to another user or clear
423
+ if (targetUserId === user?.id) {
424
+ if (updatedUsers.length > 0) {
425
+ // Switch to first available user
426
+ const nextUser = updatedUsers[0];
427
+ setUser(nextUser);
428
+ oxyServices.setTokens(nextUser.accessToken, nextUser.refreshToken || nextUser.accessToken);
429
+ await saveActiveUserId(nextUser.id);
430
+
431
+ if (onAuthStateChange) {
432
+ onAuthStateChange(nextUser);
433
+ }
434
+ } else {
435
+ // No users left
436
+ setUser(null);
437
+ oxyServices.clearTokens();
438
+ await storage.removeItem(keys.activeUserId);
439
+
440
+ if (onAuthStateChange) {
441
+ onAuthStateChange(null);
442
+ }
443
+ }
444
+ }
445
+
446
+ // Save updated users list
447
+ await saveUsersToStorage(updatedUsers);
448
+ }
449
+ } catch (err: any) {
450
+ setError(err.message || 'Logout failed');
451
+ throw err;
452
+ } finally {
453
+ setIsLoading(false);
454
+ }
455
+ };
456
+
457
+ // Logout all users
458
+ const logoutAll = async (): Promise<void> => {
459
+ if (!storage) throw new Error('Storage not initialized');
460
+
461
+ setIsLoading(true);
462
+ setError(null);
463
+
464
+ try {
465
+ // Logout each user
466
+ for (const userItem of users) {
467
+ try {
468
+ oxyServices.setTokens(userItem.accessToken, userItem.refreshToken || userItem.accessToken);
469
+ await oxyServices.logout();
470
+ } catch (logoutError) {
471
+ console.warn(`Logout failed for user ${userItem.id}:`, logoutError);
472
+ }
473
+ }
474
+
475
+ // Clear all state and storage
476
+ setUsers([]);
477
+ setUser(null);
478
+ oxyServices.clearTokens();
479
+ await clearAllStorage();
480
+
481
+ // Notify about auth state change
482
+ if (onAuthStateChange) {
483
+ onAuthStateChange(null);
484
+ }
485
+ } catch (err: any) {
486
+ setError(err.message || 'Logout all failed');
487
+ throw err;
488
+ } finally {
489
+ setIsLoading(false);
490
+ }
491
+ };
492
+
493
+ // Switch user
494
+ const switchUser = async (userId: string): Promise<void> => {
495
+ if (!storage) throw new Error('Storage not initialized');
496
+
497
+ setError(null);
498
+
499
+ try {
500
+ const targetUser = users.find(u => u.id === userId);
501
+ if (!targetUser) {
502
+ throw new Error('User not found');
503
+ }
504
+
505
+ // Validate tokens before switching
506
+ oxyServices.setTokens(targetUser.accessToken, targetUser.refreshToken || targetUser.accessToken);
507
+ const isValid = await oxyServices.validate();
508
+
509
+ if (!isValid) {
510
+ // Remove invalid user
511
+ await removeUser(userId);
512
+ throw new Error('User session is invalid');
513
+ }
514
+
515
+ // Switch to the user
516
+ setUser(targetUser);
517
+ await saveActiveUserId(userId);
518
+
519
+ // Notify about auth state change
520
+ if (onAuthStateChange) {
521
+ onAuthStateChange(targetUser);
522
+ }
523
+ } catch (err: any) {
524
+ setError(err.message || 'Switch user failed');
525
+ throw err;
526
+ }
527
+ };
528
+
529
+ // Remove user
530
+ const removeUser = async (userId: string): Promise<void> => {
531
+ if (!storage) throw new Error('Storage not initialized');
532
+
533
+ try {
534
+ const updatedUsers = users.filter(u => u.id !== userId);
535
+ setUsers(updatedUsers);
536
+
537
+ // If removing current user, switch to another or clear
538
+ if (userId === user?.id) {
539
+ if (updatedUsers.length > 0) {
540
+ await switchUser(updatedUsers[0].id);
541
+ } else {
542
+ setUser(null);
543
+ oxyServices.clearTokens();
544
+ await storage.removeItem(keys.activeUserId);
545
+
546
+ if (onAuthStateChange) {
547
+ onAuthStateChange(null);
548
+ }
549
+ }
550
+ }
551
+
552
+ // Save updated users list
553
+ await saveUsersToStorage(updatedUsers);
554
+ } catch (err: any) {
555
+ setError(err.message || 'Remove user failed');
556
+ throw err;
557
+ }
558
+ };
559
+
560
+ // Get user sessions
561
+ const getUserSessions = async (userId?: string): Promise<any[]> => {
562
+ try {
563
+ const targetUserId = userId || user?.id;
564
+ if (!targetUserId) return [];
565
+
566
+ const targetUser = users.find(u => u.id === targetUserId);
567
+ if (!targetUser) return [];
568
+
569
+ // Store current tokens to restore later
570
+ const currentUser = user;
571
+ const wasCurrentUser = targetUserId === user?.id;
572
+
573
+ if (!wasCurrentUser) {
574
+ // Temporarily switch to target user's tokens
575
+ oxyServices.setTokens(targetUser.accessToken, targetUser.refreshToken || targetUser.accessToken);
576
+ }
577
+
578
+ try {
579
+ // Use the new OxyServices method
580
+ const sessions = await oxyServices.getUserSessions();
581
+ return sessions;
582
+ } finally {
583
+ if (!wasCurrentUser && currentUser) {
584
+ // Restore original tokens
585
+ oxyServices.setTokens(currentUser.accessToken, currentUser.refreshToken || currentUser.accessToken);
586
+ }
587
+ }
588
+ } catch (err: any) {
589
+ console.error('Get user sessions failed:', err);
590
+ return [];
591
+ }
592
+ };
593
+
594
+ // Logout specific session
595
+ const logoutSession = async (sessionId: string, userId?: string): Promise<void> => {
596
+ try {
597
+ const targetUserId = userId || user?.id;
598
+ if (!targetUserId) return;
599
+
600
+ const targetUser = users.find(u => u.id === targetUserId);
601
+ if (!targetUser) return;
602
+
603
+ // Store current tokens to restore later
604
+ const currentUser = user;
605
+ const wasCurrentUser = targetUserId === user?.id;
606
+
607
+ if (!wasCurrentUser) {
608
+ // Temporarily switch to target user's tokens
609
+ oxyServices.setTokens(targetUser.accessToken, targetUser.refreshToken || targetUser.accessToken);
610
+ }
611
+
612
+ try {
613
+ // Use the new OxyServices method
614
+ await oxyServices.logoutSession(sessionId);
615
+
616
+ // If this is the current user's session, remove them from local state
617
+ if (wasCurrentUser && sessionId === targetUser.sessionId) {
618
+ await removeUser(targetUserId);
619
+ }
620
+ } finally {
621
+ if (!wasCurrentUser && currentUser) {
622
+ // Restore original tokens
623
+ oxyServices.setTokens(currentUser.accessToken, currentUser.refreshToken || currentUser.accessToken);
624
+ }
625
+ }
626
+ } catch (err: any) {
627
+ console.error('Logout session failed:', err);
628
+ throw err;
629
+ }
630
+ };
631
+
632
+ // Sign up method
633
+ const signUp = async (username: string, email: string, password: string): Promise<User> => {
634
+ if (!storage) throw new Error('Storage not initialized');
635
+
636
+ setIsLoading(true);
637
+ setError(null);
638
+
639
+ try {
640
+ const response = await oxyServices.signUp(username, email, password);
641
+ setUser(response.user);
642
+
643
+ // Store tokens
644
+ await storeTokens(response);
645
+
646
+ // Notify about auth state change
647
+ if (onAuthStateChange) {
648
+ onAuthStateChange(response.user);
649
+ }
650
+
651
+ return response.user;
652
+ } catch (err: any) {
653
+ setError(err.message || 'Sign up failed');
654
+ throw err;
655
+ } finally {
656
+ setIsLoading(false);
657
+ }
658
+ };
659
+
660
+ // Methods to control the bottom sheet
661
+ const showBottomSheet = useCallback((screenOrConfig?: string | { screen: string; props?: Record<string, any> }) => {
662
+ if (bottomSheetRef?.current) {
663
+ // Expand the bottom sheet
664
+ bottomSheetRef.current.expand();
665
+ if (typeof screenOrConfig === 'string') {
666
+ // If a screen is specified, navigate to it
667
+ if (screenOrConfig && bottomSheetRef.current._navigateToScreen) {
668
+ setTimeout(() => {
669
+ bottomSheetRef.current._navigateToScreen(screenOrConfig);
670
+ }, 100);
671
+ }
672
+ } else if (screenOrConfig && typeof screenOrConfig === 'object' && screenOrConfig.screen) {
673
+ // If an object is passed, navigate and pass props
674
+ if (bottomSheetRef.current._navigateToScreen) {
675
+ setTimeout(() => {
676
+ bottomSheetRef.current._navigateToScreen(screenOrConfig.screen, screenOrConfig.props || {});
677
+ }, 100);
678
+ }
679
+ }
680
+ }
681
+ }, [bottomSheetRef]);
682
+
683
+ const hideBottomSheet = useCallback(() => {
684
+ if (bottomSheetRef?.current) {
685
+ bottomSheetRef.current.close();
686
+ }
687
+ }, [bottomSheetRef]);
688
+
689
+ // Build context value
690
+ const contextValue: LegacyOxyContextState = {
691
+ // Single user state (current active user)
692
+ user,
693
+ isAuthenticated: !!user,
694
+ isLoading,
695
+ error,
696
+
697
+ // Multi-user state
698
+ users,
699
+
700
+ // Auth methods
701
+ login,
702
+ logout,
703
+ logoutAll,
704
+ signUp,
705
+
706
+ // Multi-user methods
707
+ switchUser,
708
+ removeUser,
709
+ getUserSessions,
710
+ logoutSession,
711
+
712
+ // OxyServices instance
713
+ oxyServices,
714
+
715
+ // Bottom sheet methods
716
+ bottomSheetRef,
717
+ showBottomSheet,
718
+ hideBottomSheet,
719
+ };
720
+
721
+ return (
722
+ <LegacyOxyContext.Provider value={contextValue}>
723
+ {children}
724
+ </LegacyOxyContext.Provider>
725
+ );
726
+ };
727
+
728
+ // Hook to use the legacy context
729
+ export const useLegacyOxy = () => {
730
+ const context = useContext(LegacyOxyContext);
731
+ if (!context) {
732
+ throw new Error('useLegacyOxy must be used within a LegacyOxyContextProvider');
733
+ }
734
+ return context;
735
+ };