@oxyhq/services 5.5.7 → 5.5.8

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 (34) hide show
  1. package/lib/commonjs/core/index.js +13 -13
  2. package/lib/commonjs/core/index.js.map +1 -1
  3. package/lib/commonjs/ui/context/OxyContext.js +467 -87
  4. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  5. package/lib/commonjs/ui/hooks/useAuthFetch.js +79 -45
  6. package/lib/commonjs/ui/hooks/useAuthFetch.js.map +1 -1
  7. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  8. package/lib/module/core/index.js +13 -6
  9. package/lib/module/core/index.js.map +1 -1
  10. package/lib/module/ui/context/OxyContext.js +467 -87
  11. package/lib/module/ui/context/OxyContext.js.map +1 -1
  12. package/lib/module/ui/hooks/useAuthFetch.js +79 -45
  13. package/lib/module/ui/hooks/useAuthFetch.js.map +1 -1
  14. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  15. package/lib/typescript/core/index.d.ts +7 -6
  16. package/lib/typescript/core/index.d.ts.map +1 -1
  17. package/lib/typescript/ui/context/OxyContext.d.ts +11 -7
  18. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  19. package/lib/typescript/ui/hooks/useAuthFetch.d.ts +10 -4
  20. package/lib/typescript/ui/hooks/useAuthFetch.d.ts.map +1 -1
  21. package/package.json +1 -1
  22. package/src/__tests__/backend-middleware.test.ts +209 -0
  23. package/src/__tests__/ui/hooks/useAuthFetch.test.ts +70 -0
  24. package/src/core/index.ts +13 -7
  25. package/src/ui/context/OxyContext.tsx +522 -99
  26. package/src/ui/hooks/useAuthFetch.ts +81 -47
  27. package/src/ui/screens/SessionManagementScreen.tsx +9 -9
  28. package/lib/commonjs/core/AuthManager.js +0 -378
  29. package/lib/commonjs/core/AuthManager.js.map +0 -1
  30. package/lib/module/core/AuthManager.js +0 -373
  31. package/lib/module/core/AuthManager.js.map +0 -1
  32. package/lib/typescript/core/AuthManager.d.ts +0 -100
  33. package/lib/typescript/core/AuthManager.d.ts.map +0 -1
  34. package/src/core/AuthManager.ts +0 -389
@@ -1,44 +1,52 @@
1
1
  import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode, useMemo } from 'react';
2
2
  import { OxyServices } from '../../core';
3
- import { AuthManager, AuthState } from '../../core/AuthManager';
4
3
  import { User } from '../../models/interfaces';
5
- import { SecureClientSession } from '../../models/secureSession';
4
+ import { SecureLoginResponse, SecureClientSession, MinimalUserData } from '../../models/secureSession';
5
+ import { DeviceManager } from '../../utils/deviceManager';
6
6
 
7
- // Context interface with session management support
7
+ // Define the context shape
8
8
  export interface OxyContextState {
9
- // Auth state from AuthManager
10
- isAuthenticated: boolean;
11
- user: User | null;
9
+ // Authentication state
10
+ user: User | null; // Current active user (loaded from server)
11
+ minimalUser: MinimalUserData | null; // Minimal user data for UI
12
+ sessions: SecureClientSession[]; // All active sessions
13
+ activeSessionId: string | null;
14
+ isAuthenticated: boolean; // Single source of truth for authentication - use this instead of service methods
12
15
  isLoading: boolean;
13
16
  error: string | null;
14
17
 
15
- // Session management
16
- sessions: any[];
17
- activeSessionId: string | null;
18
-
19
18
  // Auth methods
20
19
  login: (username: string, password: string, deviceName?: string) => Promise<User>;
21
20
  logout: (targetSessionId?: string) => Promise<void>;
21
+ logoutAll: () => Promise<void>;
22
22
  signUp: (username: string, email: string, password: string) => Promise<User>;
23
23
 
24
- // Session management methods
25
- refreshSessions: () => Promise<void>;
24
+ // Multi-session methods
26
25
  switchSession: (sessionId: string) => Promise<void>;
27
26
  removeSession: (sessionId: string) => Promise<void>;
28
- logoutAll: () => Promise<void>;
27
+ refreshSessions: () => Promise<void>;
29
28
 
30
- // Access to services and auth manager
31
- oxyServices: OxyServices;
32
- authManager: AuthManager;
29
+ // Device management methods
30
+ getDeviceSessions: () => Promise<any[]>;
31
+ logoutAllDeviceSessions: () => Promise<void>;
32
+ updateDeviceName: (deviceName: string) => Promise<void>;
33
33
 
34
- // UI controls
34
+ // Access to services
35
+ oxyServices: OxyServices;
35
36
  bottomSheetRef?: React.RefObject<any>;
37
+
38
+ // API configuration
39
+ setApiUrl: (url: string) => void;
40
+
41
+ // Methods to directly control the bottom sheet
36
42
  showBottomSheet?: (screenOrConfig?: string | { screen: string; props?: Record<string, any> }) => void;
37
43
  hideBottomSheet?: () => void;
38
44
  }
