import { XrpcHandleResolver } from "@atcute/identity-resolver"; import { isDid, isHandle } from "@atcute/lexicons/syntax"; import { ConnectionStatus, Notification } from "./types.ts"; export interface Data { setError: (error: string | null) => void; setConnectionStatus: (status: ConnectionStatus) => void; pushNotification: (item: Notification) => void; actorId: string; serviceDomain: string; doRetry?: () => number | null; } const handleResolver = new XrpcHandleResolver({ serviceUrl: "https://public.api.bsky.app", }); export const connect = async (cb: Data) => { const didOrHandle = cb.actorId.trim(); const host = cb.serviceDomain.trim(); cb.setError(null); cb.setConnectionStatus("connecting..."); let did: string; if (!didOrHandle) { cb.setConnectionStatus("error"); cb.setError("please enter a DID or a handle"); return; } else if (isHandle(didOrHandle)) { try { did = await handleResolver.resolve(didOrHandle); } catch (error) { cb.setConnectionStatus("error"); cb.setError(`can't resolve handle: ${error}`); return; } } else if (isDid(didOrHandle)) { did = didOrHandle; } else { cb.setConnectionStatus("error"); cb.setError("inputted DID / handle is not valid"); return; } if (!host) { cb.setError("please enter service host"); cb.setConnectionStatus("error"); return; } let proto = "wss"; const domain = host.split(":").at(0) ?? ""; if (["localhost", "0.0.0.0", "127.0.0.1"].some((v) => v === domain)) { proto = "ws"; } const url = `${proto}://${host}/subscribe/${did}`; try { let ws = new WebSocket(url); ws.onopen = () => { cb.setConnectionStatus("connected"); cb.setError(null); console.log("WebSocket connected to:", url); }; ws.onmessage = (event: MessageEvent) => { try { cb.pushNotification(JSON.parse(event.data)); } catch (error) { console.error("Error parsing JSON:", error); } }; ws.onclose = (ev) => { cb.setConnectionStatus("disconnected"); console.log("WebSocket disconnected"); // abnormal closure if (ev.code === 1006) { cb.setConnectionStatus("error"); const backoff = (cb.doRetry ?? (() => null))(); if (backoff) { cb.setError(`websocket closed abnormally: (${ev.code}) ${ev.reason}`); setTimeout(() => connect(cb), backoff); } } else if (ev.code === 1000 || ev.code === 1001 || ev.code === 1005) { cb.setError(null); } else { cb.setConnectionStatus("error"); cb.setError(`websocket failed: (${ev.code}) ${ev.reason}`); } }; ws.onerror = (error: Event) => { cb.setConnectionStatus("error"); cb.setError("connection failed"); console.error("WebSocket error:", error); }; return ws; } catch (error) { cb.setConnectionStatus("error"); cb.setError(`failed to create connection: ${error}`); console.error("Failed to create WebSocket:", error); } return; }; export default connect;