@oxyhq/services 16.0.0 → 18.0.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 +77 -98
- package/lib/commonjs/index.js +40 -51
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/client.js +0 -14
- package/lib/commonjs/ui/client.js.map +1 -1
- package/lib/commonjs/ui/components/OxyAccountDialog.js +742 -0
- package/lib/commonjs/ui/components/OxyAccountDialog.js.map +1 -0
- package/lib/commonjs/ui/components/OxyConsentScreen.js +461 -0
- package/lib/commonjs/ui/components/OxyConsentScreen.js.map +1 -0
- package/lib/commonjs/ui/components/OxyProvider.js +13 -6
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/OxySignInButton.js +180 -22
- package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileButton.js +25 -112
- package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
- package/lib/commonjs/ui/components/RequireOxyAuth.js +299 -0
- package/lib/commonjs/ui/components/RequireOxyAuth.js.map +1 -0
- package/lib/commonjs/ui/components/oauthNavigation.js +73 -0
- package/lib/commonjs/ui/components/oauthNavigation.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +122 -4
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +1 -1
- package/lib/commonjs/ui/hooks/useAuth.js +19 -30
- package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +37 -267
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -1
- package/lib/commonjs/ui/index.js +5 -40
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/navigation/accountDialogManager.js +91 -0
- package/lib/commonjs/ui/navigation/accountDialogManager.js.map +1 -0
- package/lib/commonjs/ui/navigation/routes.js +0 -2
- package/lib/commonjs/ui/navigation/routes.js.map +1 -1
- package/lib/commonjs/ui/screens/ManageAccountScreen.js +4 -3
- package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/session/createSessionClient.js +10 -2
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
- package/lib/module/index.js +19 -16
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/client.js +1 -2
- package/lib/module/ui/client.js.map +1 -1
- package/lib/module/ui/components/OxyAccountDialog.js +739 -0
- package/lib/module/ui/components/OxyAccountDialog.js.map +1 -0
- package/lib/module/ui/components/OxyConsentScreen.js +454 -0
- package/lib/module/ui/components/OxyConsentScreen.js.map +1 -0
- package/lib/module/ui/components/OxyProvider.js +13 -6
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/OxySignInButton.js +181 -22
- package/lib/module/ui/components/OxySignInButton.js.map +1 -1
- package/lib/module/ui/components/ProfileButton.js +26 -112
- package/lib/module/ui/components/ProfileButton.js.map +1 -1
- package/lib/module/ui/components/RequireOxyAuth.js +294 -0
- package/lib/module/ui/components/RequireOxyAuth.js.map +1 -0
- package/lib/module/ui/components/oauthNavigation.js +70 -0
- package/lib/module/ui/components/oauthNavigation.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +123 -5
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/mutations/useAccountMutations.js +1 -1
- package/lib/module/ui/hooks/useAuth.js +19 -30
- package/lib/module/ui/hooks/useAuth.js.map +1 -1
- package/lib/module/ui/hooks/useSwitchableAccounts.js +38 -267
- package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -1
- package/lib/module/ui/index.js +1 -5
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/navigation/accountDialogManager.js +81 -0
- package/lib/module/ui/navigation/accountDialogManager.js.map +1 -0
- package/lib/module/ui/navigation/routes.js +0 -2
- package/lib/module/ui/navigation/routes.js.map +1 -1
- package/lib/module/ui/screens/ManageAccountScreen.js +4 -3
- package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/module/ui/session/createSessionClient.js +10 -2
- package/lib/module/ui/session/createSessionClient.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +8 -12
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/client.d.ts +0 -4
- package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts +51 -0
- package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/OxyConsentScreen.d.ts +45 -0
- package/lib/typescript/commonjs/ui/components/OxyConsentScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts +2 -2
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts +56 -0
- package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts +11 -23
- package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts +55 -0
- package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/oauthNavigation.d.ts +36 -0
- package/lib/typescript/commonjs/ui/components/oauthNavigation.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +27 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +13 -152
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts +2 -5
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts +49 -0
- package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +9 -2
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/types/navigation.d.ts +27 -0
- package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +8 -12
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/client.d.ts +0 -4
- package/lib/typescript/module/ui/client.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts +51 -0
- package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/OxyConsentScreen.d.ts +45 -0
- package/lib/typescript/module/ui/components/OxyConsentScreen.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/OxyProvider.d.ts +2 -2
- package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxySignInButton.d.ts +56 -0
- package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ProfileButton.d.ts +11 -23
- package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts +55 -0
- package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/oauthNavigation.d.ts +36 -0
- package/lib/typescript/module/ui/components/oauthNavigation.d.ts.map +1 -0
- package/lib/typescript/module/ui/context/OxyContext.d.ts +27 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts +1 -1
- package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +13 -152
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
- package/lib/typescript/module/ui/index.d.ts +2 -5
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts +49 -0
- package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts.map +1 -0
- package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts +1 -1
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +9 -2
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/module/ui/types/navigation.d.ts +27 -0
- package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +33 -27
- package/src/ui/client.ts +0 -4
- package/src/ui/components/OxyAccountDialog.tsx +735 -0
- package/src/ui/components/OxyConsentScreen.tsx +456 -0
- package/src/ui/components/OxyProvider.tsx +14 -6
- package/src/ui/components/OxySignInButton.tsx +227 -22
- package/src/ui/components/ProfileButton.tsx +23 -126
- package/src/ui/components/RequireOxyAuth.tsx +266 -0
- package/src/ui/components/__tests__/OxyConsentScreen.test.tsx +130 -0
- package/src/ui/components/oauthNavigation.ts +89 -0
- package/src/ui/context/OxyContext.tsx +176 -9
- package/src/ui/context/__tests__/OxyContext.coldBoot.test.tsx +165 -0
- package/src/ui/hooks/mutations/useAccountMutations.ts +1 -1
- package/src/ui/hooks/useAuth.ts +19 -31
- package/src/ui/hooks/useSwitchableAccounts.ts +42 -376
- package/src/ui/index.ts +2 -5
- package/src/ui/navigation/accountDialogManager.ts +84 -0
- package/src/ui/navigation/routes.ts +0 -4
- package/src/ui/screens/ManageAccountScreen.tsx +3 -2
- package/src/ui/session/createSessionClient.ts +9 -1
- package/src/ui/types/navigation.ts +27 -0
- package/lib/commonjs/ui/components/AccountMenu.js +0 -598
- package/lib/commonjs/ui/components/AccountMenu.js.map +0 -1
- package/lib/commonjs/ui/components/AccountMenuButton.js +0 -135
- package/lib/commonjs/ui/components/AccountMenuButton.js.map +0 -1
- package/lib/commonjs/ui/components/AccountSwitcher.js +0 -780
- package/lib/commonjs/ui/components/AccountSwitcher.js.map +0 -1
- package/lib/commonjs/ui/components/AnotherDeviceQR.js +0 -116
- package/lib/commonjs/ui/components/AnotherDeviceQR.js.map +0 -1
- package/lib/commonjs/ui/components/ProfileMenu.js +0 -518
- package/lib/commonjs/ui/components/ProfileMenu.js.map +0 -1
- package/lib/commonjs/ui/components/SignInAccountChooser.js +0 -189
- package/lib/commonjs/ui/components/SignInAccountChooser.js.map +0 -1
- package/lib/commonjs/ui/components/SignInModal.js +0 -600
- package/lib/commonjs/ui/components/SignInModal.js.map +0 -1
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js +0 -501
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +0 -1
- package/lib/commonjs/ui/hooks/usePasswordSignIn.js +0 -170
- package/lib/commonjs/ui/hooks/usePasswordSignIn.js.map +0 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +0 -51
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +0 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +0 -312
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +0 -1
- package/lib/commonjs/utils/deviceFlowSignIn.js +0 -109
- package/lib/commonjs/utils/deviceFlowSignIn.js.map +0 -1
- package/lib/module/ui/components/AccountMenu.js +0 -593
- package/lib/module/ui/components/AccountMenu.js.map +0 -1
- package/lib/module/ui/components/AccountMenuButton.js +0 -130
- package/lib/module/ui/components/AccountMenuButton.js.map +0 -1
- package/lib/module/ui/components/AccountSwitcher.js +0 -774
- package/lib/module/ui/components/AccountSwitcher.js.map +0 -1
- package/lib/module/ui/components/AnotherDeviceQR.js +0 -110
- package/lib/module/ui/components/AnotherDeviceQR.js.map +0 -1
- package/lib/module/ui/components/ProfileMenu.js +0 -514
- package/lib/module/ui/components/ProfileMenu.js.map +0 -1
- package/lib/module/ui/components/SignInAccountChooser.js +0 -183
- package/lib/module/ui/components/SignInAccountChooser.js.map +0 -1
- package/lib/module/ui/components/SignInModal.js +0 -591
- package/lib/module/ui/components/SignInModal.js.map +0 -1
- package/lib/module/ui/hooks/useOxyAuthSession.js +0 -495
- package/lib/module/ui/hooks/useOxyAuthSession.js.map +0 -1
- package/lib/module/ui/hooks/usePasswordSignIn.js +0 -165
- package/lib/module/ui/hooks/usePasswordSignIn.js.map +0 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +0 -48
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +0 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +0 -306
- package/lib/module/ui/screens/OxyAuthScreen.js.map +0 -1
- package/lib/module/utils/deviceFlowSignIn.js +0 -106
- package/lib/module/utils/deviceFlowSignIn.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +0 -47
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts +0 -26
- package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts +0 -46
- package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts +0 -34
- package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts +0 -49
- package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts +0 -29
- package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +0 -28
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +0 -105
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts +0 -31
- package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts +0 -11
- package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +0 -22
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +0 -1
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +0 -97
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AccountMenu.d.ts +0 -47
- package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AccountMenuButton.d.ts +0 -26
- package/lib/typescript/module/ui/components/AccountMenuButton.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AccountSwitcher.d.ts +0 -46
- package/lib/typescript/module/ui/components/AccountSwitcher.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts +0 -34
- package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/ProfileMenu.d.ts +0 -49
- package/lib/typescript/module/ui/components/ProfileMenu.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts +0 -29
- package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/SignInModal.d.ts +0 -28
- package/lib/typescript/module/ui/components/SignInModal.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +0 -105
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts +0 -31
- package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
- package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts +0 -11
- package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +0 -22
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +0 -1
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +0 -97
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +0 -1
- package/src/ui/components/AccountMenu.tsx +0 -645
- package/src/ui/components/AccountMenuButton.tsx +0 -126
- package/src/ui/components/AccountSwitcher.tsx +0 -751
- package/src/ui/components/AnotherDeviceQR.tsx +0 -119
- package/src/ui/components/ProfileMenu.tsx +0 -564
- package/src/ui/components/SignInAccountChooser.tsx +0 -162
- package/src/ui/components/SignInModal.tsx +0 -530
- package/src/ui/hooks/useOxyAuthSession.ts +0 -635
- package/src/ui/hooks/usePasswordSignIn.ts +0 -207
- package/src/ui/screens/AccountSwitcherScreen.tsx +0 -43
- package/src/ui/screens/OxyAuthScreen.tsx +0 -324
- package/src/utils/__tests__/deviceFlowSignIn.test.ts +0 -231
- package/src/utils/deviceFlowSignIn.ts +0 -152
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RequireOxyAuth — the optional signed-out gate primitive (React Native).
|
|
3
|
+
*
|
|
4
|
+
* ONE shared way for any Oxy app to opt into a signed-out gate. It is entirely
|
|
5
|
+
* OPTIONAL: a public app renders its children untouched (`prompt="off"`); an
|
|
6
|
+
* auth-required app blocks them behind a signed-out state (`prompt="hard"`); an
|
|
7
|
+
* app that mostly works signed-out but wants a nudge shows a dismissible banner
|
|
8
|
+
* (`prompt="soft"`). Every mode reuses the ONE account dialog the provider
|
|
9
|
+
* already mounts — opening the sign-in surface is always
|
|
10
|
+
* `useOxy().openAccountDialog('signin')`; there is NO second dialog.
|
|
11
|
+
*
|
|
12
|
+
* Readiness gating (CRITICAL): the gate keys on the SDK's own readiness state
|
|
13
|
+
* (`useOxy().canUsePrivateApi` / `isPrivateApiPending`), NEVER on app-local
|
|
14
|
+
* hooks. While auth is still resolving (`isPrivateApiPending`) it renders a
|
|
15
|
+
* neutral loading state so the signed-out wall never flashes before the
|
|
16
|
+
* device-first cold boot concludes (the documented cold-boot race).
|
|
17
|
+
*
|
|
18
|
+
* Styling follows the sibling `OxyAccountDialog`/`OxySignInButton` pattern —
|
|
19
|
+
* `useTheme()` + `StyleSheet` (NOT NativeWind), so the gate renders correctly in
|
|
20
|
+
* EVERY consumer, including apps that do not run NativeWind (e.g. the accounts
|
|
21
|
+
* app).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type React from 'react';
|
|
25
|
+
import { useState, type ReactNode } from 'react';
|
|
26
|
+
import { ActivityIndicator, Pressable, StyleSheet, View } from 'react-native';
|
|
27
|
+
import { useTheme } from '@oxyhq/bloom/theme';
|
|
28
|
+
import { Text } from '@oxyhq/bloom/typography';
|
|
29
|
+
import { useOxy } from '../context/OxyContext';
|
|
30
|
+
import { OxySignInButton } from './OxySignInButton';
|
|
31
|
+
import OxyLogo from './OxyLogo';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* How `RequireOxyAuth` treats a signed-out (or still-resolving) session.
|
|
35
|
+
* - `off` — always render children (public app; a no-op provided for symmetry).
|
|
36
|
+
* - `soft` — render children, plus a dismissible sign-in banner when signed out.
|
|
37
|
+
* - `hard` — block children behind a centered signed-out state until signed in.
|
|
38
|
+
*/
|
|
39
|
+
export type RequireOxyAuthPrompt = 'off' | 'soft' | 'hard';
|
|
40
|
+
|
|
41
|
+
export interface RequireOxyAuthProps {
|
|
42
|
+
children: ReactNode;
|
|
43
|
+
/** Gate behavior. @default 'hard' */
|
|
44
|
+
prompt?: RequireOxyAuthPrompt;
|
|
45
|
+
/** Replaces the neutral loading UI shown while auth is still resolving. */
|
|
46
|
+
loadingFallback?: ReactNode;
|
|
47
|
+
/** Replaces the entire default signed-out wall (`prompt="hard"`). */
|
|
48
|
+
signedOutFallback?: ReactNode;
|
|
49
|
+
/** Title for the default `prompt="hard"` wall. */
|
|
50
|
+
title?: string;
|
|
51
|
+
/** Subtitle for the default `prompt="hard"` wall. */
|
|
52
|
+
subtitle?: string;
|
|
53
|
+
/** Message for the `prompt="soft"` banner. */
|
|
54
|
+
bannerMessage?: string;
|
|
55
|
+
/** CTA label for the `prompt="soft"` banner. */
|
|
56
|
+
bannerActionLabel?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const DEFAULT_TITLE = 'Sign in to continue';
|
|
60
|
+
const DEFAULT_SUBTITLE = 'Sign in with your Oxy account to continue.';
|
|
61
|
+
const DEFAULT_BANNER_MESSAGE = "You're browsing signed out.";
|
|
62
|
+
const DEFAULT_BANNER_ACTION = 'Sign in';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Optional signed-out gate. Wrap any subtree (or the whole app via the
|
|
66
|
+
* provider's `requireAuth` prop) to opt into a shared, readiness-safe wall.
|
|
67
|
+
*/
|
|
68
|
+
export const RequireOxyAuth: React.FC<RequireOxyAuthProps> = ({
|
|
69
|
+
children,
|
|
70
|
+
prompt = 'hard',
|
|
71
|
+
loadingFallback,
|
|
72
|
+
signedOutFallback,
|
|
73
|
+
title,
|
|
74
|
+
subtitle,
|
|
75
|
+
bannerMessage,
|
|
76
|
+
bannerActionLabel,
|
|
77
|
+
}) => {
|
|
78
|
+
const { canUsePrivateApi, isPrivateApiPending, openAccountDialog } = useOxy();
|
|
79
|
+
|
|
80
|
+
// Public app: render straight through. Cheap enough to short-circuit before
|
|
81
|
+
// touching any gate UI.
|
|
82
|
+
if (prompt === 'off') {
|
|
83
|
+
return <>{children}</>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Signed in (and token ready): render the protected subtree in every mode.
|
|
87
|
+
if (canUsePrivateApi) {
|
|
88
|
+
return <>{children}</>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (prompt === 'soft') {
|
|
92
|
+
return (
|
|
93
|
+
<SoftGate
|
|
94
|
+
pending={isPrivateApiPending}
|
|
95
|
+
message={bannerMessage ?? DEFAULT_BANNER_MESSAGE}
|
|
96
|
+
actionLabel={bannerActionLabel ?? DEFAULT_BANNER_ACTION}
|
|
97
|
+
onSignIn={() => openAccountDialog('signin')}
|
|
98
|
+
>
|
|
99
|
+
{children}
|
|
100
|
+
</SoftGate>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// prompt === 'hard'
|
|
105
|
+
if (isPrivateApiPending) {
|
|
106
|
+
return loadingFallback ? <>{loadingFallback}</> : <NeutralLoading />;
|
|
107
|
+
}
|
|
108
|
+
if (signedOutFallback) {
|
|
109
|
+
return <>{signedOutFallback}</>;
|
|
110
|
+
}
|
|
111
|
+
return <HardWall title={title ?? DEFAULT_TITLE} subtitle={subtitle ?? DEFAULT_SUBTITLE} />;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Soft gate — children + a dismissible sign-in banner while signed out.
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
interface SoftGateProps {
|
|
119
|
+
children: ReactNode;
|
|
120
|
+
pending: boolean;
|
|
121
|
+
message: string;
|
|
122
|
+
actionLabel: string;
|
|
123
|
+
onSignIn: () => void;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const SoftGate: React.FC<SoftGateProps> = ({ children, pending, message, actionLabel, onSignIn }) => {
|
|
127
|
+
const theme = useTheme();
|
|
128
|
+
const [dismissed, setDismissed] = useState(false);
|
|
129
|
+
|
|
130
|
+
// Never surface the nudge while auth is still resolving — only once the boot
|
|
131
|
+
// has concluded signed out and the user has not dismissed it.
|
|
132
|
+
const showBanner = !pending && !dismissed;
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<View style={styles.softRoot}>
|
|
136
|
+
{showBanner ? (
|
|
137
|
+
<View style={[styles.banner, { backgroundColor: theme.colors.card, borderColor: theme.colors.border }]}>
|
|
138
|
+
<OxyLogo variant="icon" size={20} fillColor={theme.colors.primary} />
|
|
139
|
+
<Text style={[styles.bannerText, { color: theme.colors.text }]} numberOfLines={2}>
|
|
140
|
+
{message}
|
|
141
|
+
</Text>
|
|
142
|
+
<Pressable
|
|
143
|
+
onPress={onSignIn}
|
|
144
|
+
style={[styles.bannerCta, { backgroundColor: theme.colors.primary }]}
|
|
145
|
+
accessibilityRole="button"
|
|
146
|
+
accessibilityLabel={actionLabel}
|
|
147
|
+
>
|
|
148
|
+
<Text style={styles.bannerCtaText}>{actionLabel}</Text>
|
|
149
|
+
</Pressable>
|
|
150
|
+
<Pressable
|
|
151
|
+
onPress={() => setDismissed(true)}
|
|
152
|
+
style={styles.bannerDismiss}
|
|
153
|
+
accessibilityRole="button"
|
|
154
|
+
accessibilityLabel="Dismiss"
|
|
155
|
+
hitSlop={8}
|
|
156
|
+
>
|
|
157
|
+
<Text style={[styles.bannerDismissText, { color: theme.colors.textSecondary }]}>✕</Text>
|
|
158
|
+
</Pressable>
|
|
159
|
+
</View>
|
|
160
|
+
) : null}
|
|
161
|
+
<View style={styles.softBody}>{children}</View>
|
|
162
|
+
</View>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Hard wall — centered signed-out state with the "Sign in with Oxy" CTA.
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
const HardWall: React.FC<{ title: string; subtitle: string }> = ({ title, subtitle }) => {
|
|
171
|
+
const theme = useTheme();
|
|
172
|
+
return (
|
|
173
|
+
<View style={[styles.wallRoot, { backgroundColor: theme.colors.background }]}>
|
|
174
|
+
<View style={[styles.logoBadge, { backgroundColor: theme.colors.primarySubtle }]}>
|
|
175
|
+
<OxyLogo variant="icon" size={44} fillColor={theme.colors.primary} />
|
|
176
|
+
</View>
|
|
177
|
+
<Text style={[styles.wallTitle, { color: theme.colors.text }]}>{title}</Text>
|
|
178
|
+
<Text style={[styles.wallSubtitle, { color: theme.colors.textSecondary }]}>{subtitle}</Text>
|
|
179
|
+
<OxySignInButton variant="contained" style={styles.wallCta} />
|
|
180
|
+
</View>
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const NeutralLoading: React.FC = () => {
|
|
185
|
+
const theme = useTheme();
|
|
186
|
+
return (
|
|
187
|
+
<View style={[styles.wallRoot, { backgroundColor: theme.colors.background }]}>
|
|
188
|
+
<ActivityIndicator color={theme.colors.primary} />
|
|
189
|
+
</View>
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const styles = StyleSheet.create({
|
|
194
|
+
softRoot: {
|
|
195
|
+
flex: 1,
|
|
196
|
+
},
|
|
197
|
+
softBody: {
|
|
198
|
+
flex: 1,
|
|
199
|
+
},
|
|
200
|
+
banner: {
|
|
201
|
+
flexDirection: 'row',
|
|
202
|
+
alignItems: 'center',
|
|
203
|
+
gap: 10,
|
|
204
|
+
paddingVertical: 8,
|
|
205
|
+
paddingHorizontal: 12,
|
|
206
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
207
|
+
},
|
|
208
|
+
bannerText: {
|
|
209
|
+
flex: 1,
|
|
210
|
+
fontSize: 13.5,
|
|
211
|
+
},
|
|
212
|
+
bannerCta: {
|
|
213
|
+
paddingVertical: 6,
|
|
214
|
+
paddingHorizontal: 14,
|
|
215
|
+
borderRadius: 999,
|
|
216
|
+
},
|
|
217
|
+
bannerCtaText: {
|
|
218
|
+
color: '#FFFFFF',
|
|
219
|
+
fontSize: 13,
|
|
220
|
+
fontWeight: '700',
|
|
221
|
+
},
|
|
222
|
+
bannerDismiss: {
|
|
223
|
+
width: 28,
|
|
224
|
+
height: 28,
|
|
225
|
+
alignItems: 'center',
|
|
226
|
+
justifyContent: 'center',
|
|
227
|
+
},
|
|
228
|
+
bannerDismissText: {
|
|
229
|
+
fontSize: 15,
|
|
230
|
+
fontWeight: '600',
|
|
231
|
+
},
|
|
232
|
+
wallRoot: {
|
|
233
|
+
flex: 1,
|
|
234
|
+
alignItems: 'center',
|
|
235
|
+
justifyContent: 'center',
|
|
236
|
+
padding: 24,
|
|
237
|
+
gap: 16,
|
|
238
|
+
},
|
|
239
|
+
logoBadge: {
|
|
240
|
+
width: 88,
|
|
241
|
+
height: 88,
|
|
242
|
+
borderRadius: 44,
|
|
243
|
+
alignItems: 'center',
|
|
244
|
+
justifyContent: 'center',
|
|
245
|
+
marginBottom: 4,
|
|
246
|
+
},
|
|
247
|
+
wallTitle: {
|
|
248
|
+
fontSize: 24,
|
|
249
|
+
fontWeight: '800',
|
|
250
|
+
letterSpacing: -0.4,
|
|
251
|
+
textAlign: 'center',
|
|
252
|
+
},
|
|
253
|
+
wallSubtitle: {
|
|
254
|
+
fontSize: 15,
|
|
255
|
+
lineHeight: 21,
|
|
256
|
+
textAlign: 'center',
|
|
257
|
+
maxWidth: 340,
|
|
258
|
+
},
|
|
259
|
+
wallCta: {
|
|
260
|
+
marginTop: 8,
|
|
261
|
+
width: '100%',
|
|
262
|
+
maxWidth: 320,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
export default RequireOxyAuth;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `OxyConsentScreen` — the unified OAuth consent surface. These tests exercise
|
|
3
|
+
* the pure presentational contract: scopes render, privacy/terms links appear
|
|
4
|
+
* only when supplied, allow/deny fire their handlers, `busy` disables both
|
|
5
|
+
* actions, and the account badge follows the display-name rule (`displayName`
|
|
6
|
+
* else `handle`).
|
|
7
|
+
*/
|
|
8
|
+
import { render, fireEvent } from '@testing-library/react';
|
|
9
|
+
import {
|
|
10
|
+
OxyConsentScreen,
|
|
11
|
+
type OxyConsentApplication,
|
|
12
|
+
type OxyConsentScreenProps,
|
|
13
|
+
} from '../OxyConsentScreen';
|
|
14
|
+
|
|
15
|
+
// OxyLogo renders through react-native-svg, which is not meaningful (and not
|
|
16
|
+
// resolvable) under the jsdom RN stub — replace it with an inert node.
|
|
17
|
+
jest.mock('../OxyLogo', () => ({ __esModule: true, default: () => null }));
|
|
18
|
+
|
|
19
|
+
const baseApp: OxyConsentApplication = { name: 'Acme Notes' };
|
|
20
|
+
|
|
21
|
+
function renderScreen(props: Partial<OxyConsentScreenProps> = {}) {
|
|
22
|
+
const onAllow = jest.fn();
|
|
23
|
+
const onDeny = jest.fn();
|
|
24
|
+
const view = render(
|
|
25
|
+
<OxyConsentScreen
|
|
26
|
+
application={props.application ?? baseApp}
|
|
27
|
+
scopes={props.scopes ?? ['openid', 'profile']}
|
|
28
|
+
user={props.user}
|
|
29
|
+
onAllow={props.onAllow ?? onAllow}
|
|
30
|
+
onDeny={props.onDeny ?? onDeny}
|
|
31
|
+
busy={props.busy}
|
|
32
|
+
error={props.error}
|
|
33
|
+
/>,
|
|
34
|
+
);
|
|
35
|
+
return { ...view, onAllow: props.onAllow ?? onAllow, onDeny: props.onDeny ?? onDeny };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('OxyConsentScreen', () => {
|
|
39
|
+
it('renders every requested scope with a friendly label and falls back to the raw scope for unknown ones', () => {
|
|
40
|
+
const { getByTestId } = renderScreen({ scopes: ['openid', 'files:write', 'custom:thing'] });
|
|
41
|
+
|
|
42
|
+
expect(getByTestId('consent-scope-openid').textContent).toContain('Confirm your identity');
|
|
43
|
+
expect(getByTestId('consent-scope-files:write').textContent).toContain('Upload and modify your files');
|
|
44
|
+
// Unknown scope → the raw scope string is shown, never an empty row.
|
|
45
|
+
expect(getByTestId('consent-scope-custom:thing').textContent).toContain('custom:thing');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('shows the basic-permissions fallback when no scopes are requested', () => {
|
|
49
|
+
const { getByTestId, queryByTestId } = renderScreen({ scopes: [] });
|
|
50
|
+
expect(getByTestId('consent-scope-basic').textContent).toContain('Sign you in and read your basic profile');
|
|
51
|
+
expect(queryByTestId('consent-scope-openid')).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('renders the provenance line for an official application', () => {
|
|
55
|
+
const { getByTestId } = renderScreen({ application: { name: 'Oxy Console', isOfficial: true } });
|
|
56
|
+
expect(getByTestId('consent-provenance').textContent).toContain('Official Oxy application');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('renders "Published by …" provenance for a third-party app with a developer', () => {
|
|
60
|
+
const { getByTestId } = renderScreen({
|
|
61
|
+
application: { name: 'Third Party', developerName: 'Widgets Inc' },
|
|
62
|
+
});
|
|
63
|
+
expect(getByTestId('consent-provenance').textContent).toContain('Widgets Inc');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('omits privacy/terms links when the application does not supply them', () => {
|
|
67
|
+
const { queryByTestId } = renderScreen();
|
|
68
|
+
expect(queryByTestId('consent-link-privacy')).toBeNull();
|
|
69
|
+
expect(queryByTestId('consent-link-terms')).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('renders privacy and terms links when supplied (field 2b)', () => {
|
|
73
|
+
const { getByTestId } = renderScreen({
|
|
74
|
+
application: {
|
|
75
|
+
name: 'Acme Notes',
|
|
76
|
+
privacyPolicyUrl: 'https://acme.example/privacy',
|
|
77
|
+
termsUrl: 'https://acme.example/terms',
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
expect(getByTestId('consent-link-privacy')).not.toBeNull();
|
|
81
|
+
expect(getByTestId('consent-link-terms')).not.toBeNull();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('fires onAllow and onDeny from the decision buttons', () => {
|
|
85
|
+
const { getByTestId, onAllow, onDeny } = renderScreen();
|
|
86
|
+
|
|
87
|
+
fireEvent.click(getByTestId('consent-allow'));
|
|
88
|
+
expect(onAllow).toHaveBeenCalledTimes(1);
|
|
89
|
+
|
|
90
|
+
fireEvent.click(getByTestId('consent-deny'));
|
|
91
|
+
expect(onDeny).toHaveBeenCalledTimes(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('disables both actions while busy', () => {
|
|
95
|
+
const { getByTestId, onAllow, onDeny } = renderScreen({ busy: true });
|
|
96
|
+
|
|
97
|
+
const allow = getByTestId('consent-allow') as HTMLButtonElement;
|
|
98
|
+
const deny = getByTestId('consent-deny') as HTMLButtonElement;
|
|
99
|
+
expect(allow.disabled).toBe(true);
|
|
100
|
+
expect(deny.disabled).toBe(true);
|
|
101
|
+
|
|
102
|
+
// A disabled jsdom <button> does not fire click — the handlers stay untouched.
|
|
103
|
+
fireEvent.click(allow);
|
|
104
|
+
fireEvent.click(deny);
|
|
105
|
+
expect(onAllow).not.toHaveBeenCalled();
|
|
106
|
+
expect(onDeny).not.toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('renders the authorizing account using the display name when present', () => {
|
|
110
|
+
const { getByTestId } = renderScreen({
|
|
111
|
+
user: { displayName: ' Alice A ', handle: 'alice', avatarUri: 'file-1' },
|
|
112
|
+
});
|
|
113
|
+
expect(getByTestId('consent-account-name').textContent).toBe('Alice A');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('falls back to the handle when the account has no display name (D5)', () => {
|
|
117
|
+
const { getByTestId } = renderScreen({ user: { handle: 'bob' } });
|
|
118
|
+
expect(getByTestId('consent-account-name').textContent).toBe('bob');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('omits the account badge entirely when no user is supplied', () => {
|
|
122
|
+
const { queryByTestId } = renderScreen({ user: undefined });
|
|
123
|
+
expect(queryByTestId('consent-account')).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('renders a blocking error when supplied', () => {
|
|
127
|
+
const { getByTestId } = renderScreen({ error: 'This request has expired.' });
|
|
128
|
+
expect(getByTestId('consent-error').textContent).toContain('This request has expired.');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform navigation for the third-party "Sign in with Oxy" OAuth flow.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of `OxySignInButton` so the button's routing logic can be unit-tested
|
|
5
|
+
* without driving a real browser navigation (jsdom's `location.assign` is
|
|
6
|
+
* non-configurable and cannot be spied). Both entry points hand a fully-built
|
|
7
|
+
* `auth.oxy.so/authorize` URL to the platform — never FedCM, an SSO bounce, or
|
|
8
|
+
* an Oxy session cookie.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Linking } from 'react-native';
|
|
12
|
+
import { logger } from '@oxyhq/core';
|
|
13
|
+
|
|
14
|
+
/** Minimal shape of the optional `expo-web-browser` auth-session result. */
|
|
15
|
+
interface WebBrowserAuthResult {
|
|
16
|
+
type?: string;
|
|
17
|
+
url?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Minimal shape of the optional `expo-web-browser` native module we depend on. */
|
|
21
|
+
interface WebBrowserModule {
|
|
22
|
+
openAuthSessionAsync?: (url: string, redirectUrl: string) => Promise<WebBrowserAuthResult>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Outcome of opening the authorize URL on native. */
|
|
26
|
+
export interface OpenAuthorizeResult {
|
|
27
|
+
/**
|
|
28
|
+
* The deep-link URL the auth session returned to (carries `?code=…&state=…`)
|
|
29
|
+
* when `expo-web-browser` observed it, else `null`. `null` means the RP must
|
|
30
|
+
* complete the exchange from its own deep-link handler (e.g. after the
|
|
31
|
+
* `Linking.openURL` fallback, which cannot observe the return URL).
|
|
32
|
+
*/
|
|
33
|
+
redirectUrl: string | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Web: hand the TOP-LEVEL document to the OAuth authorize URL (a full-page
|
|
38
|
+
* redirect, not a popup) so the RP returns to its registered `redirect_uri`.
|
|
39
|
+
* No-op where `location` is unavailable (SSR / non-browser hosts).
|
|
40
|
+
*/
|
|
41
|
+
export function redirectToAuthorize(url: string): void {
|
|
42
|
+
(globalThis as { location?: Location }).location?.assign(url);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Native: open the authorize URL in an in-app auth session via the optional
|
|
47
|
+
* `expo-web-browser` module (`openAuthSessionAsync` returns to `redirectUri`),
|
|
48
|
+
* degrading to `Linking.openURL` when the module is not installed — the same
|
|
49
|
+
* dynamic-import-with-fallback pattern services uses for haptics/netinfo.
|
|
50
|
+
*
|
|
51
|
+
* Returns the deep-link URL the session came back to when it can be observed, so
|
|
52
|
+
* the caller can hand `?code=…&state=…` back to the RP for the token exchange.
|
|
53
|
+
*/
|
|
54
|
+
export async function openAuthorizeUrlNative(
|
|
55
|
+
url: string,
|
|
56
|
+
redirectUri: string,
|
|
57
|
+
): Promise<OpenAuthorizeResult> {
|
|
58
|
+
try {
|
|
59
|
+
const mod = (await import('expo-web-browser')) as unknown as WebBrowserModule;
|
|
60
|
+
if (mod && typeof mod.openAuthSessionAsync === 'function') {
|
|
61
|
+
const result = await mod.openAuthSessionAsync(url, redirectUri);
|
|
62
|
+
const redirectUrl =
|
|
63
|
+
result && result.type === 'success' && typeof result.url === 'string'
|
|
64
|
+
? result.url
|
|
65
|
+
: null;
|
|
66
|
+
return { redirectUrl };
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
logger.warn(
|
|
70
|
+
'OxySignInButton: expo-web-browser auth session failed; falling back to Linking.openURL',
|
|
71
|
+
{ component: 'oauthNavigation' },
|
|
72
|
+
error,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Fallback: Linking cannot observe the return URL, so the RP completes the
|
|
77
|
+
// exchange from its own deep-link handler. A rejected openURL (e.g. an
|
|
78
|
+
// unregistered scheme) must not throw out of the sign-in flow.
|
|
79
|
+
try {
|
|
80
|
+
await Linking.openURL(url);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logger.warn(
|
|
83
|
+
'OxySignInButton: Linking.openURL rejected the authorize URL',
|
|
84
|
+
{ component: 'oauthNavigation' },
|
|
85
|
+
error,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return { redirectUrl: null };
|
|
89
|
+
}
|