This commit is contained in:
DoidoYo 2024-12-20 07:46:44 +00:00 committed by GitHub
commit c06201d045
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 2684 additions and 8 deletions

View file

@ -3,6 +3,7 @@ import { defineConfig } from "vite";
import visualizer from "rollup-plugin-visualizer"; import visualizer from "rollup-plugin-visualizer";
import viteCompression from "vite-plugin-compression"; import viteCompression from "vite-plugin-compression";
import VueDevTools from "vite-plugin-vue-devtools"; import VueDevTools from "vite-plugin-vue-devtools";
import { VitePWA } from "vite-plugin-pwa";
const postCssScss = require("postcss-scss"); const postCssScss = require("postcss-scss");
const postcssRTLCSS = require("postcss-rtlcss"); const postcssRTLCSS = require("postcss-rtlcss");
@ -32,6 +33,12 @@ export default defineConfig({
filter: viteCompressionFilter, filter: viteCompressionFilter,
}), }),
VueDevTools(), VueDevTools(),
VitePWA({
registerType: null,
srcDir: "src",
filename: "serviceWorker.ts",
strategies: "injectManifest",
}),
], ],
css: { css: {
postcss: { postcss: {

2462
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -140,6 +140,7 @@
"tcp-ping": "~0.1.1", "tcp-ping": "~0.1.1",
"thirty-two": "~1.0.2", "thirty-two": "~1.0.2",
"tough-cookie": "~4.1.3", "tough-cookie": "~4.1.3",
"web-push": "^3.6.7",
"ws": "^8.13.0" "ws": "^8.13.0"
}, },
"devDependencies": { "devDependencies": {
@ -154,6 +155,7 @@
"@testcontainers/rabbitmq": "^10.13.2", "@testcontainers/rabbitmq": "^10.13.2",
"@types/bootstrap": "~5.1.9", "@types/bootstrap": "~5.1.9",
"@types/node": "^20.8.6", "@types/node": "^20.8.6",
"@types/web-push": "^3.6.4",
"@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5", "@typescript-eslint/parser": "^6.7.5",
"@vitejs/plugin-vue": "~5.0.1", "@vitejs/plugin-vue": "~5.0.1",
@ -192,6 +194,7 @@
"v-pagination-3": "~0.1.7", "v-pagination-3": "~0.1.7",
"vite": "~5.2.8", "vite": "~5.2.8",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-pwa": "^0.21.1",
"vite-plugin-vue-devtools": "^7.0.15", "vite-plugin-vue-devtools": "^7.0.15",
"vue": "~3.4.2", "vue": "~3.4.2",
"vue-chartjs": "~5.2.0", "vue-chartjs": "~5.2.0",

View file

@ -0,0 +1,52 @@
const NotificationProvider = require("./notification-provider");
const { UP } = require("../../src/util");
const webpush = 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
webpush.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 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 data = JSON.stringify({
title: title,
body: `${heartbeatJSON["status"] === UP ? up : down}`
});
//send push notification using web-push lib
await webpush.sendNotification(notification.subscription, data);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Webpush;

View file

@ -69,6 +69,7 @@ const Cellsynt = require("./notification-providers/cellsynt");
const Onesender = require("./notification-providers/onesender"); const Onesender = require("./notification-providers/onesender");
const Wpush = require("./notification-providers/wpush"); const Wpush = require("./notification-providers/wpush");
const SendGrid = require("./notification-providers/send-grid"); const SendGrid = require("./notification-providers/send-grid");
const Webpush = require("./notification-providers/Webpush");
class Notification { class Notification {
@ -154,7 +155,8 @@ class Notification {
new GtxMessaging(), new GtxMessaging(),
new Cellsynt(), new Cellsynt(),
new Wpush(), new Wpush(),
new SendGrid() new SendGrid(),
new Webpush(),
]; ];
for (let item of list) { for (let item of list) {
if (! item.name) { if (! item.name) {

View file

@ -96,6 +96,8 @@ const { getSettings, setSettings, setting, initJWTSecret, checkLogin, doubleChec
log.debug("server", "Importing Notification"); log.debug("server", "Importing Notification");
const { Notification } = require("./notification"); const { Notification } = require("./notification");
Notification.init(); Notification.init();
log.debug("server", "Importing Web-Push");
const webpush = require("web-push");
log.debug("server", "Importing Database"); log.debug("server", "Importing Database");
const Database = require("./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 = webpush.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) => { socket.on("clearEvents", async (monitorID, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);

View file

@ -165,7 +165,8 @@ export default {
"whapi": "WhatsApp (Whapi)", "whapi": "WhatsApp (Whapi)",
"gtxmessaging": "GtxMessaging", "gtxmessaging": "GtxMessaging",
"Cellsynt": "Cellsynt", "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 // Put notifications here if it's not supported in most regions or its documentation is not in English

View file

@ -0,0 +1,106 @@
<template>
<button
class="mb-3"
type="button"
:class="[
'btn',
canRegister ? 'btn-primary' : 'btn-danger'
]"
:disabled="!btnEnabled"
@click="registerWebpush"
>
<div v-if="processing" class="spinner-border spinner-border-sm me-1"></div>
<span v-else-if="$parent.notification.subscription" class="me-1"></span>
{{ btnText }}
</button>
<div class="mb-3 form-text">
<a href="TODO" target="_blank">{{ $t("documentationOf", ["Webpush"]) }}</a>
</div>
</template>
<script>
export default {
data() {
return {
//store subscription info
btnEnabled: false,
btnText: "",
processing: false,
//determines if browser supports service worker
canRegister: false,
//store public vapid key
publicVapidKey: null,
};
},
mounted() {
// if already subscribed
if (this.$parent.notification.subscription) {
this.btnEnabled = false;
this.canRegister = true;
this.btnText = "Notifications Enabled";
} else { //not subscribed
//check if browser supports service worker
if (("serviceWorker" in navigator)) {
this.btnText = "Allow Notifications";
this.canRegister = true;
this.btnEnabled = true;
} else { //browser does not support service worker
this.btnText = "Browser not supported";
this.canRegister = false;
this.btnEnabled = false;
}
}
},
methods: {
async registerWebpush() {
this.processing = true;
try {
// Get the VAPID public key from the server
const publicKey = await new Promise((resolve, reject) => {
this.$root.getSocket().emit("getWebpushVapidPublicKey", (resp) => {
if (!resp.ok) {
reject(new Error(resp.msg));
}
resolve(resp.msg);
});
});
//request permission to send notifications
const permission = await Notification.requestPermission();
if (permission !== "granted") {
this.$root.toastRes({
ok: false,
msg: "Unable to get permission to notify.",
});
this.processing = false;
return;
}
//get service worker registration
const registration = await navigator.serviceWorker.ready;
//subscribe to push notifications
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: publicKey,
});
//store subscription info and update button
this.$parent.notification.subscription = subscription;
this.btnEnabled = false;
this.canRegister = true;
this.btnText = "Notifications Enabled";
} catch (error) {
console.error("Subscription failed:", error);
this.$root.toastRes({
ok: false,
msg: error
});
} finally {
this.processing = false;
}
}
},
};
</script>

View file

@ -67,6 +67,7 @@ import Cellsynt from "./Cellsynt.vue";
import WPush from "./WPush.vue"; import WPush from "./WPush.vue";
import SIGNL4 from "./SIGNL4.vue"; import SIGNL4 from "./SIGNL4.vue";
import SendGrid from "./SendGrid.vue"; import SendGrid from "./SendGrid.vue";
import Webpush from "./Webpush.vue";
/** /**
* Manage all notification form. * Manage all notification form.
@ -142,6 +143,7 @@ const NotificationFormList = {
"Cellsynt": Cellsynt, "Cellsynt": Cellsynt,
"WPush": WPush, "WPush": WPush,
"SendGrid": SendGrid, "SendGrid": SendGrid,
"Webpush": Webpush,
}; };
export default NotificationFormList; export default NotificationFormList;

25
src/serviceWorker.ts Normal file
View file

@ -0,0 +1,25 @@
// 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 (event) {
if (!(
self.Notification &&
self.Notification.permission === 'granted'
)) {
//notifications aren't supported or permission not granted!
console.log("Notifications aren't supported or permission not granted!");
return;
}
if (event.data) {
console.log(event);
let message = event.data.json();
self.registration.showNotification(message.title, {
body: message.body,
});
}
});