Skip to content

feat: add WebRTC signaling server plugin#208

Merged
melvincarvalho merged 7 commits into
gh-pagesfrom
issue-207-webrtc-signaling
Mar 15, 2026
Merged

feat: add WebRTC signaling server plugin#208
melvincarvalho merged 7 commits into
gh-pagesfrom
issue-207-webrtc-signaling

Conversation

@melvincarvalho

Copy link
Copy Markdown
Contributor

Summary

  • Lightweight WebRTC signaling server (~110 lines) for peer-to-peer connections
  • Relays SDP offers/answers and ICE candidates between WebID-authenticated users
  • Peer presence notifications (join/leave)
  • New CLI flags: --webrtc, --webrtc-path
  • WebSocket endpoint at /.webrtc (default)

How it works

JSS acts as the matchmaker — once peers exchange SDP/ICE via the signaling server, all media and data flows directly between them. No new dependencies.

Test plan

  • All 327 existing tests pass
  • Manual test: two browser tabs connect via WebSocket, exchange offer/answer

Fixes #207

Lightweight signaling server for WebRTC peer-to-peer connections.
Relays SDP offers/answers and ICE candidates between authenticated
users. The actual media/data flows directly between peers.

- New plugin: src/webrtc/index.js (~110 lines)
- CLI: --webrtc, --webrtc-path
- Env: JSS_WEBRTC, JSS_WEBRTC_PATH
- WebSocket endpoint at /.webrtc (default)
- WebID-based authentication required
- Peer presence notifications (join/leave)

Fixes #207
- 6 tests covering auth, peer presence, full signaling relay, error handling
- Skip WAC and dotfile checks for /.webrtc path
- Fix reconnection race: old socket close handler no longer deletes
  the replacement socket from the peers map

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional WebRTC signaling plugin to JSS, exposing a WebSocket endpoint for authenticated peers to exchange SDP/ICE and receive join/leave presence events.

Changes:

  • Introduces src/webrtc/index.js Fastify plugin implementing signaling + peer presence.
  • Adds CLI/config/env support for --webrtc and --webrtc-path.
  • Documents the new feature and adds a basic signaling lifecycle test.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/webrtc/index.js New WebRTC signaling WebSocket route + in-memory peer registry.
src/server.js Wires plugin behind webrtc flag; adjusts dotfile allowlist and auth bypass.
src/config.js Adds defaults + env var mapping for WebRTC settings.
bin/jss.js Adds CLI options and prints WebRTC status on startup.
README.md Documents WebRTC signaling usage and protocol.
test/webrtc.test.js Adds coverage for auth, presence, and message relay behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/webrtc/index.js Outdated
Comment on lines +27 to +29
// Connected peers: webId → WebSocket
const peers = new Map();

