├── .gitignore ├── Gulpfile.js ├── LICENSE-APACHE2 ├── README.md ├── bower.json ├── coverage ├── coverage-final.json ├── lcov-report │ ├── base.css │ ├── index.html │ ├── lib │ │ ├── Connection.js.html │ │ ├── ConnectionManager.js.html │ │ ├── JSONProtocol.js.html │ │ ├── P.js.html │ │ ├── WebRtcConnection.js.html │ │ ├── WebSocketConnection.js.html │ │ ├── index.html │ │ └── init.js.html │ ├── prettify.css │ ├── prettify.js │ ├── sort-arrow-sprite.png │ └── sorter.js └── lcov.info ├── dist ├── P.min.js ├── P.min.js.map └── p.js ├── examples ├── party │ ├── Charlie.html │ ├── alice.html │ └── bob.html └── ping-pong │ ├── ping.html │ └── pong.html ├── index.js ├── lib ├── Connection.js ├── ConnectionManager.js ├── JSONProtocol.js ├── P.js ├── WebRtcConnection.js ├── WebSocketConnection.js └── init.js ├── package.json └── test ├── specs ├── Connection.js ├── ConnectionManager.js ├── JSONProtocol.js ├── P.js ├── WebRTCConnection.js └── WebSocketConnection.js └── util.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.grunt 2 | /_SpecRunner.html 3 | /node_modules 4 | /build 5 | -------------------------------------------------------------------------------- /Gulpfile.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | gulp = require('gulp'), 3 | connect = require('gulp-connect'), 4 | bpjs = require('boilerplate-gulp-js'); 5 | 6 | bpjs(gulp, { 7 | name: 'P', 8 | entry: './index.js', 9 | sources: './lib/**/*.js', 10 | tests: './test/**/*.js', 11 | dest: './dist' 12 | }); 13 | 14 | function server(){ 15 | connect.server({ 16 | root: ['./dist', './examples'] 17 | }); 18 | } 19 | 20 | gulp.task('build', gulp.series('bpjs:build')); 21 | 22 | gulp.task('dev', gulp.parallel('bpjs:dev', server)); 23 | -------------------------------------------------------------------------------- /LICENSE-APACHE2: -------------------------------------------------------------------------------- 1 | Copyright 2014 Ozan Turgut 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # P is for peer-to-peer networking with browsers 2 | 3 | P is a small framework used to create browser-to-browser networks (as opposed to just a connection). With P, you can: 4 | 5 | * Connect to other browsers using [a simple WebSocket server](https://github.com/unsetbit/onramp). 6 | * Connect to other browsers using your established connections to other browsers. This is what makes P unique: it allows for transitive connections across peers, allowing easy creation of mesh networks. 7 | 8 | After a connection is established the middleman is no longer necessary – no proxies are involved. 9 | 10 | This is made possible by an unstable and young technology -- [WebRTC](http://www.webrtc.org/). 11 | Currently, only Chrome and Firefox support this technology. 12 | 13 | [onramp](https://github.com/unsetbit/onramp), a simple WebSocket server, is used as the signaling channel 14 | to establish initial connections. 15 | 16 | ## API 17 | ```javascript 18 | // Initializing 19 | var rootNode = P.create(); // create the root node 20 | 21 | // Connection management 22 | var webSocketNode = rootNode.connect(address); // connect to an onramp WebSocket server 23 | var webRtcNode = webSocketNode.connect(address); // connect to a peer using an onramp connection 24 | var webRtcNode = webRtcNode.connect(address); // connect to a peer using an existing peer connection 25 | anyNode.close(); // close the connection 26 | anyNode.isOpen(); // return true if the connection is open 27 | var nodeArray = anyNode.getPeers(); // returns an array of all peer connections 28 | 29 | // Firewalling connections 30 | var protectedNode = P.create({ 31 | firewall: function(offerData){ 32 | // Only accept RTC connection offers which send 'secret' as the offer data 33 | // this firewall rule will apply to any child nodes as well 34 | return offerData === 'secret'; 35 | } 36 | }); 37 | 38 | 39 | // Send offerData with a connection request 40 | anyNode.connect({address: address, offerData: 'secret'}); 41 | 42 | 43 | // Sending and receiving messages 44 | webRtcNode.send(message); // send a message to a peer; can be json, string, or arraybuffer 45 | webRtcNode.on('message', function(message){}); // listens for messages from a peer 46 | webRtcNode.on('array buffer', function(arrayBuffer){}); // listens for array buffers from a peer 47 | 48 | // Events 49 | anyNode.on('connection', function(peerNode){}); // emitted when a connection is made via this peer 50 | anyNode.on('open', function(){}); // emitted when this connection is open and ready 51 | anyNode.on('close', function(){}); // emitted when this connection is closed 52 | anyNode.on('error', function(err){}); // listens for errors for this connection 53 | anyNode.removeListener(eventName, optionalCallback); // stops listening to an event 54 | ``` 55 | 56 | 57 | ## Documentation 58 | * [Example with walkthrough](http://unsetbit.com/p/#walkthrough) 59 | * [API](http://unsetbit.com/p/#use) 60 | * [Cookbook](http://unsetbit.com/p/#cookbook) 61 | * [Contribute](http://unsetbit.com/p/#contribute) 62 | 63 | ## Release Notes 64 | * 0.3.3 - Fixed an issue in FF which caused disconnections shorting after successful connections due to a GC miscommunication which seems to clean up DataChannels which are still in use (thereby closing them). 65 | * 0.3.2 - Updated the library to align it with the latest WebRTC standards -- mainly changes in configuration objects. 66 | * 0.3.1 - Added 'firewall' option to firewall RTC requests. 67 | * 0.3 - Major refactor of internals and simplification of API, Firefox support, and respectable unit test coverage. 68 | * 0.2 - Public release 69 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "p", 3 | "description": "Peer-to-peer networking with browsers", 4 | "version": "0.3.3", 5 | "main": "./dist/p.js", 6 | "ignore": [ 7 | "./test/**", 8 | "node_modules", 9 | "Gruntfile.js", 10 | "package.json" 11 | ], 12 | "keywords": ["WebRTC", "WebSockets", "p2p", "peer-to-peer", "internet", "p"] 13 | } 14 | -------------------------------------------------------------------------------- /coverage/lcov-report/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | } 4 | body { 5 | font-family: Helvetica Neue, Helvetica,Arial; 6 | font-size: 10pt; 7 | } 8 | div.header, div.footer { 9 | background: #eee; 10 | padding: 1em; 11 | } 12 | div.header { 13 | z-index: 100; 14 | position: fixed; 15 | top: 0; 16 | border-bottom: 1px solid #666; 17 | width: 100%; 18 | } 19 | div.footer { 20 | border-top: 1px solid #666; 21 | } 22 | div.body { 23 | margin-top: 10em; 24 | } 25 | div.meta { 26 | font-size: 90%; 27 | text-align: center; 28 | } 29 | h1, h2, h3 { 30 | font-weight: normal; 31 | } 32 | h1 { 33 | font-size: 12pt; 34 | } 35 | h2 { 36 | font-size: 10pt; 37 | } 38 | pre { 39 | font-family: Consolas, Menlo, Monaco, monospace; 40 | margin: 0; 41 | padding: 0; 42 | line-height: 14px; 43 | font-size: 14px; 44 | -moz-tab-size: 2; 45 | -o-tab-size: 2; 46 | tab-size: 2; 47 | } 48 | 49 | div.path { font-size: 110%; } 50 | div.path a:link, div.path a:visited { color: #000; } 51 | table.coverage { border-collapse: collapse; margin:0; padding: 0 } 52 | 53 | table.coverage td { 54 | margin: 0; 55 | padding: 0; 56 | color: #111; 57 | vertical-align: top; 58 | } 59 | table.coverage td.line-count { 60 | width: 50px; 61 | text-align: right; 62 | padding-right: 5px; 63 | } 64 | table.coverage td.line-coverage { 65 | color: #777 !important; 66 | text-align: right; 67 | border-left: 1px solid #666; 68 | border-right: 1px solid #666; 69 | } 70 | 71 | table.coverage td.text { 72 | } 73 | 74 | table.coverage td span.cline-any { 75 | display: inline-block; 76 | padding: 0 5px; 77 | width: 40px; 78 | } 79 | table.coverage td span.cline-neutral { 80 | background: #eee; 81 | } 82 | table.coverage td span.cline-yes { 83 | background: #b5d592; 84 | color: #999; 85 | } 86 | table.coverage td span.cline-no { 87 | background: #fc8c84; 88 | } 89 | 90 | .cstat-yes { color: #111; } 91 | .cstat-no { background: #fc8c84; color: #111; } 92 | .fstat-no { background: #ffc520; color: #111 !important; } 93 | .cbranch-no { background: yellow !important; color: #111; } 94 | 95 | .cstat-skip { background: #ddd; color: #111; } 96 | .fstat-skip { background: #ddd; color: #111 !important; } 97 | .cbranch-skip { background: #ddd !important; color: #111; } 98 | 99 | .missing-if-branch { 100 | display: inline-block; 101 | margin-right: 10px; 102 | position: relative; 103 | padding: 0 4px; 104 | background: black; 105 | color: yellow; 106 | } 107 | 108 | .skip-if-branch { 109 | display: none; 110 | margin-right: 10px; 111 | position: relative; 112 | padding: 0 4px; 113 | background: #ccc; 114 | color: white; 115 | } 116 | 117 | .missing-if-branch .typ, .skip-if-branch .typ { 118 | color: inherit !important; 119 | } 120 | 121 | .entity, .metric { font-weight: bold; } 122 | .metric { display: inline-block; border: 1px solid #333; padding: 0.3em; background: white; } 123 | .metric small { font-size: 80%; font-weight: normal; color: #666; } 124 | 125 | div.coverage-summary table { border-collapse: collapse; margin: 3em; font-size: 110%; } 126 | div.coverage-summary td, div.coverage-summary table th { margin: 0; padding: 0.25em 1em; border-top: 1px solid #666; border-bottom: 1px solid #666; } 127 | div.coverage-summary th { text-align: left; border: 1px solid #666; background: #eee; font-weight: normal; } 128 | div.coverage-summary th.file { border-right: none !important; } 129 | div.coverage-summary th.pic { border-left: none !important; text-align: right; } 130 | div.coverage-summary th.pct { border-right: none !important; } 131 | div.coverage-summary th.abs { border-left: none !important; text-align: right; } 132 | div.coverage-summary td.pct { text-align: right; border-left: 1px solid #666; } 133 | div.coverage-summary td.abs { text-align: right; font-size: 90%; color: #444; border-right: 1px solid #666; } 134 | div.coverage-summary td.file { text-align: right; border-left: 1px solid #666; white-space: nowrap; } 135 | div.coverage-summary td.pic { min-width: 120px !important; } 136 | div.coverage-summary a:link { text-decoration: none; color: #000; } 137 | div.coverage-summary a:visited { text-decoration: none; color: #333; } 138 | div.coverage-summary a:hover { text-decoration: underline; } 139 | div.coverage-summary tfoot td { border-top: 1px solid #666; } 140 | 141 | div.coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | div.coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | div.coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | 155 | .high { background: #b5d592 !important; } 156 | .medium { background: #ffe87c !important; } 157 | .low { background: #fc8c84 !important; } 158 | 159 | span.cover-fill, span.cover-empty { 160 | display:inline-block; 161 | border:1px solid #444; 162 | background: white; 163 | height: 12px; 164 | } 165 | span.cover-fill { 166 | background: #ccc; 167 | border-right: 1px solid #444; 168 | } 169 | span.cover-empty { 170 | background: white; 171 | border-left: none; 172 | } 173 | span.cover-full { 174 | border-right: none !important; 175 | } 176 | pre.prettyprint { 177 | border: none !important; 178 | padding: 0 !important; 179 | margin: 0 !important; 180 | } 181 | .com { color: #999 !important; } 182 | .ignore-none { color: #999; font-weight: normal; } -------------------------------------------------------------------------------- /coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for All files 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |

Code coverage report for All files

17 |

18 | Statements: 72.39% (236 / 326)      19 | Branches: 59.76% (49 / 82)      20 | Functions: 55.7% (44 / 79)      21 | Lines: 73.02% (230 / 315)      22 | Ignored: none      23 |

24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
FileStatementsBranchesFunctionsLines
lib/72.39%(236 / 326)59.76%(49 / 82)55.7%(44 / 79)73.02%(230 / 315)
58 |
59 |
60 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /coverage/lcov-report/lib/Connection.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for lib/Connection.js 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |

Code coverage report for lib/Connection.js

17 |

18 | Statements: 84.52% (71 / 84)      19 | Branches: 75% (15 / 20)      20 | Functions: 78.95% (15 / 19)      21 | Lines: 85% (68 / 80)      22 | Ignored: none      23 |

24 |
All files » lib/ » Connection.js
25 |
26 |
27 |

 28 | 
488 | 
1 29 | 2 30 | 3 31 | 4 32 | 5 33 | 6 34 | 7 35 | 8 36 | 9 37 | 10 38 | 11 39 | 12 40 | 13 41 | 14 42 | 15 43 | 16 44 | 17 45 | 18 46 | 19 47 | 20 48 | 21 49 | 22 50 | 23 51 | 24 52 | 25 53 | 26 54 | 27 55 | 28 56 | 29 57 | 30 58 | 31 59 | 32 60 | 33 61 | 34 62 | 35 63 | 36 64 | 37 65 | 38 66 | 39 67 | 40 68 | 41 69 | 42 70 | 43 71 | 44 72 | 45 73 | 46 74 | 47 75 | 48 76 | 49 77 | 50 78 | 51 79 | 52 80 | 53 81 | 54 82 | 55 83 | 56 84 | 57 85 | 58 86 | 59 87 | 60 88 | 61 89 | 62 90 | 63 91 | 64 92 | 65 93 | 66 94 | 67 95 | 68 96 | 69 97 | 70 98 | 71 99 | 72 100 | 73 101 | 74 102 | 75 103 | 76 104 | 77 105 | 78 106 | 79 107 | 80 108 | 81 109 | 82 110 | 83 111 | 84 112 | 85 113 | 86 114 | 87 115 | 88 116 | 89 117 | 90 118 | 91 119 | 92 120 | 93 121 | 94 122 | 95 123 | 96 124 | 97 125 | 98 126 | 99 127 | 100 128 | 101 129 | 102 130 | 103 131 | 104 132 | 105 133 | 106 134 | 107 135 | 108 136 | 109 137 | 110 138 | 111 139 | 112 140 | 113 141 | 114 142 | 115 143 | 116 144 | 117 145 | 118 146 | 119 147 | 120 148 | 121 149 | 122 150 | 123 151 | 124 152 | 125 153 | 126 154 | 127 155 | 128 156 | 129 157 | 130 158 | 131 159 | 132 160 | 133 161 | 134 162 | 135 163 | 136 164 | 137 165 | 138 166 | 139 167 | 140 168 | 141 169 | 142 170 | 143 171 | 144 172 | 145 173 | 146 174 | 147 175 | 148 176 | 149 177 | 150 178 | 151 179 | 152 180 | 153 181 | 1541 182 |   183 |   184 |   185 | 1 186 | 3 187 |   188 |   189 | 1 190 | 37 191 | 32 192 |   193 | 30 194 | 30 195 |   196 | 30 197 | 15 198 | 15 199 |   200 |   201 | 30 202 |   203 |   204 |   205 | 1 206 |   207 | 1 208 |   209 | 1 210 |   211 | 1 212 |   213 |   214 |   215 |   216 | 1 217 |   218 |   219 |   220 |   221 | 1 222 |   223 | 1 224 | 3 225 |   226 |   227 | 1 228 | 3 229 |   230 |   231 | 1 232 |   233 |   234 |   235 | 1 236 | 2 237 |   238 |   239 | 1 240 | 2 241 | 1 242 |   243 |   244 | 2 245 |   246 |   247 |   248 | 2 249 |   250 | 2 251 |   252 | 2 253 | 1 254 | 1 255 |   256 |   257 | 2 258 |   259 | 2 260 |   261 |   262 | 1 263 | 1 264 |   265 |   266 | 1 267 |   268 |   269 |   270 | 1 271 | 3 272 |   273 |   274 | 1 275 | 1 276 |   277 | 1 278 |   279 |   280 |   281 |   282 | 1 283 |   284 |   285 | 1 286 | 1 287 |   288 | 1 289 |   290 |   291 |   292 |   293 | 1 294 |   295 |   296 | 1 297 | 4 298 |   299 | 3 300 |   301 |   302 | 3 303 |   304 | 3 305 | 2 306 | 2 307 |   308 |   309 | 3 310 | 3 311 |   312 | 3 313 |   314 |   315 | 1 316 | 1 317 |   318 | 1 319 |   320 |   321 |   322 |   323 | 1 324 |   325 |   326 | 1 327 | 1 328 |   329 | 1 330 | 1 331 |   332 |   333 | 1 334 |  
var JSONProtocol = require('./JSONProtocol.js'),
335 | 	its = require('its'),
336 | 	Emitter = require('events').EventEmitter;
337 |  
338 | function notImplemented(){
339 | 	throw new Error('This method is not implemented');
340 | }
341 |  
342 | function Connection(address, peers, options){
343 | 	its.string(address);
344 | 	its.defined(peers);
345 |  
346 | 	this.address = address;
347 | 	this.peers = peers;
348 |  
349 | 	if(options){
350 | 		Eif(options.emitter) this.emitter = options.emitter;
351 | 		Iif(options.firewall) this.acceptRTCConnection = options.firewall;
352 | 	}
353 |  
354 | 	if(!this.emitter) this.emitter = new Connection.Emitter();
355 | }
356 |  
357 | // Circular dependency solved in WebRTCConnection.js
358 | Connection.createWebRTCConnection = null;
359 |  
360 | Connection.Emitter = Emitter;
361 |  
362 | Connection.prototype = Object.create(JSONProtocol.prototype);
363 |  
364 | Connection.prototype.on = function(){
365 | 	this.emitter.on.apply(this.emitter, arguments);
366 | 	return this;
367 | };
368 |  
369 | Connection.prototype.removeListener = function(){
370 | 	this.emitter.removeListener.apply(this.emitter, arguments);
371 | 	return this;
372 | };
373 |  
374 | Connection.prototype.send = JSONProtocol.prototype.writeMessage;
375 |  
376 | Connection.prototype.getPeer = function(address){
377 | 	return this.peers.get(address);
378 | };
379 |  
380 | Connection.prototype.addPeer = function(peer){
381 | 	return this.peers.add(peer);
382 | };
383 |  
384 | Connection.prototype.getPeers = function() {
385 | 	return this.peers.get();
386 | };
387 |  
388 | function isString(candidate){
389 | 	return Object.prototype.toString.call(candidate) === '[object String]';
390 | }
391 |  
392 | Connection.prototype.connect = function(config){
393 | 	if(isString(config)){
394 | 		config = {address: config};
395 | 	}
396 |  
397 | 	var self = this,
398 | 		firewall = config.firewall || this.firewall,
399 | 		peer = Connection.createWebRTCConnection(config, this.peers, this, {firewall: firewall});
400 |  
401 | 	peer.writeOffer(config);
402 |  
403 | 	this.peers.add(peer);
404 |  
405 | 	peer.on('close', function(){
406 | 		self.peers.remove(peer);
407 | 		self.emitter.emit('disconnection', peer);
408 | 	});
409 |  
410 | 	this.emitter.emit('connection', peer);
411 |  
412 | 	return peer;
413 | };
414 |  
415 | Connection.prototype.readMessage = function(message){
416 | 	this.emitter.emit('message', message);
417 | };
418 |  
419 | Connection.prototype.readArrayBuffer = function(message){
420 | 	this.emitter.emit('arraybuffer', message);
421 | };
422 |  
423 | Connection.prototype.acceptRTCConnection = function(/*description, data*/){
424 | 	return true;
425 | };
426 |  
427 | Connection.prototype.readRelay = function(peerAddress, message){
428 | 	var peer = this.getPeer(peerAddress);
429 |  
430 | 	Iif(!peer){
431 | 		this.emitter.emit('error', new Error('Unknown peer at address: ' + peerAddress));
432 | 		return;
433 | 	}
434 |  
435 | 	peer.writeRelayedMessage(this.address, message);
436 | };
437 |  
438 | Connection.prototype.readRelayedIceCandidate = function(peerAddress, candidate){
439 | 	var peer = this.getPeer(peerAddress);
440 |  
441 | 	Iif(!peer){
442 | 		this.emitter.emit('error', new Error('Unknown peer at address: ' + peerAddress));
443 | 		return;
444 | 	}
445 |  
446 | 	peer.readIceCandidate(candidate);
447 | };
448 |  
449 | Connection.prototype.readRelayedOffer = function(peerAddress, description, data){
450 | 	if(!this.acceptRTCConnection(description, data)) return false;
451 |  
452 | 	var self = this,
453 | 		peer = Connection.createWebRTCConnection({address:peerAddress}, this.peers, this, {firewall: this.firewall});
454 |  
455 | 	this.addPeer(peer);
456 |  
457 | 	peer.on('close', function(){
458 | 		self.peers.remove(peer);
459 | 		self.emitter.emit('disconnection', peer);
460 | 	});
461 |  
462 | 	peer.readOffer(description);
463 | 	peer.writeAnswer();
464 |  
465 | 	this.emitter.emit('connection', peer);
466 | };
467 |  
468 | Connection.prototype.readRelayedAnswer = function(peerAddress, description){
469 | 	var peer = this.getPeer(peerAddress);
470 |  
471 | 	Iif(!peer){
472 | 		this.emitter.emit('error', new Error('Unknown peer at address: ' + peerAddress));
473 | 		return;
474 | 	}
475 |  
476 | 	peer.readAnswer(description);
477 | };
478 |  
479 | Connection.prototype.close = notImplemented; // implemented higher up
480 | Connection.prototype.getReadyState = notImplemented; // implemented higher up
481 |  
482 | Connection.prototype.isOpen = function(){
483 | 	return this.getReadyState() === 'open';
484 | };
485 |  
486 | module.exports = Connection;
487 |  
489 | 490 |
491 | 494 | 495 | 502 | 503 | 504 | 505 | -------------------------------------------------------------------------------- /coverage/lcov-report/lib/ConnectionManager.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for lib/ConnectionManager.js 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |

Code coverage report for lib/ConnectionManager.js

17 |

18 | Statements: 100% (34 / 34)      19 | Branches: 100% (8 / 8)      20 | Functions: 100% (5 / 5)      21 | Lines: 100% (31 / 31)      22 | Ignored: none      23 |

24 |
All files » lib/ » ConnectionManager.js
25 |
26 |
27 |

 28 | 
179 | 
1 29 | 2 30 | 3 31 | 4 32 | 5 33 | 6 34 | 7 35 | 8 36 | 9 37 | 10 38 | 11 39 | 12 40 | 13 41 | 14 42 | 15 43 | 16 44 | 17 45 | 18 46 | 19 47 | 20 48 | 21 49 | 22 50 | 23 51 | 24 52 | 25 53 | 26 54 | 27 55 | 28 56 | 29 57 | 30 58 | 31 59 | 32 60 | 33 61 | 34 62 | 35 63 | 36 64 | 37 65 | 38 66 | 39 67 | 40 68 | 41 69 | 42 70 | 43 71 | 44 72 | 45 73 | 46 74 | 47 75 | 48 76 | 49 77 | 50 78 | 511 79 |   80 | 1 81 |   82 | 1 83 | 10 84 | 10 85 |   86 |   87 | 1 88 | 10 89 |   90 | 1 91 |   92 |   93 | 1 94 | 11 95 |   96 | 11 97 | 11 98 |   99 | 10 100 |   101 | 8 102 | 8 103 |   104 | 8 105 | 8 106 |   107 | 1 108 |   109 | 1 110 | 4 111 |   112 | 4 113 | 4 114 |   115 | 4 116 | 4 117 |   118 | 2 119 |   120 | 2 121 | 2 122 |   123 | 2 124 | 2 125 |   126 | 1 127 |   128 | 1
var its = require('its');
129 |  
130 | function noop(){}
131 |  
132 | function ConnectionManager(){
133 | 	this.connectionMap = {};
134 | 	this.connectionList = [];
135 | }
136 |  
137 | ConnectionManager.prototype.get = function(address){
138 | 	if(address === undefined) return this.connectionList.slice();
139 |  
140 | 	return this.connectionMap[address];
141 | };
142 |  
143 | ConnectionManager.prototype.add = function(connection) {
144 | 	its.defined(connection);
145 |  
146 | 	var address = connection.address;
147 | 	its.string(address);
148 |  
149 | 	if(address in this.connectionMap) return false;
150 | 	
151 | 	this.connectionMap[address] = connection;
152 | 	this.connectionList.push(connection);
153 |  
154 | 	this.onAdd(connection);
155 | 	return true;
156 | };
157 | ConnectionManager.prototype.onAdd = noop;
158 |  
159 | ConnectionManager.prototype.remove = function(connection){
160 | 	its.defined(connection);
161 |  
162 | 	var address = connection.address;
163 | 	its.string(address);
164 |  
165 | 	var mappedConnection = this.connectionMap[address];
166 | 	if(!mappedConnection || mappedConnection !== connection) return false;
167 |  
168 | 	delete this.connectionMap[address];
169 | 	
170 | 	var index = this.connectionList.indexOf(connection);
171 | 	this.connectionList.splice(index, 1);
172 |  
173 | 	this.onRemove(connection);
174 | 	return true;
175 | };
176 | ConnectionManager.prototype.onRemove = noop;
177 |  
178 | module.exports = ConnectionManager;
180 | 181 |
182 | 185 | 186 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /coverage/lcov-report/lib/JSONProtocol.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for lib/JSONProtocol.js 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |

Code coverage report for lib/JSONProtocol.js

17 |

18 | Statements: 89.29% (50 / 56)      19 | Branches: 91.67% (11 / 12)      20 | Functions: 61.54% (8 / 13)      21 | Lines: 89.29% (50 / 56)      22 | Ignored: none      23 |

24 |
All files » lib/ » JSONProtocol.js
25 |
26 |
27 |

 28 | 
485 | 
1 29 | 2 30 | 3 31 | 4 32 | 5 33 | 6 34 | 7 35 | 8 36 | 9 37 | 10 38 | 11 39 | 12 40 | 13 41 | 14 42 | 15 43 | 16 44 | 17 45 | 18 46 | 19 47 | 20 48 | 21 49 | 22 50 | 23 51 | 24 52 | 25 53 | 26 54 | 27 55 | 28 56 | 29 57 | 30 58 | 31 59 | 32 60 | 33 61 | 34 62 | 35 63 | 36 64 | 37 65 | 38 66 | 39 67 | 40 68 | 41 69 | 42 70 | 43 71 | 44 72 | 45 73 | 46 74 | 47 75 | 48 76 | 49 77 | 50 78 | 51 79 | 52 80 | 53 81 | 54 82 | 55 83 | 56 84 | 57 85 | 58 86 | 59 87 | 60 88 | 61 89 | 62 90 | 63 91 | 64 92 | 65 93 | 66 94 | 67 95 | 68 96 | 69 97 | 70 98 | 71 99 | 72 100 | 73 101 | 74 102 | 75 103 | 76 104 | 77 105 | 78 106 | 79 107 | 80 108 | 81 109 | 82 110 | 83 111 | 84 112 | 85 113 | 86 114 | 87 115 | 88 116 | 89 117 | 90 118 | 91 119 | 92 120 | 93 121 | 94 122 | 95 123 | 96 124 | 97 125 | 98 126 | 99 127 | 100 128 | 101 129 | 102 130 | 103 131 | 104 132 | 105 133 | 106 134 | 107 135 | 108 136 | 109 137 | 110 138 | 111 139 | 112 140 | 113 141 | 114 142 | 115 143 | 116 144 | 117 145 | 118 146 | 119 147 | 120 148 | 121 149 | 122 150 | 123 151 | 124 152 | 125 153 | 126 154 | 127 155 | 128 156 | 129 157 | 130 158 | 131 159 | 132 160 | 133 161 | 134 162 | 135 163 | 136 164 | 137 165 | 138 166 | 139 167 | 140 168 | 141 169 | 142 170 | 143 171 | 144 172 | 145 173 | 146 174 | 147 175 | 148 176 | 149 177 | 150 178 | 151 179 | 152 180 | 1531 181 | 7 182 |   183 |   184 | 1 185 |   186 | 1 187 |   188 | 1 189 |   190 |   191 |   192 |   193 |   194 |   195 |   196 |   197 |   198 |   199 | 1 200 | 2 201 | 1 202 |   203 | 1 204 |   205 |   206 |   207 | 1 208 | 7 209 |   210 |   211 | 7 212 |   213 |   214 | 1 215 | 1 216 |   217 |   218 |   219 |   220 |   221 |   222 | 3 223 | 3 224 |   225 |   226 |   227 |   228 |   229 |   230 |   231 | 1 232 | 1 233 |   234 |   235 | 2 236 |   237 |   238 |   239 | 1 240 | 3 241 |   242 |   243 | 3 244 |   245 |   246 | 1 247 | 1 248 |   249 |   250 |   251 | 1 252 | 1 253 |   254 |   255 |   256 | 1 257 | 1 258 |   259 |   260 |   261 |   262 |   263 |   264 | 1 265 | 1 266 | 1 267 |   268 | 1 269 | 1 270 | 1 271 |   272 | 1 273 |   274 | 1 275 | 1 276 | 1 277 |   278 |   279 | 1 280 | 2 281 | 1 282 |   283 | 1 284 |   285 |   286 |   287 | 1 288 | 1 289 |   290 |   291 |   292 |   293 |   294 | 1 295 |   296 |   297 |   298 |   299 |   300 |   301 |   302 | 1 303 |   304 |   305 |   306 |   307 |   308 |   309 |   310 | 1 311 |   312 |   313 |   314 |   315 |   316 |   317 | 1 318 |   319 |   320 |   321 |   322 |   323 |   324 | 1 325 |   326 |   327 |   328 |   329 |   330 |   331 |   332 | 1
function notImplemented(){
333 | 	throw new Error('This method is not implemented');
334 | }
335 |  
336 | function JSONProtocol(){}
337 |  
338 | JSONProtocol.prototype.PROTOCOL_NAME = 'p';
339 |  
340 | JSONProtocol.prototype.MESSAGE_TYPE = {
341 | 	DIRECT: 0, // [0, message]
342 |  
343 | 	RTC_OFFER: 3, // [3, description, data]
344 | 	RTC_ANSWER: 4, // [4, description]
345 | 	RTC_ICE_CANDIDATE: 5, // [5, candidate]
346 |  
347 | 	RELAY: 6, // [6, address, message]
348 | 	RELAYED: 7 // [7, address, message]
349 | };
350 |  
351 | JSONProtocol.prototype.readRaw = function(message){
352 | 	if(message instanceof ArrayBuffer){
353 | 		this.readArrayBuffer(message);
354 | 	} else {
355 | 		this.readProtocolMessage(JSON.parse(message));
356 | 	}	
357 | };
358 |  
359 | JSONProtocol.prototype.readProtocolMessage = function(message){
360 | 	var MESSAGE_TYPE = this.MESSAGE_TYPE,
361 | 		messageType = message[0];
362 | 	
363 | 	switch(messageType){
364 | 		// This is a message from the remote node to this one.
365 | 		case MESSAGE_TYPE.DIRECT:
366 | 			this.readMessage(message[1]);
367 | 			break;
368 |  
369 | 		// The message was relayed by the peer on behalf of
370 | 		// a third party peer, identified by "thirdPartyPeerId".
371 | 		// This means that the peer is acting as a signalling
372 | 		// channel on behalf of the third party peer.
373 | 		case MESSAGE_TYPE.RELAYED:
374 | 			this.readRelayedMessage(message[1], message[2]);
375 | 			break;
376 |  
377 | 		// The message is intended for another peer, identified
378 | 		// by "peerId", which is also connected to this node.
379 | 		// This means that the peer is using this connection
380 | 		// as a signalling channel in order to establish a connection
381 | 		// to the other peer identified "peerId".
382 | 		case MESSAGE_TYPE.RELAY:
383 | 			this.readRelay(message[1], message[2]);
384 | 			break;
385 |  
386 | 		default:
387 | 			throw new Error('Unknown message type: ' + messageType);
388 | 	}
389 | };
390 |  
391 | JSONProtocol.prototype.readRelayedMessage = function(origin, message){
392 | 	var MESSAGE_TYPE = this.MESSAGE_TYPE,
393 | 		messageType = message[0];
394 |  
395 | 	switch(messageType){
396 | 		// An initial connection request from a third party peer
397 | 		case MESSAGE_TYPE.RTC_OFFER:
398 | 			this.readRelayedOffer(origin, message[1], message[2]);
399 | 			break;
400 | 		
401 | 		// An answer to an RTC offer sent from this node
402 | 		case MESSAGE_TYPE.RTC_ANSWER:
403 | 			this.readRelayedAnswer(origin, message[1]);
404 | 			break;
405 | 		
406 | 		// An ICE candidate from the source node
407 | 		case MESSAGE_TYPE.RTC_ICE_CANDIDATE:
408 | 			this.readRelayedIceCandidate(origin, message[1]);
409 | 			break;
410 |  
411 | 		default:
412 | 			throw new Error('Unknown message type: ' + messageType);
413 | 	}		
414 | };
415 |  
416 | JSONProtocol.prototype.readMessage = notImplemented;
417 | JSONProtocol.prototype.readArrayBuffer = notImplemented;
418 | JSONProtocol.prototype.readRelay = notImplemented;
419 |  
420 | JSONProtocol.prototype.readRelayedOffer = notImplemented;
421 | JSONProtocol.prototype.readRelayedAnswer = notImplemented;
422 | JSONProtocol.prototype.readRelayedIceCandidate = notImplemented;
423 |  
424 | JSONProtocol.prototype.writeRaw = notImplemented;
425 |  
426 | JSONProtocol.prototype.writeProtocolMessage = function(message){
427 | 	var serializedMessage = JSON.stringify(message);
428 | 	this.writeRaw(serializedMessage);
429 | };
430 |  
431 | JSONProtocol.prototype.writeMessage = function(message){
432 | 	if(message instanceof ArrayBuffer){
433 | 		this.writeRaw(message);
434 | 	} else {
435 | 		this.writeStringMessage(message);
436 | 	}
437 | };
438 |  
439 | JSONProtocol.prototype.writeStringMessage = function(message){
440 | 	this.writeProtocolMessage([
441 | 		this.MESSAGE_TYPE.DIRECT,
442 | 		message
443 | 	]);
444 | };
445 |  
446 | JSONProtocol.prototype.writeRelayedMessage = function(origin, message){
447 | 	this.writeProtocolMessage([
448 | 		this.MESSAGE_TYPE.RELAYED,
449 | 		origin,
450 | 		message
451 | 	]);
452 | };
453 |  
454 | JSONProtocol.prototype.writeRelayMessage = function(destination, message){
455 | 	this.writeProtocolMessage([
456 | 		this.MESSAGE_TYPE.RELAY,
457 | 		destination,
458 | 		message
459 | 	]);
460 | };
461 |  
462 | JSONProtocol.prototype.writeRelayAnswer = function(destination, description){
463 | 	this.writeRelayMessage(destination, [
464 | 		this.MESSAGE_TYPE.RTC_ANSWER,
465 | 		description
466 | 	]);
467 | };
468 |  
469 | JSONProtocol.prototype.writeRelayIceCandidate = function(destination, candidate){
470 | 	this.writeRelayMessage(destination, [
471 | 		this.MESSAGE_TYPE.RTC_ICE_CANDIDATE,
472 | 		candidate
473 | 	]);
474 | };
475 |  
476 | JSONProtocol.prototype.writeRelayOffer = function(destination, description, data){
477 | 	this.writeRelayMessage(destination, [
478 | 		this.MESSAGE_TYPE.RTC_OFFER,
479 | 		description,
480 | 		data
481 | 	]);
482 | };
483 |  
484 | module.exports = JSONProtocol;
486 | 487 |
488 | 491 | 492 | 499 | 500 | 501 | 502 | -------------------------------------------------------------------------------- /coverage/lcov-report/lib/P.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for lib/P.js 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |

Code coverage report for lib/P.js

17 |

18 | Statements: 96.88% (31 / 32)      19 | Branches: 50% (2 / 4)      20 | Functions: 100% (9 / 9)      21 | Lines: 100% (31 / 31)      22 | Ignored: none      23 |

24 |
All files » lib/ » P.js
25 |
26 |
27 |

 28 | 
215 | 
1 29 | 2 30 | 3 31 | 4 32 | 5 33 | 6 34 | 7 35 | 8 36 | 9 37 | 10 38 | 11 39 | 12 40 | 13 41 | 14 42 | 15 43 | 16 44 | 17 45 | 18 46 | 19 47 | 20 48 | 21 49 | 22 50 | 23 51 | 24 52 | 25 53 | 26 54 | 27 55 | 28 56 | 29 57 | 30 58 | 31 59 | 32 60 | 33 61 | 34 62 | 35 63 | 36 64 | 37 65 | 38 66 | 39 67 | 40 68 | 41 69 | 42 70 | 43 71 | 44 72 | 45 73 | 46 74 | 47 75 | 48 76 | 49 77 | 50 78 | 51 79 | 52 80 | 53 81 | 54 82 | 55 83 | 56 84 | 57 85 | 58 86 | 59 87 | 60 88 | 61 89 | 62 90 | 631 91 |   92 |   93 |   94 |   95 | 1 96 |   97 | 1 98 | 10 99 | 9 100 |   101 | 9 102 | 9 103 |   104 | 9 105 | 1 106 |   107 |   108 | 9 109 | 1 110 |   111 |   112 | 9 113 |   114 |   115 | 1 116 | 2 117 |   118 |   119 | 2 120 |   121 |   122 | 1 123 | 1 124 |   125 |   126 | 1 127 | 2 128 |   129 | 2 130 |   131 |   132 | 2 133 |   134 | 2 135 | 1 136 |   137 |   138 | 2 139 |   140 |   141 | 1 142 | 2 143 | 2 144 |   145 |   146 | 1 147 | 1 148 | 1 149 |   150 |   151 | 1 152 |  
var Emitter = require('events').EventEmitter,
153 | 	ConnectionManager = require('./ConnectionManager.js'),
154 | 	WebSocketConnection = require('./WebSocketConnection.js'),
155 | 	its = require('its');
156 |  
157 | require('./WebRtcConnection');
158 |  
159 | function P(emitter, connectionManager, options){
160 | 	its.defined(emitter);
161 | 	its.defined(connectionManager);
162 |  
163 | 	this.emitter = emitter;
164 | 	this.peers = connectionManager;
165 |  
166 | 	this.peers.onAdd = function(peer){
167 | 		emitter.emit('connection', peer);
168 | 	};
169 |  
170 | 	this.peers.onRemove = function(peer){
171 | 		emitter.emit('disconnection', peer);
172 | 	};
173 |  
174 | 	Iif(options && options.firewall) this.firewall = options.firewall;
175 | }
176 |  
177 | P.create = function(options){
178 | 	var emitter = new Emitter(),
179 | 		connectionManager = new ConnectionManager();
180 |  
181 | 	return new P(emitter, connectionManager, options);
182 | };
183 |  
184 | P.prototype.getPeers = function(){
185 | 	return this.peers.get();
186 | };
187 |  
188 | P.prototype.connect = function(address){
189 | 	its.string(address);
190 |  
191 | 	var peers = this.peers,
192 | 		peer = WebSocketConnection.create(address, this.peers, {firewall: this.firewall});
193 |  
194 | 	peers.add(peer);
195 |  
196 | 	peer.on('close', function(){
197 | 		peers.remove(peer);
198 | 	});
199 |  
200 | 	return peer;
201 | };
202 |  
203 | P.prototype.on = function(){
204 | 	this.emitter.on.apply(this.emitter, arguments);
205 | 	return this;
206 | };
207 |  
208 | P.prototype.removeListener = function(){
209 | 	this.emitter.removeListener.apply(this.emitter, arguments);
210 | 	return this;
211 | };
212 |  
213 | module.exports = P;
214 |  
216 | 217 |
218 | 221 | 222 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /coverage/lcov-report/lib/WebRtcConnection.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for lib/WebRtcConnection.js 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |

Code coverage report for lib/WebRtcConnection.js

17 |

18 | Statements: 25.88% (22 / 85)      19 | Branches: 32.14% (9 / 28)      20 | Functions: 0% (0 / 25)      21 | Lines: 26.83% (22 / 82)      22 | Ignored: none      23 |

24 |
All files » lib/ » WebRtcConnection.js
25 |
26 |
27 |

 28 | 
572 | 
1 29 | 2 30 | 3 31 | 4 32 | 5 33 | 6 34 | 7 35 | 8 36 | 9 37 | 10 38 | 11 39 | 12 40 | 13 41 | 14 42 | 15 43 | 16 44 | 17 45 | 18 46 | 19 47 | 20 48 | 21 49 | 22 50 | 23 51 | 24 52 | 25 53 | 26 54 | 27 55 | 28 56 | 29 57 | 30 58 | 31 59 | 32 60 | 33 61 | 34 62 | 35 63 | 36 64 | 37 65 | 38 66 | 39 67 | 40 68 | 41 69 | 42 70 | 43 71 | 44 72 | 45 73 | 46 74 | 47 75 | 48 76 | 49 77 | 50 78 | 51 79 | 52 80 | 53 81 | 54 82 | 55 83 | 56 84 | 57 85 | 58 86 | 59 87 | 60 88 | 61 89 | 62 90 | 63 91 | 64 92 | 65 93 | 66 94 | 67 95 | 68 96 | 69 97 | 70 98 | 71 99 | 72 100 | 73 101 | 74 102 | 75 103 | 76 104 | 77 105 | 78 106 | 79 107 | 80 108 | 81 109 | 82 110 | 83 111 | 84 112 | 85 113 | 86 114 | 87 115 | 88 116 | 89 117 | 90 118 | 91 119 | 92 120 | 93 121 | 94 122 | 95 123 | 96 124 | 97 125 | 98 126 | 99 127 | 100 128 | 101 129 | 102 130 | 103 131 | 104 132 | 105 133 | 106 134 | 107 135 | 108 136 | 109 137 | 110 138 | 111 139 | 112 140 | 113 141 | 114 142 | 115 143 | 116 144 | 117 145 | 118 146 | 119 147 | 120 148 | 121 149 | 122 150 | 123 151 | 124 152 | 125 153 | 126 154 | 127 155 | 128 156 | 129 157 | 130 158 | 131 159 | 132 160 | 133 161 | 134 162 | 135 163 | 136 164 | 137 165 | 138 166 | 139 167 | 140 168 | 141 169 | 142 170 | 143 171 | 144 172 | 145 173 | 146 174 | 147 175 | 148 176 | 149 177 | 150 178 | 151 179 | 152 180 | 153 181 | 154 182 | 155 183 | 156 184 | 157 185 | 158 186 | 159 187 | 160 188 | 161 189 | 162 190 | 163 191 | 164 192 | 165 193 | 166 194 | 167 195 | 168 196 | 169 197 | 170 198 | 171 199 | 172 200 | 173 201 | 174 202 | 175 203 | 176 204 | 177 205 | 178 206 | 179 207 | 180 208 | 181 209 | 182  210 |   211 |   212 |   213 |   214 |   215 |   216 |   217 |   218 |   219 | 1 220 |   221 |   222 | 1 223 |   224 |   225 |   226 |   227 | 1 228 |   229 |   230 | 1 231 |   232 |   233 |   234 | 1 235 |   236 | 1 237 |   238 |   239 |   240 |   241 |   242 | 1 243 |   244 |   245 |   246 |   247 |   248 |   249 |   250 |   251 |   252 |   253 |   254 |   255 |   256 |   257 |   258 |   259 |   260 |   261 |   262 |   263 |   264 |   265 |   266 |   267 |   268 |   269 |   270 |   271 |   272 |   273 |   274 |   275 |   276 |   277 |   278 |   279 |   280 |   281 |   282 |   283 |   284 |   285 |   286 |   287 |   288 |   289 |   290 |   291 |   292 |   293 |   294 |   295 |   296 |   297 |   298 |   299 |   300 |   301 |   302 |   303 |   304 |   305 |   306 | 1 307 | 1 308 |   309 |   310 |   311 |   312 |   313 |   314 | 1 315 |   316 |   317 |   318 |   319 |   320 |   321 | 1 322 |   323 | 1 324 |   325 |   326 |   327 |   328 |   329 |   330 |   331 |   332 |   333 |   334 |   335 |   336 |   337 | 1 338 |   339 |   340 |   341 |   342 |   343 | 1 344 |   345 |   346 |   347 |   348 | 1 349 |   350 |   351 |   352 | 1 353 |   354 |   355 |   356 |   357 |   358 | 1 359 |   360 |   361 |   362 |   363 |   364 |   365 |   366 |   367 | 1 368 |   369 |   370 |   371 |   372 |   373 | 1 374 |   375 |   376 |   377 |   378 |   379 |   380 |   381 |   382 | 1 383 |   384 |   385 |   386 |   387 | 1 388 |   389 | 1 390 |  
/*global
391 | 		RTCPeerConnection,
392 | 		webkitRTCPeerConnection,
393 | 		mozRTCPeerConnection,
394 | 		RTCSessionDescription,
395 | 		mozRTCSessionDescription,
396 | 		RTCIceCandidate,
397 | 		mozRTCIceCandidate
398 | */
399 |  
400 | var Connection = require('./Connection.js'),
401 | 	its = require('its');
402 |  
403 | var nativeRTCPeerConnection = (typeof RTCPeerConnection !== 'undefined')? RTCPeerConnection :
404 | 							  (typeof webkitRTCPeerConnection !== 'undefined')? webkitRTCPeerConnection :
405 | 							  (typeof mozRTCPeerConnection !== 'undefined')? mozRTCPeerConnection :
406 | 							  undefined;
407 |  
408 | var nativeRTCSessionDescription = (typeof RTCSessionDescription !== 'undefined')? RTCSessionDescription :
409 | 								  (typeof mozRTCSessionDescription !== 'undefined')? mozRTCSessionDescription :
410 | 								  undefined;
411 | var nativeRTCIceCandidate = (typeof RTCIceCandidate !== 'undefined')? RTCIceCandidate :
412 | 							(typeof mozRTCIceCandidate !== 'undefined')? mozRTCIceCandidate :
413 | 							undefined;
414 |  
415 | var log = function(){};
416 |  
417 | Iif(typeof window !== 'undefined' && window.P_DEBUGGING_ENABLED){
418 | 	log = function(label, event, obj){
419 | 		window.console.debug(label, event, obj);
420 | 	};
421 | }
422 |  
423 | function WebRTCConnection(address, peers, rtcConnection, signalingChannel, options){
424 | 	var self = this;
425 |  
426 | 	its.string(address);
427 | 	its.defined(peers);
428 | 	its.defined(rtcConnection);
429 | 	its.defined(signalingChannel);
430 |  
431 | 	Connection.call(this, address, peers, options);
432 |  
433 | 	this.signalingChannel = signalingChannel;
434 | 	this.rtcConnection = rtcConnection;
435 | 	this.rtcDataChannel = rtcConnection.createDataChannel(this.PROTOCOL_NAME, {protocol: this.PROTOCOL_NAME});
436 |  
437 |  
438 | 	// Bug in FF seems to garbage collect the stale ref causing it to close
439 | 	// the prevents it from being lost in a GC event
440 | 	this._initialRtcDataChannel = this.rtcDataChannel;
441 |  
442 |  
443 | 	this.close = rtcConnection.close.bind(rtcConnection);
444 |  
445 | 	this.rtcConnection.addEventListener('icecandidate', function(event){
446 | 		if(!event.candidate) return;
447 | 		log('ice candidate', event, self);
448 | 		self.signalingChannel.writeRelayIceCandidate(address, event.candidate);
449 | 	});
450 |  
451 | 	this.rtcConnection.addEventListener('datachannel', function(event){
452 | 		log('datachannel', event, self);
453 |  
454 | 		var rtcDataChannel = self.rtcDataChannel = event.channel;
455 | 		rtcDataChannel.addEventListener('open', function(event){
456 | 			log('remote datachannel open', event, self);
457 | 			self.emitter.emit('open', event);
458 | 		});
459 |  
460 | 		rtcDataChannel.addEventListener('close', function(event){
461 | 			log('remote datachannel close', event, self);
462 | 			self.emitter.emit('close', event);
463 | 		});
464 |  
465 | 		rtcDataChannel.addEventListener('error', function(event){
466 | 			log('remote datachannel error', event, self);
467 | 			self.emitter.emit('error', event);
468 | 		});
469 | 	});
470 |  
471 | 	this.rtcDataChannel.addEventListener('message', function(message){
472 | 		log('local datachannel message', message, self);
473 | 		self.readRaw(message.data);
474 | 	});
475 |  
476 | 	this.rtcDataChannel.addEventListener('error', function(event){
477 | 		log('local datachannel error', event, self);
478 | 		self.emitter.emit('error', event);
479 | 	});
480 |  
481 | 	this.rtcDataChannel.addEventListener('close', function(event){
482 | 		log('local datachannel close', event, self);
483 | 		self.emitter.emit('close', event);
484 | 	});
485 | }
486 |  
487 | var DEFAULT_RTC_CONFIGURATION = null;
488 | var DEFAULT_RTC_OFFER_OPTIONS = {
489 | 	offerToReceiveAudio: false,
490 | 	offerToReceiveVideo: false,
491 | 	iceRestart: false
492 | };
493 |  
494 | //DEFAULT_RTC_OFFER_OPTIONS
495 | WebRTCConnection.create = function(config, peers, signalingChannel, options){
496 | 	var rtcConfiguration = config.rtcConfiguration || DEFAULT_RTC_CONFIGURATION,
497 | 		rtcConnection = new nativeRTCPeerConnection(rtcConfiguration);
498 |  
499 | 	return new WebRTCConnection(config.address, peers, rtcConnection, signalingChannel, options);
500 | };
501 |  
502 | WebRTCConnection.prototype = Object.create(Connection.prototype);
503 |  
504 | WebRTCConnection.prototype.writeRaw = function(message){
505 | 	switch(this.rtcDataChannel.readyState){
506 | 		case 'connecting':
507 | 			throw new Error('Can\'t send a message while RTCDataChannel connecting');
508 | 		case 'open':
509 | 			this.rtcDataChannel.send(message);
510 | 			log('sent message to remote', message, this);
511 | 			break;
512 | 		case 'closing':
513 | 		case 'closed':
514 | 			throw new Error('Can\'t send a message while RTCDataChannel is closing or closed');
515 | 	}
516 | };
517 |  
518 | WebRTCConnection.prototype.readAnswer = function(description){
519 | 	var rtcSessionDescription = new nativeRTCSessionDescription(description);
520 |  
521 | 	this.rtcConnection.setRemoteDescription(rtcSessionDescription);
522 | };
523 |  
524 | WebRTCConnection.prototype.readOffer = function(description){
525 | 	var rtcSessionDescription = new nativeRTCSessionDescription(description);
526 | 	this.rtcConnection.setRemoteDescription(rtcSessionDescription);
527 | };
528 |  
529 | WebRTCConnection.prototype.readIceCandidate = function(candidate){
530 | 	this.rtcConnection.addIceCandidate(new nativeRTCIceCandidate(candidate));
531 | };
532 |  
533 | WebRTCConnection.prototype.writeAnswer = function(){
534 | 	var emitter = this.emitter,
535 | 		address = this.address,
536 | 		rtcConnection = this.rtcConnection,
537 | 		signalingChannel = this.signalingChannel;
538 |  
539 | 	function onError(err){ emitter.emit('error', err); }
540 |  
541 | 	rtcConnection.createAnswer(function(description){
542 | 		rtcConnection.setLocalDescription(description, function(){
543 | 			signalingChannel.writeRelayAnswer(address, description);
544 | 		}, onError);
545 | 	}, onError);
546 | };
547 |  
548 | WebRTCConnection.prototype.writeOffer = function(config){
549 | 	var emitter = this.emitter,
550 | 		address = this.address,
551 | 		rtcConnection = this.rtcConnection,
552 | 		signalingChannel = this.signalingChannel;
553 |  
554 | 	function onError(err){ emitter.emit('error', err); }
555 |  
556 | 	rtcConnection.createOffer(function(description){
557 | 		rtcConnection.setLocalDescription(description, function(){
558 | 			signalingChannel.writeRelayOffer(address, description, config.offerData);
559 | 		}, onError);
560 | 	}, onError, config.rtcOfferOptions || DEFAULT_RTC_OFFER_OPTIONS);
561 | };
562 |  
563 | WebRTCConnection.prototype.getReadyState = function(){
564 | 	return this.rtcDataChannel.readyState;
565 | };
566 |  
567 | // Solves the circular dependency with Connection.js
568 | Connection.createWebRTCConnection = WebRTCConnection.create;
569 |  
570 | module.exports = WebRTCConnection;
571 |  
573 | 574 |
575 | 578 | 579 | 586 | 587 | 588 | 589 | -------------------------------------------------------------------------------- /coverage/lcov-report/lib/WebSocketConnection.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for lib/WebSocketConnection.js 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |

Code coverage report for lib/WebSocketConnection.js

17 |

18 | Statements: 79.41% (27 / 34)      19 | Branches: 40% (4 / 10)      20 | Functions: 87.5% (7 / 8)      21 | Lines: 79.41% (27 / 34)      22 | Ignored: none      23 |

24 |
All files » lib/ » WebSocketConnection.js
25 |
26 |
27 |

 28 | 
