Transfer saved songs directly between paired devices with resumable OPFS writes

This commit is contained in:
Jonathan Sykes
2026-10-03 20:32:45 +08:00
parent 88e2e67eeb
commit 08ff8398b5
10 changed files with 253 additions and 1 deletions

View File

@@ -0,0 +1,28 @@
const { test, expect } = require('@playwright/test');
test('paired pages save verified bytes directly over WebRTC with metadata-only server logs', async ({ browser, request }) => {
const a = await browser.newContext(), b = await browser.newContext();
const sender = await a.newPage(), receiver = await b.newPage();
await sender.goto('/?id=sender'); await receiver.goto('/?id=receiver');
await sender.waitForFunction(() => window.ready); await receiver.waitForFunction(() => window.ready);
await receiver.evaluate(async () => {
const bytes = Uint8Array.from({ length: 2000000 }, (_, i) => i % 251), h = Sha256.create(); h.update(bytes);
const dir = await (await navigator.storage.getDirectory()).getDirectoryHandle('videos', { create: true });
const w = await (await dir.getFileHandle(`testvideo.${h.hex()}.direct.part`, { create: true })).createWritable();
await w.write(bytes.slice(0, 12345)); await w.close();
});
await sender.locator('#send').click();
await expect(receiver.getByRole('dialog')).toBeVisible();
expect(await receiver.evaluate(() => !!window.received)).toBe(false);
await receiver.getByRole('button', { name: 'Accept & save' }).click();
await receiver.waitForFunction(() => !!window.received);
const result = await receiver.evaluate(async () => {
const d = await (await navigator.storage.getDirectory()).getDirectoryHandle('videos');
const f = await (await d.getFileHandle('testvideo.mp4')).getFile();
const hash = Sha256.create(); hash.update(new Uint8Array(await f.arrayBuffer())); return { size: f.size, cid: hash.hex(), expected: window.received.cid };
});
expect(await receiver.evaluate(() => window.resumeOffsets)).toEqual([12345]);
expect(result.size).toBe(2000000); expect(result.cid).toBe(result.expected);
const logs = await (await request.get('/audit')).json();
expect(logs.every(x => !x.http && ['invite','accept','signal','complete'].includes(x.signalling))).toBe(true);
await a.close(); await b.close();
});

27
tests/fixtures/direct-server.js vendored Normal file
View File

@@ -0,0 +1,27 @@
import { createDirectRelay } from '../../server/direct-relay.js';
const relay = createDirectRelay(), peers = new Map(), log = [];
Bun.serve({ port: 8095,
async fetch(req, server) {
const url = new URL(req.url);
if (url.pathname === '/ws') { const id = url.searchParams.get('id'); if (!['sender', 'receiver'].includes(id)) return new Response('test member required', { status: 403 }); server.upgrade(req, { data: { id } }); return; }
if (url.pathname === '/audit') return Response.json(log);
if (url.pathname.startsWith('/api/')) { log.push({ http: url.pathname }); return new Response('Media forbidden in direct fixture', { status: 500 }); }
if (url.pathname === '/') return new Response(`<!doctype html><button id="send">Send</button><div id="status"></div><script src="/sha256-wasm.js"></script><script src="/sha256.js"></script><script src="/direct-protocol.js"></script><script src="/settings-sections.js"></script><script src="/direct-media.js"></script><script>
const id = new URL(location.href).searchParams.get('id');
const ws = new WebSocket('ws://localhost:8095/ws?id=' + id);
window.resumeOffsets = []; const NativeWorker = window.Worker; window.Worker = class extends NativeWorker { constructor(...args) { super(...args); this.addEventListener('message', e => { if(e.data.op === 'opened') window.resumeOffsets.push(e.data.offset); }); } };
const meta = { id: 'testvideo', title: 'Direct test', channel: 'Fixture', duration: 1 };
window.OPFS = { getFileObject: async () => new File([Uint8Array.from({length: 2000000}, (_,i)=>i%251)], 'test.mp4') };
DirectMedia.configure({ current: () => meta, notify: text => document.getElementById('status').textContent = text, saved: file => window.received = file, fallback: () => window.failed = true });
ws.onopen = () => { DirectMedia.attach('test', { send: m => ws.send(JSON.stringify(m)), peers: () => [{id: id === 'sender' ? 'receiver' : 'sender', name: 'Other'}] }); window.ready = true; };
ws.onmessage = e => DirectMedia.handle('test', JSON.parse(e.data));
document.getElementById('send').onclick = () => DirectMedia.offer('test','receiver',meta);
</script>`, { headers: { 'Content-Type': 'text/html' } });
const path = url.pathname.slice(1); if (!/^[a-z0-9-]+\.js$/.test(path)) return new Response('not found', { status: 404 });
return new Response(Bun.file(`frontend/${path}`));
}, websocket: {
open(ws) { peers.set(ws.data.id, ws); },
message(ws, message) { log.push({ signalling: typeof message === 'string' ? JSON.parse(message).action : 'BINARY' }); relay.handle(ws.data.id, typeof message === 'string' ? message : null, peers, (target,m) => target.send(JSON.stringify(m))); },
close(ws) { peers.delete(ws.data.id); },
}
});