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
) : (
- {toNiceDomain(serviceUrl)}
+ {serviceProviderLabel}
void
@@ -59,6 +61,8 @@ export const LoginForm = ({
onPressForgotPassword: () => void
onAttemptSuccess: () => void
onAttemptFailed: () => void
+ debouncedResolveService: (identifier: string) => void
+ isResolvingService: boolean
}) => {
const t = useTheme()
const [isProcessing, setIsProcessing] = useState(false)
@@ -100,6 +104,11 @@ export const LoginForm = ({
return
}
+ if (!serviceUrl) {
+ setError(_(msg`Please enter hosting provider URL`))
+ return
+ }
+
setIsProcessing(true)
try {
@@ -182,6 +191,13 @@ export const LoginForm = ({
Hosting provider
+ {isResolvingService && (
+
+ )}
{
identifierValueRef.current = v
+ // Trigger PDS auto-resolution for handles/DIDs
+ const id = v.trim()
+ if (!id) return
+ if (
+ id.startsWith('did:') ||
+ (id.includes('.') && !id.includes('@'))
+ ) {
+ debouncedResolveService(id)
+ }
}}
onSubmitEditing={() => {
passwordRef.current?.focus()
@@ -330,9 +355,9 @@ export const LoginForm = ({
Retry
- ) : !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 => (
+
+ ))}
+
+ )}
+