diff --git a/src/Lifecycle.js b/src/Lifecycle.js index f20716cae6..7fba0a0298 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -303,6 +303,12 @@ export function setLoggedIn(credentials) { localStorage.setItem("mx_device_id", credentials.deviceId); } + // The user registered as a PWLU (PassWord-Less User), the generated password + // is cached here such that the user can change it at a later time. + if (credentials.password) { + localStorage.setItem("mx_pass", credentials.password); + } + console.log("Session persisted for %s", credentials.userId); } catch (e) { console.warn("Error using local storage: can't persist session!", e); diff --git a/src/component-index.js b/src/component-index.js index d6873c6dfd..aa65cabbec 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -95,8 +96,8 @@ import views$dialogs$QuestionDialog from './components/views/dialogs/QuestionDia views$dialogs$QuestionDialog && (module.exports.components['views.dialogs.QuestionDialog'] = views$dialogs$QuestionDialog); import views$dialogs$SessionRestoreErrorDialog from './components/views/dialogs/SessionRestoreErrorDialog'; views$dialogs$SessionRestoreErrorDialog && (module.exports.components['views.dialogs.SessionRestoreErrorDialog'] = views$dialogs$SessionRestoreErrorDialog); -import views$dialogs$SetDisplayNameDialog from './components/views/dialogs/SetDisplayNameDialog'; -views$dialogs$SetDisplayNameDialog && (module.exports.components['views.dialogs.SetDisplayNameDialog'] = views$dialogs$SetDisplayNameDialog); +import views$dialogs$SetMxIdDialog from './components/views/dialogs/SetMxIdDialog'; +views$dialogs$SetMxIdDialog && (module.exports.components['views.dialogs.SetMxIdDialog'] = views$dialogs$SetMxIdDialog); import views$dialogs$TextInputDialog from './components/views/dialogs/TextInputDialog'; views$dialogs$TextInputDialog && (module.exports.components['views.dialogs.TextInputDialog'] = views$dialogs$TextInputDialog); import views$dialogs$UnknownDeviceDialog from './components/views/dialogs/UnknownDeviceDialog'; diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index c4eeb03d5f..6514b32f79 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -43,6 +43,10 @@ export default React.createClass({ onRoomCreated: React.PropTypes.func, onUserSettingsClose: React.PropTypes.func, + // Called with the credentials of a registered user (if they were a ROU that + // transitioned to PWLU) + onRegistered: React.PropTypes.func, + teamToken: React.PropTypes.string, // and lots and lots of other stuff. @@ -184,6 +188,7 @@ export default React.createClass({ roomAddress={this.props.currentRoomAlias || this.props.currentRoomId} autoJoin={this.props.autoJoin} onRoomIdResolved={this.props.onRoomIdResolved} + onRegistered={this.props.onRegistered} eventId={this.props.initialEventId} thirdPartyInvite={this.props.thirdPartyInvite} oobData={this.props.roomOobData} diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 9b8aa3426a..0fe2d6a52f 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1166,6 +1166,7 @@ module.exports = React.createClass({ onRoomIdResolved={this.onRoomIdResolved} onRoomCreated={this.onRoomCreated} onUserSettingsClose={this.onUserSettingsClose} + onRegistered={this.onRegistered} teamToken={this._teamToken} {...this.props} {...this.state} diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index a0c36374b6..848a8cc7ba 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -69,6 +69,10 @@ module.exports = React.createClass({ // once it has been resolved. onRoomIdResolved: React.PropTypes.func, + // Called with the credentials of a registered user (if they were a ROU that + // transitioned to PWLU) + onRegistered: React.PropTypes.func, + // An object representing a third party invite to join this room // Fields: // * inviteSignUrl (string) The URL used to join this room from an email invite @@ -764,38 +768,29 @@ module.exports = React.createClass({ var self = this; var cli = MatrixClientPeg.get(); - var display_name_promise = q(); - // if this is the first room we're joining, check the user has a display name - // and if they don't, prompt them to set one. - // NB. This unfortunately does not re-use the ChangeDisplayName component because - // it doesn't behave quite as desired here (we want an input field here rather than - // content-editable, and we want a default). - if (cli.getRooms().filter((r) => { - return r.hasMembershipState(cli.credentials.userId, "join"); - })) { - display_name_promise = cli.getProfileInfo(cli.credentials.userId).then((result) => { - if (!result.displayname) { - var SetDisplayNameDialog = sdk.getComponent('views.dialogs.SetDisplayNameDialog'); - var dialog_defer = q.defer(); - Modal.createDialog(SetDisplayNameDialog, { - currentDisplayName: result.displayname, - onFinished: (submitted, newDisplayName) => { - if (submitted) { - cli.setDisplayName(newDisplayName).done(() => { - dialog_defer.resolve(); - }); - } - else { - dialog_defer.reject(); - } - } - }); - return dialog_defer.promise; + var mxIdPromise = q(); + + // If the user is a ROU, allow them to transition to a PWLU + if (cli && cli.isGuest()) { + const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog'); + const defered = q.defer(); + mxIdPromise = defered.promise; + Modal.createDialog(SetMxIdDialog, { + onFinished: (submitted, credentials) => { + if (!submitted) { + defered.reject(); + return; + } + this.props.onRegistered(credentials); + defered.resolve(); } }); } - display_name_promise.then(() => { + mxIdPromise.then(() => { + this.setState({ + joining: true + }); // if this is an invite and has the 'direct' hint set, mark it as a DM room now. if (this.state.room) { const me = this.state.room.getMember(MatrixClientPeg.get().credentials.userId); @@ -870,10 +865,6 @@ module.exports = React.createClass({ }); } }).done(); - - this.setState({ - joining: true - }); }, onMessageListScroll: function(ev) { diff --git a/src/components/views/dialogs/SetDisplayNameDialog.js b/src/components/views/dialogs/SetDisplayNameDialog.js deleted file mode 100644 index 1047e05c26..0000000000 --- a/src/components/views/dialogs/SetDisplayNameDialog.js +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright 2016 OpenMarket Ltd - -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. -*/ - -import React from 'react'; -import sdk from '../../../index'; -import MatrixClientPeg from '../../../MatrixClientPeg'; - -/** - * Prompt the user to set a display name. - * - * On success, `onFinished(true, newDisplayName)` is called. - */ -export default React.createClass({ - displayName: 'SetDisplayNameDialog', - propTypes: { - onFinished: React.PropTypes.func.isRequired, - currentDisplayName: React.PropTypes.string, - }, - - getInitialState: function() { - if (this.props.currentDisplayName) { - return { value: this.props.currentDisplayName }; - } - - if (MatrixClientPeg.get().isGuest()) { - return { value : "Guest " + MatrixClientPeg.get().getUserIdLocalpart() }; - } - else { - return { value : MatrixClientPeg.get().getUserIdLocalpart() }; - } - }, - - componentDidMount: function() { - this.refs.input_value.select(); - }, - - onValueChange: function(ev) { - this.setState({ - value: ev.target.value - }); - }, - - onFormSubmit: function(ev) { - ev.preventDefault(); - this.props.onFinished(true, this.state.value); - return false; - }, - - render: function() { - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - return ( - -
- Your display name is how you'll appear to others when you speak in rooms.
- What would you like it to be? -
-
-
- -
-
- -
-
-
- ); - }, -}); diff --git a/src/components/views/dialogs/SetMxIdDialog.js b/src/components/views/dialogs/SetMxIdDialog.js new file mode 100644 index 0000000000..db88c3304b --- /dev/null +++ b/src/components/views/dialogs/SetMxIdDialog.js @@ -0,0 +1,152 @@ +/* +Copyright 2016 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd + +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. +*/ + +import q from 'q'; +import React from 'react'; +import sdk from '../../../index'; +import MatrixClientPeg from '../../../MatrixClientPeg'; + +/** + * 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, + }, + + getInitialState: function() { + return { + username : '', + doingUIAuth: false, + } + }, + + componentDidMount: function() { + this.refs.input_value.select(); + + this._matrixClient = MatrixClientPeg.get(); + }, + + onValueChange: function(ev) { + this.setState({ + username: ev.target.value + }); + }, + + onSubmit: function(ev) { + this.setState({ + doingUIAuth: true, + }); + }, + + _generatePassword: function() { + return Math.random().toString(36).slice(2); + }, + + _makeRegisterRequest: function(auth) { + // Not upgrading - changing mxids + const guestAccessToken = null; + this._generatedPassword = this._generatePassword(); + + return this._matrixClient.register( + this.state.username, + this._generatedPassword, + undefined, // session id: included in the auth dict already + auth, + {}, + guestAccessToken, + ); + }, + + _onUIAuthFinished: function(success, response) { + this.setState({ + doingUIAuth: false, + }); + console.info('Auth Finsihed', arguments); + + if (!success) { + this.setState({ errorText : response.message }); + return; + } + + // XXX Implement RTS /register here + const teamToken = null; + + this.props.onFinished(true, { + userId: response.user_id, + deviceId: response.device_id, + homeserverUrl: this._matrixClient.getHomeserverUrl(), + identityServerUrl: this._matrixClient.getIdentityServerUrl(), + accessToken: response.access_token, + password: this._generatedPassword, + teamToken: teamToken, + }); + }, + + render: function() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth'); + const Spinner = sdk.getComponent('elements.Spinner'); + let auth; + if (this.state.doingUIAuth) { + auth = ; + } + return ( + +
+

+ Beyond this point you're going to need to pick a username - your + unique identifire in Riot. +

+

+ + You can't change your username, but you can always choose how you + appear to other people in Riot by changing your display name. + +

+ + { auth } +
+ { this.state.errorText } +
+
+
+ +
+
+ ); + }, +}); diff --git a/test/test-utils.js b/test/test-utils.js index 5209465362..9f404f98eb 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -133,6 +133,7 @@ export function createTestClient() { sendHtmlMessage: () => q({}), getSyncState: () => "SYNCING", generateClientSecret: () => "t35tcl1Ent5ECr3T", + isGuest: () => false, }; }