@oxyhq/services 5.13.0 → 5.13.1

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/lib/commonjs/core/OxyServices.js +7 -7
  2. package/lib/commonjs/core/OxyServices.js.map +1 -1
  3. package/lib/commonjs/i18n/index.js +37 -1
  4. package/lib/commonjs/i18n/index.js.map +1 -1
  5. package/lib/commonjs/i18n/locales/ar-SA.json +128 -0
  6. package/lib/commonjs/i18n/locales/ca-ES.json +128 -0
  7. package/lib/commonjs/i18n/locales/de-DE.json +128 -0
  8. package/lib/commonjs/i18n/locales/en-US.json +85 -12
  9. package/lib/commonjs/i18n/locales/es-ES.json +58 -6
  10. package/lib/commonjs/i18n/locales/fr-FR.json +128 -0
  11. package/lib/commonjs/i18n/locales/it-IT.json +128 -0
  12. package/lib/commonjs/i18n/locales/ja-JP.json +127 -0
  13. package/lib/commonjs/i18n/locales/ko-KR.json +128 -0
  14. package/lib/commonjs/i18n/locales/pt-PT.json +128 -0
  15. package/lib/commonjs/i18n/locales/zh-CN.json +128 -0
  16. package/lib/commonjs/ui/components/FontLoader.js +22 -42
  17. package/lib/commonjs/ui/components/FontLoader.js.map +1 -1
  18. package/lib/commonjs/ui/components/OxyProvider.js +5 -8
  19. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  20. package/lib/commonjs/ui/components/StepBasedScreen.js +64 -44
  21. package/lib/commonjs/ui/components/StepBasedScreen.js.map +1 -1
  22. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js +14 -35
  23. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js.map +1 -1
  24. package/lib/commonjs/ui/components/internal/PinInput.js +2 -2
  25. package/lib/commonjs/ui/components/internal/PinInput.js.map +1 -1
  26. package/lib/commonjs/ui/context/OxyContext.js +434 -321
  27. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  28. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  29. package/lib/commonjs/ui/screens/SignInScreen.js +43 -39
  30. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  31. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +139 -125
  32. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  33. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +2 -4
  34. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  35. package/lib/commonjs/ui/screens/steps/RecoverRequestStep.js +45 -25
  36. package/lib/commonjs/ui/screens/steps/RecoverRequestStep.js.map +1 -1
  37. package/lib/commonjs/ui/screens/steps/RecoverResetPasswordStep.js +88 -53
  38. package/lib/commonjs/ui/screens/steps/RecoverResetPasswordStep.js.map +1 -1
  39. package/lib/commonjs/ui/screens/steps/RecoverSuccessStep.js +79 -58
  40. package/lib/commonjs/ui/screens/steps/RecoverSuccessStep.js.map +1 -1
  41. package/lib/commonjs/ui/screens/steps/RecoverVerifyStep.js +61 -52
  42. package/lib/commonjs/ui/screens/steps/RecoverVerifyStep.js.map +1 -1
  43. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js +220 -31
  44. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js.map +1 -1
  45. package/lib/commonjs/ui/screens/steps/SignInTotpStep.js +77 -50
  46. package/lib/commonjs/ui/screens/steps/SignInTotpStep.js.map +1 -1
  47. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js +527 -66
  48. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js.map +1 -1
  49. package/lib/commonjs/ui/screens/steps/SignUpIdentityStep.js +55 -30
  50. package/lib/commonjs/ui/screens/steps/SignUpIdentityStep.js.map +1 -1
  51. package/lib/commonjs/ui/screens/steps/SignUpSecurityStep.js +64 -46
  52. package/lib/commonjs/ui/screens/steps/SignUpSecurityStep.js.map +1 -1
  53. package/lib/commonjs/ui/screens/steps/SignUpSummaryStep.js +84 -146
  54. package/lib/commonjs/ui/screens/steps/SignUpSummaryStep.js.map +1 -1
  55. package/lib/commonjs/ui/screens/steps/SignUpWelcomeStep.js +113 -34
  56. package/lib/commonjs/ui/screens/steps/SignUpWelcomeStep.js.map +1 -1
  57. package/lib/commonjs/ui/stores/authStore.js +16 -20
  58. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  59. package/lib/commonjs/ui/styles/authStyles.js +2 -1
  60. package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
  61. package/lib/commonjs/ui/styles/index.js +11 -0
  62. package/lib/commonjs/ui/styles/index.js.map +1 -1
  63. package/lib/commonjs/ui/styles/spacing.js +51 -0
  64. package/lib/commonjs/ui/styles/spacing.js.map +1 -0
  65. package/lib/commonjs/utils/validationUtils.js +1 -1
  66. package/lib/module/core/OxyServices.js +7 -7
  67. package/lib/module/core/OxyServices.js.map +1 -1
  68. package/lib/module/i18n/index.js +37 -1
  69. package/lib/module/i18n/index.js.map +1 -1
  70. package/lib/module/i18n/locales/ar-SA.json +128 -0
  71. package/lib/module/i18n/locales/ca-ES.json +128 -0
  72. package/lib/module/i18n/locales/de-DE.json +128 -0
  73. package/lib/module/i18n/locales/en-US.json +85 -12
  74. package/lib/module/i18n/locales/es-ES.json +58 -6
  75. package/lib/module/i18n/locales/fr-FR.json +128 -0
  76. package/lib/module/i18n/locales/it-IT.json +128 -0
  77. package/lib/module/i18n/locales/ja-JP.json +127 -0
  78. package/lib/module/i18n/locales/ko-KR.json +128 -0
  79. package/lib/module/i18n/locales/pt-PT.json +128 -0
  80. package/lib/module/i18n/locales/zh-CN.json +128 -0
  81. package/lib/module/ui/components/FontLoader.js +23 -43
  82. package/lib/module/ui/components/FontLoader.js.map +1 -1
  83. package/lib/module/ui/components/OxyProvider.js +6 -8
  84. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  85. package/lib/module/ui/components/StepBasedScreen.js +65 -45
  86. package/lib/module/ui/components/StepBasedScreen.js.map +1 -1
  87. package/lib/module/ui/components/internal/GroupedPillButtons.js +14 -35
  88. package/lib/module/ui/components/internal/GroupedPillButtons.js.map +1 -1
  89. package/lib/module/ui/components/internal/PinInput.js +2 -2
  90. package/lib/module/ui/components/internal/PinInput.js.map +1 -1
  91. package/lib/module/ui/context/OxyContext.js +434 -321
  92. package/lib/module/ui/context/OxyContext.js.map +1 -1
  93. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  94. package/lib/module/ui/screens/SignInScreen.js +44 -40
  95. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  96. package/lib/module/ui/screens/WelcomeNewUserScreen.js +138 -126
  97. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  98. package/lib/module/ui/screens/internal/SignInUsernameStep.js +2 -4
  99. package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  100. package/lib/module/ui/screens/steps/RecoverRequestStep.js +45 -25
  101. package/lib/module/ui/screens/steps/RecoverRequestStep.js.map +1 -1
  102. package/lib/module/ui/screens/steps/RecoverResetPasswordStep.js +89 -54
  103. package/lib/module/ui/screens/steps/RecoverResetPasswordStep.js.map +1 -1
  104. package/lib/module/ui/screens/steps/RecoverSuccessStep.js +80 -59
  105. package/lib/module/ui/screens/steps/RecoverSuccessStep.js.map +1 -1
  106. package/lib/module/ui/screens/steps/RecoverVerifyStep.js +62 -53
  107. package/lib/module/ui/screens/steps/RecoverVerifyStep.js.map +1 -1
  108. package/lib/module/ui/screens/steps/SignInPasswordStep.js +221 -32
  109. package/lib/module/ui/screens/steps/SignInPasswordStep.js.map +1 -1
  110. package/lib/module/ui/screens/steps/SignInTotpStep.js +78 -51
  111. package/lib/module/ui/screens/steps/SignInTotpStep.js.map +1 -1
  112. package/lib/module/ui/screens/steps/SignInUsernameStep.js +530 -68
  113. package/lib/module/ui/screens/steps/SignInUsernameStep.js.map +1 -1
  114. package/lib/module/ui/screens/steps/SignUpIdentityStep.js +55 -30
  115. package/lib/module/ui/screens/steps/SignUpIdentityStep.js.map +1 -1
  116. package/lib/module/ui/screens/steps/SignUpSecurityStep.js +65 -47
  117. package/lib/module/ui/screens/steps/SignUpSecurityStep.js.map +1 -1
  118. package/lib/module/ui/screens/steps/SignUpSummaryStep.js +84 -146
  119. package/lib/module/ui/screens/steps/SignUpSummaryStep.js.map +1 -1
  120. package/lib/module/ui/screens/steps/SignUpWelcomeStep.js +114 -35
  121. package/lib/module/ui/screens/steps/SignUpWelcomeStep.js.map +1 -1
  122. package/lib/module/ui/stores/authStore.js +16 -20
  123. package/lib/module/ui/stores/authStore.js.map +1 -1
  124. package/lib/module/ui/styles/authStyles.js +2 -1
  125. package/lib/module/ui/styles/authStyles.js.map +1 -1
  126. package/lib/module/ui/styles/index.js +1 -0
  127. package/lib/module/ui/styles/index.js.map +1 -1
  128. package/lib/module/ui/styles/spacing.js +48 -0
  129. package/lib/module/ui/styles/spacing.js.map +1 -0
  130. package/lib/module/utils/validationUtils.js +1 -1
  131. package/lib/typescript/core/OxyServices.d.ts +4 -2
  132. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  133. package/lib/typescript/i18n/index.d.ts.map +1 -1
  134. package/lib/typescript/ui/components/FontLoader.d.ts +3 -3
  135. package/lib/typescript/ui/components/FontLoader.d.ts.map +1 -1
  136. package/lib/typescript/ui/components/OxyProvider.d.ts +2 -2
  137. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  138. package/lib/typescript/ui/components/StepBasedScreen.d.ts.map +1 -1
  139. package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts.map +1 -1
  140. package/lib/typescript/ui/context/OxyContext.d.ts +1 -0
  141. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  142. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  143. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  144. package/lib/typescript/ui/screens/steps/RecoverRequestStep.d.ts.map +1 -1
  145. package/lib/typescript/ui/screens/steps/RecoverResetPasswordStep.d.ts.map +1 -1
  146. package/lib/typescript/ui/screens/steps/RecoverSuccessStep.d.ts.map +1 -1
  147. package/lib/typescript/ui/screens/steps/RecoverVerifyStep.d.ts.map +1 -1
  148. package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts +2 -0
  149. package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts.map +1 -1
  150. package/lib/typescript/ui/screens/steps/SignInTotpStep.d.ts.map +1 -1
  151. package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts.map +1 -1
  152. package/lib/typescript/ui/screens/steps/SignUpIdentityStep.d.ts.map +1 -1
  153. package/lib/typescript/ui/screens/steps/SignUpSecurityStep.d.ts.map +1 -1
  154. package/lib/typescript/ui/screens/steps/SignUpSummaryStep.d.ts.map +1 -1
  155. package/lib/typescript/ui/screens/steps/SignUpWelcomeStep.d.ts.map +1 -1
  156. package/lib/typescript/ui/stores/authStore.d.ts +7 -3
  157. package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
  158. package/lib/typescript/ui/styles/authStyles.d.ts +1 -0
  159. package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -1
  160. package/lib/typescript/ui/styles/index.d.ts +1 -0
  161. package/lib/typescript/ui/styles/index.d.ts.map +1 -1
  162. package/lib/typescript/ui/styles/spacing.d.ts +43 -0
  163. package/lib/typescript/ui/styles/spacing.d.ts.map +1 -0
  164. package/lib/typescript/utils/validationUtils.d.ts +1 -1
  165. package/package.json +1 -1
  166. package/src/core/OxyServices.ts +10 -8
  167. package/src/i18n/index.ts +36 -0
  168. package/src/i18n/locales/ar-SA.json +128 -0
  169. package/src/i18n/locales/ca-ES.json +128 -0
  170. package/src/i18n/locales/de-DE.json +128 -0
  171. package/src/i18n/locales/en-US.json +85 -12
  172. package/src/i18n/locales/es-ES.json +58 -6
  173. package/src/i18n/locales/fr-FR.json +128 -0
  174. package/src/i18n/locales/it-IT.json +128 -0
  175. package/src/i18n/locales/ja-JP.json +127 -0
  176. package/src/i18n/locales/ko-KR.json +128 -0
  177. package/src/i18n/locales/pt-PT.json +128 -0
  178. package/src/i18n/locales/zh-CN.json +128 -0
  179. package/src/ui/components/FontLoader.tsx +17 -37
  180. package/src/ui/components/OxyProvider.tsx +14 -13
  181. package/src/ui/components/StepBasedScreen.tsx +66 -43
  182. package/src/ui/components/internal/GroupedPillButtons.tsx +15 -31
  183. package/src/ui/components/internal/PinInput.tsx +2 -2
  184. package/src/ui/context/OxyContext.tsx +404 -285
  185. package/src/ui/screens/FileManagementScreen.tsx +15 -15
  186. package/src/ui/screens/SignInScreen.tsx +59 -36
  187. package/src/ui/screens/WelcomeNewUserScreen.tsx +102 -91
  188. package/src/ui/screens/internal/SignInUsernameStep.tsx +1 -1
  189. package/src/ui/screens/steps/RecoverRequestStep.tsx +34 -24
  190. package/src/ui/screens/steps/RecoverResetPasswordStep.tsx +65 -36
  191. package/src/ui/screens/steps/RecoverSuccessStep.tsx +71 -47
  192. package/src/ui/screens/steps/RecoverVerifyStep.tsx +60 -50
  193. package/src/ui/screens/steps/SignInPasswordStep.tsx +191 -29
  194. package/src/ui/screens/steps/SignInTotpStep.tsx +68 -34
  195. package/src/ui/screens/steps/SignInUsernameStep.tsx +586 -57
  196. package/src/ui/screens/steps/SignUpIdentityStep.tsx +49 -35
  197. package/src/ui/screens/steps/SignUpSecurityStep.tsx +56 -39
  198. package/src/ui/screens/steps/SignUpSummaryStep.tsx +99 -89
  199. package/src/ui/screens/steps/SignUpWelcomeStep.tsx +88 -20
  200. package/src/ui/stores/authStore.ts +15 -19
  201. package/src/ui/styles/authStyles.ts +2 -1
  202. package/src/ui/styles/index.ts +1 -0
  203. package/src/ui/styles/spacing.ts +46 -0
  204. package/src/utils/validationUtils.ts +1 -1
