Skip to content

Commit 76cac6e

Browse files
committed
fix invalid ICE candidates, migrate to promise-based API, and use secure websockets to make chrome happy
1 parent 99ce55c commit 76cac6e

File tree

7 files changed

+174
-41
lines changed

7 files changed

+174
-41
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ An 'as simple as it gets' WebRTC example.
77

88
See [https://shanetully.com/2014/09/a-dead-simple-webrtc-example/](https://shanetully.com/2014/09/a-dead-simple-webrtc-example/) for a detailed walkthrough of the code.
99

10+
Note: This repo is kept updated. The general ideas are there, but the above blog post may be somewhat out of date with the code in this repo.
11+
1012
### Usage
1113

1214
The signaling server uses Node.js and `ws` and can be started as such:
@@ -16,9 +18,15 @@ $ npm install ws
1618
$ node server/server.js
1719
```
1820

19-
With the client running, open `client/index.html` in a recent version of either Firefox or Chrome.
21+
With the server running, open a recent version of Firefox or Chrome and visit `https://localhost:8443`. Note the HTTPS! There is no redirect from HTTP to HTTPS!
22+
23+
### TLS
24+
25+
Recent versions of Chrome require secure websockets for WebRTC. Thus, this example utilizes HTTPS. Included is a self-signed certificate that must be accepted in the browser for the example to work.
26+
27+
### Problems?
2028

21-
Note that if using Chrome and opening the file locally, you must run Chrome with the `--allow-file-access-from-files` flag. Or you could serve the files with a webserver (Python's SimpleHTTPServer is a good option).
29+
WebRTC is a rapidly evolving beast. Being an example that I don't check often, I rely on users for reports if something breaks. Issues and pull requests are greatly appreciated.
2230

2331
### License
2432

cert.pem

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIFhTCCA22gAwIBAgIJALqPiXsVwZGuMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV
3+
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
4+
aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNjA1MDMwODA1
5+
NDdaFw0xNzA1MDMwODA1NDdaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l
6+
LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV
7+
BAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMu4
8+
8c4IwR1pO7N0H1D1PFFOgveRtLZu7vk8iEjsjAf+DD8CGpZ9BeV1/D4cMmn5Q2bp
9+
y3UpwIiUeuz8fGVN3hhR3vaY2JS97qTK5+83pP09gperD5/+yZqT6CLuRoh6CYp0
10+
vEHD5CyfUK9HSRhlKVeAnWYrIWD0krN1WRrgZVIcvzlV/pmbj1aKuQ1+cXJxAEfj
11+
2/rhWTZf15hNu2fLWXemBeqQZTMOAl55CFVx5r7wPyYw4i9I2az9aj/fQRPjMQDV
12+
j/d5bdF68t5hxIf5wk/gGo8XBMBmo0uAj60Qp81wHirEr2FFMK7I+48XMFA9SfxR
13+
SahVZhjbz58K0+URD/taJewL3qrkoivJGQfL9waF+NlLJ8DoGQLN/tWNX9nsQIwu
14+
HBijg7z4hDP/cTm2dzIyxaSpSp904r3SE74n/+jI+BOWmZwo3iRJMKzWhx80VtM4
15+
qXZyHUrxCSf0Q1yqSlcyNAVpe3fCpGO9hR6ao3MX6xx3fgdQ1LUcNyZbmHFWPZ0N
16+
pp+oI8kx+9ksMMtcCJq4KGgoLaCEtJjf4a0TDiCL1eNnym79YSto2hQc8cyvc4my
17+
kerRAy9mcvmpd/e7j/eKQikNi/usWxGbeIOiy/5zG2Xrf9S2fNsc4/TdnLn+OLJX
18+
5pJ4AuVIxkA/zHsE0kcANssoJk6Hgxxek6GizytdAgMBAAGjUDBOMB0GA1UdDgQW
19+
BBSP8O2pIi6niJ4w0gHXR6PzawwRrDAfBgNVHSMEGDAWgBSP8O2pIi6niJ4w0gHX
20+
R6PzawwRrDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCkxybb3ci2
21+
NjbzjmvPflqmdTwyGOvreouCXmVHvm5vNL69LSOgZ6PmyEpyywALJqhaEhCXLJrq
22+
yyZ82My9TxYn84rukH20lyBgbjIMFnabamFAOB9LqMqM4jaedT00OEbfY+uEE5Nw
23+
NwOjNdUrrwfD2PQLAF06Orft4wex/L9psHBHaxWybnPkx+UAlA/Moz8kmCWWq9SA
24+
JPvFNXwN3WylgCgAG8kR9PYtLWQEFSp8D9lSezI1C8Ddk0ymTw4aF+p9gHbbjZw4
25+
XEEpKgRwhpHa7IrcVyeXdQ78/pYc1f7fThL88LLv7lwtJnDWqWYOJrO4HH064IKn
26+
KazLz1v9UT6yegUFiP/y688BH9LTNNf4pxGOee7LiDpfc7wyIkwrAjxoy4gc8xjp
27+
Gp6aAvLC9Wzc+i2P0Hz/CvS5ffhZ9k0XGIvSHGbMRN1G4l9cOEQadyzGZGUryFRC
28+
yBvXGBfSEYdr4OWOhdrelwpZXvl8rLRaAKU2xxIYZT9JtAXmTjWRFnjYLAuJil7e
29+
ywPbGFYsB5d9Buiw+rJLnGF4NOudk1wXBElrhdjoajDS3Miw5vn2TbuHxIleoszC
30+
fojNr80RenHT+EnY+0MecCUaeKfU4xd9nIl9I/1MpvM84zSu7Rhd0xEli6SN3fen
31+
8zRCZGBfwD1y21dDrpgWDPFxjc5Cas63bg==
32+
-----END CERTIFICATE-----

client/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<!DOCTYPE html>
22
<html>
33
<head>
4+
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
45
<script src="webrtc.js"></script>
56
</head>
67

client/webrtc.js

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
11
var localVideo;
22
var remoteVideo;
33
var peerConnection;
4-
var peerConnectionConfig = {'iceServers': [{'url': 'stun:stun.services.mozilla.com'}, {'url': 'stun:stun.l.google.com:19302'}]};
4+
var uuid;
55

6-
navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
7-
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
8-
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
9-
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
6+
var peerConnectionConfig = {
7+
'iceServers': [
8+
{'urls': 'stun:stun.services.mozilla.com'},
9+
{'urls': 'stun:stun.l.google.com:19302'},
10+
]
11+
};
1012

1113
function pageReady() {
14+
uuid = uuid();
15+
1216
localVideo = document.getElementById('localVideo');
1317
remoteVideo = document.getElementById('remoteVideo');
1418

15-
serverConnection = new WebSocket('wss://' + window.location.hostname + ':3434');
19+
serverConnection = new WebSocket('wss://' + window.location.hostname + ':8443');
1620
serverConnection.onmessage = gotMessageFromServer;
1721

1822
var constraints = {
1923
video: true,
2024
audio: true,
2125
};
2226

23-
if(navigator.getUserMedia) {
24-
navigator.getUserMedia(constraints, getUserMediaSuccess, errorHandler);
27+
if(navigator.mediaDevices.getUserMedia) {
28+
navigator.mediaDevices.getUserMedia(constraints).then(getUserMediaSuccess).catch(errorHandler);
2529
} else {
2630
alert('Your browser does not support getUserMedia API');
2731
}
@@ -39,34 +43,42 @@ function start(isCaller) {
3943
peerConnection.addStream(localStream);
4044

4145
if(isCaller) {
42-
peerConnection.createOffer(gotDescription, errorHandler);
46+
peerConnection.createOffer().then(createdDescription).catch(errorHandler);
4347
}
4448
}
4549

4650
function gotMessageFromServer(message) {
4751
if(!peerConnection) start(false);
4852

4953
var signal = JSON.parse(message.data);
54+
55+
// Ignore messages from ourself
56+
if(signal.uuid == uuid) return;
57+
5058
if(signal.sdp) {
51-
peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp), function() {
52-
peerConnection.createAnswer(gotDescription, errorHandler);
53-
}, errorHandler);
59+
peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() {
60+
// Only create answers in response to offers
61+
if(signal.sdp.type == 'offer') {
62+
peerConnection.createAnswer().then(createdDescription).catch(errorHandler);
63+
}
64+
}).catch(errorHandler);
5465
} else if(signal.ice) {
55-
peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice));
66+
peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(errorHandler);
5667
}
5768
}
5869

