Universal links for the ATmosphere. Share ATProto content with anyone, let them choose where to view it.
at testing 105 lines 2.4 kB view raw
1export type ParsedURI = { 2 type: 'post' | 'profile' | 'list' | 'record' | 'unknown'; 3 uri: string; 4 handle: string; 5 did?: string; 6 collection?: string; 7 rkey?: string; 8 error?: string; 9}; 10 11/** 12 * Parse URL path segments into structured AT URI data 13 * Examples: 14 * - /alice.bsky.social -> profile 15 * - /alice.bsky.social/app.bsky.feed.post/3k7qw... -> post 16 * - /did:plc:xxx/app.bsky.graph.list/abc -> list 17 */ 18export function parseURI(handle: string, collection?: string, rkey?: string): ParsedURI { 19 // Handle is required 20 if (!handle) { 21 return { 22 type: 'unknown', 23 uri: '', 24 handle: '', 25 error: 'Handle or DID is required', 26 }; 27 } 28 29 // Profile case (no collection/rkey) 30 if (!collection && !rkey) { 31 return { 32 type: 'profile', 33 uri: `at://${handle}`, 34 handle, 35 did: handle.startsWith('did:') ? handle : undefined, 36 }; 37 } 38 39 // Record case (has collection and rkey) 40 if (collection && rkey) { 41 let type: 'post' | 'list' | 'record' = 'record'; 42 43 if (collection === 'app.bsky.feed.post') { 44 type = 'post'; 45 } else if (collection === 'app.bsky.graph.list') { 46 type = 'list'; 47 } 48 // All other collections are treated as generic records 49 50 return { 51 type, 52 uri: `at://${handle}/${collection}/${rkey}`, 53 handle, 54 did: handle.startsWith('did:') ? handle : undefined, 55 collection, 56 rkey, 57 }; 58 } 59 60 // Invalid case 61 return { 62 type: 'unknown', 63 uri: '', 64 handle, 65 error: 'Invalid URI structure', 66 }; 67} 68 69/** 70 * Resolve a handle to a DID using the Bluesky API 71 */ 72export async function resolveHandle(handle: string): Promise<string | null> { 73 if (handle.startsWith('did:')) { 74 return handle; 75 } 76 77 try { 78 const apiUrl = process.env.NEXT_PUBLIC_BSKY_API_URL || 'https://public.api.bsky.app'; 79 const response = await fetch( 80 `${apiUrl}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}` 81 ); 82 83 if (!response.ok) { 84 return null; 85 } 86 87 const data = await response.json(); 88 return data.did || null; 89 } catch (error) { 90 console.error('Error resolving handle:', error); 91 return null; 92 } 93} 94 95/** 96 * Get display name from handle or DID 97 */ 98export function getDisplayName(handle: string, did?: string): string { 99 if (handle.startsWith('did:')) { 100 return did ? `@${did.slice(0, 16)}...` : 'Unknown'; 101 } 102 return `@${handle}`; 103} 104 105