@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.
- package/lib/commonjs/core/index.js +117 -0
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/models/secureSession.js +2 -0
- package/lib/commonjs/models/secureSession.js.map +1 -0
- package/lib/commonjs/ui/context/LegacyOxyContext.js +643 -0
- package/lib/commonjs/ui/context/LegacyOxyContext.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +215 -450
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/SecureOxyContext.js +408 -0
- package/lib/commonjs/ui/context/SecureOxyContext.js.map +1 -0
- package/lib/commonjs/ui/screens/AccountCenterScreen.js +3 -3
- package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +31 -30
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AppInfoScreen.js +4 -4
- package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SessionManagementScreen.js +34 -34
- package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +2 -2
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/core/index.js +117 -0
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/models/secureSession.js +2 -0
- package/lib/module/models/secureSession.js.map +1 -0
- package/lib/module/ui/context/LegacyOxyContext.js +639 -0
- package/lib/module/ui/context/LegacyOxyContext.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +214 -450
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/SecureOxyContext.js +403 -0
- package/lib/module/ui/context/SecureOxyContext.js.map +1 -0
- package/lib/module/ui/screens/AccountCenterScreen.js +3 -3
- package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +31 -30
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/AppInfoScreen.js +4 -4
- package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/module/ui/screens/SessionManagementScreen.js +34 -34
- package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +2 -2
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/typescript/core/index.d.ts +51 -0
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/models/secureSession.d.ts +25 -0
- package/lib/typescript/models/secureSession.d.ts.map +1 -0
- package/lib/typescript/ui/context/LegacyOxyContext.d.ts +40 -0
- package/lib/typescript/ui/context/LegacyOxyContext.d.ts.map +1 -0
- package/lib/typescript/ui/context/OxyContext.d.ts +11 -12
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/context/SecureOxyContext.d.ts +39 -0
- package/lib/typescript/ui/context/SecureOxyContext.d.ts.map +1 -0
- package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/index.ts +117 -0
- package/src/index.ts +2 -2
- package/src/models/secureSession.ts +27 -0
- package/src/ui/context/LegacyOxyContext.tsx +735 -0
- package/src/ui/context/OxyContext.tsx +412 -674
- package/src/ui/context/SecureOxyContext.tsx +473 -0
- package/src/ui/screens/AccountCenterScreen.tsx +4 -4
- package/src/ui/screens/AccountSwitcherScreen.tsx +36 -34
- package/src/ui/screens/AppInfoScreen.tsx +3 -3
- package/src/ui/screens/SessionManagementScreen.tsx +31 -35
- 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
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
async getItem(key: string): Promise<string | null> {
|
|
60
|
+
return localStorage.getItem(key);
|
|
61
|
+
}
|
|
67
62
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
async setItem(key: string, value: string): Promise<void> {
|
|
64
|
+
localStorage.setItem(key, value);
|
|
65
|
+
}
|
|
71
66
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
async removeItem(key: string): Promise<void> {
|
|
68
|
+
localStorage.removeItem(key);
|
|
69
|
+
}
|
|
75
70
|
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
107
|
-
return new WebStorage();
|
|
99
|
+
return new WebStorage();
|
|
108
100
|
};
|
|
109
101
|
|
|
110
|
-
// Storage keys
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
109
|
+
children,
|
|
110
|
+
oxyServices,
|
|
111
|
+
storageKeyPrefix = 'oxy_secure',
|
|
112
|
+
onAuthStateChange,
|
|
113
|
+
bottomSheetRef,
|
|
126
114
|
}) => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
299
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
};
|
|
142
|
+
// Effect to initialize authentication state
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
const initAuth = async () => {
|
|
145
|
+
if (!storage) return;
|
|
323
146
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
if (!storage) throw new Error('Storage not initialized');
|
|
153
|
+
console.log('SecureAuth - sessionsData:', sessionsData);
|
|
154
|
+
console.log('SecureAuth - activeSessionId:', storedActiveSessionId);
|
|
342
155
|
|
|
343
|
-
|
|
344
|
-
|
|
156
|
+
if (sessionsData) {
|
|
157
|
+
const parsedSessions: SecureClientSession[] = JSON.parse(sessionsData);
|
|
158
|
+
setSessions(parsedSessions);
|
|
345
159
|
|
|
346
|
-
|
|
347
|
-
const
|
|
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 (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
543
|
-
|
|
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
|
-
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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;
|