atmosphere explorer pds.ls
tool typescript atproto

show websocket close event in UI #23

merged opened by bas.sh targeting main from bas.sh/pdsls: push-qmpokmzpzkqu

hiii :3 this is not necessarily a pdsls issue, but i just noticed that some PDSes seem to abnormally disconnect after a short while when used as the firehose instance, but without @skyware/firehose emitting any errors, which means the connection just dies while nothing changes in the UI.

some examples: https://pdsls.dev/firehose?instance=wss%3A%2F%2Fpds.finfet.sh&cursor=0 consistently disconnects after ~30 seconds https://pdsls.dev/firehose?instance=wss%3A%2F%2Fat.nogrp.net&cursor=0 consistently disconnects after ~60 seconds

from what i can see the WebSocket API does not report this as an error (for whatever reason), but instead it's reported as e.g. code 1006 in the CloseEvent, which @skyware/firehose neither handles nor passes on to the close event handler.

tried with goat as well and it also disconnects (but actually stops the program too).

this is most likely an error on their side (web server/reverse proxy connection timeout i imagine), but i still think it would be nice to actually show when the connection drops, as this could also help PDS hosters with debugging this issue. I've added the different connection close reasons (idk if any of them besides 1006 would ever get used though) and handle them basically the same as errors for both the firehose and jetstream, but feel free to change it around of course.

in the case of an error event i first remove the close event listener to avoid it from overwriting the error message (i'm not entirely sure which one should take precedence here though but i think this is fine)

it could also be an idea to automatically reconnect when the connection drops abnormally but i couldn't really figure out how to do that nicely using @skyware/firehose. I tried switching to @atcute/firehose (which does have automatic reconnecting built-in as it uses PartySocket underneath) but that seemed kinda difficult as the op objects it returns appear to have a slightly different shape and i don't know enough about atproto to know why, so i'll leave that up to you if you ever want to do that in the future lol

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:c52wep6lj4sfbsqiz3yvb55h/sh.tangled.repo.pull/3mfrnigmfcc22
+33
Diff #0
+20
src/utils/websocket.ts
··· 1 + const _websocketCloseReasons = { 2 + 1000: "Normal Closure", 3 + 1001: "Going Away", 4 + 1002: "Protocol Error", 5 + 1003: "Unsupported Data", 6 + 1005: "No Status Received", 7 + 1006: "Abnormal Closure", 8 + 1007: "Invalid frame payload data", 9 + 1008: "Policy Violation", 10 + 1009: "Message too big", 11 + 1010: "Missing Extension", 12 + 1011: "Internal Error", 13 + 1012: "Service Restart", 14 + 1013: "Try Again Later", 15 + 1014: "Bad Gateway", 16 + 1015: "TLS Handshake", 17 + } as const; 18 + 19 + export const websocketCloseReasons = _websocketCloseReasons as typeof _websocketCloseReasons & 20 + Record<string, string>;
+13
src/views/stream/index.tsx
··· 7 7 import { JSONValue } from "../../components/json"; 8 8 import { TextInput } from "../../components/text-input"; 9 9 import { addToClipboard } from "../../utils/copy"; 10 + import { websocketCloseReasons } from "../../utils/websocket"; 10 11 import { getStreamType, STREAM_CONFIGS, STREAM_TYPES, StreamType } from "./config"; 11 12 import { StreamStats, StreamStatsPanel } from "./stats"; 12 13 ··· 183 184 setStats((prev) => ({ ...prev, eventsPerSecond: 0 })); 184 185 }; 185 186 187 + const onWebsocketClose = (event: CloseEvent) => { 188 + const code = event.code.toString(); 189 + if (code === "1000" || code === "1005") return; 190 + 191 + setNotice(`Connection closed: ${websocketCloseReasons[code] ?? "Unknown reason"}`); 192 + disconnect(); 193 + }; 194 + 186 195 const connectStream = async (formData: FormData) => { 187 196 setNotice(""); 188 197 if (connected()) { ··· 251 260 if (!isFilteredEvent || streamType !== "jetstream" || searchParams.allEvents === "on") 252 261 addRecord(rec); 253 262 }); 263 + socket.addEventListener("close", onWebsocketClose); 254 264 socket.addEventListener("error", () => { 265 + socket.removeEventListener("close", onWebsocketClose); 255 266 setNotice("Connection error"); 256 267 disconnect(); 257 268 }); ··· 262 273 cursor: cursor, 263 274 autoReconnect: false, 264 275 }); 276 + firehose.ws.addEventListener("close", onWebsocketClose); 265 277 firehose.on("error", (err) => { 278 + firehose.ws.removeEventListener("close", onWebsocketClose); 266 279 console.error(err); 267 280 const message = err instanceof Error ? err.message : "Unknown error"; 268 281 setNotice(`Connection error: ${message}`);

History

1 round 0 comments
sign up or login to add to the discussion
bas.sh submitted #0
1 commit
expand
show websocket close event
expand 0 comments
pull request successfully merged