@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.useOxy = exports.default = exports.OxyProvider = exports.OxyContextProvider = void 0;
7
7
  var _react = require("react");
8
- var _reactNative = require("react-native");
9
8
  var _core = require("../../core");
10
9
  var _deviceManager = require("../../utils/deviceManager");
11
10
  var _useSessionSocket = require("../hooks/useSessionSocket");
@@ -15,6 +14,26 @@ var _jsxRuntime = require("react/jsx-runtime");
15
14
  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
16
15
  // NOTE: We intentionally avoid importing useFollow here to prevent a require cycle.
17
16
  // If consumers relied on `const { useFollow } = useOxy()`, we provide a lazy proxy below.
17
+ // Empty follow hook fallback
18
+ const createEmptyFollowHook = () => {
19
+ const emptyResult = {
20
+ isFollowing: false,
21
+ isLoading: false,
22
+ error: null,
23
+ toggleFollow: async () => {},
24
+ setFollowStatus: () => {},
25
+ fetchStatus: async () => {},
26
+ clearError: () => {},
27
+ followerCount: null,
28
+ followingCount: null,
29
+ isLoadingCounts: false,
30
+ fetchUserCounts: async () => {},
31
+ setFollowerCount: () => {},
32
+ setFollowingCount: () => {}
33
+ };
34
+ return () => emptyResult;
35
+ };
36
+
18
37
  // Create the context with default values
