/* Copyright 2015, 2016 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. */ var q = require("q"); var React = require('react'); var MatrixClientPeg = require('../../../MatrixClientPeg'); var SdkConfig = require('../../../SdkConfig'); var sdk = require('../../../index'); var Modal = require('../../../Modal'); var ObjectUtils = require("../../../ObjectUtils"); var dis = require("../../../dispatcher"); var ScalarAuthClient = require("../../../ScalarAuthClient"); var ScalarMessaging = require('../../../ScalarMessaging'); var UserSettingsStore = require('../../../UserSettingsStore'); // parse a string as an integer; if the input is undefined, or cannot be parsed // as an integer, return a default. function parseIntWithDefault(val, def) { var res = parseInt(val); return isNaN(res) ? def : res; } module.exports = React.createClass({ displayName: 'RoomSettings', propTypes: { room: React.PropTypes.object.isRequired, onSaveClick: React.PropTypes.func, onCancelClick: React.PropTypes.func, }, getInitialState: function() { var tags = {}; Object.keys(this.props.room.tags).forEach(function(tagName) { tags[tagName] = ['yep']; }); return { name: this._yankValueFromEvent("m.room.name", "name"), topic: this._yankValueFromEvent("m.room.topic", "topic"), join_rule: this._yankValueFromEvent("m.room.join_rules", "join_rule"), history_visibility: this._yankValueFromEvent("m.room.history_visibility", "history_visibility"), guest_access: this._yankValueFromEvent("m.room.guest_access", "guest_access"), power_levels_changed: false, tags_changed: false, tags: tags, // isRoomPublished is loaded async in componentWillMount so when the component // inits, the saved value will always be undefined, however getInitialState() // is also called from the saving code so we must return the correct value here // if we have it (although this could race if the user saves before we load whether // the room is published or not). // Default to false if it's undefined, otherwise react complains about changing // components from uncontrolled to controlled isRoomPublished: this._originalIsRoomPublished || false, scalar_error: null, showIntegrationsError: false, }; }, componentWillMount: function() { ScalarMessaging.startListening(); MatrixClientPeg.get().getRoomDirectoryVisibility( this.props.room.roomId ).done((result) => { this.setState({ isRoomPublished: result.visibility === "public" }); this._originalIsRoomPublished = result.visibility === "public"; }, (err) => { console.error("Failed to get room visibility: " + err); }); this.scalarClient = new ScalarAuthClient(); this.scalarClient.connect().done(() => { this.forceUpdate(); }, (err) => { this.setState({ scalar_error: err }); }); dis.dispatch({ action: 'ui_opacity', sideOpacity: 0.3, middleOpacity: 0.3, }); }, componentWillUnmount: function() { ScalarMessaging.stopListening(); dis.dispatch({ action: 'ui_opacity', sideOpacity: 1.0, middleOpacity: 1.0, }); }, setName: function(name) { this.setState({ name: name }); }, setTopic: function(topic) { this.setState({ topic: topic }); }, save: function() { var stateWasSetDefer = q.defer(); // the caller may have JUST called setState on stuff, so we need to re-render before saving // else we won't use the latest values of things. // We can be a bit cheeky here and set a loading flag, and listen for the callback on that // to know when things have been set. this.setState({ _loading: true}, () => { stateWasSetDefer.resolve(); this.setState({ _loading: false}); }); return stateWasSetDefer.promise.then(() => { return q.allSettled(this._calcSavePromises()); }); }, _calcSavePromises: function() { const roomId = this.props.room.roomId; var promises = this.saveAliases(); // returns Promise[] var originalState = this.getInitialState(); // diff between original state and this.state to work out what has been changed console.log("Original: %s", JSON.stringify(originalState)); console.log("New: %s", JSON.stringify(this.state)); // name and topic if (this._hasDiff(this.state.name, originalState.name)) { promises.push(MatrixClientPeg.get().setRoomName(roomId, this.state.name)); } if (this._hasDiff(this.state.topic, originalState.topic)) { promises.push(MatrixClientPeg.get().setRoomTopic(roomId, this.state.topic)); } if (this.state.history_visibility !== originalState.history_visibility) { promises.push(MatrixClientPeg.get().sendStateEvent( roomId, "m.room.history_visibility", { history_visibility: this.state.history_visibility }, "" )); } if (this.state.isRoomPublished !== originalState.isRoomPublished) { promises.push(MatrixClientPeg.get().setRoomDirectoryVisibility( roomId, this.state.isRoomPublished ? "public" : "private" )); } if (this.state.join_rule !== originalState.join_rule) { promises.push(MatrixClientPeg.get().sendStateEvent( roomId, "m.room.join_rules", { join_rule: this.state.join_rule }, "" )); } if (this.state.guest_access !== originalState.guest_access) { promises.push(MatrixClientPeg.get().sendStateEvent( roomId, "m.room.guest_access", { guest_access: this.state.guest_access }, "" )); } // power levels var powerLevels = this._getPowerLevels(); if (powerLevels) { promises.push(MatrixClientPeg.get().sendStateEvent( roomId, "m.room.power_levels", powerLevels, "" )); } // tags if (this.state.tags_changed) { var tagDiffs = ObjectUtils.getKeyValueArrayDiffs(originalState.tags, this.state.tags); // [ {place: add, key: "m.favourite", val: ["yep"]} ] tagDiffs.forEach(function(diff) { switch (diff.place) { case "add": promises.push( MatrixClientPeg.get().setRoomTag(roomId, diff.key, {}) ); break; case "del": promises.push( MatrixClientPeg.get().deleteRoomTag(roomId, diff.key) ); break; default: console.error("Unknown tag operation: %s", diff.place); break; } }); } // color scheme var p; p = this.saveColor(); if (!q.isFulfilled(p)) { promises.push(p); } // url preview settings var ps = this.saveUrlPreviewSettings(); if (ps.length > 0) { promises.push(ps); } // encryption p = this.saveEnableEncryption(); if (!q.isFulfilled(p)) { promises.push(p); } this.saveBlacklistUnverifiedDevicesPerRoom(); console.log("Performing %s operations: %s", promises.length, JSON.stringify(promises)); return promises; }, saveAliases: function() { if (!this.refs.alias_settings) { return [q()]; } return this.refs.alias_settings.saveSettings(); }, saveColor: function() { if (!this.refs.color_settings) { return q(); } return this.refs.color_settings.saveSettings(); }, saveUrlPreviewSettings: function() { if (!this.refs.url_preview_settings) { return q(); } return this.refs.url_preview_settings.saveSettings(); }, saveEnableEncryption: function() { if (!this.refs.encrypt) { return q(); } var encrypt = this.refs.encrypt.checked; if (encrypt) { return q(); } var roomId = this.props.room.roomId; return MatrixClientPeg.get().sendStateEvent( roomId, "m.room.encryption", { algorithm: "m.megolm.v1.aes-sha2" } ); }, saveBlacklistUnverifiedDevicesPerRoom: function() { if (!this.refs.blacklistUnverified) return; if (this._isRoomBlacklistUnverified() !== this.refs.blacklistUnverified.checked) { this._setRoomBlacklistUnverified(this.refs.blacklistUnverified.checked); } }, _isRoomBlacklistUnverified: function() { var blacklistUnverifiedDevicesPerRoom = UserSettingsStore.getLocalSettings().blacklistUnverifiedDevicesPerRoom; if (blacklistUnverifiedDevicesPerRoom) { return blacklistUnverifiedDevicesPerRoom[this.props.room.roomId]; } return false; }, _setRoomBlacklistUnverified: function(value) { var blacklistUnverifiedDevicesPerRoom = UserSettingsStore.getLocalSettings().blacklistUnverifiedDevicesPerRoom || {}; blacklistUnverifiedDevicesPerRoom[this.props.room.roomId] = value; UserSettingsStore.setLocalSetting('blacklistUnverifiedDevicesPerRoom', blacklistUnverifiedDevicesPerRoom); this.props.room.setBlacklistUnverifiedDevices(value); }, _hasDiff: function(strA, strB) { // treat undefined as an empty string because other components may blindly // call setName("") when there has been no diff made to the name! strA = strA || ""; strB = strB || ""; return strA !== strB; }, _getPowerLevels: function() { if (!this.state.power_levels_changed) return undefined; var powerLevels = this.props.room.currentState.getStateEvents('m.room.power_levels', ''); powerLevels = powerLevels ? powerLevels.getContent() : {}; var newPowerLevels = { ban: parseInt(this.refs.ban.getValue()), kick: parseInt(this.refs.kick.getValue()), redact: parseInt(this.refs.redact.getValue()), invite: parseInt(this.refs.invite.getValue()), events_default: parseInt(this.refs.events_default.getValue()), state_default: parseInt(this.refs.state_default.getValue()), users_default: parseInt(this.refs.users_default.getValue()), users: powerLevels.users, events: powerLevels.events, }; return newPowerLevels; }, onPowerLevelsChanged: function() { this.setState({ power_levels_changed: true }); }, _yankValueFromEvent: function(stateEventType, keyName, defaultValue) { // E.g.("m.room.name","name") would yank the "name" content key from "m.room.name" var event = this.props.room.currentState.getStateEvents(stateEventType, ''); if (!event) { return defaultValue; } return event.getContent()[keyName] || defaultValue; }, _onHistoryRadioToggle: function(ev) { var self = this; var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); // cancel the click unless the user confirms it ev.preventDefault(); var value = ev.target.value; Modal.createDialog(QuestionDialog, { title: "Privacy warning", description:
End-to-end encryption is in beta and may not be reliable.
You should not yet trust it to secure data.
Devices will not yet be able to decrypt history from before they joined the room.
Once encryption is enabled for a room it cannot be turned off again (for now).
Encrypted messages will not be visible on clients that do not yet implement encryption.
{ event_type }
, you must be a
{ this.props.room.roomId }