@oxyhq/services 5.13.1 → 5.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/README.md +71 -0
  2. package/lib/commonjs/core/HttpClient.js +238 -0
  3. package/lib/commonjs/core/HttpClient.js.map +1 -0
  4. package/lib/commonjs/core/OxyServices.js +530 -332
  5. package/lib/commonjs/core/OxyServices.js.map +1 -1
  6. package/lib/commonjs/core/RequestManager.js +199 -0
  7. package/lib/commonjs/core/RequestManager.js.map +1 -0
  8. package/lib/commonjs/core/index.js +38 -1
  9. package/lib/commonjs/core/index.js.map +1 -1
  10. package/lib/commonjs/index.js +36 -0
  11. package/lib/commonjs/index.js.map +1 -1
  12. package/lib/commonjs/ui/components/Avatar.js +94 -27
  13. package/lib/commonjs/ui/components/Avatar.js.map +1 -1
  14. package/lib/commonjs/ui/components/FollowButton.js +1 -0
  15. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  16. package/lib/commonjs/ui/components/internal/TextField.js +13 -8
  17. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
  18. package/lib/commonjs/ui/context/OxyContext.js +183 -224
  19. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  20. package/lib/commonjs/ui/hooks/useSessionSocket.js +80 -22
  21. package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
  22. package/lib/commonjs/ui/index.js +4 -1
  23. package/lib/commonjs/ui/index.js.map +1 -1
  24. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +32 -2
  25. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  26. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +101 -59
  27. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  28. package/lib/commonjs/ui/screens/FileManagementScreen.js +3 -2
  29. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  30. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +75 -117
  31. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -1
  32. package/lib/commonjs/ui/screens/SignInScreen.js +0 -11
  33. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  34. package/lib/commonjs/ui/screens/SignUpScreen.js +14 -16
  35. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  36. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +50 -18
  37. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  38. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +10 -10
  39. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  40. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js +16 -26
  41. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js.map +1 -1
  42. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js +104 -212
  43. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js.map +1 -1
  44. package/lib/commonjs/ui/stores/accountStore.js +237 -0
  45. package/lib/commonjs/ui/stores/accountStore.js.map +1 -0
  46. package/lib/commonjs/ui/stores/authStore.js +2 -1
  47. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  48. package/lib/commonjs/ui/styles/authStyles.js +14 -7
  49. package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
  50. package/lib/commonjs/utils/asyncUtils.js +9 -22
  51. package/lib/commonjs/utils/asyncUtils.js.map +1 -1
  52. package/lib/commonjs/utils/cache.js +259 -0
  53. package/lib/commonjs/utils/cache.js.map +1 -0
  54. package/lib/commonjs/utils/index.js +99 -0
  55. package/lib/commonjs/utils/index.js.map +1 -1
  56. package/lib/commonjs/utils/languageUtils.js +159 -0
  57. package/lib/commonjs/utils/languageUtils.js.map +1 -0
  58. package/lib/commonjs/utils/requestUtils.js +217 -0
  59. package/lib/commonjs/utils/requestUtils.js.map +1 -0
  60. package/lib/commonjs/utils/sessionUtils.js +191 -0
  61. package/lib/commonjs/utils/sessionUtils.js.map +1 -0
  62. package/lib/module/core/HttpClient.js +232 -0
  63. package/lib/module/core/HttpClient.js.map +1 -0
  64. package/lib/module/core/OxyServices.js +528 -326
  65. package/lib/module/core/OxyServices.js.map +1 -1
  66. package/lib/module/core/RequestManager.js +194 -0
  67. package/lib/module/core/RequestManager.js.map +1 -0
  68. package/lib/module/core/index.js +2 -0
  69. package/lib/module/core/index.js.map +1 -1
  70. package/lib/module/index.js +2 -0
  71. package/lib/module/index.js.map +1 -1
  72. package/lib/module/ui/components/Avatar.js +94 -27
  73. package/lib/module/ui/components/Avatar.js.map +1 -1
  74. package/lib/module/ui/components/FollowButton.js +1 -0
  75. package/lib/module/ui/components/FollowButton.js.map +1 -1
  76. package/lib/module/ui/components/internal/TextField.js +13 -8
  77. package/lib/module/ui/components/internal/TextField.js.map +1 -1
  78. package/lib/module/ui/context/OxyContext.js +182 -223
  79. package/lib/module/ui/context/OxyContext.js.map +1 -1
  80. package/lib/module/ui/hooks/useSessionSocket.js +80 -22
  81. package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
  82. package/lib/module/ui/index.js +4 -2
  83. package/lib/module/ui/index.js.map +1 -1
  84. package/lib/module/ui/screens/AccountSettingsScreen.js +33 -2
  85. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  86. package/lib/module/ui/screens/AccountSwitcherScreen.js +102 -60
  87. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  88. package/lib/module/ui/screens/FileManagementScreen.js +3 -2
  89. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  90. package/lib/module/ui/screens/LanguageSelectorScreen.js +73 -117
  91. package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -1
  92. package/lib/module/ui/screens/SignInScreen.js +0 -11
  93. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  94. package/lib/module/ui/screens/SignUpScreen.js +14 -16
  95. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  96. package/lib/module/ui/screens/WelcomeNewUserScreen.js +50 -18
  97. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  98. package/lib/module/ui/screens/internal/SignInPasswordStep.js +10 -10
  99. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  100. package/lib/module/ui/screens/steps/SignInPasswordStep.js +16 -26
  101. package/lib/module/ui/screens/steps/SignInPasswordStep.js.map +1 -1
  102. package/lib/module/ui/screens/steps/SignInUsernameStep.js +105 -214
  103. package/lib/module/ui/screens/steps/SignInUsernameStep.js.map +1 -1
  104. package/lib/module/ui/stores/accountStore.js +229 -0
  105. package/lib/module/ui/stores/accountStore.js.map +1 -0
  106. package/lib/module/ui/stores/authStore.js +2 -1
  107. package/lib/module/ui/stores/authStore.js.map +1 -1
  108. package/lib/module/ui/styles/authStyles.js +14 -7
  109. package/lib/module/ui/styles/authStyles.js.map +1 -1
  110. package/lib/module/utils/asyncUtils.js +10 -22
  111. package/lib/module/utils/asyncUtils.js.map +1 -1
  112. package/lib/module/utils/cache.js +250 -0
  113. package/lib/module/utils/cache.js.map +1 -0
  114. package/lib/module/utils/index.js +7 -0
  115. package/lib/module/utils/index.js.map +1 -1
  116. package/lib/module/utils/languageUtils.js +151 -0
  117. package/lib/module/utils/languageUtils.js.map +1 -0
  118. package/lib/module/utils/requestUtils.js +210 -0
  119. package/lib/module/utils/requestUtils.js.map +1 -0
  120. package/lib/module/utils/sessionUtils.js +180 -0
  121. package/lib/module/utils/sessionUtils.js.map +1 -0
  122. package/lib/typescript/core/HttpClient.d.ts +64 -0
  123. package/lib/typescript/core/HttpClient.d.ts.map +1 -0
  124. package/lib/typescript/core/OxyServices.d.ts +82 -71
  125. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  126. package/lib/typescript/core/RequestManager.d.ts +67 -0
  127. package/lib/typescript/core/RequestManager.d.ts.map +1 -0
  128. package/lib/typescript/core/index.d.ts +2 -0
  129. package/lib/typescript/core/index.d.ts.map +1 -1
  130. package/lib/typescript/index.d.ts +2 -0
  131. package/lib/typescript/index.d.ts.map +1 -1
  132. package/lib/typescript/models/interfaces.d.ts +15 -0
  133. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  134. package/lib/typescript/models/session.d.ts +1 -0
  135. package/lib/typescript/models/session.d.ts.map +1 -1
  136. package/lib/typescript/ui/components/Avatar.d.ts +6 -7
  137. package/lib/typescript/ui/components/Avatar.d.ts.map +1 -1
  138. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  139. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
  140. package/lib/typescript/ui/context/OxyContext.d.ts +4 -0
  141. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  142. package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
  143. package/lib/typescript/ui/index.d.ts +2 -2
  144. package/lib/typescript/ui/index.d.ts.map +1 -1
  145. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  146. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  147. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +3 -3
  148. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -1
  149. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  150. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  151. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  152. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
  153. package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts.map +1 -1
  154. package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts.map +1 -1
  155. package/lib/typescript/ui/stores/accountStore.d.ts +34 -0
  156. package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -0
  157. package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
  158. package/lib/typescript/ui/styles/authStyles.d.ts +18 -2
  159. package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -1
  160. package/lib/typescript/utils/asyncUtils.d.ts +2 -0
  161. package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
  162. package/lib/typescript/utils/cache.d.ts +128 -0
  163. package/lib/typescript/utils/cache.d.ts.map +1 -0
  164. package/lib/typescript/utils/index.d.ts +4 -0
  165. package/lib/typescript/utils/index.d.ts.map +1 -1
  166. package/lib/typescript/utils/languageUtils.d.ts +38 -0
  167. package/lib/typescript/utils/languageUtils.d.ts.map +1 -0
  168. package/lib/typescript/utils/requestUtils.d.ts +122 -0
  169. package/lib/typescript/utils/requestUtils.d.ts.map +1 -0
  170. package/lib/typescript/utils/sessionUtils.d.ts +55 -0
  171. package/lib/typescript/utils/sessionUtils.d.ts.map +1 -0
  172. package/package.json +1 -1
  173. package/src/core/HttpClient.ts +277 -0
  174. package/src/core/OxyServices.ts +458 -351
  175. package/src/core/RequestManager.ts +240 -0
  176. package/src/core/index.ts +10 -0
  177. package/src/index.ts +10 -0
  178. package/src/models/interfaces.ts +19 -0
  179. package/src/models/session.ts +1 -1
  180. package/src/ui/components/Avatar.tsx +151 -35
  181. package/src/ui/components/FollowButton.tsx +1 -0
  182. package/src/ui/components/internal/TextField.tsx +7 -6
  183. package/src/ui/context/OxyContext.tsx +213 -217
  184. package/src/ui/hooks/useSessionSocket.ts +72 -18
  185. package/src/ui/index.ts +4 -1
  186. package/src/ui/screens/AccountSettingsScreen.tsx +34 -2
  187. package/src/ui/screens/AccountSwitcherScreen.tsx +102 -68
  188. package/src/ui/screens/FileManagementScreen.tsx +1 -1
  189. package/src/ui/screens/LanguageSelectorScreen.tsx +86 -143
  190. package/src/ui/screens/SignInScreen.tsx +0 -7
  191. package/src/ui/screens/SignUpScreen.tsx +14 -15
  192. package/src/ui/screens/WelcomeNewUserScreen.tsx +52 -15
  193. package/src/ui/screens/internal/SignInPasswordStep.tsx +4 -6
  194. package/src/ui/screens/steps/SignInPasswordStep.tsx +4 -8
  195. package/src/ui/screens/steps/SignInUsernameStep.tsx +110 -256
  196. package/src/ui/stores/accountStore.ts +285 -0
  197. package/src/ui/stores/authStore.ts +2 -1
  198. package/src/ui/styles/authStyles.ts +14 -7
  199. package/src/utils/asyncUtils.ts +10 -24
  200. package/src/utils/cache.ts +264 -0
  201. package/src/utils/index.ts +19 -0
  202. package/src/utils/languageUtils.ts +174 -0
  203. package/src/utils/requestUtils.ts +234 -0
  204. 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) || 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, normalizeLanguageCode]);
