@oxyhq/services 22.12.4 → 22.13.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 +16 -46
- package/lib/commonjs/index.js +18 -12
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/boot/runProviderColdBoot.js +51 -9
- package/lib/commonjs/ui/boot/runProviderColdBoot.js.map +1 -1
- package/lib/commonjs/ui/client.js +0 -13
- package/lib/commonjs/ui/client.js.map +1 -1
- package/lib/commonjs/ui/components/OxyAccountDialogScreen.js +27 -4
- package/lib/commonjs/ui/components/OxyAccountDialogScreen.js.map +1 -1
- package/lib/commonjs/ui/components/OxyAuthChooser.js +72 -1123
- package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/commonjs/ui/components/OxyConsentScreen.js +50 -12
- package/lib/commonjs/ui/components/OxyConsentScreen.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +4 -4
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/OxySignInButton.js +74 -40
- package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js +527 -0
- package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/SignInEntryView.js +90 -0
- package/lib/commonjs/ui/components/authChooser/SignInEntryView.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/SignInRequestView.js +144 -0
- package/lib/commonjs/ui/components/authChooser/SignInRequestView.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/SignUpView.js +188 -0
- package/lib/commonjs/ui/components/authChooser/SignUpView.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/TroubleDisclosure.js +63 -0
- package/lib/commonjs/ui/components/authChooser/TroubleDisclosure.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/primitives.js +169 -0
- package/lib/commonjs/ui/components/authChooser/primitives.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/requestSurfaces.js +149 -0
- package/lib/commonjs/ui/components/authChooser/requestSurfaces.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/signInProgress.js +54 -0
- package/lib/commonjs/ui/components/authChooser/signInProgress.js.map +1 -0
- package/lib/commonjs/ui/components/{oxyAuthChooserStyles.js → authChooser/styles.js} +19 -1
- package/lib/commonjs/ui/components/authChooser/styles.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/types.js +59 -0
- package/lib/commonjs/ui/components/authChooser/types.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/useUsernameAvailability.js +50 -0
- package/lib/commonjs/ui/components/authChooser/useUsernameAvailability.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +205 -30
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/commitSessionFlow.js +46 -0
- package/lib/commonjs/ui/context/commitSessionFlow.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +15 -2
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/context/useOxyAccountGraph.js +13 -5
- package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +6 -1
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -1
- package/lib/commonjs/ui/index.js +0 -13
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/oauth/browserAuthTransport.js +170 -0
- package/lib/commonjs/ui/oauth/browserAuthTransport.js.map +1 -0
- package/lib/commonjs/ui/oauth/completeOAuthCode.js +78 -0
- package/lib/commonjs/ui/oauth/completeOAuthCode.js.map +1 -0
- package/lib/commonjs/ui/oauth/legacyRedirectLanes.js +48 -0
- package/lib/commonjs/ui/oauth/legacyRedirectLanes.js.map +1 -0
- package/lib/commonjs/ui/oauth/oauthHandshake.js +53 -0
- package/lib/commonjs/ui/oauth/oauthHandshake.js.map +1 -0
- package/lib/commonjs/ui/oauth/oauthPopup.js +276 -0
- package/lib/commonjs/ui/oauth/oauthPopup.js.map +1 -0
- package/lib/commonjs/ui/oauth/oauthPopupMessages.js +141 -0
- package/lib/commonjs/ui/oauth/oauthPopupMessages.js.map +1 -0
- package/lib/commonjs/ui/oauth/types.js +2 -0
- package/lib/commonjs/ui/oauth/types.js.map +1 -0
- package/lib/commonjs/ui/server.js +2 -5
- package/lib/commonjs/ui/server.js.map +1 -1
- package/lib/commonjs/ui/session/authStore.js +6 -75
- package/lib/commonjs/ui/session/authStore.js.map +1 -1
- package/lib/commonjs/ui/session/createSessionClient.js +11 -3
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
- package/lib/commonjs/ui/session/identityBinding.js +95 -0
- package/lib/commonjs/ui/session/identityBinding.js.map +1 -0
- package/lib/commonjs/ui/session/index.js +13 -0
- package/lib/commonjs/ui/session/index.js.map +1 -1
- package/lib/commonjs/ui/session/nativeSecureStorage.js +90 -0
- package/lib/commonjs/ui/session/nativeSecureStorage.js.map +1 -0
- package/lib/commonjs/ui/session/tokenTransport.js +11 -1
- package/lib/commonjs/ui/session/tokenTransport.js.map +1 -1
- package/lib/commonjs/ui/utils/crossOriginRestore.js +5 -4
- package/lib/commonjs/ui/utils/crossOriginRestore.js.map +1 -1
- package/lib/commonjs/ui/utils/deliveryPlatform.js +58 -0
- package/lib/commonjs/ui/utils/deliveryPlatform.js.map +1 -0
- package/lib/commonjs/ui/utils/oauthReturn.js +72 -46
- package/lib/commonjs/ui/utils/oauthReturn.js.map +1 -1
- package/lib/module/index.js +9 -4
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/boot/runProviderColdBoot.js +51 -9
- package/lib/module/ui/boot/runProviderColdBoot.js.map +1 -1
- package/lib/module/ui/client.js +0 -1
- package/lib/module/ui/client.js.map +1 -1
- package/lib/module/ui/components/OxyAccountDialogScreen.js +27 -4
- package/lib/module/ui/components/OxyAccountDialogScreen.js.map +1 -1
- package/lib/module/ui/components/OxyAuthChooser.js +76 -1128
- package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/module/ui/components/OxyConsentScreen.js +50 -12
- package/lib/module/ui/components/OxyConsentScreen.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +4 -4
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/OxySignInButton.js +76 -42
- package/lib/module/ui/components/OxySignInButton.js.map +1 -1
- package/lib/module/ui/components/authChooser/AccountsMenuView.js +523 -0
- package/lib/module/ui/components/authChooser/AccountsMenuView.js.map +1 -0
- package/lib/module/ui/components/authChooser/SignInEntryView.js +85 -0
- package/lib/module/ui/components/authChooser/SignInEntryView.js.map +1 -0
- package/lib/module/ui/components/authChooser/SignInRequestView.js +139 -0
- package/lib/module/ui/components/authChooser/SignInRequestView.js.map +1 -0
- package/lib/module/ui/components/authChooser/SignUpView.js +184 -0
- package/lib/module/ui/components/authChooser/SignUpView.js.map +1 -0
- package/lib/module/ui/components/authChooser/TroubleDisclosure.js +59 -0
- package/lib/module/ui/components/authChooser/TroubleDisclosure.js.map +1 -0
- package/lib/module/ui/components/authChooser/primitives.js +163 -0
- package/lib/module/ui/components/authChooser/primitives.js.map +1 -0
- package/lib/module/ui/components/authChooser/requestSurfaces.js +139 -0
- package/lib/module/ui/components/authChooser/requestSurfaces.js.map +1 -0
- package/lib/module/ui/components/authChooser/signInProgress.js +50 -0
- package/lib/module/ui/components/authChooser/signInProgress.js.map +1 -0
- package/lib/module/ui/components/{oxyAuthChooserStyles.js → authChooser/styles.js} +19 -1
- package/lib/module/ui/components/authChooser/styles.js.map +1 -0
- package/lib/module/ui/components/authChooser/types.js +55 -0
- package/lib/module/ui/components/authChooser/types.js.map +1 -0
- package/lib/module/ui/components/authChooser/useUsernameAvailability.js +46 -0
- package/lib/module/ui/components/authChooser/useUsernameAvailability.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +208 -33
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/commitSessionFlow.js +45 -0
- package/lib/module/ui/context/commitSessionFlow.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +15 -2
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/context/useOxyAccountGraph.js +13 -5
- package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
- package/lib/module/ui/hooks/useSwitchableAccounts.js +6 -1
- package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -1
- package/lib/module/ui/index.js +0 -1
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/oauth/browserAuthTransport.js +167 -0
- package/lib/module/ui/oauth/browserAuthTransport.js.map +1 -0
- package/lib/module/ui/oauth/completeOAuthCode.js +75 -0
- package/lib/module/ui/oauth/completeOAuthCode.js.map +1 -0
- package/lib/module/ui/oauth/legacyRedirectLanes.js +44 -0
- package/lib/module/ui/oauth/legacyRedirectLanes.js.map +1 -0
- package/lib/module/ui/oauth/oauthHandshake.js +50 -0
- package/lib/module/ui/oauth/oauthHandshake.js.map +1 -0
- package/lib/module/ui/oauth/oauthPopup.js +268 -0
- package/lib/module/ui/oauth/oauthPopup.js.map +1 -0
- package/lib/module/ui/oauth/oauthPopupMessages.js +135 -0
- package/lib/module/ui/oauth/oauthPopupMessages.js.map +1 -0
- package/lib/module/ui/oauth/types.js +2 -0
- package/lib/module/ui/oauth/types.js.map +1 -0
- package/lib/module/ui/server.js +0 -2
- package/lib/module/ui/server.js.map +1 -1
- package/lib/module/ui/session/authStore.js +5 -75
- package/lib/module/ui/session/authStore.js.map +1 -1
- package/lib/module/ui/session/createSessionClient.js +11 -3
- package/lib/module/ui/session/createSessionClient.js.map +1 -1
- package/lib/module/ui/session/identityBinding.js +89 -0
- package/lib/module/ui/session/identityBinding.js.map +1 -0
- package/lib/module/ui/session/index.js +5 -2
- package/lib/module/ui/session/index.js.map +1 -1
- package/lib/module/ui/session/nativeSecureStorage.js +88 -0
- package/lib/module/ui/session/nativeSecureStorage.js.map +1 -0
- package/lib/module/ui/session/tokenTransport.js +11 -1
- package/lib/module/ui/session/tokenTransport.js.map +1 -1
- package/lib/module/ui/utils/crossOriginRestore.js +6 -5
- package/lib/module/ui/utils/crossOriginRestore.js.map +1 -1
- package/lib/module/ui/utils/deliveryPlatform.js +55 -0
- package/lib/module/ui/utils/deliveryPlatform.js.map +1 -0
- package/lib/module/ui/utils/oauthReturn.js +73 -47
- package/lib/module/ui/utils/oauthReturn.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts +34 -2
- package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/client.d.ts +0 -1
- package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts +21 -29
- package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyConsentScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts +17 -12
- package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts +38 -0
- package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts +32 -0
- package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/SignInRequestView.d.ts +34 -0
- package/lib/typescript/commonjs/ui/components/authChooser/SignInRequestView.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/SignUpView.d.ts +34 -0
- package/lib/typescript/commonjs/ui/components/authChooser/SignUpView.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/TroubleDisclosure.d.ts +43 -0
- package/lib/typescript/commonjs/ui/components/authChooser/TroubleDisclosure.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts +55 -0
- package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/requestSurfaces.d.ts +54 -0
- package/lib/typescript/commonjs/ui/components/authChooser/requestSurfaces.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/signInProgress.d.ts +26 -0
- package/lib/typescript/commonjs/ui/components/authChooser/signInProgress.d.ts.map +1 -0
- package/lib/typescript/{module/ui/components/oxyAuthChooserStyles.d.ts → commonjs/ui/components/authChooser/styles.d.ts} +19 -1
- package/lib/typescript/commonjs/ui/components/authChooser/styles.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts +85 -0
- package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/useUsernameAvailability.d.ts +16 -0
- package/lib/typescript/commonjs/ui/components/authChooser/useUsernameAvailability.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts +40 -0
- package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +11 -2
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +44 -1
- package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts +7 -1
- package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts +0 -1
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/oauth/browserAuthTransport.d.ts +56 -0
- package/lib/typescript/commonjs/ui/oauth/browserAuthTransport.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/oauth/completeOAuthCode.d.ts +49 -0
- package/lib/typescript/commonjs/ui/oauth/completeOAuthCode.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/oauth/legacyRedirectLanes.d.ts +40 -0
- package/lib/typescript/commonjs/ui/oauth/legacyRedirectLanes.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/oauth/oauthHandshake.d.ts +50 -0
- package/lib/typescript/commonjs/ui/oauth/oauthHandshake.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/oauth/oauthPopup.d.ts +83 -0
- package/lib/typescript/commonjs/ui/oauth/oauthPopup.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/oauth/oauthPopupMessages.d.ts +67 -0
- package/lib/typescript/commonjs/ui/oauth/oauthPopupMessages.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/oauth/types.d.ts +176 -0
- package/lib/typescript/commonjs/ui/oauth/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/server.d.ts +0 -2
- package/lib/typescript/commonjs/ui/server.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/authStore.d.ts +3 -7
- package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +8 -1
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/identityBinding.d.ts +66 -0
- package/lib/typescript/commonjs/ui/session/identityBinding.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/index.d.ts +6 -2
- package/lib/typescript/commonjs/ui/session/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/nativeSecureStorage.d.ts +25 -0
- package/lib/typescript/commonjs/ui/session/nativeSecureStorage.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +8 -1
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/types/navigation.d.ts +44 -0
- package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/crossOriginRestore.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/deliveryPlatform.d.ts +33 -0
- package/lib/typescript/commonjs/ui/utils/deliveryPlatform.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/utils/oauthReturn.d.ts +30 -16
- package/lib/typescript/commonjs/ui/utils/oauthReturn.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts +34 -2
- package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts.map +1 -1
- package/lib/typescript/module/ui/client.d.ts +0 -1
- package/lib/typescript/module/ui/client.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts +21 -29
- package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyConsentScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxySignInButton.d.ts +17 -12
- package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts +38 -0
- package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts +32 -0
- package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/SignInRequestView.d.ts +34 -0
- package/lib/typescript/module/ui/components/authChooser/SignInRequestView.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/SignUpView.d.ts +34 -0
- package/lib/typescript/module/ui/components/authChooser/SignUpView.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/TroubleDisclosure.d.ts +43 -0
- package/lib/typescript/module/ui/components/authChooser/TroubleDisclosure.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/primitives.d.ts +55 -0
- package/lib/typescript/module/ui/components/authChooser/primitives.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/requestSurfaces.d.ts +54 -0
- package/lib/typescript/module/ui/components/authChooser/requestSurfaces.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/signInProgress.d.ts +26 -0
- package/lib/typescript/module/ui/components/authChooser/signInProgress.d.ts.map +1 -0
- package/lib/typescript/{commonjs/ui/components/oxyAuthChooserStyles.d.ts → module/ui/components/authChooser/styles.d.ts} +19 -1
- package/lib/typescript/module/ui/components/authChooser/styles.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/types.d.ts +85 -0
- package/lib/typescript/module/ui/components/authChooser/types.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/useUsernameAvailability.d.ts +16 -0
- package/lib/typescript/module/ui/components/authChooser/useUsernameAvailability.d.ts.map +1 -0
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/commitSessionFlow.d.ts +40 -0
- package/lib/typescript/module/ui/context/commitSessionFlow.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +11 -2
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +44 -1
- package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts +7 -1
- package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
- package/lib/typescript/module/ui/index.d.ts +0 -1
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/oauth/browserAuthTransport.d.ts +56 -0
- package/lib/typescript/module/ui/oauth/browserAuthTransport.d.ts.map +1 -0
- package/lib/typescript/module/ui/oauth/completeOAuthCode.d.ts +49 -0
- package/lib/typescript/module/ui/oauth/completeOAuthCode.d.ts.map +1 -0
- package/lib/typescript/module/ui/oauth/legacyRedirectLanes.d.ts +40 -0
- package/lib/typescript/module/ui/oauth/legacyRedirectLanes.d.ts.map +1 -0
- package/lib/typescript/module/ui/oauth/oauthHandshake.d.ts +50 -0
- package/lib/typescript/module/ui/oauth/oauthHandshake.d.ts.map +1 -0
- package/lib/typescript/module/ui/oauth/oauthPopup.d.ts +83 -0
- package/lib/typescript/module/ui/oauth/oauthPopup.d.ts.map +1 -0
- package/lib/typescript/module/ui/oauth/oauthPopupMessages.d.ts +67 -0
- package/lib/typescript/module/ui/oauth/oauthPopupMessages.d.ts.map +1 -0
- package/lib/typescript/module/ui/oauth/types.d.ts +176 -0
- package/lib/typescript/module/ui/oauth/types.d.ts.map +1 -0
- package/lib/typescript/module/ui/server.d.ts +0 -2
- package/lib/typescript/module/ui/server.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/authStore.d.ts +3 -7
- package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +8 -1
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/identityBinding.d.ts +66 -0
- package/lib/typescript/module/ui/session/identityBinding.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/index.d.ts +6 -2
- package/lib/typescript/module/ui/session/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/nativeSecureStorage.d.ts +25 -0
- package/lib/typescript/module/ui/session/nativeSecureStorage.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/tokenTransport.d.ts +8 -1
- package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/module/ui/types/navigation.d.ts +44 -0
- package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/crossOriginRestore.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/deliveryPlatform.d.ts +33 -0
- package/lib/typescript/module/ui/utils/deliveryPlatform.d.ts.map +1 -0
- package/lib/typescript/module/ui/utils/oauthReturn.d.ts +30 -16
- package/lib/typescript/module/ui/utils/oauthReturn.d.ts.map +1 -1
- package/package.json +6 -8
- package/src/index.ts +19 -5
- package/src/ui/boot/__tests__/coldBootWebAuthMode.test.ts +281 -0
- package/src/ui/boot/runProviderColdBoot.ts +83 -13
- package/src/ui/client.ts +0 -1
- package/src/ui/components/OxyAccountDialogScreen.tsx +33 -4
- package/src/ui/components/OxyAuthChooser.tsx +100 -1230
- package/src/ui/components/OxyConsentScreen.tsx +56 -13
- package/src/ui/components/OxyProvider.tsx +4 -4
- package/src/ui/components/OxySignInButton.tsx +119 -78
- package/src/ui/components/__tests__/OxyConsentScreen.test.tsx +27 -0
- package/src/ui/components/authChooser/AccountsMenuView.tsx +569 -0
- package/src/ui/components/authChooser/SignInEntryView.tsx +115 -0
- package/src/ui/components/authChooser/SignInRequestView.tsx +163 -0
- package/src/ui/components/authChooser/SignUpView.tsx +174 -0
- package/src/ui/components/authChooser/TroubleDisclosure.tsx +81 -0
- package/src/ui/components/authChooser/primitives.tsx +144 -0
- package/src/ui/components/authChooser/requestSurfaces.tsx +116 -0
- package/src/ui/components/authChooser/signInProgress.ts +54 -0
- package/src/ui/components/{oxyAuthChooserStyles.ts → authChooser/styles.ts} +18 -0
- package/src/ui/components/authChooser/types.ts +101 -0
- package/src/ui/components/authChooser/useUsernameAvailability.ts +60 -0
- package/src/ui/context/OxyContext.tsx +226 -24
- package/src/ui/context/__tests__/commitSessionFlow.test.ts +102 -0
- package/src/ui/context/commitSessionFlow.ts +64 -0
- package/src/ui/context/hooks/useAuthOperations.ts +26 -1
- package/src/ui/context/oxyContextTypes.ts +45 -1
- package/src/ui/context/useOxyAccountGraph.ts +19 -5
- package/src/ui/hooks/useSwitchableAccounts.ts +12 -1
- package/src/ui/index.ts +0 -1
- package/src/ui/oauth/__tests__/browserAuthTransport.test.ts +335 -0
- package/src/ui/oauth/__tests__/completeOAuthCode.test.ts +142 -0
- package/src/ui/oauth/__tests__/oauthPopup.test.ts +352 -0
- package/src/ui/oauth/__tests__/oauthPopupMessages.test.ts +189 -0
- package/src/ui/oauth/__tests__/sharedCompletionPath.test.ts +174 -0
- package/src/ui/oauth/browserAuthTransport.ts +193 -0
- package/src/ui/oauth/completeOAuthCode.ts +93 -0
- package/src/ui/oauth/legacyRedirectLanes.ts +43 -0
- package/src/ui/oauth/oauthHandshake.ts +74 -0
- package/src/ui/oauth/oauthPopup.ts +277 -0
- package/src/ui/oauth/oauthPopupMessages.ts +136 -0
- package/src/ui/oauth/types.ts +150 -0
- package/src/ui/server.ts +0 -2
- package/src/ui/session/__tests__/createSessionClient.test.ts +26 -0
- package/src/ui/session/__tests__/tokenTransport.test.ts +34 -0
- package/src/ui/session/authStore.ts +5 -89
- package/src/ui/session/createSessionClient.ts +15 -2
- package/src/ui/session/identityBinding.ts +116 -0
- package/src/ui/session/index.ts +6 -2
- package/src/ui/session/nativeSecureStorage.ts +98 -0
- package/src/ui/session/tokenTransport.ts +14 -1
- package/src/ui/types/navigation.ts +44 -1
- package/src/ui/utils/__tests__/crossOriginRestore.test.ts +17 -0
- package/src/ui/utils/crossOriginRestore.ts +9 -13
- package/src/ui/utils/deliveryPlatform.ts +65 -0
- package/src/ui/utils/oauthReturn.ts +79 -65
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
- package/lib/commonjs/ui/components/FontLoader.js +0 -35
- package/lib/commonjs/ui/components/FontLoader.js.map +0 -1
- package/lib/commonjs/ui/components/FontLoader.native.js +0 -88
- package/lib/commonjs/ui/components/FontLoader.native.js.map +0 -1
- package/lib/commonjs/ui/components/oxyAuthChooserStyles.js.map +0 -1
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
- package/lib/module/ui/components/FontLoader.js +0 -29
- package/lib/module/ui/components/FontLoader.js.map +0 -1
- package/lib/module/ui/components/FontLoader.native.js +0 -83
- package/lib/module/ui/components/FontLoader.native.js.map +0 -1
- package/lib/module/ui/components/oxyAuthChooserStyles.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/FontLoader.d.ts +0 -24
- package/lib/typescript/commonjs/ui/components/FontLoader.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/FontLoader.native.d.ts +0 -13
- package/lib/typescript/commonjs/ui/components/FontLoader.native.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/FontLoader.d.ts +0 -24
- package/lib/typescript/module/ui/components/FontLoader.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/FontLoader.native.d.ts +0 -13
- package/lib/typescript/module/ui/components/FontLoader.native.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts.map +0 -1
- package/src/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
- package/src/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
- package/src/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
- package/src/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
- package/src/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
- package/src/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
- package/src/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
- package/src/ui/components/FontLoader.native.tsx +0 -88
- package/src/ui/components/FontLoader.tsx +0 -27
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
} from '@oxyhq/core';
|
|
22
22
|
import {
|
|
23
23
|
KeyManager,
|
|
24
|
+
establishIdentitySession,
|
|
24
25
|
installAuthRefreshHandler,
|
|
25
26
|
startTokenRefreshScheduler,
|
|
26
27
|
createAccountDialogController,
|
|
@@ -33,7 +34,13 @@ import {
|
|
|
33
34
|
} from '../navigation/accountDialogManager';
|
|
34
35
|
import { redirectToAuthorize } from '../components/oauthNavigation';
|
|
35
36
|
import { openPasskeyHubPopup } from '../components/passkeyHubPopup';
|
|
37
|
+
import {
|
|
38
|
+
startWebOAuthSignIn,
|
|
39
|
+
type StartWebOAuthSignInOptions,
|
|
40
|
+
} from '../oauth/browserAuthTransport';
|
|
41
|
+
import type { WebOAuthSignInResult } from '../oauth/types';
|
|
36
42
|
import { isWebBrowser } from '../utils/isWebBrowser';
|
|
43
|
+
import { resolveDeliveryPlatform } from '../utils/deliveryPlatform';
|
|
37
44
|
import { runProviderColdBoot } from '../boot/runProviderColdBoot';
|
|
38
45
|
import { loadPersistedDeviceCredential } from '../utils/deviceCredential';
|
|
39
46
|
import { useAuthStore, type AuthState } from '../stores/authStore';
|
|
@@ -57,11 +64,14 @@ import { useAvatarPicker } from '../hooks/useAvatarPicker';
|
|
|
57
64
|
import { useAccountStore } from '../stores/accountStore';
|
|
58
65
|
import {
|
|
59
66
|
createSessionClient,
|
|
67
|
+
createIdentitySessionBinding,
|
|
60
68
|
createPlatformAuthStateStore,
|
|
61
69
|
deviceStateToClientSessions,
|
|
62
70
|
activeSessionIdOf,
|
|
63
71
|
activeUserOf,
|
|
64
72
|
accountIdsOf,
|
|
73
|
+
IdentityBoundSessionError,
|
|
74
|
+
type IdentitySessionBinding,
|
|
65
75
|
} from '../session';
|
|
66
76
|
import type {
|
|
67
77
|
OxyContextState,
|
|
@@ -69,7 +79,7 @@ import type {
|
|
|
69
79
|
CommitInput,
|
|
70
80
|
} from './oxyContextTypes';
|
|
71
81
|
import { DEFAULT_SESSION_VALIDITY_MS, loadUseFollowHook } from './oxyContextHelpers';
|
|
72
|
-
import { commitDeviceSetAndResolve } from './commitSessionFlow';
|
|
82
|
+
import { commitDeviceSetAndResolve, maybeSyncHubAfterCommit } from './commitSessionFlow';
|
|
73
83
|
import { runPasskeyLogin, runPasskeyRegister, runPasskeyAdd } from './passkeyFlow';
|
|
74
84
|
import {
|
|
75
85
|
isPasskeySupported,
|
|
@@ -92,6 +102,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
92
102
|
authorizeBaseUrl,
|
|
93
103
|
storageKeyPrefix = 'oxy_session',
|
|
94
104
|
clientId: clientIdProp,
|
|
105
|
+
sessionMode = 'account',
|
|
106
|
+
webAuthMode = 'redirect',
|
|
95
107
|
hubSync = true,
|
|
96
108
|
onAuthStateChange,
|
|
97
109
|
onError,
|
|
@@ -119,6 +131,23 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
119
131
|
}
|
|
120
132
|
const authStore = authStoreRef.current;
|
|
121
133
|
|
|
134
|
+
// Identity-bound session binding (`sessionMode: 'identity'`) — the platform pin
|
|
135
|
+
// store plus the memoised pinned account id every lane below binds to. Built
|
|
136
|
+
// ONCE per provider mount and ONLY in identity mode: an account-mode provider
|
|
137
|
+
// never constructs one, so `identity` is `null` there and every branch keyed on
|
|
138
|
+
// it collapses to today's behaviour.
|
|
139
|
+
//
|
|
140
|
+
// Like `oxyServices` / `baseURL`, `sessionMode` is mount-time configuration: it
|
|
141
|
+
// is read once and a later change does not re-bind the provider. Latching it
|
|
142
|
+
// this way also fails SAFE — a provider that started identity-bound can never
|
|
143
|
+
// be downgraded mid-flight into following the device's active account.
|
|
144
|
+
const identityRef = useRef<IdentitySessionBinding | null>(null);
|
|
145
|
+
if (sessionMode === 'identity' && !identityRef.current) {
|
|
146
|
+
identityRef.current = createIdentitySessionBinding();
|
|
147
|
+
}
|
|
148
|
+
const identity = identityRef.current;
|
|
149
|
+
const isIdentityBound = identity !== null;
|
|
150
|
+
|
|
122
151
|
const {
|
|
123
152
|
user,
|
|
124
153
|
isAuthenticated,
|
|
@@ -309,10 +338,65 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
309
338
|
logger('Failed to clear local state on remote sign-out', clearError);
|
|
310
339
|
});
|
|
311
340
|
},
|
|
341
|
+
// `null` for every account-mode provider — identical to omitting the
|
|
342
|
+
// option — and the pinned account id once an identity-bound provider has
|
|
343
|
+
// resolved its pin. Read through the ref so the resolver stays stable for
|
|
344
|
+
// the client's lifetime while still seeing later resolutions.
|
|
345
|
+
() => identityRef.current?.getPinnedAccountId() ?? null,
|
|
312
346
|
);
|
|
313
347
|
}
|
|
314
348
|
const { client: sessionClient, host: sessionClientHost } = sessionClientPairRef.current;
|
|
315
349
|
|
|
350
|
+
// Re-establishment lane for an identity-bound provider whose pin is missing or
|
|
351
|
+
// whose pinned account is no longer part of this device's session set (another
|
|
352
|
+
// app signed it out, or the device was re-provisioned). Adopting whichever
|
|
353
|
+
// account the device is currently switched to is exactly the drift
|
|
354
|
+
// `sessionMode: 'identity'` exists to prevent, so the only correct answer is to
|
|
355
|
+
// re-derive the session from the local identity key.
|
|
356
|
+
//
|
|
357
|
+
// Bounded on purpose: at most ONE attempt per applied device revision, and
|
|
358
|
+
// never two concurrently — a repeated failure (locked keychain, offline, no
|
|
359
|
+
// local identity) must never spin a sign-in loop against the server. Nothing
|
|
360
|
+
// awaits it, so it can never deadlock against the projection that triggers it;
|
|
361
|
+
// the `bootstrap()` below re-applies device state, whose notify re-runs the
|
|
362
|
+
// projection with the pin now resolved.
|
|
363
|
+
const identityEstablishRef = useRef<{ revision: number | null; inFlight: boolean }>({
|
|
364
|
+
revision: null,
|
|
365
|
+
inFlight: false,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const reestablishIdentitySession = useCallback(
|
|
369
|
+
(binding: IdentitySessionBinding, revision: number): void => {
|
|
370
|
+
const tracker = identityEstablishRef.current;
|
|
371
|
+
if (tracker.inFlight || tracker.revision === revision) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
tracker.revision = revision;
|
|
375
|
+
tracker.inFlight = true;
|
|
376
|
+
void (async () => {
|
|
377
|
+
const established = await establishIdentitySession({
|
|
378
|
+
oxy: oxyServices,
|
|
379
|
+
store: authStore,
|
|
380
|
+
binding: binding.binding,
|
|
381
|
+
});
|
|
382
|
+
if (!established) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
// The verify wrote a fresh pin; adopt it before re-reading device state
|
|
386
|
+
// so the notify that follows projects against the new binding.
|
|
387
|
+
await binding.refreshPinnedAccountId();
|
|
388
|
+
await sessionClient.bootstrap();
|
|
389
|
+
})()
|
|
390
|
+
.catch((establishError) => {
|
|
391
|
+
logger('Failed to re-establish the identity-bound session', establishError);
|
|
392
|
+
})
|
|
393
|
+
.finally(() => {
|
|
394
|
+
tracker.inFlight = false;
|
|
395
|
+
});
|
|
396
|
+
},
|
|
397
|
+
[oxyServices, authStore, sessionClient, logger],
|
|
398
|
+
);
|
|
399
|
+
|
|
316
400
|
// Projects `client.getState()` onto the exposed `sessions`/`activeSessionId`/
|
|
317
401
|
// `user`. Sole authority for both locally-initiated mutations (switch/logout)
|
|
318
402
|
// AND remotely-pushed `session_state` over the `device:<deviceId>` socket.
|
|
@@ -334,6 +418,13 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
334
418
|
// Last-write-wins guard: capture the revision this projection is for, then
|
|
335
419
|
// after the async profile fetch bail if a fresher state has landed.
|
|
336
420
|
const capturedRevision = state.revision;
|
|
421
|
+
// Resolve the pin BEFORE the profile fetch (memoised: one storage + keychain
|
|
422
|
+
// read per boot) so every projection below is bound on the FIRST pass over a
|
|
423
|
+
// freshly applied state. Deferring it would let a sibling app's switch render
|
|
424
|
+
// once as this client's user before the pin corrected it. `null` for every
|
|
425
|
+
// account-mode provider, where each projection falls back to the device's
|
|
426
|
+
// active account exactly as before.
|
|
427
|
+
const pinnedAccountId = identity ? await identity.ensurePinnedAccountId() : null;
|
|
337
428
|
const ids = accountIdsOf(state);
|
|
338
429
|
let users: User[] = [];
|
|
339
430
|
try {
|
|
@@ -346,16 +437,29 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
346
437
|
if (!latest || latest.revision !== capturedRevision) {
|
|
347
438
|
return;
|
|
348
439
|
}
|
|
440
|
+
if (identity && (pinnedAccountId === null || activeSessionIdOf(latest, pinnedAccountId) === null)) {
|
|
441
|
+
// Either no verified pin, or the pinned account left this device's session
|
|
442
|
+
// set. Projecting anything here would mean projecting SOMEBODY ELSE's
|
|
443
|
+
// account, so leave the current projection untouched and re-derive the
|
|
444
|
+
// session from the local identity key instead.
|
|
445
|
+
reestablishIdentitySession(identity, latest.revision);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
349
448
|
const usersById = new Map(users.map((resolvedUser) => [resolvedUser.id, resolvedUser]));
|
|
350
|
-
updateSessions(deviceStateToClientSessions(latest, usersById));
|
|
351
|
-
setActiveSessionId(activeSessionIdOf(latest));
|
|
352
|
-
const activeUser = activeUserOf(latest, usersById);
|
|
449
|
+
updateSessions(deviceStateToClientSessions(latest, usersById, pinnedAccountId));
|
|
450
|
+
setActiveSessionId(activeSessionIdOf(latest, pinnedAccountId));
|
|
451
|
+
const activeUser = activeUserOf(latest, usersById, pinnedAccountId);
|
|
353
452
|
if (activeUser) {
|
|
354
453
|
loginSuccess(activeUser);
|
|
355
454
|
}
|
|
356
|
-
|
|
455
|
+
// The host's current account feeds the socket handshake and the bearer's
|
|
456
|
+
// token-account comparisons, so it tracks the PIN — never the account another
|
|
457
|
+
// app switched the device to.
|
|
458
|
+
sessionClientHost.setCurrentAccountId(pinnedAccountId ?? latest.activeAccountId);
|
|
357
459
|
}, [
|
|
460
|
+
identity,
|
|
358
461
|
oxyServices,
|
|
462
|
+
reestablishIdentitySession,
|
|
359
463
|
sessionClient,
|
|
360
464
|
sessionClientHost,
|
|
361
465
|
updateSessions,
|
|
@@ -397,6 +501,12 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
397
501
|
logoutStore,
|
|
398
502
|
setAuthState,
|
|
399
503
|
logger,
|
|
504
|
+
...(identity
|
|
505
|
+
? {
|
|
506
|
+
identityBinding: identity.binding,
|
|
507
|
+
refreshPinnedAccountId: () => identity.refreshPinnedAccountId(),
|
|
508
|
+
}
|
|
509
|
+
: {}),
|
|
400
510
|
});
|
|
401
511
|
|
|
402
512
|
// "That wasn't me": repudiating a flagged sign-in IS revoking the device
|
|
@@ -483,14 +593,21 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
483
593
|
// credential via `POST /session/device/token`) plus the proactive scheduler
|
|
484
594
|
// that refreshes ~60s before expiry and on tab-focus. Replaces the deleted
|
|
485
595
|
// services-local `inSessionTokenRefresh` module.
|
|
596
|
+
// An identity binding turns every re-mint pinned: it targets the pinned
|
|
597
|
+
// account instead of the device's active one, and its recovery arm becomes the
|
|
598
|
+
// PRIMARY-key identity sign-in rather than the cross-app shared keychain.
|
|
486
599
|
useEffect(() => {
|
|
487
|
-
const dispose = installAuthRefreshHandler({
|
|
600
|
+
const dispose = installAuthRefreshHandler({
|
|
601
|
+
oxy: oxyServices,
|
|
602
|
+
store: authStore,
|
|
603
|
+
...(identity ? { identity: identity.binding } : {}),
|
|
604
|
+
});
|
|
488
605
|
const scheduler = startTokenRefreshScheduler(oxyServices);
|
|
489
606
|
return () => {
|
|
490
607
|
scheduler.dispose();
|
|
491
608
|
dispose();
|
|
492
609
|
};
|
|
493
|
-
}, [oxyServices, authStore]);
|
|
610
|
+
}, [oxyServices, authStore, identity]);
|
|
494
611
|
|
|
495
612
|
// ── Session commit funnel ────────────────────────────────────────────────
|
|
496
613
|
// The single place a session becomes committed: plant tokens, persist the
|
|
@@ -571,21 +688,20 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
571
688
|
markAuthResolved: markAuthResolvedRef.current,
|
|
572
689
|
});
|
|
573
690
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
691
|
+
// Post-sign-in hub sync — a full-page redirect to auth.oxy.so. Gated HERE,
|
|
692
|
+
// inside the ONE commit funnel, so NO commit path (QR device flow,
|
|
693
|
+
// password/2FA through the account dialog, passkey, popup OAuth, cold
|
|
694
|
+
// boot) can bounce a `webAuthMode: 'popup'` tab to the IdP. See
|
|
695
|
+
// `maybeSyncHubAfterCommit` for the full gate list.
|
|
696
|
+
await maybeSyncHubAfterCommit({
|
|
697
|
+
activate: options.activate,
|
|
698
|
+
hubSyncRequested: options.hubSync === true,
|
|
699
|
+
hubSyncEnabled: hubSync,
|
|
700
|
+
webAuthMode,
|
|
701
|
+
syncHub: () => syncHubAfterSignIn(oxyServices, { enabled: hubSync }),
|
|
702
|
+
});
|
|
587
703
|
},
|
|
588
|
-
[oxyServices, authStore, updateSessions, setActiveSessionId, sessionClient, sessionClientHost, syncFromClient, loginSuccess, logger, hubSync],
|
|
704
|
+
[oxyServices, authStore, updateSessions, setActiveSessionId, sessionClient, sessionClientHost, syncFromClient, loginSuccess, logger, hubSync, webAuthMode],
|
|
589
705
|
);
|
|
590
706
|
const commitSessionRef = useRef(commitSession);
|
|
591
707
|
commitSessionRef.current = commitSession;
|
|
@@ -638,6 +754,33 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
638
754
|
[commitSession],
|
|
639
755
|
);
|
|
640
756
|
|
|
757
|
+
// ── Web third-party OAuth sign-in ──────────────────────────────────────────
|
|
758
|
+
// The shared operation `OxySignInButton` (and any RP-owned button) delegates
|
|
759
|
+
// to, so no consumer ever writes popup listeners or a token exchange. The
|
|
760
|
+
// transport picks popup vs redirect from `webAuthMode`; both lanes land on the
|
|
761
|
+
// SAME completion path (`completeOAuthCode`) and the same commit funnel here.
|
|
762
|
+
//
|
|
763
|
+
// `hubSync: false` is deliberate: hub sync is a full-page redirect to
|
|
764
|
+
// auth.oxy.so, which would undo the one thing popup mode exists to guarantee —
|
|
765
|
+
// that the relying party never leaves its route. It matches how the redirect
|
|
766
|
+
// lane's return leg commits (see `runProviderColdBoot`).
|
|
767
|
+
const startWebOAuthSignInForContext = useCallback(
|
|
768
|
+
(options: StartWebOAuthSignInOptions): Promise<WebOAuthSignInResult> =>
|
|
769
|
+
startWebOAuthSignIn(
|
|
770
|
+
{
|
|
771
|
+
mode: webAuthMode,
|
|
772
|
+
oxyServices,
|
|
773
|
+
clientId,
|
|
774
|
+
authorizeBaseUrl,
|
|
775
|
+
identityBound: isIdentityBound,
|
|
776
|
+
commitSession: (input) =>
|
|
777
|
+
commitSessionRef.current(input, { activate: true, hubSync: false }),
|
|
778
|
+
},
|
|
779
|
+
options,
|
|
780
|
+
),
|
|
781
|
+
[webAuthMode, oxyServices, clientId, authorizeBaseUrl, isIdentityBound],
|
|
782
|
+
);
|
|
783
|
+
|
|
641
784
|
// ── Unified account dialog ─────────────────────────────────────────────────
|
|
642
785
|
// The single account-chooser + sign-in surface. Built ONCE per provider mount
|
|
643
786
|
// and bound to the live `oxyServices` + `sessionClient` + this provider's
|
|
@@ -679,8 +822,13 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
679
822
|
accountDialogSurfaceRef.current?.dismiss();
|
|
680
823
|
}, []);
|
|
681
824
|
|
|
825
|
+
// Never built while identity-bound: the dialog exists to CHOOSE an account,
|
|
826
|
+
// and an identity-bound client has exactly one — the owner of the local
|
|
827
|
+
// identity key. Leaving it `null` (the same shape consumers see with no
|
|
828
|
+
// provider mounted) keeps its QR/polling machinery off entirely instead of
|
|
829
|
+
// relying on UI-level filtering to hide it.
|
|
682
830
|
const accountDialogControllerRef = useRef<AccountDialogController | null>(null);
|
|
683
|
-
if (!accountDialogControllerRef.current) {
|
|
831
|
+
if (!isIdentityBound && !accountDialogControllerRef.current) {
|
|
684
832
|
accountDialogControllerRef.current = createAccountDialogController({
|
|
685
833
|
oxyServices,
|
|
686
834
|
sessionClient,
|
|
@@ -714,11 +862,27 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
714
862
|
// popup concept there, and off-origin passkey sign-in isn't reachable
|
|
715
863
|
// (Commons owns the native flow).
|
|
716
864
|
openPopup: isWebBrowser() ? openPasskeyHubPopup : undefined,
|
|
865
|
+
// Which surface a sign-in starts from — a FACT only this consumer can
|
|
866
|
+
// supply (headless core never touches a platform global). It decides
|
|
867
|
+
// whether automatic delivery may take the same-device Commons deep link;
|
|
868
|
+
// without it the controller stays `'unknown'` and that route never fires.
|
|
869
|
+
platform: resolveDeliveryPlatform(),
|
|
717
870
|
});
|
|
718
871
|
}
|
|
719
872
|
const accountDialogController = accountDialogControllerRef.current;
|
|
720
873
|
|
|
721
874
|
const openAccountDialog = useCallback((view?: AccountDialogView): void => {
|
|
875
|
+
if (isIdentityBound) {
|
|
876
|
+
// No controller was built, and there is nothing to choose: this app's user
|
|
877
|
+
// is fixed to the owner of the local identity key.
|
|
878
|
+
if (__DEV__) {
|
|
879
|
+
loggerUtil.warn(
|
|
880
|
+
'openAccountDialog ignored: the session is identity-bound (sessionMode: "identity")',
|
|
881
|
+
{ component: 'OxyContext', method: 'openAccountDialog' },
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
722
886
|
const nextView = view ?? 'accounts';
|
|
723
887
|
accountDialogControllerRef.current?.setView(nextView);
|
|
724
888
|
|
|
@@ -762,7 +926,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
762
926
|
}
|
|
763
927
|
});
|
|
764
928
|
setAccountDialogOpen(true);
|
|
765
|
-
}, []);
|
|
929
|
+
}, [isIdentityBound]);
|
|
766
930
|
|
|
767
931
|
const closeAccountDialog = useCallback((): void => {
|
|
768
932
|
accountDialogControllerRef.current?.cancelSignIn();
|
|
@@ -867,24 +1031,46 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
867
1031
|
// ── Cold boot ────────────────────────────────────────────────────────────
|
|
868
1032
|
// Device-first session restore via `runProviderColdBoot` (see boot/runProviderColdBoot.ts).
|
|
869
1033
|
const runColdBoot = useCallback(async (): Promise<void> => {
|
|
1034
|
+
// Resolve the pin BEFORE any boot lane runs: `getPinnedAccountId()` is a
|
|
1035
|
+
// SYNCHRONOUS read on `SessionClient`'s apply path, and a stale `null` there
|
|
1036
|
+
// would let a pinned client behave like an account-mode one for the first
|
|
1037
|
+
// state it applies (including re-electing the device's active account).
|
|
1038
|
+
if (identity) {
|
|
1039
|
+
await identity.ensurePinnedAccountId();
|
|
1040
|
+
}
|
|
870
1041
|
await runProviderColdBoot({
|
|
871
1042
|
oxyServices,
|
|
872
1043
|
authStore,
|
|
873
1044
|
clientId: clientIdProp,
|
|
874
1045
|
authRedirectUri,
|
|
875
1046
|
authorizeBaseUrl,
|
|
1047
|
+
sessionMode,
|
|
1048
|
+
identity: identity?.binding,
|
|
1049
|
+
// Popup mode forbids the boot's automatic full-page `prompt=none` restore:
|
|
1050
|
+
// a domain with no local credential resolves signed out instead of
|
|
1051
|
+
// navigating the tab to the IdP (see `allowsAutomaticIdpRedirect`).
|
|
1052
|
+
webAuthMode,
|
|
876
1053
|
sessionClient,
|
|
877
1054
|
syncDeviceCredentialToHost,
|
|
878
1055
|
commitSession: (input, options) => commitSessionRef.current(input, options),
|
|
879
1056
|
markAuthResolved: () => markAuthResolvedRef.current(),
|
|
880
1057
|
setTokenReady,
|
|
881
1058
|
});
|
|
1059
|
+
// The boot may have (re-)established the identity session or cleared a pin
|
|
1060
|
+
// whose key no longer exists, so re-resolve rather than trusting the memo
|
|
1061
|
+
// taken before it ran.
|
|
1062
|
+
if (identity) {
|
|
1063
|
+
await identity.refreshPinnedAccountId();
|
|
1064
|
+
}
|
|
882
1065
|
}, [
|
|
883
1066
|
oxyServices,
|
|
884
1067
|
authStore,
|
|
885
1068
|
clientIdProp,
|
|
886
1069
|
authRedirectUri,
|
|
887
1070
|
authorizeBaseUrl,
|
|
1071
|
+
sessionMode,
|
|
1072
|
+
identity,
|
|
1073
|
+
webAuthMode,
|
|
888
1074
|
syncDeviceCredentialToHost,
|
|
889
1075
|
sessionClient,
|
|
890
1076
|
]);
|
|
@@ -974,8 +1160,14 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
974
1160
|
// Exposed `switchSession`: route through the server-authoritative
|
|
975
1161
|
// `SessionClient`. Resolve the target account from the current device state,
|
|
976
1162
|
// ask the server to switch, reproject, and return the now-active user.
|
|
1163
|
+
//
|
|
1164
|
+
// Identity-bound providers reject: this is the session-keyed twin of
|
|
1165
|
+
// `switchToAccount`, so leaving it live would be a bypass around the same rule.
|
|
977
1166
|
const switchSessionForContext = useCallback(
|
|
978
1167
|
async (sessionId: string): Promise<User> => {
|
|
1168
|
+
if (isIdentityBound) {
|
|
1169
|
+
throw new IdentityBoundSessionError('switchSession');
|
|
1170
|
+
}
|
|
979
1171
|
const targetAccountId = sessionClient
|
|
980
1172
|
.getState()
|
|
981
1173
|
?.accounts.find((account) => account.sessionId === sessionId)?.accountId;
|
|
@@ -990,7 +1182,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
990
1182
|
}
|
|
991
1183
|
return activeUser;
|
|
992
1184
|
},
|
|
993
|
-
[sessionClient, syncFromClient],
|
|
1185
|
+
[isIdentityBound, sessionClient, syncFromClient],
|
|
994
1186
|
);
|
|
995
1187
|
|
|
996
1188
|
// Thin passthroughs to the platform-agnostic KeyManager. NOTE: both now THROW
|
|
@@ -1021,6 +1213,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1021
1213
|
isAuthenticated,
|
|
1022
1214
|
tokenReady,
|
|
1023
1215
|
initialized,
|
|
1216
|
+
identityBound: isIdentityBound,
|
|
1024
1217
|
oxyServices,
|
|
1025
1218
|
sessionClient,
|
|
1026
1219
|
syncFromClient,
|
|
@@ -1046,6 +1239,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1046
1239
|
isPrivateApiPending,
|
|
1047
1240
|
isAuthResolved: authResolved,
|
|
1048
1241
|
isStorageReady: storage !== null,
|
|
1242
|
+
sessionMode: isIdentityBound ? 'identity' : 'account',
|
|
1243
|
+
webAuthMode,
|
|
1049
1244
|
error,
|
|
1050
1245
|
currentLanguage,
|
|
1051
1246
|
currentLanguages,
|
|
@@ -1061,6 +1256,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1061
1256
|
removePasskey,
|
|
1062
1257
|
revokeSuspiciousSignIn,
|
|
1063
1258
|
handleWebSession,
|
|
1259
|
+
startWebOAuthSignIn: startWebOAuthSignInForContext,
|
|
1064
1260
|
logout,
|
|
1065
1261
|
logoutAll,
|
|
1066
1262
|
switchSession: switchSessionForContext,
|
|
@@ -1100,6 +1296,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1100
1296
|
isPrivateApiPending,
|
|
1101
1297
|
authResolved,
|
|
1102
1298
|
storage,
|
|
1299
|
+
isIdentityBound,
|
|
1300
|
+
webAuthMode,
|
|
1103
1301
|
error,
|
|
1104
1302
|
currentLanguage,
|
|
1105
1303
|
currentLanguages,
|
|
@@ -1115,6 +1313,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1115
1313
|
removePasskey,
|
|
1116
1314
|
revokeSuspiciousSignIn,
|
|
1117
1315
|
handleWebSession,
|
|
1316
|
+
startWebOAuthSignInForContext,
|
|
1118
1317
|
logout,
|
|
1119
1318
|
logoutAll,
|
|
1120
1319
|
switchSessionForContext,
|
|
@@ -1172,6 +1371,8 @@ const LOADING_STATE: OxyContextState = {
|
|
|
1172
1371
|
isPrivateApiPending: true,
|
|
1173
1372
|
isAuthResolved: false,
|
|
1174
1373
|
isStorageReady: false,
|
|
1374
|
+
sessionMode: 'account',
|
|
1375
|
+
webAuthMode: 'redirect',
|
|
1175
1376
|
error: null,
|
|
1176
1377
|
currentLanguage: 'en-US',
|
|
1177
1378
|
currentLanguages: [],
|
|
@@ -1187,6 +1388,7 @@ const LOADING_STATE: OxyContextState = {
|
|
|
1187
1388
|
removePasskey: () => rejectMissingProvider<void>(),
|
|
1188
1389
|
revokeSuspiciousSignIn: () => rejectMissingProvider<void>(),
|
|
1189
1390
|
handleWebSession: () => rejectMissingProvider<void>(),
|
|
1391
|
+
startWebOAuthSignIn: () => rejectMissingProvider<WebOAuthSignInResult>(),
|
|
1190
1392
|
logout: () => rejectMissingProvider<void>(),
|
|
1191
1393
|
logoutAll: () => rejectMissingProvider<void>(),
|
|
1192
1394
|
switchSession: () => rejectMissingProvider<User>(),
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { User } from '@oxyhq/core';
|
|
2
2
|
import { logger } from '@oxyhq/core';
|
|
3
|
+
import type { WebAuthMode } from '../../oauth/types';
|
|
3
4
|
import {
|
|
4
5
|
commitDeviceSetAndResolve,
|
|
6
|
+
maybeSyncHubAfterCommit,
|
|
5
7
|
type CommitDeviceSetAndResolveDeps,
|
|
8
|
+
type MaybeSyncHubAfterCommitDeps,
|
|
6
9
|
} from '../commitSessionFlow';
|
|
7
10
|
|
|
8
11
|
// A resolved promise chain (addCurrentAccount -> start -> syncFromClient) settles
|
|
@@ -129,6 +132,19 @@ describe('commitDeviceSetAndResolve — cold boot (activate: false)', () => {
|
|
|
129
132
|
expect(deps.markAuthResolved).toHaveBeenCalledTimes(1);
|
|
130
133
|
});
|
|
131
134
|
|
|
135
|
+
it('falls back to the minimal commit-input user when getCurrentUser returns null', async () => {
|
|
136
|
+
const order: string[] = [];
|
|
137
|
+
const deps = buildDeps(order, {
|
|
138
|
+
getCurrentUser: jest.fn(async () => null),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await commitDeviceSetAndResolve(deps);
|
|
142
|
+
await flush();
|
|
143
|
+
|
|
144
|
+
expect(deps.loginSuccess).toHaveBeenCalledWith(makeUser('fallback'));
|
|
145
|
+
expect(deps.markAuthResolved).toHaveBeenCalledTimes(1);
|
|
146
|
+
});
|
|
147
|
+
|
|
132
148
|
it('logs (never swallows) a failing background reconcile and still resolves', async () => {
|
|
133
149
|
const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => undefined);
|
|
134
150
|
const order: string[] = [];
|
|
@@ -178,6 +194,92 @@ describe('commitDeviceSetAndResolve — cold boot (activate: false)', () => {
|
|
|
178
194
|
});
|
|
179
195
|
});
|
|
180
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Post-sign-in hub sync is a FULL-PAGE redirect to auth.oxy.so. It runs from the
|
|
199
|
+
* ONE commit funnel, so gating it here gates every commit path at once —
|
|
200
|
+
* `webAuthMode: 'popup'` must never bounce the tab, whichever path signed the
|
|
201
|
+
* user in (#691 phase 7).
|
|
202
|
+
*/
|
|
203
|
+
describe('maybeSyncHubAfterCommit — webAuthMode transport gate (#691 phase 7)', () => {
|
|
204
|
+
const buildHubDeps = (
|
|
205
|
+
overrides: Partial<MaybeSyncHubAfterCommitDeps> = {},
|
|
206
|
+
): MaybeSyncHubAfterCommitDeps => ({
|
|
207
|
+
activate: true,
|
|
208
|
+
hubSyncRequested: true,
|
|
209
|
+
hubSyncEnabled: true,
|
|
210
|
+
webAuthMode: 'redirect',
|
|
211
|
+
syncHub: jest.fn(async () => true),
|
|
212
|
+
...overrides,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("redirect mode: syncs the hub after a deliberate sign-in (unchanged)", async () => {
|
|
216
|
+
const deps = buildHubDeps();
|
|
217
|
+
|
|
218
|
+
await expect(maybeSyncHubAfterCommit(deps)).resolves.toBe(true);
|
|
219
|
+
|
|
220
|
+
expect(deps.syncHub).toHaveBeenCalledTimes(1);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('popup mode: NEVER syncs the hub, even on an otherwise-eligible sign-in', async () => {
|
|
224
|
+
const deps = buildHubDeps({ webAuthMode: 'popup' });
|
|
225
|
+
|
|
226
|
+
await expect(maybeSyncHubAfterCommit(deps)).resolves.toBe(false);
|
|
227
|
+
|
|
228
|
+
expect(deps.syncHub).not.toHaveBeenCalled();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('popup mode: no commit path can opt back in', async () => {
|
|
232
|
+
// Every commit path differs only in `activate` / `hubSyncRequested`; the
|
|
233
|
+
// transport gate outranks all of them.
|
|
234
|
+
for (const activate of [true, false]) {
|
|
235
|
+
for (const hubSyncRequested of [true, false]) {
|
|
236
|
+
const deps = buildHubDeps({ webAuthMode: 'popup', activate, hubSyncRequested });
|
|
237
|
+
await expect(maybeSyncHubAfterCommit(deps)).resolves.toBe(false);
|
|
238
|
+
expect(deps.syncHub).not.toHaveBeenCalled();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it.each(['popup', 'redirect'] as const)(
|
|
244
|
+
'never syncs on a cold-boot restore, in either mode (%s)',
|
|
245
|
+
async (webAuthMode: WebAuthMode) => {
|
|
246
|
+
const deps = buildHubDeps({ webAuthMode, activate: false });
|
|
247
|
+
|
|
248
|
+
await expect(maybeSyncHubAfterCommit(deps)).resolves.toBe(false);
|
|
249
|
+
|
|
250
|
+
expect(deps.syncHub).not.toHaveBeenCalled();
|
|
251
|
+
},
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
it('redirect mode: honours the per-commit opt-out (account switch / popup OAuth lanes)', async () => {
|
|
255
|
+
const deps = buildHubDeps({ hubSyncRequested: false });
|
|
256
|
+
|
|
257
|
+
await expect(maybeSyncHubAfterCommit(deps)).resolves.toBe(false);
|
|
258
|
+
|
|
259
|
+
expect(deps.syncHub).not.toHaveBeenCalled();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('redirect mode: honours the provider-level hubSync opt-out', async () => {
|
|
263
|
+
const deps = buildHubDeps({ hubSyncEnabled: false });
|
|
264
|
+
|
|
265
|
+
await expect(maybeSyncHubAfterCommit(deps)).resolves.toBe(false);
|
|
266
|
+
|
|
267
|
+
expect(deps.syncHub).not.toHaveBeenCalled();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('redirect mode: a failing hub sync is non-fatal and never rejects', async () => {
|
|
271
|
+
const deps = buildHubDeps({
|
|
272
|
+
syncHub: jest.fn(async () => {
|
|
273
|
+
throw new Error('hub ticket failed');
|
|
274
|
+
}),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await expect(maybeSyncHubAfterCommit(deps)).resolves.toBe(false);
|
|
278
|
+
|
|
279
|
+
expect(deps.syncHub).toHaveBeenCalledTimes(1);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
181
283
|
describe('commitDeviceSetAndResolve — deliberate sign-in (activate: true)', () => {
|
|
182
284
|
it('BLOCKS on the device-set reconcile before resolving auth (unchanged ordering)', async () => {
|
|
183
285
|
const order: string[] = [];
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { User } from '@oxyhq/core';
|
|
2
2
|
import { logger as loggerUtil } from '@oxyhq/core';
|
|
3
|
+
import { allowsAutomaticIdpRedirect } from '../oauth/legacyRedirectLanes';
|
|
4
|
+
import type { WebAuthMode } from '../oauth/types';
|
|
5
|
+
import { isWebBrowser } from '../utils/isWebBrowser';
|
|
3
6
|
import { isUnauthorizedStatus } from './oxyContextHelpers';
|
|
4
7
|
|
|
5
8
|
const LOG_CONTEXT = { component: 'OxyContext', method: 'commitSession' } as const;
|
|
@@ -98,6 +101,9 @@ export async function commitDeviceSetAndResolve(
|
|
|
98
101
|
}
|
|
99
102
|
fullUser = fallbackUser;
|
|
100
103
|
}
|
|
104
|
+
if (!fullUser) {
|
|
105
|
+
fullUser = fallbackUser;
|
|
106
|
+
}
|
|
101
107
|
if (fullUser) {
|
|
102
108
|
loginSuccess(fullUser);
|
|
103
109
|
onAuthStateChange?.(fullUser);
|
|
@@ -123,3 +129,61 @@ export async function commitDeviceSetAndResolve(
|
|
|
123
129
|
await hydrateAndResolve();
|
|
124
130
|
void reconcileDeviceSet().catch(logReconcileError);
|
|
125
131
|
}
|
|
132
|
+
|
|
133
|
+
export interface MaybeSyncHubAfterCommitDeps {
|
|
134
|
+
/** Deliberate sign-in (`true`) vs. a cold-boot / background commit (`false`). */
|
|
135
|
+
activate: boolean;
|
|
136
|
+
/** Per-commit opt-in — `commitSession`'s `options.hubSync`. */
|
|
137
|
+
hubSyncRequested: boolean;
|
|
138
|
+
/** Provider-level opt-out — `OxyProviderProps.hubSync`. */
|
|
139
|
+
hubSyncEnabled: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* The provider's web sign-in transport — `OxyProviderProps.webAuthMode`.
|
|
142
|
+
* A first-class input: `'popup'` forbids the redirect outright.
|
|
143
|
+
*/
|
|
144
|
+
webAuthMode: WebAuthMode;
|
|
145
|
+
/**
|
|
146
|
+
* Starts the full-page hop to `auth.oxy.so/sync`; resolves `true` when the
|
|
147
|
+
* navigation was actually started.
|
|
148
|
+
*/
|
|
149
|
+
syncHub: () => Promise<boolean>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Post-sign-in hub sync: plant this device's credential on the IdP hub so OTHER
|
|
154
|
+
* official origins can silently restore a session on their next cold boot.
|
|
155
|
+
*
|
|
156
|
+
* The tail of the ONE commit funnel (`commitSession`), extracted so every gate
|
|
157
|
+
* on this legacy lane lives — and is tested — in one place. Returns whether the
|
|
158
|
+
* full-page redirect was started; a failure is non-fatal and never propagates
|
|
159
|
+
* (the sign-in itself already succeeded).
|
|
160
|
+
*
|
|
161
|
+
* Four gates, all of which must pass:
|
|
162
|
+
* 1. `activate` — only a deliberate sign-in syncs; a cold-boot restore never does.
|
|
163
|
+
* 2. `hubSyncRequested` — the in-place lanes (account switch, popup OAuth) opt out.
|
|
164
|
+
* 3. `hubSyncEnabled` — the provider-level `hubSync` prop.
|
|
165
|
+
* 4. `allowsAutomaticIdpRedirect(webAuthMode)` — hub sync REPLACES the document
|
|
166
|
+
* with `auth.oxy.so`, so `webAuthMode: 'popup'` forbids it: a redirect that
|
|
167
|
+
* bounces the tab defeats the entire point of the mode, no matter which
|
|
168
|
+
* commit path produced the session (issue #691, "Cold boot and cross-domain
|
|
169
|
+
* behavior"). Redirect mode — the default and the compatibility path — is
|
|
170
|
+
* unchanged, and this whole function disappears with it in phase 7b.
|
|
171
|
+
*/
|
|
172
|
+
export async function maybeSyncHubAfterCommit(
|
|
173
|
+
deps: MaybeSyncHubAfterCommitDeps,
|
|
174
|
+
): Promise<boolean> {
|
|
175
|
+
const { activate, hubSyncRequested, hubSyncEnabled, webAuthMode, syncHub } = deps;
|
|
176
|
+
|
|
177
|
+
if (!activate || !hubSyncRequested || !hubSyncEnabled) return false;
|
|
178
|
+
if (!allowsAutomaticIdpRedirect(webAuthMode)) return false;
|
|
179
|
+
if (!isWebBrowser()) return false;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
return await syncHub();
|
|
183
|
+
} catch (hubError) {
|
|
184
|
+
if (__DEV__) {
|
|
185
|
+
loggerUtil.debug('Hub sync after sign-in failed (non-fatal)', LOG_CONTEXT, hubError);
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|