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