Camera sharing, and the proxy's relay for the desktop app

- A camera is a second source in the share dialog; streams carry their kind,
  so icons and wording follow. Browsers without screen capture (phones) start
  on the camera.
- Settings, Voice: the address of a mumh5 web version as relay; its STUN and
  relay are then offered with every stream and take over when no direct
  connection comes up.
- The proxy hands out relay credentials at GET /api/relay, readable from any
  origin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 23:52:18 +02:00
co-authored by Claude Opus 5.5
parent 08aa5268d0
commit 819db06bc6
19 changed files with 208 additions and 78 deletions
+5 -2
View File
@@ -111,9 +111,12 @@ test('the proxy answers STUN over UDP and announces the port', async () => {
// Relay credentials for people on this site: a username that expires and its password
const base = `http://127.0.0.1:${proxy.port}`;
assert.equal((await (await fetch(`${base}/api/config`)).json()).turn, true);
const cred = await (await post(base, 'turn', {})).json();
// What clients need for screen sharing: the port, and relay credentials that expire.
// Readable from any origin, since the desktop app asks from outside this site.
const res = await fetch(`${base}/api/relay`, { headers: { Origin: 'app://mumh5' } });
assert.equal(res.headers.get('access-control-allow-origin'), '*');
const cred = await res.json();
assert.ok(Number(cred.username) > Date.now() / 1000 && cred.credential.length > 20 && cred.port === proxy.stunPort);
assert.equal((await post(base, 'turn', {}, { Origin: 'https://evil.example' })).status, 200, 'origins are open in this test');
assert.deepEqual([...answer.subarray(28)].map((b, i) => b ^ [0x21, 0x12, 0xa4, 0x42][i]), [127, 0, 0, 1]);
client.close();
} finally {