From 0bb33200104137123c6d658a9f249456bbd36b6b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 13 Apr 2026 12:54:51 -0700 Subject: [PATCH 1/3] add abstract class with default timeout and helper method --- .../INotificationProvider.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts b/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts index 2743dfab9..b5c886408 100644 --- a/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts +++ b/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts @@ -1,7 +1,30 @@ import type { Notification } from "@/types/index.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; +import { ILogger } from "@/utils/logger.js"; +import type { OptionsOfJSONResponseBody } from "got"; +export const DEFAULT_NOTIFICATION_TIMEOUT_MS = 30_000; export interface INotificationProvider { sendMessage: (notification: Notification, message: NotificationMessage) => Promise; sendTestAlert(notification: Partial): Promise; } + +export abstract class NotificationProvider implements INotificationProvider { + protected readonly logger: ILogger; + protected readonly timeoutMs: number; + + constructor(logger: ILogger, timeoutMs: number = DEFAULT_NOTIFICATION_TIMEOUT_MS) { + this.logger = logger; + this.timeoutMs = timeoutMs; + } + + protected gotRequestOptions(): Pick { + return { + timeout: { request: this.timeoutMs }, + retry: { limit: 0 }, + }; + } + + abstract sendMessage(notification: Notification, message: NotificationMessage): Promise; + abstract sendTestAlert(notification: Partial): Promise; +} From 22f54391d03a38cee58e931ca2891c6b404e2646 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 13 Apr 2026 12:55:20 -0700 Subject: [PATCH 2/3] use abstract class --- .../notificationProviders/discord.ts | 13 ++++--------- .../infrastructure/notificationProviders/email.ts | 7 +++---- .../infrastructure/notificationProviders/matrix.ts | 12 +++--------- .../notificationProviders/pagerduty.ts | 13 ++++--------- .../infrastructure/notificationProviders/slack.ts | 13 ++++--------- .../infrastructure/notificationProviders/teams.ts | 13 ++++--------- .../notificationProviders/telegram.ts | 14 +++++--------- .../notificationProviders/webhook.ts | 13 ++++--------- 8 files changed, 31 insertions(+), 67 deletions(-) diff --git a/server/src/service/infrastructure/notificationProviders/discord.ts b/server/src/service/infrastructure/notificationProviders/discord.ts index 3bd2556ae..f498a629b 100644 --- a/server/src/service/infrastructure/notificationProviders/discord.ts +++ b/server/src/service/infrastructure/notificationProviders/discord.ts @@ -1,18 +1,11 @@ const SERVICE_NAME = "DiscordProvider"; import type { AlertDiscordPayload, DiscordEmbedField, Notification } from "@/types/index.js"; -import { INotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/index.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; import type { NotificationMessage, NotificationSeverity } from "@/types/notificationMessage.js"; import got from "got"; -import { ILogger } from "@/utils/logger.js"; - -export class DiscordProvider implements INotificationProvider { - private logger: ILogger; - - constructor(logger: ILogger) { - this.logger = logger; - } +export class DiscordProvider extends NotificationProvider { sendTestAlert = async (notification: Partial) => { if (!notification.address) { return false; @@ -23,6 +16,7 @@ export class DiscordProvider implements INotificationProvider { headers: { "Content-Type": "application/json", }, + ...this.gotRequestOptions(), }); return true; } catch (error) { @@ -55,6 +49,7 @@ export class DiscordProvider implements INotificationProvider { headers: { "Content-Type": "application/json", }, + ...this.gotRequestOptions(), }); return true; } catch (error) { diff --git a/server/src/service/infrastructure/notificationProviders/email.ts b/server/src/service/infrastructure/notificationProviders/email.ts index b3686651c..d3949c4db 100644 --- a/server/src/service/infrastructure/notificationProviders/email.ts +++ b/server/src/service/infrastructure/notificationProviders/email.ts @@ -1,17 +1,16 @@ const SERVICE_NAME = "EmailProvider"; import type { Notification } from "@/types/index.js"; -import { INotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/index.js"; import { buildTestEmail } from "@/service/infrastructure/notificationProviders/utils.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import type { ILogger } from "@/utils/logger.js"; import { IEmailService } from "@/service/infrastructure/emailService.js"; -export class EmailProvider implements INotificationProvider { +export class EmailProvider extends NotificationProvider { private emailService: IEmailService; - private logger: ILogger; constructor(emailService: IEmailService, logger: ILogger) { + super(logger); this.emailService = emailService; - this.logger = logger; } async sendTestAlert(notification: Partial): Promise { diff --git a/server/src/service/infrastructure/notificationProviders/matrix.ts b/server/src/service/infrastructure/notificationProviders/matrix.ts index 9d92e1f3c..3ba552477 100644 --- a/server/src/service/infrastructure/notificationProviders/matrix.ts +++ b/server/src/service/infrastructure/notificationProviders/matrix.ts @@ -1,19 +1,12 @@ const SERVICE_NAME = "MatrixProvider"; import got from "got"; -import type { INotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/index.js"; import type { AlertMatrixPayload, Notification } from "@/types/index.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; -import { ILogger } from "@/utils/logger.js"; import { randomUUID } from "crypto"; -export class MatrixProvider implements INotificationProvider { - private logger: ILogger; - - constructor(logger: ILogger) { - this.logger = logger; - } - +export class MatrixProvider extends NotificationProvider { sendTestAlert = async (notification: Partial) => { const { homeserverUrl, accessToken, roomId } = notification; if (!homeserverUrl || !accessToken || !roomId) { @@ -55,6 +48,7 @@ export class MatrixProvider implements INotificationProvider { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, + ...this.gotRequestOptions(), }); return true; } catch (error) { diff --git a/server/src/service/infrastructure/notificationProviders/pagerduty.ts b/server/src/service/infrastructure/notificationProviders/pagerduty.ts index 304c38cf4..2a07869ad 100644 --- a/server/src/service/infrastructure/notificationProviders/pagerduty.ts +++ b/server/src/service/infrastructure/notificationProviders/pagerduty.ts @@ -1,19 +1,12 @@ const SERVICE_NAME = "PagerDutyProvider"; import got from "got"; import type { Notification } from "@/types/index.js"; -import { INotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/index.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; -import { ILogger } from "@/utils/logger.js"; import { AlertPagerDutyPayload } from "@/types/index.js"; -export class PagerDutyProvider implements INotificationProvider { - private logger: ILogger; - - constructor(logger: ILogger) { - this.logger = logger; - } - +export class PagerDutyProvider extends NotificationProvider { async sendTestAlert(notification: Partial): Promise { try { await got.post("https://events.pagerduty.com/v2/enqueue", { @@ -28,6 +21,7 @@ export class PagerDutyProvider implements INotificationProvider { }, }, responseType: "json", + ...this.gotRequestOptions(), }); return true; } catch (error) { @@ -59,6 +53,7 @@ export class PagerDutyProvider implements INotificationProvider { const payload = this.buildPagerDutyPayload(notification, message); await got.post("https://events.pagerduty.com/v2/enqueue", { json: payload, + ...this.gotRequestOptions(), }); this.logger.info({ message: "[NEW] PagerDuty notification sent via sendMessage", diff --git a/server/src/service/infrastructure/notificationProviders/slack.ts b/server/src/service/infrastructure/notificationProviders/slack.ts index cfb135901..602347fb8 100644 --- a/server/src/service/infrastructure/notificationProviders/slack.ts +++ b/server/src/service/infrastructure/notificationProviders/slack.ts @@ -1,18 +1,11 @@ const SERVICE_NAME = "SlackProvider"; import type { Notification } from "@/types/index.js"; -import { INotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/index.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; import got, { HTTPError } from "got"; -import { ILogger } from "@/utils/logger.js"; - -export class SlackProvider implements INotificationProvider { - private logger: ILogger; - - constructor(logger: ILogger) { - this.logger = logger; - } +export class SlackProvider extends NotificationProvider { async sendTestAlert(notification: Partial): Promise { if (!notification.address) { return false; @@ -24,6 +17,7 @@ export class SlackProvider implements INotificationProvider { headers: { "Content-Type": "application/json", }, + ...this.gotRequestOptions(), }); return true; } catch (error) { @@ -54,6 +48,7 @@ export class SlackProvider implements INotificationProvider { headers: { "Content-Type": "application/json", }, + ...this.gotRequestOptions(), }); this.logger.info({ message: "[NEW] Slack notification sent via sendMessage", diff --git a/server/src/service/infrastructure/notificationProviders/teams.ts b/server/src/service/infrastructure/notificationProviders/teams.ts index dcf48c4cd..b04cdead4 100644 --- a/server/src/service/infrastructure/notificationProviders/teams.ts +++ b/server/src/service/infrastructure/notificationProviders/teams.ts @@ -1,9 +1,8 @@ const SERVICE_NAME = "TeamsProvider"; import type { Notification } from "@/types/index.js"; -import { INotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/index.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; -import type { ILogger } from "@/utils/logger.js"; import got, { HTTPError } from "got"; // Types for Adaptive Card elements @@ -58,13 +57,7 @@ type TeamsMessage = { }>; }; -export class TeamsProvider implements INotificationProvider { - private logger: ILogger; - - constructor(logger: ILogger) { - this.logger = logger; - } - +export class TeamsProvider extends NotificationProvider { async sendTestAlert(notification: Partial): Promise { if (!notification.address) { return false; @@ -90,6 +83,7 @@ export class TeamsProvider implements INotificationProvider { headers: { "Content-Type": "application/json", }, + ...this.gotRequestOptions(), }); return true; } catch (error) { @@ -117,6 +111,7 @@ export class TeamsProvider implements INotificationProvider { headers: { "Content-Type": "application/json", }, + ...this.gotRequestOptions(), }); this.logger.info({ message: "Teams notification sent via sendMessage", diff --git a/server/src/service/infrastructure/notificationProviders/telegram.ts b/server/src/service/infrastructure/notificationProviders/telegram.ts index fcb7bdacc..5f2537e9d 100644 --- a/server/src/service/infrastructure/notificationProviders/telegram.ts +++ b/server/src/service/infrastructure/notificationProviders/telegram.ts @@ -1,18 +1,11 @@ const SERVICE_NAME = "TelegramProvider"; import type { Notification } from "@/types/index.js"; -import { INotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/index.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; import got from "got"; -import { ILogger } from "@/utils/logger.js"; - -export class TelegramProvider implements INotificationProvider { - private logger: ILogger; - - constructor(logger: ILogger) { - this.logger = logger; - } +export class TelegramProvider extends NotificationProvider { async sendTestAlert(notification: Partial): Promise { if (!notification.address || !notification.accessToken) { return false; @@ -25,6 +18,7 @@ export class TelegramProvider implements INotificationProvider { text: getTestMessage(), parse_mode: "HTML", }, + ...this.gotRequestOptions(), }); return true; } catch (error) { @@ -55,7 +49,9 @@ export class TelegramProvider implements INotificationProvider { text, parse_mode: "HTML", }, + ...this.gotRequestOptions(), }); + this.logger.info({ message: "Telegram notification sent", service: SERVICE_NAME, diff --git a/server/src/service/infrastructure/notificationProviders/webhook.ts b/server/src/service/infrastructure/notificationProviders/webhook.ts index fcb5728d4..581be3995 100644 --- a/server/src/service/infrastructure/notificationProviders/webhook.ts +++ b/server/src/service/infrastructure/notificationProviders/webhook.ts @@ -1,18 +1,11 @@ const SERVICE_NAME = "WebhookProvider"; import type { Notification } from "@/types/index.js"; -import { INotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/index.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; import got from "got"; -import { ILogger } from "@/utils/logger.js"; - -export class WebhookProvider implements INotificationProvider { - private logger: ILogger; - - constructor(logger: ILogger) { - this.logger = logger; - } +export class WebhookProvider extends NotificationProvider { sendMessage = async (notification: Notification, message: NotificationMessage): Promise => { if (!notification.address) { return false; @@ -27,6 +20,7 @@ export class WebhookProvider implements INotificationProvider { headers: { "Content-Type": "application/json", }, + ...this.gotRequestOptions(), }); this.logger.info({ message: "Webhook notification sent", @@ -108,6 +102,7 @@ export class WebhookProvider implements INotificationProvider { headers: { "Content-Type": "application/json", }, + ...this.gotRequestOptions(), }); return true; } catch (error) { From 48c1e326c87bd73b2b3dd0e3de5e39938c80869e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 13 Apr 2026 12:59:49 -0700 Subject: [PATCH 3/3] use relative paths for testing --- .../src/service/infrastructure/notificationProviders/discord.ts | 2 +- .../src/service/infrastructure/notificationProviders/email.ts | 2 +- .../src/service/infrastructure/notificationProviders/matrix.ts | 2 +- .../service/infrastructure/notificationProviders/pagerduty.ts | 2 +- .../src/service/infrastructure/notificationProviders/slack.ts | 2 +- .../src/service/infrastructure/notificationProviders/teams.ts | 2 +- .../service/infrastructure/notificationProviders/telegram.ts | 2 +- .../src/service/infrastructure/notificationProviders/webhook.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/server/src/service/infrastructure/notificationProviders/discord.ts b/server/src/service/infrastructure/notificationProviders/discord.ts index f498a629b..942e0b578 100644 --- a/server/src/service/infrastructure/notificationProviders/discord.ts +++ b/server/src/service/infrastructure/notificationProviders/discord.ts @@ -1,6 +1,6 @@ const SERVICE_NAME = "DiscordProvider"; import type { AlertDiscordPayload, DiscordEmbedField, Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; import type { NotificationMessage, NotificationSeverity } from "@/types/notificationMessage.js"; import got from "got"; diff --git a/server/src/service/infrastructure/notificationProviders/email.ts b/server/src/service/infrastructure/notificationProviders/email.ts index d3949c4db..0890eac7f 100644 --- a/server/src/service/infrastructure/notificationProviders/email.ts +++ b/server/src/service/infrastructure/notificationProviders/email.ts @@ -1,6 +1,6 @@ const SERVICE_NAME = "EmailProvider"; import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; import { buildTestEmail } from "@/service/infrastructure/notificationProviders/utils.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import type { ILogger } from "@/utils/logger.js"; diff --git a/server/src/service/infrastructure/notificationProviders/matrix.ts b/server/src/service/infrastructure/notificationProviders/matrix.ts index 3ba552477..f8a093ab7 100644 --- a/server/src/service/infrastructure/notificationProviders/matrix.ts +++ b/server/src/service/infrastructure/notificationProviders/matrix.ts @@ -1,6 +1,6 @@ const SERVICE_NAME = "MatrixProvider"; import got from "got"; -import { NotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; import type { AlertMatrixPayload, Notification } from "@/types/index.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; diff --git a/server/src/service/infrastructure/notificationProviders/pagerduty.ts b/server/src/service/infrastructure/notificationProviders/pagerduty.ts index 2a07869ad..0d2fa75be 100644 --- a/server/src/service/infrastructure/notificationProviders/pagerduty.ts +++ b/server/src/service/infrastructure/notificationProviders/pagerduty.ts @@ -1,7 +1,7 @@ const SERVICE_NAME = "PagerDutyProvider"; import got from "got"; import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; import { AlertPagerDutyPayload } from "@/types/index.js"; diff --git a/server/src/service/infrastructure/notificationProviders/slack.ts b/server/src/service/infrastructure/notificationProviders/slack.ts index 602347fb8..1ab046f7f 100644 --- a/server/src/service/infrastructure/notificationProviders/slack.ts +++ b/server/src/service/infrastructure/notificationProviders/slack.ts @@ -1,6 +1,6 @@ const SERVICE_NAME = "SlackProvider"; import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; import got, { HTTPError } from "got"; diff --git a/server/src/service/infrastructure/notificationProviders/teams.ts b/server/src/service/infrastructure/notificationProviders/teams.ts index b04cdead4..f98724295 100644 --- a/server/src/service/infrastructure/notificationProviders/teams.ts +++ b/server/src/service/infrastructure/notificationProviders/teams.ts @@ -1,6 +1,6 @@ const SERVICE_NAME = "TeamsProvider"; import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; import got, { HTTPError } from "got"; diff --git a/server/src/service/infrastructure/notificationProviders/telegram.ts b/server/src/service/infrastructure/notificationProviders/telegram.ts index 5f2537e9d..9d6968fdd 100644 --- a/server/src/service/infrastructure/notificationProviders/telegram.ts +++ b/server/src/service/infrastructure/notificationProviders/telegram.ts @@ -1,6 +1,6 @@ const SERVICE_NAME = "TelegramProvider"; import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; import got from "got"; diff --git a/server/src/service/infrastructure/notificationProviders/webhook.ts b/server/src/service/infrastructure/notificationProviders/webhook.ts index 581be3995..292650f91 100644 --- a/server/src/service/infrastructure/notificationProviders/webhook.ts +++ b/server/src/service/infrastructure/notificationProviders/webhook.ts @@ -1,6 +1,6 @@ const SERVICE_NAME = "WebhookProvider"; import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/index.js"; +import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; import got from "got";