251 | 
1 29 | 2 30 | 3 31 | 4 32 | 5 33 | 6 34 | 7 35 | 8 36 | 9 37 | 10 38 | 11 39 | 12 40 | 13 41 | 14 42 | 15 43 | 16 44 | 17 45 | 18 46 | 19 47 | 20 48 | 21 49 | 22 50 | 23 51 | 24 52 | 25 53 | 26 54 | 27 55 | 28 56 | 29 57 | 30 58 | 31 59 | 32 60 | 33 61 | 34 62 | 35 63 | 36 64 | 37 65 | 38 66 | 39 67 | 40 68 | 41 69 | 42 70 | 43 71 | 44 72 | 45 73 | 46 74 | 47 75 | 48 76 | 49 77 | 50 78 | 51 79 | 52 80 | 53 81 | 54 82 | 55 83 | 56 84 | 57 85 | 58 86 | 59 87 | 60 88 | 61 89 | 62 90 | 63 91 | 64 92 | 65 93 | 66 94 | 67 95 | 68 96 | 69 97 | 70 98 | 71 99 | 72 100 | 73 101 | 74 102 | 751 103 |   104 | 1 105 |   106 |   107 |   108 |   109 |   110 |   111 | 1 112 |   113 |   114 |   115 | 1 116 | 12 117 |   118 | 12 119 |   120 | 8 121 |   122 | 8 123 |   124 | 7 125 | 1 126 |   127 |   128 | 7 129 | 1 130 |   131 |   132 | 7 133 | 1 134 |   135 |   136 | 7 137 | 1 138 |   139 |   140 |   141 | 1 142 |   143 |   144 |   145 |   146 | 1 147 | 1 148 | 2 149 |   150 |   151 |   152 |   153 | 1 154 | 1 155 |   156 |   157 |   158 | 1 159 |   160 |   161 |   162 | 1 163 | 1 164 |   165 |   166 |   167 |   168 |   169 | 1 170 |   171 |   172 |   173 |   174 |   175 | 1 176 |  
var Connection = require('./Connection.js');
177 |  
178 | var WebSocketState = {
179 | 	CONNECTING: 0,
180 | 	OPEN: 1,
181 | 	CLOSING: 2,
182 | 	CLOSED: 3
183 | };
184 |  
185 | Iif(typeof WebSocket !== 'undefined'){
186 | 	WebSocketState = WebSocket;
187 | }
188 |  
189 | function WebSocketConnection(address, peers, webSocket, options){
190 | 	var self = this;
191 |  
192 | 	Connection.call(this, address, peers, options);
193 |  
194 | 	this.webSocket = webSocket;
195 |  
196 | 	this.close = webSocket.close.bind(webSocket);
197 |  
198 | 	this.webSocket.addEventListener('message', function(message){
199 | 		self.readRaw(message.data);
200 | 	});
201 |  
202 | 	this.webSocket.addEventListener('open', function(event){
203 | 		self.emitter.emit('open', event);
204 | 	});
205 |  
206 | 	this.webSocket.addEventListener('error', function(event){
207 | 		self.emitter.emit('error', event);
208 | 	});
209 |  
210 | 	this.webSocket.addEventListener('close', function(event){
211 | 		self.emitter.emit('close', event);
212 | 	});
213 | }
214 |  
215 | WebSocketConnection.create = function(address, peers, options){
216 | 	var webSocket = new WebSocket(address, WebSocketConnection.prototype.PROTOCOL_NAME);
217 | 	return new WebSocketConnection(address, peers, webSocket, options);
218 | };
219 |  
220 | WebSocketConnection.prototype = Object.create(Connection.prototype);
221 | WebSocketConnection.prototype.writeRaw = function(message){
222 | 	switch(this.webSocket.readyState){
223 | 		case WebSocketState.CONNECTING:
224 | 			throw new Error('Can\'t send a message while WebSocket connecting');
225 |  
226 | 		case WebSocketState.OPEN:
227 | 			this.webSocket.send(message);
228 | 			break;
229 |  
230 | 		case WebSocketState.CLOSING:
231 | 		case WebSocketState.CLOSED:
232 | 			throw new Error('Can\'t send a message while WebSocket is closing or closed');
233 | 	}
234 | };
235 |  
236 | WebSocketConnection.prototype.getReadyState = function(){
237 | 	switch(this.webSocket.readyState){
238 | 		case WebSocketState.CONNECTING:
239 | 			return 'connecting';
240 | 		case WebSocketState.OPEN:
241 | 			return 'open';
242 | 		case WebSocketState.CLOSING:
243 | 			return 'closing';
244 | 		case WebSocketState.CLOSED:
245 | 			return 'closed';
246 | 	}
247 | };
248 |  
249 | module.exports = WebSocketConnection;
250 |  
252 | 253 |
254 | 257 | 258 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /coverage/lcov-report/lib/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for lib/ 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |

Code coverage report for lib/

17 |

18 | Statements: 72.39% (236 / 326)      19 | Branches: 59.76% (49 / 82)      20 | Functions: 55.7% (44 / 79)      21 | Lines: 73.02% (230 / 315)      22 | Ignored: none      23 |

24 |
All files » lib/
25 |
26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 |
FileStatementsBranchesFunctionsLines
Connection.js84.52%(71 / 84)75%(15 / 20)78.95%(15 / 19)85%(68 / 80)
ConnectionManager.js100%(34 / 34)100%(8 / 8)100%(5 / 5)100%(31 / 31)
JSONProtocol.js89.29%(50 / 56)91.67%(11 / 12)61.54%(8 / 13)89.29%(50 / 56)
P.js96.88%(31 / 32)50%(2 / 4)100%(9 / 9)100%(31 / 31)
WebRtcConnection.js25.88%(22 / 85)32.14%(9 / 28)0%(0 / 25)26.83%(22 / 82)
WebSocketConnection.js79.41%(27 / 34)40%(4 / 10)87.5%(7 / 8)79.41%(27 / 34)
init.js100%(1 / 1)100%(0 / 0)100%(0 / 0)100%(1 / 1)
136 |
137 |
138 | 141 | 142 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /coverage/lcov-report/lib/init.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for lib/init.js 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |

Code coverage report for lib/init.js

17 |

18 | Statements: 100% (1 / 1)      19 | Branches: 100% (0 / 0)      20 | Functions: 100% (0 / 0)      21 | Lines: 100% (1 / 1)      22 | Ignored: none      23 |

24 |
All files » lib/ » init.js
25 |
26 |
27 |

