import assert from 'node:assert/strict' import { once } from 'node:events' import { mkdtemp, writeFile, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { test } from 'node:test' import { WebSocket } from 'ws' import { Controller } from './controller.js' import { LanServer } from './lan.js' test('LAN: код, подтверждение, cookie, WebSocket и восстановление без повтора', async () => { const root = await mkdtemp(join(tmpdir(), 'screen-test-')) await writeFile(join(root, 'index.html'), 'local') let count = 0 const controller = new Controller((state) => { count++ server.broadcast(state) }) const server = new LanServer( root, () => controller.state, (input) => controller.command(input), ) const sockets: WebSocket[] = [] try { await server.start('127.0.0.1') const origin = server.url assert.equal((await fetch(origin)).status, 200) assert.equal((await fetch(origin + '/package.json')).status, 404) assert.equal( ( await fetch(origin + '/pair', { method: 'POST', headers: { Origin: origin }, body: JSON.stringify({ code: 'wrong' }), }) ).status, 403, ) assert.equal( ( await fetch(origin + '/pair', { method: 'POST', headers: { Origin: 'http://evil.example' }, body: '{}', }) ).status, 403, ) const denied = new WebSocket(origin.replace('http', 'ws') + '/ws', { origin }) sockets.push(denied) await new Promise((resolve) => denied.on('error', () => resolve())) const paired = await fetch(origin + '/pair', { method: 'POST', headers: { Origin: origin }, body: JSON.stringify({ code: server.code }), }) const { id } = (await paired.json()) as { id: string } assert.equal((await fetch(origin + '/pair/' + id)).status, 202) server.approve(id) const approved = await fetch(origin + '/pair/' + id) assert.equal(approved.status, 200) const cookie = approved.headers.get('set-cookie')!.split(';')[0] assert.equal((await fetch(origin + '/pair/' + id)).status, 404) async function connect() { const ws = new WebSocket(origin.replace('http', 'ws') + '/ws', { origin, headers: { Cookie: cookie }, }) sockets.push(ws) const [data] = await once(ws, 'message') return { ws, state: JSON.parse(data.toString()) } } const first = await connect() assert.equal(first.state.revision, 0) let received = once(first.ws, 'message') const command = { id: 'phone-1', action: 'play', media: 'audio', position: 0 } first.ws.send(JSON.stringify(command)) await received first.ws.send(JSON.stringify(command)) // A fresh command is a barrier proving that the duplicate was processed first. received = once(first.ws, 'message') first.ws.send(JSON.stringify({ ...command, id: 'phone-2', action: 'pause' })) await received assert.equal(count, 2) first.ws.close() await once(first.ws, 'close') const restored = await connect() assert.equal(restored.state.revision, 2) assert.equal(count, 2) const closed = once(restored.ws, 'close') restored.ws.send(JSON.stringify({ action: 'delete' })) await closed assert.equal(count, 2) const oversized = await connect() const oversizedClosed = once(oversized.ws, 'close') oversized.ws.send('x'.repeat(3000)) await oversizedClosed assert.equal(count, 2) assert.equal((await fetch(origin)).status, 200) } finally { for (const ws of sockets) ws.terminate() await server.close() await rm(root, { recursive: true, force: true }) } })