@oxyhq/services 5.16.39 → 5.16.41

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 (267) hide show
  1. package/lib/commonjs/adapters/expo/crypto.js +56 -0
  2. package/lib/commonjs/adapters/expo/crypto.js.map +1 -0
  3. package/lib/commonjs/adapters/expo/fetch.js +30 -0
  4. package/lib/commonjs/adapters/expo/fetch.js.map +1 -0
  5. package/lib/commonjs/adapters/expo/index.js +48 -0
  6. package/lib/commonjs/adapters/expo/index.js.map +1 -0
  7. package/lib/commonjs/adapters/expo/storage.js +201 -0
  8. package/lib/commonjs/adapters/expo/storage.js.map +1 -0
  9. package/lib/commonjs/adapters/index.js +41 -0
  10. package/lib/commonjs/adapters/index.js.map +1 -0
  11. package/lib/commonjs/adapters/node/crypto.js +40 -0
  12. package/lib/commonjs/adapters/node/crypto.js.map +1 -0
  13. package/lib/commonjs/adapters/node/fetch.js +62 -0
  14. package/lib/commonjs/adapters/node/fetch.js.map +1 -0
  15. package/lib/commonjs/adapters/node/index.js +34 -0
  16. package/lib/commonjs/adapters/node/index.js.map +1 -0
  17. package/lib/commonjs/adapters/node/storage.js +163 -0
  18. package/lib/commonjs/adapters/node/storage.js.map +1 -0
  19. package/lib/commonjs/core/identity-session/DeviceManager.js +237 -0
  20. package/lib/commonjs/core/identity-session/DeviceManager.js.map +1 -0
  21. package/lib/commonjs/core/identity-session/INTEGRATION_GUIDE.md +287 -0
  22. package/lib/commonjs/core/identity-session/IdentityManager.js +400 -0
  23. package/lib/commonjs/core/identity-session/IdentityManager.js.map +1 -0
  24. package/lib/commonjs/core/identity-session/IdentitySessionCore.js +394 -0
  25. package/lib/commonjs/core/identity-session/IdentitySessionCore.js.map +1 -0
  26. package/lib/commonjs/core/identity-session/RefreshManager.js +137 -0
  27. package/lib/commonjs/core/identity-session/RefreshManager.js.map +1 -0
  28. package/lib/commonjs/core/identity-session/SessionManager.js +427 -0
  29. package/lib/commonjs/core/identity-session/SessionManager.js.map +1 -0
  30. package/lib/commonjs/core/identity-session/createIdentitySessionCore.js +24 -0
  31. package/lib/commonjs/core/identity-session/createIdentitySessionCore.js.map +1 -0
  32. package/lib/commonjs/core/identity-session/errors.js +176 -0
  33. package/lib/commonjs/core/identity-session/errors.js.map +1 -0
  34. package/lib/commonjs/core/identity-session/index.js +80 -0
  35. package/lib/commonjs/core/identity-session/index.js.map +1 -0
  36. package/lib/commonjs/core/identity-session/types.js +2 -0
  37. package/lib/commonjs/core/identity-session/types.js.map +1 -0
  38. package/lib/commonjs/core/index.js +2 -21
  39. package/lib/commonjs/core/index.js.map +1 -1
  40. package/lib/commonjs/index.js +58 -8
  41. package/lib/commonjs/index.js.map +1 -1
  42. package/lib/commonjs/models/interfaces.js +7 -0
  43. package/lib/commonjs/models/interfaces.js.map +1 -1
  44. package/lib/commonjs/ui/context/OxyContext.js +434 -820
  45. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  46. package/lib/commonjs/ui/hooks/useAvatarPicker.js +52 -0
  47. package/lib/commonjs/ui/hooks/useAvatarPicker.js.map +1 -0
  48. package/lib/commonjs/ui/hooks/useIdentityTransfer.js +125 -0
  49. package/lib/commonjs/ui/hooks/useIdentityTransfer.js.map +1 -0
  50. package/lib/commonjs/ui/hooks/useTransferCodesPersistence.js +81 -0
  51. package/lib/commonjs/ui/hooks/useTransferCodesPersistence.js.map +1 -0
  52. package/lib/commonjs/ui/screens/AccountCenterScreen.js +7 -2
  53. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  54. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +12 -5
  55. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  56. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +2 -2
  57. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  58. package/lib/commonjs/ui/screens/ProfileScreen.js +6 -6
  59. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  60. package/lib/commonjs/ui/utils/sessionHelpers.js +7 -1
  61. package/lib/commonjs/ui/utils/sessionHelpers.js.map +1 -1
  62. package/lib/commonjs/utils/index.js +0 -7
  63. package/lib/commonjs/utils/index.js.map +1 -1
  64. package/lib/commonjs/utils/sessionUtils.js +8 -1
  65. package/lib/commonjs/utils/sessionUtils.js.map +1 -1
  66. package/lib/module/adapters/expo/crypto.js +51 -0
  67. package/lib/module/adapters/expo/crypto.js.map +1 -0
  68. package/lib/module/adapters/expo/fetch.js +26 -0
  69. package/lib/module/adapters/expo/fetch.js.map +1 -0
  70. package/lib/module/adapters/expo/index.js +45 -0
  71. package/lib/module/adapters/expo/index.js.map +1 -0
  72. package/lib/module/adapters/expo/storage.js +198 -0
  73. package/lib/module/adapters/expo/storage.js.map +1 -0
  74. package/lib/module/adapters/index.js +38 -0
  75. package/lib/module/adapters/index.js.map +1 -0
  76. package/lib/module/adapters/node/crypto.js +36 -0
  77. package/lib/module/adapters/node/crypto.js.map +1 -0
  78. package/lib/module/adapters/node/fetch.js +57 -0
  79. package/lib/module/adapters/node/fetch.js.map +1 -0
  80. package/lib/module/adapters/node/index.js +31 -0
  81. package/lib/module/adapters/node/index.js.map +1 -0
  82. package/lib/module/adapters/node/storage.js +159 -0
  83. package/lib/module/adapters/node/storage.js.map +1 -0
  84. package/lib/module/core/identity-session/DeviceManager.js +232 -0
  85. package/lib/module/core/identity-session/DeviceManager.js.map +1 -0
  86. package/lib/module/core/identity-session/INTEGRATION_GUIDE.md +287 -0
  87. package/lib/module/core/identity-session/IdentityManager.js +395 -0
  88. package/lib/module/core/identity-session/IdentityManager.js.map +1 -0
  89. package/lib/module/core/identity-session/IdentitySessionCore.js +390 -0
  90. package/lib/module/core/identity-session/IdentitySessionCore.js.map +1 -0
  91. package/lib/module/core/identity-session/RefreshManager.js +132 -0
  92. package/lib/module/core/identity-session/RefreshManager.js.map +1 -0
  93. package/lib/module/core/identity-session/SessionManager.js +422 -0
  94. package/lib/module/core/identity-session/SessionManager.js.map +1 -0
  95. package/lib/module/core/identity-session/createIdentitySessionCore.js +21 -0
  96. package/lib/module/core/identity-session/createIdentitySessionCore.js.map +1 -0
  97. package/lib/module/core/identity-session/errors.js +170 -0
  98. package/lib/module/core/identity-session/errors.js.map +1 -0
  99. package/lib/module/core/identity-session/index.js +17 -0
  100. package/lib/module/core/identity-session/index.js.map +1 -0
  101. package/lib/module/core/identity-session/types.js +2 -0
  102. package/lib/module/core/identity-session/types.js.map +1 -0
  103. package/lib/module/core/index.js +2 -3
  104. package/lib/module/core/index.js.map +1 -1
  105. package/lib/module/index.js +12 -2
  106. package/lib/module/index.js.map +1 -1
  107. package/lib/module/models/interfaces.js +7 -0
  108. package/lib/module/models/interfaces.js.map +1 -1
  109. package/lib/module/ui/context/OxyContext.js +436 -822
  110. package/lib/module/ui/context/OxyContext.js.map +1 -1
  111. package/lib/module/ui/hooks/useAvatarPicker.js +48 -0
  112. package/lib/module/ui/hooks/useAvatarPicker.js.map +1 -0
  113. package/lib/module/ui/hooks/useIdentityTransfer.js +121 -0
  114. package/lib/module/ui/hooks/useIdentityTransfer.js.map +1 -0
  115. package/lib/module/ui/hooks/useTransferCodesPersistence.js +77 -0
  116. package/lib/module/ui/hooks/useTransferCodesPersistence.js.map +1 -0
  117. package/lib/module/ui/screens/AccountCenterScreen.js +7 -2
  118. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  119. package/lib/module/ui/screens/AccountSettingsScreen.js +12 -5
  120. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  121. package/lib/module/ui/screens/AccountSwitcherScreen.js +2 -2
  122. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  123. package/lib/module/ui/screens/ProfileScreen.js +6 -6
  124. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  125. package/lib/module/ui/utils/sessionHelpers.js +7 -1
  126. package/lib/module/ui/utils/sessionHelpers.js.map +1 -1
  127. package/lib/module/utils/index.js +2 -1
  128. package/lib/module/utils/index.js.map +1 -1
  129. package/lib/module/utils/sessionUtils.js +8 -1
  130. package/lib/module/utils/sessionUtils.js.map +1 -1
  131. package/lib/typescript/adapters/expo/crypto.d.ts +17 -0
  132. package/lib/typescript/adapters/expo/crypto.d.ts.map +1 -0
  133. package/lib/typescript/adapters/expo/fetch.d.ts +16 -0
  134. package/lib/typescript/adapters/expo/fetch.d.ts.map +1 -0
  135. package/lib/typescript/adapters/expo/index.d.ts +23 -0
  136. package/lib/typescript/adapters/expo/index.d.ts.map +1 -0
  137. package/lib/typescript/adapters/expo/storage.d.ts +23 -0
  138. package/lib/typescript/adapters/expo/storage.d.ts.map +1 -0
  139. package/lib/typescript/adapters/index.d.ts +13 -0
  140. package/lib/typescript/adapters/index.d.ts.map +1 -0
  141. package/lib/typescript/adapters/node/crypto.d.ts +17 -0
  142. package/lib/typescript/adapters/node/crypto.d.ts.map +1 -0
  143. package/lib/typescript/adapters/node/fetch.d.ts +16 -0
  144. package/lib/typescript/adapters/node/fetch.d.ts.map +1 -0
  145. package/lib/typescript/adapters/node/index.d.ts +23 -0
  146. package/lib/typescript/adapters/node/index.d.ts.map +1 -0
  147. package/lib/typescript/adapters/node/storage.d.ts +23 -0
  148. package/lib/typescript/adapters/node/storage.d.ts.map +1 -0
  149. package/lib/typescript/core/identity-session/DeviceManager.d.ts +64 -0
  150. package/lib/typescript/core/identity-session/DeviceManager.d.ts.map +1 -0
  151. package/lib/typescript/core/identity-session/IdentityManager.d.ts +88 -0
  152. package/lib/typescript/core/identity-session/IdentityManager.d.ts.map +1 -0
  153. package/lib/typescript/core/identity-session/IdentitySessionCore.d.ts +141 -0
  154. package/lib/typescript/core/identity-session/IdentitySessionCore.d.ts.map +1 -0
  155. package/lib/typescript/core/identity-session/RefreshManager.d.ts +36 -0
  156. package/lib/typescript/core/identity-session/RefreshManager.d.ts.map +1 -0
  157. package/lib/typescript/core/identity-session/SessionManager.d.ts +104 -0
  158. package/lib/typescript/core/identity-session/SessionManager.d.ts.map +1 -0
  159. package/lib/typescript/core/identity-session/createIdentitySessionCore.d.ts +11 -0
  160. package/lib/typescript/core/identity-session/createIdentitySessionCore.d.ts.map +1 -0
  161. package/lib/typescript/core/identity-session/errors.d.ts +63 -0
  162. package/lib/typescript/core/identity-session/errors.d.ts.map +1 -0
  163. package/lib/typescript/core/identity-session/index.d.ts +14 -0
  164. package/lib/typescript/core/identity-session/index.d.ts.map +1 -0
  165. package/lib/typescript/core/identity-session/types.d.ts +196 -0
  166. package/lib/typescript/core/identity-session/types.d.ts.map +1 -0
  167. package/lib/typescript/core/index.d.ts +1 -3
  168. package/lib/typescript/core/index.d.ts.map +1 -1
  169. package/lib/typescript/core/mixins/index.d.ts +2 -2
  170. package/lib/typescript/index.d.ts +3 -2
  171. package/lib/typescript/index.d.ts.map +1 -1
  172. package/lib/typescript/models/interfaces.d.ts +5 -36
  173. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  174. package/lib/typescript/models/session.d.ts +3 -16
  175. package/lib/typescript/models/session.d.ts.map +1 -1
  176. package/lib/typescript/ui/context/OxyContext.d.ts +2 -25
  177. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  178. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts +7 -8
  179. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  180. package/lib/typescript/ui/hooks/mutations/useServicesMutations.d.ts +1 -1
  181. package/lib/typescript/ui/hooks/mutations/useServicesMutations.d.ts.map +1 -1
  182. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts +5 -5
  183. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  184. package/lib/typescript/ui/hooks/useAvatarPicker.d.ts +18 -0
  185. package/lib/typescript/ui/hooks/useAvatarPicker.d.ts.map +1 -0
  186. package/lib/typescript/ui/hooks/useIdentityTransfer.d.ts +24 -0
  187. package/lib/typescript/ui/hooks/useIdentityTransfer.d.ts.map +1 -0
  188. package/lib/typescript/ui/hooks/useTransferCodesPersistence.d.ts +6 -0
  189. package/lib/typescript/ui/hooks/useTransferCodesPersistence.d.ts.map +1 -0
  190. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  191. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  192. package/lib/typescript/ui/utils/sessionHelpers.d.ts +1 -0
  193. package/lib/typescript/ui/utils/sessionHelpers.d.ts.map +1 -1
  194. package/lib/typescript/utils/index.d.ts +0 -2
  195. package/lib/typescript/utils/index.d.ts.map +1 -1
  196. package/lib/typescript/utils/sessionUtils.d.ts.map +1 -1
  197. package/package.json +1 -1
  198. package/src/adapters/expo/crypto.ts +55 -0
  199. package/src/adapters/expo/fetch.ts +28 -0
  200. package/src/adapters/expo/index.ts +51 -0
  201. package/src/adapters/expo/storage.ts +228 -0
  202. package/src/adapters/index.ts +40 -0
  203. package/src/adapters/node/crypto.ts +39 -0
  204. package/src/adapters/node/fetch.ts +59 -0
  205. package/src/adapters/node/index.ts +37 -0
  206. package/src/adapters/node/storage.ts +170 -0
  207. package/src/core/identity-session/DeviceManager.ts +273 -0
  208. package/src/core/identity-session/INTEGRATION_GUIDE.md +287 -0
  209. package/src/core/identity-session/IdentityManager.ts +474 -0
  210. package/src/core/identity-session/IdentitySessionCore.ts +464 -0
  211. package/src/core/identity-session/RefreshManager.ts +189 -0
  212. package/src/core/identity-session/SessionManager.ts +500 -0
  213. package/src/core/identity-session/createIdentitySessionCore.ts +19 -0
  214. package/src/core/identity-session/errors.ts +197 -0
  215. package/src/core/identity-session/index.ts +15 -0
  216. package/src/core/identity-session/types.ts +188 -0
  217. package/src/core/index.ts +3 -4
  218. package/src/index.ts +28 -3
  219. package/src/models/interfaces.ts +12 -39
  220. package/src/models/session.ts +6 -16
  221. package/src/ui/context/OxyContext.tsx +442 -871
  222. package/src/ui/hooks/auth/index.ts +1 -0
  223. package/src/ui/hooks/useAvatarPicker.ts +62 -0
  224. package/src/ui/hooks/useIdentityTransfer.ts +135 -0
  225. package/src/ui/hooks/useTransferCodesPersistence.ts +80 -0
  226. package/src/ui/screens/AccountCenterScreen.tsx +7 -2
  227. package/src/ui/screens/AccountSettingsScreen.tsx +15 -8
  228. package/src/ui/screens/AccountSwitcherScreen.tsx +2 -2
  229. package/src/ui/screens/ProfileScreen.tsx +10 -10
  230. package/src/ui/utils/sessionHelpers.ts +7 -0
  231. package/src/utils/index.ts +1 -2
  232. package/src/utils/sessionUtils.ts +8 -0
  233. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +0 -704
  234. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +0 -1
  235. package/lib/commonjs/ui/context/hooks/useDeviceManagement.js +0 -73
  236. package/lib/commonjs/ui/context/hooks/useDeviceManagement.js.map +0 -1
  237. package/lib/commonjs/ui/hooks/useDeviceManagement.js +0 -73
  238. package/lib/commonjs/ui/hooks/useDeviceManagement.js.map +0 -1
  239. package/lib/commonjs/ui/hooks/useSessionManagement.js +0 -281
  240. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +0 -1
  241. package/lib/commonjs/utils/deviceManager.js +0 -177
  242. package/lib/commonjs/utils/deviceManager.js.map +0 -1
  243. package/lib/module/ui/context/hooks/useAuthOperations.js +0 -698
  244. package/lib/module/ui/context/hooks/useAuthOperations.js.map +0 -1
  245. package/lib/module/ui/context/hooks/useDeviceManagement.js +0 -68
  246. package/lib/module/ui/context/hooks/useDeviceManagement.js.map +0 -1
  247. package/lib/module/ui/hooks/useDeviceManagement.js +0 -68
  248. package/lib/module/ui/hooks/useDeviceManagement.js.map +0 -1
  249. package/lib/module/ui/hooks/useSessionManagement.js +0 -276
  250. package/lib/module/ui/hooks/useSessionManagement.js.map +0 -1
  251. package/lib/module/utils/deviceManager.js +0 -171
  252. package/lib/module/utils/deviceManager.js.map +0 -1
  253. package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts +0 -59
  254. package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +0 -1
  255. package/lib/typescript/ui/context/hooks/useDeviceManagement.d.ts +0 -27
  256. package/lib/typescript/ui/context/hooks/useDeviceManagement.d.ts.map +0 -1
  257. package/lib/typescript/ui/hooks/useDeviceManagement.d.ts +0 -27
  258. package/lib/typescript/ui/hooks/useDeviceManagement.d.ts.map +0 -1
  259. package/lib/typescript/ui/hooks/useSessionManagement.d.ts +0 -41
  260. package/lib/typescript/ui/hooks/useSessionManagement.d.ts.map +0 -1
  261. package/lib/typescript/utils/deviceManager.d.ts +0 -66
  262. package/lib/typescript/utils/deviceManager.d.ts.map +0 -1
  263. package/src/ui/context/hooks/useAuthOperations.ts +0 -773
  264. package/src/ui/context/hooks/useDeviceManagement.ts +0 -108
  265. package/src/ui/hooks/useDeviceManagement.ts +0 -108
  266. package/src/ui/hooks/useSessionManagement.ts +0 -401
  267. package/src/utils/deviceManager.ts +0 -198
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
 
