稳定测试版

This commit is contained in:
2025-10-28 19:40:13 +08:00
commit 183feef2ea
24 changed files with 11631 additions and 0 deletions

88
js/sse-server.js Normal file
View File

@@ -0,0 +1,88 @@
// sse-server.js (CommonJS)
const http = require('http');
const express = require('express');
const cors = require('cors');
function startSSE({
port = 3312,
path = '/events',
corsOrigin = '*', // 也可填 'http://localhost:8080' 或你的前端地址
heartbeatMs = 15000,
} = {}) {
const app = express();
if (corsOrigin) app.use(cors({ origin: corsOrigin, credentials: true }));
const clients = new Map(); // id -> res
app.get(path, (req, res) => {
res.set({
'Content-Type': 'text/event-stream; charset=utf-8',
'Cache-Control': 'no-cache, no-transform',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no'
});
if (corsOrigin) res.set('Access-Control-Allow-Origin', corsOrigin);
res.flushHeaders?.();
const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
clients.set(id, res);
// // 初次握手
// res.write(`event: hello\n`);
// res.write(`data: ${JSON.stringify({ connected: true, t: Date.now() })}\n\n`);
console.log("sse连接成功")
const timer = setInterval(() => {
if (!res.writableEnded) res.write(`: keep-alive ${Date.now()}\n\n`);
}, heartbeatMs);
req.on('close', () => {
clearInterval(timer);
clients.delete(id);
try { res.end(); } catch { }
});
});
// 简单健康检查
app.get('/health', (_req, res) => res.json({ ok: true, clients: clients.size }));
const server = http.createServer(app);
server.listen(port, () => {
console.log(`[SSE] listening at http://127.0.0.1:${port}${path}`);
});
// 广播event 自定义data 可传对象/字符串/Buffer
function broadcast(eventOrData, maybeData) {
const hasEventName = typeof maybeData !== 'undefined'; // 两种调用方式
const eventName = hasEventName ? eventOrData : null;
const data = hasEventName ? maybeData : eventOrData;
const payload = Buffer.isBuffer(data)
? data.toString()
: (typeof data === 'string' ? data : JSON.stringify(data));
for (const [id, res] of clients.entries()) {
try {
if (eventName && eventName !== 'message') {
res.write(`event: ${eventName}\n`);
} // 否则省略 event 行 => 触发前端 onmessage
res.write(`data: ${payload}\n\n`);
} catch {
clients.delete(id);
try { res.end(); } catch { }
}
}
}
async function close() {
for (const [, res] of clients.entries()) {
try { res.end(); } catch { }
}
clients.clear();
await new Promise(r => server.close(r));
}
return { app, server, broadcast, close, clientsCount: () => clients.size };
}
module.exports = { startSSE };