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