| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 |
- "use strict";
- function safeJson(value) {
- try {
- return JSON.stringify(value);
- } catch {
- return "{}";
- }
- }
- class SseHub {
- constructor(options = {}) {
- this.clients = new Map();
- this.keepaliveMs = Number.isFinite(options.keepaliveMs) ? Math.max(5000, options.keepaliveMs) : 25000;
- }
- _setFor(conversationId) {
- if (!this.clients.has(conversationId)) {
- this.clients.set(conversationId, new Set());
- }
- return this.clients.get(conversationId);
- }
- subscribe(conversationId, req, res) {
- const key = conversationId || "*";
- const set = this._setFor(key);
- const client = { res, key };
- set.add(client);
- res.writeHead(200, {
- "Content-Type": "text/event-stream; charset=utf-8",
- "Cache-Control": "no-cache, no-transform",
- Connection: "keep-alive",
- "X-Accel-Buffering": "no",
- });
- res.write(": connected\n\n");
- const keepalive = setInterval(() => {
- try {
- res.write(": keepalive\n\n");
- } catch {
- this._unsubscribe(client, keepalive);
- }
- }, this.keepaliveMs);
- const cleanup = () => this._unsubscribe(client, keepalive);
- req.on("close", cleanup);
- req.on("error", cleanup);
- res.on("error", cleanup);
- }
- _unsubscribe(client, timer) {
- if (timer) clearInterval(timer);
- const set = this.clients.get(client.key);
- if (!set) return;
- set.delete(client);
- if (set.size === 0) {
- this.clients.delete(client.key);
- }
- }
- _broadcast(set, event, payload) {
- if (!set || set.size === 0) return;
- const data = safeJson(payload);
- for (const client of set) {
- try {
- client.res.write(`event: ${event}\n`);
- client.res.write(`data: ${data}\n\n`);
- } catch {
- // Ignore write failures; cleanup happens via close handler.
- }
- }
- }
- publishConversation(conversationId, payload) {
- const body = {
- conversationId,
- timestamp: new Date().toISOString(),
- ...payload,
- };
- this._broadcast(this.clients.get(conversationId), "conversation_update", body);
- this._broadcast(this.clients.get("*"), "conversation_update", body);
- }
- }
- module.exports = {
- SseHub,
- };
|