diff --git a/apps/server/src/network/handlers/eventDefinitionRegistrar.ts b/apps/server/src/network/handlers/eventDefinitionRegistrar.ts new file mode 100644 index 0000000..09cf32e --- /dev/null +++ b/apps/server/src/network/handlers/eventDefinitionRegistrar.ts @@ -0,0 +1,96 @@ +/** + * eventDefinitionRegistrar + * 宣言的イベント定義の登録処理を共通化する + */ + +/** 受信payloadハンドラをバイバリアントに扱うための型 */ +type BivariantPayloadHandler = { + bivarianceHack: (payload: TPayload) => void | Promise; +}["bivarianceHack"]; + +/** 検証付きイベント定義 */ +export type GuardedEventDefinition = { + event: TEvent; + validator: (value: unknown) => value is TPayload; + orchestrate: BivariantPayloadHandler; +}; + +/** 自前検証イベント定義 */ +export type SelfValidatedEventDefinition = { + event: TEvent; + validator: (value: unknown) => value is TPayload; + orchestrate: BivariantPayloadHandler; +}; + +/** 検証不要イベント定義 */ +export type UnguardedEventDefinition = { + event: TEvent; + orchestrate: () => void | Promise; +}; + +/** 汎用イベント定義 */ +export type EventDefinition = { + event: TEvent; + orchestrate: (payload: TPayload) => void | Promise; +}; + +/** 検証付きイベント定義を登録する */ +export const registerGuardedEvents = ( + subscribe: (event: TEvent, callback: (payload: unknown) => void) => void, + createGuard: ( + event: TEvent, + validator: (value: unknown) => value is TPayload, + ) => (payload: unknown) => payload is TPayload, + definitions: GuardedEventDefinition[], +): void => { + definitions.forEach((definition) => { + const guard = createGuard(definition.event, definition.validator); + subscribe(definition.event, (payload) => { + if (!guard(payload)) { + return; + } + + void definition.orchestrate(payload); + }); + }); +}; + +/** 自前検証イベント定義を登録する */ +export const registerSelfValidatedEvents = ( + subscribe: (event: TEvent, callback: (payload: unknown) => void) => void, + definitions: SelfValidatedEventDefinition[], +): void => { + definitions.forEach((definition) => { + subscribe(definition.event, (payload) => { + if (!definition.validator(payload)) { + return; + } + + void definition.orchestrate(payload); + }); + }); +}; + +/** 検証不要イベント定義を登録する */ +export const registerUnguardedEvents = ( + subscribe: (event: TEvent, callback: () => void) => void, + definitions: UnguardedEventDefinition[], +): void => { + definitions.forEach((definition) => { + subscribe(definition.event, () => { + void definition.orchestrate(); + }); + }); +}; + +/** 汎用イベント定義を登録する */ +export const registerEvents = ( + subscribe: (event: TEvent, callback: (payload: TPayload) => void) => void, + definitions: EventDefinition[], +): void => { + definitions.forEach((definition) => { + subscribe(definition.event, (payload) => { + void definition.orchestrate(payload); + }); + }); +}; diff --git a/apps/server/src/network/handlers/game/registerGameHandlers.ts b/apps/server/src/network/handlers/game/registerGameHandlers.ts index e09974d..17cbcab 100644 --- a/apps/server/src/network/handlers/game/registerGameHandlers.ts +++ b/apps/server/src/network/handlers/game/registerGameHandlers.ts @@ -27,6 +27,39 @@ handleReadyForGameEvent, handleStartGameEvent, } from "./gameEventOrchestrators"; +import { + registerGuardedEvents, + registerSelfValidatedEvents, + registerUnguardedEvents, + type GuardedEventDefinition, + type SelfValidatedEventDefinition, + type UnguardedEventDefinition, +} from "@server/network/handlers/eventDefinitionRegistrar"; + +type PingEventDefinition = GuardedEventDefinition< + typeof protocol.SocketEvents.PING, + Parameters[1] +>; + +type MoveEventDefinition = GuardedEventDefinition< + typeof protocol.SocketEvents.MOVE, + Parameters[1] +>; + +type PlaceBombEventDefinition = GuardedEventDefinition< + typeof protocol.SocketEvents.PLACE_BOMB, + Parameters[1] +>; + +type BombHitReportEventDefinition = GuardedEventDefinition< + typeof protocol.SocketEvents.BOMB_HIT_REPORT, + Parameters[1] +>; + +type StartGameEventDefinition = SelfValidatedEventDefinition< + typeof protocol.SocketEvents.START_GAME, + Parameters[1] +>; /** ゲーム受信イベントごとの入力検証関数を保持するテーブル */ const gamePayloadValidators = { @@ -36,26 +69,6 @@ [protocol.SocketEvents.BOMB_HIT_REPORT]: isBombHitReportPayload, } as const; -/** 検証付きゲームイベント登録定義 */ -type GuardedGameEventDefinition = { - event: keyof typeof gamePayloadValidators; - validator: (value: unknown) => value is unknown; - orchestrate: (payload: unknown) => void; -}; - -/** 自前検証付きゲームイベント登録定義 */ -type SelfValidatedGameEventDefinition = { - event: typeof protocol.SocketEvents.START_GAME; - validator: (value: unknown) => value is unknown; - orchestrate: (payload: unknown) => void; -}; - -/** 検証不要ゲームイベント登録定義 */ -type UnguardedGameEventDefinition = { - event: typeof protocol.SocketEvents.READY_FOR_GAME; - orchestrate: () => void; -}; - /** ゲームイベント調停で利用する依存束を生成する */ const createGameOrchestratorDeps = ( socket: Socket, @@ -88,74 +101,59 @@ const { guardOnEvent } = createPayloadGuard(socket.id); // 検証が必要なイベントを宣言的に登録する - const guardedGameEventDefinitions: GuardedGameEventDefinition[] = [ + const guardedGameEventDefinitions: Array< + | PingEventDefinition + | MoveEventDefinition + | PlaceBombEventDefinition + | BombHitReportEventDefinition + > = [ { event: protocol.SocketEvents.PING, validator: gamePayloadValidators[protocol.SocketEvents.PING], orchestrate: (payload) => { - handlePingEvent(orchestratorDeps, payload as Parameters[1]); + handlePingEvent(orchestratorDeps, payload); }, }, { event: protocol.SocketEvents.MOVE, validator: gamePayloadValidators[protocol.SocketEvents.MOVE], orchestrate: (payload) => { - handleMoveEvent(orchestratorDeps, payload as Parameters[1]); + handleMoveEvent(orchestratorDeps, payload); }, }, { event: protocol.SocketEvents.PLACE_BOMB, validator: gamePayloadValidators[protocol.SocketEvents.PLACE_BOMB], orchestrate: (payload) => { - handlePlaceBombEvent(orchestratorDeps, payload as Parameters[1]); + handlePlaceBombEvent(orchestratorDeps, payload); }, }, { event: protocol.SocketEvents.BOMB_HIT_REPORT, validator: gamePayloadValidators[protocol.SocketEvents.BOMB_HIT_REPORT], orchestrate: (payload) => { - handleBombHitReportEvent(orchestratorDeps, payload as Parameters[1]); + handleBombHitReportEvent(orchestratorDeps, payload); }, }, ]; - guardedGameEventDefinitions.forEach((definition) => { - const guard = guardOnEvent(definition.event, definition.validator); - onEvent(definition.event, (payload) => { - if (!guard(payload)) { - return; - } - - definition.orchestrate(payload); - }); - }); + registerGuardedEvents(onEvent, guardOnEvent, guardedGameEventDefinitions); // payloadGuard対象外だが検証が必要なイベントを宣言的に登録する - const selfValidatedGameEventDefinitions: SelfValidatedGameEventDefinition[] = [ + const selfValidatedGameEventDefinitions: StartGameEventDefinition[] = [ { event: protocol.SocketEvents.START_GAME, validator: isStartGamePayload, orchestrate: (payload) => { - handleStartGameEvent( - orchestratorDeps, - payload as Parameters[1], - ); + handleStartGameEvent(orchestratorDeps, payload); }, }, ]; - selfValidatedGameEventDefinitions.forEach((definition) => { - onEvent(definition.event, (payload) => { - if (!definition.validator(payload)) { - return; - } - - definition.orchestrate(payload); - }); - }); + registerSelfValidatedEvents(onEvent, selfValidatedGameEventDefinitions); // 検証不要イベントを宣言的に登録する - const unguardedGameEventDefinitions: UnguardedGameEventDefinition[] = [ + const unguardedGameEventDefinitions: UnguardedEventDefinition[] = [ { event: protocol.SocketEvents.READY_FOR_GAME, orchestrate: () => { @@ -164,9 +162,5 @@ }, ]; - unguardedGameEventDefinitions.forEach((definition) => { - onEvent(definition.event, () => { - definition.orchestrate(); - }); - }); + registerUnguardedEvents(onEvent, unguardedGameEventDefinitions); }; diff --git a/apps/server/src/network/handlers/registerConnectionHandlers.ts b/apps/server/src/network/handlers/registerConnectionHandlers.ts index 518b5d3..859ef6a 100644 --- a/apps/server/src/network/handlers/registerConnectionHandlers.ts +++ b/apps/server/src/network/handlers/registerConnectionHandlers.ts @@ -12,10 +12,35 @@ createSocketOutputAdapters, } from "./createOutputAdapters"; import { logConnected, logDisconnected } from "./connectionEventLogger"; +import { + registerEvents, + registerUnguardedEvents, + type EventDefinition, + type UnguardedEventDefinition, +} from "./eventDefinitionRegistrar"; import type { RegisterConnectionHandlersParams, } from "../types/connectionPorts"; +/** 接続イベント調停で利用する依存束 */ +type ConnectionHandlerDeps = { + io: Server; + socket: Socket; +} & Omit; + +/** 接続ハンドラで利用する依存束を生成する */ +const createConnectionHandlerDeps = ( + params: RegisterConnectionHandlersParams, + socket: Socket, +): ConnectionHandlerDeps => { + return { + io: params.io, + socket, + roomManager: params.roomManager, + runtimeRegistry: params.runtimeRegistry, + }; +}; + /** ソケット接続と切断イベントに対する共通ハンドラを登録する */ export const registerConnectionHandlers = ({ io, @@ -24,36 +49,77 @@ }: RegisterConnectionHandlersParams) => { const disconnectOutputAdapters = createDisconnectOutputAdapters(io); - io.on(protocol.SocketEvents.CONNECT, (socket: Socket) => { - const socketOutputAdapters = createSocketOutputAdapters(io, socket); + // CONNECTイベントを宣言的に登録する + const onConnectEventDefinitions: EventDefinition< + typeof protocol.SocketEvents.CONNECT, + Socket + >[] = [ + { + event: protocol.SocketEvents.CONNECT, + orchestrate: (socket) => { + const deps = createConnectionHandlerDeps( + { + io, + roomManager, + runtimeRegistry, + }, + socket, + ); + const socketOutputAdapters = createSocketOutputAdapters(deps.io, deps.socket); - // 接続ログを記録してドメイン別ハンドラを登録する - logConnected(socket.id); + // 接続ログを記録してドメイン別ハンドラを登録する + logConnected(deps.socket.id); - registerRoomHandlers( - socket, - roomManager, - runtimeRegistry, - socketOutputAdapters.room, - ); - registerGameHandlers( - socket, - roomManager, - runtimeRegistry, - socketOutputAdapters.game, - ); + registerRoomHandlers( + deps.socket, + deps.roomManager, + deps.runtimeRegistry, + socketOutputAdapters.room, + ); + registerGameHandlers( + deps.socket, + deps.roomManager, + deps.runtimeRegistry, + socketOutputAdapters.game, + ); - socket.on(protocol.SocketEvents.DISCONNECT, () => { - // 切断ログ記録後にドメイン別の後処理を実行する - logDisconnected(socket.id); + // ソケット単位イベントを宣言的に登録する + const perSocketEventDefinitions: UnguardedEventDefinition< + typeof protocol.SocketEvents.DISCONNECT + >[] = [ + { + event: protocol.SocketEvents.DISCONNECT, + orchestrate: () => { + // 切断ログ記録後にドメイン別の後処理を実行する + logDisconnected(deps.socket.id); - disconnectCoordinator({ - socketId: socket.id, - roomManager, - runtimeRegistry, - gameOutput: disconnectOutputAdapters.game, - roomOutput: disconnectOutputAdapters.room, + disconnectCoordinator({ + socketId: deps.socket.id, + roomManager: deps.roomManager, + runtimeRegistry: deps.runtimeRegistry, + gameOutput: disconnectOutputAdapters.game, + roomOutput: disconnectOutputAdapters.room, + }); + }, + }, + ]; + + registerUnguardedEvents( + (event, callback) => { + deps.socket.on(event, callback); + }, + perSocketEventDefinitions, + ); + }, + }, + ]; + + registerEvents( + (event, callback) => { + io.on(event, (socket) => { + callback(socket); }); - }); - }); + }, + onConnectEventDefinitions, + ); }; diff --git a/apps/server/src/network/handlers/room/registerRoomHandlers.ts b/apps/server/src/network/handlers/room/registerRoomHandlers.ts index 91690d1..246ab11 100644 --- a/apps/server/src/network/handlers/room/registerRoomHandlers.ts +++ b/apps/server/src/network/handlers/room/registerRoomHandlers.ts @@ -16,19 +16,21 @@ handleJoinRoomEvent, type JoinRoomOrchestratorDeps, } from "./roomEventOrchestrators"; +import { + registerGuardedEvents, + type GuardedEventDefinition, +} from "@server/network/handlers/eventDefinitionRegistrar"; + +type JoinRoomEventDefinition = GuardedEventDefinition< + typeof protocol.SocketEvents.JOIN_ROOM, + Parameters[1] +>; /** ルーム受信イベントごとの入力検証関数を保持するテーブル */ const roomPayloadValidators = { [protocol.SocketEvents.JOIN_ROOM]: isJoinRoomPayload, } as const; -/** 検証付きルームイベント登録定義 */ -type GuardedRoomEventDefinition = { - event: keyof typeof roomPayloadValidators; - validator: (value: unknown) => value is unknown; - orchestrate: (payload: unknown) => Promise; -}; - /** ルームイベント調停で利用する依存束を生成する */ const createJoinRoomOrchestratorDeps = ( socket: Socket, @@ -64,27 +66,15 @@ const { guardOnEvent } = createPayloadGuard(socket.id); // 検証が必要なイベントを宣言的に登録する - const guardedRoomEventDefinitions: GuardedRoomEventDefinition[] = [ + const guardedRoomEventDefinitions: JoinRoomEventDefinition[] = [ { event: protocol.SocketEvents.JOIN_ROOM, validator: roomPayloadValidators[protocol.SocketEvents.JOIN_ROOM], orchestrate: async (payload) => { - await handleJoinRoomEvent( - orchestratorDeps, - payload as Parameters[1], - ); + await handleJoinRoomEvent(orchestratorDeps, payload); }, }, ]; - guardedRoomEventDefinitions.forEach((definition) => { - const guard = guardOnEvent(definition.event, definition.validator); - onEvent(definition.event, async (payload) => { - if (!guard(payload)) { - return; - } - - await definition.orchestrate(payload); - }); - }); + registerGuardedEvents(onEvent, guardOnEvent, guardedRoomEventDefinitions); };