From e9ba195d7df9c3337604c1b115ca91cbb4265c6f Mon Sep 17 00:00:00 2001 From: Jeremy Letto Date: Sun, 17 Oct 2021 00:07:30 -0500 Subject: [PATCH] Refactor websocket into separate services, clean up socket status communication (#4433) * Refactor websocket into separate services, clean up socket status communication * cleanup * add EOF lines * fix keepalive logged in check * undo change * fix keepalive connection check * cleanup * add typings * secure connection Co-authored-by: Jeremy Letto --- webclient/src/api/AuthenticationService.tsx | 24 +- webclient/src/api/ModeratorService.tsx | 8 +- webclient/src/api/RoomsService.tsx | 16 +- webclient/src/api/RouterService.tsx | 2 +- webclient/src/api/SessionService.tsx | 20 +- webclient/src/api/index.ts | 2 +- webclient/src/store/server/server.actions.ts | 3 - webclient/src/store/server/server.dispatch.ts | 3 - webclient/src/store/server/server.types.ts | 1 - webclient/src/types/game.tsx | 11 +- webclient/src/types/index.ts | 1 + webclient/src/types/message.tsx | 7 + webclient/src/types/room.tsx | 4 +- webclient/src/types/server.tsx | 13 +- webclient/src/types/sort.tsx | 2 +- webclient/src/types/user.tsx | 10 +- webclient/src/websocket/WebClient.tsx | 298 ++---------------- .../src/websocket/commands/RoomCommands.tsx | 32 +- .../websocket/commands/SessionCommands.tsx | 166 +++++----- webclient/src/websocket/commands/index.tsx | 4 +- webclient/src/websocket/events/RoomEvents.tsx | 49 +++ .../websocket/events/RoomEvents/JoinRoom.tsx | 7 - .../websocket/events/RoomEvents/LeaveRoom.tsx | 7 - .../websocket/events/RoomEvents/ListGames.tsx | 7 - .../websocket/events/RoomEvents/RoomSay.tsx | 7 - .../src/websocket/events/RoomEvents/index.tsx | 4 - .../src/websocket/events/SessionEvents.tsx | 189 +++++++++++ .../events/SessionEvents/AddToList.tsx | 18 -- .../events/SessionEvents/ConnectionClosed.tsx | 39 --- .../events/SessionEvents/ListRooms.tsx | 16 - .../events/SessionEvents/NotifyUser.tsx | 6 - .../SessionEvents/PlayerPropertiesChanges.tsx | 6 - .../events/SessionEvents/RemoveFromList.tsx | 18 -- .../SessionEvents/ServerIdentification.tsx | 19 -- .../events/SessionEvents/ServerMessage.tsx | 6 - .../events/SessionEvents/ServerShutdown.tsx | 6 - .../events/SessionEvents/UserJoined.tsx | 6 - .../events/SessionEvents/UserLeft.tsx | 6 - .../events/SessionEvents/UserMessage.tsx | 6 - .../websocket/events/SessionEvents/index.ts | 12 - webclient/src/websocket/events/index.ts | 2 + webclient/src/websocket/index.ts | 6 +- .../{RoomService.tsx => RoomPersistence.tsx} | 29 +- .../persistence/SessionPersistence.tsx | 75 +++++ .../websocket/persistence/SessionService.tsx | 94 ------ webclient/src/websocket/persistence/index.ts | 5 +- .../websocket/services/KeepAliveService.tsx | 43 +++ .../websocket/services/ProtobufService.tsx | 132 ++++++++ .../websocket/services/WebSocketService.tsx | 101 ++++++ .../NormalizeService.tsx | 12 +- webclient/src/websocket/utils/guid.util.tsx | 4 +- .../src/websocket/utils/sanitizeHtml.util.tsx | 8 +- 52 files changed, 815 insertions(+), 757 deletions(-) create mode 100644 webclient/src/types/message.tsx create mode 100644 webclient/src/websocket/events/RoomEvents.tsx delete mode 100644 webclient/src/websocket/events/RoomEvents/JoinRoom.tsx delete mode 100644 webclient/src/websocket/events/RoomEvents/LeaveRoom.tsx delete mode 100644 webclient/src/websocket/events/RoomEvents/ListGames.tsx delete mode 100644 webclient/src/websocket/events/RoomEvents/RoomSay.tsx delete mode 100644 webclient/src/websocket/events/RoomEvents/index.tsx create mode 100644 webclient/src/websocket/events/SessionEvents.tsx delete mode 100644 webclient/src/websocket/events/SessionEvents/AddToList.tsx delete mode 100644 webclient/src/websocket/events/SessionEvents/ConnectionClosed.tsx delete mode 100644 webclient/src/websocket/events/SessionEvents/ListRooms.tsx delete mode 100644 webclient/src/websocket/events/SessionEvents/NotifyUser.tsx delete mode 100644 webclient/src/websocket/events/SessionEvents/PlayerPropertiesChanges.tsx delete mode 100644 webclient/src/websocket/events/SessionEvents/RemoveFromList.tsx delete mode 100644 webclient/src/websocket/events/SessionEvents/ServerIdentification.tsx delete mode 100644 webclient/src/websocket/events/SessionEvents/ServerMessage.tsx delete mode 100644 webclient/src/websocket/events/SessionEvents/ServerShutdown.tsx delete mode 100644 webclient/src/websocket/events/SessionEvents/UserJoined.tsx delete mode 100644 webclient/src/websocket/events/SessionEvents/UserLeft.tsx delete mode 100644 webclient/src/websocket/events/SessionEvents/UserMessage.tsx delete mode 100644 webclient/src/websocket/events/SessionEvents/index.ts create mode 100644 webclient/src/websocket/events/index.ts rename webclient/src/websocket/persistence/{RoomService.tsx => RoomPersistence.tsx} (62%) create mode 100644 webclient/src/websocket/persistence/SessionPersistence.tsx delete mode 100644 webclient/src/websocket/persistence/SessionService.tsx create mode 100644 webclient/src/websocket/services/KeepAliveService.tsx create mode 100644 webclient/src/websocket/services/ProtobufService.tsx create mode 100644 webclient/src/websocket/services/WebSocketService.tsx rename webclient/src/websocket/{persistence => utils}/NormalizeService.tsx (77%) diff --git a/webclient/src/api/AuthenticationService.tsx b/webclient/src/api/AuthenticationService.tsx index ba7af5ae..153c853f 100644 --- a/webclient/src/api/AuthenticationService.tsx +++ b/webclient/src/api/AuthenticationService.tsx @@ -1,20 +1,22 @@ -import { StatusEnum } from "types"; -import { webClient } from "websocket"; +import { ServerConnectParams } from "store"; +import { StatusEnum, User} from "types"; +import { webClient, SessionCommands } from "websocket"; export default class AuthenticationService { - static connect(options) { - webClient.persistence.session.connectServer(options); - } - static disconnect() { - webClient.persistence.session.disconnectServer(); + static connect(options: ServerConnectParams): void { + SessionCommands.connect(options); } - static isConnected(state) { + static disconnect(): void { + SessionCommands.disconnect(); + } + + static isConnected(state: number): boolean { return state === StatusEnum.LOGGEDIN; } - static isModerator(user) { - const moderatorLevel = webClient.pb.ServerInfo_User.UserLevelFlag.IsModerator; + static isModerator(user: User): boolean { + const moderatorLevel = webClient.protobuf.controller.ServerInfo_User.UserLevelFlag.IsModerator; // @TODO tell cockatrice not to do this so shittily return (user.userLevel & moderatorLevel) === moderatorLevel; } @@ -22,4 +24,4 @@ export default class AuthenticationService { static isAdmin() { } -} \ No newline at end of file +} diff --git a/webclient/src/api/ModeratorService.tsx b/webclient/src/api/ModeratorService.tsx index e19f25e9..e748f6b1 100644 --- a/webclient/src/api/ModeratorService.tsx +++ b/webclient/src/api/ModeratorService.tsx @@ -1,7 +1,7 @@ -import { webClient } from "websocket"; +import { SessionCommands } from "websocket"; export default class ModeratorService { - static viewLogHistory(filters) { - webClient.commands.session.viewLogHistory(filters); + static viewLogHistory(filters): void { + SessionCommands.viewLogHistory(filters); } -} \ No newline at end of file +} diff --git a/webclient/src/api/RoomsService.tsx b/webclient/src/api/RoomsService.tsx index 487215c1..711f9539 100644 --- a/webclient/src/api/RoomsService.tsx +++ b/webclient/src/api/RoomsService.tsx @@ -1,15 +1,15 @@ -import { webClient } from "websocket"; +import { RoomCommands, SessionCommands } from "websocket"; export default class RoomsService { - static joinRoom(roomId) { - webClient.commands.session.joinRoom(roomId); + static joinRoom(roomId: number): void { + SessionCommands.joinRoom(roomId); } - static leaveRoom(roomId) { - webClient.commands.room.leaveRoom(roomId); + static leaveRoom(roomId: number): void { + RoomCommands.leaveRoom(roomId); } - static roomSay(roomId, message) { - webClient.commands.room.roomSay(roomId, message); + static roomSay(roomId: number, message: string): void { + RoomCommands.roomSay(roomId, message); } -} \ No newline at end of file +} diff --git a/webclient/src/api/RouterService.tsx b/webclient/src/api/RouterService.tsx index 4f4ca776..889afaad 100644 --- a/webclient/src/api/RouterService.tsx +++ b/webclient/src/api/RouterService.tsx @@ -4,4 +4,4 @@ export class RouterService { resolveUrl(path, params) { } -} \ No newline at end of file +} diff --git a/webclient/src/api/SessionService.tsx b/webclient/src/api/SessionService.tsx index e7af9615..5f2cebd5 100644 --- a/webclient/src/api/SessionService.tsx +++ b/webclient/src/api/SessionService.tsx @@ -1,19 +1,19 @@ -import { webClient } from "websocket"; +import { SessionCommands } from "websocket"; export default class SessionService { - static addToBuddyList(userName) { - webClient.commands.session.addToBuddyList(userName); + static addToBuddyList(userName: string) { + SessionCommands.addToBuddyList(userName); } - static removeFromBuddyList(userName) { - webClient.commands.session.removeFromBuddyList(userName); + static removeFromBuddyList(userName: string) { + SessionCommands.removeFromBuddyList(userName); } - static addToIgnoreList(userName) { - webClient.commands.session.addToIgnoreList(userName); + static addToIgnoreList(userName: string) { + SessionCommands.addToIgnoreList(userName); } - static removeFromIgnoreList(userName) { - webClient.commands.session.removeFromIgnoreList(userName); + static removeFromIgnoreList(userName: string) { + SessionCommands.removeFromIgnoreList(userName); } -} \ No newline at end of file +} diff --git a/webclient/src/api/index.ts b/webclient/src/api/index.ts index fe3bddb9..a6ae98a0 100644 --- a/webclient/src/api/index.ts +++ b/webclient/src/api/index.ts @@ -1,4 +1,4 @@ export { default as AuthenticationService } from "./AuthenticationService"; export { default as ModeratorService } from "./ModeratorService"; export { default as RoomsService } from "./RoomsService"; -export { default as SessionService } from "./SessionService"; \ No newline at end of file +export { default as SessionService } from "./SessionService"; diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts index 17a06d82..ff7abb27 100644 --- a/webclient/src/store/server/server.actions.ts +++ b/webclient/src/store/server/server.actions.ts @@ -4,9 +4,6 @@ export const Actions = { clearStore: () => ({ type: Types.CLEAR_STORE }), - connectServer: () => ({ - type: Types.CONNECT_SERVER - }), connectionClosed: reason => ({ type: Types.CONNECTION_CLOSED, reason diff --git a/webclient/src/store/server/server.dispatch.ts b/webclient/src/store/server/server.dispatch.ts index 51841d31..35ce38f0 100644 --- a/webclient/src/store/server/server.dispatch.ts +++ b/webclient/src/store/server/server.dispatch.ts @@ -6,9 +6,6 @@ export const Dispatch = { clearStore: () => { store.dispatch(Actions.clearStore()); }, - connectServer: () => { - store.dispatch(Actions.connectServer()); - }, connectionClosed: reason => { store.dispatch(Actions.connectionClosed(reason)); }, diff --git a/webclient/src/store/server/server.types.ts b/webclient/src/store/server/server.types.ts index 5e5b1e13..3d644893 100644 --- a/webclient/src/store/server/server.types.ts +++ b/webclient/src/store/server/server.types.ts @@ -1,6 +1,5 @@ export const Types = { CLEAR_STORE: "[Server] Clear Store", - CONNECT_SERVER: "[Server] Connect Server", CONNECTION_CLOSED: "[Server] Connection Closed", SERVER_MESSAGE: "[Server] Server Message", UPDATE_BUDDY_LIST: "[Server] Update Buddy List", diff --git a/webclient/src/types/game.tsx b/webclient/src/types/game.tsx index 9f072bba..c432bf81 100644 --- a/webclient/src/types/game.tsx +++ b/webclient/src/types/game.tsx @@ -1,3 +1,12 @@ +export interface Game { + description: string; + gameId: number; + gameType: string; + gameTypes: string[]; + roomId: number; + started: boolean; +} + export enum GameSortField { START_TIME = "startTime" -} \ No newline at end of file +} diff --git a/webclient/src/types/index.ts b/webclient/src/types/index.ts index 9403d6f8..4f6358d3 100644 --- a/webclient/src/types/index.ts +++ b/webclient/src/types/index.ts @@ -8,3 +8,4 @@ export * from "./user"; export * from "./routes"; export * from "./sort"; export * from "./forms"; +export * from "./message"; diff --git a/webclient/src/types/message.tsx b/webclient/src/types/message.tsx new file mode 100644 index 00000000..d7f25601 --- /dev/null +++ b/webclient/src/types/message.tsx @@ -0,0 +1,7 @@ +export interface Message { + name: string; + message: string; + messageType: number; + timeOf: number; + timeReceived: number; +} diff --git a/webclient/src/types/room.tsx b/webclient/src/types/room.tsx index 535e474c..dd2d5f6d 100644 --- a/webclient/src/types/room.tsx +++ b/webclient/src/types/room.tsx @@ -6,7 +6,7 @@ export interface Room { gameCount: number; gameList: any[]; gametypeList: any[]; - gametypeMap: { [index: number]: string; }; + gametypeMap: GametypeMap; name: string; permissionlevel: RoomAccessLevel; playerCount: number; @@ -16,6 +16,8 @@ export interface Room { order: number; } +export interface GametypeMap { [index: number]: string } + export enum RoomAccessLevel { "none" } diff --git a/webclient/src/types/server.tsx b/webclient/src/types/server.tsx index 91819737..fc6e32a2 100644 --- a/webclient/src/types/server.tsx +++ b/webclient/src/types/server.tsx @@ -1,3 +1,8 @@ +export interface ServerStatus { + status: StatusEnum; + description: string; +} + export enum StatusEnum { DISCONNECTED = 0, CONNECTING = 1, @@ -43,4 +48,10 @@ export interface Log { targetName: string; targetType: string; time: string; -} \ No newline at end of file +} + +export interface LogGroups { + room: Log[]; + game: Log[]; + chat: Log[]; +} diff --git a/webclient/src/types/sort.tsx b/webclient/src/types/sort.tsx index d880f97e..c3863fa4 100644 --- a/webclient/src/types/sort.tsx +++ b/webclient/src/types/sort.tsx @@ -6,4 +6,4 @@ export enum SortDirection { export interface SortBy { field: string; order: SortDirection; -} \ No newline at end of file +} diff --git a/webclient/src/types/user.tsx b/webclient/src/types/user.tsx index af678d83..ada239b7 100644 --- a/webclient/src/types/user.tsx +++ b/webclient/src/types/user.tsx @@ -1,12 +1,12 @@ export interface User { accountageSecs: number; - avatarBmp: Uint8Array; - country: string; - gender: number; name: string; privlevel: UserAccessLevel; - realName: string; userLevel: UserPrivLevel; + gender?: number; + realName?: string; + country?: string; + avatarBmp?: Uint8Array; } export enum UserAccessLevel { @@ -22,4 +22,4 @@ export enum UserPrivLevel { export enum UserSortField { NAME = "name" -} \ No newline at end of file +} diff --git a/webclient/src/websocket/WebClient.tsx b/webclient/src/websocket/WebClient.tsx index 2b315ddf..499fbc98 100644 --- a/webclient/src/websocket/WebClient.tsx +++ b/webclient/src/websocket/WebClient.tsx @@ -1,42 +1,16 @@ -import protobuf from "protobufjs"; +import { ServerConnectParams } from "store"; +import { ServerStatus, StatusEnum } from "types"; -import { StatusEnum } from "types"; +import { ProtobufService } from './services/ProtobufService'; +import { WebSocketService, WebSocketOptions } from "./services/WebSocketService"; -import * as roomEvents from "./events/RoomEvents"; -import * as sessionEvents from "./events/SessionEvents"; - -import { RoomService, SessionService } from "./persistence"; -import { RoomCommand, SessionCommands } from "./commands"; - -import ProtoFiles from "./ProtoFiles"; - -const roomEventKeys = Object.keys(roomEvents); -const sessionEventKeys = Object.keys(sessionEvents); - -interface ApplicationCommands { - room: RoomCommand; - session: SessionCommands; -} - -interface ApplicationPersistence { - room: RoomService; - session: SessionService; -} +import { RoomPersistence, SessionPersistence } from './persistence'; export class WebClient { - private socket: WebSocket; - private status: StatusEnum = StatusEnum.DISCONNECTED; - private keepalivecb; - private lastPingPending = false; - private cmdId = 0; - private pendingCommands = {}; - - public commands: ApplicationCommands; - public persistence: ApplicationPersistence; + public socket = new WebSocketService(this); + public protobuf = new ProtobufService(this); public protocolVersion = 14; - public pb; - public clientConfig = { "clientver" : "webclient-1.0 (2019-10-31)", "clientfeatures" : [ @@ -57,274 +31,50 @@ export class WebClient { ] }; - public options: any = { + public options: WebSocketOptions = { host: "", port: "", user: "", pass: "", - debug: false, autojoinrooms: true, keepalive: 5000 }; constructor() { - const files = ProtoFiles.map(file => `${WebClient.PB_FILE_DIR}/${file}`); - - this.pb = new protobuf.Root(); - this.pb.load(files, { keepCase: false }, (err, root) => { - if (err) { - throw err; - } + this.socket.message$.subscribe((message: MessageEvent) => { + this.protobuf.handleMessageEvent(message); }); - // This sucks. I can"t seem to get out of this - // circular dependency trap, so this is my current best. - this.commands = { - room: new RoomCommand(this), - session: new SessionCommands(this), - }; - - this.persistence = { - room: new RoomService(this), - session: new SessionService(this), - }; + this.socket.statusChange$.subscribe((status: ServerStatus) => { + this.handleStatusChange(status); + }); console.log(this); } - private clearStores() { - this.persistence.room.clearStore(); - this.persistence.session.clearStore(); + public connect(options: ServerConnectParams) { + this.options = { ...this.options, ...options }; + this.socket.connect(this.options); } - public updateStatus(status, description) { - console.log(`Status: [${status}]: ${description}`); - this.status = status; - this.persistence.session.updateStatus(status, description); + public handleStatusChange({ status, description }: ServerStatus) { + SessionPersistence.updateStatus(status, description); if (status === StatusEnum.DISCONNECTED) { - this.clearStores(); - this.endPingLoop(); this.resetConnectionvars(); + this.clearStores(); } } public resetConnectionvars() { - this.cmdId = 0; - this.pendingCommands = {}; - this.lastPingPending = false; + this.protobuf.resetCommands(); + this.socket.keepAliveService.resetPingFlag(); } - public sendCommand(cmd, callback) { - this.cmdId++; - cmd["cmdId"] = this.cmdId; - - this.pendingCommands[this.cmdId] = callback; - - if (this.socket.readyState === WebSocket.OPEN) { - this.socket.send(this.pb.CommandContainer.encode(cmd).finish()); - this.debug(() => console.log("Sent: " + cmd.toString())); - } else { - this.debug(() => console.log("Send: Not connected")); - } + private clearStores() { + RoomPersistence.clearStore(); + SessionPersistence.clearStore(); } - - public sendRoomCommand(roomId, roomCmd, callback?) { - const cmd = this.pb.CommandContainer.create({ - "roomId" : roomId, - "roomCommand" : [ roomCmd ] - }); - - this.sendCommand(cmd, raw => { - this.debug(() => console.log(raw)); - - if (callback) { - callback(raw); - } - }); - } - - public sendSessionCommand(sesCmd, callback?) { - const cmd = this.pb.CommandContainer.create({ - "sessionCommand" : [ sesCmd ] - }); - - this.sendCommand(cmd, (raw) => { - this.debug(() => console.log(raw)); - - if (callback) { - callback(raw); - } - }); - } - - public sendModeratorCommand(modCmd, callback?) { - const cmd = this.pb.CommandContainer.create({ - "moderatorCommand" : [ modCmd ] - }); - - this.sendCommand(cmd, (raw) => { - this.debug(() => console.log(raw)); - - if (callback) { - callback(raw); - } - }); - } - - public startPingLoop() { - this.keepalivecb = setInterval(() => { - // check if the previous ping got no reply - if (this.lastPingPending) { - this.disconnect(); - - this.updateStatus(StatusEnum.DISCONNECTED, "Connection timeout"); - } - - // stop the ping loop if we"re disconnected - if (this.status !== StatusEnum.LOGGEDIN) { - this.endPingLoop(); - return; - } - - // send a ping - this.lastPingPending = true; - - const ping = this.pb.Command_Ping.create(); - const command = this.pb.SessionCommand.create({ - ".Command_Ping.ext" : ping - }); - - this.sendSessionCommand(command, () => { - - this.lastPingPending = false; - }); - }, this.options.keepalive); - } - - private endPingLoop() { - clearInterval(this.keepalivecb); - this.keepalivecb = null; - } - - public connect(options) { - this.options = { ...this.options, ...options }; - - const { host, port } = this.options; - - this.socket = new WebSocket("wss://" + host + ":" + port); - this.socket.binaryType = "arraybuffer"; // We are talking binary - - this.socket.onopen = () => { - this.updateStatus(StatusEnum.CONNECTED, "Connected"); - }; - - this.socket.onclose = () => { - // dont overwrite failure messages - if (this.status !== StatusEnum.DISCONNECTED) { - this.updateStatus(StatusEnum.DISCONNECTED, "Connection Closed"); - } - }; - - this.socket.onerror = () => { - this.updateStatus(StatusEnum.DISCONNECTED, "Connection Failed"); - }; - - - this.socket.onmessage = (event) => { - const msg = this.decodeServerMessage(event); - - if (msg) { - switch (msg.messageType) { - case this.pb.ServerMessage.MessageType.RESPONSE: - this.processServerResponse(msg.response); - break; - case this.pb.ServerMessage.MessageType.ROOM_EVENT: - this.processRoomEvent(msg.roomEvent, msg); - break; - case this.pb.ServerMessage.MessageType.SESSION_EVENT: - this.processSessionEvent(msg.sessionEvent, msg); - break; - case this.pb.ServerMessage.MessageType.GAME_EVENT_CONTAINER: - // @TODO - break; - } - } - } - } - - public disconnect() { - if (this.socket) { - this.socket.close(); - } - } - - public debug(debug) { - if (this.options.debug) { - debug(); - } - } - - private decodeServerMessage(event) { - const uint8msg = new Uint8Array(event.data); - let msg; - - try { - msg = this.pb.ServerMessage.decode(uint8msg); - - this.debug(() => console.log(msg)); - - return msg; - } catch (err) { - console.error("Processing failed:", err); - - this.debug(() => { - let str = ""; - - for (let i = 0; i < uint8msg.length; i++) { - str += String.fromCharCode(uint8msg[i]); - } - - console.log(str); - }); - - return; - } - } - - private processServerResponse(response) { - const cmdId = response.cmdId; - - if (!this.pendingCommands.hasOwnProperty(cmdId)) { - return; - } - - this.pendingCommands[cmdId](response); - delete this.pendingCommands[cmdId]; - } - - private processRoomEvent(response, raw) { - this.processEvent(response, roomEvents, roomEventKeys, raw); - } - - private processSessionEvent(response, raw) { - this.processEvent(response, sessionEvents, sessionEventKeys, raw); - } - - private processEvent(response, events, keys, raw) { - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - const event = events[key]; - const payload = response[event.id]; - - if (payload) { - events[key].action(payload, this, raw); - return; - } - } - } - - static PB_FILE_DIR = `${process.env.PUBLIC_URL}/pb`; } const webClient = new WebClient(); diff --git a/webclient/src/websocket/commands/RoomCommands.tsx b/webclient/src/websocket/commands/RoomCommands.tsx index 53227e6c..1aa32ce5 100644 --- a/webclient/src/websocket/commands/RoomCommands.tsx +++ b/webclient/src/websocket/commands/RoomCommands.tsx @@ -1,48 +1,42 @@ import * as _ from 'lodash'; -import { WebClient } from "../WebClient"; +import { RoomPersistence } from '../persistence'; +import webClient from "../WebClient"; -export default class RoomCommands { - private webClient: WebClient; - - constructor(webClient) { - this.webClient = webClient; - } - - roomSay(roomId, message) { +export class RoomCommands { + static roomSay(roomId: number, message: string) { const trimmed = _.trim(message); if (!trimmed) return; - var CmdRoomSay = this.webClient.pb.Command_RoomSay.create({ + var CmdRoomSay = webClient.protobuf.controller.Command_RoomSay.create({ "message" : trimmed }); - var rc = this.webClient.pb.RoomCommand.create({ + var rc = webClient.protobuf.controller.RoomCommand.create({ ".Command_RoomSay.ext" : CmdRoomSay }); - this.webClient.sendRoomCommand(roomId, rc); + webClient.protobuf.sendRoomCommand(roomId, rc); } - leaveRoom(roomId) { - var CmdLeaveRoom = this.webClient.pb.Command_LeaveRoom.create(); + static leaveRoom(roomId: number) { + var CmdLeaveRoom = webClient.protobuf.controller.Command_LeaveRoom.create(); - var rc = this.webClient.pb.RoomCommand.create({ + var rc = webClient.protobuf.controller.RoomCommand.create({ ".Command_LeaveRoom.ext" : CmdLeaveRoom }); - this.webClient.sendRoomCommand(roomId, rc, (raw) => { + webClient.protobuf.sendRoomCommand(roomId, rc, (raw) => { const { responseCode } = raw; switch (responseCode) { - case this.webClient.pb.Response.ResponseCode.RespOk: - this.webClient.persistence.room.leaveRoom(roomId); + case webClient.protobuf.controller.Response.ResponseCode.RespOk: + RoomPersistence.leaveRoom(roomId); break; default: console.log(`Failed to leave Room ${roomId} [${responseCode}] : `, raw); } }); - } } diff --git a/webclient/src/websocket/commands/SessionCommands.tsx b/webclient/src/websocket/commands/SessionCommands.tsx index eec572a9..c13135b2 100644 --- a/webclient/src/websocket/commands/SessionCommands.tsx +++ b/webclient/src/websocket/commands/SessionCommands.tsx @@ -1,103 +1,106 @@ +import { ServerConnectParams } from "store"; import { StatusEnum } from "types"; -import { WebClient } from "../WebClient"; +import { RoomPersistence, SessionPersistence } from '../persistence'; +import webClient from "../WebClient"; import { guid } from "../utils"; -export default class SessionCommands { - private webClient: WebClient; - - constructor(webClient) { - this.webClient = webClient; +export class SessionCommands { + static connect(options: ServerConnectParams) { + webClient.socket.updateStatus(StatusEnum.CONNECTING, "Connecting..."); + webClient.connect(options); } - login() { + static disconnect() { + webClient.socket.updateStatus(StatusEnum.DISCONNECTING, "Disconnecting..."); + webClient.socket.disconnect(); + } + + static login() { const loginConfig = { - ...this.webClient.clientConfig, - "userName" : this.webClient.options.user, - "password" : this.webClient.options.pass, + ...webClient.clientConfig, + "userName" : webClient.options.user, + "password" : webClient.options.pass, "clientid" : guid() }; - const CmdLogin = this.webClient.pb.Command_Login.create(loginConfig); + const CmdLogin = webClient.protobuf.controller.Command_Login.create(loginConfig); - const command = this.webClient.pb.SessionCommand.create({ + const command = webClient.protobuf.controller.SessionCommand.create({ ".Command_Login.ext" : CmdLogin }); - this.webClient.sendSessionCommand(command, raw => { + webClient.protobuf.sendSessionCommand(command, raw => { const resp = raw[".Response_Login.ext"]; - this.webClient.debug(() => console.log(".Response_Login.ext", resp)); - switch(raw.responseCode) { - case this.webClient.pb.Response.ResponseCode.RespOk: + case webClient.protobuf.controller.Response.ResponseCode.RespOk: const { buddyList, ignoreList, userInfo } = resp; - this.webClient.persistence.session.updateBuddyList(buddyList); - this.webClient.persistence.session.updateIgnoreList(ignoreList); - this.webClient.persistence.session.updateUser(userInfo); + SessionPersistence.updateBuddyList(buddyList); + SessionPersistence.updateIgnoreList(ignoreList); + SessionPersistence.updateUser(userInfo); - this.webClient.commands.session.listUsers(); - this.webClient.commands.session.listRooms(); + SessionCommands.listUsers(); + SessionCommands.listRooms(); - this.webClient.updateStatus(StatusEnum.LOGGEDIN, "Logged in."); - this.webClient.startPingLoop(); + webClient.socket.updateStatus(StatusEnum.LOGGEDIN, "Logged in."); break; - case this.webClient.pb.Response.ResponseCode.RespClientUpdateRequired: - this.webClient.updateStatus(StatusEnum.DISCONNECTED, "Login failed: missing features"); + case webClient.protobuf.controller.Response.ResponseCode.RespClientUpdateRequired: + webClient.socket.updateStatus(StatusEnum.DISCONNECTED, "Login failed: missing features"); break; - case this.webClient.pb.Response.ResponseCode.RespWrongPassword: - case this.webClient.pb.Response.ResponseCode.RespUsernameInvalid: - this.webClient.updateStatus(StatusEnum.DISCONNECTED, "Login failed: incorrect username or password"); + case webClient.protobuf.controller.Response.ResponseCode.RespWrongPassword: + case webClient.protobuf.controller.Response.ResponseCode.RespUsernameInvalid: + webClient.socket.updateStatus(StatusEnum.DISCONNECTED, "Login failed: incorrect username or password"); break; - case this.webClient.pb.Response.ResponseCode.RespWouldOverwriteOldSession: - this.webClient.updateStatus(StatusEnum.DISCONNECTED, "Login failed: duplicated user session"); + case webClient.protobuf.controller.Response.ResponseCode.RespWouldOverwriteOldSession: + webClient.socket.updateStatus(StatusEnum.DISCONNECTED, "Login failed: duplicated user session"); break; - case this.webClient.pb.Response.ResponseCode.RespUserIsBanned: - this.webClient.updateStatus(StatusEnum.DISCONNECTED, "Login failed: banned user"); + case webClient.protobuf.controller.Response.ResponseCode.RespUserIsBanned: + webClient.socket.updateStatus(StatusEnum.DISCONNECTED, "Login failed: banned user"); break; - case this.webClient.pb.Response.ResponseCode.RespRegistrationRequired: - this.webClient.updateStatus(StatusEnum.DISCONNECTED, "Login failed: registration required"); + case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationRequired: + webClient.socket.updateStatus(StatusEnum.DISCONNECTED, "Login failed: registration required"); break; - case this.webClient.pb.Response.ResponseCode.RespClientIdRequired: - this.webClient.updateStatus(StatusEnum.DISCONNECTED, "Login failed: missing client ID"); + case webClient.protobuf.controller.Response.ResponseCode.RespClientIdRequired: + webClient.socket.updateStatus(StatusEnum.DISCONNECTED, "Login failed: missing client ID"); break; - case this.webClient.pb.Response.ResponseCode.RespContextError: - this.webClient.updateStatus(StatusEnum.DISCONNECTED, "Login failed: server error"); + case webClient.protobuf.controller.Response.ResponseCode.RespContextError: + webClient.socket.updateStatus(StatusEnum.DISCONNECTED, "Login failed: server error"); break; - case this.webClient.pb.Response.ResponseCode.RespAccountNotActivated: - this.webClient.updateStatus(StatusEnum.DISCONNECTED, "Login failed: account not activated"); + case webClient.protobuf.controller.Response.ResponseCode.RespAccountNotActivated: + webClient.socket.updateStatus(StatusEnum.DISCONNECTED, "Login failed: account not activated"); break; default: - this.webClient.updateStatus(StatusEnum.DISCONNECTED, "Login failed: unknown error " + raw.responseCode); + webClient.socket.updateStatus(StatusEnum.DISCONNECTED, "Login failed: unknown error " + raw.responseCode); } }); } - listUsers() { - const CmdListUsers = this.webClient.pb.Command_ListUsers.create(); + static listUsers() { + const CmdListUsers = webClient.protobuf.controller.Command_ListUsers.create(); - const sc = this.webClient.pb.SessionCommand.create({ + const sc = webClient.protobuf.controller.SessionCommand.create({ ".Command_ListUsers.ext" : CmdListUsers }); - this.webClient.sendSessionCommand(sc, raw => { + webClient.protobuf.sendSessionCommand(sc, raw => { const { responseCode } = raw; const response = raw[".Response_ListUsers.ext"]; if (response) { switch (responseCode) { - case this.webClient.pb.Response.ResponseCode.RespOk: - this.webClient.persistence.session.updateUsers(response.userList); + case webClient.protobuf.controller.Response.ResponseCode.RespOk: + SessionPersistence.updateUsers(response.userList); break; default: console.log(`Failed to fetch Server Rooms [${responseCode}] : `, raw); @@ -107,44 +110,43 @@ export default class SessionCommands { }); } - listRooms() { - const CmdListRooms = this.webClient.pb.Command_ListRooms.create(); + static listRooms() { + const CmdListRooms = webClient.protobuf.controller.Command_ListRooms.create(); - const sc = this.webClient.pb.SessionCommand.create({ + const sc = webClient.protobuf.controller.SessionCommand.create({ ".Command_ListRooms.ext" : CmdListRooms }); - this.webClient.sendSessionCommand(sc); + webClient.protobuf.sendSessionCommand(sc); } - joinRoom(roomId: string) { - const CmdJoinRoom = this.webClient.pb.Command_JoinRoom.create({ + static joinRoom(roomId: number) { + const CmdJoinRoom = webClient.protobuf.controller.Command_JoinRoom.create({ "roomId" : roomId }); - const sc = this.webClient.pb.SessionCommand.create({ + const sc = webClient.protobuf.controller.SessionCommand.create({ ".Command_JoinRoom.ext" : CmdJoinRoom }); - this.webClient.sendSessionCommand(sc, (raw) => { + webClient.protobuf.sendSessionCommand(sc, (raw) => { const { responseCode } = raw; let error; switch(responseCode) { - case this.webClient.pb.Response.ResponseCode.RespOk: + case webClient.protobuf.controller.Response.ResponseCode.RespOk: const { roomInfo } = raw[".Response_JoinRoom.ext"]; - this.webClient.persistence.room.joinRoom(roomInfo); - this.webClient.debug(() => console.log("Join Room: ", roomInfo.name)); + RoomPersistence.joinRoom(roomInfo); return; - case this.webClient.pb.Response.ResponseCode.RespNameNotFound: + case webClient.protobuf.controller.Response.ResponseCode.RespNameNotFound: error = "Failed to join the room: it doesn\"t exist on the server."; break; - case this.webClient.pb.Response.ResponseCode.RespContextError: + case webClient.protobuf.controller.Response.ResponseCode.RespContextError: error = "The server thinks you are in the room but Cockatrice is unable to display it. Try restarting Cockatrice."; break; - case this.webClient.pb.Response.ResponseCode.RespUserLevelTooLow: + case webClient.protobuf.controller.Response.ResponseCode.RespUserLevelTooLow: error = "You do not have the required permission to join this room."; break; default: @@ -158,68 +160,64 @@ export default class SessionCommands { }); } - addToBuddyList(userName) { + static addToBuddyList(userName: string) { this.addToList('buddy', userName); } - removeFromBuddyList(userName) { + static removeFromBuddyList(userName: string) { this.removeFromList('buddy', userName); } - addToIgnoreList(userName) { + static addToIgnoreList(userName: string) { this.addToList('ignore', userName); } - removeFromIgnoreList(userName) { + static removeFromIgnoreList(userName: string) { this.removeFromList('ignore', userName); } - addToList(list: string, userName: string) { - const CmdAddToList = this.webClient.pb.Command_AddToList.create({ list, userName }); + static addToList(list: string, userName: string) { + const CmdAddToList = webClient.protobuf.controller.Command_AddToList.create({ list, userName }); - const sc = this.webClient.pb.SessionCommand.create({ + const sc = webClient.protobuf.controller.SessionCommand.create({ ".Command_AddToList.ext" : CmdAddToList }); - this.webClient.sendSessionCommand(sc, ({ responseCode }) => { + webClient.protobuf.sendSessionCommand(sc, ({ responseCode }) => { // @TODO: filter responseCode, pop snackbar for error - this.webClient.debug(() => console.log('Added to List Response: ', responseCode)); }); } - removeFromList(list: string, userName: string) { - const CmdRemoveFromList = this.webClient.pb.Command_RemoveFromList.create({ list, userName }); + static removeFromList(list: string, userName: string) { + const CmdRemoveFromList = webClient.protobuf.controller.Command_RemoveFromList.create({ list, userName }); - const sc = this.webClient.pb.SessionCommand.create({ + const sc = webClient.protobuf.controller.SessionCommand.create({ ".Command_RemoveFromList.ext" : CmdRemoveFromList }); - this.webClient.sendSessionCommand(sc, ({ responseCode }) => { + webClient.protobuf.sendSessionCommand(sc, ({ responseCode }) => { // @TODO: filter responseCode, pop snackbar for error - this.webClient.debug(() => console.log('Removed from List Response: ', responseCode)); }); } - viewLogHistory(filters) { - const CmdViewLogHistory = this.webClient.pb.Command_ViewLogHistory.create(filters); + static viewLogHistory(filters) { + const CmdViewLogHistory = webClient.protobuf.controller.Command_ViewLogHistory.create(filters); - const sc = this.webClient.pb.ModeratorCommand.create({ + const sc = webClient.protobuf.controller.ModeratorCommand.create({ ".Command_ViewLogHistory.ext" : CmdViewLogHistory }); - this.webClient.sendModeratorCommand(sc, (raw) => { + webClient.protobuf.sendModeratorCommand(sc, (raw) => { const { responseCode } = raw; let error; switch(responseCode) { - case this.webClient.pb.Response.ResponseCode.RespOk: + case webClient.protobuf.controller.Response.ResponseCode.RespOk: const { logMessage } = raw[".Response_ViewLogHistory.ext"]; console.log("Response_ViewLogHistory: ", logMessage) - this.webClient.persistence.session.viewLogs(logMessage) - - this.webClient.debug(() => console.log("View Log History: ", logMessage)); + SessionPersistence.viewLogs(logMessage) return; default: error = "Failed to retrieve log history."; @@ -231,4 +229,4 @@ export default class SessionCommands { } }); } -} \ No newline at end of file +} diff --git a/webclient/src/websocket/commands/index.tsx b/webclient/src/websocket/commands/index.tsx index f4d523f7..2e06150d 100644 --- a/webclient/src/websocket/commands/index.tsx +++ b/webclient/src/websocket/commands/index.tsx @@ -1,2 +1,2 @@ -export { default as RoomCommand } from "./RoomCommands"; -export { default as SessionCommands } from "./SessionCommands"; \ No newline at end of file +export { RoomCommands } from "./RoomCommands"; +export { SessionCommands } from "./SessionCommands"; diff --git a/webclient/src/websocket/events/RoomEvents.tsx b/webclient/src/websocket/events/RoomEvents.tsx new file mode 100644 index 00000000..b6b6c9da --- /dev/null +++ b/webclient/src/websocket/events/RoomEvents.tsx @@ -0,0 +1,49 @@ +import { Game, Message, User } from 'types'; +import { RoomPersistence } from '../persistence/RoomPersistence'; +import { ProtobufEvents } from '../services/ProtobufService'; + +export const RoomEvents: ProtobufEvents = { + ".Event_JoinRoom.ext": joinRoom, + ".Event_LeaveRoom.ext": leaveRoom, + ".Event_ListGames.ext": listGames, + ".Event_RoomSay.ext": roomSay, +}; + +function joinRoom({ userInfo }: JoinRoomData, { roomEvent }: RoomEvent) { + const { roomId } = roomEvent; + + RoomPersistence.userJoined(roomId, userInfo); +} + +function leaveRoom({ name }: LeaveRoomData, { roomEvent }: RoomEvent) { + const { roomId } = roomEvent; + RoomPersistence.userLeft(roomId, name); +} + +function listGames({ gameList }: ListGamesData, { roomEvent }: RoomEvent) { + const { roomId } = roomEvent; + RoomPersistence.updateGames(roomId, gameList); +} + +function roomSay(message: Message, { roomEvent }: RoomEvent) { + const { roomId } = roomEvent; + RoomPersistence.addMessage(roomId, message); +} + +interface RoomEvent { + roomEvent: { + roomId: number; + } +} + +interface JoinRoomData { + userInfo: User; +} + +interface LeaveRoomData { + name: string; +} + +interface ListGamesData { + gameList: Game[]; +} diff --git a/webclient/src/websocket/events/RoomEvents/JoinRoom.tsx b/webclient/src/websocket/events/RoomEvents/JoinRoom.tsx deleted file mode 100644 index ede5100e..00000000 --- a/webclient/src/websocket/events/RoomEvents/JoinRoom.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export const JoinRoom = { - id: ".Event_JoinRoom.ext", - action: ({ userInfo }, webClient, { roomEvent }) => { - const { roomId } = roomEvent; - webClient.persistence.room.userJoined(roomId, userInfo); - } -}; diff --git a/webclient/src/websocket/events/RoomEvents/LeaveRoom.tsx b/webclient/src/websocket/events/RoomEvents/LeaveRoom.tsx deleted file mode 100644 index 53d57410..00000000 --- a/webclient/src/websocket/events/RoomEvents/LeaveRoom.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export const LeaveRoom = { - id: ".Event_LeaveRoom.ext", - action: ({ name }, webClient, { roomEvent }) => { - const { roomId } = roomEvent; - webClient.persistence.room.userLeft(roomId, name); - } -}; diff --git a/webclient/src/websocket/events/RoomEvents/ListGames.tsx b/webclient/src/websocket/events/RoomEvents/ListGames.tsx deleted file mode 100644 index 974874b2..00000000 --- a/webclient/src/websocket/events/RoomEvents/ListGames.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export const ListGames = { - id: ".Event_ListGames.ext", - action: ({ gameList }, webClient, { roomEvent }) => { - const { roomId } = roomEvent; - webClient.persistence.room.updateGames(roomId, gameList); - } -}; diff --git a/webclient/src/websocket/events/RoomEvents/RoomSay.tsx b/webclient/src/websocket/events/RoomEvents/RoomSay.tsx deleted file mode 100644 index 196f1e38..00000000 --- a/webclient/src/websocket/events/RoomEvents/RoomSay.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export const RoomSay = { - id: ".Event_RoomSay.ext", - action: (message, webClient, { roomEvent }) => { - const { roomId } = roomEvent; - webClient.persistence.room.addMessage(roomId, message); - } -}; diff --git a/webclient/src/websocket/events/RoomEvents/index.tsx b/webclient/src/websocket/events/RoomEvents/index.tsx deleted file mode 100644 index e25aad4f..00000000 --- a/webclient/src/websocket/events/RoomEvents/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export { JoinRoom } from "./JoinRoom"; -export { LeaveRoom } from "./LeaveRoom"; -export { ListGames } from "./ListGames"; -export { RoomSay } from "./RoomSay"; diff --git a/webclient/src/websocket/events/SessionEvents.tsx b/webclient/src/websocket/events/SessionEvents.tsx new file mode 100644 index 00000000..8f172a34 --- /dev/null +++ b/webclient/src/websocket/events/SessionEvents.tsx @@ -0,0 +1,189 @@ +import { Room, StatusEnum, User } from "types"; + +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, +} + +function addToList({ listName, userInfo}: AddToListData) { + switch (listName) { + case 'buddy': { + SessionPersistence.addToBuddyList(userInfo); + break; + } + case 'ignore': { + SessionPersistence.addToIgnoreList(userInfo); + break; + } + default: { + console.log('Attempted to add to unknown list: ', listName); + } + } +} + +function connectionClosed({ reason, reasonStr }: ConnectionClosedData) { + let message = ""; + + // @TODO (5) + if (reasonStr) { + message = reasonStr; + } else { + switch(reason) { + case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.USER_LIMIT_REACHED: + 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"; + break; + case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.BANNED: + message = "You are banned"; + break; + case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.DEMOTED: + message = "You were demoted"; + break; + case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.SERVER_SHUTDOWN: + message = "Scheduled server shutdown"; + break; + case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.USERNAMEINVALID: + 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"; + break; + case webClient.protobuf.controller.Event_ConnectionClosed.CloseReason.OTHER: + default: + message = "Unknown reason"; + break; + } + } + + webClient.socket.updateStatus(StatusEnum.DISCONNECTED, message); +} + +function listRooms({ roomList }: ListRoomsData) { + RoomPersistence.updateRooms(roomList); + + if (webClient.options.autojoinrooms) { + roomList.forEach(({ autoJoin, roomId }) => { + if (autoJoin) { + SessionCommands.joinRoom(roomId); + } + }); + } +} + +function notifyUser(payload) { + // console.info("Event_NotifyUser", payload); +} + +function playerPropertiesChanges(payload) { + // console.info("Event_PlayerPropertiesChanges", payload); +} + +function removeFromList({ listName, userName }: RemoveFromListData) { + switch (listName) { + case 'buddy': { + SessionPersistence.removeFromBuddyList(userName); + break; + } + case 'ignore': { + SessionPersistence.removeFromIgnoreList(userName); + break; + } + default: { + console.log('Attempted to remove from unknown list: ', listName); + } + } +} + +function serverIdentification(info: ServerIdentificationData) { + const { serverName, serverVersion, protocolVersion } = info; + + if (protocolVersion !== webClient.protocolVersion) { + SessionCommands.disconnect(); + webClient.socket.updateStatus(StatusEnum.DISCONNECTED, "Protocol version mismatch: " + protocolVersion); + return; + } + + webClient.resetConnectionvars(); + webClient.socket.updateStatus(StatusEnum.LOGGINGIN, "Logging in..."); + SessionPersistence.updateInfo(serverName, serverVersion); + SessionCommands.login(); +} + +function serverMessage({ message }: ServerMessageData) { + SessionPersistence.serverMessage(message); +} + +function serverShutdown(payload) { + // console.info("Event_ServerShutdown", payload); +} + +function userJoined({ userInfo }: UserJoinedData) { + SessionPersistence.userJoined(userInfo); +} + +function userLeft({ name }: UserLeftData) { + SessionPersistence.userLeft(name); +} + +function userMessage(payload) { + // console.info("Event_UserMessage", payload); +} + +interface SessionEvent { + sessionEvent: {} +} + +interface AddToListData { + listName: string; + userInfo: User; +} + +interface ConnectionClosedData { + endTime: number; + reason: number; + reasonStr: string; +} + +interface ListRoomsData { + roomList: Room[]; +} + +interface RemoveFromListData { + listName: string; + userName: string; +} + +interface ServerIdentificationData { + protocolVersion: number; + serverName: string; + serverVersion: string; +} + +interface ServerMessageData { + message: string; +} + +interface UserJoinedData { + userInfo: User; +} + +interface UserLeftData { + name: string; +} diff --git a/webclient/src/websocket/events/SessionEvents/AddToList.tsx b/webclient/src/websocket/events/SessionEvents/AddToList.tsx deleted file mode 100644 index 43750b56..00000000 --- a/webclient/src/websocket/events/SessionEvents/AddToList.tsx +++ /dev/null @@ -1,18 +0,0 @@ -export const AddToList = { - id: ".Event_AddToList.ext", - action: ({ listName, userInfo}, webClient) => { - switch (listName) { - case 'buddy': { - webClient.persistence.session.addToBuddyList(userInfo); - break; - } - case 'ignore': { - webClient.persistence.session.addToIgnoreList(userInfo); - break; - } - default: { - webClient.debug(() => console.log('Attempted to add to unknown list: ', listName)); - } - } - } -}; \ No newline at end of file diff --git a/webclient/src/websocket/events/SessionEvents/ConnectionClosed.tsx b/webclient/src/websocket/events/SessionEvents/ConnectionClosed.tsx deleted file mode 100644 index 116cf6a5..00000000 --- a/webclient/src/websocket/events/SessionEvents/ConnectionClosed.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { StatusEnum } from "types"; - -export const ConnectionClosed = { - id: ".Event_ConnectionClosed.ext", - action: ({ reason }, webClient) => { - let message = ""; - - // @TODO (5) - switch(reason) { - case webClient.pb.Event_ConnectionClosed.CloseReason.USER_LIMIT_REACHED: - message = "The server has reached its maximum user capacity"; - break; - case webClient.pb.Event_ConnectionClosed.CloseReason.TOO_MANY_CONNECTIONS: - message = "There are too many concurrent connections from your address"; - break; - case webClient.pb.Event_ConnectionClosed.CloseReason.BANNED: - message = "You are banned"; - break; - case webClient.pb.Event_ConnectionClosed.CloseReason.DEMOTED: - message = "You were demoted"; - break; - case webClient.pb.Event_ConnectionClosed.CloseReason.SERVER_SHUTDOWN: - message = "Scheduled server shutdown"; - break; - case webClient.pb.Event_ConnectionClosed.CloseReason.USERNAMEINVALID: - message = "Invalid username"; - break; - case webClient.pb.Event_ConnectionClosed.CloseReason.LOGGEDINELSEWERE: - message = "You have been logged out due to logging in at another location"; - break; - case webClient.pb.Event_ConnectionClosed.CloseReason.OTHER: - default: - message = "Unknown reason"; - break; - } - - webClient.updateStatus(StatusEnum.DISCONNECTED, message); - } -}; diff --git a/webclient/src/websocket/events/SessionEvents/ListRooms.tsx b/webclient/src/websocket/events/SessionEvents/ListRooms.tsx deleted file mode 100644 index 81ea7780..00000000 --- a/webclient/src/websocket/events/SessionEvents/ListRooms.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as _ from "lodash"; - -export const ListRooms = { - id: ".Event_ListRooms.ext", - action: ({ roomList }, webClient) => { - webClient.persistence.room.updateRooms(roomList); - - if (webClient.options.autojoinrooms) { - _.each(roomList, ({ autoJoin, roomId }) => { - if (autoJoin) { - webClient.commands.session.joinRoom(roomId); - } - }); - } - } -}; \ No newline at end of file diff --git a/webclient/src/websocket/events/SessionEvents/NotifyUser.tsx b/webclient/src/websocket/events/SessionEvents/NotifyUser.tsx deleted file mode 100644 index 4cd591a1..00000000 --- a/webclient/src/websocket/events/SessionEvents/NotifyUser.tsx +++ /dev/null @@ -1,6 +0,0 @@ -export const NotifyUser = { - id: ".Event_NotifyUser.ext", - action: (payload) => { - // console.info("Event_NotifyUser", payload); - } -}; \ No newline at end of file diff --git a/webclient/src/websocket/events/SessionEvents/PlayerPropertiesChanges.tsx b/webclient/src/websocket/events/SessionEvents/PlayerPropertiesChanges.tsx deleted file mode 100644 index d0ee65cb..00000000 --- a/webclient/src/websocket/events/SessionEvents/PlayerPropertiesChanges.tsx +++ /dev/null @@ -1,6 +0,0 @@ -export const PlayerPropertiesChanges = { - id: ".Event_PlayerPropertiesChanges.ext", - action: (payload) => { - // console.info("Event_PlayerPropertiesChanges", payload); - } -}; \ No newline at end of file diff --git a/webclient/src/websocket/events/SessionEvents/RemoveFromList.tsx b/webclient/src/websocket/events/SessionEvents/RemoveFromList.tsx deleted file mode 100644 index bfc88140..00000000 --- a/webclient/src/websocket/events/SessionEvents/RemoveFromList.tsx +++ /dev/null @@ -1,18 +0,0 @@ -export const RemoveFromList = { - id: ".Event_RemoveFromList.ext", - action: ({ listName, userName }, webClient) => { - switch (listName) { - case 'buddy': { - webClient.persistence.session.removeFromBuddyList(userName); - break; - } - case 'ignore': { - webClient.persistence.session.removeFromIgnoreList(userName); - break; - } - default: { - webClient.debug(() => console.log('Attempted to remove from unknown list: ', listName)); - } - } - } -}; \ No newline at end of file diff --git a/webclient/src/websocket/events/SessionEvents/ServerIdentification.tsx b/webclient/src/websocket/events/SessionEvents/ServerIdentification.tsx deleted file mode 100644 index 4fad8830..00000000 --- a/webclient/src/websocket/events/SessionEvents/ServerIdentification.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { StatusEnum } from "types"; - -export const ServerIdentification = { - id: ".Event_ServerIdentification.ext", - action: (info, webClient, _raw) => { - const { serverName, serverVersion, protocolVersion } = info; - - if (protocolVersion !== webClient.protocolVersion) { - webClient.disconnect(); - webClient.updateStatus(StatusEnum.DISCONNECTED, "Protocol version mismatch: " + protocolVersion); - return; - } - - webClient.resetConnectionvars(); - webClient.updateStatus(StatusEnum.LOGGINGIN, "Logging in..."); - webClient.persistence.session.updateInfo(serverName, serverVersion); - webClient.commands.session.login(); - } -}; diff --git a/webclient/src/websocket/events/SessionEvents/ServerMessage.tsx b/webclient/src/websocket/events/SessionEvents/ServerMessage.tsx deleted file mode 100644 index 75db410d..00000000 --- a/webclient/src/websocket/events/SessionEvents/ServerMessage.tsx +++ /dev/null @@ -1,6 +0,0 @@ -export const ServerMessage = { - id: ".Event_ServerMessage.ext", - action: ({ message }, webClient) => { - webClient.persistence.session.serverMessage(message); - } -}; diff --git a/webclient/src/websocket/events/SessionEvents/ServerShutdown.tsx b/webclient/src/websocket/events/SessionEvents/ServerShutdown.tsx deleted file mode 100644 index f7640a9e..00000000 --- a/webclient/src/websocket/events/SessionEvents/ServerShutdown.tsx +++ /dev/null @@ -1,6 +0,0 @@ -export const ServerShutdown = { - id: ".Event_ServerShutdown.ext", - action: (payload, webClient) => { - // console.info("Event_ServerShutdown", payload); - } -}; \ No newline at end of file diff --git a/webclient/src/websocket/events/SessionEvents/UserJoined.tsx b/webclient/src/websocket/events/SessionEvents/UserJoined.tsx deleted file mode 100644 index 324f52ac..00000000 --- a/webclient/src/websocket/events/SessionEvents/UserJoined.tsx +++ /dev/null @@ -1,6 +0,0 @@ -export const UserJoined = { - id: ".Event_UserJoined.ext", - action: ({ userInfo }, webClient) => { - webClient.persistence.session.userJoined(userInfo); - } -}; diff --git a/webclient/src/websocket/events/SessionEvents/UserLeft.tsx b/webclient/src/websocket/events/SessionEvents/UserLeft.tsx deleted file mode 100644 index accb80e2..00000000 --- a/webclient/src/websocket/events/SessionEvents/UserLeft.tsx +++ /dev/null @@ -1,6 +0,0 @@ -export const UserLeft = { - id: ".Event_UserLeft.ext", - action: ({ name }, webClient) => { - webClient.persistence.session.userLeft(name); - } -}; diff --git a/webclient/src/websocket/events/SessionEvents/UserMessage.tsx b/webclient/src/websocket/events/SessionEvents/UserMessage.tsx deleted file mode 100644 index 83869995..00000000 --- a/webclient/src/websocket/events/SessionEvents/UserMessage.tsx +++ /dev/null @@ -1,6 +0,0 @@ -export const UserMessage = { - id: ".Event_UserMessage.ext", - action: (payload) => { - // console.info("Event_UserMessage", payload); - } -}; \ No newline at end of file diff --git a/webclient/src/websocket/events/SessionEvents/index.ts b/webclient/src/websocket/events/SessionEvents/index.ts deleted file mode 100644 index a5fe638b..00000000 --- a/webclient/src/websocket/events/SessionEvents/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export * from "./ConnectionClosed"; -export * from "./ListRooms"; -export * from "./AddToList"; -export * from "./RemoveFromList"; -export * from "./NotifyUser"; // @TODO -export * from "./PlayerPropertiesChanges"; // @TODO -export * from "./ServerIdentification"; -export * from "./ServerMessage"; -export * from "./ServerShutdown"; // @TODO -export * from "./UserJoined"; -export * from "./UserLeft"; -export * from "./UserMessage"; // @TODO \ No newline at end of file diff --git a/webclient/src/websocket/events/index.ts b/webclient/src/websocket/events/index.ts new file mode 100644 index 00000000..3b320997 --- /dev/null +++ b/webclient/src/websocket/events/index.ts @@ -0,0 +1,2 @@ +export * from './RoomEvents'; +export * from './SessionEvents'; diff --git a/webclient/src/websocket/index.ts b/webclient/src/websocket/index.ts index c3596acd..7f9030b8 100644 --- a/webclient/src/websocket/index.ts +++ b/webclient/src/websocket/index.ts @@ -1,6 +1,2 @@ export { default as webClient } from './WebClient'; -export { default as ProtoFiles } from './ProtoFiles'; - - -// Export common used services -export { NormalizeService, RoomService} from "./persistence"; +export * from './commands'; diff --git a/webclient/src/websocket/persistence/RoomService.tsx b/webclient/src/websocket/persistence/RoomPersistence.tsx similarity index 62% rename from webclient/src/websocket/persistence/RoomService.tsx rename to webclient/src/websocket/persistence/RoomPersistence.tsx index b464b59b..ca667cbf 100644 --- a/webclient/src/websocket/persistence/RoomService.tsx +++ b/webclient/src/websocket/persistence/RoomPersistence.tsx @@ -1,33 +1,26 @@ import { store, RoomsDispatch, RoomsSelectors } from "store"; -import { WebClient } from "../WebClient"; +import { Game, Message, Room, User } from 'types'; +import NormalizeService from "../utils/NormalizeService"; -import { NormalizeService } from "websocket"; - -export default class RoomService { - webClient: WebClient; - - constructor(webClient) { - this.webClient = webClient; - } - - clearStore() { +export class RoomPersistence { + static clearStore() { RoomsDispatch.clearStore(); } - joinRoom(roomInfo) { + static joinRoom(roomInfo: Room) { NormalizeService.normalizeRoomInfo(roomInfo); RoomsDispatch.joinRoom(roomInfo); } - leaveRoom(roomId) { + static leaveRoom(roomId: number) { RoomsDispatch.leaveRoom(roomId); } - updateRooms(rooms) { + static updateRooms(rooms: Room[]) { RoomsDispatch.updateRooms(rooms); } - updateGames(roomId, gameList) { + static updateGames(roomId: number, gameList: Game[]) { const game = gameList[0]; if (!game.gameType) { @@ -42,17 +35,17 @@ export default class RoomService { RoomsDispatch.updateGames(roomId, gameList); } - addMessage(roomId, message) { + static addMessage(roomId: number, message: Message) { NormalizeService.normalizeUserMessage(message); RoomsDispatch.addMessage(roomId, message); } - userJoined(roomId, user) { + static userJoined(roomId: number, user: User) { RoomsDispatch.userJoined(roomId, user); } - userLeft(roomId, name) { + static userLeft(roomId: number, name: string) { RoomsDispatch.userLeft(roomId, name); } } diff --git a/webclient/src/websocket/persistence/SessionPersistence.tsx b/webclient/src/websocket/persistence/SessionPersistence.tsx new file mode 100644 index 00000000..b70258f8 --- /dev/null +++ b/webclient/src/websocket/persistence/SessionPersistence.tsx @@ -0,0 +1,75 @@ +import { ServerDispatch } from "store"; +import { Log, StatusEnum, User } from "types"; + +import { sanitizeHtml } from "websocket/utils"; +import NormalizeService from "../utils/NormalizeService"; + +export class SessionPersistence { + static clearStore() { + ServerDispatch.clearStore(); + } + + static connectionClosed(reason: number) { + ServerDispatch.connectionClosed(reason); + } + + static updateBuddyList(buddyList) { + ServerDispatch.updateBuddyList(buddyList); + } + + static addToBuddyList(user: User) { + ServerDispatch.addToBuddyList(user); + } + + static removeFromBuddyList(userName: string) { + ServerDispatch.removeFromBuddyList(userName); + } + + static updateIgnoreList(ignoreList) { + ServerDispatch.updateIgnoreList(ignoreList); + } + + static addToIgnoreList(user: User) { + ServerDispatch.addToIgnoreList(user); + } + + static removeFromIgnoreList(userName: string) { + ServerDispatch.removeFromIgnoreList(userName); + } + + static updateInfo(name: string, version: string) { + ServerDispatch.updateInfo(name, version); + } + + static updateStatus(state: number, description: string) { + ServerDispatch.updateStatus(state, description); + + if (state === StatusEnum.DISCONNECTED) { + this.connectionClosed(state); + } + } + + static updateUser(user: User) { + ServerDispatch.updateUser(user); + } + + static updateUsers(users: User[]) { + ServerDispatch.updateUsers(users); + } + + static userJoined(user: User) { + ServerDispatch.userJoined(user); + } + + static userLeft(userName: string) { + ServerDispatch.userLeft(userName); + } + + static viewLogs(logs: Log[]) { + ServerDispatch.viewLogs(NormalizeService.normalizeLogs(logs)); + } + + static serverMessage(message: string) { + ServerDispatch.serverMessage(sanitizeHtml(message)); + } +} diff --git a/webclient/src/websocket/persistence/SessionService.tsx b/webclient/src/websocket/persistence/SessionService.tsx deleted file mode 100644 index 0fa7a714..00000000 --- a/webclient/src/websocket/persistence/SessionService.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { ServerDispatch, ServerConnectParams } from "store"; -import { StatusEnum } from "types"; - -import { sanitizeHtml } from "websocket/utils"; -import { WebClient } from "websocket/WebClient"; - -import { NormalizeService } from "websocket"; - -export default class SessionService { - webClient: WebClient; - - constructor(webClient) { - this.webClient = webClient; - } - - clearStore() { - ServerDispatch.clearStore(); - } - - connectServer(options: ServerConnectParams) { - ServerDispatch.connectServer(); - this.webClient.updateStatus(StatusEnum.CONNECTING, "Connecting..."); - this.webClient.connect(options); - } - - disconnectServer() { - this.webClient.updateStatus(StatusEnum.DISCONNECTING, "Disconnecting..."); - this.webClient.disconnect(); - } - - connectionClosed(reason) { - ServerDispatch.connectionClosed(reason); - } - - updateBuddyList(buddyList) { - ServerDispatch.updateBuddyList(buddyList); - } - - addToBuddyList(user) { - ServerDispatch.addToBuddyList(user); - } - - removeFromBuddyList(userName) { - ServerDispatch.removeFromBuddyList(userName); - } - - updateIgnoreList(ignoreList) { - ServerDispatch.updateIgnoreList(ignoreList); - } - - addToIgnoreList(user) { - ServerDispatch.addToIgnoreList(user); - } - - removeFromIgnoreList(userName) { - ServerDispatch.removeFromIgnoreList(userName); - } - - updateInfo(name, version) { - ServerDispatch.updateInfo(name, version); - } - - updateStatus(state, description) { - ServerDispatch.updateStatus(state, description); - - if (state === StatusEnum.DISCONNECTED) { - this.connectionClosed({ reason: description }); - } - } - - updateUser(user) { - ServerDispatch.updateUser(user); - } - - updateUsers(users) { - ServerDispatch.updateUsers(users); - } - - userJoined(user) { - ServerDispatch.userJoined(user); - } - - userLeft(userId) { - ServerDispatch.userLeft(userId); - } - - viewLogs(logs) { - ServerDispatch.viewLogs(NormalizeService.normalizeLogs(logs)); - } - - serverMessage(message) { - ServerDispatch.serverMessage(sanitizeHtml(message)); - } -} diff --git a/webclient/src/websocket/persistence/index.ts b/webclient/src/websocket/persistence/index.ts index 26776f87..b7316b6a 100644 --- a/webclient/src/websocket/persistence/index.ts +++ b/webclient/src/websocket/persistence/index.ts @@ -1,3 +1,2 @@ -export { default as NormalizeService } from "./NormalizeService"; -export { default as RoomService } from "./RoomService"; -export { default as SessionService } from "./SessionService"; \ No newline at end of file +export { RoomPersistence } from "./RoomPersistence"; +export { SessionPersistence } from "./SessionPersistence"; diff --git a/webclient/src/websocket/services/KeepAliveService.tsx b/webclient/src/websocket/services/KeepAliveService.tsx new file mode 100644 index 00000000..b0ab2ee8 --- /dev/null +++ b/webclient/src/websocket/services/KeepAliveService.tsx @@ -0,0 +1,43 @@ +import { Subject } from "rxjs"; + +import { WebSocketService } from "./WebSocketService"; + +export class KeepAliveService { + private socket: WebSocketService; + + private keepalivecb: NodeJS.Timeout; + private lastPingPending: boolean; + + public disconnected$ = new Subject(); + + constructor(socket: WebSocketService) { + this.socket = socket; + } + + public startPingLoop(interval: number, ping: Function): void { + this.keepalivecb = setInterval(() => { + // check if the previous ping got no reply + if (this.lastPingPending) { + this.disconnected$.next(); + } + + // stop the ping loop if we"re disconnected + if (!this.socket.checkReadyState(WebSocket.OPEN)) { + this.endPingLoop(); + return; + } + + this.lastPingPending = true; + ping(() => this.lastPingPending = false); + }, interval); + } + + public endPingLoop() { + clearInterval(this.keepalivecb); + this.keepalivecb = null; + } + + public resetPingFlag() { + this.lastPingPending = false; + } +} diff --git a/webclient/src/websocket/services/ProtobufService.tsx b/webclient/src/websocket/services/ProtobufService.tsx new file mode 100644 index 00000000..3b3fea30 --- /dev/null +++ b/webclient/src/websocket/services/ProtobufService.tsx @@ -0,0 +1,132 @@ +import protobuf from "protobufjs"; + +import ProtoFiles from "../ProtoFiles"; +import { WebClient } from "../WebClient"; + +import { RoomEvents, SessionEvents } from '../events'; + +export interface ProtobufEvents { + [event: string]: Function; +} + +export class ProtobufService { + static PB_FILE_DIR = `${process.env.PUBLIC_URL}/pb`; + + public controller; + private cmdId = 0; + private pendingCommands: { [cmdId: string]: Function } = {}; + + private webClient: WebClient; + + constructor(webClient: WebClient) { + this.webClient = webClient; + + this.loadProtobufFiles(); + } + + public resetCommands() { + this.cmdId = 0; + this.pendingCommands = {}; + } + + public sendRoomCommand(roomId: number, roomCmd: number, callback?: Function) { + const cmd = this.controller.CommandContainer.create({ + "roomId" : roomId, + "roomCommand" : [ roomCmd ] + }); + + this.sendCommand(cmd, raw => callback && callback(raw)); + } + + public sendSessionCommand(sesCmd: number, callback?: Function) { + const cmd = this.controller.CommandContainer.create({ + "sessionCommand" : [ sesCmd ] + }); + + this.sendCommand(cmd, (raw) => callback && callback(raw)); + } + + public sendModeratorCommand(modCmd: number, callback?: Function) { + const cmd = this.controller.CommandContainer.create({ + "moderatorCommand" : [ modCmd ] + }); + + this.sendCommand(cmd, (raw) => callback && callback(raw)); + } + + public sendCommand(cmd: number, callback: Function) { + this.cmdId++; + + cmd["cmdId"] = this.cmdId; + this.pendingCommands[this.cmdId] = callback; + + if (this.webClient.socket.checkReadyState(WebSocket.OPEN)) { + this.webClient.socket.send(this.controller.CommandContainer.encode(cmd).finish()); + } + } + + public handleMessageEvent({ data }: MessageEvent): void { + try { + const uint8msg = new Uint8Array(data); + const msg = this.controller.ServerMessage.decode(uint8msg); + + if (msg) { + switch (msg.messageType) { + case this.controller.ServerMessage.MessageType.RESPONSE: + this.processServerResponse(msg.response); + break; + case this.controller.ServerMessage.MessageType.ROOM_EVENT: + this.processRoomEvent(msg.roomEvent, msg); + break; + case this.controller.ServerMessage.MessageType.SESSION_EVENT: + this.processSessionEvent(msg.sessionEvent, msg); + break; + case this.controller.ServerMessage.MessageType.GAME_EVENT_CONTAINER: + // @TODO + break; + } + } + } catch (err) { + console.error("Processing failed:", err); + } + } + + private processServerResponse(response: any) { + const { cmdId } = response; + + if (this.pendingCommands[cmdId]) { + this.pendingCommands[cmdId](response); + delete this.pendingCommands[cmdId]; + } + } + + private processRoomEvent(response: any, raw: any) { + this.processEvent(response, RoomEvents, raw); + } + + private processSessionEvent(response: any, raw: any) { + this.processEvent(response, SessionEvents, raw); + } + + private processEvent(response: any, events: ProtobufEvents, raw: any) { + for (const event in events) { + const payload = response[event]; + + if (payload) { + events[event](payload, raw); + return; + } + } + } + + private loadProtobufFiles() { + const files = ProtoFiles.map(file => `${ProtobufService.PB_FILE_DIR}/${file}`); + + this.controller = new protobuf.Root(); + this.controller.load(files, { keepCase: false }, (err, root) => { + if (err) { + throw err; + } + }); + } +} diff --git a/webclient/src/websocket/services/WebSocketService.tsx b/webclient/src/websocket/services/WebSocketService.tsx new file mode 100644 index 00000000..10701f06 --- /dev/null +++ b/webclient/src/websocket/services/WebSocketService.tsx @@ -0,0 +1,101 @@ +import { Subject } from 'rxjs'; + +import { ServerStatus, StatusEnum } from "types"; + +import { KeepAliveService } from "./KeepAliveService"; +import { WebClient } from '../WebClient'; + +export interface WebSocketOptions { + host: string; + port: string; + user: string; + pass: string; + autojoinrooms: boolean; + keepalive: number; +} + +export class WebSocketService { + private socket: WebSocket; + private webClient: WebClient; + public keepAliveService: KeepAliveService; + + public message$: Subject = new Subject(); + public statusChange$: Subject = new Subject(); + + private status: StatusEnum = StatusEnum.DISCONNECTED; + private keepalive: number; + + constructor(webClient: WebClient) { + this.webClient = webClient; + + this.keepAliveService = new KeepAliveService(this); + this.keepAliveService.disconnected$.subscribe(() => { + this.disconnect(); + this.updateStatus(StatusEnum.DISCONNECTED, "Connection timeout"); + }); + } + + public connect(options: WebSocketOptions, protocol: string = 'wss'): void { + const { host, port, keepalive } = options; + this.keepalive = keepalive; + + this.socket = this.createWebSocket(`${protocol}://${host}:${port}`); + } + + public disconnect(): void { + if (this.socket) { + this.socket.close(); + } + } + + public checkReadyState(state: number): boolean { + return this.socket.readyState === state; + } + + public send(message): void { + this.socket.send(message); + } + + public updateStatus(status: StatusEnum, description: string): void { + this.status = status; + this.statusChange$.next({status, description}); + } + + private createWebSocket(url: string): WebSocket { + const socket = new WebSocket(url); + socket.binaryType = "arraybuffer"; // We are talking binary + + socket.onopen = () => { + 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(); + }); + }); + }; + + socket.onclose = () => { + // dont overwrite failure messages + if (this.status !== StatusEnum.DISCONNECTED) { + this.updateStatus(StatusEnum.DISCONNECTED, "Connection Closed"); + } + + this.keepAliveService.endPingLoop(); + }; + + socket.onerror = () => { + this.updateStatus(StatusEnum.DISCONNECTED, "Connection Failed"); + }; + + socket.onmessage = (event: MessageEvent) => { + this.message$.next(event); + } + + return socket; + } +} diff --git a/webclient/src/websocket/persistence/NormalizeService.tsx b/webclient/src/websocket/utils/NormalizeService.tsx similarity index 77% rename from webclient/src/websocket/persistence/NormalizeService.tsx rename to webclient/src/websocket/utils/NormalizeService.tsx index f0846be8..9cba74b8 100644 --- a/webclient/src/websocket/persistence/NormalizeService.tsx +++ b/webclient/src/websocket/utils/NormalizeService.tsx @@ -1,6 +1,8 @@ +import { Game, GametypeMap, Log, LogGroups, Message, Room } from 'types'; + export default class NormalizeService { // Flatten room gameTypes into map object - static normalizeRoomInfo(roomInfo) { + static normalizeRoomInfo(roomInfo: Room): void { roomInfo.gametypeMap = {}; const { gametypeList, gametypeMap, gameList } = roomInfo; @@ -15,7 +17,7 @@ export default class NormalizeService { // Flatten gameTypes[] into gameType field // Default sortable values ("" || 0 || -1) - static normalizeGameObject(game, gametypeMap) { + static normalizeGameObject(game: Game, gametypeMap: GametypeMap): void { const { gameTypes, description } = game; const hasType = gameTypes && gameTypes.length; game.gameType = hasType ? gametypeMap[gameTypes[0]] : ""; @@ -24,17 +26,17 @@ export default class NormalizeService { } // Flatten logs[] into object mapped by targetType (room, game, chat) - static normalizeLogs(logs) { + static normalizeLogs(logs: Log[]): LogGroups { return logs.reduce((obj, log) => { const { targetType } = log; obj[targetType] = obj[targetType] || []; obj[targetType].push(log); return obj; - }, {}); + }, {} as LogGroups); } // messages sent by current user dont have their username prepended - static normalizeUserMessage(message) { + static normalizeUserMessage(message: Message): void { const { name } = message; if (name) { diff --git a/webclient/src/websocket/utils/guid.util.tsx b/webclient/src/websocket/utils/guid.util.tsx index 50ae4ead..b1e0cd84 100644 --- a/webclient/src/websocket/utils/guid.util.tsx +++ b/webclient/src/websocket/utils/guid.util.tsx @@ -1,8 +1,8 @@ -function s4() { +function s4(): string { const s4 = Math.floor((1 + Math.random()) * 0x10000); return s4.toString(16).substring(1); } -export function guid() { +export function guid(): string { return s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4(); } \ No newline at end of file diff --git a/webclient/src/websocket/utils/sanitizeHtml.util.tsx b/webclient/src/websocket/utils/sanitizeHtml.util.tsx index 77cb5e18..dd0d7050 100644 --- a/webclient/src/websocket/utils/sanitizeHtml.util.tsx +++ b/webclient/src/websocket/utils/sanitizeHtml.util.tsx @@ -1,6 +1,6 @@ import $ from "jquery"; -export function sanitizeHtml(msg) { +export function sanitizeHtml(msg: string): string { const $div = $("
").html(msg); const whitelist = { tags: "br,a,img,center,b,font", @@ -16,13 +16,13 @@ export function sanitizeHtml(msg) { return $div.html(); } -function enforceTagWhitelist($el, tags) { +function enforceTagWhitelist($el: JQuery, tags: string): void { $el.find("*").not(tags).each(function() { $(this).replaceWith(this.innerHTML); }); } -function enforceAttrWhitelist($el, attrs) { +function enforceAttrWhitelist($el: JQuery, attrs: string[]): void { $el.find("*").each(function() { var attributes = this.attributes; var i = attributes.length; @@ -34,7 +34,7 @@ function enforceAttrWhitelist($el, attrs) { }); } -function enforceHrefWhitelist($el, hrefs) { +function enforceHrefWhitelist($el: JQuery, hrefs: string[]): void { $el.find("[href]").each(function() { const $_el = $(this); const attributeValue = $_el.attr("href");