Skip to content

Commit

Permalink
refactor(api): handle error cases
Browse files Browse the repository at this point in the history
  • Loading branch information
Neosoulink committed Nov 25, 2024
1 parent 948287e commit 1880827
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 32 deletions.
51 changes: 31 additions & 20 deletions apps/api/src/players/gateways/players.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
SOCKET_MOVE_PERFORMED_TOKEN,
type GameUpdatedPayload
} from "@chess-d/shared";
import { validateFen } from "chess.js";

@WebSocketGateway({
cors: {
Expand All @@ -33,21 +32,25 @@ export class PlayersGateway

constructor(private readonly playersService: PlayersService) {}

handleConnection(@ConnectedSocket() socket: Socket) {
private handleError(socket: Socket, error: Error): void {
this.handleDisconnect(socket);

this.server.to(socket.id).emit("error", {
message: error.message,
cause: error.cause
});

console.warn("Error occurred:", error.message, `<${error.cause}>`);
}

handleConnection(@ConnectedSocket() socket: Socket): void {
const data = this.playersService.register(socket);
if (data instanceof Error) {
this.server
.to(socket.id)
.emit("error", { message: data.message, cause: data.cause });
socket.disconnect();
console.log(`Auto player "${socket.id}" disconnection:`, data.message);
return;
}
if (data instanceof Error) return this.handleError(socket, data);

const { player, roomID, room } = data;

console.log(
"=========/ Player connected /=========\n",
"\n=========/ Player connected /=========\n",
player,
`\n\nRoom "${roomID}" has ${room?.players.length} player(s).`,
`\nGame status "${room.fen}".`,
Expand All @@ -63,10 +66,16 @@ export class PlayersGateway
);
}

handleDisconnect(socket: Socket) {
const { player, room } = this.playersService.unregister(socket);
handleDisconnect(socket: Socket): void {
const unregisterRes = this.playersService.unregister(socket);

if (unregisterRes instanceof Error) return;

const { player, roomID, room } = unregisterRes;

this.server.to(player.id).disconnectSockets();
console.log(
`\nPlayer left.\nID: ${player?.id}\nTotal in rooms: ${room?.players.length ?? 0}\n`
`\nPlayer "${player?.id}" left room "${roomID}".\nTotal in rooms: ${room?.players.length ?? 0}`
);
}

Expand All @@ -75,12 +84,14 @@ export class PlayersGateway
@ConnectedSocket() socket: Socket,
@MessageBody() payload: GameUpdatedPayload
): void {
console.log("Move performed by", socket.id, payload);
const data = this.playersService.handleMove(socket, payload.move);

if (this.playersService.handleMove(socket, payload.move))
this.server
.in(socket.data?.roomID)
.except(socket.id)
.emit(SOCKET_MOVE_PERFORMED_TOKEN, payload);
if (data instanceof Error) return this.handleError(socket, data);

console.log("\nMove performed by", socket.id, payload);
this.server
.in(socket.data?.roomID)
.except(socket.id)
.emit(SOCKET_MOVE_PERFORMED_TOKEN, payload);
}
}
47 changes: 38 additions & 9 deletions apps/api/src/players/services/players.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
ColorSide,
DEFAULT_FEN,
getOppositeColorSide,
PlayerEntity
PlayerEntity,
SocketAuthInterface
} from "@chess-d/shared";
import { randomUUID, UUID } from "crypto";
import { validateFen, type Move } from "chess.js";
Expand All @@ -23,15 +24,23 @@ export class PlayersService {
room: PlayersService["rooms"][UUID];
}
| Error {
const { roomID: queryRoomID, side: colorSide } = socket.handshake.auth;
const {
roomID: queryRoomID,
side: colorSide,
fen: authFen
} = socket.handshake.auth as SocketAuthInterface;
const fen =
typeof authFen === "string" && validateFen(authFen)
? authFen
: DEFAULT_FEN;

let roomID: UUID | undefined;

if (
typeof queryRoomID === "string" &&
!Array.isArray(this.rooms[queryRoomID]?.players)
)
return new Error("Invalid room ID.");
return new Error("Invalid room ID.", { cause: "ROOM_NOT_FOUND" });

const player: PlayerEntity = {
id: socket.id,
Expand All @@ -42,7 +51,13 @@ export class PlayersService {

if (typeof queryRoomID === "string") {
if (this.rooms[queryRoomID]?.players?.length !== 1)
return new Error("Unable to join a room without a player or full.");
return new Error("Unable to join a room without a player or full.", {
cause: this.rooms[queryRoomID]
? this.rooms[queryRoomID].players.length > 1
? "ROOM_FULL"
: "ROOM_EMPTY"
: "ROOM_NOT_FOUND"
});

player.color = getOppositeColorSide(
this.rooms[queryRoomID].players[0].color
Expand All @@ -55,17 +70,24 @@ export class PlayersService {

if (!roomID) {
roomID = randomUUID();
this.rooms[roomID] = { fen: DEFAULT_FEN, players: [player] };
this.rooms[roomID] = { fen, players: [player] };
}

socket.data = { roomID };
return { player, roomID, room: this.rooms[roomID] };
}

unregister(socket: Socket) {
unregister(socket: Socket):
| {
roomID: string;
room: PlayersService["rooms"][UUID];
player: PlayerEntity;
}
| Error {
const { roomID } = socket.data;
const room = this.rooms[roomID];
if (!room) return {};

if (!room) return new Error("Room not found.", { cause: "ROOM_NOT_FOUND" });

let player: PlayerEntity | undefined;

Expand All @@ -87,11 +109,18 @@ export class PlayersService {
return { roomID, room, player };
}

handleMove(socket: Socket, move?: Move): string | null {
handleMove(socket: Socket, move?: Move): string | Error {
if (typeof move?.after !== "string" || !validateFen(move.after))
return null;
return new Error("Invalid move.", { cause: "INVALID_MOVE" });

const roomID = socket.data?.roomID;
const room = this.rooms[roomID];

if (!room || room.fen !== move.before)
return new Error("Move desynchronized with the room fen.", {
cause: "DESYNCHRONIZED"
});

this.rooms[roomID].fen = move.after;

return this.rooms[roomID].fen;
Expand Down
10 changes: 7 additions & 3 deletions apps/web/src/shared/hooks/use-socket.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { io, Socket } from "socket.io-client";
import {
ColorSide,
DEFAULT_FEN,
PlayerEntity,
SOCKET_MOVE_PERFORMED_TOKEN
SOCKET_MOVE_PERFORMED_TOKEN,
SocketAuthInterface
} from "@chess-d/shared";

export const useSocket = (): {
Expand Down Expand Up @@ -84,8 +86,10 @@ export const useSocket = (): {
]);

socket.auth = {
roomID: new URLSearchParams(location.search).get("roomID")
};
roomID: new URLSearchParams(location.search).get("roomID"),
side: ColorSide.black,
fen: DEFAULT_FEN
} satisfies SocketAuthInterface;

return {
socket,
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./board.interface";
export * from "./chess.interface";
export * from "./reactive.interface";
export * from "./socket.interface";
7 changes: 7 additions & 0 deletions packages/shared/src/interfaces/socket.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ColorSide } from "src/enums";

export interface SocketAuthInterface {
roomID?: string | null;
side?: ColorSide | null;
fen?: string | null;
}

0 comments on commit 1880827

Please sign in to comment.