@oxyhq/services 5.2.2 → 5.2.4

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 (64) hide show
  1. package/lib/commonjs/core/index.js +117 -0
  2. package/lib/commonjs/core/index.js.map +1 -1
  3. package/lib/commonjs/models/secureSession.js +2 -0
  4. package/lib/commonjs/models/secureSession.js.map +1 -0
  5. package/lib/commonjs/ui/context/LegacyOxyContext.js +643 -0
  6. package/lib/commonjs/ui/context/LegacyOxyContext.js.map +1 -0
  7. package/lib/commonjs/ui/context/OxyContext.js +215 -450
  8. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  9. package/lib/commonjs/ui/context/SecureOxyContext.js +408 -0
  10. package/lib/commonjs/ui/context/SecureOxyContext.js.map +1 -0
  11. package/lib/commonjs/ui/screens/AccountCenterScreen.js +3 -3
  12. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  13. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +31 -30
  14. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  15. package/lib/commonjs/ui/screens/AppInfoScreen.js +4 -4
  16. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  17. package/lib/commonjs/ui/screens/SessionManagementScreen.js +34 -34
  18. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  19. package/lib/commonjs/ui/screens/SignInScreen.js +2 -2
  20. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  21. package/lib/module/core/index.js +117 -0
  22. package/lib/module/core/index.js.map +1 -1
  23. package/lib/module/models/secureSession.js +2 -0
  24. package/lib/module/models/secureSession.js.map +1 -0
  25. package/lib/module/ui/context/LegacyOxyContext.js +639 -0
  26. package/lib/module/ui/context/LegacyOxyContext.js.map +1 -0
  27. package/lib/module/ui/context/OxyContext.js +214 -450
  28. package/lib/module/ui/context/OxyContext.js.map +1 -1
  29. package/lib/module/ui/context/SecureOxyContext.js +403 -0
  30. package/lib/module/ui/context/SecureOxyContext.js.map +1 -0
  31. package/lib/module/ui/screens/AccountCenterScreen.js +3 -3
  32. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  33. package/lib/module/ui/screens/AccountSwitcherScreen.js +31 -30
  34. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  35. package/lib/module/ui/screens/AppInfoScreen.js +4 -4
  36. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  37. package/lib/module/ui/screens/SessionManagementScreen.js +34 -34
  38. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  39. package/lib/module/ui/screens/SignInScreen.js +2 -2
  40. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  41. package/lib/typescript/core/index.d.ts +51 -0
  42. package/lib/typescript/core/index.d.ts.map +1 -1
  43. package/lib/typescript/models/secureSession.d.ts +25 -0
  44. package/lib/typescript/models/secureSession.d.ts.map +1 -0
  45. package/lib/typescript/ui/context/LegacyOxyContext.d.ts +40 -0
  46. package/lib/typescript/ui/context/LegacyOxyContext.d.ts.map +1 -0
  47. package/lib/typescript/ui/context/OxyContext.d.ts +11 -12
  48. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  49. package/lib/typescript/ui/context/SecureOxyContext.d.ts +39 -0
  50. package/lib/typescript/ui/context/SecureOxyContext.d.ts.map +1 -0
  51. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  52. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/src/core/index.ts +117 -0
  55. package/src/index.ts +2 -2
  56. package/src/models/secureSession.ts +27 -0
  57. package/src/ui/context/LegacyOxyContext.tsx +735 -0
  58. package/src/ui/context/OxyContext.tsx +412 -674
  59. package/src/ui/context/SecureOxyContext.tsx +473 -0
  60. package/src/ui/screens/AccountCenterScreen.tsx +4 -4
  61. package/src/ui/screens/AccountSwitcherScreen.tsx +36 -34
  62. package/src/ui/screens/AppInfoScreen.tsx +3 -3
  63. package/src/ui/screens/SessionManagementScreen.tsx +31 -35
  64. package/src/ui/screens/SignInScreen.tsx +2 -2
@@ -1,42 +1,37 @@
1
- import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
1
+ import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode, useMemo } from 'react';
2
2
  import { OxyServices } from '../../core';
3
- import { User, LoginResponse } from '../../models/interfaces';
4
-
5
- // Define authenticated user with tokens
6
- export interface AuthenticatedUser extends User {
7
- accessToken: string;
8
- refreshToken?: string;
9
- sessionId?: string;
10
- }
3
+ import { User } from '../../models/interfaces';
4
+ import { SecureLoginResponse, SecureClientSession, MinimalUserData } from '../../models/secureSession';
11
5
 
12
6
  // Define the context shape
