mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-13 17:44:48 -07:00
websocket cleanup
This commit is contained in:
parent
2aeb1542b1
commit
2afa2922e9
18 changed files with 82 additions and 93 deletions
|
|
@ -15,6 +15,14 @@ export class ModeratorRequestImpl implements WebsocketTypes.IModeratorRequest {
|
||||||
ModeratorCommands.banFromServer(minutes, userName, address, reason, visibleReason, clientid, removeMessages);
|
ModeratorCommands.banFromServer(minutes, userName, address, reason, visibleReason, clientid, removeMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forceActivateUser(usernameToActivate: string, moderatorName: string): void {
|
||||||
|
ModeratorCommands.forceActivateUser(usernameToActivate, moderatorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAdminNotes(userName: string): void {
|
||||||
|
ModeratorCommands.getAdminNotes(userName);
|
||||||
|
}
|
||||||
|
|
||||||
getBanHistory(userName: string): void {
|
getBanHistory(userName: string): void {
|
||||||
ModeratorCommands.getBanHistory(userName);
|
ModeratorCommands.getBanHistory(userName);
|
||||||
}
|
}
|
||||||
|
|
@ -27,6 +35,14 @@ export class ModeratorRequestImpl implements WebsocketTypes.IModeratorRequest {
|
||||||
ModeratorCommands.getWarnList(modName, userName, userClientid);
|
ModeratorCommands.getWarnList(modName, userName, userClientid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
grantReplayAccess(replayId: number, moderatorName: string): void {
|
||||||
|
ModeratorCommands.grantReplayAccess(replayId, moderatorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAdminNotes(userName: string, notes: string): void {
|
||||||
|
ModeratorCommands.updateAdminNotes(userName, notes);
|
||||||
|
}
|
||||||
|
|
||||||
viewLogHistory(filters: Data.ViewLogHistoryParams): void {
|
viewLogHistory(filters: Data.ViewLogHistoryParams): void {
|
||||||
ModeratorCommands.viewLogHistory(filters);
|
ModeratorCommands.viewLogHistory(filters);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import { roomsSlice } from './rooms.reducer';
|
import { roomsSlice } from './rooms.reducer';
|
||||||
|
|
||||||
export const Actions = roomsSlice.actions;
|
const SignalActions = {
|
||||||
|
gameCreated: createAction<{ roomId: number }>('rooms/gameCreated'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Actions = { ...roomsSlice.actions, ...SignalActions };
|
||||||
|
|
||||||
export type RoomsAction = ReturnType<typeof Actions[keyof typeof Actions]>;
|
export type RoomsAction = ReturnType<typeof Actions[keyof typeof Actions]>;
|
||||||
|
|
|
||||||
|
|
@ -318,14 +318,6 @@ describe('REMOVE_MESSAGES', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('GAME_CREATED', () => {
|
|
||||||
it('returns state unchanged', () => {
|
|
||||||
const state = makeRoomsState();
|
|
||||||
const result = roomsReducer(state, Actions.gameCreated({ roomId: 1 }));
|
|
||||||
expect(result).toEqual(state);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe('JOINED_GAME', () => {
|
describe('JOINED_GAME', () => {
|
||||||
it('sets joinedGameIds[roomId][gameId] = true', () => {
|
it('sets joinedGameIds[roomId][gameId] = true', () => {
|
||||||
|
|
|
||||||
|
|
@ -190,9 +190,6 @@ export const roomsSlice = createSlice({
|
||||||
state.joinedGameIds[roomId][gameId] = true;
|
state.joinedGameIds[roomId][gameId] = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Signal-only; kept for discriminated-union exhaustiveness.
|
|
||||||
gameCreated: (_state, _action: PayloadAction<{ roomId: number }>) => {},
|
|
||||||
|
|
||||||
selectGame: (state, action: PayloadAction<{ roomId: number; gameId: number | undefined }>) => {
|
selectGame: (state, action: PayloadAction<{ roomId: number; gameId: number | undefined }>) => {
|
||||||
const { roomId, gameId } = action.payload;
|
const { roomId, gameId } = action.payload;
|
||||||
state.selectedGameIds[roomId] = gameId;
|
state.selectedGameIds[roomId] = gameId;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { roomsSlice } from './rooms.reducer';
|
import { Actions } from './rooms.actions';
|
||||||
|
|
||||||
const a = roomsSlice.actions;
|
const a = Actions;
|
||||||
|
|
||||||
export const Types = {
|
export const Types = {
|
||||||
CLEAR_STORE: a.clearStore.type,
|
CLEAR_STORE: a.clearStore.type,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,36 @@
|
||||||
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
import { WebsocketTypes } from '@app/websocket/types';
|
||||||
|
|
||||||
import { serverSlice } from './server.reducer';
|
import { serverSlice } from './server.reducer';
|
||||||
|
|
||||||
export const Actions = serverSlice.actions;
|
const SignalActions = {
|
||||||
|
accountAwaitingActivation: createAction<{ options: WebsocketTypes.PendingActivationContext }>('server/accountAwaitingActivation'),
|
||||||
|
accountActivationFailed: createAction('server/accountActivationFailed'),
|
||||||
|
accountActivationSuccess: createAction('server/accountActivationSuccess'),
|
||||||
|
loginSuccessful: createAction<{ options: WebsocketTypes.LoginSuccessContext }>('server/loginSuccessful'),
|
||||||
|
loginFailed: createAction('server/loginFailed'),
|
||||||
|
connectionFailed: createAction('server/connectionFailed'),
|
||||||
|
testConnectionSuccessful: createAction('server/testConnectionSuccessful'),
|
||||||
|
testConnectionFailed: createAction('server/testConnectionFailed'),
|
||||||
|
registrationRequiresEmail: createAction('server/registrationRequiresEmail'),
|
||||||
|
registrationSuccess: createAction('server/registrationSuccess'),
|
||||||
|
registrationEmailError: createAction<{ error: string }>('server/registrationEmailError'),
|
||||||
|
registrationPasswordError: createAction<{ error: string }>('server/registrationPasswordError'),
|
||||||
|
registrationUserNameError: createAction<{ error: string }>('server/registrationUserNameError'),
|
||||||
|
resetPassword: createAction('server/resetPassword'),
|
||||||
|
resetPasswordFailed: createAction('server/resetPasswordFailed'),
|
||||||
|
resetPasswordChallenge: createAction('server/resetPasswordChallenge'),
|
||||||
|
resetPasswordSuccess: createAction('server/resetPasswordSuccess'),
|
||||||
|
reloadConfig: createAction('server/reloadConfig'),
|
||||||
|
shutdownServer: createAction('server/shutdownServer'),
|
||||||
|
updateServerMessage: createAction('server/updateServerMessage'),
|
||||||
|
accountPasswordChange: createAction('server/accountPasswordChange'),
|
||||||
|
addToList: createAction<{ list: string; userName: string }>('server/addToList'),
|
||||||
|
removeFromList: createAction<{ list: string; userName: string }>('server/removeFromList'),
|
||||||
|
grantReplayAccess: createAction<{ replayId: number; moderatorName: string }>('server/grantReplayAccess'),
|
||||||
|
forceActivateUser: createAction<{ usernameToActivate: string; moderatorName: string }>('server/forceActivateUser'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Actions = { ...serverSlice.actions, ...SignalActions };
|
||||||
|
|
||||||
export type ServerAction = ReturnType<typeof Actions[keyof typeof Actions]>;
|
export type ServerAction = ReturnType<typeof Actions[keyof typeof Actions]>;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { serverReducer, MAX_USER_MESSAGES } from './server.reducer';
|
||||||
import { Actions } from './server.actions';
|
import { Actions } from './server.actions';
|
||||||
import {
|
import {
|
||||||
makeBanHistoryItem,
|
makeBanHistoryItem,
|
||||||
makePendingActivationContext,
|
|
||||||
makeDeckList,
|
makeDeckList,
|
||||||
makeDeckTreeItem,
|
makeDeckTreeItem,
|
||||||
makeGame,
|
makeGame,
|
||||||
|
|
@ -62,24 +61,6 @@ describe('Account & Connection', () => {
|
||||||
expect(result.status.connectionAttemptMade).toBe(true);
|
expect(result.status.connectionAttemptMade).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ACCOUNT_AWAITING_ACTIVATION → returns state unchanged', () => {
|
|
||||||
const options = makePendingActivationContext();
|
|
||||||
const state = makeServerState();
|
|
||||||
const result = serverReducer(state, Actions.accountAwaitingActivation({ options }));
|
|
||||||
expect(result).toEqual(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('ACCOUNT_ACTIVATION_SUCCESS → returns state unchanged', () => {
|
|
||||||
const state = makeServerState();
|
|
||||||
const result = serverReducer(state, Actions.accountActivationSuccess());
|
|
||||||
expect(result).toEqual(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('ACCOUNT_ACTIVATION_FAILED → returns state unchanged', () => {
|
|
||||||
const state = makeServerState();
|
|
||||||
const result = serverReducer(state, Actions.accountActivationFailed());
|
|
||||||
expect(result).toEqual(state);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ export const serverSlice = createSlice({
|
||||||
|
|
||||||
updateUser: (state, action: PayloadAction<{ user: Partial<Data.ServerInfo_User> }>) => {
|
updateUser: (state, action: PayloadAction<{ user: Partial<Data.ServerInfo_User> }>) => {
|
||||||
if (state.user) {
|
if (state.user) {
|
||||||
Object.assign(state.user, action.payload.user);
|
state.user = create(Data.ServerInfo_UserSchema, { ...state.user, ...action.payload.user });
|
||||||
} else {
|
} else {
|
||||||
state.user = action.payload.user as Data.ServerInfo_User;
|
state.user = action.payload.user as Data.ServerInfo_User;
|
||||||
}
|
}
|
||||||
|
|
@ -393,42 +393,15 @@ export const serverSlice = createSlice({
|
||||||
|
|
||||||
accountEditChanged: (state, action: PayloadAction<{ user: Partial<Data.ServerInfo_User> }>) => {
|
accountEditChanged: (state, action: PayloadAction<{ user: Partial<Data.ServerInfo_User> }>) => {
|
||||||
if (state.user) {
|
if (state.user) {
|
||||||
Object.assign(state.user, action.payload.user);
|
state.user = create(Data.ServerInfo_UserSchema, { ...state.user, ...action.payload.user });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
accountImageChanged: (state, action: PayloadAction<{ user: Partial<Data.ServerInfo_User> }>) => {
|
accountImageChanged: (state, action: PayloadAction<{ user: Partial<Data.ServerInfo_User> }>) => {
|
||||||
if (state.user) {
|
if (state.user) {
|
||||||
Object.assign(state.user, action.payload.user);
|
state.user = create(Data.ServerInfo_UserSchema, { ...state.user, ...action.payload.user });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Signal-only action types — no state mutation, defined so type strings are generated
|
|
||||||
accountAwaitingActivation: (_state, _action: PayloadAction<{ options: WebsocketTypes.PendingActivationContext }>) => {},
|
|
||||||
accountActivationFailed: (_state) => {},
|
|
||||||
accountActivationSuccess: (_state) => {},
|
|
||||||
loginSuccessful: (_state, _action: PayloadAction<{ options: WebsocketTypes.LoginSuccessContext }>) => {},
|
|
||||||
loginFailed: (_state) => {},
|
|
||||||
connectionFailed: (_state) => {},
|
|
||||||
testConnectionSuccessful: (_state) => {},
|
|
||||||
testConnectionFailed: (_state) => {},
|
|
||||||
registrationRequiresEmail: (_state) => {},
|
|
||||||
registrationSuccess: (_state) => {},
|
|
||||||
registrationEmailError: (_state, _action: PayloadAction<{ error: string }>) => {},
|
|
||||||
registrationPasswordError: (_state, _action: PayloadAction<{ error: string }>) => {},
|
|
||||||
registrationUserNameError: (_state, _action: PayloadAction<{ error: string }>) => {},
|
|
||||||
resetPassword: (_state) => {},
|
|
||||||
resetPasswordFailed: (_state) => {},
|
|
||||||
resetPasswordChallenge: (_state) => {},
|
|
||||||
resetPasswordSuccess: (_state) => {},
|
|
||||||
reloadConfig: (_state) => {},
|
|
||||||
shutdownServer: (_state) => {},
|
|
||||||
updateServerMessage: (_state) => {},
|
|
||||||
accountPasswordChange: (_state) => {},
|
|
||||||
addToList: (_state, _action: PayloadAction<{ list: string; userName: string }>) => {},
|
|
||||||
removeFromList: (_state, _action: PayloadAction<{ list: string; userName: string }>) => {},
|
|
||||||
grantReplayAccess: (_state, _action: PayloadAction<{ replayId: number; moderatorName: string }>) => {},
|
|
||||||
forceActivateUser: (_state, _action: PayloadAction<{ usernameToActivate: string; moderatorName: string }>) => {},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { serverSlice } from './server.reducer';
|
import { Actions } from './server.actions';
|
||||||
|
|
||||||
const a = serverSlice.actions;
|
const a = Actions;
|
||||||
|
|
||||||
export const Types = {
|
export const Types = {
|
||||||
INITIALIZED: a.initialized.type,
|
INITIALIZED: a.initialized.type,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ const ERROR_MESSAGES: Record<number, string> = {
|
||||||
[Response_ResponseCode.RespGameFull]: 'The game is already full.',
|
[Response_ResponseCode.RespGameFull]: 'The game is already full.',
|
||||||
[Response_ResponseCode.RespWrongPassword]: 'Wrong password.',
|
[Response_ResponseCode.RespWrongPassword]: 'Wrong password.',
|
||||||
[Response_ResponseCode.RespSpectatorsNotAllowed]: 'Spectators are not allowed in this game.',
|
[Response_ResponseCode.RespSpectatorsNotAllowed]: 'Spectators are not allowed in this game.',
|
||||||
[Response_ResponseCode.RespOnlyBuddies]: "This game is only open to its creator's buddies.",
|
[Response_ResponseCode.RespOnlyBuddies]: 'This game is only open to its creator\'s buddies.',
|
||||||
[Response_ResponseCode.RespUserLevelTooLow]: 'This game is only open to registered users.',
|
[Response_ResponseCode.RespUserLevelTooLow]: 'This game is only open to registered users.',
|
||||||
[Response_ResponseCode.RespInIgnoreList]: 'You are being ignored by the creator of this game.',
|
[Response_ResponseCode.RespInIgnoreList]: 'You are being ignored by the creator of this game.',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ describe('joinGame', () => {
|
||||||
[Response_ResponseCode.RespGameFull, 'The game is already full.'],
|
[Response_ResponseCode.RespGameFull, 'The game is already full.'],
|
||||||
[Response_ResponseCode.RespWrongPassword, 'Wrong password.'],
|
[Response_ResponseCode.RespWrongPassword, 'Wrong password.'],
|
||||||
[Response_ResponseCode.RespSpectatorsNotAllowed, 'Spectators are not allowed in this game.'],
|
[Response_ResponseCode.RespSpectatorsNotAllowed, 'Spectators are not allowed in this game.'],
|
||||||
[Response_ResponseCode.RespOnlyBuddies, "This game is only open to its creator's buddies."],
|
[Response_ResponseCode.RespOnlyBuddies, 'This game is only open to its creator\'s buddies.'],
|
||||||
[Response_ResponseCode.RespUserLevelTooLow, 'This game is only open to registered users.'],
|
[Response_ResponseCode.RespUserLevelTooLow, 'This game is only open to registered users.'],
|
||||||
[Response_ResponseCode.RespInIgnoreList, 'You are being ignored by the creator of this game.'],
|
[Response_ResponseCode.RespInIgnoreList, 'You are being ignored by the creator of this game.'],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import { WebClient } from '../../WebClient';
|
||||||
import { Command_Ping_ext, Command_PingSchema } from '@app/generated';
|
import { Command_Ping_ext, Command_PingSchema } from '@app/generated';
|
||||||
|
|
||||||
export function ping(pingReceived: () => void): void {
|
export function ping(pingReceived: () => void): void {
|
||||||
|
// Uses `onResponse` (not `onSuccess`) so KeepAliveService treats any server
|
||||||
|
// reply as proof of life, independent of responseCode.
|
||||||
WebClient.instance.protobuf.sendSessionCommand(Command_Ping_ext, create(Command_PingSchema), {
|
WebClient.instance.protobuf.sendSessionCommand(Command_Ping_ext, create(Command_PingSchema), {
|
||||||
onResponse: () => pingReceived(),
|
onResponse: () => pingReceived(),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,15 @@ import { Command_ReplaySubmitCode_ext, Command_ReplaySubmitCodeSchema } from '@a
|
||||||
|
|
||||||
export function replaySubmitCode(
|
export function replaySubmitCode(
|
||||||
replayCode: string,
|
replayCode: string,
|
||||||
onSuccess?: () => void,
|
onSubmitted?: () => void,
|
||||||
onError?: (responseCode: number) => void,
|
onFailure?: (responseCode: number) => void,
|
||||||
): void {
|
): void {
|
||||||
WebClient.instance.protobuf.sendSessionCommand(
|
WebClient.instance.protobuf.sendSessionCommand(
|
||||||
Command_ReplaySubmitCode_ext,
|
Command_ReplaySubmitCode_ext,
|
||||||
create(Command_ReplaySubmitCodeSchema, { replayCode }),
|
create(Command_ReplaySubmitCodeSchema, { replayCode }),
|
||||||
{
|
{
|
||||||
onSuccess,
|
onSuccess: onSubmitted,
|
||||||
onError,
|
onError: onFailure,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -468,18 +468,18 @@ describe('replaySubmitCode', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('forwards onSuccess callback', () => {
|
it('forwards onSubmitted callback', () => {
|
||||||
const onSuccess = vi.fn();
|
const onSubmitted = vi.fn();
|
||||||
replaySubmitCode('42-abc123', onSuccess);
|
replaySubmitCode('42-abc123', onSubmitted);
|
||||||
invokeOnSuccess();
|
invokeOnSuccess();
|
||||||
expect(onSuccess).toHaveBeenCalled();
|
expect(onSubmitted).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('forwards onError callback', () => {
|
it('forwards onFailure callback', () => {
|
||||||
const onError = vi.fn();
|
const onFailure = vi.fn();
|
||||||
replaySubmitCode('42-abc123', undefined, onError);
|
replaySubmitCode('42-abc123', undefined, onFailure);
|
||||||
invokeCallback('onError', 404);
|
invokeCallback('onError', 404);
|
||||||
expect(onError).toHaveBeenCalledWith(404);
|
expect(onFailure).toHaveBeenCalledWith(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import { CommonEvents } from './index';
|
|
||||||
|
|
||||||
describe('CommonEvents', () => {
|
|
||||||
it('is an empty event map (all common events were moved to game/session events)', () => {
|
|
||||||
expect(CommonEvents).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
import type { SessionExtensionRegistry } from '../session';
|
|
||||||
|
|
||||||
export const CommonEvents: SessionExtensionRegistry = [];
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
// Event_PlayerPropertiesChanged is handled as a game event in websocket/events/game/playerPropertiesChanged.ts
|
|
||||||
// This file is retained for reference but is no longer registered in CommonEvents.
|
|
||||||
export {};
|
|
||||||
|
|
@ -107,9 +107,13 @@ export interface IModeratorRequest {
|
||||||
clientid?: string,
|
clientid?: string,
|
||||||
removeMessages?: number
|
removeMessages?: number
|
||||||
): void;
|
): void;
|
||||||
|
forceActivateUser(usernameToActivate: string, moderatorName: string): void;
|
||||||
|
getAdminNotes(userName: string): void;
|
||||||
getBanHistory(userName: string): void;
|
getBanHistory(userName: string): void;
|
||||||
getWarnHistory(userName: string): void;
|
getWarnHistory(userName: string): void;
|
||||||
getWarnList(modName: string, userName: string, userClientid: string): void;
|
getWarnList(modName: string, userName: string, userClientid: string): void;
|
||||||
|
grantReplayAccess(replayId: number, moderatorName: string): void;
|
||||||
|
updateAdminNotes(userName: string, notes: string): void;
|
||||||
viewLogHistory(filters: ViewLogHistoryParams): void;
|
viewLogHistory(filters: ViewLogHistoryParams): void;
|
||||||
warnUser(userName: string, reason: string, clientid?: string, removeMessages?: number): void;
|
warnUser(userName: string, reason: string, clientid?: string, removeMessages?: number): void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue