From af1fcb84311b8edb60b98e2cf085f2ff9bb7f3b6 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Tue, 9 Feb 2021 15:31:50 -0800 Subject: [PATCH] More web push progress. --- meshuser.js | 27 ++++++++++++++++++++++++++ public/serviceworker.js | 23 +++++++++------------- views/default.handlebars | 16 +++++++-------- webserver.js | 42 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 22 deletions(-) diff --git a/meshuser.js b/meshuser.js index 21d8bae1..403e6991 100644 --- a/meshuser.js +++ b/meshuser.js @@ -2746,6 +2746,17 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (parent.parent.multiServer != null) { // TODO: Add multi-server support } + + // If the user is not connected, use web push if available. + if ((parent.wssessions[chguser._id] == null) && (parent.sessionsCount[chguser._id] == null)) { + // Perform web push notification + var payload = { body: command.msg, icon: 8 }; // Icon 8 is the user icon. + if (command.url) { payload.url = command.url; } + if (domain.title != null) { payload.title = domain.title; } else { payload.title = "MeshCentral"; } + payload.title += ' - ' + user.name; + parent.performWebPush(domain, chguser, payload, { TTL: 60 }); // For now, 1 minute TTL + } + break; } case 'meshmessenger': @@ -2772,6 +2783,22 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (parent.parent.multiServer != null) { // TODO: Add multi-server support } + + // If the user is not connected, use web push if available. + if ((parent.wssessions[chguser._id] == null) && (parent.sessionsCount[chguser._id] == null)) { + // Create the server url + var httpsPort = ((args.aliasport == null) ? args.port : args.aliasport); // Use HTTPS alias port is specified + var xdomain = (domain.dns == null) ? domain.id : ''; + if (xdomain != '') xdomain += "/"; + var url = "https://" + parent.getWebServerName(domain) + ":" + httpsPort + "/" + xdomain + "messenger?id=meshmessenger/" + encodeURIComponent(command.userid) + "/" + encodeURIComponent(user._id); + + // Perform web push notification + var payload = { body: "Chat Request, Click here to accept.", icon: 8, url: url }; // Icon 8 is the user icon. + if (domain.title != null) { payload.title = domain.title; } else { payload.title = "MeshCentral"; } + payload.title += ' - ' + user.name; + parent.performWebPush(domain, chguser, payload, { TTL: 60 }); // For now, 1 minute TTL + } + return; } // User-to-device chat is not support in LAN-only mode yet. We need the agent to replace the IP address of the server?? diff --git a/public/serviceworker.js b/public/serviceworker.js index f9c6f3d4..39c860ba 100644 --- a/public/serviceworker.js +++ b/public/serviceworker.js @@ -1,18 +1,13 @@ self.addEventListener('push', function (event) { - console.log('Service Worker push', JSON.stringify(event)); - if (event.data) { - console.log("Push event!! ", event.data.text()); - showLocalNotification("Yolo", event.data.text(), self.registration); - } else { - console.log("Push event but no data"); - } + if (event.data == null) return; + var json = event.data.json(); + const options = { body: json.body, icon: '/favicon-303x303.png', tag: json.url }; + if (json.icon) { options.icon = '/images/notify/icons128-' + json.icon + '.png'; } + self.registration.showNotification(json.title, options); }); -const showLocalNotification = function(title, body, swRegistration) { - const options = { - body - // here you can add more properties like icon, image, vibrate, etc. - }; - swRegistration.showNotification(title, options); -}; \ No newline at end of file +self.addEventListener('notificationclick', function (event) { + event.notification.close(); + if ((event.notification.tag != null) && (event.notification.tag != '')) { event.waitUntil(self.clients.openWindow(event.notification.tag)); } +}); \ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 3022e486..f30ee904 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -12732,10 +12732,10 @@ x += ''; if (user.phone && (features & 0x02000000)) { x += ''; } if ((typeof user.email == 'string') && (user.emailVerified === true) && (features & 0x00000040)) { x += ''; } - if (!self && (activeSessions > 0)) { + if (!self && ((activeSessions > 0) || ((features2 & 8) && (user.webpush)))) { x += ''; x += ''; - if ((serverinfo != null) && (serverinfo.altmessenging != null)) { for (var i in serverinfo.altmessenging) { x += ''; } } + if ((activeSessions > 0) && (serverinfo != null) && (serverinfo.altmessenging != null)) { for (var i in serverinfo.altmessenging) { x += ''; } } } // Setup the panel @@ -13852,11 +13852,11 @@ navigator.serviceWorker.ready.then(function(reg) { reg.pushManager.subscribe({ applicationServerKey: urlBase64ToUint8Array(serverinfo.vapidpublickey), userVisibleOnly: true }).then(function(sub) { meshserver.send({ action: 'webpush', sub: sub }); - }).catch(function(e) { console.error('Unable to subscribe to push', e); }); + }).catch(function(e) { console.error('Worker: Unable to subscribe to push', e); }); }) }).catch(function(error) { // Registration failed - console.log('Registration failed', error); + console.log('Worker: Registration failed', error); }); } } @@ -14405,10 +14405,10 @@ // Used to convert Base64 public VAPID key to bytearray. function urlBase64ToUint8Array(base64String) { - const padding = '='.repeat((4 - base64String.length % 4) % 4); - const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); - const rawData = atob(base64); - const outputArray = new Uint8Array(rawData.length); + var padding = '='.repeat((4 - base64String.length % 4) % 4); + var base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); + var rawData = atob(base64); + var outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } diff --git a/webserver.js b/webserver.js index aec02d5a..c60c224f 100644 --- a/webserver.js +++ b/webserver.js @@ -6686,6 +6686,48 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } } + // Perform a web push to a user + // If any of the push fail, remove the subscription from the user's webpush subscription list. + obj.performWebPush = function (domain, user, payload, options) { + if ((parent.webpush == null) || (Array.isArray(user.webpush) == false) || (user.webpush.length == 0)) return; + + var completionFunc = function pushCompletionFunc(sub, fail) { + pushCompletionFunc.failCount += fail; + if (--pushCompletionFunc.pushCount == 0) { + if (pushCompletionFunc.failCount > 0) { + var user = pushCompletionFunc.user, newwebpush = []; + for (var i in user.webpush) { if (user.webpush[i].fail == null) { newwebpush.push(user.webpush[i]); } } + user.webpush = newwebpush; + + // Update the database + obj.db.SetUser(user); + + // Event the change + var message = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', domain: domain.id, nolog: 1 }; + if (db.changeStream) { message.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come. + var targets = ['*', 'server-users', user._id]; + if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } } + parent.DispatchEvent(targets, obj, message); + } + } + } + completionFunc.pushCount = user.webpush.length; + completionFunc.user = user; + completionFunc.domain = domain; + completionFunc.failCount = 0; + + for (var i in user.webpush) { + var errorFunc = function pushErrorFunc(error) { pushErrorFunc.sub.fail = 1; pushErrorFunc.call(pushErrorFunc.sub, 1); } + errorFunc.sub = user.webpush[i]; + errorFunc.call = completionFunc; + var successFunc = function pushSuccessFunc(value) { pushSuccessFunc.call(pushSuccessFunc.sub, 0); } + successFunc.sub = user.webpush[i]; + successFunc.call = completionFunc; + parent.webpush.sendNotification(user.webpush[i], JSON.stringify(payload), options).then(successFunc, errorFunc); + } + + } + // Return true if a mobile browser is detected. // This code comes from "http://detectmobilebrowsers.com/" and was modified, This is free and unencumbered software released into the public domain. For more information, please refer to the http://unlicense.org/ function isMobileBrowser(req) {