3
- import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
4
- import { Platform } from 'react-native';
3
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
5
4
  import { OxyServices } from '../../core';
6
5
  import { toast } from '../../lib/sonner';
7
6
  import { useAuthStore } from '../stores/authStore';
@@ -9,49 +8,20 @@ import { useShallow } from 'zustand/react/shallow';
9
8
  import { useSessionSocket } from '../hooks/useSessionSocket';
10
9
  import { useStorage } from '../hooks/useStorage';
11
10
  import { useLanguageManagement } from '../hooks/useLanguageManagement';
12
- import { useSessionManagement } from '../hooks/useSessionManagement';
13
- import { useAuthOperations } from './hooks/useAuthOperations';
14
- import { useDeviceManagement } from '../hooks/useDeviceManagement';
15
11
  import { getStorageKeys } from '../utils/storageHelpers';
16
12
  import { isInvalidSessionError, isTimeoutOrNetworkError } from '../utils/errorHandlers';
17
13
  import { showBottomSheet as globalShowBottomSheet } from '../navigation/bottomSheetManager';
18
14
  import { useQueryClient } from '@tanstack/react-query';
19
15
  import { clearQueryCache } from '../hooks/queryClient';
20
- import { KeyManager } from '../../crypto';
21
- import { translate } from '../../i18n';
22
- import { updateAvatarVisibility, updateProfileWithAvatar } from '../utils/avatarUtils';
16
+ import { createIdentitySessionCore } from '../../core/identity-session';
23
17
  import { useAccountStore } from '../stores/accountStore';