19
38
  const OxyContext = /*#__PURE__*/(0, _react.createContext)(null);
20
39
 
@@ -109,6 +128,9 @@ const OxyProvider = ({
109
128
  const [storage, setStorage] = (0, _react.useState)(null);
110
129
  const [currentLanguage, setCurrentLanguage] = (0, _react.useState)('en-US');
111
130
 
131
+ // Storage keys (memoized to prevent infinite loops) - declared early for use in helpers
132
+ const keys = (0, _react.useMemo)(() => getStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
133
+
112
134
  // Normalize language codes to BCP-47 (e.g., en-US)
113
135
  const normalizeLanguageCode = (0, _react.useCallback)(lang => {
114
136
  if (!lang) return null;
@@ -128,21 +150,52 @@ const OxyProvider = ({
128
150
  };
129
151
  return map[lang] || lang;
130
152
  }, []);
131
- // Add a new state to track token restoration
132
- const [tokenReady, setTokenReady] = (0, _react.useState)(false);
133
153
 
134
- // Storage keys (memoized to prevent infinite loops)
135
- const keys = (0, _react.useMemo)(() => getStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
154
+ // Helper to apply language preference from user/server
155
+ const applyLanguagePreference = (0, _react.useCallback)(async user => {
156
+ const userLanguage = user?.language;
157
+ if (!userLanguage || !storage) return;
158
+ try {
159
+ const serverLang = normalizeLanguageCode(userLanguage) || userLanguage;
160
+ await storage.setItem(keys.language, serverLang);
161
+ setCurrentLanguage(serverLang);
162
+ } catch (e) {
163
+ if (__DEV__) {
164
+ console.warn('Failed to apply server language preference', e);
165
+ }
166
+ }
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 => ({
172
+ sessionId: s.sessionId,
173
+ deviceId: s.deviceId,
174
+ expiresAt: s.expiresAt || new Date().toISOString(),
175
+ lastActive: s.lastActive || new Date().toISOString(),
176
+ userId: s.userId || fallbackUserId
177
+ }));
178
+ }, []);
179
+
180
+ // Token ready state - start optimistically so children render immediately
181
+ const [tokenReady, setTokenReady] = (0, _react.useState)(true);
136
182
 
137
- // Clear all storage - defined before initAuth to avoid dependency issues
183
+ // Clear all storage
138
184
  const clearAllStorage = (0, _react.useCallback)(async () => {
139
185
  if (!storage) return;
140
186
  try {
141
187
  await storage.removeItem(keys.activeSessionId);
142
188
  } catch (err) {
143
- console.error('Clear storage error:', err);
189
+ if (__DEV__) {
190
+ console.error('Clear storage error:', err);
191
+ }
192
+ onError?.({
193
+ message: 'Failed to clear storage',
194
+ code: 'STORAGE_ERROR',
195
+ status: 500
196
+ });
144
197
  }
145
- }, [storage, keys]);
198
+ }, [storage, keys, onError]);
146
199
 
147
200
  // Initialize storage
148
201
  (0, _react.useEffect)(() => {
@@ -151,25 +204,28 @@ const OxyProvider = ({
151
204
  const platformStorage = await getStorage();
152
205
  setStorage(platformStorage);
153
206
  } catch (error) {
154
- console.error('Init storage failed', error);
207
+ const errorMessage = error instanceof Error ? error.message : 'Failed to initialize storage';
155
208
  _authStore.useAuthStore.setState({
156
- error: 'Failed to initialize storage'
209
+ error: errorMessage
210
+ });
211
+ onError?.({
212
+ message: errorMessage,
213
+ code: 'STORAGE_INIT_ERROR',
214
+ status: 500
157
215
  });
158
216
  }
159
217
  };
160
218
  initStorage();
161
- }, []);
219
+ }, [onError]);
162
220
 
163
221
  // Initialize authentication state