168
-
169
- // Helper to map server sessions to client sessions
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.map(ds => ({
259
- sessionId: ds.sessionId,
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
- setSessions(mapServerSessionsToClient(serverSessions, fullUser.id));
320
+ updateSessions(mapSessionsToClient(serverSessions, undefined, fullUser.id), false);
273
321
  }
274
322
  onAuthStateChange?.(fullUser);
275
323
  } else {
276
- await clearAllStorage();
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('Session validation error', e);
331
+ console.error('Active session validation error', e);
281
332
  }
282
- await clearAllStorage();
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, mapServerSessionsToClient]);
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
- // Session is invalid, remove it from the sessions list
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
- // Get access token for this session
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
- // Refresh all device sessions after switching
334
- // Preserve existing sessions from other users to avoid losing accounts
335
- try {
336
- const deviceSessions = await oxyServices.getDeviceSessions(sessionId);
337
- const allDeviceSessions = deviceSessions.map(ds => ({
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
- // Remove invalid session from the sessions list
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, mapServerSessionsToClient, onError, activeSessionId, sessions]);
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
- const userSessions = mapServerSessionsToClient(serverSessions, fullUser.id);
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
- setSessions(allDeviceSessions);
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, mapServerSessionsToClient, onError, sessions, switchToSession]);
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
- setSessions(filteredSessions);
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
- setSessions([]);
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.map(ds => ({
655
- sessionId: ds.sessionId,
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 = mapServerSessionsToClient(serverSessions, fullUser.id);
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
- try {
709
- // Get all device sessions to support multiple accounts
710
- const deviceSessions = await oxyServices.getDeviceSessions(activeSessionId);
711
- const allDeviceSessions = deviceSessions.map(ds => ({
712
- sessionId: ds.sessionId,
713
- deviceId: ds.deviceId,
714
- expiresAt: ds.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
715
- lastActive: ds.lastActive || new Date().toISOString(),
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 serverSessions = await oxyServices.getSessionsBySessionId(activeSessionId);
727
- const userSessions = mapServerSessionsToClient(serverSessions, user?.id);
728
- // Merge with existing sessions to preserve other accounts
729
- setSessions(prevSessions => {
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.error('Refresh sessions error:', fallbackError);
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
- // If the current session is invalid, try to find another valid session
743
- if (sessions.length > 1) {
744
- const otherSessions = sessions.filter(s => s.sessionId !== activeSessionId);
745
- for (const session of otherSessions) {
746
- try {
747
- const validation = await oxyServices.validateSession(session.sessionId, {
748
- useHeaderValidation: true
749
- });
750
- if (validation.valid) {
751
- await switchToSession(session.sessionId);
752
- return;
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
- // No valid sessions found, clear all
761
- setSessions([]);
762
- setActiveSessionId(null);
763
- logoutStore();
764
- setMinimalUser(null);
765
- await clearAllStorage();
766
- onAuthStateChange?.(null);
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
- }, [activeSessionId, oxyServices, user?.id, sessions, switchToSession, logoutStore, clearAllStorage, onAuthStateChange, mapServerSessionsToClient]);
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
- setSessions([]);
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, {