Added Messenger session recording.

Ylian Saint-Hilaire committed Apr 11, 2021 at 17:36 UTC d7045452b2c7ed9f9883a5f9b5dcdc1062776f7b
5 files changed +111 -37
meshcentral-config-schema.json
+1 -1
@@ -588,7 +588,7 @@
588 "index": { "type": "boolean", "default": false },
589 "maxRecordings": { "type": "integer" },
590 "maxRecordingSizeMegabytes": { "type": "integer" },
591 - "protocols": { "type": "array", "uniqueItems": true, "items": { "type": "integer" } }
591 + "protocols": { "type": "array", "uniqueItems": true, "items": { "type": "integer" }, "description": "This is an array: 1 = Terminal, 2 = Desktop, 5 = Files, 100 = Intel AMT WSMAN, 101 = Intel AMT Redirection, 200 = Messenger" }
592 },
593 "required": [ "protocols" ]
594 },
meshrelay.js
+84 -25
@@ -308,16 +308,29 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
308 // Setup session recording
309 var sessionUser = obj.user;
310 if (sessionUser == null) { sessionUser = obj.peer.user; }
311 - if ((obj.req.query.p != null) && (obj.req.query.nodeid != null) && (sessionUser != null) && (domain.sessionrecording == true || ((typeof domain.sessionrecording == 'object') && ((domain.sessionrecording.protocols == null) || (domain.sessionrecording.protocols.indexOf(parseInt(obj.req.query.p)) >= 0))))) {
311 +
312 + // If this is a MeshMessenger session, set the protocol to 200.
313 + var xtextSession = 0;
314 + var recordSession = false;
315 + if ((obj.id.startsWith('meshmessenger/node/') == true) && (sessionUser != null) && (domain.sessionrecording == true || ((typeof domain.sessionrecording == 'object') && ((domain.sessionrecording.protocols == null) || (domain.sessionrecording.protocols.indexOf(parseInt(200)) >= 0))))) {
316 + var split = obj.id.split('/');
317 + obj.req.query.nodeid = split[1] + '/' + split[2] + '/' + split[3];
318 + recordSession = true;
319 + xtextSession = 2; // 1 = Raw recording of all strings, 2 = Record chat session messages only.
320 + }
321 + if ((obj.req.query.p != null) && (obj.req.query.nodeid != null) && (sessionUser != null) && (domain.sessionrecording == true || ((typeof domain.sessionrecording == 'object') && ((domain.sessionrecording.protocols == null) || (domain.sessionrecording.protocols.indexOf(parseInt(obj.req.query.p)) >= 0))))) { recordSession = true; }
322 +
323 + if (recordSession) {
324 // Get the computer name
325 parent.db.Get(obj.req.query.nodeid, function (err, nodes) {
326 var xusername = '', xdevicename = '', xdevicename2 = null, node = null;
327 if ((nodes != null) && (nodes.length == 1)) { node = nodes[0]; xdevicename2 = node.name; xdevicename = '-' + parent.common.makeFilename(node.name); }
328
329 // Check again if we need to do recording
318 - if (domain.sessionrecording.onlyselecteddevicegroups === true) {
319 - var mesh = parent.meshes[node.meshid];
320 - if ((mesh.flags == null) || ((mesh.flags & 4) == 0)) {
330 + if ((node == null) || (domain.sessionrecording.onlyselecteddevicegroups === true)) {
331 + var mesh = null;
332 + if (node != null) { mesh = parent.meshes[node.meshid]; }
333 + if ((node == null) || (mesh == null) || (mesh.flags == null) || ((mesh.flags & 4) == 0)) {
334 // Do not record the session, just send session start
335 try { ws.send('c'); } catch (ex) { } // Send connect to both peers
336 try { relayinfo.peer1.ws.send('c'); } catch (ex) { }
@@ -337,7 +350,9 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
350 if (sessionUser._id) { xusername = '-' + parent.common.makeFilename(sessionUser._id.split('/')[2]); }
351
352 var now = new Date(Date.now());
340 - var recFilename = 'relaysession' + ((domain.id == '') ? '' : '-') + domain.id + '-' + now.getUTCFullYear() + '-' + parent.common.zeroPad(now.getUTCMonth(), 2) + '-' + parent.common.zeroPad(now.getUTCDate(), 2) + '-' + parent.common.zeroPad(now.getUTCHours(), 2) + '-' + parent.common.zeroPad(now.getUTCMinutes(), 2) + '-' + parent.common.zeroPad(now.getUTCSeconds(), 2) + xusername + xdevicename + '-' + obj.id + '.mcrec'
353 + var xsessionid = obj.id;
354 + if ((typeof xsessionid == 'string') && (xsessionid.startsWith('meshmessenger/node/') == true)) { xsessionid = 'Messenger' }
355 + var recFilename = 'relaysession' + ((domain.id == '') ? '' : '-') + domain.id + '-' + now.getUTCFullYear() + '-' + parent.common.zeroPad(now.getUTCMonth(), 2) + '-' + parent.common.zeroPad(now.getUTCDate(), 2) + '-' + parent.common.zeroPad(now.getUTCHours(), 2) + '-' + parent.common.zeroPad(now.getUTCMinutes(), 2) + '-' + parent.common.zeroPad(now.getUTCSeconds(), 2) + xusername + xdevicename + '-' + xsessionid + (xtextSession ? '.txt' : '.mcrec');
356 var recFullFilename = null;
357 if (domain.sessionrecording.filepath) {
358 try { parent.parent.fs.mkdirSync(domain.sessionrecording.filepath); } catch (e) { }
@@ -375,9 +390,10 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
390 protocol: (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.p),
391 nodeid: (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.nodeid)
392 };
393 +
394 if (xdevicename2 != null) { metadata.devicename = xdevicename2; }
395 var firstBlock = JSON.stringify(metadata);
380 - var logfile = { fd: fd, lock: false, filename: recFullFilename, startTime: Date.now(), size: 0 };
396 + var logfile = { fd: fd, lock: false, filename: recFullFilename, startTime: Date.now(), size: 0, text: xtextSession };
397 if (node != null) { logfile.nodeid = node._id; logfile.meshid = node.meshid; logfile.name = node.name; logfile.icon = node.icon; }
398 recordingEntry(logfile, 1, 0, firstBlock, function () {
399 try { relayinfo.peer1.ws.logfile = ws.logfile = logfile; } catch (ex) {
@@ -441,6 +457,9 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
457 return null;
458 }
459 } else {
460 + // Set authenticated side as browser side for messenger sessions
461 + if ((obj.id.startsWith('meshmessenger/node/') == true) && obj.authenticated) { obj.req.query.browser = 1; }
462 +
463 // Wait for other relay connection
464 if ((obj.id.startsWith('meshmessenger/node/') == true) && obj.authenticated && (parent.parent.firebase != null)) {
465 // This is an authenticated messenger session, push messaging may be allowed. Don't hold traffic.
@@ -578,6 +597,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
597 if (obj.req.query.p == 1) { msg = 'Ended terminal session', msgid = 10; }
598 else if (obj.req.query.p == 2) { msg = 'Ended desktop session', msgid = 11; }
599 else if (obj.req.query.p == 5) { msg = 'Ended file management session', msgid = 12; }
600 + else if (obj.req.query.p == 200) { msg = 'Ended messenger session', msgid = 112; }
601 if (user) {
602 var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: user._id, username: user.name, msgid: msgid, msgArgs: [obj.id, obj.req.clientIp, obj.peer.req.clientIp, Math.floor((Date.now() - ws.time) / 1000)], msg: msg + ' \"' + obj.id + '\" from ' + obj.req.clientIp + ' to ' + obj.peer.req.clientIp + ', ' + Math.floor((Date.now() - ws.time) / 1000) + ' second(s)', protocol: obj.req.query.p, nodeid: obj.req.query.nodeid };
603 parent.parent.DispatchEvent(['*', user._id], obj, event);
@@ -617,6 +637,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
637 var event = { etype: 'relay', action: 'recording', domain: domain.id, nodeid: tag.logfile.nodeid, msg: "Finished recording session" + (sessionLength ? (', ' + sessionLength + ' second(s)') : ''), filename: basefile, size: tag.logfile.size };
638 if (user) { event.userids = [user._id]; } else if (peer.user) { event.userids = [peer.user._id]; }
639 var xprotocol = (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.p);
640 + if ((xprotocol == null) && (logfile.text == 2)) { xprotocol = 200; }
641 if (xprotocol != null) { event.protocol = parseInt(xprotocol); }
642 var mesh = parent.meshes[tag.logfile.meshid];
643 if (mesh != null) { event.meshname = mesh.name; event.meshid = mesh._id; }
@@ -648,26 +669,64 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
669 // Record a new entry in a recording log
670 function recordingEntry(logfile, type, flags, data, func, tag) {
671 try {
651 - if (typeof data == 'string') {
652 - // String write
653 - var blockData = Buffer.from(data), header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
654 - header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
655 - header.writeInt16BE(flags, 2); // Flags (1 = Binary, 2 = User)
656 - header.writeInt32BE(blockData.length, 4); // Size
657 - header.writeIntBE(new Date(), 10, 6); // Time
658 - var block = Buffer.concat([header, blockData]);
659 - parent.parent.fs.write(logfile.fd, block, 0, block.length, function () { func(logfile, tag); });
660 - logfile.size += block.length;
672 + if (logfile.text) {
673 + // Text recording format
674 + var out = '';
675 + const utcDate = new Date(Date.now());
676 + if (type == 1) {
677 + // End of start
678 + out = data + '\r\n' + utcDate.toUTCString() + ', ' + "<<<START>>>" + '\r\n';
679 + } else if (type == 3) {
680 + // End of log
681 + out = utcDate.toUTCString() + ', ' + "<<<END>>>" + '\r\n';
682 + } else if (typeof data == 'string') {
683 + // Log message
684 + if (logfile.text == 1) {
685 + out = utcDate.toUTCString() + ', ' + data + '\r\n';
686 + } else if (logfile.text == 2) {
687 + try {
688 + var x = JSON.parse(data);
689 + if (typeof x.action == 'string') {
690 + if ((x.action == 'chat') && (typeof x.msg == 'string')) { out = utcDate.toUTCString() + ', ' + (((flags & 2) ? '--> ' : '<-- ') + x.msg + '\r\n'); }
691 + else if ((x.action == 'file') && (typeof x.name == 'string') && (typeof x.size == 'number')) { out = utcDate.toUTCString() + ', ' + (((flags & 2) ? '--> ' : '<-- ') + "File Transfer" + ', \"' + x.name + '\" (' + x.size + ' ' + "bytes" + ')\r\n'); }
692 + } else { out = utcDate.toUTCString() + ', ' + data + '\r\n'; }
693 + } catch (ex) {
694 + out = utcDate.toUTCString() + ', ' + data + '\r\n';
695 + }
696 + }
697 + }
698 + if (out != null) {
699 + // Log this event
700 + const block = Buffer.from(out);
701 + parent.parent.fs.write(logfile.fd, block, 0, block.length, function () { func(logfile, tag); });
702 + logfile.size += block.length;
703 + } else {
704 + // Skip logging this.
705 + func(logfile, tag);
706 + }
707 } else {
662 - // Binary write
663 - var header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
664 - header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
665 - header.writeInt16BE(flags | 1, 2); // Flags (1 = Binary, 2 = User)
666 - header.writeInt32BE(data.length, 4); // Size
667 - header.writeIntBE(new Date(), 10, 6); // Time
668 - var block = Buffer.concat([header, data]);
669 - parent.parent.fs.write(logfile.fd, block, 0, block.length, function () { func(logfile, tag); });
670 - logfile.size += block.length;
708 + // Binary recording format
709 + if (typeof data == 'string') {
710 + // String write
711 + var blockData = Buffer.from(data), header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
712 + header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
713 + header.writeInt16BE(flags, 2); // Flags (1 = Binary, 2 = User)
714 + header.writeInt32BE(blockData.length, 4); // Size
715 + header.writeIntBE(new Date(), 10, 6); // Time
716 + var block = Buffer.concat([header, blockData]);
717 + parent.parent.fs.write(logfile.fd, block, 0, block.length, function () { func(logfile, tag); });
718 + logfile.size += block.length;
719 + } else {
720 + // Binary write
721 + var header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
722 + header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
723 + header.writeInt16BE(flags | 1, 2); // Flags (1 = Binary, 2 = User)
724 + header.writeInt32BE(data.length, 4); // Size
725 + header.writeIntBE(new Date(), 10, 6); // Time
726 + var block = Buffer.concat([header, data]);
727 + parent.parent.fs.write(logfile.fd, block, 0, block.length, function () { func(logfile, tag); });
728 + logfile.size += block.length;
729 + }
730 }
731 } catch (ex) { console.log(ex); func(logfile, tag); }
732 }
sample-config-advanced.json
+1 -1
@@ -292,7 +292,7 @@
292 "_index": true,
293 "_maxRecordings": 10,
294 "_maxRecordingSizeMegabytes": 3,
295 - "__protocols__": "Is an array: 1 = Terminal, 2 = Desktop, 5 = Files, 100 = Intel AMT WSMAN, 101 = Intel AMT Redirection",
295 + "__protocols__": "Is an array: 1 = Terminal, 2 = Desktop, 5 = Files, 100 = Intel AMT WSMAN, 101 = Intel AMT Redirection, 200 = Messenger",
296 "protocols": [ 1, 2, 101 ]
297 },
298 "_authStrategies": {
views/default.handlebars
+10 -1
@@ -11943,7 +11943,8 @@
11943 108: "User login attempt with incorrect 2nd factor from {0}, {1}, {2}",
11944 109: "User login attempt on locked account from {0}, {1}, {2}",
11945 110: "Invalid user login attempt from {0}, {1}, {2}",
11946 - 111: "Device requested Intel(R) AMT ACM TLS activation, FQDN: {0}"
11946 + 111: "Device requested Intel(R) AMT ACM TLS activation, FQDN: {0}",
11947 + 112: "Ended messenger session \"{0}\" from {1} to {2}, {3} second(s)"
11948 };
11949
11950 // Highlights the device being hovered
@@ -13702,6 +13703,14 @@
13703 }
13704 }
13705 }
13706 +
13707 + if (rec.protocol == 1) { sessionName += ' - ' + "Terminal Session"; }
13708 + if (rec.protocol == 2) { sessionName += ' - ' + "Desktop Session"; }
13709 + if (rec.protocol == 5) { sessionName += ' - ' + "File Transfer"; }
13710 + if (rec.protocol == 100) { sessionName += ' - ' + "Intel&reg; AMT WSMAN"; }
13711 + if (rec.protocol == 101) { sessionName += ' - ' + "Intel&reg; AMT Redirection"; }
13712 + if (rec.protocol == 200) { sessionName += ' - ' + "Messenger"; }
13713 +
13714 var actions = '', icon = 'm0';
13715 if (rec.present == 1) { icon = 'm1'; actions = '<div style=cursor:pointer;float:right><a onclick=downloadFile("recordings.ashx?file=' + encodeURIComponentEx(rec.filename) + '")><img src=images/link4.png height=10 width=10 title="Download Recording"></a>&nbsp;</div>'; }
13716 var x = '<tr tabindex=0 onmouseover=userMouseHover2(this,1) onmouseout=userMouseHover2(this,0) onkeypress="if (event.key==\'Enter\') showRecordingDialog(event,\'' + i + '\')"><td style=cursor:pointer>';
views/messenger.handlebars
+15 -9
@@ -23,6 +23,7 @@
23 <div id="camButton" class="icon2 topButton" title="Activate camera & microphone" style="display:none" onclick="camButtonClick()"></div>
24 <div id="micButton" class="icon6 topButton" title="Activate microphone" style="display:none" onclick="micButtonClick()"></div>
25 <div id="hangupButton" class="icon11 topRedButton" title="Hang up" style="display:none" onclick="hangUpButtonClick(1)"></div>
26 + <div id="recordIcon" class='deskareaicon' title="Server is recording this session" style="background-color:red;margin:6px;margin-top:7px;border-radius:12px;height:24px;width:24px;float:right;display:none"></div>
27 </div>
28 <div style="padding-top:9px;padding-left:6px;font-size:20px;display:inline-block"><b><span id="xtitle"></span></b></div>
29 </div>
@@ -88,6 +89,7 @@
89 var localOutText = false;
90 var remoteOutText = false;
91 var remoteImage = false;
92 + var serverRecording = false;
93
94 // File transfer state
95 var fileUploads = [];
@@ -279,7 +281,7 @@
281 //console.log('ondatachannel');
282 webchannel = ev.channel;
283 webchannel.onmessage = function (event) { processMessage(event.data, 2); };
282 - webchannel.onopen = function () { webchannel.ok = true; updateControls(); sendws({ action: 'rtcSwitch', v: 0 }); };
284 + webchannel.onopen = function () { webchannel.ok = true; updateControls(); if (serverRecording == false) { sendws({ action: 'rtcSwitch', v: 0 }); } };
285 webchannel.onclose = function (event) { if (webchannel && webchannel.ok) { disconnect(); } else { hangUpButtonClick(0); } }
286 }
287 webrtc.onnegotiationneeded = function (event) {
@@ -316,7 +318,7 @@
318 if (startDataChannel == true) {
319 webchannel = webrtc.createDataChannel('DataChannel', {}); // { ordered: false, maxRetransmits: 2 }
320 webchannel.onmessage = function (event) { processMessage(event.data, 2); };
319 - webchannel.onopen = function () { webchannel.ok = true; updateControls(); sendws({ action: 'rtcSwitch', v: 0 }); };
321 + webchannel.onopen = function () { webchannel.ok = true; updateControls(); if (serverRecording == false) { sendws({ action: 'rtcSwitch', v: 0 }); } };
322 webchannel.onclose = function (event) { if (webchannel && webchannel.ok) { disconnect(); } else { hangUpButtonClick(0); } }
323 }
324
@@ -344,11 +346,13 @@
346
347 // Indicate to peer that data traffic will no longer be sent over websocket and start holding traffic.
348 function performWebRtcSwitch() {
347 - if (webchannel && webchannel.ok) { sendws({ action: 'rtcSwitch', v: 1 }); webchannel.xoutBuffer = []; }
349 + if (!serverRecording && webchannel && webchannel.ok) { sendws({ action: 'rtcSwitch', v: 1 }); webchannel.xoutBuffer = []; }
350 }
351
352 // Disconnect everything
353 function disconnect() {
354 + serverRecording = false;
355 + QV('recordIcon', false);
356 if (state > 0) { displayControl("Connection closed."); }
357 if (state > 1) { setTimeout(start, 500); }
358 cancelAllFileTransfers();
@@ -368,7 +372,7 @@
372 function send(data) {
373 if ((state != 2) && (pushMessaging == false)) return; // If not in connected state, ignore this.
374 if (typeof data == 'object') { data = JSON.stringify(data); } // If this is an object, convert it to a string.
371 - if (webchannel && webchannel.ok) { if (webchannel.xoutBuffer != null) { webchannel.xoutBuffer.push(data); } else { webchannel.send(data); } } // If WebRTC channel is possible, use it or hold until we can use it.
375 + if (!serverRecording && webchannel && webchannel.ok) { if (webchannel.xoutBuffer != null) { webchannel.xoutBuffer.push(data); } else { webchannel.send(data); } } // If WebRTC channel is possible, use it or hold until we can use it.
376 else { if (socket != null) { try { socket.send(data); } catch (ex) { } } } // If a websocket channel is present, use that.
377 }
378
@@ -520,7 +524,7 @@
524 file.id = Math.random();
525 fileUploads.push(file);
526 chatTextSession += ((new Date()).toLocaleTimeString() + ' - ' + "Upload" + '> ' + file.name + ' (' + file.size + ' ' + "bytes" + ')\r\n');
523 - QA('xmsg', '<div style="clear:both"></div><div id="FILEUP-' + file.id + '" class="localBubble" style="width:240px;cursor:pointer" onclick="cancelFileTransfer(\'' + file.id + '\')"><div id="FILEUP-ICON-' + file.id + '" class="fileicon" style="float:left;width:32px;height:32px"></div><div><div id="FILEUP-NAME-' + file.id + '" style="height:16px;overflow:hidden;white-space:nowrap;" title="' + file.name + '">' + file.name + '</div><div style="width:200px;background-color:lightgray;margin-left:32px;border-radius:3px;margin-top:3px;height:11px"><div id="FILEUP-PROGRESS-' + file.id + '" style="width:0px;background-color:green;border-radius:3px;height:11px">&nbsp;</div></div></div></div>');
527 + QA('xmsg', '<div style="clear:both"></div><div id="FILEUP-' + file.id + '" class="localBubble" style="font-size:14px;width:240px;cursor:pointer" onclick="cancelFileTransfer(\'' + file.id + '\')"><div id="FILEUP-ICON-' + file.id + '" class="fileicon" style="float:left;width:32px;height:32px"></div><div><div id="FILEUP-NAME-' + file.id + '" style="height:20px;overflow:hidden;white-space:nowrap;" title="' + file.name + '">' + file.name + '</div><div style="width:200px;background-color:lightgray;margin-left:32px;border-radius:3px;margin-top:3px;height:11px"><div id="FILEUP-PROGRESS-' + file.id + '" style="width:0px;background-color:green;border-radius:3px;height:11px">&nbsp;</div></div></div></div>');
528 Q('xmsgparent').scrollTop = Q('xmsgparent').scrollHeight;
529 send({ action: 'file', size: file.size, id: file.id, type: file.type, name: file.name });
530 if (currentFileUpload == null) continueFileUpload();
@@ -530,7 +534,7 @@
534 if (state != 2) return;
535 fileDownloads[file.id] = file;
536 chatTextSession += ((new Date()).toLocaleTimeString() + ' - ' + "Download" + '> ' + file.name + ' (' + file.size + ' ' + "bytes" + ')\r\n');
533 - QA('xmsg', '<div style="clear:both"></div><div id="FILEUP-' + file.id + '" class="remoteBubble" style="width:240px;cursor:pointer" onclick="saveFileTransfer(\'' + file.id + '\')"><div id="FILEUP-ICON-' + file.id + '" class="fileicon" style="float:left;width:32px;height:32px"></div><div><div id="FILEUP-NAME-' + file.id + '" style="height:16px;overflow:hidden;white-space:nowrap;" title="' + file.name + '">' + file.name + '</div><div style="width:200px;background-color:lightgray;margin-left:32px;border-radius:3px;margin-top:3px;height:11px"><div id="FILEUP-PROGRESS-' + file.id + '" style="width:0px;background-color:green;border-radius:3px;height:11px">&nbsp;</div></div></div></div>');
537 + QA('xmsg', '<div style="clear:both"></div><div id="FILEUP-' + file.id + '" class="remoteBubble" style="font-size:14px;width:240px;cursor:pointer" onclick="saveFileTransfer(\'' + file.id + '\')"><div id="FILEUP-ICON-' + file.id + '" class="fileicon" style="float:left;width:32px;height:32px"></div><div><div id="FILEUP-NAME-' + file.id + '" style="height:20px;overflow:hidden;white-space:nowrap;" title="' + file.name + '">' + file.name + '</div><div style="width:200px;background-color:lightgray;margin-left:32px;border-radius:3px;margin-top:3px;height:11px"><div id="FILEUP-PROGRESS-' + file.id + '" style="width:0px;background-color:green;border-radius:3px;height:11px">&nbsp;</div></div></div></div>');
538 Q('xmsgparent').scrollTop = Q('xmsgparent').scrollHeight;
539 }
540
@@ -717,6 +721,8 @@
721 socket.onclose = function () { disconnect(); }
722 socket.onmessage = function (msg) {
723 if ((state < 2) && (typeof msg.data == 'string') && ((msg.data == 'c') || (msg.data == 'cr'))) {
724 + serverRecording = (msg.data == 'cr');
725 + QV('recordIcon', serverRecording);
726 hangUpButtonClick(0, true);
727 hangUpButtonClick(1, true);
728 hangUpButtonClick(2, true);
@@ -742,9 +748,9 @@
748 start();
749
750 function onUnLoad() {
745 - for (var i = 0; i < 3; i++) { if (webrtcSessions[i]) { webrtcSessions[i].close(); delete webrtcSessions[i]; } }
746 - if (webchannel != null) { try { webchannel.close(); } catch (e) { } webchannel = null; }
747 - if (socket != null) { try { socket.close(); } catch (e) { } socket = null; }
751 + for (var i = 0; i < 3; i++) { if (webrtcSessions[i]) { try { webrtcSessions[i].close(); delete webrtcSessions[i]; } catch (ex) { } } }
752 + if (webchannel != null) { try { webchannel.close(); } catch (ex) { } webchannel = null; }
753 + if (socket != null) { try { socket.close(); } catch (ex) { } socket = null; }
754 }
755
756 function isSafeString3(str) { return ((typeof str == 'string') && (str.indexOf('<') == -1) && (str.indexOf('>') == -1) && (str.indexOf('&') == -1) && (str.indexOf('"') == -1) && (str.indexOf('\'') == -1) && (str.indexOf('+') == -1) && (str.indexOf('(') == -1) && (str.indexOf(')') == -1) && (str.indexOf('#') == -1) && (str.indexOf(':') == -1)) };