Compare commits

..

31 Commits

Author SHA1 Message Date
MiguelMLorente 10949f30df Use server response to consume dice as defined by the backend 2025-02-09 15:54:08 +01:00
MiguelMLorente 09bd13f772 Create page transition from lobby to in-game 2025-02-09 15:54:08 +01:00
MiguelMLorente 0b5684f2b9 Fix commit and reset actions crossing pointers by creating an action stack (will be used to send data to backend) 2025-02-09 15:54:08 +01:00
MiguelMLorente 7c4a6f5603 Fix commit and reset actions crossing pointers by creating an action stack (will be used to send data to backend) 2025-02-09 15:52:36 +01:00
MiguelMLorente 0a0ca65e3b Introduce clean and commit action buttons 2025-02-09 15:52:04 +01:00
MiguelMLorente 86d5137857 Implement special dice usage and layout 2025-02-09 15:52:04 +01:00
MiguelMLorente b72a74789c Add icons for special cells and fix piece placement issues 2025-02-09 15:52:04 +01:00
MiguelMLorente 45cbf885a3 Merge pull request 'lobby-page-layout: Crate base lobby page layout' (#11) from lobby-page-layout into main
Reviewed-on: #11
2025-02-09 13:49:33 +00:00
MiguelMLorente 00c365de84 Add transition to lobby page 2025-02-09 14:38:08 +01:00
MiguelMLorente 4f9950c6a9 Create landing page base layout 2025-02-08 16:23:55 +01:00
MiguelMLorente 98e62180dc Merge branch 'landing-page-layout' 2024-12-04 23:13:27 +01:00
MiguelMLorente 3af9caff9a Merge pull request 'Enable piece placement in game board' (#8) from game-page into main
Reviewed-on: #8
2024-12-03 22:45:47 +00:00
MiguelMLorente 5fc02aa6a6 Enable pieces rotation in dice set and board 2024-12-03 23:03:28 +01:00
MiguelMLorente 035b7da335 Disallow piece placement when piece is not connected to adjacent piece or exit 2024-12-03 18:09:04 +01:00
MiguelMLorente b1d57ebfcd Disallow placing piece in places with invalid adjacent connections 2024-12-03 13:46:52 +01:00
MiguelMLorente 1ab3db8f67 Enable placing pieces in the game board 2024-12-03 12:29:10 +01:00
MiguelMLorente e7beb85302 Merge pull request 'Include dice views, images, and supporting types' (#7) from game-page into main
Reviewed-on: #7
2024-12-01 21:22:10 +00:00
MiguelMLorente 835ce2c62a Set up dice view from modelled pieces 2024-12-01 22:19:42 +01:00
MiguelMLorente 39d1ec9a77 Add the remaining pieces to the set 2024-12-01 16:28:17 +01:00
MiguelMLorente 406e4d2f75 Re-structure pieces builder and add include first pieces batch 2024-12-01 12:30:59 +01:00
MiguelMLorente 7354586ef5 Created piece and placed piece abstractions 2024-11-30 22:31:16 +01:00
MiguelMLorente 163ca86734 Create dice view to present all available dice to place in the board 2024-11-30 21:53:42 +01:00
MiguelMLorente af133bae8b Add images of pieces 2024-11-30 19:41:20 +01:00
MiguelMLorente d72984851e Merge pull request 'Merge game page board display into main branch' (#6) from game-page into main
Reviewed-on: #6
2024-11-30 11:31:31 +00:00
MiguelMLorente 0945336463 Implement board view based on board builder 2024-11-24 15:07:10 +01:00
MiguelMLorente e9305893cb Create board object builder and supporting classes and types 2024-11-23 22:50:07 +01:00
MiguelMLorente 4497a0a2f2 Created baseline for game page grid layout 2024-11-23 15:02:09 +01:00
MiguelMLorente ec6526deb5 Merge pull request 'Introduce eslint' (#4) from linter into main
Reviewed-on: #4
2024-11-21 21:34:11 +00:00
MiguelMLorente 56705d3574 Add eslint to web module 2024-11-20 23:29:21 +01:00
MiguelMLorente 250325d8d2 Pass linter on server build and start 2024-11-19 23:33:22 +01:00
MiguelMLorente d906cf486b Created linter in interface module 2024-11-19 23:25:52 +01:00
22 changed files with 390 additions and 498 deletions

19
app/package-lock.json generated
View File

@ -16,8 +16,7 @@
"@nestjs/websockets": "^10.4.7", "@nestjs/websockets": "^10.4.7",
"interface": "file:../interface", "interface": "file:../interface",
"reflect-metadata": "^0.2.0", "reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1", "rxjs": "^7.8.1"
"uuid": "^11.0.3"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^10.0.0", "@nestjs/cli": "^10.0.0",
@ -49,7 +48,8 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"socket.io": "^4.8.1", "socket.io": "^4.8.1",
"socket.io-client": "^4.8.1" "socket.io-client": "^4.8.1",
"uuid": "^11.0.3"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^8.0.0", "@eslint/js": "^8.0.0",
@ -8949,19 +8949,6 @@
"node": ">= 0.4.0" "node": ">= 0.4.0"
} }
}, },
"node_modules/uuid": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz",
"integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/v8-compile-cache-lib": { "node_modules/v8-compile-cache-lib": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",

View File

@ -26,8 +26,7 @@
"@nestjs/websockets": "^10.4.7", "@nestjs/websockets": "^10.4.7",
"interface": "file:../interface", "interface": "file:../interface",
"reflect-metadata": "^0.2.0", "reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1", "rxjs": "^7.8.1"
"uuid": "^11.0.3"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^10.0.0", "@nestjs/cli": "^10.0.0",

View File

@ -1,10 +1,9 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { PlayerService } from './players/player.service'; import { PlayerService } from './players/player.service';
import { GameService } from './games/game.service';
@Module({ @Module({
imports: [], imports: [],
providers: [AppService, PlayerService, GameService], providers: [AppService, PlayerService],
}) })
export class AppModule {} export class AppModule {}

View File

@ -1,4 +1,4 @@
import { Injectable, Logger, UseFilters } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { import {
ConnectedSocket, ConnectedSocket,
OnGatewayConnection, OnGatewayConnection,
@ -7,81 +7,35 @@ import {
WebSocketGateway, WebSocketGateway,
} from '@nestjs/websockets'; } from '@nestjs/websockets';
import { PlayerService } from './players/player.service'; import { PlayerService } from './players/player.service';
import { GameService } from 'src/games/game.service';
import { Socket } from 'socket.io'; import { Socket } from 'socket.io';
import { import { ClientEvent, CreateLobbyEvent, JoinLobbyEvent } from 'interface';
ClientEvent,
CreateLobbyEvent,
emitUpdateLobbyEvent,
JoinLobbyEvent,
} from 'interface';
import { createWsExceptionFilter } from './websocket-exception-filter';
import {
GameNotFoundException,
InvalidPlayerNameException,
MissingPlayerNameException,
PlayerNotFoundException,
} from './exceptions';
@WebSocketGateway({ cors: true }) @WebSocketGateway({ cors: true })
@Injectable() @Injectable()
export class AppService implements OnGatewayConnection { export class AppService implements OnGatewayConnection {
private readonly logger = new Logger(AppService.name); private readonly logger = new Logger(AppService.name);
constructor( constructor(private readonly playerService: PlayerService) {}
private readonly playerService: PlayerService,
private readonly gameService: GameService,
) {}
handleConnection(client: Socket) { handleConnection(client: Socket) {
this.playerService.createPlayer(client); this.playerService.createPlayer(client.id);
this.logger.log(client.id);
} }
@UseFilters(
createWsExceptionFilter([
PlayerNotFoundException,
MissingPlayerNameException,
InvalidPlayerNameException,
]),
)
@SubscribeMessage(ClientEvent.CREATE_LOBBY) @SubscribeMessage(ClientEvent.CREATE_LOBBY)
handleCreateLobby( handleCreateLobby(
@ConnectedSocket() client: Socket, @ConnectedSocket() client: Socket,
@MessageBody() event: CreateLobbyEvent, @MessageBody() event: CreateLobbyEvent,
) { ) {
this.playerService.addUserName(client.id, event.userName); this.playerService.addUserName(client.id, event.userName);
const game = this.gameService.createLobby( this.logger.log('Se ha creado un lobby');
this.playerService.getPlayer(client.id),
);
emitUpdateLobbyEvent(client, {
playerNames: game.players.map((player) => player.userName),
gameCode: game.gameCode,
});
} }
@UseFilters(
createWsExceptionFilter([
PlayerNotFoundException,
MissingPlayerNameException,
InvalidPlayerNameException,
GameNotFoundException,
]),
)
@SubscribeMessage(ClientEvent.JOIN_LOBBY) @SubscribeMessage(ClientEvent.JOIN_LOBBY)
handleJoinLobby( handleJoinLobby(
@ConnectedSocket() client: Socket, @ConnectedSocket() client: Socket,
@MessageBody() event: JoinLobbyEvent, @MessageBody() event: JoinLobbyEvent,
) { ) {
this.playerService.addUserName(client.id, event.userName); this.playerService.addUserName(client.id, event.userName);
const game = this.gameService.joinGame( this.logger.log('Te has unido a un lobby');
this.playerService.getPlayer(client.id),
event.lobbyId,
);
const playerNames = game.players.map((player) => player.userName);
game.players.forEach((player) =>
emitUpdateLobbyEvent(player.socket, {
playerNames: playerNames,
gameCode: game.gameCode,
}),
);
} }
} }

View File

@ -1,38 +0,0 @@
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 MissingPlayerNameException extends WebSocketException {
constructor() {
super('Missing player name', ErrorCode.MISSING_USER_NAME);
}
}
export class InvalidPlayerNameException extends WebSocketException {
constructor(userName: string) {
super('Invalid player name: ' + userName, ErrorCode.INVALID_USER_NAME);
}
}
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);
}
}

