import {tinyws} from "tinyws"; import { randomUUID } from 'crypto'; export default class SocketHandler { constructor(path, args) { this.path = path; this.connections = {}; this.handlers = { 'join': [], 'leave': [], 'message': [], }; this.pingInterval = null; } start(express) { express.use(tinyws()); express.use(this.path, this._receiveRequest.bind(this)); this.pingInterval = setInterval(() => { Object.values(this.connections).forEach(client => { if (++client.failedPings > 3) { this.terminateClient(client.id); } else { client.ws.ping(); } }); }, 30000); // this.pingInterval } stop() { clearInterval(this.pingInterval); } broadcast(msg) { Object.values(this.connections).forEach(client => { client.ws.send(msg); }); } async _receiveRequest(req, res) { if (req.ws) { const ws = await req.ws() return this._receiveWebsocket(req, res, ws); } else { // not handling HTTP } } async _receiveWebsocket(req, res, ws) { ws.id = randomUUID(); this.connections[ws.id] = { ws: ws, id: ws.id, joined: new Date(), failedPings: 0, }; ws.on('close', () => { this._fire('leave', ws); delete this.connections[ws.id]; }); ws.on('message', (data, ...args) => { this._fire('message', ws, data, ...args); }); ws.on('pong', () => { this.connections[ws.id].failedPings = 0; }); this._fire('join', ws); } terminateClient(uuid) { this.connections[uuid].ws.terminate(); delete this.connections[uuid]; } on(event, callback) { this.handlers[event].push(callback); } _fire(event, ...args) { this.handlers[event].forEach(cb => cb(...args)); } }