| |
| |
| |
| |
| |
| |
| |
| |
| |
| class WSClient { |
| constructor() { |
| this.socket = null; |
| this.status = 'disconnected'; |
| this.statusSubscribers = new Set(); |
| this.globalSubscribers = new Set(); |
| this.typeSubscribers = new Map(); |
| this.eventLog = []; |
| this.backoff = 1000; |
| this.maxBackoff = 16000; |
| this.shouldReconnect = true; |
| this.isOptional = true; |
| } |
|
|
| get url() { |
| const { protocol, host } = window.location; |
| const wsProtocol = protocol === 'https:' ? 'wss:' : 'ws:'; |
| |
| return `${wsProtocol}//${host}/ws`; |
| } |
|
|
| logEvent(event) { |
| const entry = { ...event, time: new Date().toISOString() }; |
| this.eventLog.push(entry); |
| this.eventLog = this.eventLog.slice(-100); |
| } |
|
|
| onStatusChange(callback) { |
| this.statusSubscribers.add(callback); |
| callback(this.status); |
| return () => this.statusSubscribers.delete(callback); |
| } |
|
|
| onMessage(callback) { |
| this.globalSubscribers.add(callback); |
| return () => this.globalSubscribers.delete(callback); |
| } |
|
|
| subscribe(type, callback) { |
| if (!this.typeSubscribers.has(type)) { |
| this.typeSubscribers.set(type, new Set()); |
| } |
| const set = this.typeSubscribers.get(type); |
| set.add(callback); |
| return () => set.delete(callback); |
| } |
|
|
| updateStatus(newStatus) { |
| this.status = newStatus; |
| this.statusSubscribers.forEach((cb) => cb(newStatus)); |
| } |
|
|
| |
| |
| |
| |
| |
| connect() { |
| if (this.socket && (this.status === 'connecting' || this.status === 'connected')) { |
| return; |
| } |
|
|
| console.log('[WebSocket] Attempting optional WebSocket connection (HTTP endpoints are recommended)'); |
| this.updateStatus('connecting'); |
| this.socket = new WebSocket(this.url); |
| this.logEvent({ type: 'status', status: 'connecting', note: 'optional' }); |
|
|
| this.socket.addEventListener('open', () => { |
| this.backoff = 1000; |
| this.updateStatus('connected'); |
| this.logEvent({ type: 'status', status: 'connected' }); |
| }); |
|
|
| this.socket.addEventListener('message', (event) => { |
| try { |
| const data = JSON.parse(event.data); |
| this.logEvent({ type: 'message', messageType: data.type || 'unknown' }); |
| this.globalSubscribers.forEach((cb) => cb(data)); |
| if (data.type && this.typeSubscribers.has(data.type)) { |
| this.typeSubscribers.get(data.type).forEach((cb) => cb(data)); |
| } |
| } catch (error) { |
| console.error('WS message parse error', error); |
| } |
| }); |
|
|
| this.socket.addEventListener('close', () => { |
| this.updateStatus('disconnected'); |
| this.logEvent({ type: 'status', status: 'disconnected', note: 'optional - use HTTP if needed' }); |
| |
| |
| if (this.shouldReconnect && this.backoff < this.maxBackoff) { |
| const delay = this.backoff; |
| this.backoff = Math.min(this.backoff * 2, this.maxBackoff); |
| console.log(`[WebSocket] Optional reconnection in ${delay}ms (or use HTTP endpoints)`); |
| setTimeout(() => this.connect(), delay); |
| } else if (this.shouldReconnect) { |
| console.log('[WebSocket] Max reconnection attempts reached. Use HTTP endpoints instead.'); |
| } |
| }); |
|
|
| this.socket.addEventListener('error', (error) => { |
| console.warn('[WebSocket] Optional WebSocket error (non-critical):', error); |
| console.info('[WebSocket] Tip: Use HTTP REST API endpoints instead - they work perfectly'); |
| this.logEvent({ |
| type: 'error', |
| details: error.message || 'unknown', |
| timestamp: new Date().toISOString(), |
| note: 'optional - HTTP endpoints available' |
| }); |
| this.updateStatus('error'); |
| |
| |
| |
| }); |
| } |
|
|
| disconnect() { |
| this.shouldReconnect = false; |
| if (this.socket) { |
| this.socket.close(); |
| } |
| } |
|
|
| getEvents() { |
| return [...this.eventLog]; |
| } |
| } |
|
|
| const wsClient = new WSClient(); |
| export default wsClient; |
|
|