13
7
  export interface OxyContextState {
14
- // Multi-user authentication state
15
- user: User | null; // Current active user
16
- users: AuthenticatedUser[]; // All authenticated users
17
- isAuthenticated: boolean;
18
- isLoading: boolean;
19
- error: string | null;
20
-
21
- // Auth methods
22
- login: (username: string, password: string) => Promise<User>;
23
- logout: (userId?: string) => Promise<void>; // Optional userId for multi-user logout
24
- logoutAll: () => Promise<void>; // Logout all users
25
- signUp: (username: string, email: string, password: string) => Promise<User>;
26
-
27
- // Multi-user methods
28
- switchUser: (userId: string) => Promise<void>;
29
- removeUser: (userId: string) => Promise<void>;
30
- getUserSessions: (userId?: string) => Promise<any[]>; // Get sessions for user
31
- logoutSession: (sessionId: string, userId?: string) => Promise<void>; // Logout specific session
32
-
33
- // Access to services
34
- oxyServices: OxyServices;
35
- bottomSheetRef?: React.RefObject<any>;
36
-
37
- // Methods to directly control the bottom sheet
38
- showBottomSheet?: (screenOrConfig?: string | { screen: string; props?: Record<string, any> }) => void;
39
- hideBottomSheet?: () => void;
8
+ // Authentication state
9
+ user: User | null; // Current active user (loaded from server)
10
+ minimalUser: MinimalUserData | null; // Minimal user data for UI
11
+ sessions: SecureClientSession[]; // All active sessions
12
+ activeSessionId: string | null;
13
+ isAuthenticated: boolean;
14
+ isLoading: boolean;
15
+ error: string | null;
16
+
17
+ // Auth methods
18
+ login: (username: string, password: string, deviceName?: string) => Promise<User>;
19
+ logout: (targetSessionId?: string) => Promise<void>;
20
+ logoutAll: () => Promise<void>;
21
+ signUp: (username: string, email: string, password: string) => Promise<User>;
22
+
23
+ // Multi-session methods
24
+ switchSession: (sessionId: string) => Promise<void>;
25
+ removeSession: (sessionId: string) => Promise<void>;
26
+ refreshSessions: () => Promise<void>;
27
+
28
+ // Access to services
29
+ oxyServices: OxyServices;
30
+ bottomSheetRef?: React.RefObject<any>;
31
+
32
+ // Methods to directly control the bottom sheet
33
+ showBottomSheet?: (screenOrConfig?: string | { screen: string; props?: Record<string, any> }) => void;
34
+ hideBottomSheet?: () => void;
40
35
  }
41
36
 
42
37
  // Create the context with default values
@@ -44,692 +39,435 @@ const OxyContext = createContext<OxyContextState | null>(null);
44
39
 
45
40
  // Props for the OxyContextProvider
46
41
  export interface OxyContextProviderProps {
47
- children: ReactNode;
48
- oxyServices: OxyServices;
49
- storageKeyPrefix?: string;
50
- onAuthStateChange?: (user: User | null) => void;
51
- bottomSheetRef?: React.RefObject<any>;
42
+ children: ReactNode;
43
+ oxyServices: OxyServices;
44
+ storageKeyPrefix?: string;
45
+ onAuthStateChange?: (user: User | null) => void;
46
+ bottomSheetRef?: React.RefObject<any>;
52
47
  }
53
48
 
54
49
  // Platform storage implementation
55
50
  interface StorageInterface {
56
- getItem: (key: string) => Promise<string | null>;
57
- setItem: (key: string, value: string) => Promise<void>;
58
- removeItem: (key: string) => Promise<void>;
59
- clear: () => Promise<void>;
51
+ getItem: (key: string) => Promise<string | null>;
52
+ setItem: (key: string, value: string) => Promise<void>;
53
+ removeItem: (key: string) => Promise<void>;
54
+ clear: () => Promise<void>;
60
55
  }
61
56
 
62
57
  // Web localStorage implementation
63
58
  class WebStorage implements StorageInterface {
64
- async getItem(key: string): Promise<string | null> {
65
- return localStorage.getItem(key);
66
- }
59
+ async getItem(key: string): Promise<string | null> {
60
+ return localStorage.getItem(key);
61
+ }
67
62
 
68
- async setItem(key: string, value: string): Promise<void> {
69
- localStorage.setItem(key, value);
70
- }
63
+ async setItem(key: string, value: string): Promise<void> {
64
+ localStorage.setItem(key, value);
65
+ }
71
66
 
72
- async removeItem(key: string): Promise<void> {
73
- localStorage.removeItem(key);
74
- }
67
+ async removeItem(key: string): Promise<void> {
68
+ localStorage.removeItem(key);
69
+ }
75
70
 
76
- async clear(): Promise<void> {
77
- localStorage.clear();
78
- }
71
+ async clear(): Promise<void> {
72
+ localStorage.clear();
73
+ }
79
74
  }
80
75
 
81
76
  // React Native AsyncStorage implementation
82
- // This will be dynamically imported only in React Native environment
83
77
  let AsyncStorage: StorageInterface;
84
78
 
85
79
  // Determine the platform and set up storage
86
80
  const isReactNative = (): boolean => {
87
- return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
81
+ return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
88
82
  };
89
83
 
90
84
  // Get appropriate storage for the platform
