UNPKG

atozas-push-notification

Version:

Real-time push notifications across platforms using socket.io

326 lines (283 loc) 8.3 kB
import { io, Socket } from 'socket.io-client'; import { ClientConfig, UserInfo, NotificationData, NotificationOptions, NotificationCallback, ConnectionCallback, ErrorCallback, UserStatusCallback, NotificationPermission } from './types'; import { NotificationDisplayManager } from './notification-display'; export class AtozasPushNotificationClient { private socket: Socket | null = null; private config: ClientConfig; private userInfo: UserInfo | null = null; private isConnected: boolean = false; private reconnectAttempts: number = 0; private displayManager: NotificationDisplayManager; // Event handlers private onNotificationCallback: NotificationCallback | null = null; private onConnectionCallback: ConnectionCallback | null = null; private onErrorCallback: ErrorCallback | null = null; private onUserStatusCallback: UserStatusCallback | null = null; constructor(config: ClientConfig) { this.config = { autoConnect: true, reconnection: true, reconnectionAttempts: 5, reconnectionDelay: 1000, reconnectionDelayMax: 5000, timeout: 20000, forceNew: false, requestPermission: true, fallbackToUI: true, showNotifications: true, enableVibration: true, ...config }; // Initialize notification display manager this.displayManager = new NotificationDisplayManager(this.config); if (this.config.autoConnect) { this.connect(); } } /** * Connect to the notification server */ public connect(): Promise<void> { return new Promise((resolve, reject) => { try { this.socket = io(this.config.url, { autoConnect: false, reconnection: this.config.reconnection, reconnectionAttempts: this.config.reconnectionAttempts, reconnectionDelay: this.config.reconnectionDelay, reconnectionDelayMax: this.config.reconnectionDelayMax, timeout: this.config.timeout, forceNew: this.config.forceNew }); this.setupEventHandlers(); this.socket.connect(); this.socket.on('connect', () => { this.isConnected = true; this.reconnectAttempts = 0; // Re-authenticate if user info exists if (this.userInfo) { this.authenticate(this.userInfo); } if (this.onConnectionCallback) { this.onConnectionCallback(true); } resolve(); }); this.socket.on('connect_error', (error) => { this.isConnected = false; if (this.onErrorCallback) { this.onErrorCallback(error); } reject(error); }); } catch (error) { reject(error); } }); } /** * Disconnect from the notification server */ public disconnect(): void { if (this.socket) { this.socket.disconnect(); this.isConnected = false; if (this.onConnectionCallback) { this.onConnectionCallback(false); } } } /** * Authenticate user with the server */ public authenticate(userInfo: UserInfo): void { this.userInfo = userInfo; if (this.socket && this.isConnected) { this.socket.emit('authenticate', userInfo); } } /** * Join a group for group notifications */ public joinGroup(groupId: string): void { if (this.socket && this.isConnected) { this.socket.emit('join_group', groupId); } } /** * Leave a group */ public leaveGroup(groupId: string): void { if (this.socket && this.isConnected) { this.socket.emit('leave_group', groupId); } } /** * Set notification callback handler */ public onNotification(callback: NotificationCallback): void { this.onNotificationCallback = callback; } /** * Set connection status callback handler */ public onConnection(callback: ConnectionCallback): void { this.onConnectionCallback = callback; } /** * Set error callback handler */ public onError(callback: ErrorCallback): void { this.onErrorCallback = callback; } /** * Set user status callback handler */ public onUserStatus(callback: UserStatusCallback): void { this.onUserStatusCallback = callback; } /** * Get connection status */ public getConnectionStatus(): boolean { return this.isConnected; } /** * Get current user info */ public getUserInfo(): UserInfo | null { return this.userInfo; } /** * Send acknowledgment for received notification */ public acknowledgeNotification(notificationId: string): void { if (this.socket && this.isConnected) { this.socket.emit('notification_ack', notificationId); } } /** * Request user status */ public requestUserStatus(userId: string): void { if (this.socket && this.isConnected) { this.socket.emit('request_user_status', userId); } } /** * Request notification permission */ public async requestNotificationPermission(): Promise<NotificationPermission> { return await this.displayManager.requestPermission(); } /** * Get notification permission status */ public getNotificationPermission(): NotificationPermission { return this.displayManager.getPermissionStatus(); } /** * Set notification click handler */ public onNotificationClick(callback: (notification: NotificationData) => void): void { this.displayManager.onNotificationClick(callback); } /** * Close all active notifications */ public closeAllNotifications(): void { this.displayManager.closeAllNotifications(); } /** * Display a local notification (without sending through socket) */ public displayLocalNotification(notification: NotificationData, options?: NotificationOptions): void { this.displayManager.displayNotification(notification, options); } /** * Setup socket event handlers */ private setupEventHandlers(): void { if (!this.socket) return; // Handle notifications this.socket.on('notification', (data: { notification: NotificationData; options?: NotificationOptions }) => { // Display notification visually if (this.config.showNotifications) { this.displayManager.displayNotification(data.notification, data.options); } // Call user callback if (this.onNotificationCallback) { this.onNotificationCallback(data.notification, data.options); } }); // Handle disconnection this.socket.on('disconnect', (reason) => { this.isConnected = false; if (this.onConnectionCallback) { this.onConnectionCallback(false); } // Auto-reconnect logic if (this.config.reconnection && reason === 'io server disconnect') { this.handleReconnection(); } }); // Handle reconnection this.socket.on('reconnect', () => { this.isConnected = true; this.reconnectAttempts = 0; // Re-authenticate if (this.userInfo) { this.authenticate(this.userInfo); } if (this.onConnectionCallback) { this.onConnectionCallback(true); } }); // Handle reconnection attempts this.socket.on('reconnect_attempt', () => { this.reconnectAttempts++; }); // Handle user status updates this.socket.on('user_status', (data: { userId: string; online: boolean }) => { if (this.onUserStatusCallback) { this.onUserStatusCallback(data.userId, data.online); } }); // Handle errors this.socket.on('error', (error) => { if (this.onErrorCallback) { this.onErrorCallback(error); } }); } /** * Handle reconnection logic */ private handleReconnection(): void { if (this.reconnectAttempts >= (this.config.reconnectionAttempts || 5)) { if (this.onErrorCallback) { this.onErrorCallback(new Error('Max reconnection attempts reached')); } return; } const delay = Math.min( this.config.reconnectionDelay! * Math.pow(2, this.reconnectAttempts), this.config.reconnectionDelayMax! ); setTimeout(() => { if (!this.isConnected && this.socket) { this.socket.connect(); } }, delay); } }