openvibers/openvibe.chat Commits

Chat: history drops the legacy ?scope=stream opt-in (C-06) (#70)

OP OpenVibers committed 11 Oct 2026 · 035db35034 · 2 files, +6 −6

This closes the `?scope=stream` half of compatibility register C-06.

`GET /api/chat/:streamId/history` spans the streamer's whole channel: every slot and session, plus offline messages. `?scope=stream` was the opt-in to the old behaviour, which read a single session. Nothing uses it:
- **Access logs:** openvibe.live's nginx logs from 2026-08-04 to today have 10,110 `/api/chat` requests and no `scope=stream`.
- **Callers:** no file in any OpenVibers repo sends it.

This PR:
- **Change:** the history route ignores `scope`. A stream whose broadcaster is unknown still reads only its own session, as before.
- **Test:** `rest.test.js` drops the `?scope=stream` path from the deletion-cursor loop; the stream and channel paths stay.

`POST /api/chat/send`, the other half of C-06, stays. It is not a fallback but Chat's documented bot API: the `chat/send-request.v1` contract, rate limits in the README, and the no-JavaScript composer shares its handler. It was last called on 2026-08-16. The register entry is updated to call it a permanent public route.

`ov test test/rest.test.js` passes.

Changes

ddiff --git a/server/chat/routes.js b/server/chat/routes.js
iindex d70bfb6..2520e0b 100644
---- a/server/chat/routes.js
++++ b/server/chat/routes.js
@@@ -550,20 +550,20 @@ router.get('/:streamId/replay', optionalAuth, async (req, res) => {
// broadcaster's stream sessions (not just the one slot/session being viewed),
// so viewers keep context when a stream restarts or the streamer runs multiple
// slots. Each message carries its source stream (title/slug/live) so the client
--// can label it and let viewers hop between the streamer's live slots.
--// Pass ?scope=stream to restrict to the single session (legacy behavior).
++// can label it and let viewers hop between the streamer's live slots. A stream whose broadcaster is
++// unknown reads its own session only. (The legacy ?scope=stream opt-in is gone: compatibility register
++// C-06, unused since at least 2026-08-04.)
router.get('/:streamId/history', optionalAuth, async (req, res) => {
try {
const streamId = positiveInt(req.params.streamId);
if (streamId === null) return res.status(400).json({ error: 'Invalid stream ID' });
const limit = Math.min(parseInt(req.query.limit || '500'), 500);
const before = req.query.before; // ISO timestamp for pagination
-- const scope = req.query.scope || 'streamer';
// Resolve which broadcaster this stream belongs to.
const stream = await ctx.ensureStream(streamId);
const broadcasterId = stream ? stream.user_id : null;
-- const spanStreamer = scope !== 'stream' && broadcasterId;
++ const spanStreamer = !!broadcasterId;
// COALESCE the channel name from the source stream's owner (live rows) OR the
// message's channel_user_id (offline rows) so the streamer/channel tag renders
ddiff --git a/test/rest.test.js b/test/rest.test.js
iindex e8001cb..092db8d 100644
---- a/test/rest.test.js
++++ b/test/rest.test.js
@@@ -139,8 +139,8 @@ t('chat history: global page and cursor delta, deleted and expired rows hidden',
const inRoom = [];
for (const i of [0, 1]) inRoom.push(Number((await h.db.saveChatMessage({ stream_id: streamId, channel_user_id: streamer.id, user_id: bob.id, username: 'Bob', message: `s${i}`, message_type: 'chat' })).lastInsertRowid));
await h.db.deleteChatMessage(inRoom[0], staff.id);
-- for (const p of [`/api/chat/${streamId}/history`, `/api/chat/${streamId}/history?scope=stream&`, `/api/chat/channel/${streamer.id}/history`]) {
-- const d = await h.http('GET', `${p}${p.endsWith('&') ? '' : '?'}after_id=${inRoom[1]}`);
++ for (const p of [`/api/chat/${streamId}/history`, `/api/chat/channel/${streamer.id}/history`]) {
++ const d = await h.http('GET', `${p}?after_id=${inRoom[1]}`);
assert.strictEqual(d.status, 200, p);
assert.deepStrictEqual(d.body.deleted_ids, [inRoom[0]], p);
}