learning_ai_invt_trdg/backend/src/services/notifier.ts

107 lines
3.8 KiB
TypeScript

import https from 'https';
import logger from '../utils/logger.js';
import { config } from '../config/index.js';
export class Notifier {
private isNotificationEnabled: boolean = true;
private readonly phoneNumbers: string[] = config.NOTIFICATION_PHONE_NUMBERS.length > 0
? config.NOTIFICATION_PHONE_NUMBERS
: []; // No default — must be configured via NOTIFICATION_PHONE_NUMBERS env var
/**
* Sets the notification status.
* @param status - True to enable, false to disable.
*/
public setNotificationStatus(status: boolean): void {
this.isNotificationEnabled = status;
logger.info(`[Notifier] WhatsApp notifications are now ${status ? 'ENABLED' : 'DISABLED'}`);
}
/**
* Gets the current notification status.
*/
public getNotificationStatus(): boolean {
return this.isNotificationEnabled;
}
/**
* Sends a WhatsApp message to all configured recipients.
* @param message - The content to send.
*/
public async sendAlert(message: string): Promise<void> {
if (!this.isNotificationEnabled) {
logger.info(`[Notifier] Notifications disabled. Skipping message: ${message.substring(0, 30)}...`);
return;
}
if (this.phoneNumbers.length === 0) {
logger.warn(`[Notifier] No phone numbers configured (set NOTIFICATION_PHONE_NUMBERS env var). Skipping.`);
return;
}
const sendPromises = this.phoneNumbers.map(number => this.sendSingleWhatsAppMessage(number, message));
try {
await Promise.all(sendPromises);
logger.info(`[Notifier] Alerts broadcasted to ${this.phoneNumbers.length} recipients.`);
} catch (error) {
logger.error('[Notifier] Broadcast failed partially/fully.');
}
}
/**
* Sends a WhatsApp message to a specific number using ZenHustles API.
*/
private sendSingleWhatsAppMessage(to: string, message: string): Promise<any> {
return new Promise((resolve, reject) => {
const data = JSON.stringify({
message: message,
toWhatsAppNumber: to
});
const options = {
hostname: config.NOTIFICATION_API_HOST,
port: 443,
path: config.NOTIFICATION_API_PATH,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data)
}
};
const req = https.request(options, (res) => {
let responseBody = '';
res.on('data', (chunk) => {
responseBody += chunk;
});
res.on('end', () => {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
try {
const parsed = JSON.parse(responseBody);
logger.info(`[Notifier] Successfully sent to ${to}`);
resolve(parsed);
} catch (e) {
logger.info(`[Notifier] Successfully sent to ${to} (Non-JSON response)`);
resolve(responseBody);
}
} else {
logger.error(`[Notifier] Failed for ${to}. Status: ${res.statusCode}, Body: ${responseBody}`);
reject(new Error(`Status ${res.statusCode}`));
}
});
});
req.on('error', (error) => {
logger.error(`[Notifier] Network error for ${to}: ${error.message}`);
reject(error);
});
req.write(data);
req.end();
});
}
}