openvibers/openrestream Commits

OpenRe: the viewer signaling URL is wss://<host>/w/<id> behind the TLS vhost (it gave the loopback worker port). (#15)

OP OpenVibers committed 7 Oct 2026 · f7edb56cf4 · 2 files, +18 −2

A webrtc session's playback descriptor gave viewers ws://<public host>:<worker listen port>/w/<id> (server/store/sessions.js). In production the worker listens on loopback 9936 behind the TLS vhost on 443 (OPENRE_WEBRTC_PUBLIC_PORT=443, since 2026-10-07), so that URL is unreachable and mixed content. Behind 443 the descriptor now says wss://<public host>/w/<id>, exactly as the WHIP and broadcaster URLs already do (store/definitions.js); without it, ws:// on the worker port as before. OpenVibe.Bot's panel plays robot video from this URL.

Tests: test/webrtc.test.js 10/10 (new: the TLS case); test/webrtc-e2e.test.js needs real mediasoup workers and runs in CI.

Changes

ddiff --git a/server/store/sessions.js b/server/store/sessions.js
iindex 3d4b28a..e55993c 100644
---- a/server/store/sessions.js
++++ b/server/store/sessions.js
@@@ -234,9 +234,13 @@ function createSessions({ db, config, events, clock, definitions, workers }) {
}
if (session.protocol === 'webrtc' && ep.publicPort) {
// The viewer signaling endpoint, keyed by the session's playback id (never the key), and
-- // the mediasoup announced IP clients must be able to reach for media to flow.
++ // the mediasoup announced IP clients must be able to reach for media to flow. Behind the TLS
++ // vhost (OPENRE_WEBRTC_PUBLIC_PORT=443) it is wss:// with no port, as the WHIP and broadcaster
++ // URLs are (store/definitions.js); otherwise ws:// on the worker's own listen port.
++ const w = config.webrtc;
++ const tls = w.publicPort === 443;
descriptor.webrtc = {
-- signaling_url: `ws://${config.webrtc.publicHost}:${ep.publicPort}/w/${session.id}`,
++ signaling_url: tls ? `wss://${w.publicHost}/w/${session.id}` : `ws://${w.publicHost}:${ep.publicPort}/w/${session.id}`,
announced_ip: config.webrtc.media.announcedIp || null,
};
}
ddiff --git a/test/webrtc.test.js b/test/webrtc.test.js
iindex 44e36a8..5872fea 100644
---- a/test/webrtc.test.js
++++ b/test/webrtc.test.js
@@@ -101,6 +101,18 @@ t('WHIP PATCH sdpfrag: credentials, candidates, end-of-candidates; malformed bod
);
});
++t('behind the TLS vhost (OPENRE_WEBRTC_PUBLIC_PORT=443) the viewer signaling URL is wss:// with no port', async () => {
++ const rt = await runtime({ dir: tmpDir(), env: { MEDIASOUP_ANNOUNCED_IP: '203.0.113.7', OPENRE_WEBRTC_PUBLIC_PORT: '443', OPENRE_WEBRTC_PUBLIC_HOST: 'ingest.openre.stream' } });
++ const { definition, key } = await rt.store.definitions.create({ owner_subject: OWNER, protocols: ['webrtc'] });
++ const w = await rt.store.workers.register({ kind: 'webrtc', endpoints: { publicPort: 9936, egressPort: 19810 } });
++ await rt.store.workers.ready(w.id);
++ const a = await rt.store.sessions.admit({ definition, key, protocol: 'webrtc', worker: await rt.store.workers.get(w.id) });
++ await rt.store.sessions.transition(a.session.id, 'live', { reason: 'media_flowing', actor: 'worker:w' });
++ const pb = await rt.store.sessions.playback(await rt.store.sessions.get(a.session.id));
++ // The worker listens on loopback 9936; nginx serves it on 443, which is what a browser must use.
++ assert.strictEqual(pb.webrtc.signaling_url, `wss://ingest.openre.stream/w/${a.session.id}`);
++});
++
t('a webrtc session playback descriptor is { signaling_url, announced_ip } and carries thumbnail_url', async () => {
const rt = await runtime({ dir: tmpDir(), env: { MEDIASOUP_ANNOUNCED_IP: '203.0.113.7' } });
const { definition, key } = await rt.store.definitions.create({ owner_subject: OWNER, protocols: ['webrtc'] });