The recipes.blue monorepo recipes.blue
recipes appview atproto
at main 2.4 kB view raw
1import type {} from '@atcute/atproto'; 2import { CompositeDidDocumentResolver, PlcDidDocumentResolver, WebDidDocumentResolver } from '@atcute/identity-resolver'; 3import { CompositeHandleResolver, DohJsonHandleResolver, WellKnownHandleResolver } from '@atcute/identity-resolver'; 4import { ActorIdentifier, AtprotoDid, Handle, isHandle } from '@atcute/lexicons/syntax'; 5import { isAtprotoDid } from '@atcute/identity'; 6import { RedisClient } from 'bun'; 7import { ProfileViewBasic } from '../../../../libs/lexicons/dist/types/blue/recipes/actor/defs.js'; 8import { Blob, LegacyBlob } from '@atcute/lexicons'; 9import { buildCdnUrl } from './cdn.js'; 10 11const handleResolver = new CompositeHandleResolver({ 12 strategy: 'race', 13 methods: { 14 dns: new DohJsonHandleResolver({ dohUrl: 'https://mozilla.cloudflare-dns.com/dns-query' }), 15 http: new WellKnownHandleResolver({ fetch }), 16 }, 17}); 18 19const didResolver = new CompositeDidDocumentResolver({ 20 methods: { 21 plc: new PlcDidDocumentResolver(), 22 web: new WebDidDocumentResolver(), 23 } 24}); 25 26const HANDLE_CACHE_TTL = 5 * 60; // 5 minutes 27 28export const parseDid = async (id: ActorIdentifier): Promise<AtprotoDid> => { 29 if (isAtprotoDid(id)) return id; 30 if (isHandle(id)) { 31 return await handleResolver.resolve(id); 32 } 33 throw Error("Invalid DID or Handle!"); 34} 35 36export const getHandle = async (did: AtprotoDid, redis: RedisClient): Promise<Handle> => { 37 let handle = await redis.get(`handle:${did}`) as Handle | null; 38 if (!handle) { 39 const didDoc = await didResolver.resolve(did); 40 if (didDoc.alsoKnownAs == null || didDoc.alsoKnownAs.length < 1) { 41 throw new Error(`User ${did} had no resolvable DID document.`); 42 } 43 handle = didDoc.alsoKnownAs[0]!.substring(5) as Handle; 44 redis.setex(`handle:${did}`, HANDLE_CACHE_TTL, handle); 45 } 46 47 return handle; 48} 49 50export const buildProfileViewBasic = async (author: { 51 did: AtprotoDid; 52 displayName: string; 53 pronouns: string | null; 54 avatarRef: Blob | LegacyBlob | null; 55 createdAt: Date; 56}, redis: RedisClient): Promise<ProfileViewBasic> => ({ 57 did: author.did, 58 handle: await getHandle(author.did, redis), 59 displayName: author.displayName, 60 pronouns: author.pronouns ?? undefined, 61 avatar: author.avatarRef ? buildCdnUrl('avatar', author.did, author.avatarRef) : undefined, 62 createdAt: author.createdAt.toISOString(), 63});