diff --git a/webclient/src/websocket/WebClient.ts b/webclient/src/websocket/WebClient.ts index 46021b3f..2878df21 100644 --- a/webclient/src/websocket/WebClient.ts +++ b/webclient/src/websocket/WebClient.ts @@ -69,14 +69,13 @@ export class WebClient { SessionPersistence.updateStatus(status, description); if (status === StatusEnum.DISCONNECTED) { - this.resetConnectionvars(); + this.protobuf.resetCommands(); this.clearStores(); } } - public resetConnectionvars() { - this.protobuf.resetCommands(); - this.socket.keepAliveService.resetPingFlag(); + public keepAlive(pingReceived: Function) { + this.protobuf.sendKeepAliveCommand(pingReceived); } private clearStores() { diff --git a/webclient/src/websocket/commands/RoomCommands.ts b/webclient/src/websocket/commands/RoomCommands.ts index e6173cfe..81d1d4fd 100644 --- a/webclient/src/websocket/commands/RoomCommands.ts +++ b/webclient/src/websocket/commands/RoomCommands.ts @@ -2,7 +2,7 @@ import { RoomPersistence } from '../persistence'; import webClient from "../WebClient"; export class RoomCommands { - static roomSay(roomId: number, message: string) { + static roomSay(roomId: number, message: string): void { const trimmed = message.trim(); if (!trimmed) return; @@ -18,7 +18,7 @@ export class RoomCommands { webClient.protobuf.sendRoomCommand(roomId, rc); } - static leaveRoom(roomId: number) { + static leaveRoom(roomId: number): void { var CmdLeaveRoom = webClient.protobuf.controller.Command_LeaveRoom.create(); var rc = webClient.protobuf.controller.RoomCommand.create({ diff --git a/webclient/src/websocket/commands/SessionCommands.ts b/webclient/src/websocket/commands/SessionCommands.ts index e3cb111f..3cd987d2 100644 --- a/webclient/src/websocket/commands/SessionCommands.ts +++ b/webclient/src/websocket/commands/SessionCommands.ts @@ -6,17 +6,17 @@ import webClient from '../WebClient'; import { guid } from '../utils'; export class SessionCommands { - static connect(options: ServerConnectParams) { + static connect(options: ServerConnectParams): void { SessionCommands.updateStatus(StatusEnum.CONNECTING, 'Connecting...'); webClient.connect(options); } - static disconnect() { + static disconnect(): void { SessionCommands.updateStatus(StatusEnum.DISCONNECTING, 'Disconnecting...'); webClient.disconnect(); } - static login() { + static login(): void { const loginConfig = { ...webClient.clientConfig, userName: webClient.options.user, @@ -86,7 +86,7 @@ export class SessionCommands { }); } - static listUsers() { + static listUsers(): void { const CmdListUsers = webClient.protobuf.controller.Command_ListUsers.create(); const sc = webClient.protobuf.controller.SessionCommand.create({ @@ -110,7 +110,7 @@ export class SessionCommands { }); } - static listRooms() { + static listRooms(): void { const CmdListRooms = webClient.protobuf.controller.Command_ListRooms.create(); const sc = webClient.protobuf.controller.SessionCommand.create({ @@ -120,7 +120,7 @@ export class SessionCommands { webClient.protobuf.sendSessionCommand(sc); } - static joinRoom(roomId: number) { + static joinRoom(roomId: number): void { const CmdJoinRoom = webClient.protobuf.controller.Command_JoinRoom.create({ roomId }); const sc = webClient.protobuf.controller.SessionCommand.create({ @@ -158,23 +158,23 @@ export class SessionCommands { }); } - static addToBuddyList(userName: string) { + static addToBuddyList(userName: string): void { this.addToList('buddy', userName); } - static removeFromBuddyList(userName: string) { + static removeFromBuddyList(userName: string): void { this.removeFromList('buddy', userName); } - static addToIgnoreList(userName: string) { + static addToIgnoreList(userName: string): void { this.addToList('ignore', userName); } - static removeFromIgnoreList(userName: string) { + static removeFromIgnoreList(userName: string): void { this.removeFromList('ignore', userName); } - static addToList(list: string, userName: string) { + static addToList(list: string, userName: string): void { const CmdAddToList = webClient.protobuf.controller.Command_AddToList.create({ list, userName }); const sc = webClient.protobuf.controller.SessionCommand.create({ @@ -186,7 +186,7 @@ export class SessionCommands { }); } - static removeFromList(list: string, userName: string) { + static removeFromList(list: string, userName: string): void { const CmdRemoveFromList = webClient.protobuf.controller.Command_RemoveFromList.create({ list, userName }); const sc = webClient.protobuf.controller.SessionCommand.create({ @@ -198,7 +198,7 @@ export class SessionCommands { }); } - static viewLogHistory(filters) { + static viewLogHistory(filters): void { const CmdViewLogHistory = webClient.protobuf.controller.Command_ViewLogHistory.create(filters); const sc = webClient.protobuf.controller.ModeratorCommand.create({ @@ -226,7 +226,7 @@ export class SessionCommands { }); } - static updateStatus(status: StatusEnum, description: string) { + static updateStatus(status: StatusEnum, description: string): void { webClient.updateStatus(status, description); } } diff --git a/webclient/src/websocket/events/RoomEvents.spec.ts b/webclient/src/websocket/events/RoomEvents.spec.ts new file mode 100644 index 00000000..2176b123 --- /dev/null +++ b/webclient/src/websocket/events/RoomEvents.spec.ts @@ -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 + ); + }); +}); \ No newline at end of file diff --git a/webclient/src/websocket/events/RoomEvents.ts b/webclient/src/websocket/events/RoomEvents.ts index b6b6c9da..f5f79b62 100644 --- a/webclient/src/websocket/events/RoomEvents.ts +++ b/webclient/src/websocket/events/RoomEvents.ts @@ -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[]; } diff --git a/webclient/src/websocket/events/SessionEvents.spec.ts b/webclient/src/websocket/events/SessionEvents.spec.ts new file mode 100644 index 00000000..f01547b1 --- /dev/null +++ b/webclient/src/websocket/events/SessionEvents.spec.ts @@ -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 + ); + }); + }); +}); diff --git a/webclient/src/websocket/events/SessionEvents.ts b/webclient/src/websocket/events/SessionEvents.ts index c2002c22..00fc0ee0 100644 --- a/webclient/src/websocket/events/SessionEvents.ts +++ b/webclient/src/websocket/events/SessionEvents.ts @@ -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; } diff --git a/webclient/src/websocket/services/KeepAliveService.spec.ts b/webclient/src/websocket/services/KeepAliveService.spec.ts new file mode 100644 index 00000000..4fc57fe1 --- /dev/null +++ b/webclient/src/websocket/services/KeepAliveService.spec.ts @@ -0,0 +1,71 @@ +import { KeepAliveService } from './KeepAliveService'; +import { WebSocketService } from "./WebSocketService"; + +import webClient from '../WebClient'; + +describe('KeepAliveService', () => { + let service: KeepAliveService; + let socket: WebSocketService; + + beforeEach(() => { + jest.useFakeTimers(); + + socket = new WebSocketService(webClient); + service = new KeepAliveService(socket); + }); + + it('should create', () => { + expect(service).toBeDefined(); + }); + + describe('startPingLoop', () => { + let resolvePing; + let interval; + let promise; + let ping; + let checkReadyStateSpy; + + beforeEach(() => { + interval = 100; + promise = new Promise(resolve => resolvePing = resolve); + ping = (done) => promise.then(done); + + checkReadyStateSpy = spyOn(socket, 'checkReadyState'); + checkReadyStateSpy.and.returnValue(true); + + service.startPingLoop(interval, ping); + jest.advanceTimersByTime(interval); + }); + + it('should start ping loop', () => { + expect((service as any).keepalivecb).toBeDefined(); + expect((service as any).lastPingPending).toBeTruthy(); + }); + + it('should call ping callback when done', (done: jest.DoneCallback) => { + resolvePing(); + + promise.then(() => { + expect((service as any).lastPingPending).toBeFalsy(); + done(); + }); + }); + + it('should fire disconnected$ if lastPingPending is still true', () => { + spyOn(service.disconnected$, 'next'); + jest.advanceTimersByTime(interval); + + expect(service.disconnected$.next).toHaveBeenCalled(); + }); + + it('should endPingLoop if socket is not open', () => { + spyOn(service, 'endPingLoop'); + checkReadyStateSpy.and.returnValue(false); + + resolvePing(); + jest.advanceTimersByTime(interval); + + expect(service.endPingLoop).toHaveBeenCalled(); + }); + }); +}); diff --git a/webclient/src/websocket/services/KeepAliveService.ts b/webclient/src/websocket/services/KeepAliveService.ts index b0ab2ee8..65d135db 100644 --- a/webclient/src/websocket/services/KeepAliveService.ts +++ b/webclient/src/websocket/services/KeepAliveService.ts @@ -35,9 +35,6 @@ export class KeepAliveService { public endPingLoop() { clearInterval(this.keepalivecb); this.keepalivecb = null; - } - - public resetPingFlag() { this.lastPingPending = false; } } diff --git a/webclient/src/websocket/services/ProtobufService.ts b/webclient/src/websocket/services/ProtobufService.ts index c6eb0b3d..515a8f42 100644 --- a/webclient/src/websocket/services/ProtobufService.ts +++ b/webclient/src/websocket/services/ProtobufService.ts @@ -65,6 +65,14 @@ export class ProtobufService { } } + public sendKeepAliveCommand(pingReceived: Function) { + const command = this.controller.SessionCommand.create({ + ".Command_Ping.ext" : this.controller.Command_Ping.create() + }); + + this.sendSessionCommand(command, pingReceived); + } + public handleMessageEvent({ data }: MessageEvent): void { try { const uint8msg = new Uint8Array(data); diff --git a/webclient/src/websocket/services/WebSocketService.ts b/webclient/src/websocket/services/WebSocketService.ts index 10701f06..0818d679 100644 --- a/webclient/src/websocket/services/WebSocketService.ts +++ b/webclient/src/websocket/services/WebSocketService.ts @@ -17,7 +17,7 @@ export interface WebSocketOptions { export class WebSocketService { private socket: WebSocket; private webClient: WebClient; - public keepAliveService: KeepAliveService; + private keepAliveService: KeepAliveService; public message$: Subject = new Subject(); public statusChange$: Subject = new Subject(); @@ -36,6 +36,10 @@ export class WebSocketService { } public connect(options: WebSocketOptions, protocol: string = 'wss'): void { + if (window.location.hostname === 'localhost') { + protocol = 'ws'; + } + const { host, port, keepalive } = options; this.keepalive = keepalive; @@ -49,7 +53,7 @@ export class WebSocketService { } public checkReadyState(state: number): boolean { - return this.socket.readyState === state; + return this.socket?.readyState === state; } public send(message): void { @@ -69,13 +73,7 @@ export class WebSocketService { this.updateStatus(StatusEnum.CONNECTED, "Connected"); this.keepAliveService.startPingLoop(this.keepalive, (pingReceived: Function) => { - const command = this.webClient.protobuf.controller.SessionCommand.create({ - ".Command_Ping.ext" : this.webClient.protobuf.controller.Command_Ping.create() - }); - - this.webClient.protobuf.sendSessionCommand(command, () => { - pingReceived(); - }); + this.webClient.keepAlive(pingReceived); }); }; diff --git a/webclient/src/websocket/utils/NormalizeService.tsx b/webclient/src/websocket/utils/NormalizeService.ts similarity index 100% rename from webclient/src/websocket/utils/NormalizeService.tsx rename to webclient/src/websocket/utils/NormalizeService.ts diff --git a/webclient/src/websocket/utils/guid.util.tsx b/webclient/src/websocket/utils/guid.util.ts similarity index 100% rename from webclient/src/websocket/utils/guid.util.tsx rename to webclient/src/websocket/utils/guid.util.ts diff --git a/webclient/src/websocket/utils/index.tsx b/webclient/src/websocket/utils/index.ts similarity index 100% rename from webclient/src/websocket/utils/index.tsx rename to webclient/src/websocket/utils/index.ts diff --git a/webclient/src/websocket/utils/sanitizeHtml.util.tsx b/webclient/src/websocket/utils/sanitizeHtml.util.ts similarity index 100% rename from webclient/src/websocket/utils/sanitizeHtml.util.tsx rename to webclient/src/websocket/utils/sanitizeHtml.util.ts