222
+ // Note: We don't set isLoading during initialization to avoid showing spinners
223
+ // Children render immediately and can check isTokenReady/isAuthenticated themselves
164
224
  (0, _react.useEffect)(() => {
165
225
  const initAuth = async () => {
166
226
  if (!storage) return;
167
- _authStore.useAuthStore.setState({
168
- isLoading: true
169
- });
227
+ // Don't set isLoading during initialization - let it happen in background
170
228
  try {
171
- setTokenReady(false);
172
-
173
229
  // Load saved language preference
174
230
  const savedLanguageRaw = await storage.getItem(keys.language);
175
231
  const savedLanguage = normalizeLanguageCode(savedLanguageRaw) || savedLanguageRaw;
@@ -194,67 +250,49 @@ const OxyProvider = ({
194
250
  username: fullUser.username,
195
251
  avatar: fullUser.avatar
196
252
  });
197
- // Apply server language if present
198
- if (fullUser?.language) {
199
- try {
200
- const serverLang = normalizeLanguageCode(fullUser.language) || fullUser.language;
201
- await storage.setItem(keys.language, serverLang);
202
- setCurrentLanguage(serverLang);
203
- } catch (e) {
204
- console.warn('Failed to apply server language preference', e);
253
+ await applyLanguagePreference(fullUser);
254
+
255
+ // Get all device sessions to support multiple accounts
256
+ try {
257
+ 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);
266
+ } catch (e) {
267
+ // Fallback to user sessions
268
+ if (__DEV__) {
269
+ console.warn('Failed to get device sessions on init, falling back to user sessions:', e);
205
270
  }
271
+ const serverSessions = await oxyServices.getSessionsBySessionId(storedActiveSessionId);
272
+ setSessions(mapServerSessionsToClient(serverSessions, fullUser.id));
206
273
  }
207
- const serverSessions = await oxyServices.getSessionsBySessionId(storedActiveSessionId);
208
- const clientSessions = serverSessions.map(s => ({
209
- sessionId: s.sessionId,
210
- deviceId: s.deviceId,
211
- expiresAt: s.expiresAt || new Date().toISOString(),
212
- lastActive: s.lastActive || new Date().toISOString(),
213
- userId: s.userId || fullUser.id
214
- }));
215
- setSessions(clientSessions);
216
274
  onAuthStateChange?.(fullUser);
217
275
  } else {
218
276
  await clearAllStorage();
219
277
  }
220
278
  } catch (e) {
221
- console.error('Session validation error', e);
279
+ if (__DEV__) {
280
+ console.error('Session validation error', e);
281
+ }
222
282
  await clearAllStorage();
223
283
  }
224
284
  }
225
285
  setTokenReady(true);
226
286
  } catch (e) {
227
- console.error('Auth init error', e);
287
+ if (__DEV__) {
288
+ console.error('Auth init error', e);
289
+ }
228
290
  await clearAllStorage();
229
- } finally {
230
- _authStore.useAuthStore.setState({
231
- isLoading: false
232
- });
291
+ setTokenReady(true);
233
292
  }
234
293
  };
235
294
  initAuth();
236
- }, [storage, oxyServices, keys, onAuthStateChange, loginSuccess, clearAllStorage]);
237
-
238
- // Remove invalid session - refresh sessions from backend
239
- const removeInvalidSession = (0, _react.useCallback)(async sessionId => {
240
- // Remove from local state
241
- const filteredSessions = sessions.filter(s => s.sessionId !== sessionId);
242
- setSessions(filteredSessions);
243
-
244
- // If there are other sessions, switch to the first one
245
- if (filteredSessions.length > 0) {
246
- await switchToSession(filteredSessions[0].sessionId);
247
- } else {
248
- // No valid sessions left
249
- setActiveSessionId(null);
250
- logoutStore();
251
- setMinimalUser(null);
252
- await storage?.removeItem(keys.activeSessionId);
253
- if (onAuthStateChange) {
254
- onAuthStateChange(null);
255
- }
256
- }
257
- }, [sessions, storage, keys, onAuthStateChange, logoutStore]);
295
+ }, [storage, oxyServices, keys, onAuthStateChange, loginSuccess, clearAllStorage, applyLanguagePreference, mapServerSessionsToClient]);
258
296
 
259
297
  // Save active session ID to storage (only session ID, no user data)