Comment thread src/webrtc/index.js Outdated
export async function webrtcPlugin(fastify, options = {}) {
const path = options.path || '/.webrtc';

await fastify.register(websocket);
Comment thread src/webrtc/index.js
Comment on lines +72 to +89
socket.on('message', (data) => {
let msg;
try {
msg = JSON.parse(data.toString());
} catch {
socket.send(JSON.stringify({ type: 'error', message: 'Invalid JSON' }));
return;
}

if (!msg.to || !msg.type) {
socket.send(JSON.stringify({ type: 'error', message: 'Missing "to" or "type" field' }));
return;
}

const target = peers.get(msg.to);
if (!target || target.readyState !== 1) {
socket.send(JSON.stringify({ type: 'error', message: 'Peer not online', peer: msg.to }));
return;
Comment thread src/server.js Outdated
// This prevents exposure of .git/, .env, .htpasswd, etc.
// Git protocol requests bypass this check when git is enabled
const ALLOWED_DOTFILES = ['.well-known', '.acl', '.meta', '.pods', '.notifications', '.account'];
const ALLOWED_DOTFILES = ['.well-known', '.acl', '.meta', '.pods', '.notifications', '.account', '.webrtc'];
Comment thread test/webrtc.test.js Outdated
Comment on lines +50 to +58
const timer = setTimeout(() => reject(new Error(`Timeout waiting for "${type}"`)), timeout);
ws.on('message', function handler(data) {
const msg = JSON.parse(data.toString());
if (msg.type === type) {
clearTimeout(timer);
ws.removeListener('message', handler);
resolve(msg);
}
});
- Move peers Map inside plugin (instance-scoped), add onClose cleanup hook
- Guard @fastify/websocket registration to avoid duplicates
- Allowlist signaling types (offer/answer/candidate/hangup), enforce 64KB max
- Only add dotfile exception when webrtcEnabled, derive from webrtcPath
- Fix test waitForMessage to clean up listeners on timeout/close

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional WebRTC signaling feature to JSS, exposing a WebSocket endpoint for relaying SDP/ICE messages between authenticated peers.

Changes:

  • Introduces a Fastify WebRTC signaling plugin with peer presence notifications and relay logic.
  • Wires new webrtc / webrtcPath configuration through CLI flags, config defaults, env vars, and server registration.
  • Adds README documentation and a new automated test suite for the signaling behavior.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/webrtc/index.js New WebRTC signaling WebSocket plugin (auth + relay + presence).
src/server.js Registers the plugin when enabled; updates dotfile allowlist and WAC-skip routing logic.
src/config.js Adds webrtc defaults and env var mapping.
bin/jss.js Adds CLI flags and passes WebRTC config into server startup.
test/webrtc.test.js Adds tests for auth, presence notifications, relay lifecycle, and errors.
README.md Documents the new WebRTC feature and protocol.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/webrtc/index.js
Comment on lines +95 to +100
socket.on('message', (data) => {
// Enforce max message size
if (data.length > MAX_MESSAGE_SIZE) {
socket.send(JSON.stringify({ type: 'error', message: 'Message too large' }));
return;
}
Comment thread src/webrtc/index.js Outdated
Comment on lines +127 to +130
// Relay the message, replacing 'to' with 'from'
const relay = { ...msg, from: webId };
delete relay.to;
target.send(JSON.stringify(relay));
Comment thread src/server.js Outdated
Comment on lines 332 to 333
if (webrtcEnabled) ALLOWED_DOTFILES.push(webrtcPath.split('/').pop());
fastify.addHook('onRequest', async (request, reply) => {
Comment thread src/server.js Outdated
request.url.startsWith('/storage/') ||
(payEnabled && isPayRequest(request.url)) ||
(mongoEnabled && (request.url === '/db' || request.url.startsWith('/db/'))) ||
(webrtcEnabled && request.url.startsWith(webrtcPath)) ||
- Use Buffer.byteLength for reliable message size enforcement
- Whitelist relay payload fields to prevent prototype pollution
- Exact path match for dotfile and WAC bypass (no prefix leakage)
- Dotfile exception only via early-return hook, not ALLOWED_DOTFILES array

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional WebRTC signaling WebSocket plugin to JSS, enabling WebID-authenticated peers to exchange SDP/ICE messages via a lightweight relay endpoint.

Changes:

  • Introduces a new webrtcPlugin (/.webrtc by default) to relay offers/answers/candidates and emit peer presence events.
  • Wires WebRTC enablement and path through server config, CLI flags, and environment variables.
  • Adds automated tests plus README documentation for the new signaling endpoint.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/webrtc/index.js New Fastify WebSocket signaling plugin with peer tracking and message relay.
src/server.js Registers the plugin behind a feature flag and allows the dotfile-like endpoint through existing guards.
src/config.js Adds webrtc / webrtcPath defaults and env var mappings.
bin/jss.js Adds CLI flags and prints WebRTC status on startup.
test/webrtc.test.js New end-to-end WebSocket tests for auth, signaling relay, and presence events.
README.md Documents new CLI flags and the WebRTC signaling protocol.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/webrtc/index.js Outdated
Comment on lines +12 to +21
* → { type: "offer", to: "<webid>", sdp: "..." }
* → { type: "answer", to: "<webid>", sdp: "..." }
* → { type: "candidate", to: "<webid>", candidate: {...} }
* → { type: "hangup", to: "<webid>" }
* ← { type: "offer", from: "<webid>", sdp: "..." }
* ← { type: "answer", from: "<webid>", sdp: "..." }
* ← { type: "candidate", from: "<webid>", candidate: {...} }
* ← { type: "hangup", from: "<webid>" }
* ← { type: "error", message: "..." }
* ← { type: "peers", peers: ["<webid>", ...] }
Comment thread src/webrtc/index.js
Comment on lines +76 to +94
// Register this peer (close old connection if reconnecting)
const existing = peers.get(webId);
if (existing) {
peers.delete(webId);
existing.close();
}
peers.set(webId, socket);
socket.webId = webId;

// Notify the peer of their identity and online peers
socket.send(JSON.stringify({
type: 'peers',
you: webId,
peers: [...peers.keys()].filter(id => id !== webId)
}));

// Notify other peers that someone came online
broadcast(webId, { type: 'peer-joined', webId });

Comment thread src/webrtc/index.js Outdated
Comment on lines +147 to +149
socket.on('error', () => {
if (peers.get(webId) === socket) {
peers.delete(webId);
Comment thread README.md

## WebRTC Signaling

Peer-to-peer communication via WebRTC, using JSS as the signaling server. Once peers are connected, all media and data flows directly between them.
Comment thread test/webrtc.test.js Outdated
Comment on lines +31 to +37
/** Connect an authenticated WebSocket for a pod user, waits for open */
function connectPeer(podName) {
const token = getPodToken(podName);
const ws = new WebSocket(wsUrl, {
headers: { 'Authorization': `Bearer ${token}` }
});
return ws;
- Document peer-joined/peer-left/you in protocol comment
- Only broadcast peer-joined for new connections, not reconnects
- Let error handler defer cleanup to close handler
- Grammar: "flows" → "flow"
- Fix misleading test docstrings

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional WebRTC signaling server feature to JSS, exposing a WebSocket endpoint that relays SDP/ICE messages between authenticated peers and integrates it into the CLI/config/docs.

Changes:

  • Introduces a new Fastify WebRTC signaling plugin (/.webrtc by default) with peer join/leave notifications and message relaying.
  • Wires the feature into server startup, config/env mapping, and CLI flags (--webrtc, --webrtc-path).
  • Adds a new test suite validating authentication, presence, and basic signaling relay behavior.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
test/webrtc.test.js Adds end-to-end tests for the new WebRTC WebSocket signaling endpoint.
src/webrtc/index.js Implements the WebRTC signaling server plugin (auth, peer tracking, relay).
src/server.js Registers the plugin when enabled and adjusts auth/dotfile bypass logic for the endpoint.
src/config.js Adds webrtc / webrtcPath defaults and env var mapping.
bin/jss.js Adds CLI flags and passes config through to server creation.
README.md Documents the new CLI flags and introduces a WebRTC signaling section.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/webrtc/index.js
Comment on lines +137 to +140
if (typeof msg.sdp === 'string') relay.sdp = msg.sdp;
if (msg.candidate != null && typeof msg.candidate === 'object' && !Array.isArray(msg.candidate)) {
relay.candidate = msg.candidate;
}
Comment thread src/webrtc/index.js
Comment on lines +101 to +106
// Enforce max message size (Buffer.byteLength for reliable byte count)
const raw = Buffer.isBuffer(data) ? data : Buffer.from(data);
if (raw.byteLength > MAX_MESSAGE_SIZE) {
socket.send(JSON.stringify({ type: 'error', message: 'Message too large' }));
return;
}
Comment thread README.md
export JSS_MONGO=true
export JSS_MONGO_URL=mongodb://localhost:27017
export JSS_MONGO_DATABASE=solid
export JSS_WEBRTC=true
Comment thread src/webrtc/index.js
Comment on lines +121 to +125
// Only relay known signaling types
if (!ALLOWED_TYPES.has(msg.type)) {
socket.send(JSON.stringify({ type: 'error', message: `Unknown type "${msg.type}"` }));
return;
}
Comment thread src/webrtc/index.js Outdated
if (msg.candidate != null && typeof msg.candidate === 'object' && !Array.isArray(msg.candidate)) {
relay.candidate = msg.candidate;
}
target.send(JSON.stringify(relay));
Comment thread src/server.js
Comment on lines +343 to +347
// Allow WebRTC signaling endpoint through when enabled
const urlNoQuery = request.url.split('?')[0];
if (webrtcEnabled && urlNoQuery === webrtcPath) {
return;
}
Comment thread src/webrtc/index.js Outdated
const data = JSON.stringify(msg);
for (const [id, socket] of peers) {
if (id !== senderWebId && socket.readyState === 1) {
socket.send(data);
Comment thread src/webrtc/index.js
Comment on lines +116 to +119
if (!msg.to || !msg.type) {
socket.send(JSON.stringify({ type: 'error', message: 'Missing "to" or "type" field' }));
return;
}

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional WebRTC signaling server to JSS, enabling authenticated peers to exchange SDP/ICE over a dedicated WebSocket endpoint for establishing peer-to-peer connections.

Changes:

  • Introduces a new webrtcPlugin implementing authenticated signaling relay and peer presence over WebSocket.
  • Wires the feature into server startup, config/env support, and CLI flags (--webrtc, --webrtc-path).
  • Adds automated tests plus README documentation for the new signaling protocol.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/webrtc.test.js Adds end-to-end tests for auth, presence, relay, and error cases.
src/webrtc/index.js Implements the signaling WebSocket route, peer tracking, and message relay.
src/server.js Registers the plugin when enabled and bypasses dotfile/WAC checks for the signaling endpoint.
src/config.js Adds defaults and environment-variable mapping for WebRTC feature flags.
bin/jss.js Exposes CLI options and passes config through to createServer().
README.md Documents CLI usage, environment variable, and the signaling message format.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/webrtc/index.js
Comment on lines +154 to +156
// Error handler: close event will follow and handle cleanup
socket.on('error', () => {});
});
Comment thread src/server.js
Comment on lines +78 to 81
// WebRTC signaling is OFF by default
const webrtcEnabled = options.webrtc ?? false;
const webrtcPath = options.webrtcPath ?? '/.webrtc';
// ActivityPub federation is OFF by default
Comment thread test/webrtc.test.js
Comment on lines +74 to +85
/** Collect messages from a WebSocket for a duration */
function collectMessages(ws, duration = 500) {
return new Promise((resolve) => {
const msgs = [];
const handler = (data) => msgs.push(JSON.parse(data.toString()));
ws.on('message', handler);
setTimeout(() => {
ws.removeListener('message', handler);
resolve(msgs);
}, duration);
});
}
@melvincarvalho
melvincarvalho merged commit b4af28c into gh-pages Mar 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: WebRTC signaling server plugin

2 participants