24
- import { logger as loggerUtil } from '../../utils/loggerUtils';
25
- import { useTransferStore, useTransferCodesForPersistence } from '../stores/transferStore';
18
+ import { useTransferStore } from '../stores/transferStore';
26
19
  import { useCheckPendingTransfers } from '../hooks/useTransferQueries';
20
+ import { useTransferCodesPersistence } from '../hooks/useTransferCodesPersistence';
21
+ import { useIdentityTransfer } from '../hooks/useIdentityTransfer';
22
+ import { useAvatarPicker } from '../hooks/useAvatarPicker';
27
23
  import { jsx as _jsx } from "react/jsx-runtime";
28
24
  const OxyContext = /*#__PURE__*/createContext(null);
29
- let cachedUseFollowHook = null;
30
- const loadUseFollowHook = () => {
31
- if (cachedUseFollowHook) {
32
- return cachedUseFollowHook;
33
- }
34
- try {
35
- // eslint-disable-next-line @typescript-eslint/no-var-requires
36
- const {
37
- useFollow
38
- } = require('../hooks/useFollow');
39
- cachedUseFollowHook = useFollow;
40
- return cachedUseFollowHook;
41
- } catch (error) {
42
- if (__DEV__) {
43
- loggerUtil.warn('useFollow hook is not available. Please import useFollow from @oxyhq/services directly.', {
44
- component: 'OxyContext',
45
- method: 'loadUseFollowHook'
46
- }, error);
47
- }
48
- const fallback = () => {
49
- throw new Error('useFollow hook is only available in the UI bundle. Import it from @oxyhq/services.');
50
- };
51
- cachedUseFollowHook = fallback;
52
- return cachedUseFollowHook;
53
- }
54
- };
55
25
  export const OxyProvider = ({
56
26
  children,
57
27
  oxyServices: providedOxyServices,
@@ -101,7 +71,6 @@ export const OxyProvider = ({
101
71
  setSyncing: state.setSyncing
102
72
  })));
103
73
  const [tokenReady, setTokenReady] = useState(true);
104
- const initializedRef = useRef(false);
105
74
  const setAuthState = useAuthStore.setState;