260
298
  const saveActiveSessionId = (0, _react.useCallback)(async sessionId => {
@@ -265,15 +303,23 @@ const OxyProvider = ({
265
303
  // Switch to a different session
266
304
  const switchToSession = (0, _react.useCallback)(async sessionId => {
267
305
  try {
268
- _authStore.useAuthStore.setState({
269
- isLoading: true
306
+ // Don't set isLoading - session switches should happen silently in background
307
+ // Validate session first before attempting to switch
308
+ const validation = await oxyServices.validateSession(sessionId, {
309
+ useHeaderValidation: true
270
310
  });
311
+ if (!validation.valid) {
312
+ // Session is invalid, remove it from the sessions list
313
+ setSessions(prevSessions => prevSessions.filter(s => s.sessionId !== sessionId));
314
+ throw new Error('Session is invalid or expired');
315
+ }
271
316
 
272
317
  // Get access token for this session
273
318
  await oxyServices.getTokenBySession(sessionId);
319
+ setTokenReady(true);
274
320
 
275
- // Load full user data
276
- const fullUser = await oxyServices.getUserBySession(sessionId);
321
+ // Load full user data - use user from validation if available, otherwise fetch
322
+ const fullUser = validation.user || (await oxyServices.getUserBySession(sessionId));
277
323
  setActiveSessionId(sessionId);
278
324
  loginSuccess(fullUser);
279
325
  setMinimalUser({
@@ -282,30 +328,88 @@ const OxyProvider = ({
282
328
  avatar: fullUser.avatar
283
329
  });
284
330
  await saveActiveSessionId(sessionId);
285
- // Apply server language if present
286
- if (fullUser?.language) {
287
- try {
288
- const serverLang = normalizeLanguageCode(fullUser.language) || fullUser.language;
289
- await storage?.setItem(keys.language, serverLang);
290
- setCurrentLanguage(serverLang);
291
- } catch (e) {
292
- console.warn('Failed to apply server language after switch', e);
331
+ 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);
293
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
+ });
294
369
  }
295
- if (onAuthStateChange) {
296
- onAuthStateChange(fullUser);
297
- }
370
+ onAuthStateChange?.(fullUser);
298
371
  } catch (error) {
299
- console.error('Switch session error:', error);
372
+ // Check if the error is due to invalid/expired session
373
+ const isInvalidSession = error?.response?.status === 401 || error?.message?.includes('Invalid or expired session') || error?.message?.includes('Session is invalid');
374
+ 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
379
+ if (sessionId === activeSessionId && sessions.length > 1) {
380
+ const otherSessions = sessions.filter(s => s.sessionId !== sessionId);
381
+ for (const otherSession of otherSessions) {
382
+ try {
383
+ const otherValidation = await oxyServices.validateSession(otherSession.sessionId, {
384
+ useHeaderValidation: true
385
+ });
386
+ if (otherValidation.valid) {
387
+ await switchToSession(otherSession.sessionId);
388
+ return;
389
+ }
390
+ } catch {
391
+ // Continue to next session
392
+ continue;
393
+ }
394
+ }
395
+ }
396
+ }
397
+ const errorMessage = error instanceof Error ? error.message : 'Failed to switch session';
398
+ if (__DEV__) {
399
+ console.error('Switch session error:', error);
400
+ }
300
401
  _authStore.useAuthStore.setState({
301
- error: 'Failed to switch session'
402
+ error: errorMessage
302
403
  });
303
- } finally {
304
- _authStore.useAuthStore.setState({
305
- isLoading: false
404
+ onError?.({
405
+ message: errorMessage,
406
+ code: isInvalidSession ? 'INVALID_SESSION' : 'SESSION_SWITCH_ERROR',
407
+ status: isInvalidSession ? 401 : 500
306
408
  });
409
+ setTokenReady(false);
410
+ throw error; // Re-throw so calling code can handle it
307
411
  }
308
- }, [oxyServices, onAuthStateChange, loginSuccess, saveActiveSessionId]);
412
+ }, [oxyServices, onAuthStateChange, loginSuccess, saveActiveSessionId, applyLanguagePreference, mapServerSessionsToClient, onError, activeSessionId, sessions]);
309
413
 
310
414
  // Login method - only store session ID, retrieve data from backend
311
415
  const login = (0, _react.useCallback)(async (username, password, deviceName) => {
@@ -315,59 +419,100 @@ const OxyProvider = ({
315
419
  error: null
316
420
  });
317
421
  try {
318
- // Get device fingerprint for enhanced device identification
319
422
  const deviceFingerprint = _deviceManager.DeviceManager.getDeviceFingerprint();
320
-
321
- // Get or generate persistent device info
322
423
  const deviceInfo = await _deviceManager.DeviceManager.getDeviceInfo();
323
- console.log('Auth - Using device fingerprint:', deviceFingerprint);
324
- console.log('Auth - Using device ID:', deviceInfo.deviceId);
325
424
  const response = await oxyServices.signIn(username, password, deviceName || deviceInfo.deviceName || _deviceManager.DeviceManager.getDefaultDeviceName(), deviceFingerprint);
326
425
 
327
426
  // Handle MFA requirement
328
- if (response && response.mfaRequired) {
329
- const err = new Error('Multi-factor authentication required');
330
- err.code = 'MFA_REQUIRED';
331
- err.mfaToken = response.mfaToken;
332
- err.expiresAt = response.expiresAt;
333
- throw err;
427
+ if (response && 'mfaRequired' in response && response.mfaRequired) {
428
+ const mfaError = new Error('Multi-factor authentication required');
429
+ mfaError.code = 'MFA_REQUIRED';
430
+ mfaError.mfaToken = response.mfaToken;
431
+ mfaError.expiresAt = response.expiresAt;
432
+ throw mfaError;
334
433
  }
434
+ const sessionResponse = response;
435
+ await oxyServices.getTokenBySession(sessionResponse.sessionId);
436
+ const fullUser = await oxyServices.getUserBySession(sessionResponse.sessionId);
335
437
 
336
- // Set as active session (only store session ID)
337
- setActiveSessionId(response.sessionId);
338
- await saveActiveSessionId(response.sessionId);
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
+ let allDeviceSessions = [];
441
+ try {
442
+ 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
+ }));
453
+ } catch (error) {
454
+ // Fallback to user sessions if device sessions fail
455
+ if (__DEV__) {
456
+ console.warn('Failed to get device sessions, falling back to user sessions:', error);
457
+ }
458
+ const serverSessions = await oxyServices.getSessionsBySessionId(sessionResponse.sessionId);
459
+ const userSessions = mapServerSessionsToClient(serverSessions, fullUser.id);
339
460
 
340
- // Get access token for API calls
341
- await oxyServices.getTokenBySession(response.sessionId);
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];
465
+ }
342
466
 
343
- // Load full user data from backend
344
- const fullUser = await oxyServices.getUserBySession(response.sessionId);
345
- loginSuccess(fullUser);
346
- setMinimalUser(response.user);
347
-
348
- // Load sessions from backend
349
- const serverSessions = await oxyServices.getSessionsBySessionId(response.sessionId);
350
- const clientSessions = serverSessions.map(serverSession => ({
351
- sessionId: serverSession.sessionId,
352
- deviceId: serverSession.deviceId,
353
- expiresAt: serverSession.expiresAt || new Date().toISOString(),
354
- lastActive: serverSession.lastActive || new Date().toISOString(),
355
- userId: serverSession.userId || fullUser.id
356
- }));
357
- setSessions(clientSessions);
358
- if (onAuthStateChange) {
359
- onAuthStateChange(fullUser);
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
+ const userUserId = fullUser.id?.toString();
470
+ const existingSession = allDeviceSessions.find(s => s.userId?.toString() === userUserId && s.sessionId !== sessionResponse.sessionId);
471
+ 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
+ try {
475
+ await oxyServices.logoutSession(sessionResponse.sessionId, sessionResponse.sessionId);
476
+ } catch (logoutError) {
477
+ if (__DEV__) {
478
+ console.warn('Failed to logout duplicate session:', logoutError);
479
+ }
480
+ }
481
+
482
+ // Switch to the existing session
483
+ await switchToSession(existingSession.sessionId);
484
+ loginSuccess(fullUser);
485
+ setMinimalUser(sessionResponse.user);
486
+
487
+ // Update sessions list (excluding the duplicate we just created)
488
+ setSessions(allDeviceSessions.filter(s => s.sessionId !== sessionResponse.sessionId));
489
+ onAuthStateChange?.(fullUser);
490
+ return fullUser;
360
491
  }
492
+
493
+ // No duplicate found, proceed with the new session
494
+ setActiveSessionId(sessionResponse.sessionId);
495
+ await saveActiveSessionId(sessionResponse.sessionId);
496
+ loginSuccess(fullUser);
497
+ setMinimalUser(sessionResponse.user);
498
+ setSessions(allDeviceSessions);
499
+ onAuthStateChange?.(fullUser);
361
500
  return fullUser;
362
501
  } catch (error) {
363
- loginFailure(error.message || 'Login failed');
502
+ const errorMessage = error instanceof Error ? error.message : 'Login failed';
503
+ loginFailure(errorMessage);
504
+ onError?.({
505
+ message: errorMessage,
506
+ code: 'LOGIN_ERROR',
507
+ status: 401
508
+ });
364
509
  throw error;
365
510
  } finally {
366
511
  _authStore.useAuthStore.setState({
367
512
  isLoading: false
368
513
  });
369
514
  }
370
- }, [storage, oxyServices, saveActiveSessionId, loginSuccess, setMinimalUser, onAuthStateChange, loginFailure]);
515
+ }, [storage, oxyServices, saveActiveSessionId, loginSuccess, onAuthStateChange, loginFailure, mapServerSessionsToClient, onError, sessions, switchToSession]);
371
516
 
372
517
  // Logout method