View File

@ -1,28 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { Game } from './game';
import { v4 as uuidv4 } from 'uuid';
import { Player } from 'src/players/player';
import { GameNotFoundException } from 'src/exceptions';
@Injectable()
export class GameService {
private readonly logger = new Logger(GameService.name);
private readonly games: Map<string, Game> = new Map();
createLobby(player: Player): Game {
const gameId = uuidv4();
const gameCode = uuidv4().slice(-5).toUpperCase();
const game: Game = new Game(gameId, gameCode, player);
this.games.set(gameCode, game);
return game;
}
joinGame(player: Player, gameCode: string): Game {
const game = this.games.get(gameCode);
if (game === undefined) {
throw new GameNotFoundException(gameCode);
}
game.players.push(player);
return game;
}
}

View File

@ -1,12 +0,0 @@
import { Player } from 'src/players/player';
export class Game {
gameId: string;
gameCode: string;
players: Player[];
constructor(gameId: string, gameCode: string, player: Player) {
this.gameId = gameId;
this.gameCode = gameCode;
this.players = [player];
}
}

View File

@ -1,38 +1,19 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { Player } from './player'; import { Player } from './player';
import {
InvalidPlayerNameException,
MissingPlayerNameException,
PlayerNotFoundException,
} from 'src/exceptions';
import { Socket } from 'socket.io/dist/socket';
@Injectable() @Injectable()
export class PlayerService { export class PlayerService {
private readonly logger = new Logger(PlayerService.name); private readonly logger = new Logger(PlayerService.name);
private readonly players: Map<string, Player> = new Map(); private readonly players: Map<string, Player> = new Map();
private readonly userNameValidator: RegExp = /^[a-zA-Z\s]{1,20}$/;
createPlayer(socket: Socket) { createPlayer(socketId: string) {
const player: Player = new Player(socket); const player: Player = new Player(socketId);
this.players.set(player.socketId, player); this.players.set(socketId, player);
this.logger.log([...this.players.entries()]);
} }
addUserName(socketId: string, userName: string) { addUserName(socketId: string, userName: string) {
if (!userName) {
throw new MissingPlayerNameException();
}
if (!this.userNameValidator.test(userName)) {
throw new InvalidPlayerNameException(userName);
}
this.players.get(socketId).userName = userName; this.players.get(socketId).userName = userName;
} this.logger.log([...this.players.entries()]);
getPlayer(socketId: string): Player {
const player = this.players.get(socketId);
if (!player) {
throw new PlayerNotFoundException(socketId);
}
return player;
} }
} }

