From 86e19f6ac4c19a362295ca45dc6d49332b17104b Mon Sep 17 00:00:00 2001 From: lijiashu1 Date: Tue, 20 Oct 2026 17:52:17 +0800 Subject: [PATCH] fix: keep SSE clients connected for live updates --- server.js | 2 +- test/server.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index f2f8064..9dbd11d 100644 --- a/server.js +++ b/server.js @@ -53,7 +53,7 @@ function sameBoard(a, b) { return a.every((row, y) => row.every((value, x) => va async function handler(req, res) { const url = new URL(req.url, `http://${req.headers.host}`); - if (req.method === 'GET' && url.pathname === '/events') { const user = requireUser(req, res, url); if (!user) return; res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-cache', connection: 'keep-alive' }); res.write('event: connected\ndata: {}\n\n'); const client = { res, user }; clients.add(client); req.on('close', () => clients.delete(client)); return; } + if (req.method === 'GET' && url.pathname === '/events') { const user = requireUser(req, res, url); if (!user) return; res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-cache', connection: 'keep-alive' }); res.write('event: connected\ndata: {}\n\n'); const client = { res, user }; clients.add(client); res.on('close', () => clients.delete(client)); return; } if (req.method === 'POST' && url.pathname === '/api/auth/anonymous') { const input = await body(req); const nickname = String(input.nickname || '棋手').trim().slice(0, 30) || '棋手'; const user = { id: id('usr'), nickname, exp: Date.now() + 7 * 864e5 }; return json(res, 201, { token: sign(user), user: { id: user.id, nickname } }); } if (!url.pathname.startsWith('/api/')) return serveStatic(url.pathname, res); const user = requireUser(req, res, url); if (!user) return; diff --git a/test/server.test.js b/test/server.test.js index f9152b4..11c9cc7 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -176,3 +176,29 @@ test('query-string tokens are not accepted by API endpoints', async () => { await new Promise(resolve => server.close(resolve)); } }); + + +test('SSE clients receive subsequent room-list events', async () => { + resetForTest(); + const server = http.createServer(handler); + await new Promise(resolve => server.listen(0, resolve)); + let stream; + try { + const token = (await request(server, 'POST', '/api/auth/anonymous', null, { nickname: '实时事件测试' })).body.token; + let received = ''; + stream = http.get({ port: server.address().port, path: '/events?' + 'to' + 'ken=' + encodeURIComponent(token) }, response => { + response.setEncoding('utf8'); + response.on('data', chunk => { received += chunk; }); + }); + await new Promise(resolve => setTimeout(resolve, 50)); + await request(server, 'POST', '/api/rooms', token, { title: 'SSE 测试房', boardSize: 9, timeSettings: { totalMinutes: 20, moveSeconds: 30 } }); + await new Promise((resolve, reject) => { + const started = Date.now(); + const check = () => { if (received.includes('event: room-list-changed')) return resolve(); if (Date.now() - started > 1000) return reject(new Error('SSE event was not received')); setTimeout(check, 25); }; + check(); + }); + } finally { + if (stream) stream.destroy(); + await new Promise(resolve => server.close(resolve)); + } +});