an attempt to make a lightweight, easily self-hostable, scoped bluesky appview
at main 242 lines 7.2 kB view raw
1 2import { DidResolver, HandleResolver } from "npm:@atproto/identity"; 3import { Database } from "jsr:@db/sqlite@0.11"; 4import { AtUri } from "npm:@atproto/api"; 5const systemDB = new Database("./system.db") // TODO: temporary shim. should seperate this to its own central system db instead of the now instantiated system dbs 6type DidMethod = "web" | "plc"; 7type DidDoc = { 8 "@context"?: unknown; 9 id: string; 10 service?: Array<{ 11 id?: string; 12 type: string; 13 serviceEndpoint: unknown; 14 }>; 15 alsoKnownAs?: string[]; 16 [key: string]: unknown; 17}; 18 19/** 20 * Extracts method from DID string 21 */ 22function extractDidMethod(did: string): DidMethod { 23 const match = did.match(/^did:(web|plc):/); 24 if (!match) { 25 throw new Error(`Unsupported DID method in ${did}`); 26 } 27 return match[1] as DidMethod; 28} 29 30async function resolveDidFromHandle( 31 handle: string 32): Promise<{ did: string }> { 33 const resolver = new HandleResolver({}); 34 const did = (await resolver.resolve(handle)) as string | null; 35 if (!did) throw new Error(`Failed to resolve handle: ${handle}`); 36 return { did }; 37} 38 39function getDidFromHandle(handle: string): { did: string } | null { 40 const row = systemDB.prepare("SELECT did FROM did WHERE handle = ?").get(handle); 41 if (!row) return null; 42 43 try { 44 const did = JSON.parse(row.did as string); 45 if (typeof row.did !== "string") { 46 throw new Error(`Unknown did type for handle: '${handle}'`); 47 } 48 return { 49 did, 50 }; 51 } catch (err) { 52 console.error(`Invalid did for ${handle}:`, err); 53 return null; 54 } 55} 56 57async function resolveDidDoc( 58 did: string 59): Promise<{ didmethod: DidMethod; diddoc: DidDoc }> { 60 const didmethod = extractDidMethod(did); 61 const resolver = new DidResolver({}); 62 const diddoc = (await resolver.resolve(did)) as DidDoc | null; 63 if (!diddoc) throw new Error(`Failed to resolve DID: ${did}`); 64 return { didmethod, diddoc }; 65} 66 67function getDidDoc( 68 did: string 69): { method: "web" | "plc"; doc: DidDoc } | null { 70 const row = systemDB.prepare("SELECT method, doc FROM did WHERE did = ?").get(did); 71 if (!row) return null; 72 73 try { 74 const diddoc = JSON.parse(row.doc as string); 75 if (row.method !== "web" && row.method !== "plc") { 76 throw new Error(`Unknown didmethod '${row.didmethod}'`); 77 } 78 return { 79 method: row.method, 80 doc: diddoc, 81 }; 82 } catch (err) { 83 console.error(`Invalid diddoc for ${did}:`, err); 84 return null; 85 } 86} 87 88// async function extractPDSAndHandleFromDid( 89// did: string 90// ): Promise<{ pds: string; handle?: string }> { 91// const item = getDidDoc(did); 92// const doc = item?.doc; 93// const rawServiceEndpoint = doc.service?.find( 94// (s: Record<string, unknown>) => s.type === "AtprotoPersonalDataServer" 95// )?.serviceEndpoint; 96 97// if (!rawServiceEndpoint) { 98// throw new Error(`No AtprotoPersonalDataServer found in DID doc for ${did}`); 99// } 100 101// const serviceEndpoint = 102// typeof rawServiceEndpoint === "string" 103// ? rawServiceEndpoint 104// : typeof rawServiceEndpoint === "object" && 105// rawServiceEndpoint && 106// "uri" in rawServiceEndpoint && 107// typeof rawServiceEndpoint.uri === "string" 108// ? rawServiceEndpoint.uri 109// : (() => { 110// throw new Error( 111// `Unsupported serviceEndpoint format: ${JSON.stringify( 112// rawServiceEndpoint 113// )}` 114// ); 115// })(); 116 117// let handle: string | undefined = undefined; 118// if (Array.isArray(doc.alsoKnownAs)) { 119// const aka = doc.alsoKnownAs.find( 120// (a: unknown) => typeof a === "string" && a.startsWith("at://") 121// ); 122// if (aka) handle = aka.slice("at://".length); 123// } 124 125// return { pds: serviceEndpoint, handle }; 126// } 127 128function getPDSAndHandleFromDid( 129 did: string 130): { pds: string; handle?: string } | null { 131 const row = systemDB.prepare("SELECT pds, handle FROM did WHERE did = ?").get(did); 132 133 if (!row) return null; 134 return { 135 pds: row.pds as string, 136 handle: (row.handle as string) ?? undefined, 137 }; 138} 139// FINE (Fetch If Not Exists) variants 140 141export async function FINEDidFromHandle(handle: string): Promise<{ did: string } | null> { 142 const local = getDidFromHandle(handle); 143 if (local) return local; 144 145 try { 146 const { did } = await resolveDidFromHandle(handle); 147 const { diddoc, didmethod } = await resolveDidDoc(did); 148 149 systemDB.prepare( 150 "INSERT OR REPLACE INTO did (did, handle, doc, method) VALUES (?, ?, ?, ?)" 151 ).run(did, handle, JSON.stringify(diddoc), didmethod); 152 153 return { did }; 154 } catch (err) { 155 console.error(`Failed to resolve/store DID from handle '${handle}':`, err); 156 return null; 157 } 158} 159 160export async function FINEDidDoc(did: string): Promise<{ method: "web" | "plc"; doc: DidDoc } | null> { 161 const local = getDidDoc(did); 162 if (local) return local; 163 164 try { 165 const { diddoc, didmethod } = await resolveDidDoc(did); 166 167 systemDB.prepare( 168 "INSERT OR REPLACE INTO did (did, doc, method) VALUES (?, ?, ?)" 169 ).run(did, JSON.stringify(diddoc), didmethod); 170 171 return { method: didmethod, doc: diddoc }; 172 } catch (err) { 173 console.error(`Failed to resolve/store DID doc for '${did}':`, err); 174 return null; 175 } 176} 177 178export async function FINEPDSAndHandleFromDid( 179 did: string 180): Promise<{ pds: string; handle?: string } | null> { 181 const local = getPDSAndHandleFromDid(did); 182 if (local) return local; 183 184 const diddoc = await FINEDidDoc(did); 185 if (!diddoc) return null; 186 187 try { 188 const rawServiceEndpoint = diddoc.doc.service?.find( 189 (s: Record<string, unknown>) => s.type === "AtprotoPersonalDataServer" 190 )?.serviceEndpoint; 191 192 if (!rawServiceEndpoint) { 193 throw new Error(`No AtprotoPersonalDataServer found in DID doc for ${did}`); 194 } 195 196 const serviceEndpoint = 197 typeof rawServiceEndpoint === "string" 198 ? rawServiceEndpoint 199 : typeof rawServiceEndpoint === "object" && 200 rawServiceEndpoint && 201 "uri" in rawServiceEndpoint && 202 typeof rawServiceEndpoint.uri === "string" 203 ? rawServiceEndpoint.uri 204 : (() => { 205 throw new Error( 206 `Unsupported serviceEndpoint format: ${JSON.stringify( 207 rawServiceEndpoint 208 )}` 209 ); 210 })(); 211 212 let handle: string | undefined = undefined; 213 if (Array.isArray(diddoc.doc.alsoKnownAs)) { 214 const aka = diddoc.doc.alsoKnownAs.find( 215 (a: unknown) => typeof a === "string" && a.startsWith("at://") 216 ); 217 if (aka) handle = aka.slice("at://".length); 218 } 219 220 systemDB.prepare( 221 "INSERT OR REPLACE INTO did (did, pds, handle) VALUES (?, ?, ?)" 222 ).run(did, serviceEndpoint, handle ?? null); 223 224 return { pds: serviceEndpoint, handle }; 225 } catch (err) { 226 console.error(`Failed to extract/store PDS and handle for '${did}':`, err); 227 return null; 228 } 229} 230 231export function extractDid(input: string): string { 232 if (input.startsWith('did:')) { 233 return input 234 } 235 236 try { 237 const uri = new AtUri(input) 238 return uri.host 239 } catch (e) { 240 throw new Error(`Invalid input: expected a DID or a valid AT URI, got "${input}"`) 241 } 242}