fix: NCWF-2 bugs and desing fixes (#7)
Co-authored-by: Lala, Shahd <Shahd.Lala@sybit.de> Reviewed-on: #7
This commit was merged in pull request #7.
This commit is contained in:
@@ -1,135 +1,115 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Router } from '@angular/router';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { ChallengeEventService } from './challenge-event.service';
|
||||
import { Challenge } from '../models/challenge.models';
|
||||
import { ChallengeService } from './challenge.service';
|
||||
|
||||
/**
|
||||
* 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 readonly challengeService = inject(ChallengeService);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
private ws: WebSocket | null = null;
|
||||
private reconnectAttempts = 0;
|
||||
private readonly maxReconnectAttempts = 5;
|
||||
private readonly reconnectDelay = 3000;
|
||||
private intentionalClose = false;
|
||||
|
||||
/**
|
||||
* Initialize WebSocket connection for challenge events
|
||||
*/
|
||||
connect(): void {
|
||||
if (this.ws) {
|
||||
return; // Already connected
|
||||
}
|
||||
if (this.ws) return;
|
||||
|
||||
const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const wsUrl = `${wsProtocol}://${window.location.host}/ws/challenges`;
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
|
||||
const url = `${environment.userWsBaseUrl}/api/user/ws?token=${encodeURIComponent(token)}`;
|
||||
|
||||
try {
|
||||
this.ws = new WebSocket(wsUrl);
|
||||
this.intentionalClose = false;
|
||||
this.ws = new WebSocket(url);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
console.log('Challenge WebSocket connected');
|
||||
this.reconnectAttempts = 0;
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
this.handleMessage(event.data);
|
||||
this.handleMessage(event.data as string);
|
||||
};
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('Challenge WebSocket error:', error);
|
||||
this.ws.onerror = () => {
|
||||
// onclose fires right after, handles reconnect
|
||||
};
|
||||
|
||||
this.ws.onclose = () => {
|
||||
console.log('Challenge WebSocket disconnected');
|
||||
this.ws = null;
|
||||
this.attemptReconnect();
|
||||
if (!this.intentionalClose) {
|
||||
this.attemptReconnect();
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to create WebSocket:', error);
|
||||
} catch {
|
||||
this.attemptReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the WebSocket connection
|
||||
*/
|
||||
disconnect(): void {
|
||||
this.intentionalClose = true;
|
||||
this.reconnectAttempts = 0;
|
||||
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 {
|
||||
let message: Record<string, unknown>;
|
||||
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');
|
||||
message = JSON.parse(data) as Record<string, unknown>;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
this.reconnectAttempts++;
|
||||
console.log(`Attempting WebSocket reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
|
||||
switch (message['type']) {
|
||||
case 'CONNECTED':
|
||||
break;
|
||||
|
||||
setTimeout(() => {
|
||||
this.connect();
|
||||
}, this.reconnectDelay);
|
||||
case 'challengeCreated': {
|
||||
const challengeId = message['challengeId'] as string | undefined;
|
||||
if (challengeId) {
|
||||
this.challengeService.getChallenge(challengeId).subscribe({
|
||||
next: challenge => this.challengeEventService.onChallengeReceived(challenge),
|
||||
error: () => { /* challenge may have already expired */ }
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'challengeAccepted': {
|
||||
const challengeId = message['challengeId'] as string | undefined;
|
||||
const gameId = message['gameId'] as string | undefined;
|
||||
if (challengeId) {
|
||||
this.challengeEventService.removeChallenge(challengeId);
|
||||
}
|
||||
if (gameId) {
|
||||
void this.router.navigate(['/game', gameId]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'challengeDeclined':
|
||||
case 'challengeExpired':
|
||||
case 'challengeCancelled': {
|
||||
const challengeId = message['challengeId'] as string | undefined;
|
||||
if (challengeId) {
|
||||
this.challengeEventService.removeChallenge(challengeId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private attemptReconnect(): void {
|
||||
if (this.intentionalClose || this.reconnectAttempts >= this.maxReconnectAttempts) return;
|
||||
this.reconnectAttempts++;
|
||||
setTimeout(() => { this.connect(); }, this.reconnectDelay);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user