5970
function gotIceCandidate(event) {
6071
if(event.candidate != null) {
61-
serverConnection.send(JSON.stringify({'ice': event.candidate}));
72+
serverConnection.send(JSON.stringify({'ice': event.candidate, 'uuid': uuid}));
6273
}
6374
}
6475

65-
function gotDescription(description) {
76+
function createdDescription(description) {
6677
console.log('got description');
67-
peerConnection.setLocalDescription(description, function () {
68-
serverConnection.send(JSON.stringify({'sdp': description}));
69-
}, function() {console.log('set description error')});
78+
79+
peerConnection.setLocalDescription(description).then(function() {
80+
serverConnection.send(JSON.stringify({'sdp': peerConnection.localDescription, 'uuid': uuid}));
81+
}).catch(errorHandler);
7082
}
7183

7284
function gotRemoteStream(event) {
@@ -77,3 +89,13 @@ function gotRemoteStream(event) {
7789
function errorHandler(error) {
7890
console.log(error);
7991
}
92+
93+
// Taken from http://stackoverflow.com/a/105074/515584
94+
// Strictly speaking, it's not a real UUID, but it gets the job done here
95+
function uuid() {
96+
function s4() {
97+
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
98+
}
99+
100+
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
101+
}

generate_cert.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#! /bin/bash
2+
3+
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes

key.pem

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDLuPHOCMEdaTuz
3+
dB9Q9TxRToL3kbS2bu75PIhI7IwH/gw/AhqWfQXldfw+HDJp+UNm6ct1KcCIlHrs
4+
/HxlTd4YUd72mNiUve6kyufvN6T9PYKXqw+f/smak+gi7kaIegmKdLxBw+Qsn1Cv
5+
R0kYZSlXgJ1mKyFg9JKzdVka4GVSHL85Vf6Zm49WirkNfnFycQBH49v64Vk2X9eY
6+
Tbtny1l3pgXqkGUzDgJeeQhVcea+8D8mMOIvSNms/Wo/30ET4zEA1Y/3eW3RevLe
7+
YcSH+cJP4BqPFwTAZqNLgI+tEKfNcB4qxK9hRTCuyPuPFzBQPUn8UUmoVWYY28+f
8+
CtPlEQ/7WiXsC96q5KIryRkHy/cGhfjZSyfA6BkCzf7VjV/Z7ECMLhwYo4O8+IQz
9+
/3E5tncyMsWkqUqfdOK90hO+J//oyPgTlpmcKN4kSTCs1ocfNFbTOKl2ch1K8Qkn
10+
9ENcqkpXMjQFaXt3wqRjvYUemqNzF+scd34HUNS1HDcmW5hxVj2dDaafqCPJMfvZ
11+
LDDLXAiauChoKC2ghLSY3+GtEw4gi9XjZ8pu/WEraNoUHPHMr3OJspHq0QMvZnL5
12+
qXf3u4/3ikIpDYv7rFsRm3iDosv+cxtl63/UtnzbHOP03Zy5/jiyV+aSeALlSMZA
13+
P8x7BNJHADbLKCZOh4McXpOhos8rXQIDAQABAoICADg+0ZPe2uJx4WfEUbkaXBLe
14+
qE4NzmTn79akHcR0epziSSNEQ271CaG2l3PWeRzFExTgy6mHY37R77ZqZzXY786r
15+
G/HddT5rye15j9t983FvgBS7x86Wm7avy1GJk7Oubd/qJufJW7/uJGqgNdAkbeuY
16+
uNwyYD7Sh4ZAid9fwNmQ0kLUOTzTtBlip4DQPiYoiLlQcbWsbeMTRwTnwSwA+qyM
17+
C+oc/7O+1Gyc4e4lSl3BGs5ChNAlPuQB+0mzK9Z/zVG7pMngnq9NUKyRNZ+NF1bS
18+
OsLyyf8M11zLG9/eT1Xq9Ik+UGV8oto+5yU0c8RTh4/AKaPuIAgQ+Bui86m0skJm
19+
k5Q/1WH4Oy0OukAcT7JE91ocODdODZcXDWOgNQ/CzKAtfS+wxGzn83CpLOmqZqge
20+
B077+Cpy6j9pe+Ba+v3cgirB6vn60jSOR9YS0pzYBHxaWhIKWq/cazqH3ZZWkv1+
21+
9ik0fQv6lhXaXQNIuWAK3aKHXHpp1vPrBFr5nv/M+b2D5W0P6mz5V4YSxa2gu9wm
22+
738u3adTstEYMm2hfmhakdaccYhaDxZnWsXwZEWLkFTwRF55l5QQx81RJXssDb5l
23+
opYIGYSeaw3GXMszy4G3SHn03mLm5PTWdGd1QNphZ8ncy6I4iAIPB741+ci7vjjr
24+
BNd54fS+3dia4UFcIxWBAoIBAQD6xJnCwRbtVBHF2Admocqkx3glIDhdr+VkBkAa
25+
i3ZDxPKyRHKJmOGLJjBbmZ6P7YI8TSAKCUIPMe/FNN7TodsLQeweqJmHj/WgOQQE
26+
m/2BcPy91NeIjWa/IPbl6Uz/rs4RqPhPN+bxNIsBbvEf5J8SNBqx8jatQxfvqQxD
27+
HIUgQQ88f7N0tQQmtu+eIh6aMwgzTTS0jtmrnuXuck9P3ce2Fa7UUcVpizvJqnf8
28+
bH9qnkDqknW2aM6GA9G4AFnBE9sMcgpUeVFQDzHBR8fftPSTxAk/vj7+/p9jTWcQ
29+
VuEVXxtJ/547C0jR8/x+BDsi4nS9DteMAyJLvbC71MgWEbWtAoIBAQDP+RCWy4x5
30+
DrLmp+BbJhyaOu20Ra35Le4tfofX88VP2ic4XSLG6+/hYPLLdq2U1W4ikKtJGp7q
31+
wpIQrCODLei8cBLeCiOge2/9e+nAZKRrqAEcbUaXqUU8pPH+3zG0fhsqvB2BZthU
32+
OSSXfJ3ZarD81ZNtyK8wEhzSHXP3WVozTgqcR9FxCrM4bHN3/UuGIrJQLD2wnbLh
33+
NDEMCmf6iV7ZLAb8czCdb6pRJ8gcpB4oQVeZqGDJlsLkK9Y8wN9oLZTnEAwCVuDq
34+
3BJQQ6uKLOP41XZ1XXl1ZOIeIwxSNMoikqHiOB9x4PcrKKpHoCSjuilXiHbsrYGf
35+
BwxEAp3v8SJxAoIBAF7c8K3UDbBKFU8aofIZUmdzbefdgHUwjT6Bfs6L43lPj+AQ
36+
NKQIyYmyMKj2PB2GY7YcFvq09eB5q5KWpZS5rftcPM58SVgXBXxPFU4JFKVa8MF/
37+
OunVVAEJn1zqHM68eggEO6r8Isksb0ljhqPiAKsKOu8GCdkRgISRFqpsp4/EDNd+
38+
F40WzTM4EP1pOtpqY7fEhSOoxn895Q2HAKnd5CblnPWE2YFLwppPeoRrJuhWZYhX
39+
T2Bp1XatCzDoMQvxTvQuT+oU2sXGebP8S4g9FCiyCC2s8nfUKseOCGcN9qf3CoO7
40+
x0fexPVnryScxSI1OKQscS3uIZM1dx4XKHnwySECggEBALEVVy2/NeYiQOyrhxq1
41+
kec1RA+awS8KD+MG+S5FL/31OC4DB8ivPvr+LN5YOCchsHyYCHDfzO8CK5Msr7RT
42+
0/cXysjrgzhzwoDpELk0ONg+HmwRE+mxRPYFUNT/QPh55DH4KXt0kcDtQx4GCvYE
43+
pZ0zUixJk/nvgkDauVKk72v+CITXlhuVy9LAbXV+5N7bDk+7y+9l59lgMl8ZQT4P
44+
2AY9OdmdT4jOewxNPlQ83jzSnn+E4pzj1SCpvurOI6w2G7K/dCpNxYfVSXa0mAy4
45+
eoj3Yb0/kVsHQo38s9IPhwn3JwZTWVsC/hLutkb0sh4DNo6E8RZICrXZL3V9cPPM
46+
s9ECggEBALOBZWycSIKihepvs7HZgRSx2/tGrVHBmfQFEdmiWLIEE5AhCLziabdO
47+
3l1Oh4ylMptiXIiyWim97o5nIfegpeRY3vRZX5xiU6cYyi30FiipGZy/XHy60jTU
48+
xrfUQ/SPNTFhONKdaWzYKg4bnZMEoAQ5K7GSNFcyIduqF/kem1CtJjuouGfY/vPm
49+
JeBAw1ZPO1bzsXm8pm+Yf50vrJpzDnDESyFC8YLtFGKZRZvZfjloYFFGXiuhmHlA
50+
5HDhGAIzJqyI7s7lHnDQ5vcYGJfq2qumn+qZp17DN2Qsse06LzA9kifV5bogzZwD
51+
0qgfH8qILf4t1ULeWxPK4n3dHZ49K2E=
52+
-----END PRIVATE KEY-----

server/server.js

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,51 @@
1-
var ws_cfg = {
2-
ssl: true,
3-
port: 3434,
4-
ssl_key: '/path/to/your/ssl.key',
5-
ssl_cert: '/path/to/your/ssl.bundle.crt'
6-
};
7-
8-
var processRequest = function(req, res) {
9-
console.log("Request received.")
10-
};
1+
var HTTPS_PORT = 8443;
112

12-
var httpServ = require('https');
133
var fs = require('fs');
14-
var app = null;
4+
var https = require('https');
5+
var WebSocketServer = require('ws').Server;
156

16-
app = httpServ.createServer({
17-
key: fs.readFileSync(ws_cfg.ssl_key),
18-
cert: fs.readFileSync(ws_cfg.ssl_cert)
19-
}, processRequest).listen(ws_cfg.port);
7+
// Yes, SSL is required
8+
var serverConfig = {
9+
key: fs.readFileSync('key.pem'),
10+
cert: fs.readFileSync('cert.pem'),
11+
};
2012

21-
var WebSocketServer = require('ws').Server;
13+
// ----------------------------------------------------------------------------------------
2214

23-
var wss = new WebSocketServer({server: app});
15+
// Create a server for the client html page
16+
var handleRequest = function(request, response) {
17+
// Render the single client html file for any request the HTTP server receives
18+
console.log('request received: ' + request.url);
2419

25-
wss.broadcast = function(data) {
26-
for(var i in this.clients) {
27-
this.clients[i].send(data);
20+
if(request.url == '/') {
21+
response.writeHead(200, {'Content-Type': 'text/html'});
22+
response.end(fs.readFileSync('client/index.html'));
23+
} else if(request.url == '/webrtc.js') {
24+
response.writeHead(200, {'Content-Type': 'application/javascript'});
25+
response.end(fs.readFileSync('client/webrtc.js'));
2826
}
2927
};
3028

29+
var httpsServer = https.createServer(serverConfig, handleRequest);
30+
httpsServer.listen(HTTPS_PORT, '0.0.0.0');
31+
32+
// ----------------------------------------------------------------------------------------
33+
34+
// Create a server for handling websocket calls
35+
var wss = new WebSocketServer({server: httpsServer});
36+
3137
wss.on('connection', function(ws) {
3238
ws.on('message', function(message) {
39+
// Broadcast any received message to all clients
3340
console.log('received: %s', message);
3441
wss.broadcast(message);
3542
});
3643
});
44+
45+
wss.broadcast = function(data) {
46+
for(var i in this.clients) {
47+
this.clients[i].send(data);
48+
}
49+
};
50+
51+
console.log('Server running. Visit https://localhost:' + HTTPS_PORT + ' in Firefox/Chrome (note the HTTPS; there is no HTTP -> HTTPS redirect!)');

0 commit comments

Comments
 (0)