openvibers/openvibe.live Commits

Chat's 'updating' notice no longer sticks after a Live deploy (#246)

OP OpenVibers committed 26 days ago · 325f8e47be · 5 files, +37 −17

* Chat's 'updating' notice no longer sticks: Live stops sending a chat restart notice (its restarts never touch Chat's sockets), and a notice only readies a quick reconnect for a minute; 'updating' shows once the socket really drops (owner's popout, 2026-10-18)

* service-kit test: readiness is the first stop step (the chat restart notice it was ordered against is gone)

Changes

ddiff --git a/docs/architecture.md b/docs/architecture.md
iindex da7c4983..b3eb9786 100644
---- a/docs/architecture.md
++++ b/docs/architecture.md
@@@ -138,8 +138,9 @@ Client WS → OpenVibe.Chat (127.0.0.1:4400; nginx routes /ws/chat, /api/chat/,
Live runs no chat WebSocket server or chat REST routes: it delivers to Chat's typed ingress through
`server/chat/chat-delivery.js` (producer pushes, moderation, cache hints) and reads Chat's snapshot for
presence. The room is derived from the stream the client joined; offline channel chat applies the
--channel's bans and chat rules. Reconnects are jittered; a `server_restart` notice shows "OpenVibe is
--updating" and refills history on reconnect. See [chat-system.md](chat-system.md).
++channel's bans and chat rules. Reconnects are jittered; Chat's `server_restart` notice makes the next reconnect quick and,
++if the socket then drops, shows "OpenVibe is updating" until it is back (history refills on reconnect). Live sends
++no such notice: its restarts do not touch chat sockets. See [chat-system.md](chat-system.md).
## Server runtime
ddiff --git a/public/js/chat.js b/public/js/chat.js
iindex d7a11e32..82acb134 100644
---- a/public/js/chat.js
++++ b/public/js/chat.js
@@@ -2787,6 +2787,7 @@ function initChat(streamId, channelUserId = null) {
*/
let _chatConnDropAt = 0;
let _chatServerUpdating = false;
++let _chatUpdatingTimer = 0;
function _chatConnState(state, detail) {
const { messages: container } = getChatEl();
if (container) {
@@@ -3428,10 +3429,14 @@ function handleChatMessage(msg) {
addSystemMessage('Message blocked by this streamer\'s Anti-Slur Nudge setting.');
break;
case 'server_restart':
-- // A deploy is restarting the server. No reload needed: the socket reconnects on its own
-- // (with a short random delay, so everyone does not arrive at once) and history refills.
++ // A deploy is restarting the chat server. No reload needed: if the socket drops it reconnects
++ // on its own (with a short random delay, so everyone does not arrive at once), and only then
++ // does the reader see "updating". A restart that never drops this socket says nothing: the
++ // notice used to show "updating" at once and clear it only on a reconnect, so it stuck when
++ // the socket stayed up (owner's popout, 2026-10-18).
_chatServerUpdating = true;
-- _chatConnState('updating');
++ clearTimeout(_chatUpdatingTimer);
++ _chatUpdatingTimer = setTimeout(() => { _chatServerUpdating = false; }, 60000);
break;
case 'update': {
// Deploy notice: one rolling card per run of deploys, replaced in place by id (never duplicated).
ddiff --git a/server/index.js b/server/index.js
iindex e28ee783..c1a8e7c8 100644
---- a/server/index.js
++++ b/server/index.js
@@@ -163,16 +163,6 @@ const stopper = gracefulStop({
// gating on /api/ready should see that before the socket actually closes.
_bootComplete = false;
},
-- async () => {
-- // Notify all chat clients before closing connections
-- try {
-- await require('./chat/chat-delivery').event({ kind: 'all' }, {
-- type: 'server_restart',
-- message: '⚙️ Chat server restarting — you will be reconnected automatically.',
-- timestamp: new Date().toISOString(),
-- });
-- } catch { /* non-critical */ }
-- },
// Kill any in-flight whisper/ffmpeg transcription children so they don't orphan
// (and so their temp files get cleaned by the close handlers). The interrupted VOD
// is left in 'processing' → re-queued to 'pending' on next boot (crash recovery).
ddiff --git a/test/chat-restart-notice.test.js b/test/chat-restart-notice.test.js
nnew file mode 100644
iindex 00000000..a9f5ced6
---- /dev/null
++++ b/test/chat-restart-notice.test.js
@@@ -0,0 +1,24 @@
++/**
++ * A restart notice never leaves "OpenVibe is updating" on screen while chat is connected (owner's popout, 2026-10-18:
++ * the notice stuck after a Live deploy). Live sends no notice (its restarts do not touch Chat's sockets); the client
++ * shows "updating" only once the socket actually drops, and forgets the notice after a minute.
++ *
++ * node test/chat-restart-notice.test.js
++ */
++'use strict';
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++
++const root = path.join(__dirname, '..');
++const index = fs.readFileSync(path.join(root, 'server', 'index.js'), 'utf8');
++const chat = fs.readFileSync(path.join(root, 'public', 'js', 'chat.js'), 'utf8');
++
++assert.ok(!/type: 'server_restart'/.test(index), "Live's shutdown sends no chat restart notice");
++const at = chat.indexOf("case 'server_restart':");
++assert.ok(at > 0, 'the client still understands the notice (Chat sends it)');
++const body = chat.slice(at, chat.indexOf('break;', at));
++assert.ok(!/_chatConnState\(/.test(body), 'the notice alone changes nothing on screen');
++assert.ok(/_chatServerUpdating = true/.test(body) && /setTimeout\(\(\) => \{ _chatServerUpdating = false; \}, 60000\)/.test(body), 'it arms a quick reconnect for a minute');
++assert.ok(/_chatConnState\(_chatServerUpdating \? 'updating' : 'reconnecting'\)/.test(chat), 'a drop after the notice says "updating"');
++console.log('chat restart notice: all checks passed');
ddiff --git a/test/service-kit.test.js b/test/service-kit.test.js
iindex 43758440..5d19bf9f 100644
---- a/test/service-kit.test.js
++++ b/test/service-kit.test.js
@@@ -30,8 +30,8 @@ assert.match(call, /deadlineExitCode: 1/);
assert.match(call, /beforeDrain:/);
assert.ok(at > source.indexOf('const server = http.createServer(app);'), 'the stopper is created after the server exists');
const stopAt = call.indexOf('stop: [');
--assert.ok(stopAt > 0 && call.indexOf('_bootComplete = false', stopAt) < call.indexOf("require('./chat/chat-delivery')", stopAt),
-- 'readiness flips before anything else stops');
++const firstStep = call.slice(stopAt, call.indexOf('\n },', stopAt));
++assert.ok(stopAt > 0 && firstStep.includes('_bootComplete = false'), 'readiness flips before anything else stops (the first stop step)');
const fn = source.slice(source.indexOf('function shutdown('), source.indexOf("process.on('SIGTERM', shutdown)"));
assert.match(fn, /if \(drill\.enabled\) \{/, 'the drill branch is still there');