Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into t3chguy/cs_verification_decoration
Conflicts: src/components/views/right_panel/VerificationPanel.js src/components/views/toasts/VerificationRequestToast.js
This commit is contained in:
commit
342fcb09c4
36 changed files with 1043 additions and 495 deletions
|
@ -19,9 +19,10 @@ import React from 'react';
|
|||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Matrix from 'matrix-js-sdk';
|
||||
import {Filter} from 'matrix-js-sdk';
|
||||
import * as sdk from '../../index';
|
||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
||||
import { _t } from '../../languageHandler';
|
||||
|
||||
/*
|
||||
|
@ -29,6 +30,9 @@ import { _t } from '../../languageHandler';
|
|||
*/
|
||||
const FilePanel = createReactClass({
|
||||
displayName: 'FilePanel',
|
||||
// This is used to track if a decrypted event was a live event and should be
|
||||
// added to the timeline.
|
||||
decryptingEvents: new Set(),
|
||||
|
||||
propTypes: {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
|
@ -40,42 +44,147 @@ const FilePanel = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.updateTimelineSet(this.props.roomId);
|
||||
onRoomTimeline(ev, room, toStartOfTimeline, removed, data) {
|
||||
if (room.roomId !== this.props.roomId) return;
|
||||
if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return;
|
||||
|
||||
if (ev.isBeingDecrypted()) {
|
||||
this.decryptingEvents.add(ev.getId());
|
||||
} else {
|
||||
this.addEncryptedLiveEvent(ev);
|
||||
}
|
||||
},
|
||||
|
||||
updateTimelineSet: function(roomId) {
|
||||
onEventDecrypted(ev, err) {
|
||||
if (ev.getRoomId() !== this.props.roomId) return;
|
||||
const eventId = ev.getId();
|
||||
|
||||
if (!this.decryptingEvents.delete(eventId)) return;
|
||||
if (err) return;
|
||||
|
||||
this.addEncryptedLiveEvent(ev);
|
||||
},
|
||||
|
||||
addEncryptedLiveEvent(ev, toStartOfTimeline) {
|
||||
if (!this.state.timelineSet) return;
|
||||
|
||||
const timeline = this.state.timelineSet.getLiveTimeline();
|
||||
if (ev.getType() !== "m.room.message") return;
|
||||
if (["m.file", "m.image", "m.video", "m.audio"].indexOf(ev.getContent().msgtype) == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.state.timelineSet.eventIdToTimeline(ev.getId())) {
|
||||
this.state.timelineSet.addEventToTimeline(ev, timeline, false);
|
||||
}
|
||||
},
|
||||
|
||||
async componentDidMount() {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
await this.updateTimelineSet(this.props.roomId);
|
||||
|
||||
if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return;
|
||||
|
||||
// The timelineSets filter makes sure that encrypted events that contain
|
||||
// URLs never get added to the timeline, even if they are live events.
|
||||
// These methods are here to manually listen for such events and add
|
||||
// them despite the filter's best efforts.
|
||||
//
|
||||
// We do this only for encrypted rooms and if an event index exists,
|
||||
// this could be made more general in the future or the filter logic
|
||||
// could be fixed.
|
||||
if (EventIndexPeg.get() !== null) {
|
||||
client.on('Room.timeline', this.onRoomTimeline.bind(this));
|
||||
client.on('Event.decrypted', this.onEventDecrypted.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (client === null) return;
|
||||
|
||||
if (!MatrixClientPeg.get().isRoomEncrypted(this.props.roomId)) return;
|
||||
|
||||
if (EventIndexPeg.get() !== null) {
|
||||
client.removeListener('Room.timeline', this.onRoomTimeline.bind(this));
|
||||
client.removeListener('Event.decrypted', this.onEventDecrypted.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
async fetchFileEventsServer(room) {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
const filter = new Filter(client.credentials.userId);
|
||||
filter.setDefinition(
|
||||
{
|
||||
"room": {
|
||||
"timeline": {
|
||||
"contains_url": true,
|
||||
"types": [
|
||||
"m.room.message",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const filterId = await client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter);
|
||||
filter.filterId = filterId;
|
||||
const timelineSet = room.getOrCreateFilteredTimelineSet(filter);
|
||||
|
||||
return timelineSet;
|
||||
},
|
||||
|
||||
onPaginationRequest(timelineWindow, direction, limit) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
const roomId = this.props.roomId;
|
||||
|
||||
const room = client.getRoom(roomId);
|
||||
|
||||
// We override the pagination request for encrypted rooms so that we ask
|
||||
// the event index to fulfill the pagination request. Asking the server
|
||||
// to paginate won't ever work since the server can't correctly filter
|
||||
// out events containing URLs
|
||||
if (client.isRoomEncrypted(roomId) && eventIndex !== null) {
|
||||
return eventIndex.paginateTimelineWindow(room, timelineWindow, direction, limit);
|
||||
} else {
|
||||
return timelineWindow.paginate(direction, limit);
|
||||
}
|
||||
},
|
||||
|
||||
async updateTimelineSet(roomId: string) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const room = client.getRoom(roomId);
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
|
||||
this.noRoom = !room;
|
||||
|
||||
if (room) {
|
||||
const filter = new Matrix.Filter(client.credentials.userId);
|
||||
filter.setDefinition(
|
||||
{
|
||||
"room": {
|
||||
"timeline": {
|
||||
"contains_url": true,
|
||||
"types": [
|
||||
"m.room.message",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
let timelineSet;
|
||||
|
||||
// FIXME: we shouldn't be doing this every time we change room - see comment above.
|
||||
client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter).then(
|
||||
(filterId)=>{
|
||||
filter.filterId = filterId;
|
||||
const timelineSet = room.getOrCreateFilteredTimelineSet(filter);
|
||||
this.setState({ timelineSet: timelineSet });
|
||||
},
|
||||
(error)=>{
|
||||
console.error("Failed to get or create file panel filter", error);
|
||||
},
|
||||
);
|
||||
try {
|
||||
timelineSet = await this.fetchFileEventsServer(room);
|
||||
|
||||
// If this room is encrypted the file panel won't be populated
|
||||
// correctly since the defined filter doesn't support encrypted
|
||||
// events and the server can't check if encrypted events contain
|
||||
// URLs.
|
||||
//
|
||||
// This is where our event index comes into place, we ask the
|
||||
// event index to populate the timelineSet for us. This call
|
||||
// will add 10 events to the live timeline of the set. More can
|
||||
// be requested using pagination.
|
||||
if (client.isRoomEncrypted(roomId) && eventIndex !== null) {
|
||||
const timeline = timelineSet.getLiveTimeline();
|
||||
await eventIndex.populateFileTimeline(timelineSet, timeline, room, 10);
|
||||
}
|
||||
|
||||
this.setState({ timelineSet: timelineSet });
|
||||
} catch (error) {
|
||||
console.error("Failed to get or create file panel filter", error);
|
||||
}
|
||||
} else {
|
||||
console.error("Failed to add filtered timelineSet for FilePanel as no room!");
|
||||
}
|
||||
|
@ -111,6 +220,7 @@ const FilePanel = createReactClass({
|
|||
manageReadMarkers={false}
|
||||
timelineSet={this.state.timelineSet}
|
||||
showUrlPreview = {false}
|
||||
onPaginationRequest={this.onPaginationRequest}
|
||||
tileShape="file_grid"
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
empty={_t('There are no visible files in this room')}
|
||||
|
|
|
@ -796,6 +796,7 @@ export default createReactClass({
|
|||
return;
|
||||
}
|
||||
|
||||
// Duplication between here and _updateE2eStatus in RoomTile
|
||||
/* At this point, the user has encryption on and cross-signing on */
|
||||
const e2eMembers = await room.getEncryptionTargetMembers();
|
||||
const verified = [];
|
||||
|
@ -812,10 +813,10 @@ export default createReactClass({
|
|||
/* Check all verified user devices. */
|
||||
for (const userId of verified) {
|
||||
const devices = await cli.getStoredDevicesForUser(userId);
|
||||
const allDevicesVerified = devices.every(({deviceId}) => {
|
||||
return cli.checkDeviceTrust(userId, deviceId).isVerified();
|
||||
const anyDeviceNotVerified = devices.some(({deviceId}) => {
|
||||
return !cli.checkDeviceTrust(userId, deviceId).isVerified();
|
||||
});
|
||||
if (!allDevicesVerified) {
|
||||
if (anyDeviceNotVerified) {
|
||||
this.setState({
|
||||
e2eStatus: "warning",
|
||||
});
|
||||
|
|
|
@ -94,6 +94,10 @@ const TimelinePanel = createReactClass({
|
|||
// callback which is called when the read-up-to mark is updated.
|
||||
onReadMarkerUpdated: PropTypes.func,
|
||||
|
||||
// callback which is called when we wish to paginate the timeline
|
||||
// window.
|
||||
onPaginationRequest: PropTypes.func,
|
||||
|
||||
// maximum number of events to show in a timeline
|
||||
timelineCap: PropTypes.number,
|
||||
|
||||
|
@ -338,6 +342,14 @@ const TimelinePanel = createReactClass({
|
|||
}
|
||||
},
|
||||
|
||||
onPaginationRequest(timelineWindow, direction, size) {
|
||||
if (this.props.onPaginationRequest) {
|
||||
return this.props.onPaginationRequest(timelineWindow, direction, size);
|
||||
} else {
|
||||
return timelineWindow.paginate(direction, size);
|
||||
}
|
||||
},
|
||||
|
||||
// set off a pagination request.
|
||||
onMessageListFillRequest: function(backwards) {
|
||||
if (!this._shouldPaginate()) return Promise.resolve(false);
|
||||
|
@ -360,7 +372,7 @@ const TimelinePanel = createReactClass({
|
|||
debuglog("TimelinePanel: Initiating paginate; backwards:"+backwards);
|
||||
this.setState({[paginatingKey]: true});
|
||||
|
||||
return this._timelineWindow.paginate(dir, PAGINATE_SIZE).then((r) => {
|
||||
return this.onPaginationRequest(this._timelineWindow, dir, PAGINATE_SIZE).then((r) => {
|
||||
if (this.unmounted) { return; }
|
||||
|
||||
debuglog("TimelinePanel: paginate complete backwards:"+backwards+"; success:"+r);
|
||||
|
|
|
@ -338,19 +338,31 @@ export default class InviteDialog extends React.PureComponent {
|
|||
const recents = [];
|
||||
for (const userId in rooms) {
|
||||
// Filter out user IDs that are already in the room / should be excluded
|
||||
if (excludedTargetIds.includes(userId)) continue;
|
||||
if (excludedTargetIds.includes(userId)) {
|
||||
console.warn(`[Invite:Recents] Excluding ${userId} from recents`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const room = rooms[userId];
|
||||
const member = room.getMember(userId);
|
||||
if (!member) continue; // just skip people who don't have memberships for some reason
|
||||
if (!member) {
|
||||
// just skip people who don't have memberships for some reason
|
||||
console.warn(`[Invite:Recents] ${userId} is missing a member object in their own DM (${room.roomId})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const lastEventTs = room.timeline && room.timeline.length
|
||||
? room.timeline[room.timeline.length - 1].getTs()
|
||||
: 0;
|
||||
if (!lastEventTs) continue; // something weird is going on with this room
|
||||
if (!lastEventTs) {
|
||||
// something weird is going on with this room
|
||||
console.warn(`[Invite:Recents] ${userId} (${room.roomId}) has a weird last timestamp: ${lastEventTs}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
recents.push({userId, user: member, lastActive: lastEventTs});
|
||||
}
|
||||
if (!recents) console.warn("[Invite:Recents] No recents to suggest!");
|
||||
|
||||
// Sort the recents by last active to save us time later
|
||||
recents.sort((a, b) => b.lastActive - a.lastActive);
|
||||
|
@ -713,11 +725,16 @@ export default class InviteDialog extends React.PureComponent {
|
|||
};
|
||||
|
||||
_toggleMember = (member: Member) => {
|
||||
let filterText = this.state.filterText;
|
||||
const targets = this.state.targets.map(t => t); // cheap clone for mutation
|
||||
const idx = targets.indexOf(member);
|
||||
if (idx >= 0) targets.splice(idx, 1);
|
||||
else targets.push(member);
|
||||
this.setState({targets});
|
||||
if (idx >= 0) {
|
||||
targets.splice(idx, 1);
|
||||
} else {
|
||||
targets.push(member);
|
||||
filterText = ""; // clear the filter when the user accepts a suggestion
|
||||
}
|
||||
this.setState({targets, filterText});
|
||||
};
|
||||
|
||||
_removeMember = (member: Member) => {
|
||||
|
@ -917,7 +934,7 @@ export default class InviteDialog extends React.PureComponent {
|
|||
key={"input"}
|
||||
rows={1}
|
||||
onChange={this._updateFilter}
|
||||
defaultValue={this.state.filterText}
|
||||
value={this.state.filterText}
|
||||
ref={this._editorRef}
|
||||
onPaste={this._onPaste}
|
||||
/>
|
||||
|
@ -985,7 +1002,7 @@ export default class InviteDialog extends React.PureComponent {
|
|||
|
||||
title = _t("Direct Messages");
|
||||
helpText = _t(
|
||||
"If you can't find someone, ask them for their username, or share your " +
|
||||
"If you can't find someone, ask them for their username, share your " +
|
||||
"username (%(userId)s) or <a>profile link</a>.",
|
||||
{userId},
|
||||
{a: (sub) => <a href={makeUserPermalink(userId)} rel="noopener" target="_blank">{sub}</a>},
|
||||
|
|
56
src/components/views/elements/crypto/VerificationQRCode.js
Normal file
56
src/components/views/elements/crypto/VerificationQRCode.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {replaceableComponent} from "../../../../utils/replaceableComponent";
|
||||
import * as qs from "qs";
|
||||
import QRCode from "qrcode-react";
|
||||
|
||||
@replaceableComponent("views.elements.crypto.VerificationQRCode")
|
||||
export default class VerificationQRCode extends React.PureComponent {
|
||||
static propTypes = {
|
||||
// Common for all kinds of QR codes
|
||||
keys: PropTypes.array.isRequired, // array of [Key ID, Base64 Key] pairs
|
||||
action: PropTypes.string.isRequired,
|
||||
keyholderUserId: PropTypes.string.isRequired,
|
||||
|
||||
// User verification use case only
|
||||
secret: PropTypes.string,
|
||||
otherUserKey: PropTypes.string, // Base64 key being verified
|
||||
requestEventId: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
action: "verify",
|
||||
};
|
||||
|
||||
render() {
|
||||
const query = {
|
||||
request: this.props.requestEventId,
|
||||
action: this.props.action,
|
||||
other_user_key: this.props.otherUserKey,
|
||||
secret: this.props.secret,
|
||||
};
|
||||
for (const key of this.props.keys) {
|
||||
query[`key_${key[0]}`] = key[1];
|
||||
}
|
||||
|
||||
const uri = `https://matrix.to/#/${this.props.keyholderUserId}?${qs.stringify(query)}`;
|
||||
|
||||
return <QRCode value={uri} size={256} logoWidth={48} logo={require("../../../../../res/img/matrix-m.svg")} />;
|
||||
}
|
||||
}
|
|
@ -93,7 +93,7 @@ export default class MKeyVerificationConclusion extends React.Component {
|
|||
}
|
||||
|
||||
if (title) {
|
||||
const subtitle = userLabelForEventRoom(request.otherUserId, mxEvent);
|
||||
const subtitle = userLabelForEventRoom(request.otherUserId, mxEvent.getRoomId());
|
||||
const classes = classNames("mx_EventTile_bubble", "mx_KeyVerification", "mx_KeyVerification_icon", {
|
||||
mx_KeyVerification_icon_verified: request.done,
|
||||
});
|
||||
|
|
|
@ -86,7 +86,7 @@ export default class MKeyVerificationRequest extends React.Component {
|
|||
if (userId === myUserId) {
|
||||
return _t("You accepted");
|
||||
} else {
|
||||
return _t("%(name)s accepted", {name: getNameForEventRoom(userId, this.props.mxEvent)});
|
||||
return _t("%(name)s accepted", {name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId())});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ export default class MKeyVerificationRequest extends React.Component {
|
|||
if (userId === myUserId) {
|
||||
return _t("You cancelled");
|
||||
} else {
|
||||
return _t("%(name)s cancelled", {name: getNameForEventRoom(userId, this.props.mxEvent)});
|
||||
return _t("%(name)s cancelled", {name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId())});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,10 +129,11 @@ export default class MKeyVerificationRequest extends React.Component {
|
|||
}
|
||||
|
||||
if (!request.initiatedByMe) {
|
||||
const name = getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId());
|
||||
title = (<div className="mx_KeyVerification_title">{
|
||||
_t("%(name)s wants to verify", {name: getNameForEventRoom(request.requestingUserId, mxEvent)})}</div>);
|
||||
_t("%(name)s wants to verify", {name})}</div>);
|
||||
subtitle = (<div className="mx_KeyVerification_subtitle">{
|
||||
userLabelForEventRoom(request.requestingUserId, mxEvent)}</div>);
|
||||
userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId())}</div>);
|
||||
if (request.requested && !request.observeOnly) {
|
||||
stateNode = (<div className="mx_KeyVerification_buttons">
|
||||
<FormButton kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} />
|
||||
|
@ -143,7 +144,7 @@ export default class MKeyVerificationRequest extends React.Component {
|
|||
title = (<div className="mx_KeyVerification_title">{
|
||||
_t("You sent a verification request")}</div>);
|
||||
subtitle = (<div className="mx_KeyVerification_subtitle">{
|
||||
userLabelForEventRoom(request.receivingUserId, mxEvent)}</div>);
|
||||
userLabelForEventRoom(request.receivingUserId, mxEvent.getRoomId())}</div>);
|
||||
}
|
||||
|
||||
if (title) {
|
||||
|
|
|
@ -18,6 +18,9 @@ import React from 'react';
|
|||
|
||||
import * as sdk from '../../../index';
|
||||
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
|
||||
import VerificationQRCode from "../elements/crypto/VerificationQRCode";
|
||||
import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import {_t} from "../../../languageHandler";
|
||||
import E2EIcon from "../rooms/E2EIcon";
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -14,76 +15,102 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {useState} from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import classNames from 'classnames';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import SettingsStore from '../../../settings/SettingsStore';
|
||||
|
||||
export default function(props) {
|
||||
const { isUser } = props;
|
||||
const isNormal = props.status === "normal";
|
||||
const isWarning = props.status === "warning";
|
||||
const isVerified = props.status === "verified";
|
||||
const e2eIconClasses = classNames({
|
||||
import {_t, _td} from '../../../languageHandler';
|
||||
import {useFeatureEnabled} from "../../../hooks/useSettings";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import Tooltip from "../elements/Tooltip";
|
||||
|
||||
export const E2E_STATE = {
|
||||
VERIFIED: "verified",
|
||||
WARNING: "warning",
|
||||
UNKNOWN: "unknown",
|
||||
NORMAL: "normal",
|
||||
};
|
||||
|
||||
const crossSigningUserTitles = {
|
||||
[E2E_STATE.WARNING]: _td("This user has not verified all of their devices."),
|
||||
[E2E_STATE.NORMAL]: _td("You have not verified this user. This user has verified all of their devices."),
|
||||
[E2E_STATE.VERIFIED]: _td("You have verified this user. This user has verified all of their devices."),
|
||||
};
|
||||
const crossSigningRoomTitles = {
|
||||
[E2E_STATE.WARNING]: _td("Someone is using an unknown device"),
|
||||
[E2E_STATE.NORMAL]: _td("This room is end-to-end encrypted"),
|
||||
[E2E_STATE.VERIFIED]: _td("Everyone in this room is verified"),
|
||||
};
|
||||
|
||||
const legacyUserTitles = {
|
||||
[E2E_STATE.WARNING]: _td("Some devices for this user are not trusted"),
|
||||
[E2E_STATE.VERIFIED]: _td("All devices for this user are trusted"),
|
||||
};
|
||||
const legacyRoomTitles = {
|
||||
[E2E_STATE.WARNING]: _td("Some devices in this encrypted room are not trusted"),
|
||||
[E2E_STATE.VERIFIED]: _td("All devices in this encrypted room are trusted"),
|
||||
};
|
||||
|
||||
const E2EIcon = ({isUser, status, className, size, onClick}) => {
|
||||
const [hover, setHover] = useState(false);
|
||||
|
||||
const classes = classNames({
|
||||
mx_E2EIcon: true,
|
||||
mx_E2EIcon_warning: isWarning,
|
||||
mx_E2EIcon_normal: isNormal,
|
||||
mx_E2EIcon_verified: isVerified,
|
||||
}, props.className);
|
||||
mx_E2EIcon_warning: status === E2E_STATE.WARNING,
|
||||
mx_E2EIcon_normal: status === E2E_STATE.NORMAL,
|
||||
mx_E2EIcon_verified: status === E2E_STATE.VERIFIED,
|
||||
}, className);
|
||||
|
||||
let e2eTitle;
|
||||
|
||||
const crossSigning = SettingsStore.isFeatureEnabled("feature_cross_signing");
|
||||
const crossSigning = useFeatureEnabled("feature_cross_signing");
|
||||
if (crossSigning && isUser) {
|
||||
if (isWarning) {
|
||||
e2eTitle = _t(
|
||||
"This user has not verified all of their devices.",
|
||||
);
|
||||
} else if (isNormal) {
|
||||
e2eTitle = _t(
|
||||
"You have not verified this user. " +
|
||||
"This user has verified all of their devices.",
|
||||
);
|
||||
} else if (isVerified) {
|
||||
e2eTitle = _t(
|
||||
"You have verified this user. " +
|
||||
"This user has verified all of their devices.",
|
||||
);
|
||||
}
|
||||
e2eTitle = crossSigningUserTitles[status];
|
||||
} else if (crossSigning && !isUser) {
|
||||
if (isWarning) {
|
||||
e2eTitle = _t(
|
||||
"Some users in this encrypted room are not verified by you or " +
|
||||
"they have not verified their own devices.",
|
||||
);
|
||||
} else if (isVerified) {
|
||||
e2eTitle = _t(
|
||||
"All users in this encrypted room are verified by you and " +
|
||||
"they have verified their own devices.",
|
||||
);
|
||||
}
|
||||
e2eTitle = crossSigningRoomTitles[status];
|
||||
} else if (!crossSigning && isUser) {
|
||||
if (isWarning) {
|
||||
e2eTitle = _t("Some devices for this user are not trusted");
|
||||
} else if (isVerified) {
|
||||
e2eTitle = _t("All devices for this user are trusted");
|
||||
}
|
||||
e2eTitle = legacyUserTitles[status];
|
||||
} else if (!crossSigning && !isUser) {
|
||||
if (isWarning) {
|
||||
e2eTitle = _t("Some devices in this encrypted room are not trusted");
|
||||
} else if (isVerified) {
|
||||
e2eTitle = _t("All devices in this encrypted room are trusted");
|
||||
}
|
||||
e2eTitle = legacyRoomTitles[status];
|
||||
}
|
||||
|
||||
let style = null;
|
||||
if (props.size) {
|
||||
style = {width: `${props.size}px`, height: `${props.size}px`};
|
||||
let style;
|
||||
if (size) {
|
||||
style = {width: `${size}px`, height: `${size}px`};
|
||||
}
|
||||
|
||||
const icon = (<div className={e2eIconClasses} style={style} title={e2eTitle} />);
|
||||
if (props.onClick) {
|
||||
return (<AccessibleButton onClick={props.onClick}>{ icon }</AccessibleButton>);
|
||||
} else {
|
||||
return icon;
|
||||
const onMouseOver = () => setHover(true);
|
||||
const onMouseOut = () => setHover(false);
|
||||
|
||||
let tip;
|
||||
if (hover) {
|
||||
tip = <Tooltip label={e2eTitle ? _t(e2eTitle) : ""} />;
|
||||
}
|
||||
}
|
||||
|
||||
if (onClick) {
|
||||
return (
|
||||
<AccessibleButton
|
||||
onClick={onClick}
|
||||
onMouseOver={onMouseOver}
|
||||
onMouseOut={onMouseOut}
|
||||
className={classes}
|
||||
style={style}
|
||||
>
|
||||
{ tip }
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
return <div onMouseOver={onMouseOver} onMouseOut={onMouseOut} className={classes} style={style}>
|
||||
{ tip }
|
||||
</div>;
|
||||
};
|
||||
|
||||
E2EIcon.propTypes = {
|
||||
isUser: PropTypes.bool,
|
||||
status: PropTypes.oneOf(Object.values(E2E_STATE)),
|
||||
className: PropTypes.string,
|
||||
size: PropTypes.number,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default E2EIcon;
|
||||
|
|
|
@ -33,6 +33,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
|||
import {ALL_RULE_TYPES} from "../../../mjolnir/BanList";
|
||||
import * as ObjectUtils from "../../../ObjectUtils";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import {E2E_STATE} from "./E2EIcon";
|
||||
|
||||
const eventTileTypes = {
|
||||
'm.room.message': 'messages.MessageEvent',
|
||||
|
@ -66,13 +67,6 @@ const stateEventTileTypes = {
|
|||
'm.room.related_groups': 'messages.TextualEvent',
|
||||
};
|
||||
|
||||
const E2E_STATE = {
|
||||
VERIFIED: "verified",
|
||||
WARNING: "warning",
|
||||
UNKNOWN: "unknown",
|
||||
NORMAL: "normal",
|
||||
};
|
||||
|
||||
// Add all the Mjolnir stuff to the renderer
|
||||
for (const evType of ALL_RULE_TYPES) {
|
||||
stateEventTileTypes[evType] = 'messages.TextualEvent';
|
||||
|
@ -578,6 +572,7 @@ export default createReactClass({
|
|||
console.error("EventTile attempted to get relations for an event without an ID");
|
||||
// Use event's special `toJSON` method to log key data.
|
||||
console.log(JSON.stringify(this.props.mxEvent, null, 4));
|
||||
console.trace("Stacktrace for https://github.com/vector-im/riot-web/issues/11120");
|
||||
}
|
||||
return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction");
|
||||
},
|
||||
|
|
51
src/components/views/rooms/InviteOnlyIcon.js
Normal file
51
src/components/views/rooms/InviteOnlyIcon.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import * as sdk from '../../../index';
|
||||
|
||||
export default class InviteOnlyIcon extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
hover: false,
|
||||
};
|
||||
}
|
||||
|
||||
onHoverStart = () => {
|
||||
this.setState({hover: true});
|
||||
};
|
||||
|
||||
onHoverEnd = () => {
|
||||
this.setState({hover: false});
|
||||
};
|
||||
|
||||
render() {
|
||||
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||
let tooltip;
|
||||
if (this.state.hover) {
|
||||
tooltip = <Tooltip className="mx_InviteOnlyIcon_tooltip" label={_t("Invite only")} dir="auto" />;
|
||||
}
|
||||
return (<div className="mx_InviteOnlyIcon"
|
||||
onMouseEnter={this.onHoverStart}
|
||||
onMouseLeave={this.onHoverEnd}
|
||||
>
|
||||
{ tooltip }
|
||||
</div>);
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import Stickerpicker from './Stickerpicker';
|
|||
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
|
||||
import ContentMessages from '../../../ContentMessages';
|
||||
import E2EIcon from './E2EIcon';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
function ComposerAvatar(props) {
|
||||
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
|
||||
|
@ -168,7 +169,6 @@ export default class MessageComposer extends React.Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.onInputStateChanged = this.onInputStateChanged.bind(this);
|
||||
this.onEvent = this.onEvent.bind(this);
|
||||
this._onRoomStateEvents = this._onRoomStateEvents.bind(this);
|
||||
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
|
||||
this._onTombstoneClick = this._onTombstoneClick.bind(this);
|
||||
|
@ -182,11 +182,6 @@ export default class MessageComposer extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// N.B. using 'event' rather than 'RoomEvents' otherwise the crypto handler
|
||||
// for 'event' fires *after* 'RoomEvent', and our room won't have yet been
|
||||
// marked as encrypted.
|
||||
// XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
|
||||
MatrixClientPeg.get().on("event", this.onEvent);
|
||||
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
this._waitForOwnMember();
|
||||
|
@ -210,7 +205,6 @@ export default class MessageComposer extends React.Component {
|
|||
|
||||
componentWillUnmount() {
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("event", this.onEvent);
|
||||
MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents);
|
||||
}
|
||||
if (this._roomStoreToken) {
|
||||
|
@ -218,13 +212,6 @@ export default class MessageComposer extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
onEvent(event) {
|
||||
if (event.getType() !== 'm.room.encryption') return;
|
||||
if (event.getRoomId() !== this.props.room.roomId) return;
|
||||
// TODO: put (encryption state??) in state
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
_onRoomStateEvents(ev, state) {
|
||||
if (ev.getRoomId() !== this.props.room.roomId) return;
|
||||
|
||||
|
@ -282,18 +269,33 @@ export default class MessageComposer extends React.Component {
|
|||
}
|
||||
|
||||
renderPlaceholderText() {
|
||||
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
||||
if (this.state.isQuoting) {
|
||||
if (roomIsEncrypted) {
|
||||
return _t('Send an encrypted reply…');
|
||||
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
if (this.state.isQuoting) {
|
||||
if (this.props.e2eStatus) {
|
||||
return _t('Send an encrypted reply…');
|
||||
} else {
|
||||
return _t('Send a reply…');
|
||||
}
|
||||
} else {
|
||||
return _t('Send a reply (unencrypted)…');
|
||||
if (this.props.e2eStatus) {
|
||||
return _t('Send an encrypted message…');
|
||||
} else {
|
||||
return _t('Send a message…');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (roomIsEncrypted) {
|
||||
return _t('Send an encrypted message…');
|
||||
if (this.state.isQuoting) {
|
||||
if (this.props.e2eStatus) {
|
||||
return _t('Send an encrypted reply…');
|
||||
} else {
|
||||
return _t('Send a reply (unencrypted)…');
|
||||
}
|
||||
} else {
|
||||
return _t('Send a message (unencrypted)…');
|
||||
if (this.props.e2eStatus) {
|
||||
return _t('Send an encrypted message…');
|
||||
} else {
|
||||
return _t('Send a message (unencrypted)…');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import {CancelButton} from './SimpleRoomHeader';
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
|
||||
import E2EIcon from './E2EIcon';
|
||||
import InviteOnlyIcon from './InviteOnlyIcon';
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'RoomHeader',
|
||||
|
@ -162,11 +163,12 @@ export default createReactClass({
|
|||
|
||||
const joinRules = this.props.room && this.props.room.currentState.getStateEvents("m.room.join_rules", "");
|
||||
const joinRule = joinRules && joinRules.getContent().join_rule;
|
||||
const joinRuleClass = classNames("mx_RoomHeader_PrivateIcon",
|
||||
{"mx_RoomHeader_isPrivate": joinRule === "invite"});
|
||||
const privateIcon = SettingsStore.isFeatureEnabled("feature_cross_signing") ?
|
||||
<div className={joinRuleClass} /> :
|
||||
undefined;
|
||||
let privateIcon;
|
||||
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
if (joinRule == "invite") {
|
||||
privateIcon = <InviteOnlyIcon />;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.onCancelClick) {
|
||||
cancelButton = <CancelButton onClick={this.props.onCancelClick} />;
|
||||
|
|
|
@ -719,7 +719,7 @@ export default createReactClass({
|
|||
},
|
||||
{
|
||||
list: this.state.lists['im.vector.fake.direct'],
|
||||
label: _t('People'),
|
||||
label: _t('Direct Messages'),
|
||||
tagName: "im.vector.fake.direct",
|
||||
order: "recent",
|
||||
incomingCall: incomingCallIfTaggedAs('im.vector.fake.direct'),
|
||||
|
|
|
@ -33,6 +33,10 @@ import RoomViewStore from '../../../stores/RoomViewStore';
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {_t} from "../../../languageHandler";
|
||||
import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex";
|
||||
import E2EIcon from './E2EIcon';
|
||||
import InviteOnlyIcon from './InviteOnlyIcon';
|
||||
// eslint-disable-next-line camelcase
|
||||
import rate_limited_func from '../../../ratelimitedfunc';
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'RoomTile',
|
||||
|
@ -70,6 +74,7 @@ export default createReactClass({
|
|||
notificationCount: this.props.room.getUnreadNotificationCount(),
|
||||
selected: this.props.room.roomId === RoomViewStore.getRoomId(),
|
||||
statusMessage: this._getStatusMessage(),
|
||||
e2eStatus: null,
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -102,6 +107,83 @@ export default createReactClass({
|
|||
return statusUser._unstable_statusMessage;
|
||||
},
|
||||
|
||||
onRoomStateMember: function(ev, state, member) {
|
||||
// we only care about leaving users
|
||||
// because trust state will change if someone joins a megolm session anyway
|
||||
if (member.membership !== "leave") {
|
||||
return;
|
||||
}
|
||||
// ignore members in other rooms
|
||||
if (member.roomId !== this.props.room.roomId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateE2eStatus();
|
||||
},
|
||||
|
||||
onUserVerificationChanged: function(userId, _trustStatus) {
|
||||
if (!this.props.room.getMember(userId)) {
|
||||
// Not in this room
|
||||
return;
|
||||
}
|
||||
this._updateE2eStatus();
|
||||
},
|
||||
|
||||
onRoomTimeline: function(ev, room) {
|
||||
if (!room) return;
|
||||
if (room.roomId != this.props.room.roomId) return;
|
||||
if (ev.getType() !== "m.room.encryption") return;
|
||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||
this.onFindingRoomToBeEncrypted();
|
||||
},
|
||||
|
||||
onFindingRoomToBeEncrypted: function() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.on("RoomState.members", this.onRoomStateMember);
|
||||
cli.on("userTrustStatusChanged", this.onUserVerificationChanged);
|
||||
|
||||
this._updateE2eStatus();
|
||||
},
|
||||
|
||||
_updateE2eStatus: async function() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (!cli.isRoomEncrypted(this.props.room.roomId)) {
|
||||
return;
|
||||
}
|
||||
if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Duplication between here and _updateE2eStatus in RoomView
|
||||
const e2eMembers = await this.props.room.getEncryptionTargetMembers();
|
||||
const verified = [];
|
||||
const unverified = [];
|
||||
e2eMembers.map(({userId}) => userId)
|
||||
.filter((userId) => userId !== cli.getUserId())
|
||||
.forEach((userId) => {
|
||||
(cli.checkUserTrust(userId).isCrossSigningVerified() ?
|
||||
verified : unverified).push(userId);
|
||||
});
|
||||
|
||||
/* Check all verified user devices. */
|
||||
for (const userId of verified) {
|
||||
const devices = await cli.getStoredDevicesForUser(userId);
|
||||
const allDevicesVerified = devices.every(({deviceId}) => {
|
||||
return cli.checkDeviceTrust(userId, deviceId).isVerified();
|
||||
});
|
||||
if (!allDevicesVerified) {
|
||||
this.setState({
|
||||
e2eStatus: "warning",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
e2eStatus: unverified.length === 0 ? "verified" : "normal",
|
||||
});
|
||||
},
|
||||
|
||||
onRoomName: function(room) {
|
||||
if (room !== this.props.room) return;
|
||||
this.setState({
|
||||
|
@ -151,10 +233,19 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
/* We bind here rather than in the definition because otherwise we wind up with the
|
||||
method only being callable once every 500ms across all instances, which would be wrong */
|
||||
this._updateE2eStatus = rate_limited_func(this._updateE2eStatus, 500);
|
||||
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.on("accountData", this.onAccountData);
|
||||
cli.on("Room.name", this.onRoomName);
|
||||
cli.on("RoomState.events", this.onJoinRule);
|
||||
if (cli.isRoomEncrypted(this.props.room.roomId)) {
|
||||
this.onFindingRoomToBeEncrypted();
|
||||
} else {
|
||||
cli.on("Room.timeline", this.onRoomTimeline);
|
||||
}
|
||||
ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
|
||||
|
@ -172,6 +263,9 @@ export default createReactClass({
|
|||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||
cli.removeListener("RoomState.events", this.onJoinRule);
|
||||
cli.removeListener("RoomState.members", this.onRoomStateMember);
|
||||
cli.removeListener("userTrustStatusChanged", this.onUserVerificationChanged);
|
||||
cli.removeListener("Room.timeline", this.onRoomTimeline);
|
||||
}
|
||||
ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
|
@ -318,7 +412,6 @@ export default createReactClass({
|
|||
'mx_RoomTile_noBadges': !badges,
|
||||
'mx_RoomTile_transparent': this.props.transparent,
|
||||
'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed,
|
||||
'mx_RoomTile_isPrivate': this.state.joinRule == "invite" && !dmUserId,
|
||||
});
|
||||
|
||||
const avatarClasses = classNames({
|
||||
|
@ -385,7 +478,8 @@ export default createReactClass({
|
|||
|
||||
let dmIndicator;
|
||||
let dmOnline;
|
||||
if (dmUserId) {
|
||||
// If we can place a shield, do that instead
|
||||
if (dmUserId && !this.state.e2eStatus) {
|
||||
dmIndicator = <img
|
||||
src={require("../../../../res/img/icon_person.svg")}
|
||||
className="mx_RoomTile_dm"
|
||||
|
@ -430,7 +524,14 @@ export default createReactClass({
|
|||
|
||||
let privateIcon = null;
|
||||
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
privateIcon = <div className="mx_RoomTile_PrivateIcon" />;
|
||||
if (this.state.joinRule == "invite" && !dmUserId) {
|
||||
privateIcon = <InviteOnlyIcon />;
|
||||
}
|
||||
}
|
||||
|
||||
let e2eIcon = null;
|
||||
if (this.state.e2eStatus) {
|
||||
e2eIcon = <E2EIcon status={this.state.e2eStatus} className="mx_RoomTile_e2eIcon" />;
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
|
@ -453,6 +554,7 @@ export default createReactClass({
|
|||
<div className="mx_RoomTile_avatar_container">
|
||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
||||
{ dmIndicator }
|
||||
{ e2eIcon }
|
||||
</div>
|
||||
</div>
|
||||
{ privateIcon }
|
||||
|
|
|
@ -23,6 +23,7 @@ import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases";
|
|||
import {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver";
|
||||
import dis from "../../../dispatcher";
|
||||
import ToastStore from "../../../stores/ToastStore";
|
||||
import Modal from "../../../Modal";
|
||||
|
||||
export default class VerificationRequestToast extends React.PureComponent {
|
||||
constructor(props) {
|
||||
|
@ -65,18 +66,16 @@ export default class VerificationRequestToast extends React.PureComponent {
|
|||
accept = async () => {
|
||||
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
||||
const {request} = this.props;
|
||||
const {event} = request;
|
||||
// no room id for to_device requests
|
||||
if (event.getRoomId()) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: event.getRoomId(),
|
||||
should_peek: false,
|
||||
});
|
||||
}
|
||||
try {
|
||||
await request.accept();
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (request.channel.roomId) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: request.channel.roomId,
|
||||
should_peek: false,
|
||||
});
|
||||
await request.accept();
|
||||
const cli = MatrixClientPeg.get();
|
||||
dis.dispatch({
|
||||
action: "set_right_panel_phase",
|
||||
phase: RIGHT_PANEL_PHASES.EncryptionPanel,
|
||||
|
@ -84,7 +83,13 @@ export default class VerificationRequestToast extends React.PureComponent {
|
|||
verificationRequest: request,
|
||||
member: cli.getUser(request.otherUserId),
|
||||
},
|
||||
});
|
||||
});} else if (request.channel.deviceId && request.verifier) {
|
||||
// show to_device verifications in dialog still
|
||||
const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog");
|
||||
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
|
||||
verifier: request.verifier,
|
||||
}, null, /* priority = */ false, /* static = */ true);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
}
|
||||
|
@ -93,13 +98,13 @@ export default class VerificationRequestToast extends React.PureComponent {
|
|||
render() {
|
||||
const FormButton = sdk.getComponent("elements.FormButton");
|
||||
const {request} = this.props;
|
||||
const {event} = request;
|
||||
const userId = request.otherUserId;
|
||||
let nameLabel = event.getRoomId() ? userLabelForEventRoom(userId, event) : userId;
|
||||
const roomId = request.channel.roomId;
|
||||
let nameLabel = roomId ? userLabelForEventRoom(userId, roomId) : userId;
|
||||
// for legacy to_device verification requests
|
||||
if (nameLabel === userId) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const user = client.getUser(event.getSender());
|
||||
const user = client.getUser(userId);
|
||||
if (user && user.displayName) {
|
||||
nameLabel = _t("%(name)s (%(userId)s)", {name: user.displayName, userId});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue