Buckets:
| /* | |
| The MIT License | |
| Copyright (c) 2012, Mozilla Foundation | |
| Copyright (c) 2012, Alan Kligman | |
| All rights reserved. | |
| Permission is hereby granted, free of charge, to any person obtaining a copy | |
| of this software and associated documentation files (the "Software"), to deal | |
| in the Software without restriction, including without limitation the rights | |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| copies of the Software, and to permit persons to whom the Software is | |
| furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in | |
| all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| THE SOFTWARE. | |
| */ | |
| (function() { | |
| /* Notes | |
| * | |
| * - Continue using prefixed names for now. | |
| * | |
| */ | |
| var io = SocketIO; | |
| var webrtcSupported = true; | |
| var RTCPeerConnection; | |
| if(window.mozRTCPeerConnection) | |
| RTCPeerConnection = window.mozRTCPeerConnection; | |
| else if(window.webkitRTCPeerConnection) | |
| RTCPeerConnection = window.webkitRTCPeerConnection; | |
| else if(window.RTCPeerConnection) | |
| RTCPeerConnection = window.RTCPeerConnection | |
| else | |
| webrtcSupported = false; | |
| var RTCSessionDescription; | |
| if(window.mozRTCSessionDescription) | |
| RTCSessionDescription = window.mozRTCSessionDescription; | |
| else if(window.webkitRTCSessionDescription) | |
| RTCSessionDescription = window.webkitRTCSessionDescription; | |
| else if(window.RTCSessionDescription) | |
| RTCSessionDescription = window.RTCSessionDescription | |
| else | |
| webrtcSupported = false; | |
| var RTCIceCandidate; | |
| if(window.mozRTCIceCandidate) | |
| RTCIceCandidate = window.mozRTCIceCandidate; | |
| else if(window.webkitRTCIceCandidate) | |
| RTCIceCandidate = window.webkitRTCIceCandidate; | |
| else if(window.RTCIceCandidate) | |
| RTCIceCandidate = window.RTCIceCandidate; | |
| else | |
| webrtcSupported = false; | |
| var getUserMedia; | |
| if(!navigator.getUserMedia) { | |
| if(navigator.mozGetUserMedia) | |
| getUserMedia = navigator.mozGetUserMedia.bind(navigator); | |
| else if(navigator.webkitGetUserMedia) | |
| getUserMedia = navigator.webkitGetUserMedia.bind(navigator); | |
| else | |
| webrtcSupported = false; | |
| } else { | |
| getUserMedia = navigator.getUserMedia.bind(navigator); | |
| } | |
| // FIXME: browser detection is gross, but I don't see another way to do this | |
| var RTCConnectProtocol; | |
| if(window.mozRTCPeerConnection) { | |
| RTCConnectProtocol = mozRTCConnectProtocol; | |
| } else if(window.webkitRTCPeerConnection) { | |
| RTCConnectProtocol = webkitRTCConnectProtocol; | |
| } else { | |
| webrtcSupported = false; | |
| } | |
| function callback(object, method, args) { | |
| if(!Array.isArray(args)) | |
| args = [args]; | |
| if(method in object && 'function' === typeof object[method]) { | |
| object[method].apply(object, args); | |
| } | |
| }; | |
| function fail(object, method, error) { | |
| if (!(error instanceof Error)) | |
| error = new Error(error); | |
| callback(object, method, [error]); | |
| }; | |
| function defer(queue, object, method, args) { | |
| if(queue) { | |
| queue.push([object, method, args]); | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| }; | |
| function processDeferredQueue(queue) { | |
| while(queue.length) { | |
| var deferred = queue.shift(); | |
| callback(deferred[0], deferred[1], deferred[2]); | |
| } | |
| }; | |
| var ONE_SECOND = 1000; // milliseconds | |
| var DEFAULT_CONNECTION_TIMEOUT = 10 * ONE_SECOND; | |
| var DEFAULT_PING_TIMEOUT = 1 * ONE_SECOND; | |
| var RELIABLE_CHANNEL_OPTIONS = { | |
| reliable: false | |
| }; | |
| var UNRELIABLE_CHANNEL_OPTIONS = { | |
| outOfOrderAllowed: true, | |
| maxRetransmitNum: 0, | |
| reliable: false | |
| }; | |
| function PendingConnectionAbortError(message) { | |
| this.name = "PendingConnectionAbortError"; | |
| this.message = (message || ""); | |
| }; | |
| PendingConnectionAbortError.prototype = Error.prototype; | |
| function ConnectionFailedError(message) { | |
| this.name = "ConnectionFailedError"; | |
| this.message = (message || ""); | |
| }; | |
| ConnectionFailedError.prototype = Error.prototype; | |
| var E = { | |
| PendingConnectionAbortError: PendingConnectionAbortError, | |
| ConnectionFailedError: ConnectionFailedError | |
| }; | |
| function WebSocketBroker(brokerUrl) { | |
| this.brokerUrl = brokerUrl; | |
| this.state = WebSocketBroker.OFFLINE; | |
| this.onstatechange = null; | |
| this.onreceive = null; | |
| this.onerror = null; | |
| this.socket = null; | |
| this.route = null; | |
| }; | |
| // States | |
| WebSocketBroker.OFFLINE = 0x00; | |
| WebSocketBroker.CONNECTING = 0x01; | |
| WebSocketBroker.CONNECTED = 0x02; | |
| // Flags | |
| WebSocketBroker.ROUTED = 0x10; | |
| WebSocketBroker.LISTENING = 0x20; | |
| WebSocketBroker.prototype.setState = function setState(state, clearFlags) { | |
| var clear = clearFlags ? 0x00 : 0xF0; | |
| this.state &= clear >>> 0; | |
| this.state |= state >>> 0; | |
| callback(this, 'onstatechange', [this.state, (state | (clear & 0x0)) >>> 0]); | |
| }; | |
| WebSocketBroker.prototype.setFlag = function setFlag(flag) { | |
| this.state = (this.state | flag) >>> 0; | |
| callback(this, 'onstatechange', [this.state, flag]) | |
| }; | |
| WebSocketBroker.prototype.clearFlag = function clearFlag(flag) { | |
| flag = (~flag) >>> 0; | |
| this.state = (this.state & flag) >>> 0; | |
| callback(this, 'onstatechange', [this.state, flag]) | |
| }; | |
| WebSocketBroker.prototype.checkState = function checkState(mask) { | |
| return !!(this.state & mask); | |
| }; | |
| WebSocketBroker.prototype.connect = function connect() { | |
| var that = this; | |
| var socket = io.connect(this.brokerUrl + '/peer', { | |
| 'sync disconnect on unload': true // partially fixes 'interrupted while page loading' warning | |
| }); | |
| socket.on('connecting', function onconnecting() { | |
| that.setState(WebSocketBroker.CONNECTING, true); | |
| }); | |
| socket.on('connect', function onconnect() { | |
| that.setState(WebSocketBroker.CONNECTED, true); | |
| }); | |
| socket.on('connect_failed', function onconnect_failed() { | |
| that.setState(WebSocketBroker.OFFLINE, true); | |
| }); | |
| socket.on('route', function onroute(route) { | |
| that.route = route; | |
| that.setFlag(WebSocketBroker.ROUTED); | |
| }); | |
| socket.on('disconnect', function ondisconnect() { | |
| that.setState(WebSocketBroker.OFFLINE, true); | |
| }); | |
| socket.on('error', function onerror(error) { | |
| fail(that, 'onerror', error); | |
| }); | |
| socket.on('receive', function onreceive(message) { | |
| var from = message['from']; | |
| var data = message['data']; | |
| callback(that, 'onreceive', [from, data]); | |
| }); | |
| this.socket = socket; | |
| }; | |
| WebSocketBroker.prototype.disconnect = function disconnect() { | |
| if(this.checkState(WebSocketBroker.CONNECTED)) { | |
| this.socket.disconnect(); | |
| this.setState(WebSocketBroker.OFFLINE, true); | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| }; | |
| WebSocketBroker.prototype.listen = function listen(options) { | |
| var that = this; | |
| if(this.checkState(WebSocketBroker.CONNECTED)) { | |
| this.socket.emit('listen', options, function onresponse(response) { | |
| if(response && response['error']) { | |
| var error = new Error(response['error']); | |
| fail(that, 'onerror', error); | |
| } else { | |
| that.setFlag(WebSocketBroker.LISTENING); | |
| } | |
| }); | |
| } | |
| }; | |
| WebSocketBroker.prototype.ignore = function ignore() { | |
| var that = this; | |
| if(this.checkState(WebSocketBroker.CONNECTED)) { | |
| this.socket.emit('ignore', null, function onresponse(response) { | |
| if(response && response['error']) { | |
| var error = new Error(response['error']); | |
| fail(that, 'onerror', error) | |
| } else { | |
| that.clearFlag(WebSocketBroker.LISTENING); | |
| } | |
| }); | |
| } | |
| }; | |
| WebSocketBroker.prototype.send = function send(to, message) { | |
| var that = this; | |
| if(this.checkState(WebSocketBroker.CONNECTED)) { | |
| this.socket.emit('send', {'to': to, 'data': message}, function onresponse(response) { | |
| if(response && response['error']) { | |
| var error = new Error(response['error']); | |
| fail(that, 'onerror', error) | |
| } | |
| }); | |
| }; | |
| }; | |
| var dataChannels = { | |
| 'reliable': 'RELIABLE', | |
| 'unreliable': 'UNRELIABLE', | |
| '@control': 'RELIABLE' | |
| }; | |
| var nextDataConnectionPort = 1; | |
| function CommonRTCConnectProtocol() { | |
| // FIXME: these timeouts should be configurable | |
| this.connectionTimeout = 10 * ONE_SECOND; | |
| this.pingTimeout = 1 * ONE_SECOND; | |
| }; | |
| CommonRTCConnectProtocol.prototype.process = function process(message) { | |
| var that = this; | |
| var type = message['type']; | |
| switch (type) { | |
| case 'ice': | |
| var candidate = JSON.parse(message['candidate']); | |
| if(candidate) | |
| this.handleIce(candidate); | |
| break; | |
| case 'offer': | |
| that.ports.remote = message['port']; | |
| var offer = { | |
| 'type': 'offer', | |
| 'sdp': message['description'] | |
| }; | |
| this.handleOffer(offer); | |
| break; | |
| case 'answer': | |
| that.ports.remote = message['port']; | |
| var answer = { | |
| 'type': 'answer', | |
| 'sdp': message['description'] | |
| }; | |
| this.handleAnswer(answer); | |
| break; | |
| case 'abort': | |
| this.handleAbort(); | |
| break; | |
| default: | |
| fail(this, 'onerror', 'unknown message'); | |
| } | |
| }; | |
| CommonRTCConnectProtocol.prototype.handleAbort = function handleAbort() { | |
| fail(this, 'onerror', new Error(E.RTCConnectProtocolAbort)); | |
| }; | |
| CommonRTCConnectProtocol.prototype.initialize = function initialize(cb) { | |
| var that = this; | |
| if(this.peerConnection) | |
| return cb(); | |
| // FIXME: peer connection servers should be configurable | |
| this.peerConnection = new RTCPeerConnection(this.connectionServers, this.connectionOptions); | |
| this.peerConnection.onicecandidate = function(event) { | |
| var message = { | |
| 'type': 'ice', | |
| 'candidate': JSON.stringify(event.candidate) | |
| }; | |
| callback(that, 'onmessage', message); | |
| }; | |
| this.peerConnection.onaddstream = function(event) { | |
| that.streams['remote'] = event.stream; | |
| }; | |
| this.peerConnection.onstatechange = function(event) { | |
| console.log(event.target.readyState); | |
| }; | |
| function createStream(useFake) { | |
| useFake = (!useVideo && !useAudio) ? true : useFake; | |
| var useVideo = !!that.options['video']; | |
| var useAudio = !!that.options['audio']; | |
| var mediaOptions = { | |
| video: useVideo, | |
| audio: (!useVideo && !useAudio) ? true : useAudio, | |
| fake: useFake | |
| }; | |
| getUserMedia(mediaOptions, | |
| function(stream) { | |
| that.peerConnection.addStream(stream); | |
| that.streams['local'] = stream; | |
| cb(); | |
| }, | |
| function(error) { | |
| console.error('!', error); | |
| if(!useFake) | |
| createStream(true); | |
| else | |
| fail(that, 'onerror', error); | |
| } | |
| ); | |
| } | |
| createStream(); | |
| }; | |
| CommonRTCConnectProtocol.prototype.handleIce = function handleIce(candidate) { | |
| var that = this; | |
| function setIce() { | |
| if(!that.peerConnection.remoteDescription) { | |
| return | |
| } | |
| that.peerConnection.addIceCandidate(new RTCIceCandidate(candidate), | |
| function() {}, | |
| function(error) { | |
| fail(that, 'onerror', error); | |
| } | |
| ); | |
| }; | |
| this.initialize(setIce); | |
| }; | |
| CommonRTCConnectProtocol.prototype.initiate = function initiate() { | |
| var that = this; | |
| this.initiator = true; | |
| function createDataChannels() { | |
| var labels = Object.keys(dataChannels); | |
| labels.forEach(function(label) { | |
| var channelOptions = that.channelOptions[dataChannels[label]]; | |
| var channel = that._pending[label] = that.peerConnection.createDataChannel(label, channelOptions); | |
| channel.binaryType = that.options['binaryType']; | |
| channel.onopen = function() { | |
| that.channels[label] = channel; | |
| delete that._pending[label]; | |
| if(Object.keys(that.channels).length === labels.length) { | |
| that.complete = true; | |
| callback(that, 'oncomplete', []); | |
| } | |
| }; | |
| channel.onerror = function(error) { | |
| console.error(error); | |
| fail(that, 'onerror', error); | |
| }; | |
| }); | |
| createOffer(); | |
| }; | |
| function createOffer() { | |
| that.peerConnection.createOffer(setLocal, | |
| function(error) { | |
| fail(that, 'onerror', error); | |
| } | |
| ); | |
| }; | |
| function setLocal(description) { | |
| that.peerConnection.setLocalDescription(new RTCSessionDescription(description), complete, | |
| function(error) { | |
| fail(that, 'onerror', error); | |
| } | |
| ); | |
| function complete() { | |
| var message = { | |
| 'type': 'offer', | |
| 'description': description['sdp'], | |
| 'port': that.ports.local | |
| }; | |
| callback(that, 'onmessage', message); | |
| }; | |
| }; | |
| this.initialize(createDataChannels); | |
| }; | |
| CommonRTCConnectProtocol.prototype.handleOffer = function handleOffer(offer) { | |
| var that = this; | |
| function handleDataChannels() { | |
| var labels = Object.keys(dataChannels); | |
| that.peerConnection.ondatachannel = function(event) { | |
| var channel = event.channel; | |
| var label = channel.label; | |
| that._pending[label] = channel; | |
| channel.binaryType = that.options['binaryType']; | |
| channel.onopen = function() { | |
| that.channels[label] = channel; | |
| delete that._pending[label]; | |
| if(Object.keys(that.channels).length === labels.length) { | |
| that.complete = true; | |
| callback(that, 'oncomplete', []); | |
| } | |
| }; | |
| channel.onerror = function(error) { | |
| console.error(error); | |
| fail(that, 'onerror', error); | |
| }; | |
| }; | |
| setRemote(); | |
| }; | |
| function setRemote() { | |
| that.peerConnection.setRemoteDescription(new RTCSessionDescription(offer), createAnswer, | |
| function(error) { | |
| fail(that, 'onerror', error); | |
| } | |
| ); | |
| }; | |
| function createAnswer() { | |
| that.peerConnection.createAnswer(setLocal, | |
| function(error) { | |
| fail(that, 'onerror', error); | |
| } | |
| ); | |
| }; | |
| function setLocal(description) { | |
| that.peerConnection.setLocalDescription(new RTCSessionDescription(description), complete, | |
| function(error) { | |
| fail(that, 'onerror', error); | |
| } | |
| ); | |
| function complete() { | |
| var message = { | |
| 'type': 'answer', | |
| 'description': description['sdp'], | |
| 'port': that.ports.local | |
| }; | |
| callback(that, 'onmessage', message); | |
| }; | |
| }; | |
| this.initialize(handleDataChannels); | |
| }; | |
| CommonRTCConnectProtocol.prototype.handleAnswer = function handleAnswer(answer) { | |
| var that = this; | |
| function setRemote() { | |
| that.peerConnection.setRemoteDescription(new RTCSessionDescription(answer), complete, | |
| function(error) { | |
| fail(that, 'onerror', error); | |
| } | |
| ); | |
| }; | |
| function complete() { | |
| }; | |
| this.initialize(setRemote); | |
| }; | |
| function mozRTCConnectProtocol(options) { | |
| this.options = options; | |
| this.onmessage = null; | |
| this.oncomplete = null; | |
| this.onerror = null; | |
| this.complete = false; | |
| this.ports = { | |
| local: nextDataConnectionPort ++, | |
| remote: null | |
| }; | |
| this.streams = { | |
| local: null, | |
| remote: null | |
| }; | |
| this.initiator = false; | |
| this.peerConnection = null; | |
| this.channels = {}; | |
| this._pending = {}; | |
| this.connectionServers = null; | |
| this.connectionOptions = null; | |
| this.channelOptions = { | |
| RELIABLE: { | |
| // defaults | |
| }, | |
| UNRELIABLE: { | |
| outOfOrderAllowed: true, | |
| maxRetransmitNum: 0 | |
| } | |
| }; | |
| }; | |
| mozRTCConnectProtocol.prototype = new CommonRTCConnectProtocol(); | |
| mozRTCConnectProtocol.prototype.constructor = mozRTCConnectProtocol; | |
| function webkitRTCConnectProtocol(options) { | |
| this.options = options; | |
| this.onmessage = null; | |
| this.oncomplete = null; | |
| this.onerror = null; | |
| this.complete = false; | |
| this.ports = { | |
| local: nextDataConnectionPort ++, | |
| remote: null | |
| }; | |
| this.streams = { | |
| local: null, | |
| remote: null | |
| }; | |
| this.initiator = false; | |
| this.peerConnection = null; | |
| this.channels = {}; | |
| this._pending = {}; | |
| this.connectionServers = {iceServers:[{url:'stun:23.21.150.121'}]}; | |
| this.connectionOptions = { | |
| 'optional': [{ 'RtpDataChannels': true }] | |
| }; | |
| this.channelOptions = { | |
| RELIABLE: { | |
| // FIXME: reliable channels do not work in chrome yet | |
| reliable: false | |
| }, | |
| UNRELIABLE: { | |
| reliable: false | |
| } | |
| }; | |
| }; | |
| webkitRTCConnectProtocol.prototype = new CommonRTCConnectProtocol(); | |
| webkitRTCConnectProtocol.prototype.constructor = webkitRTCConnectProtocol; | |
| // FIXME: this could use a cleanup | |
| var nextConnectionId = 1; | |
| function Connection(options, peerConnection, streams, channels) { | |
| var that = this; | |
| this.id = nextConnectionId ++; | |
| this.streams = streams; | |
| this.connected = false; | |
| this.messageFlag = false; | |
| this.onmessage = null; | |
| this.ondisconnect = null; | |
| this.onerror = null; | |
| this.peerConnection = peerConnection; | |
| // DataChannels | |
| this.channels = channels; | |
| this.connectionTimer = null; | |
| this.pingTimer = null; | |
| function handleConnectionTimerExpired() { | |
| if(!that.connected) | |
| return | |
| this.connectionTimer = null; | |
| if(false === that.messageFlag) { | |
| that.channels['@control'].send('ping'); | |
| this.pingTimer = window.setTimeout(handlePingTimerExpired, options['pingTimeout']); | |
| } else { | |
| that.messageFlag = false; | |
| this.connectionTimer = window.setTimeout(handleConnectionTimerExpired, options['connectionTimeout']); | |
| } | |
| }; | |
| function handlePingTimerExpired() { | |
| if(!that.connected) | |
| return | |
| this.pingTimer = null; | |
| if(false === that.messageFlag) { | |
| that.connected = false; | |
| that.close(); | |
| } else { | |
| that.messageFlag = false; | |
| this.connectionTimer = window.setTimeout(handleConnectionTimerExpired, options['connectionTimeout']); | |
| } | |
| }; | |
| Object.keys(this.channels).forEach(function(label) { | |
| var channel = that.channels[label]; | |
| if(label.match('^@')) // check for internal channels | |
| return; | |
| channel.onmessage = function onmessage(message) { | |
| that.messageFlag = true; | |
| callback(that, 'onmessage', [label, message]); | |
| }; | |
| }); | |
| this.channels['@control'].onmessage = function onmessage(message) { | |
| that.messageFlag = true; | |
| if(that.connected) { | |
| var data = message.data; | |
| if('ping' === data) { | |
| that.channels['@control'].send('pong'); | |
| } else if('pong' === data) { | |
| // ok | |
| } else if('quit' === data) { | |
| that.close(); | |
| } | |
| } | |
| }; | |
| this.connected = true; | |
| this.connectionTimer = window.setTimeout(handleConnectionTimerExpired, options['connectionTimeout']); | |
| }; | |
| Connection.prototype.close = function close() { | |
| console.log('close connection'); | |
| if(this.connected) { | |
| this.channels['@control'].send('quit'); | |
| } | |
| this.connected = false; | |
| this.peerConnection.close(); | |
| if(this.connectionTimer) { | |
| window.clearInterval(this.connectionTimer); | |
| this.connectionTimer = null; | |
| } | |
| if(this.pingTimer) { | |
| window.clearInterval(this.pingTimer); | |
| this.pingTimer = null; | |
| } | |
| this.peerConnection = null; | |
| callback(this, 'ondisconnect', []); | |
| }; | |
| Connection.prototype.send = function send(label, message) { | |
| this.channels[label].send(message); | |
| }; | |
| function PendingConnection(route, incoming) { | |
| this.route = route; | |
| this.incoming = incoming; | |
| this.proceed = true; | |
| }; | |
| PendingConnection.prototype.accept = function accept() { | |
| this.proceed = true; | |
| }; | |
| PendingConnection.prototype.reject = function reject() { | |
| this.proceed = false; | |
| }; | |
| function Peer(brokerUrl, options) { | |
| if(!webrtcSupported) | |
| throw new Error("WebRTC not supported"); | |
| var that = this; | |
| this.brokerUrl = brokerUrl; | |
| this.options = options = options || {}; | |
| options['binaryType'] = options['binaryType'] || 'arraybuffer'; | |
| options['connectionTimeout'] = options['connectionTimeout'] || 10 * ONE_SECOND; | |
| options['pingTimeout'] = options['pingTimeout'] || 1 * ONE_SECOND; | |
| this.onconnection = null; | |
| this.onpending = null; | |
| this.onroute = null; | |
| this.onerror = null; | |
| this.broker = new WebSocketBroker(brokerUrl); | |
| this.pending = {}; | |
| this.queues = { | |
| connected: [], | |
| listening: [] | |
| }; | |
| this.broker.onstatechange = function onstatechange(state, mask) { | |
| if(that.queues.connected.length && that.broker.checkState(WebSocketBroker.ROUTED)) { | |
| processDeferredQueue(that.queues.connected); | |
| if(that.queues.listening.length && that.broker.checkState(WebSocketBroker.LISTENING)) { | |
| processDeferredQueue(that.queues.listening); | |
| } | |
| } | |
| if(mask & WebSocketBroker.ROUTED) { | |
| callback(that, 'onroute', that.broker.route); | |
| } | |
| }; | |
| this.broker.onreceive = function onreceive(from, message) { | |
| var handshake; | |
| if(!that.pending.hasOwnProperty(from)) { | |
| if(!that.broker.checkState(WebSocketBroker.LISTENING)) { | |
| return; | |
| } | |
| var pendingConnection = new PendingConnection(from, /*incoming*/ true); | |
| callback(that, 'onpending', [pendingConnection]); | |
| if(!pendingConnection['proceed']) | |
| return; | |
| var handshake = that.pending[from] = new RTCConnectProtocol(that.options); | |
| handshake.oncomplete = function() { | |
| var connection = new Connection(that.options, handshake.peerConnection, handshake.streams, handshake.channels); | |
| connection['route'] = from; | |
| delete that.pending[from]; | |
| callback(that, 'onconnection', [connection]); | |
| }; | |
| handshake.onmessage = function(message) { | |
| that.broker.send(from, message); | |
| }; | |
| handshake.onerror = function(error) { | |
| delete that.pending[from]; | |
| callback(that, 'onerror', [error]); | |
| }; | |
| } else { | |
| handshake = that.pending[from]; | |
| } | |
| handshake.process(message); | |
| }; | |
| this.broker.connect(); | |
| }; | |
| Peer.prototype.listen = function listen(options) { | |
| if(!this.broker.checkState(WebSocketBroker.ROUTED)) | |
| return defer(this.queues.connected, this, 'listen', [options]); | |
| options = options || {}; | |
| options['url'] = options['url'] || window.location.toString(); | |
| options['listed'] = (undefined !== options['listed']) ? options['listed'] : true; | |
| options['metadata'] = options['metadata'] || {}; | |
| this.broker.listen(options); | |
| }; | |
| Peer.prototype.ignore = function ignore() { | |
| throw new Error('not implemented'); | |
| }; | |
| Peer.prototype.connect = function connect(route) { | |
| if(!this.broker.checkState(WebSocketBroker.ROUTED)) | |
| return defer(this.queues.connected, this, 'connect', [route]); | |
| var that = this; | |
| if(this.pending.hasOwnProperty(route)) | |
| throw new Error('already connecting to this host'); // FIXME: we can handle this better | |
| var pendingConnection = new PendingConnection(route, /*incoming*/ false); | |
| callback(that, 'onpending', [pendingConnection]); | |
| if(!pendingConnection['proceed']) | |
| return; | |
| var handshake = this.pending[route] = new RTCConnectProtocol(this.options); | |
| handshake.oncomplete = function() { | |
| var connection = new Connection(this.options, handshake.peerConnection, handshake.streams, handshake.channels); | |
| connection['route'] = route; | |
| delete that.pending[route]; | |
| callback(that, 'onconnection', [connection]); | |
| }; | |
| handshake.onmessage = function(message) { | |
| that.broker.send(route, message); | |
| }; | |
| handshake.onerror = function(error) { | |
| delete that.pending[route]; | |
| fail(that, 'onerror', error); | |
| }; | |
| handshake.initiate(); | |
| }; | |
| Peer.prototype.close = function close() { | |
| this.broker.disconnect(); | |
| }; | |
| Peer.E = E; | |
| return Peer; | |
| })(); |
Xet Storage Details
- Size:
- 24.5 kB
- Xet hash:
- 315aaf91d55c7d0958b09356857d1baf4b2a65fdb5b2dfdc9e9d58f733b6a897
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.