UNPKG

@churchapps/apphelper

Version:

Library of helper functions for React and NextJS ChurchApps

211 lines (176 loc) 6.58 kB
import { SocketHelper } from "./SocketHelper"; import { ApiHelper, UserContextInterface } from "@churchapps/helpers"; export interface NotificationCounts { notificationCount: number; pmCount: number; } export class NotificationService { private static instance: NotificationService; private counts: NotificationCounts = { notificationCount: 0, pmCount: 0 }; private listeners: Array<(counts: NotificationCounts) => void> = []; private isInitialized: boolean = false; private currentPersonId: string | null = null; private loadTimeout: any | null = null; private constructor() {} static getInstance(): NotificationService { if (!NotificationService.instance) { NotificationService.instance = new NotificationService(); } return NotificationService.instance; } /** * Initialize the notification service with user context */ async initialize(context: UserContextInterface): Promise<void> { if (this.isInitialized) return; try { // Store current person ID for conversation counting this.currentPersonId = context?.person?.id || null; // Initialize WebSocket connection await SocketHelper.init(); // Set person/church context for websocket if (context?.person?.id && context?.userChurch?.church?.id) { SocketHelper.setPersonChurch({ personId: context.person.id, churchId: context.userChurch.church.id }); } // Register handlers for notification updates this.registerWebSocketHandlers(); // Load initial notification counts await this.loadNotificationCounts(); this.isInitialized = true; } catch (error) { console.error("❌ Failed to initialize NotificationService:", error); throw error; } } /** * Register websocket handlers for real-time notification updates */ private registerWebSocketHandlers(): void { // Handler for new private messages SocketHelper.addHandler("privateMessage", "NotificationService-PM", (data: any) => { console.log('🔔 NotificationService: New private message received, updating counts'); this.debouncedLoadNotificationCounts(); }); // Handler for general notifications SocketHelper.addHandler("notification", "NotificationService-Notification", (data: any) => { console.log('🔔 NotificationService: New notification received, updating counts'); this.debouncedLoadNotificationCounts(); }); // Handler for message updates that could affect notification counts SocketHelper.addHandler("message", "NotificationService-MessageUpdate", (data: any) => { // Only update counts if the message update involves the current person if (data?.message?.personId === this.currentPersonId || data?.notifyPersonId === this.currentPersonId) { console.log('🔔 NotificationService: Message update affecting current user, updating counts'); this.debouncedLoadNotificationCounts(); } }); // Handler for reconnect events SocketHelper.addHandler("reconnect", "NotificationService-Reconnect", (data: any) => { console.log('🔔 NotificationService: WebSocket reconnected, refreshing counts'); this.loadNotificationCounts(); // Don't debounce reconnect - need immediate update }); } /** * Load notification counts from the API with debouncing */ private debouncedLoadNotificationCounts(): void { if (this.loadTimeout) { clearTimeout(this.loadTimeout); } this.loadTimeout = setTimeout(() => { this.loadNotificationCounts(); }, 300); // 300ms debounce } /** * Load notification counts from the API */ async loadNotificationCounts(): Promise<void> { try { // Use the unreadCount endpoint which returns both notification and PM counts const counts = await ApiHelper.get("/notifications/unreadCount", "MessagingApi"); const newCounts = { notificationCount: counts?.notificationCount || 0, pmCount: counts?.pmCount || 0 }; // Update counts and notify listeners this.updateCounts(newCounts); } catch (error) { console.error("❌ Failed to load notification counts:", error); // Don't throw - just log the error and keep existing counts } } /** * Update counts and notify all listeners */ private updateCounts(newCounts: NotificationCounts): void { const countsChanged = this.counts.notificationCount !== newCounts.notificationCount || this.counts.pmCount !== newCounts.pmCount; if (countsChanged) { this.counts = { ...newCounts }; // Notify all listeners this.listeners.forEach(listener => { try { listener(this.counts); } catch (error) { console.error("❌ Error in notification listener:", error); } }); } } /** * Subscribe to notification count changes */ subscribe(listener: (counts: NotificationCounts) => void): () => void { this.listeners.push(listener); // Immediately call with current counts listener(this.counts); // Return unsubscribe function return () => { this.listeners = this.listeners.filter(l => l !== listener); }; } /** * Get current notification counts */ getCounts(): NotificationCounts { return { ...this.counts }; } /** * Manually refresh notification counts */ async refresh(): Promise<void> { await this.loadNotificationCounts(); } /** * Cleanup the service */ cleanup(): void { // Clear any pending timeout if (this.loadTimeout) { clearTimeout(this.loadTimeout); this.loadTimeout = null; } // Remove websocket handlers SocketHelper.removeHandler("NotificationService-PM"); SocketHelper.removeHandler("NotificationService-Notification"); SocketHelper.removeHandler("NotificationService-MessageUpdate"); SocketHelper.removeHandler("NotificationService-Reconnect"); // Clear listeners this.listeners = []; // Reset state this.counts = { notificationCount: 0, pmCount: 0 }; this.currentPersonId = null; this.isInitialized = false; } /** * Check if service is initialized */ isReady(): boolean { return this.isInitialized && SocketHelper.isConnected(); } }