@oxyhq/services 5.13.0 → 5.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -0
- package/lib/commonjs/core/HttpClient.js +238 -0
- package/lib/commonjs/core/HttpClient.js.map +1 -0
- package/lib/commonjs/core/OxyServices.js +530 -332
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/core/RequestManager.js +199 -0
- package/lib/commonjs/core/RequestManager.js.map +1 -0
- package/lib/commonjs/core/index.js +38 -1
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/i18n/index.js +37 -1
- package/lib/commonjs/i18n/index.js.map +1 -1
- package/lib/commonjs/i18n/locales/ar-SA.json +128 -0
- package/lib/commonjs/i18n/locales/ca-ES.json +128 -0
- package/lib/commonjs/i18n/locales/de-DE.json +128 -0
- package/lib/commonjs/i18n/locales/en-US.json +85 -12
- package/lib/commonjs/i18n/locales/es-ES.json +58 -6
- package/lib/commonjs/i18n/locales/fr-FR.json +128 -0
- package/lib/commonjs/i18n/locales/it-IT.json +128 -0
- package/lib/commonjs/i18n/locales/ja-JP.json +127 -0
- package/lib/commonjs/i18n/locales/ko-KR.json +128 -0
- package/lib/commonjs/i18n/locales/pt-PT.json +128 -0
- package/lib/commonjs/i18n/locales/zh-CN.json +128 -0
- package/lib/commonjs/index.js +36 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/Avatar.js +94 -27
- package/lib/commonjs/ui/components/Avatar.js.map +1 -1
- package/lib/commonjs/ui/components/FollowButton.js +1 -0
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/FontLoader.js +22 -42
- package/lib/commonjs/ui/components/FontLoader.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +5 -8
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/StepBasedScreen.js +64 -44
- package/lib/commonjs/ui/components/StepBasedScreen.js.map +1 -1
- package/lib/commonjs/ui/components/internal/GroupedPillButtons.js +14 -35
- package/lib/commonjs/ui/components/internal/GroupedPillButtons.js.map +1 -1
- package/lib/commonjs/ui/components/internal/PinInput.js +2 -2
- package/lib/commonjs/ui/components/internal/PinInput.js.map +1 -1
- package/lib/commonjs/ui/components/internal/TextField.js +13 -8
- package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +443 -371
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionSocket.js +80 -22
- package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/commonjs/ui/index.js +4 -1
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +32 -2
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +101 -59
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +3 -2
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +75 -117
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +43 -50
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignUpScreen.js +14 -16
- package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +188 -142
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +10 -10
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +2 -4
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/RecoverRequestStep.js +45 -25
- package/lib/commonjs/ui/screens/steps/RecoverRequestStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/RecoverResetPasswordStep.js +88 -53
- package/lib/commonjs/ui/screens/steps/RecoverResetPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/RecoverSuccessStep.js +79 -58
- package/lib/commonjs/ui/screens/steps/RecoverSuccessStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/RecoverVerifyStep.js +61 -52
- package/lib/commonjs/ui/screens/steps/RecoverVerifyStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js +218 -39
- package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInTotpStep.js +77 -50
- package/lib/commonjs/ui/screens/steps/SignInTotpStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js +424 -71
- package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignUpIdentityStep.js +55 -30
- package/lib/commonjs/ui/screens/steps/SignUpIdentityStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignUpSecurityStep.js +64 -46
- package/lib/commonjs/ui/screens/steps/SignUpSecurityStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignUpSummaryStep.js +84 -146
- package/lib/commonjs/ui/screens/steps/SignUpSummaryStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignUpWelcomeStep.js +113 -34
- package/lib/commonjs/ui/screens/steps/SignUpWelcomeStep.js.map +1 -1
- package/lib/commonjs/ui/stores/accountStore.js +237 -0
- package/lib/commonjs/ui/stores/accountStore.js.map +1 -0
- package/lib/commonjs/ui/stores/authStore.js +17 -20
- package/lib/commonjs/ui/stores/authStore.js.map +1 -1
- package/lib/commonjs/ui/styles/authStyles.js +16 -8
- package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
- package/lib/commonjs/ui/styles/index.js +11 -0
- package/lib/commonjs/ui/styles/index.js.map +1 -1
- package/lib/commonjs/ui/styles/spacing.js +51 -0
- package/lib/commonjs/ui/styles/spacing.js.map +1 -0
- package/lib/commonjs/utils/asyncUtils.js +9 -22
- package/lib/commonjs/utils/asyncUtils.js.map +1 -1
- package/lib/commonjs/utils/cache.js +259 -0
- package/lib/commonjs/utils/cache.js.map +1 -0
- package/lib/commonjs/utils/index.js +99 -0
- package/lib/commonjs/utils/index.js.map +1 -1
- package/lib/commonjs/utils/languageUtils.js +159 -0
- package/lib/commonjs/utils/languageUtils.js.map +1 -0
- package/lib/commonjs/utils/requestUtils.js +217 -0
- package/lib/commonjs/utils/requestUtils.js.map +1 -0
- package/lib/commonjs/utils/sessionUtils.js +191 -0
- package/lib/commonjs/utils/sessionUtils.js.map +1 -0
- package/lib/commonjs/utils/validationUtils.js +1 -1
- package/lib/module/core/HttpClient.js +232 -0
- package/lib/module/core/HttpClient.js.map +1 -0
- package/lib/module/core/OxyServices.js +528 -326
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/core/RequestManager.js +194 -0
- package/lib/module/core/RequestManager.js.map +1 -0
- package/lib/module/core/index.js +2 -0
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/i18n/index.js +37 -1
- package/lib/module/i18n/index.js.map +1 -1
- package/lib/module/i18n/locales/ar-SA.json +128 -0
- package/lib/module/i18n/locales/ca-ES.json +128 -0
- package/lib/module/i18n/locales/de-DE.json +128 -0
- package/lib/module/i18n/locales/en-US.json +85 -12
- package/lib/module/i18n/locales/es-ES.json +58 -6
- package/lib/module/i18n/locales/fr-FR.json +128 -0
- package/lib/module/i18n/locales/it-IT.json +128 -0
- package/lib/module/i18n/locales/ja-JP.json +127 -0
- package/lib/module/i18n/locales/ko-KR.json +128 -0
- package/lib/module/i18n/locales/pt-PT.json +128 -0
- package/lib/module/i18n/locales/zh-CN.json +128 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/Avatar.js +94 -27
- package/lib/module/ui/components/Avatar.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +1 -0
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/FontLoader.js +23 -43
- package/lib/module/ui/components/FontLoader.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +6 -8
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/StepBasedScreen.js +65 -45
- package/lib/module/ui/components/StepBasedScreen.js.map +1 -1
- package/lib/module/ui/components/internal/GroupedPillButtons.js +14 -35
- package/lib/module/ui/components/internal/GroupedPillButtons.js.map +1 -1
- package/lib/module/ui/components/internal/PinInput.js +2 -2
- package/lib/module/ui/components/internal/PinInput.js.map +1 -1
- package/lib/module/ui/components/internal/TextField.js +13 -8
- package/lib/module/ui/components/internal/TextField.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +442 -370
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useSessionSocket.js +80 -22
- package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/module/ui/index.js +4 -2
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +33 -2
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +102 -60
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +3 -2
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/LanguageSelectorScreen.js +73 -117
- package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +44 -51
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/SignUpScreen.js +14 -16
- package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +187 -143
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/module/ui/screens/internal/SignInPasswordStep.js +10 -10
- package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/internal/SignInUsernameStep.js +2 -4
- package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/module/ui/screens/steps/RecoverRequestStep.js +45 -25
- package/lib/module/ui/screens/steps/RecoverRequestStep.js.map +1 -1
- package/lib/module/ui/screens/steps/RecoverResetPasswordStep.js +89 -54
- package/lib/module/ui/screens/steps/RecoverResetPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/steps/RecoverSuccessStep.js +80 -59
- package/lib/module/ui/screens/steps/RecoverSuccessStep.js.map +1 -1
- package/lib/module/ui/screens/steps/RecoverVerifyStep.js +62 -53
- package/lib/module/ui/screens/steps/RecoverVerifyStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInPasswordStep.js +219 -40
- package/lib/module/ui/screens/steps/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInTotpStep.js +78 -51
- package/lib/module/ui/screens/steps/SignInTotpStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInUsernameStep.js +426 -73
- package/lib/module/ui/screens/steps/SignInUsernameStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignUpIdentityStep.js +55 -30
- package/lib/module/ui/screens/steps/SignUpIdentityStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignUpSecurityStep.js +65 -47
- package/lib/module/ui/screens/steps/SignUpSecurityStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignUpSummaryStep.js +84 -146
- package/lib/module/ui/screens/steps/SignUpSummaryStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignUpWelcomeStep.js +114 -35
- package/lib/module/ui/screens/steps/SignUpWelcomeStep.js.map +1 -1
- package/lib/module/ui/stores/accountStore.js +229 -0
- package/lib/module/ui/stores/accountStore.js.map +1 -0
- package/lib/module/ui/stores/authStore.js +17 -20
- package/lib/module/ui/stores/authStore.js.map +1 -1
- package/lib/module/ui/styles/authStyles.js +16 -8
- package/lib/module/ui/styles/authStyles.js.map +1 -1
- package/lib/module/ui/styles/index.js +1 -0
- package/lib/module/ui/styles/index.js.map +1 -1
- package/lib/module/ui/styles/spacing.js +48 -0
- package/lib/module/ui/styles/spacing.js.map +1 -0
- package/lib/module/utils/asyncUtils.js +10 -22
- package/lib/module/utils/asyncUtils.js.map +1 -1
- package/lib/module/utils/cache.js +250 -0
- package/lib/module/utils/cache.js.map +1 -0
- package/lib/module/utils/index.js +7 -0
- package/lib/module/utils/index.js.map +1 -1
- package/lib/module/utils/languageUtils.js +151 -0
- package/lib/module/utils/languageUtils.js.map +1 -0
- package/lib/module/utils/requestUtils.js +210 -0
- package/lib/module/utils/requestUtils.js.map +1 -0
- package/lib/module/utils/sessionUtils.js +180 -0
- package/lib/module/utils/sessionUtils.js.map +1 -0
- package/lib/module/utils/validationUtils.js +1 -1
- package/lib/typescript/core/HttpClient.d.ts +64 -0
- package/lib/typescript/core/HttpClient.d.ts.map +1 -0
- package/lib/typescript/core/OxyServices.d.ts +86 -73
- package/lib/typescript/core/OxyServices.d.ts.map +1 -1
- package/lib/typescript/core/RequestManager.d.ts +67 -0
- package/lib/typescript/core/RequestManager.d.ts.map +1 -0
- package/lib/typescript/core/index.d.ts +2 -0
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/i18n/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +15 -0
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/models/session.d.ts +1 -0
- package/lib/typescript/models/session.d.ts.map +1 -1
- package/lib/typescript/ui/components/Avatar.d.ts +6 -7
- package/lib/typescript/ui/components/Avatar.d.ts.map +1 -1
- package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/ui/components/FontLoader.d.ts +3 -3
- package/lib/typescript/ui/components/FontLoader.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts +2 -2
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/components/StepBasedScreen.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +5 -0
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
- package/lib/typescript/ui/index.d.ts +2 -2
- package/lib/typescript/ui/index.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +3 -3
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/RecoverRequestStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/RecoverResetPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/RecoverSuccessStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/RecoverVerifyStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts +2 -0
- package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInTotpStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignUpIdentityStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignUpSecurityStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignUpSummaryStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignUpWelcomeStep.d.ts.map +1 -1
- package/lib/typescript/ui/stores/accountStore.d.ts +34 -0
- package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -0
- package/lib/typescript/ui/stores/authStore.d.ts +7 -3
- package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
- package/lib/typescript/ui/styles/authStyles.d.ts +19 -2
- package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -1
- package/lib/typescript/ui/styles/index.d.ts +1 -0
- package/lib/typescript/ui/styles/index.d.ts.map +1 -1
- package/lib/typescript/ui/styles/spacing.d.ts +43 -0
- package/lib/typescript/ui/styles/spacing.d.ts.map +1 -0
- package/lib/typescript/utils/asyncUtils.d.ts +2 -0
- package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
- package/lib/typescript/utils/cache.d.ts +128 -0
- package/lib/typescript/utils/cache.d.ts.map +1 -0
- package/lib/typescript/utils/index.d.ts +4 -0
- package/lib/typescript/utils/index.d.ts.map +1 -1
- package/lib/typescript/utils/languageUtils.d.ts +38 -0
- package/lib/typescript/utils/languageUtils.d.ts.map +1 -0
- package/lib/typescript/utils/requestUtils.d.ts +122 -0
- package/lib/typescript/utils/requestUtils.d.ts.map +1 -0
- package/lib/typescript/utils/sessionUtils.d.ts +55 -0
- package/lib/typescript/utils/sessionUtils.d.ts.map +1 -0
- package/lib/typescript/utils/validationUtils.d.ts +1 -1
- package/package.json +1 -1
- package/src/core/HttpClient.ts +277 -0
- package/src/core/OxyServices.ts +461 -352
- package/src/core/RequestManager.ts +240 -0
- package/src/core/index.ts +10 -0
- package/src/i18n/index.ts +36 -0
- package/src/i18n/locales/ar-SA.json +128 -0
- package/src/i18n/locales/ca-ES.json +128 -0
- package/src/i18n/locales/de-DE.json +128 -0
- package/src/i18n/locales/en-US.json +85 -12
- package/src/i18n/locales/es-ES.json +58 -6
- package/src/i18n/locales/fr-FR.json +128 -0
- package/src/i18n/locales/it-IT.json +128 -0
- package/src/i18n/locales/ja-JP.json +127 -0
- package/src/i18n/locales/ko-KR.json +128 -0
- package/src/i18n/locales/pt-PT.json +128 -0
- package/src/i18n/locales/zh-CN.json +128 -0
- package/src/index.ts +10 -0
- package/src/models/interfaces.ts +19 -0
- package/src/models/session.ts +1 -1
- package/src/ui/components/Avatar.tsx +151 -35
- package/src/ui/components/FollowButton.tsx +1 -0
- package/src/ui/components/FontLoader.tsx +17 -37
- package/src/ui/components/OxyProvider.tsx +14 -13
- package/src/ui/components/StepBasedScreen.tsx +66 -43
- package/src/ui/components/internal/GroupedPillButtons.tsx +15 -31
- package/src/ui/components/internal/PinInput.tsx +2 -2
- package/src/ui/components/internal/TextField.tsx +7 -6
- package/src/ui/context/OxyContext.tsx +441 -326
- package/src/ui/hooks/useSessionSocket.ts +72 -18
- package/src/ui/index.ts +4 -1
- package/src/ui/screens/AccountSettingsScreen.tsx +34 -2
- package/src/ui/screens/AccountSwitcherScreen.tsx +102 -68
- package/src/ui/screens/FileManagementScreen.tsx +16 -16
- package/src/ui/screens/LanguageSelectorScreen.tsx +86 -143
- package/src/ui/screens/SignInScreen.tsx +59 -43
- package/src/ui/screens/SignUpScreen.tsx +14 -15
- package/src/ui/screens/WelcomeNewUserScreen.tsx +153 -105
- package/src/ui/screens/internal/SignInPasswordStep.tsx +4 -6
- package/src/ui/screens/internal/SignInUsernameStep.tsx +1 -1
- package/src/ui/screens/steps/RecoverRequestStep.tsx +34 -24
- package/src/ui/screens/steps/RecoverResetPasswordStep.tsx +65 -36
- package/src/ui/screens/steps/RecoverSuccessStep.tsx +71 -47
- package/src/ui/screens/steps/RecoverVerifyStep.tsx +60 -50
- package/src/ui/screens/steps/SignInPasswordStep.tsx +190 -32
- package/src/ui/screens/steps/SignInTotpStep.tsx +68 -34
- package/src/ui/screens/steps/SignInUsernameStep.tsx +446 -63
- package/src/ui/screens/steps/SignUpIdentityStep.tsx +49 -35
- package/src/ui/screens/steps/SignUpSecurityStep.tsx +56 -39
- package/src/ui/screens/steps/SignUpSummaryStep.tsx +99 -89
- package/src/ui/screens/steps/SignUpWelcomeStep.tsx +88 -20
- package/src/ui/stores/accountStore.ts +285 -0
- package/src/ui/stores/authStore.ts +16 -19
- package/src/ui/styles/authStyles.ts +16 -8
- package/src/ui/styles/index.ts +1 -0
- package/src/ui/styles/spacing.ts +46 -0
- package/src/utils/asyncUtils.ts +10 -24
- package/src/utils/cache.ts +264 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/languageUtils.ts +174 -0
- package/src/utils/requestUtils.ts +234 -0
- package/src/utils/sessionUtils.ts +206 -0
- package/src/utils/validationUtils.ts +1 -1
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import { createContext, useContext, useEffect, useCallback, useMemo, useRef, useState, type ReactNode } from 'react';
|
|
3
3
|
import type { UseFollowHook } from '../hooks/useFollow.types';
|
|
4
|
-
import { View, Text } from 'react-native';
|
|
5
4
|
import { OxyServices } from '../../core';
|
|
6
5
|
import type { User, ApiError } from '../../models/interfaces';
|
|
7
6
|
import type { SessionLoginResponse, ClientSession, MinimalUserData } from '../../models/session';
|
|
7
|
+
import { normalizeAndSortSessions, mergeSessions, sessionsArraysEqual } from '../../utils/sessionUtils';
|
|
8
8
|
import { DeviceManager } from '../../utils/deviceManager';
|
|
9
9
|
import { useSessionSocket } from '../hooks/useSessionSocket';
|
|
10
10
|
import { toast } from '../../lib/sonner';
|
|
11
11
|
import { useAuthStore } from '../stores/authStore';
|
|
12
12
|
import type { BottomSheetController } from '../navigation/types';
|
|
13
13
|
import type { RouteName } from '../navigation/routes';
|
|
14
|
+
import { getLanguageMetadata, getLanguageName, getNativeLanguageName, normalizeLanguageCode } from '../../utils/languageUtils';
|
|
15
|
+
import type { LanguageMetadata } from '../../utils/languageUtils';
|
|
14
16
|
|
|
15
17
|
// Define the context shape
|
|
16
18
|
// NOTE: We intentionally avoid importing useFollow here to prevent a require cycle.
|
|
@@ -24,10 +26,14 @@ export interface OxyContextState {
|
|
|
24
26
|
activeSessionId: string | null;
|
|
25
27
|
isAuthenticated: boolean; // Single source of truth for authentication - use this instead of service methods
|
|
26
28
|
isLoading: boolean;
|
|
29
|
+
isTokenReady: boolean; // Whether the token has been loaded/restored and is ready for use
|
|
27
30
|
error: string | null;
|
|
28
31
|
|
|
29
32
|
// Language state
|
|
30
33
|
currentLanguage: string;
|
|
34
|
+
currentLanguageMetadata: LanguageMetadata | null; // Full language metadata (name, nativeName, etc.)
|
|
35
|
+
currentLanguageName: string; // Language name (e.g., 'English')
|
|
36
|
+
currentNativeLanguageName: string; // Native language name (e.g., 'Español')
|
|
31
37
|
|
|
32
38
|
// Auth methods
|
|
33
39
|
login: (username: string, password: string, deviceName?: string) => Promise<User>;
|
|
@@ -64,6 +70,26 @@ export interface OxyContextState {
|
|
|
64
70
|
useFollow: UseFollowHook; // Back-compat; prefer direct import
|
|
65
71
|
}
|
|
66
72
|
|
|
73
|
+
// Empty follow hook fallback
|
|
74
|
+
const createEmptyFollowHook = (): UseFollowHook => {
|
|
75
|
+
const emptyResult = {
|
|
76
|
+
isFollowing: false,
|
|
77
|
+
isLoading: false,
|
|
78
|
+
error: null,
|
|
79
|
+
toggleFollow: async () => { },
|
|
80
|
+
setFollowStatus: () => { },
|
|
81
|
+
fetchStatus: async () => { },
|
|
82
|
+
clearError: () => { },
|
|
83
|
+
followerCount: null,
|
|
84
|
+
followingCount: null,
|
|
85
|
+
isLoadingCounts: false,
|
|
86
|
+
fetchUserCounts: async () => { },
|
|
87
|
+
setFollowerCount: () => { },
|
|
88
|
+
setFollowingCount: () => { },
|
|
89
|
+
};
|
|
90
|
+
return () => emptyResult;
|
|
91
|
+
};
|
|
92
|
+
|
|
67
93
|
// Create the context with default values
|
|
68
94
|
const OxyContext = createContext<OxyContextState | null>(null);
|
|
69
95
|
|
|
@@ -134,6 +160,7 @@ const getStorage = async (): Promise<StorageInterface> => {
|
|
|
134
160
|
// Storage keys for sessions
|
|
135
161
|
const getStorageKeys = (prefix = 'oxy_session') => ({
|
|
136
162
|
activeSessionId: `${prefix}_active_session_id`, // Only store the active session ID
|
|
163
|
+
sessionIds: `${prefix}_session_ids`, // Store all session IDs for quick account loading
|
|
137
164
|
language: `${prefix}_language`, // Store the selected language
|
|
138
165
|
});
|
|
139
166
|
|
|
@@ -174,34 +201,98 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
174
201
|
const [minimalUser, setMinimalUser] = useState<MinimalUserData | null>(null);
|
|
175
202
|
const [sessions, setSessions] = useState<ClientSession[]>([]);
|
|
176
203
|
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
|
|
204
|
+
|
|
205
|
+
// Track in-flight refresh to prevent duplicate calls
|
|
206
|
+
const refreshInFlightRef = useRef<Promise<void> | null>(null);
|
|
207
|
+
|
|
177
208
|
const [storage, setStorage] = useState<StorageInterface | null>(null);
|
|
178
209
|
const [currentLanguage, setCurrentLanguage] = useState<string>('en-US');
|
|
179
210
|
|
|
180
|
-
//
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
211
|
+
// Storage keys (memoized to prevent infinite loops) - declared early for use in helpers
|
|
212
|
+
const keys = useMemo(() => getStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
|
|
213
|
+
|
|
214
|
+
// Helper to apply language preference from user/server
|
|
215
|
+
const applyLanguagePreference = useCallback(async (user: User): Promise<void> => {
|
|
216
|
+
const userLanguage = (user as Record<string, unknown>)?.language as string | undefined;
|
|
217
|
+
if (!userLanguage || !storage) return;
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const serverLang = normalizeLanguageCode(userLanguage);
|
|
221
|
+
await storage.setItem(keys.language, serverLang);
|
|
222
|
+
setCurrentLanguage(serverLang);
|
|
223
|
+
} catch (e) {
|
|
224
|
+
if (__DEV__) {
|
|
225
|
+
console.warn('Failed to apply server language preference', e);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}, [storage, keys.language]);
|
|
229
|
+
|
|
230
|
+
const mapSessionsToClient = useCallback((sessions: Array<{
|
|
231
|
+
sessionId: string;
|
|
232
|
+
deviceId?: string;
|
|
233
|
+
expiresAt?: string;
|
|
234
|
+
lastActive?: string;
|
|
235
|
+
user?: { id?: string; _id?: { toString(): string } };
|
|
236
|
+
userId?: string;
|
|
237
|
+
isCurrent?: boolean;
|
|
238
|
+
}>, fallbackDeviceId?: string, fallbackUserId?: string): ClientSession[] => {
|
|
239
|
+
return sessions.map((s: any) => ({
|
|
240
|
+
sessionId: s.sessionId,
|
|
241
|
+
deviceId: s.deviceId || fallbackDeviceId || '',
|
|
242
|
+
expiresAt: s.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
243
|
+
lastActive: s.lastActive || new Date().toISOString(),
|
|
244
|
+
userId: s.user?.id || s.userId || (s.user?._id?.toString()) || fallbackUserId || '',
|
|
245
|
+
isCurrent: Boolean(s.isCurrent),
|
|
246
|
+
}));
|
|
189
247
|
}, []);
|
|
190
|
-
// Add a new state to track token restoration
|
|
191
|
-
const [tokenReady, setTokenReady] = useState(false);
|
|
192
248
|
|
|
193
|
-
//
|
|
194
|
-
const
|
|
249
|
+
// Save all session IDs to storage for quick loading on initialization
|
|
250
|
+
const saveSessionIds = useCallback(async (sessionIds: string[]): Promise<void> => {
|
|
251
|
+
if (!storage) return;
|
|
252
|
+
try {
|
|
253
|
+
const uniqueIds = Array.from(new Set(sessionIds));
|
|
254
|
+
await storage.setItem(keys.sessionIds, JSON.stringify(uniqueIds));
|
|
255
|
+
} catch (err) {
|
|
256
|
+
if (__DEV__) {
|
|
257
|
+
console.warn('Failed to save session IDs:', err);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}, [storage, keys.sessionIds]);
|
|
261
|
+
|
|
262
|
+
const updateSessions = useCallback((newSessions: ClientSession[], mergeWithExisting = false) => {
|
|
263
|
+
setSessions((prevSessions) => {
|
|
264
|
+
const sessionsToProcess = mergeWithExisting
|
|
265
|
+
? mergeSessions(prevSessions, newSessions, activeSessionId, false)
|
|
266
|
+
: normalizeAndSortSessions(newSessions, activeSessionId, false);
|
|
267
|
+
|
|
268
|
+
// Save all session IDs to storage
|
|
269
|
+
if (storage) {
|
|
270
|
+
const allSessionIds = sessionsToProcess.map(s => s.sessionId);
|
|
271
|
+
saveSessionIds(allSessionIds).catch(() => {
|
|
272
|
+
// Ignore errors - non-critical
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return sessionsArraysEqual(prevSessions, sessionsToProcess) ? prevSessions : sessionsToProcess;
|
|
277
|
+
});
|
|
278
|
+
}, [activeSessionId, storage, saveSessionIds]);
|
|
195
279
|
|
|
196
|
-
//
|
|
280
|
+
// Token ready state - start optimistically so children render immediately
|
|
281
|
+
const [tokenReady, setTokenReady] = useState(true);
|
|
282
|
+
|
|
283
|
+
// Clear all storage
|
|
197
284
|
const clearAllStorage = useCallback(async (): Promise<void> => {
|
|
198
285
|
if (!storage) return;
|
|
199
286
|
try {
|
|
200
287
|
await storage.removeItem(keys.activeSessionId);
|
|
288
|
+
await storage.removeItem(keys.sessionIds);
|
|
201
289
|
} catch (err) {
|
|
202
|
-
|
|
290
|
+
if (__DEV__) {
|
|
291
|
+
console.error('Clear storage error:', err);
|
|
292
|
+
}
|
|
293
|
+
onError?.({ message: 'Failed to clear storage', code: 'STORAGE_ERROR', status: 500 });
|
|
203
294
|
}
|
|
204
|
-
}, [storage, keys]);
|
|
295
|
+
}, [storage, keys, onError]);
|
|
205
296
|
|
|
206
297
|
// Initialize storage
|
|
207
298
|
useEffect(() => {
|
|
@@ -210,21 +301,22 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
210
301
|
const platformStorage = await getStorage();
|
|
211
302
|
setStorage(platformStorage);
|
|
212
303
|
} catch (error) {
|
|
213
|
-
|
|
214
|
-
useAuthStore.setState({ error:
|
|
304
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to initialize storage';
|
|
305
|
+
useAuthStore.setState({ error: errorMessage });
|
|
306
|
+
onError?.({ message: errorMessage, code: 'STORAGE_INIT_ERROR', status: 500 });
|
|
215
307
|
}
|
|
216
308
|
};
|
|
217
309
|
initStorage();
|
|
218
|
-
}, []);
|
|
310
|
+
}, [onError]);
|
|
219
311
|
|
|
220
312
|
// Initialize authentication state
|
|
313
|
+
// Note: We don't set isLoading during initialization to avoid showing spinners
|
|
314
|
+
// Children render immediately and can check isTokenReady/isAuthenticated themselves
|
|
221
315
|
useEffect(() => {
|
|
222
316
|
const initAuth = async () => {
|
|
223
317
|
if (!storage) return;
|
|
224
|
-
|
|
318
|
+
// Don't set isLoading during initialization - let it happen in background
|
|
225
319
|
try {
|
|
226
|
-
setTokenReady(false);
|
|
227
|
-
|
|
228
320
|
// Load saved language preference
|
|
229
321
|
const savedLanguageRaw = await storage.getItem(keys.language);
|
|
230
322
|
const savedLanguage = normalizeLanguageCode(savedLanguageRaw) || savedLanguageRaw;
|
|
@@ -232,8 +324,49 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
232
324
|
setCurrentLanguage(savedLanguage);
|
|
233
325
|
}
|
|
234
326
|
|
|
327
|
+
// Load all stored session IDs and validate them
|
|
328
|
+
const storedSessionIdsJson = await storage.getItem(keys.sessionIds);
|
|
329
|
+
const storedSessionIds: string[] = storedSessionIdsJson ? JSON.parse(storedSessionIdsJson) : [];
|
|
330
|
+
|
|
235
331
|
// Try to restore active session from storage
|
|
236
332
|
const storedActiveSessionId = await storage.getItem(keys.activeSessionId);
|
|
333
|
+
const validSessions: ClientSession[] = [];
|
|
334
|
+
|
|
335
|
+
// If we have stored session IDs, validate them (even without active session)
|
|
336
|
+
if (storedSessionIds.length > 0) {
|
|
337
|
+
if (__DEV__) {
|
|
338
|
+
console.log('Loading stored sessions on init:', storedSessionIds.length);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Validate each stored session ID and build session list
|
|
342
|
+
for (const sessionId of storedSessionIds) {
|
|
343
|
+
try {
|
|
344
|
+
const validation = await oxyServices.validateSession(sessionId, { useHeaderValidation: true });
|
|
345
|
+
if (validation.valid && validation.user) {
|
|
346
|
+
validSessions.push({
|
|
347
|
+
sessionId,
|
|
348
|
+
userId: validation.user.id?.toString() || '',
|
|
349
|
+
deviceId: '',
|
|
350
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
351
|
+
lastActive: new Date().toISOString(),
|
|
352
|
+
isCurrent: sessionId === storedActiveSessionId,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
} catch (e) {
|
|
356
|
+
// Session invalid, skip it
|
|
357
|
+
if (__DEV__) {
|
|
358
|
+
console.warn('Session validation failed for:', sessionId, e);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Update sessions list with validated sessions (even if no active session)
|
|
364
|
+
if (validSessions.length > 0) {
|
|
365
|
+
updateSessions(validSessions, false);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// If we have an active session, authenticate with it
|
|
237
370
|
if (storedActiveSessionId) {
|
|
238
371
|
try {
|
|
239
372
|
const validation = await oxyServices.validateSession(storedActiveSessionId, { useHeaderValidation: true });
|
|
@@ -243,68 +376,47 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
243
376
|
const fullUser = await oxyServices.getUserBySession(storedActiveSessionId);
|
|
244
377
|
loginSuccess(fullUser);
|
|
245
378
|
setMinimalUser({ id: fullUser.id, username: fullUser.username, avatar: fullUser.avatar });
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
379
|
+
|
|
380
|
+
await applyLanguagePreference(fullUser);
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
const deviceSessions = await oxyServices.getDeviceSessions(storedActiveSessionId);
|
|
384
|
+
const allDeviceSessions = mapSessionsToClient(deviceSessions, undefined, fullUser.id);
|
|
385
|
+
updateSessions(allDeviceSessions, true);
|
|
386
|
+
} catch (e) {
|
|
387
|
+
if (__DEV__) {
|
|
388
|
+
console.warn('Failed to get device sessions on init, falling back to user sessions:', e);
|
|
254
389
|
}
|
|
390
|
+
const serverSessions = await oxyServices.getSessionsBySessionId(storedActiveSessionId);
|
|
391
|
+
updateSessions(mapSessionsToClient(serverSessions, undefined, fullUser.id), false);
|
|
255
392
|
}
|
|
256
|
-
const serverSessions = await oxyServices.getSessionsBySessionId(storedActiveSessionId);
|
|
257
|
-
const clientSessions: ClientSession[] = serverSessions.map(s => ({
|
|
258
|
-
sessionId: s.sessionId,
|
|
259
|
-
deviceId: s.deviceId,
|
|
260
|
-
expiresAt: s.expiresAt || new Date().toISOString(),
|
|
261
|
-
lastActive: s.lastActive || new Date().toISOString(),
|
|
262
|
-
userId: s.userId || fullUser.id
|
|
263
|
-
}));
|
|
264
|
-
setSessions(clientSessions);
|
|
265
393
|
onAuthStateChange?.(fullUser);
|
|
266
394
|
} else {
|
|
267
|
-
|
|
395
|
+
// Active session invalid, remove it but keep other sessions
|
|
396
|
+
await storage.removeItem(keys.activeSessionId);
|
|
397
|
+
// Update session list to remove invalid active session
|
|
398
|
+
updateSessions(validSessions.filter(s => s.sessionId !== storedActiveSessionId), false);
|
|
268
399
|
}
|
|
269
400
|
} catch (e) {
|
|
270
|
-
|
|
271
|
-
|
|
401
|
+
if (__DEV__) {
|
|
402
|
+
console.error('Active session validation error', e);
|
|
403
|
+
}
|
|
404
|
+
// Remove invalid active session but keep other sessions
|
|
405
|
+
await storage.removeItem(keys.activeSessionId);
|
|
406
|
+
updateSessions(validSessions.filter(s => s.sessionId !== storedActiveSessionId), false);
|
|
272
407
|
}
|
|
273
408
|
}
|
|
274
409
|
setTokenReady(true);
|
|
275
410
|
} catch (e) {
|
|
276
|
-
|
|
411
|
+
if (__DEV__) {
|
|
412
|
+
console.error('Auth init error', e);
|
|
413
|
+
}
|
|
277
414
|
await clearAllStorage();
|
|
278
|
-
|
|
279
|
-
useAuthStore.setState({ isLoading: false });
|
|
415
|
+
setTokenReady(true);
|
|
280
416
|
}
|
|
281
417
|
};
|
|
282
418
|
initAuth();
|
|
283
|
-
}, [storage, oxyServices, keys, onAuthStateChange, loginSuccess, clearAllStorage]);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
// Remove invalid session - refresh sessions from backend
|
|
288
|
-
const removeInvalidSession = useCallback(async (sessionId: string): Promise<void> => {
|
|
289
|
-
// Remove from local state
|
|
290
|
-
const filteredSessions = sessions.filter(s => s.sessionId !== sessionId);
|
|
291
|
-
setSessions(filteredSessions);
|
|
292
|
-
|
|
293
|
-
// If there are other sessions, switch to the first one
|
|
294
|
-
if (filteredSessions.length > 0) {
|
|
295
|
-
await switchToSession(filteredSessions[0].sessionId);
|
|
296
|
-
} else {
|
|
297
|
-
// No valid sessions left
|
|
298
|
-
setActiveSessionId(null);
|
|
299
|
-
logoutStore();
|
|
300
|
-
setMinimalUser(null);
|
|
301
|
-
await storage?.removeItem(keys.activeSessionId);
|
|
302
|
-
|
|
303
|
-
if (onAuthStateChange) {
|
|
304
|
-
onAuthStateChange(null);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}, [sessions, storage, keys, onAuthStateChange, logoutStore]);
|
|
419
|
+
}, [storage, oxyServices, keys, onAuthStateChange, loginSuccess, clearAllStorage, applyLanguagePreference, mapSessionsToClient, updateSessions]);
|
|
308
420
|
|
|
309
421
|
// Save active session ID to storage (only session ID, no user data)
|
|
310
422
|
const saveActiveSessionId = useCallback(async (sessionId: string): Promise<void> => {
|
|
@@ -312,16 +424,21 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
312
424
|
await storage.setItem(keys.activeSessionId, sessionId);
|
|
313
425
|
}, [storage, keys.activeSessionId]);
|
|
314
426
|
|
|
315
|
-
// Switch to a different session
|
|
316
427
|
const switchToSession = useCallback(async (sessionId: string): Promise<void> => {
|
|
317
428
|
try {
|
|
318
|
-
|
|
429
|
+
const validation = await oxyServices.validateSession(sessionId, { useHeaderValidation: true });
|
|
430
|
+
if (!validation.valid) {
|
|
431
|
+
updateSessions(sessions.filter(s => s.sessionId !== sessionId), false);
|
|
432
|
+
throw new Error('Session is invalid or expired');
|
|
433
|
+
}
|
|
319
434
|
|
|
320
|
-
|
|
321
|
-
|
|
435
|
+
if (!validation.user) {
|
|
436
|
+
throw new Error('User data not available from session validation');
|
|
437
|
+
}
|
|
322
438
|
|
|
323
|
-
|
|
324
|
-
|
|
439
|
+
const fullUser = validation.user;
|
|
440
|
+
await oxyServices.getTokenBySession(sessionId);
|
|
441
|
+
setTokenReady(true);
|
|
325
442
|
|
|
326
443
|
setActiveSessionId(sessionId);
|
|
327
444
|
loginSuccess(fullUser);
|
|
@@ -332,44 +449,63 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
332
449
|
});
|
|
333
450
|
|
|
334
451
|
await saveActiveSessionId(sessionId);
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
452
|
+
await applyLanguagePreference(fullUser);
|
|
453
|
+
|
|
454
|
+
oxyServices.getDeviceSessions(sessionId)
|
|
455
|
+
.then((deviceSessions) => {
|
|
456
|
+
const allDeviceSessions = mapSessionsToClient(deviceSessions, undefined, fullUser.id);
|
|
457
|
+
updateSessions(allDeviceSessions, true);
|
|
458
|
+
})
|
|
459
|
+
.catch((error) => {
|
|
460
|
+
if (__DEV__) console.warn('Failed to get device sessions after switch:', error);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
onAuthStateChange?.(fullUser);
|
|
464
|
+
} catch (error: any) {
|
|
465
|
+
const isInvalidSession = error?.response?.status === 401 ||
|
|
466
|
+
error?.message?.includes('Invalid or expired session') ||
|
|
467
|
+
error?.message?.includes('Session is invalid');
|
|
468
|
+
|
|
469
|
+
if (isInvalidSession) {
|
|
470
|
+
updateSessions(sessions.filter(s => s.sessionId !== sessionId), false);
|
|
471
|
+
|
|
472
|
+
if (sessionId === activeSessionId && sessions.length > 1) {
|
|
473
|
+
const otherSessions = sessions.filter(s => s.sessionId !== sessionId);
|
|
474
|
+
for (const otherSession of otherSessions) {
|
|
475
|
+
try {
|
|
476
|
+
const otherValidation = await oxyServices.validateSession(otherSession.sessionId, { useHeaderValidation: true });
|
|
477
|
+
if (otherValidation.valid) {
|
|
478
|
+
await switchToSession(otherSession.sessionId);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
} catch {
|
|
482
|
+
// Continue to next session
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
343
486
|
}
|
|
344
487
|
}
|
|
345
488
|
|
|
346
|
-
|
|
347
|
-
|
|
489
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to switch session';
|
|
490
|
+
if (__DEV__) {
|
|
491
|
+
console.error('Switch session error:', error);
|
|
348
492
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
useAuthStore.setState({ isLoading: false });
|
|
493
|
+
useAuthStore.setState({ error: errorMessage });
|
|
494
|
+
onError?.({ message: errorMessage, code: isInvalidSession ? 'INVALID_SESSION' : 'SESSION_SWITCH_ERROR', status: isInvalidSession ? 401 : 500 });
|
|
495
|
+
setTokenReady(false);
|
|
496
|
+
throw error; // Re-throw so calling code can handle it
|
|
354
497
|
}
|
|
355
|
-
}, [oxyServices, onAuthStateChange, loginSuccess, saveActiveSessionId]);
|
|
498
|
+
}, [oxyServices, onAuthStateChange, loginSuccess, saveActiveSessionId, applyLanguagePreference, mapSessionsToClient, onError, activeSessionId, sessions]);
|
|
356
499
|
|
|
357
|
-
// Login method - only store session ID, retrieve data from backend
|
|
358
500
|
const login = useCallback(async (username: string, password: string, deviceName?: string): Promise<User> => {
|
|
359
501
|
if (!storage) throw new Error('Storage not initialized');
|
|
360
502
|
useAuthStore.setState({ isLoading: true, error: null });
|
|
361
503
|
|
|
362
504
|
try {
|
|
363
|
-
// Get device fingerprint for enhanced device identification
|
|
364
505
|
const deviceFingerprint = DeviceManager.getDeviceFingerprint();
|
|
365
|
-
|
|
366
|
-
// Get or generate persistent device info
|
|
367
506
|
const deviceInfo = await DeviceManager.getDeviceInfo();
|
|
368
507
|
|
|
369
|
-
|
|
370
|
-
console.log('Auth - Using device ID:', deviceInfo.deviceId);
|
|
371
|
-
|
|
372
|
-
const response: any = await oxyServices.signIn(
|
|
508
|
+
const response = await oxyServices.signIn(
|
|
373
509
|
username,
|
|
374
510
|
password,
|
|
375
511
|
deviceName || deviceInfo.deviceName || DeviceManager.getDefaultDeviceName(),
|
|
@@ -377,49 +513,73 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
377
513
|
);
|
|
378
514
|
|
|
379
515
|
// Handle MFA requirement
|
|
380
|
-
if (response && response.mfaRequired) {
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
516
|
+
if (response && 'mfaRequired' in response && response.mfaRequired) {
|
|
517
|
+
const mfaError = new Error('Multi-factor authentication required') as Error & {
|
|
518
|
+
code: string;
|
|
519
|
+
mfaToken?: string;
|
|
520
|
+
expiresAt?: string;
|
|
521
|
+
};
|
|
522
|
+
mfaError.code = 'MFA_REQUIRED';
|
|
523
|
+
mfaError.mfaToken = (response as { mfaToken?: string }).mfaToken;
|
|
524
|
+
mfaError.expiresAt = (response as { expiresAt?: string }).expiresAt;
|
|
525
|
+
throw mfaError;
|
|
386
526
|
}
|
|
387
527
|
|
|
388
|
-
|
|
389
|
-
setActiveSessionId((response as SessionLoginResponse).sessionId);
|
|
390
|
-
await saveActiveSessionId((response as SessionLoginResponse).sessionId);
|
|
528
|
+
const sessionResponse = response as SessionLoginResponse;
|
|
391
529
|
|
|
392
|
-
|
|
393
|
-
await oxyServices.
|
|
530
|
+
await oxyServices.getTokenBySession(sessionResponse.sessionId);
|
|
531
|
+
const fullUser = await oxyServices.getUserBySession(sessionResponse.sessionId);
|
|
394
532
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
expiresAt: serverSession.expiresAt || new Date().toISOString(),
|
|
406
|
-
lastActive: serverSession.lastActive || new Date().toISOString(),
|
|
407
|
-
userId: serverSession.userId || fullUser.id
|
|
408
|
-
}));
|
|
409
|
-
setSessions(clientSessions);
|
|
410
|
-
|
|
411
|
-
if (onAuthStateChange) {
|
|
412
|
-
onAuthStateChange(fullUser);
|
|
533
|
+
let allDeviceSessions: ClientSession[] = [];
|
|
534
|
+
try {
|
|
535
|
+
const deviceSessions = await oxyServices.getDeviceSessions(sessionResponse.sessionId);
|
|
536
|
+
allDeviceSessions = mapSessionsToClient(deviceSessions, sessionResponse.deviceId, fullUser.id);
|
|
537
|
+
} catch (error) {
|
|
538
|
+
if (__DEV__) {
|
|
539
|
+
console.warn('Failed to get device sessions, falling back to user sessions:', error);
|
|
540
|
+
}
|
|
541
|
+
const serverSessions = await oxyServices.getSessionsBySessionId(sessionResponse.sessionId);
|
|
542
|
+
allDeviceSessions = mapSessionsToClient(serverSessions, undefined, fullUser.id);
|
|
413
543
|
}
|
|
414
544
|
|
|
545
|
+
const userUserId = fullUser.id?.toString();
|
|
546
|
+
const existingSession = allDeviceSessions.find(
|
|
547
|
+
s => s.userId?.toString() === userUserId && s.sessionId !== sessionResponse.sessionId
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
if (existingSession) {
|
|
551
|
+
try {
|
|
552
|
+
await oxyServices.logoutSession(sessionResponse.sessionId, sessionResponse.sessionId);
|
|
553
|
+
} catch (logoutError) {
|
|
554
|
+
if (__DEV__) {
|
|
555
|
+
console.warn('Failed to logout duplicate session:', logoutError);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
await switchToSession(existingSession.sessionId);
|
|
559
|
+
loginSuccess(fullUser);
|
|
560
|
+
setMinimalUser(sessionResponse.user);
|
|
561
|
+
updateSessions(allDeviceSessions.filter(s => s.sessionId !== sessionResponse.sessionId), false);
|
|
562
|
+
onAuthStateChange?.(fullUser);
|
|
563
|
+
return fullUser;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
setActiveSessionId(sessionResponse.sessionId);
|
|
567
|
+
await saveActiveSessionId(sessionResponse.sessionId);
|
|
568
|
+
loginSuccess(fullUser);
|
|
569
|
+
setMinimalUser(sessionResponse.user);
|
|
570
|
+
updateSessions(allDeviceSessions, true);
|
|
571
|
+
|
|
572
|
+
onAuthStateChange?.(fullUser);
|
|
415
573
|
return fullUser;
|
|
416
|
-
} catch (error
|
|
417
|
-
|
|
574
|
+
} catch (error) {
|
|
575
|
+
const errorMessage = error instanceof Error ? error.message : 'Login failed';
|
|
576
|
+
loginFailure(errorMessage);
|
|
577
|
+
onError?.({ message: errorMessage, code: 'LOGIN_ERROR', status: 401 });
|
|
418
578
|
throw error;
|
|
419
579
|
} finally {
|
|
420
580
|
useAuthStore.setState({ isLoading: false });
|
|
421
581
|
}
|
|
422
|
-
}, [storage, oxyServices, saveActiveSessionId, loginSuccess,
|
|
582
|
+
}, [storage, oxyServices, saveActiveSessionId, loginSuccess, onAuthStateChange, loginFailure, mapSessionsToClient, onError, sessions, switchToSession]);
|
|
423
583
|
|
|
424
584
|
// Logout method
|
|
425
585
|
const logout = useCallback(async (targetSessionId?: string): Promise<void> => {
|
|
@@ -429,17 +589,13 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
429
589
|
const sessionToLogout = targetSessionId || activeSessionId;
|
|
430
590
|
await oxyServices.logoutSession(activeSessionId, sessionToLogout);
|
|
431
591
|
|
|
432
|
-
// Remove session from local state
|
|
433
592
|
const filteredSessions = sessions.filter(s => s.sessionId !== sessionToLogout);
|
|
434
|
-
|
|
593
|
+
updateSessions(filteredSessions, false);
|
|
435
594
|
|
|
436
|
-
// If logging out active session
|
|
437
595
|
if (sessionToLogout === activeSessionId) {
|
|
438
596
|
if (filteredSessions.length > 0) {
|
|
439
|
-
// Switch to another session
|
|
440
597
|
await switchToSession(filteredSessions[0].sessionId);
|
|
441
598
|
} else {
|
|
442
|
-
// No sessions left
|
|
443
599
|
setActiveSessionId(null);
|
|
444
600
|
logoutStore();
|
|
445
601
|
setMinimalUser(null);
|
|
@@ -451,70 +607,42 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
451
607
|
}
|
|
452
608
|
}
|
|
453
609
|
} catch (error) {
|
|
454
|
-
|
|
455
|
-
|
|
610
|
+
const errorMessage = error instanceof Error ? error.message : 'Logout failed';
|
|
611
|
+
if (__DEV__) {
|
|
612
|
+
console.error('Logout error:', error);
|
|
613
|
+
}
|
|
614
|
+
useAuthStore.setState({ error: errorMessage });
|
|
615
|
+
onError?.({ message: errorMessage, code: 'LOGOUT_ERROR', status: 500 });
|
|
456
616
|
}
|
|
457
|
-
}, [activeSessionId, oxyServices, sessions, switchToSession, logoutStore,
|
|
617
|
+
}, [activeSessionId, oxyServices, sessions, switchToSession, logoutStore, storage, keys.activeSessionId, onAuthStateChange, onError]);
|
|
458
618
|
|
|
459
|
-
// Logout all sessions
|
|
460
619
|
const logoutAll = useCallback(async (): Promise<void> => {
|
|
461
|
-
console.log('logoutAll called with activeSessionId:', activeSessionId);
|
|
462
|
-
|
|
463
620
|
if (!activeSessionId) {
|
|
464
|
-
|
|
465
|
-
useAuthStore.setState({ error:
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if (!oxyServices) {
|
|
470
|
-
console.error('OxyServices not initialized');
|
|
471
|
-
useAuthStore.setState({ error: 'Service not available' });
|
|
472
|
-
throw new Error('Service not available');
|
|
621
|
+
const error = new Error('No active session found');
|
|
622
|
+
useAuthStore.setState({ error: error.message });
|
|
623
|
+
onError?.({ message: error.message, code: 'NO_SESSION_ERROR', status: 404 });
|
|
624
|
+
throw error;
|
|
473
625
|
}
|
|
474
626
|
|
|
475
627
|
try {
|
|
476
|
-
console.log('Calling oxyServices.logoutAllSessions with sessionId:', activeSessionId);
|
|
477
628
|
await oxyServices.logoutAllSessions(activeSessionId);
|
|
478
|
-
console.log('logoutAllSessions completed successfully');
|
|
479
629
|
|
|
480
|
-
|
|
481
|
-
setSessions([]);
|
|
630
|
+
updateSessions([], false);
|
|
482
631
|
setActiveSessionId(null);
|
|
483
632
|
logoutStore();
|
|
484
633
|
setMinimalUser(null);
|
|
485
634
|
await clearAllStorage();
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
if (onAuthStateChange) {
|
|
489
|
-
onAuthStateChange(null);
|
|
490
|
-
console.log('Auth state change callback called');
|
|
491
|
-
}
|
|
635
|
+
onAuthStateChange?.(null);
|
|
492
636
|
} catch (error) {
|
|
493
|
-
|
|
494
|
-
useAuthStore.setState({ error:
|
|
637
|
+
const errorMessage = error instanceof Error ? error.message : 'Logout all failed';
|
|
638
|
+
useAuthStore.setState({ error: errorMessage });
|
|
639
|
+
onError?.({ message: errorMessage, code: 'LOGOUT_ALL_ERROR', status: 500 });
|
|
495
640
|
throw error;
|
|
496
641
|
}
|
|
497
|
-
}, [activeSessionId, oxyServices, logoutStore,
|
|
642
|
+
}, [activeSessionId, oxyServices, logoutStore, clearAllStorage, onAuthStateChange, onError]);
|
|
498
643
|
|
|
499
|
-
//
|
|
500
|
-
|
|
501
|
-
const restoreToken = async () => {
|
|
502
|
-
if (activeSessionId && oxyServices) {
|
|
503
|
-
try {
|
|
504
|
-
await oxyServices.getTokenBySession(activeSessionId);
|
|
505
|
-
setTokenReady(true);
|
|
506
|
-
} catch (err) {
|
|
507
|
-
// If token restoration fails, force logout
|
|
508
|
-
await logout();
|
|
509
|
-
setTokenReady(false);
|
|
510
|
-
}
|
|
511
|
-
} else {
|
|
512
|
-
setTokenReady(true); // No session, so token is not needed
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
|
-
restoreToken();
|
|
516
|
-
// Only run when activeSessionId or oxyServices changes
|
|
517
|
-
}, [activeSessionId, oxyServices, logout]);
|
|
644
|
+
// Token restoration is handled in initAuth and switchToSession
|
|
645
|
+
// No separate effect needed - children render immediately with isTokenReady available
|
|
518
646
|
|
|
519
647
|
// Sign up method
|
|
520
648
|
const signUp = useCallback(async (username: string, email: string, password: string): Promise<User> => {
|
|
@@ -523,23 +651,18 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
523
651
|
useAuthStore.setState({ isLoading: true, error: null });
|
|
524
652
|
|
|
525
653
|
try {
|
|
526
|
-
|
|
527
|
-
const response = await oxyServices.signUp(username, email, password);
|
|
528
|
-
|
|
529
|
-
console.log('SignUp successful:', response);
|
|
530
|
-
|
|
531
|
-
// Now log the user in to create a session
|
|
532
|
-
// This will handle the session creation and device registration
|
|
654
|
+
await oxyServices.signUp(username, email, password);
|
|
533
655
|
const user = await login(username, password);
|
|
534
|
-
|
|
535
656
|
return user;
|
|
536
|
-
} catch (error
|
|
537
|
-
|
|
657
|
+
} catch (error) {
|
|
658
|
+
const errorMessage = error instanceof Error ? error.message : 'Sign up failed';
|
|
659
|
+
loginFailure(errorMessage);
|
|
660
|
+
onError?.({ message: errorMessage, code: 'SIGNUP_ERROR', status: 400 });
|
|
538
661
|
throw error;
|
|
539
662
|
} finally {
|
|
540
663
|
useAuthStore.setState({ isLoading: false });
|
|
541
664
|
}
|
|
542
|
-
}, [storage, oxyServices, login, loginFailure]);
|
|
665
|
+
}, [storage, oxyServices, login, loginFailure, onError]);
|
|
543
666
|
|
|
544
667
|
// Complete MFA login by verifying TOTP
|
|
545
668
|
const completeMfaLogin = useCallback(async (mfaToken: string, code: string): Promise<User> => {
|
|
@@ -555,190 +678,173 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
555
678
|
// Fetch access token and user data
|
|
556
679
|
await oxyServices.getTokenBySession(response.sessionId);
|
|
557
680
|
const fullUser = await oxyServices.getUserBySession(response.sessionId);
|
|
681
|
+
|
|
558
682
|
loginSuccess(fullUser);
|
|
559
683
|
setMinimalUser({ id: fullUser.id, username: fullUser.username, avatar: fullUser.avatar });
|
|
560
|
-
|
|
561
|
-
if ((fullUser as any)?.language) {
|
|
562
|
-
try {
|
|
563
|
-
const serverLang = normalizeLanguageCode((fullUser as any).language) || (fullUser as any).language;
|
|
564
|
-
await storage.setItem(keys.language, serverLang);
|
|
565
|
-
setCurrentLanguage(serverLang);
|
|
566
|
-
} catch (e) {
|
|
567
|
-
console.warn('Failed to apply server language on MFA login', e);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
684
|
+
await applyLanguagePreference(fullUser);
|
|
570
685
|
|
|
571
|
-
//
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
setSessions(clientSessions);
|
|
581
|
-
// Apply server language if present
|
|
582
|
-
if ((fullUser as any)?.language) {
|
|
583
|
-
try {
|
|
584
|
-
await storage.setItem(keys.language, (fullUser as any).language);
|
|
585
|
-
setCurrentLanguage((fullUser as any).language);
|
|
586
|
-
} catch (e) {
|
|
587
|
-
console.warn('Failed to apply server language on MFA login', e);
|
|
686
|
+
// Get all device sessions to support multiple accounts
|
|
687
|
+
try {
|
|
688
|
+
const deviceSessions = await oxyServices.getDeviceSessions(response.sessionId);
|
|
689
|
+
const allDeviceSessions = mapSessionsToClient(deviceSessions, undefined, fullUser.id);
|
|
690
|
+
updateSessions(allDeviceSessions, true);
|
|
691
|
+
} catch (error) {
|
|
692
|
+
// Fallback to user sessions if device sessions fail
|
|
693
|
+
if (__DEV__) {
|
|
694
|
+
console.warn('Failed to get device sessions for MFA, falling back to user sessions:', error);
|
|
588
695
|
}
|
|
696
|
+
const serverSessions = await oxyServices.getSessionsBySessionId(response.sessionId);
|
|
697
|
+
const userSessions = mapSessionsToClient(serverSessions, undefined, fullUser.id);
|
|
698
|
+
|
|
699
|
+
updateSessions(userSessions, true);
|
|
589
700
|
}
|
|
590
701
|
|
|
591
|
-
|
|
702
|
+
onAuthStateChange?.(fullUser);
|
|
592
703
|
return fullUser;
|
|
593
|
-
} catch (error
|
|
594
|
-
|
|
704
|
+
} catch (error) {
|
|
705
|
+
const errorMessage = error instanceof Error ? error.message : 'MFA verification failed';
|
|
706
|
+
loginFailure(errorMessage);
|
|
707
|
+
onError?.({ message: errorMessage, code: 'MFA_ERROR', status: 401 });
|
|
595
708
|
throw error;
|
|
596
709
|
} finally {
|
|
597
710
|
useAuthStore.setState({ isLoading: false });
|
|
598
711
|
}
|
|
599
|
-
}, [storage, oxyServices, loginSuccess, loginFailure, saveActiveSessionId, onAuthStateChange]);
|
|
712
|
+
}, [storage, oxyServices, loginSuccess, loginFailure, saveActiveSessionId, onAuthStateChange, applyLanguagePreference, onError]);
|
|
600
713
|
|
|
601
|
-
// Switch session method
|
|
602
714
|
const switchSession = useCallback(async (sessionId: string): Promise<void> => {
|
|
603
715
|
await switchToSession(sessionId);
|
|
604
716
|
}, [switchToSession]);
|
|
605
717
|
|
|
606
|
-
// Remove session method
|
|
607
718
|
const removeSession = useCallback(async (sessionId: string): Promise<void> => {
|
|
608
719
|
await logout(sessionId);
|
|
609
720
|
}, [logout]);
|
|
610
721
|
|
|
611
|
-
// Refresh sessions method
|
|
612
722
|
const refreshSessions = useCallback(async (): Promise<void> => {
|
|
613
|
-
|
|
723
|
+
if (!activeSessionId) return;
|
|
614
724
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
return;
|
|
725
|
+
// If a refresh is already in progress, return the existing promise
|
|
726
|
+
if (refreshInFlightRef.current) {
|
|
727
|
+
return refreshInFlightRef.current;
|
|
618
728
|
}
|
|
619
729
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
console.error('Refresh sessions error:', error);
|
|
639
|
-
|
|
640
|
-
// If the current session is invalid, try to find another valid session
|
|
641
|
-
if (sessions.length > 1) {
|
|
642
|
-
console.log('Current session invalid, trying to switch to another session...');
|
|
643
|
-
const otherSessions = sessions.filter(s => s.sessionId !== activeSessionId);
|
|
730
|
+
// Create the refresh promise
|
|
731
|
+
const refreshPromise = (async () => {
|
|
732
|
+
try {
|
|
733
|
+
const deviceSessions = await oxyServices.getDeviceSessions(activeSessionId);
|
|
734
|
+
const allDeviceSessions = mapSessionsToClient(deviceSessions, undefined, user?.id);
|
|
735
|
+
updateSessions(allDeviceSessions, true);
|
|
736
|
+
} catch (error) {
|
|
737
|
+
if (__DEV__) {
|
|
738
|
+
console.warn('Failed to refresh device sessions, falling back to user sessions:', error);
|
|
739
|
+
}
|
|
740
|
+
try {
|
|
741
|
+
const serverSessions = await oxyServices.getSessionsBySessionId(activeSessionId);
|
|
742
|
+
const userSessions = mapSessionsToClient(serverSessions, undefined, user?.id);
|
|
743
|
+
updateSessions(userSessions, true);
|
|
744
|
+
} catch (fallbackError) {
|
|
745
|
+
if (__DEV__) {
|
|
746
|
+
console.error('Refresh sessions error:', fallbackError);
|
|
747
|
+
}
|
|
644
748
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
749
|
+
// If the current session is invalid, try to find another valid session
|
|
750
|
+
if (sessions.length > 1) {
|
|
751
|
+
const otherSessions = sessions.filter(s => s.sessionId !== activeSessionId);
|
|
752
|
+
|
|
753
|
+
for (const session of otherSessions) {
|
|
754
|
+
try {
|
|
755
|
+
const validation = await oxyServices.validateSession(session.sessionId, {
|
|
756
|
+
useHeaderValidation: true
|
|
757
|
+
});
|
|
758
|
+
if (validation.valid) {
|
|
759
|
+
await switchToSession(session.sessionId);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
} catch {
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
657
766
|
}
|
|
767
|
+
|
|
768
|
+
// No valid sessions found, clear all
|
|
769
|
+
updateSessions([], false);
|
|
770
|
+
setActiveSessionId(null);
|
|
771
|
+
logoutStore();
|
|
772
|
+
setMinimalUser(null);
|
|
773
|
+
await clearAllStorage();
|
|
774
|
+
onAuthStateChange?.(null);
|
|
658
775
|
}
|
|
776
|
+
} finally {
|
|
777
|
+
// Clear the in-flight ref when done
|
|
778
|
+
refreshInFlightRef.current = null;
|
|
659
779
|
}
|
|
780
|
+
})();
|
|
660
781
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
setActiveSessionId(null);
|
|
665
|
-
logoutStore();
|
|
666
|
-
setMinimalUser(null);
|
|
667
|
-
await clearAllStorage();
|
|
668
|
-
|
|
669
|
-
if (onAuthStateChange) {
|
|
670
|
-
onAuthStateChange(null);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
}, [activeSessionId, oxyServices, user?.id, sessions, switchToSession, logoutStore, setMinimalUser, clearAllStorage, onAuthStateChange]);
|
|
782
|
+
refreshInFlightRef.current = refreshPromise;
|
|
783
|
+
return refreshPromise;
|
|
784
|
+
}, [activeSessionId, oxyServices, user?.id, updateSessions, sessions, switchToSession, logoutStore, clearAllStorage, onAuthStateChange, mapSessionsToClient]);
|
|
674
785
|
|
|
675
786
|
// Device management methods
|
|
676
|
-
const getDeviceSessions = useCallback(async (): Promise<
|
|
787
|
+
const getDeviceSessions = useCallback(async (): Promise<Array<{
|
|
788
|
+
sessionId: string;
|
|
789
|
+
deviceId: string;
|
|
790
|
+
deviceName?: string;
|
|
791
|
+
lastActive?: string;
|
|
792
|
+
expiresAt?: string;
|
|
793
|
+
}>> => {
|
|
677
794
|
if (!activeSessionId) throw new Error('No active session');
|
|
678
|
-
|
|
679
795
|
try {
|
|
680
796
|
return await oxyServices.getDeviceSessions(activeSessionId);
|
|
681
797
|
} catch (error) {
|
|
682
|
-
|
|
798
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to get device sessions';
|
|
799
|
+
onError?.({ message: errorMessage, code: 'GET_DEVICE_SESSIONS_ERROR', status: 500 });
|
|
683
800
|
throw error;
|
|
684
801
|
}
|
|
685
|
-
}, [activeSessionId, oxyServices]);
|
|
802
|
+
}, [activeSessionId, oxyServices, onError]);
|
|
686
803
|
|
|
687
804
|
const logoutAllDeviceSessions = useCallback(async (): Promise<void> => {
|
|
688
805
|
if (!activeSessionId) throw new Error('No active session');
|
|
689
806
|
|
|
690
807
|
try {
|
|
691
808
|
await oxyServices.logoutAllDeviceSessions(activeSessionId);
|
|
692
|
-
|
|
693
|
-
// Clear all local sessions since we logged out from all devices
|
|
694
|
-
setSessions([]);
|
|
809
|
+
updateSessions([], false);
|
|
695
810
|
setActiveSessionId(null);
|
|
696
811
|
logoutStore();
|
|
697
812
|
setMinimalUser(null);
|
|
698
813
|
await clearAllStorage();
|
|
699
|
-
|
|
700
|
-
if (onAuthStateChange) {
|
|
701
|
-
onAuthStateChange(null);
|
|
702
|
-
}
|
|
814
|
+
onAuthStateChange?.(null);
|
|
703
815
|
} catch (error) {
|
|
704
|
-
|
|
816
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to logout all device sessions';
|
|
817
|
+
onError?.({ message: errorMessage, code: 'LOGOUT_ALL_DEVICES_ERROR', status: 500 });
|
|
705
818
|
throw error;
|
|
706
819
|
}
|
|
707
|
-
}, [activeSessionId, oxyServices, logoutStore,
|
|
820
|
+
}, [activeSessionId, oxyServices, logoutStore, clearAllStorage, onAuthStateChange, onError]);
|
|
708
821
|
|
|
709
822
|
const updateDeviceName = useCallback(async (deviceName: string): Promise<void> => {
|
|
710
823
|
if (!activeSessionId) throw new Error('No active session');
|
|
711
824
|
|
|
712
825
|
try {
|
|
713
826
|
await oxyServices.updateDeviceName(activeSessionId, deviceName);
|
|
714
|
-
|
|
715
|
-
// Update local device info
|
|
716
827
|
await DeviceManager.updateDeviceName(deviceName);
|
|
717
828
|
} catch (error) {
|
|
718
|
-
|
|
829
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to update device name';
|
|
830
|
+
onError?.({ message: errorMessage, code: 'UPDATE_DEVICE_NAME_ERROR', status: 500 });
|
|
719
831
|
throw error;
|
|
720
832
|
}
|
|
721
|
-
}, [activeSessionId, oxyServices]);
|
|
833
|
+
}, [activeSessionId, oxyServices, onError]);
|
|
722
834
|
|
|
723
835
|
// Language management method
|
|
724
836
|
const setLanguage = useCallback(async (languageId: string): Promise<void> => {
|
|
725
837
|
if (!storage) throw new Error('Storage not initialized');
|
|
726
838
|
|
|
727
839
|
try {
|
|
728
|
-
// Save language preference
|
|
729
840
|
await storage.setItem(keys.language, languageId);
|
|
730
841
|
setCurrentLanguage(languageId);
|
|
731
|
-
|
|
732
|
-
console.log(`Language changed to ${languageId}`);
|
|
733
|
-
|
|
734
|
-
// TODO: Here you can add any additional logic needed for app-wide language updates
|
|
735
|
-
// such as updating i18n configuration, refreshing translations, etc.
|
|
736
|
-
|
|
737
842
|
} catch (error) {
|
|
738
|
-
|
|
843
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to save language preference';
|
|
844
|
+
onError?.({ message: errorMessage, code: 'LANGUAGE_SAVE_ERROR', status: 500 });
|
|
739
845
|
throw error;
|
|
740
846
|
}
|
|
741
|
-
}, [storage, keys.language]);
|
|
847
|
+
}, [storage, keys.language, onError]);
|
|
742
848
|
|
|
743
849
|
// Bottom sheet control methods
|
|
744
850
|
const showBottomSheet = useCallback((screenOrConfig?: RouteName | string | { screen: RouteName | string; props?: Record<string, any> }) => {
|
|
@@ -754,9 +860,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
754
860
|
} else if (bottomSheetRef.current.present) {
|
|
755
861
|
if (__DEV__) console.log('Presenting bottom sheet');
|
|
756
862
|
bottomSheetRef.current.present();
|
|
757
|
-
} else {
|
|
863
|
+
} else if (__DEV__) {
|
|
758
864
|
console.warn('No expand or present method available on bottomSheetRef');
|
|
759
|
-
if (__DEV__) console.log('Available methods on bottomSheetRef.current:', Object.keys(bottomSheetRef.current as any));
|
|
760
865
|
}
|
|
761
866
|
|
|
762
867
|
// Then navigate to the specified screen if provided
|
|
@@ -774,10 +879,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
774
879
|
}
|
|
775
880
|
}, 100);
|
|
776
881
|
}
|
|
777
|
-
} else {
|
|
778
|
-
console.warn('bottomSheetRef is not available');
|
|
779
|
-
console.warn('To fix this, ensure you pass a bottomSheetRef to OxyProvider:');
|
|
780
|
-
console.warn('<OxyProvider baseURL="..." bottomSheetRef={yourBottomSheetRef}>');
|
|
882
|
+
} else if (__DEV__) {
|
|
883
|
+
console.warn('bottomSheetRef is not available. Pass a bottomSheetRef to OxyProvider.');
|
|
781
884
|
}
|
|
782
885
|
}, [bottomSheetRef]);
|
|
783
886
|
|
|
@@ -811,14 +914,23 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
811
914
|
if (mod && typeof mod.useFollow === 'function') {
|
|
812
915
|
return mod.useFollow(userId);
|
|
813
916
|
}
|
|
814
|
-
|
|
815
|
-
|
|
917
|
+
if (__DEV__) {
|
|
918
|
+
console.warn('useFollow module did not export a function as expected');
|
|
919
|
+
}
|
|
920
|
+
return createEmptyFollowHook()(userId);
|
|
816
921
|
} catch (e) {
|
|
817
|
-
|
|
818
|
-
|
|
922
|
+
if (__DEV__) {
|
|
923
|
+
console.warn('Failed to dynamically load useFollow hook:', e);
|
|
924
|
+
}
|
|
925
|
+
return createEmptyFollowHook()(userId);
|
|
819
926
|
}
|
|
820
927
|
};
|
|
821
928
|
|
|
929
|
+
// Compute language metadata from currentLanguage
|
|
930
|
+
const languageMetadata = useMemo(() => getLanguageMetadata(currentLanguage), [currentLanguage]);
|
|
931
|
+
const languageName = useMemo(() => getLanguageName(currentLanguage), [currentLanguage]);
|
|
932
|
+
const nativeLanguageName = useMemo(() => getNativeLanguageName(currentLanguage), [currentLanguage]);
|
|
933
|
+
|
|
822
934
|
const contextValue: OxyContextState = useMemo(() => ({
|
|
823
935
|
user,
|
|
824
936
|
minimalUser,
|
|
@@ -826,8 +938,12 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
826
938
|
activeSessionId,
|
|
827
939
|
isAuthenticated,
|
|
828
940
|
isLoading,
|
|
941
|
+
isTokenReady: tokenReady,
|
|
829
942
|
error,
|
|
830
943
|
currentLanguage,
|
|
944
|
+
currentLanguageMetadata: languageMetadata,
|
|
945
|
+
currentLanguageName: languageName,
|
|
946
|
+
currentNativeLanguageName: nativeLanguageName,
|
|
831
947
|
login,
|
|
832
948
|
logout,
|
|
833
949
|
logoutAll,
|
|
@@ -852,8 +968,12 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
852
968
|
activeSessionId,
|
|
853
969
|
isAuthenticated,
|
|
854
970
|
isLoading,
|
|
971
|
+
tokenReady,
|
|
855
972
|
error,
|
|
856
973
|
currentLanguage,
|
|
974
|
+
languageMetadata,
|
|
975
|
+
languageName,
|
|
976
|
+
nativeLanguageName,
|
|
857
977
|
login,
|
|
858
978
|
logout,
|
|
859
979
|
logoutAll,
|
|
@@ -872,13 +992,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
872
992
|
hideBottomSheet,
|
|
873
993
|
]);
|
|
874
994
|
|
|
875
|
-
//
|
|
876
|
-
if (!tokenReady) {
|
|
877
|
-
return <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
|
878
|
-
<Text>Loading authentication...</Text>
|
|
879
|
-
</View>;
|
|
880
|
-
}
|
|
881
|
-
|
|
995
|
+
// Always render children - let the consuming app decide how to handle token loading state
|
|
882
996
|
return (
|
|
883
997
|
<OxyContext.Provider value={contextValue}>
|
|
884
998
|
{children}
|
|
@@ -899,3 +1013,4 @@ export const useOxy = (): OxyContextState => {
|
|
|
899
1013
|
};
|
|
900
1014
|
|
|
901
1015
|
export default OxyContext;
|
|
1016
|
+
|