import type { Channel, ChannelMessage, JsonValue } from "@berry/api"; import { token } from "./client.ts"; /** Open a channel to `path`: its first message authorizes it and carries `args`; what `send` is given before then waits. */ export function openChannel(path: string, args: JsonValue | undefined): Channel { const scheme = location.protocol === "https:" ? "wss:" : "ws:"; const socket = new WebSocket(`${scheme}//${location.host}${path}`); socket.binaryType = "arraybuffer"; const waiting: ChannelMessage[] = []; const encode = (message: ChannelMessage) => message instanceof Uint8Array ? message : JSON.stringify(message); let incoming!: ReadableStreamDefaultController; const messages = new ReadableStream({ start: (controller) => void (incoming = controller), cancel: () => socket.close(1000), }); socket.addEventListener("open", () => { socket.send(JSON.stringify({ token: token.get(), args: args ?? {} })); for (const message of waiting.splice(0)) socket.send(encode(message)); }); socket.addEventListener("message", (event) => { incoming.enqueue( typeof event.data === "string" ? JSON.parse(event.data) : new Uint8Array(event.data), ); }); socket.addEventListener("close", ({ code, reason }) => { try { // closed as asked, by either end; anything else is a failure, such as a restart or a refusal if (code === 1000) incoming.close(); else incoming.error(new Error(reason || `the channel closed (${code})`)); } catch { // already ended } }); return { send(message) { if (socket.readyState === WebSocket.CONNECTING) waiting.push(message); else if (socket.readyState === WebSocket.OPEN) socket.send(encode(message)); }, messages, close: () => socket.close(1000), }; }