diff --git a/src/CallHandler.js b/src/CallHandler.js
index dd9d93709f..fd56d7f1b1 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.
@@ -58,6 +59,7 @@ import sdk from './index';
import { _t } from './languageHandler';
import Matrix from 'matrix-js-sdk';
import dis from './dispatcher';
+import { showUnknownDeviceDialogForCalls } from './cryptodevices';
global.mxCalls = {
//room_id: MatrixCall
@@ -97,19 +99,54 @@ 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);
console.error(err.stack);
- 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),
+ 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);
+ showUnknownDeviceDialogForCalls(
+ MatrixClientPeg.get(),
+ room,
+ () => {
+ _reAttemptCall(call);
+ },
+ call.direction === 'outbound' ? _t("Call Anyway") : _t("Answer Anyway"),
+ call.direction === 'outbound' ? _t("Call") : _t("Answer"),
+ );
+ }
+ },
+ });
+ } else {
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+
+ Modal.createTrackedDialog('Call Failed', '', ErrorDialog, {
+ title: _t('Call Failed'),
+ description: err.message,
});
}
});
@@ -179,7 +216,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/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/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 cd75ad8798..e2871464c4 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -40,7 +40,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';
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
@@ -295,7 +294,6 @@ module.exports = React.createClass({
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
- UDEHandler.startListening();
this.focusComposer = false;
@@ -361,7 +359,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);
},
@@ -1398,13 +1395,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/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js
index 03859f522e..77d506d9af 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.
@@ -15,16 +16,26 @@ limitations under the License.
*/
import React from 'react';
+import Matrix from 'matrix-js-sdk';
import { _t } from '../../languageHandler';
import sdk from '../../index';
import WhoIsTyping from '../../WhoIsTyping';
import MatrixClientPeg from '../../MatrixClientPeg';
import MemberAvatar from '../views/avatars/MemberAvatar';
+import Resend from '../../Resend';
+import { showUnknownDeviceDialogForMessages } from '../../cryptodevices';
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',
@@ -35,9 +46,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,
@@ -98,12 +106,14 @@ module.exports = React.createClass({
return {
syncState: MatrixClientPeg.get().getSyncState(),
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
+ unsentMessages: getUnsentMessages(this.props.room),
};
},
componentWillMount: function() {
MatrixClientPeg.get().on("sync", this.onSyncStateChange);
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
+ MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
this._checkSize();
},
@@ -118,6 +128,7 @@ module.exports = React.createClass({
if (client) {
client.removeListener("sync", this.onSyncStateChange);
client.removeListener("RoomMember.typing", this.onRoomMemberTyping);
+ client.removeListener("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
}
},
@@ -136,6 +147,26 @@ module.exports = React.createClass({
});
},
+ _onResendAllClick: function() {
+ Resend.resendUnsentEvents(this.props.room);
+ },
+
+ _onCancelAllClick: function() {
+ Resend.cancelUnsentEvents(this.props.room);
+ },
+
+ _onShowDevicesClick: function() {
+ showUnknownDeviceDialogForMessages(MatrixClientPeg.get(), this.props.room);
+ },
+
+ _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()) {
@@ -155,7 +186,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;
@@ -241,6 +272,61 @@ module.exports = React.createClass({
return avatars;
},
+ _getUnsentMessageContent: function() {
+ 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 = _t(
+ "Show devices or cancel all.",
+ {},
+ {
+ 'showDevicesText': (sub) => { sub },
+ 'cancelText': (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 = _t("Resend all or cancel all now. " +
+ "You can also select individual messages to resend or cancel.",
+ {},
+ {
+ 'resendText': (sub) =>
+ { sub },
+ 'cancelText': (sub) =>
+ { sub },
+ },
+ );
+ }
+
+ return
+
+
+ { title }
+
+
+ { content }
+
+
;
+ },
+
// return suitable content for the main (text) part of the status bar.
_getContent: function() {
const EmojiText = sdk.getComponent('elements.EmojiText');
@@ -263,28 +349,8 @@ module.exports = React.createClass({
);
}
- if (this.props.unsentMessageError) {
- return (
-
-
-
- { this.props.unsentMessageError }
-
-
- {
- _t("Resend all or cancel all now. " +
- "You can also select individual messages to resend or cancel.",
- {},
- {
- 'resendText': (sub) =>
- { sub },
- 'cancelText': (sub) =>
- { sub },
- },
- ) }
-
-
- );
+ if (this.state.unsentMessages.length > 0) {
+ return this._getUnsentMessageContent();
}
// unread count trumps who is typing since the unread count is only
@@ -342,7 +408,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/structures/RoomView.js b/src/components/structures/RoomView.js
index 1fda05fb76..138c110c4f 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 MatrixClientPeg = require("../../MatrixClientPeg");
@@ -34,7 +33,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,
@@ -202,7 +199,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);
}
@@ -462,11 +458,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':
@@ -711,35 +702,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) {
@@ -784,14 +746,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({
@@ -935,11 +889,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");
@@ -1571,12 +1521,9 @@ module.exports = React.createClass({
statusBar = {
+ Object.keys(devices[userId]).map((deviceId) => {
+ MatrixClientPeg.get().setDeviceKnown(userId, deviceId, true);
+ });
+ });
+}
+
function DeviceListEntry(props) {
const {userId, device} = props;
@@ -38,10 +48,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,
};
@@ -61,10 +71,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,
};
@@ -83,7 +93,7 @@ function UnknownDeviceList(props) {
UnknownDeviceList.propTypes = {
// map from userid -> deviceid -> deviceinfo
- devices: React.PropTypes.object.isRequired,
+ devices: PropTypes.object.isRequired,
};
@@ -91,28 +101,63 @@ 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,
+ // map from userid -> deviceid -> deviceinfo or null if devices are not yet loaded
+ devices: PropTypes.object,
+
+ onFinished: PropTypes.func.isRequired,
+
+ // Label for the button that marks all devices known and tries the send again
+ sendAnywayLabel: PropTypes.string.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,
},
- componentDidMount: function() {
- // Given we've now shown the user the unknown device, it is no longer
- // unknown to them. Therefore mark it as 'known'.
- Object.keys(this.props.devices).forEach((userId) => {
- Object.keys(this.props.devices[userId]).map((deviceId) => {
- MatrixClientPeg.get().setDeviceKnown(userId, deviceId, true);
- });
- });
+ componentWillMount: function() {
+ MatrixClientPeg.get().on("deviceVerificationChanged", this._onDeviceVerificationChanged);
+ },
- // XXX: temporary logging to try to diagnose
- // https://github.com/vector-im/riot-web/issues/3148
- console.log('Opening UnknownDeviceDialog');
+ componentWillUnmount: function() {
+ if (MatrixClientPeg.get()) {
+ 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() {
+ this.props.onFinished();
+ },
+
+ _onSendAnywayClicked: function() {
+ markAllDevicesKnown(this.props.devices);
+
+ this.props.onFinished();
+ this.props.onSend();
+ },
+
+ _onSendClicked: function() {
+ this.props.onFinished();
+ this.props.onSend();
},
render: function() {
+ if (this.props.devices === null) {
+ const Spinner = sdk.getComponent("elements.Spinner");
+ return ;
+ }
+
let warning;
if (SettingsStore.getValue("blacklistUnverifiedDevices", this.props.room.roomId)) {
warning = (
@@ -133,15 +178,30 @@ 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 (
{
- // 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')}
>
@@ -154,21 +214,11 @@ export default React.createClass({
+ {sendButton}
-
diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js
index cd30f20645..1ffbfafbc5 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',
});
diff --git a/src/cryptodevices.js b/src/cryptodevices.js
new file mode 100644
index 0000000000..c93e04253f
--- /dev/null
+++ b/src/cryptodevices.js
@@ -0,0 +1,104 @@
+/*
+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.
+*/
+
+import Resend from './Resend';
+import sdk from './index';
+import Modal from './Modal';
+import { _t } from './languageHandler';
+
+/**
+ * Gets all crypto devices in a room that are marked neither known
+ * nor verified.
+ *
+ * @param {MatrixClient} matrixClient A MatrixClient
+ * @param {Room} room js-sdk room object representing the room
+ * @return {Promise} A promise which resolves to a map userId->deviceId->{@link
+ * module:crypto~DeviceInfo|DeviceInfo}.
+ */
+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;
+ });
+}
+
+/**
+ * Show the UnknownDeviceDialog for a given room. The dialog will inform the user
+ * that messages they sent to this room have not been sent due to unknown devices
+ * being present.
+ *
+ * @param {MatrixClient} matrixClient A MatrixClient
+ * @param {Room} room js-sdk room object representing the room
+ */
+export function showUnknownDeviceDialogForMessages(matrixClient, room) {
+ getUnknownDevicesForRoom(matrixClient, room).then((unknownDevices) => {
+ const onSendClicked = () => {
+ Resend.resendUnsentEvents(room);
+ };
+
+ const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog');
+ Modal.createTrackedDialog('Unknown Device Dialog', '', UnknownDeviceDialog, {
+ room: room,
+ devices: unknownDevices,
+ sendAnywayLabel: _t("Send anyway"),
+ sendLabel: _t("Send"),
+ onSend: onSendClicked,
+ }, 'mx_Dialog_unknownDevice');
+ });
+}
+
+/**
+ * Show the UnknownDeviceDialog for a given room. The dialog will inform the user
+ * that a call they tried to place or answer in the room couldn't be placed or
+ * answered due to unknown devices being present.
+ *
+ * @param {MatrixClient} matrixClient A MatrixClient
+ * @param {Room} room js-sdk room object representing the room
+ * @param {func} sendAnyway Function called when the 'call anyway' or 'call'
+ * button is pressed. This should attempt to place or answer the call again.
+ * @param {string} sendAnywayLabel Label for the button displayed to retry the call
+ * when unknown devices are still present (eg. "Call Anyway")
+ * @param {string} sendLabel Label for the button displayed to retry the call
+ * after all devices have been verified (eg. "Call")
+ */
+export function showUnknownDeviceDialogForCalls(matrixClient, room, sendAnyway, sendAnywayLabel, sendLabel) {
+ getUnknownDevicesForRoom(matrixClient, room).then((unknownDevices) => {
+ const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog');
+ Modal.createTrackedDialog('Unknown Device Dialog', '', UnknownDeviceDialog, {
+ room: room,
+ devices: unknownDevices,
+ sendAnywayLabel: sendAnywayLabel,
+ sendLabel: sendLabel,
+ onSend: sendAnyway,
+ }, 'mx_Dialog_unknownDevice');
+ });
+}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index dfa11f44c9..697704154f 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2,6 +2,13 @@
"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 Anyway": "Call Anyway",
+ "Answer Anyway": "Answer Anyway",
+ "Call": "Call",
+ "Answer": "Answer",
"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",
@@ -156,6 +163,8 @@
"%(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",
+ "Send": "Send",
"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",
@@ -695,7 +704,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",
@@ -760,17 +768,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",