Last active 21 hours ago

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