UNPKG

internet

Version:

Framework for creating peer-to-peer browser networks

173 lines (135 loc) 5.78 kB
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; var log = function(){}; if(window.P_DEBUGGING_ENABLED){ log = function(label, event, obj){ window.console.debug(label, event, obj); }; } 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, {protocol: this.PROTOCOL_NAME}); // Bug in FF seems to garbage collect the stale ref causing it to close // the prevents it from being lost in a GC event this._initialRtcDataChannel = this.rtcDataChannel; this.close = rtcConnection.close.bind(rtcConnection); this.rtcConnection.addEventListener('icecandidate', function(event){ if(!event.candidate) return; log('ice candidate', event, self); self.signalingChannel.writeRelayIceCandidate(address, event.candidate); }); this.rtcConnection.addEventListener('datachannel', function(event){ log('datachannel', event, self); var rtcDataChannel = self.rtcDataChannel = event.channel; rtcDataChannel.addEventListener('open', function(event){ log('remote datachannel open', event, self); self.emitter.emit('open', event); }); rtcDataChannel.addEventListener('close', function(event){ log('remote datachannel close', event, self); self.emitter.emit('close', event); }); rtcDataChannel.addEventListener('error', function(event){ log('remote datachannel error', event, self); self.emitter.emit('error', event); }); }); this.rtcDataChannel.addEventListener('message', function(message){ log('local datachannel message', message, self); self.readRaw(message.data); }); this.rtcDataChannel.addEventListener('error', function(event){ log('local datachannel error', event, self); self.emitter.emit('error', event); }); this.rtcDataChannel.addEventListener('close', function(event){ log('local datachannel close', event, self); self.emitter.emit('close', event); }); } var DEFAULT_RTC_CONFIGURATION = null; var DEFAULT_RTC_OFFER_OPTIONS = { offerToReceiveAudio: false, offerToReceiveVideo: false, iceRestart: false }; //DEFAULT_RTC_OFFER_OPTIONS WebRTCConnection.create = function(config, peers, signalingChannel, options){ var rtcConfiguration = config.rtcConfiguration || DEFAULT_RTC_CONFIGURATION, rtcOfferOptions = config.rtcOfferOptions || DEFAULT_RTC_OFFER_OPTIONS, rtcConnection = new nativeRTCPeerConnection(rtcConfiguration); 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); log('sent message to remote', message, this); 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.rtcOfferOptions || DEFAULT_RTC_OFFER_OPTIONS); }; WebRTCConnection.prototype.getReadyState = function(){ return this.rtcDataChannel.readyState; }; // Solves the circular dependency with Connection.js Connection.createWebRTCConnection = WebRTCConnection.create; module.exports = WebRTCConnection;