Handle transient errors during PDS detection
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import {getMain, XrpcResponseError} from '@atproto/lex'
|
||||
import {beforeEach, describe, expect, it, jest} from '@jest/globals'
|
||||
|
||||
import {com} from '#/lexicons'
|
||||
|
||||
const mockCall = jest.fn<(...args: unknown[]) => Promise<unknown>>()
|
||||
|
||||
jest.mock('#/state/session/clients', () => ({
|
||||
getPublicAppviewClient: () => ({call: mockCall}),
|
||||
}))
|
||||
|
||||
import {resolvePdsForIdentifier} from '../pds-detection'
|
||||
|
||||
function responseError(status: number) {
|
||||
return new XrpcResponseError(
|
||||
getMain(com.atproto.identity.resolveHandle),
|
||||
new Response(null, {status}),
|
||||
undefined,
|
||||
)
|
||||
}
|
||||
|
||||
describe('resolvePdsForIdentifier', () => {
|
||||
beforeEach(() => {
|
||||
mockCall.mockReset()
|
||||
})
|
||||
|
||||
it.each([429, 500, 502, 503])(
|
||||
'surfaces a transient resolveHandle %s response as a network failure',
|
||||
async status => {
|
||||
mockCall.mockRejectedValueOnce(responseError(status))
|
||||
|
||||
await expect(resolvePdsForIdentifier('samuel.fm')).rejects.toThrow(
|
||||
`Network request failed: resolveHandle returned ${status}`,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
it('treats a permanent resolveHandle 4xx response as unresolved', async () => {
|
||||
mockCall.mockRejectedValueOnce(responseError(400))
|
||||
|
||||
await expect(resolvePdsForIdentifier('missing.test')).resolves.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -3,12 +3,13 @@ import {type DidDocument, getPdsEndpoint} from '@atproto/common-web'
|
||||
import {type HandleString} from '@atproto/syntax'
|
||||
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DEFAULT_SERVICE, PUBLIC_BSKY_SERVICE} from '#/lib/constants'
|
||||
import {DEFAULT_SERVICE} from '#/lib/constants'
|
||||
import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
|
||||
import {createServiceClient} from '#/lib/lexClient'
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
import {getErrorStatus} from '#/lib/xrpc-error'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {getPublicAppviewClient} from '#/state/session/clients'
|
||||
import {com} from '#/lexicons'
|
||||
|
||||
const RQKEY_ROOT = 'pds-detection'
|
||||
@@ -149,14 +150,7 @@ export async function resolvePdsForIdentifier(
|
||||
identifier: string,
|
||||
): Promise<{did: string; pdsUrl: string | null} | null> {
|
||||
const norm = normalizeIdentifier(identifier)
|
||||
/*
|
||||
* This is a pre-auth request. Use an isolated service client rather than the
|
||||
* shared public appview client, whose global moderation configuration is
|
||||
* intended for appview content reads and can change independently of this
|
||||
* login flow. This also matches the pre-auth handle resolution used during
|
||||
* signup.
|
||||
*/
|
||||
const client = createServiceClient(PUBLIC_BSKY_SERVICE)
|
||||
const client = getPublicAppviewClient()
|
||||
try {
|
||||
let did: string
|
||||
if (norm.startsWith('did:')) {
|
||||
@@ -194,6 +188,13 @@ export async function resolvePdsForIdentifier(
|
||||
isNetworkError: isNetworkError(err),
|
||||
})
|
||||
if (isNetworkError(err)) throw err
|
||||
const status = getErrorStatus(err)
|
||||
if (status !== undefined && isTransientHttpStatus(status)) {
|
||||
throw new Error(
|
||||
`Network request failed: resolveHandle returned ${status}`,
|
||||
{cause: err},
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user