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