Last active 23 hours ago

kat's Avatar kat revised this gist 23 hours ago. Go to revision

1 file changed, 96 insertions

katproto.ts(file created)

@@ -0,0 +1,96 @@
1 + const ESCAPE_LOOKUP: Record<string, string> = {
2 + "&": "&amp;",
3 + '"': "&quot;",
4 + "'": "&apos;",
5 + "<": "&lt;",
6 + ">": "&gt;",
7 + };
8 + const PDS = "https://katproto.girlonthemoon.xyz";
9 +
10 + async 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 +
48 + async 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
95 + getKatprotoUsers().catch((err) => console.warn(err));
96 + addEventListener("load", () => checkStatus().catch((err) => console.warn(err)));
Newer Older