katproto index/homepage
1const ESCAPE_LOOKUP: Record<string, string> = {
2 "&": "&",
3 '"': """,
4 "'": "'",
5 "<": "<",
6 ">": ">",
7};
8const PDS = "https://katproto.girlonthemoon.xyz";
9
10async function getKatprotoUsers() {
11 const users = await fetch(PDS + "/xrpc/com.atproto.sync.listRepos")
12 // type cast because no point validating for smthn like this
13 // real type has more info; not needed here
14 .then((res) => res.json() as Promise<{ repos: { did: string }[] }>)
15 .then((res) =>
16 // get display name, handle, and did for each user
17 res.repos.map((repo) => ({
18 display: fetch(
19 `${PDS}/xrpc/com.atproto.repo.getRecord?repo=${repo.did}&collection=app.bsky.actor.profile&rkey=self`
20 )
21 .then((res) => res.json())
22 .then((profile) => profile.value.displayName),
23 // dont validate handles because I'm Lazy + trust myself
24 handle: fetch(
25 repo.did.startsWith("did:plc")
26 ? "https://plc.directory/" + repo.did
27 : `https://${repo.did.replace("did:web:", "")}/.well-known/did.json`
28 )
29 .then((res) => res.json())
30 .then((doc) => doc.alsoKnownAs[0].replace("at://", "")),
31 did: repo.did,
32 }))
33 )
34 .then(
35 async (users) =>
36 await Promise.all(
37 users.map(
38 async (x) =>
39 `<li><a href="https://bsky.app/profile/${x.did}">${await x.display}</a></li>`
40 )
41 )
42 );
43
44 return users.join("");
45}
46
47async function checkStatus() {
48 const statusElement = document.getElementById("current-status");
49 if (!statusElement) return;
50
51 // try get `/xrpc/_health`
52 const result = await fetch(PDS + "/xrpc/_health")
53 .then(async (res) => ({
54 status: res.status,
55 statusText: res.statusText,
56 res: await res.text(),
57 }))
58 .catch((err) => {
59 // we only return undefined if we get a type error
60 // this means we were blocked by permissions (cors) (upstream is offline)
61 // or the device couldnt connect (main server and index is offline)
62 if (err instanceof TypeError) return undefined;
63 console.warn("Ignoring:", err);
64 // if we didnt expect this error we want to bubble it up as normal
65 throw err;
66 });
67
68 // make sure html is escaped for status text
69 // this could (in theory) be anything so we should make sure its escaped properly
70 // also an & could break things
71 if (result) {
72 result.statusText = result.statusText.replaceAll(
73 /[&"'<>]/g,
74 (c) => ESCAPE_LOOKUP[c]
75 );
76 }
77
78 if (!result) {
79 statusElement.innerHTML = "🔴 Offline";
80 statusElement.title = "The server could not be reached.";
81 return;
82 }
83
84 if (result.status < 200 || result.status > 299) {
85 statusElement.innerHTML = `🟡 Some Issues: ${result.status} ${result.statusText}`;
86 statusElement.title = result.res;
87 }
88
89 statusElement.innerHTML = `🟢 Online`;
90 statusElement.title = result.res;
91}
92
93// silence errors
94getKatprotoUsers().catch((err) => console.warn(err));
95addEventListener("load", () => checkStatus().catch((err) => console.warn(err)));