From 486a129a7805453b8514635b1b65b3f5807541ea Mon Sep 17 00:00:00 2001 From: DoidoYo Date: Fri, 13 Dec 2024 14:31:37 -0500 Subject: [PATCH 01/10] Add WebPush Notification Provider From 961368b419643fb463e4a508ed2be49cb658d2da Mon Sep 17 00:00:00 2001 From: DoidoYo Date: Sun, 15 Dec 2024 23:01:34 -0500 Subject: [PATCH 02/10] Webpush with VAPID generation --- config/vite.config.js | 11 +++ package.json | 3 + server/notification-providers/Webpush.js | 53 +++++++++++++++ server/notification.js | 4 +- server/server.js | 28 ++++++++ src/components/NotificationDialog.vue | 3 +- src/components/notifications/Webpush.vue | 85 ++++++++++++++++++++++++ src/components/notifications/index.js | 2 + src/serviceWorker.ts | 23 +++++++ 9 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 server/notification-providers/Webpush.js create mode 100644 src/components/notifications/Webpush.vue create mode 100644 src/serviceWorker.ts diff --git a/config/vite.config.js b/config/vite.config.js index 7f2dfb6ff1..cb9accd51f 100644 --- a/config/vite.config.js +++ b/config/vite.config.js @@ -3,6 +3,7 @@ import { defineConfig } from "vite"; import visualizer from "rollup-plugin-visualizer"; import viteCompression from "vite-plugin-compression"; import VueDevTools from "vite-plugin-vue-devtools"; +import { VitePWA } from 'vite-plugin-pwa'; const postCssScss = require("postcss-scss"); const postcssRTLCSS = require("postcss-rtlcss"); @@ -32,6 +33,16 @@ export default defineConfig({ filter: viteCompressionFilter, }), VueDevTools(), + VitePWA({ + registerType: null, + srcDir: 'src', + filename: 'serviceWorker.ts', + strategies: 'injectManifest', + devOptions: { + enabled: true, + type: 'module' + }, + }), ], css: { postcss: { diff --git a/package.json b/package.json index 4f8eef138f..f8aa982f2e 100644 --- a/package.json +++ b/package.json @@ -140,6 +140,7 @@ "tcp-ping": "~0.1.1", "thirty-two": "~1.0.2", "tough-cookie": "~4.1.3", + "web-push": "^3.6.7", "ws": "^8.13.0" }, "devDependencies": { @@ -154,6 +155,7 @@ "@testcontainers/rabbitmq": "^10.13.2", "@types/bootstrap": "~5.1.9", "@types/node": "^20.8.6", + "@types/web-push": "^3.6.4", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "@vitejs/plugin-vue": "~5.0.1", @@ -192,6 +194,7 @@ "v-pagination-3": "~0.1.7", "vite": "~5.2.8", "vite-plugin-compression": "^0.5.1", + "vite-plugin-pwa": "^0.21.1", "vite-plugin-vue-devtools": "^7.0.15", "vue": "~3.4.2", "vue-chartjs": "~5.2.0", diff --git a/server/notification-providers/Webpush.js b/server/notification-providers/Webpush.js new file mode 100644 index 0000000000..b7d6b65d42 --- /dev/null +++ b/server/notification-providers/Webpush.js @@ -0,0 +1,53 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { UP } = require("../../src/util"); +const web_push = require('web-push'); +const { setting } = require("../util-server"); + +class Webpush extends NotificationProvider { + name = "Webpush"; + + /** + * @inheritDoc + */ + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + const okMsg = "Sent Successfully."; + + try { + // Get VAPID keys from settings + const publicVapidKey = await setting("webpushPublicVapidKey"); + const privateVapidKey = await setting("webpushPrivateVapidKey"); + // Set Vapid keys in web-push helper lib + web_push.setVapidDetails("https://github.com/louislam/uptime-kuma", publicVapidKey, privateVapidKey); + + if (heartbeatJSON === null && monitorJSON === null) { + // Test message + const data = JSON.stringify({ + title: "TEST", + body: "Test Alert - " + msg + }); + //send push notification using web-push lib + await web_push.sendNotification(notification.subscription, data); + + return okMsg; + } + + const title = `Monitor ${heartbeatJSON["status"] === UP ? "UP" : "DOWN"}`; + const down = "❌ " + monitorJSON["name"] + " is DOWN ❌"; + const up = "✅ " + monitorJSON["name"] + " is UP ✅";; + + const data = JSON.stringify({ + title: title, + body: `${heartbeatJSON["status"] === UP ? up : down}` + }); + //send push notification using web-push lib + await web_push.sendNotification(notification.subscription, data); + + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = Webpush; \ No newline at end of file diff --git a/server/notification.js b/server/notification.js index e7977eb4af..bfd9f47d69 100644 --- a/server/notification.js +++ b/server/notification.js @@ -69,6 +69,7 @@ const Cellsynt = require("./notification-providers/cellsynt"); const Onesender = require("./notification-providers/onesender"); const Wpush = require("./notification-providers/wpush"); const SendGrid = require("./notification-providers/send-grid"); +const Webpush = require("./notification-providers/Webpush"); class Notification { @@ -154,7 +155,8 @@ class Notification { new GtxMessaging(), new Cellsynt(), new Wpush(), - new SendGrid() + new SendGrid(), + new Webpush(), ]; for (let item of list) { if (! item.name) { diff --git a/server/server.js b/server/server.js index ec5ad49f68..ff4a5f2daf 100644 --- a/server/server.js +++ b/server/server.js @@ -96,6 +96,8 @@ const { getSettings, setSettings, setting, initJWTSecret, checkLogin, doubleChec log.debug("server", "Importing Notification"); const { Notification } = require("./notification"); Notification.init(); +log.debug("server", "Importing Web-Push"); +const web_push = require('web-push'); log.debug("server", "Importing Database"); const Database = require("./database"); @@ -1488,6 +1490,32 @@ let needSetup = false; } }); + socket.on("getWebpushVapidPublicKey", async (callback) => { + try { + let publicVapidKey = await Settings.get("webpushPublicVapidKey"); + + if (!publicVapidKey) { + console.debug("Generating new VAPID keys"); + const vapidKeys = web_push.generateVAPIDKeys(); + + await Settings.set("webpushPublicVapidKey", vapidKeys.publicKey); + await Settings.set("webpushPrivateVapidKey", vapidKeys.privateKey); + + publicVapidKey = vapidKeys.publicKey; + } + + callback({ + ok: true, + msg: publicVapidKey, + }); + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + socket.on("clearEvents", async (monitorID, callback) => { try { checkLogin(socket); diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index f6d7280299..d7757520e4 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -165,7 +165,8 @@ export default { "whapi": "WhatsApp (Whapi)", "gtxmessaging": "GtxMessaging", "Cellsynt": "Cellsynt", - "SendGrid": "SendGrid" + "SendGrid": "SendGrid", + "Webpush": "Webpush", }; // Put notifications here if it's not supported in most regions or its documentation is not in English diff --git a/src/components/notifications/Webpush.vue b/src/components/notifications/Webpush.vue new file mode 100644 index 0000000000..7b3d6c5a46 --- /dev/null +++ b/src/components/notifications/Webpush.vue @@ -0,0 +1,85 @@ + + + \ No newline at end of file diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index efa2af5c49..c59503ee9e 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -67,6 +67,7 @@ import Cellsynt from "./Cellsynt.vue"; import WPush from "./WPush.vue"; import SIGNL4 from "./SIGNL4.vue"; import SendGrid from "./SendGrid.vue"; +import Webpush from "./Webpush.vue"; /** * Manage all notification form. @@ -142,6 +143,7 @@ const NotificationFormList = { "Cellsynt": Cellsynt, "WPush": WPush, "SendGrid": SendGrid, + "Webpush": Webpush, }; export default NotificationFormList; diff --git a/src/serviceWorker.ts b/src/serviceWorker.ts new file mode 100644 index 0000000000..97d6462131 --- /dev/null +++ b/src/serviceWorker.ts @@ -0,0 +1,23 @@ +// Needed per Vite PWA docs +import { precacheAndRoute } from 'workbox-precaching' +declare let self: ServiceWorkerGlobalScope +precacheAndRoute(self.__WB_MANIFEST) + +// Receive push notifications +self.addEventListener('push', function (e) { + if (!( + self.Notification && + self.Notification.permission === 'granted' + )) { + //notifications aren't supported or permission not granted! + return; + } + + if (e.data) { + let message = e.data.json(); + e.waitUntil(self.registration.showNotification(message.title, { + body: message.body, + // actions: message.actions + })); + } +}); \ No newline at end of file From 1041812645d199f54cb8c22c115ef2bd267ccaa7 Mon Sep 17 00:00:00 2001 From: DoidoYo Date: Mon, 16 Dec 2024 14:21:39 -0500 Subject: [PATCH 03/10] Lint cleanup and comments --- config/vite.config.js | 12 ++--- package.json | 2 + server/notification-providers/Webpush.js | 25 +++++---- server/server.js | 8 +-- src/components/notifications/Webpush.vue | 69 +++++++++++++++--------- src/serviceWorker.ts | 14 ++--- 6 files changed, 73 insertions(+), 57 deletions(-) diff --git a/config/vite.config.js b/config/vite.config.js index cb9accd51f..b36591052f 100644 --- a/config/vite.config.js +++ b/config/vite.config.js @@ -3,7 +3,7 @@ import { defineConfig } from "vite"; import visualizer from "rollup-plugin-visualizer"; import viteCompression from "vite-plugin-compression"; import VueDevTools from "vite-plugin-vue-devtools"; -import { VitePWA } from 'vite-plugin-pwa'; +import { VitePWA } from "vite-plugin-pwa"; const postCssScss = require("postcss-scss"); const postcssRTLCSS = require("postcss-rtlcss"); @@ -35,13 +35,9 @@ export default defineConfig({ VueDevTools(), VitePWA({ registerType: null, - srcDir: 'src', - filename: 'serviceWorker.ts', - strategies: 'injectManifest', - devOptions: { - enabled: true, - type: 'module' - }, + srcDir: "src", + filename: "serviceWorker.ts", + strategies: "injectManifest", }), ], css: { diff --git a/package.json b/package.json index f8aa982f2e..457e1b0212 100644 --- a/package.json +++ b/package.json @@ -145,6 +145,7 @@ }, "devDependencies": { "@actions/github": "~5.1.1", + "@eslint/js": "^9.17.0", "@fortawesome/fontawesome-svg-core": "~1.2.36", "@fortawesome/free-regular-svg-icons": "~5.15.4", "@fortawesome/free-solid-svg-icons": "~5.15.4", @@ -177,6 +178,7 @@ "eslint-plugin-vue": "~8.7.1", "favico.js": "~0.3.10", "get-port-please": "^3.1.1", + "globals": "^15.13.0", "node-ssh": "~13.1.0", "postcss-html": "~1.5.0", "postcss-rtlcss": "~3.7.2", diff --git a/server/notification-providers/Webpush.js b/server/notification-providers/Webpush.js index b7d6b65d42..9d7b4d770c 100644 --- a/server/notification-providers/Webpush.js +++ b/server/notification-providers/Webpush.js @@ -1,7 +1,6 @@ const NotificationProvider = require("./notification-provider"); -const axios = require("axios"); const { UP } = require("../../src/util"); -const web_push = require('web-push'); +const webpush = require("web-push"); const { setting } = require("../util-server"); class Webpush extends NotificationProvider { @@ -18,31 +17,31 @@ class Webpush extends NotificationProvider { const publicVapidKey = await setting("webpushPublicVapidKey"); const privateVapidKey = await setting("webpushPrivateVapidKey"); // Set Vapid keys in web-push helper lib - web_push.setVapidDetails("https://github.com/louislam/uptime-kuma", publicVapidKey, privateVapidKey); - + webpush.setVapidDetails("https://github.com/louislam/uptime-kuma", publicVapidKey, privateVapidKey); + if (heartbeatJSON === null && monitorJSON === null) { // Test message const data = JSON.stringify({ - title: "TEST", + title: "TEST", body: "Test Alert - " + msg }); //send push notification using web-push lib - await web_push.sendNotification(notification.subscription, data); - + await webpush.sendNotification(notification.subscription, data); + return okMsg; } const title = `Monitor ${heartbeatJSON["status"] === UP ? "UP" : "DOWN"}`; const down = "❌ " + monitorJSON["name"] + " is DOWN ❌"; - const up = "✅ " + monitorJSON["name"] + " is UP ✅";; + const up = "✅ " + monitorJSON["name"] + " is UP ✅"; - const data = JSON.stringify({ - title: title, + const data = JSON.stringify({ + title: title, body: `${heartbeatJSON["status"] === UP ? up : down}` }); //send push notification using web-push lib - await web_push.sendNotification(notification.subscription, data); - + await webpush.sendNotification(notification.subscription, data); + return okMsg; } catch (error) { this.throwGeneralAxiosError(error); @@ -50,4 +49,4 @@ class Webpush extends NotificationProvider { } } -module.exports = Webpush; \ No newline at end of file +module.exports = Webpush; diff --git a/server/server.js b/server/server.js index ff4a5f2daf..d8fcb25692 100644 --- a/server/server.js +++ b/server/server.js @@ -97,7 +97,7 @@ log.debug("server", "Importing Notification"); const { Notification } = require("./notification"); Notification.init(); log.debug("server", "Importing Web-Push"); -const web_push = require('web-push'); +const webpush = require("web-push"); log.debug("server", "Importing Database"); const Database = require("./database"); @@ -1493,14 +1493,14 @@ let needSetup = false; socket.on("getWebpushVapidPublicKey", async (callback) => { try { let publicVapidKey = await Settings.get("webpushPublicVapidKey"); - + if (!publicVapidKey) { console.debug("Generating new VAPID keys"); - const vapidKeys = web_push.generateVAPIDKeys(); + const vapidKeys = webpush.generateVAPIDKeys(); await Settings.set("webpushPublicVapidKey", vapidKeys.publicKey); await Settings.set("webpushPrivateVapidKey", vapidKeys.privateKey); - + publicVapidKey = vapidKeys.publicKey; } diff --git a/src/components/notifications/Webpush.vue b/src/components/notifications/Webpush.vue index 7b3d6c5a46..5caa9627af 100644 --- a/src/components/notifications/Webpush.vue +++ b/src/components/notifications/Webpush.vue @@ -1,16 +1,14 @@