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