diff --git a/dist/js/gamepad-client.js b/dist/js/gamepad-client.js index 76f869d..63be531 100755 --- a/dist/js/gamepad-client.js +++ b/dist/js/gamepad-client.js @@ -62,62 +62,123 @@ document.addEventListener('click', function (e) { // Create a root `plink` instance. function connect() { return new Promise(function (resolve, reject) { - var plink = Plink.create(); - // Get the key (from the path or query string). var peerKey = utils.getPeerKey(); trace('Peer key: ' + peerKey); + // 1. Create a root `plink` instance. + var plink = Plink.create(); // Returns a new `Plink` instance. + trace('Attempting to connect to host'); - // Connect to `plink-server` and await connection using the peer ID. + // 2. Connect to signalling server (`plink-server`). var link = plink.connect(settings.WS_URL); - // Set a key. Other peers can use to connect to this browser using this - // key via the connected `plink-server`. - // (This returns a Promise on whether the operation succeeded.) - link.useKey(peerKey); + // This opens a connection via `P` to the signalling server + // (`plink-server`), which locally creates a `WebSocketConnection`, + // adds itself as a peer, and adds an event listener for `close`. + // + // It then passes that connection to a new `PlinkServer` instance, and + // that's what `link` is. `PlinkServer`, behind the scenes, sets event + // listeners for `message` and `open`. + // 3. Send this message containing our peer key *to* the signalling server: + // + // { + // "type": "use key", + // "key": "1234" + // } + // + // Or "set key" if we are online but the host is not. + link.on('open', function () { + trace('Connected to signalling server'); + + link.useKey(peerKey).then(function () { + trace('Sent message to signalling server: ' + + JSON.stringify({type: 'use key', key: peerKey})); + }).catch(function (err) { + warn('Host is offline; "use key" message rejected by signalling ' + + 'server: ' + err); + + link.setKey(peerKey).then(function () { + trace('Sent message to signalling server: ' + + JSON.stringify({type: 'set key', key: peerKey})); + }).catch(function (err) { + error('Failed to send "set key" mesage to signalling server: ' + + err); + }); + }); + }); + + // 4. `link` emits this `message` *from* signalling server containing a + // unique identifier (specifically, a UUID) for this peer: + // + // { + // "type": "address", + // "key": "1234", + // "address": "8b14862d-9894-2131-433a-ae2cbef85698" + // } + // + + // 5. WebRTC takes over and we do the offer/answer dance. And that's + // where `RTCPeerConnection` data channels come from. + + // 6. `RTCPeerConnection` emits `open` event when the peer has connected. + + // Event listeners for the signalling server. link.on('connection', function (peer) { - trace('[' + peer.address + '] Connected'); - - peer.on('message', function (msg) { - trace('[' + peer.address + '] Received message: ' + - (typeof msg === 'object' ? JSON.stringify(msg) : msg)); - }); + trace('[' + peer.address + '] Found peer via signalling server'); + // Event listeners for `RTCPeerConnection`. peer.on('open', function () { - trace('[' + peer.address + '] Opened'); + trace('[' + peer.address + '] Opened peer connection to game'); resolve(peer); - }); + }).on('message', function (msg) { + if (msg.type === 'bye') { + warn('[' + peer.address + '] Lost peer connection to game'); + return; + } - peer.on('close', function () { - // Connection lost with host. - // TODO: Reconnect to host (#61). - trace('[' + peer.address + '] Closed'); - }); - - peer.on('error', function (err) { - error('[' + peer.address + '] Error: ' + + trace('[' + peer.address + '] Received peer message: ' + + (typeof msg === 'object' ? JSON.stringify(msg) : msg)); + }).on('error', function (err) { + error('[' + peer.address + '] Peer error: ' + (typeof err === 'object' ? JSON.stringify(err) : err)); reject(err); + }).on('close', function () { + // Peer connection lost to host. (Unfortunately, a few browser bugs + // prevent this from firing; see below.) + trace('[' + peer.address + '] Peer closed'); }); + }).on('message', function (msg) { + trace('Received message from signalling server: ' + + JSON.stringify(msg)); + }).on('error', function (err) { + error('Could not connect to signalling server' + + settings.DEBUG ? (': ' + JSON.stringify(err)) : ''); + reject(err); }).on('close', function () { // TODO: Reconnect to signalling server (#60). - warn('Connection lost with signalling server'); - }).on('error', function (err) { - error('Could not connect to `plink-server`: ' + - JSON.stringify(err)); - reject(err); + warn('Connection to signalling server lost'); // Not peer connection + }); + + // Workaround because `RTCPeerConnection.onclose` ain't work in browsers: + // * https://code.google.com/p/webrtc/issues/detail?id=1676 + // * https://bugzilla.mozilla.org/show_bug.cgi?id=881337 + // * https://bugzilla.mozilla.org/show_bug.cgi?id=1009124 + window.addEventListener('beforeunload', function () { + send({type: 'bye'}); }); }); } connect().then(function (peer) { + trace('[' + peer.address + '] Paired to game'); + // Swap out the `send` function with one that does actual sending. send = function send(msg) { - trace('[' + peer.address + '] Sent message: ' + + trace('[' + peer.address + '] Sent peer message: ' + (typeof msg === 'object' ? JSON.stringify(msg) : msg)); peer.send(msg); }; @@ -356,7 +417,7 @@ bindKeyPresses('keyup', false); })(window, document); -},{"./lib/utils":17,"./settings":18,"plink":13}],2:[function(require,module,exports){ +},{"./lib/utils":17,"./settings":18,"plink":6}],2:[function(require,module,exports){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -725,639 +786,8 @@ process.chdir = function (dir) { }; },{}],4:[function(require,module,exports){ -module.exports = require('./lib/P.js'); -},{"./lib/P.js":8}],5:[function(require,module,exports){ -var JSONProtocol = require('./JSONProtocol.js'), - its = require('its'), - Emitter = require('events').EventEmitter; - -function notImplemented(){ - throw new Error('This method is not implemented'); -} - -function Connection(address, peers, options){ - its.string(address); - its.defined(peers); - - this.address = address; - this.peers = peers; - - if(options){ - if(options.emitter) this.emitter = options.emitter; - if(options.firewall) this.acceptRTCConnection = options.firewall; - } - - if(!this.emitter) this.emitter = new Connection.Emitter(); -} - -// Circular dependency solved in WebRTCConnection.js -Connection.createWebRTCConnection = null; -Connection.Emitter = Emitter; - -Connection.prototype = Object.create(JSONProtocol.prototype); - -Connection.prototype.on = function(){ - this.emitter.on.apply(this.emitter, arguments); - return this; -}; - -Connection.prototype.removeListener = function(){ - this.emitter.removeListener.apply(this.emitter, arguments); - return this; -}; - -Connection.prototype.send = JSONProtocol.prototype.writeMessage; - -Connection.prototype.getPeer = function(address){ - return this.peers.get(address); -}; - -Connection.prototype.addPeer = function(peer){ - return this.peers.add(peer); -}; - -Connection.prototype.getPeers = function() { - return this.peers.get(); -}; - -function isString(candidate){ - return Object.prototype.toString.call(candidate) === '[object String]'; -} - -Connection.prototype.connect = function(config){ - if(isString(config)){ - config = {address: config}; - } - - var self = this, - firewall = config.firewall || this.firewall, - peer = Connection.createWebRTCConnection(config, this.peers, this, {firewall: firewall}); - - peer.writeOffer(config); - - this.peers.add(peer); - - peer.on('close', function(){ - self.peers.remove(peer); - self.emitter.emit('disconnection', peer); - }); - - this.emitter.emit('connection', peer); - - return peer; -}; - -Connection.prototype.readMessage = function(message){ - this.emitter.emit('message', message); -}; - -Connection.prototype.readArrayBuffer = function(message){ - this.emitter.emit('arraybuffer', message); -}; - -Connection.prototype.acceptRTCConnection = function(description, data){ - return true; -}; - -Connection.prototype.readRelay = function(peerAddress, message){ - var peer = this.getPeer(peerAddress); - - if(!peer){ - this.emitter.emit('error', new Error("Unknown peer at address: " + peerAddress)); - return; - } - - peer.writeRelayedMessage(this.address, message); -}; - -Connection.prototype.readRelayedIceCandidate = function(peerAddress, candidate){ - var peer = this.getPeer(peerAddress); - - if(!peer){ - this.emitter.emit('error', new Error("Unknown peer at address: " + peerAddress)); - return; - } - - peer.readIceCandidate(candidate); -}; - -Connection.prototype.readRelayedOffer = function(peerAddress, description, data){ - if(!this.acceptRTCConnection(description, data)) return false; - - var self = this, - peer = Connection.createWebRTCConnection({address:peerAddress}, this.peers, this, {firewall: this.firewall}); - - this.addPeer(peer); - - peer.on('close', function(){ - self.peers.remove(peer); - self.emitter.emit('disconnection', peer); - }); - - peer.readOffer(description); - peer.writeAnswer(); - - this.emitter.emit('connection', peer); -}; - -Connection.prototype.readRelayedAnswer = function(peerAddress, description){ - var peer = this.getPeer(peerAddress); - - if(!peer){ - this.emitter.emit('error', new Error("Unknown peer at address: " + peerAddress)); - return; - } - - peer.readAnswer(description); -}; - -Connection.prototype.close = notImplemented; // implemented higher up -Connection.prototype.getReadyState = notImplemented; // implemented higher up - -Connection.prototype.isOpen = function(){ - return this.getReadyState() === 'open'; -}; - -module.exports = Connection; - -},{"./JSONProtocol.js":7,"events":2,"its":11}],6:[function(require,module,exports){ -var its = require('its'); - -function noop(){} - -function ConnectionManager(){ - this.connectionMap = {}; - this.connectionList = []; -} - -ConnectionManager.prototype.get = function(address){ - if(address === undefined) return this.connectionList.slice(); - - return this.connectionMap[address]; -}; - -ConnectionManager.prototype.add = function(connection) { - its.defined(connection); - - var address = connection.address; - its.string(address); - - if(address in this.connectionMap) return false; - - this.connectionMap[address] = connection; - this.connectionList.push(connection); - - this.onAdd(connection); - return true; -}; -ConnectionManager.prototype.onAdd = noop; - -ConnectionManager.prototype.remove = function(connection){ - its.defined(connection); - - var address = connection.address; - its.string(address); - - var mappedConnection = this.connectionMap[address]; - if(!mappedConnection || mappedConnection !== connection) return false; - - delete this.connectionMap[address]; - - var index = this.connectionList.indexOf(connection); - this.connectionList.splice(index, 1); - - this.onRemove(connection); - return true; -}; -ConnectionManager.prototype.onRemove = noop; - -module.exports = ConnectionManager; -},{"its":11}],7:[function(require,module,exports){ -function notImplemented(){ - throw new Error('This method is not implemented'); -} - -function JSONProtocol(){} - -JSONProtocol.prototype.PROTOCOL_NAME = 'p'; - -JSONProtocol.prototype.MESSAGE_TYPE = { - DIRECT: 0, // [0, message] - - RTC_OFFER: 3, // [3, description, data] - RTC_ANSWER: 4, // [4, description] - RTC_ICE_CANDIDATE: 5, // [5, candidate] - - RELAY: 6, // [6, address, message] - RELAYED: 7 // [7, address, message] -}; - -JSONProtocol.prototype.readRaw = function(message){ - if(message instanceof ArrayBuffer){ - this.readArrayBuffer(message); - } else { - this.readProtocolMessage(JSON.parse(message)); - } -}; - -JSONProtocol.prototype.readProtocolMessage = function(message){ - var MESSAGE_TYPE = this.MESSAGE_TYPE, - messageType = message[0]; - - switch(messageType){ - // This is a message from the remote node to this one. - case MESSAGE_TYPE.DIRECT: - this.readMessage(message[1]); - break; - - // The message was relayed by the peer on behalf of - // a third party peer, identified by "thirdPartyPeerId". - // This means that the peer is acting as a signalling - // channel on behalf of the third party peer. - case MESSAGE_TYPE.RELAYED: - this.readRelayedMessage(message[1], message[2]); - break; - - // The message is intended for another peer, identified - // by "peerId", which is also connected to this node. - // This means that the peer is using this connection - // as a signalling channel in order to establish a connection - // to the other peer identified "peerId". - case MESSAGE_TYPE.RELAY: - this.readRelay(message[1], message[2]); - break; - - default: - throw new Error('Unknown message type: ' + messageType); - } -}; - -JSONProtocol.prototype.readRelayedMessage = function(origin, message){ - var MESSAGE_TYPE = this.MESSAGE_TYPE, - messageType = message[0]; - - switch(messageType){ - // An initial connection request from a third party peer - case MESSAGE_TYPE.RTC_OFFER: - this.readRelayedOffer(origin, message[1], message[2]); - break; - - // An answer to an RTC offer sent from this node - case MESSAGE_TYPE.RTC_ANSWER: - this.readRelayedAnswer(origin, message[1]); - break; - - // An ICE candidate from the source node - case MESSAGE_TYPE.RTC_ICE_CANDIDATE: - this.readRelayedIceCandidate(origin, message[1]); - break; - - default: - throw new Error('Unknown message type: ' + messageType); - } -}; - -JSONProtocol.prototype.readMessage = notImplemented; -JSONProtocol.prototype.readArrayBuffer = notImplemented; -JSONProtocol.prototype.readRelay = notImplemented; - -JSONProtocol.prototype.readRelayedOffer = notImplemented; -JSONProtocol.prototype.readRelayedAnswer = notImplemented; -JSONProtocol.prototype.readRelayedIceCandidate = notImplemented; - -JSONProtocol.prototype.writeRaw = notImplemented; - -JSONProtocol.prototype.writeProtocolMessage = function(message){ - var serializedMessage = JSON.stringify(message); - this.writeRaw(serializedMessage); -}; - -JSONProtocol.prototype.writeMessage = function(message){ - if(message instanceof ArrayBuffer){ - this.writeRaw(message); - } else { - this.writeStringMessage(message); - } -}; - -JSONProtocol.prototype.writeStringMessage = function(message){ - this.writeProtocolMessage([ - this.MESSAGE_TYPE.DIRECT, - message - ]); -}; - -JSONProtocol.prototype.writeRelayedMessage = function(origin, message){ - this.writeProtocolMessage([ - this.MESSAGE_TYPE.RELAYED, - origin, - message - ]); -}; - -JSONProtocol.prototype.writeRelayMessage = function(destination, message){ - this.writeProtocolMessage([ - this.MESSAGE_TYPE.RELAY, - destination, - message - ]); -}; - -JSONProtocol.prototype.writeRelayAnswer = function(destination, description){ - this.writeRelayMessage(destination, [ - this.MESSAGE_TYPE.RTC_ANSWER, - description - ]); -}; - -JSONProtocol.prototype.writeRelayIceCandidate = function(destination, candidate){ - this.writeRelayMessage(destination, [ - this.MESSAGE_TYPE.RTC_ICE_CANDIDATE, - candidate - ]); -}; - -JSONProtocol.prototype.writeRelayOffer = function(destination, description, data){ - this.writeRelayMessage(destination, [ - this.MESSAGE_TYPE.RTC_OFFER, - description, - data - ]); -}; - -module.exports = JSONProtocol; -},{}],8:[function(require,module,exports){ -var Emitter = require('events').EventEmitter, - ConnectionManager = require('./ConnectionManager.js'), - WebSocketConnection = require('./WebSocketConnection.js'), - WebRTCConnection = require('./WebRTCConnection.js'), - its = require('its'); - -function P(emitter, connectionManager, options){ - its.defined(emitter); - its.defined(connectionManager); - - this.emitter = emitter; - this.peers = connectionManager; - - this.peers.onAdd = function(peer){ - emitter.emit('connection', peer); - }; - - this.peers.onRemove = function(peer){ - emitter.emit('disconnection', peer); - }; - - if(options && options.firewall) this.firewall = options.firewall; -} - -P.create = function(options){ - var emitter = new Emitter(), - connectionManager = new ConnectionManager(); - - return new P(emitter, connectionManager, options); -}; - -P.prototype.getPeers = function(){ - return this.peers.get(); -}; - -P.prototype.connect = function(address){ - its.string(address); - - var peers = this.peers, - peer = WebSocketConnection.create(address, this.peers, {firewall: this.firewall}); - - peers.add(peer); - - peer.on('close', function(){ - peers.remove(peer); - }); - - return peer; -}; - -P.prototype.on = function(){ - this.emitter.on.apply(this.emitter, arguments); - return this; -}; - -P.prototype.removeListener = function(){ - this.emitter.removeListener.apply(this.emitter, arguments); - return this; -}; - -module.exports = P; -},{"./ConnectionManager.js":6,"./WebRTCConnection.js":9,"./WebSocketConnection.js":10,"events":2,"its":11}],9:[function(require,module,exports){ -var Connection = require('./Connection.js'), - its = require('its'); - -var nativeRTCPeerConnection = (typeof RTCPeerConnection !== 'undefined')? RTCPeerConnection : - (typeof webkitRTCPeerConnection !== 'undefined')? webkitRTCPeerConnection : - (typeof mozRTCPeerConnection !== 'undefined')? mozRTCPeerConnection : - undefined; - -var nativeRTCSessionDescription = (typeof RTCSessionDescription !== 'undefined')? RTCSessionDescription : - (typeof mozRTCSessionDescription !== 'undefined')? mozRTCSessionDescription : - undefined; -var nativeRTCIceCandidate = (typeof RTCIceCandidate !== 'undefined')? RTCIceCandidate : - (typeof mozRTCIceCandidate !== 'undefined')? mozRTCIceCandidate : - undefined; - -function WebRTCConnection(address, peers, rtcConnection, signalingChannel, options){ - var self = this; - - its.string(address); - its.defined(peers); - its.defined(rtcConnection); - its.defined(signalingChannel); - - Connection.call(this, address, peers, options); - - this.signalingChannel = signalingChannel; - this.rtcConnection = rtcConnection; - this.rtcDataChannel = rtcConnection.createDataChannel(this.PROTOCOL_NAME, {reliable: false}); - - this.close = rtcConnection.close.bind(rtcConnection); - - this.rtcConnection.addEventListener('icecandidate', function(event){ - if(!event.candidate) return; - - self.signalingChannel.writeRelayIceCandidate(address, event.candidate); - }); - - this.rtcDataChannel.addEventListener('message', function(message){ - self.readRaw(message.data); - }); - - this.rtcDataChannel.addEventListener('open', function(event){ - self.emitter.emit('open', event); - }); - - this.rtcDataChannel.addEventListener('error', function(event){ - self.emitter.emit('error', event); - }); - - this.rtcDataChannel.addEventListener('close', function(event){ - self.emitter.emit('close', event); - }); -} - -var DEFAULT_RTC_CONFIGURATION = null; -var DEFAULT_MEDIA_CONSTRAINTS = { - optional: [{RtpDataChannels: true}], - mandatory: { - OfferToReceiveAudio: false, - OfferToReceiveVideo: false - } -}; - -WebRTCConnection.create = function(config, peers, signalingChannel, options){ - var rtcConfiguration = config.rtcConfiguration || DEFAULT_RTC_CONFIGURATION, - mediaConstraints = config.mediaConstraints || DEFAULT_MEDIA_CONSTRAINTS, - rtcConnection = new nativeRTCPeerConnection(rtcConfiguration, mediaConstraints); - - return new WebRTCConnection(config.address, peers, rtcConnection, signalingChannel, options); -}; - -WebRTCConnection.prototype = Object.create(Connection.prototype); - -WebRTCConnection.prototype.writeRaw = function(message){ - switch(this.rtcDataChannel.readyState){ - case 'connecting': - throw new Error('Can\'t send a message while RTCDataChannel connecting'); - case 'open': - this.rtcDataChannel.send(message); - break; - case 'closing': - case 'closed': - throw new Error('Can\'t send a message while RTCDataChannel is closing or closed'); - } -}; - -WebRTCConnection.prototype.readAnswer = function(description){ - var rtcSessionDescription = new nativeRTCSessionDescription(description); - - this.rtcConnection.setRemoteDescription(rtcSessionDescription); -}; - -WebRTCConnection.prototype.readOffer = function(description){ - var rtcSessionDescription = new nativeRTCSessionDescription(description); - - this.rtcConnection.setRemoteDescription(rtcSessionDescription); -}; - -WebRTCConnection.prototype.readIceCandidate = function(candidate){ - var emitter = this.emitter; - this.rtcConnection.addIceCandidate(new nativeRTCIceCandidate(candidate)); -}; - -WebRTCConnection.prototype.writeAnswer = function(){ - var emitter = this.emitter, - address = this.address, - rtcConnection = this.rtcConnection, - signalingChannel = this.signalingChannel; - - function onError(err){ emitter.emit('error', err); } - - rtcConnection.createAnswer(function(description){ - rtcConnection.setLocalDescription(description, function(){ - signalingChannel.writeRelayAnswer(address, description); - }, onError); - }, onError); -}; - -WebRTCConnection.prototype.writeOffer = function(config){ - var emitter = this.emitter, - address = this.address, - rtcConnection = this.rtcConnection, - signalingChannel = this.signalingChannel; - - function onError(err){ emitter.emit('error', err); } - - rtcConnection.createOffer(function(description){ - rtcConnection.setLocalDescription(description, function(){ - signalingChannel.writeRelayOffer(address, description, config.offerData); - }, onError); - }, onError, config.mediaConstraints || DEFAULT_MEDIA_CONSTRAINTS); -}; - -WebRTCConnection.prototype.getReadyState = function(){ - return this.rtcDataChannel.readyState; -}; - - -// Solves the circular dependency with Connection.js -Connection.createWebRTCConnection = WebRTCConnection.create; - -module.exports = WebRTCConnection; -},{"./Connection.js":5,"its":11}],10:[function(require,module,exports){ -var Connection = require('./Connection.js'); - -function WebSocketConnection(address, peers, webSocket, options){ - var self = this; - - Connection.call(this, address, peers, options); - - this.webSocket = webSocket; - - this.close = webSocket.close.bind(webSocket); - - this.webSocket.addEventListener('message', function(message){ - self.readRaw(message.data); - }); - - this.webSocket.addEventListener('open', function(event){ - self.emitter.emit('open', event); - }); - - this.webSocket.addEventListener('error', function(event){ - self.emitter.emit('error', event); - }); - - this.webSocket.addEventListener('close', function(event){ - self.emitter.emit('close', event); - }); -} - -WebSocketConnection.create = function(address, peers, options){ - var webSocket = new WebSocket(address, WebSocketConnection.prototype.PROTOCOL_NAME); - return new WebSocketConnection(address, peers, webSocket, options); -}; - -WebSocketConnection.prototype = Object.create(Connection.prototype); -WebSocketConnection.prototype.writeRaw = function(message){ - switch(this.webSocket.readyState){ - case WebSocket.CONNECTING: - throw new Error("Can't send a message while WebSocket connecting"); - - case WebSocket.OPEN: - this.webSocket.send(message); - break; - - case WebSocket.CLOSING: - case WebSocket.CLOSED: - throw new Error("Can't send a message while WebSocket is closing or closed"); - } -}; - -WebSocketConnection.prototype.getReadyState = function(){ - switch(this.webSocket.readyState){ - case WebSocket.CONNECTING: - return 'connecting'; - case WebSocket.OPEN: - return 'open'; - case WebSocket.CLOSING: - return 'closing'; - case WebSocket.CLOSED: - return 'closed'; - } -}; - -module.exports = WebSocketConnection; -},{"./Connection.js":5}],11:[function(require,module,exports){ module.exports = require('./lib/its.js'); -},{"./lib/its.js":12}],12:[function(require,module,exports){ +},{"./lib/its.js":5}],5:[function(require,module,exports){ // Helpers var slice = Array.prototype.slice; var toString = Object.prototype.toString; @@ -1548,9 +978,9 @@ its.range = function(expression, message){ return expression; }; -},{}],13:[function(require,module,exports){ +},{}],6:[function(require,module,exports){ module.exports = require('./lib/Plink.js'); -},{"./lib/Plink.js":14}],14:[function(require,module,exports){ +},{"./lib/Plink.js":7}],7:[function(require,module,exports){ var P = require('internet'); var PlinkServer = require('./PlinkServer.js'); @@ -1569,7 +999,7 @@ Plink.prototype.connect = function(address){ return PlinkServer.create(onramp); }; -},{"./PlinkServer.js":15,"internet":4}],15:[function(require,module,exports){ +},{"./PlinkServer.js":8,"internet":9}],8:[function(require,module,exports){ var when = require('when'); function PlinkServer(onramp){ @@ -1718,7 +1148,661 @@ PlinkServer.prototype.messageHandler = function(message){ } } }; -},{"when":16}],16:[function(require,module,exports){ +},{"when":16}],9:[function(require,module,exports){ +module.exports = require('./lib/P.js'); +},{"./lib/P.js":13}],10:[function(require,module,exports){ +var JSONProtocol = require('./JSONProtocol.js'), + its = require('its'), + Emitter = require('events').EventEmitter; + +function notImplemented(){ + throw new Error('This method is not implemented'); +} + +function Connection(address, peers, options){ + its.string(address); + its.defined(peers); + + this.address = address; + this.peers = peers; + + if(options){ + if(options.emitter) this.emitter = options.emitter; + if(options.firewall) this.acceptRTCConnection = options.firewall; + } + + if(!this.emitter) this.emitter = new Connection.Emitter(); +} + +// Circular dependency solved in WebRTCConnection.js +Connection.createWebRTCConnection = null; +Connection.Emitter = Emitter; + +Connection.prototype = Object.create(JSONProtocol.prototype); + +Connection.prototype.on = function(){ + this.emitter.on.apply(this.emitter, arguments); + return this; +}; + +Connection.prototype.removeListener = function(){ + this.emitter.removeListener.apply(this.emitter, arguments); + return this; +}; + +Connection.prototype.send = JSONProtocol.prototype.writeMessage; + +Connection.prototype.getPeer = function(address){ + return this.peers.get(address); +}; + +Connection.prototype.addPeer = function(peer){ + return this.peers.add(peer); +}; + +Connection.prototype.getPeers = function() { + return this.peers.get(); +}; + +function isString(candidate){ + return Object.prototype.toString.call(candidate) === '[object String]'; +} + +Connection.prototype.connect = function(config){ + if(isString(config)){ + config = {address: config}; + } + + var self = this, + firewall = config.firewall || this.firewall, + peer = Connection.createWebRTCConnection(config, this.peers, this, {firewall: firewall}); + + peer.writeOffer(config); + + this.peers.add(peer); + + peer.on('close', function(){ + self.peers.remove(peer); + self.emitter.emit('disconnection', peer); + }); + + this.emitter.emit('connection', peer); + + return peer; +}; + +Connection.prototype.readMessage = function(message){ + this.emitter.emit('message', message); +}; + +Connection.prototype.readArrayBuffer = function(message){ + this.emitter.emit('arraybuffer', message); +}; + +Connection.prototype.acceptRTCConnection = function(description, data){ + return true; +}; + +Connection.prototype.readRelay = function(peerAddress, message){ + var peer = this.getPeer(peerAddress); + + if(!peer){ + this.emitter.emit('error', new Error("Unknown peer at address: " + peerAddress)); + return; + } + + peer.writeRelayedMessage(this.address, message); +}; + +Connection.prototype.readRelayedIceCandidate = function(peerAddress, candidate){ + var peer = this.getPeer(peerAddress); + + if(!peer){ + this.emitter.emit('error', new Error("Unknown peer at address: " + peerAddress)); + return; + } + + peer.readIceCandidate(candidate); +}; + +Connection.prototype.readRelayedOffer = function(peerAddress, description, data){ + if(!this.acceptRTCConnection(description, data)) return false; + + var self = this, + peer = Connection.createWebRTCConnection({address:peerAddress}, this.peers, this, {firewall: this.firewall}); + + this.addPeer(peer); + + peer.on('close', function(){ + self.peers.remove(peer); + self.emitter.emit('disconnection', peer); + }); + + peer.readOffer(description); + peer.writeAnswer(); + + this.emitter.emit('connection', peer); +}; + +Connection.prototype.readRelayedAnswer = function(peerAddress, description){ + var peer = this.getPeer(peerAddress); + + if(!peer){ + this.emitter.emit('error', new Error("Unknown peer at address: " + peerAddress)); + return; + } + + peer.readAnswer(description); +}; + +Connection.prototype.close = notImplemented; // implemented higher up +Connection.prototype.getReadyState = notImplemented; // implemented higher up + +Connection.prototype.isOpen = function(){ + return this.getReadyState() === 'open'; +}; + +module.exports = Connection; + +},{"./JSONProtocol.js":12,"events":2,"its":4}],11:[function(require,module,exports){ +var its = require('its'); + +function noop(){} + +function ConnectionManager(){ + this.connectionMap = {}; + this.connectionList = []; +} + +ConnectionManager.prototype.get = function(address){ + if(address === undefined) return this.connectionList.slice(); + + return this.connectionMap[address]; +}; + +ConnectionManager.prototype.add = function(connection) { + its.defined(connection); + + var address = connection.address; + its.string(address); + + if(address in this.connectionMap) return false; + + this.connectionMap[address] = connection; + this.connectionList.push(connection); + + this.onAdd(connection); + return true; +}; +ConnectionManager.prototype.onAdd = noop; + +ConnectionManager.prototype.remove = function(connection){ + its.defined(connection); + + var address = connection.address; + its.string(address); + + var mappedConnection = this.connectionMap[address]; + if(!mappedConnection || mappedConnection !== connection) return false; + + delete this.connectionMap[address]; + + var index = this.connectionList.indexOf(connection); + this.connectionList.splice(index, 1); + + this.onRemove(connection); + return true; +}; +ConnectionManager.prototype.onRemove = noop; + +module.exports = ConnectionManager; +},{"its":4}],12:[function(require,module,exports){ +function notImplemented(){ + throw new Error('This method is not implemented'); +} + +function JSONProtocol(){} + +JSONProtocol.prototype.PROTOCOL_NAME = 'p'; + +JSONProtocol.prototype.MESSAGE_TYPE = { + DIRECT: 0, // [0, message] + + RTC_OFFER: 3, // [3, description, data] + RTC_ANSWER: 4, // [4, description] + RTC_ICE_CANDIDATE: 5, // [5, candidate] + + RELAY: 6, // [6, address, message] + RELAYED: 7 // [7, address, message] +}; + +JSONProtocol.prototype.readRaw = function(message){ + if(message instanceof ArrayBuffer){ + this.readArrayBuffer(message); + } else { + this.readProtocolMessage(JSON.parse(message)); + } +}; + +JSONProtocol.prototype.readProtocolMessage = function(message){ + var MESSAGE_TYPE = this.MESSAGE_TYPE, + messageType = message[0]; + + switch(messageType){ + // This is a message from the remote node to this one. + case MESSAGE_TYPE.DIRECT: + this.readMessage(message[1]); + break; + + // The message was relayed by the peer on behalf of + // a third party peer, identified by "thirdPartyPeerId". + // This means that the peer is acting as a signalling + // channel on behalf of the third party peer. + case MESSAGE_TYPE.RELAYED: + this.readRelayedMessage(message[1], message[2]); + break; + + // The message is intended for another peer, identified + // by "peerId", which is also connected to this node. + // This means that the peer is using this connection + // as a signalling channel in order to establish a connection + // to the other peer identified "peerId". + case MESSAGE_TYPE.RELAY: + this.readRelay(message[1], message[2]); + break; + + default: + throw new Error('Unknown message type: ' + messageType); + } +}; + +JSONProtocol.prototype.readRelayedMessage = function(origin, message){ + var MESSAGE_TYPE = this.MESSAGE_TYPE, + messageType = message[0]; + + switch(messageType){ + // An initial connection request from a third party peer + case MESSAGE_TYPE.RTC_OFFER: + this.readRelayedOffer(origin, message[1], message[2]); + break; + + // An answer to an RTC offer sent from this node + case MESSAGE_TYPE.RTC_ANSWER: + this.readRelayedAnswer(origin, message[1]); + break; + + // An ICE candidate from the source node + case MESSAGE_TYPE.RTC_ICE_CANDIDATE: + this.readRelayedIceCandidate(origin, message[1]); + break; + + default: + throw new Error('Unknown message type: ' + messageType); + } +}; + +JSONProtocol.prototype.readMessage = notImplemented; +JSONProtocol.prototype.readArrayBuffer = notImplemented; +JSONProtocol.prototype.readRelay = notImplemented; + +JSONProtocol.prototype.readRelayedOffer = notImplemented; +JSONProtocol.prototype.readRelayedAnswer = notImplemented; +JSONProtocol.prototype.readRelayedIceCandidate = notImplemented; + +JSONProtocol.prototype.writeRaw = notImplemented; + +JSONProtocol.prototype.writeProtocolMessage = function(message){ + var serializedMessage = JSON.stringify(message); + this.writeRaw(serializedMessage); +}; + +JSONProtocol.prototype.writeMessage = function(message){ + if(message instanceof ArrayBuffer){ + this.writeRaw(message); + } else { + this.writeStringMessage(message); + } +}; + +JSONProtocol.prototype.writeStringMessage = function(message){ + this.writeProtocolMessage([ + this.MESSAGE_TYPE.DIRECT, + message + ]); +}; + +JSONProtocol.prototype.writeRelayedMessage = function(origin, message){ + this.writeProtocolMessage([ + this.MESSAGE_TYPE.RELAYED, + origin, + message + ]); +}; + +JSONProtocol.prototype.writeRelayMessage = function(destination, message){ + this.writeProtocolMessage([ + this.MESSAGE_TYPE.RELAY, + destination, + message + ]); +}; + +JSONProtocol.prototype.writeRelayAnswer = function(destination, description){ + this.writeRelayMessage(destination, [ + this.MESSAGE_TYPE.RTC_ANSWER, + description + ]); +}; + +JSONProtocol.prototype.writeRelayIceCandidate = function(destination, candidate){ + this.writeRelayMessage(destination, [ + this.MESSAGE_TYPE.RTC_ICE_CANDIDATE, + candidate + ]); +}; + +JSONProtocol.prototype.writeRelayOffer = function(destination, description, data){ + this.writeRelayMessage(destination, [ + this.MESSAGE_TYPE.RTC_OFFER, + description, + data + ]); +}; + +module.exports = JSONProtocol; +},{}],13:[function(require,module,exports){ +var Emitter = require('events').EventEmitter, + ConnectionManager = require('./ConnectionManager.js'), + WebSocketConnection = require('./WebSocketConnection.js'), + WebRTCConnection = require('./WebRTCConnection.js'), + its = require('its'); + +function P(emitter, connectionManager, options){ + its.defined(emitter); + its.defined(connectionManager); + + this.emitter = emitter; + this.peers = connectionManager; + + this.peers.onAdd = function(peer){ + emitter.emit('connection', peer); + }; + + this.peers.onRemove = function(peer){ + emitter.emit('disconnection', peer); + }; + + if(options && options.firewall) this.firewall = options.firewall; +} + +P.create = function(options){ + var emitter = new Emitter(), + connectionManager = new ConnectionManager(); + + return new P(emitter, connectionManager, options); +}; + +P.prototype.getPeers = function(){ + return this.peers.get(); +}; + +P.prototype.connect = function(address){ + its.string(address); + + var peers = this.peers, + peer = WebSocketConnection.create(address, this.peers, {firewall: this.firewall}); + + peers.add(peer); + + peer.on('close', function(){ + peers.remove(peer); + }); + + return peer; +}; + +P.prototype.on = function(){ + this.emitter.on.apply(this.emitter, arguments); + return this; +}; + +P.prototype.removeListener = function(){ + this.emitter.removeListener.apply(this.emitter, arguments); + return this; +}; + +module.exports = P; +},{"./ConnectionManager.js":11,"./WebRTCConnection.js":14,"./WebSocketConnection.js":15,"events":2,"its":4}],14:[function(require,module,exports){ +var Connection = require('./Connection.js'), + its = require('its'); + +var nativeRTCPeerConnection = (typeof RTCPeerConnection !== 'undefined')? RTCPeerConnection : + (typeof webkitRTCPeerConnection !== 'undefined')? webkitRTCPeerConnection : + (typeof mozRTCPeerConnection !== 'undefined')? mozRTCPeerConnection : + undefined; + +var nativeRTCSessionDescription = (typeof RTCSessionDescription !== 'undefined')? RTCSessionDescription : + (typeof mozRTCSessionDescription !== 'undefined')? mozRTCSessionDescription : + undefined; +var nativeRTCIceCandidate = (typeof RTCIceCandidate !== 'undefined')? RTCIceCandidate : + (typeof mozRTCIceCandidate !== 'undefined')? mozRTCIceCandidate : + undefined; + +function WebRTCConnection(address, peers, rtcConnection, signalingChannel, options){ + var self = this; + + its.string(address); + its.defined(peers); + its.defined(rtcConnection); + its.defined(signalingChannel); + + Connection.call(this, address, peers, options); + + this.signalingChannel = signalingChannel; + this.rtcConnection = rtcConnection; + this.rtcDataChannel = rtcConnection.createDataChannel(this.PROTOCOL_NAME, {reliable: false}); + + this.close = rtcConnection.close.bind(rtcConnection); + + this.rtcConnection.addEventListener('icecandidate', function(event){ + if(!event.candidate) return; + + self.signalingChannel.writeRelayIceCandidate(address, event.candidate); + }); + + this.rtcDataChannel.addEventListener('message', function(message){ + // console.log('rtcDataChannel emit message'); + self.readRaw(message.data); + }); + + this.rtcDataChannel.addEventListener('open', function(event){ + // console.log('rtcDataChannel emit open'); + self.emitter.emit('open', event); + }); + + this.rtcDataChannel.addEventListener('error', function(event){ + // console.log('rtcDataChannel emit error'); + self.emitter.emit('error', event); + }); + + this.rtcDataChannel.addEventListener('close', function(event){ + // console.log('rtcDataChannel emit close'); + // new Image().src = 'http://localhost:7000/?rtcDataChannel emit close'; + self.emitter.emit('close', event); + }); + + this.rtcDataChannel.addEventListener('removestream', function(event){ + // console.log('rtcDataChannel emit removestream'); + // new Image().src = 'http://localhost:7000/?rtcDataChannel emit removestream'; + self.emitter.emit('removestream', event); + }); + + this.rtcDataChannel.addEventListener('icestatechange', function(event){ + // console.log('rtcDataChannel emit icestatechange'); + // new Image().src = 'http://localhost:7000/?rtcDataChannel emit nicestatechange'; + self.emitter.emit('icestatechange', event); + }); + + this.rtcDataChannel.addEventListener('icechange', function(event){ + // console.log('rtcDataChannel emit onicechange'); + // new Image().src = 'http://localhost:7000/?rtcDataChannel emit icechange'; + self.emitter.emit('icechange', event); + }); +} + +var DEFAULT_RTC_CONFIGURATION = null; +var DEFAULT_MEDIA_CONSTRAINTS = { + optional: [{RtpDataChannels: true}], + mandatory: { + OfferToReceiveAudio: false, + OfferToReceiveVideo: false + } +}; + +WebRTCConnection.create = function(config, peers, signalingChannel, options){ + var rtcConfiguration = config.rtcConfiguration || DEFAULT_RTC_CONFIGURATION, + mediaConstraints = config.mediaConstraints || DEFAULT_MEDIA_CONSTRAINTS, + rtcConnection = new nativeRTCPeerConnection(rtcConfiguration, mediaConstraints); + + return new WebRTCConnection(config.address, peers, rtcConnection, signalingChannel, options); +}; + +WebRTCConnection.prototype = Object.create(Connection.prototype); + +WebRTCConnection.prototype.writeRaw = function(message){ + switch(this.rtcDataChannel.readyState){ + case 'connecting': + throw new Error('Can\'t send a message while RTCDataChannel connecting'); + case 'open': + this.rtcDataChannel.send(message); + break; + case 'closing': + case 'closed': + throw new Error('Can\'t send a message while RTCDataChannel is closing or closed'); + } +}; + +WebRTCConnection.prototype.readAnswer = function(description){ + var rtcSessionDescription = new nativeRTCSessionDescription(description); + + this.rtcConnection.setRemoteDescription(rtcSessionDescription); +}; + +WebRTCConnection.prototype.readOffer = function(description){ + var rtcSessionDescription = new nativeRTCSessionDescription(description); + + this.rtcConnection.setRemoteDescription(rtcSessionDescription); +}; + +WebRTCConnection.prototype.readIceCandidate = function(candidate){ + var emitter = this.emitter; + this.rtcConnection.addIceCandidate(new nativeRTCIceCandidate(candidate)); +}; + +WebRTCConnection.prototype.writeAnswer = function(){ + var emitter = this.emitter, + address = this.address, + rtcConnection = this.rtcConnection, + signalingChannel = this.signalingChannel; + + function onError(err){ emitter.emit('error', err); } + + rtcConnection.createAnswer(function(description){ + rtcConnection.setLocalDescription(description, function(){ + signalingChannel.writeRelayAnswer(address, description); + }, onError); + }, onError); +}; + +WebRTCConnection.prototype.writeOffer = function(config){ + var emitter = this.emitter, + address = this.address, + rtcConnection = this.rtcConnection, + signalingChannel = this.signalingChannel; + + function onError(err){ emitter.emit('error', err); } + + rtcConnection.createOffer(function(description){ + rtcConnection.setLocalDescription(description, function(){ + signalingChannel.writeRelayOffer(address, description, config.offerData); + }, onError); + }, onError, config.mediaConstraints || DEFAULT_MEDIA_CONSTRAINTS); +}; + +WebRTCConnection.prototype.getReadyState = function(){ + return this.rtcDataChannel.readyState; +}; + + +// Solves the circular dependency with Connection.js +Connection.createWebRTCConnection = WebRTCConnection.create; + +module.exports = WebRTCConnection; +},{"./Connection.js":10,"its":4}],15:[function(require,module,exports){ +var Connection = require('./Connection.js'); + +function WebSocketConnection(address, peers, webSocket, options){ + var self = this; + + Connection.call(this, address, peers, options); + + this.webSocket = webSocket; + + this.close = webSocket.close.bind(webSocket); + + this.webSocket.addEventListener('message', function(message){ + self.readRaw(message.data); + }); + + this.webSocket.addEventListener('open', function(event){ + self.emitter.emit('open', event); + }); + + this.webSocket.addEventListener('error', function(event){ + self.emitter.emit('error', event); + }); + + this.webSocket.addEventListener('close', function(event){ + self.emitter.emit('close', event); + }); +} + +WebSocketConnection.create = function(address, peers, options){ + var webSocket = new WebSocket(address, WebSocketConnection.prototype.PROTOCOL_NAME); + return new WebSocketConnection(address, peers, webSocket, options); +}; + +WebSocketConnection.prototype = Object.create(Connection.prototype); +WebSocketConnection.prototype.writeRaw = function(message){ + switch(this.webSocket.readyState){ + case WebSocket.CONNECTING: + throw new Error("Can't send a message while WebSocket connecting"); + + case WebSocket.OPEN: + this.webSocket.send(message); + break; + + case WebSocket.CLOSING: + case WebSocket.CLOSED: + throw new Error("Can't send a message while WebSocket is closing or closed"); + } +}; + +WebSocketConnection.prototype.getReadyState = function(){ + switch(this.webSocket.readyState){ + case WebSocket.CONNECTING: + return 'connecting'; + case WebSocket.OPEN: + return 'open'; + case WebSocket.CLOSING: + return 'closing'; + case WebSocket.CLOSED: + return 'closed'; + } +}; + +module.exports = WebSocketConnection; +},{"./Connection.js":10}],16:[function(require,module,exports){ (function (process){ /** @license MIT License (c) copyright 2011-2013 original author or authors */ @@ -2771,15 +2855,20 @@ function toggleFullScreen() { function lockOrientation(orientation) { - var lo = (window.screen.LockOrientation || - window.screen.mozLockOrientation || - window.screen.webkitLockOrientation || - window.screen.msLockOrientation); - if (!lo) { - return warn('Orientation could not be locked'); + // Must check each individual because of a Firefox TypeError + if ('lockOrientation' in window.screen) { + return window.screen.lockOrientation(orientation); } - - return lo(orientation); + if ('mozLockOrientation' in window.screen) { + return window.screen.mozLockOrientation(orientation); + } + if ('webkitLockOrientation' in window.screen) { + return window.screen.webkitLockOrientation(orientation); + } + if ('msLockOrientation' in window.screen) { + return window.screen.msLockOrientation(orientation); + } + return warn('Orientation could not be locked'); } diff --git a/dist/js/gamepad-client.min.js b/dist/js/gamepad-client.min.js index 3d333fa..c091093 100755 --- a/dist/js/gamepad-client.min.js +++ b/dist/js/gamepad-client.min.js @@ -1 +1 @@ -!function e(t,n,r){function i(s,c){if(!n[s]){if(!t[s]){var a="function"==typeof require&&require;if(!c&&a)return a(s,!0);if(o)return o(s,!0);var u=new Error("Cannot find module '"+s+"'");throw u.code="MODULE_NOT_FOUND",u}var l=n[s]={exports:{}};t[s][0].call(l.exports,function(e){var n=t[s][1][e];return i(n?n:e)},l,l.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,c,a,u;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,c=new Array(i-1),a=1;i>a;a++)c[a-1]=arguments[a];n.apply(this,c)}else if(o(n)){for(i=arguments.length,c=new Array(i-1),a=1;i>a;a++)c[a-1]=arguments[a];for(u=n.slice(),i=u.length,a=0;i>a;a++)u[a].apply(this,c)}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,c;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(c=s;c-->0;)if(n[c]===t||n[c].listener&&n[c].listener===t){i=c;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}},{}],3:[function(e,t){function n(){}var r=t.exports={};r.nextTick=function(){var e="undefined"!=typeof window&&window.setImmediate,t="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(e)return function(e){return window.setImmediate(e)};if(t){var n=[];return window.addEventListener("message",function(e){var t=e.source;if((t===window||null===t)&&"process-tick"===e.data&&(e.stopPropagation(),n.length>0)){var r=n.shift();r()}},!0),function(e){n.push(e),window.postMessage("process-tick","*")}}return function(e){setTimeout(e,0)}}(),r.title="browser",r.browser=!0,r.env={},r.argv=[],r.on=n,r.addListener=n,r.once=n,r.off=n,r.removeListener=n,r.removeAllListeners=n,r.emit=n,r.binding=function(){throw new Error("process.binding is not supported")},r.cwd=function(){return"/"},r.chdir=function(){throw new Error("process.chdir is not supported")}},{}],4:[function(e,t){t.exports=e("./lib/P.js")},{"./lib/P.js":8}],5:[function(e,t){function n(){throw new Error("This method is not implemented")}function r(e,t,n){s.string(e),s.defined(t),this.address=e,this.peers=t,n&&(n.emitter&&(this.emitter=n.emitter),n.firewall&&(this.acceptRTCConnection=n.firewall)),this.emitter||(this.emitter=new r.Emitter)}function i(e){return"[object String]"===Object.prototype.toString.call(e)}var o=e("./JSONProtocol.js"),s=e("its"),c=e("events").EventEmitter;r.createWebRTCConnection=null,r.Emitter=c,r.prototype=Object.create(o.prototype),r.prototype.on=function(){return this.emitter.on.apply(this.emitter,arguments),this},r.prototype.removeListener=function(){return this.emitter.removeListener.apply(this.emitter,arguments),this},r.prototype.send=o.prototype.writeMessage,r.prototype.getPeer=function(e){return this.peers.get(e)},r.prototype.addPeer=function(e){return this.peers.add(e)},r.prototype.getPeers=function(){return this.peers.get()},r.prototype.connect=function(e){i(e)&&(e={address:e});var t=this,n=e.firewall||this.firewall,o=r.createWebRTCConnection(e,this.peers,this,{firewall:n});return o.writeOffer(e),this.peers.add(o),o.on("close",function(){t.peers.remove(o),t.emitter.emit("disconnection",o)}),this.emitter.emit("connection",o),o},r.prototype.readMessage=function(e){this.emitter.emit("message",e)},r.prototype.readArrayBuffer=function(e){this.emitter.emit("arraybuffer",e)},r.prototype.acceptRTCConnection=function(){return!0},r.prototype.readRelay=function(e,t){var n=this.getPeer(e);return n?void n.writeRelayedMessage(this.address,t):void this.emitter.emit("error",new Error("Unknown peer at address: "+e))},r.prototype.readRelayedIceCandidate=function(e,t){var n=this.getPeer(e);return n?void n.readIceCandidate(t):void this.emitter.emit("error",new Error("Unknown peer at address: "+e))},r.prototype.readRelayedOffer=function(e,t,n){if(!this.acceptRTCConnection(t,n))return!1;var i=this,o=r.createWebRTCConnection({address:e},this.peers,this,{firewall:this.firewall});this.addPeer(o),o.on("close",function(){i.peers.remove(o),i.emitter.emit("disconnection",o)}),o.readOffer(t),o.writeAnswer(),this.emitter.emit("connection",o)},r.prototype.readRelayedAnswer=function(e,t){var n=this.getPeer(e);return n?void n.readAnswer(t):void this.emitter.emit("error",new Error("Unknown peer at address: "+e))},r.prototype.close=n,r.prototype.getReadyState=n,r.prototype.isOpen=function(){return"open"===this.getReadyState()},t.exports=r},{"./JSONProtocol.js":7,events:2,its:11}],6:[function(e,t){function n(){}function r(){this.connectionMap={},this.connectionList=[]}var i=e("its");r.prototype.get=function(e){return void 0===e?this.connectionList.slice():this.connectionMap[e]},r.prototype.add=function(e){i.defined(e);var t=e.address;return i.string(t),t in this.connectionMap?!1:(this.connectionMap[t]=e,this.connectionList.push(e),this.onAdd(e),!0)},r.prototype.onAdd=n,r.prototype.remove=function(e){i.defined(e);var t=e.address;i.string(t);var n=this.connectionMap[t];if(!n||n!==e)return!1;delete this.connectionMap[t];var r=this.connectionList.indexOf(e);return this.connectionList.splice(r,1),this.onRemove(e),!0},r.prototype.onRemove=n,t.exports=r},{its:11}],7:[function(e,t){function n(){throw new Error("This method is not implemented")}function r(){}r.prototype.PROTOCOL_NAME="p",r.prototype.MESSAGE_TYPE={DIRECT:0,RTC_OFFER:3,RTC_ANSWER:4,RTC_ICE_CANDIDATE:5,RELAY:6,RELAYED:7},r.prototype.readRaw=function(e){e instanceof ArrayBuffer?this.readArrayBuffer(e):this.readProtocolMessage(JSON.parse(e))},r.prototype.readProtocolMessage=function(e){var t=this.MESSAGE_TYPE,n=e[0];switch(n){case t.DIRECT:this.readMessage(e[1]);break;case t.RELAYED:this.readRelayedMessage(e[1],e[2]);break;case t.RELAY:this.readRelay(e[1],e[2]);break;default:throw new Error("Unknown message type: "+n)}},r.prototype.readRelayedMessage=function(e,t){var n=this.MESSAGE_TYPE,r=t[0];switch(r){case n.RTC_OFFER:this.readRelayedOffer(e,t[1],t[2]);break;case n.RTC_ANSWER:this.readRelayedAnswer(e,t[1]);break;case n.RTC_ICE_CANDIDATE:this.readRelayedIceCandidate(e,t[1]);break;default:throw new Error("Unknown message type: "+r)}},r.prototype.readMessage=n,r.prototype.readArrayBuffer=n,r.prototype.readRelay=n,r.prototype.readRelayedOffer=n,r.prototype.readRelayedAnswer=n,r.prototype.readRelayedIceCandidate=n,r.prototype.writeRaw=n,r.prototype.writeProtocolMessage=function(e){var t=JSON.stringify(e);this.writeRaw(t)},r.prototype.writeMessage=function(e){e instanceof ArrayBuffer?this.writeRaw(e):this.writeStringMessage(e)},r.prototype.writeStringMessage=function(e){this.writeProtocolMessage([this.MESSAGE_TYPE.DIRECT,e])},r.prototype.writeRelayedMessage=function(e,t){this.writeProtocolMessage([this.MESSAGE_TYPE.RELAYED,e,t])},r.prototype.writeRelayMessage=function(e,t){this.writeProtocolMessage([this.MESSAGE_TYPE.RELAY,e,t])},r.prototype.writeRelayAnswer=function(e,t){this.writeRelayMessage(e,[this.MESSAGE_TYPE.RTC_ANSWER,t])},r.prototype.writeRelayIceCandidate=function(e,t){this.writeRelayMessage(e,[this.MESSAGE_TYPE.RTC_ICE_CANDIDATE,t])},r.prototype.writeRelayOffer=function(e,t,n){this.writeRelayMessage(e,[this.MESSAGE_TYPE.RTC_OFFER,t,n])},t.exports=r},{}],8:[function(e,t){function n(e,t,n){s.defined(e),s.defined(t),this.emitter=e,this.peers=t,this.peers.onAdd=function(t){e.emit("connection",t)},this.peers.onRemove=function(t){e.emit("disconnection",t)},n&&n.firewall&&(this.firewall=n.firewall)}var r=e("events").EventEmitter,i=e("./ConnectionManager.js"),o=e("./WebSocketConnection.js"),s=(e("./WebRTCConnection.js"),e("its"));n.create=function(e){var t=new r,o=new i;return new n(t,o,e)},n.prototype.getPeers=function(){return this.peers.get()},n.prototype.connect=function(e){s.string(e);var t=this.peers,n=o.create(e,this.peers,{firewall:this.firewall});return t.add(n),n.on("close",function(){t.remove(n)}),n},n.prototype.on=function(){return this.emitter.on.apply(this.emitter,arguments),this},n.prototype.removeListener=function(){return this.emitter.removeListener.apply(this.emitter,arguments),this},t.exports=n},{"./ConnectionManager.js":6,"./WebRTCConnection.js":9,"./WebSocketConnection.js":10,events:2,its:11}],9:[function(e,t){function n(e,t,n,o,s){var c=this;i.string(e),i.defined(t),i.defined(n),i.defined(o),r.call(this,e,t,s),this.signalingChannel=o,this.rtcConnection=n,this.rtcDataChannel=n.createDataChannel(this.PROTOCOL_NAME,{reliable:!1}),this.close=n.close.bind(n),this.rtcConnection.addEventListener("icecandidate",function(t){t.candidate&&c.signalingChannel.writeRelayIceCandidate(e,t.candidate)}),this.rtcDataChannel.addEventListener("message",function(e){c.readRaw(e.data)}),this.rtcDataChannel.addEventListener("open",function(e){c.emitter.emit("open",e)}),this.rtcDataChannel.addEventListener("error",function(e){c.emitter.emit("error",e)}),this.rtcDataChannel.addEventListener("close",function(e){c.emitter.emit("close",e)})}var r=e("./Connection.js"),i=e("its"),o="undefined"!=typeof RTCPeerConnection?RTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection?webkitRTCPeerConnection:"undefined"!=typeof mozRTCPeerConnection?mozRTCPeerConnection:void 0,s="undefined"!=typeof RTCSessionDescription?RTCSessionDescription:"undefined"!=typeof mozRTCSessionDescription?mozRTCSessionDescription:void 0,c="undefined"!=typeof RTCIceCandidate?RTCIceCandidate:"undefined"!=typeof mozRTCIceCandidate?mozRTCIceCandidate:void 0,a=null,u={optional:[{RtpDataChannels:!0}],mandatory:{OfferToReceiveAudio:!1,OfferToReceiveVideo:!1}};n.create=function(e,t,r,i){var s=e.rtcConfiguration||a,c=e.mediaConstraints||u,l=new o(s,c);return new n(e.address,t,l,r,i)},n.prototype=Object.create(r.prototype),n.prototype.writeRaw=function(e){switch(this.rtcDataChannel.readyState){case"connecting":throw new Error("Can't send a message while RTCDataChannel connecting");case"open":this.rtcDataChannel.send(e);break;case"closing":case"closed":throw new Error("Can't send a message while RTCDataChannel is closing or closed")}},n.prototype.readAnswer=function(e){var t=new s(e);this.rtcConnection.setRemoteDescription(t)},n.prototype.readOffer=function(e){var t=new s(e);this.rtcConnection.setRemoteDescription(t)},n.prototype.readIceCandidate=function(e){this.emitter,this.rtcConnection.addIceCandidate(new c(e))},n.prototype.writeAnswer=function(){function e(e){t.emit("error",e)}var t=this.emitter,n=this.address,r=this.rtcConnection,i=this.signalingChannel;r.createAnswer(function(t){r.setLocalDescription(t,function(){i.writeRelayAnswer(n,t)},e)},e)},n.prototype.writeOffer=function(e){function t(e){n.emit("error",e)}var n=this.emitter,r=this.address,i=this.rtcConnection,o=this.signalingChannel;i.createOffer(function(n){i.setLocalDescription(n,function(){o.writeRelayOffer(r,n,e.offerData)},t)},t,e.mediaConstraints||u)},n.prototype.getReadyState=function(){return this.rtcDataChannel.readyState},r.createWebRTCConnection=n.create,t.exports=n},{"./Connection.js":5,its:11}],10:[function(e,t){function n(e,t,n,i){var o=this;r.call(this,e,t,i),this.webSocket=n,this.close=n.close.bind(n),this.webSocket.addEventListener("message",function(e){o.readRaw(e.data)}),this.webSocket.addEventListener("open",function(e){o.emitter.emit("open",e)}),this.webSocket.addEventListener("error",function(e){o.emitter.emit("error",e)}),this.webSocket.addEventListener("close",function(e){o.emitter.emit("close",e)})}var r=e("./Connection.js");n.create=function(e,t,r){var i=new WebSocket(e,n.prototype.PROTOCOL_NAME);return new n(e,t,i,r)},n.prototype=Object.create(r.prototype),n.prototype.writeRaw=function(e){switch(this.webSocket.readyState){case WebSocket.CONNECTING:throw new Error("Can't send a message while WebSocket connecting");case WebSocket.OPEN:this.webSocket.send(e);break;case WebSocket.CLOSING:case WebSocket.CLOSED:throw new Error("Can't send a message while WebSocket is closing or closed")}},n.prototype.getReadyState=function(){switch(this.webSocket.readyState){case WebSocket.CONNECTING:return"connecting";case WebSocket.OPEN:return"open";case WebSocket.CLOSING:return"closing";case WebSocket.CLOSED:return"closed"}},t.exports=n},{"./Connection.js":5}],11:[function(e,t){t.exports=e("./lib/its.js")},{"./lib/its.js":12}],12:[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}},{}],13:[function(e,t){t.exports=e("./lib/Plink.js")},{"./lib/Plink.js":14}],14:[function(e,t){function n(e){this.p=r.create(e)}var r=e("internet"),i=e("./PlinkServer.js");n.create=function(e){return new n(e)},t.exports=n,n.prototype.connect=function(e){var t=this.p.connect(e);return i.create(t)}},{"./PlinkServer.js":15,internet:4}],15:[function(e,t){function n(e){this.promises={},this.onramp=e,this.waitForOpenQueue=[],this.onramp.on("message",this.messageHandler.bind(this)),this.onramp.on("open",this.openHandler.bind(this))}var r=e("when");n.create=function(e){return new n(e)},t.exports=n,n.prototype.on=function(){return this.onramp.on.apply(this.onramp,arguments),this},n.prototype.removeListener=function(){return this.onramp.removeListener.apply(this.onramp,arguments),this},n.prototype.openHandler=function(){this.waitForOpenQueue.forEach(function(e){e()}),this.waitForOpenQueue=[]},n.prototype.setKey=function(e,t){var n=this,i=this.promises["set"+e];return i||(i=this.promises["set"+e]=r.defer()),this.onramp.isOpen()?this.onramp.send({type:"set key",key:e,timeout:t}):this.waitForOpenQueue.push(function(){n.onramp.send({type:"set key",key:e,timeout:t})}),i.promise},n.prototype.revokeKey=function(e){var t=this.promises["revoke"+e];return t||(t=this.promises["revoke"+e]=r.defer()),this.onramp.send({type:"revoke key",key:e}),t.promise},n.prototype.useKey=function(e){var t=this,n=this.promises["use"+e];return n||(n=this.promises["use"+e]=r.defer()),this.onramp.isOpen()?this.onramp.send({type:"use key",key:e}):this.waitForOpenQueue.push(function(){t.onramp.send({type:"use key",key:e})}),n.promise},n.prototype.messageHandler=function(e){if(e.type){var t,n=e.key;switch(e.type){case"address":var r=this.onramp.connect(e.address);t=this.promises["use"+n],t.resolve(r),delete this.promises["use"+n];break;case"invalid key":t=this.promises["use"+n],t.reject(new Error("invalid key: "+e.key)),delete this.promises["use"+n];break;case"key set":t=this.promises["set"+n],t.resolve(e.key),delete this.promises["set"+n];break;case"key not set":t=this.promises["set"+n],t.reject(new Error("key not set: "+e.key)),delete this.promises["set"+n];break;case"key revoked":t=this.promises["revoke"+n],t.resolve(e.key),delete this.promises["revoke"+n];break;case"key revoked":t=this.promises["revoke"+n],t.resolve(new Error("key not revoked: "+e.key)),delete this.promises["revoke"+n]}}}},{when:16}],16:[function(e,t){(function(n){!function(e){"use strict";e(function(e){function t(e,t,n,r){return o(e).then(t,n,r)}function r(e){return new i(e,U.PromiseStatus&&U.PromiseStatus())}function i(e,t){function n(){return a?a.inspect():L()}function r(e,t,n,r,i){function o(o){o._when(e,t,n,r,i)}f?f.push(o):O(function(){o(a)})}function i(e){if(f){var n=f;f=K,O(function(){a=l(c,e),t&&m(a,t),u(n,a)})}}function o(e){i(new h(e))}function s(e){if(f){var t=f;O(function(){u(t,new d(e))})}}var c,a,f=[];c=this,this._status=t,this.inspect=n,this._when=r;try{e(i,o,s)}catch(p){o(p)}}function o(e){return e instanceof i?e:s(e)}function s(e){return r(function(t){t(e)})}function c(e){return t(e,function(e){return new h(e)})}function a(){function e(e,r,o){t.resolve=t.resolver.resolve=function(t){return i?s(t):(i=!0,e(t),n)},t.reject=t.resolver.reject=function(e){return i?s(new h(e)):(i=!0,r(e),n)},t.notify=t.resolver.notify=function(e){return o(e),e}}var t,n,i;return t={promise:K,resolve:K,reject:K,notify:K,resolver:{resolve:K,reject:K,notify:K}},t.promise=n=r(e),t}function u(e,t){for(var n=0;n>>0,a=Math.max(0,Math.min(n,d)),l=[],u=d-a+1,f=[],a)for(h=function(e){f.push(e),--u||(p=h=A,i(f))},p=function(e){l.push(e),--a||(p=h=A,r(l))},m=0;d>m;++m)m in e&&t(e[m],c,s,o);else r(l)}return r(c).then(i,o,s)})}function w(e,t,n,r){function i(e){return t?t(e[0]):e[0]}return v(e,1,i,n,r)}function g(e,t,n,r){return k(e,A).then(t,n,r)}function E(){return k(arguments,A)}function b(e){return k(e,S,_)}function C(e,t){return k(e,t)}function k(e,n,r){return t(e,function(e){function o(i,o,s){function c(e,c){t(e,n,r).then(function(e){a[c]=e,--l||i(a)},o,s)}var a,u,l,f;if(l=u=e.length>>>0,a=[],!l)return void i(a);for(f=0;u>f;f++)f in e?c(e[f],f):--l}return new i(o)})}function R(e,n){var r=D(F,arguments,1);return t(e,function(e){var i;return i=e.length,r[0]=function(e,r,o){return t(e,function(e){return t(r,function(t){return n(e,t,o,i)})})},P.apply(e,r)})}function S(e){return{state:"fulfilled",value:e}}function _(e){return{state:"rejected",reason:e}}function L(){return{state:"pending"}}function O(e){1===I.push(e)&&N(T)}function T(){u(I),I=[]}function A(e){return e}function x(e){throw"function"==typeof U.reportUnhandled?U.reportUnhandled():O(function(){throw e}),e}t.promise=r,t.resolve=s,t.reject=c,t.defer=a,t.join=E,t.all=g,t.map=C,t.reduce=R,t.settle=b,t.any=w,t.some=v,t.isPromise=y,t.isPromiseLike=y,j=i.prototype,j.then=function(e,t,n){var r=this;return new i(function(i,o,s){r._when(i,s,e,t,n)},this._status&&this._status.observed())},j["catch"]=j.otherwise=function(e){return this.then(K,e)},j["finally"]=j.ensure=function(e){function t(){return s(e())}return"function"==typeof e?this.then(t,t)["yield"](this):this},j.done=function(e,t){this.then(e,t)["catch"](x)},j["yield"]=function(e){return this.then(function(){return e})},j.tap=function(e){return this.then(e)["yield"](this)},j.spread=function(e){return this.then(function(t){return g(t,function(t){return e.apply(K,t)})})},j.always=function(e,t){return this.then(e,e,t)},M=Object.create||function(e){function t(){}return t.prototype=e,new t},p.prototype=M(j),p.prototype.inspect=function(){return S(this.value)},p.prototype._when=function(e,t,n){try{e("function"==typeof n?n(this.value):this.value)}catch(r){e(new h(r))}},h.prototype=M(j),h.prototype.inspect=function(){return _(this.value)},h.prototype._when=function(e,t,n,r){try{e("function"==typeof r?r(this.value):this)}catch(i){e(new h(i))}},d.prototype=M(j),d.prototype._when=function(e,t,n,r,i){try{t("function"==typeof i?i(this.value):this.value)}catch(o){t(o)}};var j,M,P,F,D,N,I,W,q,G,U,Y,z,B,K;if(z=e,I=[],U="undefined"!=typeof console?console:t,"object"==typeof n&&n.nextTick)N=n.nextTick;else if(B="function"==typeof MutationObserver&&MutationObserver||"function"==typeof WebKitMutationObserver&&WebKitMutationObserver)N=function(e,t,n){var r=e.createElement("div");return new t(n).observe(r,{attributes:!0}),function(){r.setAttribute("x","x")}}(document,B,T);else try{N=z("vertx").runOnLoop||z("vertx").runOnContext}catch(J){Y=setTimeout,N=function(e){Y(e,0)}}return W=Function.prototype,q=W.call,D=W.bind?q.bind(q):function(e,t){return e.apply(t,F.call(arguments,2))},G=[],F=G.slice,P=G.reduce||function(e){var t,n,r,i,o;if(o=0,t=Object(this),i=t.length>>>0,n=arguments,n.length<=1)for(;;){if(o in t){r=t[o++];break}if(++o>=i)throw new TypeError}else r=n[1];for(;i>o;++o)o in t&&(r=e(r,t[o],o,t));return r},t})}("function"==typeof define&&define.amd?define:function(n){t.exports=n(e)})}).call(this,e("_process"))},{_process:3}],17:[function(e,t){t.exports=function(e,t){"use strict";function n(t,n){console[n||"log"]((e.performance.now()/1e3).toFixed(3)+": "+t)}function r(e){return n(e,"error")}function i(e){return n(e,"warn")}function o(){"performance"in e||(e.performance={now:function(){return+new Date}}),"origin"in e.location||(e.location.origin=e.location.protocol+"//"+e.location.host)}function s(){return e.location.pathname.indexOf(".html")?e.location.search.substr(1):e.location.pathname.substr(1)}function c(e){return-1!==m.indexOf(e.target.nodeName.toLowerCase())}function a(){return"ontouchstart"in e||e.DocumentTouch&&t instanceof e.DocumentTouch}function u(e){var n=t.createElement("link");n.href=e.href,n.media="all",n.rel="stylesheet",n.type="text/css",Object.keys(e||{}).forEach(function(t){n[t]=e[t]}),t.querySelector("head").appendChild(n)}function l(e){return e?e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,"""):e}function f(){return!(t.fullscreenElement||t.mozFullScreenElement||t.webkitFullscreenElement||t.msFullscreenElement)}function p(){f()?(n("Entering full screen"),t.documentElement.requestFullscreen?t.documentElement.requestFullscreen():t.documentElement.mozRequestFullScreen?t.documentElement.mozRequestFullScreen():t.documentElement.webkitRequestFullscreen?t.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT):t.documentElement.msRequestFullscreen&&t.documentElement.msRequestFullscreen()):(n("Exiting full screen"),t.exitFullscreen?t.exitFullscreen():t.mozCancelFullScreen?t.mozCancelFullScreen():t.webkitExitFullscreen?t.webkitExitFullscreen():t.msExitFullscreen&&t.msExitFullscreen())}function h(t){var n=e.screen.LockOrientation||e.screen.mozLockOrientation||e.screen.webkitLockOrientation||e.screen.msLockOrientation;return n?n(t):i("Orientation could not be locked")}function d(n){var r=t.createEvent("HTMLEvents");r.initEvent(n,!0,!0),r.eventName=n,(t.body||e).dispatchEvent(r)}var m=["input","keygen","meter","option","output","progress","select","textarea"];return{trace:n,error:r,warn:i,polyfill:o,getPeerKey:s,fieldFocused:c,hasTouchEvents:a,injectCSS:u,escape:l,isFullScreen:f,toggleFullScreen:p,lockOrientation:h,triggerEvent:d}}},{}],18:[function(e,t){"use strict";var n={};try{n=e("./settings_local.js")}catch(r){}var i=e("./lib/utils")(window,document);i.polyfill();var o={GAMEPAD_ORIGIN:window.location.origin,WS_URL:"ws://"+location.hostname+":20500/",DEBUG:!1,VERSION:"0.0.1"};Object.keys(n).forEach(function(e){o[e]=n[e]}),t.exports=o},{"./lib/utils":17,"./settings_local.js":19}],19:[function(e,t){t.exports={DEBUG:!0}},{}]},{},[1]); \ No newline at end of file +!function e(t,n,r){function i(s,c){if(!n[s]){if(!t[s]){var a="function"==typeof require&&require;if(!c&&a)return a(s,!0);if(o)return o(s,!0);var u=new Error("Cannot find module '"+s+"'");throw u.code="MODULE_NOT_FOUND",u}var l=n[s]={exports:{}};t[s][0].call(l.exports,function(e){var n=t[s][1][e];return i(n?n:e)},l,l.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,c,a,u;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,c=new Array(i-1),a=1;i>a;a++)c[a-1]=arguments[a];n.apply(this,c)}else if(o(n)){for(i=arguments.length,c=new Array(i-1),a=1;i>a;a++)c[a-1]=arguments[a];for(u=n.slice(),i=u.length,a=0;i>a;a++)u[a].apply(this,c)}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,c;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(c=s;c-->0;)if(n[c]===t||n[c].listener&&n[c].listener===t){i=c;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}},{}],3:[function(e,t){function n(){}var r=t.exports={};r.nextTick=function(){var e="undefined"!=typeof window&&window.setImmediate,t="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(e)return function(e){return window.setImmediate(e)};if(t){var n=[];return window.addEventListener("message",function(e){var t=e.source;if((t===window||null===t)&&"process-tick"===e.data&&(e.stopPropagation(),n.length>0)){var r=n.shift();r()}},!0),function(e){n.push(e),window.postMessage("process-tick","*")}}return function(e){setTimeout(e,0)}}(),r.title="browser",r.browser=!0,r.env={},r.argv=[],r.on=n,r.addListener=n,r.once=n,r.off=n,r.removeListener=n,r.removeAllListeners=n,r.emit=n,r.binding=function(){throw new Error("process.binding is not supported")},r.cwd=function(){return"/"},r.chdir=function(){throw new Error("process.chdir is not supported")}},{}],4:[function(e,t){t.exports=e("./lib/its.js")},{"./lib/its.js":5}],5:[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}},{}],6:[function(e,t){t.exports=e("./lib/Plink.js")},{"./lib/Plink.js":7}],7:[function(e,t){function n(e){this.p=r.create(e)}var r=e("internet"),i=e("./PlinkServer.js");n.create=function(e){return new n(e)},t.exports=n,n.prototype.connect=function(e){var t=this.p.connect(e);return i.create(t)}},{"./PlinkServer.js":8,internet:9}],8:[function(e,t){function n(e){this.promises={},this.onramp=e,this.waitForOpenQueue=[],this.onramp.on("message",this.messageHandler.bind(this)),this.onramp.on("open",this.openHandler.bind(this))}var r=e("when");n.create=function(e){return new n(e)},t.exports=n,n.prototype.on=function(){return this.onramp.on.apply(this.onramp,arguments),this},n.prototype.removeListener=function(){return this.onramp.removeListener.apply(this.onramp,arguments),this},n.prototype.openHandler=function(){this.waitForOpenQueue.forEach(function(e){e()}),this.waitForOpenQueue=[]},n.prototype.setKey=function(e,t){var n=this,i=this.promises["set"+e];return i||(i=this.promises["set"+e]=r.defer()),this.onramp.isOpen()?this.onramp.send({type:"set key",key:e,timeout:t}):this.waitForOpenQueue.push(function(){n.onramp.send({type:"set key",key:e,timeout:t})}),i.promise},n.prototype.revokeKey=function(e){var t=this.promises["revoke"+e];return t||(t=this.promises["revoke"+e]=r.defer()),this.onramp.send({type:"revoke key",key:e}),t.promise},n.prototype.useKey=function(e){var t=this,n=this.promises["use"+e];return n||(n=this.promises["use"+e]=r.defer()),this.onramp.isOpen()?this.onramp.send({type:"use key",key:e}):this.waitForOpenQueue.push(function(){t.onramp.send({type:"use key",key:e})}),n.promise},n.prototype.messageHandler=function(e){if(e.type){var t,n=e.key;switch(e.type){case"address":var r=this.onramp.connect(e.address);t=this.promises["use"+n],t.resolve(r),delete this.promises["use"+n];break;case"invalid key":t=this.promises["use"+n],t.reject(new Error("invalid key: "+e.key)),delete this.promises["use"+n];break;case"key set":t=this.promises["set"+n],t.resolve(e.key),delete this.promises["set"+n];break;case"key not set":t=this.promises["set"+n],t.reject(new Error("key not set: "+e.key)),delete this.promises["set"+n];break;case"key revoked":t=this.promises["revoke"+n],t.resolve(e.key),delete this.promises["revoke"+n];break;case"key revoked":t=this.promises["revoke"+n],t.resolve(new Error("key not revoked: "+e.key)),delete this.promises["revoke"+n]}}}},{when:16}],9:[function(e,t){t.exports=e("./lib/P.js")},{"./lib/P.js":13}],10:[function(e,t){function n(){throw new Error("This method is not implemented")}function r(e,t,n){s.string(e),s.defined(t),this.address=e,this.peers=t,n&&(n.emitter&&(this.emitter=n.emitter),n.firewall&&(this.acceptRTCConnection=n.firewall)),this.emitter||(this.emitter=new r.Emitter)}function i(e){return"[object String]"===Object.prototype.toString.call(e)}var o=e("./JSONProtocol.js"),s=e("its"),c=e("events").EventEmitter;r.createWebRTCConnection=null,r.Emitter=c,r.prototype=Object.create(o.prototype),r.prototype.on=function(){return this.emitter.on.apply(this.emitter,arguments),this},r.prototype.removeListener=function(){return this.emitter.removeListener.apply(this.emitter,arguments),this},r.prototype.send=o.prototype.writeMessage,r.prototype.getPeer=function(e){return this.peers.get(e)},r.prototype.addPeer=function(e){return this.peers.add(e)},r.prototype.getPeers=function(){return this.peers.get()},r.prototype.connect=function(e){i(e)&&(e={address:e});var t=this,n=e.firewall||this.firewall,o=r.createWebRTCConnection(e,this.peers,this,{firewall:n});return o.writeOffer(e),this.peers.add(o),o.on("close",function(){t.peers.remove(o),t.emitter.emit("disconnection",o)}),this.emitter.emit("connection",o),o},r.prototype.readMessage=function(e){this.emitter.emit("message",e)},r.prototype.readArrayBuffer=function(e){this.emitter.emit("arraybuffer",e)},r.prototype.acceptRTCConnection=function(){return!0},r.prototype.readRelay=function(e,t){var n=this.getPeer(e);return n?void n.writeRelayedMessage(this.address,t):void this.emitter.emit("error",new Error("Unknown peer at address: "+e))},r.prototype.readRelayedIceCandidate=function(e,t){var n=this.getPeer(e);return n?void n.readIceCandidate(t):void this.emitter.emit("error",new Error("Unknown peer at address: "+e))},r.prototype.readRelayedOffer=function(e,t,n){if(!this.acceptRTCConnection(t,n))return!1;var i=this,o=r.createWebRTCConnection({address:e},this.peers,this,{firewall:this.firewall});this.addPeer(o),o.on("close",function(){i.peers.remove(o),i.emitter.emit("disconnection",o)}),o.readOffer(t),o.writeAnswer(),this.emitter.emit("connection",o)},r.prototype.readRelayedAnswer=function(e,t){var n=this.getPeer(e);return n?void n.readAnswer(t):void this.emitter.emit("error",new Error("Unknown peer at address: "+e))},r.prototype.close=n,r.prototype.getReadyState=n,r.prototype.isOpen=function(){return"open"===this.getReadyState()},t.exports=r},{"./JSONProtocol.js":12,events:2,its:4}],11:[function(e,t){function n(){}function r(){this.connectionMap={},this.connectionList=[]}var i=e("its");r.prototype.get=function(e){return void 0===e?this.connectionList.slice():this.connectionMap[e]},r.prototype.add=function(e){i.defined(e);var t=e.address;return i.string(t),t in this.connectionMap?!1:(this.connectionMap[t]=e,this.connectionList.push(e),this.onAdd(e),!0)},r.prototype.onAdd=n,r.prototype.remove=function(e){i.defined(e);var t=e.address;i.string(t);var n=this.connectionMap[t];if(!n||n!==e)return!1;delete this.connectionMap[t];var r=this.connectionList.indexOf(e);return this.connectionList.splice(r,1),this.onRemove(e),!0},r.prototype.onRemove=n,t.exports=r},{its:4}],12:[function(e,t){function n(){throw new Error("This method is not implemented")}function r(){}r.prototype.PROTOCOL_NAME="p",r.prototype.MESSAGE_TYPE={DIRECT:0,RTC_OFFER:3,RTC_ANSWER:4,RTC_ICE_CANDIDATE:5,RELAY:6,RELAYED:7},r.prototype.readRaw=function(e){e instanceof ArrayBuffer?this.readArrayBuffer(e):this.readProtocolMessage(JSON.parse(e))},r.prototype.readProtocolMessage=function(e){var t=this.MESSAGE_TYPE,n=e[0];switch(n){case t.DIRECT:this.readMessage(e[1]);break;case t.RELAYED:this.readRelayedMessage(e[1],e[2]);break;case t.RELAY:this.readRelay(e[1],e[2]);break;default:throw new Error("Unknown message type: "+n)}},r.prototype.readRelayedMessage=function(e,t){var n=this.MESSAGE_TYPE,r=t[0];switch(r){case n.RTC_OFFER:this.readRelayedOffer(e,t[1],t[2]);break;case n.RTC_ANSWER:this.readRelayedAnswer(e,t[1]);break;case n.RTC_ICE_CANDIDATE:this.readRelayedIceCandidate(e,t[1]);break;default:throw new Error("Unknown message type: "+r)}},r.prototype.readMessage=n,r.prototype.readArrayBuffer=n,r.prototype.readRelay=n,r.prototype.readRelayedOffer=n,r.prototype.readRelayedAnswer=n,r.prototype.readRelayedIceCandidate=n,r.prototype.writeRaw=n,r.prototype.writeProtocolMessage=function(e){var t=JSON.stringify(e);this.writeRaw(t)},r.prototype.writeMessage=function(e){e instanceof ArrayBuffer?this.writeRaw(e):this.writeStringMessage(e)},r.prototype.writeStringMessage=function(e){this.writeProtocolMessage([this.MESSAGE_TYPE.DIRECT,e])},r.prototype.writeRelayedMessage=function(e,t){this.writeProtocolMessage([this.MESSAGE_TYPE.RELAYED,e,t])},r.prototype.writeRelayMessage=function(e,t){this.writeProtocolMessage([this.MESSAGE_TYPE.RELAY,e,t])},r.prototype.writeRelayAnswer=function(e,t){this.writeRelayMessage(e,[this.MESSAGE_TYPE.RTC_ANSWER,t])},r.prototype.writeRelayIceCandidate=function(e,t){this.writeRelayMessage(e,[this.MESSAGE_TYPE.RTC_ICE_CANDIDATE,t])},r.prototype.writeRelayOffer=function(e,t,n){this.writeRelayMessage(e,[this.MESSAGE_TYPE.RTC_OFFER,t,n])},t.exports=r},{}],13:[function(e,t){function n(e,t,n){s.defined(e),s.defined(t),this.emitter=e,this.peers=t,this.peers.onAdd=function(t){e.emit("connection",t)},this.peers.onRemove=function(t){e.emit("disconnection",t)},n&&n.firewall&&(this.firewall=n.firewall)}var r=e("events").EventEmitter,i=e("./ConnectionManager.js"),o=e("./WebSocketConnection.js"),s=(e("./WebRTCConnection.js"),e("its"));n.create=function(e){var t=new r,o=new i;return new n(t,o,e)},n.prototype.getPeers=function(){return this.peers.get()},n.prototype.connect=function(e){s.string(e);var t=this.peers,n=o.create(e,this.peers,{firewall:this.firewall});return t.add(n),n.on("close",function(){t.remove(n)}),n},n.prototype.on=function(){return this.emitter.on.apply(this.emitter,arguments),this},n.prototype.removeListener=function(){return this.emitter.removeListener.apply(this.emitter,arguments),this},t.exports=n},{"./ConnectionManager.js":11,"./WebRTCConnection.js":14,"./WebSocketConnection.js":15,events:2,its:4}],14:[function(e,t){function n(e,t,n,o,s){var c=this;i.string(e),i.defined(t),i.defined(n),i.defined(o),r.call(this,e,t,s),this.signalingChannel=o,this.rtcConnection=n,this.rtcDataChannel=n.createDataChannel(this.PROTOCOL_NAME,{reliable:!1}),this.close=n.close.bind(n),this.rtcConnection.addEventListener("icecandidate",function(t){t.candidate&&c.signalingChannel.writeRelayIceCandidate(e,t.candidate)}),this.rtcDataChannel.addEventListener("message",function(e){c.readRaw(e.data)}),this.rtcDataChannel.addEventListener("open",function(e){c.emitter.emit("open",e)}),this.rtcDataChannel.addEventListener("error",function(e){c.emitter.emit("error",e)}),this.rtcDataChannel.addEventListener("close",function(e){c.emitter.emit("close",e)}),this.rtcDataChannel.addEventListener("removestream",function(e){c.emitter.emit("removestream",e)}),this.rtcDataChannel.addEventListener("icestatechange",function(e){c.emitter.emit("icestatechange",e)}),this.rtcDataChannel.addEventListener("icechange",function(e){c.emitter.emit("icechange",e)})}var r=e("./Connection.js"),i=e("its"),o="undefined"!=typeof RTCPeerConnection?RTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection?webkitRTCPeerConnection:"undefined"!=typeof mozRTCPeerConnection?mozRTCPeerConnection:void 0,s="undefined"!=typeof RTCSessionDescription?RTCSessionDescription:"undefined"!=typeof mozRTCSessionDescription?mozRTCSessionDescription:void 0,c="undefined"!=typeof RTCIceCandidate?RTCIceCandidate:"undefined"!=typeof mozRTCIceCandidate?mozRTCIceCandidate:void 0,a=null,u={optional:[{RtpDataChannels:!0}],mandatory:{OfferToReceiveAudio:!1,OfferToReceiveVideo:!1}};n.create=function(e,t,r,i){var s=e.rtcConfiguration||a,c=e.mediaConstraints||u,l=new o(s,c);return new n(e.address,t,l,r,i)},n.prototype=Object.create(r.prototype),n.prototype.writeRaw=function(e){switch(this.rtcDataChannel.readyState){case"connecting":throw new Error("Can't send a message while RTCDataChannel connecting");case"open":this.rtcDataChannel.send(e);break;case"closing":case"closed":throw new Error("Can't send a message while RTCDataChannel is closing or closed")}},n.prototype.readAnswer=function(e){var t=new s(e);this.rtcConnection.setRemoteDescription(t)},n.prototype.readOffer=function(e){var t=new s(e);this.rtcConnection.setRemoteDescription(t)},n.prototype.readIceCandidate=function(e){this.emitter,this.rtcConnection.addIceCandidate(new c(e))},n.prototype.writeAnswer=function(){function e(e){t.emit("error",e)}var t=this.emitter,n=this.address,r=this.rtcConnection,i=this.signalingChannel;r.createAnswer(function(t){r.setLocalDescription(t,function(){i.writeRelayAnswer(n,t)},e)},e)},n.prototype.writeOffer=function(e){function t(e){n.emit("error",e)}var n=this.emitter,r=this.address,i=this.rtcConnection,o=this.signalingChannel;i.createOffer(function(n){i.setLocalDescription(n,function(){o.writeRelayOffer(r,n,e.offerData)},t)},t,e.mediaConstraints||u)},n.prototype.getReadyState=function(){return this.rtcDataChannel.readyState},r.createWebRTCConnection=n.create,t.exports=n},{"./Connection.js":10,its:4}],15:[function(e,t){function n(e,t,n,i){var o=this;r.call(this,e,t,i),this.webSocket=n,this.close=n.close.bind(n),this.webSocket.addEventListener("message",function(e){o.readRaw(e.data)}),this.webSocket.addEventListener("open",function(e){o.emitter.emit("open",e)}),this.webSocket.addEventListener("error",function(e){o.emitter.emit("error",e)}),this.webSocket.addEventListener("close",function(e){o.emitter.emit("close",e)})}var r=e("./Connection.js");n.create=function(e,t,r){var i=new WebSocket(e,n.prototype.PROTOCOL_NAME);return new n(e,t,i,r)},n.prototype=Object.create(r.prototype),n.prototype.writeRaw=function(e){switch(this.webSocket.readyState){case WebSocket.CONNECTING:throw new Error("Can't send a message while WebSocket connecting");case WebSocket.OPEN:this.webSocket.send(e);break;case WebSocket.CLOSING:case WebSocket.CLOSED:throw new Error("Can't send a message while WebSocket is closing or closed")}},n.prototype.getReadyState=function(){switch(this.webSocket.readyState){case WebSocket.CONNECTING:return"connecting";case WebSocket.OPEN:return"open";case WebSocket.CLOSING:return"closing";case WebSocket.CLOSED:return"closed"}},t.exports=n},{"./Connection.js":10}],16:[function(e,t){(function(n){!function(e){"use strict";e(function(e){function t(e,t,n,r){return o(e).then(t,n,r)}function r(e){return new i(e,U.PromiseStatus&&U.PromiseStatus())}function i(e,t){function n(){return a?a.inspect():L()}function r(e,t,n,r,i){function o(o){o._when(e,t,n,r,i)}f?f.push(o):O(function(){o(a)})}function i(e){if(f){var n=f;f=B,O(function(){a=l(c,e),t&&m(a,t),u(n,a)})}}function o(e){i(new h(e))}function s(e){if(f){var t=f;O(function(){u(t,new d(e))})}}var c,a,f=[];c=this,this._status=t,this.inspect=n,this._when=r;try{e(i,o,s)}catch(p){o(p)}}function o(e){return e instanceof i?e:s(e)}function s(e){return r(function(t){t(e)})}function c(e){return t(e,function(e){return new h(e)})}function a(){function e(e,r,o){t.resolve=t.resolver.resolve=function(t){return i?s(t):(i=!0,e(t),n)},t.reject=t.resolver.reject=function(e){return i?s(new h(e)):(i=!0,r(e),n)},t.notify=t.resolver.notify=function(e){return o(e),e}}var t,n,i;return t={promise:B,resolve:B,reject:B,notify:B,resolver:{resolve:B,reject:B,notify:B}},t.promise=n=r(e),t}function u(e,t){for(var n=0;n>>0,a=Math.max(0,Math.min(n,d)),l=[],u=d-a+1,f=[],a)for(h=function(e){f.push(e),--u||(p=h=A,i(f))},p=function(e){l.push(e),--a||(p=h=A,r(l))},m=0;d>m;++m)m in e&&t(e[m],c,s,o);else r(l)}return r(c).then(i,o,s)})}function g(e,t,n,r){function i(e){return t?t(e[0]):e[0]}return v(e,1,i,n,r)}function w(e,t,n,r){return C(e,A).then(t,n,r)}function E(){return C(arguments,A)}function b(e){return C(e,R,_)}function k(e,t){return C(e,t)}function C(e,n,r){return t(e,function(e){function o(i,o,s){function c(e,c){t(e,n,r).then(function(e){a[c]=e,--l||i(a)},o,s)}var a,u,l,f;if(l=u=e.length>>>0,a=[],!l)return void i(a);for(f=0;u>f;f++)f in e?c(e[f],f):--l}return new i(o)})}function S(e,n){var r=D(F,arguments,1);return t(e,function(e){var i;return i=e.length,r[0]=function(e,r,o){return t(e,function(e){return t(r,function(t){return n(e,t,o,i)})})},M.apply(e,r)})}function R(e){return{state:"fulfilled",value:e}}function _(e){return{state:"rejected",reason:e}}function L(){return{state:"pending"}}function O(e){1===I.push(e)&&N(T)}function T(){u(I),I=[]}function A(e){return e}function x(e){throw"function"==typeof U.reportUnhandled?U.reportUnhandled():O(function(){throw e}),e}t.promise=r,t.resolve=s,t.reject=c,t.defer=a,t.join=E,t.all=w,t.map=k,t.reduce=S,t.settle=b,t.any=g,t.some=v,t.isPromise=y,t.isPromiseLike=y,j=i.prototype,j.then=function(e,t,n){var r=this;return new i(function(i,o,s){r._when(i,s,e,t,n)},this._status&&this._status.observed())},j["catch"]=j.otherwise=function(e){return this.then(B,e)},j["finally"]=j.ensure=function(e){function t(){return s(e())}return"function"==typeof e?this.then(t,t)["yield"](this):this},j.done=function(e,t){this.then(e,t)["catch"](x)},j["yield"]=function(e){return this.then(function(){return e})},j.tap=function(e){return this.then(e)["yield"](this)},j.spread=function(e){return this.then(function(t){return w(t,function(t){return e.apply(B,t)})})},j.always=function(e,t){return this.then(e,e,t)},P=Object.create||function(e){function t(){}return t.prototype=e,new t},p.prototype=P(j),p.prototype.inspect=function(){return R(this.value)},p.prototype._when=function(e,t,n){try{e("function"==typeof n?n(this.value):this.value)}catch(r){e(new h(r))}},h.prototype=P(j),h.prototype.inspect=function(){return _(this.value)},h.prototype._when=function(e,t,n,r){try{e("function"==typeof r?r(this.value):this)}catch(i){e(new h(i))}},d.prototype=P(j),d.prototype._when=function(e,t,n,r,i){try{t("function"==typeof i?i(this.value):this.value)}catch(o){t(o)}};var j,P,M,F,D,N,I,W,q,G,U,Y,z,J,B;if(z=e,I=[],U="undefined"!=typeof console?console:t,"object"==typeof n&&n.nextTick)N=n.nextTick;else if(J="function"==typeof MutationObserver&&MutationObserver||"function"==typeof WebKitMutationObserver&&WebKitMutationObserver)N=function(e,t,n){var r=e.createElement("div");return new t(n).observe(r,{attributes:!0}),function(){r.setAttribute("x","x")}}(document,J,T);else try{N=z("vertx").runOnLoop||z("vertx").runOnContext}catch(K){Y=setTimeout,N=function(e){Y(e,0)}}return W=Function.prototype,q=W.call,D=W.bind?q.bind(q):function(e,t){return e.apply(t,F.call(arguments,2))},G=[],F=G.slice,M=G.reduce||function(e){var t,n,r,i,o;if(o=0,t=Object(this),i=t.length>>>0,n=arguments,n.length<=1)for(;;){if(o in t){r=t[o++];break}if(++o>=i)throw new TypeError}else r=n[1];for(;i>o;++o)o in t&&(r=e(r,t[o],o,t));return r},t})}("function"==typeof define&&define.amd?define:function(n){t.exports=n(e)})}).call(this,e("_process"))},{_process:3}],17:[function(e,t){t.exports=function(e,t){"use strict";function n(t,n){console[n||"log"]((e.performance.now()/1e3).toFixed(3)+": "+t)}function r(e){return n(e,"error")}function i(e){return n(e,"warn")}function o(){"performance"in e||(e.performance={now:function(){return+new Date}}),"origin"in e.location||(e.location.origin=e.location.protocol+"//"+e.location.host)}function s(){return e.location.pathname.indexOf(".html")?e.location.search.substr(1):e.location.pathname.substr(1)}function c(e){return-1!==m.indexOf(e.target.nodeName.toLowerCase())}function a(){return"ontouchstart"in e||e.DocumentTouch&&t instanceof e.DocumentTouch}function u(e){var n=t.createElement("link");n.href=e.href,n.media="all",n.rel="stylesheet",n.type="text/css",Object.keys(e||{}).forEach(function(t){n[t]=e[t]}),t.querySelector("head").appendChild(n)}function l(e){return e?e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,"""):e}function f(){return!(t.fullscreenElement||t.mozFullScreenElement||t.webkitFullscreenElement||t.msFullscreenElement)}function p(){f()?(n("Entering full screen"),t.documentElement.requestFullscreen?t.documentElement.requestFullscreen():t.documentElement.mozRequestFullScreen?t.documentElement.mozRequestFullScreen():t.documentElement.webkitRequestFullscreen?t.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT):t.documentElement.msRequestFullscreen&&t.documentElement.msRequestFullscreen()):(n("Exiting full screen"),t.exitFullscreen?t.exitFullscreen():t.mozCancelFullScreen?t.mozCancelFullScreen():t.webkitExitFullscreen?t.webkitExitFullscreen():t.msExitFullscreen&&t.msExitFullscreen())}function h(t){return"lockOrientation"in e.screen?e.screen.lockOrientation(t):"mozLockOrientation"in e.screen?e.screen.mozLockOrientation(t):"webkitLockOrientation"in e.screen?e.screen.webkitLockOrientation(t):"msLockOrientation"in e.screen?e.screen.msLockOrientation(t):i("Orientation could not be locked")}function d(n){var r=t.createEvent("HTMLEvents");r.initEvent(n,!0,!0),r.eventName=n,(t.body||e).dispatchEvent(r)}var m=["input","keygen","meter","option","output","progress","select","textarea"];return{trace:n,error:r,warn:i,polyfill:o,getPeerKey:s,fieldFocused:c,hasTouchEvents:a,injectCSS:u,escape:l,isFullScreen:f,toggleFullScreen:p,lockOrientation:h,triggerEvent:d}}},{}],18:[function(e,t){"use strict";var n={};try{n=e("./settings_local.js")}catch(r){}var i=e("./lib/utils")(window,document);i.polyfill();var o={GAMEPAD_ORIGIN:window.location.origin,WS_URL:"ws://"+location.hostname+":20500/",DEBUG:!1,VERSION:"0.0.1"};Object.keys(n).forEach(function(e){o[e]=n[e]}),t.exports=o},{"./lib/utils":17,"./settings_local.js":19}],19:[function(e,t){t.exports={DEBUG:!0}},{}]},{},[1]); diff --git a/dist/js/gamepad-host.js b/dist/js/gamepad-host.js index e8465be..88199d7 100755 --- a/dist/js/gamepad-host.js +++ b/dist/js/gamepad-host.js @@ -122,40 +122,136 @@ Gamepad.prototype._reverse_url = function (routeName) { * @returns {Promise} * @memberOf Gamepad */ -Gamepad.prototype._handshake = function (peerKey) { +Gamepad.prototype._pair = function (peerKey) { return new Promise(function (resolve, reject) { if (!peerKey) { peerKey = utils.getPeerKey(); // The host key. } trace('Peer key: ' + peerKey); - // Create a root `plink` instance. + var numReattempts = 0; + + // 1. Create a root `plink` instance. var plink = Plink.create(); trace('Waiting for peer to connect'); - // Connect to `plink-server` and await connection using the peer ID. + // 2. Connect to signalling server (`plink-server`). var link = plink.connect(settings.WS_URL); - link.on('connection', function (peer) { - resolve(peer); - }).on('close', function () { - // TODO: Reconnect to signalling server (#60). - warn('Connection lost with signalling server'); - }).on('error', function (err) { - error('Could not connect to `plink-server`: ' + - JSON.stringify(err)); - reject(err); + + // 3. `link` emits `open` event (no event listener needed). + + // 4. Send this message containing our peer key *to* the signalling server: + // + // { + // "type": "set key", + // "key": "1234" + // } + // + // Other peers can then use the key to connect to this game via the the + // signalling server. Notice: this does not need to happen inside an + // `open` event listener. + // + // Or "use key" if controller is already online. + link.setKey(peerKey).then(function () { + trace('Sent message to signalling server: ' + + JSON.stringify({type: 'set key', key: peerKey})); + }).catch(function (err) { + warn('Controller is already online; "set key" message rejected by ' + + 'signalling server: ' + err); + + link.useKey(peerKey).then(function () { + trace('Sent message to signalling server: ' + + JSON.stringify({type: 'use key', key: peerKey})); + }).catch(function (err) { + error('Failed to send "use key" mesage to signalling server: ' + err); + }); }); - // Set a key. Other peers can use to connect to this browser using this - // key via the connected `plink-server`. - // (This returns a Promise on whether the operation succeeded.) - link.setKey(peerKey); - }); + // 5. `link` emits this `message` *from* signalling server: + // + // { + // "type": "key set", + // "key": "1234" + // } + // + + // 6. We wait for a peer to send a session description protocol (SDP) + // message to the signalling server. + + // 7. WebRTC takes over and we do the offer/answer dance. And that's + // where `RTCPeerConnection` data channels come from. + + // 8. `RTCPeerConnection` emits `open` event when a peer is connected. + + + // Event listeners for the signalling server. + + // `connection` will fire when a peer has connected using the peer key. + link.on('connection', function (peer) { + trace('[' + peer.address + '] Found peer via signalling server' + + (numReattempts ? ' (reattempt #' + numReattempts++ + ')' : '')); + resolve(peer); + + // Event listeners for `RTCPeerConnection`. + peer.on('open', function () { + trace('[' + peer.address + '] Opened peer connection to controller'); + }).on('message', function (msg) { + if (typeof msg === 'object' && msg.type) { + switch (msg.type) { + case 'bye': + // TODO: This should instead fire an event that the developer + // can then handle in the game (will likely want to pause too). + // We could offer Galaxy-styled toast notifications or modals. + warn('[' + peer.address + + '] Lost peer connection to controller'); + numReattempts++; + return; + case 'state': + trace('[' + peer.address + '] Received new controller state ' + + 'from peer: ' + + (typeof msg === 'object' ? JSON.stringify(msg) : msg)); + return this._updateState(msg.data); + default: + return warn('[' + peer.address + '] Received peer message of ' + + 'unexpected type (' + (msg.type || '') + '): ' + + (typeof msg === 'object' ? JSON.stringify(msg) : msg)); + } + } + + warn('[' + peer.address + '] Received unexpected peer message: ' + + (typeof msg === 'object' ? JSON.stringify(msg) : msg)); + }.bind(this)).on('error', function (err) { + error('[' + peer.address + '] Peer error: ' + + (typeof err === 'object' ? JSON.stringify(err) : err)); + reject(peer); + }).on('close', function () { + trace('[' + peer.address + '] Peer closed'); + }); + + // Workaround because `RTCPeerConnection.onclose` ain't work in browsers: + // * https://code.google.com/p/webrtc/issues/detail?id=1676 + // * https://bugzilla.mozilla.org/show_bug.cgi?id=881337 + // * https://bugzilla.mozilla.org/show_bug.cgi?id=1009124 + window.addEventListener('beforeunload', function () { + peer.send({type: 'bye'}); + }); + }.bind(this)).on('message', function (msg) { + trace('Received message from signalling server: ' + + JSON.stringify(msg)); + }).on('error', function (err) { + error('Could not connect to signalling server' + + settings.DEBUG ? (': ' + JSON.stringify(err)) : ''); + reject(err); + }).on('close', function () { + // TODO: Reconnect to signalling server (#60). + warn('Connection lost to signalling server'); // Not peer connection + }); + }.bind(this)); }; /** - * Connect to a controller using a shared peer key. + * Prompt the user to pair a controller and open a connection for pairing. * * Handshake with the WebSocket signalling server, listen for and establish a * a peer connection (via WebRTC's `RTCPeerConnection`), and relay messages @@ -195,7 +291,10 @@ Gamepad.prototype.pair = function (peerKey) { }, true); this.modal.open().then(function () { - trace('Modal opened'); + trace('Awaiting player to pair device'); + }).catch(function () { + warn('Failed to open modal'); + reject(); }); [ @@ -205,44 +304,10 @@ Gamepad.prototype.pair = function (peerKey) { utils.injectCSS({href: stylesheet}); }); - return this._handshake(peerKey).then(function (peer) { - trace('[' + peer.address + '] Connected'); - - peer.on('open', function () { - trace('[' + peer.address + '] Opened'); - resolve(peer); - }); - - peer.on('close', function () { - trace('[' + peer.address + '] Closed'); - }); - - peer.on('error', function (err) { - error('[' + peer.address + '] Error: ' + - (typeof err === 'object' ? JSON.stringify(err) : err)); - reject(peer); - }); - - peer.on('message', function (msg) { - if (typeof msg === 'object' && msg.type) { - switch (msg.type) { - case 'state': - trace('[' + peer.address + '] Received new controller state: ' + - (typeof msg === 'object' ? JSON.stringify(msg) : msg)); - return this._updateState(msg.data); - default: - return warn('[' + peer.address + '] Received message of ' + - 'unexpected type (' + (msg.type || '') + '): ' + - (typeof msg === 'object' ? JSON.stringify(msg) : msg)); - } - } - - warn('[' + peer.address + '] Received unexpected message: ' + - (typeof msg === 'object' ? JSON.stringify(msg) : msg)); - - resolve(peer); - }.bind(this)); - + return this._pair(peerKey).then(function (peer) { + trace('[' + peer.address + '] Paired to controller'); + }.bind(this)).then(function () { + this.modal.close(); }.bind(this)).catch(function (err) { console.trace(err.stack ? err.stack : err); }); @@ -257,17 +322,19 @@ Gamepad.prototype.pair = function (peerKey) { * @memberOf Gamepad */ Gamepad.prototype._updateState = function (data) { - Object.keys(data || {}).forEach(function (key) { - if (!this.state[key] && data[key]) { - // Button pushed. - this._emit('buttondown', key); - this._emit('buttondown.' + key, key); - } else if (this.state[key] && !data[key]) { - // Button released. - this._emit('buttonup', key); - this._emit('buttonup.' + key, key); - } - }.bind(this)); + this.state = data; + + Object.keys(data || {}).forEach(function (key) { + if (!this.state[key] && data[key]) { + // Button pushed. + this._emit('buttondown', key); + this._emit('buttondown.' + key, key); + } else if (this.state[key] && !data[key]) { + // Button released. + this._emit('buttonup', key); + this._emit('buttonup.' + key, key); + } + }.bind(this)); }; @@ -291,9 +358,11 @@ Gamepad.prototype.hidePairingScreen = function () { * @param {*} data Data to pass to the listener. */ Gamepad.prototype._emit = function (eventName, data) { - (this.listeners[eventName] || []).forEach(function (listener) { - listener.apply(listener, [data]); - }); + // TODO: Handle proper event emission (#44). + trace('Emit "' + eventName + '": ' + data); + // (this.listeners[eventName] || []).forEach(function (listener) { + // listener.apply(listener, [data]); + // }); }; @@ -361,7 +430,7 @@ module.exports = gamepad; })(window, document); -},{"./lib/modal":17,"./lib/routes":18,"./lib/utils":19,"./settings":20,"plink":13}],2:[function(require,module,exports){ +},{"./lib/modal":17,"./lib/routes":18,"./lib/utils":19,"./settings":20,"plink":6}],2:[function(require,module,exports){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -730,639 +799,8 @@ process.chdir = function (dir) { }; },{}],4:[function(require,module,exports){ -module.exports = require('./lib/P.js'); -},{"./lib/P.js":8}],5:[function(require,module,exports){ -var JSONProtocol = require('./JSONProtocol.js'), - its = require('its'), - Emitter = require('events').EventEmitter; - -function notImplemented(){ - throw new Error('This method is not implemented'); -} - -function Connection(address, peers, options){ - its.string(address); - its.defined(peers); - - this.address = address; - this.peers = peers; - - if(options){ - if(options.emitter) this.emitter = options.emitter; - if(options.firewall) this.acceptRTCConnection = options.firewall; - } - - if(!this.emitter) this.emitter = new Connection.Emitter(); -} - -// Circular dependency solved in WebRTCConnection.js -Connection.createWebRTCConnection = null; -Connection.Emitter = Emitter; - -Connection.prototype = Object.create(JSONProtocol.prototype); - -Connection.prototype.on = function(){ - this.emitter.on.apply(this.emitter, arguments); - return this; -}; - -Connection.prototype.removeListener = function(){ - this.emitter.removeListener.apply(this.emitter, arguments); - return this; -}; - -Connection.prototype.send = JSONProtocol.prototype.writeMessage; - -Connection.prototype.getPeer = function(address){ - return this.peers.get(address); -}; - -Connection.prototype.addPeer = function(peer){ - return this.peers.add(peer); -}; - -Connection.prototype.getPeers = function() { - return this.peers.get(); -}; - -function isString(candidate){ - return Object.prototype.toString.call(candidate) === '[object String]'; -} - -Connection.prototype.connect = function(config){ - if(isString(config)){ - config = {address: config}; - } - - var self = this, - firewall = config.firewall || this.firewall, - peer = Connection.createWebRTCConnection(config, this.peers, this, {firewall: firewall}); - - peer.writeOffer(config); - - this.peers.add(peer); - - peer.on('close', function(){ - self.peers.remove(peer); - self.emitter.emit('disconnection', peer); - }); - - this.emitter.emit('connection', peer); - - return peer; -}; - -Connection.prototype.readMessage = function(message){ - this.emitter.emit('message', message); -}; - -Connection.prototype.readArrayBuffer = function(message){ - this.emitter.emit('arraybuffer', message); -}; - -Connection.prototype.acceptRTCConnection = function(description, data){ - return true; -}; - -Connection.prototype.readRelay = function(peerAddress, message){ - var peer = this.getPeer(peerAddress); - - if(!peer){ - this.emitter.emit('error', new Error("Unknown peer at address: " + peerAddress)); - return; - } - - peer.writeRelayedMessage(this.address, message); -}; - -Connection.prototype.readRelayedIceCandidate = function(peerAddress, candidate){ - var peer = this.getPeer(peerAddress); - - if(!peer){ - this.emitter.emit('error', new Error("Unknown peer at address: " + peerAddress)); - return; - } - - peer.readIceCandidate(candidate); -}; - -Connection.prototype.readRelayedOffer = function(peerAddress, description, data){ - if(!this.acceptRTCConnection(description, data)) return false; - - var self = this, - peer = Connection.createWebRTCConnection({address:peerAddress}, this.peers, this, {firewall: this.firewall}); - - this.addPeer(peer); - - peer.on('close', function(){ - self.peers.remove(peer); - self.emitter.emit('disconnection', peer); - }); - - peer.readOffer(description); - peer.writeAnswer(); - - this.emitter.emit('connection', peer); -}; - -Connection.prototype.readRelayedAnswer = function(peerAddress, description){ - var peer = this.getPeer(peerAddress); - - if(!peer){ - this.emitter.emit('error', new Error("Unknown peer at address: " + peerAddress)); - return; - } - - peer.readAnswer(description); -}; - -Connection.prototype.close = notImplemented; // implemented higher up -Connection.prototype.getReadyState = notImplemented; // implemented higher up - -Connection.prototype.isOpen = function(){ - return this.getReadyState() === 'open'; -}; - -module.exports = Connection; - -},{"./JSONProtocol.js":7,"events":2,"its":11}],6:[function(require,module,exports){ -var its = require('its'); - -function noop(){} - -function ConnectionManager(){ - this.connectionMap = {}; - this.connectionList = []; -} - -ConnectionManager.prototype.get = function(address){ - if(address === undefined) return this.connectionList.slice(); - - return this.connectionMap[address]; -}; - -ConnectionManager.prototype.add = function(connection) { - its.defined(connection); - - var address = connection.address; - its.string(address); - - if(address in this.connectionMap) return false; - - this.connectionMap[address] = connection; - this.connectionList.push(connection); - - this.onAdd(connection); - return true; -}; -ConnectionManager.prototype.onAdd = noop; - -ConnectionManager.prototype.remove = function(connection){ - its.defined(connection); - - var address = connection.address; - its.string(address); - - var mappedConnection = this.connectionMap[address]; - if(!mappedConnection || mappedConnection !== connection) return false; - - delete this.connectionMap[address]; - - var index = this.connectionList.indexOf(connection); - this.connectionList.splice(index, 1); - - this.onRemove(connection); - return true; -}; -ConnectionManager.prototype.onRemove = noop; - -module.exports = ConnectionManager; -},{"its":11}],7:[function(require,module,exports){ -function notImplemented(){ - throw new Error('This method is not implemented'); -} - -function JSONProtocol(){} - -JSONProtocol.prototype.PROTOCOL_NAME = 'p'; - -JSONProtocol.prototype.MESSAGE_TYPE = { - DIRECT: 0, // [0, message] - - RTC_OFFER: 3, // [3, description, data] - RTC_ANSWER: 4, // [4, description] - RTC_ICE_CANDIDATE: 5, // [5, candidate] - - RELAY: 6, // [6, address, message] - RELAYED: 7 // [7, address, message] -}; - -JSONProtocol.prototype.readRaw = function(message){ - if(message instanceof ArrayBuffer){ - this.readArrayBuffer(message); - } else { - this.readProtocolMessage(JSON.parse(message)); - } -}; - -JSONProtocol.prototype.readProtocolMessage = function(message){ - var MESSAGE_TYPE = this.MESSAGE_TYPE, - messageType = message[0]; - - switch(messageType){ - // This is a message from the remote node to this one. - case MESSAGE_TYPE.DIRECT: - this.readMessage(message[1]); - break; - - // The message was relayed by the peer on behalf of - // a third party peer, identified by "thirdPartyPeerId". - // This means that the peer is acting as a signalling - // channel on behalf of the third party peer. - case MESSAGE_TYPE.RELAYED: - this.readRelayedMessage(message[1], message[2]); - break; - - // The message is intended for another peer, identified - // by "peerId", which is also connected to this node. - // This means that the peer is using this connection - // as a signalling channel in order to establish a connection - // to the other peer identified "peerId". - case MESSAGE_TYPE.RELAY: - this.readRelay(message[1], message[2]); - break; - - default: - throw new Error('Unknown message type: ' + messageType); - } -}; - -JSONProtocol.prototype.readRelayedMessage = function(origin, message){ - var MESSAGE_TYPE = this.MESSAGE_TYPE, - messageType = message[0]; - - switch(messageType){ - // An initial connection request from a third party peer - case MESSAGE_TYPE.RTC_OFFER: - this.readRelayedOffer(origin, message[1], message[2]); - break; - - // An answer to an RTC offer sent from this node - case MESSAGE_TYPE.RTC_ANSWER: - this.readRelayedAnswer(origin, message[1]); - break; - - // An ICE candidate from the source node - case MESSAGE_TYPE.RTC_ICE_CANDIDATE: - this.readRelayedIceCandidate(origin, message[1]); - break; - - default: - throw new Error('Unknown message type: ' + messageType); - } -}; - -JSONProtocol.prototype.readMessage = notImplemented; -JSONProtocol.prototype.readArrayBuffer = notImplemented; -JSONProtocol.prototype.readRelay = notImplemented; - -JSONProtocol.prototype.readRelayedOffer = notImplemented; -JSONProtocol.prototype.readRelayedAnswer = notImplemented; -JSONProtocol.prototype.readRelayedIceCandidate = notImplemented; - -JSONProtocol.prototype.writeRaw = notImplemented; - -JSONProtocol.prototype.writeProtocolMessage = function(message){ - var serializedMessage = JSON.stringify(message); - this.writeRaw(serializedMessage); -}; - -JSONProtocol.prototype.writeMessage = function(message){ - if(message instanceof ArrayBuffer){ - this.writeRaw(message); - } else { - this.writeStringMessage(message); - } -}; - -JSONProtocol.prototype.writeStringMessage = function(message){ - this.writeProtocolMessage([ - this.MESSAGE_TYPE.DIRECT, - message - ]); -}; - -JSONProtocol.prototype.writeRelayedMessage = function(origin, message){ - this.writeProtocolMessage([ - this.MESSAGE_TYPE.RELAYED, - origin, - message - ]); -}; - -JSONProtocol.prototype.writeRelayMessage = function(destination, message){ - this.writeProtocolMessage([ - this.MESSAGE_TYPE.RELAY, - destination, - message - ]); -}; - -JSONProtocol.prototype.writeRelayAnswer = function(destination, description){ - this.writeRelayMessage(destination, [ - this.MESSAGE_TYPE.RTC_ANSWER, - description - ]); -}; - -JSONProtocol.prototype.writeRelayIceCandidate = function(destination, candidate){ - this.writeRelayMessage(destination, [ - this.MESSAGE_TYPE.RTC_ICE_CANDIDATE, - candidate - ]); -}; - -JSONProtocol.prototype.writeRelayOffer = function(destination, description, data){ - this.writeRelayMessage(destination, [ - this.MESSAGE_TYPE.RTC_OFFER, - description, - data - ]); -}; - -module.exports = JSONProtocol; -},{}],8:[function(require,module,exports){ -var Emitter = require('events').EventEmitter, - ConnectionManager = require('./ConnectionManager.js'), - WebSocketConnection = require('./WebSocketConnection.js'), - WebRTCConnection = require('./WebRTCConnection.js'), - its = require('its'); - -function P(emitter, connectionManager, options){ - its.defined(emitter); - its.defined(connectionManager); - - this.emitter = emitter; - this.peers = connectionManager; - - this.peers.onAdd = function(peer){ - emitter.emit('connection', peer); - }; - - this.peers.onRemove = function(peer){ - emitter.emit('disconnection', peer); - }; - - if(options && options.firewall) this.firewall = options.firewall; -} - -P.create = function(options){ - var emitter = new Emitter(), - connectionManager = new ConnectionManager(); - - return new P(emitter, connectionManager, options); -}; - -P.prototype.getPeers = function(){ - return this.peers.get(); -}; - -P.prototype.connect = function(address){ - its.string(address); - - var peers = this.peers, - peer = WebSocketConnection.create(address, this.peers, {firewall: this.firewall}); - - peers.add(peer); - - peer.on('close', function(){ - peers.remove(peer); - }); - - return peer; -}; - -P.prototype.on = function(){ - this.emitter.on.apply(this.emitter, arguments); - return this; -}; - -P.prototype.removeListener = function(){ - this.emitter.removeListener.apply(this.emitter, arguments); - return this; -}; - -module.exports = P; -},{"./ConnectionManager.js":6,"./WebRTCConnection.js":9,"./WebSocketConnection.js":10,"events":2,"its":11}],9:[function(require,module,exports){ -var Connection = require('./Connection.js'), - its = require('its'); - -var nativeRTCPeerConnection = (typeof RTCPeerConnection !== 'undefined')? RTCPeerConnection : - (typeof webkitRTCPeerConnection !== 'undefined')? webkitRTCPeerConnection : - (typeof mozRTCPeerConnection !== 'undefined')? mozRTCPeerConnection : - undefined; - -var nativeRTCSessionDescription = (typeof RTCSessionDescription !== 'undefined')? RTCSessionDescription : - (typeof mozRTCSessionDescription !== 'undefined')? mozRTCSessionDescription : - undefined; -var nativeRTCIceCandidate = (typeof RTCIceCandidate !== 'undefined')? RTCIceCandidate : - (typeof mozRTCIceCandidate !== 'undefined')? mozRTCIceCandidate : - undefined; - -function WebRTCConnection(address, peers, rtcConnection, signalingChannel, options){ - var self = this; - - its.string(address); - its.defined(peers); - its.defined(rtcConnection); - its.defined(signalingChannel); - - Connection.call(this, address, peers, options); - - this.signalingChannel = signalingChannel; - this.rtcConnection = rtcConnection; - this.rtcDataChannel = rtcConnection.createDataChannel(this.PROTOCOL_NAME, {reliable: false}); - - this.close = rtcConnection.close.bind(rtcConnection); - - this.rtcConnection.addEventListener('icecandidate', function(event){ - if(!event.candidate) return; - - self.signalingChannel.writeRelayIceCandidate(address, event.candidate); - }); - - this.rtcDataChannel.addEventListener('message', function(message){ - self.readRaw(message.data); - }); - - this.rtcDataChannel.addEventListener('open', function(event){ - self.emitter.emit('open', event); - }); - - this.rtcDataChannel.addEventListener('error', function(event){ - self.emitter.emit('error', event); - }); - - this.rtcDataChannel.addEventListener('close', function(event){ - self.emitter.emit('close', event); - }); -} - -var DEFAULT_RTC_CONFIGURATION = null; -var DEFAULT_MEDIA_CONSTRAINTS = { - optional: [{RtpDataChannels: true}], - mandatory: { - OfferToReceiveAudio: false, - OfferToReceiveVideo: false - } -}; - -WebRTCConnection.create = function(config, peers, signalingChannel, options){ - var rtcConfiguration = config.rtcConfiguration || DEFAULT_RTC_CONFIGURATION, - mediaConstraints = config.mediaConstraints || DEFAULT_MEDIA_CONSTRAINTS, - rtcConnection = new nativeRTCPeerConnection(rtcConfiguration, mediaConstraints); - - return new WebRTCConnection(config.address, peers, rtcConnection, signalingChannel, options); -}; - -WebRTCConnection.prototype = Object.create(Connection.prototype); - -WebRTCConnection.prototype.writeRaw = function(message){ - switch(this.rtcDataChannel.readyState){ - case 'connecting': - throw new Error('Can\'t send a message while RTCDataChannel connecting'); - case 'open': - this.rtcDataChannel.send(message); - break; - case 'closing': - case 'closed': - throw new Error('Can\'t send a message while RTCDataChannel is closing or closed'); - } -}; - -WebRTCConnection.prototype.readAnswer = function(description){ - var rtcSessionDescription = new nativeRTCSessionDescription(description); - - this.rtcConnection.setRemoteDescription(rtcSessionDescription); -}; - -WebRTCConnection.prototype.readOffer = function(description){ - var rtcSessionDescription = new nativeRTCSessionDescription(description); - - this.rtcConnection.setRemoteDescription(rtcSessionDescription); -}; - -WebRTCConnection.prototype.readIceCandidate = function(candidate){ - var emitter = this.emitter; - this.rtcConnection.addIceCandidate(new nativeRTCIceCandidate(candidate)); -}; - -WebRTCConnection.prototype.writeAnswer = function(){ - var emitter = this.emitter, - address = this.address, - rtcConnection = this.rtcConnection, - signalingChannel = this.signalingChannel; - - function onError(err){ emitter.emit('error', err); } - - rtcConnection.createAnswer(function(description){ - rtcConnection.setLocalDescription(description, function(){ - signalingChannel.writeRelayAnswer(address, description); - }, onError); - }, onError); -}; - -WebRTCConnection.prototype.writeOffer = function(config){ - var emitter = this.emitter, - address = this.address, - rtcConnection = this.rtcConnection, - signalingChannel = this.signalingChannel; - - function onError(err){ emitter.emit('error', err); } - - rtcConnection.createOffer(function(description){ - rtcConnection.setLocalDescription(description, function(){ - signalingChannel.writeRelayOffer(address, description, config.offerData); - }, onError); - }, onError, config.mediaConstraints || DEFAULT_MEDIA_CONSTRAINTS); -}; - -WebRTCConnection.prototype.getReadyState = function(){ - return this.rtcDataChannel.readyState; -}; - - -// Solves the circular dependency with Connection.js -Connection.createWebRTCConnection = WebRTCConnection.create; - -module.exports = WebRTCConnection; -},{"./Connection.js":5,"its":11}],10:[function(require,module,exports){ -var Connection = require('./Connection.js'); - -function WebSocketConnection(address, peers, webSocket, options){ - var self = this; - - Connection.call(this, address, peers, options); - - this.webSocket = webSocket; - - this.close = webSocket.close.bind(webSocket); - - this.webSocket.addEventListener('message', function(message){ - self.readRaw(message.data); - }); - - this.webSocket.addEventListener('open', function(event){ - self.emitter.emit('open', event); - }); - - this.webSocket.addEventListener('error', function(event){ - self.emitter.emit('error', event); - }); - - this.webSocket.addEventListener('close', function(event){ - self.emitter.emit('close', event); - }); -} - -WebSocketConnection.create = function(address, peers, options){ - var webSocket = new WebSocket(address, WebSocketConnection.prototype.PROTOCOL_NAME); - return new WebSocketConnection(address, peers, webSocket, options); -}; - -WebSocketConnection.prototype = Object.create(Connection.prototype); -WebSocketConnection.prototype.writeRaw = function(message){ - switch(this.webSocket.readyState){ - case WebSocket.CONNECTING: - throw new Error("Can't send a message while WebSocket connecting"); - - case WebSocket.OPEN: - this.webSocket.send(message); - break; - - case WebSocket.CLOSING: - case WebSocket.CLOSED: - throw new Error("Can't send a message while WebSocket is closing or closed"); - } -}; - -WebSocketConnection.prototype.getReadyState = function(){ - switch(this.webSocket.readyState){ - case WebSocket.CONNECTING: - return 'connecting'; - case WebSocket.OPEN: - return 'open'; - case WebSocket.CLOSING: - return 'closing'; - case WebSocket.CLOSED: - return 'closed'; - } -}; - -module.exports = WebSocketConnection; -},{"./Connection.js":5}],11:[function(require,module,exports){ module.exports = require('./lib/its.js'); -},{"./lib/its.js":12}],12:[function(require,module,exports){ +},{"./lib/its.js":5}],5:[function(require,module,exports){ // Helpers var slice = Array.prototype.slice; var toString = Object.prototype.toString; @@ -1553,9 +991,9 @@ its.range = function(expression, message){ return expression; }; -},{}],13:[function(require,module,exports){ +},{}],6:[function(require,module,exports){ module.exports = require('./lib/Plink.js'); -},{"./lib/Plink.js":14}],14:[function(require,module,exports){ +},{"./lib/Plink.js":7}],7:[function(require,module,exports){ var P = require('internet'); var PlinkServer = require('./PlinkServer.js'); @@ -1574,7 +1012,7 @@ Plink.prototype.connect = function(address){ return PlinkServer.create(onramp); }; -},{"./PlinkServer.js":15,"internet":4}],15:[function(require,module,exports){ +},{"./PlinkServer.js":8,"internet":9}],8:[function(require,module,exports){ var when = require('when'); function PlinkServer(onramp){ @@ -1723,7 +1161,661 @@ PlinkServer.prototype.messageHandler = function(message){ } } }; -},{"when":16}],16:[function(require,module,exports){ +},{"when":16}],9:[function(require,module,exports){ +module.exports = require('./lib/P.js'); +},{"./lib/P.js":13}],10:[function(require,module,exports){ +var JSONProtocol = require('./JSONProtocol.js'), + its = require('its'), + Emitter = require('events').EventEmitter; + +function notImplemented(){ + throw new Error('This method is not implemented'); +} + +function Connection(address, peers, options){ + its.string(address); + its.defined(peers); + + this.address = address; + this.peers = peers; + + if(options){ + if(options.emitter) this.emitter = options.emitter; + if(options.firewall) this.acceptRTCConnection = options.firewall; + } + + if(!this.emitter) this.emitter = new Connection.Emitter(); +} + +// Circular dependency solved in WebRTCConnection.js +Connection.createWebRTCConnection = null; +Connection.Emitter = Emitter; + +Connection.prototype = Object.create(JSONProtocol.prototype); + +Connection.prototype.on = function(){ + this.emitter.on.apply(this.emitter, arguments); + return this; +}; + +Connection.prototype.removeListener = function(){ + this.emitter.removeListener.apply(this.emitter, arguments); + return this; +}; + +Connection.prototype.send = JSONProtocol.prototype.writeMessage; + +Connection.prototype.getPeer = function(address){ + return this.peers.get(address); +}; + +Connection.prototype.addPeer = function(peer){ + return this.peers.add(peer); +}; + +Connection.prototype.getPeers = function() { + return this.peers.get(); +}; + +function isString(candidate){ + return Object.prototype.toString.call(candidate) === '[object String]'; +} + +Connection.prototype.connect = function(config){ + if(isString(config)){ + config = {address: config}; + } + + var self = this, + firewall = config.firewall || this.firewall, + peer = Connection.createWebRTCConnection(config, this.peers, this, {firewall: firewall}); + + peer.writeOffer(config); + + this.peers.add(peer); + + peer.on('close', function(){ + self.peers.remove(peer); + self.emitter.emit('disconnection', peer); + }); + + this.emitter.emit('connection', peer); + + return peer; +}; + +Connection.prototype.readMessage = function(message){ + this.emitter.emit('message', message); +}; + +Connection.prototype.readArrayBuffer = function(message){ + this.emitter.emit('arraybuffer', message); +}; + +Connection.prototype.acceptRTCConnection = function(description, data){ + return true; +}; + +Connection.prototype.readRelay = function(peerAddress, message){ + var peer = this.getPeer(peerAddress); + + if(!peer){ + this.emitter.emit('error', new Error("Unknown peer at address: " + peerAddress)); + return; + } + + peer.writeRelayedMessage(this.address, message); +}; + +Connection.prototype.readRelayedIceCandidate = function(peerAddress, candidate){ + var peer = this.getPeer(peerAddress); + + if(!peer){ + this.emitter.emit('error', new Error("Unknown peer at address: " + peerAddress)); + return; + } + + peer.readIceCandidate(candidate); +}; + +Connection.prototype.readRelayedOffer = function(peerAddress, description, data){ + if(!this.acceptRTCConnection(description, data)) return false; + + var self = this, + peer = Connection.createWebRTCConnection({address:peerAddress}, this.peers, this, {firewall: this.firewall}); + + this.addPeer(peer); + + peer.on('close', function(){ + self.peers.remove(peer); + self.emitter.emit('disconnection', peer); + }); + + peer.readOffer(description); + peer.writeAnswer(); + + this.emitter.emit('connection', peer); +}; + +Connection.prototype.readRelayedAnswer = function(peerAddress, description){ + var peer = this.getPeer(peerAddress); + + if(!peer){ + this.emitter.emit('error', new Error("Unknown peer at address: " + peerAddress)); + return; + } + + peer.readAnswer(description); +}; + +Connection.prototype.close = notImplemented; // implemented higher up +Connection.prototype.getReadyState = notImplemented; // implemented higher up + +Connection.prototype.isOpen = function(){ + return this.getReadyState() === 'open'; +}; + +module.exports = Connection; + +},{"./JSONProtocol.js":12,"events":2,"its":4}],11:[function(require,module,exports){ +var its = require('its'); + +function noop(){} + +function ConnectionManager(){ + this.connectionMap = {}; + this.connectionList = []; +} + +ConnectionManager.prototype.get = function(address){ + if(address === undefined) return this.connectionList.slice(); + + return this.connectionMap[address]; +}; + +ConnectionManager.prototype.add = function(connection) { + its.defined(connection); + + var address = connection.address; + its.string(address); + + if(address in this.connectionMap) return false; + + this.connectionMap[address] = connection; + this.connectionList.push(connection); + + this.onAdd(connection); + return true; +}; +ConnectionManager.prototype.onAdd = noop; + +ConnectionManager.prototype.remove = function(connection){ + its.defined(connection); + + var address = connection.address; + its.string(address); + + var mappedConnection = this.connectionMap[address]; + if(!mappedConnection || mappedConnection !== connection) return false; + + delete this.connectionMap[address]; + + var index = this.connectionList.indexOf(connection); + this.connectionList.splice(index, 1); + + this.onRemove(connection); + return true; +}; +ConnectionManager.prototype.onRemove = noop; + +module.exports = ConnectionManager; +},{"its":4}],12:[function(require,module,exports){ +function notImplemented(){ + throw new Error('This method is not implemented'); +} + +function JSONProtocol(){} + +JSONProtocol.prototype.PROTOCOL_NAME = 'p'; + +JSONProtocol.prototype.MESSAGE_TYPE = { + DIRECT: 0, // [0, message] + + RTC_OFFER: 3, // [3, description, data] + RTC_ANSWER: 4, // [4, description] + RTC_ICE_CANDIDATE: 5, // [5, candidate] + + RELAY: 6, // [6, address, message] + RELAYED: 7 // [7, address, message] +}; + +JSONProtocol.prototype.readRaw = function(message){ + if(message instanceof ArrayBuffer){ + this.readArrayBuffer(message); + } else { + this.readProtocolMessage(JSON.parse(message)); + } +}; + +JSONProtocol.prototype.readProtocolMessage = function(message){ + var MESSAGE_TYPE = this.MESSAGE_TYPE, + messageType = message[0]; + + switch(messageType){ + // This is a message from the remote node to this one. + case MESSAGE_TYPE.DIRECT: + this.readMessage(message[1]); + break; + + // The message was relayed by the peer on behalf of + // a third party peer, identified by "thirdPartyPeerId". + // This means that the peer is acting as a signalling + // channel on behalf of the third party peer. + case MESSAGE_TYPE.RELAYED: + this.readRelayedMessage(message[1], message[2]); + break; + + // The message is intended for another peer, identified + // by "peerId", which is also connected to this node. + // This means that the peer is using this connection + // as a signalling channel in order to establish a connection + // to the other peer identified "peerId". + case MESSAGE_TYPE.RELAY: + this.readRelay(message[1], message[2]); + break; + + default: + throw new Error('Unknown message type: ' + messageType); + } +}; + +JSONProtocol.prototype.readRelayedMessage = function(origin, message){ + var MESSAGE_TYPE = this.MESSAGE_TYPE, + messageType = message[0]; + + switch(messageType){ + // An initial connection request from a third party peer + case MESSAGE_TYPE.RTC_OFFER: + this.readRelayedOffer(origin, message[1], message[2]); + break; + + // An answer to an RTC offer sent from this node + case MESSAGE_TYPE.RTC_ANSWER: + this.readRelayedAnswer(origin, message[1]); + break; + + // An ICE candidate from the source node + case MESSAGE_TYPE.RTC_ICE_CANDIDATE: + this.readRelayedIceCandidate(origin, message[1]); + break; + + default: + throw new Error('Unknown message type: ' + messageType); + } +}; + +JSONProtocol.prototype.readMessage = notImplemented; +JSONProtocol.prototype.readArrayBuffer = notImplemented; +JSONProtocol.prototype.readRelay = notImplemented; + +JSONProtocol.prototype.readRelayedOffer = notImplemented; +JSONProtocol.prototype.readRelayedAnswer = notImplemented; +JSONProtocol.prototype.readRelayedIceCandidate = notImplemented; + +JSONProtocol.prototype.writeRaw = notImplemented; + +JSONProtocol.prototype.writeProtocolMessage = function(message){ + var serializedMessage = JSON.stringify(message); + this.writeRaw(serializedMessage); +}; + +JSONProtocol.prototype.writeMessage = function(message){ + if(message instanceof ArrayBuffer){ + this.writeRaw(message); + } else { + this.writeStringMessage(message); + } +}; + +JSONProtocol.prototype.writeStringMessage = function(message){ + this.writeProtocolMessage([ + this.MESSAGE_TYPE.DIRECT, + message + ]); +}; + +JSONProtocol.prototype.writeRelayedMessage = function(origin, message){ + this.writeProtocolMessage([ + this.MESSAGE_TYPE.RELAYED, + origin, + message + ]); +}; + +JSONProtocol.prototype.writeRelayMessage = function(destination, message){ + this.writeProtocolMessage([ + this.MESSAGE_TYPE.RELAY, + destination, + message + ]); +}; + +JSONProtocol.prototype.writeRelayAnswer = function(destination, description){ + this.writeRelayMessage(destination, [ + this.MESSAGE_TYPE.RTC_ANSWER, + description + ]); +}; + +JSONProtocol.prototype.writeRelayIceCandidate = function(destination, candidate){ + this.writeRelayMessage(destination, [ + this.MESSAGE_TYPE.RTC_ICE_CANDIDATE, + candidate + ]); +}; + +JSONProtocol.prototype.writeRelayOffer = function(destination, description, data){ + this.writeRelayMessage(destination, [ + this.MESSAGE_TYPE.RTC_OFFER, + description, + data + ]); +}; + +module.exports = JSONProtocol; +},{}],13:[function(require,module,exports){ +var Emitter = require('events').EventEmitter, + ConnectionManager = require('./ConnectionManager.js'), + WebSocketConnection = require('./WebSocketConnection.js'), + WebRTCConnection = require('./WebRTCConnection.js'), + its = require('its'); + +function P(emitter, connectionManager, options){ + its.defined(emitter); + its.defined(connectionManager); + + this.emitter = emitter; + this.peers = connectionManager; + + this.peers.onAdd = function(peer){ + emitter.emit('connection', peer); + }; + + this.peers.onRemove = function(peer){ + emitter.emit('disconnection', peer); + }; + + if(options && options.firewall) this.firewall = options.firewall; +} + +P.create = function(options){ + var emitter = new Emitter(), + connectionManager = new ConnectionManager(); + + return new P(emitter, connectionManager, options); +}; + +P.prototype.getPeers = function(){ + return this.peers.get(); +}; + +P.prototype.connect = function(address){ + its.string(address); + + var peers = this.peers, + peer = WebSocketConnection.create(address, this.peers, {firewall: this.firewall}); + + peers.add(peer); + + peer.on('close', function(){ + peers.remove(peer); + }); + + return peer; +}; + +P.prototype.on = function(){ + this.emitter.on.apply(this.emitter, arguments); + return this; +}; + +P.prototype.removeListener = function(){ + this.emitter.removeListener.apply(this.emitter, arguments); + return this; +}; + +module.exports = P; +},{"./ConnectionManager.js":11,"./WebRTCConnection.js":14,"./WebSocketConnection.js":15,"events":2,"its":4}],14:[function(require,module,exports){ +var Connection = require('./Connection.js'), + its = require('its'); + +var nativeRTCPeerConnection = (typeof RTCPeerConnection !== 'undefined')? RTCPeerConnection : + (typeof webkitRTCPeerConnection !== 'undefined')? webkitRTCPeerConnection : + (typeof mozRTCPeerConnection !== 'undefined')? mozRTCPeerConnection : + undefined; + +var nativeRTCSessionDescription = (typeof RTCSessionDescription !== 'undefined')? RTCSessionDescription : + (typeof mozRTCSessionDescription !== 'undefined')? mozRTCSessionDescription : + undefined; +var nativeRTCIceCandidate = (typeof RTCIceCandidate !== 'undefined')? RTCIceCandidate : + (typeof mozRTCIceCandidate !== 'undefined')? mozRTCIceCandidate : + undefined; + +function WebRTCConnection(address, peers, rtcConnection, signalingChannel, options){ + var self = this; + + its.string(address); + its.defined(peers); + its.defined(rtcConnection); + its.defined(signalingChannel); + + Connection.call(this, address, peers, options); + + this.signalingChannel = signalingChannel; + this.rtcConnection = rtcConnection; + this.rtcDataChannel = rtcConnection.createDataChannel(this.PROTOCOL_NAME, {reliable: false}); + + this.close = rtcConnection.close.bind(rtcConnection); + + this.rtcConnection.addEventListener('icecandidate', function(event){ + if(!event.candidate) return; + + self.signalingChannel.writeRelayIceCandidate(address, event.candidate); + }); + + this.rtcDataChannel.addEventListener('message', function(message){ + // console.log('rtcDataChannel emit message'); + self.readRaw(message.data); + }); + + this.rtcDataChannel.addEventListener('open', function(event){ + // console.log('rtcDataChannel emit open'); + self.emitter.emit('open', event); + }); + + this.rtcDataChannel.addEventListener('error', function(event){ + // console.log('rtcDataChannel emit error'); + self.emitter.emit('error', event); + }); + + this.rtcDataChannel.addEventListener('close', function(event){ + // console.log('rtcDataChannel emit close'); + // new Image().src = 'http://localhost:7000/?rtcDataChannel emit close'; + self.emitter.emit('close', event); + }); + + this.rtcDataChannel.addEventListener('removestream', function(event){ + // console.log('rtcDataChannel emit removestream'); + // new Image().src = 'http://localhost:7000/?rtcDataChannel emit removestream'; + self.emitter.emit('removestream', event); + }); + + this.rtcDataChannel.addEventListener('icestatechange', function(event){ + // console.log('rtcDataChannel emit icestatechange'); + // new Image().src = 'http://localhost:7000/?rtcDataChannel emit nicestatechange'; + self.emitter.emit('icestatechange', event); + }); + + this.rtcDataChannel.addEventListener('icechange', function(event){ + // console.log('rtcDataChannel emit onicechange'); + // new Image().src = 'http://localhost:7000/?rtcDataChannel emit icechange'; + self.emitter.emit('icechange', event); + }); +} + +var DEFAULT_RTC_CONFIGURATION = null; +var DEFAULT_MEDIA_CONSTRAINTS = { + optional: [{RtpDataChannels: true}], + mandatory: { + OfferToReceiveAudio: false, + OfferToReceiveVideo: false + } +}; + +WebRTCConnection.create = function(config, peers, signalingChannel, options){ + var rtcConfiguration = config.rtcConfiguration || DEFAULT_RTC_CONFIGURATION, + mediaConstraints = config.mediaConstraints || DEFAULT_MEDIA_CONSTRAINTS, + rtcConnection = new nativeRTCPeerConnection(rtcConfiguration, mediaConstraints); + + return new WebRTCConnection(config.address, peers, rtcConnection, signalingChannel, options); +}; + +WebRTCConnection.prototype = Object.create(Connection.prototype); + +WebRTCConnection.prototype.writeRaw = function(message){ + switch(this.rtcDataChannel.readyState){ + case 'connecting': + throw new Error('Can\'t send a message while RTCDataChannel connecting'); + case 'open': + this.rtcDataChannel.send(message); + break; + case 'closing': + case 'closed': + throw new Error('Can\'t send a message while RTCDataChannel is closing or closed'); + } +}; + +WebRTCConnection.prototype.readAnswer = function(description){ + var rtcSessionDescription = new nativeRTCSessionDescription(description); + + this.rtcConnection.setRemoteDescription(rtcSessionDescription); +}; + +WebRTCConnection.prototype.readOffer = function(description){ + var rtcSessionDescription = new nativeRTCSessionDescription(description); + + this.rtcConnection.setRemoteDescription(rtcSessionDescription); +}; + +WebRTCConnection.prototype.readIceCandidate = function(candidate){ + var emitter = this.emitter; + this.rtcConnection.addIceCandidate(new nativeRTCIceCandidate(candidate)); +}; + +WebRTCConnection.prototype.writeAnswer = function(){ + var emitter = this.emitter, + address = this.address, + rtcConnection = this.rtcConnection, + signalingChannel = this.signalingChannel; + + function onError(err){ emitter.emit('error', err); } + + rtcConnection.createAnswer(function(description){ + rtcConnection.setLocalDescription(description, function(){ + signalingChannel.writeRelayAnswer(address, description); + }, onError); + }, onError); +}; + +WebRTCConnection.prototype.writeOffer = function(config){ + var emitter = this.emitter, + address = this.address, + rtcConnection = this.rtcConnection, + signalingChannel = this.signalingChannel; + + function onError(err){ emitter.emit('error', err); } + + rtcConnection.createOffer(function(description){ + rtcConnection.setLocalDescription(description, function(){ + signalingChannel.writeRelayOffer(address, description, config.offerData); + }, onError); + }, onError, config.mediaConstraints || DEFAULT_MEDIA_CONSTRAINTS); +}; + +WebRTCConnection.prototype.getReadyState = function(){ + return this.rtcDataChannel.readyState; +}; + + +// Solves the circular dependency with Connection.js +Connection.createWebRTCConnection = WebRTCConnection.create; + +module.exports = WebRTCConnection; +},{"./Connection.js":10,"its":4}],15:[function(require,module,exports){ +var Connection = require('./Connection.js'); + +function WebSocketConnection(address, peers, webSocket, options){ + var self = this; + + Connection.call(this, address, peers, options); + + this.webSocket = webSocket; + + this.close = webSocket.close.bind(webSocket); + + this.webSocket.addEventListener('message', function(message){ + self.readRaw(message.data); + }); + + this.webSocket.addEventListener('open', function(event){ + self.emitter.emit('open', event); + }); + + this.webSocket.addEventListener('error', function(event){ + self.emitter.emit('error', event); + }); + + this.webSocket.addEventListener('close', function(event){ + self.emitter.emit('close', event); + }); +} + +WebSocketConnection.create = function(address, peers, options){ + var webSocket = new WebSocket(address, WebSocketConnection.prototype.PROTOCOL_NAME); + return new WebSocketConnection(address, peers, webSocket, options); +}; + +WebSocketConnection.prototype = Object.create(Connection.prototype); +WebSocketConnection.prototype.writeRaw = function(message){ + switch(this.webSocket.readyState){ + case WebSocket.CONNECTING: + throw new Error("Can't send a message while WebSocket connecting"); + + case WebSocket.OPEN: + this.webSocket.send(message); + break; + + case WebSocket.CLOSING: + case WebSocket.CLOSED: + throw new Error("Can't send a message while WebSocket is closing or closed"); + } +}; + +WebSocketConnection.prototype.getReadyState = function(){ + switch(this.webSocket.readyState){ + case WebSocket.CONNECTING: + return 'connecting'; + case WebSocket.OPEN: + return 'open'; + case WebSocket.CLOSING: + return 'closing'; + case WebSocket.CLOSED: + return 'closed'; + } +}; + +module.exports = WebSocketConnection; +},{"./Connection.js":10}],16:[function(require,module,exports){ (function (process){ /** @license MIT License (c) copyright 2011-2013 original author or authors */ @@ -2674,15 +2766,16 @@ function Modal(opts, inject) { } Modal.closeAll = Modal.prototype.close = function () { - utils.trace('Closed overlay'); // Close any open modal. var openedModal = document.querySelector('.md-show'); if (openedModal) { openedModal.classList.remove('md-show'); + utils.trace('Closed modal'); } // TODO: Wait until transition end. setTimeout(function () { document.body.classList.remove('galaxy-overlayed'); + utils.trace('Hid overlay'); }, 150); }; @@ -2693,11 +2786,11 @@ Modal.injectOverlay = function () { var d = document.createElement('div'); d.className = 'md-overlay'; document.body.appendChild(d); + utils.trace('Added overlay'); } }; Modal.prototype.html = function () { - utils.trace('Created modal DOM'); var d = document.createElement('div'); d.id = 'modal-' + this.id; d.className = 'md-modal md-effect-1 ' + (this.classes || ''); @@ -2709,6 +2802,9 @@ Modal.prototype.html = function () { '
' + this.content + '
' + '' ); + + utils.trace('Created modal DOM'); + return d; }; @@ -2732,6 +2828,7 @@ Modal.prototype.inject = function () { Modal.prototype.open = function () { return new Promise(function () { + utils.trace('Opened modal'); return this.el.classList.add('md-show'); }.bind(this)); }; @@ -2867,15 +2964,20 @@ function toggleFullScreen() { function lockOrientation(orientation) { - var lo = (window.screen.LockOrientation || - window.screen.mozLockOrientation || - window.screen.webkitLockOrientation || - window.screen.msLockOrientation); - if (!lo) { - return warn('Orientation could not be locked'); + // Must check each individual because of a Firefox TypeError + if ('lockOrientation' in window.screen) { + return window.screen.lockOrientation(orientation); } - - return lo(orientation); + if ('mozLockOrientation' in window.screen) { + return window.screen.mozLockOrientation(orientation); + } + if ('webkitLockOrientation' in window.screen) { + return window.screen.webkitLockOrientation(orientation); + } + if ('msLockOrientation' in window.screen) { + return window.screen.msLockOrientation(orientation); + } + return warn('Orientation could not be locked'); } diff --git a/dist/js/gamepad-host.min.js b/dist/js/gamepad-host.min.js index bc09383..939c9f2 100755 --- a/dist/js/gamepad-host.min.js +++ b/dist/js/gamepad-host.min.js @@ -1,2 +1,2 @@ -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;"undefined"!=typeof window?t=window:"undefined"!=typeof global?t=global:"undefined"!=typeof self&&(t=self),t.gamepad=e()}}(function(){var e;return function t(e,n,r){function i(s,c){if(!n[s]){if(!e[s]){var a="function"==typeof require&&require;if(!c&&a)return a(s,!0);if(o)return o(s,!0);var u=new Error("Cannot find module '"+s+"'");throw u.code="MODULE_NOT_FOUND",u}var l=n[s]={exports:{}};e[s][0].call(l.exports,function(t){var n=e[s][1][t];return i(n?n:t)},l,l.exports,t,e,n,r)}return n[s].exports}for(var o="function"==typeof require&&require,s=0;s

URL

'+i+'

Code

'+r+"

";return this.modal=new s({id:"pairing-screen",classes:"slim",title:"Pair your mobile phone",content:o},!0),this.modal.open().then(function(){f("Modal opened")}),["https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700","/css/modal.css"].forEach(function(e){a.injectCSS({href:e})}),this._handshake(e).then(function(e){f("["+e.address+"] Connected"),e.on("open",function(){f("["+e.address+"] Opened"),t(e)}),e.on("close",function(){f("["+e.address+"] Closed")}),e.on("error",function(t){l("["+e.address+"] Error: "+("object"==typeof t?JSON.stringify(t):t)),n(e)}),e.on("message",function(n){if("object"==typeof n&&n.type)switch(n.type){case"state":return f("["+e.address+"] Received new controller state: "+("object"==typeof n?JSON.stringify(n):n)),this._updateState(n.data);default:return p("["+e.address+"] Received message of unexpected type ("+(n.type||"")+"): "+("object"==typeof n?JSON.stringify(n):n))}p("["+e.address+"] Received unexpected message: "+("object"==typeof n?JSON.stringify(n):n)),t(e)}.bind(this))}.bind(this)).catch(function(e){console.trace(e.stack?e.stack:e)})}.bind(this))},i.prototype._updateState=function(e){Object.keys(e||{}).forEach(function(t){!this.state[t]&&e[t]?(this._emit("buttondown",t),this._emit("buttondown."+t,t)):this.state[t]&&!e[t]&&(this._emit("buttonup",t),this._emit("buttonup."+t,t))}.bind(this))},i.prototype.hidePairingScreen=function(){this.modal.close()},i.prototype._emit=function(e,t){(this.listeners[e]||[]).forEach(function(e){e.apply(e,[t])})},i.prototype._bind=function(e,t){return"undefined"==typeof this.listeners[event]&&(this.listeners[event]=[]),this.listeners[event].push(t),this},i.prototype.unbind=function(e,t){return"undefined"==typeof e?void(this.listeners={}):"undefined"==typeof t?void(this.listeners[e]=[]):"undefined"==typeof this.listeners[e]?!1:(this.listeners[e].forEach(function(n,r){return n===t?(this.listeners[e].splice(r,1),!0):void 0}),!1)},t.exports=m}(window,document)},{"./lib/modal":17,"./lib/routes":18,"./lib/utils":19,"./settings":20,plink:13}],2:[function(e,t){function n(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function r(e){return"function"==typeof e}function i(e){return"number"==typeof e}function o(e){return"object"==typeof e&&null!==e}function s(e){return void 0===e}t.exports=n,n.EventEmitter=n,n.prototype._events=void 0,n.prototype._maxListeners=void 0,n.defaultMaxListeners=10,n.prototype.setMaxListeners=function(e){if(!i(e)||0>e||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},n.prototype.emit=function(e){var t,n,i,c,a,u;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,c=new Array(i-1),a=1;i>a;a++)c[a-1]=arguments[a];n.apply(this,c)}else if(o(n)){for(i=arguments.length,c=new Array(i-1),a=1;i>a;a++)c[a-1]=arguments[a];for(u=n.slice(),i=u.length,a=0;i>a;a++)u[a].apply(this,c)}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,c;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(c=s;c-->0;)if(n[c]===t||n[c].listener&&n[c].listener===t){i=c;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}},{}],3:[function(e,t){function n(){}var r=t.exports={};r.nextTick=function(){var e="undefined"!=typeof window&&window.setImmediate,t="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(e)return function(e){return window.setImmediate(e)};if(t){var n=[];return window.addEventListener("message",function(e){var t=e.source;if((t===window||null===t)&&"process-tick"===e.data&&(e.stopPropagation(),n.length>0)){var r=n.shift();r()}},!0),function(e){n.push(e),window.postMessage("process-tick","*")}}return function(e){setTimeout(e,0)}}(),r.title="browser",r.browser=!0,r.env={},r.argv=[],r.on=n,r.addListener=n,r.once=n,r.off=n,r.removeListener=n,r.removeAllListeners=n,r.emit=n,r.binding=function(){throw new Error("process.binding is not supported")},r.cwd=function(){return"/"},r.chdir=function(){throw new Error("process.chdir is not supported")}},{}],4:[function(e,t){t.exports=e("./lib/P.js")},{"./lib/P.js":8}],5:[function(e,t){function n(){throw new Error("This method is not implemented")}function r(e,t,n){s.string(e),s.defined(t),this.address=e,this.peers=t,n&&(n.emitter&&(this.emitter=n.emitter),n.firewall&&(this.acceptRTCConnection=n.firewall)),this.emitter||(this.emitter=new r.Emitter)}function i(e){return"[object String]"===Object.prototype.toString.call(e)}var o=e("./JSONProtocol.js"),s=e("its"),c=e("events").EventEmitter;r.createWebRTCConnection=null,r.Emitter=c,r.prototype=Object.create(o.prototype),r.prototype.on=function(){return this.emitter.on.apply(this.emitter,arguments),this},r.prototype.removeListener=function(){return this.emitter.removeListener.apply(this.emitter,arguments),this},r.prototype.send=o.prototype.writeMessage,r.prototype.getPeer=function(e){return this.peers.get(e)},r.prototype.addPeer=function(e){return this.peers.add(e)},r.prototype.getPeers=function(){return this.peers.get()},r.prototype.connect=function(e){i(e)&&(e={address:e});var t=this,n=e.firewall||this.firewall,o=r.createWebRTCConnection(e,this.peers,this,{firewall:n});return o.writeOffer(e),this.peers.add(o),o.on("close",function(){t.peers.remove(o),t.emitter.emit("disconnection",o)}),this.emitter.emit("connection",o),o},r.prototype.readMessage=function(e){this.emitter.emit("message",e)},r.prototype.readArrayBuffer=function(e){this.emitter.emit("arraybuffer",e)},r.prototype.acceptRTCConnection=function(){return!0},r.prototype.readRelay=function(e,t){var n=this.getPeer(e);return n?void n.writeRelayedMessage(this.address,t):void this.emitter.emit("error",new Error("Unknown peer at address: "+e))},r.prototype.readRelayedIceCandidate=function(e,t){var n=this.getPeer(e);return n?void n.readIceCandidate(t):void this.emitter.emit("error",new Error("Unknown peer at address: "+e))},r.prototype.readRelayedOffer=function(e,t,n){if(!this.acceptRTCConnection(t,n))return!1;var i=this,o=r.createWebRTCConnection({address:e},this.peers,this,{firewall:this.firewall});this.addPeer(o),o.on("close",function(){i.peers.remove(o),i.emitter.emit("disconnection",o)}),o.readOffer(t),o.writeAnswer(),this.emitter.emit("connection",o)},r.prototype.readRelayedAnswer=function(e,t){var n=this.getPeer(e);return n?void n.readAnswer(t):void this.emitter.emit("error",new Error("Unknown peer at address: "+e))},r.prototype.close=n,r.prototype.getReadyState=n,r.prototype.isOpen=function(){return"open"===this.getReadyState()},t.exports=r},{"./JSONProtocol.js":7,events:2,its:11}],6:[function(e,t){function n(){}function r(){this.connectionMap={},this.connectionList=[]}var i=e("its");r.prototype.get=function(e){return void 0===e?this.connectionList.slice():this.connectionMap[e]},r.prototype.add=function(e){i.defined(e);var t=e.address;return i.string(t),t in this.connectionMap?!1:(this.connectionMap[t]=e,this.connectionList.push(e),this.onAdd(e),!0)},r.prototype.onAdd=n,r.prototype.remove=function(e){i.defined(e);var t=e.address;i.string(t);var n=this.connectionMap[t];if(!n||n!==e)return!1;delete this.connectionMap[t];var r=this.connectionList.indexOf(e);return this.connectionList.splice(r,1),this.onRemove(e),!0},r.prototype.onRemove=n,t.exports=r},{its:11}],7:[function(e,t){function n(){throw new Error("This method is not implemented")}function r(){}r.prototype.PROTOCOL_NAME="p",r.prototype.MESSAGE_TYPE={DIRECT:0,RTC_OFFER:3,RTC_ANSWER:4,RTC_ICE_CANDIDATE:5,RELAY:6,RELAYED:7},r.prototype.readRaw=function(e){e instanceof ArrayBuffer?this.readArrayBuffer(e):this.readProtocolMessage(JSON.parse(e))},r.prototype.readProtocolMessage=function(e){var t=this.MESSAGE_TYPE,n=e[0];switch(n){case t.DIRECT:this.readMessage(e[1]);break;case t.RELAYED:this.readRelayedMessage(e[1],e[2]);break;case t.RELAY:this.readRelay(e[1],e[2]);break;default:throw new Error("Unknown message type: "+n)}},r.prototype.readRelayedMessage=function(e,t){var n=this.MESSAGE_TYPE,r=t[0];switch(r){case n.RTC_OFFER:this.readRelayedOffer(e,t[1],t[2]);break;case n.RTC_ANSWER:this.readRelayedAnswer(e,t[1]);break;case n.RTC_ICE_CANDIDATE:this.readRelayedIceCandidate(e,t[1]);break;default:throw new Error("Unknown message type: "+r)}},r.prototype.readMessage=n,r.prototype.readArrayBuffer=n,r.prototype.readRelay=n,r.prototype.readRelayedOffer=n,r.prototype.readRelayedAnswer=n,r.prototype.readRelayedIceCandidate=n,r.prototype.writeRaw=n,r.prototype.writeProtocolMessage=function(e){var t=JSON.stringify(e);this.writeRaw(t)},r.prototype.writeMessage=function(e){e instanceof ArrayBuffer?this.writeRaw(e):this.writeStringMessage(e)},r.prototype.writeStringMessage=function(e){this.writeProtocolMessage([this.MESSAGE_TYPE.DIRECT,e])},r.prototype.writeRelayedMessage=function(e,t){this.writeProtocolMessage([this.MESSAGE_TYPE.RELAYED,e,t])},r.prototype.writeRelayMessage=function(e,t){this.writeProtocolMessage([this.MESSAGE_TYPE.RELAY,e,t])},r.prototype.writeRelayAnswer=function(e,t){this.writeRelayMessage(e,[this.MESSAGE_TYPE.RTC_ANSWER,t])},r.prototype.writeRelayIceCandidate=function(e,t){this.writeRelayMessage(e,[this.MESSAGE_TYPE.RTC_ICE_CANDIDATE,t])},r.prototype.writeRelayOffer=function(e,t,n){this.writeRelayMessage(e,[this.MESSAGE_TYPE.RTC_OFFER,t,n])},t.exports=r},{}],8:[function(e,t){function n(e,t,n){s.defined(e),s.defined(t),this.emitter=e,this.peers=t,this.peers.onAdd=function(t){e.emit("connection",t)},this.peers.onRemove=function(t){e.emit("disconnection",t)},n&&n.firewall&&(this.firewall=n.firewall)}var r=e("events").EventEmitter,i=e("./ConnectionManager.js"),o=e("./WebSocketConnection.js"),s=(e("./WebRTCConnection.js"),e("its"));n.create=function(e){var t=new r,o=new i;return new n(t,o,e)},n.prototype.getPeers=function(){return this.peers.get()},n.prototype.connect=function(e){s.string(e);var t=this.peers,n=o.create(e,this.peers,{firewall:this.firewall});return t.add(n),n.on("close",function(){t.remove(n)}),n},n.prototype.on=function(){return this.emitter.on.apply(this.emitter,arguments),this},n.prototype.removeListener=function(){return this.emitter.removeListener.apply(this.emitter,arguments),this},t.exports=n},{"./ConnectionManager.js":6,"./WebRTCConnection.js":9,"./WebSocketConnection.js":10,events:2,its:11}],9:[function(e,t){function n(e,t,n,o,s){var c=this;i.string(e),i.defined(t),i.defined(n),i.defined(o),r.call(this,e,t,s),this.signalingChannel=o,this.rtcConnection=n,this.rtcDataChannel=n.createDataChannel(this.PROTOCOL_NAME,{reliable:!1}),this.close=n.close.bind(n),this.rtcConnection.addEventListener("icecandidate",function(t){t.candidate&&c.signalingChannel.writeRelayIceCandidate(e,t.candidate)}),this.rtcDataChannel.addEventListener("message",function(e){c.readRaw(e.data)}),this.rtcDataChannel.addEventListener("open",function(e){c.emitter.emit("open",e)}),this.rtcDataChannel.addEventListener("error",function(e){c.emitter.emit("error",e)}),this.rtcDataChannel.addEventListener("close",function(e){c.emitter.emit("close",e)})}var r=e("./Connection.js"),i=e("its"),o="undefined"!=typeof RTCPeerConnection?RTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection?webkitRTCPeerConnection:"undefined"!=typeof mozRTCPeerConnection?mozRTCPeerConnection:void 0,s="undefined"!=typeof RTCSessionDescription?RTCSessionDescription:"undefined"!=typeof mozRTCSessionDescription?mozRTCSessionDescription:void 0,c="undefined"!=typeof RTCIceCandidate?RTCIceCandidate:"undefined"!=typeof mozRTCIceCandidate?mozRTCIceCandidate:void 0,a=null,u={optional:[{RtpDataChannels:!0}],mandatory:{OfferToReceiveAudio:!1,OfferToReceiveVideo:!1}};n.create=function(e,t,r,i){var s=e.rtcConfiguration||a,c=e.mediaConstraints||u,l=new o(s,c);return new n(e.address,t,l,r,i)},n.prototype=Object.create(r.prototype),n.prototype.writeRaw=function(e){switch(this.rtcDataChannel.readyState){case"connecting":throw new Error("Can't send a message while RTCDataChannel connecting");case"open":this.rtcDataChannel.send(e);break;case"closing":case"closed":throw new Error("Can't send a message while RTCDataChannel is closing or closed")}},n.prototype.readAnswer=function(e){var t=new s(e);this.rtcConnection.setRemoteDescription(t)},n.prototype.readOffer=function(e){var t=new s(e);this.rtcConnection.setRemoteDescription(t)},n.prototype.readIceCandidate=function(e){this.emitter,this.rtcConnection.addIceCandidate(new c(e))},n.prototype.writeAnswer=function(){function e(e){t.emit("error",e)}var t=this.emitter,n=this.address,r=this.rtcConnection,i=this.signalingChannel;r.createAnswer(function(t){r.setLocalDescription(t,function(){i.writeRelayAnswer(n,t)},e)},e)},n.prototype.writeOffer=function(e){function t(e){n.emit("error",e)}var n=this.emitter,r=this.address,i=this.rtcConnection,o=this.signalingChannel;i.createOffer(function(n){i.setLocalDescription(n,function(){o.writeRelayOffer(r,n,e.offerData)},t)},t,e.mediaConstraints||u)},n.prototype.getReadyState=function(){return this.rtcDataChannel.readyState},r.createWebRTCConnection=n.create,t.exports=n},{"./Connection.js":5,its:11}],10:[function(e,t){function n(e,t,n,i){var o=this;r.call(this,e,t,i),this.webSocket=n,this.close=n.close.bind(n),this.webSocket.addEventListener("message",function(e){o.readRaw(e.data)}),this.webSocket.addEventListener("open",function(e){o.emitter.emit("open",e)}),this.webSocket.addEventListener("error",function(e){o.emitter.emit("error",e)}),this.webSocket.addEventListener("close",function(e){o.emitter.emit("close",e)})}var r=e("./Connection.js");n.create=function(e,t,r){var i=new WebSocket(e,n.prototype.PROTOCOL_NAME);return new n(e,t,i,r)},n.prototype=Object.create(r.prototype),n.prototype.writeRaw=function(e){switch(this.webSocket.readyState){case WebSocket.CONNECTING:throw new Error("Can't send a message while WebSocket connecting");case WebSocket.OPEN:this.webSocket.send(e);break;case WebSocket.CLOSING:case WebSocket.CLOSED:throw new Error("Can't send a message while WebSocket is closing or closed")}},n.prototype.getReadyState=function(){switch(this.webSocket.readyState){case WebSocket.CONNECTING:return"connecting";case WebSocket.OPEN:return"open";case WebSocket.CLOSING:return"closing";case WebSocket.CLOSED:return"closed"}},t.exports=n},{"./Connection.js":5}],11:[function(e,t){t.exports=e("./lib/its.js")},{"./lib/its.js":12}],12:[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}},{}],13:[function(e,t){t.exports=e("./lib/Plink.js")},{"./lib/Plink.js":14}],14:[function(e,t){function n(e){this.p=r.create(e)}var r=e("internet"),i=e("./PlinkServer.js");n.create=function(e){return new n(e)},t.exports=n,n.prototype.connect=function(e){var t=this.p.connect(e);return i.create(t)}},{"./PlinkServer.js":15,internet:4}],15:[function(e,t){function n(e){this.promises={},this.onramp=e,this.waitForOpenQueue=[],this.onramp.on("message",this.messageHandler.bind(this)),this.onramp.on("open",this.openHandler.bind(this))}var r=e("when");n.create=function(e){return new n(e)},t.exports=n,n.prototype.on=function(){return this.onramp.on.apply(this.onramp,arguments),this},n.prototype.removeListener=function(){return this.onramp.removeListener.apply(this.onramp,arguments),this},n.prototype.openHandler=function(){this.waitForOpenQueue.forEach(function(e){e()}),this.waitForOpenQueue=[]},n.prototype.setKey=function(e,t){var n=this,i=this.promises["set"+e];return i||(i=this.promises["set"+e]=r.defer()),this.onramp.isOpen()?this.onramp.send({type:"set key",key:e,timeout:t}):this.waitForOpenQueue.push(function(){n.onramp.send({type:"set key",key:e,timeout:t})}),i.promise},n.prototype.revokeKey=function(e){var t=this.promises["revoke"+e];return t||(t=this.promises["revoke"+e]=r.defer()),this.onramp.send({type:"revoke key",key:e}),t.promise},n.prototype.useKey=function(e){var t=this,n=this.promises["use"+e];return n||(n=this.promises["use"+e]=r.defer()),this.onramp.isOpen()?this.onramp.send({type:"use key",key:e}):this.waitForOpenQueue.push(function(){t.onramp.send({type:"use key",key:e})}),n.promise},n.prototype.messageHandler=function(e){if(e.type){var t,n=e.key;switch(e.type){case"address":var r=this.onramp.connect(e.address);t=this.promises["use"+n],t.resolve(r),delete this.promises["use"+n];break;case"invalid key":t=this.promises["use"+n],t.reject(new Error("invalid key: "+e.key)),delete this.promises["use"+n];break;case"key set":t=this.promises["set"+n],t.resolve(e.key),delete this.promises["set"+n];break;case"key not set":t=this.promises["set"+n],t.reject(new Error("key not set: "+e.key)),delete this.promises["set"+n];break;case"key revoked":t=this.promises["revoke"+n],t.resolve(e.key),delete this.promises["revoke"+n];break;case"key revoked":t=this.promises["revoke"+n],t.resolve(new Error("key not revoked: "+e.key)),delete this.promises["revoke"+n]}}}},{when:16}],16:[function(t,n){(function(r){!function(e){"use strict";e(function(e){function t(e,t,n,r){return o(e).then(t,n,r)}function n(e){return new i(e,q.PromiseStatus&&q.PromiseStatus())}function i(e,t){function n(){return a?a.inspect():O()}function r(e,t,n,r,i){function o(o){o._when(e,t,n,r,i)}f?f.push(o):L(function(){o(a)})}function i(e){if(f){var n=f;f=B,L(function(){a=l(c,e),t&&m(a,t),u(n,a)})}}function o(e){i(new h(e))}function s(e){if(f){var t=f;L(function(){u(t,new d(e))})}}var c,a,f=[];c=this,this._status=t,this.inspect=n,this._when=r;try{e(i,o,s)}catch(p){o(p)}}function o(e){return e instanceof i?e:s(e)}function s(e){return n(function(t){t(e)})}function c(e){return t(e,function(e){return new h(e)})}function a(){function e(e,n,o){t.resolve=t.resolver.resolve=function(t){return i?s(t):(i=!0,e(t),r)},t.reject=t.resolver.reject=function(e){return i?s(new h(e)):(i=!0,n(e),r)},t.notify=t.resolver.notify=function(e){return o(e),e}}var t,r,i;return t={promise:B,resolve:B,reject:B,notify:B,resolver:{resolve:B,reject:B,notify:B}},t.promise=r=n(e),t}function u(e,t){for(var n=0;n>>0,a=Math.max(0,Math.min(r,d)),l=[],u=d-a+1,f=[],a)for(h=function(e){f.push(e),--u||(p=h=j,i(f))},p=function(e){l.push(e),--a||(p=h=j,n(l))},m=0;d>m;++m)m in e&&t(e[m],c,s,o);else n(l)}return n(c).then(i,o,s)})}function w(e,t,n,r){function i(e){return t?t(e[0]):e[0]}return v(e,1,i,n,r)}function g(e,t,n,r){return k(e,j).then(t,n,r)}function E(){return k(arguments,j)}function b(e){return k(e,_,S)}function C(e,t){return k(e,t)}function k(e,n,r){return t(e,function(e){function o(i,o,s){function c(e,c){t(e,n,r).then(function(e){a[c]=e,--l||i(a)},o,s)}var a,u,l,f;if(l=u=e.length>>>0,a=[],!l)return void i(a);for(f=0;u>f;f++)f in e?c(e[f],f):--l}return new i(o)})}function R(e,n){var r=N(D,arguments,1);return t(e,function(e){var i;return i=e.length,r[0]=function(e,r,o){return t(e,function(e){return t(r,function(t){return n(e,t,o,i)})})},M.apply(e,r)})}function _(e){return{state:"fulfilled",value:e}}function S(e){return{state:"rejected",reason:e}}function O(){return{state:"pending"}}function L(e){1===F.push(e)&&I(T)}function T(){u(F),F=[]}function j(e){return e}function x(e){throw"function"==typeof q.reportUnhandled?q.reportUnhandled():L(function(){throw e}),e}t.promise=n,t.resolve=s,t.reject=c,t.defer=a,t.join=E,t.all=g,t.map=C,t.reduce=R,t.settle=b,t.any=w,t.some=v,t.isPromise=y,t.isPromiseLike=y,A=i.prototype,A.then=function(e,t,n){var r=this;return new i(function(i,o,s){r._when(i,s,e,t,n)},this._status&&this._status.observed())},A["catch"]=A.otherwise=function(e){return this.then(B,e)},A["finally"]=A.ensure=function(e){function t(){return s(e())}return"function"==typeof e?this.then(t,t)["yield"](this):this},A.done=function(e,t){this.then(e,t)["catch"](x)},A["yield"]=function(e){return this.then(function(){return e})},A.tap=function(e){return this.then(e)["yield"](this)},A.spread=function(e){return this.then(function(t){return g(t,function(t){return e.apply(B,t)})})},A.always=function(e,t){return this.then(e,e,t)},P=Object.create||function(e){function t(){}return t.prototype=e,new t},p.prototype=P(A),p.prototype.inspect=function(){return _(this.value)},p.prototype._when=function(e,t,n){try{e("function"==typeof n?n(this.value):this.value)}catch(r){e(new h(r))}},h.prototype=P(A),h.prototype.inspect=function(){return S(this.value)},h.prototype._when=function(e,t,n,r){try{e("function"==typeof r?r(this.value):this)}catch(i){e(new h(i))}},d.prototype=P(A),d.prototype._when=function(e,t,n,r,i){try{t("function"==typeof i?i(this.value):this.value)}catch(o){t(o)}};var A,P,M,D,N,I,F,W,G,U,q,Y,z,K,B;if(z=e,F=[],q="undefined"!=typeof console?console:t,"object"==typeof r&&r.nextTick)I=r.nextTick;else if(K="function"==typeof MutationObserver&&MutationObserver||"function"==typeof WebKitMutationObserver&&WebKitMutationObserver)I=function(e,t,n){var r=e.createElement("div");return new t(n).observe(r,{attributes:!0}),function(){r.setAttribute("x","x")}}(document,K,T);else try{I=z("vertx").runOnLoop||z("vertx").runOnContext}catch(J){Y=setTimeout,I=function(e){Y(e,0)}}return W=Function.prototype,G=W.call,N=W.bind?G.bind(G):function(e,t){return e.apply(t,D.call(arguments,2))},U=[],D=U.slice,M=U.reduce||function(e){var t,n,r,i,o;if(o=0,t=Object(this),i=t.length>>>0,n=arguments,n.length<=1)for(;;){if(o in t){r=t[o++];break}if(++o>=i)throw new TypeError}else r=n[1];for(;i>o;++o)o in t&&(r=e(r,t[o],o,t));return r},t})}("function"==typeof e&&e.amd?e:function(e){n.exports=e(t)})}).call(this,t("_process"))},{_process:3}],17:[function(e,t){t.exports=function(t,n){"use strict";function r(e,t){Object.keys(e).forEach(function(t){this[t]=e[t]}.bind(this)),t&&this.inject()}var i=e("./utils")(t,n);return r.closeAll=r.prototype.close=function(){i.trace("Closed overlay");var e=n.querySelector(".md-show");e&&e.classList.remove("md-show"),setTimeout(function(){n.body.classList.remove("galaxy-overlayed")},150)},r.injectOverlay=function(){if(i.trace("Injected overlay"),!n.querySelector(".md-overlay")){var e=n.createElement("div");e.className="md-overlay",n.body.appendChild(e)}},r.prototype.html=function(){i.trace("Created modal DOM");var e=n.createElement("div");return e.id="modal-"+this.id,e.className="md-modal md-effect-1 "+(this.classes||""),e.style.display="none",e.innerHTML='

'+i.escape(this.title)+'

Close
'+this.content+"
",e},r.prototype.inject=function(){return r.injectOverlay(),this.el=this.html(),t.setTimeout(function(){this.el.style.display="block"}.bind(this),150),n.body.appendChild(this.el),n.body.classList.add("galaxy-overlayed"),i.trace("Injected modal"),this.el},r.prototype.open=function(){return new Promise(function(){return this.el.classList.add("md-show")}.bind(this))},r}},{"./utils":19}],18:[function(e,t){t.exports={client:"/client.html"}},{}],19:[function(e,t){t.exports=function(e,t){"use strict";function n(t,n){console[n||"log"]((e.performance.now()/1e3).toFixed(3)+": "+t)}function r(e){return n(e,"error")}function i(e){return n(e,"warn")}function o(){"performance"in e||(e.performance={now:function(){return+new Date}}),"origin"in e.location||(e.location.origin=e.location.protocol+"//"+e.location.host)}function s(){return e.location.pathname.indexOf(".html")?e.location.search.substr(1):e.location.pathname.substr(1)}function c(e){return-1!==m.indexOf(e.target.nodeName.toLowerCase())}function a(){return"ontouchstart"in e||e.DocumentTouch&&t instanceof e.DocumentTouch}function u(e){var n=t.createElement("link");n.href=e.href,n.media="all",n.rel="stylesheet",n.type="text/css",Object.keys(e||{}).forEach(function(t){n[t]=e[t]}),t.querySelector("head").appendChild(n)}function l(e){return e?e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,"""):e}function f(){return!(t.fullscreenElement||t.mozFullScreenElement||t.webkitFullscreenElement||t.msFullscreenElement)}function p(){f()?(n("Entering full screen"),t.documentElement.requestFullscreen?t.documentElement.requestFullscreen():t.documentElement.mozRequestFullScreen?t.documentElement.mozRequestFullScreen():t.documentElement.webkitRequestFullscreen?t.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT):t.documentElement.msRequestFullscreen&&t.documentElement.msRequestFullscreen()):(n("Exiting full screen"),t.exitFullscreen?t.exitFullscreen():t.mozCancelFullScreen?t.mozCancelFullScreen():t.webkitExitFullscreen?t.webkitExitFullscreen():t.msExitFullscreen&&t.msExitFullscreen())}function h(t){var n=e.screen.LockOrientation||e.screen.mozLockOrientation||e.screen.webkitLockOrientation||e.screen.msLockOrientation;return n?n(t):i("Orientation could not be locked")}function d(n){var r=t.createEvent("HTMLEvents");r.initEvent(n,!0,!0),r.eventName=n,(t.body||e).dispatchEvent(r)}var m=["input","keygen","meter","option","output","progress","select","textarea"];return{trace:n,error:r,warn:i,polyfill:o,getPeerKey:s,fieldFocused:c,hasTouchEvents:a,injectCSS:u,escape:l,isFullScreen:f,toggleFullScreen:p,lockOrientation:h,triggerEvent:d}}},{}],20:[function(e,t){"use strict";var n={};try{n=e("./settings_local.js")}catch(r){}var i=e("./lib/utils")(window,document);i.polyfill();var o={GAMEPAD_ORIGIN:window.location.origin,WS_URL:"ws://"+location.hostname+":20500/",DEBUG:!1,VERSION:"0.0.1"};Object.keys(n).forEach(function(e){o[e]=n[e]}),t.exports=o -},{"./lib/utils":19,"./settings_local.js":21}],21:[function(e,t){t.exports={DEBUG:!0}},{}]},{},[1])(1)}); \ No newline at end of file +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;"undefined"!=typeof window?t=window:"undefined"!=typeof global?t=global:"undefined"!=typeof self&&(t=self),t.gamepad=e()}}(function(){var e;return function t(e,n,r){function i(s,c){if(!n[s]){if(!e[s]){var a="function"==typeof require&&require;if(!c&&a)return a(s,!0);if(o)return o(s,!0);var u=new Error("Cannot find module '"+s+"'");throw u.code="MODULE_NOT_FOUND",u}var l=n[s]={exports:{}};e[s][0].call(l.exports,function(t){var n=e[s][1][t];return i(n?n:t)},l,l.exports,t,e,n,r)}return n[s].exports}for(var o="function"==typeof require&&require,s=0;s

URL

'+i+'

Code

'+r+"

";return this.modal=new s({id:"pairing-screen",classes:"slim",title:"Pair your mobile phone",content:o},!0),this.modal.open().then(function(){f("Awaiting player to pair device")}).catch(function(){p("Failed to open modal"),n()}),["https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700","/css/modal.css"].forEach(function(e){a.injectCSS({href:e})}),this._pair(e).then(function(e){f("["+e.address+"] Paired to controller")}.bind(this)).then(function(){this.modal.close()}.bind(this)).catch(function(e){console.trace(e.stack?e.stack:e)})}.bind(this))},i.prototype._updateState=function(e){this.state=e,Object.keys(e||{}).forEach(function(t){!this.state[t]&&e[t]?(this._emit("buttondown",t),this._emit("buttondown."+t,t)):this.state[t]&&!e[t]&&(this._emit("buttonup",t),this._emit("buttonup."+t,t))}.bind(this))},i.prototype.hidePairingScreen=function(){this.modal.close()},i.prototype._emit=function(e,t){f('Emit "'+e+'": '+t)},i.prototype._bind=function(e,t){return"undefined"==typeof this.listeners[event]&&(this.listeners[event]=[]),this.listeners[event].push(t),this},i.prototype.unbind=function(e,t){return"undefined"==typeof e?void(this.listeners={}):"undefined"==typeof t?void(this.listeners[e]=[]):"undefined"==typeof this.listeners[e]?!1:(this.listeners[e].forEach(function(n,r){return n===t?(this.listeners[e].splice(r,1),!0):void 0}),!1)},t.exports=m}(window,document)},{"./lib/modal":17,"./lib/routes":18,"./lib/utils":19,"./settings":20,plink:6}],2:[function(e,t){function n(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function r(e){return"function"==typeof e}function i(e){return"number"==typeof e}function o(e){return"object"==typeof e&&null!==e}function s(e){return void 0===e}t.exports=n,n.EventEmitter=n,n.prototype._events=void 0,n.prototype._maxListeners=void 0,n.defaultMaxListeners=10,n.prototype.setMaxListeners=function(e){if(!i(e)||0>e||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},n.prototype.emit=function(e){var t,n,i,c,a,u;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,c=new Array(i-1),a=1;i>a;a++)c[a-1]=arguments[a];n.apply(this,c)}else if(o(n)){for(i=arguments.length,c=new Array(i-1),a=1;i>a;a++)c[a-1]=arguments[a];for(u=n.slice(),i=u.length,a=0;i>a;a++)u[a].apply(this,c)}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,c;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(c=s;c-->0;)if(n[c]===t||n[c].listener&&n[c].listener===t){i=c;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}},{}],3:[function(e,t){function n(){}var r=t.exports={};r.nextTick=function(){var e="undefined"!=typeof window&&window.setImmediate,t="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(e)return function(e){return window.setImmediate(e)};if(t){var n=[];return window.addEventListener("message",function(e){var t=e.source;if((t===window||null===t)&&"process-tick"===e.data&&(e.stopPropagation(),n.length>0)){var r=n.shift();r()}},!0),function(e){n.push(e),window.postMessage("process-tick","*")}}return function(e){setTimeout(e,0)}}(),r.title="browser",r.browser=!0,r.env={},r.argv=[],r.on=n,r.addListener=n,r.once=n,r.off=n,r.removeListener=n,r.removeAllListeners=n,r.emit=n,r.binding=function(){throw new Error("process.binding is not supported")},r.cwd=function(){return"/"},r.chdir=function(){throw new Error("process.chdir is not supported")}},{}],4:[function(e,t){t.exports=e("./lib/its.js")},{"./lib/its.js":5}],5:[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}},{}],6:[function(e,t){t.exports=e("./lib/Plink.js")},{"./lib/Plink.js":7}],7:[function(e,t){function n(e){this.p=r.create(e)}var r=e("internet"),i=e("./PlinkServer.js");n.create=function(e){return new n(e)},t.exports=n,n.prototype.connect=function(e){var t=this.p.connect(e);return i.create(t)}},{"./PlinkServer.js":8,internet:9}],8:[function(e,t){function n(e){this.promises={},this.onramp=e,this.waitForOpenQueue=[],this.onramp.on("message",this.messageHandler.bind(this)),this.onramp.on("open",this.openHandler.bind(this))}var r=e("when");n.create=function(e){return new n(e)},t.exports=n,n.prototype.on=function(){return this.onramp.on.apply(this.onramp,arguments),this},n.prototype.removeListener=function(){return this.onramp.removeListener.apply(this.onramp,arguments),this},n.prototype.openHandler=function(){this.waitForOpenQueue.forEach(function(e){e()}),this.waitForOpenQueue=[]},n.prototype.setKey=function(e,t){var n=this,i=this.promises["set"+e];return i||(i=this.promises["set"+e]=r.defer()),this.onramp.isOpen()?this.onramp.send({type:"set key",key:e,timeout:t}):this.waitForOpenQueue.push(function(){n.onramp.send({type:"set key",key:e,timeout:t})}),i.promise},n.prototype.revokeKey=function(e){var t=this.promises["revoke"+e];return t||(t=this.promises["revoke"+e]=r.defer()),this.onramp.send({type:"revoke key",key:e}),t.promise},n.prototype.useKey=function(e){var t=this,n=this.promises["use"+e];return n||(n=this.promises["use"+e]=r.defer()),this.onramp.isOpen()?this.onramp.send({type:"use key",key:e}):this.waitForOpenQueue.push(function(){t.onramp.send({type:"use key",key:e})}),n.promise},n.prototype.messageHandler=function(e){if(e.type){var t,n=e.key;switch(e.type){case"address":var r=this.onramp.connect(e.address);t=this.promises["use"+n],t.resolve(r),delete this.promises["use"+n];break;case"invalid key":t=this.promises["use"+n],t.reject(new Error("invalid key: "+e.key)),delete this.promises["use"+n];break;case"key set":t=this.promises["set"+n],t.resolve(e.key),delete this.promises["set"+n];break;case"key not set":t=this.promises["set"+n],t.reject(new Error("key not set: "+e.key)),delete this.promises["set"+n];break;case"key revoked":t=this.promises["revoke"+n],t.resolve(e.key),delete this.promises["revoke"+n];break;case"key revoked":t=this.promises["revoke"+n],t.resolve(new Error("key not revoked: "+e.key)),delete this.promises["revoke"+n]}}}},{when:16}],9:[function(e,t){t.exports=e("./lib/P.js")},{"./lib/P.js":13}],10:[function(e,t){function n(){throw new Error("This method is not implemented")}function r(e,t,n){s.string(e),s.defined(t),this.address=e,this.peers=t,n&&(n.emitter&&(this.emitter=n.emitter),n.firewall&&(this.acceptRTCConnection=n.firewall)),this.emitter||(this.emitter=new r.Emitter)}function i(e){return"[object String]"===Object.prototype.toString.call(e)}var o=e("./JSONProtocol.js"),s=e("its"),c=e("events").EventEmitter;r.createWebRTCConnection=null,r.Emitter=c,r.prototype=Object.create(o.prototype),r.prototype.on=function(){return this.emitter.on.apply(this.emitter,arguments),this},r.prototype.removeListener=function(){return this.emitter.removeListener.apply(this.emitter,arguments),this},r.prototype.send=o.prototype.writeMessage,r.prototype.getPeer=function(e){return this.peers.get(e)},r.prototype.addPeer=function(e){return this.peers.add(e)},r.prototype.getPeers=function(){return this.peers.get()},r.prototype.connect=function(e){i(e)&&(e={address:e});var t=this,n=e.firewall||this.firewall,o=r.createWebRTCConnection(e,this.peers,this,{firewall:n});return o.writeOffer(e),this.peers.add(o),o.on("close",function(){t.peers.remove(o),t.emitter.emit("disconnection",o)}),this.emitter.emit("connection",o),o},r.prototype.readMessage=function(e){this.emitter.emit("message",e)},r.prototype.readArrayBuffer=function(e){this.emitter.emit("arraybuffer",e)},r.prototype.acceptRTCConnection=function(){return!0},r.prototype.readRelay=function(e,t){var n=this.getPeer(e);return n?void n.writeRelayedMessage(this.address,t):void this.emitter.emit("error",new Error("Unknown peer at address: "+e))},r.prototype.readRelayedIceCandidate=function(e,t){var n=this.getPeer(e);return n?void n.readIceCandidate(t):void this.emitter.emit("error",new Error("Unknown peer at address: "+e))},r.prototype.readRelayedOffer=function(e,t,n){if(!this.acceptRTCConnection(t,n))return!1;var i=this,o=r.createWebRTCConnection({address:e},this.peers,this,{firewall:this.firewall});this.addPeer(o),o.on("close",function(){i.peers.remove(o),i.emitter.emit("disconnection",o)}),o.readOffer(t),o.writeAnswer(),this.emitter.emit("connection",o)},r.prototype.readRelayedAnswer=function(e,t){var n=this.getPeer(e);return n?void n.readAnswer(t):void this.emitter.emit("error",new Error("Unknown peer at address: "+e))},r.prototype.close=n,r.prototype.getReadyState=n,r.prototype.isOpen=function(){return"open"===this.getReadyState()},t.exports=r},{"./JSONProtocol.js":12,events:2,its:4}],11:[function(e,t){function n(){}function r(){this.connectionMap={},this.connectionList=[]}var i=e("its");r.prototype.get=function(e){return void 0===e?this.connectionList.slice():this.connectionMap[e]},r.prototype.add=function(e){i.defined(e);var t=e.address;return i.string(t),t in this.connectionMap?!1:(this.connectionMap[t]=e,this.connectionList.push(e),this.onAdd(e),!0)},r.prototype.onAdd=n,r.prototype.remove=function(e){i.defined(e);var t=e.address;i.string(t);var n=this.connectionMap[t];if(!n||n!==e)return!1;delete this.connectionMap[t];var r=this.connectionList.indexOf(e);return this.connectionList.splice(r,1),this.onRemove(e),!0},r.prototype.onRemove=n,t.exports=r},{its:4}],12:[function(e,t){function n(){throw new Error("This method is not implemented")}function r(){}r.prototype.PROTOCOL_NAME="p",r.prototype.MESSAGE_TYPE={DIRECT:0,RTC_OFFER:3,RTC_ANSWER:4,RTC_ICE_CANDIDATE:5,RELAY:6,RELAYED:7},r.prototype.readRaw=function(e){e instanceof ArrayBuffer?this.readArrayBuffer(e):this.readProtocolMessage(JSON.parse(e))},r.prototype.readProtocolMessage=function(e){var t=this.MESSAGE_TYPE,n=e[0];switch(n){case t.DIRECT:this.readMessage(e[1]);break;case t.RELAYED:this.readRelayedMessage(e[1],e[2]);break;case t.RELAY:this.readRelay(e[1],e[2]);break;default:throw new Error("Unknown message type: "+n)}},r.prototype.readRelayedMessage=function(e,t){var n=this.MESSAGE_TYPE,r=t[0];switch(r){case n.RTC_OFFER:this.readRelayedOffer(e,t[1],t[2]);break;case n.RTC_ANSWER:this.readRelayedAnswer(e,t[1]);break;case n.RTC_ICE_CANDIDATE:this.readRelayedIceCandidate(e,t[1]);break;default:throw new Error("Unknown message type: "+r)}},r.prototype.readMessage=n,r.prototype.readArrayBuffer=n,r.prototype.readRelay=n,r.prototype.readRelayedOffer=n,r.prototype.readRelayedAnswer=n,r.prototype.readRelayedIceCandidate=n,r.prototype.writeRaw=n,r.prototype.writeProtocolMessage=function(e){var t=JSON.stringify(e);this.writeRaw(t)},r.prototype.writeMessage=function(e){e instanceof ArrayBuffer?this.writeRaw(e):this.writeStringMessage(e)},r.prototype.writeStringMessage=function(e){this.writeProtocolMessage([this.MESSAGE_TYPE.DIRECT,e])},r.prototype.writeRelayedMessage=function(e,t){this.writeProtocolMessage([this.MESSAGE_TYPE.RELAYED,e,t])},r.prototype.writeRelayMessage=function(e,t){this.writeProtocolMessage([this.MESSAGE_TYPE.RELAY,e,t])},r.prototype.writeRelayAnswer=function(e,t){this.writeRelayMessage(e,[this.MESSAGE_TYPE.RTC_ANSWER,t])},r.prototype.writeRelayIceCandidate=function(e,t){this.writeRelayMessage(e,[this.MESSAGE_TYPE.RTC_ICE_CANDIDATE,t])},r.prototype.writeRelayOffer=function(e,t,n){this.writeRelayMessage(e,[this.MESSAGE_TYPE.RTC_OFFER,t,n])},t.exports=r},{}],13:[function(e,t){function n(e,t,n){s.defined(e),s.defined(t),this.emitter=e,this.peers=t,this.peers.onAdd=function(t){e.emit("connection",t)},this.peers.onRemove=function(t){e.emit("disconnection",t)},n&&n.firewall&&(this.firewall=n.firewall)}var r=e("events").EventEmitter,i=e("./ConnectionManager.js"),o=e("./WebSocketConnection.js"),s=(e("./WebRTCConnection.js"),e("its"));n.create=function(e){var t=new r,o=new i;return new n(t,o,e)},n.prototype.getPeers=function(){return this.peers.get()},n.prototype.connect=function(e){s.string(e);var t=this.peers,n=o.create(e,this.peers,{firewall:this.firewall});return t.add(n),n.on("close",function(){t.remove(n)}),n},n.prototype.on=function(){return this.emitter.on.apply(this.emitter,arguments),this},n.prototype.removeListener=function(){return this.emitter.removeListener.apply(this.emitter,arguments),this},t.exports=n},{"./ConnectionManager.js":11,"./WebRTCConnection.js":14,"./WebSocketConnection.js":15,events:2,its:4}],14:[function(e,t){function n(e,t,n,o,s){var c=this;i.string(e),i.defined(t),i.defined(n),i.defined(o),r.call(this,e,t,s),this.signalingChannel=o,this.rtcConnection=n,this.rtcDataChannel=n.createDataChannel(this.PROTOCOL_NAME,{reliable:!1}),this.close=n.close.bind(n),this.rtcConnection.addEventListener("icecandidate",function(t){t.candidate&&c.signalingChannel.writeRelayIceCandidate(e,t.candidate)}),this.rtcDataChannel.addEventListener("message",function(e){c.readRaw(e.data)}),this.rtcDataChannel.addEventListener("open",function(e){c.emitter.emit("open",e)}),this.rtcDataChannel.addEventListener("error",function(e){c.emitter.emit("error",e)}),this.rtcDataChannel.addEventListener("close",function(e){c.emitter.emit("close",e)}),this.rtcDataChannel.addEventListener("removestream",function(e){c.emitter.emit("removestream",e)}),this.rtcDataChannel.addEventListener("icestatechange",function(e){c.emitter.emit("icestatechange",e)}),this.rtcDataChannel.addEventListener("icechange",function(e){c.emitter.emit("icechange",e)})}var r=e("./Connection.js"),i=e("its"),o="undefined"!=typeof RTCPeerConnection?RTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection?webkitRTCPeerConnection:"undefined"!=typeof mozRTCPeerConnection?mozRTCPeerConnection:void 0,s="undefined"!=typeof RTCSessionDescription?RTCSessionDescription:"undefined"!=typeof mozRTCSessionDescription?mozRTCSessionDescription:void 0,c="undefined"!=typeof RTCIceCandidate?RTCIceCandidate:"undefined"!=typeof mozRTCIceCandidate?mozRTCIceCandidate:void 0,a=null,u={optional:[{RtpDataChannels:!0}],mandatory:{OfferToReceiveAudio:!1,OfferToReceiveVideo:!1}};n.create=function(e,t,r,i){var s=e.rtcConfiguration||a,c=e.mediaConstraints||u,l=new o(s,c);return new n(e.address,t,l,r,i)},n.prototype=Object.create(r.prototype),n.prototype.writeRaw=function(e){switch(this.rtcDataChannel.readyState){case"connecting":throw new Error("Can't send a message while RTCDataChannel connecting");case"open":this.rtcDataChannel.send(e);break;case"closing":case"closed":throw new Error("Can't send a message while RTCDataChannel is closing or closed")}},n.prototype.readAnswer=function(e){var t=new s(e);this.rtcConnection.setRemoteDescription(t)},n.prototype.readOffer=function(e){var t=new s(e);this.rtcConnection.setRemoteDescription(t)},n.prototype.readIceCandidate=function(e){this.emitter,this.rtcConnection.addIceCandidate(new c(e))},n.prototype.writeAnswer=function(){function e(e){t.emit("error",e)}var t=this.emitter,n=this.address,r=this.rtcConnection,i=this.signalingChannel;r.createAnswer(function(t){r.setLocalDescription(t,function(){i.writeRelayAnswer(n,t)},e)},e)},n.prototype.writeOffer=function(e){function t(e){n.emit("error",e)}var n=this.emitter,r=this.address,i=this.rtcConnection,o=this.signalingChannel;i.createOffer(function(n){i.setLocalDescription(n,function(){o.writeRelayOffer(r,n,e.offerData)},t)},t,e.mediaConstraints||u)},n.prototype.getReadyState=function(){return this.rtcDataChannel.readyState},r.createWebRTCConnection=n.create,t.exports=n},{"./Connection.js":10,its:4}],15:[function(e,t){function n(e,t,n,i){var o=this;r.call(this,e,t,i),this.webSocket=n,this.close=n.close.bind(n),this.webSocket.addEventListener("message",function(e){o.readRaw(e.data)}),this.webSocket.addEventListener("open",function(e){o.emitter.emit("open",e)}),this.webSocket.addEventListener("error",function(e){o.emitter.emit("error",e)}),this.webSocket.addEventListener("close",function(e){o.emitter.emit("close",e)})}var r=e("./Connection.js");n.create=function(e,t,r){var i=new WebSocket(e,n.prototype.PROTOCOL_NAME);return new n(e,t,i,r)},n.prototype=Object.create(r.prototype),n.prototype.writeRaw=function(e){switch(this.webSocket.readyState){case WebSocket.CONNECTING:throw new Error("Can't send a message while WebSocket connecting");case WebSocket.OPEN:this.webSocket.send(e);break;case WebSocket.CLOSING:case WebSocket.CLOSED:throw new Error("Can't send a message while WebSocket is closing or closed")}},n.prototype.getReadyState=function(){switch(this.webSocket.readyState){case WebSocket.CONNECTING:return"connecting";case WebSocket.OPEN:return"open";case WebSocket.CLOSING:return"closing";case WebSocket.CLOSED:return"closed"}},t.exports=n},{"./Connection.js":10}],16:[function(t,n){(function(r){!function(e){"use strict";e(function(e){function t(e,t,n,r){return o(e).then(t,n,r)}function n(e){return new i(e,q.PromiseStatus&&q.PromiseStatus())}function i(e,t){function n(){return a?a.inspect():O()}function r(e,t,n,r,i){function o(o){o._when(e,t,n,r,i)}f?f.push(o):L(function(){o(a)})}function i(e){if(f){var n=f;f=K,L(function(){a=l(c,e),t&&m(a,t),u(n,a)})}}function o(e){i(new h(e))}function s(e){if(f){var t=f;L(function(){u(t,new d(e))})}}var c,a,f=[];c=this,this._status=t,this.inspect=n,this._when=r;try{e(i,o,s)}catch(p){o(p)}}function o(e){return e instanceof i?e:s(e)}function s(e){return n(function(t){t(e)})}function c(e){return t(e,function(e){return new h(e)})}function a(){function e(e,n,o){t.resolve=t.resolver.resolve=function(t){return i?s(t):(i=!0,e(t),r)},t.reject=t.resolver.reject=function(e){return i?s(new h(e)):(i=!0,n(e),r)},t.notify=t.resolver.notify=function(e){return o(e),e}}var t,r,i;return t={promise:K,resolve:K,reject:K,notify:K,resolver:{resolve:K,reject:K,notify:K}},t.promise=r=n(e),t}function u(e,t){for(var n=0;n>>0,a=Math.max(0,Math.min(r,d)),l=[],u=d-a+1,f=[],a)for(h=function(e){f.push(e),--u||(p=h=j,i(f))},p=function(e){l.push(e),--a||(p=h=j,n(l))},m=0;d>m;++m)m in e&&t(e[m],c,s,o);else n(l)}return n(c).then(i,o,s)})}function g(e,t,n,r){function i(e){return t?t(e[0]):e[0]}return v(e,1,i,n,r)}function w(e,t,n,r){return k(e,j).then(t,n,r)}function b(){return k(arguments,j)}function E(e){return k(e,_,S)}function C(e,t){return k(e,t)}function k(e,n,r){return t(e,function(e){function o(i,o,s){function c(e,c){t(e,n,r).then(function(e){a[c]=e,--l||i(a)},o,s)}var a,u,l,f;if(l=u=e.length>>>0,a=[],!l)return void i(a);for(f=0;u>f;f++)f in e?c(e[f],f):--l}return new i(o)})}function R(e,n){var r=N(D,arguments,1);return t(e,function(e){var i;return i=e.length,r[0]=function(e,r,o){return t(e,function(e){return t(r,function(t){return n(e,t,o,i)})})},M.apply(e,r)})}function _(e){return{state:"fulfilled",value:e}}function S(e){return{state:"rejected",reason:e}}function O(){return{state:"pending"}}function L(e){1===I.push(e)&&F(T)}function T(){u(I),I=[]}function j(e){return e}function x(e){throw"function"==typeof q.reportUnhandled?q.reportUnhandled():L(function(){throw e}),e}t.promise=n,t.resolve=s,t.reject=c,t.defer=a,t.join=b,t.all=w,t.map=C,t.reduce=R,t.settle=E,t.any=g,t.some=v,t.isPromise=y,t.isPromiseLike=y,A=i.prototype,A.then=function(e,t,n){var r=this;return new i(function(i,o,s){r._when(i,s,e,t,n)},this._status&&this._status.observed())},A["catch"]=A.otherwise=function(e){return this.then(K,e)},A["finally"]=A.ensure=function(e){function t(){return s(e())}return"function"==typeof e?this.then(t,t)["yield"](this):this},A.done=function(e,t){this.then(e,t)["catch"](x)},A["yield"]=function(e){return this.then(function(){return e})},A.tap=function(e){return this.then(e)["yield"](this)},A.spread=function(e){return this.then(function(t){return w(t,function(t){return e.apply(K,t)})})},A.always=function(e,t){return this.then(e,e,t)},P=Object.create||function(e){function t(){}return t.prototype=e,new t},p.prototype=P(A),p.prototype.inspect=function(){return _(this.value)},p.prototype._when=function(e,t,n){try{e("function"==typeof n?n(this.value):this.value)}catch(r){e(new h(r))}},h.prototype=P(A),h.prototype.inspect=function(){return S(this.value)},h.prototype._when=function(e,t,n,r){try{e("function"==typeof r?r(this.value):this)}catch(i){e(new h(i))}},d.prototype=P(A),d.prototype._when=function(e,t,n,r,i){try{t("function"==typeof i?i(this.value):this.value)}catch(o){t(o)}};var A,P,M,D,N,F,I,W,G,U,q,Y,z,J,K;if(z=e,I=[],q="undefined"!=typeof console?console:t,"object"==typeof r&&r.nextTick)F=r.nextTick;else if(J="function"==typeof MutationObserver&&MutationObserver||"function"==typeof WebKitMutationObserver&&WebKitMutationObserver)F=function(e,t,n){var r=e.createElement("div");return new t(n).observe(r,{attributes:!0}),function(){r.setAttribute("x","x")}}(document,J,T);else try{F=z("vertx").runOnLoop||z("vertx").runOnContext}catch(B){Y=setTimeout,F=function(e){Y(e,0)}}return W=Function.prototype,G=W.call,N=W.bind?G.bind(G):function(e,t){return e.apply(t,D.call(arguments,2))},U=[],D=U.slice,M=U.reduce||function(e){var t,n,r,i,o;if(o=0,t=Object(this),i=t.length>>>0,n=arguments,n.length<=1)for(;;){if(o in t){r=t[o++];break}if(++o>=i)throw new TypeError}else r=n[1];for(;i>o;++o)o in t&&(r=e(r,t[o],o,t));return r},t})}("function"==typeof e&&e.amd?e:function(e){n.exports=e(t)})}).call(this,t("_process"))},{_process:3}],17:[function(e,t){t.exports=function(t,n){"use strict";function r(e,t){Object.keys(e).forEach(function(t){this[t]=e[t]}.bind(this)),t&&this.inject()}var i=e("./utils")(t,n);return r.closeAll=r.prototype.close=function(){var e=n.querySelector(".md-show");e&&(e.classList.remove("md-show"),i.trace("Closed modal")),setTimeout(function(){n.body.classList.remove("galaxy-overlayed"),i.trace("Hid overlay")},150)},r.injectOverlay=function(){if(i.trace("Injected overlay"),!n.querySelector(".md-overlay")){var e=n.createElement("div");e.className="md-overlay",n.body.appendChild(e),i.trace("Added overlay")}},r.prototype.html=function(){var e=n.createElement("div");return e.id="modal-"+this.id,e.className="md-modal md-effect-1 "+(this.classes||""),e.style.display="none",e.innerHTML='

'+i.escape(this.title)+'

Close
'+this.content+"
",i.trace("Created modal DOM"),e},r.prototype.inject=function(){return r.injectOverlay(),this.el=this.html(),t.setTimeout(function(){this.el.style.display="block"}.bind(this),150),n.body.appendChild(this.el),n.body.classList.add("galaxy-overlayed"),i.trace("Injected modal"),this.el},r.prototype.open=function(){return new Promise(function(){return i.trace("Opened modal"),this.el.classList.add("md-show")}.bind(this))},r}},{"./utils":19}],18:[function(e,t){t.exports={client:"/client.html"}},{}],19:[function(e,t){t.exports=function(e,t){"use strict";function n(t,n){console[n||"log"]((e.performance.now()/1e3).toFixed(3)+": "+t)}function r(e){return n(e,"error")}function i(e){return n(e,"warn")}function o(){"performance"in e||(e.performance={now:function(){return+new Date}}),"origin"in e.location||(e.location.origin=e.location.protocol+"//"+e.location.host)}function s(){return e.location.pathname.indexOf(".html")?e.location.search.substr(1):e.location.pathname.substr(1)}function c(e){return-1!==m.indexOf(e.target.nodeName.toLowerCase())}function a(){return"ontouchstart"in e||e.DocumentTouch&&t instanceof e.DocumentTouch}function u(e){var n=t.createElement("link");n.href=e.href,n.media="all",n.rel="stylesheet",n.type="text/css",Object.keys(e||{}).forEach(function(t){n[t]=e[t]}),t.querySelector("head").appendChild(n)}function l(e){return e?e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,"""):e}function f(){return!(t.fullscreenElement||t.mozFullScreenElement||t.webkitFullscreenElement||t.msFullscreenElement)}function p(){f()?(n("Entering full screen"),t.documentElement.requestFullscreen?t.documentElement.requestFullscreen():t.documentElement.mozRequestFullScreen?t.documentElement.mozRequestFullScreen():t.documentElement.webkitRequestFullscreen?t.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT):t.documentElement.msRequestFullscreen&&t.documentElement.msRequestFullscreen()):(n("Exiting full screen"),t.exitFullscreen?t.exitFullscreen():t.mozCancelFullScreen?t.mozCancelFullScreen():t.webkitExitFullscreen?t.webkitExitFullscreen():t.msExitFullscreen&&t.msExitFullscreen()) +}function h(t){return"lockOrientation"in e.screen?e.screen.lockOrientation(t):"mozLockOrientation"in e.screen?e.screen.mozLockOrientation(t):"webkitLockOrientation"in e.screen?e.screen.webkitLockOrientation(t):"msLockOrientation"in e.screen?e.screen.msLockOrientation(t):i("Orientation could not be locked")}function d(n){var r=t.createEvent("HTMLEvents");r.initEvent(n,!0,!0),r.eventName=n,(t.body||e).dispatchEvent(r)}var m=["input","keygen","meter","option","output","progress","select","textarea"];return{trace:n,error:r,warn:i,polyfill:o,getPeerKey:s,fieldFocused:c,hasTouchEvents:a,injectCSS:u,escape:l,isFullScreen:f,toggleFullScreen:p,lockOrientation:h,triggerEvent:d}}},{}],20:[function(e,t){"use strict";var n={};try{n=e("./settings_local.js")}catch(r){}var i=e("./lib/utils")(window,document);i.polyfill();var o={GAMEPAD_ORIGIN:window.location.origin,WS_URL:"ws://"+location.hostname+":20500/",DEBUG:!1,VERSION:"0.0.1"};Object.keys(n).forEach(function(e){o[e]=n[e]}),t.exports=o},{"./lib/utils":19,"./settings_local.js":21}],21:[function(e,t){t.exports={DEBUG:!0}},{}]},{},[1])(1)}); \ No newline at end of file