diff --git a/.eslintrc.js b/.eslintrc.js
index 34d3af270c..6cd0e1015e 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -13,6 +13,7 @@ module.exports = {
plugins: [
"react",
"flowtype",
+ "babel"
],
env: {
es6: true,
@@ -23,6 +24,11 @@ module.exports = {
}
},
rules: {
+ // eslint's built in no-invalid-this rule breaks with class properties
+ "no-invalid-this": "off",
+ // so we replace it with a version that is class property aware
+ "babel/no-invalid-this": "error",
+
/** react **/
// This just uses the react plugin to help eslint known when
// variables have been used in JSX
diff --git a/package.json b/package.json
index 6e7013fb93..a07e2236aa 100644
--- a/package.json
+++ b/package.json
@@ -90,6 +90,7 @@
"babel-preset-react": "^6.11.1",
"eslint": "^3.13.1",
"eslint-config-google": "^0.7.1",
+ "eslint-plugin-babel": "^4.0.1",
"eslint-plugin-flowtype": "^2.30.0",
"eslint-plugin-react": "^6.9.0",
"expect": "^1.16.0",
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 87a2878e37..d5683b35a0 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.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.
@@ -24,6 +25,8 @@ import Presence from './Presence';
import dis from './dispatcher';
import DMRoomMap from './utils/DMRoomMap';
import RtsClient from './RtsClient';
+import Modal from './Modal';
+import sdk from './index';
/**
* Called at startup, to attempt to build a logged-in Matrix session. It tries
@@ -109,16 +112,17 @@ export function loadSession(opts) {
return q();
}
- if (_restoreFromLocalStorage()) {
- return q();
- }
+ return _restoreFromLocalStorage().then((success) => {
+ if (success) {
+ return;
+ }
- if (enableGuest) {
- return _registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName);
- }
+ if (enableGuest) {
+ return _registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName);
+ }
- // fall back to login screen
- return q();
+ // fall back to login screen
+ });
}
function _loginWithToken(queryParams, defaultDeviceDisplayName) {
@@ -178,10 +182,11 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
});
}
-// returns true if a session is found in localstorage
+// returns a promise which resolves to true if a session is found in
+// localstorage
function _restoreFromLocalStorage() {
if (!localStorage) {
- return false;
+ return q(false);
}
const hs_url = localStorage.getItem("mx_hs_url");
const is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org';
@@ -208,28 +213,55 @@ function _restoreFromLocalStorage() {
identityServerUrl: is_url,
guest: is_guest,
});
- return true;
+ return q(true);
} catch (e) {
- console.log("Unable to restore session", e);
-
- var msg = e.message;
- if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") {
- msg = "You need to log back in to generate end-to-end encryption keys "
- + "for this device and submit the public key to your homeserver. "
- + "This is a once off; sorry for the inconvenience.";
- }
-
- // don't leak things into the new session
- _clearLocalStorage();
-
- throw new Error("Unable to restore previous session: " + msg);
+ return _handleRestoreFailure(e);
}
} else {
console.log("No previous session found.");
- return false;
+ return q(false);
}
}
+function _handleRestoreFailure(e) {
+ console.log("Unable to restore session", e);
+
+ let msg = e.message;
+ if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") {
+ msg = "You need to log back in to generate end-to-end encryption keys "
+ + "for this device and submit the public key to your homeserver. "
+ + "This is a once off; sorry for the inconvenience.";
+
+ _clearLocalStorage();
+
+ return q.reject(new Error(
+ "Unable to restore previous session: " + msg,
+ ));
+ }
+
+ const def = q.defer();
+ const SessionRestoreErrorDialog =
+ sdk.getComponent('views.dialogs.SessionRestoreErrorDialog');
+
+ Modal.createDialog(SessionRestoreErrorDialog, {
+ error: msg,
+ onFinished: (success) => {
+ def.resolve(success);
+ },
+ });
+
+ return def.promise.then((success) => {
+ if (success) {
+ // user clicked continue.
+ _clearLocalStorage();
+ return false;
+ }
+
+ // try, try again
+ return _restoreFromLocalStorage();
+ });
+}
+
let rtsClient = null;
export function initRtsClient(url) {
rtsClient = new RtsClient(url);
diff --git a/src/RtsClient.js b/src/RtsClient.js
index 5cf2e811ad..8c3ce54b37 100644
--- a/src/RtsClient.js
+++ b/src/RtsClient.js
@@ -50,18 +50,18 @@ export default class RtsClient {
* Track a referral with the Riot Team Server. This should be called once a referred
* user has been successfully registered.
* @param {string} referrer the user ID of one who referred the user to Riot.
- * @param {string} userId the user ID of the user being referred.
- * @param {string} userEmail the email address linked to `userId`.
+ * @param {string} sid the sign-up identity server session ID .
+ * @param {string} clientSecret the sign-up client secret.
* @returns {Promise} a promise that resolves to { team_token: 'sometoken' } upon
* success.
*/
- trackReferral(referrer, userId, userEmail) {
+ trackReferral(referrer, sid, clientSecret) {
return request(this._url + '/register',
{
body: {
referrer: referrer,
- user_id: userId,
- user_email: userEmail,
+ session_id: sid,
+ client_secret: clientSecret,
},
method: 'POST',
}
diff --git a/src/SignupStages.js b/src/SignupStages.js
index cdb9d5989b..1441682c85 100644
--- a/src/SignupStages.js
+++ b/src/SignupStages.js
@@ -149,6 +149,7 @@ class EmailIdentityStage extends Stage {
nextLink
).then(function(response) {
self.sid = response.sid;
+ self.signupInstance.setIdSid(self.sid);
return self._completeVerify();
}).then(function(request) {
request.poll_for_success = true;
diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js
index d7d3e7bc7a..66a872958c 100644
--- a/src/UserSettingsStore.js
+++ b/src/UserSettingsStore.js
@@ -26,7 +26,7 @@ var Notifier = require("./Notifier");
module.exports = {
LABS_FEATURES: [
{
- name: 'Rich Text Editor',
+ name: 'New Composer & Autocomplete',
id: 'rich_text_editor',
default: false,
},
diff --git a/src/component-index.js b/src/component-index.js
index 5b28be0627..c705150e12 100644
--- a/src/component-index.js
+++ b/src/component-index.js
@@ -31,6 +31,8 @@ import structures$CreateRoom from './components/structures/CreateRoom';
structures$CreateRoom && (module.exports.components['structures.CreateRoom'] = structures$CreateRoom);
import structures$FilePanel from './components/structures/FilePanel';
structures$FilePanel && (module.exports.components['structures.FilePanel'] = structures$FilePanel);
+import structures$InteractiveAuth from './components/structures/InteractiveAuth';
+structures$InteractiveAuth && (module.exports.components['structures.InteractiveAuth'] = structures$InteractiveAuth);
import structures$LoggedInView from './components/structures/LoggedInView';
structures$LoggedInView && (module.exports.components['structures.LoggedInView'] = structures$LoggedInView);
import structures$MatrixChat from './components/structures/MatrixChat';
@@ -75,6 +77,8 @@ import views$dialogs$BaseDialog from './components/views/dialogs/BaseDialog';
views$dialogs$BaseDialog && (module.exports.components['views.dialogs.BaseDialog'] = views$dialogs$BaseDialog);
import views$dialogs$ChatInviteDialog from './components/views/dialogs/ChatInviteDialog';
views$dialogs$ChatInviteDialog && (module.exports.components['views.dialogs.ChatInviteDialog'] = views$dialogs$ChatInviteDialog);
+import views$dialogs$ConfirmUserActionDialog from './components/views/dialogs/ConfirmUserActionDialog';
+views$dialogs$ConfirmUserActionDialog && (module.exports.components['views.dialogs.ConfirmUserActionDialog'] = views$dialogs$ConfirmUserActionDialog);
import views$dialogs$DeactivateAccountDialog from './components/views/dialogs/DeactivateAccountDialog';
views$dialogs$DeactivateAccountDialog && (module.exports.components['views.dialogs.DeactivateAccountDialog'] = views$dialogs$DeactivateAccountDialog);
import views$dialogs$ErrorDialog from './components/views/dialogs/ErrorDialog';
@@ -85,6 +89,8 @@ import views$dialogs$NeedToRegisterDialog from './components/views/dialogs/NeedT
views$dialogs$NeedToRegisterDialog && (module.exports.components['views.dialogs.NeedToRegisterDialog'] = views$dialogs$NeedToRegisterDialog);
import views$dialogs$QuestionDialog from './components/views/dialogs/QuestionDialog';
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$TextInputDialog from './components/views/dialogs/TextInputDialog';
diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js
new file mode 100644
index 0000000000..70b3c2e306
--- /dev/null
+++ b/src/components/structures/InteractiveAuth.js
@@ -0,0 +1,152 @@
+/*
+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 Matrix from 'matrix-js-sdk';
+const InteractiveAuth = Matrix.InteractiveAuth;
+
+import React from 'react';
+
+import sdk from '../../index';
+
+import {getEntryComponentForLoginType} from '../views/login/InteractiveAuthEntryComponents';
+
+export default React.createClass({
+ displayName: 'InteractiveAuth',
+
+ propTypes: {
+ // response from initial request. If not supplied, will do a request on
+ // mount.
+ authData: React.PropTypes.shape({
+ flows: React.PropTypes.array,
+ params: React.PropTypes.object,
+ session: React.PropTypes.string,
+ }),
+
+ // callback
+ makeRequest: React.PropTypes.func.isRequired,
+
+ // callback called when the auth process has finished
+ // @param {bool} status True if the operation requiring
+ // auth was completed sucessfully, false if canceled.
+ // @param result The result of the authenticated call
+ onFinished: React.PropTypes.func.isRequired,
+ },
+
+ getInitialState: function() {
+ return {
+ authStage: null,
+ busy: false,
+ errorText: null,
+ stageErrorText: null,
+ submitButtonEnabled: false,
+ };
+ },
+
+ componentWillMount: function() {
+ this._unmounted = false;
+ this._authLogic = new InteractiveAuth({
+ authData: this.props.authData,
+ doRequest: this._requestCallback,
+ startAuthStage: this._startAuthStage,
+ });
+
+ this._authLogic.attemptAuth().then((result) => {
+ this.props.onFinished(true, result);
+ }).catch((error) => {
+ console.error("Error during user-interactive auth:", error);
+ if (this._unmounted) {
+ return;
+ }
+
+ const msg = error.message || error.toString();
+ this.setState({
+ errorText: msg
+ });
+ }).done();
+ },
+
+ componentWillUnmount: function() {
+ this._unmounted = true;
+ },
+
+ _startAuthStage: function(stageType, error) {
+ this.setState({
+ authStage: stageType,
+ errorText: error ? error.error : null,
+ }, this._setFocus);
+ },
+
+ _requestCallback: function(auth) {
+ this.setState({
+ busy: true,
+ errorText: null,
+ stageErrorText: null,
+ });
+ return this.props.makeRequest(auth).finally(() => {
+ if (this._unmounted) {
+ return;
+ }
+ this.setState({
+ busy: false,
+ });
+ });
+ },
+
+ _setFocus: function() {
+ if (this.refs.stageComponent && this.refs.stageComponent.focus) {
+ this.refs.stageComponent.focus();
+ }
+ },
+
+ _submitAuthDict: function(authData) {
+ this._authLogic.submitAuthDict(authData);
+ },
+
+ _renderCurrentStage: function() {
+ const stage = this.state.authStage;
+ var StageComponent = getEntryComponentForLoginType(stage);
+ return (
+
This operation requires additional authentication.
- {this._renderCurrentStage()} - {error} -Otherwise, + click here to send a bug report. +
+ ); + } + + return ( +We encountered an error trying to restore your previous session. If + you continue, you will need to log in again, and encrypted chat + history will be unreadable.
+ +If you have previously used a more recent version of Riot, your session + may be incompatible with this version. Close this window and return + to the more recent version.
+ + {bugreport} +To continue, please enter your password.
Password:
- +