-
Notifications
You must be signed in to change notification settings - Fork 226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Discord webhook notification support #1612
Changes from 41 commits
28f5d35
172597e
d3810db
d83773a
84fe58b
92d9573
cc48f1d
8667f74
0bb7eae
d3fb41a
8f94062
1d9f94c
e44cbf3
aa977ce
964a923
e9bcf66
64c5aa9
01e73fe
d8379f7
f46b076
ccd6c58
ef41712
fa50706
67da574
023d943
012483d
8007daa
70ffe1b
12d9f15
07882e0
8409571
55c1a1c
ff2b3b4
6152679
e4e3f9c
f22b67a
d710ec0
c4168a5
afc9d46
03a938d
5be17af
4ef2b29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,59 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
triggerNotificationBodyValidation, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} from '../validation/joi.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { handleError, handleValidationError } from './controllerUtils.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const SERVICE_NAME = "NotificationController"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class NotificationController { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
constructor(notificationService, stringService) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.notificationService = notificationService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.stringService = stringService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.triggerNotification = this.triggerNotification.bind(this); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async triggerNotification(req, res, next) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await triggerNotificationBodyValidation.validateAsync(req.body, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
abortEarly: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
stripUnknown: true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
next(handleValidationError(error, SERVICE_NAME)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const { monitorId, type, platform, config } = req.body; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const networkResponse = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
monitor: { _id: monitorId, name: "Test Monitor", url: "http://www.google.com" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see the name and url in networkResponse object are hardcoded. Was this just for testing? |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
status: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
statusChanged: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
prevStatus: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (type === "webhook") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const notification = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
platform, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
config | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await this.notificationService.sendWebhookNotification( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
networkResponse, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
notification | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+36
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Drop the beat with platform-specific validation! 🎤 You've imported those validation schemas like they're backup dancers, but they're not getting any stage time! Let's make them perform! if (type === "webhook") {
+ let validationSchema;
+ switch (platform) {
+ case 'discord':
+ validationSchema = discordWebhookConfigValidation;
+ break;
+ case 'telegram':
+ validationSchema = telegramWebhookConfigValidation;
+ break;
+ case 'slack':
+ validationSchema = slackWebhookConfigValidation;
+ break;
+ default:
+ throw new Error(`Unsupported platform: ${platform}`);
+ }
+
+ await validationSchema.validateAsync(config, {
+ abortEarly: false,
+ stripUnknown: true
+ });
const notification = { 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return res.success({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
msg: "Notification sent successfully" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to use the string service here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
next(handleError(error, SERVICE_NAME, "triggerNotification")); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export default NotificationController; |
Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -38,6 +38,10 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import DistributedUptimeRoutes from "./routes/distributedUptimeRoute.js"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import DistributedUptimeController from "./controllers/distributedUptimeController.js"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import NotificationRoutes from "./routes/notificationRoute.js"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import NotificationController from "./controllers/notificationController.js"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
//JobQueue service and dependencies | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import JobQueue from "./service/jobQueue.js"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Queue, Worker } from "bullmq"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -166,7 +170,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const statusService = new StatusService(db, logger); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const notificationService = new NotificationService(emailService, db, logger); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const notificationService = new NotificationService(emailService, db, logger, networkService, stringService); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const jobQueue = new JobQueue( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
db, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -251,6 +255,11 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ServiceRegistry.get(StringService.SERVICE_NAME) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const notificationController = new NotificationController( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ServiceRegistry.get(NotificationService.SERVICE_NAME), | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ServiceRegistry.get(StringService.SERVICE_NAME) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const distributedUptimeController = new DistributedUptimeController( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ServiceRegistry.get(MongoDB.SERVICE_NAME), | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
http, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -271,6 +280,9 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const distributedUptimeRoutes = new DistributedUptimeRoutes( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
distributedUptimeController | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const notificationRoutes = new NotificationRoutes(notificationController); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Init job queue | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await jobQueue.initJobQueue(); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Middleware | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -293,6 +305,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter()); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
app.use("/api/v1/distributed-uptime", distributedUptimeRoutes.getRouter()); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
app.use("/api/v1/status-page", statusPageRoutes.getRouter()); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failure Code scanning / CodeQL Missing rate limiting High
This route handler performs
authorization Error loading related location Loading
Copilot Autofix AI 4 days ago To fix the problem, we need to introduce rate limiting to the Express application. The best way to do this is by using the
Suggested changeset
2
Server/index.js
Server/package.json
Outside changed files
This fix introduces these dependencies
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add rate limiting to protect against abuse. The notification route is properly secured with JWT verification, but it lacks rate limiting which could make it vulnerable to abuse. Apply this diff to add rate limiting: -app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
+app.use("/api/v1/notifications", verifyJWT, rateLimiter, notificationRoutes.getRouter()); And add the rate limiter middleware at the top of the file: import rateLimit from 'express-rate-limit';
const rateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
}); 🧰 Tools🪛 GitHub Check: CodeQL[failure] 308-308: Missing rate limiting |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
app.use(handleErrors); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,24 @@ | ||||||
import express from 'express'; | ||||||
import { verifyJWT } from '../middleware/verifyJWT.js'; | ||||||
|
||||||
class NotificationRoutes { | ||||||
constructor(notificationController) { | ||||||
this.notificationController = notificationController; | ||||||
this.router = express.Router(); | ||||||
this.initializeRoutes(); | ||||||
} | ||||||
|
||||||
initializeRoutes() { | ||||||
this.router.post( | ||||||
'/trigger', | ||||||
verifyJWT, | ||||||
|
||||||
this.notificationController.triggerNotification | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lose yourself in the proper binding! 🎵 The controller method needs to be bound to maintain its context, just like you need to bind yourself to the beat! - this.notificationController.triggerNotification
+ this.notificationController.triggerNotification.bind(this.notificationController) 📝 Committable suggestion
Suggested change
|
||||||
); | ||||||
} | ||||||
|
||||||
getRouter() { | ||||||
return this.router; | ||||||
} | ||||||
} | ||||||
|
||||||
export default NotificationRoutes; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -433,6 +433,46 @@ class NetworkService { | |
throw err; | ||
} | ||
|
||
async requestWebhook(platform, url, message) { | ||
try { | ||
const response = await this.axios.post(url, message, { | ||
headers: { | ||
'Content-Type': 'application/json' | ||
} | ||
}); | ||
|
||
return { | ||
type: 'webhook', | ||
status: true, | ||
code: response.status, | ||
message: `Successfully sent ${platform} notification`, | ||
payload: response.data | ||
}; | ||
|
||
} catch (error) { | ||
this.logger.warn({ | ||
message: error.message, | ||
service: this.SERVICE_NAME, | ||
method: 'requestWebhook', | ||
url, | ||
platform, | ||
error: error.message, | ||
statusCode: error.response?.status, | ||
responseData: error.response?.data, | ||
requestPayload: message | ||
}); | ||
|
||
return { | ||
type: 'webhook', | ||
status: false, | ||
code: error.response?.status || this.NETWORK_ERROR, | ||
message: `Failed to send ${platform} notification`, | ||
payload: error.response?.data | ||
}; | ||
} | ||
} | ||
|
||
|
||
Comment on lines
+436
to
+475
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. error handling and logging looks good. |
||
/** | ||
* Gets the status of a job based on its type and returns the appropriate response. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,12 @@ | ||
const SERVICE_NAME = "NotificationService"; | ||
const TELEGRAM_API_BASE_URL = "https://api.telegram.org/bot"; | ||
const PLATFORM_TYPES = ['telegram', 'slack', 'discord']; | ||
|
||
const MESSAGE_FORMATTERS = { | ||
telegram: (messageText, chatId) => ({ chat_id: chatId, text: messageText }), | ||
slack: (messageText) => ({ text: messageText }), | ||
discord: (messageText) => ({ content: messageText }) | ||
}; | ||
|
||
class NotificationService { | ||
static SERVICE_NAME = SERVICE_NAME; | ||
|
@@ -8,14 +16,111 @@ class NotificationService { | |
* @param {Object} emailService - The email service used for sending notifications. | ||
* @param {Object} db - The database instance for storing notification data. | ||
* @param {Object} logger - The logger instance for logging activities. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Be sure to update the JSdoc since this now takes a |
||
* @param {Object} networkService - The network service for sending webhook notifications. | ||
*/ | ||
constructor(emailService, db, logger) { | ||
constructor(emailService, db, logger, networkService, stringService) { | ||
this.SERVICE_NAME = SERVICE_NAME; | ||
this.emailService = emailService; | ||
this.db = db; | ||
this.logger = logger; | ||
this.networkService = networkService; | ||
this.stringService = stringService; | ||
} | ||
|
||
/** | ||
* Formats a notification message based on the monitor status and platform. | ||
* | ||
* @param {Object} monitor - The monitor object. | ||
* @param {string} monitor.name - The name of the monitor. | ||
* @param {string} monitor.url - The URL of the monitor. | ||
* @param {boolean} status - The current status of the monitor (true for up, false for down). | ||
* @param {string} platform - The notification platform (e.g., "telegram", "slack", "discord"). | ||
* @param {string} [chatId] - The chat ID for platforms that require it (e.g., Telegram). | ||
* @returns {Object|null} The formatted message object for the specified platform, or null if the platform is unsupported. | ||
*/ | ||
|
||
formatNotificationMessage(monitor, status, platform, chatId) { | ||
const messageText = this.stringService.getMonitorStatus( | ||
monitor.name, | ||
status, | ||
monitor.url | ||
); | ||
|
||
if (!PLATFORM_TYPES.includes(platform)) { | ||
return undefined; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this if/else ladder meant to fall through at the end? Should we continue execution if the platform is not one of slack, discord, or telegram? Or are we meant to return early? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, sorry, you are right. It should not continue execution. I wrote an else to return early. |
||
|
||
return MESSAGE_FORMATTERS[platform](messageText, chatId); | ||
} | ||
|
||
/** | ||
* Sends a webhook notification to a specified platform. | ||
* | ||
* @param {Object} networkResponse - The response object from the network. | ||
* @param {Object} networkResponse.monitor - The monitor object. | ||
* @param {boolean} networkResponse.status - The monitor's status (true for up, false for down). | ||
* @param {Object} notification - The notification settings. | ||
* @param {string} notification.platform - The target platform ("telegram", "slack", "discord"). | ||
* @param {Object} notification.config - The configuration object for the webhook. | ||
* @param {string} notification.config.webhookUrl - The webhook URL for the platform. | ||
* @param {string} [notification.config.botToken] - The bot token for Telegram notifications. | ||
* @param {string} [notification.config.chatId] - The chat ID for Telegram notifications. | ||
* @returns {Promise<boolean>} A promise that resolves to true if the notification was sent successfully, otherwise false. | ||
*/ | ||
|
||
async sendWebhookNotification(networkResponse, notification) { | ||
const { monitor, status } = networkResponse; | ||
const { platform } = notification; | ||
const { webhookUrl, botToken, chatId } = notification.config; | ||
|
||
// Early return if platform is not supported | ||
if (!PLATFORM_TYPES.includes(platform)) { | ||
this.logger.warn({ | ||
message: this.stringService.getWebhookUnsupportedPlatform(platform), | ||
service: this.SERVICE_NAME, | ||
method: 'sendWebhookNotification', | ||
platform | ||
}); | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this error meant to be swallowed and operations meant to continue in event of error? If not it should have method specifics added to it and rethrown to be caught by middleware There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! |
||
} | ||
|
||
// Early return for telegram if required fields are missing | ||
if (platform === 'telegram' && (!botToken || !chatId)) { | ||
this.logger.warn({ | ||
message: 'Missing required fields for Telegram notification', | ||
service: this.SERVICE_NAME, | ||
method: 'sendWebhookNotification', | ||
platform | ||
}); | ||
return false; | ||
} | ||
|
||
let url = webhookUrl; | ||
if (platform === 'telegram') { | ||
url = `${TELEGRAM_API_BASE_URL}${botToken}/sendMessage`; | ||
} | ||
|
||
// Now that we know the platform is valid, format the message | ||
const message = this.formatNotificationMessage(monitor, status, platform, chatId); | ||
|
||
try { | ||
const response = await this.networkService.requestWebhook(platform, url, message); | ||
return response.status; | ||
} catch (error) { | ||
this.logger.error({ | ||
message: this.stringService.getWebhookSendError(platform), | ||
service: this.SERVICE_NAME, | ||
method: 'sendWebhookNotification', | ||
error: error.message, | ||
stack: error.stack, | ||
url, | ||
platform, | ||
requestPayload: message | ||
}); | ||
return false; | ||
} | ||
} | ||
|
||
/** | ||
* Sends an email notification for hardware infrastructure alerts | ||
* | ||
|
@@ -57,18 +162,18 @@ class NotificationService { | |
|
||
async handleStatusNotifications(networkResponse) { | ||
try { | ||
//If status hasn't changed, we're done | ||
// If status hasn't changed, we're done | ||
if (networkResponse.statusChanged === false) return false; | ||
|
||
// if prevStatus is undefined, monitor is resuming, we're done | ||
ajhollid marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (networkResponse.prevStatus === undefined) return false; | ||
const notifications = await this.db.getNotificationsByMonitorId( | ||
networkResponse.monitorId | ||
); | ||
|
||
|
||
const notifications = await this.db.getNotificationsByMonitorId(networkResponse.monitorId); | ||
|
||
for (const notification of notifications) { | ||
if (notification.type === "email") { | ||
this.sendEmail(networkResponse, notification.address); | ||
await this.sendEmail(networkResponse, notification.address); | ||
} else if (notification.type === "webhook") { | ||
await this.sendWebhookNotification(networkResponse, notification); | ||
} | ||
// Handle other types of notifications here | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yo, where's the platform validation squad at? 🎤
We need to import those platform-specific validation schemas to keep our webhooks tight and secure!
📝 Committable suggestion