28 | 
32 | 
1 29 | 21 30 |  
require('./P.js');
31 |  
33 | 34 |
35 | 38 | 39 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.js: -------------------------------------------------------------------------------- 1 | window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); 2 | -------------------------------------------------------------------------------- /coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unsetbit/p/0074afb47f6e6ffa892bc3eb45204c7c799e79db/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | var addSorting = (function () { 2 | "use strict"; 3 | var cols, 4 | currentSort = { 5 | index: 0, 6 | desc: false 7 | }; 8 | 9 | // returns the summary table element 10 | function getTable() { return document.querySelector('.coverage-summary table'); } 11 | // returns the thead element of the summary table 12 | function getTableHeader() { return getTable().querySelector('thead tr'); } 13 | // returns the tbody element of the summary table 14 | function getTableBody() { return getTable().querySelector('tbody'); } 15 | // returns the th element for nth column 16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } 17 | 18 | // loads all columns 19 | function loadColumns() { 20 | var colNodes = getTableHeader().querySelectorAll('th'), 21 | colNode, 22 | cols = [], 23 | col, 24 | i; 25 | 26 | for (i = 0; i < colNodes.length; i += 1) { 27 | colNode = colNodes[i]; 28 | col = { 29 | key: colNode.getAttribute('data-col'), 30 | sortable: !colNode.getAttribute('data-nosort'), 31 | type: colNode.getAttribute('data-type') || 'string' 32 | }; 33 | cols.push(col); 34 | if (col.sortable) { 35 | col.defaultDescSort = col.type === 'number'; 36 | colNode.innerHTML = colNode.innerHTML + ''; 37 | } 38 | } 39 | return cols; 40 | } 41 | // attaches a data attribute to every tr element with an object 42 | // of data values keyed by column name 43 | function loadRowData(tableRow) { 44 | var tableCols = tableRow.querySelectorAll('td'), 45 | colNode, 46 | col, 47 | data = {}, 48 | i, 49 | val; 50 | for (i = 0; i < tableCols.length; i += 1) { 51 | colNode = tableCols[i]; 52 | col = cols[i]; 53 | val = colNode.getAttribute('data-value'); 54 | if (col.type === 'number') { 55 | val = Number(val); 56 | } 57 | data[col.key] = val; 58 | } 59 | return data; 60 | } 61 | // loads all row data 62 | function loadData() { 63 | var rows = getTableBody().querySelectorAll('tr'), 64 | i; 65 | 66 | for (i = 0; i < rows.length; i += 1) { 67 | rows[i].data = loadRowData(rows[i]); 68 | } 69 | } 70 | // sorts the table using the data for the ith column 71 | function sortByIndex(index, desc) { 72 | var key = cols[index].key, 73 | sorter = function (a, b) { 74 | a = a.data[key]; 75 | b = b.data[key]; 76 | return a < b ? -1 : a > b ? 1 : 0; 77 | }, 78 | finalSorter = sorter, 79 | tableBody = document.querySelector('.coverage-summary tbody'), 80 | rowNodes = tableBody.querySelectorAll('tr'), 81 | rows = [], 82 | i; 83 | 84 | if (desc) { 85 | finalSorter = function (a, b) { 86 | return -1 * sorter(a, b); 87 | }; 88 | } 89 | 90 | for (i = 0; i < rowNodes.length; i += 1) { 91 | rows.push(rowNodes[i]); 92 | tableBody.removeChild(rowNodes[i]); 93 | } 94 | 95 | rows.sort(finalSorter); 96 | 97 | for (i = 0; i < rows.length; i += 1) { 98 | tableBody.appendChild(rows[i]); 99 | } 100 | } 101 | // removes sort indicators for current column being sorted 102 | function removeSortIndicators() { 103 | var col = getNthColumn(currentSort.index), 104 | cls = col.className; 105 | 106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 107 | col.className = cls; 108 | } 109 | // adds sort indicators for current column being sorted 110 | function addSortIndicators() { 111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; 112 | } 113 | // adds event listeners for all sorter widgets 114 | function enableUI() { 115 | var i, 116 | el, 117 | ithSorter = function ithSorter(i) { 118 | var col = cols[i]; 119 | 120 | return function () { 121 | var desc = col.defaultDescSort; 122 | 123 | if (currentSort.index === i) { 124 | desc = !currentSort.desc; 125 | } 126 | sortByIndex(i, desc); 127 | removeSortIndicators(); 128 | currentSort.index = i; 129 | currentSort.desc = desc; 130 | addSortIndicators(); 131 | }; 132 | }; 133 | for (i =0 ; i < cols.length; i += 1) { 134 | if (cols[i].sortable) { 135 | el = getNthColumn(i).querySelector('.sorter'); 136 | if (el.addEventListener) { 137 | el.addEventListener('click', ithSorter(i)); 138 | } else { 139 | el.attachEvent('onclick', ithSorter(i)); 140 | } 141 | } 142 | } 143 | } 144 | // adds sorting functionality to the UI 145 | return function () { 146 | if (!getTable()) { 147 | return; 148 | } 149 | cols = loadColumns(); 150 | loadData(cols); 151 | addSortIndicators(); 152 | enableUI(); 153 | }; 154 | })(); 155 | 156 | window.addEventListener('load', addSorting); 157 | -------------------------------------------------------------------------------- /coverage/lcov.info: -------------------------------------------------------------------------------- 1 | TN: 2 | SF:/Users/ozan/code/p/lib/Connection.js 3 | FN:5,notImplemented 4 | FN:9,Connection 5 | FN:31,(anonymous_3) 6 | FN:36,(anonymous_4) 7 | FN:43,(anonymous_5) 8 | FN:47,(anonymous_6) 9 | FN:51,(anonymous_7) 10 | FN:55,isString 11 | FN:59,(anonymous_9) 12 | FN:72,(anonymous_10) 13 | FN:82,(anonymous_11) 14 | FN:86,(anonymous_12) 15 | FN:90,(anonymous_13) 16 | FN:94,(anonymous_14) 17 | FN:105,(anonymous_15) 18 | FN:116,(anonymous_16) 19 | FN:124,(anonymous_17) 20 | FN:135,(anonymous_18) 21 | FN:149,(anonymous_19) 22 | FNF:19 23 | FNH:15 24 | FNDA:3,notImplemented 25 | FNDA:37,Connection 26 | FNDA:0,(anonymous_3) 27 | FNDA:0,(anonymous_4) 28 | FNDA:3,(anonymous_5) 29 | FNDA:3,(anonymous_6) 30 | FNDA:0,(anonymous_7) 31 | FNDA:2,isString 32 | FNDA:2,(anonymous_9) 33 | FNDA:1,(anonymous_10) 34 | FNDA:1,(anonymous_11) 35 | FNDA:0,(anonymous_12) 36 | FNDA:3,(anonymous_13) 37 | FNDA:1,(anonymous_14) 38 | FNDA:1,(anonymous_15) 39 | FNDA:4,(anonymous_16) 40 | FNDA:2,(anonymous_17) 41 | FNDA:1,(anonymous_18) 42 | FNDA:1,(anonymous_19) 43 | DA:1,1 44 | DA:5,1 45 | DA:6,3 46 | DA:9,1 47 | DA:10,37 48 | DA:11,32 49 | DA:13,30 50 | DA:14,30 51 | DA:16,30 52 | DA:17,15 53 | DA:18,15 54 | DA:21,30 55 | DA:25,1 56 | DA:27,1 57 | DA:29,1 58 | DA:31,1 59 | DA:32,0 60 | DA:33,0 61 | DA:36,1 62 | DA:37,0 63 | DA:38,0 64 | DA:41,1 65 | DA:43,1 66 | DA:44,3 67 | DA:47,1 68 | DA:48,3 69 | DA:51,1 70 | DA:52,0 71 | DA:55,1 72 | DA:56,2 73 | DA:59,1 74 | DA:60,2 75 | DA:61,1 76 | DA:64,2 77 | DA:68,2 78 | DA:70,2 79 | DA:72,2 80 | DA:73,1 81 | DA:74,1 82 | DA:77,2 83 | DA:79,2 84 | DA:82,1 85 | DA:83,1 86 | DA:86,1 87 | DA:87,0 88 | DA:90,1 89 | DA:91,3 90 | DA:94,1 91 | DA:95,1 92 | DA:97,1 93 | DA:98,0 94 | DA:99,0 95 | DA:102,1 96 | DA:105,1 97 | DA:106,1 98 | DA:108,1 99 | DA:109,0 100 | DA:110,0 101 | DA:113,1 102 | DA:116,1 103 | DA:117,4 104 | DA:119,3 105 | DA:122,3 106 | DA:124,3 107 | DA:125,2 108 | DA:126,2 109 | DA:129,3 110 | DA:130,3 111 | DA:132,3 112 | DA:135,1 113 | DA:136,1 114 | DA:138,1 115 | DA:139,0 116 | DA:140,0 117 | DA:143,1 118 | DA:146,1 119 | DA:147,1 120 | DA:149,1 121 | DA:150,1 122 | DA:153,1 123 | LF:80 124 | LH:68 125 | BRDA:16,1,0,15 126 | BRDA:16,1,1,15 127 | BRDA:17,2,0,15 128 | BRDA:17,2,1,0 129 | BRDA:18,3,0,0 130 | BRDA:18,3,1,15 131 | BRDA:21,4,0,15 132 | BRDA:21,4,1,15 133 | BRDA:60,5,0,1 134 | BRDA:60,5,1,1 135 | BRDA:65,6,0,2 136 | BRDA:65,6,1,2 137 | BRDA:97,7,0,0 138 | BRDA:97,7,1,1 139 | BRDA:108,8,0,0 140 | BRDA:108,8,1,1 141 | BRDA:117,9,0,1 142 | BRDA:117,9,1,3 143 | BRDA:138,10,0,0 144 | BRDA:138,10,1,1 145 | BRF:20 146 | BRH:15 147 | end_of_record 148 | TN: 149 | SF:/Users/ozan/code/p/lib/JSONProtocol.js 150 | FN:1,notImplemented 151 | FN:5,JSONProtocol 152 | FN:20,(anonymous_3) 153 | FN:28,(anonymous_4) 154 | FN:60,(anonymous_5) 155 | FN:95,(anonymous_6) 156 | FN:100,(anonymous_7) 157 | FN:108,(anonymous_8) 158 | FN:115,(anonymous_9) 159 | FN:123,(anonymous_10) 160 | FN:131,(anonymous_11) 161 | FN:138,(anonymous_12) 162 | FN:145,(anonymous_13) 163 | FNF:13 164 | FNH:8 165 | FNDA:7,notImplemented 166 | FNDA:10,JSONProtocol 167 | FNDA:2,(anonymous_3) 168 | FNDA:7,(anonymous_4) 169 | FNDA:3,(anonymous_5) 170 | FNDA:1,(anonymous_6) 171 | FNDA:2,(anonymous_7) 172 | FNDA:1,(anonymous_8) 173 | FNDA:0,(anonymous_9) 174 | FNDA:0,(anonymous_10) 175 | FNDA:0,(anonymous_11) 176 | FNDA:0,(anonymous_12) 177 | FNDA:0,(anonymous_13) 178 | DA:1,1 179 | DA:2,7 180 | DA:5,1 181 | DA:7,1 182 | DA:9,1 183 | DA:20,1 184 | DA:21,2 185 | DA:22,1 186 | DA:24,1 187 | DA:28,1 188 | DA:29,7 189 | DA:32,7 190 | DA:35,1 191 | DA:36,1 192 | DA:43,3 193 | DA:44,3 194 | DA:52,1 195 | DA:53,1 196 | DA:56,2 197 | DA:60,1 198 | DA:61,3 199 | DA:64,3 200 | DA:67,1 201 | DA:68,1 202 | DA:72,1 203 | DA:73,1 204 | DA:77,1 205 | DA:78,1 206 | DA:81,0 207 | DA:85,1 208 | DA:86,1 209 | DA:87,1 210 | DA:89,1 211 | DA:90,1 212 | DA:91,1 213 | DA:93,1 214 | DA:95,1 215 | DA:96,1 216 | DA:97,1 217 | DA:100,1 218 | DA:101,2 219 | DA:102,1 220 | DA:104,1 221 | DA:108,1 222 | DA:109,1 223 | DA:115,1 224 | DA:116,0 225 | DA:123,1 226 | DA:124,0 227 | DA:131,1 228 | DA:132,0 229 | DA:138,1 230 | DA:139,0 231 | DA:145,1 232 | DA:146,0 233 | DA:153,1 234 | LF:56 235 | LH:50 236 | BRDA:21,1,0,1 237 | BRDA:21,1,1,1 238 | BRDA:32,2,0,1 239 | BRDA:32,2,1,3 240 | BRDA:32,2,2,1 241 | BRDA:32,2,3,2 242 | BRDA:64,3,0,1 243 | BRDA:64,3,1,1 244 | BRDA:64,3,2,1 245 | BRDA:64,3,3,0 246 | BRDA:101,4,0,1 247 | BRDA:101,4,1,1 248 | BRF:12 249 | BRH:11 250 | end_of_record 251 | TN: 252 | SF:/Users/ozan/code/p/lib/ConnectionManager.js 253 | FN:3,noop 254 | FN:5,ConnectionManager 255 | FN:10,(anonymous_3) 256 | FN:16,(anonymous_4) 257 | FN:32,(anonymous_5) 258 | FNF:5 259 | FNH:5 260 | FNDA:8,noop 261 | FNDA:10,ConnectionManager 262 | FNDA:10,(anonymous_3) 263 | FNDA:11,(anonymous_4) 264 | FNDA:4,(anonymous_5) 265 | DA:1,1 266 | DA:3,1 267 | DA:5,1 268 | DA:6,10 269 | DA:7,10 270 | DA:10,1 271 | DA:11,10 272 | DA:13,1 273 | DA:16,1 274 | DA:17,11 275 | DA:19,11 276 | DA:20,11 277 | DA:22,10 278 | DA:24,8 279 | DA:25,8 280 | DA:27,8 281 | DA:28,8 282 | DA:30,1 283 | DA:32,1 284 | DA:33,4 285 | DA:35,4 286 | DA:36,4 287 | DA:38,4 288 | DA:39,4 289 | DA:41,2 290 | DA:43,2 291 | DA:44,2 292 | DA:46,2 293 | DA:47,2 294 | DA:49,1 295 | DA:51,1 296 | LF:31 297 | LH:31 298 | BRDA:11,1,0,9 299 | BRDA:11,1,1,1 300 | BRDA:22,2,0,2 301 | BRDA:22,2,1,8 302 | BRDA:39,3,0,2 303 | BRDA:39,3,1,2 304 | BRDA:39,4,0,4 305 | BRDA:39,4,1,3 306 | BRF:8 307 | BRH:8 308 | end_of_record 309 | TN: 310 | SF:/Users/ozan/code/p/lib/init.js 311 | FNF:0 312 | FNH:0 313 | DA:1,1 314 | LF:1 315 | LH:1 316 | BRF:0 317 | BRH:0 318 | end_of_record 319 | TN: 320 | SF:/Users/ozan/code/p/lib/P.js 321 | FN:8,P 322 | FN:15,(anonymous_2) 323 | FN:19,(anonymous_3) 324 | FN:26,(anonymous_4) 325 | FN:33,(anonymous_5) 326 | FN:37,(anonymous_6) 327 | FN:45,(anonymous_7) 328 | FN:52,(anonymous_8) 329 | FN:57,(anonymous_9) 330 | FNF:9 331 | FNH:9 332 | FNDA:10,P 333 | FNDA:1,(anonymous_2) 334 | FNDA:1,(anonymous_3) 335 | FNDA:2,(anonymous_4) 336 | FNDA:1,(anonymous_5) 337 | FNDA:2,(anonymous_6) 338 | FNDA:1,(anonymous_7) 339 | FNDA:2,(anonymous_8) 340 | FNDA:1,(anonymous_9) 341 | DA:1,1 342 | DA:6,1 343 | DA:8,1 344 | DA:9,10 345 | DA:10,9 346 | DA:12,9 347 | DA:13,9 348 | DA:15,9 349 | DA:16,1 350 | DA:19,9 351 | DA:20,1 352 | DA:23,9 353 | DA:26,1 354 | DA:27,2 355 | DA:30,2 356 | DA:33,1 357 | DA:34,1 358 | DA:37,1 359 | DA:38,2 360 | DA:40,2 361 | DA:43,2 362 | DA:45,2 363 | DA:46,1 364 | DA:49,2 365 | DA:52,1 366 | DA:53,2 367 | DA:54,2 368 | DA:57,1 369 | DA:58,1 370 | DA:59,1 371 | DA:62,1 372 | LF:31 373 | LH:31 374 | BRDA:23,1,0,0 375 | BRDA:23,1,1,9 376 | BRDA:23,2,0,9 377 | BRDA:23,2,1,0 378 | BRF:4 379 | BRH:2 380 | end_of_record 381 | TN: 382 | SF:/Users/ozan/code/p/lib/WebSocketConnection.js 383 | FN:14,WebSocketConnection 384 | FN:23,(anonymous_2) 385 | FN:27,(anonymous_3) 386 | FN:31,(anonymous_4) 387 | FN:35,(anonymous_5) 388 | FN:40,(anonymous_6) 389 | FN:46,(anonymous_7) 390 | FN:61,(anonymous_8) 391 | FNF:8 392 | FNH:7 393 | FNDA:12,WebSocketConnection 394 | FNDA:1,(anonymous_2) 395 | FNDA:1,(anonymous_3) 396 | FNDA:1,(anonymous_4) 397 | FNDA:1,(anonymous_5) 398 | FNDA:0,(anonymous_6) 399 | FNDA:2,(anonymous_7) 400 | FNDA:1,(anonymous_8) 401 | DA:1,1 402 | DA:3,1 403 | DA:10,1 404 | DA:11,0 405 | DA:14,1 406 | DA:15,12 407 | DA:17,12 408 | DA:19,8 409 | DA:21,8 410 | DA:23,7 411 | DA:24,1 412 | DA:27,7 413 | DA:28,1 414 | DA:31,7 415 | DA:32,1 416 | DA:35,7 417 | DA:36,1 418 | DA:40,1 419 | DA:41,0 420 | DA:42,0 421 | DA:45,1 422 | DA:46,1 423 | DA:47,2 424 | DA:49,0 425 | DA:52,1 426 | DA:53,1 427 | DA:57,1 428 | DA:61,1 429 | DA:62,1 430 | DA:64,0 431 | DA:66,0 432 | DA:68,1 433 | DA:70,0 434 | DA:74,1 435 | LF:34 436 | LH:27 437 | BRDA:10,1,0,0 438 | BRDA:10,1,1,1 439 | BRDA:47,2,0,0 440 | BRDA:47,2,1,1 441 | BRDA:47,2,2,0 442 | BRDA:47,2,3,1 443 | BRDA:62,3,0,0 444 | BRDA:62,3,1,0 445 | BRDA:62,3,2,1 446 | BRDA:62,3,3,0 447 | BRF:10 448 | BRH:4 449 | end_of_record 450 | TN: 451 | SF:/Users/ozan/code/p/lib/WebRtcConnection.js 452 | FN:26,(anonymous_1) 453 | FN:29,(anonymous_2) 454 | FN:34,WebRTCConnection 455 | FN:56,(anonymous_4) 456 | FN:62,(anonymous_5) 457 | FN:66,(anonymous_6) 458 | FN:71,(anonymous_7) 459 | FN:76,(anonymous_8) 460 | FN:82,(anonymous_9) 461 | FN:87,(anonymous_10) 462 | FN:92,(anonymous_11) 463 | FN:106,(anonymous_12) 464 | FN:115,(anonymous_13) 465 | FN:129,(anonymous_14) 466 | FN:135,(anonymous_15) 467 | FN:140,(anonymous_16) 468 | FN:144,(anonymous_17) 469 | FN:150,onError 470 | FN:152,(anonymous_19) 471 | FN:153,(anonymous_20) 472 | FN:159,(anonymous_21) 473 | FN:165,onError 474 | FN:167,(anonymous_23) 475 | FN:168,(anonymous_24) 476 | FN:174,(anonymous_25) 477 | FNF:25 478 | FNH:0 479 | FNDA:0,(anonymous_1) 480 | FNDA:0,(anonymous_2) 481 | FNDA:0,WebRTCConnection 482 | FNDA:0,(anonymous_4) 483 | FNDA:0,(anonymous_5) 484 | FNDA:0,(anonymous_6) 485 | FNDA:0,(anonymous_7) 486 | FNDA:0,(anonymous_8) 487 | FNDA:0,(anonymous_9) 488 | FNDA:0,(anonymous_10) 489 | FNDA:0,(anonymous_11) 490 | FNDA:0,(anonymous_12) 491 | FNDA:0,(anonymous_13) 492 | FNDA:0,(anonymous_14) 493 | FNDA:0,(anonymous_15) 494 | FNDA:0,(anonymous_16) 495 | FNDA:0,(anonymous_17) 496 | FNDA:0,onError 497 | FNDA:0,(anonymous_19) 498 | FNDA:0,(anonymous_20) 499 | FNDA:0,(anonymous_21) 500 | FNDA:0,onError 501 | FNDA:0,(anonymous_23) 502 | FNDA:0,(anonymous_24) 503 | FNDA:0,(anonymous_25) 504 | DA:11,1 505 | DA:14,1 506 | DA:19,1 507 | DA:22,1 508 | DA:26,1 509 | DA:28,1 510 | DA:29,0 511 | DA:30,0 512 | DA:34,1 513 | DA:35,0 514 | DA:37,0 515 | DA:38,0 516 | DA:39,0 517 | DA:40,0 518 | DA:42,0 519 | DA:44,0 520 | DA:45,0 521 | DA:46,0 522 | DA:51,0 523 | DA:54,0 524 | DA:56,0 525 | DA:57,0 526 | DA:58,0 527 | DA:59,0 528 | DA:62,0 529 | DA:63,0 530 | DA:65,0 531 | DA:66,0 532 | DA:67,0 533 | DA:68,0 534 | DA:71,0 535 | DA:72,0 536 | DA:73,0 537 | DA:76,0 538 | DA:77,0 539 | DA:78,0 540 | DA:82,0 541 | DA:83,0 542 | DA:84,0 543 | DA:87,0 544 | DA:88,0 545 | DA:89,0 546 | DA:92,0 547 | DA:93,0 548 | DA:94,0 549 | DA:98,1 550 | DA:99,1 551 | DA:106,1 552 | DA:107,0 553 | DA:110,0 554 | DA:113,1 555 | DA:115,1 556 | DA:116,0 557 | DA:118,0 558 | DA:120,0 559 | DA:121,0 560 | DA:122,0 561 | DA:125,0 562 | DA:129,1 563 | DA:130,0 564 | DA:132,0 565 | DA:135,1 566 | DA:136,0 567 | DA:137,0 568 | DA:140,1 569 | DA:141,0 570 | DA:144,1 571 | DA:145,0 572 | DA:150,1 573 | DA:152,0 574 | DA:153,0 575 | DA:154,0 576 | DA:159,1 577 | DA:160,0 578 | DA:165,1 579 | DA:167,0 580 | DA:168,0 581 | DA:169,0 582 | DA:174,1 583 | DA:175,0 584 | DA:179,1 585 | DA:181,1 586 | LF:82 587 | LH:22 588 | BRDA:14,1,0,0 589 | BRDA:14,1,1,1 590 | BRDA:15,2,0,0 591 | BRDA:15,2,1,1 592 | BRDA:16,3,0,0 593 | BRDA:16,3,1,1 594 | BRDA:19,4,0,0 595 | BRDA:19,4,1,1 596 | BRDA:20,5,0,0 597 | BRDA:20,5,1,1 598 | BRDA:22,6,0,0 599 | BRDA:22,6,1,1 600 | BRDA:23,7,0,0 601 | BRDA:23,7,1,1 602 | BRDA:28,8,0,0 603 | BRDA:28,8,1,1 604 | BRDA:28,9,0,1 605 | BRDA:28,9,1,0 606 | BRDA:57,10,0,0 607 | BRDA:57,10,1,0 608 | BRDA:107,11,0,0 609 | BRDA:107,11,1,0 610 | BRDA:116,12,0,0 611 | BRDA:116,12,1,0 612 | BRDA:116,12,2,0 613 | BRDA:116,12,3,0 614 | BRDA:171,13,0,0 615 | BRDA:171,13,1,0 616 | BRF:28 617 | BRH:9 618 | end_of_record 619 | -------------------------------------------------------------------------------- /dist/P.min.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.P=e()}}(function(){return function e(t,n,r){function i(s,a){if(!n[s]){if(!t[s]){var c="function"==typeof require&&require;if(!a&&c)return c(s,!0);if(o)return o(s,!0);var p=new Error("Cannot find module '"+s+"'");throw p.code="MODULE_NOT_FOUND",p}var u=n[s]={exports:{}};t[s][0].call(u.exports,function(e){var n=t[s][1][e];return i(n?n:e)},u,u.exports,e,t,n,r)}return n[s].exports}for(var o="function"==typeof require&&require,s=0;se||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},n.prototype.emit=function(e){var t,n,i,a,c,p;if(this._events||(this._events={}),"error"===e&&(!this._events.error||o(this._events.error)&&!this._events.error.length)){if(t=arguments[1],t instanceof Error)throw t;throw TypeError('Uncaught, unspecified "error" event.')}if(n=this._events[e],s(n))return!1;if(r(n))switch(arguments.length){case 1:n.call(this);break;case 2:n.call(this,arguments[1]);break;case 3:n.call(this,arguments[1],arguments[2]);break;default:for(i=arguments.length,a=new Array(i-1),c=1;i>c;c++)a[c-1]=arguments[c];n.apply(this,a)}else if(o(n)){for(i=arguments.length,a=new Array(i-1),c=1;i>c;c++)a[c-1]=arguments[c];for(p=n.slice(),i=p.length,c=0;i>c;c++)p[c].apply(this,a)}return!0},n.prototype.addListener=function(e,t){var i;if(!r(t))throw TypeError("listener must be a function");if(this._events||(this._events={}),this._events.newListener&&this.emit("newListener",e,r(t.listener)?t.listener:t),this._events[e]?o(this._events[e])?this._events[e].push(t):this._events[e]=[this._events[e],t]:this._events[e]=t,o(this._events[e])&&!this._events[e].warned){var i;i=s(this._maxListeners)?n.defaultMaxListeners:this._maxListeners,i&&i>0&&this._events[e].length>i&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace())}return this},n.prototype.on=n.prototype.addListener,n.prototype.once=function(e,t){function n(){this.removeListener(e,n),i||(i=!0,t.apply(this,arguments))}if(!r(t))throw TypeError("listener must be a function");var i=!1;return n.listener=t,this.on(e,n),this},n.prototype.removeListener=function(e,t){var n,i,s,a;if(!r(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(n=this._events[e],s=n.length,i=-1,n===t||r(n.listener)&&n.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(o(n)){for(a=s;a-->0;)if(n[a]===t||n[a].listener&&n[a].listener===t){i=a;break}if(0>i)return this;1===n.length?(n.length=0,delete this._events[e]):n.splice(i,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},n.prototype.removeAllListeners=function(e){var t,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(n=this._events[e],r(n))this.removeListener(e,n);else for(;n.length;)this.removeListener(e,n[n.length-1]);return delete this._events[e],this},n.prototype.listeners=function(e){var t;return t=this._events&&this._events[e]?r(this._events[e])?[this._events[e]]:this._events[e].slice():[]},n.listenerCount=function(e,t){var n;return n=e._events&&e._events[t]?r(e._events[t])?1:e._events[t].length:0}},{}],9:[function(e,t){t.exports=e("./lib/its.js")},{"./lib/its.js":10}],10:[function(e,t){var n=Array.prototype.slice,r=Object.prototype.toString,i=/%s/,o=function(e,t){for(var n=[],r=e.split(i),o=0,s=r.length;s>o;o++)n.push(r[o]),n.push(t[o]);return n.join("")},s=t.exports=function(e,t){if(e===!1)throw t&&"string"!=typeof t?t(arguments.length>3?o(arguments[2],n.call(arguments,3)):arguments[2]):new Error(arguments.length>2?o(t,n.call(arguments,2)):t);return e};s.type=function(e,t){if(e===!1)throw new TypeError(arguments.length>2?o(t,n.call(arguments,2)):t);return e},s.undefined=function(e){return s.type.apply(null,[void 0===e].concat(n.call(arguments,1)))},s["null"]=function(e){return s.type.apply(null,[null===e].concat(n.call(arguments,1)))},s["boolean"]=function(e){return s.type.apply(null,[e===!0||e===!1||"[object Boolean]"===r.call(e)].concat(n.call(arguments,1)))},s.array=function(e){return s.type.apply(null,["[object Array]"===r.call(e)].concat(n.call(arguments,1)))},s.object=function(e){return s.type.apply(null,[e===Object(e)].concat(n.call(arguments,1)))},function(){for(var e=[["args","Arguments"],["func","Function"],["string","String"],["number","Number"],["date","Date"],["regexp","RegExp"]],t=0,i=e.length;i>t;t++)!function(){var i=e[t];s[i[0]]=function(e){return s.type.apply(null,[r.call(e)==="[object "+i[1]+"]"].concat(n.call(arguments,1)))}}()}(),"function"!=typeof/./&&(s.func=function(e){return s.type.apply(null,["function"==typeof e].concat(n.call(arguments,1)))}),s.defined=function(e,t){if(void 0===e)throw new ReferenceError(arguments.length>2?o(t,n.call(arguments,2)):t);return e},s.range=function(e,t){if(e===!1)throw new RangeError(arguments.length>2?o(t,n.call(arguments,2)):t);return e}},{}]},{},[1])(1)}); 2 | //# sourceMappingURL=P.min.js.map -------------------------------------------------------------------------------- /examples/party/Charlie.html: -------------------------------------------------------------------------------- 1 | Charlie 2 | 3 | 25 | -------------------------------------------------------------------------------- /examples/party/alice.html: -------------------------------------------------------------------------------- 1 | Alice 2 | 3 | 53 | -------------------------------------------------------------------------------- /examples/party/bob.html: -------------------------------------------------------------------------------- 1 | Bob 2 | 3 | -------------------------------------------------------------------------------- /examples/ping-pong/ping.html: -------------------------------------------------------------------------------- 1 | Ping 2 | 3 | 4 | 50 | -------------------------------------------------------------------------------- /examples/ping-pong/pong.html: -------------------------------------------------------------------------------- 1 | Pong 2 | 3 | 4 | 5 | 36 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/P.js'); -------------------------------------------------------------------------------- /lib/Connection.js: -------------------------------------------------------------------------------- 1 | var JSONProtocol = require('./JSONProtocol.js'), 2 | its = require('its'), 3 | Emitter = require('events').EventEmitter; 4 | 5 | function notImplemented(){ 6 | throw new Error('This method is not implemented'); 7 | } 8 | 9 | function Connection(address, peers, options){ 10 | its.string(address); 11 | its.defined(peers); 12 | 13 | this.address = address; 14 | this.peers = peers; 15 | 16 | if(options){ 17 | if(options.emitter) this.emitter = options.emitter; 18 | if(options.firewall) this.acceptRTCConnection = options.firewall; 19 | } 20 | 21 | if(!this.emitter) this.emitter = new Connection.Emitter(); 22 | } 23 | 24 | // Circular dependency solved in WebRTCConnection.js 25 | Connection.createWebRTCConnection = null; 26 | 27 | Connection.Emitter = Emitter; 28 | 29 | Connection.prototype = Object.create(JSONProtocol.prototype); 30 | 31 | Connection.prototype.on = function(){ 32 | this.emitter.on.apply(this.emitter, arguments); 33 | return this; 34 | }; 35 | 36 | Connection.prototype.removeListener = function(){ 37 | this.emitter.removeListener.apply(this.emitter, arguments); 38 | return this; 39 | }; 40 | 41 | Connection.prototype.send = JSONProtocol.prototype.writeMessage; 42 | 43 | Connection.prototype.getPeer = function(address){ 44 | return this.peers.get(address); 45 | }; 46 | 47 | Connection.prototype.addPeer = function(peer){ 48 | return this.peers.add(peer); 49 | }; 50 | 51 | Connection.prototype.getPeers = function() { 52 | return this.peers.get(); 53 | }; 54 | 55 | function isString(candidate){ 56 | return Object.prototype.toString.call(candidate) === '[object String]'; 57 | } 58 | 59 | Connection.prototype.connect = function(config){ 60 | if(isString(config)){ 61 | config = {address: config}; 62 | } 63 | 64 | var self = this, 65 | firewall = config.firewall || this.firewall, 66 | peer = Connection.createWebRTCConnection(config, this.peers, this, {firewall: firewall}); 67 | 68 | peer.writeOffer(config); 69 | 70 | this.peers.add(peer); 71 | 72 | peer.on('close', function(){ 73 | self.peers.remove(peer); 74 | self.emitter.emit('disconnection', peer); 75 | }); 76 | 77 | this.emitter.emit('connection', peer); 78 | 79 | return peer; 80 | }; 81 | 82 | Connection.prototype.readMessage = function(message){ 83 | this.emitter.emit('message', message); 84 | }; 85 | 86 | Connection.prototype.readArrayBuffer = function(message){ 87 | this.emitter.emit('arraybuffer', message); 88 | }; 89 | 90 | Connection.prototype.acceptRTCConnection = function(/*description, data*/){ 91 | return true; 92 | }; 93 | 94 | Connection.prototype.readRelay = function(peerAddress, message){ 95 | var peer = this.getPeer(peerAddress); 96 | 97 | if(!peer){ 98 | this.emitter.emit('error', new Error('Unknown peer at address: ' + peerAddress)); 99 | return; 100 | } 101 | 102 | peer.writeRelayedMessage(this.address, message); 103 | }; 104 | 105 | Connection.prototype.readRelayedIceCandidate = function(peerAddress, candidate){ 106 | var peer = this.getPeer(peerAddress); 107 | 108 | if(!peer){ 109 | this.emitter.emit('error', new Error('Unknown peer at address: ' + peerAddress)); 110 | return; 111 | } 112 | 113 | peer.readIceCandidate(candidate); 114 | }; 115 | 116 | Connection.prototype.readRelayedOffer = function(peerAddress, description, data){ 117 | if(!this.acceptRTCConnection(description, data)) return false; 118 | 119 | var self = this, 120 | peer = Connection.createWebRTCConnection({address:peerAddress}, this.peers, this, {firewall: this.firewall}); 121 | 122 | this.addPeer(peer); 123 | 124 | peer.on('close', function(){ 125 | self.peers.remove(peer); 126 | self.emitter.emit('disconnection', peer); 127 | }); 128 | 129 | peer.readOffer(description); 130 | peer.writeAnswer(); 131 | 132 | this.emitter.emit('connection', peer); 133 | }; 134 | 135 | Connection.prototype.readRelayedAnswer = function(peerAddress, description){ 136 | var peer = this.getPeer(peerAddress); 137 | 138 | if(!peer){ 139 | this.emitter.emit('error', new Error('Unknown peer at address: ' + peerAddress)); 140 | return; 141 | } 142 | 143 | peer.readAnswer(description); 144 | }; 145 | 146 | Connection.prototype.close = notImplemented; // implemented higher up 147 | Connection.prototype.getReadyState = notImplemented; // implemented higher up 148 | 149 | Connection.prototype.isOpen = function(){ 150 | return this.getReadyState() === 'open'; 151 | }; 152 | 153 | module.exports = Connection; 154 | -------------------------------------------------------------------------------- /lib/ConnectionManager.js: -------------------------------------------------------------------------------- 1 | var its = require('its'); 2 | 3 | function noop(){} 4 | 5 | function ConnectionManager(){ 6 | this.connectionMap = {}; 7 | this.connectionList = []; 8 | } 9 | 10 | ConnectionManager.prototype.get = function(address){ 11 | if(address === undefined) return this.connectionList.slice(); 12 | 13 | return this.connectionMap[address]; 14 | }; 15 | 16 | ConnectionManager.prototype.add = function(connection) { 17 | its.defined(connection); 18 | 19 | var address = connection.address; 20 | its.string(address); 21 | 22 | if(address in this.connectionMap) return false; 23 | 24 | this.connectionMap[address] = connection; 25 | this.connectionList.push(connection); 26 | 27 | this.onAdd(connection); 28 | return true; 29 | }; 30 | ConnectionManager.prototype.onAdd = noop; 31 | 32 | ConnectionManager.prototype.remove = function(connection){ 33 | its.defined(connection); 34 | 35 | var address = connection.address; 36 | its.string(address); 37 | 38 | var mappedConnection = this.connectionMap[address]; 39 | if(!mappedConnection || mappedConnection !== connection) return false; 40 | 41 | delete this.connectionMap[address]; 42 | 43 | var index = this.connectionList.indexOf(connection); 44 | this.connectionList.splice(index, 1); 45 | 46 | this.onRemove(connection); 47 | return true; 48 | }; 49 | ConnectionManager.prototype.onRemove = noop; 50 | 51 | module.exports = ConnectionManager; -------------------------------------------------------------------------------- /lib/JSONProtocol.js: -------------------------------------------------------------------------------- 1 | function notImplemented(){ 2 | throw new Error('This method is not implemented'); 3 | } 4 | 5 | function JSONProtocol(){} 6 | 7 | JSONProtocol.prototype.PROTOCOL_NAME = 'p'; 8 | 9 | JSONProtocol.prototype.MESSAGE_TYPE = { 10 | DIRECT: 0, // [0, message] 11 | 12 | RTC_OFFER: 3, // [3, description, data] 13 | RTC_ANSWER: 4, // [4, description] 14 | RTC_ICE_CANDIDATE: 5, // [5, candidate] 15 | 16 | RELAY: 6, // [6, address, message] 17 | RELAYED: 7 // [7, address, message] 18 | }; 19 | 20 | JSONProtocol.prototype.readRaw = function(message){ 21 | if(message instanceof ArrayBuffer){ 22 | this.readArrayBuffer(message); 23 | } else { 24 | this.readProtocolMessage(JSON.parse(message)); 25 | } 26 | }; 27 | 28 | JSONProtocol.prototype.readProtocolMessage = function(message){ 29 | var MESSAGE_TYPE = this.MESSAGE_TYPE, 30 | messageType = message[0]; 31 | 32 | switch(messageType){ 33 | // This is a message from the remote node to this one. 34 | case MESSAGE_TYPE.DIRECT: 35 | this.readMessage(message[1]); 36 | break; 37 | 38 | // The message was relayed by the peer on behalf of 39 | // a third party peer, identified by "thirdPartyPeerId". 40 | // This means that the peer is acting as a signalling 41 | // channel on behalf of the third party peer. 42 | case MESSAGE_TYPE.RELAYED: 43 | this.readRelayedMessage(message[1], message[2]); 44 | break; 45 | 46 | // The message is intended for another peer, identified 47 | // by "peerId", which is also connected to this node. 48 | // This means that the peer is using this connection 49 | // as a signalling channel in order to establish a connection 50 | // to the other peer identified "peerId". 51 | case MESSAGE_TYPE.RELAY: 52 | this.readRelay(message[1], message[2]); 53 | break; 54 | 55 | default: 56 | throw new Error('Unknown message type: ' + messageType); 57 | } 58 | }; 59 | 60 | JSONProtocol.prototype.readRelayedMessage = function(origin, message){ 61 | var MESSAGE_TYPE = this.MESSAGE_TYPE, 62 | messageType = message[0]; 63 | 64 | switch(messageType){ 65 | // An initial connection request from a third party peer 66 | case MESSAGE_TYPE.RTC_OFFER: 67 | this.readRelayedOffer(origin, message[1], message[2]); 68 | break; 69 | 70 | // An answer to an RTC offer sent from this node 71 | case MESSAGE_TYPE.RTC_ANSWER: 72 | this.readRelayedAnswer(origin, message[1]); 73 | break; 74 | 75 | // An ICE candidate from the source node 76 | case MESSAGE_TYPE.RTC_ICE_CANDIDATE: 77 | this.readRelayedIceCandidate(origin, message[1]); 78 | break; 79 | 80 | default: 81 | throw new Error('Unknown message type: ' + messageType); 82 | } 83 | }; 84 | 85 | JSONProtocol.prototype.readMessage = notImplemented; 86 | JSONProtocol.prototype.readArrayBuffer = notImplemented; 87 | JSONProtocol.prototype.readRelay = notImplemented; 88 | 89 | JSONProtocol.prototype.readRelayedOffer = notImplemented; 90 | JSONProtocol.prototype.readRelayedAnswer = notImplemented; 91 | JSONProtocol.prototype.readRelayedIceCandidate = notImplemented; 92 | 93 | JSONProtocol.prototype.writeRaw = notImplemented; 94 | 95 | JSONProtocol.prototype.writeProtocolMessage = function(message){ 96 | var serializedMessage = JSON.stringify(message); 97 | this.writeRaw(serializedMessage); 98 | }; 99 | 100 | JSONProtocol.prototype.writeMessage = function(message){ 101 | if(message instanceof ArrayBuffer){ 102 | this.writeRaw(message); 103 | } else { 104 | this.writeStringMessage(message); 105 | } 106 | }; 107 | 108 | JSONProtocol.prototype.writeStringMessage = function(message){ 109 | this.writeProtocolMessage([ 110 | this.MESSAGE_TYPE.DIRECT, 111 | message 112 | ]); 113 | }; 114 | 115 | JSONProtocol.prototype.writeRelayedMessage = function(origin, message){ 116 | this.writeProtocolMessage([ 117 | this.MESSAGE_TYPE.RELAYED, 118 | origin, 119 | message 120 | ]); 121 | }; 122 | 123 | JSONProtocol.prototype.writeRelayMessage = function(destination, message){ 124 | this.writeProtocolMessage([ 125 | this.MESSAGE_TYPE.RELAY, 126 | destination, 127 | message 128 | ]); 129 | }; 130 | 131 | JSONProtocol.prototype.writeRelayAnswer = function(destination, description){ 132 | this.writeRelayMessage(destination, [ 133 | this.MESSAGE_TYPE.RTC_ANSWER, 134 | description 135 | ]); 136 | }; 137 | 138 | JSONProtocol.prototype.writeRelayIceCandidate = function(destination, candidate){ 139 | this.writeRelayMessage(destination, [ 140 | this.MESSAGE_TYPE.RTC_ICE_CANDIDATE, 141 | candidate 142 | ]); 143 | }; 144 | 145 | JSONProtocol.prototype.writeRelayOffer = function(destination, description, data){ 146 | this.writeRelayMessage(destination, [ 147 | this.MESSAGE_TYPE.RTC_OFFER, 148 | description, 149 | data 150 | ]); 151 | }; 152 | 153 | module.exports = JSONProtocol; -------------------------------------------------------------------------------- /lib/P.js: -------------------------------------------------------------------------------- 1 | var Emitter = require('events').EventEmitter, 2 | ConnectionManager = require('./ConnectionManager.js'), 3 | WebSocketConnection = require('./WebSocketConnection.js'), 4 | its = require('its'); 5 | 6 | require('./WebRtcConnection'); 7 | 8 | function P(emitter, connectionManager, options){ 9 | its.defined(emitter); 10 | its.defined(connectionManager); 11 | 12 | this.emitter = emitter; 13 | this.peers = connectionManager; 14 | 15 | this.peers.onAdd = function(peer){ 16 | emitter.emit('connection', peer); 17 | }; 18 | 19 | this.peers.onRemove = function(peer){ 20 | emitter.emit('disconnection', peer); 21 | }; 22 | 23 | if(options && options.firewall) this.firewall = options.firewall; 24 | } 25 | 26 | P.create = function(options){ 27 | var emitter = new Emitter(), 28 | connectionManager = new ConnectionManager(); 29 | 30 | return new P(emitter, connectionManager, options); 31 | }; 32 | 33 | P.prototype.getPeers = function(){ 34 | return this.peers.get(); 35 | }; 36 | 37 | P.prototype.connect = function(address){ 38 | its.string(address); 39 | 40 | var peers = this.peers, 41 | peer = WebSocketConnection.create(address, this.peers, {firewall: this.firewall}); 42 | 43 | peers.add(peer); 44 | 45 | peer.on('close', function(){ 46 | peers.remove(peer); 47 | }); 48 | 49 | return peer; 50 | }; 51 | 52 | P.prototype.on = function(){ 53 | this.emitter.on.apply(this.emitter, arguments); 54 | return this; 55 | }; 56 | 57 | P.prototype.removeListener = function(){ 58 | this.emitter.removeListener.apply(this.emitter, arguments); 59 | return this; 60 | }; 61 | 62 | module.exports = P; 63 | -------------------------------------------------------------------------------- /lib/WebRtcConnection.js: -------------------------------------------------------------------------------- 1 | /*global 2 | RTCPeerConnection, 3 | webkitRTCPeerConnection, 4 | mozRTCPeerConnection, 5 | RTCSessionDescription, 6 | mozRTCSessionDescription, 7 | RTCIceCandidate, 8 | mozRTCIceCandidate 9 | */ 10 | 11 | var Connection = require('./Connection.js'), 12 | its = require('its'); 13 | 14 | var nativeRTCPeerConnection = (typeof RTCPeerConnection !== 'undefined')? RTCPeerConnection : 15 | (typeof webkitRTCPeerConnection !== 'undefined')? webkitRTCPeerConnection : 16 | (typeof mozRTCPeerConnection !== 'undefined')? mozRTCPeerConnection : 17 | undefined; 18 | 19 | var nativeRTCSessionDescription = (typeof RTCSessionDescription !== 'undefined')? RTCSessionDescription : 20 | (typeof mozRTCSessionDescription !== 'undefined')? mozRTCSessionDescription : 21 | undefined; 22 | var nativeRTCIceCandidate = (typeof RTCIceCandidate !== 'undefined')? RTCIceCandidate : 23 | (typeof mozRTCIceCandidate !== 'undefined')? mozRTCIceCandidate : 24 | undefined; 25 | 26 | var log = function(){}; 27 | 28 | if(typeof window !== 'undefined' && window.P_DEBUGGING_ENABLED){ 29 | log = function(label, event, obj){ 30 | window.console.debug(label, event, obj); 31 | }; 32 | } 33 | 34 | function WebRTCConnection(address, peers, rtcConnection, signalingChannel, options){ 35 | var self = this; 36 | 37 | its.string(address); 38 | its.defined(peers); 39 | its.defined(rtcConnection); 40 | its.defined(signalingChannel); 41 | 42 | Connection.call(this, address, peers, options); 43 | 44 | this.signalingChannel = signalingChannel; 45 | this.rtcConnection = rtcConnection; 46 | this.rtcDataChannel = rtcConnection.createDataChannel(this.PROTOCOL_NAME, {protocol: this.PROTOCOL_NAME}); 47 | 48 | 49 | // Bug in FF seems to garbage collect the stale ref causing it to close 50 | // the prevents it from being lost in a GC event 51 | this._initialRtcDataChannel = this.rtcDataChannel; 52 | 53 | 54 | this.close = rtcConnection.close.bind(rtcConnection); 55 | 56 | this.rtcConnection.addEventListener('icecandidate', function(event){ 57 | if(!event.candidate) return; 58 | log('ice candidate', event, self); 59 | self.signalingChannel.writeRelayIceCandidate(address, event.candidate); 60 | }); 61 | 62 | this.rtcConnection.addEventListener('datachannel', function(event){ 63 | log('datachannel', event, self); 64 | 65 | var rtcDataChannel = self.rtcDataChannel = event.channel; 66 | rtcDataChannel.addEventListener('open', function(event){ 67 | log('remote datachannel open', event, self); 68 | self.emitter.emit('open', event); 69 | }); 70 | 71 | rtcDataChannel.addEventListener('close', function(event){ 72 | log('remote datachannel close', event, self); 73 | self.emitter.emit('close', event); 74 | }); 75 | 76 | rtcDataChannel.addEventListener('error', function(event){ 77 | log('remote datachannel error', event, self); 78 | self.emitter.emit('error', event); 79 | }); 80 | }); 81 | 82 | this.rtcDataChannel.addEventListener('message', function(message){ 83 | log('local datachannel message', message, self); 84 | self.readRaw(message.data); 85 | }); 86 | 87 | this.rtcDataChannel.addEventListener('error', function(event){ 88 | log('local datachannel error', event, self); 89 | self.emitter.emit('error', event); 90 | }); 91 | 92 | this.rtcDataChannel.addEventListener('close', function(event){ 93 | log('local datachannel close', event, self); 94 | self.emitter.emit('close', event); 95 | }); 96 | } 97 | 98 | var DEFAULT_RTC_CONFIGURATION = null; 99 | var DEFAULT_RTC_OFFER_OPTIONS = { 100 | offerToReceiveAudio: false, 101 | offerToReceiveVideo: false, 102 | iceRestart: false 103 | }; 104 | 105 | //DEFAULT_RTC_OFFER_OPTIONS 106 | WebRTCConnection.create = function(config, peers, signalingChannel, options){ 107 | var rtcConfiguration = config.rtcConfiguration || DEFAULT_RTC_CONFIGURATION, 108 | rtcConnection = new nativeRTCPeerConnection(rtcConfiguration); 109 | 110 | return new WebRTCConnection(config.address, peers, rtcConnection, signalingChannel, options); 111 | }; 112 | 113 | WebRTCConnection.prototype = Object.create(Connection.prototype); 114 | 115 | WebRTCConnection.prototype.writeRaw = function(message){ 116 | switch(this.rtcDataChannel.readyState){ 117 | case 'connecting': 118 | throw new Error('Can\'t send a message while RTCDataChannel connecting'); 119 | case 'open': 120 | this.rtcDataChannel.send(message); 121 | log('sent message to remote', message, this); 122 | break; 123 | case 'closing': 124 | case 'closed': 125 | throw new Error('Can\'t send a message while RTCDataChannel is closing or closed'); 126 | } 127 | }; 128 | 129 | WebRTCConnection.prototype.readAnswer = function(description){ 130 | var rtcSessionDescription = new nativeRTCSessionDescription(description); 131 | 132 | this.rtcConnection.setRemoteDescription(rtcSessionDescription); 133 | }; 134 | 135 | WebRTCConnection.prototype.readOffer = function(description){ 136 | var rtcSessionDescription = new nativeRTCSessionDescription(description); 137 | this.rtcConnection.setRemoteDescription(rtcSessionDescription); 138 | }; 139 | 140 | WebRTCConnection.prototype.readIceCandidate = function(candidate){ 141 | this.rtcConnection.addIceCandidate(new nativeRTCIceCandidate(candidate)); 142 | }; 143 | 144 | WebRTCConnection.prototype.writeAnswer = function(){ 145 | var emitter = this.emitter, 146 | address = this.address, 147 | rtcConnection = this.rtcConnection, 148 | signalingChannel = this.signalingChannel; 149 | 150 | function onError(err){ emitter.emit('error', err); } 151 | 152 | rtcConnection.createAnswer(function(description){ 153 | rtcConnection.setLocalDescription(description, function(){ 154 | signalingChannel.writeRelayAnswer(address, description); 155 | }, onError); 156 | }, onError); 157 | }; 158 | 159 | WebRTCConnection.prototype.writeOffer = function(config){ 160 | var emitter = this.emitter, 161 | address = this.address, 162 | rtcConnection = this.rtcConnection, 163 | signalingChannel = this.signalingChannel; 164 | 165 | function onError(err){ emitter.emit('error', err); } 166 | 167 | rtcConnection.createOffer(function(description){ 168 | rtcConnection.setLocalDescription(description, function(){ 169 | signalingChannel.writeRelayOffer(address, description, config.offerData); 170 | }, onError); 171 | }, onError, config.rtcOfferOptions || DEFAULT_RTC_OFFER_OPTIONS); 172 | }; 173 | 174 | WebRTCConnection.prototype.getReadyState = function(){ 175 | return this.rtcDataChannel.readyState; 176 | }; 177 | 178 | // Solves the circular dependency with Connection.js 179 | Connection.createWebRTCConnection = WebRTCConnection.create; 180 | 181 | module.exports = WebRTCConnection; 182 | -------------------------------------------------------------------------------- /lib/WebSocketConnection.js: -------------------------------------------------------------------------------- 1 | var Connection = require('./Connection.js'); 2 | 3 | var WebSocketState = { 4 | CONNECTING: 0, 5 | OPEN: 1, 6 | CLOSING: 2, 7 | CLOSED: 3 8 | }; 9 | 10 | if(typeof WebSocket !== 'undefined'){ 11 | WebSocketState = WebSocket; 12 | } 13 | 14 | function WebSocketConnection(address, peers, webSocket, options){ 15 | var self = this; 16 | 17 | Connection.call(this, address, peers, options); 18 | 19 | this.webSocket = webSocket; 20 | 21 | this.close = webSocket.close.bind(webSocket); 22 | 23 | this.webSocket.addEventListener('message', function(message){ 24 | self.readRaw(message.data); 25 | }); 26 | 27 | this.webSocket.addEventListener('open', function(event){ 28 | self.emitter.emit('open', event); 29 | }); 30 | 31 | this.webSocket.addEventListener('error', function(event){ 32 | self.emitter.emit('error', event); 33 | }); 34 | 35 | this.webSocket.addEventListener('close', function(event){ 36 | self.emitter.emit('close', event); 37 | }); 38 | } 39 | 40 | WebSocketConnection.create = function(address, peers, options){ 41 | var webSocket = new WebSocket(address, WebSocketConnection.prototype.PROTOCOL_NAME); 42 | return new WebSocketConnection(address, peers, webSocket, options); 43 | }; 44 | 45 | WebSocketConnection.prototype = Object.create(Connection.prototype); 46 | WebSocketConnection.prototype.writeRaw = function(message){ 47 | switch(this.webSocket.readyState){ 48 | case WebSocketState.CONNECTING: 49 | throw new Error('Can\'t send a message while WebSocket connecting'); 50 | 51 | case WebSocketState.OPEN: 52 | this.webSocket.send(message); 53 | break; 54 | 55 | case WebSocketState.CLOSING: 56 | case WebSocketState.CLOSED: 57 | throw new Error('Can\'t send a message while WebSocket is closing or closed'); 58 | } 59 | }; 60 | 61 | WebSocketConnection.prototype.getReadyState = function(){ 62 | switch(this.webSocket.readyState){ 63 | case WebSocketState.CONNECTING: 64 | return 'connecting'; 65 | case WebSocketState.OPEN: 66 | return 'open'; 67 | case WebSocketState.CLOSING: 68 | return 'closing'; 69 | case WebSocketState.CLOSED: 70 | return 'closed'; 71 | } 72 | }; 73 | 74 | module.exports = WebSocketConnection; 75 | -------------------------------------------------------------------------------- /lib/init.js: -------------------------------------------------------------------------------- 1 | require('./P.js'); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "internet", 3 | "description": "Framework for creating peer-to-peer browser networks", 4 | "version": "0.3.3", 5 | "dependencies": { 6 | "its": "~1.0.2" 7 | }, 8 | "devDependencies": { 9 | "boilerplate-gulp-js": "^1.0.0", 10 | "gulp": "gulpjs/gulp.git#4.0", 11 | "gulp-connect": "^2.2.0", 12 | "sinon": "~1.7.3" 13 | }, 14 | "main": "./index.js", 15 | "optionalDependencies": {}, 16 | "engines": { 17 | "node": "*" 18 | }, 19 | "scripts": {}, 20 | "keywords": [ 21 | "peer-to-peer", 22 | "websockets", 23 | "webrtc", 24 | "p2p", 25 | "plink", 26 | "p", 27 | "onramp", 28 | "mesh", 29 | "distributed" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /test/specs/Connection.js: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon'); 2 | var util = require('../util'); 3 | 4 | var JSONProtocol = util.JSONProtocol; 5 | var Connection = util.Connection; 6 | 7 | describe('Connection', function(){ 8 | var mockConnectionManager, 9 | mockEmitter, 10 | connection, 11 | mockConnection, 12 | mockPeer, 13 | mockCreateWebRTCConnection, 14 | originalCreateWebRTCConnection = Connection.createWebRTCConnection; 15 | 16 | beforeEach(function(){ 17 | mockConnection = util.createMockConnection(); 18 | mockEmitter = util.createMockEmitter(); 19 | mockConnectionManager = util.createMockConnectionManager(); 20 | mockPeer = mockConnectionManager.mockPeer; 21 | connection = new Connection('123', mockConnectionManager, {emitter: mockEmitter}); 22 | mockCreateWebRTCConnection = sinon.stub(Connection, 'createWebRTCConnection').returns(mockConnection); 23 | }); 24 | 25 | afterEach(function(){ 26 | Connection.createWebRTCConnection = originalCreateWebRTCConnection; 27 | }); 28 | 29 | describe('constructor', function(){ 30 | it('creates a Connection object when called', function(){ 31 | var connection = new Connection('123', mockConnectionManager, {emitter:mockEmitter}); 32 | expect(connection instanceof Connection).toBe(true); 33 | expect(connection instanceof JSONProtocol).toBe(true); 34 | }); 35 | 36 | it('throws an error if required fields aren\'t provided', function(){ 37 | expect(function(){new Connection();}).toThrow(); 38 | 39 | expect(function(){new Connection('123');}).toThrow(); 40 | 41 | expect(function(){new Connection(123, mockConnectionManager);}).toThrow(); 42 | }); 43 | }); 44 | 45 | it('creates a web rtc connection using itself as a signaling channel when connect is called', function(){ 46 | var config = {address: '123'}, 47 | result = connection.connect(config); 48 | 49 | expect(result).toBe(mockConnection); 50 | expect(mockCreateWebRTCConnection.calledWith(config, mockConnectionManager, connection)).toBe(true); 51 | expect(mockConnectionManager.add.calledWith(mockConnection)).toBe(true); 52 | expect(mockConnection.on.calledWith('close')).toBe(true); 53 | expect(mockEmitter.emit.calledWith('connection', mockConnection)).toBe(true); 54 | }); 55 | 56 | it('removes disconnected peers from connection manager and emits the event', function(){ 57 | var onCloseSpy = mockConnection.on.withArgs('close'); 58 | connection.connect('123'); 59 | 60 | expect(mockConnectionManager.remove.calledWith(mockConnection)).toBe(false); 61 | expect(mockEmitter.emit.calledWith('disconnection', mockConnection)).toBe(false); 62 | 63 | onCloseSpy.firstCall.args[1](); 64 | expect(mockConnectionManager.remove.calledWith(mockConnection)).toBe(true); 65 | expect(mockEmitter.emit.calledWith('disconnection', mockConnection)).toBe(true); 66 | }); 67 | 68 | it('emits received messages', function(){ 69 | expect(mockEmitter.emit.calledWith('message')).toBe(false); 70 | connection.readMessage(123); 71 | expect(mockEmitter.emit.calledWith('message')).toBe(true); 72 | }); 73 | 74 | it('relays messages through peers', function(){ 75 | connection.readRelay('abc', 123); 76 | expect(mockPeer.writeRelayedMessage.calledWith(connection.address, 123)).toBe(true); 77 | }); 78 | 79 | it('passes along relayed ice candidates from its peers', function(){ 80 | connection.readRelayedIceCandidate('abc', 'def'); 81 | expect(mockPeer.readIceCandidate.calledWith('def')).toBe(true); 82 | }); 83 | 84 | it('passes along relayed answers from its peers', function(){ 85 | connection.readRelayedAnswer('abc', 'def'); 86 | expect(mockPeer.readAnswer.calledWith('def')).toBe(true); 87 | }); 88 | 89 | it('creates a new web rtc connection when a peer makes an acceptable offer', function(){ 90 | var onCloseSpy = mockConnection.on.withArgs('close'); 91 | 92 | connection.readRelayedOffer('123', 'def', 123); 93 | 94 | expect(mockCreateWebRTCConnection.calledWith({address:'123'}, mockConnectionManager, connection)).toBe(true); 95 | 96 | expect(mockConnection.readOffer.calledWith('def')).toBe(true); 97 | expect(mockConnection.writeAnswer.calledOnce).toBe(true); 98 | expect(mockConnectionManager.add.calledWith(mockConnection)).toBe(true); 99 | expect(mockConnection.on.calledWith('close')).toBe(true); 100 | 101 | onCloseSpy.firstCall.args[1](); 102 | expect(mockConnectionManager.remove.calledWith(mockConnection)).toBe(true); 103 | }); 104 | 105 | it('rejects offers when the acceptRTCConnection call returns false', function(){ 106 | connection.acceptRTCConnection = function(){return false;}; 107 | 108 | connection.readRelayedOffer('123', 'def', 123); 109 | 110 | expect(mockCreateWebRTCConnection.calledOnce).toBe(false); 111 | expect(mockEmitter.emit.calledWith('connection')).toBe(false); 112 | }); 113 | 114 | it('emits a connection event when a new offer results in a connection', function(){ 115 | connection.readRelayedOffer('123', 'def', 123); 116 | expect(mockEmitter.emit.calledWith('connection', mockConnection)).toBe(true); 117 | }); 118 | 119 | it('emits a disconnection event when a connection it emitted closes', function(){ 120 | var onCloseSpy = mockConnection.on.withArgs('close'); 121 | connection.readRelayedOffer('123', 'def', 123); 122 | onCloseSpy.firstCall.args[1](); 123 | expect(mockEmitter.emit.calledWith('disconnection', mockConnection)).toBe(true); 124 | }); 125 | 126 | it('throws an error when close is called because it isn\'t implemented', function(){ 127 | expect(function(){ 128 | connection.close(); 129 | }).toThrow(); 130 | 131 | expect(function(){ 132 | connection.getReadyState(); 133 | }).toThrow(); 134 | 135 | 136 | expect(function(){ 137 | connection.isOpen(); 138 | }).toThrow(); 139 | }); 140 | 141 | it('has all of the methods of the JSONProtocol object', function(){ 142 | for(var property in JSONProtocol.prototype){ 143 | if(JSONProtocol.prototype.hasOwnProperty(property)){ 144 | expect(property in connection).toBe(true); 145 | } 146 | } 147 | }); 148 | }); 149 | -------------------------------------------------------------------------------- /test/specs/ConnectionManager.js: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon'); 2 | var util = require('../util'); 3 | 4 | describe('ConnectionManager', function(){ 5 | var connectionManager, 6 | mockConnection; 7 | 8 | beforeEach(function(){ 9 | connectionManager = new util.ConnectionManager(); 10 | mockConnection = {address: '123'}; 11 | }); 12 | 13 | it('throws an error if an added connection has no address', function(){ 14 | expect(function(){ 15 | connectionManager.add({address: {anObject: true}}); 16 | }).toThrow(); 17 | }); 18 | 19 | it('can keep track of connections by address', function(){ 20 | expect(connectionManager.get().length).toBe(0); 21 | var result = connectionManager.add(mockConnection); 22 | 23 | expect(result).toBe(true); 24 | expect(connectionManager.get().length).toBe(1); 25 | expect(connectionManager.get('123')).toBe(mockConnection); 26 | }); 27 | 28 | it('returns false if an address is already taken', function(){ 29 | expect(connectionManager.get().length).toBe(0); 30 | var result = connectionManager.add(mockConnection); 31 | expect(result).toBe(true); 32 | expect(connectionManager.get().length).toBe(1); 33 | 34 | result = connectionManager.add(mockConnection); 35 | expect(result).toBe(false); 36 | expect(connectionManager.get().length).toBe(1); 37 | 38 | result = connectionManager.add({address: '123'}); 39 | expect(result).toBe(false); 40 | expect(connectionManager.get().length).toBe(1); 41 | }); 42 | 43 | it('returns false if an untracked connection is attempted to be removed', function(){ 44 | var result = connectionManager.remove(mockConnection); 45 | expect(result).toBe(false); 46 | 47 | // should also fail with a 'lookalike' connection with the same address 48 | connectionManager.add(mockConnection); 49 | result = connectionManager.remove({address: '123'}); 50 | expect(result).toBe(false); 51 | }); 52 | 53 | it('returns true if a tracked connection is removed', function(){ 54 | connectionManager.add(mockConnection); 55 | var result = connectionManager.remove(mockConnection); 56 | expect(result).toBe(true); 57 | }); 58 | 59 | it('called onAdd when an address is added', function(){ 60 | connectionManager.onAdd = sinon.spy(); 61 | expect(connectionManager.onAdd.calledOnce).toBe(false); 62 | connectionManager.add(mockConnection); 63 | expect(connectionManager.onAdd.calledOnce).toBe(true); 64 | }); 65 | 66 | it('calls onRemove when an address is removed', function(){ 67 | connectionManager.onRemove = sinon.spy(); 68 | connectionManager.add(mockConnection); 69 | expect(connectionManager.onRemove.calledOnce).toBe(false); 70 | connectionManager.remove(mockConnection); 71 | expect(connectionManager.onRemove.calledOnce).toBe(true); 72 | }); 73 | 74 | it('returns a copy of all connections when get is called without an address', function(){ 75 | connectionManager.add(mockConnection); 76 | connectionManager.add({address: 'abc'}); 77 | var connections = connectionManager.get(); 78 | expect(connections.length).toBe(2); 79 | expect(connections[0]).toBe(mockConnection); 80 | 81 | var connections2 = connectionManager.get(); 82 | expect(connections).not.toBe(connections2); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/specs/JSONProtocol.js: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon'); 2 | var util = require('../util'); 3 | 4 | var JSONProtocol = util.JSONProtocol; 5 | 6 | describe('JSONProtocol', function(){ 7 | var protocol; 8 | 9 | beforeEach(function(){ 10 | protocol = new JSONProtocol(); 11 | }); 12 | 13 | it('throws an error when unimplemented methods are called', function(){ 14 | expect(function(){ 15 | protocol.readMessage(); 16 | }).toThrow(); 17 | 18 | expect(function(){ 19 | protocol.readArrayBuffer(); 20 | }).toThrow(); 21 | 22 | expect(function(){ 23 | protocol.readRelay(); 24 | }).toThrow(); 25 | 26 | expect(function(){ 27 | protocol.readRelayedOffer(); 28 | }).toThrow(); 29 | 30 | expect(function(){ 31 | protocol.readRelayedAnswer(); 32 | }).toThrow(); 33 | 34 | expect(function(){ 35 | protocol.readRelayedIceCandidate(); 36 | }).toThrow(); 37 | 38 | expect(function(){ 39 | protocol.writeRaw(); 40 | }).toThrow(); 41 | }); 42 | 43 | it('calls seperates array buffer messages from protocol message', function(){ 44 | protocol.readArrayBuffer = sinon.spy(); 45 | protocol.readProtocolMessage = sinon.spy(); 46 | 47 | protocol.readRaw('[1,"abc"]'); 48 | expect(protocol.readArrayBuffer.calledOnce).toBe(false); 49 | expect(protocol.readProtocolMessage.calledOnce).toBe(true); 50 | 51 | protocol.readRaw(new ArrayBuffer(16)); 52 | expect(protocol.readArrayBuffer.calledOnce).toBe(true); 53 | expect(protocol.readProtocolMessage.calledOnce).toBe(true); 54 | }); 55 | 56 | it('throws an error if an invalid protocol message is fed in', function(){ 57 | expect(function(){ 58 | protocol.readProtocolMessage('not json'); 59 | }).toThrow(); 60 | 61 | expect(function(){ 62 | protocol.readProtocolMessage(JSON.stringify([9999, 'test'])); 63 | }).toThrow(); 64 | }); 65 | 66 | it('routes direct messages to readMessage', function(){ 67 | var direct = [protocol.MESSAGE_TYPE.DIRECT, 'test']; 68 | 69 | protocol.readMessage = sinon.spy(); 70 | protocol.readProtocolMessage(direct); 71 | expect(protocol.readMessage.calledWith('test')).toBe(true); 72 | }); 73 | 74 | it('routes relayed offers to readRelayedOffer', function(){ 75 | var offer = [protocol.MESSAGE_TYPE.RTC_OFFER, 'description', 'data'], 76 | relayedOffer = [protocol.MESSAGE_TYPE.RELAYED, '123', offer]; 77 | 78 | protocol.readRelayedOffer = sinon.spy(); 79 | protocol.readProtocolMessage(relayedOffer); 80 | expect(protocol.readRelayedOffer.calledWith('123', 'description', 'data')).toBe(true); 81 | }); 82 | 83 | it('routes relayed answers to readRelayedAnswer', function(){ 84 | var answer = [protocol.MESSAGE_TYPE.RTC_ANSWER, 'description'], 85 | relayedAnswer = [protocol.MESSAGE_TYPE.RELAYED, '123', answer]; 86 | 87 | protocol.readRelayedAnswer = sinon.spy(); 88 | protocol.readProtocolMessage(relayedAnswer); 89 | expect(protocol.readRelayedAnswer.calledWith('123', 'description')).toBe(true); 90 | }); 91 | 92 | it('routes relayed ice candidates to readRelayedIceCandidate', function(){ 93 | var iceCandidate = [protocol.MESSAGE_TYPE.RTC_ICE_CANDIDATE, 'description'], 94 | relayedIceCandidate = [protocol.MESSAGE_TYPE.RELAYED, '123', iceCandidate]; 95 | 96 | protocol.readRelayedIceCandidate = sinon.spy(); 97 | protocol.readProtocolMessage(relayedIceCandidate); 98 | expect(protocol.readRelayedIceCandidate.calledWith('123', 'description')).toBe(true); 99 | }); 100 | 101 | it('routes relay messages to readRelay', function(){ 102 | var offer = [protocol.MESSAGE_TYPE.RTC_OFFER, 'description', {data:123}], 103 | relay = [protocol.MESSAGE_TYPE.RELAY, 'abc', offer.slice()]; 104 | 105 | protocol.readRelay = sinon.spy(); 106 | protocol.readProtocolMessage(relay); 107 | expect(protocol.readRelay.calledWith('abc', offer)).toBe(true); 108 | }); 109 | 110 | it('writes array buffers directly to transport', function(){ 111 | var arrayBuffer = new ArrayBuffer(16); 112 | protocol.writeRaw = sinon.spy(); 113 | 114 | protocol.writeMessage(arrayBuffer); 115 | expect(protocol.writeRaw.calledWith(arrayBuffer)).toBe(true); 116 | }); 117 | 118 | it('wrap JSON messages with protocol envelop', function(){ 119 | var message = {test: 123}; 120 | protocol.writeRaw = sinon.spy(); 121 | 122 | protocol.writeMessage(message); 123 | protocol.writeRaw.calledWith('[0,{test:123}]'); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /test/specs/P.js: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon'); 2 | var util = require('../util'); 3 | 4 | describe('P', function(){ 5 | var P, 6 | mockEmitter, 7 | mockConnectionManager, 8 | mockConnection, 9 | originalWebSocketCreate = util.WebSocketConnection.create; 10 | 11 | beforeEach(function(){ 12 | P = util.P; 13 | mockEmitter = util.createMockEmitter(); 14 | mockConnectionManager = util.createMockConnectionManager(); 15 | mockConnection = util.createMockEmitter(); 16 | sinon.stub(util.WebSocketConnection, 'create').returns(mockConnection); 17 | }); 18 | 19 | afterEach(function(){ 20 | util.WebSocketConnection.create = originalWebSocketCreate; 21 | }); 22 | 23 | describe('construction', function(){ 24 | it('constructor creates a P object when called', function(){ 25 | var p = new P(mockEmitter, mockConnectionManager); 26 | 27 | p.on('test', function(){}); 28 | 29 | expect(mockEmitter.on.calledWith('test')).toBe(true); 30 | }); 31 | 32 | it('constructor throws an error if no emitter is provided', function(){ 33 | expect(function(){new P();}).toThrow(); 34 | }); 35 | 36 | it('factory creates a P object and returns the api when called', function(){ 37 | var p = P.create(); 38 | expect(p.on).not.toBeUndefined(); 39 | }); 40 | 41 | it('new instances have no connections upon creation', function(){ 42 | var p = P.create(); 43 | expect(p.getPeers().length).toBe(0); 44 | }); 45 | }); 46 | 47 | describe('connection management', function(){ 48 | it('creates websocket connections by default', function(){ 49 | var p = new P(mockEmitter, mockConnectionManager); 50 | 51 | p.connect('ws://test/'); 52 | 53 | expect(mockConnectionManager.add.calledWith(mockConnection)).toBe(true); 54 | }); 55 | 56 | it('stops tracking closed connections', function(){ 57 | var p = new P(mockEmitter, mockConnectionManager); 58 | 59 | var spy = mockConnection.on.withArgs('close'); 60 | 61 | p.connect('ws://test/'); 62 | 63 | spy.firstCall.args[1](); 64 | 65 | expect(mockConnectionManager.remove.calledWith(mockConnection)).toBe(true); 66 | }); 67 | }); 68 | 69 | describe('events', function(){ 70 | it('emits new connections', function(){ 71 | new P(mockEmitter, mockConnectionManager); 72 | 73 | var mockConnection = util.createMockEmitter(); 74 | mockConnectionManager.onAdd(mockConnection); 75 | 76 | expect(mockEmitter.emit.calledWith('connection', mockConnection)).toBe(true); 77 | }); 78 | 79 | it('emits disconnections', function(){ 80 | new P(mockEmitter, mockConnectionManager); 81 | 82 | var mockConnection = util.createMockEmitter(); 83 | mockConnectionManager.onRemove(mockConnection); 84 | 85 | expect(mockEmitter.emit.calledWith('disconnection', mockConnection)).toBe(true); 86 | }); 87 | 88 | it('allows binding for listening to events', function(){ 89 | var p = new P(mockEmitter, mockConnectionManager); 90 | var mySpy = sinon.spy(); 91 | p.on('connection', mySpy); 92 | 93 | expect(mockEmitter.on.calledWith('connection', mySpy)); 94 | }); 95 | 96 | it('allows removals of bound listeners', function(){ 97 | var p = new P(mockEmitter, mockConnectionManager); 98 | var mySpy = sinon.spy(); 99 | p.removeListener('connection', mySpy); 100 | 101 | expect(mockEmitter.removeListener.calledWith('connection', mySpy)); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/specs/WebRTCConnection.js: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon'); 2 | var util = require('../util'); 3 | 4 | var WebRTCConnection = util.WebRTCConnection; 5 | var JSONProtocol = util.JSONProtocol; 6 | var Connection = util.Connection; 7 | 8 | describe('WebRTCConnection', function(){ 9 | var nativeRTCConnection, 10 | connectionManager, 11 | rtcConnection, 12 | rtcDataChannel, 13 | signalingChannel; 14 | 15 | beforeEach(function(){ 16 | connectionManager = util.createMockConnectionManager(); 17 | nativeRTCConnection = util.createMockRTCConnection(); 18 | rtcDataChannel = nativeRTCConnection.mockRTCDataChannel; 19 | signalingChannel = util.createMockConnection(); 20 | rtcConnection = new WebRTCConnection('123', connectionManager, nativeRTCConnection, signalingChannel); 21 | }); 22 | 23 | it('throws an error when instantiated without required fields', function(){ 24 | expect(function(){ 25 | new WebRTCConnection(); 26 | }).toThrow(); 27 | 28 | // address not string 29 | expect(function(){ 30 | new WebRTCConnection(123, {}, {}, {}); 31 | }).toThrow(); 32 | 33 | expect(function(){ 34 | new WebRTCConnection(undefined, {}, {}, {}); 35 | }).toThrow(); 36 | 37 | expect(function(){ 38 | new WebRTCConnection('123', undefined, {}, {}); 39 | }).toThrow(); 40 | 41 | expect(function(){ 42 | new WebRTCConnection('123', {}, undefined, {}); 43 | }).toThrow(); 44 | 45 | expect(function(){ 46 | new WebRTCConnection('123', {}, {}, undefined); 47 | }).toThrow(); 48 | }); 49 | 50 | it('throws an error if attempting to write to a non-open socket', function(){ 51 | rtcConnection.rtcDataChannel = {readyState: 'closing'}; 52 | expect(function(){rtcConnection.writeRaw('123');}).toThrow(); 53 | }); 54 | 55 | it('returns the rtcDataChannel ready state', function(){ 56 | rtcConnection.rtcDataChannel = {readyState: 'closing'}; 57 | expect(rtcConnection.getReadyState()).toBe('closing'); 58 | }); 59 | 60 | it('propogates data channel open, error, and close events', function(){ 61 | rtcConnection.emitter = {emit: sinon.spy()}; 62 | 63 | rtcConnection.emitter = {emit: sinon.spy()}; 64 | rtcDataChannel.addEventListener.withArgs('error').firstCall.args[1](); 65 | expect(rtcConnection.emitter.emit.calledWith('error')).toBe(true); 66 | 67 | rtcConnection.emitter = {emit: sinon.spy()}; 68 | rtcDataChannel.addEventListener.withArgs('close').firstCall.args[1](); 69 | expect(rtcConnection.emitter.emit.calledWith('close')).toBe(true); 70 | }); 71 | 72 | it('passes data channel message through the protocol handler', function(){ 73 | rtcConnection.readRaw = sinon.spy(); 74 | rtcDataChannel.addEventListener.withArgs('message').firstCall.args[1]({data:123}); 75 | expect(rtcConnection.readRaw.calledWith(123)).toBe(true); 76 | }); 77 | 78 | it('relays ice candidates through signaling channel', function(){ 79 | nativeRTCConnection.addEventListener.withArgs('icecandidate').firstCall.args[1]({candidate:123}); 80 | expect(signalingChannel.writeRelayIceCandidate.calledWith('123', 123)).toBe(true); 81 | }); 82 | 83 | it('contains all properties of Connection and JSONProtocol', function(){ 84 | for(var property in JSONProtocol.prototype){ 85 | if(JSONProtocol.prototype.hasOwnProperty(property)){ 86 | expect(property in rtcConnection).toBe(true); 87 | } 88 | } 89 | 90 | 91 | for(property in Connection.prototype){ 92 | if(Connection.prototype.hasOwnProperty(property)){ 93 | expect(property in rtcConnection).toBe(true); 94 | } 95 | } 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/specs/WebSocketConnection.js: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon'); 2 | 3 | var util = require('../util'); 4 | var WebSocketConnection = util.WebSocketConnection; 5 | var JSONProtocol = util.JSONProtocol; 6 | var Connection = util.Connection; 7 | 8 | var MockWebSocket = { 9 | CLOSED: 3, 10 | CLOSING: 2, 11 | OPEN: 1 12 | }; 13 | 14 | describe('WebSocketConnection', function(){ 15 | var nativeWebSocket, 16 | connectionManager, 17 | connection, 18 | signalingChannel; 19 | 20 | beforeEach(function(){ 21 | connectionManager = util.createMockConnectionManager(); 22 | nativeWebSocket = util.createMockWebSocket(); 23 | signalingChannel = util.createMockConnection(); 24 | connection = new WebSocketConnection('123', connectionManager, nativeWebSocket); 25 | }); 26 | 27 | it('throws an error when instantiated without required fields', function(){ 28 | expect(function(){ 29 | new WebSocketConnection(); 30 | }).toThrow(); 31 | 32 | // address not string 33 | expect(function(){ 34 | new WebSocketConnection(123, {}, {}); 35 | }).toThrow(); 36 | 37 | expect(function(){ 38 | new WebSocketConnection(undefined, {}, {}); 39 | }).toThrow(); 40 | 41 | expect(function(){ 42 | new WebSocketConnection('123', undefined, {}); 43 | }).toThrow(); 44 | 45 | expect(function(){ 46 | new WebSocketConnection('123', {}, undefined); 47 | }).toThrow(); 48 | }); 49 | 50 | it('throws an error if attempting to write to a non-open socket', function(){ 51 | connection.webSocket.readyState = MockWebSocket.CLOSED; 52 | expect(function(){connection.writeRaw('123');}).toThrow(); 53 | }); 54 | 55 | it('returns the websocket ready state as webrtc-like ready state', function(){ 56 | connection.webSocket.readyState = MockWebSocket.CLOSING; 57 | expect(connection.getReadyState()).toBe('closing'); 58 | }); 59 | 60 | it('propogates websocket open, error, and close events', function(){ 61 | connection.emitter = {emit: sinon.spy()}; 62 | nativeWebSocket.addEventListener.withArgs('open').firstCall.args[1](); 63 | expect(connection.emitter.emit.calledWith('open')).toBe(true); 64 | 65 | connection.emitter = {emit: sinon.spy()}; 66 | nativeWebSocket.addEventListener.withArgs('error').firstCall.args[1](); 67 | expect(connection.emitter.emit.calledWith('error')).toBe(true); 68 | 69 | connection.emitter = {emit: sinon.spy()}; 70 | nativeWebSocket.addEventListener.withArgs('close').firstCall.args[1](); 71 | expect(connection.emitter.emit.calledWith('close')).toBe(true); 72 | }); 73 | 74 | it('passes websocket message through the protocol handler', function(){ 75 | connection.readRaw = sinon.spy(); 76 | nativeWebSocket.addEventListener.withArgs('message').firstCall.args[1]({data:123}); 77 | expect(connection.readRaw.calledWith(123)).toBe(true); 78 | }); 79 | 80 | it('writes messages to the data channel', function(){ 81 | nativeWebSocket.readyState = MockWebSocket.OPEN; 82 | connection.writeRaw('abc'); 83 | expect(nativeWebSocket.send.calledWith('abc')).toBe(true); 84 | }); 85 | 86 | it('contains all properties of Connection and JSONProtocol', function(){ 87 | for(var property in JSONProtocol.prototype){ 88 | if(JSONProtocol.prototype.hasOwnProperty(property)){ 89 | expect(property in connection).toBe(true); 90 | } 91 | } 92 | 93 | for(property in Connection.prototype){ 94 | if(Connection.prototype.hasOwnProperty(property)){ 95 | expect(property in connection).toBe(true); 96 | } 97 | } 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon'); 2 | 3 | exports.Connection = require('../lib/Connection.js'); 4 | exports.ConnectionManager = require('../lib/ConnectionManager.js'); 5 | exports.init = require('../lib/init.js'); 6 | exports.JSONProtocol = require('../lib/JSONProtocol.js'); 7 | exports.P = require('../lib/P.js'); 8 | exports.WebRTCConnection = require('../lib/WebRTCConnection.js'); 9 | exports.WebSocketConnection = require('../lib/WebSocketConnection.js'); 10 | 11 | exports.createMockEmitter = function(){ 12 | return { 13 | on: sinon.spy(), 14 | removeListener: sinon.spy(), 15 | emit: sinon.spy() 16 | }; 17 | }; 18 | 19 | exports.createMockConnection = function(){ 20 | var mock = exports.createMockEmitter(); 21 | mock.writeRelayedMessage = sinon.spy(); 22 | mock.readIceCandidate = sinon.spy(); 23 | mock.writeRelayIceCandidate = sinon.spy(); 24 | mock.writeOffer = sinon.spy(); 25 | mock.readOffer = sinon.spy(); 26 | mock.readAnswer = sinon.spy(); 27 | mock.writeAnswer = sinon.spy(); 28 | return mock; 29 | }; 30 | 31 | exports.createMockConnectionManager = function(){ 32 | var mockPeer = exports.createMockConnection(); 33 | 34 | return { 35 | mockPeer: mockPeer, 36 | get: sinon.stub().returns(mockPeer), 37 | add: sinon.spy(), 38 | remove: sinon.spy(), 39 | }; 40 | }; 41 | 42 | exports.createMockRTCConnection = function(){ 43 | var rtcDataChannel = exports.createMockRtcDataChannel(); 44 | 45 | return { 46 | mockRTCDataChannel: rtcDataChannel, 47 | close: sinon.spy(), 48 | addEventListener: sinon.spy(), 49 | createDataChannel: sinon.stub().returns(rtcDataChannel) 50 | }; 51 | }; 52 | 53 | exports.createMockWebSocket = function(){ 54 | return { 55 | close: sinon.spy(), 56 | addEventListener: sinon.spy(), 57 | send: sinon.spy() 58 | }; 59 | }; 60 | 61 | exports.createMockRtcDataChannel = function(){ 62 | return { 63 | addEventListener: sinon.spy(), 64 | send: sinon.spy() 65 | }; 66 | }; 67 | --------------------------------------------------------------------------------