@oxyhq/services 5.5.7 → 5.5.9

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