diff --git a/apps/server/src/network/handlers/game/registerGameHandlers.ts b/apps/server/src/network/handlers/game/registerGameHandlers.ts index f96235d..30840a8 100644 --- a/apps/server/src/network/handlers/game/registerGameHandlers.ts +++ b/apps/server/src/network/handlers/game/registerGameHandlers.ts @@ -15,10 +15,10 @@ } from "@server/domains/game/application/ports/gameUseCasePorts"; import { movePlayerUseCase } from "@server/domains/game/application/useCases/movePlayerUseCase"; import { pingUseCase } from "@server/domains/game/application/useCases/pingUseCase"; -import { logEvent } from "@server/logging/logEvent"; import { createCommonHandlerContext } from "@server/network/handlers/CommonHandler"; import { isMovePayload, isPingPayload } from "@server/network/validation/socketPayloadValidators"; import { createServerSocketOnBridge } from "@server/network/handlers/socketEventBridge"; +import { createPayloadGuard } from "@server/network/handlers/payloadGuard"; import { createGameOutputAdapter } from "./createGameOutputAdapter"; /** ゲーム受信イベントごとの入力検証関数を保持するテーブル */ @@ -37,15 +37,11 @@ const common = createCommonHandlerContext(io, socket); const gameOutputAdapter = createGameOutputAdapter(common); const { onEvent } = createServerSocketOnBridge(socket); + const payloadGuard = createPayloadGuard(socket.id); // 遅延計測用のPINGを検証しPONGを返す onEvent(protocol.SocketEvents.PING, (clientTime) => { - if (!gamePayloadValidators[protocol.SocketEvents.PING](clientTime)) { - logEvent("Network", { - event: "PING", - result: "ignored_invalid_payload", - socketId: socket.id, - }); + if (!payloadGuard(protocol.SocketEvents.PING, clientTime, gamePayloadValidators[protocol.SocketEvents.PING])) { return; } @@ -77,12 +73,7 @@ // 移動入力を検証しプレイヤー移動ユースケースへ連携する onEvent(protocol.SocketEvents.MOVE, (data) => { - if (!gamePayloadValidators[protocol.SocketEvents.MOVE](data)) { - logEvent("Network", { - event: "MOVE", - result: "ignored_invalid_payload", - socketId: socket.id, - }); + if (!payloadGuard(protocol.SocketEvents.MOVE, data, gamePayloadValidators[protocol.SocketEvents.MOVE])) { return; } diff --git a/apps/server/src/network/handlers/payloadGuard.ts b/apps/server/src/network/handlers/payloadGuard.ts new file mode 100644 index 0000000..6b75872 --- /dev/null +++ b/apps/server/src/network/handlers/payloadGuard.ts @@ -0,0 +1,32 @@ +/** + * payloadGuard + * 受信ペイロード検証と不正時ログ記録を共通化するガード生成を担う + */ +import { protocol } from "@repo/shared"; +import { logEvent } from "@server/logging/logEvent"; + +type PayloadValidator = (value: unknown) => value is TPayload; +type SocketEventName = (typeof protocol.SocketEvents)[keyof typeof protocol.SocketEvents]; + +/** + * 受信ペイロードを検証し,不正時は共通ログを記録するガード関数を生成する + */ +export const createPayloadGuard = (socketId: string) => { + return ( + event: SocketEventName, + payload: unknown, + validator: PayloadValidator + ): payload is TPayload => { + if (validator(payload)) { + return true; + } + + logEvent("Network", { + event, + result: "ignored_invalid_payload", + socketId, + }); + + return false; + }; +}; diff --git a/apps/server/src/network/handlers/registerConnectionHandlers.ts b/apps/server/src/network/handlers/registerConnectionHandlers.ts index fc7fad4..a4d6a7b 100644 --- a/apps/server/src/network/handlers/registerConnectionHandlers.ts +++ b/apps/server/src/network/handlers/registerConnectionHandlers.ts @@ -11,11 +11,6 @@ import { createGameDisconnectOutputAdapter } from "./game/createGameOutputAdapter"; import { createRoomDisconnectOutputAdapter } from "./room/createRoomOutputAdapter"; import type { - ConnectionGamePort, - ConnectionRoomPort, - DisconnectCoordinatorPortBundle, - DisconnectGamePort, - DisconnectRoomHandlerPort, RegisterConnectionHandlersParams, } from "../types/connectionPorts"; @@ -27,39 +22,32 @@ }: RegisterConnectionHandlersParams) => { const gameDisconnectOutputAdapter = createGameDisconnectOutputAdapter(io); const roomDisconnectOutputAdapter = createRoomDisconnectOutputAdapter(io); - const connectionGameManager: ConnectionGamePort = gameManager; - const disconnectGameManager: DisconnectGamePort = gameManager; - const connectionRoomManager: ConnectionRoomPort = roomManager; - const disconnectRoomManager: DisconnectRoomHandlerPort = roomManager; - const disconnectPorts: DisconnectCoordinatorPortBundle = { - gameManager: disconnectGameManager, - roomManager: disconnectRoomManager, - gameOutput: gameDisconnectOutputAdapter, - roomOutput: roomDisconnectOutputAdapter, - }; io.on(protocol.SocketEvents.CONNECT, (socket: Socket) => { // 接続ログを記録してドメイン別ハンドラを登録する logEvent("Network", { - event: "CONNECT", + event: protocol.SocketEvents.CONNECT, result: "connected", socketId: socket.id, }); - registerRoomHandlers(io, socket, connectionRoomManager); - registerGameHandlers(io, socket, connectionGameManager, connectionRoomManager); + registerRoomHandlers(io, socket, roomManager); + registerGameHandlers(io, socket, gameManager, roomManager); socket.on(protocol.SocketEvents.DISCONNECT, () => { // 切断ログ記録後にドメイン別の後処理を実行する logEvent("Network", { - event: "DISCONNECT", + event: protocol.SocketEvents.DISCONNECT, result: "disconnected", socketId: socket.id, }); disconnectCoordinator({ socketId: socket.id, - ...disconnectPorts, + gameManager, + roomManager, + gameOutput: gameDisconnectOutputAdapter, + roomOutput: roomDisconnectOutputAdapter, }); }); }); diff --git a/apps/server/src/network/handlers/room/registerRoomHandlers.ts b/apps/server/src/network/handlers/room/registerRoomHandlers.ts index 5c0145f..d285330 100644 --- a/apps/server/src/network/handlers/room/registerRoomHandlers.ts +++ b/apps/server/src/network/handlers/room/registerRoomHandlers.ts @@ -8,6 +8,7 @@ import { joinRoomUseCase } from "@server/domains/room/application/useCases/joinRoomUseCase"; import { logEvent } from "@server/logging/logEvent"; import { createCommonHandlerContext } from "@server/network/handlers/CommonHandler"; +import { createPayloadGuard } from "@server/network/handlers/payloadGuard"; import { createServerSocketOnBridge } from "@server/network/handlers/socketEventBridge"; import { isJoinRoomPayload } from "@server/network/validation/socketPayloadValidators"; import { createRoomOutputAdapter } from "./createRoomOutputAdapter"; @@ -26,15 +27,11 @@ const common = createCommonHandlerContext(io, socket); const roomOutputAdapter = createRoomOutputAdapter(common); const { onEvent } = createServerSocketOnBridge(socket); + const payloadGuard = createPayloadGuard(socket.id); // 参加要求のペイロード検証と参加処理を実行する onEvent(protocol.SocketEvents.JOIN_ROOM, async (data) => { - if (!roomPayloadValidators[protocol.SocketEvents.JOIN_ROOM](data)) { - logEvent("Network", { - event: "JOIN_ROOM", - result: "ignored_invalid_payload", - socketId: socket.id, - }); + if (!payloadGuard(protocol.SocketEvents.JOIN_ROOM, data, roomPayloadValidators[protocol.SocketEvents.JOIN_ROOM])) { return; } @@ -51,7 +48,7 @@ switch (joinResult.status) { case "full": logEvent("Network", { - event: "JOIN_ROOM", + event: protocol.SocketEvents.JOIN_ROOM, result: "rejected_room_full", roomId, socketId: socket.id, @@ -60,7 +57,7 @@ case "duplicate": logEvent("Network", { - event: "JOIN_ROOM", + event: protocol.SocketEvents.JOIN_ROOM, result: "rejected_duplicate", roomId, socketId: socket.id, @@ -71,7 +68,7 @@ await socket.join(roomId); roomOutputAdapter.publishRoomUpdateToRoom(roomId, joinResult.room); logEvent("RoomUseCase", { - event: "ROOM_UPDATE", + event: protocol.SocketEvents.ROOM_UPDATE, result: "emitted", roomId, socketId: socket.id, diff --git a/apps/server/src/network/handlers/socketEventBridge.ts b/apps/server/src/network/handlers/socketEventBridge.ts index 2facfe6..b14bc41 100644 --- a/apps/server/src/network/handlers/socketEventBridge.ts +++ b/apps/server/src/network/handlers/socketEventBridge.ts @@ -10,12 +10,39 @@ type ServerToClientEventPayloadMap, } from "@repo/shared"; +type BridgeTarget = { + on: (event: string, callback: (payload: unknown) => void) => void; + once: (event: string, callback: (payload: unknown) => void) => void; + off: (event: string, callback: (payload: unknown) => void) => void; + emit: (event: string, payload?: unknown) => void; +}; + /** サーバー向けの型付きソケットイベント bridge を生成する */ export const createServerSocketOnBridge = (socket: Socket) => { + const bridgeTarget: BridgeTarget = { + on: (event, callback) => { + socket.on(event, callback); + }, + once: (event, callback) => { + socket.once(event, callback); + }, + off: (event, callback) => { + socket.off(event, callback); + }, + emit: (event, payload) => { + if (payload === undefined) { + socket.emit(event); + return; + } + + socket.emit(event, payload); + }, + }; + const { onEvent, onceEvent } = createSocketEventBridge< ClientToServerEventPayloadMap, ServerToClientEventPayloadMap - >(socket as any); + >(bridgeTarget); return { onEvent,