aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-18 22:39:39 -0700
committerAdam Soltys <[email protected]>2026-10-18 22:39:39 -0700
commit15f3b1f80a5ed99e7d2db17e46318f4abe3797ce (patch)
treec5cb883a829375b2c9b2fd563e2dbb6a877c3c4e /tools
parent1842b5ebbfd9e47323f6175babc222970a21a966 (diff)
downloadcoinosv3-15f3b1f80a5ed99e7d2db17e46318f4abe3797ce.tar.gz
coinosv3-15f3b1f80a5ed99e7d2db17e46318f4abe3797ce.zip
Going live: when the server can't take a dropped stream back (it restarted) and starts a new one, the app now announces the new one, once it plays, and says the old one is over (no recording: there's nothing in it). It went on announcing the dead address, and viewers saw "The stream isn't playing right now" while the camera sent to an address nobody knew. The streamer's own view follows.
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
Diffstat (limited to 'tools')
-rw-r--r--tools/go-live-test.js36
1 files changed, 32 insertions, 4 deletions
diff --git a/tools/go-live-test.js b/tools/go-live-test.js
index 82d6c7c..61e3c02 100644
--- a/tools/go-live-test.js
+++ b/tools/go-live-test.js
@@ -8,7 +8,7 @@
import puppeteer from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { naddrEncode } from 'nostr-tools/nip19';
-import { mkdtempSync, rmSync } from 'node:fs';
+import { mkdtempSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let ok = true;
@@ -23,8 +23,17 @@ const names = Bun.serve({ port: 0, hostname: '127.0.0.1', fetch: (req) => Respon
process.env.LIVE_DATA = dir;
process.env.NAMES_URL = names.url.origin;
process.env.LIVE_BASE = `http://localhost:${PORT}/live`;
-const live = await import(process.env.LIVE_SERVER || '../../coinosv3-server/live/server.js');
-const server = Bun.serve({ port: PORT, websocket: live.websocket, fetch: async (req, srv) => {
+const LIVE_PATH = process.env.LIVE_SERVER || '../../coinosv3-server/live/server.js';
+let live = await import(LIVE_PATH);
+// each socket stays with the server (module) that took it, so a "restart"
+// (a fresh import) leaves the old connections to the old one
+const sockMod = new WeakMap();
+const websocket = {
+ open: (ws) => { sockMod.set(ws, live); live.websocket.open?.(ws); },
+ message: (ws, m) => (sockMod.get(ws) || live).websocket.message?.(ws, m),
+ close: (ws, c, r) => (sockMod.get(ws) || live).websocket.close?.(ws, c, r),
+};
+const server = Bun.serve({ port: PORT, websocket, fetch: async (req, srv) => {
const path = new URL(req.url).pathname;
if (path.startsWith('/live/')) {
if (path === '/live/ingest') try { owner = JSON.parse(atob(new URL(req.url).searchParams.get('auth'))).pubkey; } catch {}
@@ -43,7 +52,7 @@ const relayStub = (fx) => {
const Real = window.WebSocket;
window.WebSocket = function (...a) {
const ws = new Real(...a);
- if (/\/live\/ingest/.test(String(a[0]))) return ws; // the live server itself
+ if (/\/live\/ingest/.test(String(a[0]))) { (window.__ingest = window.__ingest || []).push(ws); return ws; } // the live server itself
const send = ws.send.bind(ws);
ws.send = (data) => {
try {
@@ -131,6 +140,25 @@ try {
check('the server counts the watcher', st.viewers >= 1, JSON.stringify(st));
await viewer.close();
+ console.log('\n[the server restarts mid-stream]');
+ // a fresh server, and the old one's shutdown: its streams end
+ live = await import(LIVE_PATH + '?restart=1');
+ const oldId = tag(ann, 'd');
+ const sj = `${dir}/${oldId}/stream.json`;
+ writeFileSync(sj, JSON.stringify({ ...JSON.parse(readFileSync(sj, 'utf8')), state: 'ended', ended: Date.now() }));
+ const before = (await lives(page)).length;
+ await page.evaluate(() => window.__ingest.forEach((w) => w.close()));
+ check('the app comes back as a new stream and announces it', await waitFor((n) => window.__published.filter((e) => e.kind === 30311).slice(n)
+ .some((e) => e.tags.some((x) => x[0] === 'status' && x[1] === 'live')), 40000, before));
+ const after = (await lives(page)).slice(before);
+ const again = after.find((e) => tag(e, 'status') === 'live');
+ check('...at its new address, not the dead one', again && tag(again, 'd') !== oldId && tag(again, 'streaming').includes(tag(again, 'd')), JSON.stringify(after.map((e) => [tag(e, 'd'), tag(e, 'status')])));
+ const gone = after.find((e) => tag(e, 'd') === oldId);
+ check('...the old one said to be over, with no recording', gone && tag(gone, 'status') === 'ended' && !gone.tags.some((x) => x[0] === 'recording'));
+ const pl = await (await fetch(tag(again, 'streaming'))).text();
+ check('...and the new one plays', (pl.match(/\.ts/g) || []).length >= 3, (pl.match(/\.ts/g) || []).length + ' segments');
+ check('the streamer\'s own page follows to it', await waitFor((a) => !!document.querySelector('.live-page .bc-bar'), 5000));
+
console.log('\n[the end]');
await page.evaluate(() => document.querySelector('.bc-end').click());
check('End leaves the stream\'s page', await waitFor(() => !document.querySelector('.bc-bar'), 5000));