UNPKG

@roots/bud-server

Version:

Development server for @roots/bud

90 lines (89 loc) 2.1 kB
/** * Response headers */ const headers = { 'Access-Control-Allow-Origin': `*`, 'Cache-Control': `no-cache, no-transform`, 'Content-Type': `text/event-stream;charset=utf-8`, // While behind nginx, event stream should not be buffered: // http://nginx.org/docs/http/ngx_http_proxy_module.html#proxy_buffering 'X-Accel-Buffering': `no`, }; /** * Update interval */ const updateInterval = 1000; /** * Registered clients */ let clients = {}; /** * Current client identifier */ let currentClientId = 0; /** * Static fn to execute a callback on every registered client */ const tapClients = (fn) => Object.values(clients) .filter(Boolean) .forEach(client => fn(client)); const pingClient = client => { client.write(`{data: {"action": "ping"}\n\n`); }; const closeClient = client => { if (client.finished) return; client.end(); }; /** * Hot Module Replacement event stream */ export class HotEventStream { /** * hmr interval Timer */ interval; /** * Class constructor */ constructor() { this.interval = setInterval(() => tapClients(pingClient), updateInterval).unref(); } /** * Close stream */ close() { clearInterval(this.interval); tapClients(closeClient); clients = {}; } /** * Handle update message */ handle(req, res, _next) { const id = currentClientId++; clients[id] = res; const isHttp1 = parseInt(req.httpVersion) === 1; if (isHttp1) { req.socket.setKeepAlive(true); Object.assign(headers, { Connection: `keep-alive` }); } res.writeHead(200, headers); res.write(`\n`); req.on(`close`, () => { if (!res.writableEnded) res.end(); delete clients[id]; }); } /** * Broadcast to clients */ publish(payload) { if (!payload) return; tapClients(client => { client.write(`data: ${JSON.stringify(payload)} \n\n`); }); } }