update
This commit is contained in:
@@ -1,101 +1,110 @@
|
|||||||
import nodemailer from 'nodemailer';
|
import nodemailer from "nodemailer";
|
||||||
import Mail from 'nodemailer/lib/mailer'; // Import Mail type for transporter
|
import Mail from "nodemailer/lib/mailer";
|
||||||
import SMTPTransport from 'nodemailer/lib/smtp-transport'; // Import SMTPTransport for options type
|
import SMTPTransport from "nodemailer/lib/smtp-transport";
|
||||||
import { INotificationSender } from '../notification.dispatcher.service';
|
import { INotificationSender } from "../notification.dispatcher.service";
|
||||||
import { ProcessedNotification } from '../notification.processor.service';
|
import { ProcessedNotification } from "../notification.processor.service";
|
||||||
import { EmailConfig } from '../../types/notification.types';
|
import { EmailConfig } from "../../types/notification.types";
|
||||||
import { settingsService } from '../settings.service'; // Import settingsService
|
import { settingsService } from "../settings.service";
|
||||||
|
|
||||||
class EmailSenderService implements INotificationSender {
|
class EmailSenderService implements INotificationSender {
|
||||||
|
|
||||||
async send(notification: ProcessedNotification): Promise<void> {
|
async send(notification: ProcessedNotification): Promise<void> {
|
||||||
const config = notification.config as EmailConfig;
|
const config = notification.config as EmailConfig;
|
||||||
const { to, subjectTemplate, smtpHost, smtpPort, smtpSecure, smtpUser, smtpPass, from } = config;
|
const { to, smtpHost, smtpPort, smtpSecure, smtpUser, smtpPass, from } =
|
||||||
const subject = notification.subject || 'Notification'; // Use processed subject or default
|
config;
|
||||||
const body = notification.body; // Use processed body (assuming HTML)
|
const subject = notification.subject || "Notification";
|
||||||
|
const body = notification.body;
|
||||||
|
|
||||||
if (!to) {
|
if (!to) {
|
||||||
console.error('[EmailSender] Missing recipient address (to) in configuration.');
|
console.error(
|
||||||
throw new Error('Email configuration is incomplete (missing recipient address).');
|
"[EmailSender] Missing recipient address (to) in configuration."
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
"Email configuration is incomplete (missing recipient address)."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get global settings for fallback SMTP configuration using settingsService
|
const globalSmtpHost = await settingsService.getSetting("smtpHost");
|
||||||
const globalSmtpHost = await settingsService.getSetting('smtpHost');
|
const globalSmtpPortStr = await settingsService.getSetting("smtpPort");
|
||||||
const globalSmtpPortStr = await settingsService.getSetting('smtpPort');
|
const globalSmtpSecureStr = await settingsService.getSetting(
|
||||||
const globalSmtpSecureStr = await settingsService.getSetting('smtpSecure');
|
"smtpSecure"
|
||||||
const globalSmtpUser = await settingsService.getSetting('smtpUser');
|
);
|
||||||
const globalSmtpPass = await settingsService.getSetting('smtpPass');
|
const globalSmtpUser = await settingsService.getSetting("smtpUser");
|
||||||
const globalSmtpFrom = await settingsService.getSetting('smtpFrom');
|
const globalSmtpPass = await settingsService.getSetting("smtpPass");
|
||||||
|
const globalSmtpFrom = await settingsService.getSetting("smtpFrom");
|
||||||
|
|
||||||
// Determine SMTP settings: prioritize channel-specific, then global, then defaults
|
|
||||||
const finalSmtpHost = smtpHost || globalSmtpHost;
|
const finalSmtpHost = smtpHost || globalSmtpHost;
|
||||||
const finalSmtpPort = smtpPort ?? (globalSmtpPortStr ? parseInt(globalSmtpPortStr, 10) : 587); // Default port 587
|
const finalSmtpPort =
|
||||||
const finalSmtpSecure = smtpSecure ?? (globalSmtpSecureStr === 'true') ?? false; // Default secure false
|
smtpPort ?? (globalSmtpPortStr ? parseInt(globalSmtpPortStr, 10) : 587);
|
||||||
|
const finalSmtpSecure =
|
||||||
|
smtpSecure ?? globalSmtpSecureStr === "true" ?? false;
|
||||||
const finalSmtpUser = smtpUser || globalSmtpUser;
|
const finalSmtpUser = smtpUser || globalSmtpUser;
|
||||||
const finalSmtpPass = smtpPass || globalSmtpPass;
|
const finalSmtpPass = smtpPass || globalSmtpPass;
|
||||||
const finalFrom = from || globalSmtpFrom || 'noreply@nexus-terminal.local'; // Default from
|
const finalFrom =
|
||||||
|
from || globalSmtpFrom || "noreply@nexus-terminal.local";
|
||||||
|
|
||||||
if (!finalSmtpHost) {
|
if (!finalSmtpHost) {
|
||||||
console.error('[EmailSender] SMTP host is not configured (neither channel-specific nor global).');
|
console.error(
|
||||||
throw new Error('SMTP host configuration is missing.');
|
"[EmailSender] SMTP host is not configured (neither channel-specific nor global)."
|
||||||
|
);
|
||||||
|
throw new Error("SMTP host configuration is missing.");
|
||||||
}
|
}
|
||||||
// Basic validation for port
|
|
||||||
if (isNaN(finalSmtpPort) || finalSmtpPort <= 0) {
|
if (isNaN(finalSmtpPort) || finalSmtpPort <= 0) {
|
||||||
console.error(`[EmailSender] Invalid SMTP port configured: ${finalSmtpPort}. Using default 587.`);
|
console.error(
|
||||||
// finalSmtpPort = 587; // Or throw error depending on strictness needed
|
`[EmailSender] Invalid SMTP port configured: ${finalSmtpPort}. Using default 587.`
|
||||||
|
);
|
||||||
|
|
||||||
throw new Error(`Invalid SMTP port configured: ${finalSmtpPort}`);
|
throw new Error(`Invalid SMTP port configured: ${finalSmtpPort}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove explicit type annotation to let TypeScript infer the type
|
const transporterOptions: SMTPTransport.Options = {
|
||||||
const transporterOptions: SMTPTransport.Options = { // Use specific SMTPTransport options type
|
|
||||||
host: finalSmtpHost,
|
host: finalSmtpHost,
|
||||||
port: finalSmtpPort,
|
port: finalSmtpPort,
|
||||||
secure: finalSmtpSecure, // true for 465, false for other ports
|
secure: finalSmtpSecure,
|
||||||
auth: (finalSmtpUser && finalSmtpPass) ? {
|
auth:
|
||||||
|
finalSmtpUser && finalSmtpPass
|
||||||
|
? {
|
||||||
user: finalSmtpUser,
|
user: finalSmtpUser,
|
||||||
pass: finalSmtpPass,
|
pass: finalSmtpPass,
|
||||||
} : undefined, // Only include auth if user/pass are provided
|
|
||||||
tls: {
|
|
||||||
// rejectUnauthorized should be within the tls object according to types
|
|
||||||
rejectUnauthorized: finalSmtpSecure,
|
|
||||||
// minVersion is also a valid TLS option
|
|
||||||
minVersion: 'TLSv1.2' // Explicitly require TLSv1.2 or higher for Gmail compatibility
|
|
||||||
}
|
}
|
||||||
|
: undefined,
|
||||||
|
tls: {
|
||||||
|
rejectUnauthorized: finalSmtpSecure,
|
||||||
|
|
||||||
|
minVersion: "TLSv1.2",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport(transporterOptions);
|
const transporter = nodemailer.createTransport(transporterOptions);
|
||||||
|
|
||||||
// Verify connection configuration (optional but recommended)
|
|
||||||
// try {
|
|
||||||
// await transporter.verify();
|
|
||||||
// console.log('[EmailSender] SMTP configuration verified successfully.');
|
|
||||||
// } catch (verifyError) {
|
|
||||||
// console.error('[EmailSender] SMTP configuration verification failed:', verifyError);
|
|
||||||
// throw new Error(`SMTP verification failed: ${verifyError.message}`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
const mailOptions: Mail.Options = {
|
const mailOptions: Mail.Options = {
|
||||||
from: `"${finalFrom.split('@')[0]}" <${finalFrom}>`, // sender address format "Sender Name <sender@example.com>"
|
from: `"${finalFrom.split("@")[0]}" <${finalFrom}>`,
|
||||||
to: to, // list of receivers (comma-separated)
|
to: to,
|
||||||
subject: subject, // Subject line
|
subject: subject,
|
||||||
// text: 'Plain text body', // Plain text body (optional, provide if HTML is not supported well)
|
|
||||||
html: body, // html body
|
html: body,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`[EmailSender] Sending email notification to: ${to} with subject: "${subject}"`);
|
console.log(
|
||||||
|
`[EmailSender] Sending email notification to: ${to} with subject: "${subject}"`
|
||||||
|
);
|
||||||
const info = await transporter.sendMail(mailOptions);
|
const info = await transporter.sendMail(mailOptions);
|
||||||
console.log(`[EmailSender] Email sent successfully. Message ID: ${info.messageId}`);
|
console.log(
|
||||||
|
`[EmailSender] Email sent successfully. Message ID: ${info.messageId}`
|
||||||
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`[EmailSender] Error sending email notification to ${to}:`, error);
|
console.error(
|
||||||
// Provide more specific error message if possible
|
`[EmailSender] Error sending email notification to ${to}:`,
|
||||||
throw new Error(`Failed to send email notification: ${error.message || error}`);
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Failed to send email notification: ${error.message || error}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and export singleton instance
|
|
||||||
const emailSenderService = new EmailSenderService();
|
const emailSenderService = new EmailSenderService();
|
||||||
export default emailSenderService;
|
export default emailSenderService;
|
||||||
@@ -1,53 +1,78 @@
|
|||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
import { INotificationSender } from '../notification.dispatcher.service'; // Import the interface
|
import { INotificationSender } from "../notification.dispatcher.service";
|
||||||
import { ProcessedNotification } from '../notification.processor.service';
|
import { ProcessedNotification } from "../notification.processor.service";
|
||||||
import { TelegramConfig } from '../../types/notification.types';
|
import { TelegramConfig } from "../../types/notification.types";
|
||||||
|
|
||||||
class TelegramSenderService implements INotificationSender {
|
class TelegramSenderService implements INotificationSender {
|
||||||
|
|
||||||
async send(notification: ProcessedNotification): Promise<void> {
|
async send(notification: ProcessedNotification): Promise<void> {
|
||||||
const config = notification.config as TelegramConfig;
|
const config = notification.config as TelegramConfig;
|
||||||
const { botToken, chatId } = config;
|
const { botToken, chatId } = config;
|
||||||
const messageBody = notification.body;
|
const messageBody = notification.body;
|
||||||
|
|
||||||
if (!botToken || !chatId) {
|
if (!botToken || !chatId) {
|
||||||
console.error('[TelegramSender] Missing botToken or chatId in configuration.');
|
console.error(
|
||||||
throw new Error('Telegram configuration is incomplete (missing botToken or chatId).');
|
"[TelegramSender] Missing botToken or chatId in configuration."
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
"Telegram configuration is incomplete (missing botToken or chatId)."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiUrl = `https://api.telegram.org/bot${botToken}/sendMessage`;
|
const apiUrl = `https://api.telegram.org/bot${botToken}/sendMessage`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[TelegramSender] Sending notification to chat ID: ${chatId}`);
|
console.log(
|
||||||
const response = await axios.post(apiUrl, {
|
`[TelegramSender] Sending notification to chat ID: ${chatId}`
|
||||||
|
);
|
||||||
|
const response = await axios.post(
|
||||||
|
apiUrl,
|
||||||
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
text: messageBody,
|
text: messageBody,
|
||||||
parse_mode: 'Markdown', // Use standard Markdown
|
parse_mode: "Markdown",
|
||||||
disable_web_page_preview: true // Optional: disable link previews
|
disable_web_page_preview: true,
|
||||||
}, {
|
},
|
||||||
timeout: 10000 // Set a timeout (e.g., 10 seconds)
|
{
|
||||||
});
|
timeout: 10000,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (response.data && response.data.ok) {
|
if (response.data && response.data.ok) {
|
||||||
console.log(`[TelegramSender] Successfully sent notification to chat ID: ${chatId}`);
|
console.log(
|
||||||
|
`[TelegramSender] Successfully sent notification to chat ID: ${chatId}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Log Telegram's error description if available
|
const errorDescription =
|
||||||
const errorDescription = response.data?.description || 'Unknown error from Telegram API';
|
response.data?.description || "Unknown error from Telegram API";
|
||||||
console.error(`[TelegramSender] Failed to send notification. Telegram API response: ${errorDescription}`, response.data);
|
console.error(
|
||||||
|
`[TelegramSender] Failed to send notification. Telegram API response: ${errorDescription}`,
|
||||||
|
response.data
|
||||||
|
);
|
||||||
throw new Error(`Telegram API error: ${errorDescription}`);
|
throw new Error(`Telegram API error: ${errorDescription}`);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
console.error(`[TelegramSender] Axios error sending notification: ${error.message}`, error.response?.data);
|
console.error(
|
||||||
throw new Error(`Failed to send Telegram notification (Axios Error): ${error.message}`);
|
`[TelegramSender] Axios error sending notification: ${error.message}`,
|
||||||
|
error.response?.data
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
`Failed to send Telegram notification (Axios Error): ${error.message}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.error(`[TelegramSender] Unexpected error sending notification:`, error);
|
console.error(
|
||||||
throw new Error(`Failed to send Telegram notification (Unexpected Error): ${error.message || error}`);
|
`[TelegramSender] Unexpected error sending notification:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
`Failed to send Telegram notification (Unexpected Error): ${
|
||||||
|
error.message || error
|
||||||
|
}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and export singleton instance
|
|
||||||
const telegramSenderService = new TelegramSenderService();
|
const telegramSenderService = new TelegramSenderService();
|
||||||
export default telegramSenderService;
|
export default telegramSenderService;
|
||||||
@@ -1,21 +1,19 @@
|
|||||||
import axios, { Method } from 'axios';
|
import axios, { Method } from "axios";
|
||||||
import { INotificationSender } from '../notification.dispatcher.service';
|
import { INotificationSender } from "../notification.dispatcher.service";
|
||||||
import { ProcessedNotification } from '../notification.processor.service';
|
import { ProcessedNotification } from "../notification.processor.service";
|
||||||
import { WebhookConfig } from '../../types/notification.types';
|
import { WebhookConfig } from "../../types/notification.types";
|
||||||
|
|
||||||
class WebhookSenderService implements INotificationSender {
|
class WebhookSenderService implements INotificationSender {
|
||||||
|
|
||||||
async send(notification: ProcessedNotification): Promise<void> {
|
async send(notification: ProcessedNotification): Promise<void> {
|
||||||
const config = notification.config as WebhookConfig;
|
const config = notification.config as WebhookConfig;
|
||||||
const { url, method = 'POST', headers = {} } = config; // Default method to POST
|
const { url, method = "POST", headers = {} } = config;
|
||||||
const requestBody = notification.body; // Body is already processed by the processor
|
const requestBody = notification.body;
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
console.error('[WebhookSender] Missing webhook URL in configuration.');
|
console.error("[WebhookSender] Missing webhook URL in configuration.");
|
||||||
throw new Error('Webhook configuration is incomplete (missing URL).');
|
throw new Error("Webhook configuration is incomplete (missing URL).");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate URL format (basic check)
|
|
||||||
try {
|
try {
|
||||||
new URL(url);
|
new URL(url);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -23,85 +21,106 @@ class WebhookSenderService implements INotificationSender {
|
|||||||
throw new Error(`Invalid webhook URL format: ${url}`);
|
throw new Error(`Invalid webhook URL format: ${url}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare headers
|
|
||||||
const finalHeaders: Record<string, string> = {
|
const finalHeaders: Record<string, string> = {
|
||||||
'Content-Type': 'application/json', // Default Content-Type, can be overridden by config
|
"Content-Type": "application/json",
|
||||||
...headers, // Merge custom headers from config
|
...headers,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determine HTTP method
|
const requestMethod: Method = method.toUpperCase() as Method;
|
||||||
const requestMethod: Method = method.toUpperCase() as Method; // Ensure method is uppercase and valid Axios Method type
|
const validMethods: Method[] = [
|
||||||
const validMethods: Method[] = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
|
"GET",
|
||||||
|
"POST",
|
||||||
|
"PUT",
|
||||||
|
"DELETE",
|
||||||
|
"PATCH",
|
||||||
|
"HEAD",
|
||||||
|
"OPTIONS",
|
||||||
|
];
|
||||||
if (!validMethods.includes(requestMethod)) {
|
if (!validMethods.includes(requestMethod)) {
|
||||||
console.error(`[WebhookSender] Invalid HTTP method specified: ${method}. Defaulting to POST.`);
|
console.error(
|
||||||
// requestMethod = 'POST'; // Or throw an error
|
`[WebhookSender] Invalid HTTP method specified: ${method}. Defaulting to POST.`
|
||||||
|
);
|
||||||
|
|
||||||
throw new Error(`Invalid HTTP method specified: ${method}`);
|
throw new Error(`Invalid HTTP method specified: ${method}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[WebhookSender] Sending ${requestMethod} notification to webhook URL: ${url}`);
|
console.log(
|
||||||
|
`[WebhookSender] Sending ${requestMethod} notification to webhook URL: ${url}`
|
||||||
|
);
|
||||||
|
|
||||||
// Prepare request data based on method
|
|
||||||
let requestData: any = undefined;
|
let requestData: any = undefined;
|
||||||
let requestParams: any = undefined;
|
let requestParams: any = undefined;
|
||||||
|
|
||||||
// For GET requests, data is usually sent as query params.
|
if (["POST", "PUT", "PATCH"].includes(requestMethod)) {
|
||||||
// For POST/PUT/PATCH, data is sent in the body.
|
if (
|
||||||
// We assume the processed `requestBody` is intended for the body.
|
finalHeaders["Content-Type"]
|
||||||
// If the template was designed for GET params, this might need adjustment.
|
?.toLowerCase()
|
||||||
if (['POST', 'PUT', 'PATCH'].includes(requestMethod)) {
|
.includes("application/json")
|
||||||
// Try to parse body as JSON if Content-Type suggests it, otherwise send as string
|
) {
|
||||||
if (finalHeaders['Content-Type']?.toLowerCase().includes('application/json')) {
|
|
||||||
try {
|
try {
|
||||||
requestData = JSON.parse(requestBody);
|
requestData = JSON.parse(requestBody);
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
console.warn(`[WebhookSender] Failed to parse request body as JSON for Content-Type application/json. Sending as raw string. Body: ${requestBody.substring(0,100)}...`);
|
console.warn(
|
||||||
|
`[WebhookSender] Failed to parse request body as JSON for Content-Type application/json. Sending as raw string. Body: ${requestBody.substring(
|
||||||
|
0,
|
||||||
|
100
|
||||||
|
)}...`
|
||||||
|
);
|
||||||
requestData = requestBody;
|
requestData = requestBody;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
requestData = requestBody;
|
requestData = requestBody;
|
||||||
}
|
}
|
||||||
} else if (requestMethod === 'GET') {
|
} else if (requestMethod === "GET") {
|
||||||
// For GET, we might need to parse the body (if it's a query string) or handle differently.
|
console.warn(
|
||||||
// For simplicity now, we won't automatically convert body to params for GET.
|
`[WebhookSender] Sending data in body for GET request might not be standard. URL: ${url}`
|
||||||
// User should configure GET webhooks appropriately (e.g., URL includes params).
|
);
|
||||||
console.warn(`[WebhookSender] Sending data in body for GET request might not be standard. URL: ${url}`);
|
|
||||||
// If requestBody is intended as query params, parsing logic would be needed here.
|
|
||||||
// requestParams = querystring.parse(requestBody); // Example
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
method: requestMethod,
|
method: requestMethod,
|
||||||
url: url,
|
url: url,
|
||||||
headers: finalHeaders,
|
headers: finalHeaders,
|
||||||
data: requestData,
|
data: requestData,
|
||||||
params: requestParams,
|
params: requestParams,
|
||||||
timeout: 15000 // Set a timeout (e.g., 15 seconds)
|
timeout: 15000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check response status (e.g., 2xx indicates success)
|
|
||||||
if (response.status >= 200 && response.status < 300) {
|
if (response.status >= 200 && response.status < 300) {
|
||||||
console.log(`[WebhookSender] Successfully sent notification to webhook. Status: ${response.status}`);
|
console.log(
|
||||||
|
`[WebhookSender] Successfully sent notification to webhook. Status: ${response.status}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.warn(`[WebhookSender] Webhook endpoint responded with status: ${response.status}`, response.data);
|
console.warn(
|
||||||
// Consider throwing an error for non-2xx responses depending on requirements
|
`[WebhookSender] Webhook endpoint responded with status: ${response.status}`,
|
||||||
// throw new Error(`Webhook endpoint responded with status: ${response.status}`);
|
response.data
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
console.error(`[WebhookSender] Axios error sending notification to ${url}: ${error.message}`, error.response?.status, error.response?.data);
|
console.error(
|
||||||
throw new Error(`Failed to send webhook notification (Axios Error): ${error.message}`);
|
`[WebhookSender] Axios error sending notification to ${url}: ${error.message}`,
|
||||||
|
error.response?.status,
|
||||||
|
error.response?.data
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
`Failed to send webhook notification (Axios Error): ${error.message}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.error(`[WebhookSender] Unexpected error sending notification to ${url}:`, error);
|
console.error(
|
||||||
throw new Error(`Failed to send webhook notification (Unexpected Error): ${error.message || error}`);
|
`[WebhookSender] Unexpected error sending notification to ${url}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
`Failed to send webhook notification (Unexpected Error): ${
|
||||||
|
error.message || error
|
||||||
|
}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and export singleton instance
|
|
||||||
const webhookSenderService = new WebhookSenderService();
|
const webhookSenderService = new WebhookSenderService();
|
||||||
export default webhookSenderService;
|
export default webhookSenderService;
|
||||||
Reference in New Issue
Block a user