From a29ab0976be9db9a98a535f67b396949495765e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20V=C3=A1gner?= Date: Wed, 8 Nov 2017 10:23:11 +0100 Subject: [PATCH 01/41] Make the addresses heading on the aliases settings view translatable --- src/components/views/room_settings/AliasSettings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index cb897c9daf..f1c5c0000c 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -253,7 +253,7 @@ module.exports = React.createClass({ return (
-

Addresses

+

{ _t('Addresses') }

{ _t('The main address for this room is') }: { canonical_alias_section }
From 1e7fc953b353cfce8c3a35cb0db9ec4f281e1de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20V=C3=A1gner?= Date: Wed, 8 Nov 2017 11:56:30 +0100 Subject: [PATCH 02/41] Ooops, also add the string to the default language file. --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4746a11921..83793291c9 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -420,6 +420,7 @@ "not specified": "not specified", "not set": "not set", "Remote addresses for this room:": "Remote addresses for this room:", + "Addresses": "Addresses", "The main address for this room is": "The main address for this room is", "Local addresses for this room:": "Local addresses for this room:", "This room has no local addresses": "This room has no local addresses", From 820d9c1c25c2cfb1860bd5c407a490ab614af5e8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 9 Nov 2017 15:58:15 +0000 Subject: [PATCH 03/41] Show staus bar on Unknown Device Error Don't pop up the dialog as soon as we can't send a message. Also removes dispatches used to keep the RoomStatusBar up to date. We can get the same events straight from the js-sdk via the pending events event. --- src/UnknownDeviceErrorHandler.js | 51 ------- src/components/structures/MatrixChat.js | 3 - src/components/structures/RoomStatusBar.js | 144 +++++++++++++++--- src/components/structures/RoomView.js | 49 ------ .../views/dialogs/UnknownDeviceDialog.js | 37 ++--- src/i18n/strings/en_EN.json | 8 +- 6 files changed, 140 insertions(+), 152 deletions(-) delete mode 100644 src/UnknownDeviceErrorHandler.js diff --git a/src/UnknownDeviceErrorHandler.js b/src/UnknownDeviceErrorHandler.js deleted file mode 100644 index e7d77b3b66..0000000000 --- a/src/UnknownDeviceErrorHandler.js +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright 2017 Vector Creations 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. -*/ - -import dis from './dispatcher'; -import sdk from './index'; -import Modal from './Modal'; - -let isDialogOpen = false; - -const onAction = function(payload) { - if (payload.action === 'unknown_device_error' && !isDialogOpen) { - const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog'); - isDialogOpen = true; - Modal.createTrackedDialog('Unknown Device Error', '', UnknownDeviceDialog, { - devices: payload.err.devices, - room: payload.room, - onFinished: (r) => { - isDialogOpen = false; - // XXX: temporary logging to try to diagnose - // https://github.com/vector-im/riot-web/issues/3148 - console.log('UnknownDeviceDialog closed with '+r); - }, - }, 'mx_Dialog_unknownDevice'); - } -}; - -let ref = null; - -export function startListening() { - ref = dis.register(onAction); -} - -export function stopListening() { - if (ref) { - dis.unregister(ref); - ref = null; - } -} diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index e8ca8e82fc..72497f527f 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -41,7 +41,6 @@ require('../../stores/LifecycleStore'); import PageTypes from '../../PageTypes'; import createRoom from "../../createRoom"; -import * as UDEHandler from '../../UnknownDeviceErrorHandler'; import KeyRequestHandler from '../../KeyRequestHandler'; import { _t, getCurrentLanguage } from '../../languageHandler'; @@ -280,7 +279,6 @@ module.exports = React.createClass({ componentDidMount: function() { this.dispatcherRef = dis.register(this.onAction); - UDEHandler.startListening(); this.focusComposer = false; @@ -346,7 +344,6 @@ module.exports = React.createClass({ componentWillUnmount: function() { Lifecycle.stopMatrixClient(); dis.unregister(this.dispatcherRef); - UDEHandler.stopListening(); window.removeEventListener("focus", this.onFocus); window.removeEventListener('resize', this.handleResize); }, diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index cad55351d1..c37cc7deef 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,16 +17,26 @@ limitations under the License. import React from 'react'; import { _t, _tJsx } from '../../languageHandler'; +import Matrix from 'matrix-js-sdk'; import sdk from '../../index'; import WhoIsTyping from '../../WhoIsTyping'; import MatrixClientPeg from '../../MatrixClientPeg'; import MemberAvatar from '../views/avatars/MemberAvatar'; +import Resend from '../../Resend'; +import Modal from '../../Modal'; const HIDE_DEBOUNCE_MS = 10000; const STATUS_BAR_HIDDEN = 0; const STATUS_BAR_EXPANDED = 1; const STATUS_BAR_EXPANDED_LARGE = 2; +function getUnsentMessages(room) { + if (!room) { return []; } + return room.getPendingEvents().filter(function(ev) { + return ev.status === Matrix.EventStatus.NOT_SENT; + }); +}; + module.exports = React.createClass({ displayName: 'RoomStatusBar', @@ -36,9 +47,6 @@ module.exports = React.createClass({ // the number of messages which have arrived since we've been scrolled up numUnreadMessages: React.PropTypes.number, - // string to display when there are messages in the room which had errors on send - unsentMessageError: React.PropTypes.string, - // this is true if we are fully scrolled-down, and are looking at // the end of the live timeline. atEndOfLiveTimeline: React.PropTypes.bool, @@ -99,12 +107,14 @@ module.exports = React.createClass({ return { syncState: MatrixClientPeg.get().getSyncState(), usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room), + unsentMessages: [], }; }, componentWillMount: function() { MatrixClientPeg.get().on("sync", this.onSyncStateChange); MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping); + MatrixClientPeg.get().on("Room.localEchoUpdated", this.onRoomLocalEchoUpdated); this._checkSize(); }, @@ -119,6 +129,7 @@ module.exports = React.createClass({ if (client) { client.removeListener("sync", this.onSyncStateChange); client.removeListener("RoomMember.typing", this.onRoomMemberTyping); + client.removeListener("Room.localEchoUpdated", this.onRoomLocalEchoUpdated); } }, @@ -137,6 +148,57 @@ module.exports = React.createClass({ }); }, + _onResendAllClick: function() { + Resend.resendUnsentEvents(this.props.room); + }, + + _onCancelAllClick: function() { + Resend.cancelUnsentEvents(this.props.room); + }, + + _onShowDevicesClick: function() { + this._getUnknownDevices().then((unknownDevices) => { + const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog'); + Modal.createTrackedDialog('Unknown Device Dialog', '', UnknownDeviceDialog, { + room: this.props.room, + devices: unknownDevices, + }, 'mx_Dialog_unknownDevice'); + }); + }, + + _getUnknownDevices: function() { + const roomMembers = this.props.room.getJoinedMembers().map((m) => { + return m.userId; + }); + return MatrixClientPeg.get().downloadKeys(roomMembers, false).then((devices) => { + if (this._unmounted) return; + + const unknownDevices = {}; + // This is all devices in this room, so find the unknown ones. + Object.keys(devices).forEach((userId) => { + Object.keys(devices[userId]).map((deviceId) => { + const device = devices[userId][deviceId]; + + if (device.isUnverified() && !device.isKnown()) { + if (unknownDevices[userId] === undefined) { + unknownDevices[userId] = {}; + } + unknownDevices[userId][deviceId] = device; + } + }); + }); + return unknownDevices; + }); + }, + + onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) { + if (room.roomId !== this.props.room.roomId) return; + + this.setState({ + unsentMessages: getUnsentMessages(this.props.room), + }); + }, + // Check whether current size is greater than 0, if yes call props.onVisible _checkSize: function() { if (this.props.onVisible && this._getSize()) { @@ -156,7 +218,7 @@ module.exports = React.createClass({ this.props.sentMessageAndIsAlone ) { return STATUS_BAR_EXPANDED; - } else if (this.props.unsentMessageError) { + } else if (this.state.unsentMessages.length > 0) { return STATUS_BAR_EXPANDED_LARGE; } return STATUS_BAR_HIDDEN; @@ -242,6 +304,60 @@ module.exports = React.createClass({ return avatars; }, + _getUnsentMessageContent: function(room) { + const unsentMessages = this.state.unsentMessages; + if (!unsentMessages.length) return null; + + let title; + let content; + + const hasUDE = unsentMessages.some((m) => { + return m.error && m.error.name === "UnknownDeviceError"; + }); + + if (hasUDE) { + title = _t("Message not sent due to unknown devices being present"); + content = _tJsx( + "Show devices or cancel all.", + [/(.*?)<\/a>/, /(.*?)<\/a>/], + [ + (sub) => { sub }, + (sub) => { sub }, + ], + ); + } else { + if ( + unsentMessages.length === 1 && + unsentMessages[0].error && + unsentMessages[0].error.data && + unsentMessages[0].error.data.error + ) { + title = unsentMessages[0].error.data.error; + } else { + title = _t("Some of your messages have not been sent."); + } + content = _tJsx( + "Resend all or cancel all now. "+ + "You can also select individual messages to resend or cancel.", + [/(.*?)<\/a>/, /(.*?)<\/a>/], + [ + (sub) => { sub }, + (sub) => { sub }, + ], + ); + } + + return
+ {_t("Warning")} +
+ { title } +
+
+ { content } +
+
; + }, + // return suitable content for the main (text) part of the status bar. _getContent: function() { const EmojiText = sdk.getComponent('elements.EmojiText'); @@ -264,24 +380,8 @@ module.exports = React.createClass({ ); } - if (this.props.unsentMessageError) { - return ( -
- /!\ -
- { this.props.unsentMessageError } -
-
- { _tJsx("Resend all or cancel all now. You can also select individual messages to resend or cancel.", - [/(.*?)<\/a>/, /(.*?)<\/a>/], - [ - (sub) => { sub }, - (sub) => { sub }, - ], - ) } -
-
- ); + if (this.state.unsentMessages.length > 0) { + return this._getUnsentMessageContent(); } // unread count trumps who is typing since the unread count is only diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 409b95947f..0f4531ecef 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -26,7 +26,6 @@ const React = require("react"); const ReactDOM = require("react-dom"); import Promise from 'bluebird'; const classNames = require("classnames"); -const Matrix = require("matrix-js-sdk"); import { _t } from '../../languageHandler'; const UserSettingsStore = require('../../UserSettingsStore'); @@ -35,7 +34,6 @@ const ContentMessages = require("../../ContentMessages"); const Modal = require("../../Modal"); const sdk = require('../../index'); const CallHandler = require('../../CallHandler'); -const Resend = require("../../Resend"); const dis = require("../../dispatcher"); const Tinter = require("../../Tinter"); const rate_limited_func = require('../../ratelimitedfunc'); @@ -110,7 +108,6 @@ module.exports = React.createClass({ draggingFile: false, searching: false, searchResults: null, - unsentMessageError: '', callState: null, guestsCanJoin: false, canPeek: false, @@ -204,7 +201,6 @@ module.exports = React.createClass({ if (initial) { newState.room = MatrixClientPeg.get().getRoom(newState.roomId); if (newState.room) { - newState.unsentMessageError = this._getUnsentMessageError(newState.room); newState.showApps = this._shouldShowApps(newState.room); this._onRoomLoaded(newState.room); } @@ -470,11 +466,6 @@ module.exports = React.createClass({ case 'message_send_failed': case 'message_sent': this._checkIfAlone(this.state.room); - // no break; to intentionally fall through - case 'message_send_cancelled': - this.setState({ - unsentMessageError: this._getUnsentMessageError(this.state.room), - }); break; case 'notifier_enabled': case 'upload_failed': @@ -754,35 +745,6 @@ module.exports = React.createClass({ this.setState({isAlone: joinedMembers.length === 1}); }, - _getUnsentMessageError: function(room) { - const unsentMessages = this._getUnsentMessages(room); - if (!unsentMessages.length) return ""; - - if ( - unsentMessages.length === 1 && - unsentMessages[0].error && - unsentMessages[0].error.data && - unsentMessages[0].error.data.error && - unsentMessages[0].error.name !== "UnknownDeviceError" - ) { - return unsentMessages[0].error.data.error; - } - - for (const event of unsentMessages) { - if (!event.error || event.error.name !== "UnknownDeviceError") { - return _t("Some of your messages have not been sent."); - } - } - return _t("Message not sent due to unknown devices being present"); - }, - - _getUnsentMessages: function(room) { - if (!room) { return []; } - return room.getPendingEvents().filter(function(ev) { - return ev.status === Matrix.EventStatus.NOT_SENT; - }); - }, - _updateConfCallNotification: function() { const room = this.state.room; if (!room || !this.props.ConferenceHandler) { @@ -827,14 +789,6 @@ module.exports = React.createClass({ } }, - onResendAllClick: function() { - Resend.resendUnsentEvents(this.state.room); - }, - - onCancelAllClick: function() { - Resend.cancelUnsentEvents(this.state.room); - }, - onInviteButtonClick: function() { // call AddressPickerDialog dis.dispatch({ @@ -1614,12 +1568,9 @@ module.exports = React.createClass({ statusBar = { Object.keys(this.props.devices[userId]).map((deviceId) => { MatrixClientPeg.get().setDeviceKnown(userId, deviceId, true); }); }); + this.props.onFinished(); + Resend.resendUnsentEvents(this.props.room); + }, - // XXX: temporary logging to try to diagnose - // https://github.com/vector-im/riot-web/issues/3148 - console.log('Opening UnknownDeviceDialog'); + _onDismissClicked: function() { + this.props.onFinished(); }, render: function() { @@ -139,12 +141,7 @@ export default React.createClass({ const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( { - // XXX: temporary logging to try to diagnose - // https://github.com/vector-im/riot-web/issues/3148 - console.log("UnknownDeviceDialog closed by escape"); - this.props.onFinished(); - }} + onFinished={this.props.onFinished} title={_t('Room contains unknown devices')} > @@ -157,21 +154,13 @@ export default React.createClass({
-
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4052d098c1..b83600ffb6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -727,17 +727,19 @@ "To join an existing community you'll have to know its community identifier; this will look something like +example:matrix.org.": "To join an existing community you'll have to know its community identifier; this will look something like +example:matrix.org.", "You have no visible notifications": "You have no visible notifications", "Scroll to bottom of page": "Scroll to bottom of page", + "Message not sent due to unknown devices being present": "Message not sent due to unknown devices being present", + "Show devices or cancel all.": "Show devices or cancel all.", + "Some of your messages have not been sent.": "Some of your messages have not been sent.", + "Resend all or cancel all now. You can also select individual messages to resend or cancel.": "Resend all or cancel all now. You can also select individual messages to resend or cancel.", + "Warning": "Warning", "Connectivity to the server has been lost.": "Connectivity to the server has been lost.", "Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.", - "Resend all or cancel all now. You can also select individual messages to resend or cancel.": "Resend all or cancel all now. You can also select individual messages to resend or cancel.", "%(count)s new messages|other": "%(count)s new messages", "%(count)s new messages|one": "%(count)s new message", "Active call": "Active call", "There's no one else here! Would you like to invite others or stop warning about the empty room?": "There's no one else here! Would you like to invite others or stop warning about the empty room?", "You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?", "You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?", - "Some of your messages have not been sent.": "Some of your messages have not been sent.", - "Message not sent due to unknown devices being present": "Message not sent due to unknown devices being present", "Failed to upload file": "Failed to upload file", "Server may be unavailable, overloaded, or the file too big": "Server may be unavailable, overloaded, or the file too big", "Search failed": "Search failed", From b1ec430523fffc0049d278b6167bdacf90c12817 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 9 Nov 2017 16:09:12 +0000 Subject: [PATCH 04/41] Remove now-unused dispatches --- src/CallHandler.js | 9 --------- src/Resend.js | 11 ----------- src/components/structures/MatrixChat.js | 7 ------- src/components/structures/RoomView.js | 6 +----- src/components/views/rooms/MessageComposerInput.js | 7 ------- 5 files changed, 1 insertion(+), 39 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index a9539d40e1..e2241a4955 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -105,15 +105,6 @@ function _setCallListeners(call) { call.hangup(); _setCallState(undefined, call.roomId, "ended"); }); - call.on('send_event_error', function(err) { - if (err.name === "UnknownDeviceError") { - dis.dispatch({ - action: 'unknown_device_error', - err: err, - room: MatrixClientPeg.get().getRoom(call.roomId), - }); - } - }); call.on("hangup", function() { _setCallState(undefined, call.roomId, "ended"); }); diff --git a/src/Resend.js b/src/Resend.js index 1fee5854ea..4eaee16d1b 100644 --- a/src/Resend.js +++ b/src/Resend.js @@ -44,13 +44,6 @@ module.exports = { // XXX: temporary logging to try to diagnose // https://github.com/vector-im/riot-web/issues/3148 console.log('Resend got send failure: ' + err.name + '('+err+')'); - if (err.name === "UnknownDeviceError") { - dis.dispatch({ - action: 'unknown_device_error', - err: err, - room: room, - }); - } dis.dispatch({ action: 'message_send_failed', @@ -60,9 +53,5 @@ module.exports = { }, removeFromQueue: function(event) { MatrixClientPeg.get().cancelPendingEvent(event); - dis.dispatch({ - action: 'message_send_cancelled', - event: event, - }); }, }; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 72497f527f..82b2200409 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1331,13 +1331,6 @@ module.exports = React.createClass({ cli.sendEvent(roomId, event.getType(), event.getContent()).done(() => { dis.dispatch({action: 'message_sent'}); }, (err) => { - if (err.name === 'UnknownDeviceError') { - dis.dispatch({ - action: 'unknown_device_error', - err: err, - room: cli.getRoom(roomId), - }); - } dis.dispatch({action: 'message_send_failed'}); }); }, diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 0f4531ecef..4a7229e7c5 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -932,11 +932,7 @@ module.exports = React.createClass({ file, this.state.room.roomId, MatrixClientPeg.get(), ).done(undefined, (error) => { if (error.name === "UnknownDeviceError") { - dis.dispatch({ - action: 'unknown_device_error', - err: error, - room: this.state.room, - }); + // Let the staus bar handle this return; } const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index aa019de091..9c6644f9dd 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -74,13 +74,6 @@ function onSendMessageFailed(err, room) { // XXX: temporary logging to try to diagnose // https://github.com/vector-im/riot-web/issues/3148 console.log('MessageComposer got send failure: ' + err.name + '('+err+')'); - if (err.name === "UnknownDeviceError") { - dis.dispatch({ - action: 'unknown_device_error', - err: err, - room: room, - }); - } dis.dispatch({ action: 'message_send_failed', }); From 681f43913acc1a51b34ca63b1d188381b134be9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20V=C3=A1gner?= Date: Fri, 10 Nov 2017 11:26:53 +0100 Subject: [PATCH 05/41] Make the disabled PowerSelector element showing custom value translatable. Fixes #5547 --- src/components/views/elements/PowerSelector.js | 12 ++++++------ src/i18n/strings/en_EN.json | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/PowerSelector.js b/src/components/views/elements/PowerSelector.js index a0aaa12ff1..8e60a7066d 100644 --- a/src/components/views/elements/PowerSelector.js +++ b/src/components/views/elements/PowerSelector.js @@ -18,7 +18,7 @@ limitations under the License. import React from 'react'; import * as Roles from '../../../Roles'; -import { _t } from '../../../languageHandler'; +import { _t, _tJsx } from '../../../languageHandler'; let LEVEL_ROLE_MAP = {}; const reverseRoles = {}; @@ -85,13 +85,11 @@ module.exports = React.createClass({ render: function() { let customPicker; if (this.state.custom) { - let input; if (this.props.disabled) { - input = { this.props.value }; + customPicker = { _tJsx('Custom of ', [//], [(sub) => { this.props.value }]) }; } else { - input = ; + customPicker = ; } - customPicker = of { input }; } let selectValue; @@ -102,7 +100,9 @@ module.exports = React.createClass({ } let select; if (this.props.disabled) { - select = { selectValue }; + if (!this.state.custom) { + select = { selectValue }; + } } else { // Each level must have a definition in LEVEL_ROLE_MAP const levels = [0, 50, 100]; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 83793291c9..cfc28e3432 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -581,6 +581,7 @@ "%(items)s and %(count)s others|other": "%(items)s and %(count)s others", "%(items)s and %(count)s others|one": "%(items)s and one other", "%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s", + "Custom of ": "Custom of ", "Custom level": "Custom level", "Room directory": "Room directory", "Start chat": "Start chat", From bce481585110f7676cf399eec6a17d76b8232eb9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Sat, 11 Nov 2017 15:57:20 +0000 Subject: [PATCH 06/41] Initialise unread messages value correctly --- src/components/structures/RoomStatusBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index c37cc7deef..30f6f3ee2a 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -107,7 +107,7 @@ module.exports = React.createClass({ return { syncState: MatrixClientPeg.get().getSyncState(), usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room), - unsentMessages: [], + unsentMessages: getUnsentMessages(this.props.room), }; }, From 63919befd0aad8772507765a382feb7f6a6e9e27 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 15 Nov 2017 10:49:29 +0000 Subject: [PATCH 07/41] Catch call failures due to unknown devices And show a specific dialog that you can then launch the UDD from (although currently with a 'Send Anyway' button which makes little sense for VoIP) --- src/CallHandler.js | 27 ++++++++++++++- src/components/structures/RoomStatusBar.js | 30 +++-------------- src/cryptodevices.js | 39 ++++++++++++++++++++++ src/i18n/strings/en_EN.json | 5 ++- 4 files changed, 73 insertions(+), 28 deletions(-) create mode 100644 src/cryptodevices.js diff --git a/src/CallHandler.js b/src/CallHandler.js index e2241a4955..66a84bafa7 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -59,6 +59,7 @@ import sdk from './index'; import { _t } from './languageHandler'; import Matrix from 'matrix-js-sdk'; import dis from './dispatcher'; +import { getUnknownDevicesForRoom } from './cryptodevices'; global.mxCalls = { //room_id: MatrixCall @@ -104,6 +105,31 @@ function _setCallListeners(call) { console.error(err.stack); call.hangup(); _setCallState(undefined, call.roomId, "ended"); + if (err.code === 'unknown_devices') { + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + + Modal.createTrackedDialog('Call Failed', '', QuestionDialog, { + title: _t('Call Failed'), + description: _t( + "There are unknown devices in this room: "+ + "if you proceed without verifying them, it will be "+ + "possible for someone to eavesdrop on your call" + ), + button: _t('Review Devices'), + onFinished: function(confirmed) { + if (confirmed) { + const room = MatrixClientPeg.get().getRoom(call.roomId); + getUnknownDevicesForRoom(MatrixClientPeg.get(), room).then((unknownDevices) => { + const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog'); + Modal.createTrackedDialog('Unknown Device Dialog', '', UnknownDeviceDialog, { + room: room, + devices: unknownDevices, + }, 'mx_Dialog_unknownDevice'); + }); + } + }, + }); + } }); call.on("hangup", function() { _setCallState(undefined, call.roomId, "ended"); @@ -171,7 +197,6 @@ function _setCallState(call, roomId, status) { function _onAction(payload) { function placeCall(newCall) { _setCallListeners(newCall); - _setCallState(newCall, newCall.roomId, "ringback"); if (payload.type === 'voice') { newCall.placeVoiceCall(); } else if (payload.type === 'video') { diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 30f6f3ee2a..4c45fd09a4 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -24,6 +24,7 @@ import MatrixClientPeg from '../../MatrixClientPeg'; import MemberAvatar from '../views/avatars/MemberAvatar'; import Resend from '../../Resend'; import Modal from '../../Modal'; +import { getUnknownDevicesForRoom } from '../../cryptodevices'; const HIDE_DEBOUNCE_MS = 10000; const STATUS_BAR_HIDDEN = 0; @@ -157,7 +158,9 @@ module.exports = React.createClass({ }, _onShowDevicesClick: function() { - this._getUnknownDevices().then((unknownDevices) => { + getUnknownDevicesForRoom(MatrixClientPeg.get(), this.props.room).then((unknownDevices) => { + if (this._unmounted) return; + const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog'); Modal.createTrackedDialog('Unknown Device Dialog', '', UnknownDeviceDialog, { room: this.props.room, @@ -166,31 +169,6 @@ module.exports = React.createClass({ }); }, - _getUnknownDevices: function() { - const roomMembers = this.props.room.getJoinedMembers().map((m) => { - return m.userId; - }); - return MatrixClientPeg.get().downloadKeys(roomMembers, false).then((devices) => { - if (this._unmounted) return; - - const unknownDevices = {}; - // This is all devices in this room, so find the unknown ones. - Object.keys(devices).forEach((userId) => { - Object.keys(devices[userId]).map((deviceId) => { - const device = devices[userId][deviceId]; - - if (device.isUnverified() && !device.isKnown()) { - if (unknownDevices[userId] === undefined) { - unknownDevices[userId] = {}; - } - unknownDevices[userId][deviceId] = device; - } - }); - }); - return unknownDevices; - }); - }, - onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) { if (room.roomId !== this.props.room.roomId) return; diff --git a/src/cryptodevices.js b/src/cryptodevices.js new file mode 100644 index 0000000000..d580e6f7f2 --- /dev/null +++ b/src/cryptodevices.js @@ -0,0 +1,39 @@ +/* +Copyright 2017 New Vector 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. +*/ + +export function getUnknownDevicesForRoom(matrixClient, room) { + const roomMembers = room.getJoinedMembers().map((m) => { + return m.userId; + }); + return matrixClient.downloadKeys(roomMembers, false).then((devices) => { + const unknownDevices = {}; + // This is all devices in this room, so find the unknown ones. + Object.keys(devices).forEach((userId) => { + Object.keys(devices[userId]).map((deviceId) => { + const device = devices[userId][deviceId]; + + if (device.isUnverified() && !device.isKnown()) { + if (unknownDevices[userId] === undefined) { + unknownDevices[userId] = {}; + } + unknownDevices[userId][deviceId] = device; + } + }); + }); + return unknownDevices; + }); +} + diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b83600ffb6..28ac9281f4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2,6 +2,9 @@ "This email address is already in use": "This email address is already in use", "This phone number is already in use": "This phone number is already in use", "Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email", + "Call Failed": "Call Failed", + "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call": "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call", + "Review Devices": "Review Devices", "Call Timeout": "Call Timeout", "The remote side failed to pick up": "The remote side failed to pick up", "Unable to capture screen": "Unable to capture screen", @@ -150,7 +153,6 @@ "%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s", "%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s", "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s", - "Communities": "Communities", "Message Pinning": "Message Pinning", "%(displayName)s is typing": "%(displayName)s is typing", "%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing", @@ -524,6 +526,7 @@ "Unverify": "Unverify", "Verify...": "Verify...", "No results": "No results", + "Communities": "Communities", "Home": "Home", "Integrations Error": "Integrations Error", "Could not connect to the integration server": "Could not connect to the integration server", From 93800be7425d1001851806939017312baab61961 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 15 Nov 2017 15:14:42 +0000 Subject: [PATCH 08/41] Factor out showing UnknownDeviceDialog So we can re-use it for calls that fail due to unknwon devices --- src/components/structures/RoomStatusBar.js | 14 ++------ .../views/dialogs/UnknownDeviceDialog.js | 35 +++++++------------ src/cryptodevices.js | 32 +++++++++++++++++ 3 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 4c45fd09a4..7443ff35cc 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -23,8 +23,7 @@ import WhoIsTyping from '../../WhoIsTyping'; import MatrixClientPeg from '../../MatrixClientPeg'; import MemberAvatar from '../views/avatars/MemberAvatar'; import Resend from '../../Resend'; -import Modal from '../../Modal'; -import { getUnknownDevicesForRoom } from '../../cryptodevices'; +import { getUnknownDevicesForRoom, showUnknownDeviceDialogForMessages } from '../../cryptodevices'; const HIDE_DEBOUNCE_MS = 10000; const STATUS_BAR_HIDDEN = 0; @@ -158,15 +157,7 @@ module.exports = React.createClass({ }, _onShowDevicesClick: function() { - getUnknownDevicesForRoom(MatrixClientPeg.get(), this.props.room).then((unknownDevices) => { - if (this._unmounted) return; - - const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog'); - Modal.createTrackedDialog('Unknown Device Dialog', '', UnknownDeviceDialog, { - room: this.props.room, - devices: unknownDevices, - }, 'mx_Dialog_unknownDevice'); - }); + showUnknownDeviceDialogForMessages(MatrixClientPeg.get(), this.props.room); }, onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) { @@ -414,7 +405,6 @@ module.exports = React.createClass({ return null; }, - render: function() { const content = this._getContent(); const indicator = this._getIndicator(this.state.usersTyping.length > 0); diff --git a/src/components/views/dialogs/UnknownDeviceDialog.js b/src/components/views/dialogs/UnknownDeviceDialog.js index fac29fd37c..2e89459164 100644 --- a/src/components/views/dialogs/UnknownDeviceDialog.js +++ b/src/components/views/dialogs/UnknownDeviceDialog.js @@ -16,6 +16,7 @@ limitations under the License. */ import React from 'react'; +import PropTypes from 'prop-types'; import sdk from '../../../index'; import MatrixClientPeg from '../../../MatrixClientPeg'; import GeminiScrollbar from 'react-gemini-scrollbar'; @@ -39,10 +40,10 @@ function DeviceListEntry(props) { } DeviceListEntry.propTypes = { - userId: React.PropTypes.string.isRequired, + userId: PropTypes.string.isRequired, // deviceinfo - device: React.PropTypes.object.isRequired, + device: PropTypes.object.isRequired, }; @@ -62,10 +63,10 @@ function UserUnknownDeviceList(props) { } UserUnknownDeviceList.propTypes = { - userId: React.PropTypes.string.isRequired, + userId: PropTypes.string.isRequired, // map from deviceid -> deviceinfo - userDevices: React.PropTypes.object.isRequired, + userDevices: PropTypes.object.isRequired, }; @@ -84,7 +85,7 @@ function UnknownDeviceList(props) { UnknownDeviceList.propTypes = { // map from userid -> deviceid -> deviceinfo - devices: React.PropTypes.object.isRequired, + devices: PropTypes.object.isRequired, }; @@ -92,22 +93,12 @@ export default React.createClass({ displayName: 'UnknownDeviceDialog', propTypes: { - room: React.PropTypes.object.isRequired, + room: PropTypes.object.isRequired, // map from userid -> deviceid -> deviceinfo - devices: React.PropTypes.object.isRequired, - onFinished: React.PropTypes.func.isRequired, - }, - - _onSendAnywayClicked: function() { - // Mark the devices as known so messages get encrypted to them - Object.keys(this.props.devices).forEach((userId) => { - Object.keys(this.props.devices[userId]).map((deviceId) => { - MatrixClientPeg.get().setDeviceKnown(userId, deviceId, true); - }); - }); - this.props.onFinished(); - Resend.resendUnsentEvents(this.props.room); + devices: PropTypes.object.isRequired, + onFinished: PropTypes.func.isRequired, + sendAnywayButton: PropTypes.node, }, _onDismissClicked: function() { @@ -115,7 +106,7 @@ export default React.createClass({ }, render: function() { - if (this.state.devices === null) { + if (this.props.devices === null) { const Spinner = sdk.getComponent("elements.Spinner"); return ; } @@ -156,9 +147,7 @@ export default React.createClass({
- + {this.props.sendAnywayButton} + ), + }, 'mx_Dialog_unknownDevice'); + }); +} + +function markAllDevicesKnown(matrixClient, devices) { + Object.keys(devices).forEach((userId) => { + Object.keys(devices[userId]).map((deviceId) => { + matrixClient.setDeviceKnown(userId, deviceId, true); + }); + }); +} From aeca83ff2eca5ebd97632fe071531ddc3effda3d Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 15 Nov 2017 15:20:45 +0000 Subject: [PATCH 09/41] Unused import --- src/components/structures/RoomStatusBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 7443ff35cc..eabeef7a04 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -23,7 +23,7 @@ import WhoIsTyping from '../../WhoIsTyping'; import MatrixClientPeg from '../../MatrixClientPeg'; import MemberAvatar from '../views/avatars/MemberAvatar'; import Resend from '../../Resend'; -import { getUnknownDevicesForRoom, showUnknownDeviceDialogForMessages } from '../../cryptodevices'; +import { showUnknownDeviceDialogForMessages } from '../../cryptodevices'; const HIDE_DEBOUNCE_MS = 10000; const STATUS_BAR_HIDDEN = 0; From b0027525f3bca80c30044d922333eb7ce8a6ef40 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 15 Nov 2017 17:21:04 +0000 Subject: [PATCH 10/41] Wire up Unknown Devices popup for outbound calls --- src/CallHandler.js | 38 ++++++++--- .../views/dialogs/UnknownDeviceDialog.js | 11 +++- src/cryptodevices.js | 24 +++++-- src/i18n/strings/en_EN.json | 66 +++++++++---------- 4 files changed, 92 insertions(+), 47 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index c61794a940..eba1c9995e 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -60,6 +61,7 @@ import Matrix from 'matrix-js-sdk'; import dis from './dispatcher'; import { getUnknownDevicesForRoom } from './cryptodevices'; import SettingsStore from "./settings/SettingsStore"; +import { showUnknownDeviceDialogForCalls } from './cryptodevices'; global.mxCalls = { //room_id: MatrixCall @@ -99,6 +101,18 @@ function pause(audioId) { } } +function _reAttemptCall(call) { + if (call.direction === 'outbound') { + dis.dispatch({ + action: 'place_call', + room_id: call.roomId, + type: call.type, + }); + } else { + call.answer(); + } +} + function _setCallListeners(call) { call.on("error", function(err) { console.error("Call error: %s", err); @@ -113,22 +127,30 @@ function _setCallListeners(call) { description: _t( "There are unknown devices in this room: "+ "if you proceed without verifying them, it will be "+ - "possible for someone to eavesdrop on your call" + "possible for someone to eavesdrop on your call." ), button: _t('Review Devices'), onFinished: function(confirmed) { if (confirmed) { const room = MatrixClientPeg.get().getRoom(call.roomId); - getUnknownDevicesForRoom(MatrixClientPeg.get(), room).then((unknownDevices) => { - const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog'); - Modal.createTrackedDialog('Unknown Device Dialog', '', UnknownDeviceDialog, { - room: room, - devices: unknownDevices, - }, 'mx_Dialog_unknownDevice'); - }); + showUnknownDeviceDialogForCalls( + MatrixClientPeg.get(), + room, + () => { + _reAttemptCall(call); + }, + call.direction === 'outbound' ? _t("Call Anyway") : _t("Answer Anyway"), + ); } }, }); + } else { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + + Modal.createTrackedDialog('Call Failed', '', ErrorDialog, { + title: _t('Call Failed'), + description: err.message, + }); } }); call.on("hangup", function() { diff --git a/src/components/views/dialogs/UnknownDeviceDialog.js b/src/components/views/dialogs/UnknownDeviceDialog.js index 2e89459164..d3f26d7536 100644 --- a/src/components/views/dialogs/UnknownDeviceDialog.js +++ b/src/components/views/dialogs/UnknownDeviceDialog.js @@ -98,13 +98,19 @@ export default React.createClass({ // map from userid -> deviceid -> deviceinfo devices: PropTypes.object.isRequired, onFinished: PropTypes.func.isRequired, - sendAnywayButton: PropTypes.node, + sendAnywayLabel: PropTypes.string.isRequired, + onSendAnyway: PropTypes.func.isRequired, }, _onDismissClicked: function() { this.props.onFinished(); }, + _onSendAnywayClicked: function() { + this.props.onFinished(); + this.props.onSendAnyway(); + }, + render: function() { if (this.props.devices === null) { const Spinner = sdk.getComponent("elements.Spinner"); @@ -148,6 +154,9 @@ export default React.createClass({
{this.props.sendAnywayButton} + - ), + sendAnywayLabel: _t("Send anyway"), + onSendAnyway: onSendAnywayClicked, + }, 'mx_Dialog_unknownDevice'); + }); +} + +export function showUnknownDeviceDialogForCalls(matrixClient, room, sendAnyway, sendAnywayLabel) { + getUnknownDevicesForRoom(matrixClient, room).then((unknownDevices) => { + const onSendAnywayClicked = () => { + markAllDevicesKnown(matrixClient, unknownDevices); + sendAnyway(); + }; + + const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog'); + Modal.createTrackedDialog('Unknown Device Dialog', '', UnknownDeviceDialog, { + room: room, + devices: unknownDevices, + sendAnywayLabel: sendAnywayLabel, + onSendAnyway: onSendAnywayClicked, }, 'mx_Dialog_unknownDevice'); }); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 46646aca83..05143e96b1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3,8 +3,10 @@ "This phone number is already in use": "This phone number is already in use", "Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email", "Call Failed": "Call Failed", - "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call": "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call", + "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.", "Review Devices": "Review Devices", + "Call Anyway": "Call Anyway", + "Answer Anyway": "Answer Anyway", "Call Timeout": "Call Timeout", "The remote side failed to pick up": "The remote side failed to pick up", "Unable to capture screen": "Unable to capture screen", @@ -153,24 +155,44 @@ "%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s", "%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s", "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s", - "Message Pinning": "Message Pinning", - "Presence Management": "Presence Management", "%(displayName)s is typing": "%(displayName)s is typing", "%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing", "%(names)s and %(count)s others are typing|one": "%(names)s and one other is typing", "%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing", "Failure to create room": "Failure to create room", "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.", + "Send anyway": "Send anyway", "Unnamed Room": "Unnamed Room", "Your browser does not support the required cryptography extensions": "Your browser does not support the required cryptography extensions", "Not a valid Riot keyfile": "Not a valid Riot keyfile", "Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?", "Failed to join room": "Failed to join room", + "Message Pinning": "Message Pinning", + "Presence Management": "Presence Management", + "Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing", + "Use compact timeline layout": "Use compact timeline layout", + "Hide removed messages": "Hide removed messages", + "Hide join/leave messages (invites/kicks/bans unaffected)": "Hide join/leave messages (invites/kicks/bans unaffected)", "Hide avatar changes": "Hide avatar changes", "Hide display name changes": "Hide display name changes", + "Hide read receipts": "Hide read receipts", + "Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)", + "Always show message timestamps": "Always show message timestamps", + "Autoplay GIFs and videos": "Autoplay GIFs and videos", + "Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting", + "Hide avatars in user and room mentions": "Hide avatars in user and room mentions", + "Disable big emoji in chat": "Disable big emoji in chat", + "Don't send typing notifications": "Don't send typing notifications", + "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", + "Mirror local video feed": "Mirror local video feed", + "Disable Peer-to-Peer for 1:1 calls": "Disable Peer-to-Peer for 1:1 calls", + "Opt out of analytics": "Opt out of analytics", + "Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device", + "Never send encrypted messages to unverified devices in this room from this device": "Never send encrypted messages to unverified devices in this room from this device", "Enable inline URL previews by default": "Enable inline URL previews by default", "Enable URL previews for this room (only affects you)": "Enable URL previews for this room (only affects you)", "Enable URL previews by default for participants in this room": "Enable URL previews by default for participants in this room", + "Room Colour": "Room Colour", "Active call (%(roomName)s)": "Active call (%(roomName)s)", "unknown caller": "unknown caller", "Incoming voice call from %(name)s": "Incoming voice call from %(name)s", @@ -211,9 +233,6 @@ "Delete": "Delete", "Disable Notifications": "Disable Notifications", "Enable Notifications": "Enable Notifications", - "You have enabled URL previews by default.": "You have enabled URL previews by default.", - "You have disabled URL previews by default.": "You have disabled URL previews by default.", - "URL Previews": "URL Previews", "Cannot add any more widgets": "Cannot add any more widgets", "The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.", "Add a widget": "Add a widget", @@ -381,7 +400,6 @@ "Devices will not yet be able to decrypt history from before they joined the room": "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)": "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": "Encrypted messages will not be visible on clients that do not yet implement encryption", - "Never send encrypted messages to unverified devices in this room from this device": "Never send encrypted messages to unverified devices in this room from this device", "Enable encryption": "Enable encryption", "(warning: cannot be disabled again!)": "(warning: cannot be disabled again!)", "Encryption is enabled in this room": "Encryption is enabled in this room", @@ -407,7 +425,6 @@ "Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)", "Members only (since they were invited)": "Members only (since they were invited)", "Members only (since they joined)": "Members only (since they joined)", - "Room Colour": "Room Colour", "Permissions": "Permissions", "The default role for new room members is": "The default role for new room members is", "To send messages, you must be a": "To send messages, you must be a", @@ -441,6 +458,9 @@ "Related communities for this room:": "Related communities for this room:", "This room has no related communities": "This room has no related communities", "New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)", + "You have enabled URL previews by default.": "You have enabled URL previews by default.", + "You have disabled URL previews by default.": "You have disabled URL previews by default.", + "URL Previews": "URL Previews", "Error decrypting audio": "Error decrypting audio", "Error decrypting attachment": "Error decrypting attachment", "Decrypt %(text)s": "Decrypt %(text)s", @@ -477,6 +497,7 @@ "Please enter the code it contains:": "Please enter the code it contains:", "Start authentication": "Start authentication", "powered by Matrix": "powered by Matrix", + "Username on %(hs)s": "Username on %(hs)s", "User name": "User name", "Mobile phone number": "Mobile phone number", "Forgot your password?": "Forgot your password?", @@ -484,7 +505,6 @@ "Sign in with": "Sign in with", "Email address": "Email address", "Sign in": "Sign in", - "Sign in to get started": "Sign in to get started", "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", "Email address (optional)": "Email address (optional)", "You are registering with %(SelectedTeamName)s": "You are registering with %(SelectedTeamName)s", @@ -664,7 +684,6 @@ "Room contains unknown devices": "Room contains unknown devices", "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.", "Unknown devices": "Unknown devices", - "Send anyway": "Send anyway", "Private Chat": "Private Chat", "Public Chat": "Public Chat", "Custom": "Custom", @@ -765,25 +784,9 @@ "Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others", "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", - "Autoplay GIFs and videos": "Autoplay GIFs and videos", - "Hide read receipts": "Hide read receipts", - "Don't send typing notifications": "Don't send typing notifications", - "Always show message timestamps": "Always show message timestamps", - "Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)", - "Hide join/leave messages (invites/kicks/bans unaffected)": "Hide join/leave messages (invites/kicks/bans unaffected)", - "Use compact timeline layout": "Use compact timeline layout", - "Hide removed messages": "Hide removed messages", - "Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting", - "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", - "Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing", - "Hide avatars in user and room mentions": "Hide avatars in user and room mentions", - "Disable big emoji in chat": "Disable big emoji in chat", - "Mirror local video feed": "Mirror local video feed", - "Opt out of analytics": "Opt out of analytics", - "Disable Peer-to-Peer for 1:1 calls": "Disable Peer-to-Peer for 1:1 calls", - "Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device", "Light theme": "Light theme", "Dark theme": "Dark theme", + "Status.im theme": "Status.im theme", "Can't load user settings": "Can't load user settings", "Server may be unavailable or overloaded": "Server may be unavailable or overloaded", "Sign out": "Sign out", @@ -798,7 +801,6 @@ "Interface Language": "Interface Language", "User Interface": "User Interface", "Autocomplete Delay (ms):": "Autocomplete Delay (ms):", - "Disable inline URL previews by default": "Disable inline URL previews by default", "": "", "Import E2E room keys": "Import E2E room keys", "Cryptography": "Cryptography", @@ -863,6 +865,7 @@ "Create an account": "Create an account", "This Home Server does not support login using email address.": "This Home Server does not support login using email address.", "Incorrect username and/or password.": "Incorrect username and/or password.", + "Please note you are logging into the %(hs)s server, not matrix.org.": "Please note you are logging into the %(hs)s server, not matrix.org.", "Guest access is disabled on this Home Server.": "Guest access is disabled on this Home Server.", "The phone number entered looks invalid": "The phone number entered looks invalid", "Error: Problem communicating with the given homeserver.": "Error: Problem communicating with the given homeserver.", @@ -870,7 +873,7 @@ "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.", "Sorry, this homeserver is using a login which is not recognised ": "Sorry, this homeserver is using a login which is not recognised ", "Login as guest": "Login as guest", - "Return to app": "Return to app", + "Sign in to get started": "Sign in to get started", "Failed to fetch avatar URL": "Failed to fetch avatar URL", "Set a display name:": "Set a display name:", "Upload an avatar:": "Upload an avatar:", @@ -932,8 +935,5 @@ "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.", "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.", "File to import": "File to import", - "Import": "Import", - "Status.im theme": "Status.im theme", - "Please note you are logging into the %(hs)s server, not matrix.org.": "Please note you are logging into the %(hs)s server, not matrix.org.", - "Username on %(hs)s": "Username on %(hs)s" + "Import": "Import" } From af8ff1b88866aa392b94be5e2b3a0f59b76c8a24 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 16 Nov 2017 11:07:57 +0000 Subject: [PATCH 11/41] Don't blindly hangup on a call error. Not all errors means we want to send a hangup (in fact most don't, but the most notable being when we fail to answer a call: we should not then automatically reject it). --- src/CallHandler.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index eba1c9995e..918f38976a 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -117,7 +117,6 @@ function _setCallListeners(call) { call.on("error", function(err) { console.error("Call error: %s", err); console.error(err.stack); - call.hangup(); _setCallState(undefined, call.roomId, "ended"); if (err.code === 'unknown_devices') { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); From 8ba9d26d4b72b2aaca752718e5e03450887d04a8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 16 Nov 2017 15:42:46 +0000 Subject: [PATCH 12/41] Don't set the call state to ended on error This isn't always the case, eg. just because we fail to pick up, the call is still ringing. --- src/CallHandler.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 918f38976a..5131473ec2 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -117,7 +117,6 @@ function _setCallListeners(call) { call.on("error", function(err) { console.error("Call error: %s", err); console.error(err.stack); - _setCallState(undefined, call.roomId, "ended"); if (err.code === 'unknown_devices') { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); From eea8a41ef9d533e37d5ac6b4fa3e597a5e5ceab9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 16 Nov 2017 16:20:13 +0000 Subject: [PATCH 13/41] Unused import --- src/CallHandler.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 5131473ec2..fa95b4400d 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -59,7 +59,6 @@ import sdk from './index'; import { _t } from './languageHandler'; import Matrix from 'matrix-js-sdk'; import dis from './dispatcher'; -import { getUnknownDevicesForRoom } from './cryptodevices'; import SettingsStore from "./settings/SettingsStore"; import { showUnknownDeviceDialogForCalls } from './cryptodevices'; From 65e1d49f374755f7f2d56d2e5d3defa29b080d9c Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 16 Nov 2017 17:59:42 +0000 Subject: [PATCH 14/41] More sensible buttons in UnknownDeviceDialog Just say 'Send' (or equiv) if you actually verify all the devices, rather than 'Send Anyway'. --- src/CallHandler.js | 1 + .../views/dialogs/UnknownDeviceDialog.js | 68 +++++++++++++++++-- src/cryptodevices.js | 24 ++----- src/i18n/strings/en_EN.json | 3 + 4 files changed, 72 insertions(+), 24 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index fa95b4400d..3f1f48e9a9 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -137,6 +137,7 @@ function _setCallListeners(call) { _reAttemptCall(call); }, call.direction === 'outbound' ? _t("Call Anyway") : _t("Answer Anyway"), + call.direction === 'outbound' ? _t("Call") : _t("Answer"), ); } }, diff --git a/src/components/views/dialogs/UnknownDeviceDialog.js b/src/components/views/dialogs/UnknownDeviceDialog.js index d3f26d7536..8530ebac2e 100644 --- a/src/components/views/dialogs/UnknownDeviceDialog.js +++ b/src/components/views/dialogs/UnknownDeviceDialog.js @@ -24,6 +24,14 @@ import Resend from '../../../Resend'; import { _t } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; +function markAllDevicesKnown(devices) { + Object.keys(devices).forEach((userId) => { + Object.keys(devices[userId]).map((deviceId) => { + MatrixClientPeg.get().setDeviceKnown(userId, deviceId, true); + }); + }); +} + function DeviceListEntry(props) { const {userId, device} = props; @@ -97,9 +105,33 @@ export default React.createClass({ // map from userid -> deviceid -> deviceinfo devices: PropTypes.object.isRequired, + onFinished: PropTypes.func.isRequired, + + // Label for the button that marks all devices known and tries the send again sendAnywayLabel: PropTypes.string.isRequired, - onSendAnyway: PropTypes.func.isRequired, + + // Label for the button that to send the event if you've verified all devices + sendLabel: PropTypes.string.isRequired, + + // function to retry the request once all devices are verified / known + onSend: PropTypes.func.isRequired, + }, + + componentWillMount: function() { + MatrixClientPeg.get().on("deviceVerificationChanged", this._onDeviceVerificationChanged); + }, + + componentWillUnmount: function() { + MatrixClientPeg.get().removeListener("deviceVerificationChanged", this._onDeviceVerificationChanged); + }, + + _onDeviceVerificationChanged: function(userId, deviceId, deviceInfo) { + if (this.props.devices[userId] && this.props.devices[userId][deviceId]) { + // XXX: Mutating props :/ + this.props.devices[userId][deviceId] = deviceInfo; + this.forceUpdate(); + } }, _onDismissClicked: function() { @@ -107,8 +139,15 @@ export default React.createClass({ }, _onSendAnywayClicked: function() { + markAllDevicesKnown(this.props.devices); + this.props.onFinished(); - this.props.onSendAnyway(); + this.props.onSend(); + }, + + _onSendClicked: function() { + this.props.onFinished(); + this.props.onSend(); }, render: function() { @@ -137,6 +176,26 @@ export default React.createClass({ ); } + let haveUnknownDevices = false; + Object.keys(this.props.devices).forEach((userId) => { + Object.keys(this.props.devices[userId]).map((deviceId) => { + const device = this.props.devices[userId][deviceId]; + if (device.isUnverified() && !device.isKnown()) { + haveUnknownDevices = true; + } + }); + }); + let sendButton; + if (haveUnknownDevices) { + sendButton = ; + } else { + sendButton = ; + } + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return (
- {this.props.sendAnywayButton} - + {sendButton}