91
85
  const getStorage = async (): Promise<StorageInterface> => {
92
- if (isReactNative()) {
93
- // Dynamically import AsyncStorage only in React Native environment
94
- if (!AsyncStorage) {
95
- try {
96
- const asyncStorageModule = await import('@react-native-async-storage/async-storage');
97
- AsyncStorage = asyncStorageModule.default;
98
- } catch (error) {
99
- console.error('Failed to import AsyncStorage:', error);
100
- throw new Error('AsyncStorage is required in React Native environment');
101
- }
102
- }
103
- return AsyncStorage;
86
+ if (isReactNative()) {
87
+ if (!AsyncStorage) {
88
+ try {
89
+ const asyncStorageModule = await import('@react-native-async-storage/async-storage');
90
+ AsyncStorage = asyncStorageModule.default;
91
+ } catch (error) {
92
+ console.error('Failed to import AsyncStorage:', error);
93
+ throw new Error('AsyncStorage is required in React Native environment');
94
+ }
104
95
  }
96
+ return AsyncStorage;
97
+ }
105
98
 
106
- // Default to web storage
107
- return new WebStorage();
99
+ return new WebStorage();
108
100
  };
109
101
 
110
- // Storage keys
111
- const getStorageKeys = (prefix = 'oxy') => ({
112
- users: `${prefix}_users`, // Array of authenticated users with tokens
113
- activeUserId: `${prefix}_active_user_id`, // ID of currently active user
114
- // Legacy keys for migration
115
- accessToken: `${prefix}_access_token`,
116
- refreshToken: `${prefix}_refresh_token`,
117
- user: `${prefix}_user`,
102
+ // Storage keys for secure sessions
103
+ const getSecureStorageKeys = (prefix = 'oxy_secure') => ({
104
+ sessions: `${prefix}_sessions`, // Array of SecureClientSession objects
105
+ activeSessionId: `${prefix}_active_session_id`, // ID of currently active session
118
106
  });
119
107
 
120
108
  export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
121
- children,
122
- oxyServices,
123
- storageKeyPrefix = 'oxy',
124
- onAuthStateChange,
125
- bottomSheetRef,
109
+ children,
110
+ oxyServices,
111
+ storageKeyPrefix = 'oxy_secure',
112
+ onAuthStateChange,
113
+ bottomSheetRef,
126
114
  }) => {
127
- // Authentication state
128
- const [user, setUser] = useState<User | null>(null);
129
- const [users, setUsers] = useState<AuthenticatedUser[]>([]);
130
- const [isLoading, setIsLoading] = useState(true);
131
- const [error, setError] = useState<string | null>(null);
132
- const [storage, setStorage] = useState<StorageInterface | null>(null);
133
-
134
- // Storage keys
135
- const keys = getStorageKeys(storageKeyPrefix);
136
-
137
- // Initialize storage
138
- useEffect(() => {
139
- const initStorage = async () => {
140
- try {
141
- const platformStorage = await getStorage();
142
- setStorage(platformStorage);
143
- } catch (error) {
144
- console.error('Failed to initialize storage:', error);
145
- setError('Failed to initialize storage');
146
- }
147
- };
148
-
149
- initStorage();
150
- }, []);
151
-
152
- // Effect to initialize authentication state
153
- useEffect(() => {
154
- const initAuth = async () => {
155
- if (!storage) return;
156
-
157
- setIsLoading(true);
158
- try {
159
- // Check for multi-user data first
160
- const usersData = await storage.getItem(keys.users);
161
- const activeUserId = await storage.getItem(keys.activeUserId);
162
-
163
- console.log('InitAuth - usersData:', usersData);
164
- console.log('InitAuth - activeUserId:', activeUserId);
165
-
166
- if (usersData) {
167
- // Multi-user setup exists
168
- const parsedUsers: AuthenticatedUser[] = JSON.parse(usersData);
169
- console.log('InitAuth - parsedUsers:', parsedUsers);
170
- setUsers(parsedUsers);
171
-
172
- if (activeUserId && parsedUsers.length > 0) {
173
- const activeUser = parsedUsers.find(u => u.id === activeUserId);
174
- console.log('InitAuth - activeUser found:', activeUser);
175
- if (activeUser) {
176
- setUser(activeUser);
177
- oxyServices.setTokens(activeUser.accessToken, activeUser.refreshToken || activeUser.accessToken);
178
-
179
- // Validate the tokens
180
- const isValid = await oxyServices.validate();
181
- console.log('InitAuth - token validation result:', isValid);
182
- if (!isValid) {
183
- // Remove invalid user during initialization
184
- console.log('InitAuth - removing invalid user due to failed validation');
185
- const filteredUsers = parsedUsers.filter(u => u.id !== activeUser.id);
186
- setUsers(filteredUsers);
187
- await saveUsersToStorage(filteredUsers);
188
-
189
- // If there are other users, switch to the first one
190
- if (filteredUsers.length > 0) {
191
- const newActiveUser = filteredUsers[0];
192
- setUser(newActiveUser);
193
- await saveActiveUserId(newActiveUser.id);
194
- oxyServices.setTokens(newActiveUser.accessToken, newActiveUser.refreshToken || newActiveUser.accessToken);
195
-
196
- if (onAuthStateChange) {
197
- onAuthStateChange(newActiveUser);
198
- }
199
- } else {
200
- // No valid users left
201
- setUser(null);
202
- await storage.removeItem(keys.activeUserId);
203
- oxyServices.clearTokens();
204
-
205
- if (onAuthStateChange) {
206
- onAuthStateChange(null);
207
- }
208
- }
209
- } else {
210
- console.log('InitAuth - user validated successfully, setting auth state');
211
- // Notify about auth state change
212
- if (onAuthStateChange) {
213
- onAuthStateChange(activeUser);
214
- }
215
- }
216
- }
217
- }
218
- } else {
219
- console.log('InitAuth - no users data, checking legacy auth');
220
- // Check for legacy single-user data and migrate
221
- await migrateLegacyAuth();
222
- }
223
- } catch (err) {
224
- console.error('Auth initialization error:', err);
225
- await clearAllStorage();
226
- oxyServices.clearTokens();
227
- } finally {
228
- setIsLoading(false);
229
- }
230
- };
231
-
232
- if (storage) {
233
- initAuth();
234
- }
235
- }, [storage, oxyServices, keys.users, keys.activeUserId, onAuthStateChange]);
236
-
237
- // Migrate legacy single-user authentication to multi-user
238
- const migrateLegacyAuth = async (): Promise<void> => {
239
- if (!storage) return;
240
-
241
- try {
242
- const accessToken = await storage.getItem(keys.accessToken);
243
- const refreshToken = await storage.getItem(keys.refreshToken);
244
- const storedUser = await storage.getItem(keys.user);
245
-
246
- if (accessToken && storedUser) {
247
- // Set tokens in OxyServices
248
- oxyServices.setTokens(accessToken, refreshToken || accessToken);
249
-
250
- // Validate the tokens
251
- const isValid = await oxyServices.validate();
252
-
253
- if (isValid) {
254
- const parsedUser = JSON.parse(storedUser);
255
- const authenticatedUser: AuthenticatedUser = {
256
- ...parsedUser,
257
- accessToken,
258
- refreshToken: refreshToken || undefined,
259
- };
260
-
261
- // Store in new multi-user format
262
- await storage.setItem(keys.users, JSON.stringify([authenticatedUser]));
263
- await storage.setItem(keys.activeUserId, authenticatedUser.id);
264
-
265
- // Set state
266
- setUsers([authenticatedUser]);
267
- setUser(authenticatedUser);
268
-
269
- // Notify about auth state change
270
- if (onAuthStateChange) {
271
- onAuthStateChange(authenticatedUser);
272
- }
273
- }
274
-
275
- // Clear legacy storage
276
- await storage.removeItem(keys.accessToken);
277
- await storage.removeItem(keys.refreshToken);
278
- await storage.removeItem(keys.user);
279
- }
280
- } catch (err) {
281
- console.error('Migration error:', err);
282
- }
283
- };
284
-
285
- // Helper to clear legacy storage
286
- const clearStorage = async (): Promise<void> => {
287
- if (!storage) return;
288
-
289
- try {
290
- await storage.removeItem(keys.accessToken);
291
- await storage.removeItem(keys.refreshToken);
292
- await storage.removeItem(keys.user);
293
- } catch (err) {
294
- console.error('Clear storage error:', err);
295
- }
115
+ // Authentication state
116
+ const [user, setUser] = useState<User | null>(null);
117
+ const [minimalUser, setMinimalUser] = useState<MinimalUserData | null>(null);
118
+ const [sessions, setSessions] = useState<SecureClientSession[]>([]);
119
+ const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
120
+ const [isLoading, setIsLoading] = useState(true);
121
+ const [error, setError] = useState<string | null>(null);
122
+ const [storage, setStorage] = useState<StorageInterface | null>(null);
123
+
124
+ // Storage keys (memoized to prevent infinite loops)
125
+ const keys = useMemo(() => getSecureStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
126
+
127
+ // Initialize storage
128
+ useEffect(() => {
129
+ const initStorage = async () => {
130
+ try {
131
+ const platformStorage = await getStorage();
132
+ setStorage(platformStorage);
133
+ } catch (error) {
134
+ console.error('Failed to initialize storage:', error);
135
+ setError('Failed to initialize storage');
136
+ }
296
137
  };
297
138
 
298
- // Helper to clear all storage (multi-user)
299
- const clearAllStorage = async (): Promise<void> => {
300
- if (!storage) return;
301
-
302
- try {
303
- await storage.removeItem(keys.users);
304
- await storage.removeItem(keys.activeUserId);
305
- // Also clear legacy keys
306
- await clearStorage();
307
- } catch (err) {
308
- console.error('Clear all storage error:', err);
309
- }
310
- };
311
-
312
- // Save users to storage
313
- const saveUsersToStorage = async (usersList: AuthenticatedUser[]): Promise<void> => {
314
- if (!storage) return;
315
- await storage.setItem(keys.users, JSON.stringify(usersList));
316
- };
139
+ initStorage();
140
+ }, []);
317
141
 
318
- // Save active user ID to storage
319
- const saveActiveUserId = async (userId: string): Promise<void> => {
320
- if (!storage) return;
321
- await storage.setItem(keys.activeUserId, userId);
322
- };
142
+ // Effect to initialize authentication state
143
+ useEffect(() => {
144
+ const initAuth = async () => {
145
+ if (!storage) return;
323
146
 
324
- // Utility function to handle different token response formats
325
- const storeTokens = async (response: any) => {
326
- // Store token and user data
327
- if (response.accessToken) {
328
- await storage?.setItem(keys.accessToken, response.accessToken);
329
- if (response.refreshToken) {
330
- await storage?.setItem(keys.refreshToken, response.refreshToken);
331
- }
332
- } else if (response.token) {
333
- // Handle legacy API response
334
- await storage?.setItem(keys.accessToken, response.token);
335
- }
336
- await storage?.setItem(keys.user, JSON.stringify(response.user));
337
- };
147
+ setIsLoading(true);
148
+ try {
149
+ // Load stored sessions
150
+ const sessionsData = await storage.getItem(keys.sessions);
151
+ const storedActiveSessionId = await storage.getItem(keys.activeSessionId);
338
152
 
339
- // Login method (updated for multi-user)
340
- const login = async (username: string, password: string): Promise<User> => {
341
- if (!storage) throw new Error('Storage not initialized');
153
+ console.log('SecureAuth - sessionsData:', sessionsData);
154
+ console.log('SecureAuth - activeSessionId:', storedActiveSessionId);
342
155
 
343
- setIsLoading(true);
344
- setError(null);
156
+ if (sessionsData) {
157
+ const parsedSessions: SecureClientSession[] = JSON.parse(sessionsData);
158
+ setSessions(parsedSessions);
345
159
 
346
- try {
347
- const response = await oxyServices.login(username, password);
348
- const accessToken = response.accessToken || (response as any).token;
160
+ if (storedActiveSessionId && parsedSessions.length > 0) {
161
+ const activeSession = parsedSessions.find(s => s.sessionId === storedActiveSessionId);
349
162
 
350
- if (!accessToken) {
351
- throw new Error('No access token received from login');
352
- }
353
-
354
- const newUser: AuthenticatedUser = {
355
- ...response.user,
356
- accessToken,
357
- refreshToken: response.refreshToken,
358
- // sessionId will be set by backend, but we don't get it in response yet
359
- };
360
-
361
- // Check if user already exists
362
- const existingUserIndex = users.findIndex(u => u.id === newUser.id);
363
- let updatedUsers: AuthenticatedUser[];
364
-
365
- if (existingUserIndex >= 0) {
366
- // Update existing user
367
- updatedUsers = [...users];
368
- updatedUsers[existingUserIndex] = newUser;
369
- } else {
370
- // Add new user
371
- updatedUsers = [...users, newUser];
372
- }
373
-
374
- // Update state
375
- setUsers(updatedUsers);
376
- setUser(newUser);
377
-
378
- // Save to storage
379
- await saveUsersToStorage(updatedUsers);
380
- await saveActiveUserId(newUser.id);
381
-
382
- // Notify about auth state change
383
- if (onAuthStateChange) {
384
- onAuthStateChange(newUser);
385
- }
386
-
387
- return newUser;
388
- } catch (err: any) {
389
- setError(err.message || 'Login failed');
390
- throw err;
391
- } finally {
392
- setIsLoading(false);
393
- }
394
- };
395
-
396
- // Logout method (supports multi-user)
397
- const logout = async (userId?: string): Promise<void> => {
398
- if (!storage) throw new Error('Storage not initialized');
399
-
400
- setIsLoading(true);
401
- setError(null);
402
-
403
- try {
404
- const targetUserId = userId || user?.id;
405
- if (!targetUserId) return;
406
-
407
- const targetUser = users.find(u => u.id === targetUserId);
408
- if (targetUser) {
409
- // Set the target user's tokens to logout
410
- oxyServices.setTokens(targetUser.accessToken, targetUser.refreshToken || targetUser.accessToken);
163
+ if (activeSession) {
164
+ console.log('SecureAuth - activeSession found:', activeSession);
165
+
166
+ // Validate session
167
+ try {
168
+ const validation = await oxyServices.validateSession(activeSession.sessionId);
411
169
 
412
- try {
413
- await oxyServices.logout();
414
- } catch (logoutError) {
415
- console.warn('Logout API call failed:', logoutError);
416
- }
417
-
418
- // Remove user from list
419
- const updatedUsers = users.filter(u => u.id !== targetUserId);
420
- setUsers(updatedUsers);
421
-
422
- // If logging out current user, switch to another user or clear
423
- if (targetUserId === user?.id) {
424
- if (updatedUsers.length > 0) {
425
- // Switch to first available user
426
- const nextUser = updatedUsers[0];
427
- setUser(nextUser);
428
- oxyServices.setTokens(nextUser.accessToken, nextUser.refreshToken || nextUser.accessToken);
429
- await saveActiveUserId(nextUser.id);
430
-
431
- if (onAuthStateChange) {
432
- onAuthStateChange(nextUser);
433
- }
434
- } else {
435
- // No users left
436
- setUser(null);
437
- oxyServices.clearTokens();
438
- await storage.removeItem(keys.activeUserId);
439
-
440
- if (onAuthStateChange) {
441
- onAuthStateChange(null);
442
- }
443
- }
444
- }
445
-
446
- // Save updated users list
447
- await saveUsersToStorage(updatedUsers);
448
- }
449
- } catch (err: any) {
450
- setError(err.message || 'Logout failed');
451
- throw err;
452
- } finally {
453
- setIsLoading(false);
454
- }
455
- };
456
-
457
- // Logout all users
458
- const logoutAll = async (): Promise<void> => {
459
- if (!storage) throw new Error('Storage not initialized');
460
-
461
- setIsLoading(true);
462
- setError(null);
463
-
464
- try {
465
- // Logout each user
466
- for (const userItem of users) {
467
- try {
468
- oxyServices.setTokens(userItem.accessToken, userItem.refreshToken || userItem.accessToken);
469
- await oxyServices.logout();
470
- } catch (logoutError) {
471
- console.warn(`Logout failed for user ${userItem.id}:`, logoutError);
472
- }
473
- }
474
-
475
- // Clear all state and storage
476
- setUsers([]);
477
- setUser(null);
478
- oxyServices.clearTokens();
479
- await clearAllStorage();
480
-
481
- // Notify about auth state change
482
- if (onAuthStateChange) {
483
- onAuthStateChange(null);
484
- }
485
- } catch (err: any) {
486
- setError(err.message || 'Logout all failed');
487
- throw err;
488
- } finally {
489
- setIsLoading(false);
490
- }
491
- };
492
-
493
- // Switch user
494
- const switchUser = async (userId: string): Promise<void> => {
495
- if (!storage) throw new Error('Storage not initialized');
496
-
497
- setError(null);
498
-
499
- try {
500
- const targetUser = users.find(u => u.id === userId);
501
- if (!targetUser) {
502
- throw new Error('User not found');
503
- }
504
-
505
- // Validate tokens before switching
506
- oxyServices.setTokens(targetUser.accessToken, targetUser.refreshToken || targetUser.accessToken);
507
- const isValid = await oxyServices.validate();
508
-
509
- if (!isValid) {
510
- // Remove invalid user
511
- await removeUser(userId);
512
- throw new Error('User session is invalid');
513
- }
514
-
515
- // Switch to the user
516
- setUser(targetUser);
517
- await saveActiveUserId(userId);
518
-
519
- // Notify about auth state change
520
- if (onAuthStateChange) {
521
- onAuthStateChange(targetUser);
522
- }
523
- } catch (err: any) {
524
- setError(err.message || 'Switch user failed');
525
- throw err;
526
- }
527
- };
528
-
529
- // Remove user
530
- const removeUser = async (userId: string): Promise<void> => {
531
- if (!storage) throw new Error('Storage not initialized');
532
-
533
- try {
534
- const updatedUsers = users.filter(u => u.id !== userId);
535
- setUsers(updatedUsers);
536
-
537
- // If removing current user, switch to another or clear
538
- if (userId === user?.id) {
539
- if (updatedUsers.length > 0) {
540
- await switchUser(updatedUsers[0].id);
170
+ if (validation.valid) {
171
+ console.log('SecureAuth - session validated successfully');
172
+ setActiveSessionId(activeSession.sessionId);
173
+
174
+ // Get access token for API calls
175
+ await oxyServices.getTokenBySession(activeSession.sessionId);
176
+
177
+ // Load full user data
178
+ const fullUser = await oxyServices.getUserBySession(activeSession.sessionId);
179
+ setUser(fullUser);
180
+ setMinimalUser({
181
+ id: fullUser.id,
182
+ username: fullUser.username,
183
+ avatar: fullUser.avatar
184
+ });
185
+
186
+ if (onAuthStateChange) {
187
+ onAuthStateChange(fullUser);
188
+ }
541
189
  } else {
542
- setUser(null);
543
- oxyServices.clearTokens();
544
- await storage.removeItem(keys.activeUserId);
545
-
546
- if (onAuthStateChange) {
547
- onAuthStateChange(null);
548
- }
549
- }
550
- }
551
-
552
- // Save updated users list
553
- await saveUsersToStorage(updatedUsers);
554
- } catch (err: any) {
555
- setError(err.message || 'Remove user failed');
556
- throw err;
557
- }
558
- };
559
-
560
- // Get user sessions
561
- const getUserSessions = async (userId?: string): Promise<any[]> => {
562
- try {
563
- const targetUserId = userId || user?.id;
564
- if (!targetUserId) return [];
565
-
566
- const targetUser = users.find(u => u.id === targetUserId);
567
- if (!targetUser) return [];
568
-
569
- // Store current tokens to restore later
570
- const currentUser = user;
571
- const wasCurrentUser = targetUserId === user?.id;
572
-
573
- if (!wasCurrentUser) {
574
- // Temporarily switch to target user's tokens
575
- oxyServices.setTokens(targetUser.accessToken, targetUser.refreshToken || targetUser.accessToken);
576
- }
577
-
578
- try {
579
- // Use the new OxyServices method
580
- const sessions = await oxyServices.getUserSessions();
581
- return sessions;
582
- } finally {
583
- if (!wasCurrentUser && currentUser) {
584
- // Restore original tokens
585
- oxyServices.setTokens(currentUser.accessToken, currentUser.refreshToken || currentUser.accessToken);
190
+ console.log('SecureAuth - session invalid, removing');
191
+ await removeInvalidSession(activeSession.sessionId);
586
192
  }
193
+ } catch (error) {
194
+ console.error('SecureAuth - session validation error:', error);
195
+ await removeInvalidSession(activeSession.sessionId);
196
+ }
587
197
  }
588
- } catch (err: any) {
589
- console.error('Get user sessions failed:', err);
590
- return [];
198
+ }
591
199
  }
200
+ } catch (err) {
201
+ console.error('Secure auth initialization error:', err);
202
+ await clearAllStorage();
203
+ } finally {
204
+ setIsLoading(false);
205
+ }
592
206
  };
593
207
 
594
- // Logout specific session
595
- const logoutSession = async (sessionId: string, userId?: string): Promise<void> => {
596
- try {
597
- const targetUserId = userId || user?.id;
598
- if (!targetUserId) return;
599
-
600
- const targetUser = users.find(u => u.id === targetUserId);
601
- if (!targetUser) return;
602
-
603
- // Store current tokens to restore later
604
- const currentUser = user;
605
- const wasCurrentUser = targetUserId === user?.id;
606
-
607
- if (!wasCurrentUser) {
608
- // Temporarily switch to target user's tokens
609
- oxyServices.setTokens(targetUser.accessToken, targetUser.refreshToken || targetUser.accessToken);
610
- }
611
-
612
- try {
613
- // Use the new OxyServices method
614
- await oxyServices.logoutSession(sessionId);
615
-
616
- // If this is the current user's session, remove them from local state
617
- if (wasCurrentUser && sessionId === targetUser.sessionId) {
618
- await removeUser(targetUserId);
619
- }
620
- } finally {
621
- if (!wasCurrentUser && currentUser) {
622
- // Restore original tokens
623
- oxyServices.setTokens(currentUser.accessToken, currentUser.refreshToken || currentUser.accessToken);
624
- }
625
- }
626
- } catch (err: any) {
627
- console.error('Logout session failed:', err);
628
- throw err;
629
- }
630
- };
631
-
632
- // Sign up method
633
- const signUp = async (username: string, email: string, password: string): Promise<User> => {
634
- if (!storage) throw new Error('Storage not initialized');
635
-
636
- setIsLoading(true);
637
- setError(null);
638
-
639
- try {
640
- const response = await oxyServices.signUp(username, email, password);
641
- setUser(response.user);
642
-
643
- // Store tokens
644
- await storeTokens(response);
645
-
646
- // Notify about auth state change
647
- if (onAuthStateChange) {
648
- onAuthStateChange(response.user);
649
- }
650
-
651
- return response.user;
652
- } catch (err: any) {
653
- setError(err.message || 'Sign up failed');
654
- throw err;
655
- } finally {
656
- setIsLoading(false);
657
- }
658
- };
659
-
660
- // Methods to control the bottom sheet
661
- const showBottomSheet = useCallback((screenOrConfig?: string | { screen: string; props?: Record<string, any> }) => {
662
- if (bottomSheetRef?.current) {
663
- // Expand the bottom sheet
664
- bottomSheetRef.current.expand();
665
- if (typeof screenOrConfig === 'string') {
666
- // If a screen is specified, navigate to it
667
- if (screenOrConfig && bottomSheetRef.current._navigateToScreen) {
668
- setTimeout(() => {
669
- bottomSheetRef.current._navigateToScreen(screenOrConfig);
670
- }, 100);
671
- }
672
- } else if (screenOrConfig && typeof screenOrConfig === 'object' && screenOrConfig.screen) {
673
- // If an object is passed, navigate and pass props
674
- if (bottomSheetRef.current._navigateToScreen) {
675
- setTimeout(() => {
676
- bottomSheetRef.current._navigateToScreen(screenOrConfig.screen, screenOrConfig.props || {});
677
- }, 100);
678
- }
679
- }
680
- }
681
- }, [bottomSheetRef]);
682
-
683
- const hideBottomSheet = useCallback(() => {
684
- if (bottomSheetRef?.current) {
685
- bottomSheetRef.current.close();
208
+ if (storage) {
209
+ initAuth();
210
+ }
211
+ }, [storage, oxyServices, keys, onAuthStateChange]);
212
+
213
+ // Remove invalid session
214
+ const removeInvalidSession = useCallback(async (sessionId: string): Promise<void> => {
215
+ const filteredSessions = sessions.filter(s => s.sessionId !== sessionId);
216
+ setSessions(filteredSessions);
217
+ await saveSessionsToStorage(filteredSessions);
218
+
219
+ // If there are other sessions, switch to the first one
220
+ if (filteredSessions.length > 0) {
221
+ await switchToSession(filteredSessions[0].sessionId);
222
+ } else {
223
+ // No valid sessions left
224
+ setActiveSessionId(null);
225
+ setUser(null);
226
+ setMinimalUser(null);
227
+ await storage?.removeItem(keys.activeSessionId);
228
+
229
+ if (onAuthStateChange) {
230
+ onAuthStateChange(null);
231
+ }
232
+ }
233
+ }, [sessions, storage, keys, onAuthStateChange]);
234
+
235
+ // Save sessions to storage
236
+ const saveSessionsToStorage = useCallback(async (sessionsList: SecureClientSession[]): Promise<void> => {
237
+ if (!storage) return;
238
+ await storage.setItem(keys.sessions, JSON.stringify(sessionsList));
239
+ }, [storage, keys.sessions]);
240
+
241
+ // Save active session ID to storage
242
+ const saveActiveSessionId = useCallback(async (sessionId: string): Promise<void> => {
243
+ if (!storage) return;
244
+ await storage.setItem(keys.activeSessionId, sessionId);
245
+ }, [storage, keys.activeSessionId]);
246
+
247
+ // Clear all storage
248
+ const clearAllStorage = useCallback(async (): Promise<void> => {
249
+ if (!storage) return;
250
+ try {
251
+ await storage.removeItem(keys.sessions);
252
+ await storage.removeItem(keys.activeSessionId);
253
+ } catch (err) {
254
+ console.error('Clear secure storage error:', err);
255
+ }
256
+ }, [storage, keys]);
257
+
258
+ // Switch to a different session
259
+ const switchToSession = useCallback(async (sessionId: string): Promise<void> => {
260
+ try {
261
+ setIsLoading(true);
262
+
263
+ // Get access token for this session
264
+ await oxyServices.getTokenBySession(sessionId);
265
+
266
+ // Load full user data
267
+ const fullUser = await oxyServices.getUserBySession(sessionId);
268
+
269
+ setActiveSessionId(sessionId);
270
+ setUser(fullUser);
271
+ setMinimalUser({
272
+ id: fullUser.id,
273
+ username: fullUser.username,
274
+ avatar: fullUser.avatar
275
+ });
276
+
277
+ await saveActiveSessionId(sessionId);
278
+
279
+ if (onAuthStateChange) {
280
+ onAuthStateChange(fullUser);
281
+ }
282
+ } catch (error) {
283
+ console.error('Switch session error:', error);
284
+ setError('Failed to switch session');
285
+ } finally {
286
+ setIsLoading(false);
287
+ }
288
+ }, [oxyServices, onAuthStateChange, saveActiveSessionId]);
289
+
290
+ // Secure login method
291
+ const login = async (username: string, password: string, deviceName?: string): Promise<User> => {
292
+ if (!storage) throw new Error('Storage not initialized');
293
+
294
+ setIsLoading(true);
295
+ setError(null);
296
+
297
+ try {
298
+ const response: SecureLoginResponse = await oxyServices.secureLogin(username, password, deviceName);
299
+
300
+ // Create client session object
301
+ const clientSession: SecureClientSession = {
302
+ sessionId: response.sessionId,
303
+ deviceId: response.deviceId,
304
+ expiresAt: response.expiresAt,
305
+ lastActive: new Date().toISOString()
306
+ };
307
+
308
+ // Add to sessions list
309
+ const updatedSessions = [...sessions, clientSession];
310
+ setSessions(updatedSessions);
311
+ await saveSessionsToStorage(updatedSessions);
312
+
313
+ // Set as active session
314
+ setActiveSessionId(response.sessionId);
315
+ await saveActiveSessionId(response.sessionId);
316
+
317
+ // Get access token for API calls
318
+ await oxyServices.getTokenBySession(response.sessionId);
319
+
320
+ // Load full user data
321
+ const fullUser = await oxyServices.getUserBySession(response.sessionId);
322
+ setUser(fullUser);
323
+ setMinimalUser(response.user);
324
+
325
+ if (onAuthStateChange) {
326
+ onAuthStateChange(fullUser);
327
+ }
328
+
329
+ return fullUser;
330
+ } catch (error: any) {
331
+ setError(error.message || 'Login failed');
332
+ throw error;
333
+ } finally {
334
+ setIsLoading(false);
335
+ }
336
+ };
337
+
338
+ // Logout method
339
+ const logout = async (targetSessionId?: string): Promise<void> => {
340
+ if (!activeSessionId) return;
341
+
342
+ try {
343
+ const sessionToLogout = targetSessionId || activeSessionId;
344
+ await oxyServices.logoutSecureSession(activeSessionId, sessionToLogout);
345
+
346
+ // Remove session from local storage
347
+ const filteredSessions = sessions.filter(s => s.sessionId !== sessionToLogout);
348
+ setSessions(filteredSessions);
349
+ await saveSessionsToStorage(filteredSessions);
350
+
351
+ // If logging out active session
352
+ if (sessionToLogout === activeSessionId) {
353
+ if (filteredSessions.length > 0) {
354
+ // Switch to another session
355
+ await switchToSession(filteredSessions[0].sessionId);
356
+ } else {
357
+ // No sessions left
358
+ setActiveSessionId(null);
359
+ setUser(null);
360
+ setMinimalUser(null);
361
+ await storage?.removeItem(keys.activeSessionId);
362
+
363
+ if (onAuthStateChange) {
364
+ onAuthStateChange(null);
365
+ }
686
366
  }
687
- }, [bottomSheetRef]);
688
-
689
- // Build context value
690
- const contextValue: OxyContextState = {
691
- // Single user state (current active user)
692
- user,
693
- isAuthenticated: !!user,
694
- isLoading,
695
- error,
696
-
697
- // Multi-user state
698
- users,
699
-
700
- // Auth methods
701
- login,
702
- logout,
703
- logoutAll,
704
- signUp,
705
-
706
- // Multi-user methods
707
- switchUser,
708
- removeUser,
709
- getUserSessions,
710
- logoutSession,
711
-
712
- // OxyServices instance
713
- oxyServices,
714
-
715
- // Bottom sheet methods
716
- bottomSheetRef,
717
- showBottomSheet,
718
- hideBottomSheet,
719
- };
720
-
721
- return (
722
- <OxyContext.Provider value={contextValue}>
723
- {children}
724
- </OxyContext.Provider>
725
- );
367
+ }
368
+ } catch (error) {
369
+ console.error('Logout error:', error);
370
+ setError('Logout failed');
371
+ }
372
+ };
373
+
374
+ // Logout all sessions
375
+ const logoutAll = async (): Promise<void> => {
376
+ if (!activeSessionId) return;
377
+
378
+ try {
379
+ await oxyServices.logoutAllSecureSessions(activeSessionId);
380
+
381
+ // Clear all local data
382
+ setSessions([]);
383
+ setActiveSessionId(null);
384
+ setUser(null);
385
+ setMinimalUser(null);
386
+ await clearAllStorage();
387
+
388
+ if (onAuthStateChange) {
389
+ onAuthStateChange(null);
390
+ }
391
+ } catch (error) {
392
+ console.error('Logout all error:', error);
393
+ setError('Logout all failed');
394
+ }
395
+ };
396
+
397
+ // Sign up method (placeholder - you can implement based on your needs)
398
+ const signUp = async (username: string, email: string, password: string): Promise<User> => {
399
+ // Implement sign up logic similar to secureLogin
400
+ throw new Error('Sign up not implemented yet');
401
+ };
402
+
403
+ // Switch session method
404
+ const switchSession = async (sessionId: string): Promise<void> => {
405
+ await switchToSession(sessionId);
406
+ };
407
+
408
+ // Remove session method
409
+ const removeSession = async (sessionId: string): Promise<void> => {
410
+ await logout(sessionId);
411
+ };
412
+
413
+ // Refresh sessions method
414
+ const refreshSessions = async (): Promise<void> => {
415
+ if (!activeSessionId) return;
416
+
417
+ try {
418
+ const serverSessions = await oxyServices.getSessionsBySessionId(activeSessionId);
419
+
420
+ // Update local sessions with server data
421
+ const updatedSessions: SecureClientSession[] = serverSessions.map(serverSession => ({
422
+ sessionId: serverSession.sessionId,
423
+ deviceId: serverSession.deviceId,
424
+ expiresAt: new Date().toISOString(), // You might want to get this from server
425
+ lastActive: new Date().toISOString()
426
+ }));
427
+
428
+ setSessions(updatedSessions);
429
+ await saveSessionsToStorage(updatedSessions);
430
+ } catch (error) {
431
+ console.error('Refresh sessions error:', error);
432
+ }
433
+ };
434
+
435
+ // Context value
436
+ const contextValue: OxyContextState = {
437
+ user,
438
+ minimalUser,
439
+ sessions,
440
+ activeSessionId,
441
+ isAuthenticated: !!user,
442
+ isLoading,
443
+ error,
444
+ login,
445
+ logout,
446
+ logoutAll,
447
+ signUp,
448
+ switchSession,
449
+ removeSession,
450
+ refreshSessions,
451
+ oxyServices,
452
+ bottomSheetRef,
453
+ showBottomSheet: undefined, // Implement as needed
454
+ hideBottomSheet: undefined, // Implement as needed
455
+ };
456
+
457
+ return (
458
+ <OxyContext.Provider value={contextValue}>
459
+ {children}
460
+ </OxyContext.Provider>
461
+ );
726
462
  };
727
463
 
728
464
  // Hook to use the context
729
- export const useOxy = () => {
730
- const context = useContext(OxyContext);
731
- if (!context) {
732
- throw new Error('useOxy must be used within an OxyContextProvider');
733
- }
734
- return context;
465
+ export const useOxy = (): OxyContextState => {
466
+ const context = useContext(OxyContext);
467
+ if (!context) {
468
+ throw new Error('useOxy must be used within an OxyContextProvider');
469
+ }
470
+ return context;
735
471
  };
472
+
473
+ export default OxyContext;