katproto.ts
· 3.2 KiB · TypeScript
Raw
const ESCAPE_LOOKUP: Record<string, string> = {
"&": "&",
'"': """,
"'": "'",
"<": "<",
">": ">",
};
const PDS = "https://katproto.girlonthemoon.xyz";
async function getKatprotoUsers() {
const users = await fetch(PDS + "/xrpc/com.atproto.sync.listRepos")
// type cast because no point validating for smthn like this
// real type has more info; not needed here
.then((res) => res.json() as Promise<{ repos: { did: string, active: boolean }[] }>)
.then((res) =>
// get display name, handle, and did for each user
res.repos.map((repo) => ({
status: fetch(`${PDS}/xrpc/com.atproto.sync.listRepos`).then((res) => repo.active),
display: fetch(
`${PDS}/xrpc/com.atproto.repo.getRecord?repo=${repo.did}&collection=app.bsky.actor.profile&rkey=self`
)
.then((res) => res.json())
.then((profile) => profile.value.displayName),
// dont validate handles because I'm Lazy + trust myself
handle: fetch(
repo.did.startsWith("did:plc")
? "https://plc.directory/" + repo.did
: `https://${repo.did.replace("did:web:", "")}/.well-known/did.json`
)
.then((res) => res.json())
.then((doc) => doc.alsoKnownAs[0].replace("at://", "")),
did: repo.did,
}))
)
.then(
async (users) =>
await Promise.all(
users.map(
async (x) =>
`<li><a href="https://bsky.app/profile/${x.did}">${await x.display}</a> ${await x.status}</li>`
)
)
);
return users.join("");
}
async function checkStatus() {
const statusElement = document.getElementById("current-status");
if (!statusElement) return;
// try get `/xrpc/_health`
const result = await fetch(PDS + "/xrpc/_health")
.then(async (res) => ({
status: res.status,
statusText: res.statusText,
res: await res.text(),
}))
.catch((err) => {
// we only return undefined if we get a type error
// this means we were blocked by permissions (cors) (upstream is offline)
// or the device couldnt connect (main server and index is offline)
if (err instanceof TypeError) return undefined;
console.warn("Ignoring:", err);
// if we didnt expect this error we want to bubble it up as normal
throw err;
});
// make sure html is escaped for status text
// this could (in theory) be anything so we should make sure its escaped properly
// also an & could break things
if (result) {
result.statusText = result.statusText.replaceAll(
/[&"'<>]/g,
(c) => ESCAPE_LOOKUP[c]
);
}
if (!result) {
statusElement.innerHTML = "🔴 offline";
statusElement.title = "The server could not be reached.";
return;
}
if (result.status < 200 || result.status > 299) {
statusElement.innerHTML = `🟡 some Issues: ${result.status} ${result.statusText}`;
statusElement.title = result.res;
}
statusElement.innerHTML = `🟢 online`;
statusElement.title = result.res;
}
// silence errors
getKatprotoUsers().catch((err) => console.warn(err));
addEventListener("load", () => checkStatus().catch((err) => console.warn(err)));
| 1 | const ESCAPE_LOOKUP: Record<string, string> = { |
| 2 | "&": "&", |
| 3 | '"': """, |
| 4 | "'": "'", |
| 5 | "<": "<", |
| 6 | ">": ">", |
| 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))); |
| 97 |