Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
96
fork

Configure Feed

Select the types of activity you want to include in your feed.

at a58efefd2c4e3a448a9955bedc734f0f98be4fe3 81 lines 3.2 kB view raw
1import type { HandleResolver, ResolveHandleOptions, ResolvedHandle } from '@atproto-labs/handle-resolver'; 2import type { AtprotoDid } from '@atproto/did'; 3import { logger } from './logger'; 4 5/** 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. 9 * 10 * Uses: https://slingshot.wisp.place/xrpc/com.atproto.identity.resolveHandle 11 */ 12export class SlingshotHandleResolver implements HandleResolver { 13 private readonly endpoint = 'https://slingshot.wisp.place/xrpc/com.atproto.identity.resolveHandle'; 14 15 async resolve(handle: string, options?: ResolveHandleOptions): Promise<ResolvedHandle> { 16 try { 17 logger.debug('[SlingshotHandleResolver] Resolving handle', { handle }); 18 19 const url = new URL(this.endpoint); 20 url.searchParams.set('handle', handle); 21 22 const controller = new AbortController(); 23 const timeoutId = setTimeout(() => controller.abort(), 5000); // 5s timeout 24 25 try { 26 const response = await fetch(url.toString(), { 27 signal: options?.signal || controller.signal, 28 headers: { 29 'Accept': 'application/json', 30 }, 31 }); 32 33 clearTimeout(timeoutId); 34 35 if (!response.ok) { 36 logger.error('[SlingshotHandleResolver] Failed to resolve handle', { 37 handle, 38 status: response.status, 39 statusText: response.statusText, 40 }); 41 return null; 42 } 43 44 const data = await response.json() as { did: string }; 45 46 if (!data.did) { 47 logger.warn('[SlingshotHandleResolver] No DID in response', { handle }); 48 return null; 49 } 50 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 }); 54 return null; 55 } 56 57 logger.debug('[SlingshotHandleResolver] Successfully resolved handle', { handle, did: data.did }); 58 return data.did as AtprotoDid; 59 } catch (fetchError) { 60 clearTimeout(timeoutId); 61 62 if (fetchError instanceof Error && fetchError.name === 'AbortError') { 63 logger.error('[SlingshotHandleResolver] Request aborted', { handle }); 64 throw fetchError; // Re-throw abort errors 65 } 66 67 throw fetchError; 68 } 69 } catch (error) { 70 logger.error('[SlingshotHandleResolver] Error resolving handle', error, { handle }); 71 72 // If it's an abort error, propagate it 73 if (error instanceof Error && error.name === 'AbortError') { 74 throw error; 75 } 76 77 // For other unexpected errors, return null (handle not found) 78 return null; 79 } 80 } 81}