39
45
 
46
+ // Create the context with default values
40
47
  const OxyContext = createContext<OxyContextState | null>(null);
41
48
 
49
+ // Props for the OxyContextProvider
42
50
  export interface OxyContextProviderProps {
43
51
  children: ReactNode;
44
52
  oxyServices: OxyServices;
@@ -47,7 +55,7 @@ export interface OxyContextProviderProps {
47
55
  bottomSheetRef?: React.RefObject<any>;
48
56
  }
49
57
 
50
- // Storage implementation
58
+ // Platform storage implementation
51
59
  interface StorageInterface {
52
60
  getItem: (key: string) => Promise<string | null>;
53
61
  setItem: (key: string, value: string) => Promise<void>;
@@ -55,6 +63,7 @@ interface StorageInterface {
55
63
  clear: () => Promise<void>;
56
64
  }
57
65
 
66
+ // Web localStorage implementation
58
67
  class WebStorage implements StorageInterface {
59
68
  async getItem(key: string): Promise<string | null> {
60
69
  return localStorage.getItem(key);
@@ -73,12 +82,15 @@ class WebStorage implements StorageInterface {
73
82
  }
74
83
  }
75
84
 
85
+ // React Native AsyncStorage implementation
76
86
  let AsyncStorage: StorageInterface;
77
87
 
88
+ // Determine the platform and set up storage
78
89
  const isReactNative = (): boolean => {
79
90
  return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
80
91
  };
81
92
 
93
+ // Get appropriate storage for the platform
82
94
  const getStorage = async (): Promise<StorageInterface> => {
83
95
  if (isReactNative()) {
84
96
  if (!AsyncStorage) {
@@ -92,9 +104,16 @@ const getStorage = async (): Promise<StorageInterface> => {
92
104
  }
93
105
  return AsyncStorage;
94
106
  }
107
+
95
108
  return new WebStorage();
96
109
  };
97
110
 
111
+ // Storage keys for secure sessions
112
+ const getSecureStorageKeys = (prefix = 'oxy_secure') => ({
113
+ sessions: `${prefix}_sessions`, // Array of SecureClientSession objects
114
+ activeSessionId: `${prefix}_active_session_id`, // ID of currently active session
115
+ });
116
+
98
117
  export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
99
118
  children,
100
119
  oxyServices,
@@ -102,29 +121,17 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
102
121
  onAuthStateChange,
103
122
  bottomSheetRef,
104
123
  }) => {
124
+ // Authentication state
125
+ const [user, setUser] = useState<User | null>(null);
126
+ const [minimalUser, setMinimalUser] = useState<MinimalUserData | null>(null);
127
+ const [sessions, setSessions] = useState<SecureClientSession[]>([]);
128
+ const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
129
+ const [isLoading, setIsLoading] = useState(true);
130
+ const [error, setError] = useState<string | null>(null);
105
131
  const [storage, setStorage] = useState<StorageInterface | null>(null);
106
- const [authState, setAuthState] = useState<AuthState>({
107
- isAuthenticated: false,
108
- accessToken: null,
109
- user: null,
110
- activeSessionId: null,
111
- sessions: [],
112
- isLoading: true,
113
- error: null,
114
- });
115
-
116
- // Create AuthManager instance
117
- const authManager = useMemo(() => {
118
- return new AuthManager({
119
- oxyServices,
120
- onStateChange: (newState) => {
121
- setAuthState(newState);
122
- if (onAuthStateChange) {
123
- onAuthStateChange(newState.user);
124
- }
125
- },
126
- });
127
- }, [oxyServices, onAuthStateChange]);
132
+
133
+ // Storage keys (memoized to prevent infinite loops)
134
+ const keys = useMemo(() => getSecureStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
128
135
 
129
136
  // Initialize storage
130
137
  useEffect(() => {
@@ -134,109 +141,499 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
134
141
  setStorage(platformStorage);
135
142
  } catch (error) {
136
143
  console.error('Failed to initialize storage:', error);
137
- setAuthState(prev => ({ ...prev, error: 'Failed to initialize storage', isLoading: false }));
144
+ setError('Failed to initialize storage');
138
145
  }
139
146
  };
147
+
140
148
  initStorage();
141
149
  }, []);
142
150
 
143
- // Initialize authentication
151
+ // Effect to initialize authentication state
144
152
  useEffect(() => {
145
153
  const initAuth = async () => {
146
154
  if (!storage) return;
147
155
 
156
+ setIsLoading(true);
148
157
  try {
149
- const activeSessionId = await storage.getItem(`${storageKeyPrefix}_active_session_id`);
150
- await authManager.initialize(activeSessionId);
151
- } catch (error) {
152
- console.error('Auth initialization failed:', error);
153
- await storage.removeItem(`${storageKeyPrefix}_active_session_id`);
158
+ // Load stored sessions
159
+ const sessionsData = await storage.getItem(keys.sessions);
160
+ const storedActiveSessionId = await storage.getItem(keys.activeSessionId);
161
+
162
+ console.log('SecureAuth - sessionsData:', sessionsData);
163
+ console.log('SecureAuth - activeSessionId:', storedActiveSessionId);
164
+
165
+ if (sessionsData) {
166
+ const parsedSessions: SecureClientSession[] = JSON.parse(sessionsData);
167
+
168
+ // Migrate old session format to include user info
169
+ const migratedSessions: SecureClientSession[] = [];
170
+ let shouldUpdateStorage = false;
171
+
172
+ for (const session of parsedSessions) {
173
+ if (!session.userId || !session.username) {
174
+ // Session is missing user info, try to fetch it
175
+ try {
176
+ const sessionUser = await oxyServices.getUserBySession(session.sessionId);
177
+ migratedSessions.push({
178
+ ...session,
179
+ userId: sessionUser.id,
180
+ username: sessionUser.username
181
+ });
182
+ shouldUpdateStorage = true;
183
+ console.log(`Migrated session ${session.sessionId} for user ${sessionUser.username}`);
184
+ } catch (error) {
185
+ // Session might be invalid, skip it
186
+ console.log(`Removing invalid session ${session.sessionId}:`, error);
187
+ shouldUpdateStorage = true;
188
+ }
189
+ } else {
190
+ // Session already has user info
191
+ migratedSessions.push(session);
192
+ }
193
+ }
194
+
195
+ // Update storage if we made changes
196
+ if (shouldUpdateStorage) {
197
+ await saveSessionsToStorage(migratedSessions);
198
+ }
199
+
200
+ setSessions(migratedSessions);
201
+
202
+ if (storedActiveSessionId && migratedSessions.length > 0) {
203
+ const activeSession = migratedSessions.find(s => s.sessionId === storedActiveSessionId);
204
+
205
+ if (activeSession) {
206
+ console.log('SecureAuth - activeSession found:', activeSession);
207
+
208
+ // Validate session
209
+ try {
210
+ const validation = await oxyServices.validateSession(activeSession.sessionId);
211
+
212
+ if (validation.valid) {
213
+ console.log('SecureAuth - session validated successfully');
214
+ setActiveSessionId(activeSession.sessionId);
215
+
216
+ // Get access token for API calls
217
+ await oxyServices.getTokenBySession(activeSession.sessionId);
218
+
219
+ // Load full user data
220
+ const fullUser = await oxyServices.getUserBySession(activeSession.sessionId);
221
+ setUser(fullUser);
222
+ setMinimalUser({
223
+ id: fullUser.id,
224
+ username: fullUser.username,
225
+ avatar: fullUser.avatar
226
+ });
227
+
228
+ if (onAuthStateChange) {
229
+ onAuthStateChange(fullUser);
230
+ }
231
+ } else {
232
+ console.log('SecureAuth - session invalid, removing');
233
+ await removeInvalidSession(activeSession.sessionId);
234
+ }
235
+ } catch (error) {
236
+ console.error('SecureAuth - session validation error:', error);
237
+ await removeInvalidSession(activeSession.sessionId);
238
+ }
239
+ }
240
+ }
241
+ }
242
+ } catch (err) {
243
+ console.error('Secure auth initialization error:', err);
244
+ await clearAllStorage();
245
+ } finally {
246
+ setIsLoading(false);
154
247
  }
155
248
  };
156
249
 
157
250
  if (storage) {
158
251
  initAuth();
159
252
  }
160
- }, [storage, authManager, storageKeyPrefix]);
253
+ }, [storage, oxyServices, keys, onAuthStateChange]);
254
+
255
+ // Remove invalid session
256
+ const removeInvalidSession = useCallback(async (sessionId: string): Promise<void> => {
257
+ const filteredSessions = sessions.filter(s => s.sessionId !== sessionId);
258
+ setSessions(filteredSessions);
259
+ await saveSessionsToStorage(filteredSessions);
260
+
261
+ // If there are other sessions, switch to the first one
262
+ if (filteredSessions.length > 0) {
263
+ await switchToSession(filteredSessions[0].sessionId);
264
+ } else {
265
+ // No valid sessions left
266
+ setActiveSessionId(null);
267
+ setUser(null);
268
+ setMinimalUser(null);
269
+ await storage?.removeItem(keys.activeSessionId);
270
+
271
+ if (onAuthStateChange) {
272
+ onAuthStateChange(null);
273
+ }
274
+ }
275
+ }, [sessions, storage, keys, onAuthStateChange]);
276
+
277
+ // Save sessions to storage
278
+ const saveSessionsToStorage = useCallback(async (sessionsList: SecureClientSession[]): Promise<void> => {
279
+ if (!storage) return;
280
+ await storage.setItem(keys.sessions, JSON.stringify(sessionsList));
281
+ }, [storage, keys.sessions]);
282
+
283
+ // Save active session ID to storage
284
+ const saveActiveSessionId = useCallback(async (sessionId: string): Promise<void> => {
285
+ if (!storage) return;
286
+ await storage.setItem(keys.activeSessionId, sessionId);
287
+ }, [storage, keys.activeSessionId]);
288
+
289
+ // Clear all storage
290
+ const clearAllStorage = useCallback(async (): Promise<void> => {
291
+ if (!storage) return;
292
+ try {
293
+ await storage.removeItem(keys.sessions);
294
+ await storage.removeItem(keys.activeSessionId);
295
+ } catch (err) {
296
+ console.error('Clear secure storage error:', err);
297
+ }
298
+ }, [storage, keys]);
161
299
 
