2017-04-28 15:22:55 +03:00
|
|
|
/*
|
|
|
|
Copyright 2016 OpenMarket Ltd
|
2017-05-02 11:56:14 +03:00
|
|
|
Copyright 2017 Vector Creations Ltd
|
2017-04-28 15:22:55 +03:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2017-07-12 15:58:14 +03:00
|
|
|
import Promise from 'bluebird';
|
2017-04-28 15:22:55 +03:00
|
|
|
import React from 'react';
|
|
|
|
import sdk from '../../../index';
|
|
|
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
2017-05-10 16:22:17 +03:00
|
|
|
import classnames from 'classnames';
|
2017-05-30 15:14:14 +03:00
|
|
|
import KeyCode from '../../../KeyCode';
|
2017-06-05 17:36:10 +03:00
|
|
|
import { _t, _tJsx } from '../../../languageHandler';
|
2017-05-10 16:22:17 +03:00
|
|
|
|
|
|
|
// The amount of time to wait for further changes to the input username before
|
|
|
|
// sending a request to the server
|
2017-05-29 01:28:29 +03:00
|
|
|
const USERNAME_CHECK_DEBOUNCE_MS = 250;
|
2017-04-28 15:22:55 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Prompt the user to set a display name.
|
|
|
|
*
|
|
|
|
* On success, `onFinished(true, newDisplayName)` is called.
|
|
|
|
*/
|
|
|
|
export default React.createClass({
|
|
|
|
displayName: 'SetMxIdDialog',
|
|
|
|
propTypes: {
|
|
|
|
onFinished: React.PropTypes.func.isRequired,
|
2017-05-10 16:28:48 +03:00
|
|
|
// Called when the user requests to register with a different homeserver
|
|
|
|
onDifferentServerClicked: React.PropTypes.func.isRequired,
|
2017-05-29 03:32:31 +03:00
|
|
|
// Called if the user wants to switch to login instead
|
|
|
|
onLoginClick: React.PropTypes.func.isRequired,
|
2017-04-28 15:22:55 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
getInitialState: function() {
|
|
|
|
return {
|
2017-05-10 16:22:17 +03:00
|
|
|
// The entered username
|
|
|
|
username: '',
|
|
|
|
// Indicate ongoing work on the username
|
|
|
|
usernameBusy: false,
|
|
|
|
// Indicate error with username
|
|
|
|
usernameError: '',
|
|
|
|
// Assume the homeserver supports username checking until "M_UNRECOGNIZED"
|
|
|
|
usernameCheckSupport: true,
|
|
|
|
|
|
|
|
// Whether the auth UI is currently being used
|
2017-04-28 15:22:55 +03:00
|
|
|
doingUIAuth: false,
|
2017-05-10 16:22:17 +03:00
|
|
|
// Indicate error with auth
|
|
|
|
authError: '',
|
|
|
|
};
|
2017-04-28 15:22:55 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
componentDidMount: function() {
|
|
|
|
this.refs.input_value.select();
|
|
|
|
|
|
|
|
this._matrixClient = MatrixClientPeg.get();
|
|
|
|
},
|
|
|
|
|
|
|
|
onValueChange: function(ev) {
|
|
|
|
this.setState({
|
2017-05-10 16:22:17 +03:00
|
|
|
username: ev.target.value,
|
|
|
|
usernameBusy: true,
|
|
|
|
usernameError: '',
|
|
|
|
}, () => {
|
|
|
|
if (!this.state.username || !this.state.usernameCheckSupport) {
|
|
|
|
this.setState({
|
|
|
|
usernameBusy: false,
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Debounce the username check to limit number of requests sent
|
|
|
|
if (this._usernameCheckTimeout) {
|
|
|
|
clearTimeout(this._usernameCheckTimeout);
|
|
|
|
}
|
|
|
|
this._usernameCheckTimeout = setTimeout(() => {
|
|
|
|
this._doUsernameCheck().finally(() => {
|
|
|
|
this.setState({
|
|
|
|
usernameBusy: false,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}, USERNAME_CHECK_DEBOUNCE_MS);
|
2017-04-28 15:22:55 +03:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2017-05-30 15:02:35 +03:00
|
|
|
onKeyUp: function(ev) {
|
2017-05-30 15:14:14 +03:00
|
|
|
if (ev.keyCode === KeyCode.ENTER) {
|
2017-05-30 15:02:35 +03:00
|
|
|
this.onSubmit();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-04-28 15:22:55 +03:00
|
|
|
onSubmit: function(ev) {
|
|
|
|
this.setState({
|
|
|
|
doingUIAuth: true,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2017-05-10 16:22:17 +03:00
|
|
|
_doUsernameCheck: function() {
|
2017-07-31 15:28:43 +03:00
|
|
|
// XXX: SPEC-1
|
|
|
|
// Check if username is valid
|
|
|
|
if (encodeURIComponent(this.state.username) !== this.state.username) {
|
|
|
|
this.setState({
|
|
|
|
usernameError: _t('User names may only contain letters, numbers, dots, hyphens and underscores.'),
|
|
|
|
});
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
2017-05-10 16:22:17 +03:00
|
|
|
// Check if username is available
|
|
|
|
return this._matrixClient.isUsernameAvailable(this.state.username).then(
|
|
|
|
(isAvailable) => {
|
|
|
|
if (isAvailable) {
|
|
|
|
this.setState({usernameError: ''});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
(err) => {
|
|
|
|
// Indicate whether the homeserver supports username checking
|
|
|
|
const newState = {
|
|
|
|
usernameCheckSupport: err.errcode !== "M_UNRECOGNIZED",
|
|
|
|
};
|
2017-05-26 15:13:57 +03:00
|
|
|
console.error('Error whilst checking username availability: ', err);
|
2017-05-10 16:22:17 +03:00
|
|
|
switch (err.errcode) {
|
|
|
|
case "M_USER_IN_USE":
|
2017-06-05 17:36:10 +03:00
|
|
|
newState.usernameError = _t('Username not available');
|
2017-05-10 16:22:17 +03:00
|
|
|
break;
|
|
|
|
case "M_INVALID_USERNAME":
|
2017-06-05 17:36:10 +03:00
|
|
|
newState.usernameError = _t(
|
2017-06-07 20:28:49 +03:00
|
|
|
'Username invalid: %(errMessage)s',
|
|
|
|
{ errMessage: err.message},
|
2017-06-05 17:36:10 +03:00
|
|
|
);
|
2017-05-10 16:22:17 +03:00
|
|
|
break;
|
|
|
|
case "M_UNRECOGNIZED":
|
|
|
|
// This homeserver doesn't support username checking, assume it's
|
|
|
|
// fine and rely on the error appearing in registration step.
|
|
|
|
newState.usernameError = '';
|
|
|
|
break;
|
2017-05-26 15:13:57 +03:00
|
|
|
case undefined:
|
2017-06-05 17:36:10 +03:00
|
|
|
newState.usernameError = _t('Something went wrong!');
|
2017-05-26 15:13:57 +03:00
|
|
|
break;
|
2017-05-10 16:22:17 +03:00
|
|
|
default:
|
2017-06-05 17:36:10 +03:00
|
|
|
newState.usernameError = _t(
|
2017-06-07 20:28:49 +03:00
|
|
|
'An error occurred: %(error_string)s',
|
|
|
|
{ error_string: err.message },
|
2017-06-05 17:36:10 +03:00
|
|
|
);
|
2017-05-10 16:22:17 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
this.setState(newState);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2017-05-02 12:07:06 +03:00
|
|
|
_generatePassword: function() {
|
|
|
|
return Math.random().toString(36).slice(2);
|
2017-04-28 15:22:55 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
_makeRegisterRequest: function(auth) {
|
|
|
|
// Not upgrading - changing mxids
|
|
|
|
const guestAccessToken = null;
|
2017-05-10 16:22:17 +03:00
|
|
|
if (!this._generatedPassword) {
|
|
|
|
this._generatedPassword = this._generatePassword();
|
|
|
|
}
|
2017-04-28 15:22:55 +03:00
|
|
|
return this._matrixClient.register(
|
|
|
|
this.state.username,
|
2017-05-02 12:07:06 +03:00
|
|
|
this._generatedPassword,
|
2017-04-28 15:22:55 +03:00
|
|
|
undefined, // session id: included in the auth dict already
|
|
|
|
auth,
|
|
|
|
{},
|
|
|
|
guestAccessToken,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
_onUIAuthFinished: function(success, response) {
|
|
|
|
this.setState({
|
|
|
|
doingUIAuth: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!success) {
|
2017-05-10 16:22:17 +03:00
|
|
|
this.setState({ authError: response.message });
|
2017-04-28 15:22:55 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// XXX Implement RTS /register here
|
|
|
|
const teamToken = null;
|
|
|
|
|
2017-05-25 16:54:28 +03:00
|
|
|
this.props.onFinished(true, {
|
2017-04-28 15:22:55 +03:00
|
|
|
userId: response.user_id,
|
|
|
|
deviceId: response.device_id,
|
|
|
|
homeserverUrl: this._matrixClient.getHomeserverUrl(),
|
|
|
|
identityServerUrl: this._matrixClient.getIdentityServerUrl(),
|
|
|
|
accessToken: response.access_token,
|
2017-05-02 12:07:06 +03:00
|
|
|
password: this._generatedPassword,
|
2017-04-28 15:22:55 +03:00
|
|
|
teamToken: teamToken,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
|
|
|
const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth');
|
|
|
|
const Spinner = sdk.getComponent('elements.Spinner');
|
2017-05-10 16:22:17 +03:00
|
|
|
|
2017-04-28 15:22:55 +03:00
|
|
|
let auth;
|
|
|
|
if (this.state.doingUIAuth) {
|
|
|
|
auth = <InteractiveAuth
|
|
|
|
matrixClient={this._matrixClient}
|
|
|
|
makeRequest={this._makeRegisterRequest}
|
|
|
|
onAuthFinished={this._onUIAuthFinished}
|
|
|
|
inputs={{}}
|
|
|
|
poll={true}
|
|
|
|
/>;
|
|
|
|
}
|
2017-05-10 16:22:17 +03:00
|
|
|
const inputClasses = classnames({
|
|
|
|
"mx_SetMxIdDialog_input": true,
|
|
|
|
"error": Boolean(this.state.usernameError),
|
|
|
|
});
|
|
|
|
|
|
|
|
let usernameIndicator = null;
|
|
|
|
let usernameBusyIndicator = null;
|
|
|
|
if (this.state.usernameBusy) {
|
|
|
|
usernameBusyIndicator = <Spinner w="24" h="24"/>;
|
|
|
|
} else {
|
|
|
|
const usernameAvailable = this.state.username &&
|
|
|
|
this.state.usernameCheckSupport && !this.state.usernameError;
|
|
|
|
const usernameIndicatorClasses = classnames({
|
|
|
|
"error": Boolean(this.state.usernameError),
|
|
|
|
"success": usernameAvailable,
|
|
|
|
});
|
|
|
|
usernameIndicator = <div className={usernameIndicatorClasses}>
|
2017-06-05 17:36:10 +03:00
|
|
|
{ usernameAvailable ? _t('Username available') : this.state.usernameError }
|
2017-05-10 16:22:17 +03:00
|
|
|
</div>;
|
|
|
|
}
|
|
|
|
|
|
|
|
let authErrorIndicator = null;
|
|
|
|
if (this.state.authError) {
|
|
|
|
authErrorIndicator = <div className="error">
|
|
|
|
{ this.state.authError }
|
|
|
|
</div>;
|
|
|
|
}
|
|
|
|
const canContinue = this.state.username &&
|
|
|
|
!this.state.usernameError &&
|
|
|
|
!this.state.usernameBusy;
|
|
|
|
|
2017-04-28 15:22:55 +03:00
|
|
|
return (
|
|
|
|
<BaseDialog className="mx_SetMxIdDialog"
|
|
|
|
onFinished={this.props.onFinished}
|
2017-05-10 16:22:17 +03:00
|
|
|
title="To get started, please pick a username!"
|
2017-04-28 15:22:55 +03:00
|
|
|
>
|
|
|
|
<div className="mx_Dialog_content">
|
2017-05-10 16:22:17 +03:00
|
|
|
<div className="mx_SetMxIdDialog_input_group">
|
|
|
|
<input type="text" ref="input_value" value={this.state.username}
|
2017-05-30 15:02:35 +03:00
|
|
|
autoFocus={true}
|
|
|
|
onChange={this.onValueChange}
|
|
|
|
onKeyUp={this.onKeyUp}
|
|
|
|
size="30"
|
2017-05-10 16:22:17 +03:00
|
|
|
className={inputClasses}
|
|
|
|
/>
|
|
|
|
{ usernameBusyIndicator }
|
|
|
|
</div>
|
|
|
|
{ usernameIndicator }
|
2017-04-28 15:22:55 +03:00
|
|
|
<p>
|
2017-06-05 17:36:10 +03:00
|
|
|
{ _tJsx(
|
|
|
|
'This will be your account name on the <span></span> ' +
|
|
|
|
'homeserver, or you can pick a <a>different server</a>.',
|
|
|
|
[
|
|
|
|
/<span><\/span>/,
|
|
|
|
/<a>(.*?)<\/a>/,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
(sub) => <span>{this.props.homeserverUrl}</span>,
|
|
|
|
(sub) => <a href="#" onClick={this.props.onDifferentServerClicked}>{sub}</a>,
|
|
|
|
],
|
|
|
|
)}
|
2017-04-28 15:22:55 +03:00
|
|
|
</p>
|
2017-05-29 03:32:31 +03:00
|
|
|
<p>
|
2017-06-05 17:36:10 +03:00
|
|
|
{ _tJsx(
|
|
|
|
'If you already have a Matrix account you can <a>log in</a> instead.',
|
|
|
|
/<a>(.*?)<\/a>/,
|
|
|
|
[(sub) => <a href="#" onClick={this.props.onLoginClick}>{sub}</a>],
|
|
|
|
)}
|
2017-05-29 03:32:31 +03:00
|
|
|
</p>
|
2017-04-28 15:22:55 +03:00
|
|
|
{ auth }
|
2017-05-10 16:22:17 +03:00
|
|
|
{ authErrorIndicator }
|
2017-04-28 15:22:55 +03:00
|
|
|
</div>
|
|
|
|
<div className="mx_Dialog_buttons">
|
|
|
|
<input className="mx_Dialog_primary"
|
|
|
|
type="submit"
|
2017-06-05 17:36:10 +03:00
|
|
|
value={_t("Continue")}
|
2017-04-28 15:22:55 +03:00
|
|
|
onClick={this.onSubmit}
|
2017-05-10 16:22:17 +03:00
|
|
|
disabled={!canContinue}
|
2017-04-28 15:22:55 +03:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</BaseDialog>
|
|
|
|
);
|
|
|
|
},
|
|
|
|
});
|