UNPKG

@roots/bud-server

Version:

Development server for @roots/bud

126 lines (105 loc) 2.39 kB
import type {Payload} from '@roots/bud-server/middleware/hot' import type { NextFunction, NextHandleFunction, } from '@roots/bud-support/express' import type {IncomingMessage, ServerResponse} from 'node:http' export interface HotEventStream { close(): void handler: NextHandleFunction publish(payload: Payload): void } /** * 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: CallableFunction) => 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 */ public interval: NodeJS.Timeout /** * Class constructor */ public constructor() { this.interval = setInterval( () => tapClients(pingClient), updateInterval, ).unref() } /** * Close stream */ public close() { clearInterval(this.interval) tapClients(closeClient) clients = {} } /** * Handle update message */ public handle( req: IncomingMessage, res: ServerResponse, _next?: NextFunction, ) { 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 */ public publish(payload: Partial<Payload>) { if (!payload) return tapClients(client => { client.write(`data: ${JSON.stringify(payload)} \n\n`) }) } }