├── 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 |
15 |
16 |
17 | 18 |
19 | 20 | 21 | Fork me on GitHub 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 | --------------------------------------------------------------------------------