diff --git a/app/src/app.service.ts b/app/src/app.service.ts index eb7a29f..f1ccff9 100644 --- a/app/src/app.service.ts +++ b/app/src/app.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, UseFilters } from '@nestjs/common'; import { ConnectedSocket, OnGatewayConnection, @@ -10,6 +10,8 @@ import { PlayerService } from './players/player.service'; import { GameService } from 'src/games/game.service'; import { Socket } from 'socket.io'; import { ClientEvent, CreateLobbyEvent, JoinLobbyEvent } from 'interface'; +import { WsExceptionFilter } from './websocket-exception-filter'; +import { PlayerNotFoundException } from './exceptions'; @WebSocketGateway({ cors: true }) @Injectable() @@ -24,6 +26,7 @@ export class AppService implements OnGatewayConnection { this.playerService.createPlayer(client.id); } + @UseFilters(new WsExceptionFilter([PlayerNotFoundException])) @SubscribeMessage(ClientEvent.CREATE_LOBBY) handleCreateLobby( @ConnectedSocket() client: Socket, diff --git a/app/src/exceptions.ts b/app/src/exceptions.ts new file mode 100644 index 0000000..0fb2095 --- /dev/null +++ b/app/src/exceptions.ts @@ -0,0 +1,29 @@ +import { WsException } from '@nestjs/websockets'; +import { ErrorCode } from 'interface'; + +export abstract class WebSocketException extends WsException { + public readonly errorCode: ErrorCode; + + constructor(message: string, errorCode: ErrorCode) { + super(message); + this.errorCode = errorCode; + } +} + +export class PlayerNotFoundException extends WebSocketException { + constructor(playerId: string) { + super( + 'Unable to find player from ID: ' + playerId, + ErrorCode.PLAYER_NOT_FOUND, + ); + } +} + +export class GameNotFoundException extends WebSocketException { + constructor(gameId: string) { + super( + 'Unable to find game from ID: ' + gameId, + ErrorCode.GAME_NOT_FOUND, + ); + } +} diff --git a/app/src/players/player.service.ts b/app/src/players/player.service.ts index 46cf383..5aa1594 100644 --- a/app/src/players/player.service.ts +++ b/app/src/players/player.service.ts @@ -1,5 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { Player } from './player'; +import { PlayerNotFoundException } from 'src/exceptions'; @Injectable() export class PlayerService { @@ -17,6 +18,10 @@ export class PlayerService { getPlayer(socketId: string) { this.logger.log(this.players.get(socketId).userName); + const player = this.players.get(socketId); + if (!player) { + throw new PlayerNotFoundException(socketId); + } return this.players.get(socketId); } } diff --git a/app/src/websocket-exception-filter.ts b/app/src/websocket-exception-filter.ts new file mode 100644 index 0000000..a1535d1 --- /dev/null +++ b/app/src/websocket-exception-filter.ts @@ -0,0 +1,44 @@ +import { ArgumentsHost, Catch, Logger } from '@nestjs/common'; +import { BaseWsExceptionFilter } from '@nestjs/websockets'; +import { GameNotFoundException, PlayerNotFoundException, WebSocketException } from './exceptions'; +import { Socket } from 'socket.io'; +import { ClientEvent } from 'interface'; +import { emitCreateLobbyError } from 'interface'; + +@Catch(PlayerNotFoundException, GameNotFoundException) +export class WsExceptionFilter extends BaseWsExceptionFilter { + private readonly logger = new Logger(BaseWsExceptionFilter.name); + private readonly caughtExceptions: (typeof WebSocketException)[]; + + constructor(caughtExceptions: (typeof WebSocketException)[]) { + super(); + this.caughtExceptions = caughtExceptions; + } + + catch(exception: WebSocketException, host: ArgumentsHost) { + const socket = host.switchToWs().getClient() as Socket; + const method = host.switchToWs().getPattern(); + const data: object = host.switchToWs().getData(); + this.logger.log( + `Caught exception: ${exception} + for request to ${method} + from client ${socket.id} + with input ${data}`, + ); + + if ( + !this.caughtExceptions.find( + (caughtException) => exception instanceof caughtException, + ) + ) { + this.logger.fatal("Uncaught exception", exception) + super.catch(exception, host); + } + + switch (method) { + case ClientEvent.CREATE_LOBBY: + emitCreateLobbyError(socket, { error: exception.errorCode }); + break; + } + } +} diff --git a/interface/index.ts b/interface/index.ts index 11effb9..9b19a04 100644 --- a/interface/index.ts +++ b/interface/index.ts @@ -1,4 +1,5 @@ export * from "./constants/TrackType"; +export * from "./server-events/CreateLobbyError"; export * from "./server-events/ServerError"; export * from "./server-events/ServerEvent"; export * from "./server-events/UpdateLobbyEvent"; diff --git a/interface/server-events/CreateLobbyError.ts b/interface/server-events/CreateLobbyError.ts new file mode 100644 index 0000000..d488e52 --- /dev/null +++ b/interface/server-events/CreateLobbyError.ts @@ -0,0 +1,22 @@ +import { Socket as ServerSocket } from "socket.io"; +import { Socket as ClientSocket } from "socket.io-client"; +import { ErrorCode, ServerError } from "./ServerError"; + +export type CreateLobbyError = { + error: ErrorCode.PLAYER_NOT_FOUND | ErrorCode.GAME_NOT_FOUND; +}; + +export const emitCreateLobbyError = ( + socket: ServerSocket, + payload: CreateLobbyError, +) => { + socket.emit(ServerError.CREATE_LOBBY_ERROR, payload); +}; + +export const attachHandlerToCreateLobbyError = ( + socket: ClientSocket, + handler: (payload: CreateLobbyError) => void, +): (() => void) => { + socket.on(ServerError.CREATE_LOBBY_ERROR, handler); + return () => socket.off(ServerError.CREATE_LOBBY_ERROR); +}; diff --git a/interface/server-events/ServerError.ts b/interface/server-events/ServerError.ts index 12c0b96..adb14fd 100644 --- a/interface/server-events/ServerError.ts +++ b/interface/server-events/ServerError.ts @@ -2,3 +2,8 @@ export enum ServerError { CREATE_LOBBY_ERROR = "create-lobby-error", JOIN_LOBBY_ERROR = "join-lobby-error", } + +export enum ErrorCode { + PLAYER_NOT_FOUND = "player-not-found", + GAME_NOT_FOUND = "game-not-found", +} diff --git a/web/src/index.tsx b/web/src/index.tsx index 74b954e..1c80ced 100644 --- a/web/src/index.tsx +++ b/web/src/index.tsx @@ -8,6 +8,7 @@ const root = ReactDOM.createRoot( document.getElementById("root") as HTMLElement, ); const socket = io("http://localhost:3010"); +socket.on("exception", console.log); root.render(