UNPKG

livechat-widget

Version:

LiveChat Widget for Next.js applications

239 lines (200 loc) 8.7 kB
import { ChatMessage, WebSocketMessage } from '@/types/chat'; export class WebSocketService { private socket: WebSocket | null = null; private messageListeners: ((message: ChatMessage) => void)[] = []; private connectionListeners: ((connected: boolean) => void)[] = []; private reconnectInterval: NodeJS.Timeout | null = null; private reconnectAttempts = 0; private maxReconnectAttempts = 5; private reconnectDelay = 3000; // 3 seconds private receivedMessageIds = new Set<string>(); private roomCode: string | null = null; private appId: string | null = null; private userCode: string | null = null; private deleteMessageListeners: ((messageId: string) => void)[] = []; constructor(private url: string) {} connect(roomCode: string, appId: string, userCode: string = 'anonymous'): Promise<boolean> { if (this.socket) { this.disconnect(); } this.receivedMessageIds.clear(); const fullUrl = `${this.url}?room_code=${roomCode}&app_id=${appId}&user_code=${userCode}`; this.socket = new WebSocket(fullUrl); this.roomCode = roomCode; this.appId = appId; this.userCode = userCode; return new Promise((resolve) => { if (this.socket) { this.socket.onopen = () => { console.log('WebSocket connection established'); this.reconnectAttempts = 0; this.notifyConnectionListeners(true); resolve(true); }; this.socket.onmessage = (event) => { try { const data: WebSocketMessage = JSON.parse(event.data); // ตรวจสอบประเภทของข้อความ if (data.type === 'chat' && data.payload) { // ตรวจสอบว่า payload มีรูปแบบ snake_case const payload = data.payload as ChatMessage; // ตรวจสอบว่ามี user_info หรือไม่ if (!payload.user_info) { payload.user_info = { username: 'Unknown User', profile_image: '/image/profile.png' }; } // ตรวจสอบว่ามี created_at หรือไม่ if (!payload.created_at) { payload.created_at = new Date().toISOString(); } // สร้าง id ถ้าไม่มี if (!payload.id) { payload.id = `${payload.user_code}_${new Date(payload.created_at).getTime()}`; } // ตรวจสอบว่าข้อความซ้ำกันหรือไม่ if (!this.receivedMessageIds.has(payload.id)) { this.receivedMessageIds.add(payload.id); this.notifyMessageListeners(payload); } else { console.log('Duplicate message received, ignoring:', payload.id); } } else if (data.type === 'message_deleted' && data.payload) { // รับการแจ้งเตือนการลบข้อความ const deletePayload = data.payload as { room_code: string; app_id: string; message_id: string; }; // console.log('Message deleted notification received:', deletePayload); // แจ้งเตือนผู้ฟังเกี่ยวกับการลบข้อความ this.notifyDeleteMessageListeners(deletePayload.message_id); } } catch (error) { console.error('Error parsing WebSocket message:', error); } }; this.socket.onclose = (event) => { console.log('WebSocket connection closed:', event.code, event.reason); this.notifyConnectionListeners(false); this.socket = null; // Attempt to reconnect if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectInterval = setTimeout(() => { this.reconnectAttempts++; this.connect(this.roomCode!, this.appId!, this.userCode || 'anonymous').then((connected) => { if (!connected) { resolve(false); } }); }, this.reconnectDelay); } else { resolve(false); } }; this.socket.onerror = (error) => { console.error('WebSocket error:', error); resolve(false); }; } else { resolve(false); } }); } disconnect(): void { if (this.socket) { this.socket.close(); this.socket = null; } // ล้าง receivedMessageIds เมื่อตัดการเชื่อมต่อ this.receivedMessageIds.clear(); if (this.reconnectInterval) { clearTimeout(this.reconnectInterval); this.reconnectInterval = null; } } sendMessage(message: ChatMessage): void { if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { console.error('WebSocket is not connected'); // ลองเชื่อมต่อใหม่ถ้าไม่มีการเชื่อมต่อ if (!this.socket) { console.log('Attempting to reconnect before sending message...'); // ใช้ roomCode และ appId ที่เก็บไว้ในตัวแปรระดับคลาส const roomCode = this.roomCode; const appId = this.appId; const userCode = this.userCode; if (roomCode && appId && userCode) { this.connect(roomCode, appId, userCode).then(connected => { if (connected) { console.log('Reconnected successfully, sending message...'); this.sendMessage(message); } else { console.error('Failed to reconnect, message not sent'); } }); } else { console.error('Cannot reconnect: missing roomCode, appId or userCode'); } } return; } // ตรวจสอบว่า message มี user_info หรือไม่ if (!message.user_info) { message.user_info = { username: 'Unknown User', profile_image: '/image/profile.png' }; } const wsMessage: WebSocketMessage = { type: 'chat', payload: message, }; this.socket.send(JSON.stringify(wsMessage)); } addMessageListener(listener: (message: ChatMessage) => void): void { this.messageListeners.push(listener); } removeMessageListener(listener: (message: ChatMessage) => void): void { this.messageListeners = this.messageListeners.filter((l) => l !== listener); } addConnectionListener(listener: (connected: boolean) => void): void { this.connectionListeners.push(listener); } removeConnectionListener(listener: (connected: boolean) => void): void { this.connectionListeners = this.connectionListeners.filter((l) => l !== listener); } addDeleteMessageListener(listener: (messageId: string) => void): void { this.deleteMessageListeners.push(listener); } removeDeleteMessageListener(listener: (messageId: string) => void): void { this.deleteMessageListeners = this.deleteMessageListeners.filter(l => l !== listener); } private notifyMessageListeners(message: ChatMessage): void { this.messageListeners.forEach((listener) => listener(message)); } private notifyConnectionListeners(connected: boolean): void { this.connectionListeners.forEach((listener) => listener(connected)); } private notifyDeleteMessageListeners(messageId: string): void { this.deleteMessageListeners.forEach((listener) => listener(messageId)); } isConnected(): boolean { return this.socket !== null && this.socket.readyState === WebSocket.OPEN; } } // สร้าง instance ใหม่ทุกครั้ง let defaultWsUrl = ''; let wsServiceInstance: WebSocketService | null = null; // เพิ่มฟังก์ชันสำหรับกำหนด WebSocket URL export function setWebSocketUrl(url: string): void { defaultWsUrl = url; wsServiceInstance = null; // Reset instance when URL changes } // ฟังก์ชันสำหรับดึง WebSocketService export function getWebSocketService(url?: string): WebSocketService { if (!wsServiceInstance) { wsServiceInstance = new WebSocketService(url || defaultWsUrl); } return wsServiceInstance; }