Completed server load-balancing support

This commit is contained in:
Ylian Saint-Hilaire 2017-09-20 14:44:22 -07:00
parent 1031eca853
commit 453383f851
19 changed files with 305 additions and 56 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -26,7 +26,7 @@ function createMeshCore(agent) {
var lastNetworkInfo = null;
var lastPublicLocationInfo = null;
var selfInfoUpdateTimer = null;
obj.useNativePipes = (process.platform == 'win32');
obj.useNativePipes = true; //(process.platform == 'win32');
var http = require('http');
var fs = require('fs');
@ -158,7 +158,7 @@ function createMeshCore(agent) {
};
// Replace a string with a number if the string is an exact number
function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) == x)) { x = parseInt(x); } return x; }
function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; }
// Convert decimal to hex
function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
@ -218,16 +218,48 @@ function createMeshCore(agent) {
return results;
}
// Parge a URL string into an options object
function parseUrl(url) {
var x = url.split('/');
if (x.length < 4) return null;
var y = x[2].split(':');
var options = {};
var options = { protocol: x[0], hostname: y[0], path: '/' + x.splice(3).join('/') };
if (y.length == 1) { options.port = ((x[0] == 'https:') || (x[0] == 'wss:')) ? 443 : 80; } else { options.port = parseInt(y[1]); }
if (isNaN(options.port) == true) return null;
return options;
// Get server target url with a custom path
function getServerTargetUrl(path) {
var x = mesh.ServerUrl;
//sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl);
if (x == null) { return null; }
if (path == null) { path = ''; }
x = http.parseUri(x);
if (x == null) return null;
return x.protocol + '//' + x.host + ':' + x.port + '/' + path;
}
// Get server url. If the url starts with "*/..." change it, it not use the url as is.
function getServerTargetUrlEx(url) {
if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); }
return url;
}
// Send a wake-on-lan packet
function sendWakeOnLan(hexMac) {
var count = 0;
try {
var interfaces = require('os').networkInterfaces();
var magic = 'FFFFFFFFFFFF';
for (var x = 1; x <= 16; ++x) { magic += hexMac; }
var magicbin = Buffer.from(magic, 'hex');
for (var adapter in interfaces) {
if (interfaces.hasOwnProperty(adapter)) {
for (var i = 0; i < interfaces[adapter].length; ++i) {
var addr = interfaces[adapter][i];
if ((addr.family == 'IPv4') && (addr.mac != '00:00:00:00:00:00')) {
var socket = require('dgram').createSocket({ type: "udp4" });
socket.bind({ address: addr.address });
socket.setBroadcast(true);
socket.send(magicbin, 7, "255.255.255.255");
count++;
}
}
}
}
} catch (e) { }
return count;
}
// Handle a mesh agent command
@ -245,21 +277,26 @@ function createMeshCore(agent) {
else if (data.type == 'tunnel') { // Process a new tunnel connection request
if (data.value && data.sessionid) {
// Create a new tunnel object
var tunnel = http.request(parseUrl(data.value));
tunnel.upgrade = onTunnelUpgrade;
tunnel.sessionid = data.sessionid;
tunnel.rights = data.rights;
tunnel.state = 0;
tunnel.url = data.value;
tunnel.protocol = 0;
//sendConsoleText(data.value);
var xurl = getServerTargetUrlEx(data.value);
//sendConsoleText(xurl);
if (xurl != null) {
var tunnel = http.request(http.parseUri(xurl));
tunnel.upgrade = onTunnelUpgrade;
tunnel.sessionid = data.sessionid;
tunnel.rights = data.rights;
tunnel.state = 0;
tunnel.url = xurl;
tunnel.protocol = 0;
// Put the tunnel in the tunnels list
var index = 1;
while (tunnels[index]) { index++; }
tunnel.index = index;
tunnels[index] = tunnel;
// Put the tunnel in the tunnels list
var index = 1;
while (tunnels[index]) { index++; }
tunnel.index = index;
tunnels[index] = tunnel;
sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
}
}
}
break;
@ -267,7 +304,7 @@ function createMeshCore(agent) {
case 'wakeonlan': {
// Send wake-on-lan on all interfaces for all MAC addresses in data.macs array. The array is a list of HEX MAC addresses.
sendConsoleText('Server requesting wake-on-lan for: ' + data.macs.join(', '));
// TODO!!!!
for (var i in data.macs) { sendWakeOnLan(data.macs[i]); }
break;
}
case 'poweraction': {
@ -554,7 +591,7 @@ function createMeshCore(agent) {
var response = null;
switch (cmd) {
case 'help': { // Displays available commands
response = 'Available commands: help, info, args, print, type, dbget, dbset, dbcompact, parseurl, httpget, wslist, wsconnect, wssend, wsclose, notify, ls, amt, netinfo, location, power.';
response = 'Available commands: help, info, args, print, type, dbget, dbset, dbcompact, parseuri, httpget, wslist, wsconnect, wssend, wsclose, notify, ls, amt, netinfo, location, power, wakeonlan.';
break;
}
case 'notify': { // Send a notification message to the mesh
@ -569,7 +606,7 @@ function createMeshCore(agent) {
break;
}
case 'info': { // Return information about the agent and agent core module
response = 'Current Core: ' + obj.meshCoreInfo + '.\r\nAgent Time: ' + Date() + '.\r\nUser Rights: 0x' + rights.toString(16) + '.\r\nPlatform Info: ' + process.platform + '.\r\nCapabilities: ' + obj.meshCoreCapabilities + '.\r\nNative Pipes: ' + obj.useNativePipes + '.';
response = 'Current Core: ' + obj.meshCoreInfo + '.\r\nAgent Time: ' + Date() + '.\r\nUser Rights: 0x' + rights.toString(16) + '.\r\nPlatform Info: ' + process.platform + '.\r\nCapabilities: ' + obj.meshCoreCapabilities + '.\r\nNative Pipes: ' + obj.useNativePipes + '.\r\nServer URL: ' + mesh.ServerUrl + '.';
break;
}
case 'selfinfo': { // Return self information block
@ -628,10 +665,6 @@ function createMeshCore(agent) {
response = 'Database compacted: ' + r;
break;
}
case 'parseurl': {
response = objToString(parseUrl(args['_'][0]));
break;
}
case 'httpget': {
if (consoleHttpRequest != null) {
response = 'HTTP operation already in progress.';
@ -639,7 +672,7 @@ function createMeshCore(agent) {
if (args['_'].length != 1) {
response = 'Proper usage: httpget (url)';
} else {
var options = parseUrl(args['_'][0]);
var options = http.parseUri(args['_'][0]);
options.method = 'GET';
if (options == null) {
response = 'Invalid url.';
@ -648,7 +681,7 @@ function createMeshCore(agent) {
consoleHttpRequest.sessionid = sessionid;
if (consoleHttpRequest != null) {
consoleHttpRequest.end();
response = 'HTTPGET ' + options.protocol + '//' + options.hostname + ':' + options.port + options.path;
response = 'HTTPGET ' + options.protocol + '//' + options.host + ':' + options.port + options.path;
}
}
}
@ -670,7 +703,7 @@ function createMeshCore(agent) {
} else {
var httprequest = null;
try {
httprequest = http.request(parseUrl(args['_'][0]));
httprequest = http.request(http.parseUri(args['_'][0]));
} catch (e) { response = 'Invalid HTTP websocket request'; }
if (httprequest != null) {
httprequest.upgrade = onWebSocketUpgrade;
@ -756,7 +789,18 @@ function createMeshCore(agent) {
break;
}
case 'netinfo': { // Show network interface information
response = objToString(mesh.NetInfo, 0, '.');
//response = objToString(mesh.NetInfo, 0, '.');
var interfaces = require('os').networkInterfaces();
response = objToString(interfaces, 0, '.');
break;
}
case 'wakeonlan': { // Send wake-on-lan
if ((args['_'].length != 1) || (args['_'][0].length != 12)) {
response = 'Proper usage: wakeonlan [mac], for example "wakeonlan 010203040506".';
} else {
var count = sendWakeOnLan(args['_'][0]);
response = 'Sent wake-on-lan on ' + count + ' interface(s).';
}
break;
}
case 'sendall': { // Send a message to all consoles on this mesh
@ -782,6 +826,10 @@ function createMeshCore(agent) {
});
break;
}
case 'parseuri': {
response = JSON.stringify(http.parseUri(args['_'][0]));
break;
}
default: { // This is an unknown command, return an error message
response = 'Unknown command \"' + cmd + '\", type \"help\" for list of avaialble commands.';
break;

View File

@ -378,9 +378,12 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
delete command.sessionid; // Remove the sessionid, since we are sending to that sessionid, so it's implyed.
ws.send(JSON.stringify(command));
} else if (obj.parent.parent.multiServer != null) {
// We need to send this message to other servers
command.fromNodeid = obj.dbNodeKey;
obj.parent.parent.multiServer.DispatchMessage(command);
// See if we can send this to a peer server
var serverid = obj.parent.wsPeerSessions2[command.sessionid];
if (serverid != null) {
command.fromNodeid = obj.dbNodeKey;
obj.parent.parent.multiServer.DispatchMessageSingleServer(command, serverid);
}
}
}
} else if (command.userid != null) { // If this command has a userid, that is the target.

View File

@ -2017,3 +2017,60 @@ Error: not opened
at Receiver.opcodes.2.finish (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\Receiver.js:633:12)
-------- 9/20/2017, 11:40:47 AM --------
C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\multiserver.js:522
obj.wsPeerSessions3[peerServerId] = userToSession; // ServerId --> UserId --> SessionId
^
TypeError: Cannot set property 'AmtMachine7' of undefined
at Object.obj.ProcessPeerServerMessage (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\multiserver.js:522:51)
at processServerData (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\multiserver.js:377:32)
at WebSocket.<anonymous> (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\multiserver.js:262:49)
at emitTwo (events.js:106:13)
at WebSocket.emit (events.js:191:7)
at Receiver.ontext (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\WebSocket.js:841:10)
at C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\Receiver.js:536:18
at Receiver.applyExtensions (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\Receiver.js:371:5)
at C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\Receiver.js:508:14
at Receiver.flush (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\Receiver.js:347:3)
-------- 9/20/2017, 11:40:51 AM --------
C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\multiserver.js:522
obj.wsPeerSessions3[peerServerId] = userToSession; // ServerId --> UserId --> SessionId
^
TypeError: Cannot set property 'AmtMachine7' of undefined
at Object.obj.ProcessPeerServerMessage (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\multiserver.js:522:51)
at processServerData (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\multiserver.js:377:32)
at WebSocket.<anonymous> (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\multiserver.js:262:49)
at emitTwo (events.js:106:13)
at WebSocket.emit (events.js:191:7)
at Receiver.ontext (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\WebSocket.js:841:10)
at C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\Receiver.js:536:18
at Receiver.applyExtensions (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\Receiver.js:371:5)
at C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\Receiver.js:508:14
at Receiver.flush (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\Receiver.js:347:3)
-------- 9/20/2017, 1:55:44 PM --------
C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\webserver.js:1155
for (var i in obj.wssessions) { if (obj.wssessions[i][0].domainid == domain.id) { wssessions[i] = obj.wssessions[i].length; } }
^
TypeError: Cannot read property 'domainid' of undefined
at WebSocket.<anonymous> (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\webserver.js:1155:89)
at emitTwo (events.js:106:13)
at WebSocket.emit (events.js:191:7)
at Receiver.ontext (C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\WebSocket.js:841:10)
at C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\Receiver.js:536:18
at C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\Receiver.js:368:7
at C:\Users\Default.DESKTOP-M9I88C9\Desktop\AmtWebApp\meshcentral\node_modules\ws\lib\PerMessageDeflate.js:249:5
at afterWrite (_stream_writable.js:360:3)
at onwrite (_stream_writable.js:351:7)
at WritableState.onwrite (_stream_writable.js:89:5)

View File

@ -472,17 +472,29 @@ module.exports.CreateMultiServer = function (parent, args) {
obj.SetupPeerServer = function (server, peerServerId) {
console.log('Connected to peer server ' + peerServerId + '.');
obj.peerServers[peerServerId] = server;
// Send the list of connections to the peer
server.send(JSON.stringify({ action: 'connectivityTable', connectivityTable: obj.parent.peerConnectivityByNode[obj.parent.serverId] }));
// Send a list of user sessions to the peer
server.send(JSON.stringify({ action: 'sessionsTable', sessionsTable: Object.keys(obj.parent.webserver.wssessions2) }));
}
// We disconnected to a peer server, clean up everything
obj.ClearPeerServer = function (server, peerServerId) {
console.log('Disconnected from peer server ' + peerServerId + '.');
// Clean up the connectivity state
delete obj.peerServers[peerServerId];
//delete obj.parent.peerConnectivityByMesh[peerServerId]; // TODO: We will need to re-adjust all of the node power states.
var oldList = obj.parent.peerConnectivityByNode[peerServerId];
obj.parent.peerConnectivityByNode[peerServerId] = {};
obj.parent.UpdateConnectivityState(oldList);
// Clean up the sessions list
for (var i in obj.parent.webserver.wsPeerSessions[peerServerId]) { delete obj.parent.webserver.wsPeerSessions2[obj.parent.webserver.wsPeerSessions[peerServerId][i]]; }
delete obj.parent.webserver.wsPeerSessions[peerServerId];
delete obj.parent.webserver.wsPeerSessions3[peerServerId];
obj.parent.webserver.recountSessions(); // Recount all sessions
}
// Process a message coming from a peer server
@ -498,6 +510,43 @@ module.exports.CreateMultiServer = function (parent, args) {
obj.parent.UpdateConnectivityState(msg.connectivityTable);
break;
}
case 'sessionsTable': {
obj.parent.webserver.wsPeerSessions[peerServerId] = msg.sessionsTable;
var userToSession = {};
for (var i in msg.sessionsTable) {
var sessionid = msg.sessionsTable[i];
obj.parent.webserver.wsPeerSessions2[sessionid] = peerServerId;
var userid = sessionid.split('/').slice(0, 3).join('/'); // Take the sessionid and keep only the userid partion
if (userToSession[userid] == null) { userToSession[userid] = [sessionid]; } else { userToSession[userid].push(sessionid); } // UserId -> [ SessionId ]
}
obj.parent.webserver.wsPeerSessions3[peerServerId] = userToSession; // ServerId --> UserId --> SessionId
obj.parent.webserver.recountSessions(); // Recount all sessions
break;
}
case 'sessionStart': {
obj.parent.webserver.wsPeerSessions[peerServerId].push(msg.sessionid);
obj.parent.webserver.wsPeerSessions2[msg.sessionid] = peerServerId;
var userid = msg.sessionid.split('/').slice(0, 3).join('/');
if (obj.parent.webserver.wsPeerSessions3[peerServerId] == null) { obj.parent.webserver.wsPeerSessions3[peerServerId] = {}; }
if (obj.parent.webserver.wsPeerSessions3[peerServerId][userid] == null) { obj.parent.webserver.wsPeerSessions3[peerServerId][userid] = [ msg.sessionid ]; } else { obj.parent.webserver.wsPeerSessions3[peerServerId][userid].push(msg.sessionid); }
obj.parent.webserver.recountSessions(msg.sessionid); // Recount a specific user
break;
}
case 'sessionEnd': {
var i = obj.parent.webserver.wsPeerSessions[peerServerId].indexOf(msg.sessionid);
if (i >= 0) { obj.parent.webserver.wsPeerSessions[peerServerId].splice(i, 1); }
delete obj.parent.webserver.wsPeerSessions2[msg.sessionid];
var userid = msg.sessionid.split('/').slice(0, 3).join('/');
if (obj.parent.webserver.wsPeerSessions3[peerServerId][userid] != null) {
i = obj.parent.webserver.wsPeerSessions3[peerServerId][userid].indexOf(msg.sessionid);
if (i >= 0) {
obj.parent.webserver.wsPeerSessions3[peerServerId][userid].splice(i, 1);
if (obj.parent.webserver.wsPeerSessions3[peerServerId][userid].length == 0) { delete obj.parent.webserver.wsPeerSessions3[peerServerId][userid]; }
}
}
obj.parent.webserver.recountSessions(msg.sessionid); // Recount a specific user
break;
}
case 'SetConnectivityState': {
obj.parent.SetConnectivityState(msg.meshid, msg.nodeid, msg.connectTime, msg.connectType, msg.powerState, peerServerId);
break;

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.0.7-y",
"version": "0.0.8-b",
"keywords": [
"Remote Management",
"Intel AMT",

View File

@ -24,7 +24,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
obj.Start = function (nodeid) {
var url2, url = window.location.protocol.replace("http", "ws") + "//" + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + "/meshrelay.ashx?id=" + obj.tunnelid;
if (serverPublicNamePort) { url2 = window.location.protocol.replace("http", "ws") + "//" + serverPublicNamePort + "/meshrelay.ashx?id=" + obj.tunnelid; } else { url2 = url; }
//if (serverPublicNamePort) { url2 = window.location.protocol.replace("http", "ws") + "//" + serverPublicNamePort + "/meshrelay.ashx?id=" + obj.tunnelid; } else { url2 = url; }
obj.nodeid = nodeid;
obj.connectstate = 0;
obj.socket = new WebSocket(url);
@ -33,7 +33,8 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
obj.socket.onerror = function (e) { console.error(e); }
obj.socket.onclose = obj.xxOnSocketClosed;
obj.xxStateChange(1);
obj.meshserver.Send({ action: 'msg', type: 'tunnel', nodeid: obj.nodeid, value: url2 });
//obj.meshserver.Send({ action: 'msg', type: 'tunnel', nodeid: obj.nodeid, value: url2 });
obj.meshserver.Send({ action: 'msg', type: 'tunnel', nodeid: obj.nodeid, value: "*/meshrelay.ashx?id=" + obj.tunnelid });
//obj.debug("Agent Redir Start: " + url);
}

View File

@ -150,7 +150,7 @@
if ('{{loginmode}}' != '') { go({{loginmode}}); } else { go(1); }
QV('newAccountDiv', '{{{newAccount}}}' != '0' );
if ((passhint != null) && (passhint.length > 0)) { QV("showPassHintLink", true); }
QV("anewaccountpass", (newAccountPass == 1));
QV("newAccountPass", (newAccountPass == 1));
}
function showPassHint() {

View File

@ -74,10 +74,14 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
// Main lists
obj.wsagents = {};
obj.wssessions = {}; // UserId --> Array Of Sessions
obj.wssessions2 = {}; // UserId + SessionId --> Session
obj.wsrelays = {}; // Id -> Relay
obj.wsPeerRelays = {}; // Id -> { ServerId, Time }
obj.wssessions = {}; // UserId --> Array Of Sessions
obj.wssessions2 = {}; // "UserId + SessionRnd" --> Session (Note that the SessionId is the UserId + / + SessionRnd)
obj.wsPeerSessions = {}; // ServerId --> Array Of "UserId + SessionRnd"
obj.wsPeerSessions2 = {}; // "UserId + SessionRnd" --> ServerId
obj.wsPeerSessions3 = {}; // ServerId --> UserId --> [ SessionId ]
obj.sessionsCount = {}; // Merged session counters, used when doing server peering. UserId --> SessionCount
obj.wsrelays = {}; // Id -> Relay
obj.wsPeerRelays = {}; // Id -> { ServerId, Time }
// Setup randoms
obj.crypto.randomBytes(32, function (err, buf) { obj.httpAuthRandom = buf; });
@ -383,7 +387,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
res.render(obj.path.join(__dirname, 'views/default'), { viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.certificates.CommonName, serverPublicPort: args.port, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, mpspass: args.mpspass });
} else {
// Send back the login application
res.render(obj.path.join(__dirname, 'views/login'), { loginmode: req.session.loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newAccounts, newAccountPass: ((domain.newAccountsPass == null)?0:1), serverDnsName: obj.certificates.CommonName, serverPublicPort: obj.args.port });
res.render(obj.path.join(__dirname, 'views/login'), { loginmode: req.session.loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newAccounts, newAccountPass: (((domain.newAccountsPass == null) || (domain.newAccountsPass == ''))?0:1), serverDnsName: obj.certificates.CommonName, serverPublicPort: obj.args.port });
}
}
@ -941,8 +945,15 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
ws.sessionId = user._id + '/' + ('' + Math.random()).substring(2);
obj.wssessions2[ws.sessionId] = ws;
if (!obj.wssessions[user._id]) { obj.wssessions[user._id] = [ws]; } else { obj.wssessions[user._id].push(ws); }
obj.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: obj.wssessions[user._id].length, nolog: 1, domain: domain.id })
if (obj.parent.multiServer == null) {
obj.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: obj.wssessions[user._id].length, nolog: 1, domain: domain.id })
} else {
obj.recountSessions(ws.sessionId); // Recount sessions
}
// If we have peer servers, inform them of the new session
if (obj.parent.multiServer != null) { obj.parent.multiServer.DispatchMessage({ action: 'sessionStart', sessionid: ws.sessionId }); }
// Handle events
ws.HandleEvent = function (source, event) {
if (!event.domain || event.domain == domain.id) {
@ -1136,11 +1147,17 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
}
case 'wssessioncount':
{
// Request a list of all web socket session count
if ((user.siteadmin & 2) == 0) break;
// Request a list of all web socket user session count
var wssessions = {};
for (var i in obj.wssessions) { if (obj.wssessions[i][0].domainid == domain.id) { wssessions[i] = obj.wssessions[i].length; } }
ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: wssessions }));
if ((user.siteadmin & 2) == 0) break;
if (obj.parent.multiServer == null) {
// No peering, use simple session counting
for (var i in obj.wssessions) { if (obj.wssessions[i][0].domainid == domain.id) { wssessions[i] = obj.wssessions[i].length; } }
} else {
// We have peer servers, use more complex session counting
for (var userid in obj.sessionsCount) { if (userid.split('/')[1] == domain.id) { wssessions[userid] = obj.sessionsCount[userid]; } }
}
ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: wssessions })); // wssessions is: userid --> count
break;
}
case 'deleteuser':
@ -1664,10 +1681,19 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
if (i >= 0) {
obj.wssessions[ws.userid].splice(i, 1);
var user = obj.users[ws.userid];
if (user) { obj.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: obj.wssessions[ws.userid].length, nolog: 1, domain: domain.id }) }
if (user) {
if (obj.parent.multiServer == null) {
obj.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: user.name, count: obj.wssessions[ws.userid].length, nolog: 1, domain: domain.id })
} else {
obj.recountSessions(ws.sessionId); // Recount sessions
}
}
if (obj.wssessions[ws.userid].length == 0) { delete obj.wssessions[ws.userid]; }
}
}
// If we have peer servers, inform them of the disconnected session
if (obj.parent.multiServer != null) { obj.parent.multiServer.DispatchMessage({ action: 'sessionEnd', sessionid: ws.sessionId }); }
});
// Send user information to web socket, this is the first thing we send
@ -2126,5 +2152,70 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
// Start server on a free port
CheckListenPort(obj.args.port, StartWebServer);
/*
obj.wssessions = {}; // UserId --> Array Of Sessions
obj.wssessions2 = {}; // "UserId + SessionRnd" --> Session (Note that the SessionId is the UserId + / + SessionRnd)
obj.wsPeerSessions = {}; // ServerId --> Array Of "UserId + SessionRnd"
obj.wsPeerSessions2 = {}; // "UserId + SessionRnd" --> ServerId
obj.wsPeerSessions3 = {}; // ServerId --> UserId --> [ SessionId ]
*/
// Count sessions and event any changes
obj.recountSessions = function (changedSessionId) {
if (changedSessionId == null) {
// Recount all sessions
// Calculate the session count for all userid's
var newSessionsCount = {};
for (var userid in obj.wssessions) { newSessionsCount[userid] = obj.wssessions[userid].length; }
for (var serverid in obj.wsPeerSessions3) {
for (var userid in obj.wsPeerSessions3[serverid]) {
var c = obj.wsPeerSessions3[serverid][userid].length;
if (newSessionsCount[userid] == null) { newSessionsCount[userid] = c; } else { newSessionsCount[userid] += c; }
}
}
// See what session counts have changed, event any changes
for (var userid in newSessionsCount) {
var newcount = newSessionsCount[userid];
var oldcount = obj.sessionsCount[userid];
if (oldcount == null) { oldcount = 0; } else { delete obj.sessionsCount[userid]; }
if (newcount != oldcount) {
var x = userid.split('/');
obj.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: x[2], count: newcount, domain: x[1], nolog: 1, nopeers: 1 })
}
}
// If there are any counts left in the old counts, event to zero
for (var userid in obj.sessionsCount) {
var oldcount = obj.sessionsCount[userid];
if ((oldcount != null) && (oldcount != 0)) {
var x = userid.split('/');
obj.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: x[2], count: 0, domain: x[1], nolog: 1, nopeers: 1 })
}
}
// Set the new session counts
obj.sessionsCount = newSessionsCount;
} else {
// Figure out the userid
var userid = changedSessionId.split('/').slice(0, 3).join('/');
// Recount only changedSessionId
var newcount = 0;
if (obj.wssessions[userid] != null) { newcount = obj.wssessions[userid].length; }
for (var serverid in obj.wsPeerSessions3) { if (obj.wsPeerSessions3[serverid][userid] != null) { newcount += obj.wsPeerSessions3[serverid][userid].length; } }
var oldcount = obj.sessionsCount[userid];
if (oldcount == null) { oldcount = 0; }
// If the count changed, update and event
if (newcount != oldcount) {
var x = userid.split('/');
obj.parent.DispatchEvent(['*'], obj, { action: 'wssessioncount', username: x[2], count: newcount, domain: x[1], nolog: 1, nopeers: 1 })
obj.sessionsCount[userid] = newcount;
}
}
}
return obj;
}