diff --git a/webclient/src/containers/Login/Login.tsx b/webclient/src/containers/Login/Login.tsx index 04153c98..5dcea4c3 100644 --- a/webclient/src/containers/Login/Login.tsx +++ b/webclient/src/containers/Login/Login.tsx @@ -57,13 +57,13 @@ const useStyles = makeStyles(theme => ({ }, })); -const Login = ({ state, description }: LoginProps) => { +const Login = ({ state, description, connectOptions }: LoginProps) => { const classes = useStyles(); const { t } = useTranslation(); const isConnected = AuthenticationService.isConnected(state); - const [hostIdToRemember, setHostIdToRemember] = useState(null); + const [rememberLogin, setRememberLogin] = useState(null); const [dialogState, setDialogState] = useState({ passwordResetRequestDialog: false, resetPasswordDialog: false, @@ -100,35 +100,16 @@ const Login = ({ state, description }: LoginProps) => { }, [ServerTypes.CONNECTION_FAILED, ServerTypes.LOGIN_FAILED], []); useReduxEffect(({ options: { hashedPassword } }) => { - if (hostIdToRemember) { - HostDTO.get(hostIdToRemember).then(host => { - host.hashedPassword = hashedPassword; - host.save(); - }); - } - }, ServerTypes.LOGIN_SUCCESSFUL, [hostIdToRemember]); + updateHost(hashedPassword, rememberLogin); + }, ServerTypes.LOGIN_SUCCESSFUL, [rememberLogin]); const showDescription = () => { return !isConnected && description?.length; }; const onSubmitLogin = useCallback((loginForm) => { - const { - userName, - password, - selectedHost, - selectedHost: { - id: hostId, - hashedPassword - }, - remember - } = loginForm; - - updateHost(loginForm); - - if (remember) { - setHostIdToRemember(hostId); - } + setRememberLogin(loginForm); + const { userName, password, selectedHost, remember } = loginForm; const options: WebSocketConnectOptions = { ...getHostPort(selectedHost), @@ -136,8 +117,8 @@ const Login = ({ state, description }: LoginProps) => { password }; - if (!password) { - options.hashedPassword = hashedPassword; + if (remember && !password) { + options.hashedPassword = selectedHost.hashedPassword; } AuthenticationService.login(options as WebSocketConnectOptions); @@ -145,7 +126,7 @@ const Login = ({ state, description }: LoginProps) => { const [submitButtonDisabled, resetSubmitButton, handleLogin] = useFireOnce(onSubmitLogin); - const updateHost = ({ selectedHost, userName, hashedPassword, remember }) => { + const updateHost = (hashedPassword, { selectedHost, remember, userName }) => { HostDTO.get(selectedHost.id).then(hostDTO => { hostDTO.remember = remember; hostDTO.userName = remember ? userName : null; @@ -155,8 +136,9 @@ const Login = ({ state, description }: LoginProps) => { }); }; - const handleRegistrationDialogSubmit = (form) => { - const { userName, password, email, country, realName, selectedHost } = form; + const handleRegistrationDialogSubmit = (registerForm) => { + setRememberLogin(registerForm); + const { userName, password, email, country, realName, selectedHost } = registerForm; AuthenticationService.register({ ...getHostPort(selectedHost), @@ -169,7 +151,10 @@ const Login = ({ state, description }: LoginProps) => { }; const handleAccountActivationDialogSubmit = ({ token }) => { - AuthenticationService.activateAccount({ token } as any); + AuthenticationService.activateAccount({ + ...connectOptions, + token, + }); }; const handleRequestPasswordResetDialogSubmit = (form) => { @@ -177,17 +162,17 @@ const Login = ({ state, description }: LoginProps) => { const { host, port } = getHostPort(selectedHost); if (email) { - AuthenticationService.resetPasswordChallenge({ userName, email, host, port } as any); + AuthenticationService.resetPasswordChallenge({ userName, email, host, port }); } else { setUserToResetPassword(userName); - AuthenticationService.resetPasswordRequest({ userName, host, port } as any); + AuthenticationService.resetPasswordRequest({ userName, host, port }); } }; const handleResetPasswordDialogSubmit = ({ userName, token, newPassword, selectedHost }) => { const { host, port } = getHostPort(selectedHost); - AuthenticationService.resetPassword({ userName, token, newPassword, host, port } as any); + AuthenticationService.resetPassword({ userName, token, newPassword, host, port }); }; const skipTokenRequest = (userName) => { @@ -354,11 +339,13 @@ const Login = ({ state, description }: LoginProps) => { interface LoginProps { state: number; description: string; + connectOptions: WebSocketConnectOptions; } const mapStateToProps = state => ({ state: ServerSelectors.getState(state), description: ServerSelectors.getDescription(state), + connectOptions: ServerSelectors.getConnectOptions(state), }); export default connect(mapStateToProps)(Login); diff --git a/webclient/src/forms/LoginForm/LoginForm.tsx b/webclient/src/forms/LoginForm/LoginForm.tsx index 8c3c2231..112fac0e 100644 --- a/webclient/src/forms/LoginForm/LoginForm.tsx +++ b/webclient/src/forms/LoginForm/LoginForm.tsx @@ -13,7 +13,7 @@ import { APP_USER } from 'types'; import './LoginForm.css'; const PASSWORD_LABEL = 'Password'; -const STORED_PASSWORD_LABEL = '* SAVED *'; +const STORED_PASSWORD_LABEL = '* Saved Password *'; const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginFormProps) => { const [host, setHost] = useState(null); @@ -33,7 +33,7 @@ const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginForm return errors; } - const useStoredPassword = (remember) => remember && host.hashedPassword; + const useStoredPassword = (remember, password) => remember && host.hashedPassword && !password; const togglePasswordLabel = (useStoredLabel) => { setPasswordLabel(useStoredLabel ? STORED_PASSWORD_LABEL : PASSWORD_LABEL); }; @@ -78,12 +78,12 @@ const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginForm onRememberChange(host.remember); onAutoConnectChange(host.remember && autoConnect); - togglePasswordLabel(useStoredPassword(host.remember)); + togglePasswordLabel(useStoredPassword(host.remember, values.password)); }, [host]); const onUserNameChange = (userName) => { const fieldChanged = host.userName?.toLowerCase() !== values.userName?.toLowerCase(); - if (useStoredPassword(values.remember) && fieldChanged) { + if (useStoredPassword(values.remember, values.password) && fieldChanged) { setHost(({ hashedPassword, ...s }) => ({ ...s, userName })); } } @@ -95,7 +95,7 @@ const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginForm onAutoConnectChange(false); } - togglePasswordLabel(useStoredPassword(checked)); + togglePasswordLabel(useStoredPassword(checked, values.password)); } const onAutoConnectChange = (checked) => { @@ -119,7 +119,7 @@ const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginForm setPasswordLabel(PASSWORD_LABEL)} - onBlur={() => togglePasswordLabel(useStoredPassword(values.remember))} + onBlur={() => togglePasswordLabel(useStoredPassword(values.remember, values.password))} name='password' type='password' component={InputField} diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts index 767d597b..edc1b9ed 100644 --- a/webclient/src/store/server/server.actions.ts +++ b/webclient/src/store/server/server.actions.ts @@ -105,8 +105,9 @@ export const Actions = { type: Types.REGISTRATION_USERNAME_ERROR, error }), - accountAwaitingActivation: () => ({ + accountAwaitingActivation: (options: WebSocketConnectOptions) => ({ type: Types.ACCOUNT_AWAITING_ACTIVATION, + options }), accountActivationSuccess: () => ({ type: Types.ACCOUNT_ACTIVATION_SUCCESS, diff --git a/webclient/src/store/server/server.dispatch.ts b/webclient/src/store/server/server.dispatch.ts index bd4c85c3..43bbfa0d 100644 --- a/webclient/src/store/server/server.dispatch.ts +++ b/webclient/src/store/server/server.dispatch.ts @@ -1,6 +1,7 @@ import { reset } from 'redux-form'; import { Actions } from './server.actions'; import { store } from 'store'; +import { WebSocketConnectOptions } from 'types'; export const Dispatch = { initialized: () => { @@ -92,8 +93,8 @@ export const Dispatch = { registrationUserNameError: (error) => { store.dispatch(Actions.registrationUserNameError(error)); }, - accountAwaitingActivation: () => { - store.dispatch(Actions.accountAwaitingActivation()); + accountAwaitingActivation: (options: WebSocketConnectOptions) => { + store.dispatch(Actions.accountAwaitingActivation(options)); }, accountActivationSuccess: () => { store.dispatch(Actions.accountActivationSuccess()); diff --git a/webclient/src/store/server/server.interfaces.ts b/webclient/src/store/server/server.interfaces.ts index 1de6f00d..afcd9c0e 100644 --- a/webclient/src/store/server/server.interfaces.ts +++ b/webclient/src/store/server/server.interfaces.ts @@ -1,4 +1,4 @@ -import { Log, SortBy, User, UserSortField } from 'types'; +import { Log, SortBy, User, UserSortField, WebSocketConnectOptions } from 'types'; export interface ServerConnectParams { host: string; @@ -48,6 +48,7 @@ export interface ServerState { user: User; users: User[]; sortUsersBy: ServerStateSortUsersBy; + connectOptions: WebSocketConnectOptions; } export interface ServerStateStatus { diff --git a/webclient/src/store/server/server.reducer.ts b/webclient/src/store/server/server.reducer.ts index 15ae6c18..060f512f 100644 --- a/webclient/src/store/server/server.reducer.ts +++ b/webclient/src/store/server/server.reducer.ts @@ -29,7 +29,8 @@ const initialState: ServerState = { sortUsersBy: { field: UserSortField.NAME, order: SortDirection.ASC - } + }, + connectOptions: {}, }; export const serverReducer = (state = initialState, action: any) => { @@ -40,6 +41,21 @@ export const serverReducer = (state = initialState, action: any) => { initialized: true } } + case Types.ACCOUNT_AWAITING_ACTIVATION: { + return { + ...state, + connectOptions: { + ...action.options + } + } + } + case Types.ACCOUNT_ACTIVATION_FAILED: + case Types.ACCOUNT_ACTIVATION_SUCCESS: { + return { + ...state, + connectOptions: {} + } + } case Types.CLEAR_STORE: { return { ...initialState, diff --git a/webclient/src/store/server/server.selectors.ts b/webclient/src/store/server/server.selectors.ts index 0eca5779..26394642 100644 --- a/webclient/src/store/server/server.selectors.ts +++ b/webclient/src/store/server/server.selectors.ts @@ -6,6 +6,7 @@ interface State { export const Selectors = { getInitialized: ({ server }: State) => server.initialized, + getConnectOptions: ({ server }: State) => server.connectOptions, getMessage: ({ server }: State) => server.info.message, getName: ({ server }: State) => server.info.name, getVersion: ({ server }: State) => server.info.version, diff --git a/webclient/src/types/server.tsx b/webclient/src/types/server.tsx index e8bfccd6..2a302bdc 100644 --- a/webclient/src/types/server.tsx +++ b/webclient/src/types/server.tsx @@ -19,6 +19,7 @@ export interface WebSocketConnectOptions { password?: string; hashedPassword?: string; newPassword?: string; + token?: string; email?: string; realName?: string; country?: string; diff --git a/webclient/src/websocket/WebClient.ts b/webclient/src/websocket/WebClient.ts index d7a1b679..8e706ab8 100644 --- a/webclient/src/websocket/WebClient.ts +++ b/webclient/src/websocket/WebClient.ts @@ -11,8 +11,9 @@ export class WebClient { public protocolVersion = 14; public clientConfig = { - 'clientver': 'webclient-1.0 (2019-10-31)', - 'clientfeatures': [ + clientid: 'webatrice', + clientver: 'webclient-1.0 (2019-10-31)', + clientfeatures: [ 'client_id', 'client_ver', 'feature_set', @@ -30,22 +31,13 @@ export class WebClient { ] }; - public options: WebSocketConnectOptions = { - host: '', - port: '', - userName: '', - password: '', - hashedPassword: '', - newPassword: '', - email: '', - realName: '', - country: '', - clientid: null, - reason: null, + public clientOptions = { autojoinrooms: true, keepalive: 5000 }; + public options: WebSocketConnectOptions; + public connectionAttemptMade = false; constructor() { @@ -64,7 +56,7 @@ export class WebClient { public connect(options: WebSocketConnectOptions) { this.connectionAttemptMade = true; - this.options = { ...this.options, ...options }; + this.options = options; this.socket.connect(this.options); } diff --git a/webclient/src/websocket/commands/SessionCommands.spec.ts b/webclient/src/websocket/commands/SessionCommands.spec.ts index 83d82215..ef40c30e 100644 --- a/webclient/src/websocket/commands/SessionCommands.spec.ts +++ b/webclient/src/websocket/commands/SessionCommands.spec.ts @@ -1,4 +1,4 @@ -import { StatusEnum, WebSocketConnectReason } from 'types'; +import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; import { SessionCommands } from './SessionCommands'; @@ -81,22 +81,25 @@ describe('SessionCommands', () => { }); describe('login', () => { + let options: WebSocketConnectOptions; + beforeEach(() => { webClient.protobuf.controller.Command_Login = { create: args => args }; - webClient.options.userName = 'user'; - webClient.options.password = 'pass'; + options = { + userName: 'userName', + password: 'password', + }; }); it('should call protobuf controller methods and sendCommand', () => { - SessionCommands.login(); + SessionCommands.login(options); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalled(); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith({ '.Command_Login.ext': { ...webClient.clientConfig, - userName: webClient.options.userName, - password: webClient.options.password, - clientid: expect.any(String) + userName: options.userName, + password: options.password } }, expect.any(Function)); }); @@ -128,7 +131,7 @@ describe('SessionCommands', () => { jest.spyOn(SessionCommands, 'listUsers').mockImplementation(() => {}); jest.spyOn(SessionCommands, 'listRooms').mockImplementation(() => {}); - SessionCommands.login(); + SessionCommands.login(options); expect(SessionPersistence.updateBuddyList).toHaveBeenCalledWith(response[respKey].buddyList); expect(SessionPersistence.updateIgnoreList).toHaveBeenCalledWith(response[respKey].ignoreList); @@ -144,7 +147,7 @@ describe('SessionCommands', () => { webClient.protobuf.controller.Response.ResponseCode.RespClientUpdateRequired = RespClientUpdateRequired; response.responseCode = RespClientUpdateRequired; - SessionCommands.login(); + SessionCommands.login(options); expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Login failed: missing features'); }); @@ -154,7 +157,7 @@ describe('SessionCommands', () => { webClient.protobuf.controller.Response.ResponseCode.RespWrongPassword = RespWrongPassword; response.responseCode = RespWrongPassword; - SessionCommands.login(); + SessionCommands.login(options); expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Login failed: incorrect username or password'); }); @@ -164,7 +167,7 @@ describe('SessionCommands', () => { webClient.protobuf.controller.Response.ResponseCode.RespUsernameInvalid = RespUsernameInvalid; response.responseCode = RespUsernameInvalid; - SessionCommands.login(); + SessionCommands.login(options); expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Login failed: incorrect username or password'); }); @@ -174,7 +177,7 @@ describe('SessionCommands', () => { webClient.protobuf.controller.Response.ResponseCode.RespWouldOverwriteOldSession = RespWouldOverwriteOldSession; response.responseCode = RespWouldOverwriteOldSession; - SessionCommands.login(); + SessionCommands.login(options); expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Login failed: duplicated user session'); }); @@ -184,7 +187,7 @@ describe('SessionCommands', () => { webClient.protobuf.controller.Response.ResponseCode.RespUserIsBanned = RespUserIsBanned; response.responseCode = RespUserIsBanned; - SessionCommands.login(); + SessionCommands.login(options); expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Login failed: banned user'); }); @@ -194,7 +197,7 @@ describe('SessionCommands', () => { webClient.protobuf.controller.Response.ResponseCode.RespRegistrationRequired = RespRegistrationRequired; response.responseCode = RespRegistrationRequired; - SessionCommands.login(); + SessionCommands.login(options); expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Login failed: registration required'); }); @@ -204,7 +207,7 @@ describe('SessionCommands', () => { webClient.protobuf.controller.Response.ResponseCode.RespClientIdRequired = RespClientIdRequired; response.responseCode = RespClientIdRequired; - SessionCommands.login(); + SessionCommands.login(options); expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Login failed: missing client ID'); }); @@ -214,7 +217,7 @@ describe('SessionCommands', () => { webClient.protobuf.controller.Response.ResponseCode.RespContextError = RespContextError; response.responseCode = RespContextError; - SessionCommands.login(); + SessionCommands.login(options); expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Login failed: server error'); }); @@ -224,7 +227,7 @@ describe('SessionCommands', () => { webClient.protobuf.controller.Response.ResponseCode.RespAccountNotActivated = RespAccountNotActivated; response.responseCode = RespAccountNotActivated; - SessionCommands.login(); + SessionCommands.login(options); expect(SessionCommands.updateStatus).toHaveBeenCalledWith( StatusEnum.DISCONNECTED, @@ -237,7 +240,7 @@ describe('SessionCommands', () => { webClient.protobuf.controller.Response.ResponseCode.UnknownCode = UnknownCode; response.responseCode = UnknownCode; - SessionCommands.login(); + SessionCommands.login(options); expect(SessionCommands.updateStatus).toHaveBeenCalledWith( StatusEnum.DISCONNECTED, @@ -248,23 +251,24 @@ describe('SessionCommands', () => { }); describe('register', () => { + let options: WebSocketConnectOptions; + beforeEach(() => { webClient.protobuf.controller.Command_Register = { create: args => args }; - webClient.options = { + options = { ...webClient.options, - user: 'user', - pass: 'pass', + userName: 'userName', + password: 'password', email: 'email@example.com', country: 'us', realName: 'realName', clientid: 'abcdefg' - } as any; + }; }); it('should call protobuf controller methods and sendCommand', () => { - SessionCommands.register(); + SessionCommands.register(options); - const options = webClient.options as unknown as ServerRegisterParams; expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalled(); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith({ @@ -275,7 +279,6 @@ describe('SessionCommands', () => { email: options.email, country: options.country, realName: options.realName, - clientid: expect.any(String) } }, expect.any(Function)); }); @@ -302,7 +305,7 @@ describe('SessionCommands', () => { describe('RespRegistrationAccepted', () => { it('should call SessionCommands.login()', () => { jest.spyOn(SessionCommands, 'login').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.login).toHaveBeenCalled(); @@ -321,7 +324,7 @@ describe('SessionCommands', () => { it('should call SessionPersistence.accountAwaitingActivation()', () => { jest.spyOn(SessionCommands, 'login').mockImplementation(() => {}); jest.spyOn(SessionPersistence, 'accountAwaitingActivation').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.login).not.toHaveBeenCalled(); expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalled(); @@ -329,7 +332,7 @@ describe('SessionCommands', () => { it('should disconnect', () => { jest.spyOn(SessionCommands, 'disconnect').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.disconnect).toHaveBeenCalled(); }); @@ -347,7 +350,7 @@ describe('SessionCommands', () => { it('should call SessionPersistence.registrationUserNameError()', () => { jest.spyOn(SessionCommands, 'login').mockImplementation(() => {}); jest.spyOn(SessionPersistence, 'registrationUserNameError').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.login).not.toHaveBeenCalled(); expect(SessionPersistence.registrationUserNameError).toHaveBeenCalledWith(expect.any(String)); @@ -355,7 +358,7 @@ describe('SessionCommands', () => { it('should disconnect', () => { jest.spyOn(SessionCommands, 'disconnect').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.disconnect).toHaveBeenCalled(); }); @@ -373,7 +376,7 @@ describe('SessionCommands', () => { it('should call SessionPersistence.registrationUserNameError()', () => { jest.spyOn(SessionCommands, 'login').mockImplementation(() => {}); jest.spyOn(SessionPersistence, 'registrationUserNameError').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.login).not.toHaveBeenCalled(); expect(SessionPersistence.registrationUserNameError).toHaveBeenCalledWith(expect.any(String)); @@ -381,7 +384,7 @@ describe('SessionCommands', () => { it('should disconnect', () => { jest.spyOn(SessionCommands, 'disconnect').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.disconnect).toHaveBeenCalled(); }); @@ -399,7 +402,7 @@ describe('SessionCommands', () => { it('should call SessionPersistence.registrationPasswordError()', () => { jest.spyOn(SessionCommands, 'login').mockImplementation(() => {}); jest.spyOn(SessionPersistence, 'registrationPasswordError').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.login).not.toHaveBeenCalled(); expect(SessionPersistence.registrationPasswordError).toHaveBeenCalledWith(expect.any(String)); @@ -407,7 +410,7 @@ describe('SessionCommands', () => { it('should disconnect', () => { jest.spyOn(SessionCommands, 'disconnect').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.disconnect).toHaveBeenCalled(); }); @@ -425,7 +428,7 @@ describe('SessionCommands', () => { it('should call SessionPersistence.registrationRequiresEmail()', () => { jest.spyOn(SessionCommands, 'login').mockImplementation(() => {}); jest.spyOn(SessionPersistence, 'registrationRequiresEmail').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.login).not.toHaveBeenCalled(); expect(SessionPersistence.registrationRequiresEmail).toHaveBeenCalled(); @@ -433,7 +436,7 @@ describe('SessionCommands', () => { it('should disconnect', () => { jest.spyOn(SessionCommands, 'disconnect').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.disconnect).toHaveBeenCalled(); }); @@ -451,7 +454,7 @@ describe('SessionCommands', () => { it('should call SessionPersistence.registrationEmailError()', () => { jest.spyOn(SessionCommands, 'login').mockImplementation(() => {}); jest.spyOn(SessionPersistence, 'registrationEmailError').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.login).not.toHaveBeenCalled(); expect(SessionPersistence.registrationEmailError).toHaveBeenCalledWith(expect.any(String)); @@ -459,7 +462,7 @@ describe('SessionCommands', () => { it('should disconnect', () => { jest.spyOn(SessionCommands, 'disconnect').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.disconnect).toHaveBeenCalled(); }); @@ -477,7 +480,7 @@ describe('SessionCommands', () => { it('should call SessionPersistence.registrationEmailError()', () => { jest.spyOn(SessionCommands, 'login').mockImplementation(() => {}); jest.spyOn(SessionPersistence, 'registrationEmailError').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.login).not.toHaveBeenCalled(); expect(SessionPersistence.registrationEmailError).toHaveBeenCalledWith(expect.any(String)); @@ -485,7 +488,7 @@ describe('SessionCommands', () => { it('should disconnect', () => { jest.spyOn(SessionCommands, 'disconnect').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.disconnect).toHaveBeenCalled(); }); @@ -503,7 +506,7 @@ describe('SessionCommands', () => { it('should call SessionPersistence.registrationFailed()', () => { jest.spyOn(SessionCommands, 'login').mockImplementation(() => {}); jest.spyOn(SessionPersistence, 'registrationFailed').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.login).not.toHaveBeenCalled(); expect(SessionPersistence.registrationFailed).toHaveBeenCalledWith(expect.any(String)); @@ -511,7 +514,7 @@ describe('SessionCommands', () => { it('should disconnect', () => { jest.spyOn(SessionCommands, 'disconnect').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.disconnect).toHaveBeenCalled(); }); @@ -529,7 +532,7 @@ describe('SessionCommands', () => { it('should call SessionPersistence.registrationFailed()', () => { jest.spyOn(SessionCommands, 'login').mockImplementation(() => {}); jest.spyOn(SessionPersistence, 'registrationFailed').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.login).not.toHaveBeenCalled(); expect(SessionPersistence.registrationFailed).toHaveBeenCalledWith(expect.any(String)); @@ -537,7 +540,7 @@ describe('SessionCommands', () => { it('should disconnect', () => { jest.spyOn(SessionCommands, 'disconnect').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.disconnect).toHaveBeenCalled(); }); @@ -555,7 +558,7 @@ describe('SessionCommands', () => { it('should call SessionPersistence.registrationFailed()', () => { jest.spyOn(SessionCommands, 'login').mockImplementation(() => {}); jest.spyOn(SessionPersistence, 'registrationFailed').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.login).not.toHaveBeenCalled(); expect(SessionPersistence.registrationFailed).toHaveBeenCalledWith(expect.any(String)); @@ -563,7 +566,7 @@ describe('SessionCommands', () => { it('should disconnect', () => { jest.spyOn(SessionCommands, 'disconnect').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.disconnect).toHaveBeenCalled(); }); @@ -581,7 +584,7 @@ describe('SessionCommands', () => { it('should call SessionPersistence.registrationFailed()', () => { jest.spyOn(SessionCommands, 'login').mockImplementation(() => {}); jest.spyOn(SessionPersistence, 'registrationFailed').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.login).not.toHaveBeenCalled(); expect(SessionPersistence.registrationFailed).toHaveBeenCalledWith(expect.any(String)); @@ -589,7 +592,7 @@ describe('SessionCommands', () => { it('should disconnect', () => { jest.spyOn(SessionCommands, 'disconnect').mockImplementation(() => {}); - SessionCommands.register(); + SessionCommands.register(options); expect(SessionCommands.disconnect).toHaveBeenCalled(); }); @@ -598,27 +601,25 @@ describe('SessionCommands', () => { }); describe('activateAccount', () => { + let options: WebSocketConnectOptions; + beforeEach(() => { webClient.protobuf.controller.Command_Activate = { create: args => args }; - webClient.options = { - ...webClient.options, - user: 'user', - activationCode: 'token', - clientid: 'abcdefg' - } as any; + options = { + userName: 'userName', + token: 'token', + }; }); it('should call protobuf controller methods and sendCommand', () => { - SessionCommands.activateAccount(); + SessionCommands.activateAccount(options); - const options = webClient.options as unknown as AccountActivationParams; expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith({ '.Command_Activate.ext': { ...webClient.clientConfig, userName: options.userName, token: options.token, - clientid: expect.any(String) } }, expect.any(Function)); }); @@ -644,7 +645,7 @@ describe('SessionCommands', () => { }); it('should activate user and login if correct activation token used', () => { - SessionCommands.activateAccount(); + SessionCommands.activateAccount(options); expect(SessionCommands.login).toHaveBeenCalled(); expect(SessionPersistence.accountActivationFailed).not.toHaveBeenCalled(); @@ -655,7 +656,7 @@ describe('SessionCommands', () => { response.responseCode = RespActivationFailed; webClient.protobuf.controller.Response.ResponseCode.RespActivationFailed = RespActivationFailed; - SessionCommands.activateAccount(); + SessionCommands.activateAccount(options); expect(SessionCommands.login).not.toHaveBeenCalled(); expect(SessionPersistence.accountActivationFailed).toHaveBeenCalled(); diff --git a/webclient/src/websocket/commands/SessionCommands.ts b/webclient/src/websocket/commands/SessionCommands.ts index b4fa790e..3d9ee2ea 100644 --- a/webclient/src/websocket/commands/SessionCommands.ts +++ b/webclient/src/websocket/commands/SessionCommands.ts @@ -37,8 +37,8 @@ export class SessionCommands { webClient.disconnect(); } - static login(passwordSalt?: string): void { - const { userName, password, hashedPassword } = webClient.options; + static login(options: WebSocketConnectOptions, passwordSalt?: string): void { + const { userName, password, hashedPassword } = options; const loginConfig: any = { ...webClient.clientConfig, @@ -109,7 +109,7 @@ export class SessionCommands { case webClient.protobuf.controller.Response.ResponseCode.RespAccountNotActivated: SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: account not activated'); - SessionPersistence.accountAwaitingActivation(); + SessionPersistence.accountAwaitingActivation(options); break; default: @@ -121,8 +121,8 @@ export class SessionCommands { }); } - static requestPasswordSalt(): void { - const { userName } = webClient.options as unknown as RequestPasswordSaltParams; + static requestPasswordSalt(options: WebSocketConnectOptions): void { + const { userName } = options as RequestPasswordSaltParams; const registerConfig = { ...webClient.clientConfig, @@ -140,20 +140,20 @@ export class SessionCommands { case webClient.protobuf.controller.Response.ResponseCode.RespOk: { const passwordSalt = raw['.Response_PasswordSalt.ext']?.passwordSalt; - switch (webClient.options.reason) { + switch (options.reason) { case WebSocketConnectReason.ACTIVATE_ACCOUNT: { - SessionCommands.activateAccount(passwordSalt); + SessionCommands.activateAccount(options, passwordSalt); break; } case WebSocketConnectReason.PASSWORD_RESET: { - SessionCommands.resetPassword(passwordSalt); + SessionCommands.resetPassword(options, passwordSalt); break; } case WebSocketConnectReason.LOGIN: default: { - SessionCommands.login(passwordSalt); + SessionCommands.login(options, passwordSalt); } } @@ -168,7 +168,7 @@ export class SessionCommands { } } - switch (webClient.options.reason) { + switch (options.reason) { case WebSocketConnectReason.ACTIVATE_ACCOUNT: { SessionPersistence.accountActivationFailed(); break; @@ -189,12 +189,11 @@ export class SessionCommands { }); } - static register(passwordSalt?: string): void { - const { userName, password, email, country, realName } = webClient.options as unknown as ServerRegisterParams; + static register(options: WebSocketConnectOptions, passwordSalt?: string): void { + const { userName, password, email, country, realName } = options as ServerRegisterParams; const registerConfig: any = { ...webClient.clientConfig, - clientid: 'webatrice', userName, email, country, @@ -215,14 +214,14 @@ export class SessionCommands { webClient.protobuf.sendSessionCommand(sc, raw => { if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespRegistrationAccepted) { - SessionCommands.login(passwordSalt); + SessionCommands.login(options, passwordSalt); SessionPersistence.registrationSuccess() return; } switch (raw.responseCode) { case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationAcceptedNeedsActivation: - SessionPersistence.accountAwaitingActivation(); + SessionPersistence.accountAwaitingActivation(options); break; case webClient.protobuf.controller.Response.ResponseCode.RespUserAlreadyExists: SessionPersistence.registrationUserNameError('Username is taken'); @@ -259,12 +258,11 @@ export class SessionCommands { }); }; - static activateAccount(passwordSalt?: string): void { - const { userName, token } = webClient.options as unknown as AccountActivationParams; + static activateAccount(options: WebSocketConnectOptions, passwordSalt?: string): void { + const { userName, token } = options as unknown as AccountActivationParams; const accountActivationConfig = { ...webClient.clientConfig, - clientid: 'webatrice', userName, token, }; @@ -278,7 +276,7 @@ export class SessionCommands { webClient.protobuf.sendSessionCommand(sc, raw => { if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespActivationAccepted) { SessionPersistence.accountActivationSuccess(); - SessionCommands.login(passwordSalt); + SessionCommands.login(options, passwordSalt); } else { SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Account Activation Failed'); SessionCommands.disconnect(); @@ -287,12 +285,11 @@ export class SessionCommands { }); } - static resetPasswordRequest(): void { - const { userName } = webClient.options as unknown as ForgotPasswordParams; + static resetPasswordRequest(options: WebSocketConnectOptions): void { + const { userName } = options as unknown as ForgotPasswordParams; const forgotPasswordConfig = { ...webClient.clientConfig, - clientid: 'webatrice', userName, }; @@ -322,12 +319,11 @@ export class SessionCommands { }); } - static resetPasswordChallenge(): void { - const { userName, email } = webClient.options as unknown as ForgotPasswordChallengeParams; + static resetPasswordChallenge(options: WebSocketConnectOptions): void { + const { userName, email } = options as unknown as ForgotPasswordChallengeParams; const forgotPasswordChallengeConfig = { ...webClient.clientConfig, - clientid: 'webatrice', userName, email, }; @@ -351,12 +347,11 @@ export class SessionCommands { }); } - static resetPassword(passwordSalt?: string): void { - const { userName, token, newPassword } = webClient.options as unknown as ForgotPasswordResetParams; + static resetPassword(options: WebSocketConnectOptions, passwordSalt?: string): void { + const { userName, token, newPassword } = options as unknown as ForgotPasswordResetParams; const forgotPasswordResetConfig: any = { ...webClient.clientConfig, - clientid: 'webatrice', userName, token, }; diff --git a/webclient/src/websocket/events/SessionEvents.spec.ts b/webclient/src/websocket/events/SessionEvents.spec.ts index dc82a362..c3ae4351 100644 --- a/webclient/src/websocket/events/SessionEvents.spec.ts +++ b/webclient/src/websocket/events/SessionEvents.spec.ts @@ -216,7 +216,7 @@ describe('SessionEvents', () => { describe('.Event_ListRooms.ext', () => { beforeEach(() => { - webClient.options.autojoinrooms = false; + webClient.clientOptions.autojoinrooms = false; jest.spyOn(RoomPersistence, 'updateRooms').mockImplementation(() => {}); }); @@ -229,7 +229,7 @@ describe('SessionEvents', () => { }); it('should call SessionCommands.joinRoom if webClient and room is configured for autojoin', () => { - webClient.options.autojoinrooms = true; + webClient.clientOptions.autojoinrooms = true; jest.spyOn(SessionCommands, 'joinRoom').mockImplementation(() => {}); const data: ListRoomsData = { roomList: [{ roomId, autoJoin: true } as any, { roomId: 2, autoJoin: false } as any] }; @@ -291,6 +291,7 @@ describe('SessionEvents', () => { jest.spyOn(SessionPersistence, 'updateInfo').mockImplementation(() => {}); webClient.protobuf.controller.Event_ServerIdentification = { ServerOptions: { SupportsPasswordHash: 1 } }; + webClient.options = {}; }); it('update status/info and login', () => { diff --git a/webclient/src/websocket/events/SessionEvents.ts b/webclient/src/websocket/events/SessionEvents.ts index 4636470a..e35ec66d 100644 --- a/webclient/src/websocket/events/SessionEvents.ts +++ b/webclient/src/websocket/events/SessionEvents.ts @@ -79,7 +79,7 @@ function connectionClosed({ reason, reasonStr }: ConnectionClosedData) { function listRooms({ roomList }: ListRoomsData) { RoomPersistence.updateRooms(roomList); - if (webClient.options.autojoinrooms) { + if (webClient.clientOptions.autojoinrooms) { roomList.forEach(({ autoJoin, roomId }) => { if (autoJoin) { SessionCommands.joinRoom(roomId); @@ -120,45 +120,49 @@ function serverIdentification(info: ServerIdentificationData) { return; } - switch (webClient.options.reason) { + const getPasswordSalt = passwordSaltSupported(serverOptions, webClient); + const { options } = webClient; + + switch (options.reason) { case WebSocketConnectReason.LOGIN: SessionCommands.updateStatus(StatusEnum.LOGGING_IN, 'Logging In...'); - if (passwordSaltSupported(serverOptions, webClient)) { - SessionCommands.requestPasswordSalt(); + if (getPasswordSalt) { + SessionCommands.requestPasswordSalt(options); } else { - SessionCommands.login(); + SessionCommands.login(options); } break; case WebSocketConnectReason.REGISTER: - const passwordSalt = passwordSaltSupported(serverOptions, webClient) ? generateSalt() : null; - SessionCommands.register(passwordSalt); + const passwordSalt = getPasswordSalt ? generateSalt() : null; + SessionCommands.register(options, passwordSalt); break; case WebSocketConnectReason.ACTIVATE_ACCOUNT: - if (passwordSaltSupported(serverOptions, webClient)) { - SessionCommands.requestPasswordSalt(); + if (getPasswordSalt) { + SessionCommands.requestPasswordSalt(options); } else { - SessionCommands.activateAccount(); + SessionCommands.activateAccount(options); } break; case WebSocketConnectReason.PASSWORD_RESET_REQUEST: - SessionCommands.resetPasswordRequest(); + SessionCommands.resetPasswordRequest(options); break; case WebSocketConnectReason.PASSWORD_RESET_CHALLENGE: - SessionCommands.resetPasswordChallenge(); + SessionCommands.resetPasswordChallenge(options); break; case WebSocketConnectReason.PASSWORD_RESET: - if (passwordSaltSupported(serverOptions, webClient)) { - SessionCommands.requestPasswordSalt(); + if (getPasswordSalt) { + SessionCommands.requestPasswordSalt(options); } else { - SessionCommands.resetPassword(); + SessionCommands.resetPassword(options); } break; default: - SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Unknown Connection Reason: ' + webClient.options.reason); + SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Unknown Connection Reason: ' + options.reason); SessionCommands.disconnect(); break; } + webClient.options = {}; SessionPersistence.updateInfo(serverName, serverVersion); } diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts index 0b46ad96..8768cabf 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.ts @@ -89,8 +89,8 @@ export class SessionPersistence { ServerDispatch.serverMessage(sanitizeHtml(message)); } - static accountAwaitingActivation() { - ServerDispatch.accountAwaitingActivation(); + static accountAwaitingActivation(options: WebSocketConnectOptions) { + ServerDispatch.accountAwaitingActivation(options); } static accountActivationSuccess() { diff --git a/webclient/src/websocket/services/WebSocketService.ts b/webclient/src/websocket/services/WebSocketService.ts index f1b98eeb..1cc67ca3 100644 --- a/webclient/src/websocket/services/WebSocketService.ts +++ b/webclient/src/websocket/services/WebSocketService.ts @@ -32,8 +32,8 @@ export class WebSocketService { protocol = 'ws'; } - const { host, port, keepalive } = options; - this.keepalive = keepalive; + const { host, port } = options; + this.keepalive = this.webClient.clientOptions.keepalive; this.socket = this.createWebSocket(`${protocol}://${host}:${port}`); }