From 8725ef38639573bf6f0b36d6ee6cac305e5ef70a Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 11 May 2017 17:47:45 +0100 Subject: [PATCH 01/12] Remove "Current Password" input if mx_pass exists If the user is PWLU, do not show "Current Password" field in ChangePassword and when setting a new password, use the cached password. --- src/components/structures/LoggedInView.js | 1 + src/components/structures/MatrixChat.js | 9 ++++- src/components/structures/UserSettings.js | 5 +++ .../views/settings/ChangePassword.js | 38 +++++++++++-------- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 4001227355..2afb43bf47 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -216,6 +216,7 @@ export default React.createClass({ enableLabs={this.props.config.enableLabs} referralBaseUrl={this.props.config.referralBaseUrl} teamToken={this.props.teamToken} + cachedPassword={this.props.cachedPassword} />; if (!this.props.collapse_rhs) right_panel = ; break; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 0c8c60ba5c..b3fa6e9040 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -590,6 +590,12 @@ module.exports = React.createClass({ payload.releaseNotes ); break; + case 'password_changed': + this.setState({ + userHasGeneratedPassword: false, + }); + localStorage.removeItem("mx_pass"); + break; } }, @@ -1176,7 +1182,8 @@ module.exports = React.createClass({ onUserSettingsClose={this.onUserSettingsClose} onRegistered={this.onRegistered} teamToken={this._teamToken} - userHasGeneratedPassword={this.state.userHasGeneratedPassword} + cachedPassword={this.state.userHasGeneratedPassword ? + localStorage.getItem('mx_pass') : null} {...this.props} {...this.state} /> diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 46dce8bd2e..fa0fcadf0e 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -139,6 +139,9 @@ module.exports = React.createClass({ // Team token for the referral link. If falsy, the referral section will // not appear teamToken: React.PropTypes.string, + + // the user is a PWLU (/w password stashed in localStorage 'mx_pass') + cachedPassword: React.PropTypes.string, }, getDefaultProps: function() { @@ -331,6 +334,7 @@ module.exports = React.createClass({ receive push notifications on other devices until you log back in to them.`, }); + dis.dispatch({action: 'password_changed'}); }, onUpgradeClicked: function() { @@ -894,6 +898,7 @@ module.exports = React.createClass({ rowLabelClassName="mx_UserSettings_profileLabelCell" rowInputClassName="mx_UserSettings_profileInputCell" buttonClassName="mx_UserSettings_button mx_UserSettings_changePasswordButton" + cachedPassword={this.props.cachedPassword} onError={this.onPasswordChangeError} onFinished={this.onPasswordChanged} /> ); diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index 20ce45e5dd..bbb5d14219 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -31,7 +31,10 @@ module.exports = React.createClass({ rowClassName: React.PropTypes.string, rowLabelClassName: React.PropTypes.string, rowInputClassName: React.PropTypes.string, - buttonClassName: React.PropTypes.string + buttonClassName: React.PropTypes.string, + + // user is a PWLU (/w password stashed in localStorage 'mx_pass') + cachedPassword: React.PropTypes.string, }, Phases: { @@ -121,10 +124,10 @@ module.exports = React.createClass({ matrixClient: MatrixClientPeg.get(), } ); - }, + }, onClickChange: function() { - var old_password = this.refs.old_input.value; + var old_password = this.props.cachedPassword || this.refs.old_input.value; var new_password = this.refs.new_input.value; var confirm_password = this.refs.confirm_input.value; var err = this.props.onCheckPassword( @@ -139,23 +142,28 @@ module.exports = React.createClass({ }, render: function() { - var rowClassName = this.props.rowClassName; - var rowLabelClassName = this.props.rowLabelClassName; - var rowInputClassName = this.props.rowInputClassName; - var buttonClassName = this.props.buttonClassName; + const rowClassName = this.props.rowClassName; + const rowLabelClassName = this.props.rowLabelClassName; + const rowInputClassName = this.props.rowInputClassName; + const buttonClassName = this.props.buttonClassName; + + let currentPassword = null; + if (!this.props.cachedPassword) { + currentPassword =
+
+ +
+
+ +
+
; + } switch (this.state.phase) { case this.Phases.Edit: return (
-
-
- -
-
- -
-
+ { currentPassword }
From 1176573f39b3c2531887e36a2d6c79b3136c5ab5 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 12 May 2017 12:02:45 +0100 Subject: [PATCH 02/12] Implement SessionStore This wraps session-related state into a basic flux store. The localStorage item 'mx_pass' is the only thing managed by this store for now but it could easily be extended to track other items (like the teamToken which is passed around through props a lot) --- src/Lifecycle.js | 12 ++-- src/components/structures/LoggedInView.js | 3 +- src/components/structures/MatrixChat.js | 29 +++++---- src/components/structures/UserSettings.js | 4 -- .../views/settings/ChangePassword.js | 25 ++++++-- src/stores/SessionStore.js | 63 +++++++++++++++++++ 6 files changed, 104 insertions(+), 32 deletions(-) create mode 100644 src/stores/SessionStore.js diff --git a/src/Lifecycle.js b/src/Lifecycle.js index a7a06401da..decb544b3c 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -289,7 +289,6 @@ export function setLoggedIn(credentials) { // Resolves by default let teamPromise = Promise.resolve(null); - let isPasswordStored = false; // persist the session if (localStorage) { @@ -312,8 +311,11 @@ export function setLoggedIn(credentials) { // 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); - isPasswordStored = true; + // Update SessionStore + dis.dispatch({ + action: 'cached_password', + cachedPassword: credentials.password, + }); } console.log("Session persisted for %s", credentials.userId); @@ -339,10 +341,10 @@ export function setLoggedIn(credentials) { MatrixClientPeg.replaceUsingCreds(credentials); teamPromise.then((teamToken) => { - dis.dispatch({action: 'on_logged_in', teamToken: teamToken, isPasswordStored}); + dis.dispatch({action: 'on_logged_in', teamToken: teamToken}); }, (err) => { console.warn("Failed to get team token on login", err); - dis.dispatch({action: 'on_logged_in', teamToken: null, isPasswordStored}); + dis.dispatch({action: 'on_logged_in', teamToken: null}); }); startMatrixClient(); diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 2afb43bf47..0851c01a18 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -51,7 +51,7 @@ export default React.createClass({ // Has the user generated a password that is stored in local storage? // (are they a PWLU?) - userHasGeneratedPassword: React.PropTypes.boolean, + userHasGeneratedPassword: React.PropTypes.bool, // and lots and lots of other stuff. }, @@ -216,7 +216,6 @@ export default React.createClass({ enableLabs={this.props.config.enableLabs} referralBaseUrl={this.props.config.referralBaseUrl} teamToken={this.props.teamToken} - cachedPassword={this.props.cachedPassword} />; if (!this.props.collapse_rhs) right_panel = ; break; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index b3fa6e9040..d7e24c019a 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -40,6 +40,8 @@ var PageTypes = require('../../PageTypes'); var createRoom = require("../../createRoom"); import * as UDEHandler from '../../UnknownDeviceErrorHandler'; +import getSessionStore from '../../stores/SessionStore'; + module.exports = React.createClass({ displayName: 'MatrixChat', @@ -139,8 +141,7 @@ module.exports = React.createClass({ register_is_url: null, register_id_sid: null, - // Initially, use localStorage as source of truth - userHasGeneratedPassword: localStorage && localStorage.getItem('mx_pass'), + userHasGeneratedPassword: false, }; return s; }, @@ -249,6 +250,10 @@ module.exports = React.createClass({ register_hs_url: paramHs, }); } + + this._sessionStore = getSessionStore(); + this._sessionStore.on('update', this._setStateFromSessionStore); + this._setStateFromSessionStore(); }, componentDidMount: function() { @@ -590,12 +595,6 @@ module.exports = React.createClass({ payload.releaseNotes ); break; - case 'password_changed': - this.setState({ - userHasGeneratedPassword: false, - }); - localStorage.removeItem("mx_pass"); - break; } }, @@ -765,15 +764,11 @@ module.exports = React.createClass({ /** * Called when a new logged in session has started */ - _onLoggedIn: function(teamToken, isPasswordStored) { + _onLoggedIn: function(teamToken) { this.setState({ guestCreds: null, loggedIn: true, loggingIn: false, - // isPasswordStored only true when ROU sets a username and becomes PWLU. - // (the password was randomly generated and stored in localStorage). - userHasGeneratedPassword: - this.state.userHasGeneratedPassword || isPasswordStored, }); if (teamToken) { @@ -902,6 +897,12 @@ module.exports = React.createClass({ }); }, + _setStateFromSessionStore() { + this.setState({ + userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()), + }); + }, + onFocus: function(ev) { dis.dispatch({action: 'focus_composer'}); }, @@ -1182,8 +1183,6 @@ module.exports = React.createClass({ onUserSettingsClose={this.onUserSettingsClose} onRegistered={this.onRegistered} teamToken={this._teamToken} - cachedPassword={this.state.userHasGeneratedPassword ? - localStorage.getItem('mx_pass') : null} {...this.props} {...this.state} /> diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index fa0fcadf0e..d352d5cae8 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -139,9 +139,6 @@ module.exports = React.createClass({ // Team token for the referral link. If falsy, the referral section will // not appear teamToken: React.PropTypes.string, - - // the user is a PWLU (/w password stashed in localStorage 'mx_pass') - cachedPassword: React.PropTypes.string, }, getDefaultProps: function() { @@ -898,7 +895,6 @@ module.exports = React.createClass({ rowLabelClassName="mx_UserSettings_profileLabelCell" rowInputClassName="mx_UserSettings_profileInputCell" buttonClassName="mx_UserSettings_button mx_UserSettings_changePasswordButton" - cachedPassword={this.props.cachedPassword} onError={this.onPasswordChangeError} onFinished={this.onPasswordChanged} /> ); diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index bbb5d14219..3a1c777cd9 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -22,6 +22,8 @@ var Modal = require("../../../Modal"); var sdk = require("../../../index"); import AccessibleButton from '../elements/AccessibleButton'; +import getSessionStore from '../../../stores/SessionStore'; + module.exports = React.createClass({ displayName: 'ChangePassword', propTypes: { @@ -32,9 +34,6 @@ module.exports = React.createClass({ rowLabelClassName: React.PropTypes.string, rowInputClassName: React.PropTypes.string, buttonClassName: React.PropTypes.string, - - // user is a PWLU (/w password stashed in localStorage 'mx_pass') - cachedPassword: React.PropTypes.string, }, Phases: { @@ -63,10 +62,24 @@ module.exports = React.createClass({ getInitialState: function() { return { - phase: this.Phases.Edit + phase: this.Phases.Edit, + cachedPassword: null, }; }, + componentWillMount: function() { + this.sessionStore = getSessionStore(); + this.sessionStore.on('update', this.setStateFromSessionStore); + + this.setStateFromSessionStore(); + }, + + setStateFromSessionStore: function() { + this.setState({ + cachedPassword: this.sessionStore.getCachedPassword(), + }); + }, + changePassword: function(old_password, new_password) { var cli = MatrixClientPeg.get(); @@ -127,7 +140,7 @@ module.exports = React.createClass({ }, onClickChange: function() { - var old_password = this.props.cachedPassword || this.refs.old_input.value; + var old_password = this.state.cachedPassword || this.refs.old_input.value; var new_password = this.refs.new_input.value; var confirm_password = this.refs.confirm_input.value; var err = this.props.onCheckPassword( @@ -148,7 +161,7 @@ module.exports = React.createClass({ const buttonClassName = this.props.buttonClassName; let currentPassword = null; - if (!this.props.cachedPassword) { + if (!this.state.cachedPassword) { currentPassword =
diff --git a/src/stores/SessionStore.js b/src/stores/SessionStore.js new file mode 100644 index 0000000000..1c19494e23 --- /dev/null +++ b/src/stores/SessionStore.js @@ -0,0 +1,63 @@ +import dis from '../dispatcher'; +import EventEmitter from 'events'; + +/** + * A class for storing application state to do with the session. This is a simple flux + * store that listens for actions and updates its state accordingly, informing any + * listeners (views) of state changes via the 'update' event. + */ +function SessionStore() { + // Initialise state + this._state = { + cachedPassword: localStorage.getItem('mx_pass'), + }; + + dis.register(this._onAction.bind(this)); +} + +// Inherit from EventEmitter +SessionStore.prototype = EventEmitter.prototype; + +SessionStore.prototype._update = function() { + // Persist state to localStorage + if (this._state.cachedPassword) { + localStorage.setItem('mx_pass', this._state.cachedPassword); + } else { + localStorage.removeItem('mx_pass', this._state.cachedPassword); + } + + this.emit('update'); +}; + +SessionStore.prototype._setState = function(newState) { + this._state = Object.assign(this._state, newState); + this._update(); +}; + +SessionStore.prototype._onAction = function(payload) { + switch (payload.action) { + case 'cached_password': + this._setState({ + cachedPassword: payload.cachedPassword, + }); + break; + case 'password_changed': + this._setState({ + cachedPassword: null, + }); + break; + } +}; + +SessionStore.prototype.getCachedPassword = function() { + return this._state.cachedPassword; +}; + +// Export singleton getter +let singletonSessionStore = null; +export default function getSessionStore() { + if (!singletonSessionStore) { + singletonSessionStore = new SessionStore(); + } + return singletonSessionStore; +} From 5c8187dc8f9804e01ad4d01af27d07801caebc2c Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 12 May 2017 15:47:37 +0100 Subject: [PATCH 03/12] Explicitly pass thru userHasGeneratedPassword --- src/components/structures/MatrixChat.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index d7e24c019a..5975d6cf5f 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1183,6 +1183,7 @@ module.exports = React.createClass({ onUserSettingsClose={this.onUserSettingsClose} onRegistered={this.onRegistered} teamToken={this._teamToken} + userHasGeneratedPassword={this.state.userHasGeneratedPassword} {...this.props} {...this.state} /> From 6ffe7ef9b2aabcb41c10043fe762c529f80617dc Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 12 May 2017 15:50:01 +0100 Subject: [PATCH 04/12] Use same singleton impl as MatrixClientPeg for SessionStore --- src/stores/SessionStore.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/stores/SessionStore.js b/src/stores/SessionStore.js index 1c19494e23..7be3885fde 100644 --- a/src/stores/SessionStore.js +++ b/src/stores/SessionStore.js @@ -55,9 +55,7 @@ SessionStore.prototype.getCachedPassword = function() { // Export singleton getter let singletonSessionStore = null; -export default function getSessionStore() { - if (!singletonSessionStore) { - singletonSessionStore = new SessionStore(); - } - return singletonSessionStore; +if (!singletonSessionStore) { + singletonSessionStore = new SessionStore(); } +module.exports = singletonSessionStore; From 536724e7c5b6f55352311ad2856e95ece60ab5cb Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 12 May 2017 15:58:44 +0100 Subject: [PATCH 05/12] ES6 SessionStore --- src/components/structures/MatrixChat.js | 4 +- .../views/settings/ChangePassword.js | 4 +- src/stores/SessionStore.js | 89 ++++++++++--------- 3 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 5975d6cf5f..b8b3f51422 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -40,7 +40,7 @@ var PageTypes = require('../../PageTypes'); var createRoom = require("../../createRoom"); import * as UDEHandler from '../../UnknownDeviceErrorHandler'; -import getSessionStore from '../../stores/SessionStore'; +import sessionStore from '../../stores/SessionStore'; module.exports = React.createClass({ displayName: 'MatrixChat', @@ -251,7 +251,7 @@ module.exports = React.createClass({ }); } - this._sessionStore = getSessionStore(); + this._sessionStore = sessionStore; this._sessionStore.on('update', this._setStateFromSessionStore); this._setStateFromSessionStore(); }, diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index 3a1c777cd9..c20bc47152 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -22,7 +22,7 @@ var Modal = require("../../../Modal"); var sdk = require("../../../index"); import AccessibleButton from '../elements/AccessibleButton'; -import getSessionStore from '../../../stores/SessionStore'; +import sessionStore from '../../../stores/SessionStore'; module.exports = React.createClass({ displayName: 'ChangePassword', @@ -68,7 +68,7 @@ module.exports = React.createClass({ }, componentWillMount: function() { - this.sessionStore = getSessionStore(); + this.sessionStore = sessionStore; this.sessionStore.on('update', this.setStateFromSessionStore); this.setStateFromSessionStore(); diff --git a/src/stores/SessionStore.js b/src/stores/SessionStore.js index 7be3885fde..bf605d7f07 100644 --- a/src/stores/SessionStore.js +++ b/src/stores/SessionStore.js @@ -6,53 +6,54 @@ import EventEmitter from 'events'; * store that listens for actions and updates its state accordingly, informing any * listeners (views) of state changes via the 'update' event. */ -function SessionStore() { - // Initialise state - this._state = { - cachedPassword: localStorage.getItem('mx_pass'), - }; +class SessionStore extends EventEmitter { + constructor() { + super(); - dis.register(this._onAction.bind(this)); + // Initialise state + this._state = { + cachedPassword: localStorage.getItem('mx_pass'), + }; + + dis.register(this._onAction.bind(this)); + } + + _update() { + // Persist state to localStorage + if (this._state.cachedPassword) { + localStorage.setItem('mx_pass', this._state.cachedPassword); + } else { + localStorage.removeItem('mx_pass', this._state.cachedPassword); + } + + this.emit('update'); + } + + _setState(newState) { + this._state = Object.assign(this._state, newState); + this._update(); + } + + _onAction(payload) { + switch (payload.action) { + case 'cached_password': + this._setState({ + cachedPassword: payload.cachedPassword, + }); + break; + case 'password_changed': + this._setState({ + cachedPassword: null, + }); + break; + } + } + + getCachedPassword() { + return this._state.cachedPassword; + } } -// Inherit from EventEmitter -SessionStore.prototype = EventEmitter.prototype; - -SessionStore.prototype._update = function() { - // Persist state to localStorage - if (this._state.cachedPassword) { - localStorage.setItem('mx_pass', this._state.cachedPassword); - } else { - localStorage.removeItem('mx_pass', this._state.cachedPassword); - } - - this.emit('update'); -}; - -SessionStore.prototype._setState = function(newState) { - this._state = Object.assign(this._state, newState); - this._update(); -}; - -SessionStore.prototype._onAction = function(payload) { - switch (payload.action) { - case 'cached_password': - this._setState({ - cachedPassword: payload.cachedPassword, - }); - break; - case 'password_changed': - this._setState({ - cachedPassword: null, - }); - break; - } -}; - -SessionStore.prototype.getCachedPassword = function() { - return this._state.cachedPassword; -}; - // Export singleton getter let singletonSessionStore = null; if (!singletonSessionStore) { From 2b4c87aca6eac0d32081624093a1f25fd0683621 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 12 May 2017 16:02:38 +0100 Subject: [PATCH 06/12] Remove useless comment --- src/stores/SessionStore.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stores/SessionStore.js b/src/stores/SessionStore.js index bf605d7f07..d3370d2df3 100644 --- a/src/stores/SessionStore.js +++ b/src/stores/SessionStore.js @@ -54,7 +54,6 @@ class SessionStore extends EventEmitter { } } -// Export singleton getter let singletonSessionStore = null; if (!singletonSessionStore) { singletonSessionStore = new SessionStore(); From da3cb0ee48aaea420a31fd9e060e159dfd7e910f Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 15 May 2017 14:52:19 +0100 Subject: [PATCH 07/12] SessionStore extends flux.Store --- src/components/structures/MatrixChat.js | 2 +- .../views/settings/ChangePassword.js | 10 ++++----- src/stores/SessionStore.js | 21 ++++++++++++------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index b8b3f51422..4556148986 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -252,7 +252,7 @@ module.exports = React.createClass({ } this._sessionStore = sessionStore; - this._sessionStore.on('update', this._setStateFromSessionStore); + this._sessionStore.addListener(this._setStateFromSessionStore); this._setStateFromSessionStore(); }, diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index c20bc47152..4d8373bc52 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -68,15 +68,15 @@ module.exports = React.createClass({ }, componentWillMount: function() { - this.sessionStore = sessionStore; - this.sessionStore.on('update', this.setStateFromSessionStore); + this._sessionStore = sessionStore; + this._sessionStore.addListener(this._setStateFromSessionStore); - this.setStateFromSessionStore(); + this._setStateFromSessionStore(); }, - setStateFromSessionStore: function() { + _setStateFromSessionStore: function() { this.setState({ - cachedPassword: this.sessionStore.getCachedPassword(), + cachedPassword: this._sessionStore.getCachedPassword(), }); }, diff --git a/src/stores/SessionStore.js b/src/stores/SessionStore.js index d3370d2df3..1570f58688 100644 --- a/src/stores/SessionStore.js +++ b/src/stores/SessionStore.js @@ -1,21 +1,26 @@ import dis from '../dispatcher'; -import EventEmitter from 'events'; +import {Store} from 'flux/utils'; /** * A class for storing application state to do with the session. This is a simple flux * store that listens for actions and updates its state accordingly, informing any - * listeners (views) of state changes via the 'update' event. + * listeners (views) of state changes. + * + * Usage: + * ``` + * sessionStore.addListener(() => { + * this.setState({ cachedPassword: sessionStore.getCachedPassword() }) + * }) + * ``` */ -class SessionStore extends EventEmitter { +class SessionStore extends Store { constructor() { - super(); + super(dis); // Initialise state this._state = { cachedPassword: localStorage.getItem('mx_pass'), }; - - dis.register(this._onAction.bind(this)); } _update() { @@ -26,7 +31,7 @@ class SessionStore extends EventEmitter { localStorage.removeItem('mx_pass', this._state.cachedPassword); } - this.emit('update'); + this.__emitChange(); } _setState(newState) { @@ -34,7 +39,7 @@ class SessionStore extends EventEmitter { this._update(); } - _onAction(payload) { + __onDispatch(payload) { switch (payload.action) { case 'cached_password': this._setState({ From f73cf772fb83db136cd44fd3cc40e50aec83905e Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 15 May 2017 14:56:05 +0100 Subject: [PATCH 08/12] Move sessionStore ref from MatrixChat to LoggedInView MatrixChat didn't actually use the sessionStore, so this is one less prop to pass. --- src/components/structures/LoggedInView.js | 17 ++++++++++++----- src/components/structures/MatrixChat.js | 12 ------------ 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 0851c01a18..240a3499a2 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -23,6 +23,7 @@ import Notifier from '../../Notifier'; import PageTypes from '../../PageTypes'; import sdk from '../../index'; import dis from '../../dispatcher'; +import sessionStore from '../../stores/SessionStore'; /** * This is what our MatrixChat shows when we are logged in. The precise view is @@ -49,10 +50,6 @@ export default React.createClass({ teamToken: React.PropTypes.string, - // Has the user generated a password that is stored in local storage? - // (are they a PWLU?) - userHasGeneratedPassword: React.PropTypes.bool, - // and lots and lots of other stuff. }, @@ -80,6 +77,10 @@ export default React.createClass({ this._scrollStateMap = {}; document.addEventListener('keydown', this._onKeyDown); + + this._sessionStore = sessionStore; + this._sessionStore.addListener(this._setStateFromSessionStore); + this._setStateFromSessionStore(); }, componentWillUnmount: function() { @@ -97,6 +98,12 @@ export default React.createClass({ return this.refs.roomView.canResetTimeline(); }, + _setStateFromSessionStore() { + this.setState({ + userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()), + }); + }, + _onKeyDown: function(ev) { /* // Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers @@ -257,7 +264,7 @@ export default React.createClass({ />; } else if (this.props.matrixClient.isGuest()) { topBar = ; - } else if (this.props.userHasGeneratedPassword) { + } else if (this.state.userHasGeneratedPassword) { topBar = ; } else if (Notifier.supportsDesktopNotifications() && !Notifier.isEnabled() && !Notifier.isToolbarHidden()) { topBar = ; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 4556148986..45b4d07055 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -40,8 +40,6 @@ var PageTypes = require('../../PageTypes'); var createRoom = require("../../createRoom"); import * as UDEHandler from '../../UnknownDeviceErrorHandler'; -import sessionStore from '../../stores/SessionStore'; - module.exports = React.createClass({ displayName: 'MatrixChat', @@ -250,10 +248,6 @@ module.exports = React.createClass({ register_hs_url: paramHs, }); } - - this._sessionStore = sessionStore; - this._sessionStore.addListener(this._setStateFromSessionStore); - this._setStateFromSessionStore(); }, componentDidMount: function() { @@ -897,12 +891,6 @@ module.exports = React.createClass({ }); }, - _setStateFromSessionStore() { - this.setState({ - userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()), - }); - }, - onFocus: function(ev) { dis.dispatch({action: 'focus_composer'}); }, From eb0041d21ab603e3b0d1f0474068ded68f09dba7 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 15 May 2017 17:03:54 +0100 Subject: [PATCH 09/12] Remove redundant state --- src/components/structures/MatrixChat.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 45b4d07055..c5b58c3285 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -138,8 +138,6 @@ module.exports = React.createClass({ register_hs_url: null, register_is_url: null, register_id_sid: null, - - userHasGeneratedPassword: false, }; return s; }, @@ -1171,7 +1169,6 @@ module.exports = React.createClass({ onUserSettingsClose={this.onUserSettingsClose} onRegistered={this.onRegistered} teamToken={this._teamToken} - userHasGeneratedPassword={this.state.userHasGeneratedPassword} {...this.props} {...this.state} /> From 269fd511300bc001604d70a02e96e6e8bef1c283 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 15 May 2017 17:17:32 +0100 Subject: [PATCH 10/12] Remove SessionStore listener on unmount --- src/components/structures/LoggedInView.js | 7 ++++++- src/components/views/settings/ChangePassword.js | 10 +++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 240a3499a2..bbbf6dff0e 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -79,12 +79,17 @@ export default React.createClass({ document.addEventListener('keydown', this._onKeyDown); this._sessionStore = sessionStore; - this._sessionStore.addListener(this._setStateFromSessionStore); + this._removeSSListener = this._sessionStore.addListener( + this._setStateFromSessionStore, + ).remove; this._setStateFromSessionStore(); }, componentWillUnmount: function() { document.removeEventListener('keydown', this._onKeyDown); + if (this._removeSSListener) { + this._removeSSListener(); + } }, getScrollStateForRoom: function(roomId) { diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index 4d8373bc52..07680818df 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -69,11 +69,19 @@ module.exports = React.createClass({ componentWillMount: function() { this._sessionStore = sessionStore; - this._sessionStore.addListener(this._setStateFromSessionStore); + this._removeSSListener = this._sessionStore.addListener( + this._setStateFromSessionStore, + ).remove; this._setStateFromSessionStore(); }, + componentWillUnmount: function() { + if (this._removeSSListener) { + this._removeSSListener(); + } + }, + _setStateFromSessionStore: function() { this.setState({ cachedPassword: this._sessionStore.getCachedPassword(), From eb36e979c2591f252fb007bc2460b9d1b2dcbd6a Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Tue, 16 May 2017 11:52:51 +0100 Subject: [PATCH 11/12] Reference store token, call .remove on it on unmount --- src/components/structures/LoggedInView.js | 8 ++++---- src/components/views/settings/ChangePassword.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index bbbf6dff0e..a64ae0a25c 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -79,16 +79,16 @@ export default React.createClass({ document.addEventListener('keydown', this._onKeyDown); this._sessionStore = sessionStore; - this._removeSSListener = this._sessionStore.addListener( + this._sessionStoreToken = this._sessionStore.addListener( this._setStateFromSessionStore, - ).remove; + ); this._setStateFromSessionStore(); }, componentWillUnmount: function() { document.removeEventListener('keydown', this._onKeyDown); - if (this._removeSSListener) { - this._removeSSListener(); + if (this._sessionStoreToken) { + this._sessionStoreToken.remove(); } }, diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index 07680818df..e3845390de 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -69,16 +69,16 @@ module.exports = React.createClass({ componentWillMount: function() { this._sessionStore = sessionStore; - this._removeSSListener = this._sessionStore.addListener( + this._sessionStoreToken = this._sessionStore.addListener( this._setStateFromSessionStore, - ).remove; + ); this._setStateFromSessionStore(); }, componentWillUnmount: function() { - if (this._removeSSListener) { - this._removeSSListener(); + if (this._sessionStoreToken) { + this._sessionStoreToken.remove(); } }, From 633c6b39f6a1dd98613898287499ec7696a95099 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Tue, 16 May 2017 11:58:37 +0100 Subject: [PATCH 12/12] Add comment to Lifecycle --- src/Lifecycle.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index decb544b3c..20d5836dae 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -185,6 +185,14 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) { // returns a promise which resolves to true if a session is found in // localstorage +// +// N.B. Lifecycle.js should not maintain any further localStorage state, we +// are moving towards using SessionStore to keep track of state related +// to the current session (which is typically backed by localStorage). +// +// The plan is to gradually move the localStorage access done here into +// SessionStore to avoid bugs where the view becomes out-of-sync with +// localStorage (e.g. teamToken, isGuest etc.) function _restoreFromLocalStorage() { if (!localStorage) { return q(false);