import { browser } from '$app/environment'; export class GameNotifications { private originalTitle: string; private titleInterval: ReturnType | null = null; private notificationPermission: NotificationPermission = 'default'; constructor() { if (browser) { this.originalTitle = document.title; // iOS Safari doesn't support the Notification API this.notificationPermission = ('Notification' in window) ? Notification.permission : 'denied'; } else { this.originalTitle = ''; } } /** * Request notification permission from the user */ async requestPermission(): Promise { if (!browser || !('Notification' in window)) { return false; } if (Notification.permission === 'granted') { this.notificationPermission = 'granted'; return true; } if (Notification.permission !== 'denied') { const permission = await Notification.requestPermission(); this.notificationPermission = permission; return permission === 'granted'; } return false; } /** * Start flashing the page title to get attention */ startTitleFlash(message: string) { if (!browser) return; this.stopTitleFlash(); // Clear any existing flash this.originalTitle = document.title; let isOriginal = true; this.titleInterval = setInterval(() => { document.title = isOriginal ? message : this.originalTitle; isOriginal = !isOriginal; }, 1000); } /** * Stop flashing the title and restore the original */ stopTitleFlash() { if (!browser) return; if (this.titleInterval) { clearInterval(this.titleInterval); this.titleInterval = null; } document.title = this.originalTitle; } /** * Show a browser push notification */ async showNotification(title: string, options?: NotificationOptions) { if (!browser || !('Notification' in window)) { return; } // Auto-request permission if not denied if (this.notificationPermission === 'default') { await this.requestPermission(); } if (this.notificationPermission === 'granted') { try { const notification = new Notification(title, { icon: '/favicon.png', badge: '/favicon.png', ...options }); // Auto-close after 5 seconds setTimeout(() => notification.close(), 5000); return notification; } catch (err) { console.error('Failed to show notification:', err); } } } /** * Notify user of their turn with both title flash and push notification */ notifyYourTurn(opponentHandle?: string) { const message = opponentHandle ? `Your turn vs ${opponentHandle}!` : 'Your turn!'; this.startTitleFlash(`⚫ ${message}`); this.showNotification('Cloud Go - Your Turn', { body: message, tag: 'your-turn', requireInteraction: false }); } /** * Notify user of a new move in the game they're watching */ notifyNewMove(playerHandle?: string) { const message = playerHandle ? `${playerHandle} played a move` : 'New move played'; this.startTitleFlash(`🔄 ${message}`); this.showNotification('Cloud Go - New Move', { body: message, tag: 'new-move', requireInteraction: false }); } /** * Clean up when component unmounts */ cleanup() { this.stopTitleFlash(); } } /** * Check if the page is currently visible/focused */ export function isPageVisible(): boolean { if (!browser) return false; return document.visibilityState === 'visible'; } /** * Listen for visibility changes */ export function onVisibilityChange(callback: (visible: boolean) => void): () => void { if (!browser) return () => {}; const handler = () => { callback(document.visibilityState === 'visible'); }; document.addEventListener('visibilitychange', handler); return () => { document.removeEventListener('visibilitychange', handler); }; }