106
75
  const logger = useCallback((message, err) => {
107
76
  if (__DEV__) {
@@ -121,55 +90,29 @@ export const OxyProvider = ({
121
90
  logger
122
91
  });
123
92
 
124
- // Invalidate KeyManager cache on mount to prevent stale cache issues
125
- // useLayoutEffect runs synchronously after render but before paint, ensuring cache
126
- // invalidation happens before any async operations start
127
- useLayoutEffect(() => {
128
- if (Platform.OS !== 'web') {
129
- KeyManager.invalidateCache();
130
- }
131
- }, []);
132
-
133
- // Identity integrity check and auto-restore on startup
134
- // Skip on web platform - identity storage is only available on native platforms
93
+ // Initialize Identity Session Core
94
+ const [identityCore, setIdentityCore] = useState(null);
95
+ const [isCoreInitialized, setIsCoreInitialized] = useState(false);
135
96
  useEffect(() => {
136
- if (!storage || !isStorageReady) return;
137
- if (Platform.OS === 'web') return; // Identity operations are native-only
138
-
139
- const checkAndRestoreIdentity = async () => {
97
+ let mounted = true;
98
+ const initCore = async () => {
140
99
  try {
141
- // Cache was already invalidated synchronously above
142
-
143
- // Check if identity exists and verify integrity
144
- const hasIdentity = await KeyManager.hasIdentity();
145
- if (hasIdentity) {
146
- const isValid = await KeyManager.verifyIdentityIntegrity();
147
- if (!isValid) {
148
- // Try to restore from backup (cache will be invalidated inside restoreIdentityFromBackup)
149
- const restored = await KeyManager.restoreIdentityFromBackup();
150
- if (__DEV__) {
151
- logger(restored ? 'Identity restored from backup successfully' : 'Identity integrity check failed - user may need to restore from backup file');
152
- }
153
- } else {
154
- // Identity is valid - ensure backup is up to date
155
- await KeyManager.backupIdentity();
156
- }
157
- } else {
158
- // No identity - try to restore from backup (cache will be invalidated inside restoreIdentityFromBackup)
159
- const restored = await KeyManager.restoreIdentityFromBackup();
160
- if (restored && __DEV__) {
161
- logger('Identity restored from backup on startup');
162
- }
100
+ const core = await createIdentitySessionCore(oxyServices.getBaseURL() || baseURL);
101
+ if (mounted) {
102
+ setIdentityCore(core);
103
+ setIsCoreInitialized(true);
163
104
  }
164
105
  } catch (error) {
165
106
  if (__DEV__) {
166
- logger('Error during identity integrity check', error);
107
+ logger('Failed to initialize identity session core', error);
167
108
  }
168
- // Don't block app startup - user can recover with backup file
169
109
  }
170
110
  };
171
- checkAndRestoreIdentity();
172
- }, [storage, isStorageReady, logger]);
111
+ initCore();
112
+ return () => {
113
+ mounted = false;
114
+ };
115
+ }, [oxyServices, baseURL, logger]);
173
116
  const {
174
117
  currentLanguage,
175
118
  metadata: currentLanguageMetadata,
@@ -184,70 +127,112 @@ export const OxyProvider = ({
184
127
  logger
185
128
  });
186
129
  const queryClient = useQueryClient();
187
- const {
188
- sessions,
189
- activeSessionId,
190
- setActiveSessionId: setActiveSessionIdFromHook,
191
- updateSessions,
192
- switchSession,
193
- refreshSessions,
194
- clearSessionState,
195
- saveActiveSessionId,
196
- trackRemovedSession
197
- } = useSessionManagement({
198
- oxyServices,
199
- storage,
200
- storageKeyPrefix,
201
- loginSuccess,
202
- logoutStore,
203
- applyLanguagePreference,
204
- onAuthStateChange,
205
- onError,
206
- setAuthError: message => setAuthState({
207
- error: message
208
- }),
209
- logger,
210
- setTokenReady,
211
- queryClient
212
- });
213
- const {
214
- createIdentity,
215
- importIdentity: importIdentityBase,
216
- signIn,
217
- logout,
218
- logoutAll,
219
- hasIdentity,
220
- getPublicKey,
221
- isIdentitySynced,
222
- syncIdentity: syncIdentityBase
223
- } = useAuthOperations({
224
- oxyServices,
225
- storage,
226
- sessions,
227
- activeSessionId,
228
- setActiveSessionId: setActiveSessionIdFromHook,
229
- updateSessions,
230
- saveActiveSessionId,
231
- clearSessionState,
232
- switchSession,
233
- applyLanguagePreference,
234
- onAuthStateChange,
235
- onError,
236
- loginSuccess,
237
- loginFailure,
238
- logoutStore,
239
- setAuthState,
240
- setIdentitySynced,
241
- setSyncing,
242
- logger
243
- });
244
130
 
245
- // syncIdentity - TanStack Query handles offline mutations automatically
246
- const syncIdentity = useCallback(() => syncIdentityBase(), [syncIdentityBase]);
131
+ // Session state management using core
132
+ const [sessions, setSessions] = useState([]);
133
+ const [activeSessionId, setActiveSessionId] = useState(null);
134
+
135
+ // Load sessions from core
136
+ const loadSessionsFromCore = useCallback(async () => {
137
+ if (!identityCore || !isCoreInitialized) return;
138
+ try {
139
+ const coreSessions = await identityCore.getAllSessions();
140
+ const activeId = await identityCore.getActiveSessionId();
141
+
142
+ // Convert core sessions to ClientSession format
143
+ const clientSessions = coreSessions.map(s => ({
144
+ deviceInfo: s.deviceInfo,
145
+ sessionId: s.sessionId,
146
+ deviceId: s.deviceId,
147
+ userId: s.userId,
148
+ expiresAt: s.expiresAt,
149
+ lastActive: s.lastActive,
150
+ isCurrent: s.isCurrent || false
151
+ }));
152
+ setSessions(clientSessions);
153
+ setActiveSessionId(activeId);
154
+ } catch (error) {
155
+ if (__DEV__) {
156
+ logger('Failed to load sessions from core', error);
157
+ }
158
+ }
159
+ }, [identityCore, isCoreInitialized, logger]);
160
+
161
+ // Load sessions when core is initialized
162
+ useEffect(() => {
163
+ if (isCoreInitialized) {
164
+ loadSessionsFromCore();
165
+ }
166
+ }, [isCoreInitialized, loadSessionsFromCore]);
167
+
168
+ // Subscribe to core events
169
+ useEffect(() => {
170
+ if (!identityCore || !isCoreInitialized) return;
171
+ const unsubscribe = identityCore.subscribe(event => {
172
+ if (event.type === 'session:created' || event.type === 'session:refreshed' || event.type === 'session:invalidated' || event.type === 'session:all-invalidated') {
173
+ loadSessionsFromCore();
174
+ }
175
+ });
176
+ return unsubscribe;
177
+ }, [identityCore, isCoreInitialized, loadSessionsFromCore]);
178
+
179
+ // Identity operations using core
180
+ const hasIdentity = useCallback(async () => {
181
+ if (!identityCore || !isCoreInitialized) return false;
182
+ return await identityCore.hasIdentity();
183
+ }, [identityCore, isCoreInitialized]);
184
+ const getPublicKey = useCallback(async () => {
185
+ if (!identityCore || !isCoreInitialized) return null;
186
+ return await identityCore.getPublicKey();
187
+ }, [identityCore, isCoreInitialized]);
188
+ const isIdentitySynced = useCallback(async () => {
189
+ if (!storage) return false;
190
+ const synced = await storage.getItem('oxy_identity_synced');
191
+ return synced === 'true';
192
+ }, [storage]);
193
+ const createIdentity = useCallback(async username => {
194
+ if (!identityCore || !isCoreInitialized) {
195
+ throw new Error('Identity core not initialized');
196
+ }
197
+ setSyncing(true);
198
+ try {
199
+ // Create identity using core
200
+ const identity = await identityCore.createIdentity(username);
247
201
 
248
- // Wrapper for importIdentity to handle legacy calls gracefully
202
+ // Try to register with backend
203
+ let synced = false;
204
+ try {
205
+ const {
206
+ signature,
207
+ publicKey,
208
+ timestamp
209
+ } = await identityCore.createRegistrationSignature();
210
+ const result = await oxyServices.register(publicKey, signature, timestamp, username);
211
+ if (result?.user) {
212
+ loginSuccess(result.user);
213
+ synced = true;
214
+ await storage?.setItem('oxy_identity_synced', 'true');
215
+ setIdentitySynced(true);
216
+ }
217
+ } catch (error) {
218
+ // Registration failed (likely offline) - identity still created locally
219
+ if (__DEV__) {
220
+ logger('Failed to register identity with backend (offline mode)', error);
221
+ }
222
+ }
223
+ return {
224
+ synced
225
+ };
226
+ } finally {
227
+ setSyncing(false);
228
+ }
229
+ }, [identityCore, isCoreInitialized, oxyServices, storage, loginSuccess, setIdentitySynced, setSyncing, logger]);
249
230
  const importIdentity = useCallback(async (backupData, password) => {
250
- // Handle legacy calls with single string argument (old recovery phrase signature)
231
+ if (!identityCore || !isCoreInitialized) {
232
+ throw new Error('Identity core not initialized');
233
+ }
234
+
235
+ // Handle legacy calls with single string argument
251
236
  if (typeof backupData === 'string') {
252
237
  throw new Error('Recovery phrase import is no longer supported. Please use backup file import or QR code transfer instead.');
253
238
  }
@@ -256,90 +241,279 @@ export const OxyProvider = ({
256
241
  if (!password || typeof password !== 'string') {
257
242
  throw new Error('Password is required for backup file import.');
258
243
  }
259
- return importIdentityBase(backupData, password);
260
- }, [importIdentityBase]);
244
+ setSyncing(true);
245
+ try {
246
+ // Import identity using core
247
+ const identity = await identityCore.importIdentity(backupData, password);
261
248
 
262
- // Storage keys for transfer codes
263
- const TRANSFER_CODES_STORAGE_KEY = `${storageKeyPrefix}_transfer_codes`;
264
- const ACTIVE_TRANSFER_STORAGE_KEY = `${storageKeyPrefix}_active_transfer_id`;
249
+ // Try to register with backend if not already registered
250
+ let synced = false;
251
+ try {
252
+ const {
253
+ signature,
254
+ publicKey,
255
+ timestamp
256
+ } = await identityCore.createRegistrationSignature();
257
+ const result = await oxyServices.register(publicKey, signature, timestamp);
258
+ if (result?.user) {
259
+ loginSuccess(result.user);
260
+ synced = true;
261
+ await storage?.setItem('oxy_identity_synced', 'true');
262
+ setIdentitySynced(true);
263
+ }
264
+ } catch (error) {
265
+ // Check if user already exists (409 conflict)
266
+ if (error?.status === 409) {
267
+ // User already registered - try to sign in instead
268
+ try {
269
+ await signIn();
270
+ synced = true;
271
+ await storage?.setItem('oxy_identity_synced', 'true');
272
+ setIdentitySynced(true);
273
+ } catch (signInError) {
274
+ if (__DEV__) {
275
+ logger('Failed to sign in after import', signInError);
276
+ }
277
+ }
278
+ } else if (__DEV__) {
279
+ logger('Failed to register identity with backend (offline mode)', error);
280
+ }
281
+ }
282
+ return {
283
+ synced
284
+ };
285
+ } finally {
286
+ setSyncing(false);
287
+ }
288
+ }, [identityCore, isCoreInitialized, oxyServices, storage, loginSuccess, setIdentitySynced, setSyncing, logger]);
289
+ const syncIdentity = useCallback(async username => {
290
+ if (!identityCore || !isCoreInitialized) {
291
+ throw new Error('Identity core not initialized');
292
+ }
293
+ const publicKey = await identityCore.getPublicKey();
294
+ if (!publicKey) {
295
+ throw new Error('No identity found');
296
+ }
297
+ setSyncing(true);
298
+ try {
299
+ const {
300
+ signature,
301
+ publicKey: pk,
302
+ timestamp
303
+ } = await identityCore.createRegistrationSignature();
304
+ const result = await oxyServices.register(pk, signature, timestamp, username);
305
+ if (result?.user) {
306
+ loginSuccess(result.user);
307
+ await storage?.setItem('oxy_identity_synced', 'true');
308
+ setIdentitySynced(true);
309
+ return result.user;
310
+ }
311
+ throw new Error('Registration failed');
312
+ } catch (error) {
313
+ // Check if user already exists (409 conflict) - try to sign in
314
+ if (error?.status === 409) {
315
+ const user = await signIn();
316
+ await storage?.setItem('oxy_identity_synced', 'true');
317
+ setIdentitySynced(true);
318
+ return user;
319
+ }
320
+ throw error;
321
+ } finally {
322
+ setSyncing(false);
323
+ }
324
+ }, [identityCore, isCoreInitialized, oxyServices, storage, loginSuccess, setIdentitySynced, setSyncing]);
325
+ const signIn = useCallback(async deviceName => {
326
+ if (!identityCore || !isCoreInitialized) {
327
+ throw new Error('Identity core not initialized');
328
+ }
329
+ setAuthState({
330
+ isLoading: true
331
+ });
332
+ try {
333
+ // Create session using core
334
+ const session = await identityCore.createSession(deviceName);
265
335
 
266
- // Clear all account data when identity is lost (for accounts app)
267
- // In accounts app, identity = account, so losing identity means losing everything
268
- const clearAllAccountData = useCallback(async () => {
269
- if (__DEV__) {
270
- logger('Clearing all account data - identity changed or lost');
336
+ // Get user from OxyServices
337
+ const user = await oxyServices.getUserBySession(session.sessionId);
338
+ if (!user) {
339
+ throw new Error('Failed to get user data');
340
+ }
341
+
342
+ // Set token in OxyServices
343
+ if (session.accessToken) {
344
+ oxyServices.setTokens(session.accessToken, session.refreshToken || undefined);
345
+ }
346
+
347
+ // Update state
348
+ loginSuccess(user);
349
+ await loadSessionsFromCore();
350
+ onAuthStateChange?.(user);
351
+ await applyLanguagePreference(user);
352
+ setTokenReady(true);
353
+ return user;
354
+ } catch (error) {
355
+ const errorMessage = error instanceof Error ? error.message : String(error);
356
+ loginFailure(errorMessage);
357
+ throw error;
358
+ } finally {
359
+ setAuthState({
360
+ isLoading: false
361
+ });
271
362
  }
363
+ }, [identityCore, isCoreInitialized, oxyServices, loginSuccess, loginFailure, setAuthState, loadSessionsFromCore, onAuthStateChange, applyLanguagePreference, setTokenReady]);
364
+
365
+ // Session management functions
366
+ const clearSessionState = useCallback(async () => {
367
+ setSessions([]);
368
+ setActiveSessionId(null);
369
+ logoutStore();
370
+ onAuthStateChange?.(null);
272
371
 
273
- // Clear TanStack Query cache (in-memory)
372
+ // Clear TanStack Query cache
274
373
  queryClient.clear();
275
374
 
276
- // Clear persisted query cache
375
+ // Clear persisted query cache and session storage
277
376
  if (storage) {
278
377
  try {
279
378
  await clearQueryCache(storage);
379
+ await storage.removeItem(storageKeys.activeSessionId);
380
+ await storage.removeItem(storageKeys.sessionIds);
381
+ await storage.removeItem('oxy_identity_synced').catch(() => {});
280
382
  } catch (error) {
281
- logger('Failed to clear persisted query cache', error);
383
+ if (__DEV__) {
384
+ logger('Failed to clear session storage', error);
385
+ }
386
+ }
387
+ }
388
+ }, [logoutStore, onAuthStateChange, queryClient, storage, storageKeys, logger]);
389
+ const logout = useCallback(async targetSessionId => {
390
+ if (!identityCore || !isCoreInitialized) return;
391
+ try {
392
+ await identityCore.invalidateSession(targetSessionId);
393
+ await loadSessionsFromCore();
394
+ await clearSessionState();
395
+ setTokenReady(false);
396
+ } catch (error) {
397
+ if (__DEV__) {
398
+ logger('Failed to logout', error);
399
+ }
400
+ }
401
+ }, [identityCore, isCoreInitialized, loadSessionsFromCore, clearSessionState, setTokenReady, logger]);
402
+ const logoutAll = useCallback(async () => {
403
+ if (!identityCore || !isCoreInitialized) return;
404
+ try {
405
+ await identityCore.invalidateAllSessions();
406
+ await loadSessionsFromCore();
407
+ await clearSessionState();
408
+ setTokenReady(false);
409
+ } catch (error) {
410
+ if (__DEV__) {
411
+ logger('Failed to logout all', error);
412
+ }
413
+ }
414
+ }, [identityCore, isCoreInitialized, loadSessionsFromCore, clearSessionState, setTokenReady, logger]);
415
+ const switchSession = useCallback(async sessionId => {
416
+ if (!identityCore || !isCoreInitialized) {
417
+ throw new Error('Identity core not initialized');
418
+ }
419
+ try {
420
+ // Validate session
421
+ const validation = await oxyServices.validateSession(sessionId, {
422
+ useHeaderValidation: true
423
+ });
424
+ if (!validation?.valid || !validation.user) {
425
+ throw new Error('Session is invalid or expired');
426
+ }
427
+ const user = validation.user;
428
+
429
+ // Get token for this session
430
+ await oxyServices.getTokenBySession(sessionId);
431
+
432
+ // Update active session in core
433
+ const sessionManager = identityCore.getSessionManager();
434
+ await sessionManager.setActiveSessionId(sessionId);
435
+
436
+ // Update local state
437
+ setActiveSessionId(sessionId);
438
+ loginSuccess(user);
439
+ await applyLanguagePreference(user);
440
+ onAuthStateChange?.(user);
441
+ setTokenReady(true);
442
+ await loadSessionsFromCore();
443
+ return user;
444
+ } catch (error) {
445
+ if (isInvalidSessionError(error)) {
446
+ // Remove invalid session from list
447
+ setSessions(prev => prev.filter(s => s.sessionId !== sessionId));
448
+ if (sessionId === activeSessionId) {
449
+ await clearSessionState();
450
+ }
282
451
  }
452
+ throw error;
283
453
  }
454
+ }, [identityCore, isCoreInitialized, oxyServices, loginSuccess, applyLanguagePreference, onAuthStateChange, setTokenReady, loadSessionsFromCore, activeSessionId, clearSessionState]);
455
+ const refreshSessions = useCallback(async () => {
456
+ if (!identityCore || !isCoreInitialized || !activeSessionId) return;
457
+ try {
458
+ // Refresh current session
459
+ await identityCore.refreshSession();
284
460
 
285
- // Clear session state (sessions, activeSessionId, storage)
461
+ // Reload sessions from core
462
+ await loadSessionsFromCore();
463
+ } catch (error) {
464
+ if (isInvalidSessionError(error)) {
465
+ await clearSessionState();
466
+ } else if (__DEV__) {
467
+ logger('Failed to refresh sessions', error);
468
+ }
469
+ }
470
+ }, [identityCore, isCoreInitialized, activeSessionId, loadSessionsFromCore, clearSessionState, logger]);
471
+
472
+ // Device management functions
473
+ const getDeviceSessions = useCallback(async () => {
474
+ if (!activeSessionId) throw new Error('No active session');
475
+ return await oxyServices.getDeviceSessions(activeSessionId);
476
+ }, [activeSessionId, oxyServices]);
477
+ const logoutAllDeviceSessions = useCallback(async () => {
478
+ if (!activeSessionId) throw new Error('No active session');
479
+ await oxyServices.logoutAllDeviceSessions(activeSessionId);
286
480
  await clearSessionState();
481
+ }, [activeSessionId, oxyServices, clearSessionState]);
482
+ const updateDeviceName = useCallback(async deviceName => {
483
+ if (!identityCore || !isCoreInitialized || !activeSessionId) {
484
+ throw new Error('Identity core not initialized or no active session');
485
+ }
486
+ await oxyServices.updateDeviceName(activeSessionId, deviceName);
287
487
 
288
- // Clear identity sync state from storage
488
+ // Update device name in core
489
+ const deviceManager = identityCore.getDeviceManager();
490
+ await deviceManager.updateDeviceName(deviceName);
491
+ }, [identityCore, isCoreInitialized, activeSessionId, oxyServices]);
492
+ useTransferCodesPersistence(storageKeyPrefix);
493
+ const clearAllAccountData = useCallback(async () => {
494
+ if (__DEV__) logger('Clearing all account data - identity changed or lost');
495
+ queryClient.clear();
496
+ await clearSessionState();
289
497
  if (storage) {
290
498
  try {
499
+ await clearQueryCache(storage);
291
500
  await storage.removeItem('oxy_identity_synced');
292
501
  } catch (error) {
293
- logger('Failed to clear identity sync state', error);
502
+ logger('Failed to clear persisted data', error);
294
503
  }
295
504
  }
296
-
297
- // Reset auth store identity sync state
298
505
  useAuthStore.getState().setIdentitySynced(false);
299
506
  useAuthStore.getState().setSyncing(false);
300
-
301
- // Reset account store
302
507
  useAccountStore.getState().reset();
303
-
304
- // CRITICAL: Clear ALL transfer codes and active transfer state
305
- // This prevents transfer state from previous identity from lingering
306
- if (storage) {
307
- try {
308
- await storage.removeItem(TRANSFER_CODES_STORAGE_KEY);
309
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
310
-
311
- // Also clear Zustand transfer store
312
- useTransferStore.getState().clearAll();
313
- if (__DEV__) {
314
- logger('Cleared all transfer state');
315
- }
316
- } catch (error) {
317
- logger('Failed to clear transfer state', error);
318
- }
319
- }
320
-
321
- // Clear HTTP service cache
508
+ useTransferStore.getState().clearAll();
322
509
  oxyServices.clearCache();
510
+ identityCore?.getIdentityManager().invalidateCache();
511
+ }, [queryClient, storage, clearSessionState, logger, oxyServices, identityCore]);
323
512
 
324
- // Force KeyManager cache invalidation
325
- KeyManager.invalidateCache();
326
- }, [queryClient, storage, clearSessionState, logger, oxyServices, TRANSFER_CODES_STORAGE_KEY, ACTIVE_TRANSFER_STORAGE_KEY]);
327
-
328
- // Extract Zustand store functions early (before they're used in callbacks)
329
- const getAllPendingTransfersStore = useTransferStore(state => state.getAllPendingTransfers);
330
- const getActiveTransferIdStore = useTransferStore(state => state.getActiveTransferId);
331
- const storeTransferCodeStore = useTransferStore(state => state.storeTransferCode);
332
- const getTransferCodeStore = useTransferStore(state => state.getTransferCode);
333
- const updateTransferStateStore = useTransferStore(state => state.updateTransferState);
334
- const clearTransferCodeStore = useTransferStore(state => state.clearTransferCode);
335
-
336
- // Transfer code management functions (must be defined before deleteIdentityAndClearAccount)
337
- const getAllPendingTransfers = useCallback(() => {
338
- return getAllPendingTransfersStore();
339
- }, [getAllPendingTransfersStore]);
340
- const getActiveTransferId = useCallback(() => {
341
- return getActiveTransferIdStore();
342
- }, [getActiveTransferIdStore]);
513
+ // Get transfer store functions (used in deleteIdentityAndClearAccount)
514
+ const getAllPendingTransfers = useTransferStore(state => state.getAllPendingTransfers);
515
+ const getActiveTransferId = useTransferStore(state => state.getActiveTransferId);
516
+ const getTransferCode = useTransferStore(state => state.getTransferCode);
343
517
 
344
518
  // Delete identity and clear all account data
345
519
  // In accounts app, deleting identity means losing the account completely
@@ -361,659 +535,106 @@ export const OxyProvider = ({
361
535
  await clearAllAccountData();
362
536
 
363
537
  // Then delete the identity keys
364
- await KeyManager.deleteIdentity(skipBackup, force, userConfirmed);
365
- }, [clearAllAccountData, getAllPendingTransfers, getActiveTransferId]);
366
-
367
- // Network reconnect sync - TanStack Query automatically retries mutations on reconnect
368
- // We only need to sync identity if it's not synced
369
- useEffect(() => {
370
- if (!storage) return;
371
- let wasOffline = false;
372
- let checkTimeout = null;
373
- let lastReconnectionLog = 0;
374
- const RECONNECTION_LOG_DEBOUNCE_MS = 5000; // 5 seconds
375
-
376
- // Circuit breaker and exponential backoff state
377
- const stateRef = {
378
- consecutiveFailures: 0,
379
- currentInterval: 10000,
380
- // Start with 10 seconds
381
- baseInterval: 10000,
382
- // Base interval in milliseconds
383
- maxInterval: 60000,
384
- // Maximum interval (60 seconds)
385
- maxFailures: 5 // Circuit breaker threshold
386
- };
387
- const scheduleNextCheck = () => {
388
- if (checkTimeout) {
389
- clearTimeout(checkTimeout);
390
- }
391
- checkTimeout = setTimeout(() => {
392
- checkNetworkAndSync();
393
- }, stateRef.currentInterval);
394
- };
395
- const checkNetworkAndSync = async () => {
396
- try {
397
- // Try a lightweight health check to see if we're online
398
- await oxyServices.healthCheck().catch(() => {
399
- wasOffline = true;
400
- throw new Error('Health check failed');
401
- });
402
-
403
- // Health check succeeded - reset circuit breaker and backoff
404
- if (stateRef.consecutiveFailures > 0) {
405
- stateRef.consecutiveFailures = 0;
406
- stateRef.currentInterval = stateRef.baseInterval;
407
- }
408
-
409
- // If we were offline and now we're online, sync identity if needed
410
- if (wasOffline) {
411
- const now = Date.now();
412
- const timeSinceLastLog = now - lastReconnectionLog;
413
- if (timeSinceLastLog >= RECONNECTION_LOG_DEBOUNCE_MS) {
414
- logger('Network reconnected, checking identity sync...');
415
- lastReconnectionLog = now;
416
-
417
- // Sync identity first (if not synced)
418
- try {
419
- const hasIdentityValue = await hasIdentity();
420
- if (hasIdentityValue) {
421
- // Check sync status directly - sync if not explicitly 'true'
422
- // undefined = not synced yet, 'false' = explicitly not synced, 'true' = synced
423
- const syncStatus = await storage.getItem('oxy_identity_synced');
424
- if (syncStatus !== 'true') {
425
- await syncIdentity();
426
- }
427
- }
428
- } catch (syncError) {
429
- // Skip sync silently if username is required (expected when offline onboarding)
430
- if (syncError?.code === 'USERNAME_REQUIRED' || syncError?.message === 'USERNAME_REQUIRED') {
431
- if (__DEV__) {
432
- loggerUtil.debug('Sync skipped - username required', {
433
- component: 'OxyContext',
434
- method: 'checkNetworkAndSync'
435
- }, syncError);
436
- }
437
- // Don't log or show error - username will be set later
438
- } else if (!isTimeoutOrNetworkError(syncError)) {
439
- // Only log unexpected errors - timeouts/network issues are expected when offline
440
- logger('Error syncing identity on reconnect', syncError);
441
- } else if (__DEV__) {
442
- loggerUtil.debug('Identity sync timeout (expected when offline)', {
443
- component: 'OxyContext',
444
- method: 'checkNetworkAndSync'
445
- }, syncError);
446
- }
447
- }
448
-
449
- // Check for pending transfers that may have completed while offline
450
- // This is handled by useCheckPendingTransfers hook which runs automatically
451
- // when authenticated and online
452
- }
453
-
454
- // TanStack Query will automatically retry pending mutations
455
- // Reset flag immediately after processing (whether logged or not)
456
- wasOffline = false;
457
- }
458
- } catch (error) {
459
- // Network check failed - we're offline
460
- wasOffline = true;
461
-
462
- // Increment failure count and apply exponential backoff
463
- stateRef.consecutiveFailures++;
464
-
465
- // Calculate new interval with exponential backoff, capped at maxInterval
466
- const backoffMultiplier = Math.min(Math.pow(2, stateRef.consecutiveFailures - 1), stateRef.maxInterval / stateRef.baseInterval);
467
- stateRef.currentInterval = Math.min(stateRef.baseInterval * backoffMultiplier, stateRef.maxInterval);
468
-
469
- // If we hit the circuit breaker threshold, use max interval
470
- if (stateRef.consecutiveFailures >= stateRef.maxFailures) {
471
- stateRef.currentInterval = stateRef.maxInterval;
472
- }
473
- } finally {
474
- // Always schedule next check (will use updated interval)
475
- scheduleNextCheck();
476
- }
477
- };
478
-
479
- // Check immediately
480
- checkNetworkAndSync();
481
- return () => {
482
- if (checkTimeout) {
483
- clearTimeout(checkTimeout);
484
- }
485
- };
486
- }, [oxyServices, storage, syncIdentity, logger, hasIdentity]);
487
- const {
488
- getDeviceSessions,
489
- logoutAllDeviceSessions,
490
- updateDeviceName
491
- } = useDeviceManagement({
492
- oxyServices,
493
- activeSessionId,
494
- onError,
495
- clearSessionState,
496
- logger
497
- });
498
- const useFollowHook = loadUseFollowHook();
499
- const restoreSessionsFromStorage = useCallback(async () => {
500
- if (!storage) {
501
- return;
538
+ if (identityCore) {
539
+ await identityCore.deleteIdentity(skipBackup, force, userConfirmed);
540
+ } else {
541
+ throw new Error('Identity core not initialized');
502
542
  }
543
+ }, [clearAllAccountData, identityCore, getAllPendingTransfers, getActiveTransferId]);
544
+ const restoreSessionsFromStorage = useCallback(async () => {
545
+ if (!identityCore || !isCoreInitialized) return;
503
546
  setTokenReady(false);
504
547
  try {
505
- // CRITICAL: Get current identity's public key first
506
- // Only restore sessions that belong to this identity
507
- // Force cache invalidation to ensure we get the actual current identity
508
- KeyManager.invalidateCache();
509
- const currentPublicKey = await KeyManager.getPublicKey().catch(() => null);
510
- const storedSessionIdsJson = await storage.getItem(storageKeys.sessionIds);
511
- const storedSessionIds = storedSessionIdsJson ? JSON.parse(storedSessionIdsJson) : [];
512
- const storedActiveSessionId = await storage.getItem(storageKeys.activeSessionId);
513
-
514
- // If no identity exists, clear all sessions and return
515
- if (!currentPublicKey) {
516
- if (storedSessionIds.length > 0 || storedActiveSessionId) {
517
- if (__DEV__) {
518
- logger('No identity found - clearing all stored sessions to prevent cross-identity data leak');
519
- }
520
- await clearSessionState();
521
- }
548
+ await loadSessionsFromCore();
549
+ const activeId = await identityCore.getActiveSessionId();
550
+ if (!activeId) {
522
551
  setTokenReady(true);
523
552
  return;
524
553
  }
525
- if (__DEV__) {
526
- logger('Restoring sessions for identity', {
527
- publicKey: currentPublicKey.substring(0, 16) + '...',
528
- sessionCount: storedSessionIds.length
529
- });
530
- }
531
- const validSessions = [];
532
- const invalidSessionIds = []; // Track invalid sessions for batch removal
533
-
534
- if (storedSessionIds.length > 0) {
535
- for (const sessionId of storedSessionIds) {
554
+ try {
555
+ await switchSession(activeId);
556
+ } catch (switchError) {
557
+ if (isTimeoutOrNetworkError(switchError)) {
536
558
  try {
537
- const validation = await oxyServices.validateSession(sessionId, {
538
- useHeaderValidation: true
559
+ const validation = await oxyServices.validateSession(activeId, {
560
+ useHeaderValidation: false
539
561
  });
540
562
  if (validation?.valid && validation.user) {
541
- // CRITICAL: Verify session belongs to current identity
542
- // Compare session's publicKey to current identity's publicKey
543
- if (validation.user.publicKey !== currentPublicKey) {
544
- // Session belongs to different identity - skip it and log warning
545
- if (__DEV__) {
546
- logger('CRITICAL: Skipping session from different identity during restoration', {
547
- sessionPublicKey: validation.user.publicKey?.substring(0, 16) + '...',
548
- currentPublicKey: currentPublicKey.substring(0, 16) + '...',
549
- sessionId: sessionId.substring(0, 16) + '...'
550
- });
551
- }
552
- // Mark for batch removal
553
- invalidSessionIds.push(sessionId);
554
- continue;
555
- }
556
- const now = new Date();
557
- validSessions.push({
558
- sessionId,
559
- deviceId: '',
560
- expiresAt: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
561
- lastActive: now.toISOString(),
562
- userId: validation.user.id?.toString() ?? '',
563
- isCurrent: sessionId === storedActiveSessionId
564
- });
565
- }
566
- } catch (validationError) {
567
- // Silently handle expected errors (invalid sessions, timeouts, network issues) during restoration
568
- // Only log unexpected errors
569
- if (!isInvalidSessionError(validationError) && !isTimeoutOrNetworkError(validationError)) {
570
- logger('Session validation failed during init', validationError);
571
- } else if (__DEV__ && isTimeoutOrNetworkError(validationError)) {
572
- // Only log timeouts in dev mode for debugging
573
- loggerUtil.debug('Session validation timeout (expected when offline)', {
574
- component: 'OxyContext',
575
- method: 'restoreSessionsFromStorage'
576
- }, validationError);
577
- }
578
- }
579
- }
580
-
581
- // Batch remove invalid sessions from storage (performance optimization)
582
- if (invalidSessionIds.length > 0) {
583
- try {
584
- // Use Set for O(n) lookup instead of O(n²) with includes()
585
- const invalidSessionSet = new Set(invalidSessionIds);
586
- const updatedIds = storedSessionIds.filter(id => !invalidSessionSet.has(id));
587
- await storage.setItem(storageKeys.sessionIds, JSON.stringify(updatedIds));
588
- if (__DEV__) {
589
- logger('Removed invalid sessions from storage', {
590
- count: invalidSessionIds.length
591
- });
592
- }
593
- } catch (cleanupError) {
594
- // Ignore cleanup errors - will be cleaned on next restart
595
- if (__DEV__) {
596
- logger('Failed to remove invalid sessions from storage', cleanupError);
597
- }
598
- }
599
- }
600
- if (validSessions.length > 0) {
601
- updateSessions(validSessions, {
602
- merge: false
603
- });
604
- }
605
- }
606
- if (storedActiveSessionId) {
607
- try {
608
- await switchSession(storedActiveSessionId);
609
- } catch (switchError) {
610
- // Silently handle expected errors (invalid sessions, timeouts, network issues)
611
- if (isInvalidSessionError(switchError)) {
612
- await storage.removeItem(storageKeys.activeSessionId);
613
- updateSessions(validSessions.filter(session => session.sessionId !== storedActiveSessionId), {
614
- merge: false
615
- });
616
- // Don't log expected session errors during restoration
617
- } else if (isTimeoutOrNetworkError(switchError)) {
618
- // Timeout/network error - non-critical, don't block
619
- // However, if we have valid sessions, we should still set activeSessionId
620
- // so that isAuthenticated can be computed correctly
621
- if (validSessions.length > 0) {
622
- const matchingSession = validSessions.find(s => s.sessionId === storedActiveSessionId);
623
- if (matchingSession) {
624
- // Set active session even if validation timed out (offline scenario)
625
- setActiveSessionIdFromHook(storedActiveSessionId);
626
- // Try to get user from session if possible (might fail offline, but that's OK)
627
- try {
628
- const validation = await oxyServices.validateSession(storedActiveSessionId, {
629
- useHeaderValidation: false
630
- });
631
- if (validation?.valid && validation.user) {
632
- loginSuccess(validation.user);
633
- }
634
- } catch {
635
- // Ignore - we're offline, will sync when online
636
- }
637
- }
638
- }
639
- if (__DEV__) {
640
- loggerUtil.debug('Active session validation timeout (expected when offline)', {
641
- component: 'OxyContext',
642
- method: 'restoreSessionsFromStorage'
643
- }, switchError);
644
- }
645
- } else {
646
- // Only log unexpected errors
647
- logger('Active session validation error', switchError);
648
- }
649
- }
650
- } else if (validSessions.length > 0) {
651
- // No stored active session, but we have valid sessions - activate the first one
652
- const firstSession = validSessions[0];
653
- try {
654
- await switchSession(firstSession.sessionId);
655
- } catch (switchError) {
656
- // If switch fails, at least set the activeSessionId so UI can show sessions
657
- if (isTimeoutOrNetworkError(switchError)) {
658
- setActiveSessionIdFromHook(firstSession.sessionId);
659
- // Try to get user from session
660
- try {
661
- const validation = await oxyServices.validateSession(firstSession.sessionId, {
662
- useHeaderValidation: false
663
- });
664
- if (validation?.valid && validation.user) {
665
- loginSuccess(validation.user);
666
- }
667
- } catch {
668
- // Ignore - offline scenario
563
+ loginSuccess(validation.user);
669
564
  }
565
+ } catch {
566
+ // Offline - will sync when online
670
567
  }
568
+ } else if (isInvalidSessionError(switchError)) {
569
+ await identityCore.invalidateSession(activeId);
570
+ await loadSessionsFromCore();
671
571
  }
672
572
  }
673
573
  } catch (error) {
674
- if (__DEV__) {
675
- loggerUtil.error('Auth init error', error instanceof Error ? error : new Error(String(error)), {
676
- component: 'OxyContext',
677
- method: 'restoreSessionsFromStorage'
678
- });
679
- }
574
+ if (__DEV__) logger('Failed to restore sessions from storage', error);
680
575
  await clearSessionState();
681
576
  } finally {
682
577
  setTokenReady(true);
683
578
  }
684
- }, [clearSessionState, logger, oxyServices, storage, storageKeys.activeSessionId, storageKeys.sessionIds, switchSession, updateSessions, setActiveSessionIdFromHook, loginSuccess]);
579
+ }, [identityCore, isCoreInitialized, loadSessionsFromCore, switchSession, loginSuccess, clearSessionState, oxyServices, logger]);
580
+
581
+ // Restore sessions when core is initialized
685
582
  useEffect(() => {
686
- if (!storage || initializedRef.current) {
687
- return;
583
+ if (isCoreInitialized) {
584
+ void restoreSessionsFromStorage();
688
585
  }
689
- initializedRef.current = true;
690
- void restoreSessionsFromStorage();
691
- }, [restoreSessionsFromStorage, storage]);
586
+ }, [isCoreInitialized, restoreSessionsFromStorage]);
692
587
  const activeSession = activeSessionId ? sessions.find(session => session.sessionId === activeSessionId) : undefined;
693
588
  const currentDeviceId = activeSession?.deviceId ?? null;
694
589
 
695
590
  // Compute isAuthenticated from actual session state, not just auth store
696
591
  // This ensures UI shows correct state even if loginSuccess wasn't called during restoration
697
- const isAuthenticatedFromSessions = useMemo(() => {
698
- return !!(activeSessionId && sessions.length > 0 && user);
699
- }, [activeSessionId, sessions.length, user]);
700
-
701
- // Use session-based authentication state if auth store says not authenticated but we have sessions
702
- // This handles the case where sessions were restored but loginSuccess wasn't called
703
592
  const computedIsAuthenticated = useMemo(() => {
704
- return isAuthenticatedFromStore || isAuthenticatedFromSessions;
705
- }, [isAuthenticatedFromStore, isAuthenticatedFromSessions]);
706
-
707
- // Get userId from JWT token (MongoDB ObjectId) for socket room matching
708
- // user.id contains the MongoDB ObjectId, user.publicKey contains the cryptographic public key
593
+ return isAuthenticatedFromStore || !!(activeSessionId && sessions.length > 0 && user);
594
+ }, [isAuthenticatedFromStore, activeSessionId, sessions.length, user]);
709
595
  const userId = oxyServices.getCurrentUserId() || user?.id;
710
-
711
- // Use Zustand store for transfer state management
712
- // Storage keys are defined above (TRANSFER_CODES_STORAGE_KEY, ACTIVE_TRANSFER_STORAGE_KEY)
713
- const isRestored = useTransferStore(state => state.isRestored);
714
- const restoreFromStorage = useTransferStore(state => state.restoreFromStorage);
715
- const markRestored = useTransferStore(state => state.markRestored);
716
- const cleanupExpired = useTransferStore(state => state.cleanupExpired);
717
-
718
- // Load transfer codes from storage on startup (only once)
719
- useEffect(() => {
720
- if (!storage || !isStorageReady || isRestored) return;
721
- const loadTransferCodes = async () => {
722
- try {
723
- // Load transfer codes
724
- const storedCodes = await storage.getItem(TRANSFER_CODES_STORAGE_KEY);
725
- const storedActiveTransferId = await storage.getItem(ACTIVE_TRANSFER_STORAGE_KEY);
726
- const parsedCodes = storedCodes ? JSON.parse(storedCodes) : {};
727
- const activeTransferId = storedActiveTransferId || null;
728
-
729
- // Restore to Zustand store (store handles validation and expiration)
730
- restoreFromStorage(parsedCodes, activeTransferId);
731
- markRestored();
732
- if (__DEV__ && Object.keys(parsedCodes).length > 0) {
733
- logger('Restored transfer codes from storage', {
734
- count: Object.keys(parsedCodes).length,
735
- hasActiveTransfer: !!activeTransferId
736
- });
737
- }
738
- } catch (error) {
739
- if (__DEV__) {
740
- logger('Failed to load transfer codes from storage', error);
741
- }
742
- // Mark as restored even on error to prevent retries
743
- markRestored();
744
- }
745
- };
746
- loadTransferCodes();
747
- }, [storage, isStorageReady, isRestored, restoreFromStorage, markRestored, logger, storageKeyPrefix]);
748
-
749
- // Persist transfer codes to storage whenever store changes
750
- const {
751
- transferCodes,
752
- activeTransferId
753
- } = useTransferCodesForPersistence();
754
- useEffect(() => {
755
- if (!storage || !isStorageReady || !isRestored) return;
756
- const persistTransferCodes = async () => {
757
- try {
758
- await storage.setItem(TRANSFER_CODES_STORAGE_KEY, JSON.stringify(transferCodes));
759
- if (activeTransferId) {
760
- await storage.setItem(ACTIVE_TRANSFER_STORAGE_KEY, activeTransferId);
761
- } else {
762
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
763
- }
764
- } catch (error) {
765
- if (__DEV__) {
766
- logger('Failed to persist transfer codes', error);
767
- }
768
- }
769
- };
770
- persistTransferCodes();
771
- }, [transferCodes, activeTransferId, storage, isStorageReady, isRestored, logger]);
772
-
773
- // Cleanup expired transfer codes (every minute)
774
- useEffect(() => {
775
- const cleanup = setInterval(() => {
776
- cleanupExpired();
777
- }, 60000); // Check every minute
778
-
779
- return () => clearInterval(cleanup);
780
- }, [cleanupExpired]);
781
-
782
- // Transfer code management functions using Zustand store
783
- const storeTransferCode = useCallback(async (transferId, code, sourceDeviceId, publicKey) => {
784
- storeTransferCodeStore(transferId, code, sourceDeviceId, publicKey);
785
- if (__DEV__) {
786
- logger('Stored transfer code', {
787
- transferId,
788
- sourceDeviceId,
789
- publicKey: publicKey.substring(0, 16) + '...'
790
- });
791
- }
792
- }, [logger, storeTransferCodeStore]);
793
- const getTransferCode = useCallback(transferId => {
794
- return getTransferCodeStore(transferId);
795
- }, [getTransferCodeStore]);
796
- const updateTransferState = useCallback(async (transferId, state) => {
797
- updateTransferStateStore(transferId, state);
798
- if (__DEV__) {
799
- logger('Updated transfer state', {
800
- transferId,
801
- state
802
- });
803
- }
804
- }, [logger, updateTransferStateStore]);
805
- const clearTransferCode = useCallback(async transferId => {
806
- clearTransferCodeStore(transferId);
807
- if (__DEV__) {
808
- logger('Cleared transfer code', {
809
- transferId
810
- });
811
- }
812
- }, [logger, clearTransferCodeStore]);
813
- const refreshSessionsWithUser = useCallback(() => refreshSessions(userId), [refreshSessions, userId]);
814
- const handleSessionRemoved = useCallback(sessionId => {
815
- trackRemovedSession(sessionId);
816
- }, [trackRemovedSession]);
817
- const handleRemoteSignOut = useCallback(() => {
818
- toast.info('You have been signed out remotely.');
819
- logout().catch(remoteError => logger('Failed to process remote sign out', remoteError));
820
- }, [logger, logout]);
821
-
822
- // Check pending transfers when authenticated and online using TanStack Query
823
- // Check for pending transfers that may have completed while offline
824
- // Results are processed via socket events (onIdentityTransferComplete), so we don't need to process query results here
825
596
  useCheckPendingTransfers(oxyServices, computedIsAuthenticated);
826
- const handleIdentityTransferComplete = useCallback(async data => {
827
- try {
828
- logger('Received identity transfer complete notification', {
829
- transferId: data.transferId,
830
- sourceDeviceId: data.sourceDeviceId,
831
- currentDeviceId,
832
- hasActiveSession: activeSessionId !== null,
833
- publicKey: data.publicKey.substring(0, 16) + '...'
834
- });
835
- const storedTransfer = getTransferCode(data.transferId);
836
- if (!storedTransfer) {
837
- logger('Transfer code not found for transferId', {
838
- transferId: data.transferId
839
- });
840
- toast.error('Transfer verification failed: Code not found. Identity will not be deleted.');
841
- return;
842
- }
843
-
844
- // Verify publicKey matches first (most important check)
845
- const publicKeyMatches = data.publicKey === storedTransfer.publicKey;
846
- if (!publicKeyMatches) {
847
- logger('Public key mismatch for transfer', {
848
- transferId: data.transferId,
849
- receivedPublicKey: data.publicKey.substring(0, 16) + '...',
850
- storedPublicKey: storedTransfer.publicKey.substring(0, 16) + '...'
851
- });
852
- toast.error('Transfer verification failed: Public key mismatch. Identity will not be deleted.');
853
- return;
854
- }
855
-
856
- // Verify deviceId matches - very lenient since publicKey is the critical check
857
- // If publicKey matches, we allow deletion even if deviceId doesn't match exactly
858
- // This handles cases where deviceId might not be available or slightly different
859
- const deviceIdMatches =
860
- // Exact match
861
- data.sourceDeviceId && data.sourceDeviceId === currentDeviceId ||
862
- // Stored sourceDeviceId matches current deviceId
863
- storedTransfer.sourceDeviceId && storedTransfer.sourceDeviceId === currentDeviceId;
864
-
865
- // If publicKey matches, we're very lenient with deviceId - only warn but don't block
866
- if (!deviceIdMatches && publicKeyMatches) {
867
- logger('Device ID mismatch for transfer, but publicKey matches - proceeding with deletion', {
868
- transferId: data.transferId,
869
- receivedDeviceId: data.sourceDeviceId,
870
- storedDeviceId: storedTransfer.sourceDeviceId,
871
- currentDeviceId,
872
- hasActiveSession: activeSessionId !== null
873
- });
874
- // Proceed with deletion - publicKey match is the critical verification
875
- } else if (!deviceIdMatches && !publicKeyMatches) {
876
- // Both don't match - this is suspicious, block deletion
877
- logger('Device ID and publicKey mismatch for transfer', {
878
- transferId: data.transferId,
879
- receivedDeviceId: data.sourceDeviceId,
880
- currentDeviceId
881
- });
882
- toast.error('Transfer verification failed: Device and key mismatch. Identity will not be deleted.');
883
- return;
884
- }
885
-
886
- // Verify transfer code matches (if provided)
887
- // Transfer code is optional - if not provided, we still proceed if publicKey matches
888
- if (data.transferCode) {
889
- const codeMatches = data.transferCode.toUpperCase() === storedTransfer.code.toUpperCase();
890
- if (!codeMatches) {
891
- logger('Transfer code mismatch, but publicKey matches - proceeding with deletion', {
892
- transferId: data.transferId,
893
- receivedCode: data.transferCode,
894
- storedCode: storedTransfer.code.substring(0, 2) + '****'
895
- });
896
- // Don't block - publicKey match is sufficient, code mismatch might be due to user error
897
- // Log warning but proceed
898
- }
899
- }
900
-
901
- // Check if transfer is too old (safety timeout - 10 minutes)
902
- const transferAge = Date.now() - storedTransfer.timestamp;
903
- const tenMinutes = 10 * 60 * 1000;
904
- if (transferAge > tenMinutes) {
905
- logger('Transfer confirmation received too late', {
906
- transferId: data.transferId,
907
- age: transferAge,
908
- ageMinutes: Math.round(transferAge / 60000)
909
- });
910
- toast.error('Transfer verification failed: Confirmation received too late. Identity will not be deleted.');
911
- clearTransferCode(data.transferId);
912
- return;
913
- }
914
-
915
- // NOTE: Target device verification already happened server-side when notifyTransferComplete was called
916
- // The server verified that the target device is authenticated and has the matching public key
917
- // Additional client-side verification is not necessary and would require source device authentication
918
- // which may not be available. The existing checks (public key match, transfer code, device ID) are sufficient.
919
-
920
- logger('All transfer verifications passed, deleting identity from source device', {
921
- transferId: data.transferId,
922
- sourceDeviceId: data.sourceDeviceId,
923
- publicKey: data.publicKey.substring(0, 16) + '...'
924
- });
925
- try {
926
- // Verify identity still exists before deletion (safety check)
927
- const identityStillExists = await KeyManager.hasIdentity();
928
- if (!identityStillExists) {
929
- logger('Identity already deleted - skipping deletion', {
930
- transferId: data.transferId
931
- });
932
- await updateTransferState(data.transferId, 'completed');
933
- await clearTransferCode(data.transferId);
934
- return;
935
- }
936
- await deleteIdentityAndClearAccount(false, false, true);
937
-
938
- // Verify identity was actually deleted
939
- const identityDeleted = !(await KeyManager.hasIdentity());
940
- if (!identityDeleted) {
941
- logger('Identity deletion failed - identity still exists', {
942
- transferId: data.transferId
943
- });
944
- await updateTransferState(data.transferId, 'failed');
945
- throw new Error('Identity deletion failed - identity still exists');
946
- }
947
- await updateTransferState(data.transferId, 'completed');
948
- await clearTransferCode(data.transferId);
949
- logger('Identity successfully deleted and transfer code cleared', {
950
- transferId: data.transferId
951
- });
952
- toast.success('Identity successfully transferred and removed from this device');
953
- } catch (deleteError) {
954
- logger('Error during identity deletion', deleteError);
955
- await updateTransferState(data.transferId, 'failed');
956
- throw deleteError;
957
- }
958
- } catch (error) {
959
- logger('Failed to delete identity after transfer', error);
960
- toast.error(error?.message || 'Failed to remove identity from this device. Please try again manually from Security Settings.');
961
- }
962
- }, [deleteIdentityAndClearAccount, logger, getTransferCode, clearTransferCode, updateTransferState, currentDeviceId, activeSessionId, oxyServices]);
597
+ const {
598
+ handleIdentityTransferComplete
599
+ } = useIdentityTransfer({
600
+ identityCore,
601
+ currentDeviceId,
602
+ activeSessionId,
603
+ getPublicKey,
604
+ deleteIdentityAndClearAccount,
605
+ logger
606
+ });
963
607
  useSessionSocket({
964
608
  userId,
965
609
  activeSessionId,
966
610
  currentDeviceId,
967
- refreshSessions: refreshSessionsWithUser,
611
+ refreshSessions,
968
612
  logout,
969
613
  clearSessionState,
970
614
  baseURL: oxyServices.getBaseURL(),
971
615
  getAccessToken: () => oxyServices.getAccessToken(),
972
616
  getTransferCode: getTransferCode,
973
- onRemoteSignOut: handleRemoteSignOut,
974
- onSessionRemoved: handleSessionRemoved,
617
+ onRemoteSignOut: () => {
618
+ toast.info('You have been signed out remotely.');
619
+ logout().catch(err => logger('Failed to process remote sign out', err));
620
+ },
621
+ onSessionRemoved: sessionId => {
622
+ setSessions(prev => prev.filter(s => s.sessionId !== sessionId));
623
+ if (sessionId === activeSessionId) {
624
+ setActiveSessionId(null);
625
+ }
626
+ },
975
627
  onIdentityTransferComplete: handleIdentityTransferComplete
976
628
  });
977
- const switchSessionForContext = useCallback(async sessionId => {
978
- await switchSession(sessionId);
979
- }, [switchSession]);
980
-
981
- // Create showBottomSheet function that uses the global function
982
- const showBottomSheetForContext = useCallback(screenOrConfig => {
983
- globalShowBottomSheet(screenOrConfig);
984
- }, []);
985
-
986
- // Create openAvatarPicker function
987
- const openAvatarPicker = useCallback(() => {
988
- showBottomSheetForContext({
989
- screen: 'FileManagement',
990
- props: {
991
- selectMode: true,
992
- multiSelect: false,
993
- disabledMimeTypes: ['video/', 'audio/', 'application/pdf'],
994
- afterSelect: 'none',
995
- // Don't navigate away - stay on current screen
996
- onSelect: async file => {
997
- if (!file.contentType.startsWith('image/')) {
998
- toast.error(translate(currentLanguage, 'editProfile.toasts.selectImage') || 'Please select an image file');
999
- return;
1000
- }
1001
- try {
1002
- // Update file visibility to public for avatar
1003
- await updateAvatarVisibility(file.id, oxyServices, 'OxyContext');
1004
-
1005
- // Update user profile (handles query invalidation and accountStore update)
1006
- await updateProfileWithAvatar({
1007
- avatar: file.id
1008
- }, oxyServices, activeSessionId, queryClient, syncIdentity);
1009
- toast.success(translate(currentLanguage, 'editProfile.toasts.avatarUpdated') || 'Avatar updated');
1010
- } catch (e) {
1011
- toast.error(e.message || translate(currentLanguage, 'editProfile.toasts.updateAvatarFailed') || 'Failed to update avatar');
1012
- }
1013
- }
1014
- }
1015
- });
1016
- }, [oxyServices, currentLanguage, showBottomSheetForContext, activeSessionId, queryClient, syncIdentity]);
629
+ const {
630
+ openAvatarPicker
631
+ } = useAvatarPicker({
632
+ oxyServices,
633
+ currentLanguage,
634
+ activeSessionId,
635
+ queryClient,
636
+ syncIdentity
637
+ });
1017
638
  const contextValue = useMemo(() => ({
1018
639
  user,
1019
640
  sessions,
@@ -1036,21 +657,15 @@ export const OxyProvider = ({
1036
657
  isIdentitySynced,
1037
658
  syncIdentity,
1038
659
  deleteIdentityAndClearAccount,
1039
- storeTransferCode,
1040
- getTransferCode,
1041
- clearTransferCode,
1042
- getAllPendingTransfers,
1043
- getActiveTransferId,
1044
- updateTransferState,
1045
660
  identitySyncState: {
1046
661
  isSynced: isIdentitySyncedStore ?? true,
1047
662
  isSyncing: isSyncing ?? false
1048
663
  },
1049
664
  logout,
1050
665
  logoutAll,
1051
- switchSession: switchSessionForContext,
666
+ switchSession,
1052
667
  removeSession: logout,
1053
- refreshSessions: refreshSessionsWithUser,
668
+ refreshSessions,
1054
669
  setLanguage,
1055
670
  getDeviceSessions,
1056
671
  logoutAllDeviceSessions,
@@ -1058,10 +673,9 @@ export const OxyProvider = ({
1058
673
  clearSessionState,
1059
674
  clearAllAccountData,
1060
675
  oxyServices,
1061
- useFollow: useFollowHook,
1062
- showBottomSheet: showBottomSheetForContext,
676
+ showBottomSheet: globalShowBottomSheet,
1063
677
  openAvatarPicker
1064
- }), [activeSessionId, currentDeviceId, createIdentity, importIdentity, signIn, hasIdentity, getPublicKey, isIdentitySynced, syncIdentity, deleteIdentityAndClearAccount, storeTransferCode, getTransferCode, clearTransferCode, getAllPendingTransfers, getActiveTransferId, updateTransferState, isIdentitySyncedStore, isSyncing, currentLanguage, currentLanguageMetadata, currentLanguageName, currentNativeLanguageName, error, getDeviceSessions, computedIsAuthenticated, isLoading, logout, logoutAll, logoutAllDeviceSessions, oxyServices, refreshSessionsWithUser, sessions, setLanguage, switchSessionForContext, tokenReady, isStorageReady, updateDeviceName, clearAllAccountData, useFollowHook, user, showBottomSheetForContext, openAvatarPicker]);
678
+ }), [activeSessionId, currentDeviceId, createIdentity, importIdentity, signIn, hasIdentity, getPublicKey, isIdentitySynced, syncIdentity, deleteIdentityAndClearAccount, isIdentitySyncedStore, isSyncing, currentLanguage, currentLanguageMetadata, currentLanguageName, currentNativeLanguageName, error, getDeviceSessions, computedIsAuthenticated, isLoading, logout, logoutAll, logoutAllDeviceSessions, oxyServices, refreshSessions, sessions, setLanguage, switchSession, tokenReady, isStorageReady, updateDeviceName, clearAllAccountData, user, openAvatarPicker]);
1065
679
  return /*#__PURE__*/_jsx(OxyContext.Provider, {
1066
680
  value: contextValue,
1067
681
  children: children