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