Webatrice websocket refactor (#4435)

* add unit tests for websocket events

* add unit tests for KeepAliveService, clean up keepAlive termination flow

* put keepAlive command in protobuf service and expose thru webClient

* secure wss

* rename files tsx to ts

* add localhost support for ws/wss connection

Co-authored-by: Jeremy Letto <jeremy.letto@datasite.com>
This commit is contained in:
Jeremy Letto 2021-10-17 19:52:59 -05:00 committed by GitHub
parent f75ff2a7c8
commit 586f23cfa9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 568 additions and 77 deletions

View file

@ -0,0 +1,64 @@
import { Message } from 'types';
import {
RoomEvents,
RoomEvent,
JoinRoomData,
LeaveRoomData,
ListGamesData,
} from './RoomEvents';
import { RoomPersistence } from '../persistence/RoomPersistence';
describe('RoomEvents', () => {
it('.Event_JoinRoom.ext should call RoomPersistence.userJoined', () => {
spyOn(RoomPersistence, 'userJoined');
const data: JoinRoomData = { userInfo: {} as any };
const event: RoomEvent = { roomEvent: { roomId: 1 } };
RoomEvents['.Event_JoinRoom.ext'](data, event);
expect(RoomPersistence.userJoined).toHaveBeenCalledWith(
event.roomEvent.roomId,
data.userInfo
);
});
it('.Event_LeaveRoom.ext should call RoomPersistence.userLeft', () => {
spyOn(RoomPersistence, 'userLeft');
const data: LeaveRoomData = { name: '' };
const event: RoomEvent = { roomEvent: { roomId: 1 } };
RoomEvents['.Event_LeaveRoom.ext'](data, event);
expect(RoomPersistence.userLeft).toHaveBeenCalledWith(
event.roomEvent.roomId,
data.name
);
});
it('.Event_ListGames.ext should call RoomPersistence.updateGames', () => {
spyOn(RoomPersistence, 'updateGames');
const data: ListGamesData = { gameList: [] };
const event: RoomEvent = { roomEvent: { roomId: 1 } };
RoomEvents['.Event_ListGames.ext'](data, event);
expect(RoomPersistence.updateGames).toHaveBeenCalledWith(
event.roomEvent.roomId,
data.gameList
);
});
it('.Event_RoomSay.ext should call RoomPersistence.addMessage', () => {
spyOn(RoomPersistence, 'addMessage');
const data: Message = {} as any;
const event: RoomEvent = { roomEvent: { roomId: 1 } };
RoomEvents['.Event_RoomSay.ext'](data, event);
expect(RoomPersistence.addMessage).toHaveBeenCalledWith(
event.roomEvent.roomId,
data
);
});
});

View file

@ -30,20 +30,20 @@ function roomSay(message: Message, { roomEvent }: RoomEvent) {
RoomPersistence.addMessage(roomId, message);
}
interface RoomEvent {
export interface RoomEvent {
roomEvent: {
roomId: number;
}
}
interface JoinRoomData {
export interface JoinRoomData {
userInfo: User;
}
interface LeaveRoomData {
export interface LeaveRoomData {
name: string;
}
interface ListGamesData {
export interface ListGamesData {
gameList: Game[];
}

View file

@ -0,0 +1,355 @@
import { StatusEnum } from "types";
import {
SessionEvents,
SessionEvent,
AddToListData,
ConnectionClosedData,
ListRoomsData,
RemoveFromListData,
ServerIdentificationData,
ServerMessageData,
UserJoinedData,
UserLeftData,
} from './SessionEvents';
import { SessionCommands } from "../commands";
import { RoomPersistence, SessionPersistence } from '../persistence';
import webClient from '../WebClient';
describe('SessionEvents', () => {
const roomId = 1;
beforeEach(() => {
spyOn(SessionCommands, 'updateStatus');
});
describe('.Event_AddToList.ext', () => {
it('should call SessionPersistence.addToBuddyList if buddy listName', () => {
spyOn(SessionPersistence, 'addToBuddyList');
const data: AddToListData = { listName: 'buddy', userInfo: {} as any };
SessionEvents['.Event_AddToList.ext'](data);
expect(SessionPersistence.addToBuddyList).toHaveBeenCalledWith(
data.userInfo
);
});
it('should call SessionPersistence.addToIgnoreList if ignore listName', () => {
spyOn(SessionPersistence, 'addToIgnoreList');
const data: AddToListData = { listName: 'ignore', userInfo: {} as any };
SessionEvents['.Event_AddToList.ext'](data);
expect(SessionPersistence.addToIgnoreList).toHaveBeenCalledWith(
data.userInfo
);
});
it('should call console.log if unknown listName', () => {
spyOn(console, 'log');
const data: AddToListData = { listName: 'unknown', userInfo: {} as any };
SessionEvents['.Event_AddToList.ext'](data);
expect(console.log).toHaveBeenCalledWith(
`Attempted to add to unknown list: ${data.listName}`
);
});
});
describe('.Event_ConnectionClosed.ext', () => {
describe('with reasonStr', () => {
it('should call SessionCommands.updateStatus', () => {
const data: ConnectionClosedData = { endTime: 0, reason: 0, reasonStr: 'reasonStr' };
SessionEvents['.Event_ConnectionClosed.ext'](data);
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(
StatusEnum.DISCONNECTED,
data.reasonStr
);
});
});
describe('without reasonStr', () => {
beforeEach(() => {
webClient.protobuf.controller.Event_ConnectionClosed = { CloseReason: {} };
});
describe('USER_LIMIT_REACHED', () => {
it('should call SessionCommands.updateStatus', () => {
const USER_LIMIT_REACHED = 1;
webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.USER_LIMIT_REACHED = USER_LIMIT_REACHED;
const data: ConnectionClosedData = { endTime: 0, reason: USER_LIMIT_REACHED, reasonStr: null };
SessionEvents['.Event_ConnectionClosed.ext'](data);
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(
StatusEnum.DISCONNECTED,
'The server has reached its maximum user capacity'
);
});
});
describe('TOO_MANY_CONNECTIONS', () => {
it('should call SessionCommands.updateStatus', () => {
const TOO_MANY_CONNECTIONS = 1;
webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.TOO_MANY_CONNECTIONS = TOO_MANY_CONNECTIONS;
const data: ConnectionClosedData = { endTime: 0, reason: TOO_MANY_CONNECTIONS, reasonStr: null };
SessionEvents['.Event_ConnectionClosed.ext'](data);
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(
StatusEnum.DISCONNECTED,
'There are too many concurrent connections from your address'
);
});
});
describe('BANNED', () => {
it('should call SessionCommands.updateStatus', () => {
const BANNED = 1;
webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.BANNED = BANNED;
const data: ConnectionClosedData = { endTime: 0, reason: BANNED, reasonStr: null };
SessionEvents['.Event_ConnectionClosed.ext'](data);
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(
StatusEnum.DISCONNECTED,
'You are banned'
);
});
});
describe('DEMOTED', () => {
it('should call SessionCommands.updateStatus', () => {
const DEMOTED = 1;
webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.DEMOTED = DEMOTED;
const data: ConnectionClosedData = { endTime: 0, reason: DEMOTED, reasonStr: null };
SessionEvents['.Event_ConnectionClosed.ext'](data);
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(
StatusEnum.DISCONNECTED,
'You were demoted'
);
});
});
describe('SERVER_SHUTDOWN', () => {
it('should call SessionCommands.updateStatus', () => {
const SERVER_SHUTDOWN = 1;
webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.SERVER_SHUTDOWN = SERVER_SHUTDOWN;
const data: ConnectionClosedData = { endTime: 0, reason: SERVER_SHUTDOWN, reasonStr: null };
SessionEvents['.Event_ConnectionClosed.ext'](data);
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(
StatusEnum.DISCONNECTED,
'Scheduled server shutdown'
);
});
});
describe('USERNAMEINVALID', () => {
it('should call SessionCommands.updateStatus', () => {
const USERNAMEINVALID = 1;
webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.USERNAMEINVALID = USERNAMEINVALID;
const data: ConnectionClosedData = { endTime: 0, reason: USERNAMEINVALID, reasonStr: null };
SessionEvents['.Event_ConnectionClosed.ext'](data);
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(
StatusEnum.DISCONNECTED,
'Invalid username'
);
});
});
describe('LOGGEDINELSEWERE', () => {
it('should call SessionCommands.updateStatus', () => {
const LOGGEDINELSEWERE = 1;
webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.LOGGEDINELSEWERE = LOGGEDINELSEWERE;
const data: ConnectionClosedData = { endTime: 0, reason: LOGGEDINELSEWERE, reasonStr: null };
SessionEvents['.Event_ConnectionClosed.ext'](data);
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(
StatusEnum.DISCONNECTED,
'You have been logged out due to logging in at another location'
);
});
});
describe('OTHER', () => {
it('should call SessionCommands.updateStatus', () => {
const OTHER = 1;
webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.OTHER = OTHER;
const data: ConnectionClosedData = { endTime: 0, reason: OTHER, reasonStr: null };
SessionEvents['.Event_ConnectionClosed.ext'](data);
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(
StatusEnum.DISCONNECTED,
'Unknown reason'
);
});
});
describe('UNKNOWN', () => {
it('should call SessionCommands.updateStatus', () => {
const UNKNOWN = 1;
webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.UNKNOWN = UNKNOWN;
const data: ConnectionClosedData = { endTime: 0, reason: UNKNOWN, reasonStr: null };
SessionEvents['.Event_ConnectionClosed.ext'](data);
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(
StatusEnum.DISCONNECTED,
'Unknown reason'
);
});
});
});
});
describe('.Event_ListRooms.ext', () => {
beforeEach(() => {
webClient.options.autojoinrooms = false;
spyOn(RoomPersistence, 'updateRooms');
});
it('should call RoomPersistence.updateRooms', () => {
const data: ListRoomsData = { roomList: [{ roomId, autoJoin: false } as any] };
SessionEvents['.Event_ListRooms.ext'](data);
expect(RoomPersistence.updateRooms).toHaveBeenCalledWith(data.roomList);
});
it('should call SessionCommands.joinRoom if webClient and room is configured for autojoin', () => {
webClient.options.autojoinrooms = true;
spyOn(SessionCommands, 'joinRoom');
const data: ListRoomsData = { roomList: [{ roomId, autoJoin: true } as any, { roomId: 2, autoJoin: false } as any] };
SessionEvents['.Event_ListRooms.ext'](data);
expect(SessionCommands.joinRoom).toHaveBeenCalledTimes(1);
expect(SessionCommands.joinRoom).toHaveBeenCalledWith(data.roomList[0].roomId);
});
});
describe('.Event_RemoveFromList.ext', () => {
it('should call SessionPersistence.removeFromBuddyList if buddy listName', () => {
spyOn(SessionPersistence, 'removeFromBuddyList');
const data: RemoveFromListData = { listName: 'buddy', userName: '' };
SessionEvents['.Event_RemoveFromList.ext'](data);
expect(SessionPersistence.removeFromBuddyList).toHaveBeenCalledWith(
data.userName
);
});
it('should call SessionPersistence.removeFromIgnoreList if ignore listName', () => {
spyOn(SessionPersistence, 'removeFromIgnoreList');
const data: RemoveFromListData = { listName: 'ignore', userName: '' };
SessionEvents['.Event_RemoveFromList.ext'](data);
expect(SessionPersistence.removeFromIgnoreList).toHaveBeenCalledWith(
data.userName
);
});
it('should call console.log if unknown listName', () => {
spyOn(console, 'log');
const data: RemoveFromListData = { listName: 'unknown', userName: '' };
SessionEvents['.Event_RemoveFromList.ext'](data);
expect(console.log).toHaveBeenCalledWith(
`Attempted to remove from unknown list: ${data.listName}`
);
});
});
describe('.Event_ServerIdentification.ext', () => {
it('update status/info and login', () => {
spyOn(SessionPersistence, 'updateInfo');
spyOn(SessionCommands, 'login');
webClient.protocolVersion = 0;
const data: ServerIdentificationData = {
serverName: 'serverName',
serverVersion: 'serverVersion',
protocolVersion: 0,
};
SessionEvents['.Event_ServerIdentification.ext'](data);
expect(SessionPersistence.updateInfo).toHaveBeenCalledWith(data.serverName, data.serverVersion);
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.LOGGINGIN, 'Logging in...');
expect(SessionCommands.login).toHaveBeenCalled();
});
it('should disconnect if protocolVersion mismatched', () => {
spyOn(SessionCommands, 'login');
spyOn(SessionCommands, 'disconnect');
webClient.protocolVersion = 0;
const data: ServerIdentificationData = {
serverName: '',
serverVersion: '',
protocolVersion: 1,
};
SessionEvents['.Event_ServerIdentification.ext'](data);
expect(SessionCommands.disconnect).toHaveBeenCalled();
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, `Protocol version mismatch: ${data.protocolVersion}`);
expect(SessionCommands.login).not.toHaveBeenCalled();
});
});
describe('.Event_ServerMessage.ext', () => {
it('should call SessionPersistence.serverMessage', () => {
spyOn(SessionPersistence, 'serverMessage');
const data: ServerMessageData = { message: 'message' };
SessionEvents['.Event_ServerMessage.ext'](data);
expect(SessionPersistence.serverMessage).toHaveBeenCalledWith(
data.message
);
});
});
describe('.Event_UserJoined.ext', () => {
it('should call SessionPersistence.userJoined', () => {
spyOn(SessionPersistence, 'userJoined');
const data: UserJoinedData = { userInfo: {} as any };
SessionEvents['.Event_UserJoined.ext'](data);
expect(SessionPersistence.userJoined).toHaveBeenCalledWith(
data.userInfo
);
});
});
describe('.Event_UserLeft.ext', () => {
it('should call SessionPersistence.userLeft', () => {
spyOn(SessionPersistence, 'userLeft');
const data: UserLeftData = { name: '' };
SessionEvents['.Event_UserLeft.ext'](data);
expect(SessionPersistence.userLeft).toHaveBeenCalledWith(
data.name
);
});
});
});

View file

@ -1,23 +1,23 @@
import { Room, StatusEnum, User } from "types";
import { Room, StatusEnum, User } from 'types';
import { SessionCommands } from "../commands";
import { SessionCommands } from '../commands';
import { RoomPersistence, SessionPersistence } from '../persistence';
import { ProtobufEvents } from '../services/ProtobufService';
import webClient from '../WebClient';
export const SessionEvents: ProtobufEvents = {
".Event_AddToList.ext": addToList,
".Event_ConnectionClosed.ext": connectionClosed,
".Event_ListRooms.ext": listRooms,
".Event_NotifyUser.ext": notifyUser,
".Event_PlayerPropertiesChanges.ext": playerPropertiesChanges,
".Event_RemoveFromList.ext": removeFromList,
".Event_ServerIdentification.ext": serverIdentification,
".Event_ServerMessage.ext": serverMessage,
".Event_ServerShutdown.ext": serverShutdown,
".Event_UserJoined.ext": userJoined,
".Event_UserLeft.ext": userLeft,
".Event_UserMessage.ext": userMessage,
'.Event_AddToList.ext': addToList,
'.Event_ConnectionClosed.ext': connectionClosed,
'.Event_ListRooms.ext': listRooms,
'.Event_NotifyUser.ext': notifyUser,
'.Event_PlayerPropertiesChanges.ext': playerPropertiesChanges,
'.Event_RemoveFromList.ext': removeFromList,
'.Event_ServerIdentification.ext': serverIdentification,
'.Event_ServerMessage.ext': serverMessage,
'.Event_ServerShutdown.ext': serverShutdown,
'.Event_UserJoined.ext': userJoined,
'.Event_UserLeft.ext': userLeft,
'.Event_UserMessage.ext': userMessage,
}
function addToList({ listName, userInfo}: AddToListData) {
@ -31,13 +31,13 @@ function addToList({ listName, userInfo}: AddToListData) {
break;
}
default: {
console.log('Attempted to add to unknown list: ', listName);
console.log(`Attempted to add to unknown list: ${listName}`);
}
}
}
function connectionClosed({ reason, reasonStr }: ConnectionClosedData) {
let message = "";
let message = '';
// @TODO (5)
if (reasonStr) {
@ -45,29 +45,29 @@ function connectionClosed({ reason, reasonStr }: ConnectionClosedData) {
} else {
switch(reason) {
case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.USER_LIMIT_REACHED:
message = "The server has reached its maximum user capacity";
message = 'The server has reached its maximum user capacity';
break;
case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.TOO_MANY_CONNECTIONS:
message = "There are too many concurrent connections from your address";
message = 'There are too many concurrent connections from your address';
break;
case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.BANNED:
message = "You are banned";
message = 'You are banned';
break;
case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.DEMOTED:
message = "You were demoted";
message = 'You were demoted';
break;
case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.SERVER_SHUTDOWN:
message = "Scheduled server shutdown";
message = 'Scheduled server shutdown';
break;
case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.USERNAMEINVALID:
message = "Invalid username";
message = 'Invalid username';
break;
case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.LOGGEDINELSEWERE:
message = "You have been logged out due to logging in at another location";
message = 'You have been logged out due to logging in at another location';
break;
case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.OTHER:
default:
message = "Unknown reason";
message = 'Unknown reason';
break;
}
}
@ -88,11 +88,11 @@ function listRooms({ roomList }: ListRoomsData) {
}
function notifyUser(payload) {
// console.info("Event_NotifyUser", payload);
// console.info('Event_NotifyUser', payload);
}
function playerPropertiesChanges(payload) {
// console.info("Event_PlayerPropertiesChanges", payload);
// console.info('Event_PlayerPropertiesChanges', payload);
}
function removeFromList({ listName, userName }: RemoveFromListData) {
@ -106,7 +106,7 @@ function removeFromList({ listName, userName }: RemoveFromListData) {
break;
}
default: {
console.log('Attempted to remove from unknown list: ', listName);
console.log(`Attempted to remove from unknown list: ${listName}`);
}
}
}
@ -116,13 +116,12 @@ function serverIdentification(info: ServerIdentificationData) {
if (protocolVersion !== webClient.protocolVersion) {
SessionCommands.disconnect();
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, "Protocol version mismatch: " + protocolVersion);
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, `Protocol version mismatch: ${protocolVersion}`);
return;
}
webClient.resetConnectionvars();
SessionPersistence.updateInfo(serverName, serverVersion);
SessionCommands.updateStatus(StatusEnum.LOGGINGIN, "Logging in...");
SessionCommands.updateStatus(StatusEnum.LOGGINGIN, 'Logging in...');
SessionCommands.login();
}
@ -131,7 +130,7 @@ function serverMessage({ message }: ServerMessageData) {
}
function serverShutdown(payload) {
// console.info("Event_ServerShutdown", payload);
// console.info('Event_ServerShutdown', payload);
}
function userJoined({ userInfo }: UserJoinedData) {
@ -143,47 +142,47 @@ function userLeft({ name }: UserLeftData) {
}
function userMessage(payload) {
// console.info("Event_UserMessage", payload);
// console.info('Event_UserMessage', payload);
}
interface SessionEvent {
export interface SessionEvent {
sessionEvent: {}
}
interface AddToListData {
export interface AddToListData {
listName: string;
userInfo: User;
}
interface ConnectionClosedData {
export interface ConnectionClosedData {
endTime: number;
reason: number;
reasonStr: string;
}
interface ListRoomsData {
export interface ListRoomsData {
roomList: Room[];
}
interface RemoveFromListData {
export interface RemoveFromListData {
listName: string;
userName: string;
}
interface ServerIdentificationData {
export interface ServerIdentificationData {
protocolVersion: number;
serverName: string;
serverVersion: string;
}
interface ServerMessageData {
export interface ServerMessageData {
message: string;
}
interface UserJoinedData {
export interface UserJoinedData {
userInfo: User;
}
interface UserLeftData {
export interface UserLeftData {
name: string;
}