an attempt to make a lightweight, easily self-hostable, scoped bluesky appview
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}