[READ-ONLY] Mirror of https://github.com/danielroe/roe.dev. This is the code and content for my personal website, built in Nuxt. roe.dev
0

Configure Feed

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

roe.dev / modules / atproto / index.ts
2.7 kB 71 lines
1/** 2 * Resolves the site's atproto identity at build time and surfaces it on 3 * `runtimeConfig.atproto.{handle,did}`. The DID and PDS endpoint are also 4 * mirrored to `public.atproto.{did,service}` so client code can read them 5 * without a round-trip. The handle is sourced from 6 * `social.networks.bluesky.identifier`; the PDS endpoint is read from the 7 * `#atproto_pds` service entry of the resolved DID document. 8 */ 9import { AtpAgent } from '@atproto/api' 10import { defineNuxtModule, useNuxt } from 'nuxt/kit' 11 12interface DidDocument { 13 service?: Array<{ id: string, type: string, serviceEndpoint: string }> 14} 15 16async function resolvePdsEndpoint (did: string): Promise<string | null> { 17 if (!did.startsWith('did:plc:') && !did.startsWith('did:web:')) return null 18 const url = did.startsWith('did:plc:') 19 ? `https://plc.directory/${did}` 20 : `https://${did.slice('did:web:'.length)}/.well-known/did.json` 21 const res = await fetch(url) 22 if (!res.ok) throw new Error(`${url} -> ${res.status}`) 23 const doc = await res.json() as DidDocument 24 const pds = doc.service?.find(s => s.id === '#atproto_pds' || s.id.endsWith('#atproto_pds')) 25 return pds?.serviceEndpoint ?? null 26} 27 28export default defineNuxtModule({ 29 meta: { name: 'atproto' }, 30 async setup () { 31 const nuxt = useNuxt() 32 33 const social = nuxt.options.social as { networks?: { bluesky?: { identifier?: string } } } | false | undefined 34 const handle = social ? social.networks?.bluesky?.identifier : null 35 if (!handle) { 36 console.warn('[atproto] No Bluesky handle configured under social.networks.bluesky.identifier; downstream modules will be inert.') 37 return 38 } 39 40 const cfg = nuxt.options.runtimeConfig.atproto 41 const publicCfg = nuxt.options.runtimeConfig.public.atproto 42 cfg.handle = handle 43 44 if (nuxt.options._prepare || nuxt.options.test) return 45 if (cfg.did && publicCfg.service) return 46 47 try { 48 if (!cfg.did) { 49 const agent = new AtpAgent({ service: 'https://public.api.bsky.app' }) 50 const { data } = await agent.resolveHandle({ handle }) 51 cfg.did = data.did 52 publicCfg.did = data.did 53 } 54 55 if (!publicCfg.service) { 56 const endpoint = await resolvePdsEndpoint(cfg.did) 57 if (!endpoint) { 58 console.warn(`[atproto] DID doc for ${cfg.did} has no #atproto_pds service entry.`) 59 } 60 else { 61 publicCfg.service = endpoint 62 } 63 } 64 65 console.info(`[atproto] Resolved ${handle} -> ${cfg.did} @ ${publicCfg.service || '(no PDS)'}`) 66 } 67 catch (err) { 68 console.warn(`[atproto] Failed to resolve identity for ${handle}:`, err instanceof Error ? err.message : err) 69 } 70 }, 71})