From 6d1e06dfd606b93d029e9c9b84eea6f8b3b6294e Mon Sep 17 00:00:00 2001 From: Shahd Lala Date: Wed, 13 May 2026 00:01:26 +0200 Subject: [PATCH] fix: NCWF-1 401 (#6) Co-authored-by: shahdlala66 Co-authored-by: Lala, Shahd Reviewed-on: https://git.janis-eccarius.de/NowChess/NowChess-Frontend/pulls/6 --- src/app/pages/welcome/welcome.component.html | 8 +++++ src/app/pages/welcome/welcome.component.ts | 37 +++++++------------- src/app/services/game-stream.service.ts | 24 +++++++++---- src/app/services/stream-handler.service.ts | 25 ++++++++++--- 4 files changed, 58 insertions(+), 36 deletions(-) diff --git a/src/app/pages/welcome/welcome.component.html b/src/app/pages/welcome/welcome.component.html index e6ba5a2..e192bff 100644 --- a/src/app/pages/welcome/welcome.component.html +++ b/src/app/pages/welcome/welcome.component.html @@ -263,6 +263,14 @@ } + @if (showChallengeDialog) { +
+
+ +
+
+ } + @if (errorMessage) {

{{ errorMessage }}

} diff --git a/src/app/pages/welcome/welcome.component.ts b/src/app/pages/welcome/welcome.component.ts index 3669d3b..9a3bbaa 100644 --- a/src/app/pages/welcome/welcome.component.ts +++ b/src/app/pages/welcome/welcome.component.ts @@ -67,6 +67,7 @@ export class WelcomeComponent implements OnInit, OnDestroy { showOptionsDialog = false; showJoinDialog = false; showImportDialog = false; + showChallengeDialog = false; gameIdInput = ''; importMode: ImportMode = 'fen'; @@ -223,11 +224,21 @@ export class WelcomeComponent implements OnInit, OnDestroy { } startOneVsOne(): void { - if (!this.requireAuth(() => this.performStartOneVsOne())) { + if (!this.requireAuth(() => this.openChallengeDialog())) { return; } - this.performStartOneVsOne(); + this.openChallengeDialog(); + } + + openChallengeDialog(): void { + this.closeAllDialogs(); + this.showChallengeDialog = true; + } + + closeChallengeDialog(): void { + this.showChallengeDialog = false; + this.errorMessage = ''; } startVsBot(difficulty: Difficulty): void { @@ -352,28 +363,6 @@ export class WelcomeComponent implements OnInit, OnDestroy { action(); } - private performStartOneVsOne(): void { - if (this.creating) { - return; - } - - this.errorMessage = ''; - this.creating = true; - - this.gameApi - .createGame() - .pipe(finalize(() => (this.creating = false))) - .subscribe({ - next: (game) => { - void this.router.navigate(['/game', game.gameId], { - state: { theme: this.isSunsetMode ? 'light' : 'dark' } - }); - }, - error: (error) => { - this.errorMessage = getErrorMessage(error, 'Unable to create a game.'); - } - }); - } private performStartVsBot(difficulty: Difficulty): void { if (this.creating) { diff --git a/src/app/services/game-stream.service.ts b/src/app/services/game-stream.service.ts index 9ed823b..2c19265 100644 --- a/src/app/services/game-stream.service.ts +++ b/src/app/services/game-stream.service.ts @@ -10,6 +10,7 @@ export class GameStreamService { private readonly destroyRef = inject(DestroyRef); private streamSubscription: Subscription | null = null; private pollSubscription: Subscription | null = null; + private lastGameStateHash: string | null = null; startStreaming( gameId: string, @@ -20,7 +21,10 @@ export class GameStreamService { .streamGame(gameId) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ - next: (event) => onEvent(event), + next: (event) => { + this.lastGameStateHash = JSON.stringify(event); + onEvent(event); + }, error: () => { onStreamError(); this.startPolling(gameId, onEvent); @@ -37,7 +41,7 @@ export class GameStreamService { return; } - this.pollSubscription = interval(1500) + this.pollSubscription = interval(5000) .pipe( startWith(0), switchMap(() => this.gameApi.getGame(gameId)), @@ -45,11 +49,16 @@ export class GameStreamService { ) .subscribe({ next: (game) => { - const event: GameStreamEvent = { - type: 'gameFull', - game - }; - onEvent(event); + // Only emit if game state changed to avoid unnecessary updates + const stateHash = JSON.stringify(game.state); + if (this.lastGameStateHash !== stateHash) { + this.lastGameStateHash = stateHash; + const event: GameStreamEvent = { + type: 'gameFull', + game + }; + onEvent(event); + } } }); } @@ -59,5 +68,6 @@ export class GameStreamService { this.pollSubscription?.unsubscribe(); this.streamSubscription = null; this.pollSubscription = null; + this.lastGameStateHash = null; } } diff --git a/src/app/services/stream-handler.service.ts b/src/app/services/stream-handler.service.ts index 7a9db6d..c41bde6 100644 --- a/src/app/services/stream-handler.service.ts +++ b/src/app/services/stream-handler.service.ts @@ -84,8 +84,19 @@ export class StreamHandlerService { } }; + // Set timeout to fallback if WebSocket doesn't connect quickly + const connectionTimeoutId = setTimeout(() => { + if (!connected && !fallbackActive) { + console.warn(`[StreamHandler] WebSocket timeout for ${gameId}, attempting NDJSON fallback`); + ws.close(); + void startNdjsonFallback(); + } + }, 3000); + ws.onopen = () => { connected = true; + clearTimeout(connectionTimeoutId); + console.log(`[StreamHandler] WebSocket connected for ${gameId}`); }; ws.onmessage = (message) => { @@ -97,19 +108,23 @@ export class StreamHandlerService { }; ws.onerror = (error) => { - console.warn(`[StreamHandler] WebSocket error for ${gameId}, attempting NDJSON fallback:`, error); - if (!connected) { + console.warn(`[StreamHandler] WebSocket error for ${gameId}:`, error); + clearTimeout(connectionTimeoutId); + if (!connected && !fallbackActive) { void startNdjsonFallback(); } }; ws.onclose = () => { + clearTimeout(connectionTimeoutId); console.warn(`[StreamHandler] WebSocket closed for ${gameId}, connected=${connected}`); - if (!connected) { + if (connected) { + // Connection was established but closed, stream is complete + observer.complete(); + } else if (!fallbackActive) { + // Connection never established, try fallback console.log(`[StreamHandler] Starting NDJSON fallback for ${gameId}`); void startNdjsonFallback(); - } else { - observer.complete(); } };