@oxyhq/services 5.16.40 → 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.
- package/lib/commonjs/adapters/expo/crypto.js +56 -0
- package/lib/commonjs/adapters/expo/crypto.js.map +1 -0
- package/lib/commonjs/adapters/expo/fetch.js +30 -0
- package/lib/commonjs/adapters/expo/fetch.js.map +1 -0
- package/lib/commonjs/adapters/expo/index.js +48 -0
- package/lib/commonjs/adapters/expo/index.js.map +1 -0
- package/lib/commonjs/adapters/expo/storage.js +201 -0
- package/lib/commonjs/adapters/expo/storage.js.map +1 -0
- package/lib/commonjs/adapters/index.js +41 -0
- package/lib/commonjs/adapters/index.js.map +1 -0
- package/lib/commonjs/adapters/node/crypto.js +40 -0
- package/lib/commonjs/adapters/node/crypto.js.map +1 -0
- package/lib/commonjs/adapters/node/fetch.js +62 -0
- package/lib/commonjs/adapters/node/fetch.js.map +1 -0
- package/lib/commonjs/adapters/node/index.js +34 -0
- package/lib/commonjs/adapters/node/index.js.map +1 -0
- package/lib/commonjs/adapters/node/storage.js +163 -0
- package/lib/commonjs/adapters/node/storage.js.map +1 -0
- package/lib/commonjs/core/identity-session/DeviceManager.js +237 -0
- package/lib/commonjs/core/identity-session/DeviceManager.js.map +1 -0
- package/lib/commonjs/core/identity-session/INTEGRATION_GUIDE.md +287 -0
- package/lib/commonjs/core/identity-session/IdentityManager.js +400 -0
- package/lib/commonjs/core/identity-session/IdentityManager.js.map +1 -0
- package/lib/commonjs/core/identity-session/IdentitySessionCore.js +394 -0
- package/lib/commonjs/core/identity-session/IdentitySessionCore.js.map +1 -0
- package/lib/commonjs/core/identity-session/RefreshManager.js +137 -0
- package/lib/commonjs/core/identity-session/RefreshManager.js.map +1 -0
- package/lib/commonjs/core/identity-session/SessionManager.js +427 -0
- package/lib/commonjs/core/identity-session/SessionManager.js.map +1 -0
- package/lib/commonjs/core/identity-session/createIdentitySessionCore.js +24 -0
- package/lib/commonjs/core/identity-session/createIdentitySessionCore.js.map +1 -0
- package/lib/commonjs/core/identity-session/errors.js +176 -0
- package/lib/commonjs/core/identity-session/errors.js.map +1 -0
- package/lib/commonjs/core/identity-session/index.js +80 -0
- package/lib/commonjs/core/identity-session/index.js.map +1 -0
- package/lib/commonjs/core/identity-session/types.js +2 -0
- package/lib/commonjs/core/identity-session/types.js.map +1 -0
- package/lib/commonjs/core/index.js +2 -21
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/index.js +58 -8
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/models/interfaces.js +7 -0
- package/lib/commonjs/models/interfaces.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +434 -820
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useAvatarPicker.js +52 -0
- package/lib/commonjs/ui/hooks/useAvatarPicker.js.map +1 -0
- package/lib/commonjs/ui/hooks/useIdentityTransfer.js +125 -0
- package/lib/commonjs/ui/hooks/useIdentityTransfer.js.map +1 -0
- package/lib/commonjs/ui/hooks/useTransferCodesPersistence.js +81 -0
- package/lib/commonjs/ui/hooks/useTransferCodesPersistence.js.map +1 -0
- package/lib/commonjs/ui/screens/AccountCenterScreen.js +7 -2
- package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +12 -5
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +2 -2
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +6 -6
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/utils/sessionHelpers.js +7 -1
- package/lib/commonjs/ui/utils/sessionHelpers.js.map +1 -1
- package/lib/commonjs/utils/index.js +0 -7
- package/lib/commonjs/utils/index.js.map +1 -1
- package/lib/commonjs/utils/sessionUtils.js +8 -1
- package/lib/commonjs/utils/sessionUtils.js.map +1 -1
- package/lib/module/adapters/expo/crypto.js +51 -0
- package/lib/module/adapters/expo/crypto.js.map +1 -0
- package/lib/module/adapters/expo/fetch.js +26 -0
- package/lib/module/adapters/expo/fetch.js.map +1 -0
- package/lib/module/adapters/expo/index.js +45 -0
- package/lib/module/adapters/expo/index.js.map +1 -0
- package/lib/module/adapters/expo/storage.js +198 -0
- package/lib/module/adapters/expo/storage.js.map +1 -0
- package/lib/module/adapters/index.js +38 -0
- package/lib/module/adapters/index.js.map +1 -0
- package/lib/module/adapters/node/crypto.js +36 -0
- package/lib/module/adapters/node/crypto.js.map +1 -0
- package/lib/module/adapters/node/fetch.js +57 -0
- package/lib/module/adapters/node/fetch.js.map +1 -0
- package/lib/module/adapters/node/index.js +31 -0
- package/lib/module/adapters/node/index.js.map +1 -0
- package/lib/module/adapters/node/storage.js +159 -0
- package/lib/module/adapters/node/storage.js.map +1 -0
- package/lib/module/core/identity-session/DeviceManager.js +232 -0
- package/lib/module/core/identity-session/DeviceManager.js.map +1 -0
- package/lib/module/core/identity-session/INTEGRATION_GUIDE.md +287 -0
- package/lib/module/core/identity-session/IdentityManager.js +395 -0
- package/lib/module/core/identity-session/IdentityManager.js.map +1 -0
- package/lib/module/core/identity-session/IdentitySessionCore.js +390 -0
- package/lib/module/core/identity-session/IdentitySessionCore.js.map +1 -0
- package/lib/module/core/identity-session/RefreshManager.js +132 -0
- package/lib/module/core/identity-session/RefreshManager.js.map +1 -0
- package/lib/module/core/identity-session/SessionManager.js +422 -0
- package/lib/module/core/identity-session/SessionManager.js.map +1 -0
- package/lib/module/core/identity-session/createIdentitySessionCore.js +21 -0
- package/lib/module/core/identity-session/createIdentitySessionCore.js.map +1 -0
- package/lib/module/core/identity-session/errors.js +170 -0
- package/lib/module/core/identity-session/errors.js.map +1 -0
- package/lib/module/core/identity-session/index.js +17 -0
- package/lib/module/core/identity-session/index.js.map +1 -0
- package/lib/module/core/identity-session/types.js +2 -0
- package/lib/module/core/identity-session/types.js.map +1 -0
- package/lib/module/core/index.js +2 -3
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/index.js +12 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/models/interfaces.js +7 -0
- package/lib/module/models/interfaces.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +436 -822
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useAvatarPicker.js +48 -0
- package/lib/module/ui/hooks/useAvatarPicker.js.map +1 -0
- package/lib/module/ui/hooks/useIdentityTransfer.js +121 -0
- package/lib/module/ui/hooks/useIdentityTransfer.js.map +1 -0
- package/lib/module/ui/hooks/useTransferCodesPersistence.js +77 -0
- package/lib/module/ui/hooks/useTransferCodesPersistence.js.map +1 -0
- package/lib/module/ui/screens/AccountCenterScreen.js +7 -2
- package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +12 -5
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +2 -2
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +6 -6
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/utils/sessionHelpers.js +7 -1
- package/lib/module/ui/utils/sessionHelpers.js.map +1 -1
- package/lib/module/utils/index.js +2 -1
- package/lib/module/utils/index.js.map +1 -1
- package/lib/module/utils/sessionUtils.js +8 -1
- package/lib/module/utils/sessionUtils.js.map +1 -1
- package/lib/typescript/adapters/expo/crypto.d.ts +17 -0
- package/lib/typescript/adapters/expo/crypto.d.ts.map +1 -0
- package/lib/typescript/adapters/expo/fetch.d.ts +16 -0
- package/lib/typescript/adapters/expo/fetch.d.ts.map +1 -0
- package/lib/typescript/adapters/expo/index.d.ts +23 -0
- package/lib/typescript/adapters/expo/index.d.ts.map +1 -0
- package/lib/typescript/adapters/expo/storage.d.ts +23 -0
- package/lib/typescript/adapters/expo/storage.d.ts.map +1 -0
- package/lib/typescript/adapters/index.d.ts +13 -0
- package/lib/typescript/adapters/index.d.ts.map +1 -0
- package/lib/typescript/adapters/node/crypto.d.ts +17 -0
- package/lib/typescript/adapters/node/crypto.d.ts.map +1 -0
- package/lib/typescript/adapters/node/fetch.d.ts +16 -0
- package/lib/typescript/adapters/node/fetch.d.ts.map +1 -0
- package/lib/typescript/adapters/node/index.d.ts +23 -0
- package/lib/typescript/adapters/node/index.d.ts.map +1 -0
- package/lib/typescript/adapters/node/storage.d.ts +23 -0
- package/lib/typescript/adapters/node/storage.d.ts.map +1 -0
- package/lib/typescript/core/identity-session/DeviceManager.d.ts +64 -0
- package/lib/typescript/core/identity-session/DeviceManager.d.ts.map +1 -0
- package/lib/typescript/core/identity-session/IdentityManager.d.ts +88 -0
- package/lib/typescript/core/identity-session/IdentityManager.d.ts.map +1 -0
- package/lib/typescript/core/identity-session/IdentitySessionCore.d.ts +141 -0
- package/lib/typescript/core/identity-session/IdentitySessionCore.d.ts.map +1 -0
- package/lib/typescript/core/identity-session/RefreshManager.d.ts +36 -0
- package/lib/typescript/core/identity-session/RefreshManager.d.ts.map +1 -0
- package/lib/typescript/core/identity-session/SessionManager.d.ts +104 -0
- package/lib/typescript/core/identity-session/SessionManager.d.ts.map +1 -0
- package/lib/typescript/core/identity-session/createIdentitySessionCore.d.ts +11 -0
- package/lib/typescript/core/identity-session/createIdentitySessionCore.d.ts.map +1 -0
- package/lib/typescript/core/identity-session/errors.d.ts +63 -0
- package/lib/typescript/core/identity-session/errors.d.ts.map +1 -0
- package/lib/typescript/core/identity-session/index.d.ts +14 -0
- package/lib/typescript/core/identity-session/index.d.ts.map +1 -0
- package/lib/typescript/core/identity-session/types.d.ts +196 -0
- package/lib/typescript/core/identity-session/types.d.ts.map +1 -0
- package/lib/typescript/core/index.d.ts +1 -3
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/core/mixins/index.d.ts +2 -2
- package/lib/typescript/index.d.ts +3 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +5 -36
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/models/session.d.ts +3 -16
- package/lib/typescript/models/session.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +2 -25
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts +7 -8
- package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/mutations/useServicesMutations.d.ts +1 -1
- package/lib/typescript/ui/hooks/mutations/useServicesMutations.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts +5 -5
- package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useAvatarPicker.d.ts +18 -0
- package/lib/typescript/ui/hooks/useAvatarPicker.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/useIdentityTransfer.d.ts +24 -0
- package/lib/typescript/ui/hooks/useIdentityTransfer.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/useTransferCodesPersistence.d.ts +6 -0
- package/lib/typescript/ui/hooks/useTransferCodesPersistence.d.ts.map +1 -0
- package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/utils/sessionHelpers.d.ts +1 -0
- package/lib/typescript/ui/utils/sessionHelpers.d.ts.map +1 -1
- package/lib/typescript/utils/index.d.ts +0 -2
- package/lib/typescript/utils/index.d.ts.map +1 -1
- package/lib/typescript/utils/sessionUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/adapters/expo/crypto.ts +55 -0
- package/src/adapters/expo/fetch.ts +28 -0
- package/src/adapters/expo/index.ts +51 -0
- package/src/adapters/expo/storage.ts +228 -0
- package/src/adapters/index.ts +40 -0
- package/src/adapters/node/crypto.ts +39 -0
- package/src/adapters/node/fetch.ts +59 -0
- package/src/adapters/node/index.ts +37 -0
- package/src/adapters/node/storage.ts +170 -0
- package/src/core/identity-session/DeviceManager.ts +273 -0
- package/src/core/identity-session/INTEGRATION_GUIDE.md +287 -0
- package/src/core/identity-session/IdentityManager.ts +474 -0
- package/src/core/identity-session/IdentitySessionCore.ts +464 -0
- package/src/core/identity-session/RefreshManager.ts +189 -0
- package/src/core/identity-session/SessionManager.ts +500 -0
- package/src/core/identity-session/createIdentitySessionCore.ts +19 -0
- package/src/core/identity-session/errors.ts +197 -0
- package/src/core/identity-session/index.ts +15 -0
- package/src/core/identity-session/types.ts +188 -0
- package/src/core/index.ts +3 -4
- package/src/index.ts +28 -3
- package/src/models/interfaces.ts +12 -39
- package/src/models/session.ts +6 -16
- package/src/ui/context/OxyContext.tsx +442 -871
- package/src/ui/hooks/auth/index.ts +1 -0
- package/src/ui/hooks/useAvatarPicker.ts +62 -0
- package/src/ui/hooks/useIdentityTransfer.ts +135 -0
- package/src/ui/hooks/useTransferCodesPersistence.ts +80 -0
- package/src/ui/screens/AccountCenterScreen.tsx +7 -2
- package/src/ui/screens/AccountSettingsScreen.tsx +15 -8
- package/src/ui/screens/AccountSwitcherScreen.tsx +2 -2
- package/src/ui/screens/ProfileScreen.tsx +10 -10
- package/src/ui/utils/sessionHelpers.ts +7 -0
- package/src/utils/index.ts +1 -2
- package/src/utils/sessionUtils.ts +8 -0
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +0 -732
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +0 -1
- package/lib/commonjs/ui/context/hooks/useDeviceManagement.js +0 -73
- package/lib/commonjs/ui/context/hooks/useDeviceManagement.js.map +0 -1
- package/lib/commonjs/ui/hooks/useDeviceManagement.js +0 -73
- package/lib/commonjs/ui/hooks/useDeviceManagement.js.map +0 -1
- package/lib/commonjs/ui/hooks/useSessionManagement.js +0 -281
- package/lib/commonjs/ui/hooks/useSessionManagement.js.map +0 -1
- package/lib/commonjs/utils/deviceManager.js +0 -177
- package/lib/commonjs/utils/deviceManager.js.map +0 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +0 -726
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +0 -1
- package/lib/module/ui/context/hooks/useDeviceManagement.js +0 -68
- package/lib/module/ui/context/hooks/useDeviceManagement.js.map +0 -1
- package/lib/module/ui/hooks/useDeviceManagement.js +0 -68
- package/lib/module/ui/hooks/useDeviceManagement.js.map +0 -1
- package/lib/module/ui/hooks/useSessionManagement.js +0 -276
- package/lib/module/ui/hooks/useSessionManagement.js.map +0 -1
- package/lib/module/utils/deviceManager.js +0 -171
- package/lib/module/utils/deviceManager.js.map +0 -1
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts +0 -59
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +0 -1
- package/lib/typescript/ui/context/hooks/useDeviceManagement.d.ts +0 -27
- package/lib/typescript/ui/context/hooks/useDeviceManagement.d.ts.map +0 -1
- package/lib/typescript/ui/hooks/useDeviceManagement.d.ts +0 -27
- package/lib/typescript/ui/hooks/useDeviceManagement.d.ts.map +0 -1
- package/lib/typescript/ui/hooks/useSessionManagement.d.ts +0 -41
- package/lib/typescript/ui/hooks/useSessionManagement.d.ts.map +0 -1
- package/lib/typescript/utils/deviceManager.d.ts +0 -66
- package/lib/typescript/utils/deviceManager.d.ts.map +0 -1
- package/src/ui/context/hooks/useAuthOperations.ts +0 -801
- package/src/ui/context/hooks/useDeviceManagement.ts +0 -108
- package/src/ui/hooks/useDeviceManagement.ts +0 -108
- package/src/ui/hooks/useSessionManagement.ts +0 -401
- package/src/utils/deviceManager.ts +0 -198
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { createContext, useCallback, useContext, useEffect,
|
|
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 {
|
|
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 {
|
|
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
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const checkAndRestoreIdentity = async () => {
|
|
97
|
+
let mounted = true;
|
|
98
|
+
const initCore = async () => {
|
|
140
99
|
try {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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('
|
|
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
|
-
|
|
172
|
-
|
|
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
|
-
//
|
|
246
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
260
|
-
|
|
244
|
+
setSyncing(true);
|
|
245
|
+
try {
|
|
246
|
+
// Import identity using core
|
|
247
|
+
const identity = await identityCore.importIdentity(backupData, password);
|
|
261
248
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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(
|
|
538
|
-
useHeaderValidation:
|
|
559
|
+
const validation = await oxyServices.validateSession(activeId, {
|
|
560
|
+
useHeaderValidation: false
|
|
539
561
|
});
|
|
540
562
|
if (validation?.valid && validation.user) {
|
|
541
|
-
|
|
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
|
-
}, [
|
|
579
|
+
}, [identityCore, isCoreInitialized, loadSessionsFromCore, switchSession, loginSuccess, clearSessionState, oxyServices, logger]);
|
|
580
|
+
|
|
581
|
+
// Restore sessions when core is initialized
|
|
685
582
|
useEffect(() => {
|
|
686
|
-
if (
|
|
687
|
-
|
|
583
|
+
if (isCoreInitialized) {
|
|
584
|
+
void restoreSessionsFromStorage();
|
|
688
585
|
}
|
|
689
|
-
|
|
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 ||
|
|
705
|
-
}, [isAuthenticatedFromStore,
|
|
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
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|
|
611
|
+
refreshSessions,
|
|
968
612
|
logout,
|
|
969
613
|
clearSessionState,
|
|
970
614
|
baseURL: oxyServices.getBaseURL(),
|
|
971
615
|
getAccessToken: () => oxyServices.getAccessToken(),
|
|
972
616
|
getTransferCode: getTransferCode,
|
|
973
|
-
onRemoteSignOut:
|
|
974
|
-
|
|
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
|
|
978
|
-
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
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
|
|
666
|
+
switchSession,
|
|
1052
667
|
removeSession: logout,
|
|
1053
|
-
refreshSessions
|
|
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
|
-
|
|
1062
|
-
showBottomSheet: showBottomSheetForContext,
|
|
676
|
+
showBottomSheet: globalShowBottomSheet,
|
|
1063
677
|
openAvatarPicker
|
|
1064
|
-
}), [activeSessionId, currentDeviceId, createIdentity, importIdentity, signIn, hasIdentity, getPublicKey, isIdentitySynced, syncIdentity, deleteIdentityAndClearAccount,
|
|
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
|