import { Injectable, inject } from '@angular/core'; import { Subject } from 'rxjs'; import { ChallengeEventService } from './challenge-event.service'; import { Challenge } from '../models/challenge.models'; /** * Service to handle WebSocket connections for challenge events * Listens for incoming challenge notifications and emits them to ChallengeEventService */ @Injectable({ providedIn: 'root' }) export class ChallengeWebSocketService { private readonly challengeEventService = inject(ChallengeEventService); private ws: WebSocket | null = null; private reconnectAttempts = 0; private readonly maxReconnectAttempts = 5; private readonly reconnectDelay = 3000; /** * Initialize WebSocket connection for challenge events */ connect(): void { if (this.ws) { return; // Already connected } const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; const wsUrl = `${wsProtocol}://${window.location.host}/ws/challenges`; try { this.ws = new WebSocket(wsUrl); this.ws.onopen = () => { console.log('Challenge WebSocket connected'); this.reconnectAttempts = 0; }; this.ws.onmessage = (event) => { this.handleMessage(event.data); }; this.ws.onerror = (error) => { console.error('Challenge WebSocket error:', error); }; this.ws.onclose = () => { console.log('Challenge WebSocket disconnected'); this.ws = null; this.attemptReconnect(); }; } catch (error) { console.error('Failed to create WebSocket:', error); this.attemptReconnect(); } } /** * Close the WebSocket connection */ disconnect(): void { if (this.ws) { this.ws.close(); this.ws = null; } } /** * Send a message through WebSocket */ send(message: any): void { if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(message)); } } /** * Handle incoming WebSocket messages */ private handleMessage(data: string): void { try { const message = JSON.parse(data); if (!message.type) { return; } switch (message.type) { case 'challenge.received': if (message.challenge) { this.challengeEventService.onChallengeReceived(message.challenge as Challenge); } break; case 'challenge.accepted': if (message.challenge) { this.challengeEventService.onChallengeAccepted(message.challenge as Challenge); } break; case 'challenge.declined': if (message.challengeId) { this.challengeEventService.removeChallenge(message.challengeId); } break; case 'challenge.expired': if (message.challengeId) { this.challengeEventService.removeChallenge(message.challengeId); } break; default: console.debug('Unknown challenge message type:', message.type); } } catch (error) { console.error('Failed to parse WebSocket message:', error); } } /** * Attempt to reconnect to WebSocket */ private attemptReconnect(): void { if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error('Max WebSocket reconnection attempts reached'); return; } this.reconnectAttempts++; console.log(`Attempting WebSocket reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`); setTimeout(() => { this.connect(); }, this.reconnectDelay); } }