import { IncomingMessage, Server } from 'http'; import ws, { WebSocket } from 'ws'; import { RandomEvent, RandomEvents } from '../../@types/randomEvents'; import { MessageTypes, UnknownWSMessage, WSMessage } from '../../@types/ws'; import { EventEmitter } from 'events'; import { cfg } from '.'; import { getState } from '../../modules/module.cfg-loader'; import { console } from '../../modules/log'; declare interface ExternalEvent { on(event: T, listener: (msg: WSMessage, respond: (data: MessageTypes[T][1]) => void) => void): this; emit(event: T, msg: WSMessage, respond: (data: MessageTypes[T][1]) => void): boolean; } class ExternalEvent extends EventEmitter {} export default class WebSocketHandler { private wsServer: ws.Server; public events: ExternalEvent = new ExternalEvent(); constructor(server: Server) { this.wsServer = new ws.WebSocketServer({ noServer: true, path: '/private' }); this.wsServer.on('connection', (socket, req) => { console.info(`[WS] Connection from '${req.socket.remoteAddress}'`); socket.on('error', (er) => console.error(`[WS] ${er}`)); socket.on('message', (data) => { const json = JSON.parse(data.toString()) as UnknownWSMessage; this.events.emit(json.name, json as any, (data) => { this.wsServer.clients.forEach(client => { if (client.readyState !== WebSocket.OPEN) return; client.send(JSON.stringify({ data, id: json.id, name: json.name }), (er) => { if (er) console.error(`[WS] ${er}`); }); }); }); }); }); server.on('upgrade', (request, socket, head) => { if (!this.wsServer.shouldHandle(request)) return; if (!this.authenticate(request)) { socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); socket.destroy(); console.info(`[WS] ${request.socket.remoteAddress} tried to connect but used a wrong password.`); return; } this.wsServer.handleUpgrade(request, socket, head, socket => { this.wsServer.emit('connection', socket, request); }); }); } public sendMessage(data: RandomEvent) { this.wsServer.clients.forEach(client => { if (client.readyState !== WebSocket.OPEN) return; client.send(JSON.stringify(data), (er) => { if (er) console.error(`[WS] ${er}`); }); }); } private authenticate(request: IncomingMessage): boolean { const search = new URL(`http://${request.headers.host}${request.url}`).searchParams; return cfg.gui.password === (search.get('password') ?? undefined); } } export class PublicWebSocket { private wsServer: ws.Server; private state = getState(); constructor(server: Server) { this.wsServer = new ws.WebSocketServer({ noServer: true, path: '/public' }); this.wsServer.on('connection', (socket, req) => { console.info(`[WS] Connection to public ws from '${req.socket.remoteAddress}'`); socket.on('error', (er) => console.error(`[WS] ${er}`)); socket.on('message', (msg) => { const data = JSON.parse(msg.toString()) as UnknownWSMessage; switch (data.name) { case 'isSetup': this.send(socket, data.id, data.name, this.state.setup); break; case 'requirePassword': this.send(socket, data.id, data.name, cfg.gui.password !== undefined); break; } }); }); server.on('upgrade', (request, socket, head) => { if (!this.wsServer.shouldHandle(request)) return; this.wsServer.handleUpgrade(request, socket, head, socket => { this.wsServer.emit('connection', socket, request); }); }); } private send(client: ws.WebSocket, id: string, name: string, data: any) { client.send(JSON.stringify({ data, id, name }), (er) => { if (er) console.error(`[WS] ${er}`); }); } }