/** * @file * @copyright 2020 Aleksej Komarov * @license MIT */ import http from 'http'; import { inspect } from 'util'; import { createLogger, directLog } from '../logging.js'; import { require } from '../require.js'; import { loadSourceMaps, retrace } from './retrace.js'; const WebSocket = require('ws'); const logger = createLogger('link'); const DEBUG = process.argv.includes('--debug'); export { loadSourceMaps }; export const setupLink = () => new LinkServer(); class LinkServer { constructor() { logger.log('setting up'); this.wss = null; this.setupWebSocketLink(); this.setupHttpLink(); } // WebSocket-based client link setupWebSocketLink() { const port = 3000; this.wss = new WebSocket.Server({ port }); this.wss.on('connection', ws => { logger.log('client connected'); ws.on('message', json => { const msg = deserializeObject(json); this.handleLinkMessage(ws, msg); }); ws.on('close', () => { logger.log('client disconnected'); }); }); logger.log(`listening on port ${port} (WebSocket)`); } // One way HTTP-based client link for IE8 setupHttpLink() { const port = 3001; this.httpServer = http.createServer((req, res) => { if (req.method === 'POST') { let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', () => { const msg = deserializeObject(body); this.handleLinkMessage(null, msg); res.end(); }); return; } res.write('Hello'); res.end(); }); this.httpServer.listen(port); logger.log(`listening on port ${port} (HTTP)`); } handleLinkMessage(ws, msg) { const { type, payload } = msg; if (type === 'log') { const { level, ns, args } = payload; // Skip debug messages if (level <= 0 && !DEBUG) { return; } directLog(ns, ...args.map(arg => { if (typeof arg === 'object') { return inspect(arg, { depth: Infinity, colors: true, compact: 8, }); } return arg; })); return; } if (type === 'relay') { for (let client of this.wss.clients) { if (client === ws) { continue; } this.sendMessage(client, msg); } return; } logger.log('unhandled message', msg); } sendMessage(ws, msg) { ws.send(JSON.stringify(msg)); } broadcastMessage(msg) { const clients = [...this.wss.clients]; if (clients.length === 0) { return; } logger.log(`broadcasting ${msg.type} to ${clients.length} clients`); for (let client of clients) { const json = JSON.stringify(msg); client.send(json); } } } const deserializeObject = str => { return JSON.parse(str, (key, value) => { if (typeof value === 'object' && value !== null) { if (value.__undefined__) { // NOTE: You should not rely on deserialized object's undefined, // this is purely for inspection purposes. return { [inspect.custom]: () => undefined, }; } if (value.__number__) { return parseFloat(value.__number__); } if (value.__error__) { if (!value.stack) { return value.string; } return retrace(value.stack); } return value; } return value; }); };