MINIMUM SIGNAL

Experimental service to handle WebRTC singalling so you don't have to.

RATIONALE

While WebRTC's main selling point is that it is peer-to-peer, every WebRTC application needs a central signalling server to facilitate establishing this direct connection.

Writing these is somewhat tedious and requires setting up the infrastructure to host it. What if this existed as a service that you could just use and focus on building the client side of your WebRTC application?

This way, no special server-side code needs to be written. The client parts (HTML/JS/CSS) could be hosted on some static service like S3 or GitHub Pages, or they could be native applications.

MODEL

WebRTC uses an "offer" and "answer" model, where one party puts sends an "offer" encoded in a JSON object and the other party responds similarly with an "answer" JSON object. Minimum Signal uses a slot system to allow clients to exchange offers and answers. Slots are arbitrary strings, currently capped at 125 bytes. They can be communicated out of band. E.g. SMS, AirDrop, email, or shouted out across the room.

If two peers want to establish a connection:

  1. Both attempt to post their offers to the same slot.
  2. Whichever gets their offer up first (A) waits for a response. The other peer (B) will get A's offer in the response when they attempt to upload theirs.
  3. B discards their original offer, generates an answer based on A's offer, and posts it to the same slot.
  4. A will receive B's answer and they both carry on the WebRTC nogotiations directly.
  5. At this point, Minimum Signal's role is finished and the slot is free to be used by someone else.

This slot model is similar to what the non-crypto parts of Magic Wormhole use.

API

POST https://example.com/:slot

If the body is an offer and the slot is free, the request will block until someone uploads an answer to the same slot, at which point it will return the answer.

If the body is an offer and the slot is busy, the response will be the original offer.

If the body is an answer, it will be forwarded to the original sender of the offer.

All other requests are invalid.

USAGE EXAMPLE

Here's some example JavaScript to demostrate the usage of the API. The dial() function returns an RTCPeerConnection object.

let dial = async (slot, config) => {
	let initconn = pc => {
		// Initialise a PeerConnection as you need, e.g. by adding streams
		// or data channels. Here we add a data channel and assign it to
		// the global variable dc.
		dc = pc.createDataChannel("data", {negotiated: true, id: 0});
		let decoder = new TextDecoder(); // default utf8
		dc.onmessage = (e) => {
			console.log(decoder.decode(new Uint8Array(e.data)));
		}
	}
	let pc = new RTCPeerConnection(config);
	initconn(pc);
	await pc.setLocalDescription(await pc.createOffer()) // Create an offer.
	// Wait for ICE candidates.
	await new Promise(r=>{pc.onicecandidate=e=>{if(e.candidate === null){r()}}})
	// Upload offer.
	let response = await fetch("https://example.com/"+slot, {
		method: 'POST',
		body: JSON.stringify(pc.localDescription)
	})
	let remote = await response.json();
	if (remote["type"] === "offer") {
		// We got back another offer, which means someone else (possibly
		// the party we're trying to reach) beat us to this slot.
		// Throw away our offer and accept this one, creating an answer.
		pc = new RTCPeerConnection(config);
		initconn(pc);
		// await pc.setLocalDescription({"type":"rollback"}); // Firefox only
		await pc.setRemoteDescription(new RTCSessionDescription(remote));
		await pc.setLocalDescription(await pc.createAnswer());
		// Wait for ICE candidates.
		await new Promise(r=>{pc.onicecandidate=e=>{if(e.candidate === null){r()}}})

		// Upload answer.
		await fetch("https://example.com/"+slot, {
			method: 'POST',
			body: JSON.stringify(pc.localDescription)
		})
	} else if (remote["type"] === "answer") {
		// We got back an answer to our offer. Accept it.
		await pc.setRemoteDescription(new RTCSessionDescription(remote));
	}
	return pc
}

SECURITY CONSIDIRATIONS

On its own, this scheme is not secure.

In the best case, assuming the slot name is a long and difficult to guess string, the trust model would still have to include the operator of the signalling server, since they can see and potentially modify both parties' SDPs.

For a demo that might be good enough, but for any useful application you'll need to implement a way for A to authenticate B on this potentially untrusted link. Some PAKE might be a good way to do it and fits well with the slot system. Again, cf. Magic Wormhole.

LIMITATIONS

There is no support for Trickle ICE. The offer and answer must have all candidates to be considered.

DISCLAIMER

The authors offer an instance of this service hosted at https://minimumsignal.0f.io/. The authors takes absolutely no responsibity and offers no promises for the reliability or availability of this experiment.

We reserve the right to call quits any time. If Google can do this we sure can.