//import SubscriptionService from './subscriptionService.js'; import VapidService from './vapidService.js'; import { VapidKeys } from './VapidKeys.js'; import * as https from 'https'; import * as http_ece from 'http_ece'; import crypto from 'crypto'; //import { Subscription } from "./Subscription.js" export interface Message { title: string; body?: string; [key: string]: any; } export interface BrowserSubscription { endpoint: string; keys: { p256dh: string; auth: string; }; } export class NotificationService { private static instance: NotificationService; // private subscriptionService: SubscriptionService = SubscriptionService.getInstance(); private vapidService: VapidService = VapidService.getInstance(); constructor() { } public static getInstance(): NotificationService { if (!NotificationService.instance) { NotificationService.instance = new NotificationService(); } return NotificationService.instance; } private generateSalt(length = 16): Buffer { return crypto.randomBytes(length); } async sendNotification(subscription: BrowserSubscription, message: Message) { await this.pushToEndpoint(subscription, message); } private async pushToEndpoint(subscription: BrowserSubscription, message: Message): Promise { const payloadString = JSON.stringify(message); const payloadBuffer = Buffer.from(payloadString, 'utf-8'); const encrypted = await this.encrypt(subscription.keys.p256dh, subscription.keys.auth, payloadBuffer); const endpoint = subscription.endpoint; const vapidHeaders = await this.vapidService.createVapidAuthHeader(endpoint, 12 * 60 * 60, 'mailto:example@example.com'); const parsedUrl = new URL(subscription.endpoint); const options: https.RequestOptions = { method: 'POST', hostname: parsedUrl.hostname, path: parsedUrl.pathname, port: parsedUrl.port, headers: { ...vapidHeaders, 'TTL': '60', 'Content-Encoding': 'aes128gcm', 'Content-Type': 'application/octet-stream', 'Content-Length': encrypted.length }, }; return new Promise((resolve, reject) => { const req = https.request(options, (res) => { if (res.statusCode! >= 200 && res.statusCode! < 300) { resolve(); } else { reject(new Error(`Failed to send push notification. Status code: ${res.statusCode}`)); } }); req.on('error', (error) => { reject(new Error(`Failed to send push notification. Error: ${error.message}`)); }); req.write(encrypted); req.end(); }); } private async encrypt(publicKey: string, auth: string, payload: Buffer): Promise { try { console.log('Public Key:', publicKey); console.log('Auth:', auth); const vapidKeys: VapidKeys[] = await this.vapidService.getVapidKeys(); const vapidkey: VapidKeys = vapidKeys[0]; const vapidPrivateKeyBase64: string = vapidkey['privateKey']; console.log(vapidPrivateKeyBase64); const vapidPrivateKeyBuffer: Buffer = Buffer.from(vapidPrivateKeyBase64, 'base64'); const ecdh = crypto.createECDH('prime256v1'); ecdh.setPrivateKey(vapidPrivateKeyBuffer); const publicKeyBuffer: Buffer = Buffer.from(publicKey, 'base64'); // const authBuffer: Buffer = Buffer.from(auth, 'base64'); return http_ece.encrypt(payload, { 'salt': this.generateSalt(), 'privateKey': ecdh, // Your VAPID private key 'publicKey': publicKeyBuffer, // Client's public key from the subscription object subscription.keys.p256dh 'authSecret': auth // Client's auth secret from the subscription object subscription.keys.auth }); } catch (error) { console.error('Error encrypting payload:', error); // Handle the error as appropriate for your application throw error; } } }