From 820d9c1c25c2cfb1860bd5c407a490ab614af5e8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 9 Nov 2017 15:58:15 +0000 Subject: [PATCH] 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",