diff --git a/src/components/forms/HostingProvider.tsx b/src/components/forms/HostingProvider.tsx index 110090017..72164bd09 100644 --- a/src/components/forms/HostingProvider.tsx +++ b/src/components/forms/HostingProvider.tsx @@ -18,7 +18,7 @@ export function HostingProvider({ onOpenDialog, minimal, }: { - serviceUrl: string + serviceUrl?: string | undefined onSelectServiceUrl: (provider: string) => void onOpenDialog?: () => void minimal?: boolean @@ -26,6 +26,8 @@ export function HostingProvider({ const serverInputControl = useDialogControl() const t = useTheme() const {_} = useLingui() + const serviceProviderLabel = + serviceUrl === undefined ? _(msg`Automatic`) : toNiceDomain(serviceUrl) const onPressSelectService = React.useCallback(() => { Keyboard.dismiss() @@ -45,7 +47,7 @@ export function HostingProvider({ You are creating an account on ) : ( - ) : !serviceDescription ? ( + ) : !serviceDescription && serviceUrl !== undefined ? ( <> - + Connecting... diff --git a/src/screens/Login/index.tsx b/src/screens/Login/index.tsx index 601d76606..4a48d489e 100644 --- a/src/screens/Login/index.tsx +++ b/src/screens/Login/index.tsx @@ -1,14 +1,16 @@ -import React, {useRef} from 'react' +import React, {useCallback, useMemo, useRef} from 'react' import {KeyboardAvoidingView} from 'react-native' import {LayoutAnimationConfig} from 'react-native-reanimated' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import debounce from 'lodash.debounce' import {DEFAULT_SERVICE} from '#/lib/constants' import {logEvent} from '#/lib/statsig/statsig' import {logger} from '#/logger' +import {resolvePdsServiceUrl} from '#/state/queries/resolve-identity' import {useServiceQuery} from '#/state/queries/service' -import {type SessionAccount, useSession} from '#/state/session' +import {type SessionAccount, useAgent, useSession} from '#/state/session' import {useLoggedOutView} from '#/state/shell/logged-out' import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout' import {ForgotPasswordForm} from '#/screens/Login/ForgotPasswordForm' @@ -32,15 +34,17 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => { const failedAttemptCountRef = useRef(0) const startTimeRef = useRef(Date.now()) + const agent = useAgent() const {accounts} = useSession() const {requestedAccountSwitchTo} = useLoggedOutView() const requestedAccount = accounts.find( acc => acc.did === requestedAccountSwitchTo, ) + const [isResolvingService, setIsResolvingService] = React.useState(false) const [error, setError] = React.useState('') - const [serviceUrl, setServiceUrl] = React.useState( - requestedAccount?.service || DEFAULT_SERVICE, + const [serviceUrl, setServiceUrl] = React.useState( + requestedAccount?.service, ) const [initialHandle, setInitialHandle] = React.useState( requestedAccount?.handle || '', @@ -57,7 +61,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => { data: serviceDescription, error: serviceError, refetch: refetchService, - } = useServiceQuery(serviceUrl) + } = useServiceQuery(serviceUrl ?? '') const onSelectAccount = (account?: SessionAccount) => { if (account?.service) { @@ -88,6 +92,43 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => { } }, [serviceError, serviceUrl, _]) + const resolveIdentity = useCallback( + async (identifier: string) => { + setIsResolvingService(true) + + try { + const getDid = async () => { + if (identifier.startsWith('did:')) return identifier + else + return ( + await agent.resolveHandle({ + handle: identifier, + }) + ).data.did + } + + const did = (await getDid()) as `did:${string}` + const pdsUrl = await resolvePdsServiceUrl(did) + + if (pdsUrl.endsWith('.bsky.network')) { + setServiceUrl('https://bsky.social') + } else { + setServiceUrl(pdsUrl) + } + } catch (err) { + logger.error(`Service auto-resolution failed: ${err}`) + } finally { + setIsResolvingService(false) + } + }, + [agent], + ) + + const debouncedResolveService = useMemo( + () => debounce(resolveIdentity, 800), + [resolveIdentity], + ) + const onPressForgotPassword = () => { setCurrentForm(Forms.ForgotPassword) logEvent('signin:forgotPasswordPressed', {}) @@ -136,6 +177,8 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => { } onPressForgotPassword={onPressForgotPassword} onPressRetryConnect={refetchService} + debouncedResolveService={debouncedResolveService} + isResolvingService={isResolvingService} /> ) break @@ -155,7 +198,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => { content = ( void}) => { content = ( gotoForm(Forms.ForgotPassword)} onPasswordSet={() => gotoForm(Forms.PasswordUpdated)} diff --git a/src/view/com/auth/server-input/index.tsx b/src/view/com/auth/server-input/index.tsx index 9fd426a9b..568ef3aa5 100644 --- a/src/view/com/auth/server-input/index.tsx +++ b/src/view/com/auth/server-input/index.tsx @@ -13,7 +13,6 @@ import {Admonition} from '#/components/Admonition' import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import * as TextField from '#/components/forms/TextField' -import * as ToggleButton from '#/components/forms/ToggleButton' import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' import {InlineLinkText} from '#/components/Link' import {P, Text} from '#/components/Typography' @@ -29,7 +28,6 @@ export function ServerInputDialog({ const formRef = useRef(null) // persist these options between dialog open/close - const [fixedOption, setFixedOption] = useState(BSKY_SERVICE) const [previousCustomAddress, setPreviousCustomAddress] = useState('') const onClose = useCallback(() => { @@ -41,9 +39,9 @@ export function ServerInputDialog({ } } logEvent('signin:hostingProviderPressed', { - hostingProviderDidChange: fixedOption !== BSKY_SERVICE, + hostingProviderDidChange: false, // stubbed for PDS auto-resolution }) - }, [onSelect, fixedOption]) + }, [onSelect]) return ( @@ -65,13 +61,9 @@ type DialogInnerRef = {getFormState: () => string | null} function DialogInner({ formRef, - fixedOption, - setFixedOption, initialCustomAddress, }: { formRef: React.Ref - fixedOption: string - setFixedOption: (opt: string) => void initialCustomAddress: string }) { const control = Dialog.useDialogContext() @@ -88,14 +80,9 @@ function DialogInner({ formRef, () => ({ getFormState: () => { - let url - if (fixedOption === 'custom') { - url = customAddress.trim().toLowerCase() - if (!url) { - return null - } - } else { - url = fixedOption + let url = customAddress.trim().toLowerCase() + if (!url) { + return null } if (!url.startsWith('http://') && !url.startsWith('https://')) { if (url === 'localhost' || url.startsWith('localhost:')) { @@ -105,18 +92,16 @@ function DialogInner({ } } - if (fixedOption === 'custom') { - if (!pdsAddressHistory.includes(url)) { - const newHistory = [url, ...pdsAddressHistory.slice(0, 4)] - setPdsAddressHistory(newHistory) - persisted.write('pdsAddressHistory', newHistory) - } + if (!pdsAddressHistory.includes(url)) { + const newHistory = [url, ...pdsAddressHistory.slice(0, 4)] + setPdsAddressHistory(newHistory) + persisted.write('pdsAddressHistory', newHistory) } return url }, }), - [customAddress, fixedOption, pdsAddressHistory], + [customAddress, pdsAddressHistory], ) const isFirstTimeUser = accounts.length === 0 @@ -129,22 +114,8 @@ function DialogInner({ Choose your account provider - setFixedOption(values[0])}> - - {_(msg`Bluesky`)} - - - {_(msg`Custom`)} - - - - {fixedOption === BSKY_SERVICE && isFirstTimeUser && ( + + {isFirstTimeUser && ( Bluesky is an open network where you can choose your own provider. @@ -154,47 +125,45 @@ function DialogInner({ )} - {fixedOption === 'custom' && ( - - - Server address - - - - - - {pdsAddressHistory.length > 0 && ( - - {pdsAddressHistory.map(uri => ( - - ))} - - )} - - )} + + + Server address + + + + + + {pdsAddressHistory.length > 0 && ( + + {pdsAddressHistory.map(uri => ( + + ))} + + )} +