373
518
  const logout = (0, _react.useCallback)(async targetSessionId => {
@@ -397,74 +542,59 @@ const OxyProvider = ({
397
542
  }
398
543
  }
399
544
  } catch (error) {
400
- console.error('Logout error:', error);
545
+ const errorMessage = error instanceof Error ? error.message : 'Logout failed';
546
+ if (__DEV__) {
547
+ console.error('Logout error:', error);
548
+ }
401
549
  _authStore.useAuthStore.setState({
402
- error: 'Logout failed'
550
+ error: errorMessage
551
+ });
552
+ onError?.({
553
+ message: errorMessage,
554
+ code: 'LOGOUT_ERROR',
555
+ status: 500
403
556
  });
404
557
  }
405
- }, [activeSessionId, oxyServices, sessions, switchToSession, logoutStore, setMinimalUser, storage, keys.activeSessionId, onAuthStateChange]);
558
+ }, [activeSessionId, oxyServices, sessions, switchToSession, logoutStore, storage, keys.activeSessionId, onAuthStateChange, onError]);
406
559
 
407
560
  // Logout all sessions
408
561
  const logoutAll = (0, _react.useCallback)(async () => {
409
- console.log('logoutAll called with activeSessionId:', activeSessionId);
410
562
  if (!activeSessionId) {
411
- console.error('No active session ID found, cannot logout all');
563
+ const error = new Error('No active session found');
412
564
  _authStore.useAuthStore.setState({
413
- error: 'No active session found'
565
+ error: error.message
414
566
  });
415
- throw new Error('No active session found');
416
- }
417
- if (!oxyServices) {
418
- console.error('OxyServices not initialized');
419
- _authStore.useAuthStore.setState({
420
- error: 'Service not available'
567
+ onError?.({
568
+ message: error.message,
569
+ code: 'NO_SESSION_ERROR',
570
+ status: 404
421
571
  });
422
- throw new Error('Service not available');
572
+ throw error;
423
573
  }
424
574
  try {
425
- console.log('Calling oxyServices.logoutAllSessions with sessionId:', activeSessionId);
426
575
  await oxyServices.logoutAllSessions(activeSessionId);
427
- console.log('logoutAllSessions completed successfully');
428
-
429
- // Clear all local data
430
576
  setSessions([]);
431
577
  setActiveSessionId(null);
432
578
  logoutStore();
433
579
  setMinimalUser(null);
434
580
  await clearAllStorage();
435
- console.log('Local storage cleared');
436
- if (onAuthStateChange) {
437
- onAuthStateChange(null);
438
- console.log('Auth state change callback called');
439
- }
581
+ onAuthStateChange?.(null);
440
582
  } catch (error) {
441
- console.error('Logout all error:', error);
583
+ const errorMessage = error instanceof Error ? error.message : 'Logout all failed';
442
584
  _authStore.useAuthStore.setState({
443
- error: `Logout all failed: ${error instanceof Error ? error.message : 'Unknown error'}`
585
+ error: errorMessage
586
+ });
587
+ onError?.({
588
+ message: errorMessage,
589
+ code: 'LOGOUT_ALL_ERROR',
590
+ status: 500
444
591
  });
445
592
  throw error;
446
593
  }
447
- }, [activeSessionId, oxyServices, logoutStore, setMinimalUser, clearAllStorage, onAuthStateChange]);
594
+ }, [activeSessionId, oxyServices, logoutStore, clearAllStorage, onAuthStateChange, onError]);
448
595
 
449
- // Effect to restore token on app load or session switch
450
- (0, _react.useEffect)(() => {
451
- const restoreToken = async () => {
452
- if (activeSessionId && oxyServices) {
453
- try {
454
- await oxyServices.getTokenBySession(activeSessionId);
455
- setTokenReady(true);
456
- } catch (err) {
457
- // If token restoration fails, force logout
458
- await logout();
459
- setTokenReady(false);
460
- }
461
- } else {
462
- setTokenReady(true); // No session, so token is not needed
463
- }
464
- };
465
- restoreToken();
466
- // Only run when activeSessionId or oxyServices changes
467
- }, [activeSessionId, oxyServices, logout]);
596
+ // Token restoration is handled in initAuth and switchToSession
597
+ // No separate effect needed - children render immediately with isTokenReady available
468
598
 
469
599
  // Sign up method
470
600
  const signUp = (0, _react.useCallback)(async (username, email, password) => {
@@ -474,23 +604,24 @@ const OxyProvider = ({
474
604
  error: null
475
605
  });
476
606
  try {
477
- // Create new account using the OxyServices signUp method
478
- const response = await oxyServices.signUp(username, email, password);
479
- console.log('SignUp successful:', response);
480
-
481
- // Now log the user in to create a session
482
- // This will handle the session creation and device registration
607
+ await oxyServices.signUp(username, email, password);
483
608
  const user = await login(username, password);
484
609
  return user;
485
610
  } catch (error) {
486
- loginFailure(error.message || 'Sign up failed');
611
+ const errorMessage = error instanceof Error ? error.message : 'Sign up failed';
612
+ loginFailure(errorMessage);
613
+ onError?.({
614
+ message: errorMessage,
615
+ code: 'SIGNUP_ERROR',
616
+ status: 400
617
+ });
487
618
  throw error;
488
619
  } finally {
489
620
  _authStore.useAuthStore.setState({
490
621
  isLoading: false
491
622
  });
492
623
  }
493
- }, [storage, oxyServices, login, loginFailure]);
624
+ }, [storage, oxyServices, login, loginFailure, onError]);
494
625
 
495
626
  // Complete MFA login by verifying TOTP
496
627
  const completeMfaLogin = (0, _react.useCallback)(async (mfaToken, code) => {
@@ -515,116 +646,127 @@ const OxyProvider = ({
515
646
  username: fullUser.username,
516
647
  avatar: fullUser.avatar
517
648
  });
518
- // Apply server language if present
519
- if (fullUser?.language) {
520
- try {
521
- const serverLang = normalizeLanguageCode(fullUser.language) || fullUser.language;
522
- await storage.setItem(keys.language, serverLang);
523
- setCurrentLanguage(serverLang);
524
- } catch (e) {
525
- console.warn('Failed to apply server language on MFA login', e);
526
- }
527
- }
649
+ await applyLanguagePreference(fullUser);
528
650
 
529
- // Load sessions list
530
- const serverSessions = await oxyServices.getSessionsBySessionId(response.sessionId);
531
- const clientSessions = serverSessions.map(s => ({
532
- sessionId: s.sessionId,
533
- deviceId: s.deviceId,
534
- expiresAt: s.expiresAt || new Date().toISOString(),
535
- lastActive: s.lastActive || new Date().toISOString(),
536
- userId: s.userId || fullUser.id
537
- }));
538
- setSessions(clientSessions);
539
- // Apply server language if present
540
- if (fullUser?.language) {
541
- try {
542
- await storage.setItem(keys.language, fullUser.language);
543
- setCurrentLanguage(fullUser.language);
544
- } catch (e) {
545
- console.warn('Failed to apply server language on MFA login', e);
651
+ // Get all device sessions to support multiple accounts
652
+ try {
653
+ 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);
662
+ } catch (error) {
663
+ // Fallback to user sessions if device sessions fail
664
+ if (__DEV__) {
665
+ console.warn('Failed to get device sessions for MFA, falling back to user sessions:', error);
546
666
  }
667
+ 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
+ });
547
676
  }
548
- if (onAuthStateChange) onAuthStateChange(fullUser);
677
+ onAuthStateChange?.(fullUser);
549
678
  return fullUser;
550
679
  } catch (error) {
551
- loginFailure(error.message || 'MFA verification failed');
680
+ const errorMessage = error instanceof Error ? error.message : 'MFA verification failed';
681
+ loginFailure(errorMessage);
682
+ onError?.({
683
+ message: errorMessage,
684
+ code: 'MFA_ERROR',
685
+ status: 401
686
+ });
552
687
  throw error;
553
688
  } finally {
554
689
  _authStore.useAuthStore.setState({
555
690
  isLoading: false
556
691
  });
557
692
  }
