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