···
1
+
import type { HandleResolver, ResolveHandleOptions, ResolvedHandle } from '@atproto-labs/handle-resolver';
2
+
import type { AtprotoDid } from '@atproto/did';
3
+
import { logger } from './logger';
6
+
* Custom HandleResolver that uses Slingshot's identity resolver service
7
+
* to work around bugs in atproto-oauth-node when handles have redirects
8
+
* in their well-known configuration.
10
+
* Uses: https://slingshot.microcosm.blue/xrpc/com.atproto.identity.resolveHandle
12
+
export class SlingshotHandleResolver implements HandleResolver {
13
+
private readonly endpoint = 'https://slingshot.microcosm.blue/xrpc/com.atproto.identity.resolveHandle';
15
+
async resolve(handle: string, options?: ResolveHandleOptions): Promise<ResolvedHandle> {
17
+
logger.debug('[SlingshotHandleResolver] Resolving handle', { handle });
19
+
const url = new URL(this.endpoint);
20
+
url.searchParams.set('handle', handle);
22
+
const controller = new AbortController();
23
+
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5s timeout
26
+
const response = await fetch(url.toString(), {
27
+
signal: options?.signal || controller.signal,
29
+
'Accept': 'application/json',
33
+
clearTimeout(timeoutId);
36
+
logger.error('[SlingshotHandleResolver] Failed to resolve handle', {
38
+
status: response.status,
39
+
statusText: response.statusText,
44
+
const data = await response.json() as { did: string };
47
+
logger.warn('[SlingshotHandleResolver] No DID in response', { handle });
51
+
// Validate that it's a proper DID format
52
+
if (!data.did.startsWith('did:')) {
53
+
logger.error('[SlingshotHandleResolver] Invalid DID format', { handle, did: data.did });
57
+
logger.debug('[SlingshotHandleResolver] Successfully resolved handle', { handle, did: data.did });
58
+
return data.did as AtprotoDid;
59
+
} catch (fetchError) {
60
+
clearTimeout(timeoutId);
62
+
if (fetchError instanceof Error && fetchError.name === 'AbortError') {
63
+
logger.error('[SlingshotHandleResolver] Request aborted', { handle });
64
+
throw fetchError; // Re-throw abort errors
70
+
logger.error('[SlingshotHandleResolver] Error resolving handle', error, { handle });
72
+
// If it's an abort error, propagate it
73
+
if (error instanceof Error && error.name === 'AbortError') {
77
+
// For other unexpected errors, return null (handle not found)