558
- }, [storage, oxyServices, loginSuccess, loginFailure, saveActiveSessionId, onAuthStateChange]);
693
+ }, [storage, oxyServices, loginSuccess, loginFailure, saveActiveSessionId, onAuthStateChange, applyLanguagePreference, onError]);
559
694
 
560
- // Switch session method
695
+ // Switch session method (wrapper for consistency)
561
696
  const switchSession = (0, _react.useCallback)(async sessionId => {
562
697
  await switchToSession(sessionId);
563
698
  }, [switchToSession]);
564
699
 
565
- // Remove session method
700
+ // Remove session method (wrapper for consistency)
566
701
  const removeSession = (0, _react.useCallback)(async sessionId => {
567
702
  await logout(sessionId);
568
703
  }, [logout]);
569
704
 
570
705
  // Refresh sessions method
571
706
  const refreshSessions = (0, _react.useCallback)(async () => {
572
- console.log('refreshSessions called with activeSessionId:', activeSessionId);
573
- if (!activeSessionId) {
574
- console.log('refreshSessions: No activeSessionId, returning');
575
- return;
576
- }
707
+ if (!activeSessionId) return;
577
708
  try {
578
- console.log('refreshSessions: Calling getSessionsBySessionId...');
579
- const serverSessions = await oxyServices.getSessionsBySessionId(activeSessionId);
580
- console.log('refreshSessions: Server sessions received:', serverSessions);
581
-
582
- // Update local sessions with server data
583
- const updatedSessions = serverSessions.map(serverSession => ({
584
- sessionId: serverSession.sessionId,
585
- deviceId: serverSession.deviceId,
586
- expiresAt: serverSession.expiresAt || new Date().toISOString(),
587
- lastActive: serverSession.lastActive || new Date().toISOString(),
588
- userId: serverSession.userId || user?.id
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
589
717
  }));
590
- console.log('refreshSessions: Updated sessions:', updatedSessions);
591
- setSessions(updatedSessions);
592
- console.log('refreshSessions: Sessions updated in state');
718
+ setSessions(allDeviceSessions);
593
719
  } catch (error) {
594
- console.error('Refresh sessions error:', 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
+ }
725
+ 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) {
738
+ if (__DEV__) {
739
+ console.error('Refresh sessions error:', fallbackError);
740
+ }
595
741
 
596
- // If the current session is invalid, try to find another valid session
597
- if (sessions.length > 1) {
598
- console.log('Current session invalid, trying to switch to another session...');
599
- const otherSessions = sessions.filter(s => s.sessionId !== activeSessionId);
600
- for (const session of otherSessions) {
601
- try {
602
- // Try to validate this session
603
- await oxyServices.validateSession(session.sessionId, {
604
- useHeaderValidation: true
605
- });
606
- console.log('Found valid session, switching to:', session.sessionId);
607
- await switchToSession(session.sessionId);
608
- return; // Successfully switched to another session
609
- } catch (sessionError) {
610
- console.log('Session validation failed for:', session.sessionId, sessionError);
611
- continue; // Try next session
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;
753
+ }
754
+ } catch {
755
+ continue;
756
+ }
612
757
  }
613
758
  }
614
- }
615
759
 
616
- // If no valid sessions found, clear all sessions
617
- console.log('No valid sessions found, clearing all sessions');
618
- setSessions([]);
619
- setActiveSessionId(null);
620
- logoutStore();
621
- setMinimalUser(null);
622
- await clearAllStorage();
623
- if (onAuthStateChange) {
624
- onAuthStateChange(null);
760
+ // No valid sessions found, clear all
761
+ setSessions([]);
762
+ setActiveSessionId(null);
763
+ logoutStore();
764
+ setMinimalUser(null);
765
+ await clearAllStorage();
766
+ onAuthStateChange?.(null);
625
767
  }
626
768
  }
627
- }, [activeSessionId, oxyServices, user?.id, sessions, switchToSession, logoutStore, setMinimalUser, clearAllStorage, onAuthStateChange]);
769
+ }, [activeSessionId, oxyServices, user?.id, sessions, switchToSession, logoutStore, clearAllStorage, onAuthStateChange, mapServerSessionsToClient]);
628
770
 
629
771
  // Device management methods
630
772
  const getDeviceSessions = (0, _react.useCallback)(async () => {
@@ -632,58 +774,67 @@ const OxyProvider = ({
632
774
  try {
633
775
  return await oxyServices.getDeviceSessions(activeSessionId);
634
776
  } catch (error) {
635
- console.error('Get device sessions error:', error);
777
+ const errorMessage = error instanceof Error ? error.message : 'Failed to get device sessions';
778
+ onError?.({
779
+ message: errorMessage,
780
+ code: 'GET_DEVICE_SESSIONS_ERROR',
781
+ status: 500
782
+ });
636
783
  throw error;
637
784
  }
638
- }, [activeSessionId, oxyServices]);
785
+ }, [activeSessionId, oxyServices, onError]);
639
786
  const logoutAllDeviceSessions = (0, _react.useCallback)(async () => {
640
787
  if (!activeSessionId) throw new Error('No active session');
641
788
  try {
642
789
  await oxyServices.logoutAllDeviceSessions(activeSessionId);
643
-
644
- // Clear all local sessions since we logged out from all devices
645
790
  setSessions([]);
646
791
  setActiveSessionId(null);
647
792
  logoutStore();
648
793
  setMinimalUser(null);
649
794
  await clearAllStorage();
650
- if (onAuthStateChange) {
651
- onAuthStateChange(null);
652
- }
795
+ onAuthStateChange?.(null);
653
796
  } catch (error) {
654
- console.error('Logout all device sessions error:', error);
797
+ const errorMessage = error instanceof Error ? error.message : 'Failed to logout all device sessions';
798
+ onError?.({
799
+ message: errorMessage,
800
+ code: 'LOGOUT_ALL_DEVICES_ERROR',
801
+ status: 500
802
+ });
655
803
  throw error;
656
804
  }
657
- }, [activeSessionId, oxyServices, logoutStore, setMinimalUser, clearAllStorage, onAuthStateChange]);
805
+ }, [activeSessionId, oxyServices, logoutStore, clearAllStorage, onAuthStateChange, onError]);
658
806
  const updateDeviceName = (0, _react.useCallback)(async deviceName => {
659
807
  if (!activeSessionId) throw new Error('No active session');
660
808
  try {
661
809
  await oxyServices.updateDeviceName(activeSessionId, deviceName);
662
-
663
- // Update local device info
664
810
  await _deviceManager.DeviceManager.updateDeviceName(deviceName);
665
811
  } catch (error) {
666
- console.error('Update device name error:', error);
812
+ const errorMessage = error instanceof Error ? error.message : 'Failed to update device name';
813
+ onError?.({
814
+ message: errorMessage,
815
+ code: 'UPDATE_DEVICE_NAME_ERROR',
816
+ status: 500
817
+ });
667
818
  throw error;
668
819
  }
669
- }, [activeSessionId, oxyServices]);
820
+ }, [activeSessionId, oxyServices, onError]);
670
821
 
