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