UNPKG

tiddlywiki-plugin-dev

Version:

[![](https://img.shields.io/badge/Join-TiddlyWiki_CN-blue)](https://github.com/tiddly-gittly)

149 lines (148 loc) 4.04 kB
import { WebSocketServer, WebSocket } from "ws"; const refreshHeartBeat = (ws) => { ws.isAlive = true; if (ws.heartBeatInterval) { clearInterval(ws.heartBeatInterval); } ws.heartBeatInterval = setInterval(() => { if (ws.isAlive === false) { clearInterval(ws.heartBeatInterval); delete ws.heartBeatInterval; return ws.terminate(); } ws.isAlive = false; ws.ping(); }, 5e3); }; const DEV_WS_PATHNAME = "/__dev_ws"; const parseClientGeneration = (url) => { if (!url) { return 0; } try { const generation = Number( new URL(url, "http://localhost").searchParams.get("gen") ); return Number.isFinite(generation) ? generation : 0; } catch { return 0; } }; const parseRequestPathname = (url) => { if (!url) { return ""; } try { return new URL(url, "http://localhost").pathname; } catch { return url; } }; const createNotifyServer = async () => { const server = new WebSocketServer({ noServer: true }); let connectionCount = 0; let currentGeneration = 0; let saveBusy = false; const saveBusyCallbacks = []; server.on("connection", (ws, request) => { connectionCount++; const clientId = connectionCount; const clientGeneration = parseClientGeneration(request?.url); console.log( `[Modern.TiddlyDev] [ws] Client #${clientId} connected (total: ${server.clients.size})` ); refreshHeartBeat(ws); ws.ping(); ws.on("pong", () => refreshHeartBeat(ws)); ws.on("message", (data) => { const msg = String(data); if (msg === "save-start") { if (!saveBusy) { saveBusy = true; saveBusyCallbacks.forEach((cb) => cb(true)); } return; } if (msg === "save-end") { if (saveBusy) { saveBusy = false; saveBusyCallbacks.forEach((cb) => cb(false)); } return; } }); ws.on("close", () => { if (ws.heartBeatInterval) { clearInterval(ws.heartBeatInterval); } const remaining = server.clients?.size ?? 0; console.log( `[Modern.TiddlyDev] [ws] Client #${clientId} disconnected (total: ${remaining})` ); }); if (currentGeneration > 0 && clientGeneration < currentGeneration && ws.readyState === WebSocket.OPEN) { console.log( `[Modern.TiddlyDev] [refresh] Client #${clientId} is from build ${clientGeneration} (current ${currentGeneration}) — telling it to refresh` ); ws.send("refresh"); } }); const onUpgrade = (request, socket, head) => { if (parseRequestPathname(request.url) === DEV_WS_PATHNAME) { server.handleUpgrade(request, socket, head, (ws) => { server.emit("connection", ws, request); }); } }; const attachToHttpServer = (httpServer) => { httpServer.on("upgrade", onUpgrade); return () => { httpServer.removeListener("upgrade", onUpgrade); }; }; const notifyRefresh = () => { const count = server.clients.size; if (count === 0) { console.log( `[Modern.TiddlyDev] [refresh] No clients connected — skipping browser notification` ); } else { console.log( `[Modern.TiddlyDev] [refresh] Notifying ${count} client(s) to refresh` ); server.clients.forEach((ws) => { if (ws.readyState === WebSocket.OPEN) { ws.send("refresh"); } }); } }; const closeAllClients = () => { for (const ws of server.clients) { ws.terminate(); } }; const setBuildGeneration = (generation) => { if (Number.isFinite(generation)) { currentGeneration = generation; } }; return { server, attachToHttpServer, closeAllClients, notifyRefresh, setBuildGeneration, onSaveBusyChange: (cb) => { saveBusyCallbacks.push(cb); return () => { const i = saveBusyCallbacks.indexOf(cb); if (i !== -1) saveBusyCallbacks.splice(i, 1); }; } }; }; export { createNotifyServer };