From 1216a517b7f725c787609db453eaa48f99c92918 Mon Sep 17 00:00:00 2001 From: Adam Hovorka Date: Wed, 17 Jul 2019 23:23:52 -0600 Subject: Flesh out the WebSocket system with some amenities --- app.js | 23 ++++++++++++------ lib/socket.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ public/index.html | 1 + public/main.js | 12 +++++++--- public/sock.js | 43 ++++++++++++++++++++++++++++++++++ 5 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 lib/socket.js create mode 100644 public/sock.js diff --git a/app.js b/app.js index e026555..d6c8d7e 100644 --- a/app.js +++ b/app.js @@ -1,11 +1,11 @@ "use strict" -const WebSocket = require("ws"); -const http = require("http"); -const fs = require("fs"); +const http = require("http"); +const fs = require("fs"); const Router = require("./lib/router"); const Static = require("./lib/static"); +const Socket = require("./lib/socket"); if (!fs.existsSync("./logs")) fs.mkdirSync("./logs"); @@ -14,8 +14,8 @@ const PORT = Math.clamp(+process.env.PORT||8080, 1, 65535); const HOST = "0.0.0.0"; const server = http.createServer(); -const wss = new WebSocket.Server({server}); const stat = new Static("./public"); +const wss = new Socket(server); const app = new Router(); function sj(res, data) { // For convenience @@ -38,9 +38,18 @@ app.get("/hist.json", (req, res) => sj(res, [])); // req.pipe(fs.createWriteStream("./static/"+path[1])); //}); -wss.on("connection", ws => { - ws.on("message", msg => console.log("msg", msg)); - ws.send("hello"); +wss.on("open", client => { + console.log(`${Date.now()} SOCK open`); + client.send("hello", {foo:"bar"}); +}); + +wss.on("close", client => { + console.log(`${Date.now()} SOCK close`); +}); + +wss.on("world", (client, data) => { + console.log(`${Date.now()} SOCK world`, data); + wss.sendAll("yay", {meh:"woot"}); }); server.on("request", (req, res) => { diff --git a/lib/socket.js b/lib/socket.js new file mode 100644 index 0000000..971bacd --- /dev/null +++ b/lib/socket.js @@ -0,0 +1,70 @@ +"use strict" + +const WebSocket = require("ws"); + +class Client { + constructor(ws) { + this.ws = ws; + this.handlers = {}; + + ws.on("message", msg => { const d = JSON.parse(msg); + (this.handlers[d.type]||[]).forEach(cb => cb(d.data)); + }); + + const closecb = () => + (this.handlers["close"]||[]).forEach(cb => cb()); + + ws.on("close", closecb); + ws.on("error", closecb); + } + + on(type, cb) { + (this.handlers[type]||(this.handlers[type]=[])).push(cb); + } + + send(type, data) { + this.ws.send(JSON.stringify({type, data})); + } +} + +module.exports = class Socket { + constructor(server) { + this.wss = new WebSocket.Server({server}); + this.handlers = {}; + this.clients = []; + + this.wss.on("connection", ws => { + const client = new Client(ws); + this.clients.push(client); + + ws.on("message", msg => { const d = JSON.parse(msg); + (this.handlers[d.type]||[]).forEach(cb => cb(client, d.data)); + }); + + let pingTimeout; + const ping = () => { client.send("ping", {}); + pingTimeout = setTimeout(() => ws.terminate(), 3e4); }; + client.on("pong", () => { clearTimeout(pingTimeout); + setTimeout(ping, 1e3); }); ping(); + + const closecb = () => { + const i = this.clients.indexOf(client); if (i < 0) return; + (this.handlers["close"]||[]).forEach(cb => cb(client)); + this.clients.splice(i, 1); + } + + ws.on("close", closecb); + ws.on("error", closecb); + + (this.handlers["open"]||[]).forEach(cb => cb(client)); + }); + } + + on(type, cb) { + (this.handlers[type]||(this.handlers[type]=[])).push(cb); + } + + sendAll(type, data) { + this.clients.forEach(c => c.send(type, data)); + } +}; diff --git a/public/index.html b/public/index.html index c3828a5..a5892c1 100644 --- a/public/index.html +++ b/public/index.html @@ -11,6 +11,7 @@

nomicvote

+ diff --git a/public/main.js b/public/main.js index b3c7986..46d5432 100644 --- a/public/main.js +++ b/public/main.js @@ -2,9 +2,15 @@ document.addEventListener("DOMContentLoaded", async () => { "use strict" const $ = s => document.querySelector(s); const secure = location.protocol === "https:"; -const sock = new WebSocket(`ws${secure?"s":""}://${location.host}/ws`); +sock.init(`ws${secure?"s":""}://${location.host}/ws`); -sock.onmessage = e => console.log("sock", e); -sock.onopen = () => sock.send("yay"); +sock.on("hello", e => { + console.log("hello", e); + sock.send("world", {foo:"bar"}); +}); + +sock.on("yay", e => { + console.log("yay", e); +}); }); diff --git a/public/sock.js b/public/sock.js new file mode 100644 index 0000000..8cc73f0 --- /dev/null +++ b/public/sock.js @@ -0,0 +1,43 @@ +const sock = (() => { "use strict" +const refresh = () => setTimeout(() => location.reload(), 1e3); + +let ws, pingTimeout; +const prequeue = []; +const handlers = { + "reset": [refresh], + "ping": [() => { + clearTimeout(pingTimeout); + pingTimeout = setTimeout(refresh, 3e4); + sock.send("pong", {}); + }], +}; + +const sock = { + init: url => { + ws = new WebSocket(url); + ws.addEventListener("close", refresh); + ws.addEventListener("error", refresh); + ws.addEventListener("message", e => { + const d = JSON.parse(e.data); + (handlers[d.type]||[]).forEach(cb => cb(d.data)); + }); + ws.addEventListener("open", e => { + while (prequeue.length) sock.send(...prequeue.shift()); + (handlers["open"]||[]).forEach(cb => cb()); + delete handlers["open"]; + }); + }, + + on: (type, cb) => { + if (type === "open" && ws && ws.readyState === WebSocket.OPEN) cb(); + else (handlers[type]||(handlers[type]=[])).push(cb); + }, + + send: (type, data) => { + if (ws && ws.readyState === WebSocket.OPEN) + ws.send(JSON.stringify({type, data})); + else prequeue.push([type, data]); + }, +}; + +return sock })(); -- cgit v1.2.3-54-g00ecf