import { WorkflowRun } from '../types/github'; export class NotificationService { private static instance: NotificationService; private hasPermission = false; private constructor() { this.checkPermission(); } static getInstance(): NotificationService { if (!NotificationService.instance) { NotificationService.instance = new NotificationService(); } return NotificationService.instance; } private checkPermission(): void { if (!('Notification' in window)) { console.log('This browser does not support desktop notification'); return; } this.hasPermission = Notification.permission === 'granted'; } async requestPermission(): Promise { if (!('Notification' in window)) { console.log('This browser does not support desktop notification'); return false; } if (Notification.permission === 'granted') { this.hasPermission = true; return true; } if (Notification.permission !== 'denied') { const permission = await Notification.requestPermission(); this.hasPermission = permission === 'granted'; return this.hasPermission; } return false; } canShowNotifications(): boolean { return this.hasPermission && 'Notification' in window; } showFailureNotification(failedRuns: WorkflowRun[]): void { if (!this.canShowNotifications()) { return; } const count = failedRuns.length; const title = count === 1 ? 'Build Failed' : `${count} Builds Failed`; const body = count === 1 ? `${failedRuns[0].repository.full_name} - ${failedRuns[0].display_title}` : `Multiple builds have failed across ${new Set(failedRuns.map(r => r.repository.full_name)).size} repositories`; const notification = new Notification(title, { body, icon: '/favicon.ico', badge: '/favicon.ico', tag: 'build-failure', renotify: true, requireInteraction: false, silent: false }); // Auto-close after 5 seconds setTimeout(() => { notification.close(); }, 5000); // Optional: Click to focus window notification.onclick = () => { window.focus(); notification.close(); }; } showSuccessNotification(successRuns: WorkflowRun[]): void { if (!this.canShowNotifications()) { return; } const count = successRuns.length; const title = count === 1 ? 'Build Recovered' : `${count} Builds Recovered`; const body = count === 1 ? `${successRuns[0].repository.full_name} - ${successRuns[0].display_title}` : `Multiple builds have recovered across ${new Set(successRuns.map(r => r.repository.full_name)).size} repositories`; const notification = new Notification(title, { body, icon: '/favicon.ico', badge: '/favicon.ico', tag: 'build-success', renotify: true, requireInteraction: false, silent: false }); // Auto-close after 3 seconds setTimeout(() => { notification.close(); }, 3000); notification.onclick = () => { window.focus(); notification.close(); }; } showWaitingNotification(waitingRuns: WorkflowRun[]): void { if (!this.canShowNotifications()) { return; } const count = waitingRuns.length; const title = count === 1 ? 'Build Waiting' : `${count} Builds Waiting`; const body = count === 1 ? `${waitingRuns[0].repository.full_name} - ${waitingRuns[0].display_title}` : `Multiple builds are now waiting across ${new Set(waitingRuns.map(r => r.repository.full_name)).size} repositories`; const notification = new Notification(title, { body, icon: '/favicon.ico', badge: '/favicon.ico', tag: 'build-waiting', renotify: true, requireInteraction: false, silent: false }); // Auto-close after 4 seconds setTimeout(() => { notification.close(); }, 4000); notification.onclick = () => { window.focus(); notification.close(); }; } }