Merge pull request #82 from matrix-org/kegan/guest-access

Implement guest access and upgrading
This commit is contained in:
Kegsay 2016-01-11 15:19:39 +00:00
commit 3cd805e71d
11 changed files with 269 additions and 45 deletions

51
src/GuestAccess.js Normal file
View file

@ -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;

View file

@ -18,6 +18,7 @@ limitations under the License.
// A thing that holds your Matrix Client // A thing that holds your Matrix Client
var Matrix = require("matrix-js-sdk"); var Matrix = require("matrix-js-sdk");
var GuestAccess = require("./GuestAccess");
var matrixClient = null; var matrixClient = null;
@ -33,7 +34,7 @@ function deviceId() {
return id; 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 = { var opts = {
baseUrl: hs_url, baseUrl: hs_url,
idBaseUrl: is_url, idBaseUrl: is_url,
@ -47,6 +48,15 @@ function createClient(hs_url, is_url, user_id, access_token) {
} }
matrixClient = Matrix.createClient(opts); 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) { if (localStorage) {
@ -54,12 +64,18 @@ if (localStorage) {
var is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org'; var is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org';
var access_token = localStorage.getItem("mx_access_token"); var access_token = localStorage.getItem("mx_access_token");
var user_id = localStorage.getItem("mx_user_id"); var user_id = localStorage.getItem("mx_user_id");
var guestAccess = new GuestAccess(localStorage);
if (access_token && user_id && hs_url) { 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 { class MatrixClient {
constructor(guestAccess) {
this.guestAccess = guestAccess;
}
get() { get() {
return matrixClient; 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) { if (localStorage) {
try { try {
localStorage.clear(); localStorage.clear();
@ -105,7 +121,8 @@ class MatrixClient {
console.warn("Error using local storage"); 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) { if (localStorage) {
try { try {
localStorage.setItem("mx_hs_url", hs_url); localStorage.setItem("mx_hs_url", hs_url);
@ -122,6 +139,6 @@ class MatrixClient {
} }
if (!global.mxMatrixClient) { if (!global.mxMatrixClient) {
global.mxMatrixClient = new MatrixClient(); global.mxMatrixClient = new MatrixClient(new GuestAccess(localStorage));
} }
module.exports = global.mxMatrixClient; module.exports = global.mxMatrixClient;

View file

@ -73,6 +73,11 @@ class Presence {
} }
var old_state = this.state; var old_state = this.state;
this.state = newState; this.state = newState;
if (MatrixClientPeg.get().isGuest()) {
return; // don't try to set presence when a guest; it won't work.
}
var self = this; var self = this;
MatrixClientPeg.get().setPresence(this.state).done(function() { MatrixClientPeg.get().setPresence(this.state).done(function() {
console.log("Presence: %s", newState); console.log("Presence: %s", newState);

View file

@ -69,6 +69,10 @@ class Register extends Signup {
this.params.idSid = idSid; this.params.idSid = idSid;
} }
setGuestAccessToken(token) {
this.guestAccessToken = token;
}
getStep() { getStep() {
return this._step; return this._step;
} }
@ -126,7 +130,8 @@ class Register extends Signup {
} }
return MatrixClientPeg.get().register( 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) { ).then(function(result) {
self.credentials = result; self.credentials = result;
self.setStep("COMPLETE"); self.setStep("COMPLETE");

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
'use strict'; 'use strict';
var q = require("q");
var MatrixClientPeg = require("./MatrixClientPeg"); var MatrixClientPeg = require("./MatrixClientPeg");
var Notifier = require("./Notifier"); var Notifier = require("./Notifier");
@ -35,6 +35,11 @@ module.exports = {
}, },
loadThreePids: function() { loadThreePids: function() {
if (MatrixClientPeg.get().isGuest()) {
return q({
threepids: []
}); // guests can't poke 3pid endpoint
}
return MatrixClientPeg.get().getThreePids(); return MatrixClientPeg.get().getThreePids();
}, },

View file

@ -42,6 +42,7 @@ module.exports = React.createClass({
ConferenceHandler: React.PropTypes.any, ConferenceHandler: React.PropTypes.any,
onNewScreen: React.PropTypes.func, onNewScreen: React.PropTypes.func,
registrationUrl: React.PropTypes.string, registrationUrl: React.PropTypes.string,
enableGuest: React.PropTypes.bool,
startingQueryParams: React.PropTypes.object startingQueryParams: React.PropTypes.object
}, },
@ -83,8 +84,21 @@ module.exports = React.createClass({
}, },
componentDidMount: function() { 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); this.dispatcherRef = dis.register(this.onAction);
if (this.state.logged_in) { 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.startMatrixClient();
} }
this.focusComposer = false; this.focusComposer = false;
@ -93,8 +107,11 @@ module.exports = React.createClass({
this.scrollStateMap = {}; this.scrollStateMap = {};
document.addEventListener("keydown", this.onKeyDown); document.addEventListener("keydown", this.onKeyDown);
window.addEventListener("focus", this.onFocus); window.addEventListener("focus", this.onFocus);
if (this.state.logged_in) { if (this.state.logged_in) {
this.notifyNewScreen(''); this.notifyNewScreen('');
} else if (this._autoRegisterAsGuest) {
this._registerAsGuest();
} else { } else {
this.notifyNewScreen('login'); 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) { onAction: function(payload) {
var roomIndexDelta = 1; var roomIndexDelta = 1;
@ -180,6 +225,14 @@ module.exports = React.createClass({
screen: 'post_registration' screen: 'post_registration'
}); });
break; break;
case 'start_upgrade_registration':
this.replaceState({
screen: "register",
upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(),
guestAccessToken: MatrixClientPeg.get().getAccessToken()
});
this.notifyNewScreen('register');
break;
case 'token_login': case 'token_login':
if (this.state.logged_in) return; if (this.state.logged_in) return;
@ -382,10 +435,11 @@ module.exports = React.createClass({
}, },
onLoggedIn: function(credentials) { 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( MatrixClientPeg.replaceUsingAccessToken(
credentials.homeserverUrl, credentials.identityServerUrl, credentials.homeserverUrl, credentials.identityServerUrl,
credentials.userId, credentials.accessToken credentials.userId, credentials.accessToken, credentials.guest
); );
this.setState({ this.setState({
screen: undefined, screen: undefined,
@ -715,12 +769,20 @@ module.exports = React.createClass({
</div> </div>
); );
} }
} 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 Spinner = sdk.getComponent('elements.Spinner');
var logoutLink;
if (this.state.logged_in) {
logoutLink = (
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
Logout
</a>
);
}
return ( return (
<div className="mx_MatrixChat_splash"> <div className="mx_MatrixChat_splash">
<Spinner /> <Spinner />
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>Logout</a> {logoutLink}
</div> </div>
); );
} else if (this.state.screen == 'register') { } else if (this.state.screen == 'register') {
@ -730,6 +792,9 @@ module.exports = React.createClass({
sessionId={this.state.register_session_id} sessionId={this.state.register_session_id}
idSid={this.state.register_id_sid} idSid={this.state.register_id_sid}
email={this.props.startingQueryParams.email} 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} hsUrl={this.props.config.default_hs_url}
isUrl={this.props.config.default_is_url} isUrl={this.props.config.default_is_url}
registrationUrl={this.props.registrationUrl} registrationUrl={this.props.registrationUrl}

View file

@ -97,6 +97,24 @@ module.exports = React.createClass({
this.forceUpdate(); 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() { componentWillUnmount: function() {
@ -422,6 +440,12 @@ module.exports = React.createClass({
joining: false, joining: false,
joinError: error 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({ this.setState({
joining: true joining: true
@ -712,7 +736,7 @@ module.exports = React.createClass({
return ret; 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_name = this.state.room.name;
var old_topic = this.state.room.currentState.getStateEvents('m.room.topic', ''); var old_topic = this.state.room.currentState.getStateEvents('m.room.topic', '');
@ -738,46 +762,54 @@ module.exports = React.createClass({
var deferreds = []; var deferreds = [];
if (old_name != new_name && new_name != undefined && new_name) { if (old_name != newVals.name && newVals.name != undefined && newVals.name) {
deferreds.push( 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( 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( deferreds.push(
MatrixClientPeg.get().sendStateEvent( MatrixClientPeg.get().sendStateEvent(
this.state.room.roomId, "m.room.join_rules", { 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( deferreds.push(
MatrixClientPeg.get().sendStateEvent( MatrixClientPeg.get().sendStateEvent(
this.state.room.roomId, "m.room.history_visibility", { 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( deferreds.push(
MatrixClientPeg.get().sendStateEvent( 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) { if (deferreds.length) {
var self = this; var self = this;
q.all(deferreds).fail(function(err) { q.all(deferreds).fail(function(err) {
@ -862,19 +894,15 @@ module.exports = React.createClass({
uploadingRoomSettings: true, uploadingRoomSettings: true,
}); });
var new_name = this.refs.header.getRoomName(); this.uploadNewState({
var new_topic = this.refs.room_settings.getTopic(); name: this.refs.header.getRoomName(),
var new_join_rule = this.refs.room_settings.getJoinRules(); topic: this.refs.room_settings.getTopic(),
var new_history_visibility = this.refs.room_settings.getHistoryVisibility(); join_rule: this.refs.room_settings.getJoinRules(),
var new_power_levels = this.refs.room_settings.getPowerLevels(); history_visibility: this.refs.room_settings.getHistoryVisibility(),
power_levels: this.refs.room_settings.getPowerLevels(),
this.uploadNewState( guest_join: this.refs.room_settings.canGuestsJoin(),
new_name, guest_read: this.refs.room_settings.canGuestsRead()
new_topic, });
new_join_rule,
new_history_visibility,
new_power_levels
);
}, },
onCancelClick: function() { onCancelClick: function() {

View file

@ -135,6 +135,12 @@ module.exports = React.createClass({
}); });
}, },
onUpgradeClicked: function() {
dis.dispatch({
action: "start_upgrade_registration"
});
},
onLogoutPromptCancel: function() { onLogoutPromptCancel: function() {
this.logoutModal.closeDialog(); this.logoutModal.closeDialog();
}, },
@ -164,6 +170,28 @@ module.exports = React.createClass({
this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null
); );
var accountJsx;
if (MatrixClientPeg.get().isGuest()) {
accountJsx = (
<div className="mx_UserSettings_button" onClick={this.onUpgradeClicked}>
Upgrade (It's free!)
</div>
);
}
else {
accountJsx = (
<ChangePassword
className="mx_UserSettings_accountTable"
rowClassName="mx_UserSettings_profileTableRow"
rowLabelClassName="mx_UserSettings_profileLabelCell"
rowInputClassName="mx_UserSettings_profileInputCell"
buttonClassName="mx_UserSettings_button"
onError={this.onPasswordChangeError}
onFinished={this.onPasswordChanged} />
);
}
return ( return (
<div className="mx_UserSettings"> <div className="mx_UserSettings">
<RoomHeader simpleHeader="Settings" /> <RoomHeader simpleHeader="Settings" />
@ -213,14 +241,7 @@ module.exports = React.createClass({
<h2>Account</h2> <h2>Account</h2>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
<ChangePassword {accountJsx}
className="mx_UserSettings_accountTable"
rowClassName="mx_UserSettings_profileTableRow"
rowLabelClassName="mx_UserSettings_profileLabelCell"
rowInputClassName="mx_UserSettings_profileInputCell"
buttonClassName="mx_UserSettings_button"
onError={this.onPasswordChangeError}
onFinished={this.onPasswordChanged} />
</div> </div>
<div className="mx_UserSettings_logout"> <div className="mx_UserSettings_logout">

View file

@ -19,7 +19,6 @@ limitations under the License.
var React = require('react'); var React = require('react');
var sdk = require('../../../index'); var sdk = require('../../../index');
var MatrixClientPeg = require('../../../MatrixClientPeg');
var dis = require('../../../dispatcher'); var dis = require('../../../dispatcher');
var Signup = require("../../../Signup"); var Signup = require("../../../Signup");
var ServerConfig = require("../../views/login/ServerConfig"); var ServerConfig = require("../../views/login/ServerConfig");
@ -40,6 +39,9 @@ module.exports = React.createClass({
hsUrl: React.PropTypes.string, hsUrl: React.PropTypes.string,
isUrl: React.PropTypes.string, isUrl: React.PropTypes.string,
email: 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. // registration shouldn't know or care how login is done.
onLoginClick: React.PropTypes.func.isRequired onLoginClick: React.PropTypes.func.isRequired
}, },
@ -63,6 +65,7 @@ module.exports = React.createClass({
this.registerLogic.setSessionId(this.props.sessionId); this.registerLogic.setSessionId(this.props.sessionId);
this.registerLogic.setRegistrationUrl(this.props.registrationUrl); this.registerLogic.setRegistrationUrl(this.props.registrationUrl);
this.registerLogic.setIdSid(this.props.idSid); this.registerLogic.setIdSid(this.props.idSid);
this.registerLogic.setGuestAccessToken(this.props.guestAccessToken);
this.registerLogic.recheckState(); this.registerLogic.recheckState();
}, },
@ -186,7 +189,9 @@ module.exports = React.createClass({
registerStep = ( registerStep = (
<RegistrationForm <RegistrationForm
showEmail={true} showEmail={true}
defaultUsername={this.props.username}
defaultEmail={this.props.email} defaultEmail={this.props.email}
disableUsernameChanges={this.props.disableUsernameChanges}
minPasswordLength={MIN_PASSWORD_LENGTH} minPasswordLength={MIN_PASSWORD_LENGTH}
onError={this.onFormValidationFailed} onError={this.onFormValidationFailed}
onRegisterClick={this.onFormSubmit} /> onRegisterClick={this.onFormSubmit} />

View file

@ -30,6 +30,7 @@ module.exports = React.createClass({
defaultUsername: React.PropTypes.string, defaultUsername: React.PropTypes.string,
showEmail: React.PropTypes.bool, showEmail: React.PropTypes.bool,
minPasswordLength: React.PropTypes.number, minPasswordLength: React.PropTypes.number,
disableUsernameChanges: React.PropTypes.bool,
onError: React.PropTypes.func, onError: React.PropTypes.func,
onRegisterClick: React.PropTypes.func // onRegisterClick(Object) => ?Promise onRegisterClick: React.PropTypes.func // onRegisterClick(Object) => ?Promise
}, },
@ -109,7 +110,8 @@ module.exports = React.createClass({
{emailSection} {emailSection}
<br /> <br />
<input className="mx_Login_field" type="text" ref="username" <input className="mx_Login_field" type="text" ref="username"
placeholder="User name" defaultValue={this.state.username} /> placeholder="User name" defaultValue={this.state.username}
disabled={this.props.disableUsernameChanges} />
<br /> <br />
<input className="mx_Login_field" type="password" ref="password" <input className="mx_Login_field" type="password" ref="password"
placeholder="Password" defaultValue={this.state.password} /> placeholder="Password" defaultValue={this.state.password} />

View file

@ -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() { getTopic: function() {
return this.refs.topic.value; return this.refs.topic.value;
}, },
@ -83,6 +91,10 @@ module.exports = React.createClass({
if (history_visibility) history_visibility = history_visibility.getContent().history_visibility; if (history_visibility) history_visibility = history_visibility.getContent().history_visibility;
var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', ''); 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 || {}; var events_levels = power_levels.events || {};
@ -154,6 +166,14 @@ module.exports = React.createClass({
<textarea className="mx_RoomSettings_description" placeholder="Topic" defaultValue={topic} ref="topic"/> <br/> <textarea className="mx_RoomSettings_description" placeholder="Topic" defaultValue={topic} ref="topic"/> <br/>
<label><input type="checkbox" ref="is_private" defaultChecked={join_rule != "public"}/> Make this room private</label> <br/> <label><input type="checkbox" ref="is_private" defaultChecked={join_rule != "public"}/> Make this room private</label> <br/>
<label><input type="checkbox" ref="share_history" defaultChecked={history_visibility == "shared"}/> Share message history with new users</label> <br/> <label><input type="checkbox" ref="share_history" defaultChecked={history_visibility == "shared"}/> Share message history with new users</label> <br/>
<label>
<input type="checkbox" ref="guests_read" defaultChecked={history_visibility === "world_readable"}/>
Allow guests to read messages in this room
</label> <br/>
<label>
<input type="checkbox" ref="guests_join" defaultChecked={guest_access === "can_join"}/>
Allow guests to join this room
</label> <br/>
<label className="mx_RoomSettings_encrypt"><input type="checkbox" /> Encrypt room</label> <br/> <label className="mx_RoomSettings_encrypt"><input type="checkbox" /> Encrypt room</label> <br/>
<h3>Power levels</h3> <h3>Power levels</h3>