/* * Does the stream delay hold back the INTERNET without holding back the ROOM? * * Starts the real server on a spare port with a throwaway DATA_DIR, connects * two listeners — one plain (the room), one carrying x-forwarded-for (through * the tunnel) — plus a fake engine, and times when each one is told things. */ import { spawn } from 'child_process'; import { WebSocket } from 'ws'; import fs from 'fs'; import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; const DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'ct-delay-')); const PORT = 3199; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const srv = spawn(process.execPath, [path.join(__dirname, 'server', 'server.js')], { env: { ...process.env, PORT: String(PORT), DATA_DIR: DIR }, stdio: ['ignore', 'pipe', 'pipe'] }); const logs = []; srv.stdout.on('data', (d) => logs.push(String(d))); srv.stderr.on('data', (d) => logs.push('ERR ' + d)); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const B = 'http://127.0.0.1:' + PORT; /* Wait for OUR server, identified by the config it writes into OUR dir — a stray copy left on this port by an earlier run would answer /healthz and then be tested instead. */ const CFG = path.join(DIR, 'config.json'); for (let i = 0; i < 80; i++) { if (fs.existsSync(CFG)) { try { await fetch(B + '/healthz'); break; } catch (_) {} } await sleep(150); } if (!fs.existsSync(CFG)) { console.error('server never started. log:\n' + logs.join('')); srv.kill(); process.exit(1); } const cfg = JSON.parse(fs.readFileSync(CFG, 'utf8')); const KEY = cfg.controlToken, PIN = cfg.operatorPin, EKEY = cfg.engineKey; console.log('server up on ' + PORT + '; new PIN is ' + PIN.length + ' digits'); const DELAY = 4; await fetch(B + '/api/control/delay-set?s=' + DELAY + '&key=' + KEY); console.log('stream delay set to ' + DELAY + 's\n'); function listener(label, headers) { const ws = new WebSocket(B.replace('http', 'ws') + '/ws?role=listener&lang=eng_Latn', { headers }); const got = []; ws.on('message', (raw) => { const m = JSON.parse(raw); got.push({ t: m.t, at: Date.now(), text: m.text, delaySec: m.delaySec }); }); return { label, ws, got, ready: new Promise((r) => ws.on('open', r)) }; } const room = listener('room ', {}); const remote = listener('remote', { 'x-forwarded-for': '203.0.113.7' }); await Promise.all([room.ready, remote.ready]); const eng = new WebSocket(B.replace('http', 'ws') + '/ws?role=engine&key=' + encodeURIComponent(EKEY)); await new Promise((r) => eng.on('open', r)); await fetch(B + '/api/control/start?key=' + KEY); await sleep(300); const hello = (l) => l.got.find((g) => g.t === 'hello'); console.log('hello.delaySec room=' + hello(room).delaySec + ' remote=' + hello(remote).delaySec); const t0 = Date.now(); eng.send(JSON.stringify({ t: 'final', text: 'God has a plan for your life', cid: 'c1', tr: {} })); await sleep(DELAY * 1000 + 1200); let pass = 0, fail = 0; const check = (label, ok, detail) => { console.log((ok ? ' ok ' : ' FAIL ') + label + (detail ? ' ' + detail : '')); ok ? pass++ : fail++; }; console.log('\ncaption arrival, measured from the moment the engine sent it:'); for (const l of [room, remote]) { const c = l.got.find((g) => g.t === 'caption'); const dt = c ? (c.at - t0) / 1000 : null; console.log(' ' + l.label + ' ' + (c ? dt.toFixed(2) + 's "' + c.text + '"' : 'NEVER ARRIVED')); } const rc = room.got.find((g) => g.t === 'caption'), xc = remote.got.find((g) => g.t === 'caption'); check('the room gets it immediately', rc && (rc.at - t0) < 700, rc ? ((rc.at-t0)/1000).toFixed(2) + 's' : 'missing'); check('the internet waits the delay', xc && (xc.at - t0) >= DELAY * 1000 - 300, xc ? ((xc.at-t0)/1000).toFixed(2) + 's' : 'missing'); check('both got the same words', rc && xc && rc.text === xc.text); check('hello tells the room 0', hello(room).delaySec === 0); check('hello tells the internet ' + DELAY, hello(remote).delaySec === DELAY); console.log('\nlate joiner backlog (scrollback in the hello frame):'); function joiner(headers) { const ws = new WebSocket(B.replace('http', 'ws') + '/ws?role=listener&lang=eng_Latn', { headers }); return new Promise((res) => { ws.on('message', (raw) => { const m = JSON.parse(raw); if (m.t === 'hello') { ws.close(); res(m); } }); }); } const n = (h) => (h.scrollback || []).length; const jr = await joiner({}); const jx = await joiner({ 'x-forwarded-for': '203.0.113.8' }); console.log(' a sentence spoken ' + DELAY + '+s ago:'); console.log(' room backlog ' + n(jr) + ' · remote backlog ' + n(jx)); check('the room sees it', n(jr) >= 1); check('the internet sees it too — it is past their clock', n(jx) >= 1); /* Now the case that matters: something said JUST NOW must not reach a remote joiner's backlog, or the delay is defeated by refreshing the page. */ const before = n(jx); eng.send(JSON.stringify({ t: 'final', text: 'a secret just spoken', cid: 'c2', tr: {} })); await sleep(400); const jr2 = await joiner({}); const jx2 = await joiner({ 'x-forwarded-for': '203.0.113.9' }); const txt = (h) => (h.scrollback || []).map((r) => r.text).join(' | '); console.log(' a sentence spoken 0.4s ago:'); console.log(' room backlog: ' + txt(jr2)); console.log(' remote backlog: ' + txt(jx2)); check('the room sees the new sentence', txt(jr2).includes('a secret just spoken')); check('the internet does NOT — refreshing cannot skip the delay', !txt(jx2).includes('a secret just spoken')); check('server sized the audio window for the delay', logs.join('').includes('audio window now')); console.log('\n--- server log ---'); console.log(logs.join('').split('\n').filter((l) => /delay|window|START/.test(l)).map((l) => ' ' + l).join('\n')); console.log('\n' + pass + ' passed, ' + fail + ' failed'); srv.kill(); fs.rmSync(DIR, { recursive: true, force: true }); process.exit(fail ? 1 : 0);