diff --git a/src/GuestAccess.js b/src/GuestAccess.js new file mode 100644 index 0000000000..ef48d23ded --- /dev/null +++ b/src/GuestAccess.js @@ -0,0 +1,51 @@ +/* +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. +*/ +const IS_GUEST_KEY = "matrix-is-guest"; + +class GuestAccess { + + constructor(localStorage) { + this.localStorage = localStorage; + try { + this._isGuest = localStorage.getItem(IS_GUEST_KEY) === "true"; + } + catch (e) {} // don't care + } + + setPeekedRoom(roomId) { + // we purposefully do not persist this to local storage as peeking is + // entirely transient. + this._peekedRoomId = roomId; + } + + getPeekedRoom() { + return this._peekedRoomId; + } + + isGuest() { + return this._isGuest; + } + + markAsGuest(isGuest) { + try { + this.localStorage.setItem(IS_GUEST_KEY, JSON.stringify(isGuest)); + } catch (e) {} // ignore. If they don't do LS, they'll just get a new account. + this._isGuest = isGuest; + this._peekedRoomId = null; + } +} + +module.exports = GuestAccess; diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 4a83ed09d9..dbb3dbf83e 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -18,6 +18,7 @@ limitations under the License. // A thing that holds your Matrix Client var Matrix = require("matrix-js-sdk"); +var GuestAccess = require("./GuestAccess"); var matrixClient = null; @@ -33,7 +34,7 @@ function deviceId() { return id; } -function createClient(hs_url, is_url, user_id, access_token) { +function createClient(hs_url, is_url, user_id, access_token, guestAccess) { var opts = { baseUrl: hs_url, idBaseUrl: is_url, @@ -47,6 +48,15 @@ function createClient(hs_url, is_url, user_id, access_token) { } matrixClient = Matrix.createClient(opts); + if (guestAccess) { + console.log("Guest: %s", guestAccess.isGuest()); + matrixClient.setGuest(guestAccess.isGuest()); + var peekedRoomId = guestAccess.getPeekedRoom(); + if (peekedRoomId) { + console.log("Peeking in room %s", peekedRoomId); + matrixClient.peekInRoom(peekedRoomId); + } + } } if (localStorage) { @@ -54,12 +64,18 @@ if (localStorage) { 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"); + var guestAccess = new GuestAccess(localStorage); if (access_token && user_id && hs_url) { - createClient(hs_url, is_url, user_id, access_token); + createClient(hs_url, is_url, user_id, access_token, guestAccess); } } class MatrixClient { + + constructor(guestAccess) { + this.guestAccess = guestAccess; + } + get() { return matrixClient; } @@ -97,7 +113,7 @@ class MatrixClient { } } - replaceUsingAccessToken(hs_url, is_url, user_id, access_token) { + replaceUsingAccessToken(hs_url, is_url, user_id, access_token, isGuest) { if (localStorage) { try { localStorage.clear(); @@ -105,7 +121,8 @@ class MatrixClient { console.warn("Error using local storage"); } } - createClient(hs_url, is_url, user_id, access_token); + this.guestAccess.markAsGuest(Boolean(isGuest)); + createClient(hs_url, is_url, user_id, access_token, this.guestAccess); if (localStorage) { try { localStorage.setItem("mx_hs_url", hs_url); @@ -122,6 +139,6 @@ class MatrixClient { } if (!global.mxMatrixClient) { - global.mxMatrixClient = new MatrixClient(); + global.mxMatrixClient = new MatrixClient(new GuestAccess(localStorage)); } module.exports = global.mxMatrixClient; diff --git a/src/Presence.js b/src/Presence.js index 5c9d6945a3..4152d7a487 100644 --- a/src/Presence.js +++ b/src/Presence.js @@ -73,6 +73,11 @@ class Presence { } var old_state = this.state; this.state = newState; + + if (MatrixClientPeg.get().isGuest()) { + return; // don't try to set presence when a guest; it won't work. + } + var self = this; MatrixClientPeg.get().setPresence(this.state).done(function() { console.log("Presence: %s", newState); diff --git a/src/Signup.js b/src/Signup.js index 74c4ad5f19..42468959fe 100644 --- a/src/Signup.js +++ b/src/Signup.js @@ -69,6 +69,10 @@ class Register extends Signup { this.params.idSid = idSid; } + setGuestAccessToken(token) { + this.guestAccessToken = token; + } + getStep() { return this._step; } @@ -126,7 +130,8 @@ class Register extends Signup { } return MatrixClientPeg.get().register( - this.username, this.password, this.params.sessionId, authDict, bindEmail + this.username, this.password, this.params.sessionId, authDict, bindEmail, + this.guestAccessToken ).then(function(result) { self.credentials = result; self.setStep("COMPLETE"); diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index 1b1e8810a9..45aca1f0dc 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -15,7 +15,7 @@ limitations under the License. */ 'use strict'; - +var q = require("q"); var MatrixClientPeg = require("./MatrixClientPeg"); var Notifier = require("./Notifier"); @@ -35,6 +35,11 @@ module.exports = { }, loadThreePids: function() { + if (MatrixClientPeg.get().isGuest()) { + return q({ + threepids: [] + }); // guests can't poke 3pid endpoint + } return MatrixClientPeg.get().getThreePids(); }, diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index d732a54922..320dad09b3 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -42,6 +42,7 @@ module.exports = React.createClass({ ConferenceHandler: React.PropTypes.any, onNewScreen: React.PropTypes.func, registrationUrl: React.PropTypes.string, + enableGuest: React.PropTypes.bool, startingQueryParams: React.PropTypes.object }, @@ -83,8 +84,21 @@ module.exports = React.createClass({ }, componentDidMount: function() { + this._autoRegisterAsGuest = false; + if (this.props.enableGuest) { + if (!this.props.config || !this.props.config.default_hs_url) { + console.error("Cannot enable guest access: No supplied config prop for HS/IS URLs"); + } + else { + this._autoRegisterAsGuest = true; + } + } + this.dispatcherRef = dis.register(this.onAction); if (this.state.logged_in) { + // Don't auto-register as a guest. This applies if you refresh the page on a + // logged in client THEN hit the Sign Out button. + this._autoRegisterAsGuest = false; this.startMatrixClient(); } this.focusComposer = false; @@ -93,8 +107,11 @@ module.exports = React.createClass({ this.scrollStateMap = {}; document.addEventListener("keydown", this.onKeyDown); window.addEventListener("focus", this.onFocus); + if (this.state.logged_in) { this.notifyNewScreen(''); + } else if (this._autoRegisterAsGuest) { + this._registerAsGuest(); } else { this.notifyNewScreen('login'); } @@ -126,6 +143,34 @@ module.exports = React.createClass({ } }, + _registerAsGuest: function() { + var self = this; + var config = this.props.config; + console.log("Doing guest login on %s", config.default_hs_url); + MatrixClientPeg.replaceUsingUrls( + config.default_hs_url, config.default_is_url + ); + MatrixClientPeg.get().registerGuest().done(function(creds) { + console.log("Registered as guest: %s", creds.user_id); + self._setAutoRegisterAsGuest(false); + self.onLoggedIn({ + userId: creds.user_id, + accessToken: creds.access_token, + homeserverUrl: config.default_hs_url, + identityServerUrl: config.default_is_url, + guest: true + }); + }, function(err) { + console.error(err.data); + self._setAutoRegisterAsGuest(false); + }); + }, + + _setAutoRegisterAsGuest: function(shouldAutoRegister) { + this._autoRegisterAsGuest = shouldAutoRegister; + this.forceUpdate(); + }, + onAction: function(payload) { var roomIndexDelta = 1; @@ -180,6 +225,14 @@ module.exports = React.createClass({ screen: 'post_registration' }); break; + case 'start_upgrade_registration': + this.replaceState({ + screen: "register", + upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(), + guestAccessToken: MatrixClientPeg.get().getAccessToken() + }); + this.notifyNewScreen('register'); + break; case 'token_login': if (this.state.logged_in) return; @@ -382,10 +435,11 @@ module.exports = React.createClass({ }, onLoggedIn: function(credentials) { - console.log("onLoggedIn => %s", credentials.userId); + credentials.guest = Boolean(credentials.guest); + console.log("onLoggedIn => %s (guest=%s)", credentials.userId, credentials.guest); MatrixClientPeg.replaceUsingAccessToken( credentials.homeserverUrl, credentials.identityServerUrl, - credentials.userId, credentials.accessToken + credentials.userId, credentials.accessToken, credentials.guest ); this.setState({ screen: undefined, @@ -715,12 +769,20 @@ module.exports = React.createClass({ ); } - } else if (this.state.logged_in) { + } else if (this.state.logged_in || (!this.state.logged_in && this._autoRegisterAsGuest)) { var Spinner = sdk.getComponent('elements.Spinner'); + var logoutLink; + if (this.state.logged_in) { + logoutLink = ( + + Logout + + ); + } return (
- Logout + {logoutLink}
); } else if (this.state.screen == 'register') { @@ -730,6 +792,9 @@ module.exports = React.createClass({ sessionId={this.state.register_session_id} idSid={this.state.register_id_sid} email={this.props.startingQueryParams.email} + username={this.state.upgradeUsername} + disableUsernameChanges={Boolean(this.state.upgradeUsername)} + guestAccessToken={this.state.guestAccessToken} hsUrl={this.props.config.default_hs_url} isUrl={this.props.config.default_is_url} registrationUrl={this.props.registrationUrl} diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 5cffefad26..c77bd99e38 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -97,6 +97,24 @@ module.exports = React.createClass({ this.forceUpdate(); } }); + // if this is an unknown room then we're in one of three states: + // - This is a room we can peek into (search engine) (we can /peek) + // - This is a room we can publicly join or were invited to. (we can /join) + // - This is a room we cannot join at all. (no action can help us) + // We can't try to /join because this may implicitly accept invites (!) + // We can /peek though. If it fails then we present the join UI. If it + // succeeds then great, show the preview (but we still may be able to /join!). + if (!this.state.room) { + console.log("Attempting to peek into room %s", this.props.roomId); + MatrixClientPeg.get().peekInRoom(this.props.roomId).done(function() { + // we don't need to do anything - JS SDK will emit Room events + // which will update the UI. + }, function(err) { + console.error("Failed to peek into room: %s", err); + }); + } + + }, componentWillUnmount: function() { @@ -422,6 +440,12 @@ module.exports = React.createClass({ joining: false, joinError: error }); + var msg = error.message ? error.message : JSON.stringify(error); + var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createDialog(ErrorDialog, { + title: "Failed to join room", + description: msg + }); }); this.setState({ joining: true @@ -712,7 +736,7 @@ module.exports = React.createClass({ return ret; }, - uploadNewState: function(new_name, new_topic, new_join_rule, new_history_visibility, new_power_levels) { + uploadNewState: function(newVals) { var old_name = this.state.room.name; var old_topic = this.state.room.currentState.getStateEvents('m.room.topic', ''); @@ -738,46 +762,54 @@ module.exports = React.createClass({ var deferreds = []; - if (old_name != new_name && new_name != undefined && new_name) { + if (old_name != newVals.name && newVals.name != undefined && newVals.name) { deferreds.push( - MatrixClientPeg.get().setRoomName(this.state.room.roomId, new_name) + MatrixClientPeg.get().setRoomName(this.state.room.roomId, newVals.name) ); } - if (old_topic != new_topic && new_topic != undefined) { + if (old_topic != newVals.topic && newVals.topic != undefined) { deferreds.push( - MatrixClientPeg.get().setRoomTopic(this.state.room.roomId, new_topic) + MatrixClientPeg.get().setRoomTopic(this.state.room.roomId, newVals.topic) ); } - if (old_join_rule != new_join_rule && new_join_rule != undefined) { + if (old_join_rule != newVals.join_rule && newVals.join_rule != undefined) { deferreds.push( MatrixClientPeg.get().sendStateEvent( this.state.room.roomId, "m.room.join_rules", { - join_rule: new_join_rule, + join_rule: newVals.join_rule, }, "" ) ); } - if (old_history_visibility != new_history_visibility && new_history_visibility != undefined) { + if (old_history_visibility != newVals.history_visibility && + newVals.history_visibility != undefined) { deferreds.push( MatrixClientPeg.get().sendStateEvent( this.state.room.roomId, "m.room.history_visibility", { - history_visibility: new_history_visibility, + history_visibility: newVals.history_visibility, }, "" ) ); } - if (new_power_levels) { + if (newVals.power_levels) { deferreds.push( MatrixClientPeg.get().sendStateEvent( - this.state.room.roomId, "m.room.power_levels", new_power_levels, "" + this.state.room.roomId, "m.room.power_levels", newVals.power_levels, "" ) ); } + deferreds.push( + MatrixClientPeg.get().setGuestAccess(this.state.room.roomId, { + allowRead: newVals.guest_read, + allowJoin: newVals.guest_join + }) + ); + if (deferreds.length) { var self = this; q.all(deferreds).fail(function(err) { @@ -862,19 +894,15 @@ module.exports = React.createClass({ uploadingRoomSettings: true, }); - var new_name = this.refs.header.getRoomName(); - var new_topic = this.refs.room_settings.getTopic(); - var new_join_rule = this.refs.room_settings.getJoinRules(); - var new_history_visibility = this.refs.room_settings.getHistoryVisibility(); - var new_power_levels = this.refs.room_settings.getPowerLevels(); - - this.uploadNewState( - new_name, - new_topic, - new_join_rule, - new_history_visibility, - new_power_levels - ); + this.uploadNewState({ + name: this.refs.header.getRoomName(), + topic: this.refs.room_settings.getTopic(), + join_rule: this.refs.room_settings.getJoinRules(), + history_visibility: this.refs.room_settings.getHistoryVisibility(), + power_levels: this.refs.room_settings.getPowerLevels(), + guest_join: this.refs.room_settings.canGuestsJoin(), + guest_read: this.refs.room_settings.canGuestsRead() + }); }, onCancelClick: function() { diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index c1550f9b6b..ddf4229170 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -135,6 +135,12 @@ module.exports = React.createClass({ }); }, + onUpgradeClicked: function() { + dis.dispatch({ + action: "start_upgrade_registration" + }); + }, + onLogoutPromptCancel: function() { this.logoutModal.closeDialog(); }, @@ -164,6 +170,28 @@ module.exports = React.createClass({ this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null ); + var accountJsx; + + if (MatrixClientPeg.get().isGuest()) { + accountJsx = ( +
+ Upgrade (It's free!) +
+ ); + } + else { + accountJsx = ( + + ); + } + return (
@@ -213,14 +241,7 @@ module.exports = React.createClass({

Account

- + {accountJsx}
diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index 7f6e408fef..f89d65d740 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -19,7 +19,6 @@ limitations under the License. var React = require('react'); var sdk = require('../../../index'); -var MatrixClientPeg = require('../../../MatrixClientPeg'); var dis = require('../../../dispatcher'); var Signup = require("../../../Signup"); var ServerConfig = require("../../views/login/ServerConfig"); @@ -40,6 +39,9 @@ module.exports = React.createClass({ hsUrl: React.PropTypes.string, isUrl: React.PropTypes.string, email: React.PropTypes.string, + username: React.PropTypes.string, + guestAccessToken: React.PropTypes.string, + disableUsernameChanges: React.PropTypes.bool, // registration shouldn't know or care how login is done. onLoginClick: React.PropTypes.func.isRequired }, @@ -63,6 +65,7 @@ module.exports = React.createClass({ this.registerLogic.setSessionId(this.props.sessionId); this.registerLogic.setRegistrationUrl(this.props.registrationUrl); this.registerLogic.setIdSid(this.props.idSid); + this.registerLogic.setGuestAccessToken(this.props.guestAccessToken); this.registerLogic.recheckState(); }, @@ -186,7 +189,9 @@ module.exports = React.createClass({ registerStep = ( diff --git a/src/components/views/login/RegistrationForm.js b/src/components/views/login/RegistrationForm.js index bc461dd1bb..534464a4ae 100644 --- a/src/components/views/login/RegistrationForm.js +++ b/src/components/views/login/RegistrationForm.js @@ -30,6 +30,7 @@ module.exports = React.createClass({ defaultUsername: React.PropTypes.string, showEmail: React.PropTypes.bool, minPasswordLength: React.PropTypes.number, + disableUsernameChanges: React.PropTypes.bool, onError: React.PropTypes.func, onRegisterClick: React.PropTypes.func // onRegisterClick(Object) => ?Promise }, @@ -109,7 +110,8 @@ module.exports = React.createClass({ {emailSection}
+ placeholder="User name" defaultValue={this.state.username} + disabled={this.props.disableUsernameChanges} />
diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index 211ecbd71a..9e07385d65 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -31,6 +31,14 @@ module.exports = React.createClass({ }; }, + canGuestsJoin: function() { + return this.refs.guests_join.checked; + }, + + canGuestsRead: function() { + return this.refs.guests_read.checked; + }, + getTopic: function() { return this.refs.topic.value; }, @@ -83,6 +91,10 @@ module.exports = React.createClass({ if (history_visibility) history_visibility = history_visibility.getContent().history_visibility; var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', ''); + var guest_access = this.props.room.currentState.getStateEvents('m.room.guest_access', ''); + if (guest_access) { + guest_access = guest_access.getContent().guest_access; + } var events_levels = power_levels.events || {}; @@ -154,6 +166,14 @@ module.exports = React.createClass({