Added device state notifications to messaging applications.
Ylian Saint-Hilaire committed
Nov 1, 2022 at 22:39 UTC
1a6e78efeda27053e08d438012d7008e9e467233
3 files changed
+263
-16
meshcentral.js
+21
-2
@@ -2302,7 +2302,7 @@ function CreateMeshCentralServer(config, args) {
2302
const domainId = meshSplit[1];
2303
if (obj.config.domains[domainId] == null) return;
2304
const mailserver = obj.config.domains[domainId].mailserver;
2305
- if (mailserver == null) return;
2305
+ if ((mailserver == null) && (obj.msgserver == null)) return;
2306
2307
// Get the device group for this device
2308
const mesh = obj.webserver.meshes[meshid];
@@ -2335,7 +2335,8 @@ function CreateMeshCentralServer(config, args) {
2335
if (user.notify[nodeid] != null) { notify |= user.notify[nodeid]; }
2336
}
2337
2338
- if ((notify & 48) != 0) {
2338
+ // Email notifications
2339
+ if ((mailserver != null) && ((notify & 48) != 0)) {
2340
if (stateSet == true) {
2341
if ((notify & 16) != 0) {
2342
mailserver.notifyDeviceConnect(user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo);
@@ -2351,6 +2352,24 @@ function CreateMeshCentralServer(config, args) {
2352
}
2353
}
2354
}
2355
+
2356
+ // Messaging notifications
2357
+ if ((obj.msgserver != null) && ((notify & 384) != 0)) {
2358
+ if (stateSet == true) {
2359
+ if ((notify & 128) != 0) {
2360
+ obj.msgserver.notifyDeviceConnect(user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo);
2361
+ } else {
2362
+ obj.msgserver.cancelNotifyDeviceDisconnect(user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo);
2363
+ }
2364
+ }
2365
+ else if (stateSet == false) {
2366
+ if ((notify & 256) != 0) {
2367
+ obj.msgserver.notifyDeviceDisconnect(user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo);
2368
+ } else {
2369
+ obj.msgserver.cancelNotifyDeviceConnect(user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo);
2370
+ }
2371
+ }
2372
+ }
2373
}
2374
}
2375
}
meshmessaging.js
+206
-1
@@ -122,6 +122,7 @@ module.exports.CreateServer = function (parent) {
122
obj.callMeBotClient = null;
123
obj.pushoverClient = null;
124
obj.zulipClient = null;
125
+ const sortCollator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })
126
127
// Telegram client setup
128
if (parent.config.messaging.telegram) {
@@ -347,7 +348,7 @@ module.exports.CreateServer = function (parent) {
348
if (func != null) { func(true); }
349
} else {
350
// No providers found
350
- func(false, "No messaging providers found for this message.");
351
+ if (func != null) { func(false, "No messaging providers found for this message."); }
352
}
353
}
354
@@ -436,6 +437,210 @@ module.exports.CreateServer = function (parent) {
437
obj.sendMessage(to, sms, domain, func);
438
};
439
440
+ // Send device state change notification
441
+ obj.sendDeviceNotify = function (domain, username, to, connections, disconnections, lang) {
442
+ if (to == null) return;
443
+ parent.debug('email', "Sending device state change message to " + to);
444
+
445
+ var sms = [];
446
+ if (connections.length > 0) { sms.push('Connections: ' + connections.join(', ')); } // TODO: Translate 'Connections: '
447
+ if (disconnections.length > 0) { sms.push('Disconnections: ' + disconnections.join(', ')); } // TODO: Translate 'Disconnections: '
448
+ if (sms.length == 0) return;
449
+ sms = sms.join(' - ');
450
+ if (sms.length > 1000) { sms = sms.substring(0, 997) + '...'; } // Limit messages to 1000 characters
451
+
452
+ // Send the message
453
+ obj.sendMessage(to, sms, domain, null);
454
+ };
455
+
456
+
457
+ //
458
+ // Device connetion and disconnection notifications
459
+ //
460
+
461
+ obj.deviceNotifications = {}; // UserId --> { timer, nodes: nodeid --> connectType }
462
+
463
+ // A device connected and a user needs to be notified about it.
464
+ obj.notifyDeviceConnect = function (user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo) {
465
+ const mesh = parent.webserver.meshes[meshid];
466
+ if (mesh == null) return;
467
+
468
+ // Add the user and start a timer
469
+ if (obj.deviceNotifications[user._id] == null) {
470
+ obj.deviceNotifications[user._id] = { nodes: {} };
471
+ obj.deviceNotifications[user._id].timer = setTimeout(function () { sendDeviceNotifications(user._id); }, 1 * 60 * 1000); // 1 minute before message is sent
472
+ }
473
+
474
+ // Add the device
475
+ if (obj.deviceNotifications[user._id].nodes[nodeid] == null) {
476
+ obj.deviceNotifications[user._id].nodes[nodeid] = { c: connectType }; // This device connection need to be added
477
+ } else {
478
+ const info = obj.deviceNotifications[user._id].nodes[nodeid];
479
+ if ((info.d != null) && ((info.d & connectType) != 0)) {
480
+ info.d -= connectType; // This device disconnect cancels out a device connection
481
+ if (((info.c == null) || (info.c == 0)) && ((info.d == null) || (info.d == 0))) {
482
+ // This device no longer needs a notification
483
+ delete obj.deviceNotifications[user._id].nodes[nodeid];
484
+ if (Object.keys(obj.deviceNotifications[user._id].nodes).length == 0) {
485
+ // This user no longer needs a notification
486
+ clearTimeout(obj.deviceNotifications[user._id].timer);
487
+ delete obj.deviceNotifications[user._id];
488
+ }
489
+ return;
490
+ }
491
+ } else {
492
+ if (info.c != null) {
493
+ info.c |= connectType; // This device disconnect needs to be added
494
+ } else {
495
+ info.c = connectType; // This device disconnect needs to be added
496
+ }
497
+ }
498
+ }
499
+
500
+ // Set the device group name
501
+ if ((extraInfo != null) && (extraInfo.name != null)) { obj.deviceNotifications[user._id].nodes[nodeid].nn = extraInfo.name; }
502
+ obj.deviceNotifications[user._id].nodes[nodeid].mn = mesh.name;
503
+ }
504
+
505
+ // Cancel a device disconnect notification
506
+ obj.cancelNotifyDeviceDisconnect = function (user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo) {
507
+ const mesh = parent.webserver.meshes[meshid];
508
+ if (mesh == null) return;
509
+
510
+ if ((obj.deviceNotifications[user._id] != null) && (obj.deviceNotifications[user._id].nodes[nodeid] != null)) {
511
+ const info = obj.deviceNotifications[user._id].nodes[nodeid];
512
+ if ((info.d != null) && ((info.d & connectType) != 0)) {
513
+ info.d -= connectType; // This device disconnect cancels out a device connection
514
+ if (((info.c == null) || (info.c == 0)) && ((info.d == null) || (info.d == 0))) {
515
+ // This device no longer needs a notification
516
+ delete obj.deviceNotifications[user._id].nodes[nodeid];
517
+ if (Object.keys(obj.deviceNotifications[user._id].nodes).length == 0) {
518
+ // This user no longer needs a notification
519
+ clearTimeout(obj.deviceNotifications[user._id].timer);
520
+ delete obj.deviceNotifications[user._id];
521
+ }
522
+ }
523
+ }
524
+ }
525
+ }
526
+
527
+ // A device disconnected and a user needs to be notified about it.
528
+ obj.notifyDeviceDisconnect = function (user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo) {
529
+ const mesh = parent.webserver.meshes[meshid];
530
+ if (mesh == null) return;
531
+
532
+ // Add the user and start a timer
533
+ if (obj.deviceNotifications[user._id] == null) {
534
+ obj.deviceNotifications[user._id] = { nodes: {} };
535
+ obj.deviceNotifications[user._id].timer = setTimeout(function () { sendDeviceNotifications(user._id); }, 1 * 60 * 1000); // 1 minute before message is sent
536
+ }
537
+
538
+ // Add the device
539
+ if (obj.deviceNotifications[user._id].nodes[nodeid] == null) {
540
+ obj.deviceNotifications[user._id].nodes[nodeid] = { d: connectType }; // This device disconnect need to be added
541
+ } else {
542
+ const info = obj.deviceNotifications[user._id].nodes[nodeid];
543
+ if ((info.c != null) && ((info.c & connectType) != 0)) {
544
+ info.c -= connectType; // This device disconnect cancels out a device connection
545
+ if (((info.d == null) || (info.d == 0)) && ((info.c == null) || (info.c == 0))) {
546
+ // This device no longer needs a notification
547
+ delete obj.deviceNotifications[user._id].nodes[nodeid];
548
+ if (Object.keys(obj.deviceNotifications[user._id].nodes).length == 0) {
549
+ // This user no longer needs a notification
550
+ clearTimeout(obj.deviceNotifications[user._id].timer);
551
+ delete obj.deviceNotifications[user._id];
552
+ }
553
+ return;
554
+ }
555
+ } else {
556
+ if (info.d != null) {
557
+ info.d |= connectType; // This device disconnect needs to be added
558
+ } else {
559
+ info.d = connectType; // This device disconnect needs to be added
560
+ }
561
+ }
562
+ }
563
+
564
+ // Set the device group name
565
+ if ((extraInfo != null) && (extraInfo.name != null)) { obj.deviceNotifications[user._id].nodes[nodeid].nn = extraInfo.name; }
566
+ obj.deviceNotifications[user._id].nodes[nodeid].mn = mesh.name;
567
+ }
568
+
569
+ // Cancel a device connect notification
570
+ obj.cancelNotifyDeviceConnect = function (user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo) {
571
+ const mesh = parent.webserver.meshes[meshid];
572
+ if (mesh == null) return;
573
+
574
+ if ((obj.deviceNotifications[user._id] != null) && (obj.deviceNotifications[user._id].nodes[nodeid] != null)) {
575
+ const info = obj.deviceNotifications[user._id].nodes[nodeid];
576
+ if ((info.c != null) && ((info.c & connectType) != 0)) {
577
+ info.c -= connectType; // This device disconnect cancels out a device connection
578
+ if (((info.d == null) || (info.d == 0)) && ((info.c == null) || (info.c == 0))) {
579
+ // This device no longer needs a notification
580
+ delete obj.deviceNotifications[user._id].nodes[nodeid];
581
+ if (Object.keys(obj.deviceNotifications[user._id].nodes).length == 0) {
582
+ // This user no longer needs a notification
583
+ clearTimeout(obj.deviceNotifications[user._id].timer);
584
+ delete obj.deviceNotifications[user._id];
585
+ }
586
+ }
587
+ }
588
+ }
589
+ }
590
+
591
+ // Send a notification about device connections and disconnections to a user
592
+ function sendDeviceNotifications(userid) {
593
+ if (obj.deviceNotifications[userid] == null) return;
594
+ clearTimeout(obj.deviceNotifications[userid].timer);
595
+
596
+ var connections = [];
597
+ var disconnections = [];
598
+
599
+ for (var nodeid in obj.deviceNotifications[userid].nodes) {
600
+ var info = obj.deviceNotifications[userid].nodes[nodeid];
601
+ if ((info.c != null) && (info.c > 0) && (info.nn != null) && (info.mn != null)) {
602
+ /*
603
+ var c = [];
604
+ if (info.c & 1) { c.push("Agent"); }
605
+ if (info.c & 2) { c.push("CIRA"); }
606
+ if (info.c & 4) { c.push("AMT"); }
607
+ if (info.c & 8) { c.push("AMT-Relay"); }
608
+ if (info.c & 16) { c.push("MQTT"); }
609
+ connections.push(info.mn + ', ' + info.nn + ': ' + c.join(', '));
610
+ */
611
+ if (info.c & 1) { connections.push(info.nn); }
612
+ }
613
+ if ((info.d != null) && (info.d > 0) && (info.nn != null) && (info.mn != null)) {
614
+ /*
615
+ var d = [];
616
+ if (info.d & 1) { d.push("Agent"); }
617
+ if (info.d & 2) { d.push("CIRA"); }
618
+ if (info.d & 4) { d.push("AMT"); }
619
+ if (info.d & 8) { d.push("AMT-Relay"); }
620
+ if (info.d & 16) { d.push("MQTT"); }
621
+ disconnections.push(info.mn + ', ' + info.nn + ': ' + d.join(', '));
622
+ */
623
+ if (info.d & 1) { disconnections.push(info.nn); }
624
+ }
625
+ }
626
+
627
+ // Sort the notifications
628
+ connections.sort(sortCollator.compare);
629
+ disconnections.sort(sortCollator.compare);
630
+
631
+ // Get the user and domain
632
+ const user = parent.webserver.users[userid];
633
+ if ((user == null) || (user.email == null) || (user.emailVerified !== true)) return;
634
+ const domain = obj.parent.config.domains[user.domain];
635
+ if (domain == null) return;
636
+
637
+ // Send the message
638
+ obj.sendDeviceNotify(domain, user.name, user.msghandle, connections, disconnections, user.llang);
639
+
640
+ // Clean up
641
+ delete obj.deviceNotifications[userid];
642
+ }
643
+
644
return obj;
645
};
646
meshuser.js
+36
-13
@@ -1717,12 +1717,15 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
1717
{
1718
if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
1719
1720
- // 2 = WebPage device connections
1721
- // 4 = WebPage device disconnections
1722
- // 8 = WebPage device desktop and serial events
1723
- // 16 = Email device connections
1724
- // 32 = Email device disconnections
1725
- // 64 = Email device help request
1720
+ // 2 = WebPage device connections
1721
+ // 4 = WebPage device disconnections
1722
+ // 8 = WebPage device desktop and serial events
1723
+ // 16 = Email device connections
1724
+ // 32 = Email device disconnections
1725
+ // 64 = Email device help request
1726
+ // 128 = Messaging device connections
1727
+ // 256 = Messaging device disconnections
1728
+ // 512 = Messaging device help request
1729
1730
var err = null;
1731
try {
@@ -1767,12 +1770,15 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
1770
{
1771
if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
1772
1770
- // 2 = WebPage device connections
1771
- // 4 = WebPage device disconnections
1772
- // 8 = WebPage device desktop and serial events
1773
- // 16 = Email device connections
1774
- // 32 = Email device disconnections
1775
- // 64 = Email device help request
1773
+ // 2 = WebPage device connections
1774
+ // 4 = WebPage device disconnections
1775
+ // 8 = WebPage device desktop and serial events
1776
+ // 16 = Email device connections
1777
+ // 32 = Email device disconnections
1778
+ // 64 = Email device help request
1779
+ // 128 = Messaging device connections
1780
+ // 256 = Messaging device disconnections
1781
+ // 512 = Messaging device help request
1782
1783
var err = null;
1784
try {
@@ -5312,6 +5318,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
5318
'dupagents': [serverUserCommandDupAgents, ""],
5319
'email': [serverUserCommandEmail, ""],
5320
'emailnotifications': [serverUserCommandEmailNotifications, ""],
5321
+ 'msgnotifications': [serverUserCommandMessageNotifications, ""],
5322
'firebase': [serverUserCommandFirebase, ""],
5323
'heapdump': [serverUserCommandHeapDump, ""],
5324
'heapdump2': [serverUserCommandHeapDump2, ""],
@@ -6892,7 +6899,23 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
6899
x += ' ' + info.mn + ', ' + info.nn + ', c:' + (info.c ? info.c : 0) + ', d:' + (info.d ? info.d : 0) + '\r\n';
6900
}
6901
}
6895
- cmdData.result = ((x == '')?'None':x);
6902
+ cmdData.result = ((x == '') ? 'None' : x);
6903
+ }
6904
+ }
6905
+
6906
+ function serverUserCommandMessageNotifications(cmdData) {
6907
+ if (parent.parent.msgserver == null) {
6908
+ cmdData.result = "No messaging service enabled.";
6909
+ } else {
6910
+ var x = '';
6911
+ for (var userid in parent.parent.msgserver.deviceNotifications) {
6912
+ x += userid + '\r\n';
6913
+ for (var nodeid in parent.parent.msgserver.deviceNotifications[userid].nodes) {
6914
+ const info = parent.parent.msgserver.deviceNotifications[userid].nodes[nodeid];
6915
+ x += ' ' + info.mn + ', ' + info.nn + ', c:' + (info.c ? info.c : 0) + ', d:' + (info.d ? info.d : 0) + '\r\n';
6916
+ }
6917
+ }
6918
+ cmdData.result = ((x == '') ? 'None' : x);
6919
}
6920
}
6921