View File

@ -1,12 +1,7 @@
import { Socket } from 'socket.io';
export class Player { export class Player {
socketId: string; socketId: string;
socket: Socket;
userName?: string; userName?: string;
constructor(socketId: string) {
constructor(socket: Socket) { this.socketId = socketId;
this.socket = socket;
this.socketId = socket.id;
} }
} }

View File

@ -1,36 +0,0 @@
import { ArgumentsHost, Catch, Logger } from '@nestjs/common';
import { BaseWsExceptionFilter } from '@nestjs/websockets';
import { WebSocketException } from './exceptions';
import { Socket } from 'socket.io';
import { ClientEvent } from 'interface';
import { emitCreateLobbyError, emitJoinLobbyError } from 'interface';
export const createWsExceptionFilter: (
exceptionList: (typeof WebSocketException)[],
) => BaseWsExceptionFilter<WebSocketException> = (handledExceptions) => {
@Catch(...handledExceptions)
class WsExceptionFilter extends BaseWsExceptionFilter<WebSocketException> {
private readonly logger = new Logger(BaseWsExceptionFilter.name);
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 ${JSON.stringify(data)}`,
);
switch (method) {
case ClientEvent.CREATE_LOBBY:
emitCreateLobbyError(socket, { error: exception.errorCode });
break;
case ClientEvent.JOIN_LOBBY:
emitJoinLobbyError(socket, { error: exception.errorCode });
}
}
}
return new WsExceptionFilter();
};

View File

@ -4,15 +4,6 @@ export * from "./constants/ExitType";
export * from "./constants/InternalNodeType"; export * from "./constants/InternalNodeType";
export * from "./constants/Pieces"; export * from "./constants/Pieces";
export * from "./constants/TrackType"; export * from "./constants/TrackType";
export * from "./server-events/CreateLobbyError";
export * from "./server-events/JoinLobbyError";
export * from "./server-events/ServerError";
export * from "./server-events/ServerEvent";
export * from "./server-events/StartRoundEvent";
export * from "./server-events/UpdateLobbyEvent";
export * from "./client-events/ClientEvent";
export * from "./client-events/CreateLobbyEvent";
export * from "./client-events/JoinLobbyEvent";
export * from "./types/Border"; export * from "./types/Border";
export * from "./types/Cell"; export * from "./types/Cell";
export * from "./types/Exit"; export * from "./types/Exit";
@ -21,3 +12,10 @@ export * from "./types/InternalNode";
export * from "./types/Piece"; export * from "./types/Piece";
export * from "./types/PlacedPiece"; export * from "./types/PlacedPiece";
export * from "./BoardBuilder"; export * from "./BoardBuilder";
export * from "./server-events/ServerError";
export * from "./server-events/ServerEvent";
export * from "./server-events/StartRoundEvent";
export * from "./server-events/UpdateLobbyEvent";
export * from "./client-events/ClientEvent";
export * from "./client-events/CreateLobbyEvent";
export * from "./client-events/JoinLobbyEvent";

View File

@ -248,6 +248,12 @@
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
"license": "MIT"
},
"node_modules/@types/cors": { "node_modules/@types/cors": {
"version": "2.8.17", "version": "2.8.17",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
@ -266,21 +272,21 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.23.0", "version": "8.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.23.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz",
"integrity": "sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w==", "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/scope-manager": "8.17.0",
"@typescript-eslint/type-utils": "8.23.0", "@typescript-eslint/type-utils": "8.17.0",
"@typescript-eslint/utils": "8.23.0", "@typescript-eslint/utils": "8.17.0",
"@typescript-eslint/visitor-keys": "8.23.0", "@typescript-eslint/visitor-keys": "8.17.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^1.3.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -291,21 +297,25 @@
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0"
"typescript": ">=4.8.4 <5.8.0" },
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.23.0", "version": "8.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.23.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz",
"integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==", "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==",
"dev": true, "dev": true,
"license": "MIT", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/scope-manager": "8.17.0",
"@typescript-eslint/types": "8.23.0", "@typescript-eslint/types": "8.17.0",
"@typescript-eslint/typescript-estree": "8.23.0", "@typescript-eslint/typescript-estree": "8.17.0",
"@typescript-eslint/visitor-keys": "8.23.0", "@typescript-eslint/visitor-keys": "8.17.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -316,19 +326,23 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0"
"typescript": ">=4.8.4 <5.8.0" },
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.23.0", "version": "8.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.23.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz",
"integrity": "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==", "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.23.0", "@typescript-eslint/types": "8.17.0",
"@typescript-eslint/visitor-keys": "8.23.0" "@typescript-eslint/visitor-keys": "8.17.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -339,16 +353,16 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.23.0", "version": "8.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.23.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz",
"integrity": "sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA==", "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.23.0", "@typescript-eslint/typescript-estree": "8.17.0",
"@typescript-eslint/utils": "8.23.0", "@typescript-eslint/utils": "8.17.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^1.3.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -358,14 +372,18 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0"
"typescript": ">=4.8.4 <5.8.0" },
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.23.0", "version": "8.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.23.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz",
"integrity": "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==", "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -377,20 +395,20 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.23.0", "version": "8.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.23.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz",
"integrity": "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==", "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==",
"dev": true, "dev": true,
"license": "MIT", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.23.0", "@typescript-eslint/types": "8.17.0",
"@typescript-eslint/visitor-keys": "8.23.0", "@typescript-eslint/visitor-keys": "8.17.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
"minimatch": "^9.0.4", "minimatch": "^9.0.4",
"semver": "^7.6.0", "semver": "^7.6.0",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^1.3.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -399,21 +417,23 @@
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependenciesMeta": {
"typescript": ">=4.8.4 <5.8.0" "typescript": {
"optional": true
}
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.23.0", "version": "8.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.23.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz",
"integrity": "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==", "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/scope-manager": "8.17.0",
"@typescript-eslint/types": "8.23.0", "@typescript-eslint/types": "8.17.0",
"@typescript-eslint/typescript-estree": "8.23.0" "@typescript-eslint/typescript-estree": "8.17.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -423,18 +443,22 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0"
"typescript": ">=4.8.4 <5.8.0" },
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.23.0", "version": "8.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.23.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz",
"integrity": "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==", "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.23.0", "@typescript-eslint/types": "8.17.0",
"eslint-visitor-keys": "^4.2.0" "eslint-visitor-keys": "^4.2.0"
}, },
"engines": { "engines": {
@ -719,11 +743,12 @@
} }
}, },
"node_modules/engine.io": { "node_modules/engine.io": {
"version": "6.6.4", "version": "6.6.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz",
"integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12", "@types/cors": "^2.8.12",
"@types/node": ">=10.0.0", "@types/node": ">=10.0.0",
"accepts": "~1.3.4", "accepts": "~1.3.4",
@ -739,9 +764,9 @@
} }
}, },
"node_modules/engine.io-client": { "node_modules/engine.io-client": {
"version": "6.6.3", "version": "6.6.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz",
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", "integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@socket.io/component-emitter": "~3.1.0", "@socket.io/component-emitter": "~3.1.0",
@ -1023,9 +1048,9 @@
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.3.3", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -1033,7 +1058,7 @@
"@nodelib/fs.walk": "^1.2.3", "@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2", "glob-parent": "^5.1.2",
"merge2": "^1.3.0", "merge2": "^1.3.0",
"micromatch": "^4.0.8" "micromatch": "^4.0.4"
}, },
"engines": { "engines": {
"node": ">=8.6.0" "node": ">=8.6.0"
@ -1208,9 +1233,9 @@
} }
}, },
"node_modules/globals": { "node_modules/globals": {
"version": "15.12.0", "version": "15.13.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-15.13.0.tgz",
"integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", "integrity": "sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -1643,9 +1668,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.3.3", "version": "3.4.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
@ -1766,9 +1791,9 @@
} }
}, },
"node_modules/semver": { "node_modules/semver": {
"version": "7.7.1", "version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
@ -1934,16 +1959,16 @@
} }
}, },
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "2.0.1", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
"integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.12" "node": ">=16"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": ">=4.8.4" "typescript": ">=4.2.0"
} }
}, },
"node_modules/tslib": { "node_modules/tslib": {
@ -1993,15 +2018,15 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.23.0", "version": "8.15.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.23.0.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.15.0.tgz",
"integrity": "sha512-/LBRo3HrXr5LxmrdYSOCvoAMm7p2jNizNfbIpCgvG4HMsnoprRUOce/+8VJ9BDYWW68rqIENE/haVLWPeFZBVQ==", "integrity": "sha512-wY4FRGl0ZI+ZU4Jo/yjdBu0lVTSML58pu6PgGtJmCufvzfV565pUF6iACQt092uFOd49iLOTX/sEVmHtbSrS+w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.23.0", "@typescript-eslint/eslint-plugin": "8.15.0",
"@typescript-eslint/parser": "8.23.0", "@typescript-eslint/parser": "8.15.0",
"@typescript-eslint/utils": "8.23.0" "@typescript-eslint/utils": "8.15.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -2011,8 +2036,223 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0"
"typescript": ">=4.8.4 <5.8.0" },
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz",
"integrity": "sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.15.0",
"@typescript-eslint/type-utils": "8.15.0",
"@typescript-eslint/utils": "8.15.0",
"@typescript-eslint/visitor-keys": "8.15.0",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
"eslint": "^8.57.0 || ^9.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.15.0.tgz",
"integrity": "sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/scope-manager": "8.15.0",
"@typescript-eslint/types": "8.15.0",
"@typescript-eslint/typescript-estree": "8.15.0",
"@typescript-eslint/visitor-keys": "8.15.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz",
"integrity": "sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.15.0",
"@typescript-eslint/visitor-keys": "8.15.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/type-utils": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz",
"integrity": "sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "8.15.0",
"@typescript-eslint/utils": "8.15.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/types": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.15.0.tgz",
"integrity": "sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz",
"integrity": "sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/types": "8.15.0",
"@typescript-eslint/visitor-keys": "8.15.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz",
"integrity": "sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.15.0",
"@typescript-eslint/types": "8.15.0",
"@typescript-eslint/typescript-estree": "8.15.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz",
"integrity": "sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.15.0",
"eslint-visitor-keys": "^4.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/typescript-eslint/node_modules/eslint-visitor-keys": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/undici-types": { "node_modules/undici-types": {

View File

@ -23,8 +23,8 @@
"typescript-eslint": "^8.15.0" "typescript-eslint": "^8.15.0"
}, },
"dependencies": { "dependencies": {
"uuid": "^11.0.3",
"socket.io": "^4.8.1", "socket.io": "^4.8.1",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1"
"uuid": "^11.0.3"
} }
} }

View File

@ -1,22 +0,0 @@
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;
};
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);
};

View File

@ -1,22 +0,0 @@
import { Socket as ServerSocket } from "socket.io";
import { Socket as ClientSocket } from "socket.io-client";
import { ErrorCode, ServerError } from "./ServerError";
export type JoinLobbyError = {
error: ErrorCode;
};
export const emitJoinLobbyError = (
socket: ServerSocket,
payload: JoinLobbyError,
) => {
socket.emit(ServerError.JOIN_LOBBY_ERROR, payload);
};
export const attachHandlerToJoinLobbyError = (
socket: ClientSocket,
handler: (payload: JoinLobbyError) => void,
): (() => void) => {
socket.on(ServerError.JOIN_LOBBY_ERROR, handler);
return () => socket.off(ServerError.JOIN_LOBBY_ERROR);
};

View File

@ -2,12 +2,3 @@ export enum ServerError {
CREATE_LOBBY_ERROR = "create-lobby-error", CREATE_LOBBY_ERROR = "create-lobby-error",
JOIN_LOBBY_ERROR = "join-lobby-error", JOIN_LOBBY_ERROR = "join-lobby-error",
} }
export enum ErrorCode {
PLAYER_NOT_FOUND = "player-not-found",
GAME_NOT_FOUND = "game-not-found",
MISSING_USER_NAME = "missing-player-name",
INVALID_USER_NAME = "invalid-player-name",
DUPLICATE_USER_NAME = "duplicate-player-name",
MISSING_GAME_CODE = "missing-game-code",
}

3
web/package-lock.json generated
View File

@ -39,7 +39,8 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"socket.io": "^4.8.1", "socket.io": "^4.8.1",
"socket.io-client": "^4.8.1" "socket.io-client": "^4.8.1",
"uuid": "^11.0.3"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^8.0.0", "@eslint/js": "^8.0.0",

View File

@ -1,9 +1,3 @@
@use "node_modules/@picocss/pico/scss/pico" with ( @use "node_modules/@picocss/pico/scss/pico" with (
$theme-color: "pumpkin" $theme-color: "pumpkin"
); );
#root, #app {
height: 100%;
width: 100%;
padding: 0;
}

View File

@ -2,7 +2,6 @@ import React, { useCallback, useState } from "react";
import { Socket } from "socket.io-client"; import { Socket } from "socket.io-client";
import LandingPage from "./pages/landing/LandingPage"; import LandingPage from "./pages/landing/LandingPage";
import GamePage from "./pages/game/GamePage"; import GamePage from "./pages/game/GamePage";
import "./App.scss";
import LobbyPage from "./pages/lobby/LobbyPage"; import LobbyPage from "./pages/lobby/LobbyPage";
import { import {
attachHandlerToStartRoundEvent, attachHandlerToStartRoundEvent,

View File

@ -8,7 +8,6 @@ const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement, document.getElementById("root") as HTMLElement,
); );
const socket = io("http://localhost:3010"); const socket = io("http://localhost:3010");
socket.on("exception", console.log);
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<App socket={socket} /> <App socket={socket} />

View File

@ -1,44 +0,0 @@
.landing-page {
height: 100%;
width: 100%;
padding: 2%;
margin: 0 auto;
text-align: center;
background: linear-gradient(-45deg, #03444a, #00a8a8, #f1bc52, #ff8f4b);
background-size: 400% 400%;
animation: gradient 15s ease infinite;
height: 100vh;
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.landing-page-body {
margin: auto;
width: 20%;
min-width: 300px;
#error-message {
color: #222;
}
}
> div {
padding: 2%;
}
button {
font-size: 1rem;
width: 70%;
margin-bottom: var(--pico-spacing)
}
}

View File

@ -1,15 +1,11 @@
import { import {
attachHandlerToCreateLobbyError,
attachHandlerToJoinLobbyError,
CreateLobbyEvent, CreateLobbyEvent,
ErrorCode,
handleCreateLobby, handleCreateLobby,
handleJoinLobby, handleJoinLobby,
JoinLobbyEvent, JoinLobbyEvent,
} from "interface"; } from "interface";
import { ChangeEvent, useEffect, useState } from "react"; import React, { ChangeEvent } from "react";
import { Socket } from "socket.io-client"; import { Socket } from "socket.io-client";
import "./LandingPage.scss";
export interface LandingPageProps { export interface LandingPageProps {
socket: Socket; socket: Socket;
@ -18,81 +14,42 @@ export interface LandingPageProps {
const LandingPage = (props: LandingPageProps) => { const LandingPage = (props: LandingPageProps) => {
const { socket } = props; const { socket } = props;
const [userName, setUsername] = useState(""); const createLobbyPayload: CreateLobbyEvent = { userName: "" };
const [lobbyId, setLobbyId] = useState(""); const joinLobbyPayload: JoinLobbyEvent = { userName: "", lobbyId: "" };
const createLobbyPayload: CreateLobbyEvent = { userName }; const registerUsername = (event: ChangeEvent<HTMLInputElement>) => {
const joinLobbyPayload: JoinLobbyEvent = { userName, lobbyId }; createLobbyPayload.userName = event.target.value;
const registerUsername = (event: ChangeEvent<HTMLInputElement>) => joinLobbyPayload.userName = event.target.value;
setUsername(event.target.value); };
const registerLobbyId = (event: ChangeEvent<HTMLInputElement>) => const registerLobbyId = (event: ChangeEvent<HTMLInputElement>) =>
setLobbyId(event.target.value); (joinLobbyPayload.lobbyId = event.target.value);
const [receivedError, setReceivedError] = useState("" as ErrorCode);
const userNameErrorCodesToMessageMap = new Map([
[ErrorCode.MISSING_USER_NAME, "Introduce your player name"],
[
ErrorCode.INVALID_USER_NAME,
"Player name must be letters or spaces only and up to 25 characters",
],
[ErrorCode.DUPLICATE_USER_NAME, "Player name is already under use"],
]);
const userNameErrorMessage =
userNameErrorCodesToMessageMap.get(receivedError);
const gameCodeErrorCodesToMessageMap = new Map([
[ErrorCode.GAME_NOT_FOUND, "No game found with such lobby ID"],
[ErrorCode.MISSING_GAME_CODE, "Introduce a game code"],
]);
const gameCodeErrorMessage =
gameCodeErrorCodesToMessageMap.get(receivedError);
useEffect(() =>
attachHandlerToCreateLobbyError(socket, (event) =>
setReceivedError(event.error),
),
);
useEffect(() =>
attachHandlerToJoinLobbyError(socket, (event) =>
setReceivedError(event.error),
),
);
return ( return (
<div className="landing-page"> <React.Fragment>
<div> <div className="landing-page-title">
<h1>Trains And Roads</h1> <h1>Trains And Roads</h1>
</div> </div>
<div className="landing-page-body"> <div className="user-name-input">
<div> <input placeholder="Enter username" onChange={registerUsername}></input>
<input </div>
placeholder="Enter username" <div className="create-lobby-button">
onChange={registerUsername}
maxLength={20}
></input>
{userNameErrorMessage && (
<small id="error-message">{userNameErrorMessage}</small>
)}
<button onClick={() => handleCreateLobby(socket, createLobbyPayload)}> <button onClick={() => handleCreateLobby(socket, createLobbyPayload)}>
Create Lobby Create Lobby
</button> </button>
</div> </div>
<div> <div className="join-lobby">
<input <input
className="lobby-id-input"
placeholder="Enter Lobby Id" placeholder="Enter Lobby Id"
onChange={registerLobbyId} onChange={registerLobbyId}
maxLength={5}
></input> ></input>
{gameCodeErrorMessage && (
<small id="error-message">{gameCodeErrorMessage}</small>
)}
<button <button
className="secondary" className="join-lobby-button secondary"
onClick={() => handleJoinLobby(socket, joinLobbyPayload)} onClick={() => handleJoinLobby(socket, joinLobbyPayload)}
> >
Join Lobby Join Lobby
</button> </button>
</div> </div>
</div> </React.Fragment>
</div>
); );
}; };
export default LandingPage; export default LandingPage;