@oxyhq/services 5.13.1 → 5.13.3
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/README.md +71 -0
- package/lib/commonjs/core/HttpClient.js +238 -0
- package/lib/commonjs/core/HttpClient.js.map +1 -0
- package/lib/commonjs/core/OxyServices.js +538 -332
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/core/RequestManager.js +199 -0
- package/lib/commonjs/core/RequestManager.js.map +1 -0
- package/lib/commonjs/core/index.js +38 -1
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/index.js +36 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/Avatar.js +94 -27
- package/lib/commonjs/ui/components/Avatar.js.map +1 -1
- package/lib/commonjs/ui/components/FollowButton.js +1 -0
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/internal/TextField.js +13 -8
- package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +183 -224
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionSocket.js +80 -22
- package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/commonjs/ui/index.js +4 -1
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +32 -2
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +101 -59
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +3 -2
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +75 -117
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +0 -11
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignUpScreen.js +14 -16
- package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +50 -18
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +10 -10
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js +16 -26
- package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js +104 -212
- package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js.map +1 -1
- package/lib/commonjs/ui/stores/accountStore.js +237 -0
- package/lib/commonjs/ui/stores/accountStore.js.map +1 -0
- package/lib/commonjs/ui/stores/authStore.js +2 -1
- package/lib/commonjs/ui/stores/authStore.js.map +1 -1
- package/lib/commonjs/ui/styles/authStyles.js +14 -7
- package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
- package/lib/commonjs/utils/asyncUtils.js +9 -22
- package/lib/commonjs/utils/asyncUtils.js.map +1 -1
- package/lib/commonjs/utils/cache.js +259 -0
- package/lib/commonjs/utils/cache.js.map +1 -0
- package/lib/commonjs/utils/index.js +99 -0
- package/lib/commonjs/utils/index.js.map +1 -1
- package/lib/commonjs/utils/languageUtils.js +159 -0
- package/lib/commonjs/utils/languageUtils.js.map +1 -0
- package/lib/commonjs/utils/requestUtils.js +217 -0
- package/lib/commonjs/utils/requestUtils.js.map +1 -0
- package/lib/commonjs/utils/sessionUtils.js +191 -0
- package/lib/commonjs/utils/sessionUtils.js.map +1 -0
- package/lib/module/core/HttpClient.js +232 -0
- package/lib/module/core/HttpClient.js.map +1 -0
- package/lib/module/core/OxyServices.js +536 -326
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/core/RequestManager.js +194 -0
- package/lib/module/core/RequestManager.js.map +1 -0
- package/lib/module/core/index.js +2 -0
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/Avatar.js +94 -27
- package/lib/module/ui/components/Avatar.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +1 -0
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/internal/TextField.js +13 -8
- package/lib/module/ui/components/internal/TextField.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +182 -223
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useSessionSocket.js +80 -22
- package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/module/ui/index.js +4 -2
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +33 -2
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +102 -60
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +3 -2
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/LanguageSelectorScreen.js +73 -117
- package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +0 -11
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/SignUpScreen.js +14 -16
- package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +50 -18
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/module/ui/screens/internal/SignInPasswordStep.js +10 -10
- package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInPasswordStep.js +16 -26
- package/lib/module/ui/screens/steps/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInUsernameStep.js +105 -214
- package/lib/module/ui/screens/steps/SignInUsernameStep.js.map +1 -1
- package/lib/module/ui/stores/accountStore.js +229 -0
- package/lib/module/ui/stores/accountStore.js.map +1 -0
- package/lib/module/ui/stores/authStore.js +2 -1
- package/lib/module/ui/stores/authStore.js.map +1 -1
- package/lib/module/ui/styles/authStyles.js +14 -7
- package/lib/module/ui/styles/authStyles.js.map +1 -1
- package/lib/module/utils/asyncUtils.js +10 -22
- package/lib/module/utils/asyncUtils.js.map +1 -1
- package/lib/module/utils/cache.js +250 -0
- package/lib/module/utils/cache.js.map +1 -0
- package/lib/module/utils/index.js +7 -0
- package/lib/module/utils/index.js.map +1 -1
- package/lib/module/utils/languageUtils.js +151 -0
- package/lib/module/utils/languageUtils.js.map +1 -0
- package/lib/module/utils/requestUtils.js +210 -0
- package/lib/module/utils/requestUtils.js.map +1 -0
- package/lib/module/utils/sessionUtils.js +180 -0
- package/lib/module/utils/sessionUtils.js.map +1 -0
- package/lib/typescript/core/HttpClient.d.ts +64 -0
- package/lib/typescript/core/HttpClient.d.ts.map +1 -0
- package/lib/typescript/core/OxyServices.d.ts +88 -71
- package/lib/typescript/core/OxyServices.d.ts.map +1 -1
- package/lib/typescript/core/RequestManager.d.ts +67 -0
- package/lib/typescript/core/RequestManager.d.ts.map +1 -0
- package/lib/typescript/core/index.d.ts +2 -0
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +15 -0
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/models/session.d.ts +1 -0
- package/lib/typescript/models/session.d.ts.map +1 -1
- package/lib/typescript/ui/components/Avatar.d.ts +6 -7
- package/lib/typescript/ui/components/Avatar.d.ts.map +1 -1
- package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +4 -0
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
- package/lib/typescript/ui/index.d.ts +2 -2
- package/lib/typescript/ui/index.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +3 -3
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts.map +1 -1
- package/lib/typescript/ui/stores/accountStore.d.ts +34 -0
- package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -0
- package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
- package/lib/typescript/ui/styles/authStyles.d.ts +18 -2
- package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -1
- package/lib/typescript/utils/asyncUtils.d.ts +2 -0
- package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
- package/lib/typescript/utils/cache.d.ts +128 -0
- package/lib/typescript/utils/cache.d.ts.map +1 -0
- package/lib/typescript/utils/index.d.ts +4 -0
- package/lib/typescript/utils/index.d.ts.map +1 -1
- package/lib/typescript/utils/languageUtils.d.ts +38 -0
- package/lib/typescript/utils/languageUtils.d.ts.map +1 -0
- package/lib/typescript/utils/requestUtils.d.ts +122 -0
- package/lib/typescript/utils/requestUtils.d.ts.map +1 -0
- package/lib/typescript/utils/sessionUtils.d.ts +55 -0
- package/lib/typescript/utils/sessionUtils.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/core/HttpClient.ts +277 -0
- package/src/core/OxyServices.ts +466 -351
- package/src/core/RequestManager.ts +240 -0
- package/src/core/index.ts +10 -0
- package/src/index.ts +10 -0
- package/src/models/interfaces.ts +19 -0
- package/src/models/session.ts +1 -1
- package/src/ui/components/Avatar.tsx +151 -35
- package/src/ui/components/FollowButton.tsx +1 -0
- package/src/ui/components/internal/TextField.tsx +7 -6
- package/src/ui/context/OxyContext.tsx +213 -217
- package/src/ui/hooks/useSessionSocket.ts +72 -18
- package/src/ui/index.ts +4 -1
- package/src/ui/screens/AccountSettingsScreen.tsx +34 -2
- package/src/ui/screens/AccountSwitcherScreen.tsx +102 -68
- package/src/ui/screens/FileManagementScreen.tsx +1 -1
- package/src/ui/screens/LanguageSelectorScreen.tsx +86 -143
- package/src/ui/screens/SignInScreen.tsx +0 -7
- package/src/ui/screens/SignUpScreen.tsx +14 -15
- package/src/ui/screens/WelcomeNewUserScreen.tsx +52 -15
- package/src/ui/screens/internal/SignInPasswordStep.tsx +4 -6
- package/src/ui/screens/steps/SignInPasswordStep.tsx +4 -8
- package/src/ui/screens/steps/SignInUsernameStep.tsx +110 -256
- package/src/ui/stores/accountStore.ts +285 -0
- package/src/ui/stores/authStore.ts +2 -1
- package/src/ui/styles/authStyles.ts +14 -7
- package/src/utils/asyncUtils.ts +10 -24
- package/src/utils/cache.ts +264 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/languageUtils.ts +174 -0
- package/src/utils/requestUtils.ts +234 -0
- package/src/utils/sessionUtils.ts +206 -0
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { createContext, useContext, useEffect, useCallback, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { OxyServices } from '../../core';
|
|
5
|
+
import { normalizeAndSortSessions, mergeSessions, sessionsArraysEqual } from '../../utils/sessionUtils';
|
|
5
6
|
import { DeviceManager } from '../../utils/deviceManager';
|
|
6
7
|
import { useSessionSocket } from '../hooks/useSessionSocket';
|
|
7
8
|
import { toast } from '../../lib/sonner';
|
|
8
9
|
import { useAuthStore } from '../stores/authStore';
|
|
10
|
+
import { getLanguageMetadata, getLanguageName, getNativeLanguageName, normalizeLanguageCode } from '../../utils/languageUtils';
|
|
9
11
|
|
|
10
12
|
// Define the context shape
|
|
11
13
|
// NOTE: We intentionally avoid importing useFollow here to prevent a require cycle.
|
|
@@ -83,6 +85,8 @@ const getStorage = async () => {
|
|
|
83
85
|
const getStorageKeys = (prefix = 'oxy_session') => ({
|
|
84
86
|
activeSessionId: `${prefix}_active_session_id`,
|
|
85
87
|
// Only store the active session ID
|
|
88
|
+
sessionIds: `${prefix}_session_ids`,
|
|
89
|
+
// Store all session IDs for quick account loading
|
|
86
90
|
language: `${prefix}_language` // Store the selected language
|
|
87
91
|
});
|
|
88
92
|
export const OxyProvider = ({
|
|
@@ -122,38 +126,21 @@ export const OxyProvider = ({
|
|
|
122
126
|
const [minimalUser, setMinimalUser] = useState(null);
|
|
123
127
|
const [sessions, setSessions] = useState([]);
|
|
124
128
|
const [activeSessionId, setActiveSessionId] = useState(null);
|
|
129
|
+
|
|
130
|
+
// Track in-flight refresh to prevent duplicate calls
|
|
131
|
+
const refreshInFlightRef = useRef(null);
|
|
125
132
|
const [storage, setStorage] = useState(null);
|
|
126
133
|
const [currentLanguage, setCurrentLanguage] = useState('en-US');
|
|
127
134
|
|
|
128
135
|
// Storage keys (memoized to prevent infinite loops) - declared early for use in helpers
|
|
129
136
|
const keys = useMemo(() => getStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
|
|
130
137
|
|
|
131
|
-
// Normalize language codes to BCP-47 (e.g., en-US)
|
|
132
|
-
const normalizeLanguageCode = useCallback(lang => {
|
|
133
|
-
if (!lang) return null;
|
|
134
|
-
if (lang.includes('-')) return lang;
|
|
135
|
-
const map = {
|
|
136
|
-
en: 'en-US',
|
|
137
|
-
es: 'es-ES',
|
|
138
|
-
ca: 'ca-ES',
|
|
139
|
-
fr: 'fr-FR',
|
|
140
|
-
de: 'de-DE',
|
|
141
|
-
it: 'it-IT',
|
|
142
|
-
pt: 'pt-PT',
|
|
143
|
-
ja: 'ja-JP',
|
|
144
|
-
ko: 'ko-KR',
|
|
145
|
-
zh: 'zh-CN',
|
|
146
|
-
ar: 'ar-SA'
|
|
147
|
-
};
|
|
148
|
-
return map[lang] || lang;
|
|
149
|
-
}, []);
|
|
150
|
-
|
|
151
138
|
// Helper to apply language preference from user/server
|
|
152
139
|
const applyLanguagePreference = useCallback(async user => {
|
|
153
140
|
const userLanguage = user?.language;
|
|
154
141
|
if (!userLanguage || !storage) return;
|
|
155
142
|
try {
|
|
156
|
-
const serverLang = normalizeLanguageCode(userLanguage)
|
|
143
|
+
const serverLang = normalizeLanguageCode(userLanguage);
|
|
157
144
|
await storage.setItem(keys.language, serverLang);
|
|
158
145
|
setCurrentLanguage(serverLang);
|
|
159
146
|
} catch (e) {
|
|
@@ -161,19 +148,45 @@ export const OxyProvider = ({
|
|
|
161
148
|
console.warn('Failed to apply server language preference', e);
|
|
162
149
|
}
|
|
163
150
|
}
|
|
164
|
-
}, [storage, keys.language
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const mapServerSessionsToClient = useCallback((serverSessions, fallbackUserId) => {
|
|
168
|
-
return serverSessions.map(s => ({
|
|
151
|
+
}, [storage, keys.language]);
|
|
152
|
+
const mapSessionsToClient = useCallback((sessions, fallbackDeviceId, fallbackUserId) => {
|
|
153
|
+
return sessions.map(s => ({
|
|
169
154
|
sessionId: s.sessionId,
|
|
170
|
-
deviceId: s.deviceId,
|
|
171
|
-
expiresAt: s.expiresAt || new Date().toISOString(),
|
|
155
|
+
deviceId: s.deviceId || fallbackDeviceId || '',
|
|
156
|
+
expiresAt: s.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
172
157
|
lastActive: s.lastActive || new Date().toISOString(),
|
|
173
|
-
userId: s.userId || fallbackUserId
|
|
158
|
+
userId: s.user?.id || s.userId || s.user?._id?.toString() || fallbackUserId || '',
|
|
159
|
+
isCurrent: Boolean(s.isCurrent)
|
|
174
160
|
}));
|
|
175
161
|
}, []);
|
|
176
162
|
|
|
163
|
+
// Save all session IDs to storage for quick loading on initialization
|
|
164
|
+
const saveSessionIds = useCallback(async sessionIds => {
|
|
165
|
+
if (!storage) return;
|
|
166
|
+
try {
|
|
167
|
+
const uniqueIds = Array.from(new Set(sessionIds));
|
|
168
|
+
await storage.setItem(keys.sessionIds, JSON.stringify(uniqueIds));
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (__DEV__) {
|
|
171
|
+
console.warn('Failed to save session IDs:', err);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}, [storage, keys.sessionIds]);
|
|
175
|
+
const updateSessions = useCallback((newSessions, mergeWithExisting = false) => {
|
|
176
|
+
setSessions(prevSessions => {
|
|
177
|
+
const sessionsToProcess = mergeWithExisting ? mergeSessions(prevSessions, newSessions, activeSessionId, false) : normalizeAndSortSessions(newSessions, activeSessionId, false);
|
|
178
|
+
|
|
179
|
+
// Save all session IDs to storage
|
|
180
|
+
if (storage) {
|
|
181
|
+
const allSessionIds = sessionsToProcess.map(s => s.sessionId);
|
|
182
|
+
saveSessionIds(allSessionIds).catch(() => {
|
|
183
|
+
// Ignore errors - non-critical
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return sessionsArraysEqual(prevSessions, sessionsToProcess) ? prevSessions : sessionsToProcess;
|
|
187
|
+
});
|
|
188
|
+
}, [activeSessionId, storage, saveSessionIds]);
|
|
189
|
+
|
|
177
190
|
// Token ready state - start optimistically so children render immediately
|
|
178
191
|
const [tokenReady, setTokenReady] = useState(true);
|
|
179
192
|
|
|
@@ -182,6 +195,7 @@ export const OxyProvider = ({
|
|
|
182
195
|
if (!storage) return;
|
|
183
196
|
try {
|
|
184
197
|
await storage.removeItem(keys.activeSessionId);
|
|
198
|
+
await storage.removeItem(keys.sessionIds);
|
|
185
199
|
} catch (err) {
|
|
186
200
|
if (__DEV__) {
|
|
187
201
|
console.error('Clear storage error:', err);
|
|
@@ -230,8 +244,51 @@ export const OxyProvider = ({
|
|
|
230
244
|
setCurrentLanguage(savedLanguage);
|
|
231
245
|
}
|
|
232
246
|
|
|
247
|
+
// Load all stored session IDs and validate them
|
|
248
|
+
const storedSessionIdsJson = await storage.getItem(keys.sessionIds);
|
|
249
|
+
const storedSessionIds = storedSessionIdsJson ? JSON.parse(storedSessionIdsJson) : [];
|
|
250
|
+
|
|
233
251
|
// Try to restore active session from storage
|
|
234
252
|
const storedActiveSessionId = await storage.getItem(keys.activeSessionId);
|
|
253
|
+
const validSessions = [];
|
|
254
|
+
|
|
255
|
+
// If we have stored session IDs, validate them (even without active session)
|
|
256
|
+
if (storedSessionIds.length > 0) {
|
|
257
|
+
if (__DEV__) {
|
|
258
|
+
console.log('Loading stored sessions on init:', storedSessionIds.length);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Validate each stored session ID and build session list
|
|
262
|
+
for (const sessionId of storedSessionIds) {
|
|
263
|
+
try {
|
|
264
|
+
const validation = await oxyServices.validateSession(sessionId, {
|
|
265
|
+
useHeaderValidation: true
|
|
266
|
+
});
|
|
267
|
+
if (validation.valid && validation.user) {
|
|
268
|
+
validSessions.push({
|
|
269
|
+
sessionId,
|
|
270
|
+
userId: validation.user.id?.toString() || '',
|
|
271
|
+
deviceId: '',
|
|
272
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
273
|
+
lastActive: new Date().toISOString(),
|
|
274
|
+
isCurrent: sessionId === storedActiveSessionId
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
} catch (e) {
|
|
278
|
+
// Session invalid, skip it
|
|
279
|
+
if (__DEV__) {
|
|
280
|
+
console.warn('Session validation failed for:', sessionId, e);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Update sessions list with validated sessions (even if no active session)
|
|
286
|
+
if (validSessions.length > 0) {
|
|
287
|
+
updateSessions(validSessions, false);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// If we have an active session, authenticate with it
|
|
235
292
|
if (storedActiveSessionId) {
|
|
236
293
|
try {
|
|
237
294
|
const validation = await oxyServices.validateSession(storedActiveSessionId, {
|
|
@@ -248,35 +305,31 @@ export const OxyProvider = ({
|
|
|
248
305
|
avatar: fullUser.avatar
|
|
249
306
|
});
|
|
250
307
|
await applyLanguagePreference(fullUser);
|
|
251
|
-
|
|
252
|
-
// Get all device sessions to support multiple accounts
|
|
253
308
|
try {
|
|
254
309
|
const deviceSessions = await oxyServices.getDeviceSessions(storedActiveSessionId);
|
|
255
|
-
const allDeviceSessions = deviceSessions
|
|
256
|
-
|
|
257
|
-
deviceId: ds.deviceId,
|
|
258
|
-
expiresAt: ds.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
259
|
-
lastActive: ds.lastActive || new Date().toISOString(),
|
|
260
|
-
userId: ds.user?.id || ds.userId || fullUser.id
|
|
261
|
-
}));
|
|
262
|
-
setSessions(allDeviceSessions);
|
|
310
|
+
const allDeviceSessions = mapSessionsToClient(deviceSessions, undefined, fullUser.id);
|
|
311
|
+
updateSessions(allDeviceSessions, true);
|
|
263
312
|
} catch (e) {
|
|
264
|
-
// Fallback to user sessions
|
|
265
313
|
if (__DEV__) {
|
|
266
314
|
console.warn('Failed to get device sessions on init, falling back to user sessions:', e);
|
|
267
315
|
}
|
|
268
316
|
const serverSessions = await oxyServices.getSessionsBySessionId(storedActiveSessionId);
|
|
269
|
-
|
|
317
|
+
updateSessions(mapSessionsToClient(serverSessions, undefined, fullUser.id), false);
|
|
270
318
|
}
|
|
271
319
|
onAuthStateChange?.(fullUser);
|
|
272
320
|
} else {
|
|
273
|
-
|
|
321
|
+
// Active session invalid, remove it but keep other sessions
|
|
322
|
+
await storage.removeItem(keys.activeSessionId);
|
|
323
|
+
// Update session list to remove invalid active session
|
|
324
|
+
updateSessions(validSessions.filter(s => s.sessionId !== storedActiveSessionId), false);
|
|
274
325
|
}
|
|
275
326
|
} catch (e) {
|
|
276
327
|
if (__DEV__) {
|
|
277
|
-
console.error('
|
|
328
|
+
console.error('Active session validation error', e);
|
|
278
329
|
}
|
|
279
|
-
|
|
330
|
+
// Remove invalid active session but keep other sessions
|
|
331
|
+
await storage.removeItem(keys.activeSessionId);
|
|
332
|
+
updateSessions(validSessions.filter(s => s.sessionId !== storedActiveSessionId), false);
|
|
280
333
|
}
|
|
281
334
|
}
|
|
282
335
|
setTokenReady(true);
|
|
@@ -289,34 +342,28 @@ export const OxyProvider = ({
|
|
|
289
342
|
}
|
|
290
343
|
};
|
|
291
344
|
initAuth();
|
|
292
|
-
}, [storage, oxyServices, keys, onAuthStateChange, loginSuccess, clearAllStorage, applyLanguagePreference,
|
|
345
|
+
}, [storage, oxyServices, keys, onAuthStateChange, loginSuccess, clearAllStorage, applyLanguagePreference, mapSessionsToClient, updateSessions]);
|
|
293
346
|
|
|
294
347
|
// Save active session ID to storage (only session ID, no user data)
|
|
295
348
|
const saveActiveSessionId = useCallback(async sessionId => {
|
|
296
349
|
if (!storage) return;
|
|
297
350
|
await storage.setItem(keys.activeSessionId, sessionId);
|
|
298
351
|
}, [storage, keys.activeSessionId]);
|
|
299
|
-
|
|
300
|
-
// Switch to a different session
|
|
301
352
|
const switchToSession = useCallback(async sessionId => {
|
|
302
353
|
try {
|
|
303
|
-
// Don't set isLoading - session switches should happen silently in background
|
|
304
|
-
// Validate session first before attempting to switch
|
|
305
354
|
const validation = await oxyServices.validateSession(sessionId, {
|
|
306
355
|
useHeaderValidation: true
|
|
307
356
|
});
|
|
308
357
|
if (!validation.valid) {
|
|
309
|
-
|
|
310
|
-
setSessions(prevSessions => prevSessions.filter(s => s.sessionId !== sessionId));
|
|
358
|
+
updateSessions(sessions.filter(s => s.sessionId !== sessionId), false);
|
|
311
359
|
throw new Error('Session is invalid or expired');
|
|
312
360
|
}
|
|
313
|
-
|
|
314
|
-
|
|
361
|
+
if (!validation.user) {
|
|
362
|
+
throw new Error('User data not available from session validation');
|
|
363
|
+
}
|
|
364
|
+
const fullUser = validation.user;
|
|
315
365
|
await oxyServices.getTokenBySession(sessionId);
|
|
316
366
|
setTokenReady(true);
|
|
317
|
-
|
|
318
|
-
// Load full user data - use user from validation if available, otherwise fetch
|
|
319
|
-
const fullUser = validation.user || (await oxyServices.getUserBySession(sessionId));
|
|
320
367
|
setActiveSessionId(sessionId);
|
|
321
368
|
loginSuccess(fullUser);
|
|
322
369
|
setMinimalUser({
|
|
@@ -326,53 +373,17 @@ export const OxyProvider = ({
|
|
|
326
373
|
});
|
|
327
374
|
await saveActiveSessionId(sessionId);
|
|
328
375
|
await applyLanguagePreference(fullUser);
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
sessionId: ds.sessionId,
|
|
336
|
-
deviceId: ds.deviceId,
|
|
337
|
-
expiresAt: ds.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
338
|
-
lastActive: ds.lastActive || new Date().toISOString(),
|
|
339
|
-
userId: ds.user?.id || ds.userId || fullUser.id
|
|
340
|
-
}));
|
|
341
|
-
// Merge with existing sessions to preserve other accounts
|
|
342
|
-
setSessions(prevSessions => {
|
|
343
|
-
const existingSessionIds = new Set(prevSessions.map(s => s.sessionId));
|
|
344
|
-
const newSessions = allDeviceSessions.filter(s => !existingSessionIds.has(s.sessionId));
|
|
345
|
-
// Combine existing sessions with new ones, prioritizing new data for existing sessions
|
|
346
|
-
const sessionMap = new Map(prevSessions.map(s => [s.sessionId, s]));
|
|
347
|
-
allDeviceSessions.forEach(s => sessionMap.set(s.sessionId, s));
|
|
348
|
-
return Array.from(sessionMap.values());
|
|
349
|
-
});
|
|
350
|
-
} catch (error) {
|
|
351
|
-
// Fallback to user sessions - merge with existing to preserve other accounts
|
|
352
|
-
if (__DEV__) {
|
|
353
|
-
console.warn('Failed to get device sessions after switch, falling back to user sessions:', error);
|
|
354
|
-
}
|
|
355
|
-
const serverSessions = await oxyServices.getSessionsBySessionId(sessionId);
|
|
356
|
-
const userSessions = mapServerSessionsToClient(serverSessions, fullUser.id);
|
|
357
|
-
// Merge with existing sessions to preserve other accounts
|
|
358
|
-
setSessions(prevSessions => {
|
|
359
|
-
const existingSessionIds = new Set(prevSessions.map(s => s.sessionId));
|
|
360
|
-
const newSessions = userSessions.filter(s => !existingSessionIds.has(s.sessionId));
|
|
361
|
-
// Combine existing sessions with new ones, prioritizing new data for existing sessions
|
|
362
|
-
const sessionMap = new Map(prevSessions.map(s => [s.sessionId, s]));
|
|
363
|
-
userSessions.forEach(s => sessionMap.set(s.sessionId, s));
|
|
364
|
-
return Array.from(sessionMap.values());
|
|
365
|
-
});
|
|
366
|
-
}
|
|
376
|
+
oxyServices.getDeviceSessions(sessionId).then(deviceSessions => {
|
|
377
|
+
const allDeviceSessions = mapSessionsToClient(deviceSessions, undefined, fullUser.id);
|
|
378
|
+
updateSessions(allDeviceSessions, true);
|
|
379
|
+
}).catch(error => {
|
|
380
|
+
if (__DEV__) console.warn('Failed to get device sessions after switch:', error);
|
|
381
|
+
});
|
|
367
382
|
onAuthStateChange?.(fullUser);
|
|
368
383
|
} catch (error) {
|
|
369
|
-
// Check if the error is due to invalid/expired session
|
|
370
384
|
const isInvalidSession = error?.response?.status === 401 || error?.message?.includes('Invalid or expired session') || error?.message?.includes('Session is invalid');
|
|
371
385
|
if (isInvalidSession) {
|
|
372
|
-
|
|
373
|
-
setSessions(prevSessions => prevSessions.filter(s => s.sessionId !== sessionId));
|
|
374
|
-
|
|
375
|
-
// If this was the active session, try to switch to another valid session
|
|
386
|
+
updateSessions(sessions.filter(s => s.sessionId !== sessionId), false);
|
|
376
387
|
if (sessionId === activeSessionId && sessions.length > 1) {
|
|
377
388
|
const otherSessions = sessions.filter(s => s.sessionId !== sessionId);
|
|
378
389
|
for (const otherSession of otherSessions) {
|
|
@@ -406,9 +417,7 @@ export const OxyProvider = ({
|
|
|
406
417
|
setTokenReady(false);
|
|
407
418
|
throw error; // Re-throw so calling code can handle it
|
|
408
419
|
}
|
|
409
|
-
}, [oxyServices, onAuthStateChange, loginSuccess, saveActiveSessionId, applyLanguagePreference,
|
|
410
|
-
|
|
411
|
-
// Login method - only store session ID, retrieve data from backend
|
|
420
|
+
}, [oxyServices, onAuthStateChange, loginSuccess, saveActiveSessionId, applyLanguagePreference, mapSessionsToClient, onError, activeSessionId, sessions]);
|
|
412
421
|
const login = useCallback(async (username, password, deviceName) => {
|
|
413
422
|
if (!storage) throw new Error('Storage not initialized');
|
|
414
423
|
useAuthStore.setState({
|
|
@@ -431,43 +440,20 @@ export const OxyProvider = ({
|
|
|
431
440
|
const sessionResponse = response;
|
|
432
441
|
await oxyServices.getTokenBySession(sessionResponse.sessionId);
|
|
433
442
|
const fullUser = await oxyServices.getUserBySession(sessionResponse.sessionId);
|
|
434
|
-
|
|
435
|
-
// Get all device sessions to check for duplicates BEFORE setting the new session as active
|
|
436
|
-
// This returns all sessions on the device, not just for the current user
|
|
437
443
|
let allDeviceSessions = [];
|
|
438
444
|
try {
|
|
439
445
|
const deviceSessions = await oxyServices.getDeviceSessions(sessionResponse.sessionId);
|
|
440
|
-
|
|
441
|
-
// Map device sessions to client format
|
|
442
|
-
// Device sessions include user info, so we can map them directly
|
|
443
|
-
allDeviceSessions = deviceSessions.map(ds => ({
|
|
444
|
-
sessionId: ds.sessionId,
|
|
445
|
-
deviceId: ds.deviceId || sessionResponse.deviceId,
|
|
446
|
-
expiresAt: ds.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
447
|
-
lastActive: ds.lastActive || new Date().toISOString(),
|
|
448
|
-
userId: ds.user?.id || ds.userId || ds.user?._id?.toString() || fullUser.id
|
|
449
|
-
}));
|
|
446
|
+
allDeviceSessions = mapSessionsToClient(deviceSessions, sessionResponse.deviceId, fullUser.id);
|
|
450
447
|
} catch (error) {
|
|
451
|
-
// Fallback to user sessions if device sessions fail
|
|
452
448
|
if (__DEV__) {
|
|
453
449
|
console.warn('Failed to get device sessions, falling back to user sessions:', error);
|
|
454
450
|
}
|
|
455
451
|
const serverSessions = await oxyServices.getSessionsBySessionId(sessionResponse.sessionId);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
// Merge with existing sessions to preserve other accounts
|
|
459
|
-
const existingSessionIds = new Set((sessions || []).map(s => s.sessionId));
|
|
460
|
-
const newSessions = userSessions.filter(s => !existingSessionIds.has(s.sessionId));
|
|
461
|
-
allDeviceSessions = [...(sessions || []), ...newSessions];
|
|
452
|
+
allDeviceSessions = mapSessionsToClient(serverSessions, undefined, fullUser.id);
|
|
462
453
|
}
|
|
463
|
-
|
|
464
|
-
// Check if this user is already signed in with another session on this device
|
|
465
|
-
// Compare userId as string to handle both string and ObjectId formats
|
|
466
454
|
const userUserId = fullUser.id?.toString();
|
|
467
455
|
const existingSession = allDeviceSessions.find(s => s.userId?.toString() === userUserId && s.sessionId !== sessionResponse.sessionId);
|
|
468
456
|
if (existingSession) {
|
|
469
|
-
// User is already signed in on this device, switch to existing session instead
|
|
470
|
-
// Logout the newly created session to clean it up
|
|
471
457
|
try {
|
|
472
458
|
await oxyServices.logoutSession(sessionResponse.sessionId, sessionResponse.sessionId);
|
|
473
459
|
} catch (logoutError) {
|
|
@@ -475,24 +461,18 @@ export const OxyProvider = ({
|
|
|
475
461
|
console.warn('Failed to logout duplicate session:', logoutError);
|
|
476
462
|
}
|
|
477
463
|
}
|
|
478
|
-
|
|
479
|
-
// Switch to the existing session
|
|
480
464
|
await switchToSession(existingSession.sessionId);
|
|
481
465
|
loginSuccess(fullUser);
|
|
482
466
|
setMinimalUser(sessionResponse.user);
|
|
483
|
-
|
|
484
|
-
// Update sessions list (excluding the duplicate we just created)
|
|
485
|
-
setSessions(allDeviceSessions.filter(s => s.sessionId !== sessionResponse.sessionId));
|
|
467
|
+
updateSessions(allDeviceSessions.filter(s => s.sessionId !== sessionResponse.sessionId), false);
|
|
486
468
|
onAuthStateChange?.(fullUser);
|
|
487
469
|
return fullUser;
|
|
488
470
|
}
|
|
489
|
-
|
|
490
|
-
// No duplicate found, proceed with the new session
|
|
491
471
|
setActiveSessionId(sessionResponse.sessionId);
|
|
492
472
|
await saveActiveSessionId(sessionResponse.sessionId);
|
|
493
473
|
loginSuccess(fullUser);
|
|
494
474
|
setMinimalUser(sessionResponse.user);
|
|
495
|
-
|
|
475
|
+
updateSessions(allDeviceSessions, true);
|
|
496
476
|
onAuthStateChange?.(fullUser);
|
|
497
477
|
return fullUser;
|
|
498
478
|
} catch (error) {
|
|
@@ -509,7 +489,7 @@ export const OxyProvider = ({
|
|
|
509
489
|
isLoading: false
|
|
510
490
|
});
|
|
511
491
|
}
|
|
512
|
-
}, [storage, oxyServices, saveActiveSessionId, loginSuccess, onAuthStateChange, loginFailure,
|
|
492
|
+
}, [storage, oxyServices, saveActiveSessionId, loginSuccess, onAuthStateChange, loginFailure, mapSessionsToClient, onError, sessions, switchToSession]);
|
|
513
493
|
|
|
514
494
|
// Logout method
|
|
515
495
|
const logout = useCallback(async targetSessionId => {
|
|
@@ -517,18 +497,12 @@ export const OxyProvider = ({
|
|
|
517
497
|
try {
|
|
518
498
|
const sessionToLogout = targetSessionId || activeSessionId;
|
|
519
499
|
await oxyServices.logoutSession(activeSessionId, sessionToLogout);
|
|
520
|
-
|
|
521
|
-
// Remove session from local state
|
|
522
500
|
const filteredSessions = sessions.filter(s => s.sessionId !== sessionToLogout);
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
// If logging out active session
|
|
501
|
+
updateSessions(filteredSessions, false);
|
|
526
502
|
if (sessionToLogout === activeSessionId) {
|
|
527
503
|
if (filteredSessions.length > 0) {
|
|
528
|
-
// Switch to another session
|
|
529
504
|
await switchToSession(filteredSessions[0].sessionId);
|
|
530
505
|
} else {
|
|
531
|
-
// No sessions left
|
|
532
506
|
setActiveSessionId(null);
|
|
533
507
|
logoutStore();
|
|
534
508
|
setMinimalUser(null);
|
|
@@ -553,8 +527,6 @@ export const OxyProvider = ({
|
|
|
553
527
|
});
|
|
554
528
|
}
|
|
555
529
|
}, [activeSessionId, oxyServices, sessions, switchToSession, logoutStore, storage, keys.activeSessionId, onAuthStateChange, onError]);
|
|
556
|
-
|
|
557
|
-
// Logout all sessions
|
|
558
530
|
const logoutAll = useCallback(async () => {
|
|
559
531
|
if (!activeSessionId) {
|
|
560
532
|
const error = new Error('No active session found');
|
|
@@ -570,7 +542,7 @@ export const OxyProvider = ({
|
|
|
570
542
|
}
|
|
571
543
|
try {
|
|
572
544
|
await oxyServices.logoutAllSessions(activeSessionId);
|
|
573
|
-
|
|
545
|
+
updateSessions([], false);
|
|
574
546
|
setActiveSessionId(null);
|
|
575
547
|
logoutStore();
|
|
576
548
|
setMinimalUser(null);
|
|
@@ -648,28 +620,16 @@ export const OxyProvider = ({
|
|
|
648
620
|
// Get all device sessions to support multiple accounts
|
|
649
621
|
try {
|
|
650
622
|
const deviceSessions = await oxyServices.getDeviceSessions(response.sessionId);
|
|
651
|
-
const allDeviceSessions = deviceSessions
|
|
652
|
-
|
|
653
|
-
deviceId: ds.deviceId,
|
|
654
|
-
expiresAt: ds.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
655
|
-
lastActive: ds.lastActive || new Date().toISOString(),
|
|
656
|
-
userId: ds.user?.id || ds.userId || fullUser.id
|
|
657
|
-
}));
|
|
658
|
-
setSessions(allDeviceSessions);
|
|
623
|
+
const allDeviceSessions = mapSessionsToClient(deviceSessions, undefined, fullUser.id);
|
|
624
|
+
updateSessions(allDeviceSessions, true);
|
|
659
625
|
} catch (error) {
|
|
660
626
|
// Fallback to user sessions if device sessions fail
|
|
661
627
|
if (__DEV__) {
|
|
662
628
|
console.warn('Failed to get device sessions for MFA, falling back to user sessions:', error);
|
|
663
629
|
}
|
|
664
630
|
const serverSessions = await oxyServices.getSessionsBySessionId(response.sessionId);
|
|
665
|
-
const userSessions =
|
|
666
|
-
|
|
667
|
-
// Merge with existing sessions to preserve other accounts
|
|
668
|
-
setSessions(prevSessions => {
|
|
669
|
-
const existingSessionIds = new Set(prevSessions.map(s => s.sessionId));
|
|
670
|
-
const newSessions = userSessions.filter(s => !existingSessionIds.has(s.sessionId));
|
|
671
|
-
return [...prevSessions, ...newSessions];
|
|
672
|
-
});
|
|
631
|
+
const userSessions = mapSessionsToClient(serverSessions, undefined, fullUser.id);
|
|
632
|
+
updateSessions(userSessions, true);
|
|
673
633
|
}
|
|
674
634
|
onAuthStateChange?.(fullUser);
|
|
675
635
|
return fullUser;
|
|
@@ -688,82 +648,73 @@ export const OxyProvider = ({
|
|
|
688
648
|
});
|
|
689
649
|
}
|
|
690
650
|
}, [storage, oxyServices, loginSuccess, loginFailure, saveActiveSessionId, onAuthStateChange, applyLanguagePreference, onError]);
|
|
691
|
-
|
|
692
|
-
// Switch session method (wrapper for consistency)
|
|
693
651
|
const switchSession = useCallback(async sessionId => {
|
|
694
652
|
await switchToSession(sessionId);
|
|
695
653
|
}, [switchToSession]);
|
|
696
|
-
|
|
697
|
-
// Remove session method (wrapper for consistency)
|
|
698
654
|
const removeSession = useCallback(async sessionId => {
|
|
699
655
|
await logout(sessionId);
|
|
700
656
|
}, [logout]);
|
|
701
|
-
|
|
702
|
-
// Refresh sessions method
|
|
703
657
|
const refreshSessions = useCallback(async () => {
|
|
704
658
|
if (!activeSessionId) return;
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
userId: ds.user?.id || ds.userId || user?.id
|
|
714
|
-
}));
|
|
715
|
-
setSessions(allDeviceSessions);
|
|
716
|
-
} catch (error) {
|
|
717
|
-
// Fallback to user sessions if device sessions fail
|
|
718
|
-
// Merge with existing sessions to preserve other accounts
|
|
719
|
-
if (__DEV__) {
|
|
720
|
-
console.warn('Failed to refresh device sessions, falling back to user sessions:', error);
|
|
721
|
-
}
|
|
659
|
+
|
|
660
|
+
// If a refresh is already in progress, return the existing promise
|
|
661
|
+
if (refreshInFlightRef.current) {
|
|
662
|
+
return refreshInFlightRef.current;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Create the refresh promise
|
|
666
|
+
const refreshPromise = (async () => {
|
|
722
667
|
try {
|
|
723
|
-
const
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
const existingSessionIds = new Set(prevSessions.map(s => s.sessionId));
|
|
728
|
-
const newSessions = userSessions.filter(s => !existingSessionIds.has(s.sessionId));
|
|
729
|
-
// Combine existing sessions with new ones, prioritizing new data for existing sessions
|
|
730
|
-
const sessionMap = new Map(prevSessions.map(s => [s.sessionId, s]));
|
|
731
|
-
userSessions.forEach(s => sessionMap.set(s.sessionId, s));
|
|
732
|
-
return Array.from(sessionMap.values());
|
|
733
|
-
});
|
|
734
|
-
} catch (fallbackError) {
|
|
668
|
+
const deviceSessions = await oxyServices.getDeviceSessions(activeSessionId);
|
|
669
|
+
const allDeviceSessions = mapSessionsToClient(deviceSessions, undefined, user?.id);
|
|
670
|
+
updateSessions(allDeviceSessions, true);
|
|
671
|
+
} catch (error) {
|
|
735
672
|
if (__DEV__) {
|
|
736
|
-
console.
|
|
673
|
+
console.warn('Failed to refresh device sessions, falling back to user sessions:', error);
|
|
737
674
|
}
|
|
675
|
+
try {
|
|
676
|
+
const serverSessions = await oxyServices.getSessionsBySessionId(activeSessionId);
|
|
677
|
+
const userSessions = mapSessionsToClient(serverSessions, undefined, user?.id);
|
|
678
|
+
updateSessions(userSessions, true);
|
|
679
|
+
} catch (fallbackError) {
|
|
680
|
+
if (__DEV__) {
|
|
681
|
+
console.error('Refresh sessions error:', fallbackError);
|
|
682
|
+
}
|
|
738
683
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
684
|
+
// If the current session is invalid, try to find another valid session
|
|
685
|
+
if (sessions.length > 1) {
|
|
686
|
+
const otherSessions = sessions.filter(s => s.sessionId !== activeSessionId);
|
|
687
|
+
for (const session of otherSessions) {
|
|
688
|
+
try {
|
|
689
|
+
const validation = await oxyServices.validateSession(session.sessionId, {
|
|
690
|
+
useHeaderValidation: true
|
|
691
|
+
});
|
|
692
|
+
if (validation.valid) {
|
|
693
|
+
await switchToSession(session.sessionId);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
} catch {
|
|
697
|
+
continue;
|
|
750
698
|
}
|
|
751
|
-
} catch {
|
|
752
|
-
continue;
|
|
753
699
|
}
|
|
754
700
|
}
|
|
755
|
-
}
|
|
756
701
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
702
|
+
// No valid sessions found, clear all
|
|
703
|
+
updateSessions([], false);
|
|
704
|
+
setActiveSessionId(null);
|
|
705
|
+
logoutStore();
|
|
706
|
+
setMinimalUser(null);
|
|
707
|
+
await clearAllStorage();
|
|
708
|
+
onAuthStateChange?.(null);
|
|
709
|
+
}
|
|
710
|
+
} finally {
|
|
711
|
+
// Clear the in-flight ref when done
|
|
712
|
+
refreshInFlightRef.current = null;
|
|
764
713
|
}
|
|
765
|
-
}
|
|
766
|
-
|
|
714
|
+
})();
|
|
715
|
+
refreshInFlightRef.current = refreshPromise;
|
|
716
|
+
return refreshPromise;
|
|
717
|
+
}, [activeSessionId, oxyServices, user?.id, updateSessions, sessions, switchToSession, logoutStore, clearAllStorage, onAuthStateChange, mapSessionsToClient]);
|
|
767
718
|
|
|
768
719
|
// Device management methods
|
|
769
720
|
const getDeviceSessions = useCallback(async () => {
|
|
@@ -784,7 +735,7 @@ export const OxyProvider = ({
|
|
|
784
735
|
if (!activeSessionId) throw new Error('No active session');
|
|
785
736
|
try {
|
|
786
737
|
await oxyServices.logoutAllDeviceSessions(activeSessionId);
|
|
787
|
-
|
|
738
|
+
updateSessions([], false);
|
|
788
739
|
setActiveSessionId(null);
|
|
789
740
|
logoutStore();
|
|
790
741
|
setMinimalUser(null);
|
|
@@ -910,6 +861,11 @@ export const OxyProvider = ({
|
|
|
910
861
|
return createEmptyFollowHook()(userId);
|
|
911
862
|
}
|
|
912
863
|
};
|
|
864
|
+
|
|
865
|
+
// Compute language metadata from currentLanguage
|
|
866
|
+
const languageMetadata = useMemo(() => getLanguageMetadata(currentLanguage), [currentLanguage]);
|
|
867
|
+
const languageName = useMemo(() => getLanguageName(currentLanguage), [currentLanguage]);
|
|
868
|
+
const nativeLanguageName = useMemo(() => getNativeLanguageName(currentLanguage), [currentLanguage]);
|
|
913
869
|
const contextValue = useMemo(() => ({
|
|
914
870
|
user,
|
|
915
871
|
minimalUser,
|
|
@@ -920,6 +876,9 @@ export const OxyProvider = ({
|
|
|
920
876
|
isTokenReady: tokenReady,
|
|
921
877
|
error,
|
|
922
878
|
currentLanguage,
|
|
879
|
+
currentLanguageMetadata: languageMetadata,
|
|
880
|
+
currentLanguageName: languageName,
|
|
881
|
+
currentNativeLanguageName: nativeLanguageName,
|
|
923
882
|
login,
|
|
924
883
|
logout,
|
|
925
884
|
logoutAll,
|
|
@@ -941,7 +900,7 @@ export const OxyProvider = ({
|
|
|
941
900
|
// Only depend on user ID, not the entire user object
|
|
942
901
|
minimalUser?.id, sessions.length,
|
|
943
902
|
// Only depend on sessions count, not the entire array
|
|
944
|
-
activeSessionId, isAuthenticated, isLoading, tokenReady, error, currentLanguage, login, logout, logoutAll, signUp, completeMfaLogin, switchSession, removeSession, refreshSessions, setLanguage, getDeviceSessions, logoutAllDeviceSessions, updateDeviceName, oxyServices, bottomSheetRef, showBottomSheet, hideBottomSheet]);
|
|
903
|
+
activeSessionId, isAuthenticated, isLoading, tokenReady, error, currentLanguage, languageMetadata, languageName, nativeLanguageName, login, logout, logoutAll, signUp, completeMfaLogin, switchSession, removeSession, refreshSessions, setLanguage, getDeviceSessions, logoutAllDeviceSessions, updateDeviceName, oxyServices, bottomSheetRef, showBottomSheet, hideBottomSheet]);
|
|
945
904
|
|
|
946
905
|
// Always render children - let the consuming app decide how to handle token loading state
|
|
947
906
|
return /*#__PURE__*/_jsx(OxyContext.Provider, {
|