fix: NCWF-1 401 (#6)

Co-authored-by: shahdlala66 <shahd.lala66@gmail.com>
Co-authored-by: Lala, Shahd <Shahd.Lala@sybit.de>
Reviewed-on: #6
This commit was merged in pull request #6.
This commit is contained in:
2026-05-13 00:01:26 +02:00
parent ac4fe8b005
commit 6d1e06dfd6
4 changed files with 58 additions and 36 deletions
@@ -263,6 +263,14 @@
</div> </div>
} }
@if (showChallengeDialog) {
<div class="dialog-overlay" (click)="closeChallengeDialog()">
<div class="dialog-card" (click)="$event.stopPropagation()">
<app-challenge-create-dialog (closeChallengeDialog)="closeChallengeDialog()"></app-challenge-create-dialog>
</div>
</div>
}
@if (errorMessage) { @if (errorMessage) {
<p class="error-banner">{{ errorMessage }}</p> <p class="error-banner">{{ errorMessage }}</p>
} }
+13 -24
View File
@@ -67,6 +67,7 @@ export class WelcomeComponent implements OnInit, OnDestroy {
showOptionsDialog = false; showOptionsDialog = false;
showJoinDialog = false; showJoinDialog = false;
showImportDialog = false; showImportDialog = false;
showChallengeDialog = false;
gameIdInput = ''; gameIdInput = '';
importMode: ImportMode = 'fen'; importMode: ImportMode = 'fen';
@@ -223,11 +224,21 @@ export class WelcomeComponent implements OnInit, OnDestroy {
} }
startOneVsOne(): void { startOneVsOne(): void {
if (!this.requireAuth(() => this.performStartOneVsOne())) { if (!this.requireAuth(() => this.openChallengeDialog())) {
return; return;
} }
this.performStartOneVsOne(); this.openChallengeDialog();
}
openChallengeDialog(): void {
this.closeAllDialogs();
this.showChallengeDialog = true;
}
closeChallengeDialog(): void {
this.showChallengeDialog = false;
this.errorMessage = '';
} }
startVsBot(difficulty: Difficulty): void { startVsBot(difficulty: Difficulty): void {
@@ -352,28 +363,6 @@ export class WelcomeComponent implements OnInit, OnDestroy {
action(); 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 { private performStartVsBot(difficulty: Difficulty): void {
if (this.creating) { if (this.creating) {
+17 -7
View File
@@ -10,6 +10,7 @@ export class GameStreamService {
private readonly destroyRef = inject(DestroyRef); private readonly destroyRef = inject(DestroyRef);
private streamSubscription: Subscription | null = null; private streamSubscription: Subscription | null = null;
private pollSubscription: Subscription | null = null; private pollSubscription: Subscription | null = null;
private lastGameStateHash: string | null = null;
startStreaming( startStreaming(
gameId: string, gameId: string,
@@ -20,7 +21,10 @@ export class GameStreamService {
.streamGame(gameId) .streamGame(gameId)
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({ .subscribe({
next: (event) => onEvent(event), next: (event) => {
this.lastGameStateHash = JSON.stringify(event);
onEvent(event);
},
error: () => { error: () => {
onStreamError(); onStreamError();
this.startPolling(gameId, onEvent); this.startPolling(gameId, onEvent);
@@ -37,7 +41,7 @@ export class GameStreamService {
return; return;
} }
this.pollSubscription = interval(1500) this.pollSubscription = interval(5000)
.pipe( .pipe(
startWith(0), startWith(0),
switchMap(() => this.gameApi.getGame(gameId)), switchMap(() => this.gameApi.getGame(gameId)),
@@ -45,11 +49,16 @@ export class GameStreamService {
) )
.subscribe({ .subscribe({
next: (game) => { next: (game) => {
const event: GameStreamEvent = { // Only emit if game state changed to avoid unnecessary updates
type: 'gameFull', const stateHash = JSON.stringify(game.state);
game if (this.lastGameStateHash !== stateHash) {
}; this.lastGameStateHash = stateHash;
onEvent(event); const event: GameStreamEvent = {
type: 'gameFull',
game
};
onEvent(event);
}
} }
}); });
} }
@@ -59,5 +68,6 @@ export class GameStreamService {
this.pollSubscription?.unsubscribe(); this.pollSubscription?.unsubscribe();
this.streamSubscription = null; this.streamSubscription = null;
this.pollSubscription = null; this.pollSubscription = null;
this.lastGameStateHash = null;
} }
} }
+20 -5
View File
@@ -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 = () => { ws.onopen = () => {
connected = true; connected = true;
clearTimeout(connectionTimeoutId);
console.log(`[StreamHandler] WebSocket connected for ${gameId}`);
}; };
ws.onmessage = (message) => { ws.onmessage = (message) => {
@@ -97,19 +108,23 @@ export class StreamHandlerService {
}; };
ws.onerror = (error) => { ws.onerror = (error) => {
console.warn(`[StreamHandler] WebSocket error for ${gameId}, attempting NDJSON fallback:`, error); console.warn(`[StreamHandler] WebSocket error for ${gameId}:`, error);
if (!connected) { clearTimeout(connectionTimeoutId);
if (!connected && !fallbackActive) {
void startNdjsonFallback(); void startNdjsonFallback();
} }
}; };
ws.onclose = () => { ws.onclose = () => {
clearTimeout(connectionTimeoutId);
console.warn(`[StreamHandler] WebSocket closed for ${gameId}, connected=${connected}`); 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}`); console.log(`[StreamHandler] Starting NDJSON fallback for ${gameId}`);
void startNdjsonFallback(); void startNdjsonFallback();
} else {
observer.complete();
} }
}; };