162
- // Auth methods that integrate with storage
163
- const login = useCallback(async (username: string, password: string, deviceName?: string): Promise<User> => {
164
- await authManager.login(username, password, deviceName);
300
+ // Switch to a different session
301
+ const switchToSession = useCallback(async (sessionId: string): Promise<void> => {
302
+ try {
303
+ setIsLoading(true);
165
304
 
166
- // Save session ID to storage
167
- const sessionId = authManager.getActiveSessionId();
168
- if (sessionId && storage) {
169
- await storage.setItem(`${storageKeyPrefix}_active_session_id`, sessionId);
305
+ // Get access token for this session
306
+ await oxyServices.getTokenBySession(sessionId);
307
+
308
+ // Load full user data
309
+ const fullUser = await oxyServices.getUserBySession(sessionId);
310
+
311
+ setActiveSessionId(sessionId);
312
+ setUser(fullUser);
313
+ setMinimalUser({
314
+ id: fullUser.id,
315
+ username: fullUser.username,
316
+ avatar: fullUser.avatar
317
+ });
318
+
319
+ await saveActiveSessionId(sessionId);
320
+
321
+ if (onAuthStateChange) {
322
+ onAuthStateChange(fullUser);
323
+ }
324
+ } catch (error) {
325
+ console.error('Switch session error:', error);
326
+ setError('Failed to switch session');
327
+ } finally {
328
+ setIsLoading(false);
170
329
  }
330
+ }, [oxyServices, onAuthStateChange, saveActiveSessionId]);
331
+
332
+ // Secure login method
333
+ const login = async (username: string, password: string, deviceName?: string): Promise<User> => {
334
+ if (!storage) throw new Error('Storage not initialized');
335
+
336
+ setIsLoading(true);
337
+ setError(null);
338
+
339
+ try {
340
+ // Get device fingerprint for enhanced device identification
341
+ const deviceFingerprint = DeviceManager.getDeviceFingerprint();
342
+
343
+ // Get or generate persistent device info
344
+ const deviceInfo = await DeviceManager.getDeviceInfo();
345
+
346
+ console.log('SecureAuth - Using device fingerprint:', deviceFingerprint);
347
+ console.log('SecureAuth - Using device ID:', deviceInfo.deviceId);
348
+
349
+ const response: SecureLoginResponse = await oxyServices.secureLogin(
350
+ username,
351
+ password,
352
+ deviceName || deviceInfo.deviceName || DeviceManager.getDefaultDeviceName(),
353
+ deviceFingerprint
354
+ );
355
+
356
+ // Create client session object with user info for duplicate detection
357
+ const clientSession: SecureClientSession = {
358
+ sessionId: response.sessionId,
359
+ deviceId: response.deviceId,
360
+ expiresAt: response.expiresAt,
361
+ lastActive: new Date().toISOString(),
362
+ userId: response.user.id,
363
+ username: response.user.username
364
+ };
365
+
366
+ // Check if this user already has a session (prevent duplicate accounts)
367
+ const existingUserSessionIndex = sessions.findIndex(s =>
368
+ s.userId === response.user.id || s.username === response.user.username
369
+ );
370
+
371
+ let updatedSessions: SecureClientSession[];
372
+
373
+ if (existingUserSessionIndex !== -1) {
374
+ // User already has a session - replace it with the new one (reused session scenario)
375
+ const existingSession = sessions[existingUserSessionIndex];
376
+ updatedSessions = [...sessions];
377
+ updatedSessions[existingUserSessionIndex] = clientSession;
378
+
379
+ console.log(`Reusing/updating existing session for user ${response.user.username}. Previous session: ${existingSession.sessionId}, New session: ${response.sessionId}`);
380
+
381
+ // If the replaced session was the active one, update active session
382
+ if (activeSessionId === existingSession.sessionId) {
383
+ setActiveSessionId(response.sessionId);
384
+ await saveActiveSessionId(response.sessionId);
385
+ }
386
+ } else {
387
+ // Add new session for new user
388
+ updatedSessions = [...sessions, clientSession];
389
+ console.log(`Added new session for user ${response.user.username} on device ${response.deviceId}`);
390
+ }
391
+
392
+ setSessions(updatedSessions);
393
+ await saveSessionsToStorage(updatedSessions);
394
+
395
+ // Set as active session
396
+ setActiveSessionId(response.sessionId);
397
+ await saveActiveSessionId(response.sessionId);
398
+
399
+ // Get access token for API calls
400
+ await oxyServices.getTokenBySession(response.sessionId);
171
401
 
172
- return authManager.getCurrentUser();
173
- }, [authManager, storage, storageKeyPrefix]);
402
+ // Load full user data
403
+ const fullUser = await oxyServices.getUserBySession(response.sessionId);
404
+ setUser(fullUser);
405
+ setMinimalUser(response.user);
174
406
 
175
- const logout = useCallback(async (targetSessionId?: string): Promise<void> => {
176
- await authManager.logout(targetSessionId);
407
+ if (onAuthStateChange) {
408
+ onAuthStateChange(fullUser);
409
+ }
177
410
 
178
- // Clear from storage if logging out current session
179
- if (!targetSessionId && storage) {
180
- await storage.removeItem(`${storageKeyPrefix}_active_session_id`);
411
+ return fullUser;
412
+ } catch (error: any) {
413
+ setError(error.message || 'Login failed');
414
+ throw error;
415
+ } finally {
416
+ setIsLoading(false);
181
417
  }
182
- }, [authManager, storage, storageKeyPrefix]);
418
+ };
183
419
 
184
- const refreshSessions = useCallback(async (): Promise<void> => {
185
- await authManager.refreshSessions();
186
- }, [authManager]);
420
+ // Logout method
421
+ const logout = async (targetSessionId?: string): Promise<void> => {
422
+ if (!activeSessionId) return;
423
+
424
+ try {
425
+ const sessionToLogout = targetSessionId || activeSessionId;
426
+ await oxyServices.logoutSecureSession(activeSessionId, sessionToLogout);
427
+
428
+ // Remove session from local storage
429
+ const filteredSessions = sessions.filter(s => s.sessionId !== sessionToLogout);
430
+ setSessions(filteredSessions);
431
+ await saveSessionsToStorage(filteredSessions);
432
+
433
+ // If logging out active session
434
+ if (sessionToLogout === activeSessionId) {
435
+ if (filteredSessions.length > 0) {
436
+ // Switch to another session
437
+ await switchToSession(filteredSessions[0].sessionId);
438
+ } else {
439
+ // No sessions left
440
+ setActiveSessionId(null);
441
+ setUser(null);
442
+ setMinimalUser(null);
443
+ await storage?.removeItem(keys.activeSessionId);
444
+
445
+ if (onAuthStateChange) {
446
+ onAuthStateChange(null);
447
+ }
448
+ }
449
+ }
450
+ } catch (error) {
451
+ console.error('Logout error:', error);
452
+ setError('Logout failed');
453
+ }
454
+ };
187
455
 
188
- const switchSession = useCallback(async (sessionId: string): Promise<void> => {
189
- await authManager.switchSession(sessionId);
456
+ // Logout all sessions
457
+ const logoutAll = async (): Promise<void> => {
458
+ console.log('logoutAll called with activeSessionId:', activeSessionId);
190
459
 
191
- // Update storage with new active session
192
- if (storage) {
193
- await storage.setItem(`${storageKeyPrefix}_active_session_id`, sessionId);
460
+ if (!activeSessionId) {
461
+ console.error('No active session ID found, cannot logout all');
462
+ setError('No active session found');
463
+ throw new Error('No active session found');
464
+ }
465
+
466
+ if (!oxyServices) {
467
+ console.error('OxyServices not initialized');
468
+ setError('Service not available');
469
+ throw new Error('Service not available');
194
470
  }
195
- }, [authManager, storage, storageKeyPrefix]);
196
471
 
197
- const removeSession = useCallback(async (sessionId: string): Promise<void> => {
198
- await authManager.removeSession(sessionId);
199
- }, [authManager]);
472
+ try {
473
+ console.log('Calling oxyServices.logoutAllSecureSessions with sessionId:', activeSessionId);
474
+ await oxyServices.logoutAllSecureSessions(activeSessionId);
475
+ console.log('logoutAllSecureSessions completed successfully');
476
+
477
+ // Clear all local data
478
+ setSessions([]);
479
+ setActiveSessionId(null);
480
+ setUser(null);
481
+ setMinimalUser(null);
482
+ await clearAllStorage();
483
+ console.log('Local storage cleared');
484
+
485
+ if (onAuthStateChange) {
486
+ onAuthStateChange(null);
487
+ console.log('Auth state change callback called');
488
+ }
489
+ } catch (error) {
490
+ console.error('Logout all error:', error);
491
+ setError(`Logout all failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
492
+ throw error;
493
+ }
494
+ };
200
495
 
201
- const logoutAll = useCallback(async (): Promise<void> => {
202
- await authManager.logoutAll();
496
+ // Sign up method
497
+ const signUp = async (username: string, email: string, password: string): Promise<User> => {
498
+ if (!storage) throw new Error('Storage not initialized');
203
499
 
204
- // Clear from storage
205
- if (storage) {
206
- await storage.removeItem(`${storageKeyPrefix}_active_session_id`);
500
+ setIsLoading(true);
501
+ setError(null);
502
+
503
+ try {
504
+ // Create new account using the OxyServices signUp method
505
+ const response = await oxyServices.signUp(username, email, password);
506
+
507
+ console.log('SignUp successful:', response);
508
+
509
+ // Now log the user in securely to create a session
510
+ // This will handle the session creation and device registration
511
+ const user = await login(username, password);
512
+
513
+ return user;
514
+ } catch (error: any) {
515
+ setError(error.message || 'Sign up failed');
516
+ throw error;
517
+ } finally {
518
+ setIsLoading(false);
519
+ }
520
+ };
521
+
522
+ // Switch session method
523
+ const switchSession = async (sessionId: string): Promise<void> => {
524
+ await switchToSession(sessionId);
525
+ };
526
+
527
+ // Remove session method
528
+ const removeSession = async (sessionId: string): Promise<void> => {
529
+ await logout(sessionId);
530
+ };
531
+
532
+ // Refresh sessions method
533
+ const refreshSessions = async (): Promise<void> => {
534
+ if (!activeSessionId) return;
535
+
536
+ try {
537
+ const serverSessions = await oxyServices.getSessionsBySessionId(activeSessionId);
538
+
539
+ // Update local sessions with server data
540
+ const updatedSessions: SecureClientSession[] = serverSessions.map(serverSession => ({
541
+ sessionId: serverSession.sessionId,
542
+ deviceId: serverSession.deviceId,
543
+ expiresAt: new Date().toISOString(), // You might want to get this from server
544
+ lastActive: new Date().toISOString()
545
+ }));
546
+
547
+ setSessions(updatedSessions);
548
+ await saveSessionsToStorage(updatedSessions);
549
+ } catch (error) {
550
+ console.error('Refresh sessions error:', error);
207
551
  }
208
- }, [authManager, storage, storageKeyPrefix]);
552
+ };
553
+
554
+ // Device management methods
555
+ const getDeviceSessions = async (): Promise<any[]> => {
556
+ if (!activeSessionId) throw new Error('No active session');
557
+
558
+ try {
559
+ return await oxyServices.getDeviceSessions(activeSessionId);
560
+ } catch (error) {
561
+ console.error('Get device sessions error:', error);
562
+ throw error;
563
+ }
564
+ };
209
565
 
210
- const signUp = useCallback(async (username: string, email: string, password: string): Promise<User> => {
211
- await authManager.signUp(username, email, password);
566
+ const logoutAllDeviceSessions = async (): Promise<void> => {
567
+ if (!activeSessionId) throw new Error('No active session');
212
568
 
213
- // Save session ID to storage after auto-login
214
- const sessionId = authManager.getActiveSessionId();
215
- if (sessionId && storage) {
216
- await storage.setItem(`${storageKeyPrefix}_active_session_id`, sessionId);
569
+ try {
570
+ await oxyServices.logoutAllDeviceSessions(activeSessionId);
571
+
572
+ // Clear all local sessions since we logged out from all devices
573
+ setSessions([]);
574
+ setActiveSessionId(null);
575
+ setUser(null);
576
+ setMinimalUser(null);
577
+ await clearAllStorage();
578
+
579
+ if (onAuthStateChange) {
580
+ onAuthStateChange(null);
581
+ }
582
+ } catch (error) {
583
+ console.error('Logout all device sessions error:', error);
584
+ throw error;
217
585
  }
586
+ };
587
+
588
+ const updateDeviceName = async (deviceName: string): Promise<void> => {
589
+ if (!activeSessionId) throw new Error('No active session');
218
590
 
219
- return authManager.getCurrentUser();
220
- }, [authManager, storage, storageKeyPrefix]);
591
+ try {
592
+ await oxyServices.updateDeviceName(activeSessionId, deviceName);
221
593
 
222
- // Bottom sheet controls
594
+ // Update local device info
595
+ await DeviceManager.updateDeviceName(deviceName);
596
+ } catch (error) {
597
+ console.error('Update device name error:', error);
598
+ throw error;
599
+ }
600
+ };
601
+
602
+ // Bottom sheet control methods
223
603
  const showBottomSheet = useCallback((screenOrConfig?: string | { screen: string; props?: Record<string, any> }) => {
604
+ console.log('showBottomSheet called with:', screenOrConfig);
605
+
224
606
  if (bottomSheetRef?.current) {
607
+ console.log('bottomSheetRef is available');
608
+
609
+ // First, show the bottom sheet
225
610
  if (bottomSheetRef.current.expand) {
611
+ console.log('Expanding bottom sheet');
226
612
  bottomSheetRef.current.expand();
227
613
  } else if (bottomSheetRef.current.present) {
614
+ console.log('Presenting bottom sheet');
228
615
  bottomSheetRef.current.present();
616
+ } else {
617
+ console.warn('No expand or present method available on bottomSheetRef');
229
618
  }
230
619
 
620
+ // Then navigate to the specified screen if provided
231
621
  if (screenOrConfig) {
622
+ // Add a small delay to ensure the bottom sheet is opened first
232
623
  setTimeout(() => {
233
624
  if (typeof screenOrConfig === 'string') {
625
+ // Simple screen name
626
+ console.log('Navigating to screen:', screenOrConfig);
234
627
  bottomSheetRef.current?._navigateToScreen?.(screenOrConfig);
235
628
  } else {
629
+ // Screen with props
630
+ console.log('Navigating to screen with props:', screenOrConfig.screen, screenOrConfig.props);
236
631
  bottomSheetRef.current?._navigateToScreen?.(screenOrConfig.screen, screenOrConfig.props);
237
632
  }
238
633
  }, 100);
239
634
  }
635
+ } else {
636
+ console.warn('bottomSheetRef is not available');
240
637
  }
241
638
  }, [bottomSheetRef]);
242
639
 
@@ -246,22 +643,47 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
246
643
  }
247
644
  }, [bottomSheetRef]);
248
645
 
646
+ // API URL configuration
647
+ const setApiUrl = useCallback((url: string) => {
648
+ try {
649
+ oxyServices.setBaseURL(url);
650
+ } catch (error) {
651
+ console.error('Failed to update API URL:', error);
652
+ setError(`Failed to update API URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
653
+ }
654
+ }, [oxyServices]);
655
+
656
+ // Compute comprehensive authentication status
657
+ // This is the single source of truth for authentication across the entire app
658
+ const isAuthenticated = useMemo(() => {
659
+ // User is authenticated if:
660
+ // 1. We have a full user object loaded, OR
661
+ // 2. We have an active session (token will be fetched on-demand)
662
+ // This covers both the loaded state and the loading-but-authenticated state
663
+ return !!user || !!activeSessionId;
664
+ }, [user, activeSessionId]);
665
+
666
+ // Context value
249
667
  const contextValue: OxyContextState = {
250
- isAuthenticated: authState.isAuthenticated,
251
- user: authState.user,
252
- isLoading: authState.isLoading,
253
- error: authState.error,
254
- sessions: authState.sessions,
255
- activeSessionId: authState.activeSessionId,
668
+ user,
669
+ minimalUser,
670
+ sessions,
671
+ activeSessionId,
672
+ isAuthenticated,
673
+ isLoading,
674
+ error,
256
675
  login,
257
676
  logout,
677
+ logoutAll,
258
678
  signUp,
259
- refreshSessions,
260
679
  switchSession,
261
680
  removeSession,
262
- logoutAll,
681
+ refreshSessions,
682
+ getDeviceSessions,
683
+ logoutAllDeviceSessions,
684
+ updateDeviceName,
263
685
  oxyServices,
264
- authManager,
686
+ setApiUrl,
265
687
  bottomSheetRef,
266
688
  showBottomSheet,
267
689
  hideBottomSheet,
@@ -274,6 +696,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
274
696
  );
275
697
  };
276
698
 
699
+ // Hook to use the context
277
700
  export const useOxy = (): OxyContextState => {
278
701
  const context = useContext(OxyContext);
279
702
  if (!context) {