UNPKG

@lodestar/api

Version:

A Typescript REST client for the Ethereum Consensus API

66 lines 3.78 kB
import { ApiError, createFastifyRoutes } from "../../utils/server/index.js"; import { eventTypes, getDefinitions, getEventSerdes } from "../routes/events.js"; export function getRoutes(config, methods) { const eventSerdes = getEventSerdes(config); const serverRoutes = createFastifyRoutes(getDefinitions(config), methods); return { // Non-JSON route. Server Sent Events (SSE) eventstream: { ...serverRoutes.eventstream, handler: async (req, res) => { const validTopics = new Set(Object.values(eventTypes)); for (const topic of req.query.topics) { if (!validTopics.has(topic)) { throw new ApiError(400, `Invalid topic: ${topic}`); } } const controller = new AbortController(); try { // Add injected headers from other plugins. This is required for fastify-cors for example // From: https://github.com/NodeFactoryIo/fastify-sse-v2/blob/b1686a979fbf655fb9936c0560294a0c094734d4/src/plugin.ts for (const [key, value] of Object.entries(res.getHeaders())) { if (value !== undefined) res.raw.setHeader(key, value); } res.raw.setHeader("Content-Type", "text/event-stream"); res.raw.setHeader("Cache-Control", "no-cache,no-transform"); res.raw.setHeader("Connection", "keep-alive"); // It was reported that chrome and firefox do not play well with compressed event-streams https://github.com/lolo32/fastify-sse/issues/2 res.raw.setHeader("x-no-compression", 1); // In case this beacon node is behind a NGINX, instruct it to disable buffering which can disrupt SSE by // infinitely buffering it. http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering // Source: https://stackoverflow.com/questions/13672743/eventsource-server-sent-events-through-nginx res.raw.setHeader("X-Accel-Buffering", "no"); await new Promise((resolve, reject) => { void methods.eventstream({ topics: req.query.topics, signal: controller.signal, onEvent: (event) => { try { const data = eventSerdes.toJson(event); res.raw.write(serializeSSEEvent({ event: event.type, data })); } catch (e) { reject(e); } }, }); // The stream will never end by the server unless the node is stopped. // In that case the BeaconNode class will call server.close() and end this connection. // The client may disconnect and we need to clean the subscriptions. req.socket.once("close", () => resolve()); req.socket.once("end", () => resolve()); }); // api.eventstream will never stop, so no need to ever call `res.raw.end();` } finally { controller.abort(); } }, }, }; } export function serializeSSEEvent(chunk) { return [`event: ${chunk.event}`, `data: ${JSON.stringify(chunk.data)}`, "\n"].join("\n"); } //# sourceMappingURL=events.js.map