0
0

feat(turn): self-hosted coturn support + time-limited creds + failure UX

- /api/ice: when TURN_SECRET is set, mint short-lived HMAC credentials
  (coturn use-auth-secret) so no permanent password is exposed and the relay
  can't be abused. Static TURN_USERNAME/CREDENTIAL still supported.
- share.html: connection watchdog + clear "couldn't connect on this network"
  message instead of a blank screen when no path can be established.
- deploy/coturn: ready-to-run turnserver.conf + docker-compose + README for
  hosting our own TURN on a VM we own (flat cost, no per-GB billing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
2026-06-16 14:36:05 +05:30
Ursprung 6ac280f178
Commit 54b74d5db1
5 geänderte Dateien mit 121 neuen und 8 gelöschten Zeilen
+15 -7
Datei anzeigen
@@ -122,16 +122,24 @@ route('GET', '/api/setup-state', async (req, res) => {
json(res, 200, { registrationOpen: !anyUser || process.env.ALLOW_REGISTRATION === '1' });
});
// ICE servers for WebRTC. Always includes a public STUN; adds managed TURN if
// configured via env (TURN_URLS comma-separated, TURN_USERNAME, TURN_CREDENTIAL).
// ICE servers for WebRTC. Always includes a public STUN; adds our TURN relay if
// configured. Two credential modes:
// - Shared secret (recommended, coturn `use-auth-secret`): set TURN_SECRET and we mint
// time-limited credentials per request (no permanent password is ever handed out, so
// outsiders can't reuse your relay). Optional TURN_TTL seconds (default 24h).
// - Static: set TURN_USERNAME + TURN_CREDENTIAL for a fixed long-term credential.
route('GET', '/api/ice', async (req, res) => {
const iceServers = [{ urls: 'stun:stun.l.google.com:19302' }];
if (process.env.TURN_URLS) {
iceServers.push({
urls: process.env.TURN_URLS.split(',').map((u) => u.trim()).filter(Boolean),
username: process.env.TURN_USERNAME || '',
credential: process.env.TURN_CREDENTIAL || '',
});
const urls = process.env.TURN_URLS.split(',').map((u) => u.trim()).filter(Boolean);
let username = process.env.TURN_USERNAME || '';
let credential = process.env.TURN_CREDENTIAL || '';
if (process.env.TURN_SECRET) {
const ttl = parseInt(process.env.TURN_TTL || '86400', 10);
username = String(Math.floor(Date.now() / 1000) + ttl); // coturn expects "<expiry>"
credential = require('crypto').createHmac('sha1', process.env.TURN_SECRET).update(username).digest('base64');
}
iceServers.push({ urls, username, credential });
}
json(res, 200, { iceServers });
});