@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
|
@@ -1,12 +1,73 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import { useCallback, useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import { useCallback, useState, useEffect, useMemo, useRef } from 'react';
|
|
3
3
|
import { TouchableOpacity, Text, View, StyleSheet, type ViewStyle, type TextStyle, type StyleProp, Platform } from 'react-native';
|
|
4
|
+
import {
|
|
5
|
+
logger,
|
|
6
|
+
generatePkcePair,
|
|
7
|
+
generateOAuthState,
|
|
8
|
+
buildOAuthAuthorizeUrl,
|
|
9
|
+
type PublicApplication,
|
|
10
|
+
} from '@oxyhq/core';
|
|
4
11
|
import { useAuthStore } from '../stores/authStore';
|
|
5
12
|
import { useShallow } from 'zustand/react/shallow';
|
|
6
13
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
7
14
|
import { useOxy } from '../context/OxyContext';
|
|
8
15
|
import OxyLogo from './OxyLogo';
|
|
9
|
-
import {
|
|
16
|
+
import { subscribeToSignInModal } from '../navigation/accountDialogManager';
|
|
17
|
+
import { redirectToAuthorize, openAuthorizeUrlNative } from './oauthNavigation';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `sessionStorage` keys under which a third-party "Sign in with Oxy" OAuth flow
|
|
21
|
+
* persists its CSRF `state` and PKCE `code_verifier` across the authorize
|
|
22
|
+
* redirect. The Relying Party's redirect-URI callback reads them back to
|
|
23
|
+
* validate the returned `state` and replay the verifier on the token exchange.
|
|
24
|
+
*
|
|
25
|
+
* Web only: a browser RP navigates away to `auth.oxy.so` and back, so the
|
|
26
|
+
* handshake must survive a full-page redirect. Native completes the flow inside
|
|
27
|
+
* a single `WebBrowser` auth session and surfaces the handshake via
|
|
28
|
+
* {@link OxySignInButtonProps.onOAuthResult} instead.
|
|
29
|
+
*/
|
|
30
|
+
export const OXY_OAUTH_STATE_STORAGE_KEY = 'oxy_oauth_state';
|
|
31
|
+
export const OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY = 'oxy_oauth_code_verifier';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The OAuth handshake surfaced to a NATIVE third-party RP via
|
|
35
|
+
* {@link OxySignInButtonProps.onOAuthResult} so it can finish the code exchange
|
|
36
|
+
* (`POST /auth/oauth/token`). Web RPs read the same `state` / `code_verifier`
|
|
37
|
+
* back from `sessionStorage` across the redirect and do not need this callback.
|
|
38
|
+
*/
|
|
39
|
+
export interface OxyOAuthResult {
|
|
40
|
+
/** Deep-link URL the native auth session returned to (`?code=…&state=…`), or `null` if unobserved. */
|
|
41
|
+
redirectUrl: string | null;
|
|
42
|
+
/** The CSRF `state` sent on the authorize request; the RP must match it on return. */
|
|
43
|
+
state: string;
|
|
44
|
+
/** The PKCE `code_verifier` to replay on the token exchange. */
|
|
45
|
+
codeVerifier: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Persist the OAuth CSRF `state` + PKCE `code_verifier` for the RP callback.
|
|
50
|
+
* Returns `false` when the handshake could not be stored — no `sessionStorage`
|
|
51
|
+
* (SSR / non-browser host) or a write that threw (`SecurityError` /
|
|
52
|
+
* `QuotaExceededError`, e.g. Safari private mode) — so the caller aborts the
|
|
53
|
+
* flow cleanly rather than redirect to a callback that cannot validate `state`.
|
|
54
|
+
*/
|
|
55
|
+
function persistOAuthHandshake(state: string, codeVerifier: string): boolean {
|
|
56
|
+
const store = (globalThis as { sessionStorage?: Storage }).sessionStorage;
|
|
57
|
+
try {
|
|
58
|
+
if (!store) throw new Error('sessionStorage is unavailable');
|
|
59
|
+
store.setItem(OXY_OAUTH_STATE_STORAGE_KEY, state);
|
|
60
|
+
store.setItem(OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY, codeVerifier);
|
|
61
|
+
return true;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
logger.warn(
|
|
64
|
+
'OxySignInButton: could not persist the OAuth handshake to sessionStorage; aborting third-party sign-in',
|
|
65
|
+
{ component: 'OxySignInButton' },
|
|
66
|
+
error,
|
|
67
|
+
);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
10
71
|
|
|
11
72
|
export interface OxySignInButtonProps {
|
|
12
73
|
/**
|
|
@@ -48,6 +109,37 @@ export interface OxySignInButtonProps {
|
|
|
48
109
|
* @default false
|
|
49
110
|
*/
|
|
50
111
|
showWhenAuthenticated?: boolean;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Exact registered redirect URI the OAuth authorization code is returned to.
|
|
115
|
+
* REQUIRED only for third-party (`type: 'third_party'`) applications, which
|
|
116
|
+
* sign in via an OAuth + PKCE redirect to `auth.oxy.so`. First-party /
|
|
117
|
+
* official apps open the in-app dialog and ignore this prop. If a third-party
|
|
118
|
+
* app resolves without it, the button logs an error and does nothing (it will
|
|
119
|
+
* not invent a redirect URI).
|
|
120
|
+
*/
|
|
121
|
+
oauthRedirectUri?: string;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Native only: receives the OAuth handshake after a third-party auth session
|
|
125
|
+
* so the RP can finish the token exchange. On web the handshake is read back
|
|
126
|
+
* from `sessionStorage` across the full-page redirect, so this is not used
|
|
127
|
+
* there. A native third-party sign-in with NO `onOAuthResult` handler cannot
|
|
128
|
+
* complete (the `state` + `code_verifier` are lost) and logs a warning.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```tsx
|
|
132
|
+
* <OxySignInButton
|
|
133
|
+
* oauthRedirectUri="myapp://oauth/callback"
|
|
134
|
+
* onOAuthResult={({ redirectUrl, state, codeVerifier }) => {
|
|
135
|
+
* if (!redirectUrl) return;
|
|
136
|
+
* const code = new URL(redirectUrl).searchParams.get('code');
|
|
137
|
+
* // → POST /auth/oauth/token { code, code_verifier: codeVerifier, state }
|
|
138
|
+
* }}
|
|
139
|
+
* />
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
onOAuthResult?: (result: OxyOAuthResult) => void;
|
|
51
143
|
}
|
|
52
144
|
|
|
53
145
|
/**
|
|
@@ -82,39 +174,152 @@ export const OxySignInButton: React.FC<OxySignInButtonProps> = ({
|
|
|
82
174
|
text = 'Sign in with Oxy',
|
|
83
175
|
disabled = false,
|
|
84
176
|
showWhenAuthenticated = false,
|
|
177
|
+
oauthRedirectUri,
|
|
178
|
+
onOAuthResult,
|
|
85
179
|
}) => {
|
|
86
180
|
const theme = useTheme();
|
|
87
|
-
const {
|
|
181
|
+
const { openAccountDialog, oxyServices, clientId } = useOxy();
|
|
88
182
|
const { isAuthenticated, isLoading } = useAuthStore(
|
|
89
183
|
useShallow((state) => ({ isAuthenticated: state.isAuthenticated, isLoading: state.isLoading }))
|
|
90
184
|
);
|
|
91
|
-
// Tracks
|
|
92
|
-
// "Signing in..." while it
|
|
93
|
-
//
|
|
185
|
+
// Tracks whether the unified account dialog is open so we can show
|
|
186
|
+
// "Signing in..." while it is. The manager reports visibility on every
|
|
187
|
+
// change regardless of platform or what opened/closed it.
|
|
94
188
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
95
189
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
190
|
+
useEffect(() => subscribeToSignInModal(setIsModalOpen), []);
|
|
191
|
+
|
|
192
|
+
// The application's public identity is resolved lazily on first press and its
|
|
193
|
+
// promise cached, so rapid taps share one in-flight resolve. The cache is
|
|
194
|
+
// KEYED on the identity inputs (clientId + the oxyServices instance): if
|
|
195
|
+
// either changes the cache is invalidated and re-resolved — without a
|
|
196
|
+
// useEffect. A rejected resolve clears the cache so a later press can retry.
|
|
197
|
+
const appResolutionRef = useRef<{
|
|
198
|
+
clientId: string;
|
|
199
|
+
oxyServices: typeof oxyServices;
|
|
200
|
+
promise: Promise<PublicApplication>;
|
|
201
|
+
} | null>(null);
|
|
202
|
+
// Re-entrancy guard: a routing pass may await network + crypto before it
|
|
203
|
+
// redirects, so block a second concurrent press from racing the sessionStorage
|
|
204
|
+
// handshake against a different PKCE pair.
|
|
205
|
+
const routingRef = useRef(false);
|
|
206
|
+
|
|
207
|
+
const resolvePublicApplication = useCallback((): Promise<PublicApplication> | null => {
|
|
208
|
+
if (!clientId) return null;
|
|
209
|
+
const cached = appResolutionRef.current;
|
|
210
|
+
if (cached && cached.clientId === clientId && cached.oxyServices === oxyServices) {
|
|
211
|
+
return cached.promise;
|
|
212
|
+
}
|
|
213
|
+
const promise = oxyServices.getPublicApplication(clientId).catch((error) => {
|
|
214
|
+
// Only clear if this is still the live entry (a later resolve may have
|
|
215
|
+
// replaced it after a clientId/oxyServices change).
|
|
216
|
+
if (appResolutionRef.current?.promise === promise) {
|
|
217
|
+
appResolutionRef.current = null;
|
|
218
|
+
}
|
|
219
|
+
throw error;
|
|
220
|
+
});
|
|
221
|
+
appResolutionRef.current = { clientId, oxyServices, promise };
|
|
222
|
+
return promise;
|
|
223
|
+
}, [clientId, oxyServices]);
|
|
224
|
+
|
|
225
|
+
// Official / first-party surface: the in-app account + sign-in dialog.
|
|
226
|
+
const startOfficialSignIn = useCallback(() => {
|
|
227
|
+
openAccountDialog('signin');
|
|
228
|
+
}, [openAccountDialog]);
|
|
229
|
+
|
|
230
|
+
// Third-party surface: an OAuth 2.0 authorization-code + PKCE redirect to
|
|
231
|
+
// auth.oxy.so. No FedCM, no SSO bounce, no Oxy session cookies.
|
|
232
|
+
const startThirdPartyOAuth = useCallback(
|
|
233
|
+
async (app: PublicApplication): Promise<void> => {
|
|
234
|
+
if (!clientId) {
|
|
235
|
+
startOfficialSignIn();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (!oauthRedirectUri) {
|
|
239
|
+
logger.error(
|
|
240
|
+
'OxySignInButton: a third_party application requires the `oauthRedirectUri` prop to start the OAuth redirect; sign-in aborted',
|
|
241
|
+
undefined,
|
|
242
|
+
{ component: 'OxySignInButton', clientId, application: app.name },
|
|
243
|
+
);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const [pkce, state] = await Promise.all([generatePkcePair(), generateOAuthState()]);
|
|
248
|
+
const authorizeUrl = buildOAuthAuthorizeUrl({
|
|
249
|
+
clientId,
|
|
250
|
+
redirectUri: oauthRedirectUri,
|
|
251
|
+
state,
|
|
252
|
+
codeChallenge: pkce.codeChallenge,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
if (Platform.OS === 'web') {
|
|
256
|
+
// Persist the handshake for the RP callback, then hand the
|
|
257
|
+
// top-level document to the IdP. Without storage the callback
|
|
258
|
+
// cannot validate `state`, so abort cleanly rather than redirect.
|
|
259
|
+
if (!persistOAuthHandshake(state, pkce.codeVerifier)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
redirectToAuthorize(authorizeUrl);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Native: open the in-app auth session, then hand the handshake to the
|
|
267
|
+
// RP so it can complete the token exchange from its deep-link callback.
|
|
268
|
+
const { redirectUrl } = await openAuthorizeUrlNative(authorizeUrl, oauthRedirectUri);
|
|
269
|
+
if (onOAuthResult) {
|
|
270
|
+
onOAuthResult({ redirectUrl, state, codeVerifier: pkce.codeVerifier });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
logger.warn(
|
|
274
|
+
'OxySignInButton: native third-party sign-in cannot complete without an `onOAuthResult` handler; the code exchange is the RP\'s responsibility (state + code_verifier were not surfaced)',
|
|
275
|
+
{ component: 'OxySignInButton', application: app.name },
|
|
276
|
+
);
|
|
277
|
+
},
|
|
278
|
+
[clientId, oauthRedirectUri, onOAuthResult, startOfficialSignIn],
|
|
279
|
+
);
|
|
101
280
|
|
|
102
|
-
//
|
|
103
|
-
// -
|
|
104
|
-
// -
|
|
281
|
+
// Resolve the Application once, then route: third-party → OAuth redirect;
|
|
282
|
+
// first-party / official / unresolved → the in-app dialog. Resolution failure
|
|
283
|
+
// NEVER breaks an official app's sign-in — it falls back to the dialog.
|
|
284
|
+
const routeSignIn = useCallback(async (): Promise<void> => {
|
|
285
|
+
if (routingRef.current) return;
|
|
286
|
+
routingRef.current = true;
|
|
287
|
+
try {
|
|
288
|
+
const resolving = resolvePublicApplication();
|
|
289
|
+
if (!resolving) {
|
|
290
|
+
startOfficialSignIn();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
let app: PublicApplication;
|
|
294
|
+
try {
|
|
295
|
+
app = await resolving;
|
|
296
|
+
} catch (error) {
|
|
297
|
+
logger.warn(
|
|
298
|
+
'OxySignInButton: could not resolve the application; opening the sign-in dialog',
|
|
299
|
+
{ component: 'OxySignInButton', clientId },
|
|
300
|
+
error,
|
|
301
|
+
);
|
|
302
|
+
startOfficialSignIn();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (app.type === 'third_party' && !app.isOfficial) {
|
|
306
|
+
await startThirdPartyOAuth(app);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
startOfficialSignIn();
|
|
310
|
+
} finally {
|
|
311
|
+
routingRef.current = false;
|
|
312
|
+
}
|
|
313
|
+
}, [resolvePublicApplication, startOfficialSignIn, startThirdPartyOAuth, clientId]);
|
|
314
|
+
|
|
315
|
+
// Defer to a caller-supplied handler, otherwise route by application type.
|
|
105
316
|
const handlePress = useCallback(() => {
|
|
106
317
|
if (onPress) {
|
|
107
318
|
onPress();
|
|
108
319
|
return;
|
|
109
320
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
setIsModalOpen(true);
|
|
113
|
-
showSignInModal();
|
|
114
|
-
} else {
|
|
115
|
-
showBottomSheet?.('OxyAuth');
|
|
116
|
-
}
|
|
117
|
-
}, [onPress, showBottomSheet]);
|
|
321
|
+
void routeSignIn();
|
|
322
|
+
}, [onPress, routeSignIn]);
|
|
118
323
|
|
|
119
324
|
const themedStyles = useMemo(() => StyleSheet.create({
|
|
120
325
|
button: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import { useCallback,
|
|
2
|
+
import { useCallback, useState } from 'react';
|
|
3
3
|
import {
|
|
4
4
|
View,
|
|
5
5
|
Pressable,
|
|
@@ -14,21 +14,11 @@ import { Text } from '@oxyhq/bloom/typography';
|
|
|
14
14
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
15
15
|
import { getAccountDisplayName, getAccountFallbackHandle } from '@oxyhq/core';
|
|
16
16
|
import { useAuth } from '../hooks/useAuth';
|
|
17
|
+
import { useOxy } from '../context/OxyContext';
|
|
17
18
|
import { useI18n } from '../hooks/useI18n';
|
|
18
|
-
import ProfileMenu, { type ProfileMenuAnchor } from './ProfileMenu';
|
|
19
19
|
|
|
20
20
|
const isWeb = Platform.OS === 'web';
|
|
21
21
|
|
|
22
|
-
/**
|
|
23
|
-
* Fixed popover width used by {@link ProfileMenu} on web. `ProfileButton`
|
|
24
|
-
* mirrors this constant when computing the trigger-relative anchor so the
|
|
25
|
-
* left-edge clamp keeps the panel fully on-screen. Keep the two in sync.
|
|
26
|
-
*/
|
|
27
|
-
const MENU_WIDTH = 300;
|
|
28
|
-
|
|
29
|
-
/** Gutter, in px, kept between the popover and the viewport edges on web. */
|
|
30
|
-
const VIEWPORT_GUTTER = 8;
|
|
31
|
-
|
|
32
22
|
/**
|
|
33
23
|
* Web-only Bluesky-style hover animation timings, mirrored from
|
|
34
24
|
* `social-app/src/view/shell/desktop/LeftNav.tsx`. The row background and the
|
|
@@ -67,23 +57,15 @@ export interface ProfileButtonProps {
|
|
|
67
57
|
*/
|
|
68
58
|
avatarSize?: number;
|
|
69
59
|
/** Navigate to the "Manage account" surface (settings). */
|
|
70
|
-
onNavigateManage
|
|
60
|
+
onNavigateManage?: () => void;
|
|
71
61
|
/** Start the add-account / sign-in flow for an additional account. */
|
|
72
|
-
onAddAccount
|
|
62
|
+
onAddAccount?: () => void;
|
|
73
63
|
/** Optional: navigate to the signed-in user's own profile. */
|
|
74
64
|
onNavigateProfile?: () => void;
|
|
75
|
-
/** Called before the active identity changes so apps can clear scoped state. */
|
|
76
|
-
onBeforeSessionChange?: () => void | Promise<void>;
|
|
77
65
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* behavior.
|
|
82
|
-
* - `'down'`: the menu opens DOWNWARD, for a trigger placed at the TOP of a
|
|
83
|
-
* sidebar (the accounts app pattern).
|
|
84
|
-
* - `'auto'`: opens downward when there is more room below the trigger than
|
|
85
|
-
* above, otherwise upward.
|
|
86
|
-
* Native (bottom-sheet) is unaffected — this only influences the web popover.
|
|
66
|
+
* Retained for source compatibility. The trigger now opens the unified
|
|
67
|
+
* `OxyAccountDialog` (a centered / bottom-sheet modal) rather than an
|
|
68
|
+
* anchored popover, so popover placement no longer applies.
|
|
87
69
|
*/
|
|
88
70
|
placement?: 'up' | 'down' | 'auto';
|
|
89
71
|
/**
|
|
@@ -98,36 +80,27 @@ export interface ProfileButtonProps {
|
|
|
98
80
|
|
|
99
81
|
/**
|
|
100
82
|
* Self-contained sidebar account trigger, modeled on Bluesky's ProfileCard and
|
|
101
|
-
* the Oxy inbox's `MailboxDrawer` footer.
|
|
102
|
-
*
|
|
83
|
+
* the Oxy inbox's `MailboxDrawer` footer. Pressing it opens the unified
|
|
84
|
+
* {@link OxyAccountDialog} — the single account switcher + sign-in surface — via
|
|
85
|
+
* `useOxy().openAccountDialog`.
|
|
103
86
|
*
|
|
104
87
|
* Three auth states from {@link useAuth}:
|
|
105
88
|
* - **Undetermined** (`!isAuthResolved || isPrivateApiPending`): a neutral
|
|
106
89
|
* avatar-sized skeleton circle, no press.
|
|
107
90
|
* - **Signed in**: a pressable row — Bloom `Avatar` + display name + `@handle`
|
|
108
|
-
* +
|
|
109
|
-
*
|
|
110
|
-
*
|
|
91
|
+
* + a chevron. Press opens the dialog on its `accounts` view.
|
|
92
|
+
* - **Signed out**: a "Sign in" row that calls `useAuth().signIn()` (which opens
|
|
93
|
+
* the dialog on its `signin` view).
|
|
111
94
|
*
|
|
112
95
|
* Styling uses react-native `StyleSheet` + the Bloom theme (via `useTheme`) so
|
|
113
96
|
* the layout renders identically in EVERY consumer — including apps that do not
|
|
114
97
|
* use NativeWind (e.g. the accounts app). Only the web hover animation keeps
|
|
115
98
|
* dynamic inline `style` (the CSS transition/transform values), which is what
|
|
116
99
|
* the `react-native-web-style.d.ts` augmentation exists for.
|
|
117
|
-
*
|
|
118
|
-
* All display strings resolve through `@oxyhq/core`'s
|
|
119
|
-
* `getAccountDisplayName` / `getAccountFallbackHandle` — no hand-rolled name
|
|
120
|
-
* fallbacks. Avatars pass the bare file id as `source` + `variant="thumb"`, so
|
|
121
|
-
* the app's registered `ImageResolver` builds the thumbnail URL.
|
|
122
100
|
*/
|
|
123
101
|
const ProfileButton: React.FC<ProfileButtonProps> = ({
|
|
124
102
|
expanded = true,
|
|
125
103
|
avatarSize,
|
|
126
|
-
onNavigateManage,
|
|
127
|
-
onAddAccount,
|
|
128
|
-
onNavigateProfile,
|
|
129
|
-
onBeforeSessionChange,
|
|
130
|
-
placement = 'up',
|
|
131
104
|
className,
|
|
132
105
|
style,
|
|
133
106
|
}) => {
|
|
@@ -138,76 +111,20 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
|
|
|
138
111
|
isPrivateApiPending,
|
|
139
112
|
signIn,
|
|
140
113
|
} = useAuth();
|
|
114
|
+
const { openAccountDialog } = useOxy();
|
|
141
115
|
const { colors } = useTheme();
|
|
142
116
|
const { t, locale } = useI18n();
|
|
143
117
|
|
|
144
|
-
const [menuOpen, setMenuOpen] = useState(false);
|
|
145
|
-
const [anchor, setAnchor] = useState<ProfileMenuAnchor | null>(null);
|
|
146
|
-
|
|
147
118
|
// Web-only hover/focus tracking for the Bluesky-style reveal animation.
|
|
148
119
|
// Native has no hover, so these stay false and the row renders statically.
|
|
149
120
|
const [hovered, setHovered] = useState(false);
|
|
150
121
|
const [focused, setFocused] = useState(false);
|
|
151
122
|
|
|
152
|
-
// Trigger ref for the web anchor measurement. RN-Web exposes `measure()` on
|
|
153
|
-
// host views, so we anchor the popover to the button rect and open upward.
|
|
154
|
-
const triggerRef = useRef<View | null>(null);
|
|
155
|
-
|
|
156
123
|
const resolvedAvatarSize = avatarSize ?? (expanded ? 40 : 32);
|
|
157
124
|
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
setMenuOpen(true);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
const node = triggerRef.current;
|
|
165
|
-
if (!node || typeof node.measure !== 'function' || typeof window === 'undefined') {
|
|
166
|
-
setAnchor(null);
|
|
167
|
-
setMenuOpen(true);
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
node.measure((_x, _y, _w, height, pageX, pageY) => {
|
|
171
|
-
if (typeof pageX !== 'number' || typeof pageY !== 'number') {
|
|
172
|
-
setAnchor(null);
|
|
173
|
-
} else {
|
|
174
|
-
// Clamp the panel's left edge inside the viewport gutter.
|
|
175
|
-
const maxLeft = Math.max(
|
|
176
|
-
VIEWPORT_GUTTER,
|
|
177
|
-
window.innerWidth - MENU_WIDTH - VIEWPORT_GUTTER,
|
|
178
|
-
);
|
|
179
|
-
const left = Math.min(Math.max(VIEWPORT_GUTTER, pageX), maxLeft);
|
|
180
|
-
// Bottom edge of the trigger in page coordinates. `height` is a
|
|
181
|
-
// number for host views; guard defensively for non-web shims.
|
|
182
|
-
const triggerBottom = pageY + (typeof height === 'number' ? height : 0);
|
|
183
|
-
// Resolve `'auto'` by comparing the room below the trigger with
|
|
184
|
-
// the room above it; pick whichever direction has more space.
|
|
185
|
-
const openDown =
|
|
186
|
-
placement === 'down' ||
|
|
187
|
-
(placement === 'auto' &&
|
|
188
|
-
window.innerHeight - triggerBottom > pageY);
|
|
189
|
-
if (openDown) {
|
|
190
|
-
// Anchor the panel's TOP edge just below the trigger bottom so
|
|
191
|
-
// the menu opens downward from a top-of-sidebar trigger.
|
|
192
|
-
const top = Math.max(VIEWPORT_GUTTER, triggerBottom + VIEWPORT_GUTTER);
|
|
193
|
-
setAnchor({ left, top });
|
|
194
|
-
} else {
|
|
195
|
-
// Anchor the panel's BOTTOM edge just above the trigger top so
|
|
196
|
-
// the menu opens upward from this footer button.
|
|
197
|
-
const bottom = Math.max(
|
|
198
|
-
VIEWPORT_GUTTER,
|
|
199
|
-
window.innerHeight - pageY + VIEWPORT_GUTTER,
|
|
200
|
-
);
|
|
201
|
-
setAnchor({ left, bottom });
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
setMenuOpen(true);
|
|
205
|
-
});
|
|
206
|
-
}, [placement]);
|
|
207
|
-
|
|
208
|
-
const handleClose = useCallback(() => {
|
|
209
|
-
setMenuOpen(false);
|
|
210
|
-
}, []);
|
|
125
|
+
const openDialog = useCallback(() => {
|
|
126
|
+
openAccountDialog('accounts');
|
|
127
|
+
}, [openAccountDialog]);
|
|
211
128
|
|
|
212
129
|
// ── Undetermined: skeleton circle, no interaction. ──────────────────────
|
|
213
130
|
if (!isAuthResolved || isPrivateApiPending) {
|
|
@@ -285,10 +202,10 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
|
|
|
285
202
|
/>
|
|
286
203
|
);
|
|
287
204
|
|
|
288
|
-
// `active` combines hover
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
const active = hovered || focused
|
|
205
|
+
// `active` combines hover and keyboard focus — exactly like Bluesky's
|
|
206
|
+
// `state.hovered || state.focused`. Only web ever sets these, so on native
|
|
207
|
+
// the row renders statically.
|
|
208
|
+
const active = hovered || focused;
|
|
292
209
|
const accountLabel = t('accountSwitcher.switchWhileSignedInAs', { name: displayName })
|
|
293
210
|
|| `Switch account, signed in as ${displayName}`;
|
|
294
211
|
|
|
@@ -318,24 +235,14 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
|
|
|
318
235
|
return (
|
|
319
236
|
<View className={className} style={style}>
|
|
320
237
|
<Pressable
|
|
321
|
-
ref={triggerRef}
|
|
322
238
|
style={[styles.collapsedTrigger, collapsedBgStyle]}
|
|
323
|
-
onPress={
|
|
239
|
+
onPress={openDialog}
|
|
324
240
|
accessibilityRole="button"
|
|
325
241
|
accessibilityLabel={accountLabel}
|
|
326
242
|
{...webInteractionProps}
|
|
327
243
|
>
|
|
328
244
|
{avatarNode}
|
|
329
245
|
</Pressable>
|
|
330
|
-
<ProfileMenu
|
|
331
|
-
open={menuOpen}
|
|
332
|
-
onClose={handleClose}
|
|
333
|
-
anchor={anchor}
|
|
334
|
-
onNavigateManage={onNavigateManage}
|
|
335
|
-
onAddAccount={onAddAccount}
|
|
336
|
-
onNavigateProfile={onNavigateProfile}
|
|
337
|
-
onBeforeSessionChange={onBeforeSessionChange}
|
|
338
|
-
/>
|
|
339
246
|
</View>
|
|
340
247
|
);
|
|
341
248
|
}
|
|
@@ -402,9 +309,8 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
|
|
|
402
309
|
return (
|
|
403
310
|
<View className={className} style={[styles.fullWidth, style]}>
|
|
404
311
|
<Pressable
|
|
405
|
-
ref={triggerRef}
|
|
406
312
|
style={[styles.row, styles.rowExpanded, rowStyle]}
|
|
407
|
-
onPress={
|
|
313
|
+
onPress={openDialog}
|
|
408
314
|
accessibilityRole="button"
|
|
409
315
|
accessibilityLabel={accountLabel}
|
|
410
316
|
{...webInteractionProps}
|
|
@@ -434,15 +340,6 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
|
|
|
434
340
|
/>
|
|
435
341
|
</View>
|
|
436
342
|
</Pressable>
|
|
437
|
-
<ProfileMenu
|
|
438
|
-
open={menuOpen}
|
|
439
|
-
onClose={handleClose}
|
|
440
|
-
anchor={anchor}
|
|
441
|
-
onNavigateManage={onNavigateManage}
|
|
442
|
-
onAddAccount={onAddAccount}
|
|
443
|
-
onNavigateProfile={onNavigateProfile}
|
|
444
|
-
onBeforeSessionChange={onBeforeSessionChange}
|
|
445
|
-
/>
|
|
446
343
|
</View>
|
|
447
344
|
);
|
|
448
345
|
};
|