aboutsummaryrefslogtreecommitdiff
path: root/src/wrtcp.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/wrtcp.js')
-rw-r--r--src/wrtcp.js821
1 files changed, 821 insertions, 0 deletions
diff --git a/src/wrtcp.js b/src/wrtcp.js
new file mode 100644
index 00000000..e3f0e43b
--- /dev/null
+++ b/src/wrtcp.js
@@ -0,0 +1,821 @@
+/*
+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(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;
+
+})(); \ No newline at end of file