From 5fe74ac9f9ee966388bbab9a829e8ffc3b76b0a5 Mon Sep 17 00:00:00 2001 From: xNewz Date: Mon, 29 Dec 2025 12:29:55 +0700 Subject: [PATCH 1/5] Add migration to drop legacy LINE Notify configs --- .../2025-12-29-0000-remove-line-notify.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 db/knex_migrations/2025-12-29-0000-remove-line-notify.js diff --git a/db/knex_migrations/2025-12-29-0000-remove-line-notify.js b/db/knex_migrations/2025-12-29-0000-remove-line-notify.js new file mode 100644 index 000000000..2011ce852 --- /dev/null +++ b/db/knex_migrations/2025-12-29-0000-remove-line-notify.js @@ -0,0 +1,30 @@ +exports.up = async function (knex) { + const notifications = await knex("notification").select("id", "config"); + const lineNotifyIDs = []; + + for (const { id, config } of notifications) { + try { + const parsedConfig = JSON.parse(config || "{}"); + const type = typeof parsedConfig.type === "string" ? parsedConfig.type.toLowerCase() : ""; + + if (type === "linenotify" || type === "line-notify") { + lineNotifyIDs.push(id); + } + } catch (error) { + // Ignore invalid JSON blobs here; they are handled elsewhere in the app. + } + } + + if (lineNotifyIDs.length === 0) { + return; + } + + await knex.transaction(async (trx) => { + await trx("monitor_notification").whereIn("notification_id", lineNotifyIDs).del(); + await trx("notification").whereIn("id", lineNotifyIDs).del(); + }); +}; + +exports.down = async function () { + // Removal of LINE Notify configs is not reversible. +}; From 76283148ba3696e739669d0cbd41a1e64d0e4078 Mon Sep 17 00:00:00 2001 From: xNewz Date: Tue, 30 Dec 2025 07:41:30 +0700 Subject: [PATCH 2/5] Remove LINE Notify provider --- server/notification-providers/linenotify.js | 53 --------------------- server/notification.js | 2 - src/components/NotificationDialog.vue | 1 - src/components/notifications/LineNotify.vue | 9 ---- src/components/notifications/index.js | 2 - 5 files changed, 67 deletions(-) delete mode 100644 server/notification-providers/linenotify.js delete mode 100644 src/components/notifications/LineNotify.vue diff --git a/server/notification-providers/linenotify.js b/server/notification-providers/linenotify.js deleted file mode 100644 index 30b2e800d..000000000 --- a/server/notification-providers/linenotify.js +++ /dev/null @@ -1,53 +0,0 @@ -const NotificationProvider = require("./notification-provider"); -const axios = require("axios"); -const qs = require("qs"); -const { DOWN, UP } = require("../../src/util"); - -class LineNotify extends NotificationProvider { - name = "LineNotify"; - - /** - * @inheritdoc - */ - async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { - const okMsg = "Sent Successfully."; - const url = "https://notify-api.line.me/api/notify"; - - try { - let config = { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - "Authorization": "Bearer " + notification.lineNotifyAccessToken - } - }; - config = this.getAxiosConfigWithProxy(config); - if (heartbeatJSON == null) { - let testMessage = { - "message": msg, - }; - await axios.post(url, qs.stringify(testMessage), config); - } else if (heartbeatJSON["status"] === DOWN) { - let downMessage = { - "message": "\n[🔴 Down]\n" + - "Name: " + monitorJSON["name"] + " \n" + - heartbeatJSON["msg"] + "\n" + - `Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}` - }; - await axios.post(url, qs.stringify(downMessage), config); - } else if (heartbeatJSON["status"] === UP) { - let upMessage = { - "message": "\n[✅ Up]\n" + - "Name: " + monitorJSON["name"] + " \n" + - heartbeatJSON["msg"] + "\n" + - `Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}` - }; - await axios.post(url, qs.stringify(upMessage), config); - } - return okMsg; - } catch (error) { - this.throwGeneralAxiosError(error); - } - } -} - -module.exports = LineNotify; diff --git a/server/notification.js b/server/notification.js index e25bca08b..555f10cc1 100644 --- a/server/notification.js +++ b/server/notification.js @@ -25,7 +25,6 @@ const HeiiOnCall = require("./notification-providers/heii-oncall"); const Keep = require("./notification-providers/keep"); const Kook = require("./notification-providers/kook"); const Line = require("./notification-providers/line"); -const LineNotify = require("./notification-providers/linenotify"); const LunaSea = require("./notification-providers/lunasea"); const Matrix = require("./notification-providers/matrix"); const Mattermost = require("./notification-providers/mattermost"); @@ -124,7 +123,6 @@ class Notification { new Keep(), new Kook(), new Line(), - new LineNotify(), new LunaSea(), new Matrix(), new Mattermost(), diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 98df20abb..9ed38e9cb 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -129,7 +129,6 @@ export default { "Keep": "Keep", "Kook": "Kook", "line": "LINE Messenger", - "LineNotify": "LINE Notify", "lunasea": "LunaSea", "matrix": "Matrix", "mattermost": "Mattermost", diff --git a/src/components/notifications/LineNotify.vue b/src/components/notifications/LineNotify.vue deleted file mode 100644 index 0f6897f46..000000000 --- a/src/components/notifications/LineNotify.vue +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index 9bc4f050d..b0fa68342 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -24,7 +24,6 @@ import HeiiOnCall from "./HeiiOnCall.vue"; import Keep from "./Keep.vue"; import Kook from "./Kook.vue"; import Line from "./Line.vue"; -import LineNotify from "./LineNotify.vue"; import LunaSea from "./LunaSea.vue"; import Matrix from "./Matrix.vue"; import Mattermost from "./Mattermost.vue"; @@ -112,7 +111,6 @@ const NotificationFormList = { "Keep": Keep, "Kook": Kook, "line": Line, - "LineNotify": LineNotify, "lunasea": LunaSea, "matrix": Matrix, "mattermost": Mattermost, From 7e745793127122615cfc5c8e1ddcdc17bea8a203 Mon Sep 17 00:00:00 2001 From: xNewz Date: Tue, 30 Dec 2025 07:52:49 +0700 Subject: [PATCH 3/5] Handle websocket errors with missing messages --- server/monitor-types/websocket-upgrade.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/server/monitor-types/websocket-upgrade.js b/server/monitor-types/websocket-upgrade.js index c53225306..a89bb21f0 100644 --- a/server/monitor-types/websocket-upgrade.js +++ b/server/monitor-types/websocket-upgrade.js @@ -36,12 +36,23 @@ class WebSocketMonitorType extends MonitorType { }); ws.onerror = (error) => { + const invalidAcceptCodes = [ "WS_ERR_INVALID_SEC_WEBSOCKET_ACCEPT_HEADER" ]; + let message = error?.message || ""; + + // Some ws versions may not populate the message even for invalid accept headers + if (!message && invalidAcceptCodes.includes(error?.code)) { + message = "Invalid Sec-WebSocket-Accept header"; + } else if (!message) { + message = "Unknown websocket error"; + } + // Give user the choice to ignore Sec-WebSocket-Accept header - if (monitor.wsIgnoreSecWebsocketAcceptHeader && error.message === "Invalid Sec-WebSocket-Accept header") { + if (monitor.wsIgnoreSecWebsocketAcceptHeader && (message === "Invalid Sec-WebSocket-Accept header" || invalidAcceptCodes.includes(error?.code))) { resolve([ "101 - OK", 1000 ]); + return; } // Upgrade failed, return message to user - resolve([ error.message, error.code ]); + resolve([ message, error?.code ]); }; ws.onclose = (event) => { From 026552519a2bd47df154969703fd5e9ce0202423 Mon Sep 17 00:00:00 2001 From: xNewz Date: Tue, 30 Dec 2025 07:58:56 +0700 Subject: [PATCH 4/5] Use random port for insecure websocket test --- test/backend-test/test-websocket.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/backend-test/test-websocket.js b/test/backend-test/test-websocket.js index 33660d134..d9f18e7f8 100644 --- a/test/backend-test/test-websocket.js +++ b/test/backend-test/test-websocket.js @@ -48,12 +48,16 @@ describe("Websocket Test", { }); test("Insecure Websocket", async (t) => { - t.after(() => wss.close()); const websocketMonitor = new WebSocketMonitorType(); - const wss = new WebSocketServer({ port: 8080 }); + const wss = new WebSocketServer({ port: 0 }); + t.after(() => wss.close()); + await new Promise((resolve) => wss.on("listening", resolve)); + + const address = wss.address(); + const port = typeof address === "object" && address ? address.port : 0; const monitor = { - url: "ws://localhost:8080", + url: `ws://localhost:${port}`, wsIgnoreSecWebsocketAcceptHeader: false, }; From 7ef7abe6be8fb87bc8dc4584a0ffe50d0b358292 Mon Sep 17 00:00:00 2001 From: xNewz Date: Tue, 30 Dec 2025 14:15:21 +0700 Subject: [PATCH 5/5] fix: remove accidental websocket changes --- server/monitor-types/websocket-upgrade.js | 14 ++------------ test/backend-test/test-websocket.js | 10 +++------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/server/monitor-types/websocket-upgrade.js b/server/monitor-types/websocket-upgrade.js index a89bb21f0..762e8df3b 100644 --- a/server/monitor-types/websocket-upgrade.js +++ b/server/monitor-types/websocket-upgrade.js @@ -36,23 +36,13 @@ class WebSocketMonitorType extends MonitorType { }); ws.onerror = (error) => { - const invalidAcceptCodes = [ "WS_ERR_INVALID_SEC_WEBSOCKET_ACCEPT_HEADER" ]; - let message = error?.message || ""; - - // Some ws versions may not populate the message even for invalid accept headers - if (!message && invalidAcceptCodes.includes(error?.code)) { - message = "Invalid Sec-WebSocket-Accept header"; - } else if (!message) { - message = "Unknown websocket error"; - } - // Give user the choice to ignore Sec-WebSocket-Accept header - if (monitor.wsIgnoreSecWebsocketAcceptHeader && (message === "Invalid Sec-WebSocket-Accept header" || invalidAcceptCodes.includes(error?.code))) { + if (monitor.wsIgnoreSecWebsocketAcceptHeader && error.message === "Invalid Sec-WebSocket-Accept header") { resolve([ "101 - OK", 1000 ]); return; } // Upgrade failed, return message to user - resolve([ message, error?.code ]); + resolve([ error.message, error.code ]); }; ws.onclose = (event) => { diff --git a/test/backend-test/test-websocket.js b/test/backend-test/test-websocket.js index d9f18e7f8..33660d134 100644 --- a/test/backend-test/test-websocket.js +++ b/test/backend-test/test-websocket.js @@ -48,16 +48,12 @@ describe("Websocket Test", { }); test("Insecure Websocket", async (t) => { - const websocketMonitor = new WebSocketMonitorType(); - const wss = new WebSocketServer({ port: 0 }); t.after(() => wss.close()); - await new Promise((resolve) => wss.on("listening", resolve)); - - const address = wss.address(); - const port = typeof address === "object" && address ? address.port : 0; + const websocketMonitor = new WebSocketMonitorType(); + const wss = new WebSocketServer({ port: 8080 }); const monitor = { - url: `ws://localhost:${port}`, + url: "ws://localhost:8080", wsIgnoreSecWebsocketAcceptHeader: false, };