mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-01 11:03:54 -07:00
fix websocket refactor mess
This commit is contained in:
parent
fea21b5057
commit
53639a8448
19 changed files with 179 additions and 209 deletions
|
|
@ -23,16 +23,22 @@ const rules = [
|
||||||
|
|
||||||
{ from: { type: 'websocket' }, allow: types('generated') },
|
{ from: { type: 'websocket' }, allow: types('generated') },
|
||||||
{ from: { type: 'store' }, allow: types('types') },
|
{ from: { type: 'store' }, allow: types('types') },
|
||||||
{ from: { type: 'api' }, allow: types('types', 'store', 'websocket') },
|
{ from: { type: 'api' }, allow: types('store', 'types', 'websocket') },
|
||||||
|
|
||||||
{ from: { type: 'hooks' }, allow: types('services', 'types') },
|
{ from: { type: 'hooks' }, allow: types('api', 'services', 'types', 'websocket') },
|
||||||
{ from: { type: 'images' }, allow: types('types') },
|
{ from: { type: 'images' }, allow: types('types') },
|
||||||
{ from: { type: 'services' }, allow: types('api', 'store', 'types') },
|
{ from: { type: 'services' }, allow: types('api', 'store', 'types') },
|
||||||
|
|
||||||
{ from: { type: 'components' }, allow: types('api', 'dialogs', 'forms', 'hooks', 'images', 'services', 'types', 'store') },
|
{
|
||||||
{ from: { type: 'containers' }, allow: types('api', 'components', 'dialogs', 'forms', 'hooks', 'images', 'services', 'types', 'store') },
|
from: { type: 'components' },
|
||||||
{ from: { type: 'dialogs' }, allow: types('components', 'forms', 'hooks', 'services', 'types', 'store') },
|
allow: types('api', 'dialogs', 'forms', 'hooks', 'images', 'services', 'store', 'types')
|
||||||
{ from: { type: 'forms' }, allow: types('components', 'hooks', 'types', 'services', 'store') },
|
},
|
||||||
|
{
|
||||||
|
from: { type: 'containers' },
|
||||||
|
allow: types('api', 'components', 'dialogs', 'forms', 'hooks', 'images', 'services', 'store', 'types')
|
||||||
|
},
|
||||||
|
{ from: { type: 'dialogs' }, allow: types('components', 'forms', 'hooks', 'services', 'store', 'types') },
|
||||||
|
{ from: { type: 'forms' }, allow: types('components', 'hooks', 'services', 'store', 'types') },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const boundariesConfig = [
|
export const boundariesConfig = [
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,2 @@
|
||||||
export { initWebClient } from './initWebClient';
|
|
||||||
export { createWebClientResponse } from './response';
|
export { createWebClientResponse } from './response';
|
||||||
export { createWebClientRequest } from './request';
|
export { createWebClientRequest } from './request';
|
||||||
|
|
||||||
import { createWebClientRequest } from './request';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UI-facing request surface. The request implementations are created once
|
|
||||||
* at module load. They access `WebClient.instance` at call time (via lazy
|
|
||||||
* internal references), so the singleton only needs to exist by the first
|
|
||||||
* actual command send.
|
|
||||||
*/
|
|
||||||
export const request = createWebClientRequest();
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import {
|
|
||||||
WebClient,
|
|
||||||
SessionEvents,
|
|
||||||
RoomEvents,
|
|
||||||
GameEvents,
|
|
||||||
SessionCommands,
|
|
||||||
} from '@app/websocket';
|
|
||||||
import type { WebClientConfig } from '@app/websocket';
|
|
||||||
|
|
||||||
import { createWebClientResponse } from './response';
|
|
||||||
|
|
||||||
export function initWebClient(): void {
|
|
||||||
const response = createWebClientResponse();
|
|
||||||
|
|
||||||
const config: WebClientConfig = {
|
|
||||||
response,
|
|
||||||
sessionEvents: SessionEvents,
|
|
||||||
roomEvents: RoomEvents,
|
|
||||||
gameEvents: GameEvents,
|
|
||||||
keepAliveFn: (cb) => SessionCommands.ping(cb),
|
|
||||||
};
|
|
||||||
|
|
||||||
new WebClient(config);
|
|
||||||
}
|
|
||||||
|
|
@ -13,7 +13,7 @@ import AddIcon from '@mui/icons-material/Add';
|
||||||
import EditRoundedIcon from '@mui/icons-material/Edit';
|
import EditRoundedIcon from '@mui/icons-material/Edit';
|
||||||
import ErrorOutlinedIcon from '@mui/icons-material/ErrorOutlined';
|
import ErrorOutlinedIcon from '@mui/icons-material/ErrorOutlined';
|
||||||
|
|
||||||
import { request } from '@app/api';
|
import { useWebClient } from '@app/hooks';
|
||||||
import { KnownHostDialog } from '@app/dialogs';
|
import { KnownHostDialog } from '@app/dialogs';
|
||||||
import { useReduxEffect } from '@app/hooks';
|
import { useReduxEffect } from '@app/hooks';
|
||||||
import { HostDTO } from '@app/services';
|
import { HostDTO } from '@app/services';
|
||||||
|
|
@ -64,6 +64,7 @@ const KnownHosts = (props) => {
|
||||||
const { touched, error, warning } = meta;
|
const { touched, error, warning } = meta;
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const webClient = useWebClient();
|
||||||
|
|
||||||
const [hostsState, setHostsState] = useState({
|
const [hostsState, setHostsState] = useState({
|
||||||
hosts: [],
|
hosts: [],
|
||||||
|
|
@ -197,7 +198,7 @@ const KnownHosts = (props) => {
|
||||||
setTestingConnection(TestConnection.TESTING);
|
setTestingConnection(TestConnection.TESTING);
|
||||||
|
|
||||||
const options = { ...App.getHostPort(hostsState.selectedHost) };
|
const options = { ...App.getHostPort(hostsState.selectedHost) };
|
||||||
request.authentication.testConnection(options);
|
webClient.request.authentication.testConnection(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import Menu from '@mui/material/Menu';
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
|
||||||
import { Images } from '@app/images';
|
import { Images } from '@app/images';
|
||||||
import { request } from '@app/api';
|
import { useWebClient } from '@app/hooks';
|
||||||
import { ServerSelectors } from '@app/store';
|
import { ServerSelectors } from '@app/store';
|
||||||
import { App, Data } from '@app/types';
|
import { App, Data } from '@app/types';
|
||||||
import { useAppSelector } from '@app/store';
|
import { useAppSelector } from '@app/store';
|
||||||
|
|
@ -18,6 +18,7 @@ const UserDisplay = ({ user }: UserDisplayProps) => {
|
||||||
const buddyList = useAppSelector(state => ServerSelectors.getBuddyList(state));
|
const buddyList = useAppSelector(state => ServerSelectors.getBuddyList(state));
|
||||||
const ignoreList = useAppSelector(state => ServerSelectors.getIgnoreList(state));
|
const ignoreList = useAppSelector(state => ServerSelectors.getIgnoreList(state));
|
||||||
const [position, setPosition] = useState<{ x: number; y: number } | null>(null);
|
const [position, setPosition] = useState<{ x: number; y: number } | null>(null);
|
||||||
|
const webClient = useWebClient();
|
||||||
|
|
||||||
const { name, country } = user;
|
const { name, country } = user;
|
||||||
|
|
||||||
|
|
@ -32,19 +33,19 @@ const UserDisplay = ({ user }: UserDisplayProps) => {
|
||||||
const isIgnored = Boolean(ignoreList[user.name]);
|
const isIgnored = Boolean(ignoreList[user.name]);
|
||||||
|
|
||||||
const onAddBuddy = () => {
|
const onAddBuddy = () => {
|
||||||
request.session.addToBuddyList(user.name);
|
webClient.request.session.addToBuddyList(user.name);
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
const onRemoveBuddy = () => {
|
const onRemoveBuddy = () => {
|
||||||
request.session.removeFromBuddyList(user.name);
|
webClient.request.session.removeFromBuddyList(user.name);
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
const onAddIgnore = () => {
|
const onAddIgnore = () => {
|
||||||
request.session.addToIgnoreList(user.name);
|
webClient.request.session.addToIgnoreList(user.name);
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
const onRemoveIgnore = () => {
|
const onRemoveIgnore = () => {
|
||||||
request.session.removeFromIgnoreList(user.name);
|
webClient.request.session.removeFromIgnoreList(user.name);
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import ListItemButton from '@mui/material/ListItemButton';
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from '@mui/material/Paper';
|
||||||
|
|
||||||
import { UserDisplay, VirtualList, AuthGuard, LanguageDropdown } from '@app/components';
|
import { UserDisplay, VirtualList, AuthGuard, LanguageDropdown } from '@app/components';
|
||||||
import { request } from '@app/api';
|
import { useWebClient } from '@app/hooks';
|
||||||
import { ServerSelectors } from '@app/store';
|
import { ServerSelectors } from '@app/store';
|
||||||
import Layout from '../Layout/Layout';
|
import Layout from '../Layout/Layout';
|
||||||
import { useAppSelector } from '@app/store';
|
import { useAppSelector } from '@app/store';
|
||||||
|
|
@ -23,17 +23,18 @@ const Account = () => {
|
||||||
const serverName = useAppSelector(state => ServerSelectors.getName(state));
|
const serverName = useAppSelector(state => ServerSelectors.getName(state));
|
||||||
const serverVersion = useAppSelector(state => ServerSelectors.getVersion(state));
|
const serverVersion = useAppSelector(state => ServerSelectors.getVersion(state));
|
||||||
const user = useAppSelector(state => ServerSelectors.getUser(state));
|
const user = useAppSelector(state => ServerSelectors.getUser(state));
|
||||||
|
const webClient = useWebClient();
|
||||||
const { country, realName, name, userLevel, accountageSecs, avatarBmp } = user || {};
|
const { country, realName, name, userLevel, accountageSecs, avatarBmp } = user || {};
|
||||||
let url = URL.createObjectURL(new Blob([avatarBmp as BlobPart], { 'type': 'image/png' }));
|
let url = URL.createObjectURL(new Blob([avatarBmp as BlobPart], { 'type': 'image/png' }));
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleAddToBuddies = ({ userName }) => {
|
const handleAddToBuddies = ({ userName }) => {
|
||||||
request.session.addToBuddyList(userName);
|
webClient.request.session.addToBuddyList(userName);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddToIgnore = ({ userName }) => {
|
const handleAddToIgnore = ({ userName }) => {
|
||||||
request.session.addToIgnoreList(userName);
|
webClient.request.session.addToIgnoreList(userName);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ import CloseIcon from '@mui/icons-material/Close';
|
||||||
import MailOutlineRoundedIcon from '@mui/icons-material/MailOutlineRounded';
|
import MailOutlineRoundedIcon from '@mui/icons-material/MailOutlineRounded';
|
||||||
import MenuRoundedIcon from '@mui/icons-material/MenuRounded';
|
import MenuRoundedIcon from '@mui/icons-material/MenuRounded';
|
||||||
|
|
||||||
import { request } from '@app/api';
|
|
||||||
import { CardImportDialog } from '@app/dialogs';
|
import { CardImportDialog } from '@app/dialogs';
|
||||||
|
import { useWebClient } from '@app/hooks';
|
||||||
import { Images } from '@app/images';
|
import { Images } from '@app/images';
|
||||||
import { RoomsSelectors, ServerSelectors } from '@app/store';
|
import { RoomsSelectors, ServerSelectors } from '@app/store';
|
||||||
import { App } from '@app/types';
|
import { App } from '@app/types';
|
||||||
|
|
@ -28,6 +28,7 @@ const LeftNav = () => {
|
||||||
const isConnected = useAppSelector(ServerSelectors.getIsConnected);
|
const isConnected = useAppSelector(ServerSelectors.getIsConnected);
|
||||||
const isModerator = useAppSelector(ServerSelectors.getIsUserModerator);
|
const isModerator = useAppSelector(ServerSelectors.getIsUserModerator);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const webClient = useWebClient();
|
||||||
const [state, setState] = useState<LeftNavState>({
|
const [state, setState] = useState<LeftNavState>({
|
||||||
anchorEl: null,
|
anchorEl: null,
|
||||||
showCardImportDialog: false,
|
showCardImportDialog: false,
|
||||||
|
|
@ -66,7 +67,7 @@ const LeftNav = () => {
|
||||||
|
|
||||||
const leaveRoom = (event, roomId) => {
|
const leaveRoom = (event, roomId) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
request.rooms.leaveRoom(roomId);
|
webClient.request.rooms.leaveRoom(roomId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openImportCardWizard = () => {
|
const openImportCardWizard = () => {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,10 @@ import Button from '@mui/material/Button';
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from '@mui/material/Paper';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
import { request } from '@app/api';
|
|
||||||
import { RegistrationDialog, RequestPasswordResetDialog, ResetPasswordDialog, AccountActivationDialog } from '@app/dialogs';
|
import { RegistrationDialog, RequestPasswordResetDialog, ResetPasswordDialog, AccountActivationDialog } from '@app/dialogs';
|
||||||
import { LanguageDropdown } from '@app/components';
|
import { LanguageDropdown } from '@app/components';
|
||||||
import { LoginForm } from '@app/forms';
|
import { LoginForm } from '@app/forms';
|
||||||
import { useReduxEffect, useFireOnce } from '@app/hooks';
|
import { useReduxEffect, useFireOnce, useWebClient } from '@app/hooks';
|
||||||
import { Images } from '@app/images';
|
import { Images } from '@app/images';
|
||||||
import { HostDTO, serverProps } from '@app/services';
|
import { HostDTO, serverProps } from '@app/services';
|
||||||
import { App, Enriched } from '@app/types';
|
import { App, Enriched } from '@app/types';
|
||||||
|
|
@ -67,6 +66,7 @@ const Root = styled('div')(({ theme }) => ({
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
const description = useAppSelector(s => ServerSelectors.getDescription(s));
|
const description = useAppSelector(s => ServerSelectors.getDescription(s));
|
||||||
const isConnected = useAppSelector(ServerSelectors.getIsConnected);
|
const isConnected = useAppSelector(ServerSelectors.getIsConnected);
|
||||||
|
const webClient = useWebClient();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [pendingActivationOptions, setPendingActivationOptions] = useState<Enriched.PendingActivationContext | null>(null);
|
const [pendingActivationOptions, setPendingActivationOptions] = useState<Enriched.PendingActivationContext | null>(null);
|
||||||
|
|
@ -134,7 +134,7 @@ const Login = () => {
|
||||||
options.hashedPassword = selectedHost.hashedPassword;
|
options.hashedPassword = selectedHost.hashedPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.authentication.login(options);
|
webClient.request.authentication.login(options);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [submitButtonDisabled, resetSubmitButton, handleLogin] = useFireOnce(onSubmitLogin);
|
const [submitButtonDisabled, resetSubmitButton, handleLogin] = useFireOnce(onSubmitLogin);
|
||||||
|
|
@ -153,7 +153,7 @@ const Login = () => {
|
||||||
setRememberLogin(registerForm);
|
setRememberLogin(registerForm);
|
||||||
const { userName, password, email, country, realName, selectedHost } = registerForm;
|
const { userName, password, email, country, realName, selectedHost } = registerForm;
|
||||||
|
|
||||||
request.authentication.register({
|
webClient.request.authentication.register({
|
||||||
...App.getHostPort(selectedHost),
|
...App.getHostPort(selectedHost),
|
||||||
userName,
|
userName,
|
||||||
password,
|
password,
|
||||||
|
|
@ -167,7 +167,7 @@ const Login = () => {
|
||||||
if (!pendingActivationOptions) {
|
if (!pendingActivationOptions) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
request.authentication.activateAccount({
|
webClient.request.authentication.activateAccount({
|
||||||
host: pendingActivationOptions.host,
|
host: pendingActivationOptions.host,
|
||||||
port: pendingActivationOptions.port,
|
port: pendingActivationOptions.port,
|
||||||
userName: pendingActivationOptions.userName,
|
userName: pendingActivationOptions.userName,
|
||||||
|
|
@ -180,17 +180,17 @@ const Login = () => {
|
||||||
const { host, port } = App.getHostPort(selectedHost);
|
const { host, port } = App.getHostPort(selectedHost);
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
request.authentication.resetPasswordChallenge({ userName, email, host, port });
|
webClient.request.authentication.resetPasswordChallenge({ userName, email, host, port });
|
||||||
} else {
|
} else {
|
||||||
setUserToResetPassword(userName);
|
setUserToResetPassword(userName);
|
||||||
request.authentication.resetPasswordRequest({ userName, host, port });
|
webClient.request.authentication.resetPasswordRequest({ userName, host, port });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResetPasswordDialogSubmit = ({ userName, token, newPassword, selectedHost }) => {
|
const handleResetPasswordDialogSubmit = ({ userName, token, newPassword, selectedHost }) => {
|
||||||
const { host, port } = App.getHostPort(selectedHost);
|
const { host, port } = App.getHostPort(selectedHost);
|
||||||
|
|
||||||
request.authentication.resetPassword({ userName, token, newPassword, host, port });
|
webClient.request.authentication.resetPassword({ userName, token, newPassword, host, port });
|
||||||
};
|
};
|
||||||
|
|
||||||
const skipTokenRequest = (userName) => {
|
const skipTokenRequest = (userName) => {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
import { request } from '@app/api';
|
|
||||||
import { AuthGuard, ModGuard } from '@app/components';
|
import { AuthGuard, ModGuard } from '@app/components';
|
||||||
import { SearchForm } from '@app/forms';
|
import { SearchForm } from '@app/forms';
|
||||||
|
import { useWebClient } from '@app/hooks';
|
||||||
import { ServerDispatch, ServerSelectors } from '@app/store';
|
import { ServerDispatch, ServerSelectors } from '@app/store';
|
||||||
import { Data } from '@app/types';
|
import { Data } from '@app/types';
|
||||||
import { useAppSelector } from '@app/store';
|
import { useAppSelector } from '@app/store';
|
||||||
|
|
@ -13,6 +13,7 @@ import './Logs.css';
|
||||||
|
|
||||||
const Logs = () => {
|
const Logs = () => {
|
||||||
const logs = useAppSelector(state => ServerSelectors.getLogs(state));
|
const logs = useAppSelector(state => ServerSelectors.getLogs(state));
|
||||||
|
const webClient = useWebClient();
|
||||||
const MAXIMUM_RESULTS = 1000;
|
const MAXIMUM_RESULTS = 1000;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -51,7 +52,7 @@ const Logs = () => {
|
||||||
trimmedFields.maximumResults = MAXIMUM_RESULTS;
|
trimmedFields.maximumResults = MAXIMUM_RESULTS;
|
||||||
|
|
||||||
if (required.length) {
|
if (required.length) {
|
||||||
request.moderator.viewLogHistory(trimmedFields);
|
webClient.request.moderator.viewLogHistory(trimmedFields);
|
||||||
} else {
|
} else {
|
||||||
// @TODO use yet-to-be-implemented banner/alert
|
// @TODO use yet-to-be-implemented banner/alert
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import { useNavigate, useParams, generatePath } from 'react-router-dom';
|
||||||
import ListItemButton from '@mui/material/ListItemButton';
|
import ListItemButton from '@mui/material/ListItemButton';
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from '@mui/material/Paper';
|
||||||
|
|
||||||
import { request } from '@app/api';
|
|
||||||
import { ScrollToBottomOnChanges, ThreePaneLayout, UserDisplay, VirtualList, AuthGuard } from '@app/components';
|
import { ScrollToBottomOnChanges, ThreePaneLayout, UserDisplay, VirtualList, AuthGuard } from '@app/components';
|
||||||
|
import { useWebClient } from '@app/hooks';
|
||||||
import { RoomsSelectors } from '@app/store';
|
import { RoomsSelectors } from '@app/store';
|
||||||
import { useAppSelector } from '@app/store';
|
import { useAppSelector } from '@app/store';
|
||||||
import { App } from '@app/types';
|
import { App } from '@app/types';
|
||||||
|
|
@ -29,6 +29,7 @@ const Room = () => {
|
||||||
const room = rooms[roomId];
|
const room = rooms[roomId];
|
||||||
const roomMessages = messages[roomId];
|
const roomMessages = messages[roomId];
|
||||||
const users = useAppSelector(state => RoomsSelectors.getSortedRoomUsers(state, roomId));
|
const users = useAppSelector(state => RoomsSelectors.getSortedRoomUsers(state, roomId));
|
||||||
|
const webClient = useWebClient();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!joined.find(r => r.info.roomId === roomId)) {
|
if (!joined.find(r => r.info.roomId === roomId)) {
|
||||||
|
|
@ -38,7 +39,7 @@ const Room = () => {
|
||||||
|
|
||||||
const handleRoomSay = ({ message }) => {
|
const handleRoomSay = ({ message }) => {
|
||||||
if (message) {
|
if (message) {
|
||||||
request.rooms.roomSay(roomId, message);
|
webClient.request.rooms.roomSay(roomId, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,20 @@ import TableCell from '@mui/material/TableCell';
|
||||||
import TableHead from '@mui/material/TableHead';
|
import TableHead from '@mui/material/TableHead';
|
||||||
import TableRow from '@mui/material/TableRow';
|
import TableRow from '@mui/material/TableRow';
|
||||||
|
|
||||||
|
import { useWebClient } from '@app/hooks';
|
||||||
import { request } from '@app/api';
|
|
||||||
import { App } from '@app/types';
|
import { App } from '@app/types';
|
||||||
|
|
||||||
import './Rooms.css';
|
import './Rooms.css';
|
||||||
|
|
||||||
const Rooms = ({ rooms, joinedRooms }) => {
|
const Rooms = ({ rooms, joinedRooms }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const webClient = useWebClient();
|
||||||
|
|
||||||
function onClick(roomId) {
|
function onClick(roomId) {
|
||||||
if (joinedRooms.find(room => room.info.roomId === roomId)) {
|
if (joinedRooms.find(room => room.info.roomId === roomId)) {
|
||||||
navigate(generatePath(App.RouteEnum.ROOM, { roomId }));
|
navigate(generatePath(App.RouteEnum.ROOM, { roomId }));
|
||||||
} else {
|
} else {
|
||||||
request.rooms.joinRoom(roomId);
|
webClient.request.rooms.joinRoom(roomId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,4 @@ export * from './useFireOnce';
|
||||||
export * from './useDebounce';
|
export * from './useDebounce';
|
||||||
export * from './useLocaleSort';
|
export * from './useLocaleSort';
|
||||||
export * from './useReduxEffect';
|
export * from './useReduxEffect';
|
||||||
|
export * from './useWebClient';
|
||||||
|
|
|
||||||
19
webclient/src/hooks/useWebClient.tsx
Normal file
19
webclient/src/hooks/useWebClient.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { createContext, useContext, useState, ReactNode } from 'react';
|
||||||
|
import { WebClient } from '@app/websocket';
|
||||||
|
import { createWebClientRequest, createWebClientResponse } from '@app/api';
|
||||||
|
|
||||||
|
const WebClientContext = createContext<WebClient | null>(null);
|
||||||
|
|
||||||
|
export function WebClientProvider({ children }: { children: ReactNode }) {
|
||||||
|
const [client] = useState(() => new WebClient(createWebClientRequest(), createWebClientResponse()));
|
||||||
|
|
||||||
|
return <WebClientContext value={client}>{children}</WebClientContext>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useWebClient(): WebClient {
|
||||||
|
const client = useContext(WebClientContext);
|
||||||
|
if (!client) {
|
||||||
|
throw new Error('useWebClient must be used within a WebClientProvider');
|
||||||
|
}
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
@ -2,40 +2,29 @@
|
||||||
// creates the Redux store or connects to Redux DevTools.
|
// creates the Redux store or connects to Redux DevTools.
|
||||||
import './polyfills';
|
import './polyfills';
|
||||||
|
|
||||||
import { StrictMode, useRef } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { StyledEngineProvider } from '@mui/material';
|
import { StyledEngineProvider } from '@mui/material';
|
||||||
import { ThemeProvider } from '@mui/material/styles';
|
import { ThemeProvider } from '@mui/material/styles';
|
||||||
|
|
||||||
import { initWebClient } from '@app/api';
|
import { WebClientProvider } from '@app/hooks';
|
||||||
import { AppShell } from '@app/containers';
|
import { AppShell } from '@app/containers';
|
||||||
import { materialTheme } from './material-theme';
|
import { materialTheme } from './material-theme';
|
||||||
|
|
||||||
import './i18n';
|
import './i18n';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
function useInitWebClient() {
|
|
||||||
const initialized = useRef(false);
|
|
||||||
|
|
||||||
if (!initialized.current) {
|
|
||||||
initialized.current = true;
|
|
||||||
initWebClient();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppWithMaterialTheme = () => {
|
const AppWithMaterialTheme = () => {
|
||||||
// Instantiate the WebClient singleton before any container renders or any
|
|
||||||
// hook touches WebClient.instance.
|
|
||||||
useInitWebClient();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StrictMode>
|
<WebClientProvider>
|
||||||
<StyledEngineProvider injectFirst>
|
<StrictMode>
|
||||||
<ThemeProvider theme={materialTheme}>
|
<StyledEngineProvider injectFirst>
|
||||||
<AppShell />
|
<ThemeProvider theme={materialTheme}>
|
||||||
</ThemeProvider>
|
<AppShell />
|
||||||
</StyledEngineProvider>
|
</ThemeProvider>
|
||||||
</StrictMode>
|
</StyledEngineProvider>
|
||||||
|
</StrictMode>
|
||||||
|
</WebClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
const captured = vi.hoisted(() => ({
|
const captured = vi.hoisted(() => ({
|
||||||
wsOptions: null as WebSocketServiceConfig | null,
|
wsOptions: null as WebSocketServiceConfig | null,
|
||||||
pbOptions: null as SocketTransport | null,
|
pbOptions: null as SocketTransport | null,
|
||||||
pbEvents: null as EventRegistries | null,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./services/WebSocketService', () => ({
|
vi.mock('./services/WebSocketService', () => ({
|
||||||
|
|
@ -18,9 +17,8 @@ vi.mock('./services/WebSocketService', () => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./services/ProtobufService', () => ({
|
vi.mock('./services/ProtobufService', () => ({
|
||||||
ProtobufService: vi.fn().mockImplementation(function ProtobufServiceImpl(transport: SocketTransport, events: EventRegistries) {
|
ProtobufService: vi.fn().mockImplementation(function ProtobufServiceImpl(transport: SocketTransport) {
|
||||||
captured.pbOptions = transport;
|
captured.pbOptions = transport;
|
||||||
captured.pbEvents = events;
|
|
||||||
return {
|
return {
|
||||||
handleMessageEvent: vi.fn(),
|
handleMessageEvent: vi.fn(),
|
||||||
resetCommands: vi.fn(),
|
resetCommands: vi.fn(),
|
||||||
|
|
@ -34,10 +32,10 @@ import { ProtobufService } from './services/ProtobufService';
|
||||||
import { StatusEnum } from './interfaces/StatusEnum';
|
import { StatusEnum } from './interfaces/StatusEnum';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { Mock } from 'vitest';
|
import { Mock } from 'vitest';
|
||||||
import { SocketTransport, EventRegistries } from './services/ProtobufService';
|
import { SocketTransport } from './services/ProtobufService';
|
||||||
import { WebSocketServiceConfig } from './services/WebSocketService';
|
import { WebSocketServiceConfig } from './services/WebSocketService';
|
||||||
import type { IWebClientResponse } from './interfaces';
|
import type { IWebClientResponse, IWebClientRequest } from './interfaces';
|
||||||
import type { WebClientConfig, ConnectTarget } from './interfaces/WebClientConfig';
|
import type { ConnectTarget } from './interfaces/WebClientConfig';
|
||||||
import { installMockWebSocket } from './__mocks__/helpers';
|
import { installMockWebSocket } from './__mocks__/helpers';
|
||||||
|
|
||||||
function makeMockResponse(): IWebClientResponse {
|
function makeMockResponse(): IWebClientResponse {
|
||||||
|
|
@ -58,28 +56,21 @@ function makeMockResponse(): IWebClientResponse {
|
||||||
} as unknown as IWebClientResponse;
|
} as unknown as IWebClientResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeMockConfig(response: IWebClientResponse): WebClientConfig {
|
function makeMockRequest(): IWebClientRequest {
|
||||||
return {
|
return {} as IWebClientRequest;
|
||||||
response,
|
|
||||||
sessionEvents: [],
|
|
||||||
roomEvents: [],
|
|
||||||
gameEvents: [],
|
|
||||||
keepAliveFn: vi.fn(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('WebClient', () => {
|
describe('WebClient', () => {
|
||||||
let client: WebClient;
|
let client: WebClient;
|
||||||
let mockResponse: IWebClientResponse;
|
let mockResponse: IWebClientResponse;
|
||||||
let mockConfig: WebClientConfig;
|
let mockRequest: IWebClientRequest;
|
||||||
let messageSubject: Subject<MessageEvent>;
|
let messageSubject: Subject<MessageEvent>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(WebClient as unknown as { _instance: WebClient | null })._instance = null;
|
(WebClient as unknown as { _instance: WebClient | null })._instance = null;
|
||||||
|
|
||||||
(ProtobufService as Mock).mockImplementation(function ProtobufServiceImpl(transport: SocketTransport, events: EventRegistries) {
|
(ProtobufService as Mock).mockImplementation(function ProtobufServiceImpl(transport: SocketTransport) {
|
||||||
captured.pbOptions = transport;
|
captured.pbOptions = transport;
|
||||||
captured.pbEvents = events;
|
|
||||||
return {
|
return {
|
||||||
handleMessageEvent: vi.fn(),
|
handleMessageEvent: vi.fn(),
|
||||||
resetCommands: vi.fn(),
|
resetCommands: vi.fn(),
|
||||||
|
|
@ -99,8 +90,8 @@ describe('WebClient', () => {
|
||||||
vi.spyOn(console, 'log').mockImplementation(() => {});
|
vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
|
||||||
mockResponse = makeMockResponse();
|
mockResponse = makeMockResponse();
|
||||||
mockConfig = makeMockConfig(mockResponse);
|
mockRequest = makeMockRequest();
|
||||||
client = new WebClient(mockConfig);
|
client = new WebClient(mockRequest, mockResponse);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|
@ -109,9 +100,9 @@ describe('WebClient', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('constructor', () => {
|
describe('constructor', () => {
|
||||||
it('stores the response and config on the instance', () => {
|
it('stores the request and response on the instance', () => {
|
||||||
|
expect(client.request).toBe(mockRequest);
|
||||||
expect(client.response).toBe(mockResponse);
|
expect(client.response).toBe(mockResponse);
|
||||||
expect(client.config).toBe(mockConfig);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('subscribes socket.message$ to protobuf.handleMessageEvent', () => {
|
it('subscribes socket.message$ to protobuf.handleMessageEvent', () => {
|
||||||
|
|
@ -129,7 +120,7 @@ describe('WebClient', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws when instantiated more than once', () => {
|
it('throws when instantiated more than once', () => {
|
||||||
expect(() => new WebClient(makeMockConfig(makeMockResponse()))).toThrow(/singleton/);
|
expect(() => new WebClient(makeMockRequest(), makeMockResponse())).toThrow(/singleton/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -224,10 +215,9 @@ describe('WebClient', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('constructor closures', () => {
|
describe('constructor closures', () => {
|
||||||
it('keepAliveFn forwards from config to WebSocketService', () => {
|
it('keepAliveFn is set to ping function in WebSocketService', () => {
|
||||||
const cb = vi.fn();
|
expect(captured.wsOptions!.keepAliveFn).toBeDefined();
|
||||||
captured.wsOptions!.keepAliveFn(cb);
|
expect(typeof captured.wsOptions!.keepAliveFn).toBe('function');
|
||||||
expect(mockConfig.keepAliveFn).toHaveBeenCalledWith(cb);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('onStatusChange routes to response.session.updateStatus and updates own status', () => {
|
it('onStatusChange routes to response.session.updateStatus and updates own status', () => {
|
||||||
|
|
@ -241,12 +231,6 @@ describe('WebClient', () => {
|
||||||
expect(mockResponse.session.connectionFailed).toHaveBeenCalled();
|
expect(mockResponse.session.connectionFailed).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes event registries from config to ProtobufService', () => {
|
|
||||||
expect(captured.pbEvents!.sessionEvents).toBe(mockConfig.sessionEvents);
|
|
||||||
expect(captured.pbEvents!.roomEvents).toBe(mockConfig.roomEvents);
|
|
||||||
expect(captured.pbEvents!.gameEvents).toBe(mockConfig.gameEvents);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('send closure delegates to socket.send', () => {
|
it('send closure delegates to socket.send', () => {
|
||||||
const data = new Uint8Array([1, 2, 3]);
|
const data = new Uint8Array([1, 2, 3]);
|
||||||
captured.pbOptions!.send(data);
|
captured.pbOptions!.send(data);
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,40 @@
|
||||||
import { StatusEnum } from './interfaces/StatusEnum';
|
import { ping } from './commands/session';
|
||||||
|
import { CLIENT_OPTIONS } from './config';
|
||||||
|
import type {
|
||||||
|
ConnectTarget,
|
||||||
|
IWebClientRequest,
|
||||||
|
IWebClientResponse,
|
||||||
|
} from './interfaces';
|
||||||
|
import { StatusEnum } from './interfaces';
|
||||||
import { ProtobufService } from './services/ProtobufService';
|
import { ProtobufService } from './services/ProtobufService';
|
||||||
import { WebSocketService } from './services/WebSocketService';
|
import { WebSocketService } from './services/WebSocketService';
|
||||||
import { CLIENT_OPTIONS } from './config';
|
|
||||||
import type { IWebClientResponse } from './interfaces';
|
|
||||||
import type { WebClientConfig, ConnectTarget } from './interfaces/WebClientConfig';
|
|
||||||
|
|
||||||
export class WebClient {
|
export class WebClient {
|
||||||
private static _instance: WebClient | null = null;
|
private static _instance: WebClient | null = null;
|
||||||
|
|
||||||
public static get instance(): WebClient {
|
static get instance(): WebClient {
|
||||||
if (!WebClient._instance) {
|
if (!WebClient._instance) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'WebClient has not been initialized. Instantiate it via `new WebClient(config)` before accessing `WebClient.instance`.'
|
'WebClient has not been initialized. Instantiate it via `new WebClient()` before accessing `WebClient.instance`.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return WebClient._instance;
|
return WebClient._instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public socket: WebSocketService;
|
protobuf: ProtobufService;
|
||||||
public protobuf: ProtobufService;
|
socket: WebSocketService;
|
||||||
public response: IWebClientResponse;
|
status: StatusEnum;
|
||||||
public config: WebClientConfig;
|
|
||||||
|
|
||||||
public status: StatusEnum;
|
constructor(
|
||||||
|
public request: IWebClientRequest,
|
||||||
constructor(config: WebClientConfig) {
|
public response: IWebClientResponse
|
||||||
|
) {
|
||||||
if (WebClient._instance) {
|
if (WebClient._instance) {
|
||||||
throw new Error('WebClient is a singleton and has already been initialized.');
|
throw new Error('WebClient is a singleton and has already been initialized.');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.config = config;
|
|
||||||
this.response = config.response;
|
|
||||||
|
|
||||||
this.socket = new WebSocketService({
|
this.socket = new WebSocketService({
|
||||||
keepAliveFn: config.keepAliveFn,
|
keepAliveFn: ping,
|
||||||
onStatusChange: (status, description) => {
|
onStatusChange: (status, description) => {
|
||||||
this.response.session.updateStatus(status, description);
|
this.response.session.updateStatus(status, description);
|
||||||
this.updateStatus(status);
|
this.updateStatus(status);
|
||||||
|
|
@ -47,12 +48,7 @@ export class WebClient {
|
||||||
{
|
{
|
||||||
send: (data) => this.socket.send(data),
|
send: (data) => this.socket.send(data),
|
||||||
isOpen: () => this.socket.checkReadyState(WebSocket.OPEN),
|
isOpen: () => this.socket.checkReadyState(WebSocket.OPEN),
|
||||||
},
|
}
|
||||||
{
|
|
||||||
sessionEvents: config.sessionEvents,
|
|
||||||
roomEvents: config.roomEvents,
|
|
||||||
gameEvents: config.gameEvents,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.socket.message$.subscribe((message: MessageEvent) => {
|
this.socket.message$.subscribe((message: MessageEvent) => {
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,7 @@ export type {
|
||||||
IModeratorRequest,
|
IModeratorRequest,
|
||||||
IWebClientRequest,
|
IWebClientRequest,
|
||||||
} from './WebClientRequest';
|
} from './WebClientRequest';
|
||||||
|
|
||||||
|
export * from './WebClientConfig';
|
||||||
|
export * from './WebSocketConfig';
|
||||||
|
export * from './StatusEnum';
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,25 @@ vi.mock('@bufbuild/protobuf', async (importOriginal) => ({
|
||||||
setExtension: vi.fn(),
|
setExtension: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../WebClient');
|
vi.mock('../events/game', () => ({
|
||||||
|
GameEvents: [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../events/room', () => ({
|
||||||
|
RoomEvents: [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../events/session', () => ({
|
||||||
|
SessionEvents: [],
|
||||||
|
}));
|
||||||
|
|
||||||
import { create, fromBinary, hasExtension, getExtension } from '@bufbuild/protobuf';
|
import { create, fromBinary, hasExtension, getExtension } from '@bufbuild/protobuf';
|
||||||
import type { GenExtension } from '@bufbuild/protobuf/codegenv2';
|
import type { GenExtension } from '@bufbuild/protobuf/codegenv2';
|
||||||
|
|
||||||
import { ProtobufService } from './ProtobufService';
|
import { ProtobufService } from './ProtobufService';
|
||||||
import type { EventRegistries } from './ProtobufService';
|
import { GameEvents } from '../events/game';
|
||||||
|
import { RoomEvents } from '../events/room';
|
||||||
|
import { SessionEvents } from '../events/session';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AdminCommand,
|
AdminCommand,
|
||||||
|
|
@ -43,18 +55,17 @@ type ProtobufInternal = ProtobufService & {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mockSocket: { isOpen: ReturnType<typeof vi.fn>; send: ReturnType<typeof vi.fn> };
|
let mockSocket: { isOpen: ReturnType<typeof vi.fn>; send: ReturnType<typeof vi.fn> };
|
||||||
let mockEvents: EventRegistries;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockSocket = {
|
mockSocket = {
|
||||||
isOpen: vi.fn().mockReturnValue(true),
|
isOpen: vi.fn().mockReturnValue(true),
|
||||||
send: vi.fn(),
|
send: vi.fn(),
|
||||||
};
|
};
|
||||||
mockEvents = {
|
|
||||||
sessionEvents: [],
|
// Reset event registries
|
||||||
roomEvents: [],
|
(GameEvents as any).length = 0;
|
||||||
gameEvents: [],
|
(RoomEvents as any).length = 0;
|
||||||
};
|
(SessionEvents as any).length = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ProtobufService', () => {
|
describe('ProtobufService', () => {
|
||||||
|
|
@ -67,7 +78,7 @@ describe('ProtobufService', () => {
|
||||||
|
|
||||||
describe('resetCommands', () => {
|
describe('resetCommands', () => {
|
||||||
it('resets cmdId and pendingCommands', () => {
|
it('resets cmdId and pendingCommands', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
service.sendSessionCommand(sessionExt, vi.fn());
|
service.sendSessionCommand(sessionExt, vi.fn());
|
||||||
expect((service as ProtobufInternal).cmdId).toBe(1);
|
expect((service as ProtobufInternal).cmdId).toBe(1);
|
||||||
service.resetCommands();
|
service.resetCommands();
|
||||||
|
|
@ -78,7 +89,7 @@ describe('ProtobufService', () => {
|
||||||
|
|
||||||
describe('sendCommand', () => {
|
describe('sendCommand', () => {
|
||||||
it('increments cmdId and stores callback', () => {
|
it('increments cmdId and stores callback', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
const cb = vi.fn();
|
const cb = vi.fn();
|
||||||
service.sendCommand(create(CommandContainerSchema), cb);
|
service.sendCommand(create(CommandContainerSchema), cb);
|
||||||
expect((service as ProtobufInternal).cmdId).toBe(1);
|
expect((service as ProtobufInternal).cmdId).toBe(1);
|
||||||
|
|
@ -86,14 +97,14 @@ describe('ProtobufService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends encoded data when socket is OPEN', () => {
|
it('sends encoded data when socket is OPEN', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
mockSocket.isOpen.mockReturnValue(true);
|
mockSocket.isOpen.mockReturnValue(true);
|
||||||
service.sendCommand(create(CommandContainerSchema), vi.fn());
|
service.sendCommand(create(CommandContainerSchema), vi.fn());
|
||||||
expect(mockSocket.send).toHaveBeenCalled();
|
expect(mockSocket.send).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not register callback or increment cmdId when transport is closed', () => {
|
it('does not register callback or increment cmdId when transport is closed', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
mockSocket.isOpen.mockReturnValue(false);
|
mockSocket.isOpen.mockReturnValue(false);
|
||||||
const cb = vi.fn();
|
const cb = vi.fn();
|
||||||
service.sendCommand(create(CommandContainerSchema), cb);
|
service.sendCommand(create(CommandContainerSchema), cb);
|
||||||
|
|
@ -105,14 +116,14 @@ describe('ProtobufService', () => {
|
||||||
|
|
||||||
describe('sendSessionCommand', () => {
|
describe('sendSessionCommand', () => {
|
||||||
it('stores callback and increments cmdId', () => {
|
it('stores callback and increments cmdId', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
service.sendSessionCommand(sessionExt, {});
|
service.sendSessionCommand(sessionExt, {});
|
||||||
expect((service as ProtobufInternal).cmdId).toBe(1);
|
expect((service as ProtobufInternal).cmdId).toBe(1);
|
||||||
expect((service as ProtobufInternal).pendingCommands.get(1)).toBeTypeOf('function');
|
expect((service as ProtobufInternal).pendingCommands.get(1)).toBeTypeOf('function');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invokes onResponse with raw response when the pending command is triggered', () => {
|
it('invokes onResponse with raw response when the pending command is triggered', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
const cb = vi.fn();
|
const cb = vi.fn();
|
||||||
service.sendSessionCommand(sessionExt, {}, { onResponse: cb });
|
service.sendSessionCommand(sessionExt, {}, { onResponse: cb });
|
||||||
|
|
||||||
|
|
@ -123,7 +134,7 @@ describe('ProtobufService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not throw when no callback is provided and pending command is triggered', () => {
|
it('does not throw when no callback is provided and pending command is triggered', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
service.sendSessionCommand(sessionExt, {});
|
service.sendSessionCommand(sessionExt, {});
|
||||||
|
|
||||||
const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!;
|
const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!;
|
||||||
|
|
@ -133,13 +144,13 @@ describe('ProtobufService', () => {
|
||||||
|
|
||||||
describe('sendRoomCommand', () => {
|
describe('sendRoomCommand', () => {
|
||||||
it('stores callback and increments cmdId', () => {
|
it('stores callback and increments cmdId', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
service.sendRoomCommand(42, roomExt, {});
|
service.sendRoomCommand(42, roomExt, {});
|
||||||
expect((service as ProtobufInternal).cmdId).toBe(1);
|
expect((service as ProtobufInternal).cmdId).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invokes onResponse with raw response when the pending command is triggered', () => {
|
it('invokes onResponse with raw response when the pending command is triggered', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
const cb = vi.fn();
|
const cb = vi.fn();
|
||||||
service.sendRoomCommand(42, roomExt, {}, { onResponse: cb });
|
service.sendRoomCommand(42, roomExt, {}, { onResponse: cb });
|
||||||
|
|
||||||
|
|
@ -150,7 +161,7 @@ describe('ProtobufService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not throw when no callback is provided and pending command is triggered', () => {
|
it('does not throw when no callback is provided and pending command is triggered', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
service.sendRoomCommand(42, roomExt, {});
|
service.sendRoomCommand(42, roomExt, {});
|
||||||
|
|
||||||
const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!;
|
const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!;
|
||||||
|
|
@ -160,13 +171,13 @@ describe('ProtobufService', () => {
|
||||||
|
|
||||||
describe('sendGameCommand', () => {
|
describe('sendGameCommand', () => {
|
||||||
it('stores callback and increments cmdId', () => {
|
it('stores callback and increments cmdId', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
service.sendGameCommand(7, gameExt, {});
|
service.sendGameCommand(7, gameExt, {});
|
||||||
expect((service as ProtobufInternal).cmdId).toBe(1);
|
expect((service as ProtobufInternal).cmdId).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invokes onResponse with raw response when the pending command is triggered', () => {
|
it('invokes onResponse with raw response when the pending command is triggered', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
const cb = vi.fn();
|
const cb = vi.fn();
|
||||||
service.sendGameCommand(7, gameExt, {}, { onResponse: cb });
|
service.sendGameCommand(7, gameExt, {}, { onResponse: cb });
|
||||||
|
|
||||||
|
|
@ -177,7 +188,7 @@ describe('ProtobufService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not throw when no callback is provided and pending command is triggered', () => {
|
it('does not throw when no callback is provided and pending command is triggered', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
service.sendGameCommand(7, gameExt, {});
|
service.sendGameCommand(7, gameExt, {});
|
||||||
|
|
||||||
const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!;
|
const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!;
|
||||||
|
|
@ -187,13 +198,13 @@ describe('ProtobufService', () => {
|
||||||
|
|
||||||
describe('sendModeratorCommand', () => {
|
describe('sendModeratorCommand', () => {
|
||||||
it('stores callback and increments cmdId', () => {
|
it('stores callback and increments cmdId', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
service.sendModeratorCommand(moderatorExt, {});
|
service.sendModeratorCommand(moderatorExt, {});
|
||||||
expect((service as ProtobufInternal).cmdId).toBe(1);
|
expect((service as ProtobufInternal).cmdId).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invokes onResponse with raw response when the pending command is triggered', () => {
|
it('invokes onResponse with raw response when the pending command is triggered', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
const cb = vi.fn();
|
const cb = vi.fn();
|
||||||
service.sendModeratorCommand(moderatorExt, {}, { onResponse: cb });
|
service.sendModeratorCommand(moderatorExt, {}, { onResponse: cb });
|
||||||
|
|
||||||
|
|
@ -204,7 +215,7 @@ describe('ProtobufService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not throw when no callback is provided and pending command is triggered', () => {
|
it('does not throw when no callback is provided and pending command is triggered', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
service.sendModeratorCommand(moderatorExt, {});
|
service.sendModeratorCommand(moderatorExt, {});
|
||||||
|
|
||||||
const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!;
|
const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!;
|
||||||
|
|
@ -214,13 +225,13 @@ describe('ProtobufService', () => {
|
||||||
|
|
||||||
describe('sendAdminCommand', () => {
|
describe('sendAdminCommand', () => {
|
||||||
it('stores callback and increments cmdId', () => {
|
it('stores callback and increments cmdId', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
service.sendAdminCommand(adminExt, {});
|
service.sendAdminCommand(adminExt, {});
|
||||||
expect((service as ProtobufInternal).cmdId).toBe(1);
|
expect((service as ProtobufInternal).cmdId).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invokes onResponse with raw response when the pending command is triggered', () => {
|
it('invokes onResponse with raw response when the pending command is triggered', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
const cb = vi.fn();
|
const cb = vi.fn();
|
||||||
service.sendAdminCommand(adminExt, {}, { onResponse: cb });
|
service.sendAdminCommand(adminExt, {}, { onResponse: cb });
|
||||||
|
|
||||||
|
|
@ -231,7 +242,7 @@ describe('ProtobufService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not throw when no callback is provided and pending command is triggered', () => {
|
it('does not throw when no callback is provided and pending command is triggered', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
service.sendAdminCommand(adminExt, {});
|
service.sendAdminCommand(adminExt, {});
|
||||||
|
|
||||||
const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!;
|
const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!;
|
||||||
|
|
@ -241,7 +252,7 @@ describe('ProtobufService', () => {
|
||||||
|
|
||||||
describe('handleMessageEvent', () => {
|
describe('handleMessageEvent', () => {
|
||||||
it('routes RESPONSE message to processServerResponse', () => {
|
it('routes RESPONSE message to processServerResponse', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
const cb = vi.fn();
|
const cb = vi.fn();
|
||||||
(service as ProtobufInternal).cmdId = 1;
|
(service as ProtobufInternal).cmdId = 1;
|
||||||
(service as ProtobufInternal).pendingCommands.set(1, cb);
|
(service as ProtobufInternal).pendingCommands.set(1, cb);
|
||||||
|
|
@ -259,7 +270,7 @@ describe('ProtobufService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('routes ROOM_EVENT message', () => {
|
it('routes ROOM_EVENT message', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
const processRoomEvent = vi.spyOn(service as ProtobufInternal, 'processRoomEvent');
|
const processRoomEvent = vi.spyOn(service as ProtobufInternal, 'processRoomEvent');
|
||||||
|
|
||||||
vi.mocked(fromBinary).mockReturnValue(
|
vi.mocked(fromBinary).mockReturnValue(
|
||||||
|
|
@ -273,7 +284,7 @@ describe('ProtobufService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('routes SESSION_EVENT message', () => {
|
it('routes SESSION_EVENT message', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
const processSessionEvent = vi.spyOn(service as ProtobufInternal, 'processSessionEvent');
|
const processSessionEvent = vi.spyOn(service as ProtobufInternal, 'processSessionEvent');
|
||||||
|
|
||||||
vi.mocked(fromBinary).mockReturnValue(
|
vi.mocked(fromBinary).mockReturnValue(
|
||||||
|
|
@ -287,7 +298,7 @@ describe('ProtobufService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('routes GAME_EVENT_CONTAINER message', () => {
|
it('routes GAME_EVENT_CONTAINER message', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
const processGameEvent = vi.spyOn(service as ProtobufInternal, 'processGameEvent');
|
const processGameEvent = vi.spyOn(service as ProtobufInternal, 'processGameEvent');
|
||||||
|
|
||||||
vi.mocked(fromBinary).mockReturnValue(
|
vi.mocked(fromBinary).mockReturnValue(
|
||||||
|
|
@ -301,7 +312,7 @@ describe('ProtobufService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs unknown message types (default case)', () => {
|
it('logs unknown message types (default case)', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
|
||||||
vi.mocked(fromBinary).mockReturnValue(
|
vi.mocked(fromBinary).mockReturnValue(
|
||||||
|
|
@ -316,13 +327,13 @@ describe('ProtobufService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does nothing when decoded message is null', () => {
|
it('does nothing when decoded message is null', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
vi.mocked(fromBinary).mockReturnValue(null!);
|
vi.mocked(fromBinary).mockReturnValue(null!);
|
||||||
expect(() => service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent)).not.toThrow();
|
expect(() => service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent)).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('catches and logs decode errors', () => {
|
it('catches and logs decode errors', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
vi.mocked(fromBinary).mockImplementation(() => {
|
vi.mocked(fromBinary).mockImplementation(() => {
|
||||||
throw new Error('decode error');
|
throw new Error('decode error');
|
||||||
|
|
@ -335,7 +346,7 @@ describe('ProtobufService', () => {
|
||||||
|
|
||||||
describe('processGameEvent', () => {
|
describe('processGameEvent', () => {
|
||||||
it('returns early when container has no eventList', () => {
|
it('returns early when container has no eventList', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
vi.mocked(hasExtension).mockReturnValue(false);
|
vi.mocked(hasExtension).mockReturnValue(false);
|
||||||
(service as ProtobufInternal).processGameEvent(null, {});
|
(service as ProtobufInternal).processGameEvent(null, {});
|
||||||
expect(hasExtension).not.toHaveBeenCalled();
|
expect(hasExtension).not.toHaveBeenCalled();
|
||||||
|
|
@ -346,8 +357,8 @@ describe('ProtobufService', () => {
|
||||||
const mockExt = {} as GenExtension<GameEvent, unknown>;
|
const mockExt = {} as GenExtension<GameEvent, unknown>;
|
||||||
const payload = { someData: 1 };
|
const payload = { someData: 1 };
|
||||||
|
|
||||||
mockEvents.gameEvents.push([mockExt, handler] as any);
|
(GameEvents as any).push([mockExt, handler]);
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
vi.mocked(hasExtension).mockReturnValue(true);
|
vi.mocked(hasExtension).mockReturnValue(true);
|
||||||
vi.mocked(getExtension).mockReturnValue(payload);
|
vi.mocked(getExtension).mockReturnValue(payload);
|
||||||
|
|
||||||
|
|
@ -364,8 +375,8 @@ describe('ProtobufService', () => {
|
||||||
const mockExt = {} as GenExtension<GameEvent, unknown>;
|
const mockExt = {} as GenExtension<GameEvent, unknown>;
|
||||||
const payload = { someData: 1 };
|
const payload = { someData: 1 };
|
||||||
|
|
||||||
mockEvents.gameEvents.push([mockExt, handler] as any);
|
(GameEvents as any).push([mockExt, handler]);
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
vi.mocked(hasExtension).mockReturnValue(true);
|
vi.mocked(hasExtension).mockReturnValue(true);
|
||||||
vi.mocked(getExtension).mockReturnValue(payload);
|
vi.mocked(getExtension).mockReturnValue(payload);
|
||||||
|
|
||||||
|
|
@ -380,7 +391,7 @@ describe('ProtobufService', () => {
|
||||||
|
|
||||||
describe('processServerResponse', () => {
|
describe('processServerResponse', () => {
|
||||||
it('returns early when response is undefined', () => {
|
it('returns early when response is undefined', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
(service as ProtobufInternal).pendingCommands.set(1, vi.fn());
|
(service as ProtobufInternal).pendingCommands.set(1, vi.fn());
|
||||||
(service as ProtobufInternal).processServerResponse(undefined);
|
(service as ProtobufInternal).processServerResponse(undefined);
|
||||||
expect((service as ProtobufInternal).pendingCommands.size).toBe(1);
|
expect((service as ProtobufInternal).pendingCommands.size).toBe(1);
|
||||||
|
|
@ -389,7 +400,7 @@ describe('ProtobufService', () => {
|
||||||
|
|
||||||
describe('processRoomEvent', () => {
|
describe('processRoomEvent', () => {
|
||||||
it('returns early when event is undefined', () => {
|
it('returns early when event is undefined', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
vi.mocked(hasExtension).mockReturnValue(false);
|
vi.mocked(hasExtension).mockReturnValue(false);
|
||||||
(service as ProtobufInternal).processRoomEvent(undefined);
|
(service as ProtobufInternal).processRoomEvent(undefined);
|
||||||
expect(hasExtension).not.toHaveBeenCalled();
|
expect(hasExtension).not.toHaveBeenCalled();
|
||||||
|
|
@ -400,8 +411,8 @@ describe('ProtobufService', () => {
|
||||||
const mockExt = {} as GenExtension<RoomEvent, unknown>;
|
const mockExt = {} as GenExtension<RoomEvent, unknown>;
|
||||||
const payload = { roomData: 1 };
|
const payload = { roomData: 1 };
|
||||||
|
|
||||||
mockEvents.roomEvents.push([mockExt, handler] as any);
|
(RoomEvents as any).push([mockExt, handler]);
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
vi.mocked(hasExtension).mockReturnValue(true);
|
vi.mocked(hasExtension).mockReturnValue(true);
|
||||||
vi.mocked(getExtension).mockReturnValue(payload);
|
vi.mocked(getExtension).mockReturnValue(payload);
|
||||||
|
|
||||||
|
|
@ -414,7 +425,7 @@ describe('ProtobufService', () => {
|
||||||
|
|
||||||
describe('processSessionEvent', () => {
|
describe('processSessionEvent', () => {
|
||||||
it('returns early when event is undefined', () => {
|
it('returns early when event is undefined', () => {
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
vi.mocked(hasExtension).mockReturnValue(false);
|
vi.mocked(hasExtension).mockReturnValue(false);
|
||||||
(service as ProtobufInternal).processSessionEvent(undefined);
|
(service as ProtobufInternal).processSessionEvent(undefined);
|
||||||
expect(hasExtension).not.toHaveBeenCalled();
|
expect(hasExtension).not.toHaveBeenCalled();
|
||||||
|
|
@ -425,8 +436,8 @@ describe('ProtobufService', () => {
|
||||||
const mockExt = {} as GenExtension<SessionEvent, unknown>;
|
const mockExt = {} as GenExtension<SessionEvent, unknown>;
|
||||||
const payload = { sessionData: 1 };
|
const payload = { sessionData: 1 };
|
||||||
|
|
||||||
mockEvents.sessionEvents.push([mockExt, handler] as any);
|
(SessionEvents as any).push([mockExt, handler]);
|
||||||
const service = new ProtobufService(mockSocket, mockEvents);
|
const service = new ProtobufService(mockSocket);
|
||||||
vi.mocked(hasExtension).mockReturnValue(true);
|
vi.mocked(hasExtension).mockReturnValue(true);
|
||||||
vi.mocked(getExtension).mockReturnValue(payload);
|
vi.mocked(getExtension).mockReturnValue(payload);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,11 @@ import {
|
||||||
type GameEventContainer,
|
type GameEventContainer,
|
||||||
type SessionEvent,
|
type SessionEvent,
|
||||||
type RoomEvent,
|
type RoomEvent,
|
||||||
type RegistryEntry,
|
|
||||||
type GameEvent,
|
|
||||||
} from '@app/generated';
|
} from '@app/generated';
|
||||||
|
|
||||||
|
import { GameEvents } from '../events/game';
|
||||||
|
import { RoomEvents } from '../events/room';
|
||||||
|
import { SessionEvents } from '../events/session';
|
||||||
import type { GameEventMeta } from '../interfaces/WebSocketConfig';
|
import type { GameEventMeta } from '../interfaces/WebSocketConfig';
|
||||||
import { type CommandOptions, handleResponse } from './command-options';
|
import { type CommandOptions, handleResponse } from './command-options';
|
||||||
|
|
||||||
|
|
@ -33,23 +34,11 @@ export interface SocketTransport {
|
||||||
isOpen(): boolean;
|
isOpen(): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EventRegistries {
|
|
||||||
sessionEvents: RegistryEntry<unknown, SessionEvent>[];
|
|
||||||
roomEvents: RegistryEntry<unknown, RoomEvent, RoomEvent>[];
|
|
||||||
gameEvents: RegistryEntry<unknown, GameEvent, GameEventMeta>[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ProtobufService {
|
export class ProtobufService {
|
||||||
private cmdId = 0;
|
private cmdId = 0;
|
||||||
private pendingCommands = new Map<number, (response: Response) => void>();
|
private pendingCommands = new Map<number, (response: Response) => void>();
|
||||||
|
|
||||||
private transport: SocketTransport;
|
constructor(private transport: SocketTransport) {}
|
||||||
private events: EventRegistries;
|
|
||||||
|
|
||||||
constructor(transport: SocketTransport, events: EventRegistries) {
|
|
||||||
this.transport = transport;
|
|
||||||
this.events = events;
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetCommands() {
|
public resetCommands() {
|
||||||
this.cmdId = 0;
|
this.cmdId = 0;
|
||||||
|
|
@ -189,7 +178,7 @@ export class ProtobufService {
|
||||||
if (!event) {
|
if (!event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const [ext, handler] of this.events.roomEvents) {
|
for (const [ext, handler] of RoomEvents) {
|
||||||
if (hasExtension(event, ext)) {
|
if (hasExtension(event, ext)) {
|
||||||
handler(getExtension(event, ext), event);
|
handler(getExtension(event, ext), event);
|
||||||
return;
|
return;
|
||||||
|
|
@ -201,7 +190,7 @@ export class ProtobufService {
|
||||||
if (!event) {
|
if (!event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const [ext, handler] of this.events.sessionEvents) {
|
for (const [ext, handler] of SessionEvents) {
|
||||||
if (hasExtension(event, ext)) {
|
if (hasExtension(event, ext)) {
|
||||||
handler(getExtension(event, ext), undefined);
|
handler(getExtension(event, ext), undefined);
|
||||||
return;
|
return;
|
||||||
|
|
@ -225,7 +214,7 @@ export class ProtobufService {
|
||||||
forcedByJudge: forcedByJudge ?? 0,
|
forcedByJudge: forcedByJudge ?? 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [ext, handler] of this.events.gameEvents) {
|
for (const [ext, handler] of GameEvents) {
|
||||||
if (hasExtension(event, ext)) {
|
if (hasExtension(event, ext)) {
|
||||||
handler(getExtension(event, ext), meta);
|
handler(getExtension(event, ext), meta);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue