From 348198380cd21b9c70212aa19b55cb9222c4dc63 Mon Sep 17 00:00:00 2001 From: Alex Benzer Date: Fri, 19 Jun 2026 13:21:03 -0700 Subject: [PATCH] Address PR review: fix Bluesky host routing --- src/lib/api/resolve-pds.ts | 12 +++++++++++- src/screens/Login/LoginForm.tsx | 16 ++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/lib/api/resolve-pds.ts b/src/lib/api/resolve-pds.ts index edcfb32e6c..50a9675b71 100644 --- a/src/lib/api/resolve-pds.ts +++ b/src/lib/api/resolve-pds.ts @@ -1,6 +1,9 @@ import {BSKY_SERVICE} from '#/lib/constants' import {Agent} from '#/state/session/agent' +// Cap the DID-doc fetch so a slow/hanging host can't keep a resolution pending. +const DID_DOC_FETCH_TIMEOUT_MS = 5e3 + /** * Resolves an atproto handle (or DID) to the user's PDS service URL. * @@ -65,8 +68,13 @@ async function fetchDidDoc(did: string): Promise { `Unsupported DID method: ${did}`, ) } + // Bound the fetch - the background query has no other timeout, so a did:web + // (or plc.directory) host that hangs would otherwise keep the resolution + // pending indefinitely. + const controller = new AbortController() + const timer = setTimeout(() => controller.abort(), DID_DOC_FETCH_TIMEOUT_MS) try { - const res = await fetch(url) + const res = await fetch(url, {signal: controller.signal}) if (!res.ok) { throw new Error(`HTTP ${res.status}`) } @@ -77,6 +85,8 @@ async function fetchDidDoc(did: string): Promise { `Failed to fetch DID doc from ${url}`, e, ) + } finally { + clearTimeout(timer) } } diff --git a/src/screens/Login/LoginForm.tsx b/src/screens/Login/LoginForm.tsx index e6bfad79d8..73f9af9ba0 100644 --- a/src/screens/Login/LoginForm.tsx +++ b/src/screens/Login/LoginForm.tsx @@ -162,8 +162,12 @@ export const LoginForm = ({ // Fire analytics on resolve success/failure. useEffect(() => { if (resolveQuery.isSuccess && resolveQuery.data) { + // Only count duration when a fetch actually ran (a cache hit leaves the + // ref at 0); reset after so a re-render can't re-emit a stale delta. + const start = resolveStartRef.current + resolveStartRef.current = 0 ax.metric('signin:pdsResolve:success', { - durationMs: Date.now() - resolveStartRef.current, + durationMs: start ? Date.now() - start : 0, isBlueskySocial: isBlueskyHostedPds(resolveQuery.data.pds), }) } @@ -262,7 +266,15 @@ export const LoginForm = ({ let service = customServerOverride ?? serviceUrl if (!customServerOverride && looksResolvable(fullIdent)) { const resolvedPds = await resolvePdsOnSubmit(queryClient, fullIdent) - if (resolvedPds) service = resolvedPds + // Only reroute to the resolved PDS for non-Bluesky hosts. A normal + // Bluesky account resolves to a sharded *.host.bsky.network endpoint, + // but must keep signing in through the bsky.social entryway + // (serviceUrl) - using the shard as account.service would flip + // downstream isSelfHosted / isBskyPds checks and suppress email + // verification. The resolution still drives the status UI either way. + if (resolvedPds && !isBlueskyHostedPds(resolvedPds)) { + service = resolvedPds + } } // TODO remove double login