c02414ea40
Co-authored-by: Lala, Shahd <Shahd.Lala@sybit.de> Reviewed-on: #7
116 lines
3.7 KiB
TypeScript
116 lines
3.7 KiB
TypeScript
import { Injectable, inject } from '@angular/core';
|
|
import { Router } from '@angular/router';
|
|
import { environment } from '../../environments/environment';
|
|
import { ChallengeEventService } from './challenge-event.service';
|
|
import { ChallengeService } from './challenge.service';
|
|
|
|
@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;
|
|
|
|
connect(): void {
|
|
if (this.ws) return;
|
|
|
|
const token = localStorage.getItem('token');
|
|
if (!token) return;
|
|
|
|
const url = `${environment.userWsBaseUrl}/api/user/ws?token=${encodeURIComponent(token)}`;
|
|
|
|
try {
|
|
this.intentionalClose = false;
|
|
this.ws = new WebSocket(url);
|
|
|
|
this.ws.onopen = () => {
|
|
this.reconnectAttempts = 0;
|
|
};
|
|
|
|
this.ws.onmessage = (event) => {
|
|
this.handleMessage(event.data as string);
|
|
};
|
|
|
|
this.ws.onerror = () => {
|
|
// onclose fires right after, handles reconnect
|
|
};
|
|
|
|
this.ws.onclose = () => {
|
|
this.ws = null;
|
|
if (!this.intentionalClose) {
|
|
this.attemptReconnect();
|
|
}
|
|
};
|
|
} catch {
|
|
this.attemptReconnect();
|
|
}
|
|
}
|
|
|
|
disconnect(): void {
|
|
this.intentionalClose = true;
|
|
this.reconnectAttempts = 0;
|
|
if (this.ws) {
|
|
this.ws.close();
|
|
this.ws = null;
|
|
}
|
|
}
|
|
|
|
private handleMessage(data: string): void {
|
|
let message: Record<string, unknown>;
|
|
try {
|
|
message = JSON.parse(data) as Record<string, unknown>;
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
switch (message['type']) {
|
|
case 'CONNECTED':
|
|
break;
|
|
|
|
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);
|
|
}
|
|
}
|