[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
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})