From a7e4a2847eaadcd3ca1882078cb6815598adcfb4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 13 Jul 2015 19:14:02 +0100 Subject: [PATCH 1/7] Start of registration support. --- skins/base/views/pages/MatrixChat.js | 5 + skins/base/views/templates/Login.js | 1 + skins/base/views/templates/Register.js | 55 ++++++++ src/ComponentBroker.js | 1 + src/controllers/pages/MatrixChat.js | 20 ++- src/controllers/templates/Login.js | 10 +- src/controllers/templates/Register.js | 179 +++++++++++++++++++++++++ 7 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 skins/base/views/templates/Register.js create mode 100644 src/controllers/templates/Register.js diff --git a/skins/base/views/pages/MatrixChat.js b/skins/base/views/pages/MatrixChat.js index f9e199e6e..f2480ddc9 100644 --- a/skins/base/views/pages/MatrixChat.js +++ b/skins/base/views/pages/MatrixChat.js @@ -23,6 +23,7 @@ var RoomList = ComponentBroker.get('organisms/RoomList'); var RoomView = ComponentBroker.get('organisms/RoomView'); var MatrixToolbar = ComponentBroker.get('molecules/MatrixToolbar'); var Login = ComponentBroker.get('templates/Login'); +var Register = ComponentBroker.get('templates/Register'); var MatrixChatController = require("../../../../src/controllers/pages/MatrixChat"); @@ -51,6 +52,10 @@ module.exports = React.createClass({ return ( ); + } else if (this.state.screen == 'register') { + return ( + + ); } else { return ( diff --git a/skins/base/views/templates/Login.js b/skins/base/views/templates/Login.js index 6cd721e20..ceae07ec4 100644 --- a/skins/base/views/templates/Login.js +++ b/skins/base/views/templates/Login.js @@ -40,6 +40,7 @@ module.exports = React.createClass({

Please log in:

{this.componentForStep(this.state.step)}
{this.state.errorText}
+ Create a new account ); } diff --git a/skins/base/views/templates/Register.js b/skins/base/views/templates/Register.js new file mode 100644 index 000000000..676fa2e27 --- /dev/null +++ b/skins/base/views/templates/Register.js @@ -0,0 +1,55 @@ +/* +Copyright 2015 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. +*/ + +'use strict'; + +var React = require('react'); + +var ComponentBroker = require("../../../../src/ComponentBroker"); + +var Loader = require("react-loader"); + +var RegisterController = require("../../../../src/controllers/templates/Register"); + +module.exports = React.createClass({ + displayName: 'Register', + mixins: [RegisterController], + + registerContent: function() { + if (this.state.busy) { + return ( + + ); + } else { + return ( +
+

Create a new account:

+ {this.componentForStep(this.state.step)} +
{this.state.errorText}
+ Sign in with existing account +
+ ); + } + }, + + render: function() { + return ( +
+ {this.registerContent()} +
+ ); + } +}); diff --git a/src/ComponentBroker.js b/src/ComponentBroker.js index 32b07a033..c9f9eefb3 100644 --- a/src/ComponentBroker.js +++ b/src/ComponentBroker.js @@ -81,5 +81,6 @@ require('../skins/base/views/molecules/MemberTile'); require('../skins/base/views/organisms/RoomList'); require('../skins/base/views/organisms/RoomView'); require('../skins/base/views/templates/Login'); +require('../skins/base/views/templates/Register'); require('../skins/base/views/organisms/Notifier'); } diff --git a/src/controllers/pages/MatrixChat.js b/src/controllers/pages/MatrixChat.js index 717f91e7a..1ac1c6790 100644 --- a/src/controllers/pages/MatrixChat.js +++ b/src/controllers/pages/MatrixChat.js @@ -67,10 +67,25 @@ module.exports = { logged_in: false, ready: false }); + localStorage.removeItem("mx_hs_url"); + localStorage.removeItem("mx_user_id"); + localStorage.removeItem("mx_access_token"); Notifier.stop(); MatrixClientPeg.get().removeAllListeners(); MatrixClientPeg.replace(null); break; + case 'start_registration': + if (this.state.logged_in) return; + this.setState({ + screen: 'register' + }); + break; + case 'start_login': + if (this.state.logged_in) return; + this.setState({ + screen: 'login' + }); + break; case 'view_room': this.focusComposer = true; this.setState({ @@ -99,7 +114,10 @@ module.exports = { }, onLoggedIn: function() { - this.setState({logged_in: true}); + this.setState({ + screen: 'register', + logged_in: true + }); this.startMatrixClient(); }, diff --git a/src/controllers/templates/Login.js b/src/controllers/templates/Login.js index f3c58cf2f..a5bd43ccf 100644 --- a/src/controllers/templates/Login.js +++ b/src/controllers/templates/Login.js @@ -20,6 +20,7 @@ var React = require('react'); var MatrixClientPeg = require("../../MatrixClientPeg"); var Matrix = require("matrix-js-sdk"); +var dis = require("../../dispatcher"); var ComponentBroker = require("../../ComponentBroker"); @@ -85,9 +86,6 @@ module.exports = { if (that.props.onLoggedIn) { that.props.onLoggedIn(); } - /*dis.dispatch({ - 'action': 'logged_in' - });*/ }, function(error) { that.setStep("stage_m.login.password"); that.setState({errorText: 'Login failed.'}); @@ -118,4 +116,10 @@ module.exports = { ); } }, + + showRegister: function() { + dis.dispatch({ + action: 'start_registration' + }); + } }; diff --git a/src/controllers/templates/Register.js b/src/controllers/templates/Register.js new file mode 100644 index 000000000..12e10c0df --- /dev/null +++ b/src/controllers/templates/Register.js @@ -0,0 +1,179 @@ +/* +Copyright 2015 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. +*/ + +'use strict'; + +var React = require('react'); + +var MatrixClientPeg = require("../../MatrixClientPeg"); +var Matrix = require("matrix-js-sdk"); +var dis = require("../../dispatcher"); + +var ComponentBroker = require("../../ComponentBroker"); + +var ServerConfig = ComponentBroker.get("molecules/ServerConfig"); + +module.exports = { + getInitialState: function() { + return { + step: 'initial', + busy: false, + currentStep: 0, + totalSteps: 1 + }; + }, + + setStep: function(step) { + this.setState({ step: step, errorText: '', busy: false }); + }, + + getSupportedStageTypes: function() { + return ['m.login.email.identity', 'm.login.recaptcha']; + }, + + chooseFlow: function(flows) { + // this is fairly simple right now + var supportedTypes = this.getSupportedStageTypes(); + + var emailFlow = null; + var otherFlow = null; + for (var flowI = 0; flowI < flows.length; ++flowI) { + var flow = flows[flowI]; + var flowHasEmail = false; + var flowSupported = true; + for (var stageI = 0; stageI < flow.stages.length; ++stageI) { + var stage = flow.stages[stageI]; + + if (supportedTypes.indexOf(stage) == -1) { + flowSupported = false; + } + + if (stage == 'm.login.email.identity') { + flowHasEmail = true; + } + } + if (flowSupported) { + if (flowHasEmail) { + emailFlow = flow; + } else { + otherFlow = flow; + } + } + } + + if (this.savedParams.email != '') { + return emailFlow; + } else { + return otherFlow; + } + }, + + onInitialStageSubmit: function(ev) { + ev.preventDefault(); + MatrixClientPeg.replaceUsingUrl(this.refs.serverConfig.getHsUrl()); + this.setState({hs_url: this.refs.serverConfig.getHsUrl()}); + var cli = MatrixClientPeg.get(); + this.setState({busy: true}); + var self = this; + + var email = this.refs.email.getDOMNode().value; + var username = this.refs.username.getDOMNode().value; + var password = this.refs.password.getDOMNode().value; + + this.savedParams = { + email: email, + username: username, + password: password + }; + + cli.register(username, password).done(function(result) { + self.onRegistered(); + }, function(error) { + if (error.httpStatus == 401) { + var flow = self.chooseFlow(error.data.flows); + self.setState({ + busy: false, + flows: flow, + currentStep: 1, + totalSteps: flow.stages.length+1, + flowStage: 0 + }); + self.setStep('stage_'+flow.stages[0]); + } else { + self.setStep("initial"); + self.setState({ + busy: false, + errorText: 'Unable to contact the given Home Server' + }); + } + }); + }, + + onRegistered: function(user_id, access_token) { + MatrixClientPeg.replace(Matrix.createClient({ + baseUrl: this.state.hs_url, + userId: data.user_id, + accessToken: data.access_token + })); + var localStorage = window.localStorage; + if (localStorage) { + localStorage.setItem("mx_hs_url", this.state.hs_url); + localStorage.setItem("mx_user_id", user_id); + localStorage.setItem("mx_access_token", access_token); + } else { + console.warn("No local storage available: can't persist session!"); + } + if (that.props.onLoggedIn) { + that.props.onLoggedIn(); + } + }, + + componentForStep: function(step) { + switch (step) { + case 'initial': + return ( +
+
+ Email:
+ Username:
+ Password:
+ + + +
+ ); + // XXX: clearly these should be separate organisms + case 'stage_m.login.email.identity': + return ( +
+ Please check your email to continue registration. +
+ ); + case 'stage_m.login.recaptcha': + return ( +
+ This is the recaptcha stage. Sucks, doesn't it. +
+ ); + } + }, + + showLogin: function() { + dis.dispatch({ + action: 'start_login' + }); + } +}; From aacc31b2ceaaf576415a1287207c378235c62123 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 14 Jul 2015 18:46:15 +0100 Subject: [PATCH 2/7] Registration works with recaptcha --- skins/base/views/organisms/RoomView.js | 6 ++ src/controllers/organisms/RoomView.js | 2 +- src/controllers/templates/Register.js | 136 +++++++++++++++++++------ 3 files changed, 112 insertions(+), 32 deletions(-) diff --git a/skins/base/views/organisms/RoomView.js b/skins/base/views/organisms/RoomView.js index 81a59c2e8..8de03a84a 100644 --- a/skins/base/views/organisms/RoomView.js +++ b/skins/base/views/organisms/RoomView.js @@ -38,6 +38,12 @@ module.exports = React.createClass({ mixins: [RoomViewController], render: function() { + if (!this.state.room) { + return ( +
+ ); + } + var myUserId = MatrixClientPeg.get().credentials.userId; if (this.state.room.currentState.members[myUserId].membership == 'invite') { if (this.state.joining) { diff --git a/src/controllers/organisms/RoomView.js b/src/controllers/organisms/RoomView.js index 6049eee0e..10b375cda 100644 --- a/src/controllers/organisms/RoomView.js +++ b/src/controllers/organisms/RoomView.js @@ -36,7 +36,7 @@ var tileTypes = { module.exports = { getInitialState: function() { return { - room: MatrixClientPeg.get().getRoom(this.props.roomId), + room: this.props.roomId ? MatrixClientPeg.get().getRoom(this.props.roomId) : null, messageCap: INITIAL_SIZE } }, diff --git a/src/controllers/templates/Register.js b/src/controllers/templates/Register.js index 12e10c0df..307a1dbe7 100644 --- a/src/controllers/templates/Register.js +++ b/src/controllers/templates/Register.js @@ -36,6 +36,17 @@ module.exports = { }; }, + componentDidUpdate: function() { + // Just putting a script tag into the returned jsx doesn't work, annoyingly, + // so we do this instead. + if (this.refs.recaptchaContainer) { + var scriptTag = document.createElement('script'); + window.mx_on_recaptcha_loaded = this.onCaptchaLoaded; + scriptTag.setAttribute('src', "https://www.google.com/recaptcha/api.js?onload=mx_on_recaptcha_loaded&render=explicit"); + this.refs.recaptchaContainer.getDOMNode().appendChild(scriptTag); + } + }, + setStep: function(step) { this.setState({ step: step, errorText: '', busy: false }); }, @@ -76,6 +87,7 @@ module.exports = { if (this.savedParams.email != '') { return emailFlow; +t } else { return otherFlow; } @@ -89,44 +101,57 @@ module.exports = { this.setState({busy: true}); var self = this; - var email = this.refs.email.getDOMNode().value; - var username = this.refs.username.getDOMNode().value; - var password = this.refs.password.getDOMNode().value; - this.savedParams = { - email: email, - username: username, - password: password + email: this.refs.email.getDOMNode().value, + username: this.refs.username.getDOMNode().value, + password: this.refs.password.getDOMNode().value }; - cli.register(username, password).done(function(result) { - self.onRegistered(); - }, function(error) { - if (error.httpStatus == 401) { - var flow = self.chooseFlow(error.data.flows); + this.tryRegister(); + }, + + startStage: function(stageName) { + var self = this; + this.setStep('stage_'+stageName); + switch(stageName) { + case 'm.login.email.identity': self.setState({ - busy: false, - flows: flow, - currentStep: 1, - totalSteps: flow.stages.length+1, - flowStage: 0 + busy: true }); - self.setStep('stage_'+flow.stages[0]); - } else { - self.setStep("initial"); - self.setState({ - busy: false, - errorText: 'Unable to contact the given Home Server' + var cli = MatrixClientPeg.get(); + this.savedParams.client_secret = cli.generarteClientSecret(); + this.savedParams.send_attempt = 1; + cli.requestEmailToken( + this.savedParams.email, + this.savedParams.client_secret, + this.savedParams.send_attempt + ).done(function(response) { + self.setState({ + busy: false, + }); + self.setStep('stage_m.login.email.identity'); + }, function(error) { + self.setState({ + busy: false, + errorText: 'Unable to contact the given Home Server' + }); }); - } - }); + break; + case 'm.login.recaptcha': + if (!this.authParams || !this.authParams['m.login.recaptcha'].public_key) { + this.setState({ + errorText: "This server has not supplied enough information for Recaptcha authentication" + }); + } + break; + } }, onRegistered: function(user_id, access_token) { MatrixClientPeg.replace(Matrix.createClient({ baseUrl: this.state.hs_url, - userId: data.user_id, - accessToken: data.access_token + userId: user_id, + accessToken: access_token })); var localStorage = window.localStorage; if (localStorage) { @@ -136,8 +161,8 @@ module.exports = { } else { console.warn("No local storage available: can't persist session!"); } - if (that.props.onLoggedIn) { - that.props.onLoggedIn(); + if (this.props.onLoggedIn) { + this.props.onLoggedIn(); } }, @@ -164,13 +189,62 @@ module.exports = { ); case 'stage_m.login.recaptcha': return ( -
- This is the recaptcha stage. Sucks, doesn't it. +
+ This Home Server would like to make sure you're not a robot +
); } }, + onCaptchaLoaded: function() { + if (this.refs.recaptchaContainer) { + var sitekey = this.authParams['m.login.recaptcha'].public_key; + global.grecaptcha.render('mx_recaptcha', { + 'sitekey': sitekey, + 'callback': this.onCaptchaDone + }); + } + }, + + onCaptchaDone: function(captcha_response) { + this.tryRegister({ + type: 'm.login.recaptcha', + response: captcha_response + }); + }, + + tryRegister: function(auth) { + var self = this; + MatrixClientPeg.get().register( + this.savedParams.username, + this.savedParams.password, + this.authSessionId, + auth + ).done(function(result) { + self.onRegistered(result.user_id, result.access_token); + }, function(error) { + if (error.httpStatus == 401) { + self.authParams = error.data.params; + var flow = self.chooseFlow(error.data.flows); + self.setState({ + busy: false, + flows: flow, + currentStep: 1, + totalSteps: flow.stages.length+1, + flowStage: 0 + }); + self.startStage(flow.stages[0]); + } else { + self.setStep("initial"); + self.setState({ + busy: false, + errorText: 'Unable to contact the given Home Server' + }); + } + }); + }, + showLogin: function() { dis.dispatch({ action: 'start_login' From 03d048c06f7a248404741a047cd819722f475a29 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 14 Jul 2015 19:39:18 +0100 Subject: [PATCH 3/7] Get as far as requesting a token --- src/MatrixClientPeg.js | 9 +++++++-- src/controllers/molecules/ServerConfig.js | 2 +- src/controllers/templates/Login.js | 12 ++++++++++-- src/controllers/templates/Register.js | 14 ++++++++++---- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index a1c820ee1..0b6c26496 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -24,11 +24,13 @@ var matrixClient = null; var localStorage = window.localStorage; if (localStorage) { var hs_url = localStorage.getItem("mx_hs_url"); + var is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org'; var access_token = localStorage.getItem("mx_access_token"); var user_id = localStorage.getItem("mx_user_id"); if (access_token && user_id && hs_url) { matrixClient = Matrix.createClient({ baseUrl: hs_url, + idBaseUrl: is_url, accessToken: access_token, userId: user_id }); @@ -44,8 +46,11 @@ module.exports = { matrixClient = cli; }, - replaceUsingUrl: function(hs_url) { - matrixClient = Matrix.createClient(hs_url); + replaceUsingUrls: function(hs_url, is_url) { + matrixClient = Matrix.createClient({ + baseUrl: hs_url, + idBaseUrl: is_url + }); } }; diff --git a/src/controllers/molecules/ServerConfig.js b/src/controllers/molecules/ServerConfig.js index 3cd5156ba..76909a148 100644 --- a/src/controllers/molecules/ServerConfig.js +++ b/src/controllers/molecules/ServerConfig.js @@ -30,7 +30,7 @@ module.exports = { return { onHsUrlChanged: function() {}, onIsUrlChanged: function() {}, - default_hs_url: 'https://matrix.org/', + default_hs_url: 'http://localhost:8008', default_is_url: 'https://matrix.org/' }; }, diff --git a/src/controllers/templates/Login.js b/src/controllers/templates/Login.js index a5bd43ccf..48ff6dc9f 100644 --- a/src/controllers/templates/Login.js +++ b/src/controllers/templates/Login.js @@ -42,8 +42,14 @@ module.exports = { onHSChosen: function(ev) { ev.preventDefault(); - MatrixClientPeg.replaceUsingUrl(this.refs.serverConfig.getHsUrl()); - this.setState({hs_url: this.refs.serverConfig.getHsUrl()}); + MatrixClientPeg.replaceUsingUrls( + this.refs.serverConfig.getHsUrl(), + this.refs.serverConfig.getIsUrl() + ); + this.setState({ + hs_url: this.refs.serverConfig.getHsUrl(), + is_url: this.refs.serverConfig.getIsUrl() + }); this.setStep("fetch_stages"); var cli = MatrixClientPeg.get(); this.setState({busy: true}); @@ -72,12 +78,14 @@ module.exports = { // XXX: we assume this means we're logged in, but there could be a next stage MatrixClientPeg.replace(Matrix.createClient({ baseUrl: that.state.hs_url, + idBaseUrl: that.state.is_url, userId: data.user_id, accessToken: data.access_token })); var localStorage = window.localStorage; if (localStorage) { localStorage.setItem("mx_hs_url", that.state.hs_url); + localStorage.setItem("mx_is_url", that.state.is_url); localStorage.setItem("mx_user_id", data.user_id); localStorage.setItem("mx_access_token", data.access_token); } else { diff --git a/src/controllers/templates/Register.js b/src/controllers/templates/Register.js index 307a1dbe7..c562a79e7 100644 --- a/src/controllers/templates/Register.js +++ b/src/controllers/templates/Register.js @@ -87,7 +87,6 @@ module.exports = { if (this.savedParams.email != '') { return emailFlow; -t } else { return otherFlow; } @@ -95,8 +94,14 @@ t onInitialStageSubmit: function(ev) { ev.preventDefault(); - MatrixClientPeg.replaceUsingUrl(this.refs.serverConfig.getHsUrl()); - this.setState({hs_url: this.refs.serverConfig.getHsUrl()}); + MatrixClientPeg.replaceUsingUrls( + this.refs.serverConfig.getHsUrl(), + this.refs.serverConfig.getIsUrl() + ); + this.setState({ + hs_url: this.refs.serverConfig.getHsUrl(), + is_url: this.refs.serverConfig.getIsUrl() + }); var cli = MatrixClientPeg.get(); this.setState({busy: true}); var self = this; @@ -119,7 +124,7 @@ t busy: true }); var cli = MatrixClientPeg.get(); - this.savedParams.client_secret = cli.generarteClientSecret(); + this.savedParams.client_secret = cli.generateClientSecret(); this.savedParams.send_attempt = 1; cli.requestEmailToken( this.savedParams.email, @@ -150,6 +155,7 @@ t onRegistered: function(user_id, access_token) { MatrixClientPeg.replace(Matrix.createClient({ baseUrl: this.state.hs_url, + idBaseUrl: this.state.is_url, userId: user_id, accessToken: access_token })); From 4756427e6129155072ba1f165c5a96668b2b92d5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 15 Jul 2015 19:25:36 +0100 Subject: [PATCH 4/7] First cut working regisatration --- examples/trivial/index.js | 27 ++++++- skins/base/views/pages/MatrixChat.js | 5 +- src/controllers/pages/MatrixChat.js | 40 +++++++++- src/controllers/templates/Login.js | 3 +- src/controllers/templates/Register.js | 105 ++++++++++++++++++++++++-- 5 files changed, 164 insertions(+), 16 deletions(-) diff --git a/examples/trivial/index.js b/examples/trivial/index.js index 5752e6bda..6e48ac1b9 100644 --- a/examples/trivial/index.js +++ b/examples/trivial/index.js @@ -24,7 +24,30 @@ var React = require("react"); // maps cannot pass through two stages). var MatrixReactSdk = require("../../src/index"); -React.render( - , +function routeUrl(location) { + if (location.hash.indexOf('#/register') == 0) { + var hashparts = location.hash.split('?'); + if (hashparts.length != 2) return; + var pairs = hashparts[1].split('&'); + var params = {}; + for (var i = 0; i < pairs.length; ++i) { + var parts = pairs[i].split('='); + if (parts.length != 2) continue; + params[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]); + } + window.matrixChat.resumeRegistration(params); + } +} + +window.onload = function() { + routeUrl(window.location); +} + +var onNewScreen = function(screen) { + window.location.hash = '#/'+screen; +} + +window.matrixChat = React.render( + , document.getElementById('matrixchat') ); diff --git a/skins/base/views/pages/MatrixChat.js b/skins/base/views/pages/MatrixChat.js index f2480ddc9..e2123b151 100644 --- a/skins/base/views/pages/MatrixChat.js +++ b/skins/base/views/pages/MatrixChat.js @@ -54,7 +54,10 @@ module.exports = React.createClass({ ); } else if (this.state.screen == 'register') { return ( - + ); } else { return ( diff --git a/src/controllers/pages/MatrixChat.js b/src/controllers/pages/MatrixChat.js index 1ac1c6790..a687aee22 100644 --- a/src/controllers/pages/MatrixChat.js +++ b/src/controllers/pages/MatrixChat.js @@ -44,6 +44,11 @@ module.exports = { this.focusComposer = false; document.addEventListener("keydown", this.onKeyDown); window.addEventListener("focus", this.onFocus); + if (this.state.logged_in) { + this.notifyNewScreen(''); + } else { + this.notifyNewScreen('login'); + } }, componentWillUnmount: function() { @@ -63,7 +68,7 @@ module.exports = { switch (payload.action) { case 'logout': - this.setState({ + this.replaceState({ logged_in: false, ready: false }); @@ -76,15 +81,17 @@ module.exports = { break; case 'start_registration': if (this.state.logged_in) return; - this.setState({ + this.replaceState({ screen: 'register' }); + this.notifyNewScreen('register'); break; case 'start_login': if (this.state.logged_in) return; - this.setState({ + this.replaceState({ screen: 'login' }); + this.notifyNewScreen('login'); break; case 'view_room': this.focusComposer = true; @@ -115,10 +122,11 @@ module.exports = { onLoggedIn: function() { this.setState({ - screen: 'register', + screen: undefined, logged_in: true }); this.startMatrixClient(); + this.notifyNewScreen(''); }, startMatrixClient: function() { @@ -155,6 +163,30 @@ module.exports = { onFocus: function(ev) { dis.dispatch({action: 'focus_composer'}); + }, + + resumeRegistration(params) { + if (!params.hs_url) return false; + if (!params.is_url) return false; + if (!params.client_secret) return false; + if (!params.session_id) return false; + if (!params.sid) return false; + if (this.state.logged_in) return false; + + this.setState({ + screen: 'register', + register_client_secret: params.client_secret, + register_session_id: params.session_id, + register_hs_url: params.hs_url, + register_is_url: params.is_url, + register_id_sid: params.sid + }); + }, + + notifyNewScreen: function(screen) { + if (this.props.onNewScreen) { + this.props.onNewScreen(screen); + } } }; diff --git a/src/controllers/templates/Login.js b/src/controllers/templates/Login.js index 48ff6dc9f..714fb2720 100644 --- a/src/controllers/templates/Login.js +++ b/src/controllers/templates/Login.js @@ -125,7 +125,8 @@ module.exports = { } }, - showRegister: function() { + showRegister: function(ev) { + ev.preventDefault(); dis.dispatch({ action: 'start_registration' }); diff --git a/src/controllers/templates/Register.js b/src/controllers/templates/Register.js index c562a79e7..3e7f07c74 100644 --- a/src/controllers/templates/Register.js +++ b/src/controllers/templates/Register.js @@ -36,6 +36,45 @@ module.exports = { }; }, + componentWillMount: function() { + this.readNewProps(); + }, + + componentWillReceiveProps: function() { + this.readNewProps(); + }, + + readNewProps: function() { + if (this.props.clientSecret && this.props.hsUrl && + this.props.isUrl && this.props.sessionId && + this.props.idSid) { + this.authSessionId = this.props.sessionId; + MatrixClientPeg.replaceUsingUrls( + this.props.hsUrl, + this.props.isUrl + ); + this.setState({ + hs_url: this.props.hsUrl, + is_url: this.props.isUrl + }); + this.savedParams = {client_secret: this.props.clientSecret}; + this.setState({busy: true}); + + var isLocation = document.createElement('a'); + isLocation.href = this.props.isUrl; + + var auth = { + type: 'm.login.email.identity', + threepid_creds: { + sid: this.props.idSid, + client_secret: this.savedParams.client_secret, + id_server: isLocation.host + } + }; + this.tryRegister(auth); + } + }, + componentDidUpdate: function() { // Just putting a script tag into the returned jsx doesn't work, annoyingly, // so we do this instead. @@ -85,13 +124,36 @@ module.exports = { } } - if (this.savedParams.email != '') { + if ( + this.savedParams.email != '' || + this.completedStages.indexOf('m.login.email.identity' > -1) + ) { return emailFlow; } else { return otherFlow; } }, + firstUncompletedStageIndex: function(flow) { + if (this.completedStages === undefined) return 0; + for (var i = 0; i < flow.stages.length; ++i) { + if (this.completedStages.indexOf(flow.stages[i]) == -1) { + return i; + } + } + }, + + numCompletedStages: function(flow) { + if (this.completedStages === undefined) return 0; + var nCompleted = 0; + for (var i = 0; i < flow.stages.length; ++i) { + if (this.completedStages.indexOf(flow.stages[i]) > -1) { + ++nCompleted; + } + } + return nCompleted; + }, + onInitialStageSubmit: function(ev) { ev.preventDefault(); MatrixClientPeg.replaceUsingUrls( @@ -126,10 +188,24 @@ module.exports = { var cli = MatrixClientPeg.get(); this.savedParams.client_secret = cli.generateClientSecret(); this.savedParams.send_attempt = 1; + + var nextLink = window.location.protocol + '//' + + window.location.host + + window.location.pathname + + '#/register?client_secret=' + + encodeURIComponent(this.savedParams.client_secret) + + "&hs_url=" + + encodeURIComponent(this.state.hs_url) + + "&is_url=" + + encodeURIComponent(this.state.is_url) + + "&session_id=" + + encodeURIComponent(this.authSessionId); + cli.requestEmailToken( this.savedParams.email, this.savedParams.client_secret, - this.savedParams.send_attempt + this.savedParams.send_attempt, + nextLink ).done(function(response) { self.setState({ busy: false, @@ -230,28 +306,41 @@ module.exports = { ).done(function(result) { self.onRegistered(result.user_id, result.access_token); }, function(error) { - if (error.httpStatus == 401) { + if (error.httpStatus == 401 && error.data.flows) { self.authParams = error.data.params; + self.authSessionId = error.data.session; + + self.completedStages = error.data.completed; + var flow = self.chooseFlow(error.data.flows); + + var flowStage = self.firstUncompletedStageIndex(flow); + var numDone = self.numCompletedStages(flow); + self.setState({ busy: false, flows: flow, - currentStep: 1, + currentStep: 1+numDone, totalSteps: flow.stages.length+1, - flowStage: 0 + flowStage: flowStage }); - self.startStage(flow.stages[0]); + self.startStage(flow.stages[flowStage]); } else { + var errorText = "Unable to contact the given Home Server"; + if (error.httpStatus == 401) { + errorText = "Authorisation failed!"; + } self.setStep("initial"); self.setState({ busy: false, - errorText: 'Unable to contact the given Home Server' + errorText: errorText }); } }); }, - showLogin: function() { + showLogin: function(ev) { + ev.preventDefault(); dis.dispatch({ action: 'start_login' }); From 23d9cee299c391cbbd426d26075aa5f41915bcbe Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 15 Jul 2015 19:30:10 +0100 Subject: [PATCH 5/7] didn't mean to commit that --- src/controllers/molecules/ServerConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/molecules/ServerConfig.js b/src/controllers/molecules/ServerConfig.js index 76909a148..3cd5156ba 100644 --- a/src/controllers/molecules/ServerConfig.js +++ b/src/controllers/molecules/ServerConfig.js @@ -30,7 +30,7 @@ module.exports = { return { onHsUrlChanged: function() {}, onIsUrlChanged: function() {}, - default_hs_url: 'http://localhost:8008', + default_hs_url: 'https://matrix.org/', default_is_url: 'https://matrix.org/' }; }, From 77114e0081f51f943e6b3113b2c58708579ad10b Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 15 Jul 2015 20:33:12 +0100 Subject: [PATCH 6/7] Improve registration so the container page can pick what URL it's going to route through to registration. --- examples/trivial/index.js | 38 ++++++++++++++---- skins/base/views/pages/MatrixChat.js | 1 + src/controllers/molecules/ServerConfig.js | 2 +- src/controllers/pages/MatrixChat.js | 48 ++++++++++++++--------- src/controllers/templates/Register.js | 12 +++--- 5 files changed, 66 insertions(+), 35 deletions(-) diff --git a/examples/trivial/index.js b/examples/trivial/index.js index 6e48ac1b9..2be905495 100644 --- a/examples/trivial/index.js +++ b/examples/trivial/index.js @@ -24,30 +24,52 @@ var React = require("react"); // maps cannot pass through two stages). var MatrixReactSdk = require("../../src/index"); +// Here, we do some crude URL analysis to allow +// deep-linking. We only support registration +// deep-links in this example. function routeUrl(location) { if (location.hash.indexOf('#/register') == 0) { var hashparts = location.hash.split('?'); - if (hashparts.length != 2) return; - var pairs = hashparts[1].split('&'); var params = {}; - for (var i = 0; i < pairs.length; ++i) { - var parts = pairs[i].split('='); - if (parts.length != 2) continue; - params[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]); + if (hashparts.length == 2) { + var pairs = hashparts[1].split('&'); + for (var i = 0; i < pairs.length; ++i) { + var parts = pairs[i].split('='); + if (parts.length != 2) continue; + params[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]); + } } - window.matrixChat.resumeRegistration(params); + window.matrixChat.showScreen('register', params); } } +var loaded = false; + window.onload = function() { routeUrl(window.location); + loaded = true; } +// This will be called whenever the SDK changes screens, +// so a web page can update the URL bar appropriately. var onNewScreen = function(screen) { + if (!loaded) return; window.location.hash = '#/'+screen; } +// We use this to work out what URL the SDK should +// pass through when registering to allow the user to +// click back to the client having registered. +// It's up to us to recognise if we're loaded with +// this URL and tell MatrixClient to resume registration. +var makeRegistrationUrl = function() { + return window.location.protocol + '//' + + window.location.host + + window.location.pathname + + '#/register'; +} + window.matrixChat = React.render( - , + , document.getElementById('matrixchat') ); diff --git a/skins/base/views/pages/MatrixChat.js b/skins/base/views/pages/MatrixChat.js index e2123b151..0231af442 100644 --- a/skins/base/views/pages/MatrixChat.js +++ b/skins/base/views/pages/MatrixChat.js @@ -57,6 +57,7 @@ module.exports = React.createClass({ ); } else { diff --git a/src/controllers/molecules/ServerConfig.js b/src/controllers/molecules/ServerConfig.js index 3cd5156ba..76909a148 100644 --- a/src/controllers/molecules/ServerConfig.js +++ b/src/controllers/molecules/ServerConfig.js @@ -30,7 +30,7 @@ module.exports = { return { onHsUrlChanged: function() {}, onIsUrlChanged: function() {}, - default_hs_url: 'https://matrix.org/', + default_hs_url: 'http://localhost:8008', default_is_url: 'https://matrix.org/' }; }, diff --git a/src/controllers/pages/MatrixChat.js b/src/controllers/pages/MatrixChat.js index a687aee22..44a4df075 100644 --- a/src/controllers/pages/MatrixChat.js +++ b/src/controllers/pages/MatrixChat.js @@ -81,9 +81,23 @@ module.exports = { break; case 'start_registration': if (this.state.logged_in) return; - this.replaceState({ - screen: 'register' - }); + var newState = payload.params || {}; + newState.screen = 'register'; + if ( + payload.params && + payload.params.client_secret && + payload.params.session_id && + payload.params.hs_url && + payload.params.is_url && + payload.params.sid + ) { + newState.register_client_secret = payload.params.client_secret; + newState.register_session_id = payload.params.session_id; + newState.register_hs_url = payload.params.hs_url; + newState.register_is_url = payload.params.is_url; + newState.register_id_sid = payload.params.sid; + } + this.replaceState(newState); this.notifyNewScreen('register'); break; case 'start_login': @@ -165,22 +179,18 @@ module.exports = { dis.dispatch({action: 'focus_composer'}); }, - resumeRegistration(params) { - if (!params.hs_url) return false; - if (!params.is_url) return false; - if (!params.client_secret) return false; - if (!params.session_id) return false; - if (!params.sid) return false; - if (this.state.logged_in) return false; - - this.setState({ - screen: 'register', - register_client_secret: params.client_secret, - register_session_id: params.session_id, - register_hs_url: params.hs_url, - register_is_url: params.is_url, - register_id_sid: params.sid - }); + showScreen(screen, params) { + if (screen == 'register') { + dis.dispatch({ + action: 'start_registration', + params: params + }); + } else if (screen == 'login') { + dis.dispatch({ + action: 'start_login', + params: params + }); + } }, notifyNewScreen: function(screen) { diff --git a/src/controllers/templates/Register.js b/src/controllers/templates/Register.js index 3e7f07c74..650fdd6b2 100644 --- a/src/controllers/templates/Register.js +++ b/src/controllers/templates/Register.js @@ -189,10 +189,8 @@ module.exports = { this.savedParams.client_secret = cli.generateClientSecret(); this.savedParams.send_attempt = 1; - var nextLink = window.location.protocol + '//' + - window.location.host + - window.location.pathname + - '#/register?client_secret=' + + var nextLink = this.props.registrationUrl + + '?client_secret=' + encodeURIComponent(this.savedParams.client_secret) + "&hs_url=" + encodeURIComponent(this.state.hs_url) + @@ -254,9 +252,9 @@ module.exports = { return (
- Email:
- Username:
- Password:
+ Email:
+ Username:
+ Password:
From 931a4f29c80302445571d2d57a6cdd57a6e42fe7 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 16 Jul 2015 09:57:14 +0100 Subject: [PATCH 7/7] Argh, accidentally committed again --- src/controllers/molecules/ServerConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/molecules/ServerConfig.js b/src/controllers/molecules/ServerConfig.js index 76909a148..3cd5156ba 100644 --- a/src/controllers/molecules/ServerConfig.js +++ b/src/controllers/molecules/ServerConfig.js @@ -30,7 +30,7 @@ module.exports = { return { onHsUrlChanged: function() {}, onIsUrlChanged: function() {}, - default_hs_url: 'http://localhost:8008', + default_hs_url: 'https://matrix.org/', default_is_url: 'https://matrix.org/' }; },