import { HostDTO } from 'services'; import { StatusEnum, WebSocketConnectReason, WebSocketConnectOptions } from 'types'; import { RoomPersistence, SessionPersistence } from '../persistence'; import webClient from '../WebClient'; import { guid, hashPassword } from '../utils'; import { AccountActivationParams, ForgotPasswordChallengeParams, ForgotPasswordParams, ForgotPasswordResetParams, RequestPasswordSaltParams, ServerRegisterParams } from '../../store'; import NormalizeService from '../utils/NormalizeService'; export class SessionCommands { static connect(options: WebSocketConnectOptions, reason: WebSocketConnectReason): void { switch (reason) { case WebSocketConnectReason.LOGIN: case WebSocketConnectReason.REGISTER: case WebSocketConnectReason.ACTIVATE_ACCOUNT: case WebSocketConnectReason.PASSWORD_RESET_REQUEST: case WebSocketConnectReason.PASSWORD_RESET_CHALLENGE: case WebSocketConnectReason.PASSWORD_RESET: SessionCommands.updateStatus(StatusEnum.CONNECTING, 'Connecting...'); break; case WebSocketConnectReason.TEST_CONNECTION: webClient.testConnect({ ...options }); return; default: SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Unknown Connection Attempt: ' + reason); return; } webClient.connect({ ...options, reason }); } static disconnect(): void { webClient.disconnect(); } static login(options: WebSocketConnectOptions, passwordSalt?: string): void { const { userName, password, hashedPassword } = options; const loginConfig: any = { ...webClient.clientConfig, clientid: 'webatrice', userName, }; if (passwordSalt) { loginConfig.hashedPassword = hashedPassword || hashPassword(passwordSalt, password); } else { loginConfig.password = password; } const CmdLogin = webClient.protobuf.controller.Command_Login.create(loginConfig); const command = webClient.protobuf.controller.SessionCommand.create({ '.Command_Login.ext': CmdLogin }); webClient.protobuf.sendSessionCommand(command, raw => { const resp = raw['.Response_Login.ext']; if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespOk) { const { buddyList, ignoreList, userInfo } = resp; SessionPersistence.updateBuddyList(buddyList); SessionPersistence.updateIgnoreList(ignoreList); SessionPersistence.updateUser(userInfo); SessionPersistence.loginSuccessful(loginConfig); SessionCommands.listUsers(); SessionCommands.listRooms(); SessionCommands.updateStatus(StatusEnum.LOGGED_IN, 'Logged in.'); return; } switch (raw.responseCode) { case webClient.protobuf.controller.Response.ResponseCode.RespClientUpdateRequired: SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: missing features'); break; case webClient.protobuf.controller.Response.ResponseCode.RespWrongPassword: case webClient.protobuf.controller.Response.ResponseCode.RespUsernameInvalid: SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: incorrect username or password'); break; case webClient.protobuf.controller.Response.ResponseCode.RespWouldOverwriteOldSession: SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: duplicated user session'); break; case webClient.protobuf.controller.Response.ResponseCode.RespUserIsBanned: SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: banned user'); break; case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationRequired: SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: registration required'); break; case webClient.protobuf.controller.Response.ResponseCode.RespClientIdRequired: SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: missing client ID'); break; case webClient.protobuf.controller.Response.ResponseCode.RespContextError: SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: server error'); break; case webClient.protobuf.controller.Response.ResponseCode.RespAccountNotActivated: SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: account not activated'); SessionPersistence.accountAwaitingActivation(options); break; default: SessionCommands.updateStatus(StatusEnum.DISCONNECTED, `Login failed: unknown error: ${raw.responseCode}`); } SessionPersistence.loginFailed(); SessionCommands.disconnect(); }); } static requestPasswordSalt(options: WebSocketConnectOptions): void { const { userName } = options as RequestPasswordSaltParams; const registerConfig = { ...webClient.clientConfig, userName, }; const CmdRequestPasswordSalt = webClient.protobuf.controller.Command_RequestPasswordSalt.create(registerConfig); const sc = webClient.protobuf.controller.SessionCommand.create({ '.Command_RequestPasswordSalt.ext': CmdRequestPasswordSalt }); webClient.protobuf.sendSessionCommand(sc, raw => { switch (raw.responseCode) { case webClient.protobuf.controller.Response.ResponseCode.RespOk: { const passwordSalt = raw['.Response_PasswordSalt.ext']?.passwordSalt; switch (options.reason) { case WebSocketConnectReason.ACTIVATE_ACCOUNT: { SessionCommands.activateAccount(options, passwordSalt); break; } case WebSocketConnectReason.PASSWORD_RESET: { SessionCommands.resetPassword(options, passwordSalt); break; } case WebSocketConnectReason.LOGIN: default: { SessionCommands.login(options, passwordSalt); } } return; } case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationRequired: { SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: registration required'); break; } default: { SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: Unknown Reason'); } } switch (options.reason) { case WebSocketConnectReason.ACTIVATE_ACCOUNT: { SessionPersistence.accountActivationFailed(); break; } case WebSocketConnectReason.PASSWORD_RESET: { SessionPersistence.resetPasswordFailed(); break; } case WebSocketConnectReason.LOGIN: default: { SessionPersistence.loginFailed(); } } SessionCommands.disconnect(); }); } static register(options: WebSocketConnectOptions, passwordSalt?: string): void { const { userName, password, email, country, realName } = options as ServerRegisterParams; const registerConfig: any = { ...webClient.clientConfig, userName, email, country, realName, }; if (passwordSalt) { registerConfig.hashedPassword = hashPassword(passwordSalt, password); } else { registerConfig.password = password; } const CmdRegister = webClient.protobuf.controller.Command_Register.create(registerConfig); const sc = webClient.protobuf.controller.SessionCommand.create({ '.Command_Register.ext': CmdRegister }); webClient.protobuf.sendSessionCommand(sc, raw => { if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespRegistrationAccepted) { SessionCommands.login(options, passwordSalt); SessionPersistence.registrationSuccess() return; } switch (raw.responseCode) { case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationAcceptedNeedsActivation: SessionPersistence.accountAwaitingActivation(options); break; case webClient.protobuf.controller.Response.ResponseCode.RespUserAlreadyExists: SessionPersistence.registrationUserNameError('Username is taken'); break; case webClient.protobuf.controller.Response.ResponseCode.RespUsernameInvalid: console.error('ResponseCode.RespUsernameInvalid', raw.reasonStr); SessionPersistence.registrationUserNameError('Invalid username'); break; case webClient.protobuf.controller.Response.ResponseCode.RespPasswordTooShort: SessionPersistence.registrationPasswordError('Your password was too short'); break; case webClient.protobuf.controller.Response.ResponseCode.RespEmailRequiredToRegister: SessionPersistence.registrationRequiresEmail(); break; case webClient.protobuf.controller.Response.ResponseCode.RespEmailBlackListed: SessionPersistence.registrationEmailError('This email provider has been blocked'); break; case webClient.protobuf.controller.Response.ResponseCode.RespTooManyRequests: SessionPersistence.registrationEmailError('Max accounts reached for this email'); break; case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationDisabled: SessionPersistence.registrationFailed('Registration is currently disabled'); break; case webClient.protobuf.controller.Response.ResponseCode.RespUserIsBanned: SessionPersistence.registrationFailed(NormalizeService.normalizeBannedUserError(raw.reasonStr, raw.endTime)); break; case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationFailed: default: SessionPersistence.registrationFailed('Registration failed due to a server issue'); break; } SessionCommands.disconnect(); }); }; static activateAccount(options: WebSocketConnectOptions, passwordSalt?: string): void { const { userName, token } = options as unknown as AccountActivationParams; const accountActivationConfig = { ...webClient.clientConfig, userName, token, }; const CmdActivate = webClient.protobuf.controller.Command_Activate.create(accountActivationConfig); const sc = webClient.protobuf.controller.SessionCommand.create({ '.Command_Activate.ext': CmdActivate }); webClient.protobuf.sendSessionCommand(sc, raw => { if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespActivationAccepted) { SessionPersistence.accountActivationSuccess(); SessionCommands.login(options, passwordSalt); } else { SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Account Activation Failed'); SessionCommands.disconnect(); SessionPersistence.accountActivationFailed(); } }); } static resetPasswordRequest(options: WebSocketConnectOptions): void { const { userName } = options as unknown as ForgotPasswordParams; const forgotPasswordConfig = { ...webClient.clientConfig, userName, }; const CmdForgotPasswordRequest = webClient.protobuf.controller.Command_ForgotPasswordRequest.create(forgotPasswordConfig); const sc = webClient.protobuf.controller.SessionCommand.create({ '.Command_ForgotPasswordRequest.ext': CmdForgotPasswordRequest }); webClient.protobuf.sendSessionCommand(sc, raw => { if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespOk) { const resp = raw['.Response_ForgotPasswordRequest.ext']; if (resp.challengeEmail) { SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null); SessionPersistence.resetPasswordChallenge(); } else { SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null); SessionPersistence.resetPassword(); } } else { SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null); SessionPersistence.resetPasswordFailed(); } SessionCommands.disconnect(); }); } static resetPasswordChallenge(options: WebSocketConnectOptions): void { const { userName, email } = options as unknown as ForgotPasswordChallengeParams; const forgotPasswordChallengeConfig = { ...webClient.clientConfig, userName, email, }; const CmdForgotPasswordChallenge = webClient.protobuf.controller.Command_ForgotPasswordChallenge.create(forgotPasswordChallengeConfig); const sc = webClient.protobuf.controller.SessionCommand.create({ '.Command_ForgotPasswordChallenge.ext': CmdForgotPasswordChallenge }); webClient.protobuf.sendSessionCommand(sc, raw => { if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespOk) { SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null); SessionPersistence.resetPassword(); } else { SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null); SessionPersistence.resetPasswordFailed(); } SessionCommands.disconnect(); }); } static resetPassword(options: WebSocketConnectOptions, passwordSalt?: string): void { const { userName, token, newPassword } = options as unknown as ForgotPasswordResetParams; const forgotPasswordResetConfig: any = { ...webClient.clientConfig, userName, token, }; if (passwordSalt) { forgotPasswordResetConfig.hashedNewPassword = hashPassword(passwordSalt, newPassword); } else { forgotPasswordResetConfig.newPassword = newPassword; } const CmdForgotPasswordReset = webClient.protobuf.controller.Command_ForgotPasswordReset.create(forgotPasswordResetConfig); const sc = webClient.protobuf.controller.SessionCommand.create({ '.Command_ForgotPasswordReset.ext': CmdForgotPasswordReset }); webClient.protobuf.sendSessionCommand(sc, raw => { if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespOk) { SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null); SessionPersistence.resetPasswordSuccess(); } else { SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null); SessionPersistence.resetPasswordFailed(); } SessionCommands.disconnect(); }); } static listUsers(): void { const CmdListUsers = webClient.protobuf.controller.Command_ListUsers.create(); const sc = webClient.protobuf.controller.SessionCommand.create({ '.Command_ListUsers.ext': CmdListUsers }); webClient.protobuf.sendSessionCommand(sc, raw => { const { responseCode } = raw; const response = raw['.Response_ListUsers.ext']; if (response) { switch (responseCode) { case webClient.protobuf.controller.Response.ResponseCode.RespOk: SessionPersistence.updateUsers(response.userList); break; default: console.log(`Failed to fetch Server Rooms [${responseCode}] : `, raw); } } }); } static listRooms(): void { const CmdListRooms = webClient.protobuf.controller.Command_ListRooms.create(); const sc = webClient.protobuf.controller.SessionCommand.create({ '.Command_ListRooms.ext': CmdListRooms }); webClient.protobuf.sendSessionCommand(sc); } static joinRoom(roomId: number): void { const CmdJoinRoom = webClient.protobuf.controller.Command_JoinRoom.create({ roomId }); const sc = webClient.protobuf.controller.SessionCommand.create({ '.Command_JoinRoom.ext': CmdJoinRoom }); webClient.protobuf.sendSessionCommand(sc, (raw) => { const { responseCode } = raw; let error; switch (responseCode) { case webClient.protobuf.controller.Response.ResponseCode.RespOk: const { roomInfo } = raw['.Response_JoinRoom.ext']; RoomPersistence.joinRoom(roomInfo); return; case webClient.protobuf.controller.Response.ResponseCode.RespNameNotFound: error = 'Failed to join the room: it doesn\'t exist on the server.'; break; 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 webClient.protobuf.controller.Response.ResponseCode.RespUserLevelTooLow: error = 'You do not have the required permission to join this room.'; break; default: error = 'Failed to join the room due to an unknown error.'; break; } if (error) { console.error(responseCode, error); } }); } static addToBuddyList(userName: string): void { this.addToList('buddy', userName); } static removeFromBuddyList(userName: string): void { this.removeFromList('buddy', userName); } static addToIgnoreList(userName: string): void { this.addToList('ignore', userName); } static removeFromIgnoreList(userName: string): void { this.removeFromList('ignore', userName); } static addToList(list: string, userName: string): void { const CmdAddToList = webClient.protobuf.controller.Command_AddToList.create({ list, userName }); const sc = webClient.protobuf.controller.SessionCommand.create({ '.Command_AddToList.ext': CmdAddToList }); webClient.protobuf.sendSessionCommand(sc, ({ responseCode }) => { // @TODO: filter responseCode, pop snackbar for error }); } static removeFromList(list: string, userName: string): void { const CmdRemoveFromList = webClient.protobuf.controller.Command_RemoveFromList.create({ list, userName }); const sc = webClient.protobuf.controller.SessionCommand.create({ '.Command_RemoveFromList.ext': CmdRemoveFromList }); webClient.protobuf.sendSessionCommand(sc, ({ responseCode }) => { // @TODO: filter responseCode, pop snackbar for error }); } static viewLogHistory(filters): void { const CmdViewLogHistory = webClient.protobuf.controller.Command_ViewLogHistory.create(filters); const sc = webClient.protobuf.controller.ModeratorCommand.create({ '.Command_ViewLogHistory.ext': CmdViewLogHistory }); webClient.protobuf.sendModeratorCommand(sc, (raw) => { const { responseCode } = raw; let error; switch (responseCode) { case webClient.protobuf.controller.Response.ResponseCode.RespOk: const { logMessage } = raw['.Response_ViewLogHistory.ext']; SessionPersistence.viewLogs(logMessage) return; default: error = 'Failed to retrieve log history.'; break; } if (error) { console.error(responseCode, error); } }); } static updateStatus(status: StatusEnum, description: string): void { webClient.updateStatus(status, description); } }