@roots/bud-server
Version:
Development server for @roots/bud
90 lines (89 loc) • 2.1 kB
JavaScript
/**
* 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`);
});
}
}