From 141f0e59f54a9dce67af9a38a57694ed78b46333 Mon Sep 17 00:00:00 2001 From: seavor Date: Tue, 14 Apr 2026 14:39:46 -0500 Subject: [PATCH] refactor web socket layer --- .../src/api/AuthenticationService.spec.ts | 17 +- webclient/src/api/AuthenticationService.tsx | 6 +- webclient/src/forms/LoginForm/LoginForm.tsx | 6 +- webclient/src/store/index.ts | 2 +- .../store/server/__mocks__/server-fixtures.ts | 1 + .../src/store/server/server.actions.spec.ts | 8 + webclient/src/store/server/server.actions.ts | 3 + .../src/store/server/server.dispatch.spec.ts | 10 + webclient/src/store/server/server.dispatch.ts | 3 + .../src/store/server/server.interfaces.ts | 1 + .../src/store/server/server.reducer.spec.ts | 6 + webclient/src/store/server/server.reducer.ts | 7 + .../src/store/server/server.selectors.spec.ts | 14 +- .../src/store/server/server.selectors.ts | 1 + webclient/src/store/server/server.types.ts | 1 + webclient/src/websocket/WebClient.spec.ts | 62 +++-- webclient/src/websocket/WebClient.ts | 49 +--- .../__mocks__/sessionCommandMocks.ts | 6 +- .../src/websocket/commands/admin/adjustMod.ts | 4 +- .../commands/admin/adminCommands.spec.ts | 19 +- .../websocket/commands/admin/reloadConfig.ts | 4 +- .../commands/admin/shutdownServer.ts | 4 +- .../commands/admin/updateServerMessage.ts | 4 +- .../src/websocket/commands/game/attachCard.ts | 4 +- .../commands/game/changeZoneProperties.ts | 4 +- .../src/websocket/commands/game/concede.ts | 4 +- .../websocket/commands/game/createArrow.ts | 4 +- .../websocket/commands/game/createCounter.ts | 4 +- .../websocket/commands/game/createToken.ts | 4 +- .../src/websocket/commands/game/deckSelect.ts | 4 +- .../src/websocket/commands/game/delCounter.ts | 4 +- .../websocket/commands/game/deleteArrow.ts | 4 +- .../src/websocket/commands/game/drawCards.ts | 4 +- .../src/websocket/commands/game/dumpZone.ts | 4 +- .../src/websocket/commands/game/flipCard.ts | 4 +- .../commands/game/gameCommands.spec.ts | 93 ++++--- .../src/websocket/commands/game/gameSay.ts | 4 +- .../websocket/commands/game/incCardCounter.ts | 4 +- .../src/websocket/commands/game/incCounter.ts | 4 +- .../src/websocket/commands/game/judge.ts | 4 +- .../websocket/commands/game/kickFromGame.ts | 4 +- .../src/websocket/commands/game/leaveGame.ts | 4 +- .../src/websocket/commands/game/moveCard.ts | 4 +- .../src/websocket/commands/game/mulligan.ts | 4 +- .../src/websocket/commands/game/nextTurn.ts | 4 +- .../src/websocket/commands/game/readyStart.ts | 4 +- .../websocket/commands/game/revealCards.ts | 4 +- .../websocket/commands/game/reverseTurn.ts | 4 +- .../websocket/commands/game/setActivePhase.ts | 4 +- .../websocket/commands/game/setCardAttr.ts | 4 +- .../websocket/commands/game/setCardCounter.ts | 4 +- .../src/websocket/commands/game/setCounter.ts | 4 +- .../commands/game/setSideboardLock.ts | 4 +- .../commands/game/setSideboardPlan.ts | 4 +- .../src/websocket/commands/game/shuffle.ts | 4 +- .../src/websocket/commands/game/unconcede.ts | 4 +- .../src/websocket/commands/game/undoDraw.ts | 4 +- .../commands/moderator/banFromServer.ts | 4 +- .../commands/moderator/forceActivateUser.ts | 4 +- .../commands/moderator/getAdminNotes.ts | 4 +- .../commands/moderator/getBanHistory.ts | 4 +- .../commands/moderator/getWarnHistory.ts | 4 +- .../commands/moderator/getWarnList.ts | 4 +- .../commands/moderator/grantReplayAccess.ts | 14 +- .../moderator/moderatorCommands.spec.ts | 37 +-- .../commands/moderator/updateAdminNotes.ts | 4 +- .../commands/moderator/viewLogHistory.ts | 4 +- .../websocket/commands/moderator/warnUser.ts | 4 +- .../src/websocket/commands/room/createGame.ts | 4 +- .../src/websocket/commands/room/joinGame.ts | 4 +- .../src/websocket/commands/room/leaveRoom.ts | 4 +- .../commands/room/roomCommands.spec.ts | 23 +- .../src/websocket/commands/room/roomSay.ts | 4 +- .../websocket/commands/session/accountEdit.ts | 4 +- .../commands/session/accountImage.ts | 4 +- .../commands/session/accountPassword.ts | 4 +- .../websocket/commands/session/activate.ts | 6 +- .../websocket/commands/session/addToList.ts | 4 +- .../src/websocket/commands/session/deckDel.ts | 4 +- .../websocket/commands/session/deckDelDir.ts | 4 +- .../websocket/commands/session/deckList.ts | 4 +- .../websocket/commands/session/deckNewDir.ts | 4 +- .../websocket/commands/session/deckUpload.ts | 4 +- .../session/forgotPasswordChallenge.ts | 6 +- .../commands/session/forgotPasswordRequest.ts | 6 +- .../commands/session/forgotPasswordReset.ts | 6 +- .../commands/session/getGamesOfUser.ts | 4 +- .../websocket/commands/session/getUserInfo.ts | 4 +- .../websocket/commands/session/joinRoom.ts | 4 +- .../websocket/commands/session/listRooms.ts | 4 +- .../websocket/commands/session/listUsers.ts | 4 +- .../src/websocket/commands/session/login.ts | 6 +- .../src/websocket/commands/session/message.ts | 4 +- .../src/websocket/commands/session/ping.ts | 4 +- .../websocket/commands/session/register.ts | 6 +- .../commands/session/removeFromList.ts | 4 +- .../commands/session/replayDeleteMatch.ts | 4 +- .../commands/session/replayGetCode.ts | 4 +- .../websocket/commands/session/replayList.ts | 4 +- .../commands/session/replayModifyMatch.ts | 4 +- .../commands/session/replaySubmitCode.ts | 4 +- .../commands/session/requestPasswordSalt.ts | 6 +- .../session/sessionCommands-complex.spec.ts | 33 +-- .../session/sessionCommands-simple.spec.ts | 57 ++--- webclient/src/websocket/config.ts | 27 ++ .../src/websocket/events/common/index.ts | 2 +- webclient/src/websocket/events/game/index.ts | 2 +- webclient/src/websocket/events/room/index.ts | 2 +- .../src/websocket/events/session/index.ts | 2 +- .../src/websocket/events/session/listRooms.ts | 4 +- .../events/session/serverIdentification.ts | 3 +- .../events/session/sessionEvents.spec.ts | 14 +- .../persistence/SessionPersistence.spec.ts | 18 ++ .../persistence/SessionPersistence.ts | 4 + .../websocket/services/BackendService.spec.ts | 131 ---------- .../src/websocket/services/BackendService.ts | 114 --------- .../services/KeepAliveService.spec.ts | 18 +- .../services/ProtobufService.spec.ts | 230 +++++++++++------- .../src/websocket/services/ProtobufService.ts | 174 +++++++------ .../services/WebSocketService.spec.ts | 44 ++-- .../websocket/services/WebSocketService.ts | 25 +- .../services/command-options.spec.ts | 59 +++++ .../src/websocket/services/command-options.ts | 54 ++++ .../src/websocket/services/protobuf-types.ts | 44 ++++ 124 files changed, 927 insertions(+), 853 deletions(-) create mode 100644 webclient/src/websocket/config.ts delete mode 100644 webclient/src/websocket/services/BackendService.spec.ts delete mode 100644 webclient/src/websocket/services/BackendService.ts create mode 100644 webclient/src/websocket/services/command-options.spec.ts create mode 100644 webclient/src/websocket/services/command-options.ts create mode 100644 webclient/src/websocket/services/protobuf-types.ts diff --git a/webclient/src/api/AuthenticationService.spec.ts b/webclient/src/api/AuthenticationService.spec.ts index 1ba4cd252..9d9dfcd79 100644 --- a/webclient/src/api/AuthenticationService.spec.ts +++ b/webclient/src/api/AuthenticationService.spec.ts @@ -3,9 +3,6 @@ vi.mock('websocket', () => ({ connect: vi.fn(), disconnect: vi.fn(), }, - webClient: { - connectionAttemptMade: false, - }, })); vi.mock('generated/proto/serverinfo_user_pb', () => ({ @@ -15,7 +12,7 @@ vi.mock('generated/proto/serverinfo_user_pb', () => ({ })); import { AuthenticationService } from './AuthenticationService'; -import { SessionCommands, webClient } from 'websocket'; +import { SessionCommands } from 'websocket'; import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; const testOptions: WebSocketConnectOptions = { host: 'localhost', port: '4748', userName: 'user', password: 'pw' }; @@ -124,16 +121,4 @@ describe('AuthenticationService', () => { expect(AuthenticationService.isAdmin()).toBeUndefined(); }); }); - - describe('connectionAttemptMade', () => { - it('returns webClient.connectionAttemptMade when false', () => { - (webClient as any).connectionAttemptMade = false; - expect(AuthenticationService.connectionAttemptMade()).toBe(false); - }); - - it('returns webClient.connectionAttemptMade when true', () => { - (webClient as any).connectionAttemptMade = true; - expect(AuthenticationService.connectionAttemptMade()).toBe(true); - }); - }); }); diff --git a/webclient/src/api/AuthenticationService.tsx b/webclient/src/api/AuthenticationService.tsx index c5128fcbd..eca17118c 100644 --- a/webclient/src/api/AuthenticationService.tsx +++ b/webclient/src/api/AuthenticationService.tsx @@ -1,5 +1,5 @@ import { StatusEnum, User, WebSocketConnectReason, WebSocketConnectOptions } from 'types'; -import { SessionCommands, webClient } from 'websocket'; +import { SessionCommands } from 'websocket'; import { ServerInfo_User_UserLevelFlag } from 'generated/proto/serverinfo_user_pb'; export class AuthenticationService { @@ -48,8 +48,4 @@ export class AuthenticationService { static isAdmin() { } - - static connectionAttemptMade() { - return webClient.connectionAttemptMade; - } } diff --git a/webclient/src/forms/LoginForm/LoginForm.tsx b/webclient/src/forms/LoginForm/LoginForm.tsx index ed387c045..20c819304 100644 --- a/webclient/src/forms/LoginForm/LoginForm.tsx +++ b/webclient/src/forms/LoginForm/LoginForm.tsx @@ -5,11 +5,12 @@ import { useTranslation } from 'react-i18next'; import Button from '@mui/material/Button'; -import { AuthenticationService } from 'api'; import { CheckboxField, InputField, KnownHosts } from 'components'; import { useAutoConnect } from 'hooks'; import { HostDTO, SettingDTO } from 'services'; import { APP_USER } from 'types'; +import { useAppSelector } from 'store'; +import { Selectors as ServerSelectors } from 'store/server'; import './LoginForm.css'; @@ -21,6 +22,7 @@ const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginForm const [host, setHost] = useState(null); const [useStoredPasswordLabel, setUseStoredPasswordLabel] = useState(false); const [autoConnect, setAutoConnect] = useAutoConnect(); + const connectionAttemptMade = useAppSelector(ServerSelectors.getConnectionAttemptMade); const validate = values => { const errors: any = {}; @@ -54,7 +56,7 @@ const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginForm useEffect(() => { SettingDTO.get(APP_USER).then((userSetting: SettingDTO) => { - if (userSetting?.autoConnect && !AuthenticationService.connectionAttemptMade()) { + if (userSetting?.autoConnect && !connectionAttemptMade) { HostDTO.getAll().then(hosts => { let lastSelectedHost = hosts.find(({ lastSelected }) => lastSelected); diff --git a/webclient/src/store/index.ts b/webclient/src/store/index.ts index 7c257b09e..2ee14345d 100644 --- a/webclient/src/store/index.ts +++ b/webclient/src/store/index.ts @@ -1,4 +1,4 @@ -export { store } from './store'; +export { store, useAppSelector, useAppDispatch } from './store'; // Common export { SortUtil } from './common'; diff --git a/webclient/src/store/server/__mocks__/server-fixtures.ts b/webclient/src/store/server/__mocks__/server-fixtures.ts index 05c2f9747..330ad5159 100644 --- a/webclient/src/store/server/__mocks__/server-fixtures.ts +++ b/webclient/src/store/server/__mocks__/server-fixtures.ts @@ -130,6 +130,7 @@ export function makeServerState(overrides: Partial = {}): ServerSta buddyList: [], ignoreList: [], status: { + connectionAttemptMade: false, state: StatusEnum.DISCONNECTED, description: null, }, diff --git a/webclient/src/store/server/server.actions.spec.ts b/webclient/src/store/server/server.actions.spec.ts index f6817d93f..b25e768f1 100644 --- a/webclient/src/store/server/server.actions.spec.ts +++ b/webclient/src/store/server/server.actions.spec.ts @@ -25,6 +25,10 @@ describe('Actions', () => { expect(Actions.clearStore()).toEqual({ type: Types.CLEAR_STORE }); }); + it('connectionAttempted', () => { + expect(Actions.connectionAttempted()).toEqual({ type: Types.CONNECTION_ATTEMPTED }); + }); + it('loginSuccessful', () => { const options = makeConnectOptions(); expect(Actions.loginSuccessful(options)).toEqual({ type: Types.LOGIN_SUCCESSFUL, options }); @@ -360,4 +364,8 @@ describe('Actions', () => { const gametypeMap = { 1: 'Standard' }; expect(Actions.gamesOfUser('alice', games, gametypeMap)).toEqual({ type: Types.GAMES_OF_USER, userName: 'alice', games, gametypeMap }); }); + + it('clearRegistrationErrors', () => { + expect(Actions.clearRegistrationErrors()).toEqual({ type: Types.CLEAR_REGISTRATION_ERRORS }); + }); }); diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts index f0c20b2fc..e0c5fbc84 100644 --- a/webclient/src/store/server/server.actions.ts +++ b/webclient/src/store/server/server.actions.ts @@ -14,6 +14,9 @@ export const Actions = { clearStore: () => ({ type: Types.CLEAR_STORE }), + connectionAttempted: () => ({ + type: Types.CONNECTION_ATTEMPTED + }), loginSuccessful: (options: WebSocketConnectOptions) => ({ type: Types.LOGIN_SUCCESSFUL, options diff --git a/webclient/src/store/server/server.dispatch.spec.ts b/webclient/src/store/server/server.dispatch.spec.ts index 5a4198d2f..ad2365a79 100644 --- a/webclient/src/store/server/server.dispatch.spec.ts +++ b/webclient/src/store/server/server.dispatch.spec.ts @@ -32,6 +32,11 @@ describe('Dispatch', () => { expect(store.dispatch).toHaveBeenCalledWith(Actions.clearStore()); }); + it('connectionAttempted dispatches Actions.connectionAttempted()', () => { + Dispatch.connectionAttempted(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.connectionAttempted()); + }); + it('loginSuccessful dispatches Actions.loginSuccessful()', () => { const options = makeConnectOptions(); Dispatch.loginSuccessful(options); @@ -391,4 +396,9 @@ describe('Dispatch', () => { Dispatch.gamesOfUser('alice', games, gametypeMap); expect(store.dispatch).toHaveBeenCalledWith(Actions.gamesOfUser('alice', games, gametypeMap)); }); + + it('clearRegistrationErrors dispatches correctly', () => { + Dispatch.clearRegistrationErrors(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.clearRegistrationErrors()); + }); }); diff --git a/webclient/src/store/server/server.dispatch.ts b/webclient/src/store/server/server.dispatch.ts index 6f0177a7f..2030a4dad 100644 --- a/webclient/src/store/server/server.dispatch.ts +++ b/webclient/src/store/server/server.dispatch.ts @@ -14,6 +14,9 @@ export const Dispatch = { clearStore: () => { store.dispatch(Actions.clearStore()); }, + connectionAttempted: () => { + store.dispatch(Actions.connectionAttempted()); + }, loginSuccessful: (options: WebSocketConnectOptions) => { store.dispatch(Actions.loginSuccessful(options)); }, diff --git a/webclient/src/store/server/server.interfaces.ts b/webclient/src/store/server/server.interfaces.ts index 6ee8f29fe..e4cc101fa 100644 --- a/webclient/src/store/server/server.interfaces.ts +++ b/webclient/src/store/server/server.interfaces.ts @@ -76,6 +76,7 @@ export interface ServerState { } export interface ServerStateStatus { + connectionAttemptMade: boolean; description: string; state: number; } diff --git a/webclient/src/store/server/server.reducer.spec.ts b/webclient/src/store/server/server.reducer.spec.ts index aa961dfe0..f3ec5bbde 100644 --- a/webclient/src/store/server/server.reducer.spec.ts +++ b/webclient/src/store/server/server.reducer.spec.ts @@ -55,6 +55,12 @@ describe('Initialisation', () => { // ── Account & Connection ───────────────────────────────────────────────────── describe('Account & Connection', () => { + it('CONNECTION_ATTEMPTED → sets connectionAttemptMade to true', () => { + const state = makeServerState({ status: { connectionAttemptMade: false, state: StatusEnum.DISCONNECTED, description: null } }); + const result = serverReducer(state, { type: Types.CONNECTION_ATTEMPTED }); + expect(result.status.connectionAttemptMade).toBe(true); + }); + it('ACCOUNT_AWAITING_ACTIVATION → returns state unchanged', () => { const options = makeConnectOptions(); const state = makeServerState(); diff --git a/webclient/src/store/server/server.reducer.ts b/webclient/src/store/server/server.reducer.ts index 859b0a168..17440339d 100644 --- a/webclient/src/store/server/server.reducer.ts +++ b/webclient/src/store/server/server.reducer.ts @@ -69,6 +69,7 @@ const initialState: ServerState = { ignoreList: [], status: { + connectionAttemptMade: false, state: StatusEnum.DISCONNECTED, description: null }, @@ -112,6 +113,12 @@ export const serverReducer = (state = initialState, action: ServerAction) => { initialized: true } } + case Types.CONNECTION_ATTEMPTED: { + return { + ...state, + status: { ...state.status, connectionAttemptMade: true } + }; + } case Types.ACCOUNT_AWAITING_ACTIVATION: { return state; } diff --git a/webclient/src/store/server/server.selectors.spec.ts b/webclient/src/store/server/server.selectors.spec.ts index 9adcdbaca..f8658524f 100644 --- a/webclient/src/store/server/server.selectors.spec.ts +++ b/webclient/src/store/server/server.selectors.spec.ts @@ -34,15 +34,20 @@ describe('Selectors', () => { }); it('getDescription → returns status.description', () => { - const state = makeServerState({ status: { state: StatusEnum.CONNECTED, description: 'ok' } }); + const state = makeServerState({ status: { connectionAttemptMade: false, state: StatusEnum.CONNECTED, description: 'ok' } }); expect(Selectors.getDescription(rootState(state))).toBe('ok'); }); it('getState → returns status.state', () => { - const state = makeServerState({ status: { state: StatusEnum.LOGGED_IN, description: null } }); + const state = makeServerState({ status: { connectionAttemptMade: false, state: StatusEnum.LOGGED_IN, description: null } }); expect(Selectors.getState(rootState(state))).toBe(StatusEnum.LOGGED_IN); }); + it('getConnectionAttemptMade → returns status.connectionAttemptMade', () => { + const state = makeServerState({ status: { connectionAttemptMade: true, state: StatusEnum.DISCONNECTED, description: null } }); + expect(Selectors.getConnectionAttemptMade(rootState(state))).toBe(true); + }); + it('getUser → returns user', () => { const user = makeUser({ name: 'Alice' }); const state = makeServerState({ user }); @@ -89,4 +94,9 @@ describe('Selectors', () => { const state = makeServerState({ backendDecks: null }); expect(Selectors.getBackendDecks(rootState(state))).toBeNull(); }); + + it('getRegistrationError → returns registrationError', () => { + const state = makeServerState({ registrationError: 'bad input' }); + expect(Selectors.getRegistrationError(rootState(state))).toBe('bad input'); + }); }); diff --git a/webclient/src/store/server/server.selectors.ts b/webclient/src/store/server/server.selectors.ts index 260757ab9..e7e0ca1bc 100644 --- a/webclient/src/store/server/server.selectors.ts +++ b/webclient/src/store/server/server.selectors.ts @@ -11,6 +11,7 @@ export const Selectors = { getVersion: ({ server }: State) => server.info.version, getDescription: ({ server }: State) => server.status.description, getState: ({ server }: State) => server.status.state, + getConnectionAttemptMade: ({ server }: State) => server.status.connectionAttemptMade, getUser: ({ server }: State) => server.user, getUsers: ({ server }: State) => server.users, getLogs: ({ server }: State) => server.logs, diff --git a/webclient/src/store/server/server.types.ts b/webclient/src/store/server/server.types.ts index 159e33094..89d3e7666 100644 --- a/webclient/src/store/server/server.types.ts +++ b/webclient/src/store/server/server.types.ts @@ -1,6 +1,7 @@ export const Types = { INITIALIZED: '[Server] Initialized', CLEAR_STORE: '[Server] Clear Store', + CONNECTION_ATTEMPTED: '[Server] Connection Attempted', LOGIN_SUCCESSFUL: '[Server] Login Successful', LOGIN_FAILED: '[Server] Login Failed', CONNECTION_CLOSED: '[Server] Connection Closed', diff --git a/webclient/src/websocket/WebClient.spec.ts b/webclient/src/websocket/WebClient.spec.ts index 4f27d5580..799c7e552 100644 --- a/webclient/src/websocket/WebClient.spec.ts +++ b/webclient/src/websocket/WebClient.spec.ts @@ -1,19 +1,27 @@ +const captured = vi.hoisted(() => ({ + wsOptions: null as any, + pbOptions: null as any, +})); + vi.mock('./services/WebSocketService', () => ({ - WebSocketService: vi.fn().mockImplementation(function WebSocketServiceImpl() { + WebSocketService: vi.fn().mockImplementation(function WebSocketServiceImpl(options: any) { + captured.wsOptions = options; return { message$: { subscribe: vi.fn() }, connect: vi.fn(), testConnect: vi.fn(), disconnect: vi.fn(), + send: vi.fn(), + checkReadyState: vi.fn().mockReturnValue(true), }; }), })); vi.mock('./services/ProtobufService', () => ({ - ProtobufService: vi.fn().mockImplementation(function ProtobufServiceImpl() { + ProtobufService: vi.fn().mockImplementation(function ProtobufServiceImpl(options: any) { + captured.pbOptions = options; return { handleMessageEvent: vi.fn(), - sendKeepAliveCommand: vi.fn(), resetCommands: vi.fn(), }; }), @@ -21,17 +29,22 @@ vi.mock('./services/ProtobufService', () => ({ vi.mock('./persistence', () => ({ RoomPersistence: { clearStore: vi.fn() }, - SessionPersistence: { clearStore: vi.fn(), initialized: vi.fn() }, + SessionPersistence: { clearStore: vi.fn(), initialized: vi.fn(), connectionAttempted: vi.fn() }, })); vi.mock('store', () => ({ GameDispatch: { clearStore: vi.fn() }, })); +vi.mock('./commands/session', () => ({ + ping: vi.fn(), +})); + import { WebClient } from './WebClient'; import { WebSocketService } from './services/WebSocketService'; import { ProtobufService } from './services/ProtobufService'; import { RoomPersistence, SessionPersistence } from './persistence'; +import { ping } from './commands/session'; import { StatusEnum } from 'types'; import { Subject } from 'rxjs'; import { Mock } from 'vitest'; @@ -42,20 +55,23 @@ describe('WebClient', () => { beforeEach(() => { vi.clearAllMocks(); - (ProtobufService as Mock).mockImplementation(function ProtobufServiceImpl() { + (ProtobufService as Mock).mockImplementation(function ProtobufServiceImpl(options: any) { + captured.pbOptions = options; return { handleMessageEvent: vi.fn(), - sendKeepAliveCommand: vi.fn(), resetCommands: vi.fn(), }; }); messageSubject = new Subject(); - (WebSocketService as Mock).mockImplementation(function WebSocketServiceImpl() { + (WebSocketService as Mock).mockImplementation(function WebSocketServiceImpl(options: any) { + captured.wsOptions = options; return { message$: messageSubject, connect: vi.fn(), testConnect: vi.fn(), disconnect: vi.fn(), + send: vi.fn(), + checkReadyState: vi.fn().mockReturnValue(true), }; }); // suppress console.log from constructor in non-test-env check @@ -80,10 +96,10 @@ describe('WebClient', () => { }); describe('connect', () => { - it('sets connectionAttemptMade to true', () => { + it('calls SessionPersistence.connectionAttempted', () => { const opts: any = { host: 'h', port: 1 }; client.connect(opts); - expect(client.connectionAttemptMade).toBe(true); + expect(SessionPersistence.connectionAttempted).toHaveBeenCalled(); }); it('stores options and calls socket.connect', () => { @@ -109,14 +125,6 @@ describe('WebClient', () => { }); }); - describe('keepAlive', () => { - it('delegates to protobuf.sendKeepAliveCommand', () => { - const pingCb = vi.fn(); - client.keepAlive(pingCb); - expect(client.protobuf.sendKeepAliveCommand).toHaveBeenCalledWith(pingCb); - }); - }); - describe('updateStatus', () => { it('sets the status', () => { client.updateStatus(StatusEnum.CONNECTED); @@ -136,4 +144,24 @@ describe('WebClient', () => { expect(RoomPersistence.clearStore).not.toHaveBeenCalled(); }); }); + + describe('constructor closures', () => { + it('keepAliveFn calls ping with the callback', () => { + const cb = vi.fn(); + captured.wsOptions.keepAliveFn(cb); + expect(ping).toHaveBeenCalledWith(cb); + }); + + it('send closure delegates to socket.send', () => { + const data = new Uint8Array([1, 2, 3]); + captured.pbOptions.send(data); + expect(client.socket.send).toHaveBeenCalledWith(data); + }); + + it('isOpen closure delegates to socket.checkReadyState', () => { + const result = captured.pbOptions.isOpen(); + expect(client.socket.checkReadyState).toHaveBeenCalledWith(WebSocket.OPEN); + expect(result).toBe(true); + }); + }); }); diff --git a/webclient/src/websocket/WebClient.ts b/webclient/src/websocket/WebClient.ts index 302a0da3f..5895c653a 100644 --- a/webclient/src/websocket/WebClient.ts +++ b/webclient/src/websocket/WebClient.ts @@ -2,47 +2,28 @@ import { StatusEnum, WebSocketConnectOptions } from 'types'; import { ProtobufService } from './services/ProtobufService'; import { WebSocketService } from './services/WebSocketService'; +import { ping } from './commands/session'; import { GameDispatch } from 'store'; import { RoomPersistence, SessionPersistence } from './persistence'; export class WebClient { - public socket = new WebSocketService(this); - public protobuf = new ProtobufService(this); - - public protocolVersion = 14; - public clientConfig = { - clientid: 'webatrice', - clientver: 'webclient-1.0 (2019-10-31)', - clientfeatures: [ - 'client_id', - 'client_ver', - 'feature_set', - 'room_chat_history', - 'client_warnings', - /* unimplemented features */ - 'forgot_password', - 'idle_client', - 'mod_log_lookup', - 'user_ban_history', - // satisfy server reqs for POC - 'websocket', - '2.7.0_min_version', - '2.8.0_min_version' - ] - }; - - public clientOptions = { - autojoinrooms: true, - keepalive: 5000 - }; + public socket: WebSocketService; + public protobuf: ProtobufService; public options: WebSocketConnectOptions; public status: StatusEnum; - public connectionAttemptMade = false; - constructor() { + this.socket = new WebSocketService({ + keepAliveFn: (cb) => ping(cb), + }); + + this.protobuf = new ProtobufService({ + send: (data) => this.socket.send(data), + isOpen: () => this.socket.checkReadyState(WebSocket.OPEN), + }); + this.socket.message$.subscribe((message: MessageEvent) => { this.protobuf.handleMessageEvent(message); }); @@ -55,7 +36,7 @@ export class WebClient { } public connect(options: WebSocketConnectOptions) { - this.connectionAttemptMade = true; + SessionPersistence.connectionAttempted(); this.options = options; this.socket.connect(options); } @@ -77,10 +58,6 @@ export class WebClient { } } - public keepAlive(pingReceived: () => void) { - this.protobuf.sendKeepAliveCommand(pingReceived); - } - private clearStores() { GameDispatch.clearStore(); RoomPersistence.clearStore(); diff --git a/webclient/src/websocket/__mocks__/sessionCommandMocks.ts b/webclient/src/websocket/__mocks__/sessionCommandMocks.ts index cbab4833c..227eca31c 100644 --- a/webclient/src/websocket/__mocks__/sessionCommandMocks.ts +++ b/webclient/src/websocket/__mocks__/sessionCommandMocks.ts @@ -17,11 +17,11 @@ export function makeWebClientMock() { testConnect: vi.fn(), disconnect: vi.fn(), updateStatus: vi.fn(), - clientConfig: { clientid: 'webatrice', clientver: '1.0', clientfeatures: [] }, options: {}, - protocolVersion: 14, status: 0, - connectionAttemptMade: false, + protobuf: { + sendSessionCommand: vi.fn(), + }, }; } diff --git a/webclient/src/websocket/commands/admin/adjustMod.ts b/webclient/src/websocket/commands/admin/adjustMod.ts index 7b61e322a..f6fba41e8 100644 --- a/webclient/src/websocket/commands/admin/adjustMod.ts +++ b/webclient/src/websocket/commands/admin/adjustMod.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_AdjustMod_ext, Command_AdjustModSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function adjustMod(userName: string, shouldBeMod?: boolean, shouldBeJudge?: boolean): void { - BackendService.sendAdminCommand(Command_AdjustMod_ext, create(Command_AdjustModSchema, { userName, shouldBeMod, shouldBeJudge }), { + webClient.protobuf.sendAdminCommand(Command_AdjustMod_ext, create(Command_AdjustModSchema, { userName, shouldBeMod, shouldBeJudge }), { onSuccess: () => { AdminPersistence.adjustMod(userName, shouldBeMod, shouldBeJudge); }, diff --git a/webclient/src/websocket/commands/admin/adminCommands.spec.ts b/webclient/src/websocket/commands/admin/adminCommands.spec.ts index fd1778e9f..d3422d148 100644 --- a/webclient/src/websocket/commands/admin/adminCommands.spec.ts +++ b/webclient/src/websocket/commands/admin/adminCommands.spec.ts @@ -1,7 +1,6 @@ -vi.mock('../../services/BackendService', () => ({ - BackendService: { - sendAdminCommand: vi.fn(), - }, +vi.mock('../../WebClient', () => ({ + __esModule: true, + default: { protobuf: { sendAdminCommand: vi.fn() } }, })); vi.mock('../../persistence', () => ({ @@ -14,7 +13,7 @@ vi.mock('../../persistence', () => ({ })); import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { AdminPersistence } from '../../persistence'; import { adjustMod } from './adjustMod'; import { reloadConfig } from './reloadConfig'; @@ -24,7 +23,7 @@ import { updateServerMessage } from './updateServerMessage'; import { Mock } from 'vitest'; const { invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendAdminCommand as Mock, + webClient.protobuf.sendAdminCommand as Mock, 2 ); @@ -37,7 +36,7 @@ describe('adjustMod', () => { it('calls sendAdminCommand with Command_AdjustMod', () => { adjustMod('alice', true, false); - expect(BackendService.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); }); it('onSuccess calls AdminPersistence.adjustMod', () => { @@ -54,7 +53,7 @@ describe('reloadConfig', () => { it('calls sendAdminCommand with Command_ReloadConfig', () => { reloadConfig(); - expect(BackendService.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); }); it('onSuccess calls AdminPersistence.reloadConfig', () => { @@ -71,7 +70,7 @@ describe('shutdownServer', () => { it('calls sendAdminCommand with Command_ShutdownServer', () => { shutdownServer('maintenance', 10); - expect(BackendService.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); }); it('onSuccess calls AdminPersistence.shutdownServer', () => { @@ -88,7 +87,7 @@ describe('updateServerMessage', () => { it('calls sendAdminCommand with Command_UpdateServerMessage', () => { updateServerMessage(); - expect(BackendService.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); }); it('onSuccess calls AdminPersistence.updateServerMessage', () => { diff --git a/webclient/src/websocket/commands/admin/reloadConfig.ts b/webclient/src/websocket/commands/admin/reloadConfig.ts index e72735c60..08c92ffee 100644 --- a/webclient/src/websocket/commands/admin/reloadConfig.ts +++ b/webclient/src/websocket/commands/admin/reloadConfig.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReloadConfig_ext, Command_ReloadConfigSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function reloadConfig(): void { - BackendService.sendAdminCommand(Command_ReloadConfig_ext, create(Command_ReloadConfigSchema), { + webClient.protobuf.sendAdminCommand(Command_ReloadConfig_ext, create(Command_ReloadConfigSchema), { onSuccess: () => { AdminPersistence.reloadConfig(); }, diff --git a/webclient/src/websocket/commands/admin/shutdownServer.ts b/webclient/src/websocket/commands/admin/shutdownServer.ts index 7fc32fb24..86cd75259 100644 --- a/webclient/src/websocket/commands/admin/shutdownServer.ts +++ b/webclient/src/websocket/commands/admin/shutdownServer.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ShutdownServer_ext, Command_ShutdownServerSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function shutdownServer(reason: string, minutes: number): void { - BackendService.sendAdminCommand(Command_ShutdownServer_ext, create(Command_ShutdownServerSchema, { reason, minutes }), { + webClient.protobuf.sendAdminCommand(Command_ShutdownServer_ext, create(Command_ShutdownServerSchema, { reason, minutes }), { onSuccess: () => { AdminPersistence.shutdownServer(); }, diff --git a/webclient/src/websocket/commands/admin/updateServerMessage.ts b/webclient/src/websocket/commands/admin/updateServerMessage.ts index d81b63028..4bd1c3f12 100644 --- a/webclient/src/websocket/commands/admin/updateServerMessage.ts +++ b/webclient/src/websocket/commands/admin/updateServerMessage.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_UpdateServerMessage_ext, Command_UpdateServerMessageSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function updateServerMessage(): void { - BackendService.sendAdminCommand(Command_UpdateServerMessage_ext, create(Command_UpdateServerMessageSchema), { + webClient.protobuf.sendAdminCommand(Command_UpdateServerMessage_ext, create(Command_UpdateServerMessageSchema), { onSuccess: () => { AdminPersistence.updateServerMessage(); }, diff --git a/webclient/src/websocket/commands/game/attachCard.ts b/webclient/src/websocket/commands/game/attachCard.ts index 08162f651..08c6b110f 100644 --- a/webclient/src/websocket/commands/game/attachCard.ts +++ b/webclient/src/websocket/commands/game/attachCard.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_AttachCardSchema, Command_AttachCard_ext } from 'generated/proto/command_attach_card_pb'; import { AttachCardParams } from 'types'; export function attachCard(gameId: number, params: AttachCardParams): void { - BackendService.sendGameCommand(gameId, Command_AttachCard_ext, create(Command_AttachCardSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_AttachCard_ext, create(Command_AttachCardSchema, params)); } diff --git a/webclient/src/websocket/commands/game/changeZoneProperties.ts b/webclient/src/websocket/commands/game/changeZoneProperties.ts index 7ee1feaca..a35e9ec25 100644 --- a/webclient/src/websocket/commands/game/changeZoneProperties.ts +++ b/webclient/src/websocket/commands/game/changeZoneProperties.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ChangeZonePropertiesSchema, Command_ChangeZoneProperties_ext } from 'generated/proto/command_change_zone_properties_pb'; import { ChangeZonePropertiesParams } from 'types'; export function changeZoneProperties(gameId: number, params: ChangeZonePropertiesParams): void { - BackendService.sendGameCommand(gameId, Command_ChangeZoneProperties_ext, create(Command_ChangeZonePropertiesSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_ChangeZoneProperties_ext, create(Command_ChangeZonePropertiesSchema, params)); } diff --git a/webclient/src/websocket/commands/game/concede.ts b/webclient/src/websocket/commands/game/concede.ts index 50e9c6d29..8aaad96b1 100644 --- a/webclient/src/websocket/commands/game/concede.ts +++ b/webclient/src/websocket/commands/game/concede.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ConcedeSchema, Command_Concede_ext } from 'generated/proto/command_concede_pb'; export function concede(gameId: number): void { - BackendService.sendGameCommand(gameId, Command_Concede_ext, create(Command_ConcedeSchema)); + webClient.protobuf.sendGameCommand(gameId, Command_Concede_ext, create(Command_ConcedeSchema)); } diff --git a/webclient/src/websocket/commands/game/createArrow.ts b/webclient/src/websocket/commands/game/createArrow.ts index 0a641585b..d85b3168e 100644 --- a/webclient/src/websocket/commands/game/createArrow.ts +++ b/webclient/src/websocket/commands/game/createArrow.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_CreateArrowSchema, Command_CreateArrow_ext } from 'generated/proto/command_create_arrow_pb'; import { CreateArrowParams } from 'types'; export function createArrow(gameId: number, params: CreateArrowParams): void { - BackendService.sendGameCommand(gameId, Command_CreateArrow_ext, create(Command_CreateArrowSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_CreateArrow_ext, create(Command_CreateArrowSchema, params)); } diff --git a/webclient/src/websocket/commands/game/createCounter.ts b/webclient/src/websocket/commands/game/createCounter.ts index 1bbf99478..53153efc3 100644 --- a/webclient/src/websocket/commands/game/createCounter.ts +++ b/webclient/src/websocket/commands/game/createCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_CreateCounterSchema, Command_CreateCounter_ext } from 'generated/proto/command_create_counter_pb'; import { CreateCounterParams } from 'types'; export function createCounter(gameId: number, params: CreateCounterParams): void { - BackendService.sendGameCommand(gameId, Command_CreateCounter_ext, create(Command_CreateCounterSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_CreateCounter_ext, create(Command_CreateCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/createToken.ts b/webclient/src/websocket/commands/game/createToken.ts index 4b554e688..06780afba 100644 --- a/webclient/src/websocket/commands/game/createToken.ts +++ b/webclient/src/websocket/commands/game/createToken.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_CreateTokenSchema, Command_CreateToken_ext } from 'generated/proto/command_create_token_pb'; import { CreateTokenParams } from 'types'; export function createToken(gameId: number, params: CreateTokenParams): void { - BackendService.sendGameCommand(gameId, Command_CreateToken_ext, create(Command_CreateTokenSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_CreateToken_ext, create(Command_CreateTokenSchema, params)); } diff --git a/webclient/src/websocket/commands/game/deckSelect.ts b/webclient/src/websocket/commands/game/deckSelect.ts index 60a874150..33905c7ac 100644 --- a/webclient/src/websocket/commands/game/deckSelect.ts +++ b/webclient/src/websocket/commands/game/deckSelect.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeckSelectSchema, Command_DeckSelect_ext } from 'generated/proto/command_deck_select_pb'; import { DeckSelectParams } from 'types'; export function deckSelect(gameId: number, params: DeckSelectParams): void { - BackendService.sendGameCommand(gameId, Command_DeckSelect_ext, create(Command_DeckSelectSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_DeckSelect_ext, create(Command_DeckSelectSchema, params)); } diff --git a/webclient/src/websocket/commands/game/delCounter.ts b/webclient/src/websocket/commands/game/delCounter.ts index b84fb5a0f..ba9e10d55 100644 --- a/webclient/src/websocket/commands/game/delCounter.ts +++ b/webclient/src/websocket/commands/game/delCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DelCounterSchema, Command_DelCounter_ext } from 'generated/proto/command_del_counter_pb'; import { DelCounterParams } from 'types'; export function delCounter(gameId: number, params: DelCounterParams): void { - BackendService.sendGameCommand(gameId, Command_DelCounter_ext, create(Command_DelCounterSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_DelCounter_ext, create(Command_DelCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/deleteArrow.ts b/webclient/src/websocket/commands/game/deleteArrow.ts index 67929e2a2..dc15a2d00 100644 --- a/webclient/src/websocket/commands/game/deleteArrow.ts +++ b/webclient/src/websocket/commands/game/deleteArrow.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeleteArrowSchema, Command_DeleteArrow_ext } from 'generated/proto/command_delete_arrow_pb'; import { DeleteArrowParams } from 'types'; export function deleteArrow(gameId: number, params: DeleteArrowParams): void { - BackendService.sendGameCommand(gameId, Command_DeleteArrow_ext, create(Command_DeleteArrowSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_DeleteArrow_ext, create(Command_DeleteArrowSchema, params)); } diff --git a/webclient/src/websocket/commands/game/drawCards.ts b/webclient/src/websocket/commands/game/drawCards.ts index ad5a08771..1ba96ca8f 100644 --- a/webclient/src/websocket/commands/game/drawCards.ts +++ b/webclient/src/websocket/commands/game/drawCards.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DrawCardsSchema, Command_DrawCards_ext } from 'generated/proto/command_draw_cards_pb'; import { DrawCardsParams } from 'types'; export function drawCards(gameId: number, params: DrawCardsParams): void { - BackendService.sendGameCommand(gameId, Command_DrawCards_ext, create(Command_DrawCardsSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_DrawCards_ext, create(Command_DrawCardsSchema, params)); } diff --git a/webclient/src/websocket/commands/game/dumpZone.ts b/webclient/src/websocket/commands/game/dumpZone.ts index 9075b2dcf..3cee63e7e 100644 --- a/webclient/src/websocket/commands/game/dumpZone.ts +++ b/webclient/src/websocket/commands/game/dumpZone.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DumpZoneSchema, Command_DumpZone_ext } from 'generated/proto/command_dump_zone_pb'; import { DumpZoneParams } from 'types'; export function dumpZone(gameId: number, params: DumpZoneParams): void { - BackendService.sendGameCommand(gameId, Command_DumpZone_ext, create(Command_DumpZoneSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_DumpZone_ext, create(Command_DumpZoneSchema, params)); } diff --git a/webclient/src/websocket/commands/game/flipCard.ts b/webclient/src/websocket/commands/game/flipCard.ts index c12729d46..b8d1130cf 100644 --- a/webclient/src/websocket/commands/game/flipCard.ts +++ b/webclient/src/websocket/commands/game/flipCard.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_FlipCardSchema, Command_FlipCard_ext } from 'generated/proto/command_flip_card_pb'; import { FlipCardParams } from 'types'; export function flipCard(gameId: number, params: FlipCardParams): void { - BackendService.sendGameCommand(gameId, Command_FlipCard_ext, create(Command_FlipCardSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_FlipCard_ext, create(Command_FlipCardSchema, params)); } diff --git a/webclient/src/websocket/commands/game/gameCommands.spec.ts b/webclient/src/websocket/commands/game/gameCommands.spec.ts index 1daf3a7dc..ddb671f88 100644 --- a/webclient/src/websocket/commands/game/gameCommands.spec.ts +++ b/webclient/src/websocket/commands/game/gameCommands.spec.ts @@ -1,4 +1,4 @@ -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { create, setExtension } from '@bufbuild/protobuf'; import { GameCommandSchema, Command_Judge_ext } from 'generated/proto/game_commands_pb'; import { Command_DrawCardsSchema, Command_DrawCards_ext } from 'generated/proto/command_draw_cards_pb'; @@ -66,8 +66,9 @@ import { undoDraw } from './undoDraw'; import { unconcede } from './unconcede'; import { judge } from './judge'; -vi.mock('../../services/BackendService', () => ({ - BackendService: { sendGameCommand: vi.fn() }, +vi.mock('../../WebClient', () => ({ + __esModule: true, + default: { protobuf: { sendGameCommand: vi.fn() } }, })); const gameId = 1; @@ -75,116 +76,124 @@ const gameId = 1; import { Mock } from 'vitest'; beforeEach(() => { - (BackendService.sendGameCommand as Mock).mockClear(); + (webClient.protobuf.sendGameCommand as Mock).mockClear(); }); -describe('Game commands — delegate to BackendService.sendGameCommand', () => { +describe('Game commands — delegate to webClient.protobuf.sendGameCommand', () => { it('attachCard sends Command_AttachCard', () => { attachCard(gameId, { cardId: 10, startZone: 'hand' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_AttachCard_ext, expect.objectContaining({ cardId: 10, startZone: 'hand' }) ); }); it('changeZoneProperties sends Command_ChangeZoneProperties', () => { changeZoneProperties(gameId, { zoneName: 'side' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_ChangeZoneProperties_ext, expect.objectContaining({ zoneName: 'side' }) ); }); it('concede sends Command_Concede with empty object', () => { concede(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Concede_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Concede_ext, expect.any(Object)); }); it('createArrow sends Command_CreateArrow', () => { createArrow(gameId, { startPlayerId: 1, startZone: 'hand' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_CreateArrow_ext, expect.objectContaining({ startPlayerId: 1, startZone: 'hand' }) ); }); it('createCounter sends Command_CreateCounter', () => { createCounter(gameId, { counterName: 'life' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_CreateCounter_ext, expect.objectContaining({ counterName: 'life' }) ); }); it('createToken sends Command_CreateToken', () => { createToken(gameId, { cardName: 'Goblin', zone: 'play' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_CreateToken_ext, expect.objectContaining({ cardName: 'Goblin', zone: 'play' }) ); }); it('deckSelect sends Command_DeckSelect', () => { deckSelect(gameId, { deckId: 5 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DeckSelect_ext, expect.objectContaining({ deckId: 5 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DeckSelect_ext, expect.objectContaining({ deckId: 5 })); }); it('delCounter sends Command_DelCounter', () => { delCounter(gameId, { counterId: 3 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DelCounter_ext, expect.objectContaining({ counterId: 3 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_DelCounter_ext, expect.objectContaining({ counterId: 3 }) + ); }); it('deleteArrow sends Command_DeleteArrow', () => { deleteArrow(gameId, { arrowId: 2 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DeleteArrow_ext, expect.objectContaining({ arrowId: 2 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_DeleteArrow_ext, expect.objectContaining({ arrowId: 2 }) + ); }); it('drawCards sends Command_DrawCards', () => { drawCards(gameId, { number: 3 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DrawCards_ext, expect.objectContaining({ number: 3 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DrawCards_ext, expect.objectContaining({ number: 3 })); }); it('dumpZone sends Command_DumpZone', () => { dumpZone(gameId, { playerId: 2, zoneName: 'library' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_DumpZone_ext, expect.objectContaining({ playerId: 2, zoneName: 'library' }) ); }); it('flipCard sends Command_FlipCard', () => { flipCard(gameId, { cardId: 7, faceDown: false }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_FlipCard_ext, expect.objectContaining({ cardId: 7, faceDown: false }) ); }); it('gameSay sends Command_GameSay', () => { gameSay(gameId, { message: 'hello' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_GameSay_ext, expect.objectContaining({ message: 'hello' })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_GameSay_ext, expect.objectContaining({ message: 'hello' }) + ); }); it('incCardCounter sends Command_IncCardCounter', () => { incCardCounter(gameId, { cardId: 5, counterId: 1 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_IncCardCounter_ext, expect.objectContaining({ cardId: 5, counterId: 1 }) ); }); it('incCounter sends Command_IncCounter', () => { incCounter(gameId, { counterId: 1, delta: 5 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_IncCounter_ext, expect.objectContaining({ counterId: 1, delta: 5 }) ); }); it('kickFromGame sends Command_KickFromGame', () => { kickFromGame(gameId, { playerId: 2 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_KickFromGame_ext, expect.objectContaining({ playerId: 2 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_KickFromGame_ext, expect.objectContaining({ playerId: 2 }) + ); }); it('leaveGame sends Command_LeaveGame with empty object', () => { leaveGame(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_LeaveGame_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_LeaveGame_ext, expect.any(Object)); }); it('moveCard sends Command_MoveCard', () => { moveCard(gameId, { startZone: 'hand', targetZone: 'graveyard' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_MoveCard_ext, expect.objectContaining({ startZone: 'hand', targetZone: 'graveyard' }) ); @@ -192,39 +201,45 @@ describe('Game commands — delegate to BackendService.sendGameCommand', () => { it('mulligan sends Command_Mulligan', () => { mulligan(gameId, { number: 7 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Mulligan_ext, expect.objectContaining({ number: 7 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_Mulligan_ext, expect.objectContaining({ number: 7 }) + ); }); it('nextTurn sends Command_NextTurn with empty object', () => { nextTurn(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_NextTurn_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_NextTurn_ext, expect.any(Object)); }); it('readyStart sends Command_ReadyStart', () => { readyStart(gameId, { ready: true }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_ReadyStart_ext, expect.objectContaining({ ready: true })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_ReadyStart_ext, expect.objectContaining({ ready: true }) + ); }); it('revealCards sends Command_RevealCards', () => { revealCards(gameId, { zoneName: 'hand', cardId: [1, 2] }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_RevealCards_ext, expect.objectContaining({ zoneName: 'hand', cardId: [1, 2] }) ); }); it('reverseTurn sends Command_ReverseTurn with empty object', () => { reverseTurn(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_ReverseTurn_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_ReverseTurn_ext, expect.any(Object)); }); it('setActivePhase sends Command_SetActivePhase', () => { setActivePhase(gameId, { phase: 2 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_SetActivePhase_ext, expect.objectContaining({ phase: 2 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_SetActivePhase_ext, expect.objectContaining({ phase: 2 }) + ); }); it('setCardAttr sends Command_SetCardAttr', () => { setCardAttr(gameId, { zone: 'play', cardId: 5, attrValue: '2' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_SetCardAttr_ext, expect.objectContaining({ zone: 'play', cardId: 5, attrValue: '2' }) ); @@ -232,45 +247,47 @@ describe('Game commands — delegate to BackendService.sendGameCommand', () => { it('setCardCounter sends Command_SetCardCounter', () => { setCardCounter(gameId, { cardId: 5, counterId: 1 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_SetCardCounter_ext, expect.objectContaining({ cardId: 5, counterId: 1 }) ); }); it('setCounter sends Command_SetCounter', () => { setCounter(gameId, { counterId: 1, value: 10 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_SetCounter_ext, expect.objectContaining({ counterId: 1, value: 10 }) ); }); it('setSideboardLock sends Command_SetSideboardLock', () => { setSideboardLock(gameId, { locked: true }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_SetSideboardLock_ext, expect.objectContaining({ locked: true }) ); }); it('setSideboardPlan sends Command_SetSideboardPlan', () => { setSideboardPlan(gameId, { moveList: [] }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_SetSideboardPlan_ext, expect.objectContaining({ moveList: expect.any(Array) }) ); }); it('shuffle sends Command_Shuffle', () => { shuffle(gameId, { zoneName: 'hand' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Shuffle_ext, expect.objectContaining({ zoneName: 'hand' })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_Shuffle_ext, expect.objectContaining({ zoneName: 'hand' }) + ); }); it('undoDraw sends Command_UndoDraw with empty object', () => { undoDraw(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_UndoDraw_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_UndoDraw_ext, expect.any(Object)); }); it('unconcede sends Command_Unconcede with empty object', () => { unconcede(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Unconcede_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Unconcede_ext, expect.any(Object)); }); it('judge sends Command_Judge with targetId and wrapped gameCommand array', () => { @@ -278,7 +295,7 @@ describe('Game commands — delegate to BackendService.sendGameCommand', () => { const innerCmd = create(GameCommandSchema); setExtension(innerCmd, Command_DrawCards_ext, create(Command_DrawCardsSchema, { number: 2 })); judge(gameId, targetId, innerCmd); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_Judge_ext, expect.objectContaining({ targetId: 3, gameCommand: expect.any(Array) }) diff --git a/webclient/src/websocket/commands/game/gameSay.ts b/webclient/src/websocket/commands/game/gameSay.ts index a55085526..86a798f9c 100644 --- a/webclient/src/websocket/commands/game/gameSay.ts +++ b/webclient/src/websocket/commands/game/gameSay.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GameSaySchema, Command_GameSay_ext } from 'generated/proto/command_game_say_pb'; import { GameSayParams } from 'types'; export function gameSay(gameId: number, params: GameSayParams): void { - BackendService.sendGameCommand(gameId, Command_GameSay_ext, create(Command_GameSaySchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_GameSay_ext, create(Command_GameSaySchema, params)); } diff --git a/webclient/src/websocket/commands/game/incCardCounter.ts b/webclient/src/websocket/commands/game/incCardCounter.ts index dd678044f..6a72d7baa 100644 --- a/webclient/src/websocket/commands/game/incCardCounter.ts +++ b/webclient/src/websocket/commands/game/incCardCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_IncCardCounterSchema, Command_IncCardCounter_ext } from 'generated/proto/command_inc_card_counter_pb'; import { IncCardCounterParams } from 'types'; export function incCardCounter(gameId: number, params: IncCardCounterParams): void { - BackendService.sendGameCommand(gameId, Command_IncCardCounter_ext, create(Command_IncCardCounterSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_IncCardCounter_ext, create(Command_IncCardCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/incCounter.ts b/webclient/src/websocket/commands/game/incCounter.ts index 997fc1303..f00a358c9 100644 --- a/webclient/src/websocket/commands/game/incCounter.ts +++ b/webclient/src/websocket/commands/game/incCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_IncCounterSchema, Command_IncCounter_ext } from 'generated/proto/command_inc_counter_pb'; import { IncCounterParams } from 'types'; export function incCounter(gameId: number, params: IncCounterParams): void { - BackendService.sendGameCommand(gameId, Command_IncCounter_ext, create(Command_IncCounterSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_IncCounter_ext, create(Command_IncCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/judge.ts b/webclient/src/websocket/commands/game/judge.ts index 57ad869eb..5142131bb 100644 --- a/webclient/src/websocket/commands/game/judge.ts +++ b/webclient/src/websocket/commands/game/judge.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_JudgeSchema, Command_Judge_ext } from 'generated/proto/game_commands_pb'; import type { GameCommand } from 'generated/proto/game_commands_pb'; export function judge(gameId: number, targetId: number, innerGameCommand: GameCommand): void { - BackendService.sendGameCommand(gameId, Command_Judge_ext, create(Command_JudgeSchema, { + webClient.protobuf.sendGameCommand(gameId, Command_Judge_ext, create(Command_JudgeSchema, { targetId, gameCommand: [innerGameCommand], })); diff --git a/webclient/src/websocket/commands/game/kickFromGame.ts b/webclient/src/websocket/commands/game/kickFromGame.ts index e56574e41..ef41a755c 100644 --- a/webclient/src/websocket/commands/game/kickFromGame.ts +++ b/webclient/src/websocket/commands/game/kickFromGame.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_KickFromGameSchema, Command_KickFromGame_ext } from 'generated/proto/command_kick_from_game_pb'; import { KickFromGameParams } from 'types'; export function kickFromGame(gameId: number, params: KickFromGameParams): void { - BackendService.sendGameCommand(gameId, Command_KickFromGame_ext, create(Command_KickFromGameSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_KickFromGame_ext, create(Command_KickFromGameSchema, params)); } diff --git a/webclient/src/websocket/commands/game/leaveGame.ts b/webclient/src/websocket/commands/game/leaveGame.ts index 6bdcfc661..f46503510 100644 --- a/webclient/src/websocket/commands/game/leaveGame.ts +++ b/webclient/src/websocket/commands/game/leaveGame.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_LeaveGameSchema, Command_LeaveGame_ext } from 'generated/proto/command_leave_game_pb'; export function leaveGame(gameId: number): void { - BackendService.sendGameCommand(gameId, Command_LeaveGame_ext, create(Command_LeaveGameSchema)); + webClient.protobuf.sendGameCommand(gameId, Command_LeaveGame_ext, create(Command_LeaveGameSchema)); } diff --git a/webclient/src/websocket/commands/game/moveCard.ts b/webclient/src/websocket/commands/game/moveCard.ts index 193d85b93..400a6171c 100644 --- a/webclient/src/websocket/commands/game/moveCard.ts +++ b/webclient/src/websocket/commands/game/moveCard.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_MoveCardSchema, Command_MoveCard_ext } from 'generated/proto/command_move_card_pb'; import { MoveCardParams } from 'types'; export function moveCard(gameId: number, params: MoveCardParams): void { - BackendService.sendGameCommand(gameId, Command_MoveCard_ext, create(Command_MoveCardSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_MoveCard_ext, create(Command_MoveCardSchema, params)); } diff --git a/webclient/src/websocket/commands/game/mulligan.ts b/webclient/src/websocket/commands/game/mulligan.ts index 54c42635e..d9285ddfc 100644 --- a/webclient/src/websocket/commands/game/mulligan.ts +++ b/webclient/src/websocket/commands/game/mulligan.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_MulliganSchema, Command_Mulligan_ext } from 'generated/proto/command_mulligan_pb'; import { MulliganParams } from 'types'; export function mulligan(gameId: number, params: MulliganParams): void { - BackendService.sendGameCommand(gameId, Command_Mulligan_ext, create(Command_MulliganSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_Mulligan_ext, create(Command_MulliganSchema, params)); } diff --git a/webclient/src/websocket/commands/game/nextTurn.ts b/webclient/src/websocket/commands/game/nextTurn.ts index 756cca49a..70b332ef2 100644 --- a/webclient/src/websocket/commands/game/nextTurn.ts +++ b/webclient/src/websocket/commands/game/nextTurn.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_NextTurnSchema, Command_NextTurn_ext } from 'generated/proto/command_next_turn_pb'; export function nextTurn(gameId: number): void { - BackendService.sendGameCommand(gameId, Command_NextTurn_ext, create(Command_NextTurnSchema)); + webClient.protobuf.sendGameCommand(gameId, Command_NextTurn_ext, create(Command_NextTurnSchema)); } diff --git a/webclient/src/websocket/commands/game/readyStart.ts b/webclient/src/websocket/commands/game/readyStart.ts index d6a608d9c..def0a445e 100644 --- a/webclient/src/websocket/commands/game/readyStart.ts +++ b/webclient/src/websocket/commands/game/readyStart.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReadyStartSchema, Command_ReadyStart_ext } from 'generated/proto/command_ready_start_pb'; import { ReadyStartParams } from 'types'; export function readyStart(gameId: number, params: ReadyStartParams): void { - BackendService.sendGameCommand(gameId, Command_ReadyStart_ext, create(Command_ReadyStartSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_ReadyStart_ext, create(Command_ReadyStartSchema, params)); } diff --git a/webclient/src/websocket/commands/game/revealCards.ts b/webclient/src/websocket/commands/game/revealCards.ts index 18e054613..aaa860928 100644 --- a/webclient/src/websocket/commands/game/revealCards.ts +++ b/webclient/src/websocket/commands/game/revealCards.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_RevealCardsSchema, Command_RevealCards_ext } from 'generated/proto/command_reveal_cards_pb'; import { RevealCardsParams } from 'types'; export function revealCards(gameId: number, params: RevealCardsParams): void { - BackendService.sendGameCommand(gameId, Command_RevealCards_ext, create(Command_RevealCardsSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_RevealCards_ext, create(Command_RevealCardsSchema, params)); } diff --git a/webclient/src/websocket/commands/game/reverseTurn.ts b/webclient/src/websocket/commands/game/reverseTurn.ts index 38d34600a..e9be52a5b 100644 --- a/webclient/src/websocket/commands/game/reverseTurn.ts +++ b/webclient/src/websocket/commands/game/reverseTurn.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReverseTurnSchema, Command_ReverseTurn_ext } from 'generated/proto/command_reverse_turn_pb'; export function reverseTurn(gameId: number): void { - BackendService.sendGameCommand(gameId, Command_ReverseTurn_ext, create(Command_ReverseTurnSchema)); + webClient.protobuf.sendGameCommand(gameId, Command_ReverseTurn_ext, create(Command_ReverseTurnSchema)); } diff --git a/webclient/src/websocket/commands/game/setActivePhase.ts b/webclient/src/websocket/commands/game/setActivePhase.ts index 799a700f3..9ccb04284 100644 --- a/webclient/src/websocket/commands/game/setActivePhase.ts +++ b/webclient/src/websocket/commands/game/setActivePhase.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_SetActivePhaseSchema, Command_SetActivePhase_ext } from 'generated/proto/command_set_active_phase_pb'; import { SetActivePhaseParams } from 'types'; export function setActivePhase(gameId: number, params: SetActivePhaseParams): void { - BackendService.sendGameCommand(gameId, Command_SetActivePhase_ext, create(Command_SetActivePhaseSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_SetActivePhase_ext, create(Command_SetActivePhaseSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setCardAttr.ts b/webclient/src/websocket/commands/game/setCardAttr.ts index f1b2bbd56..5e7e8c57f 100644 --- a/webclient/src/websocket/commands/game/setCardAttr.ts +++ b/webclient/src/websocket/commands/game/setCardAttr.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_SetCardAttrSchema, Command_SetCardAttr_ext } from 'generated/proto/command_set_card_attr_pb'; import { SetCardAttrParams } from 'types'; export function setCardAttr(gameId: number, params: SetCardAttrParams): void { - BackendService.sendGameCommand(gameId, Command_SetCardAttr_ext, create(Command_SetCardAttrSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_SetCardAttr_ext, create(Command_SetCardAttrSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setCardCounter.ts b/webclient/src/websocket/commands/game/setCardCounter.ts index 2aea270f8..18a8a991f 100644 --- a/webclient/src/websocket/commands/game/setCardCounter.ts +++ b/webclient/src/websocket/commands/game/setCardCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_SetCardCounterSchema, Command_SetCardCounter_ext } from 'generated/proto/command_set_card_counter_pb'; import { SetCardCounterParams } from 'types'; export function setCardCounter(gameId: number, params: SetCardCounterParams): void { - BackendService.sendGameCommand(gameId, Command_SetCardCounter_ext, create(Command_SetCardCounterSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_SetCardCounter_ext, create(Command_SetCardCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setCounter.ts b/webclient/src/websocket/commands/game/setCounter.ts index fdbb90bf9..579561d62 100644 --- a/webclient/src/websocket/commands/game/setCounter.ts +++ b/webclient/src/websocket/commands/game/setCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_SetCounterSchema, Command_SetCounter_ext } from 'generated/proto/command_set_counter_pb'; import { SetCounterParams } from 'types'; export function setCounter(gameId: number, params: SetCounterParams): void { - BackendService.sendGameCommand(gameId, Command_SetCounter_ext, create(Command_SetCounterSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_SetCounter_ext, create(Command_SetCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setSideboardLock.ts b/webclient/src/websocket/commands/game/setSideboardLock.ts index a8b82dff9..ff639a461 100644 --- a/webclient/src/websocket/commands/game/setSideboardLock.ts +++ b/webclient/src/websocket/commands/game/setSideboardLock.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_SetSideboardLockSchema, Command_SetSideboardLock_ext } from 'generated/proto/command_set_sideboard_lock_pb'; import { SetSideboardLockParams } from 'types'; export function setSideboardLock(gameId: number, params: SetSideboardLockParams): void { - BackendService.sendGameCommand(gameId, Command_SetSideboardLock_ext, create(Command_SetSideboardLockSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_SetSideboardLock_ext, create(Command_SetSideboardLockSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setSideboardPlan.ts b/webclient/src/websocket/commands/game/setSideboardPlan.ts index 65cac24e6..d69d85c89 100644 --- a/webclient/src/websocket/commands/game/setSideboardPlan.ts +++ b/webclient/src/websocket/commands/game/setSideboardPlan.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_SetSideboardPlanSchema, Command_SetSideboardPlan_ext } from 'generated/proto/command_set_sideboard_plan_pb'; import { SetSideboardPlanParams } from 'types'; export function setSideboardPlan(gameId: number, params: SetSideboardPlanParams): void { - BackendService.sendGameCommand(gameId, Command_SetSideboardPlan_ext, create(Command_SetSideboardPlanSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_SetSideboardPlan_ext, create(Command_SetSideboardPlanSchema, params)); } diff --git a/webclient/src/websocket/commands/game/shuffle.ts b/webclient/src/websocket/commands/game/shuffle.ts index 750926d3b..943ad4a57 100644 --- a/webclient/src/websocket/commands/game/shuffle.ts +++ b/webclient/src/websocket/commands/game/shuffle.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ShuffleSchema, Command_Shuffle_ext } from 'generated/proto/command_shuffle_pb'; import { ShuffleParams } from 'types'; export function shuffle(gameId: number, params: ShuffleParams): void { - BackendService.sendGameCommand(gameId, Command_Shuffle_ext, create(Command_ShuffleSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_Shuffle_ext, create(Command_ShuffleSchema, params)); } diff --git a/webclient/src/websocket/commands/game/unconcede.ts b/webclient/src/websocket/commands/game/unconcede.ts index 82cc0d63f..e6888f7ce 100644 --- a/webclient/src/websocket/commands/game/unconcede.ts +++ b/webclient/src/websocket/commands/game/unconcede.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_UnconcedeSchema, Command_Unconcede_ext } from 'generated/proto/command_concede_pb'; export function unconcede(gameId: number): void { - BackendService.sendGameCommand(gameId, Command_Unconcede_ext, create(Command_UnconcedeSchema)); + webClient.protobuf.sendGameCommand(gameId, Command_Unconcede_ext, create(Command_UnconcedeSchema)); } diff --git a/webclient/src/websocket/commands/game/undoDraw.ts b/webclient/src/websocket/commands/game/undoDraw.ts index 9f70df3b8..dd0a3358f 100644 --- a/webclient/src/websocket/commands/game/undoDraw.ts +++ b/webclient/src/websocket/commands/game/undoDraw.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_UndoDrawSchema, Command_UndoDraw_ext } from 'generated/proto/command_undo_draw_pb'; export function undoDraw(gameId: number): void { - BackendService.sendGameCommand(gameId, Command_UndoDraw_ext, create(Command_UndoDrawSchema)); + webClient.protobuf.sendGameCommand(gameId, Command_UndoDraw_ext, create(Command_UndoDrawSchema)); } diff --git a/webclient/src/websocket/commands/moderator/banFromServer.ts b/webclient/src/websocket/commands/moderator/banFromServer.ts index 9fba4e72b..c0227fddd 100644 --- a/webclient/src/websocket/commands/moderator/banFromServer.ts +++ b/webclient/src/websocket/commands/moderator/banFromServer.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_BanFromServer_ext, Command_BanFromServerSchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; export function banFromServer(minutes: number, userName?: string, address?: string, reason?: string, visibleReason?: string, clientid?: string, removeMessages?: number): void { - BackendService.sendModeratorCommand(Command_BanFromServer_ext, create(Command_BanFromServerSchema, { + webClient.protobuf.sendModeratorCommand(Command_BanFromServer_ext, create(Command_BanFromServerSchema, { minutes, userName, address, reason, visibleReason, clientid, removeMessages }), { onSuccess: () => { diff --git a/webclient/src/websocket/commands/moderator/forceActivateUser.ts b/webclient/src/websocket/commands/moderator/forceActivateUser.ts index 43dd1fc65..b31f8df31 100644 --- a/webclient/src/websocket/commands/moderator/forceActivateUser.ts +++ b/webclient/src/websocket/commands/moderator/forceActivateUser.ts @@ -1,5 +1,5 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ForceActivateUser_ext, Command_ForceActivateUserSchema, } from 'generated/proto/moderator_commands_pb'; @@ -7,7 +7,7 @@ import { ModeratorPersistence } from '../../persistence'; export function forceActivateUser(usernameToActivate: string, moderatorName: string): void { const cmd = create(Command_ForceActivateUserSchema, { usernameToActivate, moderatorName }); - BackendService.sendModeratorCommand(Command_ForceActivateUser_ext, cmd, { + webClient.protobuf.sendModeratorCommand(Command_ForceActivateUser_ext, cmd, { onSuccess: () => { ModeratorPersistence.forceActivateUser(usernameToActivate, moderatorName); }, diff --git a/webclient/src/websocket/commands/moderator/getAdminNotes.ts b/webclient/src/websocket/commands/moderator/getAdminNotes.ts index 48b374581..9f554e536 100644 --- a/webclient/src/websocket/commands/moderator/getAdminNotes.ts +++ b/webclient/src/websocket/commands/moderator/getAdminNotes.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GetAdminNotes_ext, Command_GetAdminNotesSchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; import { Response_GetAdminNotes_ext } from 'generated/proto/response_get_admin_notes_pb'; export function getAdminNotes(userName: string): void { - BackendService.sendModeratorCommand(Command_GetAdminNotes_ext, create(Command_GetAdminNotesSchema, { userName }), { + webClient.protobuf.sendModeratorCommand(Command_GetAdminNotes_ext, create(Command_GetAdminNotesSchema, { userName }), { responseExt: Response_GetAdminNotes_ext, onSuccess: (response) => { ModeratorPersistence.getAdminNotes(userName, response.notes); diff --git a/webclient/src/websocket/commands/moderator/getBanHistory.ts b/webclient/src/websocket/commands/moderator/getBanHistory.ts index a6a084912..c0fc9253d 100644 --- a/webclient/src/websocket/commands/moderator/getBanHistory.ts +++ b/webclient/src/websocket/commands/moderator/getBanHistory.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GetBanHistory_ext, Command_GetBanHistorySchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; import { Response_BanHistory_ext } from 'generated/proto/response_ban_history_pb'; export function getBanHistory(userName: string): void { - BackendService.sendModeratorCommand(Command_GetBanHistory_ext, create(Command_GetBanHistorySchema, { userName }), { + webClient.protobuf.sendModeratorCommand(Command_GetBanHistory_ext, create(Command_GetBanHistorySchema, { userName }), { responseExt: Response_BanHistory_ext, onSuccess: (response) => { ModeratorPersistence.banHistory(userName, response.banList); diff --git a/webclient/src/websocket/commands/moderator/getWarnHistory.ts b/webclient/src/websocket/commands/moderator/getWarnHistory.ts index 15b03595e..4699b9333 100644 --- a/webclient/src/websocket/commands/moderator/getWarnHistory.ts +++ b/webclient/src/websocket/commands/moderator/getWarnHistory.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GetWarnHistory_ext, Command_GetWarnHistorySchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; import { Response_WarnHistory_ext } from 'generated/proto/response_warn_history_pb'; export function getWarnHistory(userName: string): void { - BackendService.sendModeratorCommand(Command_GetWarnHistory_ext, create(Command_GetWarnHistorySchema, { userName }), { + webClient.protobuf.sendModeratorCommand(Command_GetWarnHistory_ext, create(Command_GetWarnHistorySchema, { userName }), { responseExt: Response_WarnHistory_ext, onSuccess: (response) => { ModeratorPersistence.warnHistory(userName, response.warnList); diff --git a/webclient/src/websocket/commands/moderator/getWarnList.ts b/webclient/src/websocket/commands/moderator/getWarnList.ts index 49282811e..60c268f10 100644 --- a/webclient/src/websocket/commands/moderator/getWarnList.ts +++ b/webclient/src/websocket/commands/moderator/getWarnList.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GetWarnList_ext, Command_GetWarnListSchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; import { Response_WarnList_ext } from 'generated/proto/response_warn_list_pb'; export function getWarnList(modName: string, userName: string, userClientid: string): void { - BackendService.sendModeratorCommand(Command_GetWarnList_ext, create(Command_GetWarnListSchema, { modName, userName, userClientid }), { + webClient.protobuf.sendModeratorCommand(Command_GetWarnList_ext, create(Command_GetWarnListSchema, { modName, userName, userClientid }), { responseExt: Response_WarnList_ext, onSuccess: (response) => { ModeratorPersistence.warnListOptions([response]); diff --git a/webclient/src/websocket/commands/moderator/grantReplayAccess.ts b/webclient/src/websocket/commands/moderator/grantReplayAccess.ts index 84adc725e..3826dd9d4 100644 --- a/webclient/src/websocket/commands/moderator/grantReplayAccess.ts +++ b/webclient/src/websocket/commands/moderator/grantReplayAccess.ts @@ -1,14 +1,18 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GrantReplayAccess_ext, Command_GrantReplayAccessSchema, } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; export function grantReplayAccess(replayId: number, moderatorName: string): void { - BackendService.sendModeratorCommand(Command_GrantReplayAccess_ext, create(Command_GrantReplayAccessSchema, { replayId, moderatorName }), { - onSuccess: () => { - ModeratorPersistence.grantReplayAccess(replayId, moderatorName); + webClient.protobuf.sendModeratorCommand( + Command_GrantReplayAccess_ext, + create(Command_GrantReplayAccessSchema, { replayId, moderatorName }), + { + onSuccess: () => { + ModeratorPersistence.grantReplayAccess(replayId, moderatorName); + }, }, - }); + ); } diff --git a/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts index 2ca5eea7b..70a1bd9ff 100644 --- a/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts +++ b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts @@ -1,7 +1,6 @@ -vi.mock('../../services/BackendService', () => ({ - BackendService: { - sendModeratorCommand: vi.fn(), - }, +vi.mock('../../WebClient', () => ({ + __esModule: true, + default: { protobuf: { sendModeratorCommand: vi.fn() } }, })); vi.mock('../../persistence', () => ({ @@ -20,7 +19,7 @@ vi.mock('../../persistence', () => ({ })); import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { ModeratorPersistence } from '../../persistence'; import { Command_BanFromServer_ext, @@ -53,7 +52,7 @@ import { warnUser } from './warnUser'; import { Mock } from 'vitest'; const { invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendModeratorCommand as Mock, + webClient.protobuf.sendModeratorCommand as Mock, 2 ); @@ -66,7 +65,7 @@ describe('banFromServer', () => { it('calls sendModeratorCommand with Command_BanFromServer', () => { banFromServer(30, 'alice', '1.2.3.4', 'reason', 'visible', 'cid', 1); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( Command_BanFromServer_ext, expect.objectContaining({ minutes: 30, userName: 'alice' }), expect.any(Object) @@ -87,7 +86,9 @@ describe('forceActivateUser', () => { it('calls sendModeratorCommand with Command_ForceActivateUser', () => { forceActivateUser('alice', 'mod1'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith(Command_ForceActivateUser_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( + Command_ForceActivateUser_ext, expect.any(Object), expect.any(Object) + ); }); it('onSuccess calls ModeratorPersistence.forceActivateUser', () => { @@ -104,7 +105,7 @@ describe('getAdminNotes', () => { it('calls sendModeratorCommand with Command_GetAdminNotes', () => { getAdminNotes('alice'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( Command_GetAdminNotes_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_GetAdminNotes_ext }) @@ -126,7 +127,7 @@ describe('getBanHistory', () => { it('calls sendModeratorCommand with Command_GetBanHistory', () => { getBanHistory('alice'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( Command_GetBanHistory_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_BanHistory_ext }) @@ -148,7 +149,7 @@ describe('getWarnHistory', () => { it('calls sendModeratorCommand with Command_GetWarnHistory', () => { getWarnHistory('alice'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( Command_GetWarnHistory_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_WarnHistory_ext }) @@ -170,7 +171,7 @@ describe('getWarnList', () => { it('calls sendModeratorCommand with Command_GetWarnList', () => { getWarnList('mod1', 'alice', 'US'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( Command_GetWarnList_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_WarnList_ext }) @@ -192,7 +193,9 @@ describe('grantReplayAccess', () => { it('calls sendModeratorCommand with Command_GrantReplayAccess', () => { grantReplayAccess(10, 'mod1'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith(Command_GrantReplayAccess_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( + Command_GrantReplayAccess_ext, expect.any(Object), expect.any(Object) + ); }); it('onSuccess calls ModeratorPersistence.grantReplayAccess', () => { @@ -209,7 +212,9 @@ describe('updateAdminNotes', () => { it('calls sendModeratorCommand with Command_UpdateAdminNotes', () => { updateAdminNotes('alice', 'new notes'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith(Command_UpdateAdminNotes_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( + Command_UpdateAdminNotes_ext, expect.any(Object), expect.any(Object) + ); }); it('onSuccess calls ModeratorPersistence.updateAdminNotes', () => { @@ -226,7 +231,7 @@ describe('viewLogHistory', () => { it('calls sendModeratorCommand with Command_ViewLogHistory', () => { viewLogHistory({ dateRange: 7 } as any); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( Command_ViewLogHistory_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_ViewLogHistory_ext }) @@ -248,7 +253,7 @@ describe('warnUser', () => { it('calls sendModeratorCommand with Command_WarnUser', () => { warnUser('alice', 'bad behavior', 'cid'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith(Command_WarnUser_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith(Command_WarnUser_ext, expect.any(Object), expect.any(Object)); }); it('onSuccess calls ModeratorPersistence.warnUser', () => { diff --git a/webclient/src/websocket/commands/moderator/updateAdminNotes.ts b/webclient/src/websocket/commands/moderator/updateAdminNotes.ts index 6f50e71aa..448f58e23 100644 --- a/webclient/src/websocket/commands/moderator/updateAdminNotes.ts +++ b/webclient/src/websocket/commands/moderator/updateAdminNotes.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_UpdateAdminNotes_ext, Command_UpdateAdminNotesSchema, } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; export function updateAdminNotes(userName: string, notes: string): void { - BackendService.sendModeratorCommand(Command_UpdateAdminNotes_ext, create(Command_UpdateAdminNotesSchema, { userName, notes }), { + webClient.protobuf.sendModeratorCommand(Command_UpdateAdminNotes_ext, create(Command_UpdateAdminNotesSchema, { userName, notes }), { onSuccess: () => { ModeratorPersistence.updateAdminNotes(userName, notes); }, diff --git a/webclient/src/websocket/commands/moderator/viewLogHistory.ts b/webclient/src/websocket/commands/moderator/viewLogHistory.ts index 82d101f99..264a535b2 100644 --- a/webclient/src/websocket/commands/moderator/viewLogHistory.ts +++ b/webclient/src/websocket/commands/moderator/viewLogHistory.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ViewLogHistory_ext, Command_ViewLogHistorySchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; import { Response_ViewLogHistory_ext } from 'generated/proto/response_viewlog_history_pb'; import { LogFilters } from 'types'; export function viewLogHistory(filters: LogFilters): void { - BackendService.sendModeratorCommand(Command_ViewLogHistory_ext, create(Command_ViewLogHistorySchema, filters), { + webClient.protobuf.sendModeratorCommand(Command_ViewLogHistory_ext, create(Command_ViewLogHistorySchema, filters), { responseExt: Response_ViewLogHistory_ext, onSuccess: (response) => { ModeratorPersistence.viewLogs(response.logMessage); diff --git a/webclient/src/websocket/commands/moderator/warnUser.ts b/webclient/src/websocket/commands/moderator/warnUser.ts index 20cd7b565..dfe2f3211 100644 --- a/webclient/src/websocket/commands/moderator/warnUser.ts +++ b/webclient/src/websocket/commands/moderator/warnUser.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_WarnUser_ext, Command_WarnUserSchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; export function warnUser(userName: string, reason: string, clientid?: string, removeMessages?: number): void { const cmd = create(Command_WarnUserSchema, { userName, reason, clientid, removeMessages }); - BackendService.sendModeratorCommand(Command_WarnUser_ext, cmd, { + webClient.protobuf.sendModeratorCommand(Command_WarnUser_ext, cmd, { onSuccess: () => { ModeratorPersistence.warnUser(userName); }, diff --git a/webclient/src/websocket/commands/room/createGame.ts b/webclient/src/websocket/commands/room/createGame.ts index c49691657..be1b5b401 100644 --- a/webclient/src/websocket/commands/room/createGame.ts +++ b/webclient/src/websocket/commands/room/createGame.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_CreateGame_ext, Command_CreateGameSchema } from 'generated/proto/room_commands_pb'; import { RoomPersistence } from '../../persistence'; import { GameConfig } from 'types'; export function createGame(roomId: number, gameConfig: GameConfig): void { - BackendService.sendRoomCommand(roomId, Command_CreateGame_ext, create(Command_CreateGameSchema, gameConfig), { + webClient.protobuf.sendRoomCommand(roomId, Command_CreateGame_ext, create(Command_CreateGameSchema, gameConfig), { onSuccess: () => { RoomPersistence.gameCreated(roomId); }, diff --git a/webclient/src/websocket/commands/room/joinGame.ts b/webclient/src/websocket/commands/room/joinGame.ts index 3c5db7d98..9975eccef 100644 --- a/webclient/src/websocket/commands/room/joinGame.ts +++ b/webclient/src/websocket/commands/room/joinGame.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_JoinGame_ext, Command_JoinGameSchema } from 'generated/proto/room_commands_pb'; import { RoomPersistence } from '../../persistence'; import { JoinGameParams } from 'types'; export function joinGame(roomId: number, joinGameParams: JoinGameParams): void { - BackendService.sendRoomCommand(roomId, Command_JoinGame_ext, create(Command_JoinGameSchema, joinGameParams), { + webClient.protobuf.sendRoomCommand(roomId, Command_JoinGame_ext, create(Command_JoinGameSchema, joinGameParams), { onSuccess: () => { RoomPersistence.joinedGame(roomId, joinGameParams.gameId); }, diff --git a/webclient/src/websocket/commands/room/leaveRoom.ts b/webclient/src/websocket/commands/room/leaveRoom.ts index 445f15ee3..67303f510 100644 --- a/webclient/src/websocket/commands/room/leaveRoom.ts +++ b/webclient/src/websocket/commands/room/leaveRoom.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_LeaveRoom_ext, Command_LeaveRoomSchema } from 'generated/proto/room_commands_pb'; import { RoomPersistence } from '../../persistence'; export function leaveRoom(roomId: number): void { - BackendService.sendRoomCommand(roomId, Command_LeaveRoom_ext, create(Command_LeaveRoomSchema), { + webClient.protobuf.sendRoomCommand(roomId, Command_LeaveRoom_ext, create(Command_LeaveRoomSchema), { onSuccess: () => { RoomPersistence.leaveRoom(roomId); }, diff --git a/webclient/src/websocket/commands/room/roomCommands.spec.ts b/webclient/src/websocket/commands/room/roomCommands.spec.ts index 691455c56..0916eb233 100644 --- a/webclient/src/websocket/commands/room/roomCommands.spec.ts +++ b/webclient/src/websocket/commands/room/roomCommands.spec.ts @@ -1,7 +1,6 @@ -vi.mock('../../services/BackendService', () => ({ - BackendService: { - sendRoomCommand: vi.fn(), - }, +vi.mock('../../WebClient', () => ({ + __esModule: true, + default: { protobuf: { sendRoomCommand: vi.fn() } }, })); vi.mock('../../persistence', () => ({ @@ -13,7 +12,7 @@ vi.mock('../../persistence', () => ({ })); import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { RoomPersistence } from '../../persistence'; import { Command_CreateGame_ext, Command_JoinGame_ext, Command_LeaveRoom_ext, Command_RoomSay_ext } from 'generated/proto/room_commands_pb'; import { createGame } from './createGame'; @@ -24,7 +23,7 @@ import { roomSay } from './roomSay'; import { Mock } from 'vitest'; const { invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendRoomCommand as Mock, + webClient.protobuf.sendRoomCommand as Mock, // sendRoomCommand(roomId, ext, value, options) — options at index 3 3 ); @@ -38,7 +37,7 @@ describe('createGame', () => { it('calls sendRoomCommand with Command_CreateGame', () => { createGame(5, { maxPlayers: 4 } as any); - expect(BackendService.sendRoomCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendRoomCommand).toHaveBeenCalledWith( 5, Command_CreateGame_ext, expect.objectContaining({ maxPlayers: 4 }), expect.any(Object) ); }); @@ -57,7 +56,7 @@ describe('joinGame', () => { it('calls sendRoomCommand with Command_JoinGame', () => { joinGame(7, { gameId: 42, password: '' } as any); - expect(BackendService.sendRoomCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendRoomCommand).toHaveBeenCalledWith( 7, Command_JoinGame_ext, expect.objectContaining({ gameId: 42, password: '' }), expect.any(Object) ); }); @@ -76,7 +75,7 @@ describe('leaveRoom', () => { it('calls sendRoomCommand with Command_LeaveRoom', () => { leaveRoom(3); - expect(BackendService.sendRoomCommand).toHaveBeenCalledWith(3, Command_LeaveRoom_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendRoomCommand).toHaveBeenCalledWith(3, Command_LeaveRoom_ext, expect.any(Object), expect.any(Object)); }); it('onSuccess calls RoomPersistence.leaveRoom with roomId', () => { @@ -93,7 +92,7 @@ describe('roomSay', () => { it('calls sendRoomCommand with trimmed message', () => { roomSay(2, ' hello '); - expect(BackendService.sendRoomCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendRoomCommand).toHaveBeenCalledWith( 2, Command_RoomSay_ext, expect.objectContaining({ message: 'hello' }) @@ -102,11 +101,11 @@ describe('roomSay', () => { it('does not call sendRoomCommand when message is blank', () => { roomSay(2, ' '); - expect(BackendService.sendRoomCommand).not.toHaveBeenCalled(); + expect(webClient.protobuf.sendRoomCommand).not.toHaveBeenCalled(); }); it('does not call sendRoomCommand when message is empty string', () => { roomSay(2, ''); - expect(BackendService.sendRoomCommand).not.toHaveBeenCalled(); + expect(webClient.protobuf.sendRoomCommand).not.toHaveBeenCalled(); }); }); diff --git a/webclient/src/websocket/commands/room/roomSay.ts b/webclient/src/websocket/commands/room/roomSay.ts index 0ebf62b00..d847dc527 100644 --- a/webclient/src/websocket/commands/room/roomSay.ts +++ b/webclient/src/websocket/commands/room/roomSay.ts @@ -1,5 +1,5 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_RoomSay_ext, Command_RoomSaySchema } from 'generated/proto/room_commands_pb'; export function roomSay(roomId: number, message: string): void { @@ -9,5 +9,5 @@ export function roomSay(roomId: number, message: string): void { return; } - BackendService.sendRoomCommand(roomId, Command_RoomSay_ext, create(Command_RoomSaySchema, { message: trimmed })); + webClient.protobuf.sendRoomCommand(roomId, Command_RoomSay_ext, create(Command_RoomSaySchema, { message: trimmed })); } diff --git a/webclient/src/websocket/commands/session/accountEdit.ts b/webclient/src/websocket/commands/session/accountEdit.ts index f36f48bf9..ad77b4dfb 100644 --- a/webclient/src/websocket/commands/session/accountEdit.ts +++ b/webclient/src/websocket/commands/session/accountEdit.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_AccountEdit_ext, Command_AccountEditSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; export function accountEdit(passwordCheck: string, realName?: string, email?: string, country?: string): void { const cmd = create(Command_AccountEditSchema, { passwordCheck, realName, email, country }); - BackendService.sendSessionCommand(Command_AccountEdit_ext, cmd, { + webClient.protobuf.sendSessionCommand(Command_AccountEdit_ext, cmd, { onSuccess: () => { SessionPersistence.accountEditChanged(realName, email, country); }, diff --git a/webclient/src/websocket/commands/session/accountImage.ts b/webclient/src/websocket/commands/session/accountImage.ts index 8a5eebe18..da603055f 100644 --- a/webclient/src/websocket/commands/session/accountImage.ts +++ b/webclient/src/websocket/commands/session/accountImage.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_AccountImage_ext, Command_AccountImageSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; export function accountImage(image: Uint8Array): void { - BackendService.sendSessionCommand(Command_AccountImage_ext, create(Command_AccountImageSchema, { image }), { + webClient.protobuf.sendSessionCommand(Command_AccountImage_ext, create(Command_AccountImageSchema, { image }), { onSuccess: () => { SessionPersistence.accountImageChanged(image); }, diff --git a/webclient/src/websocket/commands/session/accountPassword.ts b/webclient/src/websocket/commands/session/accountPassword.ts index 7a06ca6d7..5cc561421 100644 --- a/webclient/src/websocket/commands/session/accountPassword.ts +++ b/webclient/src/websocket/commands/session/accountPassword.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_AccountPassword_ext, Command_AccountPasswordSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; export function accountPassword(oldPassword: string, newPassword: string, hashedNewPassword: string): void { const cmd = create(Command_AccountPasswordSchema, { oldPassword, newPassword, hashedNewPassword }); - BackendService.sendSessionCommand(Command_AccountPassword_ext, cmd, { + webClient.protobuf.sendSessionCommand(Command_AccountPassword_ext, cmd, { onSuccess: () => { SessionPersistence.accountPasswordChange(); }, diff --git a/webclient/src/websocket/commands/session/activate.ts b/webclient/src/websocket/commands/session/activate.ts index 25340d106..49614ab37 100644 --- a/webclient/src/websocket/commands/session/activate.ts +++ b/webclient/src/websocket/commands/session/activate.ts @@ -2,8 +2,8 @@ import { AccountActivationParams } from 'store'; import { StatusEnum, WebSocketConnectOptions } from 'types'; import { create } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_Activate_ext, Command_ActivateSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; import { Response_ResponseCode } from 'generated/proto/response_pb'; @@ -13,8 +13,8 @@ import { disconnect, login, updateStatus } from './'; export function activate(options: WebSocketConnectOptions, password?: string, passwordSalt?: string): void { const { userName, token } = options as unknown as AccountActivationParams; - BackendService.sendSessionCommand(Command_Activate_ext, create(Command_ActivateSchema, { - ...webClient.clientConfig, + webClient.protobuf.sendSessionCommand(Command_Activate_ext, create(Command_ActivateSchema, { + ...CLIENT_CONFIG, userName, token, }), { diff --git a/webclient/src/websocket/commands/session/addToList.ts b/webclient/src/websocket/commands/session/addToList.ts index 7dbee06e2..74bdfa23c 100644 --- a/webclient/src/websocket/commands/session/addToList.ts +++ b/webclient/src/websocket/commands/session/addToList.ts @@ -1,5 +1,5 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_AddToList_ext, Command_AddToListSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; @@ -12,7 +12,7 @@ export function addToIgnoreList(userName: string): void { } export function addToList(list: string, userName: string): void { - BackendService.sendSessionCommand(Command_AddToList_ext, create(Command_AddToListSchema, { list, userName }), { + webClient.protobuf.sendSessionCommand(Command_AddToList_ext, create(Command_AddToListSchema, { list, userName }), { onSuccess: () => { SessionPersistence.addToList(list, userName); }, diff --git a/webclient/src/websocket/commands/session/deckDel.ts b/webclient/src/websocket/commands/session/deckDel.ts index 04816ad46..bf0bd2672 100644 --- a/webclient/src/websocket/commands/session/deckDel.ts +++ b/webclient/src/websocket/commands/session/deckDel.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeckDelSchema, Command_DeckDel_ext } from 'generated/proto/command_deck_del_pb'; import { SessionPersistence } from '../../persistence'; export function deckDel(deckId: number): void { - BackendService.sendSessionCommand(Command_DeckDel_ext, create(Command_DeckDelSchema, { deckId }), { + webClient.protobuf.sendSessionCommand(Command_DeckDel_ext, create(Command_DeckDelSchema, { deckId }), { onSuccess: () => { SessionPersistence.deleteServerDeck(deckId); }, diff --git a/webclient/src/websocket/commands/session/deckDelDir.ts b/webclient/src/websocket/commands/session/deckDelDir.ts index 1d7782e2d..7c442b575 100644 --- a/webclient/src/websocket/commands/session/deckDelDir.ts +++ b/webclient/src/websocket/commands/session/deckDelDir.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeckDelDirSchema, Command_DeckDelDir_ext } from 'generated/proto/command_deck_del_dir_pb'; import { SessionPersistence } from '../../persistence'; export function deckDelDir(path: string): void { - BackendService.sendSessionCommand(Command_DeckDelDir_ext, create(Command_DeckDelDirSchema, { path }), { + webClient.protobuf.sendSessionCommand(Command_DeckDelDir_ext, create(Command_DeckDelDirSchema, { path }), { onSuccess: () => { SessionPersistence.deleteServerDeckDir(path); }, diff --git a/webclient/src/websocket/commands/session/deckList.ts b/webclient/src/websocket/commands/session/deckList.ts index f1260dcfc..71f611a26 100644 --- a/webclient/src/websocket/commands/session/deckList.ts +++ b/webclient/src/websocket/commands/session/deckList.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeckListSchema, Command_DeckList_ext } from 'generated/proto/command_deck_list_pb'; import { SessionPersistence } from '../../persistence'; import { Response_DeckList_ext } from 'generated/proto/response_deck_list_pb'; export function deckList(): void { - BackendService.sendSessionCommand(Command_DeckList_ext, create(Command_DeckListSchema), { + webClient.protobuf.sendSessionCommand(Command_DeckList_ext, create(Command_DeckListSchema), { responseExt: Response_DeckList_ext, onSuccess: (response) => { if (response.root) { diff --git a/webclient/src/websocket/commands/session/deckNewDir.ts b/webclient/src/websocket/commands/session/deckNewDir.ts index dc6f75481..d208bf0f4 100644 --- a/webclient/src/websocket/commands/session/deckNewDir.ts +++ b/webclient/src/websocket/commands/session/deckNewDir.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeckNewDirSchema, Command_DeckNewDir_ext } from 'generated/proto/command_deck_new_dir_pb'; import { SessionPersistence } from '../../persistence'; export function deckNewDir(path: string, dirName: string): void { - BackendService.sendSessionCommand(Command_DeckNewDir_ext, create(Command_DeckNewDirSchema, { path, dirName }), { + webClient.protobuf.sendSessionCommand(Command_DeckNewDir_ext, create(Command_DeckNewDirSchema, { path, dirName }), { onSuccess: () => { SessionPersistence.createServerDeckDir(path, dirName); }, diff --git a/webclient/src/websocket/commands/session/deckUpload.ts b/webclient/src/websocket/commands/session/deckUpload.ts index d3c3576b2..a13596329 100644 --- a/webclient/src/websocket/commands/session/deckUpload.ts +++ b/webclient/src/websocket/commands/session/deckUpload.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeckUploadSchema, Command_DeckUpload_ext } from 'generated/proto/command_deck_upload_pb'; import { SessionPersistence } from '../../persistence'; import { Response_DeckUpload_ext } from 'generated/proto/response_deck_upload_pb'; export function deckUpload(path: string, deckId: number, deckList: string): void { - BackendService.sendSessionCommand(Command_DeckUpload_ext, create(Command_DeckUploadSchema, { path, deckId, deckList }), { + webClient.protobuf.sendSessionCommand(Command_DeckUpload_ext, create(Command_DeckUploadSchema, { path, deckId, deckList }), { responseExt: Response_DeckUpload_ext, onSuccess: (response) => { if (response.newFile) { diff --git a/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts b/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts index 71d105f8d..98cfb25ac 100644 --- a/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts +++ b/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts @@ -2,8 +2,8 @@ import { ForgotPasswordChallengeParams } from 'store'; import { StatusEnum, WebSocketConnectOptions } from 'types'; import { create } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_ForgotPasswordChallenge_ext, Command_ForgotPasswordChallengeSchema, } from 'generated/proto/session_commands_pb'; @@ -13,8 +13,8 @@ import { disconnect, updateStatus } from './'; export function forgotPasswordChallenge(options: WebSocketConnectOptions): void { const { userName, email } = options as unknown as ForgotPasswordChallengeParams; - BackendService.sendSessionCommand(Command_ForgotPasswordChallenge_ext, create(Command_ForgotPasswordChallengeSchema, { - ...webClient.clientConfig, + webClient.protobuf.sendSessionCommand(Command_ForgotPasswordChallenge_ext, create(Command_ForgotPasswordChallengeSchema, { + ...CLIENT_CONFIG, userName, email, }), { diff --git a/webclient/src/websocket/commands/session/forgotPasswordRequest.ts b/webclient/src/websocket/commands/session/forgotPasswordRequest.ts index a14cd1ee5..6760b3908 100644 --- a/webclient/src/websocket/commands/session/forgotPasswordRequest.ts +++ b/webclient/src/websocket/commands/session/forgotPasswordRequest.ts @@ -2,8 +2,8 @@ import { ForgotPasswordParams } from 'store'; import { StatusEnum, WebSocketConnectOptions } from 'types'; import { create } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_ForgotPasswordRequest_ext, Command_ForgotPasswordRequestSchema, } from 'generated/proto/session_commands_pb'; @@ -15,8 +15,8 @@ import { disconnect, updateStatus } from './'; export function forgotPasswordRequest(options: WebSocketConnectOptions): void { const { userName } = options as unknown as ForgotPasswordParams; - BackendService.sendSessionCommand(Command_ForgotPasswordRequest_ext, create(Command_ForgotPasswordRequestSchema, { - ...webClient.clientConfig, + webClient.protobuf.sendSessionCommand(Command_ForgotPasswordRequest_ext, create(Command_ForgotPasswordRequestSchema, { + ...CLIENT_CONFIG, userName, }), { responseExt: Response_ForgotPasswordRequest_ext, diff --git a/webclient/src/websocket/commands/session/forgotPasswordReset.ts b/webclient/src/websocket/commands/session/forgotPasswordReset.ts index 543467b18..90625fd5c 100644 --- a/webclient/src/websocket/commands/session/forgotPasswordReset.ts +++ b/webclient/src/websocket/commands/session/forgotPasswordReset.ts @@ -3,8 +3,8 @@ import { StatusEnum, WebSocketConnectOptions } from 'types'; import { create } from '@bufbuild/protobuf'; import type { MessageInitShape } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_ForgotPasswordReset_ext, Command_ForgotPasswordResetSchema, } from 'generated/proto/session_commands_pb'; @@ -17,7 +17,7 @@ export function forgotPasswordReset(options: WebSocketConnectOptions, newPasswor const { userName, token } = options as unknown as ForgotPasswordResetParams; const params: MessageInitShape = { - ...webClient.clientConfig, + ...CLIENT_CONFIG, userName, token, ...(passwordSalt @@ -25,7 +25,7 @@ export function forgotPasswordReset(options: WebSocketConnectOptions, newPasswor : { newPassword }), }; - BackendService.sendSessionCommand(Command_ForgotPasswordReset_ext, create(Command_ForgotPasswordResetSchema, params), { + webClient.protobuf.sendSessionCommand(Command_ForgotPasswordReset_ext, create(Command_ForgotPasswordResetSchema, params), { onSuccess: () => { updateStatus(StatusEnum.DISCONNECTED, null); SessionPersistence.resetPasswordSuccess(); diff --git a/webclient/src/websocket/commands/session/getGamesOfUser.ts b/webclient/src/websocket/commands/session/getGamesOfUser.ts index 4f2cb2109..184a31494 100644 --- a/webclient/src/websocket/commands/session/getGamesOfUser.ts +++ b/webclient/src/websocket/commands/session/getGamesOfUser.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GetGamesOfUser_ext, Command_GetGamesOfUserSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; import { Response_GetGamesOfUser_ext } from 'generated/proto/response_get_games_of_user_pb'; export function getGamesOfUser(userName: string): void { - BackendService.sendSessionCommand(Command_GetGamesOfUser_ext, create(Command_GetGamesOfUserSchema, { userName }), { + webClient.protobuf.sendSessionCommand(Command_GetGamesOfUser_ext, create(Command_GetGamesOfUserSchema, { userName }), { responseExt: Response_GetGamesOfUser_ext, onSuccess: (response) => { SessionPersistence.getGamesOfUser(userName, response); diff --git a/webclient/src/websocket/commands/session/getUserInfo.ts b/webclient/src/websocket/commands/session/getUserInfo.ts index 2a93e81d1..cf1bf0f4c 100644 --- a/webclient/src/websocket/commands/session/getUserInfo.ts +++ b/webclient/src/websocket/commands/session/getUserInfo.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GetUserInfo_ext, Command_GetUserInfoSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; import { Response_GetUserInfo_ext } from 'generated/proto/response_get_user_info_pb'; export function getUserInfo(userName: string): void { - BackendService.sendSessionCommand(Command_GetUserInfo_ext, create(Command_GetUserInfoSchema, { userName }), { + webClient.protobuf.sendSessionCommand(Command_GetUserInfo_ext, create(Command_GetUserInfoSchema, { userName }), { responseExt: Response_GetUserInfo_ext, onSuccess: (response) => { SessionPersistence.getUserInfo(response.userInfo); diff --git a/webclient/src/websocket/commands/session/joinRoom.ts b/webclient/src/websocket/commands/session/joinRoom.ts index 371f98c16..267a7e07b 100644 --- a/webclient/src/websocket/commands/session/joinRoom.ts +++ b/webclient/src/websocket/commands/session/joinRoom.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_JoinRoom_ext, Command_JoinRoomSchema } from 'generated/proto/session_commands_pb'; import { RoomPersistence } from '../../persistence'; import { Response_JoinRoom_ext } from 'generated/proto/response_join_room_pb'; export function joinRoom(roomId: number): void { - BackendService.sendSessionCommand(Command_JoinRoom_ext, create(Command_JoinRoomSchema, { roomId }), { + webClient.protobuf.sendSessionCommand(Command_JoinRoom_ext, create(Command_JoinRoomSchema, { roomId }), { responseExt: Response_JoinRoom_ext, onSuccess: (response) => { if (response.roomInfo) { diff --git a/webclient/src/websocket/commands/session/listRooms.ts b/webclient/src/websocket/commands/session/listRooms.ts index 9a4efde14..efaeab483 100644 --- a/webclient/src/websocket/commands/session/listRooms.ts +++ b/webclient/src/websocket/commands/session/listRooms.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ListRooms_ext, Command_ListRoomsSchema } from 'generated/proto/session_commands_pb'; export function listRooms(): void { - BackendService.sendSessionCommand(Command_ListRooms_ext, create(Command_ListRoomsSchema)); + webClient.protobuf.sendSessionCommand(Command_ListRooms_ext, create(Command_ListRoomsSchema)); } diff --git a/webclient/src/websocket/commands/session/listUsers.ts b/webclient/src/websocket/commands/session/listUsers.ts index e10fe2ba5..50f831234 100644 --- a/webclient/src/websocket/commands/session/listUsers.ts +++ b/webclient/src/websocket/commands/session/listUsers.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ListUsers_ext, Command_ListUsersSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; import { Response_ListUsers_ext } from 'generated/proto/response_list_users_pb'; export function listUsers(): void { - BackendService.sendSessionCommand(Command_ListUsers_ext, create(Command_ListUsersSchema), { + webClient.protobuf.sendSessionCommand(Command_ListUsers_ext, create(Command_ListUsersSchema), { responseExt: Response_ListUsers_ext, onSuccess: (response) => { SessionPersistence.updateUsers(response.userList); diff --git a/webclient/src/websocket/commands/session/login.ts b/webclient/src/websocket/commands/session/login.ts index 68b0cd896..3d93ec0ec 100644 --- a/webclient/src/websocket/commands/session/login.ts +++ b/webclient/src/websocket/commands/session/login.ts @@ -1,8 +1,8 @@ import { StatusEnum, WebSocketConnectOptions } from 'types'; import { create } from '@bufbuild/protobuf'; import type { MessageInitShape } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_Login_ext, Command_LoginSchema } from 'generated/proto/session_commands_pb'; import { hashPassword } from '../../utils'; import { SessionPersistence } from '../../persistence'; @@ -20,7 +20,7 @@ export function login(options: WebSocketConnectOptions, password?: string, passw const { userName, hashedPassword } = options; const loginConfig: MessageInitShape = { - ...webClient.clientConfig, + ...CLIENT_CONFIG, clientid: 'webatrice', userName, ...(passwordSalt @@ -35,7 +35,7 @@ export function login(options: WebSocketConnectOptions, password?: string, passw disconnect(); }; - BackendService.sendSessionCommand(Command_Login_ext, create(Command_LoginSchema, loginConfig), { + webClient.protobuf.sendSessionCommand(Command_Login_ext, create(Command_LoginSchema, loginConfig), { responseExt: Response_Login_ext, onSuccess: (resp) => { const { buddyList, ignoreList, userInfo } = resp; diff --git a/webclient/src/websocket/commands/session/message.ts b/webclient/src/websocket/commands/session/message.ts index 19946779e..b310010be 100644 --- a/webclient/src/websocket/commands/session/message.ts +++ b/webclient/src/websocket/commands/session/message.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_Message_ext, Command_MessageSchema } from 'generated/proto/session_commands_pb'; export function message(userName: string, message: string): void { - BackendService.sendSessionCommand(Command_Message_ext, create(Command_MessageSchema, { userName, message })); + webClient.protobuf.sendSessionCommand(Command_Message_ext, create(Command_MessageSchema, { userName, message })); } diff --git a/webclient/src/websocket/commands/session/ping.ts b/webclient/src/websocket/commands/session/ping.ts index a8bbc7b94..bb015094a 100644 --- a/webclient/src/websocket/commands/session/ping.ts +++ b/webclient/src/websocket/commands/session/ping.ts @@ -1,9 +1,9 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_Ping_ext, Command_PingSchema } from 'generated/proto/session_commands_pb'; export function ping(pingReceived: () => void): void { - BackendService.sendSessionCommand(Command_Ping_ext, create(Command_PingSchema), { + webClient.protobuf.sendSessionCommand(Command_Ping_ext, create(Command_PingSchema), { onResponse: () => pingReceived(), }); } diff --git a/webclient/src/websocket/commands/session/register.ts b/webclient/src/websocket/commands/session/register.ts index cb2020102..8166f9f8e 100644 --- a/webclient/src/websocket/commands/session/register.ts +++ b/webclient/src/websocket/commands/session/register.ts @@ -3,8 +3,8 @@ import { StatusEnum, WebSocketConnectOptions } from 'types'; import { create, getExtension } from '@bufbuild/protobuf'; import type { MessageInitShape } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_Register_ext, Command_RegisterSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; import { hashPassword } from '../../utils'; @@ -17,7 +17,7 @@ export function register(options: WebSocketConnectOptions, password?: string, pa const { userName, email, country, realName } = options as ServerRegisterParams; const params: MessageInitShape = { - ...webClient.clientConfig, + ...CLIENT_CONFIG, userName, email, country, @@ -33,7 +33,7 @@ export function register(options: WebSocketConnectOptions, password?: string, pa disconnect(); }; - BackendService.sendSessionCommand(Command_Register_ext, create(Command_RegisterSchema, params), { + webClient.protobuf.sendSessionCommand(Command_Register_ext, create(Command_RegisterSchema, params), { onResponseCode: { [Response_ResponseCode.RespRegistrationAccepted]: () => { login(options, password, passwordSalt); diff --git a/webclient/src/websocket/commands/session/removeFromList.ts b/webclient/src/websocket/commands/session/removeFromList.ts index ff1254e27..033701815 100644 --- a/webclient/src/websocket/commands/session/removeFromList.ts +++ b/webclient/src/websocket/commands/session/removeFromList.ts @@ -1,5 +1,5 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_RemoveFromList_ext, Command_RemoveFromListSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; @@ -12,7 +12,7 @@ export function removeFromIgnoreList(userName: string): void { } export function removeFromList(list: string, userName: string): void { - BackendService.sendSessionCommand(Command_RemoveFromList_ext, create(Command_RemoveFromListSchema, { list, userName }), { + webClient.protobuf.sendSessionCommand(Command_RemoveFromList_ext, create(Command_RemoveFromListSchema, { list, userName }), { onSuccess: () => { SessionPersistence.removeFromList(list, userName); }, diff --git a/webclient/src/websocket/commands/session/replayDeleteMatch.ts b/webclient/src/websocket/commands/session/replayDeleteMatch.ts index 7d243e1b2..09f99c84a 100644 --- a/webclient/src/websocket/commands/session/replayDeleteMatch.ts +++ b/webclient/src/websocket/commands/session/replayDeleteMatch.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReplayDeleteMatchSchema, Command_ReplayDeleteMatch_ext } from 'generated/proto/command_replay_delete_match_pb'; import { SessionPersistence } from '../../persistence'; export function replayDeleteMatch(gameId: number): void { - BackendService.sendSessionCommand(Command_ReplayDeleteMatch_ext, create(Command_ReplayDeleteMatchSchema, { gameId }), { + webClient.protobuf.sendSessionCommand(Command_ReplayDeleteMatch_ext, create(Command_ReplayDeleteMatchSchema, { gameId }), { onSuccess: () => { SessionPersistence.replayDeleteMatch(gameId); }, diff --git a/webclient/src/websocket/commands/session/replayGetCode.ts b/webclient/src/websocket/commands/session/replayGetCode.ts index 77f72c330..8c5b1121c 100644 --- a/webclient/src/websocket/commands/session/replayGetCode.ts +++ b/webclient/src/websocket/commands/session/replayGetCode.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReplayGetCodeSchema, Command_ReplayGetCode_ext } from 'generated/proto/command_replay_get_code_pb'; import { Response_ReplayGetCode_ext } from 'generated/proto/response_replay_get_code_pb'; export function replayGetCode(gameId: number, onCodeReceived: (code: string) => void): void { - BackendService.sendSessionCommand(Command_ReplayGetCode_ext, create(Command_ReplayGetCodeSchema, { gameId }), { + webClient.protobuf.sendSessionCommand(Command_ReplayGetCode_ext, create(Command_ReplayGetCodeSchema, { gameId }), { responseExt: Response_ReplayGetCode_ext, onSuccess: (response) => { onCodeReceived(response.replayCode); diff --git a/webclient/src/websocket/commands/session/replayList.ts b/webclient/src/websocket/commands/session/replayList.ts index 1d6dc8aa5..7fd45a3b2 100644 --- a/webclient/src/websocket/commands/session/replayList.ts +++ b/webclient/src/websocket/commands/session/replayList.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReplayListSchema, Command_ReplayList_ext } from 'generated/proto/command_replay_list_pb'; import { SessionPersistence } from '../../persistence'; import { Response_ReplayList_ext } from 'generated/proto/response_replay_list_pb'; export function replayList(): void { - BackendService.sendSessionCommand(Command_ReplayList_ext, create(Command_ReplayListSchema), { + webClient.protobuf.sendSessionCommand(Command_ReplayList_ext, create(Command_ReplayListSchema), { responseExt: Response_ReplayList_ext, onSuccess: (response) => { SessionPersistence.replayList(response.matchList); diff --git a/webclient/src/websocket/commands/session/replayModifyMatch.ts b/webclient/src/websocket/commands/session/replayModifyMatch.ts index fa2af33c5..b104a34ec 100644 --- a/webclient/src/websocket/commands/session/replayModifyMatch.ts +++ b/webclient/src/websocket/commands/session/replayModifyMatch.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReplayModifyMatchSchema, Command_ReplayModifyMatch_ext } from 'generated/proto/command_replay_modify_match_pb'; import { SessionPersistence } from '../../persistence'; export function replayModifyMatch(gameId: number, doNotHide: boolean): void { - BackendService.sendSessionCommand(Command_ReplayModifyMatch_ext, create(Command_ReplayModifyMatchSchema, { gameId, doNotHide }), { + webClient.protobuf.sendSessionCommand(Command_ReplayModifyMatch_ext, create(Command_ReplayModifyMatchSchema, { gameId, doNotHide }), { onSuccess: () => { SessionPersistence.replayModifyMatch(gameId, doNotHide); }, diff --git a/webclient/src/websocket/commands/session/replaySubmitCode.ts b/webclient/src/websocket/commands/session/replaySubmitCode.ts index 1ae371e8d..0d55a79db 100644 --- a/webclient/src/websocket/commands/session/replaySubmitCode.ts +++ b/webclient/src/websocket/commands/session/replaySubmitCode.ts @@ -1,5 +1,5 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReplaySubmitCodeSchema, Command_ReplaySubmitCode_ext } from 'generated/proto/command_replay_submit_code_pb'; export function replaySubmitCode( @@ -7,7 +7,7 @@ export function replaySubmitCode( onSuccess?: () => void, onError?: (responseCode: number) => void, ): void { - BackendService.sendSessionCommand(Command_ReplaySubmitCode_ext, create(Command_ReplaySubmitCodeSchema, { replayCode }), { + webClient.protobuf.sendSessionCommand(Command_ReplaySubmitCode_ext, create(Command_ReplaySubmitCodeSchema, { replayCode }), { onSuccess, onError, }); diff --git a/webclient/src/websocket/commands/session/requestPasswordSalt.ts b/webclient/src/websocket/commands/session/requestPasswordSalt.ts index 5ba515719..0e9d40e87 100644 --- a/webclient/src/websocket/commands/session/requestPasswordSalt.ts +++ b/webclient/src/websocket/commands/session/requestPasswordSalt.ts @@ -2,8 +2,8 @@ import { RequestPasswordSaltParams } from 'store'; import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; import { create } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_RequestPasswordSalt_ext, Command_RequestPasswordSaltSchema, } from 'generated/proto/session_commands_pb'; @@ -36,8 +36,8 @@ export function requestPasswordSalt(options: WebSocketConnectOptions, password?: disconnect(); }; - BackendService.sendSessionCommand(Command_RequestPasswordSalt_ext, create(Command_RequestPasswordSaltSchema, { - ...webClient.clientConfig, + webClient.protobuf.sendSessionCommand(Command_RequestPasswordSalt_ext, create(Command_RequestPasswordSaltSchema, { + ...CLIENT_CONFIG, userName, }), { responseExt: Response_PasswordSalt_ext, diff --git a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts index d2ba2bd50..f82dc2b1d 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts @@ -1,12 +1,6 @@ // Tests for complex session commands that call webClient directly // or have multiple branching callbacks. -vi.mock('../../services/BackendService', () => ({ - BackendService: { - sendSessionCommand: vi.fn(), - }, -})); - vi.mock('../../persistence', async () => { const { makeSessionPersistenceMock } = await import('../../__mocks__/sessionCommandMocks'); return { @@ -33,7 +27,6 @@ vi.mock('./', async () => { import { Mock } from 'vitest'; import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; -import { BackendService } from '../../services/BackendService'; import { SessionPersistence } from '../../persistence'; import webClient from '../../WebClient'; import * as SessionIndexMocks from './'; @@ -66,7 +59,7 @@ import { forgotPasswordReset } from './forgotPasswordReset'; import { requestPasswordSalt } from './requestPasswordSalt'; const { invokeOnSuccess, invokeResponseCode, invokeOnError } = makeCallbackHelpers( - BackendService.sendSessionCommand as Mock, + webClient.protobuf.sendSessionCommand as Mock, 2 ); @@ -144,7 +137,7 @@ describe('login', () => { it('sends Command_Login with plain password when no salt', () => { login({ userName: 'alice' } as any, 'pw'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Login_ext, expect.objectContaining({ password: 'pw' }), expect.objectContaining({ responseExt: Response_Login_ext }) @@ -153,7 +146,7 @@ describe('login', () => { it('sends Command_Login with hashedPassword when salt is given', () => { login({ userName: 'alice' } as any, 'pw', 'salt'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Login_ext, expect.objectContaining({ hashedPassword: 'hashed_pw' }), expect.objectContaining({ responseExt: Response_Login_ext }) @@ -162,7 +155,7 @@ describe('login', () => { it('uses options.hashedPassword if provided', () => { login({ userName: 'alice', hashedPassword: 'pre_hashed' } as any, 'pw', 'salt'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Login_ext, expect.objectContaining({ hashedPassword: 'pre_hashed' }), expect.objectContaining({ responseExt: Response_Login_ext }) @@ -270,7 +263,7 @@ describe('register', () => { it('sends Command_Register with plain password when no salt', () => { register({ userName: 'alice', email: 'a@b.com', country: 'US', realName: 'Al' } as any, 'pw'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Register_ext, expect.objectContaining({ password: 'pw' }), expect.any(Object) @@ -279,7 +272,7 @@ describe('register', () => { it('uses hashedPassword when salt is provided', () => { register({ userName: 'alice' } as any, 'pw', 'salt'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Register_ext, expect.objectContaining({ hashedPassword: 'hashed_pw' }), expect.any(Object) @@ -373,12 +366,12 @@ describe('activate', () => { it('sends Command_Activate with userName and token, not password', () => { activate({ userName: 'alice', token: 'tok' } as any, 'pw'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Activate_ext, expect.objectContaining({ userName: 'alice', token: 'tok' }), expect.any(Object) ); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Activate_ext, expect.not.objectContaining({ password: expect.anything() }), expect.any(Object) @@ -407,7 +400,7 @@ describe('forgotPasswordChallenge', () => { it('sends Command_ForgotPasswordChallenge', () => { forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ForgotPasswordChallenge_ext, expect.any(Object), expect.any(Object) ); }); @@ -434,7 +427,7 @@ describe('forgotPasswordRequest', () => { it('sends Command_ForgotPasswordRequest', () => { forgotPasswordRequest({ userName: 'alice' } as any); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ForgotPasswordRequest_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_ForgotPasswordRequest_ext }) @@ -472,7 +465,7 @@ describe('forgotPasswordReset', () => { it('sends Command_ForgotPasswordReset with plain newPassword when no salt', () => { forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ForgotPasswordReset_ext, expect.objectContaining({ newPassword: 'newpw' }), expect.any(Object) @@ -481,7 +474,7 @@ describe('forgotPasswordReset', () => { it('sends hashed new password when salt provided', () => { forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw', 'salt'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ForgotPasswordReset_ext, expect.objectContaining({ hashedNewPassword: 'hashed_pw' }), expect.any(Object) @@ -510,7 +503,7 @@ describe('requestPasswordSalt', () => { it('sends Command_RequestPasswordSalt', () => { requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_RequestPasswordSalt_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_PasswordSalt_ext }) diff --git a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts index d6b1e3744..b135e8d15 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts @@ -1,11 +1,5 @@ // Shared mock setup for session command tests -vi.mock('../../services/BackendService', () => ({ - BackendService: { - sendSessionCommand: vi.fn(), - }, -})); - vi.mock('../../persistence', async () => { const { makeSessionPersistenceMock } = await import('../../__mocks__/sessionCommandMocks'); return { @@ -33,7 +27,6 @@ vi.mock('./', async () => { import { Mock } from 'vitest'; import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; -import { BackendService } from '../../services/BackendService'; import { SessionPersistence } from '../../persistence'; import { RoomPersistence } from '../../persistence'; import webClient from '../../WebClient'; @@ -95,7 +88,7 @@ import { replayGetCode } from './replayGetCode'; import { replaySubmitCode } from './replaySubmitCode'; const { invokeOnSuccess, invokeCallback } = makeCallbackHelpers( - BackendService.sendSessionCommand as Mock, + webClient.protobuf.sendSessionCommand as Mock, 2 ); @@ -113,7 +106,7 @@ describe('accountEdit', () => { it('sends Command_AccountEdit with correct params', () => { accountEdit('pw', 'Alice', 'a@b.com', 'US'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_AccountEdit_ext, expect.objectContaining({ passwordCheck: 'pw', realName: 'Alice', email: 'a@b.com', country: 'US' }), expect.any(Object) @@ -133,7 +126,7 @@ describe('accountImage', () => { it('sends Command_AccountImage', () => { const img = new Uint8Array([1, 2]); accountImage(img); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_AccountImage_ext, expect.objectContaining({ image: img }), expect.any(Object) ); }); @@ -151,7 +144,7 @@ describe('accountPassword', () => { it('sends Command_AccountPassword', () => { accountPassword('old', 'new', 'hashed'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_AccountPassword_ext, expect.objectContaining({ oldPassword: 'old', newPassword: 'new', hashedNewPassword: 'hashed' }), expect.any(Object) @@ -170,7 +163,7 @@ describe('deckDel', () => { it('sends Command_DeckDel', () => { deckDel(42); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_DeckDel_ext, expect.objectContaining({ deckId: 42 }), expect.any(Object) @@ -189,7 +182,7 @@ describe('deckDelDir', () => { it('sends Command_DeckDelDir', () => { deckDelDir('/path'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_DeckDelDir_ext, expect.objectContaining({ path: '/path' }), expect.any(Object) ); }); @@ -206,7 +199,7 @@ describe('deckList', () => { it('sends Command_DeckList', () => { deckList(); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_DeckList_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_DeckList_ext }) @@ -226,7 +219,7 @@ describe('deckNewDir', () => { it('sends Command_DeckNewDir', () => { deckNewDir('/path', 'dir'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_DeckNewDir_ext, expect.objectContaining({ path: '/path', dirName: 'dir' }), expect.any(Object) ); }); @@ -243,7 +236,7 @@ describe('deckUpload', () => { it('sends Command_DeckUpload', () => { deckUpload('/path', 1, 'content'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_DeckUpload_ext, expect.objectContaining({ path: '/path', deckId: 1, deckList: 'content' }), expect.objectContaining({ responseExt: Response_DeckUpload_ext }) @@ -272,7 +265,7 @@ describe('getGamesOfUser', () => { it('sends Command_GetGamesOfUser', () => { getGamesOfUser('alice'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_GetGamesOfUser_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_GetGamesOfUser_ext }) @@ -292,7 +285,7 @@ describe('getUserInfo', () => { it('sends Command_GetUserInfo', () => { getUserInfo('alice'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_GetUserInfo_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_GetUserInfo_ext }) @@ -312,7 +305,7 @@ describe('joinRoom', () => { it('sends Command_JoinRoom', () => { joinRoom(5); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_JoinRoom_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_JoinRoom_ext }) @@ -332,7 +325,7 @@ describe('listRooms (command)', () => { it('sends Command_ListRooms', () => { listRooms(); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(Command_ListRooms_ext, expect.any(Object)); + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith(Command_ListRooms_ext, expect.any(Object)); }); }); @@ -341,7 +334,7 @@ describe('listUsers', () => { it('sends Command_ListUsers', () => { listUsers(); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ListUsers_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_ListUsers_ext }) @@ -361,7 +354,7 @@ describe('message', () => { it('sends Command_Message', () => { message('bob', 'hi'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Message_ext, expect.objectContaining({ userName: 'bob', message: 'hi' }) ); }); @@ -374,7 +367,7 @@ describe('ping', () => { it('sends Command_Ping', () => { const pingReceived = vi.fn(); ping(pingReceived); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(Command_Ping_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith(Command_Ping_ext, expect.any(Object), expect.any(Object)); }); it('calls pingReceived via onResponse', () => { @@ -390,7 +383,7 @@ describe('replayDeleteMatch', () => { it('sends Command_ReplayDeleteMatch', () => { replayDeleteMatch(7); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ReplayDeleteMatch_ext, expect.objectContaining({ gameId: 7 }), expect.any(Object) @@ -409,7 +402,7 @@ describe('replayList', () => { it('sends Command_ReplayList', () => { replayList(); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ReplayList_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_ReplayList_ext }) @@ -429,7 +422,7 @@ describe('replayModifyMatch', () => { it('sends Command_ReplayModifyMatch', () => { replayModifyMatch(7, true); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ReplayModifyMatch_ext, expect.objectContaining({ gameId: 7, doNotHide: true }), expect.any(Object) ); }); @@ -446,7 +439,7 @@ describe('addToList / addToBuddyList / addToIgnoreList', () => { it('addToBuddyList sends Command_AddToList with list=buddy', () => { addToBuddyList('alice'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_AddToList_ext, expect.objectContaining({ list: 'buddy' }), expect.any(Object) @@ -455,7 +448,7 @@ describe('addToList / addToBuddyList / addToIgnoreList', () => { it('addToIgnoreList sends Command_AddToList with list=ignore', () => { addToIgnoreList('bob'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_AddToList_ext, expect.objectContaining({ list: 'ignore' }), expect.any(Object) @@ -474,7 +467,7 @@ describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { it('removeFromBuddyList sends Command_RemoveFromList with list=buddy', () => { removeFromBuddyList('alice'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_RemoveFromList_ext, expect.objectContaining({ list: 'buddy' }), expect.any(Object) @@ -483,7 +476,7 @@ describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { it('removeFromIgnoreList sends Command_RemoveFromList with list=ignore', () => { removeFromIgnoreList('bob'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_RemoveFromList_ext, expect.objectContaining({ list: 'ignore' }), expect.any(Object) @@ -502,7 +495,7 @@ describe('replayGetCode', () => { it('sends Command_ReplayGetCode with gameId and responseExt', () => { replayGetCode(42, vi.fn()); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ReplayGetCode_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_ReplayGetCode_ext }) @@ -522,7 +515,7 @@ describe('replaySubmitCode', () => { it('sends Command_ReplaySubmitCode with replayCode', () => { replaySubmitCode('42-abc123'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ReplaySubmitCode_ext, expect.objectContaining({ replayCode: '42-abc123' }), expect.any(Object) ); }); diff --git a/webclient/src/websocket/config.ts b/webclient/src/websocket/config.ts new file mode 100644 index 000000000..9f3d12643 --- /dev/null +++ b/webclient/src/websocket/config.ts @@ -0,0 +1,27 @@ +export const CLIENT_CONFIG = { + clientid: 'webatrice', + clientver: 'webclient-1.0 (2019-10-31)', + clientfeatures: [ + 'client_id', + 'client_ver', + 'feature_set', + 'room_chat_history', + 'client_warnings', + /* unimplemented features */ + 'forgot_password', + 'idle_client', + 'mod_log_lookup', + 'user_ban_history', + // satisfy server reqs for POC + 'websocket', + '2.7.0_min_version', + '2.8.0_min_version' + ] +} as const; + +export const PROTOCOL_VERSION = 14; + +export const CLIENT_OPTIONS = { + autojoinrooms: true, + keepalive: 5000 +} as const; diff --git a/webclient/src/websocket/events/common/index.ts b/webclient/src/websocket/events/common/index.ts index f0be28944..c6832988d 100644 --- a/webclient/src/websocket/events/common/index.ts +++ b/webclient/src/websocket/events/common/index.ts @@ -1,3 +1,3 @@ -import { SessionExtensionRegistry } from '../../services/ProtobufService'; +import { SessionExtensionRegistry } from '../../services/protobuf-types'; export const CommonEvents: SessionExtensionRegistry = []; diff --git a/webclient/src/websocket/events/game/index.ts b/webclient/src/websocket/events/game/index.ts index 8c7963481..2e75dabc8 100644 --- a/webclient/src/websocket/events/game/index.ts +++ b/webclient/src/websocket/events/game/index.ts @@ -1,4 +1,4 @@ -import { GameExtensionRegistry, makeGameEntry } from '../../services/ProtobufService'; +import { GameExtensionRegistry, makeGameEntry } from '../../services/protobuf-types'; import { attachCard } from './attachCard'; import { changeZoneProperties } from './changeZoneProperties'; import { createArrow } from './createArrow'; diff --git a/webclient/src/websocket/events/room/index.ts b/webclient/src/websocket/events/room/index.ts index 799986dec..f1832faa9 100644 --- a/webclient/src/websocket/events/room/index.ts +++ b/webclient/src/websocket/events/room/index.ts @@ -1,4 +1,4 @@ -import { RoomExtensionRegistry, makeRoomEntry } from '../../services/ProtobufService'; +import { RoomExtensionRegistry, makeRoomEntry } from '../../services/protobuf-types'; import { joinRoom } from './joinRoom'; import { leaveRoom } from './leaveRoom'; diff --git a/webclient/src/websocket/events/session/index.ts b/webclient/src/websocket/events/session/index.ts index 84dc7ba7f..d2b8cc4e9 100644 --- a/webclient/src/websocket/events/session/index.ts +++ b/webclient/src/websocket/events/session/index.ts @@ -1,4 +1,4 @@ -import { SessionExtensionRegistry, makeSessionEntry } from '../../services/ProtobufService'; +import { SessionExtensionRegistry, makeSessionEntry } from '../../services/protobuf-types'; import { addToList } from './addToList'; import { connectionClosed } from './connectionClosed'; import { listRooms } from './listRooms'; diff --git a/webclient/src/websocket/events/session/listRooms.ts b/webclient/src/websocket/events/session/listRooms.ts index faba2968e..697bdaed3 100644 --- a/webclient/src/websocket/events/session/listRooms.ts +++ b/webclient/src/websocket/events/session/listRooms.ts @@ -1,4 +1,4 @@ -import webClient from '../../WebClient'; +import { CLIENT_OPTIONS } from '../../config'; import { joinRoom } from '../../commands/session'; import { RoomPersistence } from '../../persistence'; import { ListRoomsData } from './interfaces'; @@ -6,7 +6,7 @@ import { ListRoomsData } from './interfaces'; export function listRooms({ roomList }: ListRoomsData): void { RoomPersistence.updateRooms(roomList); - if (webClient.clientOptions.autojoinrooms) { + if (CLIENT_OPTIONS.autojoinrooms) { roomList.forEach(({ autoJoin, roomId }) => { if (autoJoin) { joinRoom(roomId); diff --git a/webclient/src/websocket/events/session/serverIdentification.ts b/webclient/src/websocket/events/session/serverIdentification.ts index 594d26e6d..d5998d3d3 100644 --- a/webclient/src/websocket/events/session/serverIdentification.ts +++ b/webclient/src/websocket/events/session/serverIdentification.ts @@ -1,6 +1,7 @@ import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; import webClient from '../../WebClient'; +import { PROTOCOL_VERSION } from '../../config'; import { activate, disconnect, @@ -18,7 +19,7 @@ import { SessionPersistence } from '../../persistence'; export function serverIdentification(info: ServerIdentificationData): void { const { serverName, serverVersion, protocolVersion, serverOptions } = info; - if (protocolVersion !== webClient.protocolVersion) { + if (protocolVersion !== PROTOCOL_VERSION) { updateStatus(StatusEnum.DISCONNECTED, `Protocol version mismatch: ${protocolVersion}`); disconnect(); return; diff --git a/webclient/src/websocket/events/session/sessionEvents.spec.ts b/webclient/src/websocket/events/session/sessionEvents.spec.ts index 9f662cde7..7b35ad135 100644 --- a/webclient/src/websocket/events/session/sessionEvents.spec.ts +++ b/webclient/src/websocket/events/session/sessionEvents.spec.ts @@ -27,12 +27,15 @@ vi.mock('../../persistence', () => ({ vi.mock('../../WebClient', () => ({ __esModule: true, default: { - clientOptions: { autojoinrooms: false }, options: {}, - protocolVersion: 14, }, })); +vi.mock('../../config', () => ({ + CLIENT_OPTIONS: { autojoinrooms: false }, + PROTOCOL_VERSION: 14, +})); + vi.mock('../../commands/session', () => ({ joinRoom: vi.fn(), updateStatus: vi.fn(), @@ -70,6 +73,7 @@ import { Event_ServerIdentificationSchema } from 'generated/proto/event_server_i import { SessionPersistence, RoomPersistence } from '../../persistence'; import webClient from '../../WebClient'; +import * as Config from '../../config'; import * as SessionCmds from '../../commands/session'; import * as Utils from '../../utils'; import { gameJoined } from './gameJoined'; @@ -266,13 +270,13 @@ describe('listRooms', () => { }); it('does not call joinRoom when autojoinrooms is false', () => { - (webClient as any).clientOptions = { autojoinrooms: false }; + (Config as any).CLIENT_OPTIONS = { autojoinrooms: false }; listRooms(create(Event_ListRoomsSchema, { roomList: [{ autoJoin: true, roomId: 1 }] as any[] })); expect(SessionCmds.joinRoom).not.toHaveBeenCalled(); }); it('calls joinRoom for autoJoin rooms when autojoinrooms is true', () => { - (webClient as any).clientOptions = { autojoinrooms: true }; + (Config as any).CLIENT_OPTIONS = { autojoinrooms: true }; listRooms(create(Event_ListRoomsSchema, { roomList: [{ autoJoin: true, roomId: 2 }, { autoJoin: false, roomId: 3 }] as any[] })); expect(SessionCmds.joinRoom).toHaveBeenCalledTimes(1); expect(SessionCmds.joinRoom).toHaveBeenCalledWith(2); @@ -373,7 +377,7 @@ describe('connectionClosed', () => { describe('serverIdentification', () => { beforeEach(() => { - (webClient as any).protocolVersion = 14; + (Config as any).PROTOCOL_VERSION = 14; (webClient as any).options = {}; }); diff --git a/webclient/src/websocket/persistence/SessionPersistence.spec.ts b/webclient/src/websocket/persistence/SessionPersistence.spec.ts index 5bac239f4..9dbe4a10f 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.spec.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.spec.ts @@ -1,6 +1,7 @@ vi.mock('store', () => ({ ServerDispatch: { initialized: vi.fn(), + connectionAttempted: vi.fn(), clearStore: vi.fn(), loginSuccessful: vi.fn(), loginFailed: vi.fn(), @@ -387,4 +388,21 @@ describe('SessionPersistence', () => { SessionPersistence.replayDeleteMatch(7); expect(ServerDispatch.replayDeleteMatch).toHaveBeenCalledWith(7); }); + + it('connectionAttempted delegates to ServerDispatch', () => { + SessionPersistence.connectionAttempted(); + expect(ServerDispatch.connectionAttempted).toHaveBeenCalled(); + }); + + it('playerPropertiesChanged does nothing when payload has no playerProperties', () => { + SessionPersistence.playerPropertiesChanged(5, 1, {} as any); + expect(GameDispatch.playerPropertiesChanged).not.toHaveBeenCalled(); + }); + + it('getGamesOfUser handles rooms with missing gametypeList', () => { + const room = {} as any; + const game = { gameId: 5 }; + SessionPersistence.getGamesOfUser('alice', { roomList: [room], gameList: [game] } as any); + expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', [game], {}); + }); }); diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts index d15215ec9..fcf670163 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.ts @@ -19,6 +19,10 @@ export class SessionPersistence { ServerDispatch.initialized(); } + static connectionAttempted() { + ServerDispatch.connectionAttempted(); + } + static clearStore() { ServerDispatch.clearStore(); } diff --git a/webclient/src/websocket/services/BackendService.spec.ts b/webclient/src/websocket/services/BackendService.spec.ts deleted file mode 100644 index 5f33fd06e..000000000 --- a/webclient/src/websocket/services/BackendService.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -vi.mock('@bufbuild/protobuf', () => ({ - create: vi.fn().mockReturnValue({}), - setExtension: vi.fn(), - getExtension: vi.fn(), -})); - -vi.mock('generated/proto/response_pb', () => ({ - Response_ResponseCode: { RespOk: 1 }, -})); - -vi.mock('../WebClient', () => { - const mockProtobuf = { - sendGameCommand: vi.fn(), - sendSessionCommand: vi.fn(), - sendRoomCommand: vi.fn(), - sendModeratorCommand: vi.fn(), - sendAdminCommand: vi.fn(), - }; - return { __esModule: true, default: { protobuf: mockProtobuf } }; -}); - -import { getExtension } from '@bufbuild/protobuf'; -import { BackendService } from './BackendService'; -import webClient from '../WebClient'; - -beforeEach(() => { - vi.clearAllMocks(); -}); - -function captureCallback(sendFn: ReturnType) { - const protobuf = webClient.protobuf as any; - const usesIndex2 = sendFn === protobuf.sendRoomCommand || sendFn === protobuf.sendGameCommand; - return sendFn.mock.calls[0][usesIndex2 ? 2 : 1]; -} - -describe('BackendService', () => { - describe('send commands', () => { - it.each([ - ['sendGameCommand', () => BackendService.sendGameCommand(7, {} as any, {} as any)], - ['sendSessionCommand', () => BackendService.sendSessionCommand({} as any, {} as any)], - ['sendRoomCommand', () => BackendService.sendRoomCommand(5, {} as any, {} as any)], - ['sendModeratorCommand', () => BackendService.sendModeratorCommand({} as any, {} as any)], - ['sendAdminCommand', () => BackendService.sendAdminCommand({} as any, {} as any)], - ])('%s delegates to protobuf', (methodName, invoke) => { - invoke(); - expect((webClient.protobuf as any)[methodName]).toHaveBeenCalled(); - }); - }); - - describe('handleResponse via non-session command callbacks', () => { - it('sendGameCommand callback invokes handleResponse', () => { - const onSuccess = vi.fn(); - BackendService.sendGameCommand(7, {} as any, {} as any, { onSuccess }); - const cb = (webClient.protobuf as any).sendGameCommand.mock.calls[0][2]; - cb({ responseCode: 1 }); - expect(onSuccess).toHaveBeenCalled(); - }); - - it('sendRoomCommand callback invokes handleResponse', () => { - const onSuccess = vi.fn(); - BackendService.sendRoomCommand(5, {} as any, {} as any, { onSuccess }); - captureCallback((webClient.protobuf as any).sendRoomCommand)({ responseCode: 1 }); - expect(onSuccess).toHaveBeenCalled(); - }); - - it('sendModeratorCommand callback invokes handleResponse', () => { - const onSuccess = vi.fn(); - BackendService.sendModeratorCommand({} as any, {} as any, { onSuccess }); - captureCallback((webClient.protobuf as any).sendModeratorCommand)({ responseCode: 1 }); - expect(onSuccess).toHaveBeenCalled(); - }); - - it('sendAdminCommand callback invokes handleResponse', () => { - const onSuccess = vi.fn(); - BackendService.sendAdminCommand({} as any, {} as any, { onSuccess }); - captureCallback((webClient.protobuf as any).sendAdminCommand)({ responseCode: 1 }); - expect(onSuccess).toHaveBeenCalled(); - }); - }); - - describe('handleResponse (via sendSessionCommand callback)', () => { - function invokeCallback(options: any, raw: any) { - BackendService.sendSessionCommand({} as any, {} as any, options); - const cb = (webClient.protobuf as any).sendSessionCommand.mock.calls[0][1]; - cb(raw); - } - - it('calls onResponse and returns early when provided', () => { - const onResponse = vi.fn(); - const onSuccess = vi.fn(); - invokeCallback({ onResponse, onSuccess }, { responseCode: 99 }); - expect(onResponse).toHaveBeenCalled(); - expect(onSuccess).not.toHaveBeenCalled(); - }); - - it('calls onSuccess when responseCode is RespOk and no responseExt', () => { - const onSuccess = vi.fn(); - const raw = { responseCode: 1 }; - invokeCallback({ onSuccess }, raw); - expect(onSuccess).toHaveBeenCalledWith(); - }); - - it('calls onSuccess with nested response when responseExt is set', () => { - vi.mocked(getExtension).mockReturnValue({ nested: true }); - const onSuccess = vi.fn(); - const fakeExt = {} as any; - const raw = { responseCode: 1 }; - invokeCallback({ onSuccess, responseExt: fakeExt }, raw); - expect(onSuccess).toHaveBeenCalledWith({ nested: true }, raw); - }); - - it('calls onResponseCode handler when code matches', () => { - const specificHandler = vi.fn(); - invokeCallback({ onResponseCode: { 5: specificHandler } }, { responseCode: 5 }); - expect(specificHandler).toHaveBeenCalled(); - }); - - it('calls onError when responseCode is not RespOk and no specific handler', () => { - const onError = vi.fn(); - invokeCallback({ onError }, { responseCode: 99 }); - expect(onError).toHaveBeenCalledWith(99, { responseCode: 99 }); - }); - - it('logs error to console when no callbacks for non-RespOk response', () => { - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - invokeCallback({}, { responseCode: 42 }); - expect(consoleSpy).toHaveBeenCalled(); - consoleSpy.mockRestore(); - }); - }); -}); diff --git a/webclient/src/websocket/services/BackendService.ts b/webclient/src/websocket/services/BackendService.ts deleted file mode 100644 index 61b856e83..000000000 --- a/webclient/src/websocket/services/BackendService.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { create, getExtension, setExtension } from '@bufbuild/protobuf'; -import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; - -import webClient from '../WebClient'; -import { Response_ResponseCode, type Response } from 'generated/proto/response_pb'; -import { SessionCommandSchema, type SessionCommand } from 'generated/proto/session_commands_pb'; -import { GameCommandSchema, type GameCommand } from 'generated/proto/game_commands_pb'; -import { RoomCommandSchema, type RoomCommand } from 'generated/proto/room_commands_pb'; -import { ModeratorCommandSchema, type ModeratorCommand } from 'generated/proto/moderator_commands_pb'; -import { AdminCommandSchema, type AdminCommand } from 'generated/proto/admin_commands_pb'; - -interface CommandOptionsBase { - onError?: (responseCode: number, raw: Response) => void; - onResponseCode?: { [code: number]: (raw: Response) => void }; - onResponse?: (raw: Response) => void; -} - -export interface CommandOptionsWithResponse extends CommandOptionsBase { - responseExt: GenExtension; - onSuccess?: (response: R, raw: Response) => void; -} - -export interface CommandOptionsWithoutResponse extends CommandOptionsBase { - responseExt?: undefined; - onSuccess?: () => void; -} - -export type CommandOptions = CommandOptionsWithResponse | CommandOptionsWithoutResponse; - -function hasResponseExt(options: CommandOptions): options is CommandOptionsWithResponse { - return options.responseExt !== undefined; -} - -export class BackendService { - static sendGameCommand(gameId: number, ext: GenExtension, value: V, options?: CommandOptions): void { - const cmd = create(GameCommandSchema); - setExtension(cmd, ext, value); - webClient.protobuf.sendGameCommand(gameId, cmd, (raw: Response) => { - if (options) { - BackendService.handleResponse(ext.typeName, raw, options); - } - }); - } - - static sendSessionCommand(ext: GenExtension, value: V, options?: CommandOptions): void { - const cmd = create(SessionCommandSchema); - setExtension(cmd, ext, value); - webClient.protobuf.sendSessionCommand(cmd, raw => { - if (options) { - BackendService.handleResponse(ext.typeName, raw, options); - } - }); - } - - static sendRoomCommand(roomId: number, ext: GenExtension, value: V, options?: CommandOptions): void { - const cmd = create(RoomCommandSchema); - setExtension(cmd, ext, value); - webClient.protobuf.sendRoomCommand(roomId, cmd, raw => { - if (options) { - BackendService.handleResponse(ext.typeName, raw, options); - } - }); - } - - static sendModeratorCommand(ext: GenExtension, value: V, options?: CommandOptions): void { - const cmd = create(ModeratorCommandSchema); - setExtension(cmd, ext, value); - webClient.protobuf.sendModeratorCommand(cmd, raw => { - if (options) { - BackendService.handleResponse(ext.typeName, raw, options); - } - }); - } - - static sendAdminCommand(ext: GenExtension, value: V, options?: CommandOptions): void { - const cmd = create(AdminCommandSchema); - setExtension(cmd, ext, value); - webClient.protobuf.sendAdminCommand(cmd, raw => { - if (options) { - BackendService.handleResponse(ext.typeName, raw, options); - } - }); - } - - private static handleResponse(typeName: string, raw: Response, options: CommandOptions): void { - if (options.onResponse) { - options.onResponse(raw); - return; - } - - const { responseCode } = raw; - - if (responseCode === Response_ResponseCode.RespOk) { - if (hasResponseExt(options)) { - options.onSuccess?.(getExtension(raw, options.responseExt), raw); - } else { - options.onSuccess?.(); - } - return; - } - - if (options.onResponseCode?.[responseCode]) { - options.onResponseCode[responseCode](raw); - return; - } - - if (options.onError) { - options.onError(responseCode, raw); - } else { - console.error(`${typeName} failed with response code: ${responseCode}`); - } - } -} - diff --git a/webclient/src/websocket/services/KeepAliveService.spec.ts b/webclient/src/websocket/services/KeepAliveService.spec.ts index ed53da05f..4d45d6305 100644 --- a/webclient/src/websocket/services/KeepAliveService.spec.ts +++ b/webclient/src/websocket/services/KeepAliveService.spec.ts @@ -1,23 +1,17 @@ -vi.mock('../WebClient', () => ({ - __esModule: true, - default: { - socket: { - checkReadyState: vi.fn(), - }, - }, -})); - import { KeepAliveService } from './KeepAliveService'; +import { WebSocketService } from './WebSocketService'; -import webClient from '../WebClient'; +vi.mock('./WebSocketService'); describe('KeepAliveService', () => { let service: KeepAliveService; + let mockSocket: { checkReadyState: ReturnType }; beforeEach(() => { vi.useFakeTimers(); - service = new KeepAliveService(webClient.socket); + mockSocket = { checkReadyState: vi.fn().mockReturnValue(true) }; + service = new KeepAliveService(mockSocket as unknown as WebSocketService); }); it('should create', () => { @@ -36,7 +30,7 @@ describe('KeepAliveService', () => { promise = new Promise(resolve => resolvePing = resolve); ping = (done) => promise.then(done); - checkReadyStateSpy = vi.spyOn(webClient.socket, 'checkReadyState'); + checkReadyStateSpy = vi.spyOn(mockSocket, 'checkReadyState'); checkReadyStateSpy.mockImplementation(() => true); service.startPingLoop(interval, ping); diff --git a/webclient/src/websocket/services/ProtobufService.spec.ts b/webclient/src/websocket/services/ProtobufService.spec.ts index deb01a4bd..4a4ec401d 100644 --- a/webclient/src/websocket/services/ProtobufService.spec.ts +++ b/webclient/src/websocket/services/ProtobufService.spec.ts @@ -4,6 +4,7 @@ vi.mock('@bufbuild/protobuf', () => ({ toBinary: vi.fn().mockReturnValue(new Uint8Array()), hasExtension: vi.fn().mockReturnValue(false), getExtension: vi.fn(), + setExtension: vi.fn(), })); vi.mock('generated/proto/commands_pb', () => ({ @@ -20,11 +21,6 @@ vi.mock('generated/proto/server_message_pb', () => ({ }, })); -vi.mock('../commands/session', () => ({ - SessionCommands: { ping: vi.fn() }, - ping: vi.fn(), -})); - vi.mock('../events', () => ({ GameEvents: [], RoomEvents: [], @@ -39,8 +35,7 @@ vi.mock('../WebClient', () => ({ import { fromBinary, hasExtension, getExtension } from '@bufbuild/protobuf'; import { ServerMessage_MessageType } from 'generated/proto/server_message_pb'; import { ProtobufService } from './ProtobufService'; -import { ping as sessionPing } from '../commands/session'; -import { GameEvents } from '../events'; +import { GameEvents, RoomEvents, SessionEvents } from '../events'; let mockSocket: any; @@ -48,7 +43,7 @@ beforeEach(() => { vi.clearAllMocks(); mockSocket = { - checkReadyState: vi.fn().mockReturnValue(true), + isOpen: vi.fn().mockReturnValue(true), send: vi.fn(), }; }); @@ -56,34 +51,34 @@ beforeEach(() => { describe('ProtobufService', () => { describe('resetCommands', () => { it('resets cmdId and pendingCommands', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); service.sendSessionCommand({} as any, vi.fn()); expect((service as any).cmdId).toBe(1); service.resetCommands(); expect((service as any).cmdId).toBe(0); - expect((service as any).pendingCommands).toEqual({}); + expect((service as any).pendingCommands).toEqual(new Map()); }); }); describe('sendCommand', () => { it('increments cmdId and stores callback', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const cb = vi.fn(); service.sendCommand({} as any, cb); expect((service as any).cmdId).toBe(1); - expect((service as any).pendingCommands[1]).toBe(cb); + expect((service as any).pendingCommands.get(1)).toBe(cb); }); it('sends encoded data when socket is OPEN', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - mockSocket.checkReadyState.mockReturnValue(true); + const service = new ProtobufService(mockSocket); + mockSocket.isOpen.mockReturnValue(true); service.sendCommand({} as any, vi.fn()); expect(mockSocket.send).toHaveBeenCalled(); }); it('does not send when socket is not OPEN', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - mockSocket.checkReadyState.mockReturnValue(false); + const service = new ProtobufService(mockSocket); + mockSocket.isOpen.mockReturnValue(false); service.sendCommand({} as any, vi.fn()); expect(mockSocket.send).not.toHaveBeenCalled(); }); @@ -91,156 +86,146 @@ describe('ProtobufService', () => { describe('sendSessionCommand', () => { it('stores callback and increments cmdId', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - const cb = vi.fn(); - service.sendSessionCommand({} as any, cb); + const service = new ProtobufService(mockSocket); + service.sendSessionCommand({} as any, {}); expect((service as any).cmdId).toBe(1); - expect((service as any).pendingCommands[1]).toBeTypeOf('function'); + expect((service as any).pendingCommands.get(1)).toBeTypeOf('function'); }); - it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + it('invokes onResponse with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendSessionCommand({} as any, cb); + service.sendSessionCommand({} as any, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); storedCb({ responseData: true }); expect(cb).toHaveBeenCalledWith({ responseData: true }); }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendSessionCommand({} as any); + const service = new ProtobufService(mockSocket); + service.sendSessionCommand({} as any, {}); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); expect(() => storedCb({ responseData: true })).not.toThrow(); }); }); describe('sendRoomCommand', () => { it('stores callback and increments cmdId', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendRoomCommand(42, {} as any, vi.fn()); + const service = new ProtobufService(mockSocket); + service.sendRoomCommand(42, {} as any, {}); expect((service as any).cmdId).toBe(1); }); - it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + it('invokes onResponse with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendRoomCommand(42, {} as any, cb); + service.sendRoomCommand(42, {} as any, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); storedCb({ responseData: true }); expect(cb).toHaveBeenCalledWith({ responseData: true }); }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendRoomCommand(42, {} as any); + const service = new ProtobufService(mockSocket); + service.sendRoomCommand(42, {} as any, {}); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); expect(() => storedCb({ responseData: true })).not.toThrow(); }); }); describe('sendGameCommand', () => { it('stores callback and increments cmdId', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendGameCommand(7, {} as any, vi.fn()); + const service = new ProtobufService(mockSocket); + service.sendGameCommand(7, {} as any, {}); expect((service as any).cmdId).toBe(1); }); - it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + it('invokes onResponse with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendGameCommand(7, {} as any, cb); + service.sendGameCommand(7, {} as any, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); storedCb({ responseData: true }); expect(cb).toHaveBeenCalledWith({ responseData: true }); }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendGameCommand(7, {} as any); + const service = new ProtobufService(mockSocket); + service.sendGameCommand(7, {} as any, {}); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); expect(() => storedCb({ responseData: true })).not.toThrow(); }); }); describe('sendModeratorCommand', () => { it('stores callback and increments cmdId', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendModeratorCommand({} as any, vi.fn()); + const service = new ProtobufService(mockSocket); + service.sendModeratorCommand({} as any, {}); expect((service as any).cmdId).toBe(1); }); - it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + it('invokes onResponse with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendModeratorCommand({} as any, cb); + service.sendModeratorCommand({} as any, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); storedCb({ responseData: true }); expect(cb).toHaveBeenCalledWith({ responseData: true }); }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendModeratorCommand({} as any); + const service = new ProtobufService(mockSocket); + service.sendModeratorCommand({} as any, {}); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); expect(() => storedCb({ responseData: true })).not.toThrow(); }); }); describe('sendAdminCommand', () => { it('stores callback and increments cmdId', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendAdminCommand({} as any, vi.fn()); + const service = new ProtobufService(mockSocket); + service.sendAdminCommand({} as any, {}); expect((service as any).cmdId).toBe(1); }); - it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + it('invokes onResponse with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendAdminCommand({} as any, cb); + service.sendAdminCommand({} as any, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); storedCb({ responseData: true }); expect(cb).toHaveBeenCalledWith({ responseData: true }); }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendAdminCommand({} as any); + const service = new ProtobufService(mockSocket); + service.sendAdminCommand({} as any, {}); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); expect(() => storedCb({ responseData: true })).not.toThrow(); }); }); - describe('sendKeepAliveCommand', () => { - it('delegates to SessionCommands.ping', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - const pingReceived = vi.fn(); - service.sendKeepAliveCommand(pingReceived); - expect(sessionPing).toHaveBeenCalledWith(pingReceived); - }); - }); - describe('handleMessageEvent', () => { it('routes RESPONSE message to processServerResponse', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const cb = vi.fn(); (service as any).cmdId = 1; - (service as any).pendingCommands[1] = cb; + (service as any).pendingCommands.set(1, cb); vi.mocked(fromBinary).mockReturnValue({ messageType: ServerMessage_MessageType.RESPONSE, @@ -249,11 +234,11 @@ describe('ProtobufService', () => { service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); expect(cb).toHaveBeenCalledWith({ cmdId: BigInt(1) }); - expect((service as any).pendingCommands[1]).toBeUndefined(); + expect((service as any).pendingCommands.get(1)).toBeUndefined(); }); it('routes ROOM_EVENT message', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const processRoomEvent = vi.spyOn(service as any, 'processRoomEvent'); vi.mocked(fromBinary).mockReturnValue({ @@ -266,7 +251,7 @@ describe('ProtobufService', () => { }); it('routes SESSION_EVENT message', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const processSessionEvent = vi.spyOn(service as any, 'processSessionEvent'); vi.mocked(fromBinary).mockReturnValue({ @@ -279,7 +264,7 @@ describe('ProtobufService', () => { }); it('routes GAME_EVENT_CONTAINER message', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const processGameEvent = vi.spyOn(service as any, 'processGameEvent'); vi.mocked(fromBinary).mockReturnValue({ @@ -292,7 +277,7 @@ describe('ProtobufService', () => { }); it('logs unknown message types (default case)', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); vi.mocked(fromBinary).mockReturnValue({ @@ -305,13 +290,13 @@ describe('ProtobufService', () => { }); it('does nothing when decoded message is null', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); vi.mocked(fromBinary).mockReturnValue(null as any); expect(() => service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent)).not.toThrow(); }); it('catches and logs decode errors', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); vi.mocked(fromBinary).mockImplementation(() => { throw new Error('decode error'); @@ -324,14 +309,14 @@ describe('ProtobufService', () => { describe('processGameEvent', () => { it('returns early when container has no eventList', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); vi.mocked(hasExtension).mockReturnValue(false); (service as any).processGameEvent(null, {}); expect(hasExtension).not.toHaveBeenCalled(); }); it('dispatches to a GameEvents handler when hasExtension returns true', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const handler = vi.fn(); const mockExt = {}; const payload = { someData: 1 }; @@ -349,6 +334,85 @@ describe('ProtobufService', () => { expect(handler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: 42, playerId: 5 })); (GameEvents as any).pop(); }); + + it('defaults gameId and playerId to -1 when undefined', () => { + const service = new ProtobufService(mockSocket); + const handler = vi.fn(); + const mockExt = {}; + const payload = { someData: 1 }; + + (GameEvents as any).push([mockExt, handler]); + vi.mocked(hasExtension).mockReturnValue(true); + vi.mocked(getExtension).mockReturnValue(payload); + + (service as any).processGameEvent({ + gameId: undefined, + eventList: [{ playerId: undefined }], + }); + + expect(handler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: -1, playerId: -1 })); + (GameEvents as any).pop(); + }); + }); + + describe('processServerResponse', () => { + it('returns early when response is undefined', () => { + const service = new ProtobufService(mockSocket); + (service as any).pendingCommands.set(1, vi.fn()); + (service as any).processServerResponse(undefined); + expect((service as any).pendingCommands.size).toBe(1); + }); + }); + + describe('processRoomEvent', () => { + it('returns early when event is undefined', () => { + const service = new ProtobufService(mockSocket); + vi.mocked(hasExtension).mockReturnValue(false); + (service as any).processRoomEvent(undefined); + expect(hasExtension).not.toHaveBeenCalled(); + }); + + it('dispatches to a RoomEvents handler when hasExtension returns true', () => { + const service = new ProtobufService(mockSocket); + const handler = vi.fn(); + const mockExt = {}; + const payload = { roomData: 1 }; + + (RoomEvents as any).push([mockExt, handler]); + vi.mocked(hasExtension).mockReturnValue(true); + vi.mocked(getExtension).mockReturnValue(payload); + + const event = { roomId: 10 }; + (service as any).processRoomEvent(event); + + expect(handler).toHaveBeenCalledWith(payload, event); + (RoomEvents as any).pop(); + }); + }); + + describe('processSessionEvent', () => { + it('returns early when event is undefined', () => { + const service = new ProtobufService(mockSocket); + vi.mocked(hasExtension).mockReturnValue(false); + (service as any).processSessionEvent(undefined); + expect(hasExtension).not.toHaveBeenCalled(); + }); + + it('dispatches to a SessionEvents handler when hasExtension returns true', () => { + const service = new ProtobufService(mockSocket); + const handler = vi.fn(); + const mockExt = {}; + const payload = { sessionData: 1 }; + + (SessionEvents as any).push([mockExt, handler]); + vi.mocked(hasExtension).mockReturnValue(true); + vi.mocked(getExtension).mockReturnValue(payload); + + (service as any).processSessionEvent({ sessionId: 7 }); + + expect(handler).toHaveBeenCalledWith(payload); + (SessionEvents as any).pop(); + }); }); }); diff --git a/webclient/src/websocket/services/ProtobufService.ts b/webclient/src/websocket/services/ProtobufService.ts index de6e287c0..7333c58b2 100644 --- a/webclient/src/websocket/services/ProtobufService.ts +++ b/webclient/src/websocket/services/ProtobufService.ts @@ -1,140 +1,132 @@ -import { create, fromBinary, hasExtension, getExtension, toBinary } from '@bufbuild/protobuf'; +import { create, fromBinary, hasExtension, getExtension, setExtension, toBinary } from '@bufbuild/protobuf'; import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; import type { Response } from 'generated/proto/response_pb'; import type { RoomEvent } from 'generated/proto/room_event_pb'; import type { SessionEvent } from 'generated/proto/session_event_pb'; import type { GameEventContainer } from 'generated/proto/game_event_container_pb'; -import type { GameEvent } from 'generated/proto/game_event_pb'; import { GameEvents, RoomEvents, SessionEvents } from '../events'; -import { WebClient } from '../WebClient'; -import { SessionCommands } from 'websocket'; import { GameEventMeta } from 'types'; import { CommandContainerSchema, type CommandContainer } from 'generated/proto/commands_pb'; import { ServerMessageSchema, ServerMessage_MessageType, type ServerMessage } from 'generated/proto/server_message_pb'; -import type { SessionCommand } from 'generated/proto/session_commands_pb'; -import type { GameCommand } from 'generated/proto/game_commands_pb'; -import type { RoomCommand } from 'generated/proto/room_commands_pb'; -import type { ModeratorCommand } from 'generated/proto/moderator_commands_pb'; -import type { AdminCommand } from 'generated/proto/admin_commands_pb'; +import { SessionCommandSchema, type SessionCommand } from 'generated/proto/session_commands_pb'; +import { GameCommandSchema, type GameCommand } from 'generated/proto/game_commands_pb'; +import { RoomCommandSchema, type RoomCommand } from 'generated/proto/room_commands_pb'; +import { ModeratorCommandSchema, type ModeratorCommand } from 'generated/proto/moderator_commands_pb'; +import { AdminCommandSchema, type AdminCommand } from 'generated/proto/admin_commands_pb'; -// Per-family registry entry types. Each family hardcodes its parent message type -// and the handler's exact secondary-argument signature, eliminating the previous -// `...args: unknown[]` erasure. +import { type CommandOptions, handleResponse } from './command-options'; -type SessionRegistryEntry = [ - GenExtension, - (value: V) => void -]; -export type SessionExtensionRegistry = SessionRegistryEntry[]; - -type RoomRegistryEntry = [ - GenExtension, - (value: V, roomEvent: RoomEvent) => void -]; -export type RoomExtensionRegistry = RoomRegistryEntry[]; - -type GameRegistryEntry = [ - GenExtension, - (value: V, meta: GameEventMeta) => void -]; -export type GameExtensionRegistry = GameRegistryEntry[]; - -/** - * Type-safe factory functions. The compiler verifies at the call site that the - * handler's parameter types match the extension's value type and the family's - * secondary argument type. - */ -export function makeSessionEntry( - ext: GenExtension, - handler: (value: V) => void -): SessionRegistryEntry { - return [ext as GenExtension, handler as (value: unknown) => void]; -} - -export function makeRoomEntry( - ext: GenExtension, - handler: (value: V, roomEvent: RoomEvent) => void -): RoomRegistryEntry { - return [ext as GenExtension, handler as RoomRegistryEntry[1]]; -} - -export function makeGameEntry( - ext: GenExtension, - handler: (value: V, meta: GameEventMeta) => void -): GameRegistryEntry { - return [ext as GenExtension, handler as GameRegistryEntry[1]]; +export interface SocketTransport { + send(data: Uint8Array): void; + isOpen(): boolean; } export class ProtobufService { private cmdId = 0; - private pendingCommands: { [cmdId: string]: (response: Response) => void } = {}; + private pendingCommands = new Map void>(); - private webClient: WebClient; + private transport: SocketTransport; - constructor(webClient: WebClient) { - this.webClient = webClient; + constructor(transport: SocketTransport) { + this.transport = transport; } public resetCommands() { this.cmdId = 0; - this.pendingCommands = {}; + this.pendingCommands.clear(); } - public sendGameCommand(gameId: number, gameCmd: GameCommand, callback?: (raw: Response) => void) { - const cmd = create(CommandContainerSchema, { - gameId, - gameCommand: [gameCmd], + public sendGameCommand( + gameId: number, + ext: GenExtension, + value: V, + options?: CommandOptions + ): void { + const gameCmd = create(GameCommandSchema); + setExtension(gameCmd, ext, value); + const cmd = create(CommandContainerSchema, { gameId, gameCommand: [gameCmd] }); + this.sendCommand(cmd, raw => { + if (options) { + handleResponse(ext.typeName, raw, options); + } }); - this.sendCommand(cmd, (raw: Response) => callback?.(raw)); } - public sendRoomCommand(roomId: number, roomCmd: RoomCommand, callback?: (raw: Response) => void) { - const cmd = create(CommandContainerSchema, { - roomId, - roomCommand: [roomCmd], + public sendRoomCommand( + roomId: number, + ext: GenExtension, + value: V, + options?: CommandOptions + ): void { + const roomCmd = create(RoomCommandSchema); + setExtension(roomCmd, ext, value); + const cmd = create(CommandContainerSchema, { roomId, roomCommand: [roomCmd] }); + this.sendCommand(cmd, raw => { + if (options) { + handleResponse(ext.typeName, raw, options); + } }); - this.sendCommand(cmd, raw => callback?.(raw)); } - public sendSessionCommand(sesCmd: SessionCommand, callback?: (raw: Response) => void) { - const cmd = create(CommandContainerSchema, { - sessionCommand: [sesCmd], + public sendSessionCommand( + ext: GenExtension, + value: V, + options?: CommandOptions + ): void { + const sesCmd = create(SessionCommandSchema); + setExtension(sesCmd, ext, value); + const cmd = create(CommandContainerSchema, { sessionCommand: [sesCmd] }); + this.sendCommand(cmd, raw => { + if (options) { + handleResponse(ext.typeName, raw, options); + } }); - this.sendCommand(cmd, (raw) => callback?.(raw)); } - public sendModeratorCommand(modCmd: ModeratorCommand, callback?: (raw: Response) => void) { - const cmd = create(CommandContainerSchema, { - moderatorCommand: [modCmd], + public sendModeratorCommand( + ext: GenExtension, + value: V, + options?: CommandOptions + ): void { + const modCmd = create(ModeratorCommandSchema); + setExtension(modCmd, ext, value); + const cmd = create(CommandContainerSchema, { moderatorCommand: [modCmd] }); + this.sendCommand(cmd, raw => { + if (options) { + handleResponse(ext.typeName, raw, options); + } }); - this.sendCommand(cmd, (raw) => callback?.(raw)); } - public sendAdminCommand(adminCmd: AdminCommand, callback?: (raw: Response) => void) { - const cmd = create(CommandContainerSchema, { - adminCommand: [adminCmd], + public sendAdminCommand( + ext: GenExtension, + value: V, + options?: CommandOptions + ): void { + const adminCmd = create(AdminCommandSchema); + setExtension(adminCmd, ext, value); + const cmd = create(CommandContainerSchema, { adminCommand: [adminCmd] }); + this.sendCommand(cmd, raw => { + if (options) { + handleResponse(ext.typeName, raw, options); + } }); - this.sendCommand(cmd, (raw) => callback?.(raw)); } public sendCommand(cmd: CommandContainer, callback: (raw: Response) => void) { this.cmdId++; cmd.cmdId = BigInt(this.cmdId); - this.pendingCommands[this.cmdId] = callback; + this.pendingCommands.set(this.cmdId, callback); - if (this.webClient.socket.checkReadyState(WebSocket.OPEN)) { - this.webClient.socket.send(toBinary(CommandContainerSchema, cmd)); + if (this.transport.isOpen()) { + this.transport.send(toBinary(CommandContainerSchema, cmd)); } } - public sendKeepAliveCommand(pingReceived: () => void) { - SessionCommands.ping(pingReceived); - } - public handleMessageEvent({ data }: MessageEvent): void { try { const uint8msg = new Uint8Array(data); @@ -170,9 +162,9 @@ export class ProtobufService { } const cmdId = Number(response.cmdId); - if (this.pendingCommands[cmdId]) { - this.pendingCommands[cmdId](response); - delete this.pendingCommands[cmdId]; + if (this.pendingCommands.has(cmdId)) { + this.pendingCommands.get(cmdId)!(response); + this.pendingCommands.delete(cmdId); } } diff --git a/webclient/src/websocket/services/WebSocketService.spec.ts b/webclient/src/websocket/services/WebSocketService.spec.ts index 67fa1ed3d..f01c2b2c1 100644 --- a/webclient/src/websocket/services/WebSocketService.spec.ts +++ b/webclient/src/websocket/services/WebSocketService.spec.ts @@ -5,6 +5,10 @@ vi.mock('../WebClient', () => ({ WebClient: vi.fn(), })); +vi.mock('../config', () => ({ + CLIENT_OPTIONS: { keepalive: 1000 }, +})); + vi.mock('../commands/session', () => ({ updateStatus: vi.fn(), })); @@ -25,7 +29,7 @@ import { StatusEnum } from 'types'; let MockWS: Mock; let mockInstance: ReturnType['mockInstance']; let restoreWebSocket: ReturnType['restore']; -let mockWebClient: any; +let mockConfig: any; beforeEach(() => { vi.useFakeTimers(); @@ -36,10 +40,8 @@ beforeEach(() => { mockInstance = installed.mockInstance; restoreWebSocket = installed.restore; - mockWebClient = { - status: StatusEnum.CONNECTED, - clientOptions: { keepalive: 1000 }, - keepAlive: vi.fn(), + mockConfig = { + keepAliveFn: vi.fn(), }; }); @@ -50,25 +52,25 @@ afterEach(() => { describe('WebSocketService', () => { function createConnectedService() { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); service.connect({ host: 'h', port: 1 } as any, 'ws'); return service; } function createTestConnectedService() { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); service.testConnect({ host: 'h', port: 1 } as any, 'ws'); return service; } describe('constructor', () => { it('subscribes disconnected$ from KeepAliveService', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); expect(service).toBeDefined(); }); it('calls disconnect and updateStatus when keepAlive disconnected$ fires', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); service.connect({ host: 'localhost', port: 8080 } as any, 'ws'); // trigger keepAliveService.disconnected$ (service as any).keepAliveService.disconnected$.next(); @@ -79,7 +81,7 @@ describe('WebSocketService', () => { describe('connect', () => { it('creates a WebSocket with wss protocol by default', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); Object.defineProperty(window, 'location', { value: { hostname: 'example.com' }, writable: true, @@ -90,7 +92,7 @@ describe('WebSocketService', () => { }); it('switches to ws protocol when hostname is localhost', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); Object.defineProperty(window, 'location', { value: { hostname: 'localhost' }, writable: true, @@ -127,22 +129,22 @@ describe('WebSocketService', () => { }); it('starts the ping loop with the keepalive interval', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); const startSpy = vi.spyOn((service as any).keepAliveService, 'startPingLoop'); service.connect({ host: 'h', port: 1 } as any, 'ws'); mockInstance.onopen(); expect(startSpy).toHaveBeenCalledWith(1000, expect.any(Function)); }); - it('ping loop callback calls webClient.keepAlive', () => { - const service = new WebSocketService(mockWebClient); + it('ping loop callback calls keepAliveFn', () => { + const service = new WebSocketService(mockConfig); const startSpy = vi.spyOn((service as any).keepAliveService, 'startPingLoop'); service.connect({ host: 'h', port: 1 } as any, 'ws'); mockInstance.onopen(); const pingCb = startSpy.mock.calls[0][1] as (done: Function) => void; const done = vi.fn(); pingCb(done); - expect(mockWebClient.keepAlive).toHaveBeenCalledWith(done); + expect(mockConfig.keepAliveFn).toHaveBeenCalledWith(done); }); }); @@ -155,13 +157,13 @@ describe('WebSocketService', () => { it('does not overwrite status if already DISCONNECTED', () => { createConnectedService(); - mockWebClient.status = StatusEnum.DISCONNECTED; + mockInstance.onerror(); mockInstance.onclose(); expect(updateStatus).not.toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Connection Closed'); }); it('ends the ping loop on close', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); const endSpy = vi.spyOn((service as any).keepAliveService, 'endPingLoop'); service.connect({ host: 'h', port: 1 } as any, 'ws'); mockInstance.onclose(); @@ -226,7 +228,7 @@ describe('WebSocketService', () => { }); it('returns false when socket is null', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); // no connect called, socket is undefined expect(service.checkReadyState(WebSocket.OPEN)).toBe(false); }); @@ -234,7 +236,7 @@ describe('WebSocketService', () => { describe('testConnect', () => { it('creates a test WebSocket with correct URL', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); Object.defineProperty(window, 'location', { value: { hostname: 'example.com' }, writable: true, @@ -245,7 +247,7 @@ describe('WebSocketService', () => { }); it('uses ws protocol on localhost', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); Object.defineProperty(window, 'location', { value: { hostname: 'localhost' }, writable: true, @@ -256,7 +258,7 @@ describe('WebSocketService', () => { }); it('closes previous testSocket when connecting again', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); service.testConnect({ host: 'h', port: 1 } as any, 'ws'); const firstInstance = mockInstance; // install second mock instance and restore after test diff --git a/webclient/src/websocket/services/WebSocketService.ts b/webclient/src/websocket/services/WebSocketService.ts index 4de880687..d0b09eab5 100644 --- a/webclient/src/websocket/services/WebSocketService.ts +++ b/webclient/src/websocket/services/WebSocketService.ts @@ -3,23 +3,28 @@ import { Subject } from 'rxjs'; import { StatusEnum, WebSocketConnectOptions } from 'types'; import { KeepAliveService } from './KeepAliveService'; -import { WebClient } from '../WebClient'; +import { CLIENT_OPTIONS } from '../config'; import { SessionPersistence } from '../persistence'; import { updateStatus } from '../commands/session'; +export interface WebSocketServiceConfig { + keepAliveFn: (pingReceived: () => void) => void; +} + export class WebSocketService { private socket: WebSocket; private testSocket: WebSocket; - private webClient: WebClient; + private config: WebSocketServiceConfig; private keepAliveService: KeepAliveService; + private errorFired = false; public message$: Subject = new Subject(); private keepalive: number; - constructor(webClient: WebClient) { - this.webClient = webClient; + constructor(config: WebSocketServiceConfig) { + this.config = config; this.keepAliveService = new KeepAliveService(this); this.keepAliveService.disconnected$.subscribe(() => { @@ -34,7 +39,7 @@ export class WebSocketService { } const { host, port } = options; - this.keepalive = this.webClient.clientOptions.keepalive; + this.keepalive = CLIENT_OPTIONS.keepalive; this.socket = this.createWebSocket(`${protocol}://${host}:${port}`); } @@ -71,23 +76,25 @@ export class WebSocketService { socket.onopen = () => { clearTimeout(connectionTimer); + this.errorFired = false; updateStatus(StatusEnum.CONNECTED, 'Connected'); this.keepAliveService.startPingLoop(this.keepalive, (pingReceived: () => void) => { - this.webClient.keepAlive(pingReceived); + this.config.keepAliveFn(pingReceived); }); }; socket.onclose = () => { // dont overwrite failure messages - if (this.webClient.status !== StatusEnum.DISCONNECTED) { + if (!this.errorFired) { updateStatus(StatusEnum.DISCONNECTED, 'Connection Closed'); } - + this.errorFired = false; this.keepAliveService.endPingLoop(); }; socket.onerror = () => { + this.errorFired = true; updateStatus(StatusEnum.DISCONNECTED, 'Connection Failed'); SessionPersistence.connectionFailed(); }; @@ -108,7 +115,7 @@ export class WebSocketService { const socket = new WebSocket(url); socket.binaryType = 'arraybuffer'; - const connectionTimer = setTimeout(() => socket.close(), this.webClient.clientOptions.keepalive); + const connectionTimer = setTimeout(() => socket.close(), CLIENT_OPTIONS.keepalive); socket.onopen = () => { clearTimeout(connectionTimer); diff --git a/webclient/src/websocket/services/command-options.spec.ts b/webclient/src/websocket/services/command-options.spec.ts new file mode 100644 index 000000000..8c520781c --- /dev/null +++ b/webclient/src/websocket/services/command-options.spec.ts @@ -0,0 +1,59 @@ +vi.mock('@bufbuild/protobuf', () => ({ + getExtension: vi.fn(), +})); + +vi.mock('generated/proto/response_pb', () => ({ + Response_ResponseCode: { RespOk: 1 }, +})); + +import { getExtension } from '@bufbuild/protobuf'; +import { handleResponse } from './command-options'; + +beforeEach(() => { + vi.resetAllMocks(); +}); + +describe('handleResponse', () => { + it('calls onResponse and returns early when provided', () => { + const onResponse = vi.fn(); + const onSuccess = vi.fn(); + handleResponse('test', { responseCode: 99 } as any, { onResponse, onSuccess }); + expect(onResponse).toHaveBeenCalled(); + expect(onSuccess).not.toHaveBeenCalled(); + }); + + it('calls onSuccess when responseCode is RespOk and no responseExt', () => { + const onSuccess = vi.fn(); + const raw = { responseCode: 1 } as any; + handleResponse('test', raw, { onSuccess }); + expect(onSuccess).toHaveBeenCalledWith(); + }); + + it('calls onSuccess with nested response when responseExt is set', () => { + vi.mocked(getExtension).mockReturnValue({ nested: true } as any); + const onSuccess = vi.fn(); + const fakeExt = {} as any; + const raw = { responseCode: 1 } as any; + handleResponse('test', raw, { onSuccess, responseExt: fakeExt }); + expect(onSuccess).toHaveBeenCalledWith({ nested: true }, raw); + }); + + it('calls onResponseCode handler when code matches', () => { + const specificHandler = vi.fn(); + handleResponse('test', { responseCode: 5 } as any, { onResponseCode: { 5: specificHandler } }); + expect(specificHandler).toHaveBeenCalled(); + }); + + it('calls onError when responseCode is not RespOk and no specific handler', () => { + const onError = vi.fn(); + handleResponse('test', { responseCode: 99 } as any, { onError }); + expect(onError).toHaveBeenCalledWith(99, { responseCode: 99 }); + }); + + it('logs error to console when no callbacks for non-RespOk response', () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + handleResponse('test.Type', { responseCode: 42 } as any, {}); + expect(consoleSpy).toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); +}); diff --git a/webclient/src/websocket/services/command-options.ts b/webclient/src/websocket/services/command-options.ts new file mode 100644 index 000000000..d9d3b0be8 --- /dev/null +++ b/webclient/src/websocket/services/command-options.ts @@ -0,0 +1,54 @@ +import { getExtension } from '@bufbuild/protobuf'; +import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; +import { Response_ResponseCode, type Response } from 'generated/proto/response_pb'; + +interface CommandOptionsBase { + onError?: (responseCode: number, raw: Response) => void; + onResponseCode?: { [code: number]: (raw: Response) => void }; + onResponse?: (raw: Response) => void; +} + +export interface CommandOptionsWithResponse extends CommandOptionsBase { + responseExt: GenExtension; + onSuccess?: (response: R, raw: Response) => void; +} + +export interface CommandOptionsWithoutResponse extends CommandOptionsBase { + responseExt?: undefined; + onSuccess?: () => void; +} + +export type CommandOptions = CommandOptionsWithResponse | CommandOptionsWithoutResponse; + +export function hasResponseExt(options: CommandOptions): options is CommandOptionsWithResponse { + return options.responseExt !== undefined; +} + +export function handleResponse(typeName: string, raw: Response, options: CommandOptions): void { + if (options.onResponse) { + options.onResponse(raw); + return; + } + + const { responseCode } = raw; + + if (responseCode === Response_ResponseCode.RespOk) { + if (hasResponseExt(options)) { + options.onSuccess?.(getExtension(raw, options.responseExt), raw); + } else { + options.onSuccess?.(); + } + return; + } + + if (options.onResponseCode?.[responseCode]) { + options.onResponseCode[responseCode](raw); + return; + } + + if (options.onError) { + options.onError(responseCode, raw); + } else { + console.error(`${typeName} failed with response code: ${responseCode}`); + } +} diff --git a/webclient/src/websocket/services/protobuf-types.ts b/webclient/src/websocket/services/protobuf-types.ts new file mode 100644 index 000000000..395442de7 --- /dev/null +++ b/webclient/src/websocket/services/protobuf-types.ts @@ -0,0 +1,44 @@ +import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; +import type { RoomEvent } from 'generated/proto/room_event_pb'; +import type { SessionEvent } from 'generated/proto/session_event_pb'; +import type { GameEvent } from 'generated/proto/game_event_pb'; +import type { GameEventMeta } from 'types'; + +type SessionRegistryEntry = [ + GenExtension, + (value: V) => void +]; +export type SessionExtensionRegistry = SessionRegistryEntry[]; + +type RoomRegistryEntry = [ + GenExtension, + (value: V, roomEvent: RoomEvent) => void +]; +export type RoomExtensionRegistry = RoomRegistryEntry[]; + +type GameRegistryEntry = [ + GenExtension, + (value: V, meta: GameEventMeta) => void +]; +export type GameExtensionRegistry = GameRegistryEntry[]; + +export function makeSessionEntry( + ext: GenExtension, + handler: (value: V) => void +): SessionRegistryEntry { + return [ext as GenExtension, handler as (value: unknown) => void]; +} + +export function makeRoomEntry( + ext: GenExtension, + handler: (value: V, roomEvent: RoomEvent) => void +): RoomRegistryEntry { + return [ext as GenExtension, handler as RoomRegistryEntry[1]]; +} + +export function makeGameEntry( + ext: GenExtension, + handler: (value: V, meta: GameEventMeta) => void +): GameRegistryEntry { + return [ext as GenExtension, handler as GameRegistryEntry[1]]; +}