From 2afa2922e9812e484de358b6841c140ee34fbfe6 Mon Sep 17 00:00:00 2001 From: seavor Date: Mon, 20 Apr 2026 00:37:23 -0500 Subject: [PATCH] websocket cleanup --- .../src/api/request/ModeratorRequestImpl.ts | 16 +++++++++ webclient/src/store/rooms/rooms.actions.ts | 8 ++++- .../src/store/rooms/rooms.reducer.spec.ts | 8 ----- webclient/src/store/rooms/rooms.reducer.ts | 3 -- webclient/src/store/rooms/rooms.types.ts | 4 +-- webclient/src/store/server/server.actions.ts | 33 ++++++++++++++++++- .../src/store/server/server.reducer.spec.ts | 19 ----------- webclient/src/store/server/server.reducer.ts | 33 ++----------------- webclient/src/store/server/server.types.ts | 4 +-- .../src/websocket/commands/room/joinGame.ts | 2 +- .../commands/room/roomCommands.spec.ts | 2 +- .../src/websocket/commands/session/ping.ts | 2 ++ .../commands/session/replaySubmitCode.ts | 8 ++--- .../session/sessionCommands-simple.spec.ts | 16 ++++----- .../events/common/commonEvents.spec.ts | 7 ---- .../src/websocket/events/common/index.ts | 3 -- .../events/common/playerPropertiesChanged.ts | 3 -- .../src/websocket/types/WebClientRequest.ts | 4 +++ 18 files changed, 82 insertions(+), 93 deletions(-) delete mode 100644 webclient/src/websocket/events/common/commonEvents.spec.ts delete mode 100644 webclient/src/websocket/events/common/index.ts delete mode 100644 webclient/src/websocket/events/common/playerPropertiesChanged.ts diff --git a/webclient/src/api/request/ModeratorRequestImpl.ts b/webclient/src/api/request/ModeratorRequestImpl.ts index 97984e397..d8b7dd40e 100644 --- a/webclient/src/api/request/ModeratorRequestImpl.ts +++ b/webclient/src/api/request/ModeratorRequestImpl.ts @@ -15,6 +15,14 @@ export class ModeratorRequestImpl implements WebsocketTypes.IModeratorRequest { ModeratorCommands.banFromServer(minutes, userName, address, reason, visibleReason, clientid, removeMessages); } + forceActivateUser(usernameToActivate: string, moderatorName: string): void { + ModeratorCommands.forceActivateUser(usernameToActivate, moderatorName); + } + + getAdminNotes(userName: string): void { + ModeratorCommands.getAdminNotes(userName); + } + getBanHistory(userName: string): void { ModeratorCommands.getBanHistory(userName); } @@ -27,6 +35,14 @@ export class ModeratorRequestImpl implements WebsocketTypes.IModeratorRequest { ModeratorCommands.getWarnList(modName, userName, userClientid); } + grantReplayAccess(replayId: number, moderatorName: string): void { + ModeratorCommands.grantReplayAccess(replayId, moderatorName); + } + + updateAdminNotes(userName: string, notes: string): void { + ModeratorCommands.updateAdminNotes(userName, notes); + } + viewLogHistory(filters: Data.ViewLogHistoryParams): void { ModeratorCommands.viewLogHistory(filters); } diff --git a/webclient/src/store/rooms/rooms.actions.ts b/webclient/src/store/rooms/rooms.actions.ts index bfb921079..df59d1483 100644 --- a/webclient/src/store/rooms/rooms.actions.ts +++ b/webclient/src/store/rooms/rooms.actions.ts @@ -1,5 +1,11 @@ +import { createAction } from '@reduxjs/toolkit'; + import { roomsSlice } from './rooms.reducer'; -export const Actions = roomsSlice.actions; +const SignalActions = { + gameCreated: createAction<{ roomId: number }>('rooms/gameCreated'), +}; + +export const Actions = { ...roomsSlice.actions, ...SignalActions }; export type RoomsAction = ReturnType; diff --git a/webclient/src/store/rooms/rooms.reducer.spec.ts b/webclient/src/store/rooms/rooms.reducer.spec.ts index 554ce3ac6..71d0fd5df 100644 --- a/webclient/src/store/rooms/rooms.reducer.spec.ts +++ b/webclient/src/store/rooms/rooms.reducer.spec.ts @@ -318,14 +318,6 @@ describe('REMOVE_MESSAGES', () => { }); -describe('GAME_CREATED', () => { - it('returns state unchanged', () => { - const state = makeRoomsState(); - const result = roomsReducer(state, Actions.gameCreated({ roomId: 1 })); - expect(result).toEqual(state); - }); -}); - describe('JOINED_GAME', () => { it('sets joinedGameIds[roomId][gameId] = true', () => { diff --git a/webclient/src/store/rooms/rooms.reducer.ts b/webclient/src/store/rooms/rooms.reducer.ts index 38a771d31..31a961e72 100644 --- a/webclient/src/store/rooms/rooms.reducer.ts +++ b/webclient/src/store/rooms/rooms.reducer.ts @@ -190,9 +190,6 @@ export const roomsSlice = createSlice({ state.joinedGameIds[roomId][gameId] = true; }, - // Signal-only; kept for discriminated-union exhaustiveness. - gameCreated: (_state, _action: PayloadAction<{ roomId: number }>) => {}, - selectGame: (state, action: PayloadAction<{ roomId: number; gameId: number | undefined }>) => { const { roomId, gameId } = action.payload; state.selectedGameIds[roomId] = gameId; diff --git a/webclient/src/store/rooms/rooms.types.ts b/webclient/src/store/rooms/rooms.types.ts index 43c4f1a98..607e6cc47 100644 --- a/webclient/src/store/rooms/rooms.types.ts +++ b/webclient/src/store/rooms/rooms.types.ts @@ -1,6 +1,6 @@ -import { roomsSlice } from './rooms.reducer'; +import { Actions } from './rooms.actions'; -const a = roomsSlice.actions; +const a = Actions; export const Types = { CLEAR_STORE: a.clearStore.type, diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts index c0ef7ed3d..1b16afd01 100644 --- a/webclient/src/store/server/server.actions.ts +++ b/webclient/src/store/server/server.actions.ts @@ -1,5 +1,36 @@ +import { createAction } from '@reduxjs/toolkit'; +import { WebsocketTypes } from '@app/websocket/types'; + import { serverSlice } from './server.reducer'; -export const Actions = serverSlice.actions; +const SignalActions = { + accountAwaitingActivation: createAction<{ options: WebsocketTypes.PendingActivationContext }>('server/accountAwaitingActivation'), + accountActivationFailed: createAction('server/accountActivationFailed'), + accountActivationSuccess: createAction('server/accountActivationSuccess'), + loginSuccessful: createAction<{ options: WebsocketTypes.LoginSuccessContext }>('server/loginSuccessful'), + loginFailed: createAction('server/loginFailed'), + connectionFailed: createAction('server/connectionFailed'), + testConnectionSuccessful: createAction('server/testConnectionSuccessful'), + testConnectionFailed: createAction('server/testConnectionFailed'), + registrationRequiresEmail: createAction('server/registrationRequiresEmail'), + registrationSuccess: createAction('server/registrationSuccess'), + registrationEmailError: createAction<{ error: string }>('server/registrationEmailError'), + registrationPasswordError: createAction<{ error: string }>('server/registrationPasswordError'), + registrationUserNameError: createAction<{ error: string }>('server/registrationUserNameError'), + resetPassword: createAction('server/resetPassword'), + resetPasswordFailed: createAction('server/resetPasswordFailed'), + resetPasswordChallenge: createAction('server/resetPasswordChallenge'), + resetPasswordSuccess: createAction('server/resetPasswordSuccess'), + reloadConfig: createAction('server/reloadConfig'), + shutdownServer: createAction('server/shutdownServer'), + updateServerMessage: createAction('server/updateServerMessage'), + accountPasswordChange: createAction('server/accountPasswordChange'), + addToList: createAction<{ list: string; userName: string }>('server/addToList'), + removeFromList: createAction<{ list: string; userName: string }>('server/removeFromList'), + grantReplayAccess: createAction<{ replayId: number; moderatorName: string }>('server/grantReplayAccess'), + forceActivateUser: createAction<{ usernameToActivate: string; moderatorName: string }>('server/forceActivateUser'), +}; + +export const Actions = { ...serverSlice.actions, ...SignalActions }; export type ServerAction = ReturnType; diff --git a/webclient/src/store/server/server.reducer.spec.ts b/webclient/src/store/server/server.reducer.spec.ts index 328e10aa5..7ba081626 100644 --- a/webclient/src/store/server/server.reducer.spec.ts +++ b/webclient/src/store/server/server.reducer.spec.ts @@ -5,7 +5,6 @@ import { serverReducer, MAX_USER_MESSAGES } from './server.reducer'; import { Actions } from './server.actions'; import { makeBanHistoryItem, - makePendingActivationContext, makeDeckList, makeDeckTreeItem, makeGame, @@ -62,24 +61,6 @@ describe('Account & Connection', () => { expect(result.status.connectionAttemptMade).toBe(true); }); - it('ACCOUNT_AWAITING_ACTIVATION → returns state unchanged', () => { - const options = makePendingActivationContext(); - const state = makeServerState(); - const result = serverReducer(state, Actions.accountAwaitingActivation({ options })); - expect(result).toEqual(state); - }); - - it('ACCOUNT_ACTIVATION_SUCCESS → returns state unchanged', () => { - const state = makeServerState(); - const result = serverReducer(state, Actions.accountActivationSuccess()); - expect(result).toEqual(state); - }); - - it('ACCOUNT_ACTIVATION_FAILED → returns state unchanged', () => { - const state = makeServerState(); - const result = serverReducer(state, Actions.accountActivationFailed()); - expect(result).toEqual(state); - }); }); diff --git a/webclient/src/store/server/server.reducer.ts b/webclient/src/store/server/server.reducer.ts index 1aa7efc8b..140c89969 100644 --- a/webclient/src/store/server/server.reducer.ts +++ b/webclient/src/store/server/server.reducer.ts @@ -185,7 +185,7 @@ export const serverSlice = createSlice({ updateUser: (state, action: PayloadAction<{ user: Partial }>) => { if (state.user) { - Object.assign(state.user, action.payload.user); + state.user = create(Data.ServerInfo_UserSchema, { ...state.user, ...action.payload.user }); } else { state.user = action.payload.user as Data.ServerInfo_User; } @@ -393,42 +393,15 @@ export const serverSlice = createSlice({ accountEditChanged: (state, action: PayloadAction<{ user: Partial }>) => { if (state.user) { - Object.assign(state.user, action.payload.user); + state.user = create(Data.ServerInfo_UserSchema, { ...state.user, ...action.payload.user }); } }, accountImageChanged: (state, action: PayloadAction<{ user: Partial }>) => { if (state.user) { - Object.assign(state.user, action.payload.user); + state.user = create(Data.ServerInfo_UserSchema, { ...state.user, ...action.payload.user }); } }, - - // Signal-only action types — no state mutation, defined so type strings are generated - accountAwaitingActivation: (_state, _action: PayloadAction<{ options: WebsocketTypes.PendingActivationContext }>) => {}, - accountActivationFailed: (_state) => {}, - accountActivationSuccess: (_state) => {}, - loginSuccessful: (_state, _action: PayloadAction<{ options: WebsocketTypes.LoginSuccessContext }>) => {}, - loginFailed: (_state) => {}, - connectionFailed: (_state) => {}, - testConnectionSuccessful: (_state) => {}, - testConnectionFailed: (_state) => {}, - registrationRequiresEmail: (_state) => {}, - registrationSuccess: (_state) => {}, - registrationEmailError: (_state, _action: PayloadAction<{ error: string }>) => {}, - registrationPasswordError: (_state, _action: PayloadAction<{ error: string }>) => {}, - registrationUserNameError: (_state, _action: PayloadAction<{ error: string }>) => {}, - resetPassword: (_state) => {}, - resetPasswordFailed: (_state) => {}, - resetPasswordChallenge: (_state) => {}, - resetPasswordSuccess: (_state) => {}, - reloadConfig: (_state) => {}, - shutdownServer: (_state) => {}, - updateServerMessage: (_state) => {}, - accountPasswordChange: (_state) => {}, - addToList: (_state, _action: PayloadAction<{ list: string; userName: string }>) => {}, - removeFromList: (_state, _action: PayloadAction<{ list: string; userName: string }>) => {}, - grantReplayAccess: (_state, _action: PayloadAction<{ replayId: number; moderatorName: string }>) => {}, - forceActivateUser: (_state, _action: PayloadAction<{ usernameToActivate: string; moderatorName: string }>) => {}, }, }); diff --git a/webclient/src/store/server/server.types.ts b/webclient/src/store/server/server.types.ts index d3def9004..d6224d6ac 100644 --- a/webclient/src/store/server/server.types.ts +++ b/webclient/src/store/server/server.types.ts @@ -1,6 +1,6 @@ -import { serverSlice } from './server.reducer'; +import { Actions } from './server.actions'; -const a = serverSlice.actions; +const a = Actions; export const Types = { INITIALIZED: a.initialized.type, diff --git a/webclient/src/websocket/commands/room/joinGame.ts b/webclient/src/websocket/commands/room/joinGame.ts index d7b4b8d19..c9d25811d 100644 --- a/webclient/src/websocket/commands/room/joinGame.ts +++ b/webclient/src/websocket/commands/room/joinGame.ts @@ -13,7 +13,7 @@ const ERROR_MESSAGES: Record = { [Response_ResponseCode.RespGameFull]: 'The game is already full.', [Response_ResponseCode.RespWrongPassword]: 'Wrong password.', [Response_ResponseCode.RespSpectatorsNotAllowed]: 'Spectators are not allowed in this game.', - [Response_ResponseCode.RespOnlyBuddies]: "This game is only open to its creator's buddies.", + [Response_ResponseCode.RespOnlyBuddies]: 'This game is only open to its creator\'s buddies.', [Response_ResponseCode.RespUserLevelTooLow]: 'This game is only open to registered users.', [Response_ResponseCode.RespInIgnoreList]: 'You are being ignored by the creator of this game.', }; diff --git a/webclient/src/websocket/commands/room/roomCommands.spec.ts b/webclient/src/websocket/commands/room/roomCommands.spec.ts index d1e81477f..e0c37a31a 100644 --- a/webclient/src/websocket/commands/room/roomCommands.spec.ts +++ b/webclient/src/websocket/commands/room/roomCommands.spec.ts @@ -75,7 +75,7 @@ describe('joinGame', () => { [Response_ResponseCode.RespGameFull, 'The game is already full.'], [Response_ResponseCode.RespWrongPassword, 'Wrong password.'], [Response_ResponseCode.RespSpectatorsNotAllowed, 'Spectators are not allowed in this game.'], - [Response_ResponseCode.RespOnlyBuddies, "This game is only open to its creator's buddies."], + [Response_ResponseCode.RespOnlyBuddies, 'This game is only open to its creator\'s buddies.'], [Response_ResponseCode.RespUserLevelTooLow, 'This game is only open to registered users.'], [Response_ResponseCode.RespInIgnoreList, 'You are being ignored by the creator of this game.'], ]; diff --git a/webclient/src/websocket/commands/session/ping.ts b/webclient/src/websocket/commands/session/ping.ts index fab3c9272..c0491a654 100644 --- a/webclient/src/websocket/commands/session/ping.ts +++ b/webclient/src/websocket/commands/session/ping.ts @@ -3,6 +3,8 @@ import { WebClient } from '../../WebClient'; import { Command_Ping_ext, Command_PingSchema } from '@app/generated'; export function ping(pingReceived: () => void): void { + // Uses `onResponse` (not `onSuccess`) so KeepAliveService treats any server + // reply as proof of life, independent of responseCode. WebClient.instance.protobuf.sendSessionCommand(Command_Ping_ext, create(Command_PingSchema), { onResponse: () => pingReceived(), }); diff --git a/webclient/src/websocket/commands/session/replaySubmitCode.ts b/webclient/src/websocket/commands/session/replaySubmitCode.ts index aa0e403e0..22ef418bc 100644 --- a/webclient/src/websocket/commands/session/replaySubmitCode.ts +++ b/webclient/src/websocket/commands/session/replaySubmitCode.ts @@ -4,15 +4,15 @@ import { Command_ReplaySubmitCode_ext, Command_ReplaySubmitCodeSchema } from '@a export function replaySubmitCode( replayCode: string, - onSuccess?: () => void, - onError?: (responseCode: number) => void, + onSubmitted?: () => void, + onFailure?: (responseCode: number) => void, ): void { WebClient.instance.protobuf.sendSessionCommand( Command_ReplaySubmitCode_ext, create(Command_ReplaySubmitCodeSchema, { replayCode }), { - onSuccess, - onError, + onSuccess: onSubmitted, + onError: onFailure, } ); } diff --git a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts index 289ca227a..486526fbd 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts @@ -468,18 +468,18 @@ describe('replaySubmitCode', () => { ); }); - it('forwards onSuccess callback', () => { - const onSuccess = vi.fn(); - replaySubmitCode('42-abc123', onSuccess); + it('forwards onSubmitted callback', () => { + const onSubmitted = vi.fn(); + replaySubmitCode('42-abc123', onSubmitted); invokeOnSuccess(); - expect(onSuccess).toHaveBeenCalled(); + expect(onSubmitted).toHaveBeenCalled(); }); - it('forwards onError callback', () => { - const onError = vi.fn(); - replaySubmitCode('42-abc123', undefined, onError); + it('forwards onFailure callback', () => { + const onFailure = vi.fn(); + replaySubmitCode('42-abc123', undefined, onFailure); invokeCallback('onError', 404); - expect(onError).toHaveBeenCalledWith(404); + expect(onFailure).toHaveBeenCalledWith(404); }); }); diff --git a/webclient/src/websocket/events/common/commonEvents.spec.ts b/webclient/src/websocket/events/common/commonEvents.spec.ts deleted file mode 100644 index cdfaa86dc..000000000 --- a/webclient/src/websocket/events/common/commonEvents.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { CommonEvents } from './index'; - -describe('CommonEvents', () => { - it('is an empty event map (all common events were moved to game/session events)', () => { - expect(CommonEvents).toEqual([]); - }); -}); diff --git a/webclient/src/websocket/events/common/index.ts b/webclient/src/websocket/events/common/index.ts deleted file mode 100644 index cda3024f3..000000000 --- a/webclient/src/websocket/events/common/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { SessionExtensionRegistry } from '../session'; - -export const CommonEvents: SessionExtensionRegistry = []; diff --git a/webclient/src/websocket/events/common/playerPropertiesChanged.ts b/webclient/src/websocket/events/common/playerPropertiesChanged.ts deleted file mode 100644 index ff2527fd5..000000000 --- a/webclient/src/websocket/events/common/playerPropertiesChanged.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Event_PlayerPropertiesChanged is handled as a game event in websocket/events/game/playerPropertiesChanged.ts -// This file is retained for reference but is no longer registered in CommonEvents. -export {}; diff --git a/webclient/src/websocket/types/WebClientRequest.ts b/webclient/src/websocket/types/WebClientRequest.ts index ab878969a..533346f97 100644 --- a/webclient/src/websocket/types/WebClientRequest.ts +++ b/webclient/src/websocket/types/WebClientRequest.ts @@ -107,9 +107,13 @@ export interface IModeratorRequest { clientid?: string, removeMessages?: number ): void; + forceActivateUser(usernameToActivate: string, moderatorName: string): void; + getAdminNotes(userName: string): void; getBanHistory(userName: string): void; getWarnHistory(userName: string): void; getWarnList(modName: string, userName: string, userClientid: string): void; + grantReplayAccess(replayId: number, moderatorName: string): void; + updateAdminNotes(userName: string, notes: string): void; viewLogHistory(filters: ViewLogHistoryParams): void; warnUser(userName: string, reason: string, clientid?: string, removeMessages?: number): void; }