├── test
├── .gitignore
├── node_modules
│ └── .gitignore
├── package.json
├── style.css
├── index.html
├── server.js
├── client.js
└── datachannel.js
├── dotcloud.yml
├── supervisord.conf
├── .dotcloud
└── config
├── .gitignore
├── .project
├── package.json
├── certs
├── certrequest.csr
├── certificate.pem
└── privatekey.pem
├── deploy.sh
├── lib
├── EventTarget.js
└── reliable.min.js
├── README.md
├── websockserver.go
├── src
├── datachannel-alfred.js
└── datachannel.js
├── server.js
├── bin
└── build.js
└── dist
├── datachannel.min.js
└── datachannel.js
/test/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 |
--------------------------------------------------------------------------------
/test/node_modules/.gitignore:
--------------------------------------------------------------------------------
1 | /express
2 |
--------------------------------------------------------------------------------
/dotcloud.yml:
--------------------------------------------------------------------------------
1 | www:
2 | type: nodejs
3 | config:
4 | node_version: v0.6.x
5 |
--------------------------------------------------------------------------------
/test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies":
3 | {
4 | "express": "2.5.1"
5 | }
6 | }
--------------------------------------------------------------------------------
/supervisord.conf:
--------------------------------------------------------------------------------
1 | [program:node]
2 | command = node server.js
3 | directory = /home/dotcloud/current
--------------------------------------------------------------------------------
/.dotcloud/config:
--------------------------------------------------------------------------------
1 | {
2 | "push_branch": null,
3 | "application": "datachannel",
4 | "version": "0.9.1",
5 | "push_protocol": "rsync"
6 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 |
10 | pids
11 | logs
12 | results
13 |
14 | npm-debug.log
15 | /node_modules
16 | /test native
17 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | DataChannel-polyfill
4 |
5 |
6 | EventTarget.js
7 | reliable
8 |
9 |
10 |
11 |
12 | org.nodeclipse.ui.NodeNature
13 |
14 |
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "DataChannel-polyfill",
3 | "version": "1.0.0-3",
4 | "description": "WebRTC DataChannels polyfill using WebSockets",
5 | "devDependencies": {
6 | "uglify-js": "~1.3.4"
7 | },
8 | "dependencies": {
9 | "ws": "0.4.x"
10 | },
11 | "subdomain": "DataChannel-polyfill",
12 | "scripts": {
13 | "start": "server.js"
14 | },
15 | "engines": {
16 | "node": "0.8.x"
17 | }
18 | }
--------------------------------------------------------------------------------
/test/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | }
4 |
5 | html {
6 | overflow-y: scroll;
7 | }
8 |
9 | body {
10 | padding: 0;
11 | margin: 0;
12 | }
13 |
14 | #chatbox {
15 | height: 100%;
16 | right: 20px;
17 | left: 20px;
18 | bottom: 0px;
19 | }
20 |
21 | #chatinput {
22 | width: 100%;
23 | height: 5%;
24 | padding-top: 7px;
25 | padding-bottom: 7px;
26 | border-radius: 8px;
27 | border-style:solid;
28 | border-width:1px;
29 | }
30 |
31 | #messages {
32 | overflow: scroll;
33 | height: 90%;
34 | width: 100%;
35 | background-color: white;
36 | border-radius: 8px;
37 | border-style:solid;
38 | border-width:1px;
39 | }
40 |
--------------------------------------------------------------------------------
/certs/certrequest.csr:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE REQUEST-----
2 | MIIBmTCCAQICAQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
3 | ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9j
4 | YWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwaCx+QLRkmsucOGCu
5 | QpIgpB0PCClfMvbVHZhj2tr2r7qRmNFW3KgHc/jM5DPcLHk5DsHWTEemFAoX5qw2
6 | KQX6CDafXgrHSkzjvVVYAqsYNAI/l46ZujT3LUmyrFJ6NRZ49dzK28EaqnQi4iP4
7 | yErhnwyaAjNTQh2lDN2InfiEdQIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAKZ2J
8 | UWkutG0KPO0ZI79iCO0lNbAHb6lMdf4BTUHSITImk7P9T247SDsiGsCcIcEJh/n3
9 | k7yM6p3WZZ9lmENcP1gypD5etlIBDpK4lFDGH9P0dmMnjIiTSqVVz+c6+Z/E2RxB
10 | th4o+26usWW+HMQ/rj6SbTtfUZHJnUqFH4DvwDI=
11 | -----END CERTIFICATE REQUEST-----
12 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | # Simple script to deploy ShareIt! automatically to the different static web
4 | # hostings and have all of them updated.
5 | #
6 | # It will be of none interest for you since it's only purposed for internal use
7 |
8 |
9 | echo
10 | echo "* Push code to GitHub master branch *"
11 | git checkout master
12 | git pull
13 | git push origin master
14 |
15 | echo
16 | echo "* Deploy in GitHub *"
17 | git checkout gh-pages
18 | git rebase master
19 | git push origin gh-pages
20 | git checkout master # Going back to master branch
21 |
22 | echo
23 | echo "* Deploy on Nodejitsu *"
24 | jitsu deploy
25 |
26 | echo
27 | echo "* Deploy on dotCloud *"
28 | dotcloud push
--------------------------------------------------------------------------------
/certs/certificate.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICKTCCAZICCQDoturXAxl08jANBgkqhkiG9w0BAQUFADBZMQswCQYDVQQGEwJB
3 | VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
4 | cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTIwODE2MDkyMDE3WhcN
5 | MTIwOTE1MDkyMDE3WjBZMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0
6 | ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAls
7 | b2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALBoLH5AtGSay5w4
8 | YK5CkiCkHQ8IKV8y9tUdmGPa2vavupGY0VbcqAdz+MzkM9wseTkOwdZMR6YUChfm
9 | rDYpBfoINp9eCsdKTOO9VVgCqxg0Aj+Xjpm6NPctSbKsUno1Fnj13MrbwRqqdCLi
10 | I/jISuGfDJoCM1NCHaUM3Yid+IR1AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEADpEa
11 | NdHGT6IirX8seuUDRaEKfR1fqmZpKxgfjCCfnWrm6nZin+g8+KJtn5NJcOMk15ob
12 | wrnxQFIJYVsI72Nq2woxeGOkyy7Gyo3h/bfV0mGbz+ueSuyqhgCP/Yl6OFYJILk2
13 | Nf8KaA3U58iniSBlZHa/QwwHXY0UVBoLI+9YGq8=
14 | -----END CERTIFICATE-----
15 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Test DataChannel-polyfill
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/certs/privatekey.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICXAIBAAKBgQCwaCx+QLRkmsucOGCuQpIgpB0PCClfMvbVHZhj2tr2r7qRmNFW
3 | 3KgHc/jM5DPcLHk5DsHWTEemFAoX5qw2KQX6CDafXgrHSkzjvVVYAqsYNAI/l46Z
4 | ujT3LUmyrFJ6NRZ49dzK28EaqnQi4iP4yErhnwyaAjNTQh2lDN2InfiEdQIDAQAB
5 | AoGAPZgyKOco1ZPjEinxQAdIhJMo/k2oPHBMFT+fLQaGu+nB6AKG3aBwHyIUNl5b
6 | 6T2yGgUYE15RCiEnIQ9PBB4w0ncrYYasVLrF6FEOoNoRu6Kn1eZJhhCu/ZUNQ40M
7 | t8chpEqa95oqd505IuvXjGPtNDx+QO8LXx8w8haYCxDsW6UCQQDeZj0DOjOHmMG8
8 | 7k4Z0k1vFFNjz+VsHtpi+IAS0G6Zkb/79vgKWZk+RNrUQmfpZ3SCi22fjou88MSN
9 | hMLqiENTAkEAyw8U3AH/lUDm9IWrsXDJYK99tuxJ7vWzIvTLbStmuufK/wej+Z8+
10 | TU93QP2nm/WDqx2Iu8fweIWLcfn2nMSoFwJBAKoqripnAQCSiEKoUEf/mtIQSlIH
11 | w3I8FTs/CrfalZWZaL1A1b8NmWyEwK9CdEGpsAMm7eT068TQsIPb/kv3oRMCQE2k
12 | d6JkNTj5txW6YtsXODj7AUPHd8GiOxvbpEka9k3zQYBgiIdzJ1xiTUQReVw99Vmh
13 | kl3S5pT+40HyhSSOYYMCQGt38273Hwkd4Lod9z9lQzQHafvEvL1P3+vOg16Dxz6e
14 | TtAvpyrbLHvTgRM2JtfhicngfWJ2b2bgjpc28E4+nZ4=
15 | -----END RSA PRIVATE KEY-----
16 |
--------------------------------------------------------------------------------
/lib/EventTarget.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author mrdoob / http://mrdoob.com/
3 | * @author Jesús Leganés Combarro "Piranna"
4 | */
5 |
6 | var EventTarget = function()
7 | {
8 | var listeners = {};
9 |
10 | this.addEventListener = function(type, listener)
11 | {
12 | if(listeners[type] === undefined)
13 | listeners[type] = [];
14 |
15 | if(listeners[type].indexOf(listener) === -1)
16 | listeners[type].push(listener);
17 | };
18 |
19 | this.dispatchEvent = function(event)
20 | {
21 | var listenerArray = (listeners[event.type] || []);
22 |
23 | var dummyListener = this['on' + event.type];
24 | if(typeof dummyListener == 'function')
25 | listenerArray = listenerArray.concat(dummyListener);
26 |
27 | for(var i=0, l=listenerArray.length; i
10 |
11 | This code can be found at https://github.com/piranna/DataChannel-polyfill
12 |
13 | How to use it
14 | -------------
15 | The polyfill is splitted in two parts, the polyfill 'per se' at datachannel.js
16 | and the server backend at server-datachannel.js (it needs Node.js, obviusly). To
17 | use it it's a matter of three steps:
18 |
19 | 1. include the datachannel.js file on a script tag on the head section of yout
20 | HTML code and call to DCPF_install() function giving the backend server URL
21 | as parameter
22 |
23 | 2. run the backend server
24 |
25 | 3. there's no third step! :-)
26 |
27 | There's also two (public) SSL enabled backend servers that you can use at
28 | wss://datachannel-polyfill.nodejitsu.com and wss://datachannel-piranna.dotcloud.com.
29 | Just for testing purposes please, the bandwidth is high, but not infinite... :-)
30 |
31 | How to test it
32 | --------------
33 | On the 'test' folder you can find a little P2P chat that can be used for testing
34 | or just learn how to use the DataChannel API. To run it
35 |
36 | 1. add a copy of datachannel.js file on the 'test' folder (a symbolic link is
37 | enought)
38 |
39 | 2. run the test server
40 |
41 | 3. run the backend server (by default is using the one at Nodejitsu)
42 |
43 | 4. open several browsers pointing to http://localhost:8000 to start chatting :-)
44 |
45 | Don't worry about the dotcloud.yml and supervisord.conf files, they are specific
46 | for the dotCloud backend server.
47 |
48 | Requeriments
49 | ------------
50 | * a web browser with the PeerConnection object (currently Chrome/Chromium >= 19)
51 |
52 | * a Node.js server with the 'ws' module installed for the backend server
53 |
54 | Future work
55 | -----------
56 | * Use native implementations if both ends support them (currently it requires at
57 | least one of the ends doesn't support native DataChannels so it can use the
58 | polyfill).
59 |
60 | * Detect and allow the coexistence of native and polyfill implementations on the
61 | same network (use polyfill if one of the two ends doesn't support native
62 | DataChannels)
63 |
--------------------------------------------------------------------------------
/websockserver.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "code.google.com/p/go.net/websocket"
7 | "strings"
8 | )
9 |
10 | // ===================
11 | // C O N N E C T I O N
12 | // ===================
13 |
14 | type connection struct {
15 | // The websocket connection.
16 | ws *websocket.Conn
17 |
18 | id string
19 |
20 | peer *connection
21 |
22 | // Buffered channel of outbound messages.
23 | send chan string
24 | }
25 |
26 | var connections map[string]*connection = make(map[string]*connection)
27 |
28 | func (c *connection) reader() {
29 | for {
30 |
31 | fmt.Println(len(connections))
32 |
33 | var message string
34 | err := websocket.Message.Receive(c.ws, &message)
35 | if err != nil {
36 | fmt.Println(err)
37 | break
38 | }
39 |
40 | fmt.Println("R: " + c.id + " : " + message)
41 |
42 | if len(message) > 7 && message[0:8] == "connect:" {
43 | {
44 | connectParts := strings.Split(message, ":")
45 | if len(connectParts) == 3 {
46 | c.id = connectParts[1]
47 | connections[c.id] = c
48 |
49 | if _,ok := connections[connectParts[2]]; ok {
50 | c.peer = connections[connectParts[2]]
51 | connections[connectParts[2]].peer = c
52 | }
53 | }
54 | }
55 | } else if (c.peer != nil) {
56 |
57 | c.peer.send <- message
58 |
59 | }
60 |
61 | }
62 | c.send <- "disconnect" // Send disconnect to my own writer.
63 | delete(connections, c.id) // Remove myself from connection map.
64 | fmt.Println("R: Connection lost:" + c.id)
65 | c.ws.Close()
66 | }
67 |
68 | func (c *connection) writer() {
69 | for {
70 | message := <- c.send
71 | if message == "disconnect" {
72 | break;
73 | }
74 | fmt.Println("W: " + c.id + " : " + message)
75 | err := websocket.Message.Send(c.ws, message)
76 | if err != nil {
77 | break
78 | }
79 | }
80 | fmt.Println("W: Connection lost:" + c.id)
81 | c.ws.Close()
82 | }
83 |
84 | // =======
85 | // M A I N
86 | // =======
87 |
88 | func wsHandler(ws *websocket.Conn) {
89 | fmt.Println("NEW WEBSOCKET!");
90 | fmt.Println(ws.Config())
91 | fmt.Println(ws.RemoteAddr())
92 | c := &connection{send: make(chan string, 256), ws: ws}
93 | go c.writer()
94 | c.reader()
95 | }
96 |
97 | func main() {
98 | http.Handle("/", websocket.Handler(wsHandler));
99 | http.ListenAndServe(":8000", nil)
100 | }
101 |
--------------------------------------------------------------------------------
/test/server.js:
--------------------------------------------------------------------------------
1 | var app = require('express').createServer();
2 | app.listen(8000);
3 |
4 |
5 | app.get('/', function(req, res) {
6 | res.sendfile(__dirname + '/index.html');
7 | });
8 |
9 | app.get('/style.css', function(req, res) {
10 | res.sendfile(__dirname + '/style.css');
11 | });
12 |
13 | app.get('/reliable.min.js', function(req, res) {
14 | res.sendfile(__dirname + '/reliable.min.js');
15 | });
16 |
17 | app.get('/datachannel.js', function(req, res) {
18 | res.sendfile(__dirname + '/datachannel.js');
19 | });
20 |
21 | app.get('/client.js', function(req, res) {
22 | res.sendfile(__dirname + '/client.js');
23 | });
24 |
25 |
26 | var WebSocketServer = require('ws').Server
27 | var wss = new WebSocketServer({server: app});
28 |
29 |
30 | //Array to store connections
31 | var sockets = [];
32 | sockets.find = function(id)
33 | {
34 | for(var i = 0; i < sockets.length; i++)
35 | {
36 | var socket = sockets[i];
37 | if(id === socket.id)
38 | return socket;
39 | }
40 | }
41 |
42 |
43 | wss.on('connection', function(socket)
44 | {
45 | socket.onmessage = function(message)
46 | {
47 | var args = JSON.parse(message.data);
48 |
49 | var eventName = args[0]
50 | var socketId = args[1]
51 |
52 | var soc = sockets.find(socketId);
53 | if(soc)
54 | {
55 | console.log(eventName);
56 |
57 | args[1] = socket.id
58 |
59 | soc.send(JSON.stringify(args), function(error)
60 | {
61 | if(error)
62 | console.log(error);
63 | });
64 | }
65 | }
66 |
67 | socket.onclose = function()
68 | {
69 | console.log('close');
70 |
71 | // remove socket and send peer.remove to all other sockets
72 | sockets.splice(sockets.indexOf(socket), 1);
73 |
74 | for(var i = 0; i < sockets.length; i++)
75 | {
76 | var soc = sockets[i];
77 |
78 | console.log(soc.id);
79 |
80 | soc.send(JSON.stringify(["peer.remove", socket.id]),
81 | function(error)
82 | {
83 | if(error)
84 | console.log(error);
85 | });
86 | }
87 | }
88 |
89 | // generate a 4 digit hex code randomly
90 | function S4()
91 | {
92 | return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
93 | }
94 |
95 | // make a REALLY COMPLICATED AND RANDOM id, kudos to dennis
96 | function id()
97 | {
98 | return S4()+S4() +"-"+ S4() +"-"+ S4() +"-"+ S4() +"-"+ S4()+S4()+S4();
99 | }
100 |
101 | socket.id = id();
102 | console.log('new socket got id: ' + socket.id);
103 |
104 | // Notify that there's a new peer connected and build a list of peers
105 | var connectionsId = [];
106 |
107 | for(var i = 0; i < sockets.length; i++)
108 | {
109 | var soc = sockets[i];
110 |
111 | connectionsId.push(soc.id);
112 |
113 | // inform the peers that they have a new peer
114 | soc.send(JSON.stringify(["peer.create", socket.id]),
115 | function(error)
116 | {
117 | if (error)
118 | console.log(error);
119 | });
120 | }
121 |
122 | // send new peer a list of all prior peers
123 | socket.send(JSON.stringify(["peers", connectionsId]),
124 | function(error)
125 | {
126 | if(error)
127 | console.log(error);
128 | });
129 |
130 | sockets.push(socket);
131 | });
--------------------------------------------------------------------------------
/src/datachannel-alfred.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var
4 | // Configuration:
5 | hostname = window.CHANNEL_HOST || window.location.host || 'localhost:8000',
6 | websocketServer = "ws://"+hostname+"/",
7 |
8 | // For browser compatibility:
9 | PeerConnection = window.PeerConnection
10 | || window.RTCPeerConnection
11 | || window.mozPeerConnection
12 | || window.webkitRTCPeerConnection
13 | || window.webkitPeerConnection00;
14 |
15 | if (typeof(PeerConnection) === 'undefined') {
16 | console.error('Your browser does not support PeerConnection.');
17 | return;
18 | }
19 |
20 | var pc = new PeerConnection(null);
21 |
22 | if (typeof(pc.createDataChannel) !== 'undefined') {
23 | try {
24 | // This will throw when data channels is not implemented properly yet
25 | pc.createDataChannel('polyfill')
26 |
27 | // If we get this far you already have DataChannel support.
28 | return console.log('REAL DATACHANNELS!');
29 | } catch(e){
30 | // TODO verify the Error
31 | }
32 | }
33 |
34 | function DataChannel(peerConnection,label,dataChannelDict) {
35 | this.readyState = "connecting";
36 | this.label = label;
37 | this.reliable = (!dataChannelDict || !dataChannelDict.reliable);
38 | this._peerConnection = peerConnection;
39 | this._queue = [];
40 | this._webSocket = new WebSocket(websocketServer);
41 |
42 | this._webSocket.onclose = function() {
43 | this.readyState = "closed";
44 | if (typeof this.onclose == 'function'){
45 | this.onclose()
46 | }
47 | }.bind(this);
48 |
49 | this._webSocket.onopen = function() {
50 | this.readyState = "open";
51 | this._identify();
52 |
53 | if (typeof this.onopen == 'function'){
54 | this.onopen()
55 | }
56 |
57 | if (typeof this._peerConnection.ondatachannel == 'function') {
58 | var evt = document.createEvent('Event')
59 | evt.initEvent('datachannel', true, true)
60 | evt.channel = this;
61 | this._peerConnection.ondatachannel(evt)
62 | }
63 |
64 | // empty the queue
65 | while(this._queue.length) {
66 | data = this._queue.shift();
67 | this.send(data);
68 | }
69 | }.bind(this);
70 |
71 | this._webSocket.onmessage = function(msg) {
72 | if (typeof this.onmessage == 'function') {
73 | this.onmessage(msg);
74 | }
75 | }.bind(this);
76 |
77 | this._webSocket.onerror = function(msg) {
78 | console.error(msg)
79 | if (typeof this.onerror == 'function') {
80 | this.onerror(msg);
81 | }
82 | }.bind(this);
83 | };
84 |
85 | DataChannel.prototype._identify = function() {
86 | if (this._peerConnection === null) return false;
87 |
88 | if (this._peerConnection._localDescription && this._peerConnection._remoteDescription) {
89 | this._localId = description2id(this._peerConnection._localDescription) + '_' + this.label
90 | this._remoteId = description2id(this._peerConnection._remoteDescription) + '_' + this.label
91 | this.send('connect:' + this._localId + ':' + this._remoteId );
92 | }
93 | };
94 |
95 | DataChannel.prototype.close = function() {
96 | this.readyState = "closing";
97 | this._webSocket.close();
98 | };
99 |
100 | DataChannel.prototype.send = function(data) {
101 | if( this.readyState == 'open' ) {
102 | this._webSocket.send(data);
103 | } else if( this.reliable ) { // queue messages when "reliable"
104 | this._queue.push(data);
105 | }
106 | };
107 |
108 | PeerConnection.prototype.createDataChannel = function(label, dataChannelDict) {
109 | console.log('createDataChannel',label,dataChannelDict)
110 | var channel = new DataChannel(this,label,dataChannelDict);
111 |
112 | if (typeof(this._allDataChannels) == 'undefined') {
113 | this._allDataChannels = [];
114 | }
115 | this._allDataChannels.push(channel);
116 |
117 | return channel;
118 | }
119 |
120 | function description2id(description) {
121 | var result = description.sdp.replace(/(\r\n|\n|\r)/gm, '\n')
122 | var re = new RegExp("o=.+");
123 | result = re.exec(result)
124 | return result[0]
125 | }
126 |
127 | // Overwrite PeerConnection's description setters, to get ID:s for the websocket connections.
128 |
129 | var
130 | setLocalDescription = PeerConnection.prototype.setLocalDescription,
131 | setRemoteDescription = PeerConnection.prototype.setRemoteDescription;
132 |
133 | PeerConnection.prototype.setLocalDescription = function(description, successCallback, errorCallback) {
134 | this._localDescription = description;
135 | if (typeof(this._allDataChannels) != 'undefined') {
136 | for (var i in this._allDataChannels) {
137 | this._allDataChannels[i]._identify();
138 | }
139 | }
140 | setLocalDescription.apply(this, arguments);
141 | };
142 |
143 | PeerConnection.prototype.setRemoteDescription = function(description) {
144 | this._remoteDescription = description;
145 | if (typeof(this._allDataChannels) != 'undefined') {
146 | for (var i in this._allDataChannels) {
147 | this._allDataChannels[i]._identify();
148 | }
149 | };
150 | setRemoteDescription.apply(this, arguments);
151 | };
152 |
153 | }());
154 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | // SSL Certificates
2 | var fs = require('fs');
3 |
4 | var options = {key: fs.readFileSync('certs/privatekey.pem').toString(),
5 | cert: fs.readFileSync('certs/certificate.pem').toString(),
6 | ca: [fs.readFileSync('certs/certrequest.csr').toString()]}
7 |
8 | // Get AppFog port, or set 8080 as default one (dotCloud mandatory)
9 | var port = process.env.VMC_APP_PORT || 8080
10 |
11 | // HTTP server
12 | function requestListener(req, res)
13 | {
14 | res.writeHead(200, {'Content-Type': 'text/html'});
15 | res.write('This is a DataChannel polyfill backend server. ')
16 | res.write('You can get a copy of the source code at ')
17 | res.end('GitHub')
18 | }
19 |
20 | var server = require('http').createServer(requestListener)
21 | //var server = require('https').createServer(options, requestListener)
22 | server.listen(port);
23 |
24 | // DataChannel proxy server
25 | var WebSocketServer = require('ws').Server
26 | var wss = new WebSocketServer({server: server});
27 |
28 | //Dict to store connections
29 | wss.sockets = {}
30 |
31 | wss.on('connection', function(socket)
32 | {
33 | // Forward raw message to the other peer
34 | function onmessage_proxy(message)
35 | {
36 | this.peer.send(message.data)
37 | };
38 |
39 | // Handshake
40 | socket.onmessage = function(message)
41 | {
42 | var args = JSON.parse(message.data)
43 |
44 | var eventName = args[0]
45 | var socketId = args[1]
46 |
47 | var soc = wss.sockets[socketId]
48 |
49 | switch(eventName)
50 | {
51 | case 'create': // socketId is the peer ID
52 | if(soc)
53 | {
54 | // Both peers support native DataChannels
55 | if(args[3] && soc.nativeSupport)
56 | {
57 | // Notify that both peers has native support
58 | socket.send(JSON.stringify(['create.native']))
59 | soc.send(JSON.stringify(['create.native']))
60 |
61 | // Close both peers since they have native support
62 | socket.close();
63 | soc.close();
64 | }
65 |
66 | // At least one of the peers doesn't support DataChannels
67 | else
68 | {
69 | // Register the new peer connection
70 | socket.id = id()
71 | wss.sockets[socket.id] = socket
72 |
73 | // Send the other peer the request to connect
74 | args[1] = socket.id
75 | args.splice(3, 1)
76 |
77 | soc.send(JSON.stringify(args))
78 | }
79 | }
80 |
81 | // Second peer was not connected
82 | // Send error message and close socket
83 | else
84 | {
85 | socket.send(JSON.stringify(['create.error', socketId]))
86 | socket.close();
87 | }
88 |
89 | break
90 |
91 | case 'ready': // socketId is the UDT ID
92 | if(soc)
93 | {
94 | // Link peers and update onmessage event to just forward
95 | socket.peer = soc
96 | soc.peer = socket
97 |
98 | socket.onmessage = onmessage_proxy
99 | soc.onmessage = onmessage_proxy
100 |
101 | // Send 'ready' signal to the first peer and dettach it
102 | soc.send(JSON.stringify(['ready']))
103 |
104 | delete soc.id
105 | delete wss.sockets[socketId]
106 | }
107 |
108 | // First peer was disconnected
109 | // Send error message and close socket
110 | else
111 | {
112 | socket.send(JSON.stringify(['ready.error', socketId]))
113 | socket.close();
114 | }
115 |
116 | break
117 |
118 | // Register peer signaling socket with this ID
119 | case 'setId':
120 | wss.sockets[socketId] = socket
121 | wss.sockets[socketId].nativeSupport = args[2]
122 | }
123 | };
124 |
125 | // Peer connection is closed, close the other end
126 | socket.onclose = function()
127 | {
128 | // Sockets were connected, just close them
129 | if(socket.peer != undefined)
130 | socket.peer.close();
131 |
132 | // Socket was not connected, remove it from sockets list
133 | else
134 | delete wss.sockets[socket.id]
135 | };
136 | })
137 |
138 | // generate a 4 digit hex code randomly
139 | function S4()
140 | {
141 | return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
142 | }
143 |
144 | // make a REALLY COMPLICATED AND RANDOM id, kudos to dennis
145 | function id()
146 | {
147 | return S4()+S4() +"-"+ S4() +"-"+ S4() +"-"+ S4() +"-"+ S4()+S4()+S4()
148 | }
--------------------------------------------------------------------------------
/bin/build.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 |
5 | var fs = require('fs')
6 | , package = JSON.parse(fs.readFileSync(__dirname+ '/../package.json'))
7 | , uglify = require('uglify-js');
8 |
9 | /**
10 | * License headers.
11 | *
12 | * @api private
13 | */
14 |
15 | var template = '/*! datachannel.%ext% build:' + package.version +
16 | ', %type%. Copyright(c) 2013 Jesús Leganés Combarro "Piranna" */'
17 | , prefix = '\n(function(exports){\n'
18 | , development = template.replace('%type%', 'development').replace('%ext%', 'js')
19 | , production = template.replace('%type%', 'production').replace('%ext%', 'min.js')
20 | , suffix = '\n})(this);\n';
21 |
22 | /**
23 | * If statements, these allows you to create server side & client side
24 | * compatible code using specially designed `if` statements that remove server
25 | * side designed code from the source files
26 | *
27 | * @api private
28 | */
29 |
30 | var starttagIF = '// if node'
31 | , endtagIF = '// end node';
32 |
33 | /**
34 | * The modules that are required to create a base build of BinaryJS client.
35 | *
36 | * @const
37 | * @type {Array}
38 | * @api private
39 | */
40 |
41 | var base = [
42 | '../lib/EventTarget.js'
43 | , '../lib/reliable.min.js'
44 | , 'datachannel.js'
45 | ];
46 |
47 | var DIST_FOLDER = '/../dist/';
48 | var SOURCE_FOLDER = '/../src/';
49 | var OUTPUT_NAME = 'datachannel';
50 |
51 |
52 | /**
53 | * @param {Array} transports The transports that needs to be bundled.
54 | * @param {Object} [options] Options to configure the building process.
55 | * @param {Function} callback Last argument should always be the callback
56 | * @callback {String|Boolean} err An optional argument, if it exists than an
57 | * error occurred during the build process.
58 | * @callback {String} result The result of the build process.
59 | * @api public
60 | */
61 |
62 | var builder = module.exports = function () {
63 | var options, callback, error = null
64 | , args = Array.prototype.slice.call(arguments, 0)
65 | , settings = {
66 | minify: true
67 | , node: false
68 | , custom: []
69 | };
70 |
71 | // Fancy pancy argument support this makes any pattern possible mainly
72 | // because we require only one of each type
73 | args.forEach(function (arg) {
74 | var type = Object.prototype.toString.call(arg)
75 | .replace(/\[object\s(\w+)\]/gi , '$1' ).toLowerCase();
76 |
77 | switch (type) {
78 | case 'object':
79 | return options = arg;
80 | case 'function':
81 | return callback = arg;
82 | }
83 | });
84 |
85 | // Add defaults
86 | options = options || {};
87 |
88 | // Merge the data
89 | for(var option in options) {
90 | settings[option] = options[option];
91 | }
92 |
93 | var files = [];
94 | base.forEach(function (file) {
95 | files.push(__dirname + SOURCE_FOLDER + file);
96 | });
97 |
98 | var results = {};
99 | files.forEach(function (file) {
100 | fs.readFile(file, function (err, content) {
101 | if (err) error = err;
102 | results[file] = content;
103 |
104 | // check if we are done yet, or not.. Just by checking the size of the result
105 | // object.
106 | if (Object.keys(results).length !== files.length) return;
107 |
108 |
109 | // concatinate the file contents in order
110 | var code = development
111 | , ignore = 0;
112 |
113 | code += prefix;
114 |
115 | files.forEach(function (file) {
116 | code += results[file];
117 | });
118 |
119 | // check if we need to add custom code
120 | if (settings.custom.length) {
121 | settings.custom.forEach(function (content) {
122 | code += content;
123 | });
124 | }
125 |
126 | if (!settings.node) {
127 | code = code.split('\n').filter(function (line) {
128 | // check if there are tags in here
129 | var start = line.indexOf(starttagIF) >= 0
130 | , end = line.indexOf(endtagIF) >= 0
131 | , ret = ignore;
132 |
133 | // ignore the current line
134 | if (start) {
135 | ignore++;
136 | ret = ignore;
137 | }
138 |
139 | // stop ignoring the next line
140 | if (end) {
141 | ignore--;
142 | }
143 |
144 | return ret == 0;
145 | }).join('\n');
146 | }
147 |
148 | code += suffix;
149 |
150 | // check if we need to process it any further
151 | if (settings.minify) {
152 | var ast = uglify.parser.parse(code);
153 | ast = uglify.uglify.ast_mangle(ast);
154 | ast = uglify.uglify.ast_squeeze(ast);
155 |
156 | code = production + uglify.uglify.gen_code(ast, { ascii_only: true });
157 | }
158 |
159 | callback(error, code);
160 | })
161 | })
162 | };
163 |
164 | /**
165 | * @type {String}
166 | * @api public
167 | */
168 |
169 | builder.version = package.version;
170 |
171 | /**
172 | * Command line support, this allows us to generate builds without having
173 | * to load it as module.
174 | */
175 |
176 | if (!module.parent){
177 | // the first 2 are `node` and the path to this file, we don't need them
178 | var args = process.argv.slice(2);
179 | // build a development build
180 | builder(args.length ? args : false, { minify:false }, function (err, content) {
181 | if (err) return console.error(err);
182 | console.log(__dirname);
183 | fs.write(
184 | fs.openSync(__dirname + DIST_FOLDER + OUTPUT_NAME + '.js', 'w')
185 | , content
186 | , 0
187 | , 'utf8'
188 | );
189 | console.log('Successfully generated the development build: ' + OUTPUT_NAME + '.js');
190 | });
191 |
192 | // and build a production build
193 | builder(args.length ? args : false, function (err, content) {
194 | if (err) return console.error(err);
195 |
196 | fs.write(
197 | fs.openSync(__dirname + DIST_FOLDER + OUTPUT_NAME + '.min.js', 'w')
198 | , content
199 | , 0
200 | , 'utf8'
201 | );
202 | console.log('Successfully generated the production build: ' + OUTPUT_NAME + '.min.js');
203 | });
204 | }
--------------------------------------------------------------------------------
/test/client.js:
--------------------------------------------------------------------------------
1 | var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
2 |
3 | if(RTCPeerConnection == undefined)
4 | alert('Your browser is not supported or you have to turn on flags. In chrome'+
5 | ' you go to chrome://flags and turn on Enable PeerConnection remember '+
6 | 'to restart chrome');
7 |
8 |
9 | // Holds the STUN server to use for PeerConnections.
10 | var SERVER = "stun:stun.l.google.com:19302";
11 |
12 | // Reference to the lone PeerConnection instances.
13 | var peerConnections = {};
14 |
15 |
16 | // Chat functions
17 |
18 | function addToChat(msg, color)
19 | {
20 | // Sanitize the input
21 | msg = msg.replace(/' + msg + '';
25 | else
26 | msg = '' + msg + '';
27 |
28 | var messages = document.getElementById('messages');
29 | messages.innerHTML = messages.innerHTML + msg + '
';
30 | messages.scrollTop = 10000;
31 | }
32 |
33 | function initChat()
34 | {
35 | var input = document.getElementById("chatinput");
36 | var color = "#"+((1<<24)*Math.random()|0).toString(16);
37 |
38 | input.addEventListener('keydown', function(event)
39 | {
40 | var key = event.which || event.keyCode;
41 | if(key === 13)
42 | {
43 | for(var peerConnection in peerConnections)
44 | {
45 | var channel = peerConnections[peerConnection]._datachannels['chat']
46 | if(channel)
47 | channel.send(JSON.stringify({"messages": input.value, "color": color}));
48 | }
49 |
50 | addToChat(input.value);
51 | input.value = "";
52 | }
53 | }, false);
54 | }
55 |
56 |
57 | // Create PeerConnection
58 |
59 | function createPeerConnection(uid, socket)
60 | {
61 | console.log('createPeerConnection '+uid);
62 |
63 | var pc = new RTCPeerConnection({iceServers: [{url: SERVER}]},
64 | {optional: [{RtpDataChannels: true}]});
65 | pc.onicecandidate = function(event)
66 | {
67 | if(event.candidate)
68 | socket.send(JSON.stringify(["peer.candidate", uid, event.candidate]));
69 | }
70 |
71 | peerConnections[uid] = pc
72 |
73 | return pc;
74 | }
75 |
76 |
77 | function initDataChannel(pc, channel)
78 | {
79 | console.log("initDataChannel '"+channel.label+"' on "+pc);
80 |
81 | channel.onmessage = function(message)
82 | {
83 | var data = JSON.parse(message.data)
84 |
85 | addToChat(data.messages, data.color.toString(16))
86 | }
87 | channel.onclose = function()
88 | {
89 | delete pc._datachannels[channel.label]
90 | }
91 |
92 | pc._datachannels = {}
93 | pc._datachannels[channel.label] = channel
94 | };
95 |
96 |
97 | window.addEventListener('load', function()
98 | {
99 | var socket = new WebSocket("ws://localhost:8000/");
100 | socket.onopen = function()
101 | {
102 | socket.onmessage = function(message)
103 | {
104 | console.log("RECEIVED: "+message.data);
105 |
106 | var args = JSON.parse(message.data);
107 |
108 | var eventName = args[0]
109 | var uids = args[1]
110 | var sdp = args[2]
111 |
112 | switch(eventName)
113 | {
114 | case 'peers':
115 | for(var i = 0; i < uids.length; i++)
116 | {
117 | var uid = uids[i]
118 |
119 | // Create PeerConnection
120 | var pc = createPeerConnection(uid, socket);
121 |
122 | var channel = pc.createDataChannel('chat')
123 | initDataChannel(pc, channel)
124 |
125 | // Send offer to new PeerConnection
126 | pc.createOffer(function(offer)
127 | {
128 | console.log("createOffer: "+uid+", "+offer.sdp);
129 |
130 | socket.send(JSON.stringify(["offer", uid, offer.sdp]));
131 |
132 | pc.setLocalDescription(offer);
133 | },
134 | function(code)
135 | {
136 | console.error("Failure callback: " + code);
137 | });
138 | }
139 | break
140 |
141 | case 'offer':
142 | var uid = uids
143 |
144 | var pc = peerConnections[uid];
145 | pc.setRemoteDescription(new RTCSessionDescription({sdp: sdp,
146 | type: 'offer'}));
147 |
148 | // Send answer
149 | pc.createAnswer(function(answer)
150 | {
151 | console.log("createAnswer: "+uid+", "+answer.sdp);
152 |
153 | socket.send(JSON.stringify(["answer", uid, answer.sdp]));
154 |
155 | pc.setLocalDescription(answer);
156 | });
157 | break
158 |
159 | case 'answer':
160 | var uid = uids
161 |
162 | var pc = peerConnections[uid];
163 | pc.setRemoteDescription(new RTCSessionDescription({sdp: sdp,
164 | type: 'answer'}));
165 | break
166 |
167 | case 'peer.candidate':
168 | var uid = uids
169 | var candidate = new RTCIceCandidate(sdp)
170 |
171 | var pc = peerConnections[uid];
172 | pc.addIceCandidate(candidate);
173 | break
174 |
175 | case 'peer.create':
176 | var uid = uids
177 |
178 | var pc = createPeerConnection(uid, socket);
179 | pc.addEventListener('datachannel', function(event)
180 | {
181 | initDataChannel(pc, event.channel)
182 | })
183 | break
184 |
185 | case 'peer.remove':
186 | var uid = uids
187 |
188 | delete peerConnections[uid];
189 | break
190 | }
191 | };
192 | };
193 | socket.onerror = function(error)
194 | {
195 | console.error(error)
196 | }
197 |
198 |
199 | initChat();
200 | })
--------------------------------------------------------------------------------
/lib/reliable.min.js:
--------------------------------------------------------------------------------
1 | /*! reliable.min.js build:0.1.0, production. Copyright(c) 2013 Michelle Bu */(function(e){function n(){this._pieces=[],this._parts=[]}function r(e){this.index=0,this.dataBuffer=e,this.dataView=new Uint8Array(this.dataBuffer),this.length=this.dataBuffer.byteLength}function i(e){this.utf8=e,this.bufferBuilder=new n}function o(e,t){if(!(this instanceof o))return new o(e);this._dc=e,s.debug=t,this._outgoing={},this._incoming={},this._received={},this._window=1e3,this._mtu=500,this._interval=0,this._count=0,this._queue=[],this._setupDC()}var t={};t.useBlobBuilder=function(){try{return new Blob([]),!1}catch(e){return!0}}(),t.useArrayBufferView=!t.useBlobBuilder&&function(){try{return(new Blob([new Uint8Array([])])).size===0}catch(e){return!0}}(),e.binaryFeatures=t,e.BlobBuilder=window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder||window.BlobBuilder,n.prototype.append=function(e){typeof e=="number"?this._pieces.push(e):(this._flush(),this._parts.push(e))},n.prototype._flush=function(){if(this._pieces.length>0){var e=new Uint8Array(this._pieces);t.useArrayBufferView||(e=e.buffer),this._parts.push(e),this._pieces=[]}},n.prototype.getBuffer=function(){this._flush();if(t.useBlobBuilder){var e=new BlobBuilder;for(var n=0,r=this._parts.length;n>31,n=(e>>23&255)-127,r=e&8388607|8388608;return(t==0?1:-1)*r*Math.pow(2,n-23)},r.prototype.unpack_double=function(){var e=this.unpack_uint32(),t=this.unpack_uint32(),n=e>>31,r=(e>>20&2047)-1023,i=e&1048575|1048576,s=i*Math.pow(2,r-20)+t*Math.pow(2,r-52);return(n==0?1:-1)*s},r.prototype.read=function(e){var t=this.index;if(t+e<=this.length)return this.dataView.subarray(t,t+e);throw new Error("BinaryPackFailure: read index out of range")},i.prototype.pack=function(e){var n=typeof e;if(n=="string")this.pack_string(e);else if(n=="number")Math.floor(e)===e?this.pack_integer(e):this.pack_double(e);else if(n=="boolean")e===!0?this.bufferBuilder.append(195):e===!1&&this.bufferBuilder.append(194);else if(n=="undefined")this.bufferBuilder.append(192);else{if(n!="object")throw new Error('Type "'+n+'" not yet supported');if(e===null)this.bufferBuilder.append(192);else{var r=e.constructor;if(r==Array)this.pack_array(e);else if(r==Blob||r==File)this.pack_bin(e);else if(r==ArrayBuffer)t.useArrayBufferView?this.pack_bin(new Uint8Array(e)):this.pack_bin(e);else if("BYTES_PER_ELEMENT"in e)t.useArrayBufferView?this.pack_bin(e):this.pack_bin(e.buffer);else if(r==Object)this.pack_object(e);else if(r==Date)this.pack_string(e.toString());else{if(typeof e.toBinaryPack!="function")throw new Error('Type "'+r.toString()+'" not yet supported');this.bufferBuilder.append(e.toBinaryPack())}}}return this.bufferBuilder.getBuffer()},i.prototype.pack_bin=function(e){var t=e.length||e.byteLength||e.size;if(t<=15)this.pack_uint8(160+t);else if(t<=65535)this.bufferBuilder.append(218),this.pack_uint16(t);else{if(!(t<=4294967295))throw new Error("Invalid length");this.bufferBuilder.append(219),this.pack_uint32(t)}this.bufferBuilder.append(e)},i.prototype.pack_string=function(e){var t;if(this.utf8){var n=new Blob([e]);t=n.size}else t=e.length;if(t<=15)this.pack_uint8(176+t);else if(t<=65535)this.bufferBuilder.append(216),this.pack_uint16(t);else{if(!(t<=4294967295))throw new Error("Invalid length");this.bufferBuilder.append(217),this.pack_uint32(t)}this.bufferBuilder.append(e)},i.prototype.pack_array=function(e){var t=e.length;if(t<=15)this.pack_uint8(144+t);else if(t<=65535)this.bufferBuilder.append(220),this.pack_uint16(t);else{if(!(t<=4294967295))throw new Error("Invalid length");this.bufferBuilder.append(221),this.pack_uint32(t)}for(var n=0;n>8),this.bufferBuilder.append(e&255)},i.prototype.pack_uint32=function(e){var t=e&4294967295;this.bufferBuilder.append((t&4278190080)>>>24),this.bufferBuilder.append((t&16711680)>>>16),this.bufferBuilder.append((t&65280)>>>8),this.bufferBuilder.append(t&255)},i.prototype.pack_uint64=function(e){var t=e/Math.pow(2,32),n=e%Math.pow(2,32);this.bufferBuilder.append((t&4278190080)>>>24),this.bufferBuilder.append((t&16711680)>>>16),this.bufferBuilder.append((t&65280)>>>8),this.bufferBuilder.append(t&255),this.bufferBuilder.append((n&4278190080)>>>24),this.bufferBuilder.append((n&16711680)>>>16),this.bufferBuilder.append((n&65280)>>>8),this.bufferBuilder.append(n&255)},i.prototype.pack_int8=function(e){this.bufferBuilder.append(e&255)},i.prototype.pack_int16=function(e){this.bufferBuilder.append((e&65280)>>8),this.bufferBuilder.append(e&255)},i.prototype.pack_int32=function(e){this.bufferBuilder.append(e>>>24&255),this.bufferBuilder.append((e&16711680)>>>16),this.bufferBuilder.append((e&65280)>>>8),this.bufferBuilder.append(e&255)},i.prototype.pack_int64=function(e){var t=Math.floor(e/Math.pow(2,32)),n=e%Math.pow(2,32);this.bufferBuilder.append((t&4278190080)>>>24),this.bufferBuilder.append((t&16711680)>>>16),this.bufferBuilder.append((t&65280)>>>8),this.bufferBuilder.append(t&255),this.bufferBuilder.append((n&4278190080)>>>24),this.bufferBuilder.append((n&16711680)>>>16),this.bufferBuilder.append((n&65280)>>>8),this.bufferBuilder.append(n&255)};var s={debug:!1,inherits:function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})},extend:function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e},pack:BinaryPack.pack,unpack:BinaryPack.unpack,log:function(){if(s.debug){var e=[];for(var t=0;t=i.chunks.length?(s.log("Time: ",new Date-i.timer),delete this._outgoing[t]):this._processAcks()}break;case"chunk":i=n;if(!i){var f=this._received[t];if(f===!0)break;i={ack:["ack",t,0],chunks:[]},this._incoming[t]=i}var l=e[2],c=e[3];i.chunks[l]=new Uint8Array(c),l===i.ack[2]&&this._calculateNextAck(t),this._ack(t);break;default:this._handleSend(e)}},o.prototype._chunk=function(e){var t=[],n=e.size,r=0;while(r=n.length&&r.push(["end",e,n.length]),r._multiple=!0,this._handleSend(r)},o.prototype._complete=function(e){s.log("Completed called for",e);var t=this,n=this._incoming[e].chunks,r=new Blob(n);s.blobToArrayBuffer(r,function(e){var n=document.createEvent("Event");n.initEvent("message",!0,!0),n.data=s.unpack(e),t.dispatchEvent(n)}),delete this._incoming[e]},o.prototype._listeners={},o.prototype.addEventListener=function(e,t,n){e=="message"?(this._listeners[e]===undefined&&(this._listeners[e]=[]),this._listeners[e].indexOf(t)===-1&&this._listeners[e].push(t)):this._dc.addEventListener(e,t,n)},o.prototype.dispatchEvent=function(e){if(type=="message"){var t=this._listeners[e.type]||[],n=this["on"+e.type];typeof n=="function"&&(t=t.concat(n));for(var r=0,i=t.length;r1?t[0]+n+t[1]:e},o.prototype.addEventListener=function(e,t){var n=this;this._listeners={};var r=this._listeners[t]=function(e){t.call(n,e)};this._dc.addEventListener(e,r)},o.prototype.dispatchEvent=function(e){this._dc.dispatchEvent(e)},o.prototype.removeEventListener=function(e,t){var n=this._listeners[t];this._dc.removeEventListener(e,n)},o.prototype.onmessage=function(e){},e.Reliable=o})(this)
--------------------------------------------------------------------------------
/src/datachannel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * DataChannel polyfill
3 | *
4 | * Web browser polyfill that implements the WebRTC DataChannel API over a
5 | * websocket. It implements the full latest DataChannel API specification
6 | * defined at 2013-01-16.
7 | *
8 | * Copyright (C) 2012-2013 Jesús Leganés Combarro "Piranna"
9 | *
10 | * This code can be found at https://github.com/piranna/DataChannel-polyfill
11 | *
12 | *
13 | * This program is free software: you can redistribute it and/or modify it under
14 | * the terms of the GNU Affero General Public License as published by the Free
15 | * Software Foundation, either version 3 of the License, or (at your option) any
16 | * later version.
17 | *
18 | * This program is distributed in the hope that it will be useful, but WITHOUT
19 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
21 | * details.
22 | *
23 | * You should have received a copy of the GNU Affero General Public License
24 | * along with this program. If not, see .
25 | */
26 |
27 |
28 | /**
29 | * Install the polyfill
30 | * @param {String} ws_url URL to the backend server
31 | */
32 | function DCPF_install(ws_url)
33 | {
34 | // Fallbacks for vendor-specific variables until the spec is finalized.
35 | var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
36 |
37 | // Check if browser has support for WebRTC PeerConnection
38 | if(RTCPeerConnection == undefined)
39 | {
40 | console.error("Your browser doesn't support RTCPeerConnection, please use "+
41 | "one of the latest versions of Chrome/Chromium or Firefox");
42 | return "old browser";
43 | }
44 |
45 | var createDataChannel;
46 | var supportReliable = false;
47 |
48 | //Holds the STUN server to use for PeerConnections.
49 | var SERVER = "stun:stun.l.google.com:19302";
50 |
51 | // Check if browser has support for native WebRTC DataChannel
52 | var pc = new RTCPeerConnection({iceServers: [{url: SERVER}]},
53 | {optional: [{RtpDataChannels: true}]})
54 |
55 | try
56 | {
57 | // Check native
58 | pc.createDataChannel('DCPF_install__checkSupport',{reliable:false}).close();
59 |
60 | // Native support available, store the function
61 | createDataChannel = pc.createDataChannel
62 |
63 | // Check reliable
64 | pc.createDataChannel('DCPF_install__checkSupport').close();
65 |
66 | // Native reliable support available
67 | supportReliable = true
68 | }
69 | catch(e){}
70 | finally
71 | {
72 | pc.close()
73 | }
74 |
75 |
76 | // DataChannel polyfill using WebSockets as 'underlying data transport'
77 | function RTCDataChannel(label, dataChannelDict)
78 | {
79 | var self = this
80 |
81 | EventTarget.call(this)
82 |
83 | this.close = function()
84 | {
85 | if(this._udt)
86 | this._udt.close()
87 | }
88 | this.send = function(data)
89 | {
90 | if(this._udt)
91 | this._udt.send(data)
92 | }
93 |
94 | // binaryType
95 | this.__defineGetter__("binaryType", function()
96 | {
97 | return self._udt.binaryType;
98 | });
99 | this.__defineSetter__("binaryType", function(binaryType)
100 | {
101 | self._udt.binaryType = binaryType;
102 | });
103 |
104 | // bufferedAmount
105 | this.__defineGetter__("bufferedAmount", function()
106 | {
107 | if(!self._udt)
108 | return 0
109 |
110 | return self._udt.bufferedAmount;
111 | });
112 |
113 | // label
114 | this.__defineGetter__("label", function()
115 | {
116 | return label;
117 | });
118 |
119 | // readyState
120 | this.__defineGetter__("readyState", function()
121 | {
122 | if(!self._udt)
123 | return "connecting"
124 |
125 | switch(self._udt.readyState)
126 | {
127 | case 0: return "connecting"
128 | case 1: return "open"
129 | case 2: return "closing"
130 | case 3: return "closed"
131 | }
132 | });
133 |
134 | // id
135 | var id = (dataChannelDict.id != undefined) ? dataChannelDict.id : 0
136 | this.__defineGetter__("id", function()
137 | {
138 | return id;
139 | });
140 |
141 | // maxRetransmitTime
142 | var maxRetransmitTime = (dataChannelDict.maxRetransmitTime != undefined) ? dataChannelDict.maxRetransmitTime : null
143 | this.__defineGetter__("maxRetransmitTime", function()
144 | {
145 | return maxRetransmitTime;
146 | });
147 |
148 | // maxRetransmits
149 | var maxRetransmits = (dataChannelDict.maxRetransmits != undefined) ? dataChannelDict.maxRetransmits : null
150 | this.__defineGetter__("maxRetransmits", function()
151 | {
152 | return maxRetransmits;
153 | });
154 |
155 | if(maxRetransmitTime && maxRetransmits)
156 | throw SyntaxError
157 |
158 | var reliable = !(maxRetransmitTime || maxRetransmits)
159 |
160 | // negotiated
161 | var negotiated = (dataChannelDict.negotiated != undefined) ? dataChannelDict.negotiated : false
162 | this.__defineGetter__("negotiated", function()
163 | {
164 | return negotiated;
165 | });
166 |
167 | // ordered
168 | var ordered = (dataChannelDict.ordered != undefined) ? dataChannelDict.ordered : false
169 | this.__defineGetter__("ordered", function()
170 | {
171 | return ordered;
172 | });
173 |
174 | // protocol
175 | var protocol = (dataChannelDict.protocol != undefined) ? dataChannelDict.protocol : ""
176 | this.__defineGetter__("protocol", function()
177 | {
178 | return protocol;
179 | });
180 | }
181 |
182 |
183 | function createUDT(pc, channel, onopen)
184 | {
185 | pc._channels = pc._channels || {}
186 | pc._channels[channel.label] = channel
187 |
188 | if(!pc._peerId)
189 | {
190 | console.warn("No peer ID")
191 | return
192 | }
193 |
194 | console.info("Creating UDT")
195 |
196 | // Use a WebSocket as 'underlying data transport' to create the DataChannel
197 | channel._udt = new WebSocket(ws_url)
198 | channel._udt.addEventListener('close', function(event)
199 | {
200 | // if(error && channel.onerror)
201 | // {
202 | // channel.onerror(error)
203 | // return
204 | // }
205 |
206 | channel.dispatchEvent(event);
207 | })
208 | channel._udt.addEventListener('error', function(event)
209 | {
210 | channel.dispatchEvent(event);
211 | })
212 | channel._udt.addEventListener('open', function(event)
213 | {
214 | onopen(channel, pc)
215 | })
216 | }
217 |
218 |
219 | // Public function to initiate the creation of a new DataChannel
220 | if(createDataChannel && !supportReliable && Reliable)
221 | {
222 | RTCPeerConnection.prototype.createDataChannel = function(label, dataChannelDict)
223 | {
224 | dataChannelDict = dataChannelDict || {}
225 | dataChannelDict.reliable = false
226 |
227 | var channel = new Reliable(createDataChannel.call(this, label, dataChannelDict))
228 | channel.label = label
229 |
230 | return channel
231 | }
232 |
233 | var dispatchEvent = RTCPeerConnection.prototype.dispatchEvent;
234 | RTCPeerConnection.prototype.dispatchEvent = function(event)
235 | {
236 | var channel = event.channel;
237 |
238 | if(event.type == 'datachannel' && !(channel instanceof Reliable))
239 | {
240 | event.channel = new Reliable(channel)
241 | event.channel.label = channel.label
242 | }
243 |
244 | dispatchEvent.call(this, event)
245 | };
246 | }
247 |
248 | else
249 | RTCPeerConnection.prototype.createDataChannel = function(label, dataChannelDict)
250 | {
251 | // Back-ward compatibility
252 | if(this.readyState)
253 | this.signalingState = this.readyState
254 | // Back-ward compatibility
255 |
256 | if(this.signalingState == "closed")
257 | throw INVALID_STATE;
258 |
259 | if(!label)
260 | throw "'label' is not defined"
261 | dataChannelDict = dataChannelDict || {}
262 |
263 | var channel = new RTCDataChannel(label, dataChannelDict)
264 |
265 | createUDT(this, channel, onopen)
266 |
267 | return channel
268 | }
269 |
270 |
271 | // Private function to 'catch' the 'ondatachannel' event
272 | function ondatachannel(pc, socketId, label, dataChannelDict)
273 | {
274 | // Back-ward compatibility
275 | if(pc.readyState)
276 | pc.signalingState = pc.readyState
277 | // Back-ward compatibility
278 |
279 | if(pc.signalingState == "closed")
280 | return;
281 |
282 | var channel = new RTCDataChannel(label, dataChannelDict)
283 |
284 | createUDT(pc, channel, function(channel)
285 | {
286 | // Set channel as open
287 | channel.send(JSON.stringify(["ready", socketId]))
288 |
289 | // Set onmessage event to bypass messages to user defined function
290 | channel._udt.onmessage = function(event)
291 | {
292 | channel.dispatchEvent(event);
293 | }
294 |
295 | // Set channel as open
296 | var event = document.createEvent('Event')
297 | event.initEvent('open', true, true)
298 |
299 | channel.dispatchEvent(event);
300 | })
301 |
302 | var event = document.createEvent('Event')
303 | event.initEvent('datachannel', true, true)
304 | event.channel = channel
305 |
306 | pc.dispatchEvent(event);
307 | }
308 |
309 |
310 | function onopen(channel, pc)
311 | {
312 | // Wait until the other end of the channel is ready
313 | channel._udt.onmessage = function(message)
314 | {
315 | var args = JSON.parse(message.data);
316 |
317 | var eventName = args[0]
318 |
319 | switch(eventName)
320 | {
321 | // Both peers support native DataChannels
322 | case 'create.native':
323 | {
324 | // Close the ad-hoc signaling channel
325 | if(pc._signaling)
326 | pc._signaling.close();
327 |
328 | // Make native DataChannels to be created by default
329 | pc.createDataChannel = createDataChannel
330 |
331 | // Start native DataChannel connection
332 | channel._udt = pc.createDataChannel(channel.label, channel.reliable)
333 | }
334 | break
335 |
336 | // Connection through backend server is ready
337 | case 'ready':
338 | {
339 | // Back-ward compatibility
340 | if(pc.readyState)
341 | pc.signalingState = pc.readyState
342 | // Back-ward compatibility
343 |
344 | // PeerConnection is closed, do nothing
345 | if(self.signalingState == "closed")
346 | return;
347 |
348 | // Set onmessage event to bypass messages to user defined function
349 | channel._udt.onmessage = function(event)
350 | {
351 | channel.dispatchEvent(event);
352 | }
353 |
354 | // Set channel as open
355 | var event = document.createEvent('Event')
356 | event.initEvent('open', true, true)
357 |
358 | channel.dispatchEvent(event);
359 | }
360 | break
361 |
362 | default:
363 | console.error("Unknown event '"+eventName+"'")
364 | }
365 | }
366 |
367 | // Query to the other peer to create a new DataChannel with us
368 | channel.send(JSON.stringify(['create', pc._peerId, channel.label,
369 | channel.reliable, Boolean(createDataChannel)]))
370 | }
371 |
372 |
373 | // Get the SDP session ID from a RTCSessionDescription object
374 | function getId(description)
375 | {
376 | var pattern = /^o=.+/gm
377 | var result = pattern.exec(description.sdp);
378 |
379 | return result[0].substring(2)
380 | }
381 |
382 | // Overwrite setters to catch the session IDs
383 | var setLocalDescription = RTCPeerConnection.prototype.setLocalDescription
384 | var setRemoteDescription = RTCPeerConnection.prototype.setRemoteDescription
385 | var closeRTC = RTCPeerConnection.prototype.close;
386 |
387 | RTCPeerConnection.prototype.close = function()
388 | {
389 | if(this._signaling)
390 | this._signaling.close();
391 |
392 | closeRTC.call(this);
393 | };
394 |
395 | if(!createDataChannel)
396 | RTCPeerConnection.prototype.setLocalDescription = function(description,
397 | successCallback,
398 | failureCallback)
399 | {
400 | var self = this
401 |
402 | // Create a signalling channel with a WebSocket on the proxy server with the
403 | // defined ID and wait for new 'create' messages to create new DataChannels
404 | if(this._signaling)
405 | this._signaling.close();
406 |
407 | this._signaling = new WebSocket(ws_url)
408 | this._signaling.onopen = function(event)
409 | {
410 | this.onmessage = function(event)
411 | {
412 | var args = JSON.parse(event.data)
413 |
414 | switch(args[0])
415 | {
416 | case 'create':
417 | ondatachannel(self, args[1], args[2], {reliable: args[3]})
418 | break
419 |
420 | // Both peers support native DataChannels
421 | case 'create.native':
422 | // Make native DataChannels to be created by default
423 | self.createDataChannel = createDataChannel
424 | }
425 | }
426 |
427 | this.send(JSON.stringify(['setId', "pc."+getId(description),
428 | Boolean(createDataChannel)]))
429 | }
430 | this._signaling.onerror = function(error)
431 | {
432 | console.error(error)
433 | }
434 |
435 | setLocalDescription.call(this, description, successCallback, failureCallback)
436 | }
437 |
438 | if(!createDataChannel)
439 | RTCPeerConnection.prototype.setRemoteDescription = function(description,
440 | successCallback,
441 | failureCallback)
442 | {
443 | // Set the PeerConnection peer ID
444 | this._peerId = "pc."+getId(description)
445 |
446 | // Initialize pending channels
447 | for(var label in this._channels)
448 | {
449 | var channel = this._channels[label]
450 |
451 | if(channel._udt == undefined)
452 | createUDT(this, channel, onopen)
453 | }
454 |
455 | setRemoteDescription.call(this, description, successCallback, failureCallback)
456 | }
457 |
458 | if(createDataChannel && !supportReliable && Reliable)
459 | {
460 | var createOffer = RTCPeerConnection.prototype.createOffer;
461 | var createAnswer = RTCPeerConnection.prototype.createAnswer;
462 |
463 | RTCPeerConnection.prototype.createOffer = function(successCallback,
464 | failureCallback,
465 | constraints)
466 | {
467 | createOffer.call(this, function(offer)
468 | {
469 | offer.sdp = Reliable.higherBandwidthSDP(offer.sdp);
470 | successCallback(offer)
471 | },
472 | failureCallback, constraints)
473 | }
474 |
475 | RTCPeerConnection.prototype.createAnswer = function(successCallback,
476 | failureCallback,
477 | constraints)
478 | {
479 | createAnswer.call(this, function(answer)
480 | {
481 | answer.sdp = Reliable.higherBandwidthSDP(answer.sdp);
482 | successCallback(answer)
483 | },
484 | failureCallback, constraints)
485 | }
486 | }
487 |
488 |
489 |
490 | // Notify to the user if we have native DataChannels support or not
491 | if(createDataChannel)
492 | {
493 | if(supportReliable)
494 | {
495 | console.log("Both native and polyfill WebRTC DataChannel are available");
496 | return "native";
497 | }
498 |
499 | if(Reliable)
500 | console.warn("Native WebRTC DataChannel is not reliable, using polyfill instead");
501 | else
502 | console.error("Native WebRTC DataChannel is not reliable and not included polyfill");
503 |
504 | return "native no reliable";
505 | }
506 |
507 | console.warn("WebRTC DataChannel is only available thought polyfill");
508 | return "polyfill";
509 | }
510 |
511 | exports.DCPF_install = DCPF_install;
--------------------------------------------------------------------------------
/dist/datachannel.min.js:
--------------------------------------------------------------------------------
1 | /*! datachannel.min.js build:1.0.0-3, production. Copyright(c) 2013 Jesús Leganés Combarro "Piranna" */(function(e){function n(e){function a(e,n){var r=this;t.call(this),this.close=function(){this._udt&&this._udt.close()},this.send=function(e){this._udt&&this._udt.send(e)},this.__defineGetter__("binaryType",function(){return r._udt.binaryType}),this.__defineSetter__("binaryType",function(e){r._udt.binaryType=e}),this.__defineGetter__("bufferedAmount",function(){return r._udt?r._udt.bufferedAmount:0}),this.__defineGetter__("label",function(){return e}),this.__defineGetter__("readyState",function(){if(!r._udt)return"connecting";switch(r._udt.readyState){case 0:return"connecting";case 1:return"open";case 2:return"closing";case 3:return"closed"}});var i=n.id!=undefined?n.id:0;this.__defineGetter__("id",function(){return i});var s=n.maxRetransmitTime!=undefined?n.maxRetransmitTime:null;this.__defineGetter__("maxRetransmitTime",function(){return s});var o=n.maxRetransmits!=undefined?n.maxRetransmits:null;this.__defineGetter__("maxRetransmits",function(){return o});if(s&&o)throw SyntaxError;var u=!s&&!o,a=n.negotiated!=undefined?n.negotiated:!1;this.__defineGetter__("negotiated",function(){return a});var f=n.ordered!=undefined?n.ordered:!1;this.__defineGetter__("ordered",function(){return f});var l=n.protocol!=undefined?n.protocol:"";this.__defineGetter__("protocol",function(){return l})}function f(t,n,r){t._channels=t._channels||{},t._channels[n.label]=n;if(!t._peerId){console.warn("No peer ID");return}console.info("Creating UDT"),n._udt=new WebSocket(e),n._udt.addEventListener("close",function(e){n.dispatchEvent(e)}),n._udt.addEventListener("error",function(e){n.dispatchEvent(e)}),n._udt.addEventListener("open",function(e){r(n,t)})}function c(e,t,n,r){e.readyState&&(e.signalingState=e.readyState);if(e.signalingState=="closed")return;var i=new a(n,r);f(e,i,function(e){e.send(JSON.stringify(["ready",t])),e._udt.onmessage=function(t){e.dispatchEvent(t)};var n=document.createEvent("Event");n.initEvent("open",!0,!0),e.dispatchEvent(n)});var s=document.createEvent("Event");s.initEvent("datachannel",!0,!0),s.channel=i,e.dispatchEvent(s)}function h(e,t){e._udt.onmessage=function(n){var i=JSON.parse(n.data),s=i[0];switch(s){case"create.native":t._signaling&&t._signaling.close(),t.createDataChannel=r,e._udt=t.createDataChannel(e.label,e.reliable);break;case"ready":t.readyState&&(t.signalingState=t.readyState);if(self.signalingState=="closed")return;e._udt.onmessage=function(t){e.dispatchEvent(t)};var o=document.createEvent("Event");o.initEvent("open",!0,!0),e.dispatchEvent(o);break;default:console.error("Unknown event '"+s+"'")}},e.send(JSON.stringify(["create",t._peerId,e.label,e.reliable,Boolean(r)]))}function p(e){var t=/^o=.+/gm,n=t.exec(e.sdp);return n[0].substring(2)}var n=window.RTCPeerConnection||window.webkitRTCPeerConnection||window.mozRTCPeerConnection;if(n==undefined)return console.error("Your browser doesn't support RTCPeerConnection, please use one of the latest versions of Chrome/Chromium or Firefox"),"old browser";var r,i=!1,s="stun:stun.l.google.com:19302",o=new n({iceServers:[{url:s}]},{optional:[{RtpDataChannels:!0}]});try{o.createDataChannel("DCPF_install__checkSupport",{reliable:!1}).close(),r=o.createDataChannel,o.createDataChannel("DCPF_install__checkSupport").close(),i=!0}catch(u){}finally{o.close()}if(r&&!i&&Reliable){n.prototype.createDataChannel=function(e,t){t=t||{},t.reliable=!1;var n=new Reliable(r.call(this,e,t));return n.label=e,n};var l=n.prototype.dispatchEvent;n.prototype.dispatchEvent=function(e){var t=e.channel;e.type=="datachannel"&&!(t instanceof Reliable)&&(e.channel=new Reliable(t),e.channel.label=t.label),l.call(this,e)}}else n.prototype.createDataChannel=function(e,t){this.readyState&&(this.signalingState=this.readyState);if(this.signalingState=="closed")throw INVALID_STATE;if(!e)throw"'label' is not defined";t=t||{};var n=new a(e,t);return f(this,n,h),n};var d=n.prototype.setLocalDescription,v=n.prototype.setRemoteDescription,m=n.prototype.close;n.prototype.close=function(){this._signaling&&this._signaling.close(),m.call(this)},r||(n.prototype.setLocalDescription=function(t,n,i){var s=this;this._signaling&&this._signaling.close(),this._signaling=new WebSocket(e),this._signaling.onopen=function(e){this.onmessage=function(e){var t=JSON.parse(e.data);switch(t[0]){case"create":c(s,t[1],t[2],{reliable:t[3]});break;case"create.native":s.createDataChannel=r}},this.send(JSON.stringify(["setId","pc."+p(t),Boolean(r)]))},this._signaling.onerror=function(e){console.error(e)},d.call(this,t,n,i)}),r||(n.prototype.setRemoteDescription=function(e,t,n){this._peerId="pc."+p(e);for(var r in this._channels){var i=this._channels[r];i._udt==undefined&&f(this,i,h)}v.call(this,e,t,n)});if(r&&!i&&Reliable){var g=n.prototype.createOffer,y=n.prototype.createAnswer;n.prototype.createOffer=function(e,t,n){g.call(this,function(t){t.sdp=Reliable.higherBandwidthSDP(t.sdp),e(t)},t,n)},n.prototype.createAnswer=function(e,t,n){y.call(this,function(t){t.sdp=Reliable.higherBandwidthSDP(t.sdp),e(t)},t,n)}}return r?i?(console.log("Both native and polyfill WebRTC DataChannel are available"),"native"):(Reliable?console.warn("Native WebRTC DataChannel is not reliable, using polyfill instead"):console.error("Native WebRTC DataChannel is not reliable and not included polyfill"),"native no reliable"):(console.warn("WebRTC DataChannel is only available thought polyfill"),"polyfill")}var t=function(){var e={};this.addEventListener=function(t,n){e[t]===undefined&&(e[t]=[]),e[t].indexOf(n)===-1&&e[t].push(n)},this.dispatchEvent=function(t){var n=e[t.type]||[],r=this["on"+t.type];typeof r=="function"&&(n=n.concat(r));for(var i=0,s=n.length;i0){var e=new Uint8Array(this._pieces);s.useArrayBufferView||(e=e.buffer),this._parts.push(e),this._pieces=[]}},t.prototype.getBuffer=function(){this._flush();if(s.useBlobBuilder){var e=new BlobBuilder;for(var t=0,n=this._parts.length;t>31,n=(e>>23&255)-127,r=e&8388607|8388608;return(t==0?1:-1)*r*Math.pow(2,n-23)},n.prototype.unpack_double=function(){var e=this.unpack_uint32(),t=this.unpack_uint32(),n=e>>31,r=(e>>20&2047)-1023,i=e&1048575|1048576,s=i*Math.pow(2,r-20)+t*Math.pow(2,r-52);return(n==0?1:-1)*s},n.prototype.read=function(e){var t=this.index;if(t+e<=this.length)return this.dataView.subarray(t,t+e);throw new Error("BinaryPackFailure: read index out of range")},r.prototype.pack=function(e){var t=typeof e;if(t=="string")this.pack_string(e);else if(t=="number")Math.floor(e)===e?this.pack_integer(e):this.pack_double(e);else if(t=="boolean")e===!0?this.bufferBuilder.append(195):e===!1&&this.bufferBuilder.append(194);else if(t=="undefined")this.bufferBuilder.append(192);else{if(t!="object")throw new Error('Type "'+t+'" not yet supported');if(e===null)this.bufferBuilder.append(192);else{var n=e.constructor;if(n==Array)this.pack_array(e);else if(n==Blob||n==File)this.pack_bin(e);else if(n==ArrayBuffer)s.useArrayBufferView?this.pack_bin(new Uint8Array(e)):this.pack_bin(e);else if("BYTES_PER_ELEMENT"in e)s.useArrayBufferView?this.pack_bin(e):this.pack_bin(e.buffer);else if(n==Object)this.pack_object(e);else if(n==Date)this.pack_string(e.toString());else{if(typeof e.toBinaryPack!="function")throw new Error('Type "'+n.toString()+'" not yet supported');this.bufferBuilder.append(e.toBinaryPack())}}}return this.bufferBuilder.getBuffer()},r.prototype.pack_bin=function(e){var t=e.length||e.byteLength||e.size;if(t<=15)this.pack_uint8(160+t);else if(t<=65535)this.bufferBuilder.append(218),this.pack_uint16(t);else{if(!(t<=4294967295))throw new Error("Invalid length");this.bufferBuilder.append(219),this.pack_uint32(t)}this.bufferBuilder.append(e)},r.prototype.pack_string=function(e){var t;if(this.utf8){var n=new Blob([e]);t=n.size}else t=e.length;if(t<=15)this.pack_uint8(176+t);else if(t<=65535)this.bufferBuilder.append(216),this.pack_uint16(t);else{if(!(t<=4294967295))throw new Error("Invalid length");this.bufferBuilder.append(217),this.pack_uint32(t)}this.bufferBuilder.append(e)},r.prototype.pack_array=function(e){var t=e.length;if(t<=15)this.pack_uint8(144+t);else if(t<=65535)this.bufferBuilder.append(220),this.pack_uint16(t);else{if(!(t<=4294967295))throw new Error("Invalid length");this.bufferBuilder.append(221),this.pack_uint32(t)}for(var n=0;n>8),this.bufferBuilder.append(e&255)},r.prototype.pack_uint32=function(e){var t=e&4294967295;this.bufferBuilder.append((t&4278190080)>>>24),this.bufferBuilder.append((t&16711680)>>>16),this.bufferBuilder.append((t&65280)>>>8),this.bufferBuilder.append(t&255)},r.prototype.pack_uint64=function(e){var t=e/Math.pow(2,32),n=e%Math.pow(2,32);this.bufferBuilder.append((t&4278190080)>>>24),this.bufferBuilder.append((t&16711680)>>>16),this.bufferBuilder.append((t&65280)>>>8),this.bufferBuilder.append(t&255),this.bufferBuilder.append((n&4278190080)>>>24),this.bufferBuilder.append((n&16711680)>>>16),this.bufferBuilder.append((n&65280)>>>8),this.bufferBuilder.append(n&255)},r.prototype.pack_int8=function(e){this.bufferBuilder.append(e&255)},r.prototype.pack_int16=function(e){this.bufferBuilder.append((e&65280)>>8),this.bufferBuilder.append(e&255)},r.prototype.pack_int32=function(e){this.bufferBuilder.append(e>>>24&255),this.bufferBuilder.append((e&16711680)>>>16),this.bufferBuilder.append((e&65280)>>>8),this.bufferBuilder.append(e&255)},r.prototype.pack_int64=function(e){var t=Math.floor(e/Math.pow(2,32)),n=e%Math.pow(2,32);this.bufferBuilder.append((t&4278190080)>>>24),this.bufferBuilder.append((t&16711680)>>>16),this.bufferBuilder.append((t&65280)>>>8),this.bufferBuilder.append(t&255),this.bufferBuilder.append((n&4278190080)>>>24),this.bufferBuilder.append((n&16711680)>>>16),this.bufferBuilder.append((n&65280)>>>8),this.bufferBuilder.append(n&255)};var o={debug:!1,inherits:function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})},extend:function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e},pack:BinaryPack.pack,unpack:BinaryPack.unpack,log:function(){if(o.debug){var e=[];for(var t=0;t=i.chunks.length?(o.log("Time: ",new Date-i.timer),delete this._outgoing[t]):this._processAcks()}break;case"chunk":i=n;if(!i){var f=this._received[t];if(f===!0)break;i={ack:["ack",t,0],chunks:[]},this._incoming[t]=i}var l=e[2],c=e[3];i.chunks[l]=new Uint8Array(c),l===i.ack[2]&&this._calculateNextAck(t),this._ack(t);break;default:this._handleSend(e)}},i.prototype._chunk=function(e){var t=[],n=e.size,r=0;while(r=n.length&&r.push(["end",e,n.length]),r._multiple=!0,this._handleSend(r)},i.prototype._complete=function(e){o.log("Completed called for",e);var t=this,n=this._incoming[e].chunks,r=new Blob(n);o.blobToArrayBuffer(r,function(e){var n=document.createEvent("Event");n.initEvent("message",!0,!0),n.data=o.unpack(e),t.dispatchEvent(n)}),delete this._incoming[e]},i.prototype._listeners={},i.prototype.addEventListener=function(e,t,n){e=="message"?(this._listeners[e]===undefined&&(this._listeners[e]=[]),this._listeners[e].indexOf(t)===-1&&this._listeners[e].push(t)):this._dc.addEventListener(e,t,n)},i.prototype.dispatchEvent=function(e){if(type=="message"){var t=this._listeners[e.type]||[],n=this["on"+e.type];typeof n=="function"&&(t=t.concat(n));for(var r=0,i=t.length;r1?t[0]+n+t[1]:e},i.prototype.addEventListener=function(e,t){var n=this;this._listeners={};var r=this._listeners[t]=function(e){t.call(n,e)};this._dc.addEventListener(e,r)},i.prototype.dispatchEvent=function(e){this._dc.dispatchEvent(e)},i.prototype.removeEventListener=function(e,t){var n=this._listeners[t];this._dc.removeEventListener(e,n)},i.prototype.onmessage=function(e){},e.Reliable=i}(this),e.DCPF_install=n})(this)
--------------------------------------------------------------------------------
/dist/datachannel.js:
--------------------------------------------------------------------------------
1 | /*! datachannel.js build:1.0.0-3, development. Copyright(c) 2013 Jesús Leganés Combarro "Piranna" */
2 | (function(exports){
3 | /**
4 | * @author mrdoob / http://mrdoob.com/
5 | * @author Jesús Leganés Combarro "Piranna"
6 | */
7 |
8 | var EventTarget = function()
9 | {
10 | var listeners = {};
11 |
12 | this.addEventListener = function(type, listener)
13 | {
14 | if(listeners[type] === undefined)
15 | listeners[type] = [];
16 |
17 | if(listeners[type].indexOf(listener) === -1)
18 | listeners[type].push(listener);
19 | };
20 |
21 | this.dispatchEvent = function(event)
22 | {
23 | var listenerArray = (listeners[event.type] || []);
24 |
25 | var dummyListener = this['on' + event.type];
26 | if(typeof dummyListener == 'function')
27 | listenerArray = listenerArray.concat(dummyListener);
28 |
29 | for(var i=0, l=listenerArray.length; i */(function(e){function n(){this._pieces=[],this._parts=[]}function r(e){this.index=0,this.dataBuffer=e,this.dataView=new Uint8Array(this.dataBuffer),this.length=this.dataBuffer.byteLength}function i(e){this.utf8=e,this.bufferBuilder=new n}function o(e,t){if(!(this instanceof o))return new o(e);this._dc=e,s.debug=t,this._outgoing={},this._incoming={},this._received={},this._window=1e3,this._mtu=500,this._interval=0,this._count=0,this._queue=[],this._setupDC()}var t={};t.useBlobBuilder=function(){try{return new Blob([]),!1}catch(e){return!0}}(),t.useArrayBufferView=!t.useBlobBuilder&&function(){try{return(new Blob([new Uint8Array([])])).size===0}catch(e){return!0}}(),e.binaryFeatures=t,e.BlobBuilder=window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder||window.BlobBuilder,n.prototype.append=function(e){typeof e=="number"?this._pieces.push(e):(this._flush(),this._parts.push(e))},n.prototype._flush=function(){if(this._pieces.length>0){var e=new Uint8Array(this._pieces);t.useArrayBufferView||(e=e.buffer),this._parts.push(e),this._pieces=[]}},n.prototype.getBuffer=function(){this._flush();if(t.useBlobBuilder){var e=new BlobBuilder;for(var n=0,r=this._parts.length;n>31,n=(e>>23&255)-127,r=e&8388607|8388608;return(t==0?1:-1)*r*Math.pow(2,n-23)},r.prototype.unpack_double=function(){var e=this.unpack_uint32(),t=this.unpack_uint32(),n=e>>31,r=(e>>20&2047)-1023,i=e&1048575|1048576,s=i*Math.pow(2,r-20)+t*Math.pow(2,r-52);return(n==0?1:-1)*s},r.prototype.read=function(e){var t=this.index;if(t+e<=this.length)return this.dataView.subarray(t,t+e);throw new Error("BinaryPackFailure: read index out of range")},i.prototype.pack=function(e){var n=typeof e;if(n=="string")this.pack_string(e);else if(n=="number")Math.floor(e)===e?this.pack_integer(e):this.pack_double(e);else if(n=="boolean")e===!0?this.bufferBuilder.append(195):e===!1&&this.bufferBuilder.append(194);else if(n=="undefined")this.bufferBuilder.append(192);else{if(n!="object")throw new Error('Type "'+n+'" not yet supported');if(e===null)this.bufferBuilder.append(192);else{var r=e.constructor;if(r==Array)this.pack_array(e);else if(r==Blob||r==File)this.pack_bin(e);else if(r==ArrayBuffer)t.useArrayBufferView?this.pack_bin(new Uint8Array(e)):this.pack_bin(e);else if("BYTES_PER_ELEMENT"in e)t.useArrayBufferView?this.pack_bin(e):this.pack_bin(e.buffer);else if(r==Object)this.pack_object(e);else if(r==Date)this.pack_string(e.toString());else{if(typeof e.toBinaryPack!="function")throw new Error('Type "'+r.toString()+'" not yet supported');this.bufferBuilder.append(e.toBinaryPack())}}}return this.bufferBuilder.getBuffer()},i.prototype.pack_bin=function(e){var t=e.length||e.byteLength||e.size;if(t<=15)this.pack_uint8(160+t);else if(t<=65535)this.bufferBuilder.append(218),this.pack_uint16(t);else{if(!(t<=4294967295))throw new Error("Invalid length");this.bufferBuilder.append(219),this.pack_uint32(t)}this.bufferBuilder.append(e)},i.prototype.pack_string=function(e){var t;if(this.utf8){var n=new Blob([e]);t=n.size}else t=e.length;if(t<=15)this.pack_uint8(176+t);else if(t<=65535)this.bufferBuilder.append(216),this.pack_uint16(t);else{if(!(t<=4294967295))throw new Error("Invalid length");this.bufferBuilder.append(217),this.pack_uint32(t)}this.bufferBuilder.append(e)},i.prototype.pack_array=function(e){var t=e.length;if(t<=15)this.pack_uint8(144+t);else if(t<=65535)this.bufferBuilder.append(220),this.pack_uint16(t);else{if(!(t<=4294967295))throw new Error("Invalid length");this.bufferBuilder.append(221),this.pack_uint32(t)}for(var n=0;n>8),this.bufferBuilder.append(e&255)},i.prototype.pack_uint32=function(e){var t=e&4294967295;this.bufferBuilder.append((t&4278190080)>>>24),this.bufferBuilder.append((t&16711680)>>>16),this.bufferBuilder.append((t&65280)>>>8),this.bufferBuilder.append(t&255)},i.prototype.pack_uint64=function(e){var t=e/Math.pow(2,32),n=e%Math.pow(2,32);this.bufferBuilder.append((t&4278190080)>>>24),this.bufferBuilder.append((t&16711680)>>>16),this.bufferBuilder.append((t&65280)>>>8),this.bufferBuilder.append(t&255),this.bufferBuilder.append((n&4278190080)>>>24),this.bufferBuilder.append((n&16711680)>>>16),this.bufferBuilder.append((n&65280)>>>8),this.bufferBuilder.append(n&255)},i.prototype.pack_int8=function(e){this.bufferBuilder.append(e&255)},i.prototype.pack_int16=function(e){this.bufferBuilder.append((e&65280)>>8),this.bufferBuilder.append(e&255)},i.prototype.pack_int32=function(e){this.bufferBuilder.append(e>>>24&255),this.bufferBuilder.append((e&16711680)>>>16),this.bufferBuilder.append((e&65280)>>>8),this.bufferBuilder.append(e&255)},i.prototype.pack_int64=function(e){var t=Math.floor(e/Math.pow(2,32)),n=e%Math.pow(2,32);this.bufferBuilder.append((t&4278190080)>>>24),this.bufferBuilder.append((t&16711680)>>>16),this.bufferBuilder.append((t&65280)>>>8),this.bufferBuilder.append(t&255),this.bufferBuilder.append((n&4278190080)>>>24),this.bufferBuilder.append((n&16711680)>>>16),this.bufferBuilder.append((n&65280)>>>8),this.bufferBuilder.append(n&255)};var s={debug:!1,inherits:function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})},extend:function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e},pack:BinaryPack.pack,unpack:BinaryPack.unpack,log:function(){if(s.debug){var e=[];for(var t=0;t=i.chunks.length?(s.log("Time: ",new Date-i.timer),delete this._outgoing[t]):this._processAcks()}break;case"chunk":i=n;if(!i){var f=this._received[t];if(f===!0)break;i={ack:["ack",t,0],chunks:[]},this._incoming[t]=i}var l=e[2],c=e[3];i.chunks[l]=new Uint8Array(c),l===i.ack[2]&&this._calculateNextAck(t),this._ack(t);break;default:this._handleSend(e)}},o.prototype._chunk=function(e){var t=[],n=e.size,r=0;while(r=n.length&&r.push(["end",e,n.length]),r._multiple=!0,this._handleSend(r)},o.prototype._complete=function(e){s.log("Completed called for",e);var t=this,n=this._incoming[e].chunks,r=new Blob(n);s.blobToArrayBuffer(r,function(e){var n=document.createEvent("Event");n.initEvent("message",!0,!0),n.data=s.unpack(e),t.dispatchEvent(n)}),delete this._incoming[e]},o.prototype._listeners={},o.prototype.addEventListener=function(e,t,n){e=="message"?(this._listeners[e]===undefined&&(this._listeners[e]=[]),this._listeners[e].indexOf(t)===-1&&this._listeners[e].push(t)):this._dc.addEventListener(e,t,n)},o.prototype.dispatchEvent=function(e){if(type=="message"){var t=this._listeners[e.type]||[],n=this["on"+e.type];typeof n=="function"&&(t=t.concat(n));for(var r=0,i=t.length;r1?t[0]+n+t[1]:e},o.prototype.addEventListener=function(e,t){var n=this;this._listeners={};var r=this._listeners[t]=function(e){t.call(n,e)};this._dc.addEventListener(e,r)},o.prototype.dispatchEvent=function(e){this._dc.dispatchEvent(e)},o.prototype.removeEventListener=function(e,t){var n=this._listeners[t];this._dc.removeEventListener(e,n)},o.prototype.onmessage=function(e){},e.Reliable=o})(this)/**
43 | * DataChannel polyfill
44 | *
45 | * Web browser polyfill that implements the WebRTC DataChannel API over a
46 | * websocket. It implements the full latest DataChannel API specification
47 | * defined at 2013-01-16.
48 | *
49 | * Copyright (C) 2012-2013 Jesús Leganés Combarro "Piranna"
50 | *
51 | * This code can be found at https://github.com/piranna/DataChannel-polyfill
52 | *
53 | *
54 | * This program is free software: you can redistribute it and/or modify it under
55 | * the terms of the GNU Affero General Public License as published by the Free
56 | * Software Foundation, either version 3 of the License, or (at your option) any
57 | * later version.
58 | *
59 | * This program is distributed in the hope that it will be useful, but WITHOUT
60 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
61 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
62 | * details.
63 | *
64 | * You should have received a copy of the GNU Affero General Public License
65 | * along with this program. If not, see .
66 | */
67 |
68 |
69 | /**
70 | * Install the polyfill
71 | * @param {String} ws_url URL to the backend server
72 | */
73 | function DCPF_install(ws_url)
74 | {
75 | // Fallbacks for vendor-specific variables until the spec is finalized.
76 | var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
77 |
78 | // Check if browser has support for WebRTC PeerConnection
79 | if(RTCPeerConnection == undefined)
80 | {
81 | console.error("Your browser doesn't support RTCPeerConnection, please use "+
82 | "one of the latest versions of Chrome/Chromium or Firefox");
83 | return "old browser";
84 | }
85 |
86 | var createDataChannel;
87 | var supportReliable = false;
88 |
89 | //Holds the STUN server to use for PeerConnections.
90 | var SERVER = "stun:stun.l.google.com:19302";
91 |
92 | // Check if browser has support for native WebRTC DataChannel
93 | var pc = new RTCPeerConnection({iceServers: [{url: SERVER}]},
94 | {optional: [{RtpDataChannels: true}]})
95 |
96 | try
97 | {
98 | // Check native
99 | pc.createDataChannel('DCPF_install__checkSupport',{reliable:false}).close();
100 |
101 | // Native support available, store the function
102 | createDataChannel = pc.createDataChannel
103 |
104 | // Check reliable
105 | pc.createDataChannel('DCPF_install__checkSupport').close();
106 |
107 | // Native reliable support available
108 | supportReliable = true
109 | }
110 | catch(e){}
111 | finally
112 | {
113 | pc.close()
114 | }
115 |
116 |
117 | // DataChannel polyfill using WebSockets as 'underlying data transport'
118 | function RTCDataChannel(label, dataChannelDict)
119 | {
120 | var self = this
121 |
122 | EventTarget.call(this)
123 |
124 | this.close = function()
125 | {
126 | if(this._udt)
127 | this._udt.close()
128 | }
129 | this.send = function(data)
130 | {
131 | if(this._udt)
132 | this._udt.send(data)
133 | }
134 |
135 | // binaryType
136 | this.__defineGetter__("binaryType", function()
137 | {
138 | return self._udt.binaryType;
139 | });
140 | this.__defineSetter__("binaryType", function(binaryType)
141 | {
142 | self._udt.binaryType = binaryType;
143 | });
144 |
145 | // bufferedAmount
146 | this.__defineGetter__("bufferedAmount", function()
147 | {
148 | if(!self._udt)
149 | return 0
150 |
151 | return self._udt.bufferedAmount;
152 | });
153 |
154 | // label
155 | this.__defineGetter__("label", function()
156 | {
157 | return label;
158 | });
159 |
160 | // readyState
161 | this.__defineGetter__("readyState", function()
162 | {
163 | if(!self._udt)
164 | return "connecting"
165 |
166 | switch(self._udt.readyState)
167 | {
168 | case 0: return "connecting"
169 | case 1: return "open"
170 | case 2: return "closing"
171 | case 3: return "closed"
172 | }
173 | });
174 |
175 | // id
176 | var id = (dataChannelDict.id != undefined) ? dataChannelDict.id : 0
177 | this.__defineGetter__("id", function()
178 | {
179 | return id;
180 | });
181 |
182 | // maxRetransmitTime
183 | var maxRetransmitTime = (dataChannelDict.maxRetransmitTime != undefined) ? dataChannelDict.maxRetransmitTime : null
184 | this.__defineGetter__("maxRetransmitTime", function()
185 | {
186 | return maxRetransmitTime;
187 | });
188 |
189 | // maxRetransmits
190 | var maxRetransmits = (dataChannelDict.maxRetransmits != undefined) ? dataChannelDict.maxRetransmits : null
191 | this.__defineGetter__("maxRetransmits", function()
192 | {
193 | return maxRetransmits;
194 | });
195 |
196 | if(maxRetransmitTime && maxRetransmits)
197 | throw SyntaxError
198 |
199 | var reliable = !(maxRetransmitTime || maxRetransmits)
200 |
201 | // negotiated
202 | var negotiated = (dataChannelDict.negotiated != undefined) ? dataChannelDict.negotiated : false
203 | this.__defineGetter__("negotiated", function()
204 | {
205 | return negotiated;
206 | });
207 |
208 | // ordered
209 | var ordered = (dataChannelDict.ordered != undefined) ? dataChannelDict.ordered : false
210 | this.__defineGetter__("ordered", function()
211 | {
212 | return ordered;
213 | });
214 |
215 | // protocol
216 | var protocol = (dataChannelDict.protocol != undefined) ? dataChannelDict.protocol : ""
217 | this.__defineGetter__("protocol", function()
218 | {
219 | return protocol;
220 | });
221 | }
222 |
223 |
224 | function createUDT(pc, channel, onopen)
225 | {
226 | pc._channels = pc._channels || {}
227 | pc._channels[channel.label] = channel
228 |
229 | if(!pc._peerId)
230 | {
231 | console.warn("No peer ID")
232 | return
233 | }
234 |
235 | console.info("Creating UDT")
236 |
237 | // Use a WebSocket as 'underlying data transport' to create the DataChannel
238 | channel._udt = new WebSocket(ws_url)
239 | channel._udt.addEventListener('close', function(event)
240 | {
241 | // if(error && channel.onerror)
242 | // {
243 | // channel.onerror(error)
244 | // return
245 | // }
246 |
247 | channel.dispatchEvent(event);
248 | })
249 | channel._udt.addEventListener('error', function(event)
250 | {
251 | channel.dispatchEvent(event);
252 | })
253 | channel._udt.addEventListener('open', function(event)
254 | {
255 | onopen(channel, pc)
256 | })
257 | }
258 |
259 |
260 | // Public function to initiate the creation of a new DataChannel
261 | if(createDataChannel && !supportReliable && Reliable)
262 | {
263 | RTCPeerConnection.prototype.createDataChannel = function(label, dataChannelDict)
264 | {
265 | dataChannelDict = dataChannelDict || {}
266 | dataChannelDict.reliable = false
267 |
268 | var channel = new Reliable(createDataChannel.call(this, label, dataChannelDict))
269 | channel.label = label
270 |
271 | return channel
272 | }
273 |
274 | var dispatchEvent = RTCPeerConnection.prototype.dispatchEvent;
275 | RTCPeerConnection.prototype.dispatchEvent = function(event)
276 | {
277 | var channel = event.channel;
278 |
279 | if(event.type == 'datachannel' && !(channel instanceof Reliable))
280 | {
281 | event.channel = new Reliable(channel)
282 | event.channel.label = channel.label
283 | }
284 |
285 | dispatchEvent.call(this, event)
286 | };
287 | }
288 |
289 | else
290 | RTCPeerConnection.prototype.createDataChannel = function(label, dataChannelDict)
291 | {
292 | // Back-ward compatibility
293 | if(this.readyState)
294 | this.signalingState = this.readyState
295 | // Back-ward compatibility
296 |
297 | if(this.signalingState == "closed")
298 | throw INVALID_STATE;
299 |
300 | if(!label)
301 | throw "'label' is not defined"
302 | dataChannelDict = dataChannelDict || {}
303 |
304 | var channel = new RTCDataChannel(label, dataChannelDict)
305 |
306 | createUDT(this, channel, onopen)
307 |
308 | return channel
309 | }
310 |
311 |
312 | // Private function to 'catch' the 'ondatachannel' event
313 | function ondatachannel(pc, socketId, label, dataChannelDict)
314 | {
315 | // Back-ward compatibility
316 | if(pc.readyState)
317 | pc.signalingState = pc.readyState
318 | // Back-ward compatibility
319 |
320 | if(pc.signalingState == "closed")
321 | return;
322 |
323 | var channel = new RTCDataChannel(label, dataChannelDict)
324 |
325 | createUDT(pc, channel, function(channel)
326 | {
327 | // Set channel as open
328 | channel.send(JSON.stringify(["ready", socketId]))
329 |
330 | // Set onmessage event to bypass messages to user defined function
331 | channel._udt.onmessage = function(event)
332 | {
333 | channel.dispatchEvent(event);
334 | }
335 |
336 | // Set channel as open
337 | var event = document.createEvent('Event')
338 | event.initEvent('open', true, true)
339 |
340 | channel.dispatchEvent(event);
341 | })
342 |
343 | var event = document.createEvent('Event')
344 | event.initEvent('datachannel', true, true)
345 | event.channel = channel
346 |
347 | pc.dispatchEvent(event);
348 | }
349 |
350 |
351 | function onopen(channel, pc)
352 | {
353 | // Wait until the other end of the channel is ready
354 | channel._udt.onmessage = function(message)
355 | {
356 | var args = JSON.parse(message.data);
357 |
358 | var eventName = args[0]
359 |
360 | switch(eventName)
361 | {
362 | // Both peers support native DataChannels
363 | case 'create.native':
364 | {
365 | // Close the ad-hoc signaling channel
366 | if(pc._signaling)
367 | pc._signaling.close();
368 |
369 | // Make native DataChannels to be created by default
370 | pc.createDataChannel = createDataChannel
371 |
372 | // Start native DataChannel connection
373 | channel._udt = pc.createDataChannel(channel.label, channel.reliable)
374 | }
375 | break
376 |
377 | // Connection through backend server is ready
378 | case 'ready':
379 | {
380 | // Back-ward compatibility
381 | if(pc.readyState)
382 | pc.signalingState = pc.readyState
383 | // Back-ward compatibility
384 |
385 | // PeerConnection is closed, do nothing
386 | if(self.signalingState == "closed")
387 | return;
388 |
389 | // Set onmessage event to bypass messages to user defined function
390 | channel._udt.onmessage = function(event)
391 | {
392 | channel.dispatchEvent(event);
393 | }
394 |
395 | // Set channel as open
396 | var event = document.createEvent('Event')
397 | event.initEvent('open', true, true)
398 |
399 | channel.dispatchEvent(event);
400 | }
401 | break
402 |
403 | default:
404 | console.error("Unknown event '"+eventName+"'")
405 | }
406 | }
407 |
408 | // Query to the other peer to create a new DataChannel with us
409 | channel.send(JSON.stringify(['create', pc._peerId, channel.label,
410 | channel.reliable, Boolean(createDataChannel)]))
411 | }
412 |
413 |
414 | // Get the SDP session ID from a RTCSessionDescription object
415 | function getId(description)
416 | {
417 | var pattern = /^o=.+/gm
418 | var result = pattern.exec(description.sdp);
419 |
420 | return result[0].substring(2)
421 | }
422 |
423 | // Overwrite setters to catch the session IDs
424 | var setLocalDescription = RTCPeerConnection.prototype.setLocalDescription
425 | var setRemoteDescription = RTCPeerConnection.prototype.setRemoteDescription
426 | var closeRTC = RTCPeerConnection.prototype.close;
427 |
428 | RTCPeerConnection.prototype.close = function()
429 | {
430 | if(this._signaling)
431 | this._signaling.close();
432 |
433 | closeRTC.call(this);
434 | };
435 |
436 | if(!createDataChannel)
437 | RTCPeerConnection.prototype.setLocalDescription = function(description,
438 | successCallback,
439 | failureCallback)
440 | {
441 | var self = this
442 |
443 | // Create a signalling channel with a WebSocket on the proxy server with the
444 | // defined ID and wait for new 'create' messages to create new DataChannels
445 | if(this._signaling)
446 | this._signaling.close();
447 |
448 | this._signaling = new WebSocket(ws_url)
449 | this._signaling.onopen = function(event)
450 | {
451 | this.onmessage = function(event)
452 | {
453 | var args = JSON.parse(event.data)
454 |
455 | switch(args[0])
456 | {
457 | case 'create':
458 | ondatachannel(self, args[1], args[2], {reliable: args[3]})
459 | break
460 |
461 | // Both peers support native DataChannels
462 | case 'create.native':
463 | // Make native DataChannels to be created by default
464 | self.createDataChannel = createDataChannel
465 | }
466 | }
467 |
468 | this.send(JSON.stringify(['setId', "pc."+getId(description),
469 | Boolean(createDataChannel)]))
470 | }
471 | this._signaling.onerror = function(error)
472 | {
473 | console.error(error)
474 | }
475 |
476 | setLocalDescription.call(this, description, successCallback, failureCallback)
477 | }
478 |
479 | if(!createDataChannel)
480 | RTCPeerConnection.prototype.setRemoteDescription = function(description,
481 | successCallback,
482 | failureCallback)
483 | {
484 | // Set the PeerConnection peer ID
485 | this._peerId = "pc."+getId(description)
486 |
487 | // Initialize pending channels
488 | for(var label in this._channels)
489 | {
490 | var channel = this._channels[label]
491 |
492 | if(channel._udt == undefined)
493 | createUDT(this, channel, onopen)
494 | }
495 |
496 | setRemoteDescription.call(this, description, successCallback, failureCallback)
497 | }
498 |
499 | if(createDataChannel && !supportReliable && Reliable)
500 | {
501 | var createOffer = RTCPeerConnection.prototype.createOffer;
502 | var createAnswer = RTCPeerConnection.prototype.createAnswer;
503 |
504 | RTCPeerConnection.prototype.createOffer = function(successCallback,
505 | failureCallback,
506 | constraints)
507 | {
508 | createOffer.call(this, function(offer)
509 | {
510 | offer.sdp = Reliable.higherBandwidthSDP(offer.sdp);
511 | successCallback(offer)
512 | },
513 | failureCallback, constraints)
514 | }
515 |
516 | RTCPeerConnection.prototype.createAnswer = function(successCallback,
517 | failureCallback,
518 | constraints)
519 | {
520 | createAnswer.call(this, function(answer)
521 | {
522 | answer.sdp = Reliable.higherBandwidthSDP(answer.sdp);
523 | successCallback(answer)
524 | },
525 | failureCallback, constraints)
526 | }
527 | }
528 |
529 |
530 |
531 | // Notify to the user if we have native DataChannels support or not
532 | if(createDataChannel)
533 | {
534 | if(supportReliable)
535 | {
536 | console.log("Both native and polyfill WebRTC DataChannel are available");
537 | return "native";
538 | }
539 |
540 | if(Reliable)
541 | console.warn("Native WebRTC DataChannel is not reliable, using polyfill instead");
542 | else
543 | console.error("Native WebRTC DataChannel is not reliable and not included polyfill");
544 |
545 | return "native no reliable";
546 | }
547 |
548 | console.warn("WebRTC DataChannel is only available thought polyfill");
549 | return "polyfill";
550 | }
551 |
552 | exports.DCPF_install = DCPF_install;
553 | })(this);
554 |
--------------------------------------------------------------------------------
/test/datachannel.js:
--------------------------------------------------------------------------------
1 | /*! datachannel.js build:1.0.0-3, development. Copyright(c) 2013 Jesús Leganés Combarro "Piranna" */
2 | (function(exports){
3 | /**
4 | * @author mrdoob / http://mrdoob.com/
5 | * @author Jesús Leganés Combarro "Piranna"
6 | */
7 |
8 | var EventTarget = function()
9 | {
10 | var listeners = {};
11 |
12 | this.addEventListener = function(type, listener)
13 | {
14 | if(listeners[type] === undefined)
15 | listeners[type] = [];
16 |
17 | if(listeners[type].indexOf(listener) === -1)
18 | listeners[type].push(listener);
19 | };
20 |
21 | this.dispatchEvent = function(event)
22 | {
23 | var listenerArray = (listeners[event.type] || []);
24 |
25 | var dummyListener = this['on' + event.type];
26 | if(typeof dummyListener == 'function')
27 | listenerArray = listenerArray.concat(dummyListener);
28 |
29 | for(var i=0, l=listenerArray.length; i */(function(e){function n(){this._pieces=[],this._parts=[]}function r(e){this.index=0,this.dataBuffer=e,this.dataView=new Uint8Array(this.dataBuffer),this.length=this.dataBuffer.byteLength}function i(e){this.utf8=e,this.bufferBuilder=new n}function o(e,t){if(!(this instanceof o))return new o(e);this._dc=e,s.debug=t,this._outgoing={},this._incoming={},this._received={},this._window=1e3,this._mtu=500,this._interval=0,this._count=0,this._queue=[],this._setupDC()}var t={};t.useBlobBuilder=function(){try{return new Blob([]),!1}catch(e){return!0}}(),t.useArrayBufferView=!t.useBlobBuilder&&function(){try{return(new Blob([new Uint8Array([])])).size===0}catch(e){return!0}}(),e.binaryFeatures=t,e.BlobBuilder=window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder||window.BlobBuilder,n.prototype.append=function(e){typeof e=="number"?this._pieces.push(e):(this._flush(),this._parts.push(e))},n.prototype._flush=function(){if(this._pieces.length>0){var e=new Uint8Array(this._pieces);t.useArrayBufferView||(e=e.buffer),this._parts.push(e),this._pieces=[]}},n.prototype.getBuffer=function(){this._flush();if(t.useBlobBuilder){var e=new BlobBuilder;for(var n=0,r=this._parts.length;n>31,n=(e>>23&255)-127,r=e&8388607|8388608;return(t==0?1:-1)*r*Math.pow(2,n-23)},r.prototype.unpack_double=function(){var e=this.unpack_uint32(),t=this.unpack_uint32(),n=e>>31,r=(e>>20&2047)-1023,i=e&1048575|1048576,s=i*Math.pow(2,r-20)+t*Math.pow(2,r-52);return(n==0?1:-1)*s},r.prototype.read=function(e){var t=this.index;if(t+e<=this.length)return this.dataView.subarray(t,t+e);throw new Error("BinaryPackFailure: read index out of range")},i.prototype.pack=function(e){var n=typeof e;if(n=="string")this.pack_string(e);else if(n=="number")Math.floor(e)===e?this.pack_integer(e):this.pack_double(e);else if(n=="boolean")e===!0?this.bufferBuilder.append(195):e===!1&&this.bufferBuilder.append(194);else if(n=="undefined")this.bufferBuilder.append(192);else{if(n!="object")throw new Error('Type "'+n+'" not yet supported');if(e===null)this.bufferBuilder.append(192);else{var r=e.constructor;if(r==Array)this.pack_array(e);else if(r==Blob||r==File)this.pack_bin(e);else if(r==ArrayBuffer)t.useArrayBufferView?this.pack_bin(new Uint8Array(e)):this.pack_bin(e);else if("BYTES_PER_ELEMENT"in e)t.useArrayBufferView?this.pack_bin(e):this.pack_bin(e.buffer);else if(r==Object)this.pack_object(e);else if(r==Date)this.pack_string(e.toString());else{if(typeof e.toBinaryPack!="function")throw new Error('Type "'+r.toString()+'" not yet supported');this.bufferBuilder.append(e.toBinaryPack())}}}return this.bufferBuilder.getBuffer()},i.prototype.pack_bin=function(e){var t=e.length||e.byteLength||e.size;if(t<=15)this.pack_uint8(160+t);else if(t<=65535)this.bufferBuilder.append(218),this.pack_uint16(t);else{if(!(t<=4294967295))throw new Error("Invalid length");this.bufferBuilder.append(219),this.pack_uint32(t)}this.bufferBuilder.append(e)},i.prototype.pack_string=function(e){var t;if(this.utf8){var n=new Blob([e]);t=n.size}else t=e.length;if(t<=15)this.pack_uint8(176+t);else if(t<=65535)this.bufferBuilder.append(216),this.pack_uint16(t);else{if(!(t<=4294967295))throw new Error("Invalid length");this.bufferBuilder.append(217),this.pack_uint32(t)}this.bufferBuilder.append(e)},i.prototype.pack_array=function(e){var t=e.length;if(t<=15)this.pack_uint8(144+t);else if(t<=65535)this.bufferBuilder.append(220),this.pack_uint16(t);else{if(!(t<=4294967295))throw new Error("Invalid length");this.bufferBuilder.append(221),this.pack_uint32(t)}for(var n=0;n>8),this.bufferBuilder.append(e&255)},i.prototype.pack_uint32=function(e){var t=e&4294967295;this.bufferBuilder.append((t&4278190080)>>>24),this.bufferBuilder.append((t&16711680)>>>16),this.bufferBuilder.append((t&65280)>>>8),this.bufferBuilder.append(t&255)},i.prototype.pack_uint64=function(e){var t=e/Math.pow(2,32),n=e%Math.pow(2,32);this.bufferBuilder.append((t&4278190080)>>>24),this.bufferBuilder.append((t&16711680)>>>16),this.bufferBuilder.append((t&65280)>>>8),this.bufferBuilder.append(t&255),this.bufferBuilder.append((n&4278190080)>>>24),this.bufferBuilder.append((n&16711680)>>>16),this.bufferBuilder.append((n&65280)>>>8),this.bufferBuilder.append(n&255)},i.prototype.pack_int8=function(e){this.bufferBuilder.append(e&255)},i.prototype.pack_int16=function(e){this.bufferBuilder.append((e&65280)>>8),this.bufferBuilder.append(e&255)},i.prototype.pack_int32=function(e){this.bufferBuilder.append(e>>>24&255),this.bufferBuilder.append((e&16711680)>>>16),this.bufferBuilder.append((e&65280)>>>8),this.bufferBuilder.append(e&255)},i.prototype.pack_int64=function(e){var t=Math.floor(e/Math.pow(2,32)),n=e%Math.pow(2,32);this.bufferBuilder.append((t&4278190080)>>>24),this.bufferBuilder.append((t&16711680)>>>16),this.bufferBuilder.append((t&65280)>>>8),this.bufferBuilder.append(t&255),this.bufferBuilder.append((n&4278190080)>>>24),this.bufferBuilder.append((n&16711680)>>>16),this.bufferBuilder.append((n&65280)>>>8),this.bufferBuilder.append(n&255)};var s={debug:!1,inherits:function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})},extend:function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e},pack:BinaryPack.pack,unpack:BinaryPack.unpack,log:function(){if(s.debug){var e=[];for(var t=0;t=i.chunks.length?(s.log("Time: ",new Date-i.timer),delete this._outgoing[t]):this._processAcks()}break;case"chunk":i=n;if(!i){var f=this._received[t];if(f===!0)break;i={ack:["ack",t,0],chunks:[]},this._incoming[t]=i}var l=e[2],c=e[3];i.chunks[l]=new Uint8Array(c),l===i.ack[2]&&this._calculateNextAck(t),this._ack(t);break;default:this._handleSend(e)}},o.prototype._chunk=function(e){var t=[],n=e.size,r=0;while(r=n.length&&r.push(["end",e,n.length]),r._multiple=!0,this._handleSend(r)},o.prototype._complete=function(e){s.log("Completed called for",e);var t=this,n=this._incoming[e].chunks,r=new Blob(n);s.blobToArrayBuffer(r,function(e){var n=document.createEvent("Event");n.initEvent("message",!0,!0),n.data=s.unpack(e),t.dispatchEvent(n)}),delete this._incoming[e]},o.prototype._listeners={},o.prototype.addEventListener=function(e,t,n){e=="message"?(this._listeners[e]===undefined&&(this._listeners[e]=[]),this._listeners[e].indexOf(t)===-1&&this._listeners[e].push(t)):this._dc.addEventListener(e,t,n)},o.prototype.dispatchEvent=function(e){if(type=="message"){var t=this._listeners[e.type]||[],n=this["on"+e.type];typeof n=="function"&&(t=t.concat(n));for(var r=0,i=t.length;r1?t[0]+n+t[1]:e},o.prototype.addEventListener=function(e,t){var n=this;this._listeners={};var r=this._listeners[t]=function(e){t.call(n,e)};this._dc.addEventListener(e,r)},o.prototype.dispatchEvent=function(e){this._dc.dispatchEvent(e)},o.prototype.removeEventListener=function(e,t){var n=this._listeners[t];this._dc.removeEventListener(e,n)},o.prototype.onmessage=function(e){},e.Reliable=o})(this)/**
43 | * DataChannel polyfill
44 | *
45 | * Web browser polyfill that implements the WebRTC DataChannel API over a
46 | * websocket. It implements the full latest DataChannel API specification
47 | * defined at 2013-01-16.
48 | *
49 | * Copyright (C) 2012-2013 Jesús Leganés Combarro "Piranna"
50 | *
51 | * This code can be found at https://github.com/piranna/DataChannel-polyfill
52 | *
53 | *
54 | * This program is free software: you can redistribute it and/or modify it under
55 | * the terms of the GNU Affero General Public License as published by the Free
56 | * Software Foundation, either version 3 of the License, or (at your option) any
57 | * later version.
58 | *
59 | * This program is distributed in the hope that it will be useful, but WITHOUT
60 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
61 | * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
62 | * details.
63 | *
64 | * You should have received a copy of the GNU Affero General Public License
65 | * along with this program. If not, see .
66 | */
67 |
68 |
69 | /**
70 | * Install the polyfill
71 | * @param {String} ws_url URL to the backend server
72 | */
73 | function DCPF_install(ws_url)
74 | {
75 | // Fallbacks for vendor-specific variables until the spec is finalized.
76 | var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
77 |
78 | // Check if browser has support for WebRTC PeerConnection
79 | if(RTCPeerConnection == undefined)
80 | {
81 | console.error("Your browser doesn't support RTCPeerConnection, please use "+
82 | "one of the latest versions of Chrome/Chromium or Firefox");
83 | return "old browser";
84 | }
85 |
86 | var createDataChannel;
87 | var supportReliable = false;
88 |
89 | //Holds the STUN server to use for PeerConnections.
90 | var SERVER = "stun:stun.l.google.com:19302";
91 |
92 | // Check if browser has support for native WebRTC DataChannel
93 | var pc = new RTCPeerConnection({iceServers: [{url: SERVER}]},
94 | {optional: [{RtpDataChannels: true}]})
95 |
96 | try
97 | {
98 | // Check native
99 | pc.createDataChannel('DCPF_install__checkSupport',{reliable:false}).close();
100 |
101 | // Native support available, store the function
102 | createDataChannel = pc.createDataChannel
103 |
104 | // Check reliable
105 | pc.createDataChannel('DCPF_install__checkSupport').close();
106 |
107 | // Native reliable support available
108 | supportReliable = true
109 | }
110 | catch(e){}
111 | finally
112 | {
113 | pc.close()
114 | }
115 |
116 |
117 | // DataChannel polyfill using WebSockets as 'underlying data transport'
118 | function RTCDataChannel(label, dataChannelDict)
119 | {
120 | var self = this
121 |
122 | EventTarget.call(this)
123 |
124 | this.close = function()
125 | {
126 | if(this._udt)
127 | this._udt.close()
128 | }
129 | this.send = function(data)
130 | {
131 | if(this._udt)
132 | this._udt.send(data)
133 | }
134 |
135 | // binaryType
136 | this.__defineGetter__("binaryType", function()
137 | {
138 | return self._udt.binaryType;
139 | });
140 | this.__defineSetter__("binaryType", function(binaryType)
141 | {
142 | self._udt.binaryType = binaryType;
143 | });
144 |
145 | // bufferedAmount
146 | this.__defineGetter__("bufferedAmount", function()
147 | {
148 | if(!self._udt)
149 | return 0
150 |
151 | return self._udt.bufferedAmount;
152 | });
153 |
154 | // label
155 | this.__defineGetter__("label", function()
156 | {
157 | return label;
158 | });
159 |
160 | // readyState
161 | this.__defineGetter__("readyState", function()
162 | {
163 | if(!self._udt)
164 | return "connecting"
165 |
166 | switch(self._udt.readyState)
167 | {
168 | case 0: return "connecting"
169 | case 1: return "open"
170 | case 2: return "closing"
171 | case 3: return "closed"
172 | }
173 | });
174 |
175 | // id
176 | var id = (dataChannelDict.id != undefined) ? dataChannelDict.id : 0
177 | this.__defineGetter__("id", function()
178 | {
179 | return id;
180 | });
181 |
182 | // maxRetransmitTime
183 | var maxRetransmitTime = (dataChannelDict.maxRetransmitTime != undefined) ? dataChannelDict.maxRetransmitTime : null
184 | this.__defineGetter__("maxRetransmitTime", function()
185 | {
186 | return maxRetransmitTime;
187 | });
188 |
189 | // maxRetransmits
190 | var maxRetransmits = (dataChannelDict.maxRetransmits != undefined) ? dataChannelDict.maxRetransmits : null
191 | this.__defineGetter__("maxRetransmits", function()
192 | {
193 | return maxRetransmits;
194 | });
195 |
196 | if(maxRetransmitTime && maxRetransmits)
197 | throw SyntaxError
198 |
199 | var reliable = !(maxRetransmitTime || maxRetransmits)
200 |
201 | // negotiated
202 | var negotiated = (dataChannelDict.negotiated != undefined) ? dataChannelDict.negotiated : false
203 | this.__defineGetter__("negotiated", function()
204 | {
205 | return negotiated;
206 | });
207 |
208 | // ordered
209 | var ordered = (dataChannelDict.ordered != undefined) ? dataChannelDict.ordered : false
210 | this.__defineGetter__("ordered", function()
211 | {
212 | return ordered;
213 | });
214 |
215 | // protocol
216 | var protocol = (dataChannelDict.protocol != undefined) ? dataChannelDict.protocol : ""
217 | this.__defineGetter__("protocol", function()
218 | {
219 | return protocol;
220 | });
221 | }
222 |
223 |
224 | function createUDT(pc, channel, onopen)
225 | {
226 | pc._channels = pc._channels || {}
227 | pc._channels[channel.label] = channel
228 |
229 | if(!pc._peerId)
230 | {
231 | console.warn("No peer ID")
232 | return
233 | }
234 |
235 | console.info("Creating UDT")
236 |
237 | // Use a WebSocket as 'underlying data transport' to create the DataChannel
238 | channel._udt = new WebSocket(ws_url)
239 | channel._udt.addEventListener('close', function(event)
240 | {
241 | // if(error && channel.onerror)
242 | // {
243 | // channel.onerror(error)
244 | // return
245 | // }
246 |
247 | channel.dispatchEvent(event);
248 | })
249 | channel._udt.addEventListener('error', function(event)
250 | {
251 | channel.dispatchEvent(event);
252 | })
253 | channel._udt.addEventListener('open', function(event)
254 | {
255 | onopen(channel, pc)
256 | })
257 | }
258 |
259 |
260 | // Public function to initiate the creation of a new DataChannel
261 | if(createDataChannel && !supportReliable && Reliable)
262 | {
263 | RTCPeerConnection.prototype.createDataChannel = function(label, dataChannelDict)
264 | {
265 | dataChannelDict = dataChannelDict || {}
266 | dataChannelDict.reliable = false
267 |
268 | var channel = new Reliable(createDataChannel.call(this, label, dataChannelDict))
269 | channel.label = label
270 |
271 | return channel
272 | }
273 |
274 | var dispatchEvent = RTCPeerConnection.prototype.dispatchEvent;
275 | RTCPeerConnection.prototype.dispatchEvent = function(event)
276 | {
277 | var channel = event.channel;
278 |
279 | if(event.type == 'datachannel' && !(channel instanceof Reliable))
280 | {
281 | event.channel = new Reliable(channel)
282 | event.channel.label = channel.label
283 | }
284 |
285 | dispatchEvent.call(this, event)
286 | };
287 | }
288 |
289 | else
290 | RTCPeerConnection.prototype.createDataChannel = function(label, dataChannelDict)
291 | {
292 | // Back-ward compatibility
293 | if(this.readyState)
294 | this.signalingState = this.readyState
295 | // Back-ward compatibility
296 |
297 | if(this.signalingState == "closed")
298 | throw INVALID_STATE;
299 |
300 | if(!label)
301 | throw "'label' is not defined"
302 | dataChannelDict = dataChannelDict || {}
303 |
304 | var channel = new RTCDataChannel(label, dataChannelDict)
305 |
306 | createUDT(this, channel, onopen)
307 |
308 | return channel
309 | }
310 |
311 |
312 | // Private function to 'catch' the 'ondatachannel' event
313 | function ondatachannel(pc, socketId, label, dataChannelDict)
314 | {
315 | // Back-ward compatibility
316 | if(pc.readyState)
317 | pc.signalingState = pc.readyState
318 | // Back-ward compatibility
319 |
320 | if(pc.signalingState == "closed")
321 | return;
322 |
323 | var channel = new RTCDataChannel(label, dataChannelDict)
324 |
325 | createUDT(pc, channel, function(channel)
326 | {
327 | // Set channel as open
328 | channel.send(JSON.stringify(["ready", socketId]))
329 |
330 | // Set onmessage event to bypass messages to user defined function
331 | channel._udt.onmessage = function(event)
332 | {
333 | channel.dispatchEvent(event);
334 | }
335 |
336 | // Set channel as open
337 | var event = document.createEvent('Event')
338 | event.initEvent('open', true, true)
339 |
340 | channel.dispatchEvent(event);
341 | })
342 |
343 | var event = document.createEvent('Event')
344 | event.initEvent('datachannel', true, true)
345 | event.channel = channel
346 |
347 | pc.dispatchEvent(event);
348 | }
349 |
350 |
351 | function onopen(channel, pc)
352 | {
353 | // Wait until the other end of the channel is ready
354 | channel._udt.onmessage = function(message)
355 | {
356 | var args = JSON.parse(message.data);
357 |
358 | var eventName = args[0]
359 |
360 | switch(eventName)
361 | {
362 | // Both peers support native DataChannels
363 | case 'create.native':
364 | {
365 | // Close the ad-hoc signaling channel
366 | if(pc._signaling)
367 | pc._signaling.close();
368 |
369 | // Make native DataChannels to be created by default
370 | pc.createDataChannel = createDataChannel
371 |
372 | // Start native DataChannel connection
373 | channel._udt = pc.createDataChannel(channel.label, channel.reliable)
374 | }
375 | break
376 |
377 | // Connection through backend server is ready
378 | case 'ready':
379 | {
380 | // Back-ward compatibility
381 | if(pc.readyState)
382 | pc.signalingState = pc.readyState
383 | // Back-ward compatibility
384 |
385 | // PeerConnection is closed, do nothing
386 | if(self.signalingState == "closed")
387 | return;
388 |
389 | // Set onmessage event to bypass messages to user defined function
390 | channel._udt.onmessage = function(event)
391 | {
392 | channel.dispatchEvent(event);
393 | }
394 |
395 | // Set channel as open
396 | var event = document.createEvent('Event')
397 | event.initEvent('open', true, true)
398 |
399 | channel.dispatchEvent(event);
400 | }
401 | break
402 |
403 | default:
404 | console.error("Unknown event '"+eventName+"'")
405 | }
406 | }
407 |
408 | // Query to the other peer to create a new DataChannel with us
409 | channel.send(JSON.stringify(['create', pc._peerId, channel.label,
410 | channel.reliable, Boolean(createDataChannel)]))
411 | }
412 |
413 |
414 | // Get the SDP session ID from a RTCSessionDescription object
415 | function getId(description)
416 | {
417 | var pattern = /^o=.+/gm
418 | var result = pattern.exec(description.sdp);
419 |
420 | return result[0].substring(2)
421 | }
422 |
423 | // Overwrite setters to catch the session IDs
424 | var setLocalDescription = RTCPeerConnection.prototype.setLocalDescription
425 | var setRemoteDescription = RTCPeerConnection.prototype.setRemoteDescription
426 | var closeRTC = RTCPeerConnection.prototype.close;
427 |
428 | RTCPeerConnection.prototype.close = function()
429 | {
430 | if(this._signaling)
431 | this._signaling.close();
432 |
433 | closeRTC.call(this);
434 | };
435 |
436 | if(!createDataChannel)
437 | RTCPeerConnection.prototype.setLocalDescription = function(description,
438 | successCallback,
439 | failureCallback)
440 | {
441 | var self = this
442 |
443 | // Create a signalling channel with a WebSocket on the proxy server with the
444 | // defined ID and wait for new 'create' messages to create new DataChannels
445 | if(this._signaling)
446 | this._signaling.close();
447 |
448 | this._signaling = new WebSocket(ws_url)
449 | this._signaling.onopen = function(event)
450 | {
451 | this.onmessage = function(event)
452 | {
453 | var args = JSON.parse(event.data)
454 |
455 | switch(args[0])
456 | {
457 | case 'create':
458 | ondatachannel(self, args[1], args[2], {reliable: args[3]})
459 | break
460 |
461 | // Both peers support native DataChannels
462 | case 'create.native':
463 | // Make native DataChannels to be created by default
464 | self.createDataChannel = createDataChannel
465 | }
466 | }
467 |
468 | this.send(JSON.stringify(['setId', "pc."+getId(description),
469 | Boolean(createDataChannel)]))
470 | }
471 | this._signaling.onerror = function(error)
472 | {
473 | console.error(error)
474 | }
475 |
476 | setLocalDescription.call(this, description, successCallback, failureCallback)
477 | }
478 |
479 | if(!createDataChannel)
480 | RTCPeerConnection.prototype.setRemoteDescription = function(description,
481 | successCallback,
482 | failureCallback)
483 | {
484 | // Set the PeerConnection peer ID
485 | this._peerId = "pc."+getId(description)
486 |
487 | // Initialize pending channels
488 | for(var label in this._channels)
489 | {
490 | var channel = this._channels[label]
491 |
492 | if(channel._udt == undefined)
493 | createUDT(this, channel, onopen)
494 | }
495 |
496 | setRemoteDescription.call(this, description, successCallback, failureCallback)
497 | }
498 |
499 | if(createDataChannel && !supportReliable && Reliable)
500 | {
501 | var createOffer = RTCPeerConnection.prototype.createOffer;
502 | var createAnswer = RTCPeerConnection.prototype.createAnswer;
503 |
504 | RTCPeerConnection.prototype.createOffer = function(successCallback,
505 | failureCallback,
506 | constraints)
507 | {
508 | createOffer.call(this, function(offer)
509 | {
510 | offer.sdp = Reliable.higherBandwidthSDP(offer.sdp);
511 | successCallback(offer)
512 | },
513 | failureCallback, constraints)
514 | }
515 |
516 | RTCPeerConnection.prototype.createAnswer = function(successCallback,
517 | failureCallback,
518 | constraints)
519 | {
520 | createAnswer.call(this, function(answer)
521 | {
522 | answer.sdp = Reliable.higherBandwidthSDP(answer.sdp);
523 | successCallback(answer)
524 | },
525 | failureCallback, constraints)
526 | }
527 | }
528 |
529 |
530 |
531 | // Notify to the user if we have native DataChannels support or not
532 | if(createDataChannel)
533 | {
534 | if(supportReliable)
535 | {
536 | console.log("Both native and polyfill WebRTC DataChannel are available");
537 | return "native";
538 | }
539 |
540 | if(Reliable)
541 | console.warn("Native WebRTC DataChannel is not reliable, using polyfill instead");
542 | else
543 | console.error("Native WebRTC DataChannel is not reliable and not included polyfill");
544 |
545 | return "native no reliable";
546 | }
547 |
548 | console.warn("WebRTC DataChannel is only available thought polyfill");
549 | return "polyfill";
550 | }
551 |
552 | exports.DCPF_install = DCPF_install;
553 | })(this);
554 |
--------------------------------------------------------------------------------