From da7494c46c3cd6dcabd3deedf7e5f5906c6b8a3c Mon Sep 17 00:00:00 2001 From: Baivab Sarkar Date: Wed, 1 Jul 2026 20:44:46 +0530 Subject: [PATCH 01/21] Add temporary live share rooms --- functions/live-signal/[[path]].js | 122 ++++++++ index.html | 62 +++- script.js | 492 +++++++++++++++++++++++++++++- styles.css | 96 ++++++ 4 files changed, 762 insertions(+), 10 deletions(-) create mode 100644 functions/live-signal/[[path]].js diff --git a/functions/live-signal/[[path]].js b/functions/live-signal/[[path]].js new file mode 100644 index 00000000..02a31a47 --- /dev/null +++ b/functions/live-signal/[[path]].js @@ -0,0 +1,122 @@ +const topics = new Map(); + +function safeSend(socket, message) { + try { + socket.send(JSON.stringify(message)); + } catch (_) { + try { + socket.close(); + } catch (_) {} + } +} + +function parseMessage(data) { + if (typeof data === "string") { + return JSON.parse(data); + } + + if (data instanceof ArrayBuffer) { + return JSON.parse(new TextDecoder().decode(data)); + } + + return null; +} + +function removeSocketFromTopic(socket, topicName) { + const subscribers = topics.get(topicName); + if (!subscribers) return; + subscribers.delete(socket); + if (subscribers.size === 0) { + topics.delete(topicName); + } +} + +function cleanupSocket(socket, subscribedTopics) { + subscribedTopics.forEach((topicName) => removeSocketFromTopic(socket, topicName)); + subscribedTopics.clear(); +} + +function handleSocket(socket) { + const subscribedTopics = new Set(); + let closed = false; + + socket.accept(); + + socket.addEventListener("message", (event) => { + if (closed) return; + + let message; + try { + message = parseMessage(event.data); + } catch (_) { + return; + } + + if (!message || typeof message.type !== "string") return; + + if (message.type === "subscribe") { + (Array.isArray(message.topics) ? message.topics : []).forEach((topicName) => { + if (typeof topicName !== "string" || topicName.length > 256) return; + if (!topics.has(topicName)) { + topics.set(topicName, new Set()); + } + topics.get(topicName).add(socket); + subscribedTopics.add(topicName); + }); + return; + } + + if (message.type === "unsubscribe") { + (Array.isArray(message.topics) ? message.topics : []).forEach((topicName) => { + if (typeof topicName !== "string") return; + removeSocketFromTopic(socket, topicName); + subscribedTopics.delete(topicName); + }); + return; + } + + if (message.type === "publish" && typeof message.topic === "string") { + const receivers = topics.get(message.topic); + if (!receivers) return; + message.clients = receivers.size; + receivers.forEach((receiver) => safeSend(receiver, message)); + return; + } + + if (message.type === "ping") { + safeSend(socket, { type: "pong" }); + } + }); + + const close = () => { + if (closed) return; + closed = true; + cleanupSocket(socket, subscribedTopics); + }; + + socket.addEventListener("close", close); + socket.addEventListener("error", close); +} + +export async function onRequest({ request }) { + const upgradeHeader = request.headers.get("Upgrade") || ""; + + if (upgradeHeader.toLowerCase() !== "websocket") { + return new Response("Markdown Viewer live signaling endpoint", { + headers: { + "Cache-Control": "no-store", + "Content-Type": "text/plain; charset=utf-8" + } + }); + } + + const pair = new WebSocketPair(); + const [client, server] = Object.values(pair); + + handleSocket(server); + + return new Response(null, { + status: 101, + webSocket: client + }); +} diff --git a/index.html b/index.html index a7e5c2f2..fba38269 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - + @@ -180,8 +180,12 @@

Markdown Viewer

Copy - + +