View the firehose for an account but it looks ✨ fancy ✨ and also is not for debugging its for looking pretty. Url params are `did` and `firehose`
account-firehose.html edited
240 lines 7.6 kB view raw
1<!doctype html> 2<html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <title>Account status</title> 7 8 <script type="importmap"> 9 { 10 "imports": { 11 "@atcute/cbor": "https://unpkg.com/@atcute/cbor/dist/index.js", 12 "@atcute/cid": "https://unpkg.com/@atcute/cid/dist/index.js", 13 "@atcute/multibase": "https://unpkg.com/@atcute/multibase/dist/index.js", 14 "@atcute/uint8array": "https://unpkg.com/@atcute/uint8array/dist/index.js", 15 "#bases/base16": "https://unpkg.com/@atcute/multibase/dist/bases/base16-web.js", 16 "#bases/base64": "https://unpkg.com/@atcute/multibase/dist/bases/base64-web.js" 17 } 18 } 19 </script> 20 <script type="module"> 21 const params = new URLSearchParams(new URL(window.location).search); 22 window.user = params.get("did") ?? "did:plc:4zht3z4caxwrw3dlsybodywc"; 23 window.pds = params.get("pds") ?? "katproto.girlonthemoon.xyz"; 24 25 window.handle = await ( 26 window.user.startsWith("did:plc") 27 ? fetch(`https://plc.directory/${window.user}`) 28 : fetch(`https://${window.user}/.well-known/did.json`) 29 ) 30 .then((res) => res.json()) 31 .then((res) => 32 res.alsoKnownAs 33 ? (res.alsoKnownAs[0] ?? "handle.invalid") 34 : "handle.invalid" 35 ); 36 37 document.querySelector("title").innerHTML = 38 `${window.handle} &bull; ${window.user}`; 39 </script> 40 41 <style> 42 html { 43 background: repeating-linear-gradient(#fff1, #0000 5px, #fff1 10px), 44 black; 45 min-height: 100%; 46 font-family: 47 "Monaspace neon var", 48 -moz-fixed, 49 monospace; 50 font-weight: bold; 51 } 52 53 body, 54 body * { 55 /* override some resource://content-accessible/plaintext.css styles in ff */ 56 white-space: pre !important; 57 width: max-content; 58 text-shadow: 0 0 5px lime; 59 color: lime; 60 61 font-size: 10px; 62 63 &::selection { 64 color: black; 65 background-color: lime; 66 } 67 } 68 69 ul { 70 margin: 0; 71 padding: 0; 72 list-style-type: "> "; 73 padding-inline-start: 2ch; 74 } 75 </style> 76 <script type="module"> 77 import * as CBOR from "@atcute/cbor"; 78 import * as CID from "@atcute/cid"; 79 80 const events = document.createElement("ul"); 81 82 document.body.innerHTML = `${window.user} ${window.handle} ${window.pds}\n`; 83 document.body.append(events); 84 85 const ws = new WebSocket( 86 "wss://" + window.pds + "/xrpc/com.atproto.sync.subscribeRepos" 87 ); 88 89 ws.addEventListener("open", () => 90 console.info( 91 "connected to wss://" + 92 window.pds + 93 "/xrpc/com.atproto.sync.subscribeRepos" 94 ) 95 ); 96 ws.addEventListener("error", (err) => console.warn(err)); 97 ws.addEventListener("close", () => console.error("Connection closed :(")); 98 99 ws.addEventListener( 100 "message", 101 async (/** @type {{ data: Blob }} */ { data }) => { 102 const [header, remainder] = CBOR.decodeFirst(await data.bytes()); 103 const [payload] = CBOR.decodeFirst(remainder); 104 105 /** @type {{ 106 * t: "#commit", 107 * repo: string, 108 * rev: string, 109 * commit: { bytes: Uint8Array }, 110 * blocks: Uint8Array, 111 * ops: { 112 * action: "create" | "update" | "delete", 113 * path: string, 114 * cid: { bytes: Uint8Array } 115 * }[] 116 * } | { 117 * t: "#sync", 118 * did: string, 119 * rev: string, 120 * blocks: Uint8Array 121 * } | { 122 * t: "#identity", 123 * did: string, 124 * handle: string 125 * } | { 126 * t: "#account", 127 * did: string, 128 * active: boolean, 129 * status: string, 130 * } | { 131 * t: "#info", 132 * name: string, 133 * message: string, 134 * }} */ 135 const msg = { 136 ...header, 137 ...payload, 138 }; 139 140 switch (msg.t) { 141 case "#commit": { 142 console.log("#commit", msg.repo, msg.rev); 143 break; 144 } 145 case "#sync": { 146 console.log("#sync", msg.did, msg.rev); 147 break; 148 } 149 case "#account": { 150 console.log("#account", msg.did, msg.status); 151 break; 152 } 153 case "#identity": { 154 console.log("#account", msg.did, msg.handle); 155 break; 156 } 157 case "#info": { 158 console.log("#info", msg.name, msg.message); 159 break; 160 } 161 } 162 163 // skip other users 164 if ( 165 (msg.t === "#commit" && msg.repo !== window.user) || 166 ((msg.t === "#sync" || 167 msg.t === "#account" || 168 msg.t === "#identity") && 169 msg.did !== window.user) 170 ) 171 return; 172 173 const li = document.createElement("li"); 174 const pre = document.createElement("pre"); 175 176 try { 177 pre.innerText = ( 178 msg.t === "#commit" 179 ? [ 180 `#commit {`, 181 ` "repo": "${payload.repo}",`, 182 ` "rev": "${payload.rev}",`, 183 ` "commit": "${CID.toString(CID.decode(payload.commit.bytes))}",`, 184 ` "ops": [`, 185 payload.ops 186 .map((op) => 187 [ 188 ` {`, 189 ` "action": "${op.action}",`, 190 ` "path": "${op.path}",`, 191 ` "cid": ${op.cid ? `"${CID.toString(CID.decode(op.cid.bytes))}"` : "null"}`, 192 `}`, 193 ].join("\n ") 194 ) 195 .join(",\n"), 196 ` ],`, 197 `}`, 198 ] 199 : msg.t === "#sync" 200 ? [ 201 `#sync {`, 202 ` "did": "${msg.did}",`, 203 ` "rev": "${msg.rev}",`, 204 `}`, 205 ] 206 : msg.t === "#account" 207 ? [ 208 `#account {`, 209 ` "did": "${msg.did}",`, 210 ` "active": ${msg.active},`, 211 ` "status": "${msg.status}",`, 212 `}`, 213 ] 214 : msg.t === "#identity" 215 ? [ 216 `#identity {`, 217 ` "did": "${msg.did}",`, 218 ` "handle": "${msg.handle}",`, 219 `}`, 220 ] 221 : msg.t === "#info" 222 ? [ 223 `#info { "name": "${msg.name}", "message": "${msg.message}" }`, 224 ] 225 : ["#unknown {}"] 226 ).join("\n"); 227 } catch (err) { 228 console.error(err, msg, data); 229 } 230 231 if (pre.innerText === "") return; 232 233 li.append(pre); 234 events.append(li); 235 } 236 ); 237 </script> 238 </head> 239 <body></body> 240</html>