671
822
  // Language management method
672
823
  const setLanguage = (0, _react.useCallback)(async languageId => {
673
824
  if (!storage) throw new Error('Storage not initialized');
674
825
  try {
675
- // Save language preference
676
826
  await storage.setItem(keys.language, languageId);
677
827
  setCurrentLanguage(languageId);
678
- console.log(`Language changed to ${languageId}`);
679
-
680
- // TODO: Here you can add any additional logic needed for app-wide language updates
681
- // such as updating i18n configuration, refreshing translations, etc.
682
828
  } catch (error) {
683
- console.error('Error saving language preference:', error);
829
+ const errorMessage = error instanceof Error ? error.message : 'Failed to save language preference';
830
+ onError?.({
831
+ message: errorMessage,
832
+ code: 'LANGUAGE_SAVE_ERROR',
833
+ status: 500
834
+ });
684
835
  throw error;
685
836
  }
686
- }, [storage, keys.language]);
837
+ }, [storage, keys.language, onError]);
687
838
 
688
839
  // Bottom sheet control methods
689
840
  const showBottomSheet = (0, _react.useCallback)(screenOrConfig => {
@@ -698,9 +849,8 @@ const OxyProvider = ({
698
849
  } else if (bottomSheetRef.current.present) {
699
850
  if (__DEV__) console.log('Presenting bottom sheet');
700
851
  bottomSheetRef.current.present();
701
- } else {
852
+ } else if (__DEV__) {
702
853
  console.warn('No expand or present method available on bottomSheetRef');
703
- if (__DEV__) console.log('Available methods on bottomSheetRef.current:', Object.keys(bottomSheetRef.current));
704
854
  }
705
855
 
706
856
  // Then navigate to the specified screen if provided
@@ -718,10 +868,8 @@ const OxyProvider = ({
718
868
  }
719
869
  }, 100);
720
870
  }
721
- } else {
722
- console.warn('bottomSheetRef is not available');
723
- console.warn('To fix this, ensure you pass a bottomSheetRef to OxyProvider:');
724
- console.warn('<OxyProvider baseURL="..." bottomSheetRef={yourBottomSheetRef}>');
871
+ } else if (__DEV__) {
872
+ console.warn('bottomSheetRef is not available. Pass a bottomSheetRef to OxyProvider.');
725
873
  }
726
874
  }, [bottomSheetRef]);
727
875
  const hideBottomSheet = (0, _react.useCallback)(() => {
@@ -754,39 +902,15 @@ const OxyProvider = ({
754
902
  if (mod && typeof mod.useFollow === 'function') {
755
903
  return mod.useFollow(userId);
756
904
  }
757
- console.warn('useFollow module did not export a function as expected');
758
- return {
759
- isFollowing: false,
760
- isLoading: false,
761
- error: null,
762
- toggleFollow: async () => {},
763
- setFollowStatus: () => {},
764
- fetchStatus: async () => {},
765
- clearError: () => {},
766
- followerCount: null,
767
- followingCount: null,
768
- isLoadingCounts: false,
769
- fetchUserCounts: async () => {},
770
- setFollowerCount: () => {},
771
- setFollowingCount: () => {}
772
- };
905
+ if (__DEV__) {
906
+ console.warn('useFollow module did not export a function as expected');
907
+ }
908
+ return createEmptyFollowHook()(userId);
773
909
  } catch (e) {
774
- console.warn('Failed to dynamically load useFollow hook:', e);
775
- return {
776
- isFollowing: false,
777
- isLoading: false,
778
- error: null,
779
- toggleFollow: async () => {},
780
- setFollowStatus: () => {},
781
- fetchStatus: async () => {},
782
- clearError: () => {},
783
- followerCount: null,
784
- followingCount: null,
785
- isLoadingCounts: false,
786
- fetchUserCounts: async () => {},
787
- setFollowerCount: () => {},
788
- setFollowingCount: () => {}
789
- };
910
+ if (__DEV__) {
911
+ console.warn('Failed to dynamically load useFollow hook:', e);
912
+ }
913
+ return createEmptyFollowHook()(userId);
790
914
  }
791
915
  };
792
916
  const contextValue = (0, _react.useMemo)(() => ({
@@ -796,6 +920,7 @@ const OxyProvider = ({
796
920
  activeSessionId,
797
921
  isAuthenticated,
798
922
  isLoading,
923
+ isTokenReady: tokenReady,
799
924
  error,
800
925
  currentLanguage,
801
926
  login,
@@ -819,21 +944,9 @@ const OxyProvider = ({
819
944
  // Only depend on user ID, not the entire user object
820
945
  minimalUser?.id, sessions.length,
821
946
  // Only depend on sessions count, not the entire array
822
- activeSessionId, isAuthenticated, isLoading, error, currentLanguage, login, logout, logoutAll, signUp, completeMfaLogin, switchSession, removeSession, refreshSessions, setLanguage, getDeviceSessions, logoutAllDeviceSessions, updateDeviceName, oxyServices, bottomSheetRef, showBottomSheet, hideBottomSheet]);
823
-
824
- // Wrap children rendering to block until token is ready
825
- if (!tokenReady) {
826
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
827
- style: {
828
- flex: 1,
829
- justifyContent: 'center',
830
- alignItems: 'center'
831
- },
832
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
833
- children: "Loading authentication..."
834
- })
835
- });
836
- }
947
+ activeSessionId, isAuthenticated, isLoading, tokenReady, error, currentLanguage, login, logout, logoutAll, signUp, completeMfaLogin, switchSession, removeSession, refreshSessions, setLanguage, getDeviceSessions, logoutAllDeviceSessions, updateDeviceName, oxyServices, bottomSheetRef, showBottomSheet, hideBottomSheet]);
948
+
949
+ // Always render children - let the consuming app decide how to handle token loading state
837
950
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(OxyContext.Provider, {
838
951
  value: contextValue,
839
952
  children: children