mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 05:04:57 +08:00
Merge pull request #3796 from matrix-org/bwindels/verification-right-panel
Initial support for verification in right panel
This commit is contained in:
commit
95a0ebaf06
@ -63,7 +63,6 @@ import { countRoomsWithNotif } from '../../RoomNotifs';
|
||||
import { ThemeWatcher } from "../../theme";
|
||||
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
||||
import { defer } from "../../utils/promise";
|
||||
import KeyVerificationStateObserver from '../../utils/KeyVerificationStateObserver';
|
||||
import ToastStore from "../../stores/ToastStore";
|
||||
|
||||
/** constants for MatrixChat.state.view */
|
||||
@ -1454,18 +1453,13 @@ export default createReactClass({
|
||||
|
||||
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
cli.on("crypto.verification.request", request => {
|
||||
let requestObserver;
|
||||
if (request.event.getRoomId()) {
|
||||
requestObserver = new KeyVerificationStateObserver(
|
||||
request.event, MatrixClientPeg.get());
|
||||
}
|
||||
|
||||
if (!requestObserver || requestObserver.pending) {
|
||||
console.log(`MatrixChat got a .request ${request.channel.transactionId}`, request.event.getRoomId());
|
||||
if (request.pending) {
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: 'verifreq_' + request.event.getId(),
|
||||
key: 'verifreq_' + request.channel.transactionId,
|
||||
title: _t("Verification Request"),
|
||||
icon: "verification",
|
||||
props: {request, requestObserver},
|
||||
props: {request},
|
||||
component: sdk.getComponent("toasts.VerificationRequestToast"),
|
||||
});
|
||||
}
|
||||
|
@ -160,6 +160,7 @@ export default class RightPanel extends React.Component {
|
||||
groupId: payload.groupId,
|
||||
member: payload.member,
|
||||
event: payload.event,
|
||||
verificationRequest: payload.verificationRequest,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -168,6 +169,7 @@ export default class RightPanel extends React.Component {
|
||||
const MemberList = sdk.getComponent('rooms.MemberList');
|
||||
const MemberInfo = sdk.getComponent('rooms.MemberInfo');
|
||||
const UserInfo = sdk.getComponent('right_panel.UserInfo');
|
||||
const EncryptionPanel = sdk.getComponent('right_panel.EncryptionPanel');
|
||||
const ThirdPartyMemberInfo = sdk.getComponent('rooms.ThirdPartyMemberInfo');
|
||||
const NotificationPanel = sdk.getComponent('structures.NotificationPanel');
|
||||
const FilePanel = sdk.getComponent('structures.FilePanel');
|
||||
@ -235,6 +237,8 @@ export default class RightPanel extends React.Component {
|
||||
panel = <NotificationPanel />;
|
||||
} else if (this.state.phase === RIGHT_PANEL_PHASES.FilePanel) {
|
||||
panel = <FilePanel roomId={this.props.roomId} resizeNotifier={this.props.resizeNotifier} />;
|
||||
} else if (this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel) {
|
||||
panel = <EncryptionPanel member={this.state.member} verificationRequest={this.state.verificationRequest} />;
|
||||
}
|
||||
|
||||
const classes = classNames("mx_RightPanel", "mx_fadable", {
|
||||
|
@ -1134,7 +1134,7 @@ const TimelinePanel = createReactClass({
|
||||
const allowPartial = opts.allowPartial || false;
|
||||
|
||||
const messagePanel = this._messagePanel.current;
|
||||
if (messagePanel === undefined) return null;
|
||||
if (!messagePanel) return null;
|
||||
|
||||
const messagePanelNode = ReactDOM.findDOMNode(messagePanel);
|
||||
if (!messagePanelNode) return null; // sometimes this happens for fresh rooms/post-sync
|
||||
|
@ -24,8 +24,7 @@ import * as sdk from '../../../index';
|
||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
|
||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
import createRoom from "../../../createRoom";
|
||||
import {ensureDMExists} from "../../../createRoom";
|
||||
import dis from "../../../dispatcher";
|
||||
import SettingsStore from '../../../settings/SettingsStore';
|
||||
|
||||
@ -100,9 +99,15 @@ export default class DeviceVerifyDialog extends React.Component {
|
||||
if (!verifyingOwnDevice && SettingsStore.getValue("feature_cross_signing")) {
|
||||
const roomId = await ensureDMExistsAndOpen(this.props.userId);
|
||||
// throws upon cancellation before having started
|
||||
this._verifier = await client.requestVerificationDM(
|
||||
const request = await client.requestVerificationDM(
|
||||
this.props.userId, roomId, [verificationMethods.SAS],
|
||||
);
|
||||
await request.waitFor(r => r.ready || r.started);
|
||||
if (request.ready) {
|
||||
this._verifier = request.beginKeyVerification(verificationMethods.SAS);
|
||||
} else {
|
||||
this._verifier = request.verifier;
|
||||
}
|
||||
} else {
|
||||
this._verifier = client.beginKeyVerification(
|
||||
verificationMethods.SAS, this.props.userId, this.props.device.deviceId,
|
||||
@ -316,23 +321,7 @@ export default class DeviceVerifyDialog extends React.Component {
|
||||
}
|
||||
|
||||
async function ensureDMExistsAndOpen(userId) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId);
|
||||
const rooms = roomIds.map(id => client.getRoom(id));
|
||||
const suitableDMRooms = rooms.filter(r => {
|
||||
if (r && r.getMyMembership() === "join") {
|
||||
const member = r.getMember(userId);
|
||||
return member && (member.membership === "invite" || member.membership === "join");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
let roomId;
|
||||
if (suitableDMRooms.length) {
|
||||
const room = suitableDMRooms[0];
|
||||
roomId = room.roomId;
|
||||
} else {
|
||||
roomId = await createRoom({dmUserId: userId, spinner: false, andView: false});
|
||||
}
|
||||
const roomId = ensureDMExists(MatrixClientPeg.get(), userId);
|
||||
// don't use andView and spinner in createRoom, together, they cause this dialog to close and reopen,
|
||||
// we causes us to loose the verifier and restart, and we end up having two verification requests
|
||||
dis.dispatch({
|
||||
|
@ -19,102 +19,83 @@ import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import KeyVerificationStateObserver, {getNameForEventRoom, userLabelForEventRoom}
|
||||
import {getNameForEventRoom, userLabelForEventRoom}
|
||||
from '../../../utils/KeyVerificationStateObserver';
|
||||
|
||||
export default class MKeyVerificationConclusion extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.keyVerificationState = null;
|
||||
this.state = {
|
||||
done: false,
|
||||
cancelled: false,
|
||||
otherPartyUserId: null,
|
||||
cancelPartyUserId: null,
|
||||
};
|
||||
const rel = this.props.mxEvent.getRelation();
|
||||
if (rel) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const room = client.getRoom(this.props.mxEvent.getRoomId());
|
||||
const requestEvent = room.findEventById(rel.event_id);
|
||||
if (requestEvent) {
|
||||
this._createStateObserver(requestEvent, client);
|
||||
this.state = this._copyState();
|
||||
} else {
|
||||
const findEvent = event => {
|
||||
if (event.getId() === rel.event_id) {
|
||||
this._createStateObserver(event, client);
|
||||
this.setState(this._copyState());
|
||||
room.removeListener("Room.timeline", findEvent);
|
||||
}
|
||||
};
|
||||
room.on("Room.timeline", findEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_createStateObserver(requestEvent, client) {
|
||||
this.keyVerificationState = new KeyVerificationStateObserver(requestEvent, client, () => {
|
||||
this.setState(this._copyState());
|
||||
});
|
||||
}
|
||||
|
||||
_copyState() {
|
||||
const {done, cancelled, otherPartyUserId, cancelPartyUserId} = this.keyVerificationState;
|
||||
return {done, cancelled, otherPartyUserId, cancelPartyUserId};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.keyVerificationState) {
|
||||
this.keyVerificationState.attach();
|
||||
const request = this.props.mxEvent.verificationRequest;
|
||||
if (request) {
|
||||
request.on("change", this._onRequestChanged);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.keyVerificationState) {
|
||||
this.keyVerificationState.detach();
|
||||
const request = this.props.mxEvent.verificationRequest;
|
||||
if (request) {
|
||||
request.off("change", this._onRequestChanged);
|
||||
}
|
||||
}
|
||||
|
||||
_getName(userId) {
|
||||
const roomId = this.props.mxEvent.getRoomId();
|
||||
const client = MatrixClientPeg.get();
|
||||
const room = client.getRoom(roomId);
|
||||
const member = room.getMember(userId);
|
||||
return member ? member.name : userId;
|
||||
_onRequestChanged = () => {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
_shouldRender(mxEvent, request) {
|
||||
// normally should not happen
|
||||
if (!request) {
|
||||
return false;
|
||||
}
|
||||
// .cancel event that was sent after the verification finished, ignore
|
||||
if (mxEvent.getType() === "m.key.verification.cancel" && !request.cancelled) {
|
||||
return false;
|
||||
}
|
||||
// .done event that was sent after the verification cancelled, ignore
|
||||
if (mxEvent.getType() === "m.key.verification.done" && !request.done) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_userLabel(userId) {
|
||||
const name = this._getName(userId);
|
||||
if (name !== userId) {
|
||||
return _t("%(name)s (%(userId)s)", {name, userId});
|
||||
} else {
|
||||
return userId;
|
||||
// request hasn't concluded yet
|
||||
if (request.pending) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {mxEvent} = this.props;
|
||||
const request = mxEvent.verificationRequest;
|
||||
|
||||
if (!this._shouldRender(mxEvent, request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
const myUserId = client.getUserId();
|
||||
|
||||
let title;
|
||||
|
||||
if (this.state.done) {
|
||||
title = _t("You verified %(name)s", {name: getNameForEventRoom(this.state.otherPartyUserId, mxEvent)});
|
||||
} else if (this.state.cancelled) {
|
||||
if (mxEvent.getSender() === myUserId) {
|
||||
if (request.done) {
|
||||
title = _t("You verified %(name)s", {name: getNameForEventRoom(request.otherUserId, mxEvent)});
|
||||
} else if (request.cancelled) {
|
||||
const userId = request.cancellingUserId;
|
||||
if (userId === myUserId) {
|
||||
title = _t("You cancelled verifying %(name)s",
|
||||
{name: getNameForEventRoom(this.state.otherPartyUserId, mxEvent)});
|
||||
} else if (mxEvent.getSender() === this.state.otherPartyUserId) {
|
||||
{name: getNameForEventRoom(request.otherUserId, mxEvent)});
|
||||
} else {
|
||||
title = _t("%(name)s cancelled verifying",
|
||||
{name: getNameForEventRoom(this.state.otherPartyUserId, mxEvent)});
|
||||
{name: getNameForEventRoom(userId, mxEvent)});
|
||||
}
|
||||
}
|
||||
|
||||
if (title) {
|
||||
const subtitle = userLabelForEventRoom(this.state.otherPartyUserId, mxEvent);
|
||||
const subtitle = userLabelForEventRoom(request.otherUserId, mxEvent);
|
||||
const classes = classNames("mx_EventTile_bubble", "mx_KeyVerification", "mx_KeyVerification_icon", {
|
||||
mx_KeyVerification_icon_verified: this.state.done,
|
||||
mx_KeyVerification_icon_verified: request.done,
|
||||
});
|
||||
return (<div className={classes}>
|
||||
<div className="mx_KeyVerification_title">{title}</div>
|
||||
|
@ -17,48 +17,66 @@ limitations under the License.
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
|
||||
import * as sdk from '../../../index';
|
||||
import Modal from "../../../Modal";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import KeyVerificationStateObserver, {getNameForEventRoom, userLabelForEventRoom}
|
||||
import {getNameForEventRoom, userLabelForEventRoom}
|
||||
from '../../../utils/KeyVerificationStateObserver';
|
||||
import dis from "../../../dispatcher";
|
||||
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases";
|
||||
|
||||
export default class MKeyVerificationRequest extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.keyVerificationState = new KeyVerificationStateObserver(this.props.mxEvent, MatrixClientPeg.get(), () => {
|
||||
this.setState(this._copyState());
|
||||
});
|
||||
this.state = this._copyState();
|
||||
}
|
||||
|
||||
_copyState() {
|
||||
const {accepted, done, cancelled, cancelPartyUserId, otherPartyUserId} = this.keyVerificationState;
|
||||
return {accepted, done, cancelled, cancelPartyUserId, otherPartyUserId};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.keyVerificationState.attach();
|
||||
const request = this.props.mxEvent.verificationRequest;
|
||||
if (request) {
|
||||
request.on("change", this._onRequestChanged);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.keyVerificationState.detach();
|
||||
const request = this.props.mxEvent.verificationRequest;
|
||||
if (request) {
|
||||
request.off("change", this._onRequestChanged);
|
||||
}
|
||||
}
|
||||
|
||||
_onAcceptClicked = () => {
|
||||
const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
|
||||
// todo: validate event, for example if it has sas in the methods.
|
||||
const verifier = MatrixClientPeg.get().acceptVerificationDM(this.props.mxEvent, verificationMethods.SAS);
|
||||
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
|
||||
verifier,
|
||||
}, null, /* priority = */ false, /* static = */ true);
|
||||
_openRequest = () => {
|
||||
const {verificationRequest} = this.props.mxEvent;
|
||||
dis.dispatch({
|
||||
action: "set_right_panel_phase",
|
||||
phase: RIGHT_PANEL_PHASES.EncryptionPanel,
|
||||
refireParams: {verificationRequest},
|
||||
});
|
||||
};
|
||||
|
||||
_onRejectClicked = () => {
|
||||
// todo: validate event, for example if it has sas in the methods.
|
||||
const verifier = MatrixClientPeg.get().acceptVerificationDM(this.props.mxEvent, verificationMethods.SAS);
|
||||
verifier.cancel("User declined");
|
||||
_onRequestChanged = () => {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
_onAcceptClicked = async () => {
|
||||
const request = this.props.mxEvent.verificationRequest;
|
||||
if (request) {
|
||||
try {
|
||||
await request.accept();
|
||||
this._openRequest();
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_onRejectClicked = async () => {
|
||||
const request = this.props.mxEvent.verificationRequest;
|
||||
if (request) {
|
||||
try {
|
||||
await request.cancel();
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_acceptedLabel(userId) {
|
||||
@ -82,46 +100,49 @@ export default class MKeyVerificationRequest extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
const FormButton = sdk.getComponent("elements.FormButton");
|
||||
|
||||
const {mxEvent} = this.props;
|
||||
const fromUserId = mxEvent.getSender();
|
||||
const content = mxEvent.getContent();
|
||||
const toUserId = content.to;
|
||||
const client = MatrixClientPeg.get();
|
||||
const myUserId = client.getUserId();
|
||||
const isOwn = fromUserId === myUserId;
|
||||
const request = mxEvent.verificationRequest;
|
||||
|
||||
if (!request || request.invalid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let title;
|
||||
let subtitle;
|
||||
let stateNode;
|
||||
|
||||
if (this.state.accepted || this.state.cancelled) {
|
||||
const accepted = request.ready || request.started || request.done;
|
||||
if (accepted || request.cancelled) {
|
||||
let stateLabel;
|
||||
if (this.state.accepted) {
|
||||
stateLabel = this._acceptedLabel(toUserId);
|
||||
} else if (this.state.cancelled) {
|
||||
stateLabel = this._cancelledLabel(this.state.cancelPartyUserId);
|
||||
if (accepted) {
|
||||
stateLabel = (<AccessibleButton onClick={this._openRequest}>
|
||||
{this._acceptedLabel(request.receivingUserId)}
|
||||
</AccessibleButton>);
|
||||
} else {
|
||||
stateLabel = this._cancelledLabel(request.cancellingUserId);
|
||||
}
|
||||
stateNode = (<div className="mx_KeyVerification_state">{stateLabel}</div>);
|
||||
}
|
||||
|
||||
if (toUserId === myUserId) { // request sent to us
|
||||
if (!request.initiatedByMe) {
|
||||
title = (<div className="mx_KeyVerification_title">{
|
||||
_t("%(name)s wants to verify", {name: getNameForEventRoom(fromUserId, mxEvent)})}</div>);
|
||||
_t("%(name)s wants to verify", {name: getNameForEventRoom(request.requestingUserId, mxEvent)})}</div>);
|
||||
subtitle = (<div className="mx_KeyVerification_subtitle">{
|
||||
userLabelForEventRoom(fromUserId, mxEvent)}</div>);
|
||||
const isResolved = !(this.state.accepted || this.state.cancelled || this.state.done);
|
||||
if (isResolved) {
|
||||
const FormButton = sdk.getComponent("elements.FormButton");
|
||||
userLabelForEventRoom(request.requestingUserId, mxEvent)}</div>);
|
||||
if (request.requested && !request.observeOnly) {
|
||||
stateNode = (<div className="mx_KeyVerification_buttons">
|
||||
<FormButton kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} />
|
||||
<FormButton onClick={this._onAcceptClicked} label={_t("Accept")} />
|
||||
</div>);
|
||||
}
|
||||
} else if (isOwn) { // request sent by us
|
||||
} else { // request sent by us
|
||||
title = (<div className="mx_KeyVerification_title">{
|
||||
_t("You sent a verification request")}</div>);
|
||||
subtitle = (<div className="mx_KeyVerification_subtitle">{
|
||||
userLabelForEventRoom(this.state.otherPartyUserId, mxEvent)}</div>);
|
||||
userLabelForEventRoom(request.receivingUserId, mxEvent)}</div>);
|
||||
}
|
||||
|
||||
if (title) {
|
||||
|
31
src/components/views/right_panel/EncryptionInfo.js
Normal file
31
src/components/views/right_panel/EncryptionInfo.js
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2019 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 * as sdk from '../../../index';
|
||||
import {_t} from "../../../languageHandler";
|
||||
|
||||
export default class EncryptionInfo extends React.PureComponent {
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
return (<div className="mx_UserInfo"><div className="mx_UserInfo_container">
|
||||
<h3>{_t("Verify User")}</h3>
|
||||
<p>{_t("For extra security, verify this user by checking a one-time code on both of your devices.")}</p>
|
||||
<p>{_t("For maximum security, do this in person.")}</p>
|
||||
<AccessibleButton kind="primary" onClick={this.props.onStartVerification}>{_t("Start Verification")}</AccessibleButton>
|
||||
</div></div>);
|
||||
}
|
||||
}
|
48
src/components/views/right_panel/EncryptionPanel.js
Normal file
48
src/components/views/right_panel/EncryptionPanel.js
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 2019 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 EncryptionInfo from "./EncryptionInfo";
|
||||
import VerificationPanel from "./VerificationPanel";
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import {ensureDMExists} from "../../../createRoom";
|
||||
|
||||
export default class EncryptionPanel extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
const request = this.props.verificationRequest || this.state.verificationRequest;
|
||||
const {member} = this.props;
|
||||
if (request) {
|
||||
return <VerificationPanel request={request} key={request.channel.transactionId} />;
|
||||
} else if (member) {
|
||||
return <EncryptionInfo onStartVerification={this._onStartVerification} member={member} />;
|
||||
} else {
|
||||
return <p>Not a member nor request, not sure what to render</p>;
|
||||
}
|
||||
}
|
||||
|
||||
_onStartVerification = async () => {
|
||||
const client = MatrixClientPeg.get();
|
||||
const {member} = this.props;
|
||||
const roomId = await ensureDMExists(client, member.userId);
|
||||
const verificationRequest = await client.requestVerificationDM(member.userId, roomId);
|
||||
this.setState({verificationRequest});
|
||||
};
|
||||
}
|
@ -28,6 +28,7 @@ import RightPanelStore from "../../../stores/RightPanelStore";
|
||||
const MEMBER_PHASES = [
|
||||
RIGHT_PANEL_PHASES.RoomMemberList,
|
||||
RIGHT_PANEL_PHASES.RoomMemberInfo,
|
||||
RIGHT_PANEL_PHASES.EncryptionPanel,
|
||||
RIGHT_PANEL_PHASES.Room3pidMemberInfo,
|
||||
];
|
||||
|
||||
|
@ -40,6 +40,7 @@ import E2EIcon from "../rooms/E2EIcon";
|
||||
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||
import {textualPowerLevel} from '../../../Roles';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases";
|
||||
|
||||
const _disambiguateDevices = (devices) => {
|
||||
const names = Object.create(null);
|
||||
@ -117,6 +118,14 @@ function verifyDevice(userId, device) {
|
||||
}, null, /* priority = */ false, /* static = */ true);
|
||||
}
|
||||
|
||||
function verifyUser(user) {
|
||||
dis.dispatch({
|
||||
action: "set_right_panel_phase",
|
||||
phase: RIGHT_PANEL_PHASES.EncryptionPanel,
|
||||
refireParams: {member: user},
|
||||
});
|
||||
}
|
||||
|
||||
function DeviceItem({userId, device}) {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const deviceTrust = cli.checkDeviceTrust(userId, device.deviceId);
|
||||
@ -1225,15 +1234,13 @@ const UserInfo = ({user, groupId, roomId, onClose}) => {
|
||||
setDevices(null);
|
||||
}
|
||||
}
|
||||
if (isRoomEncrypted) {
|
||||
_downloadDeviceList();
|
||||
}
|
||||
|
||||
// Handle being unmounted
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [cli, user.userId, isRoomEncrypted]);
|
||||
}, [cli, user.userId]);
|
||||
|
||||
// Listen to changes
|
||||
useEffect(() => {
|
||||
@ -1249,18 +1256,13 @@ const UserInfo = ({user, groupId, roomId, onClose}) => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (isRoomEncrypted) {
|
||||
cli.on("deviceVerificationChanged", onDeviceVerificationChanged);
|
||||
}
|
||||
// Handle being unmounted
|
||||
return () => {
|
||||
cancel = true;
|
||||
if (isRoomEncrypted) {
|
||||
cli.removeListener("deviceVerificationChanged", onDeviceVerificationChanged);
|
||||
}
|
||||
};
|
||||
}, [cli, user.userId, isRoomEncrypted]);
|
||||
}, [cli, user.userId]);
|
||||
|
||||
let text;
|
||||
if (!isRoomEncrypted) {
|
||||
@ -1275,17 +1277,19 @@ const UserInfo = ({user, groupId, roomId, onClose}) => {
|
||||
text = _t("Messages in this room are end-to-end encrypted.");
|
||||
}
|
||||
|
||||
const devicesSection = isRoomEncrypted ?
|
||||
(<DevicesSection loading={devices === undefined} devices={devices} userId={user.userId} />) : null;
|
||||
|
||||
const userVerified = cli.checkUserTrust(user.userId).isVerified();
|
||||
const isMe = user.userId === cli.getUserId();
|
||||
let verifyButton;
|
||||
if (!userVerified) {
|
||||
verifyButton = <AccessibleButton className="mx_UserInfo_verify" onClick={() => verifyDevice(user.userId, null)}>
|
||||
if (!userVerified && !isMe) {
|
||||
verifyButton = <AccessibleButton className="mx_UserInfo_verify" onClick={() => verifyUser(user)}>
|
||||
{_t("Verify")}
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
const devicesSection = <DevicesSection
|
||||
loading={devices === undefined}
|
||||
devices={devices} userId={user.userId} />;
|
||||
|
||||
const securitySection = (
|
||||
<div className="mx_UserInfo_container">
|
||||
<h3>{ _t("Security") }</h3>
|
||||
|
119
src/components/views/right_panel/VerificationPanel.js
Normal file
119
src/components/views/right_panel/VerificationPanel.js
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright 2019 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 * as sdk from '../../../index';
|
||||
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
|
||||
|
||||
export default class VerificationPanel extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this._hasVerifier = !!props.request.verifier;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className="mx_UserInfo">
|
||||
<div className="mx_UserInfo_container">
|
||||
{ this.renderStatus() }
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
renderStatus() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const Spinner = sdk.getComponent('elements.Spinner');
|
||||
const {request} = this.props;
|
||||
|
||||
if (request.requested) {
|
||||
return (<p>Waiting for {request.otherUserId} to accept ... <Spinner /></p>);
|
||||
} else if (request.ready) {
|
||||
const verifyButton = <AccessibleButton kind="primary" onClick={this._startSAS}>
|
||||
Verify by emoji
|
||||
</AccessibleButton>;
|
||||
return (<p>{request.otherUserId} is ready, start {verifyButton}</p>);
|
||||
} else if (request.started) {
|
||||
if (this.state.sasWaitingForOtherParty) {
|
||||
return <p>Waiting for {request.otherUserId} to confirm ...</p>;
|
||||
} else if (this.state.sasEvent) {
|
||||
const VerificationShowSas = sdk.getComponent('views.verification.VerificationShowSas');
|
||||
return (<div>
|
||||
<VerificationShowSas
|
||||
sas={this.state.sasEvent.sas}
|
||||
onCancel={this._onSasMismatchesClick}
|
||||
onDone={this._onSasMatchesClick}
|
||||
/>
|
||||
</div>);
|
||||
} else {
|
||||
return (<p>Setting up SAS verification...</p>);
|
||||
}
|
||||
} else if (request.done) {
|
||||
return <p>verified {request.otherUserId}!!</p>;
|
||||
} else if (request.cancelled) {
|
||||
return <p>cancelled by {request.cancellingUserId}!</p>;
|
||||
}
|
||||
}
|
||||
|
||||
_startSAS = async () => {
|
||||
const verifier = this.props.request.beginKeyVerification(verificationMethods.SAS);
|
||||
try {
|
||||
await verifier.verify();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
this.setState({sasEvent: null});
|
||||
}
|
||||
};
|
||||
|
||||
_onSasMatchesClick = () => {
|
||||
this.setState({sasWaitingForOtherParty: true});
|
||||
this.state.sasEvent.confirm();
|
||||
};
|
||||
|
||||
_onSasMismatchesClick = () => {
|
||||
this.state.sasEvent.cancel();
|
||||
};
|
||||
|
||||
_onVerifierShowSas = (sasEvent) => {
|
||||
this.setState({sasEvent});
|
||||
};
|
||||
|
||||
_onRequestChange = async () => {
|
||||
const {request} = this.props;
|
||||
if (!this._hasVerifier && !!request.verifier) {
|
||||
request.verifier.on('show_sas', this._onVerifierShowSas);
|
||||
try {
|
||||
// on the requester side, this is also awaited in _startSAS,
|
||||
// but that's ok as verify should return the same promise.
|
||||
await request.verifier.verify();
|
||||
} catch (err) {
|
||||
console.error("error verify", err);
|
||||
}
|
||||
} else if (this._hasVerifier && !request.verifier) {
|
||||
request.verifier.removeListener('show_sas', this._onVerifierShowSas);
|
||||
}
|
||||
this._hasVerifier = !!request.verifier;
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.request.on("change", this._onRequestChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.request.off("change", this._onRequestChange);
|
||||
}
|
||||
}
|
@ -18,55 +18,40 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as sdk from "../../../index";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Modal from "../../../Modal";
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
|
||||
import KeyVerificationStateObserver, {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver";
|
||||
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases";
|
||||
import {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver";
|
||||
import dis from "../../../dispatcher";
|
||||
import ToastStore from "../../../stores/ToastStore";
|
||||
|
||||
export default class VerificationRequestToast extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const {event, timeout} = props.request;
|
||||
// to_device requests don't have a timestamp, so consider them age=0
|
||||
const age = event.getTs() ? event.getLocalAge() : 0;
|
||||
const remaining = Math.max(0, timeout - age);
|
||||
const counter = Math.ceil(remaining / 1000);
|
||||
this.state = {counter};
|
||||
if (this.props.requestObserver) {
|
||||
this.props.requestObserver.setCallback(this._checkRequestIsPending);
|
||||
}
|
||||
this.state = {counter: Math.ceil(props.request.timeout / 1000)};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.requestObserver) {
|
||||
this.props.requestObserver.attach();
|
||||
this._checkRequestIsPending();
|
||||
}
|
||||
const {request} = this.props;
|
||||
this._intervalHandle = setInterval(() => {
|
||||
let {counter} = this.state;
|
||||
counter -= 1;
|
||||
if (counter <= 0) {
|
||||
this.cancel();
|
||||
} else {
|
||||
counter = Math.max(0, counter - 1);
|
||||
this.setState({counter});
|
||||
}
|
||||
}, 1000);
|
||||
request.on("change", this._checkRequestIsPending);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this._intervalHandle);
|
||||
if (this.props.requestObserver) {
|
||||
this.props.requestObserver.detach();
|
||||
}
|
||||
const {request} = this.props;
|
||||
request.off("change", this._checkRequestIsPending);
|
||||
}
|
||||
|
||||
_checkRequestIsPending = () => {
|
||||
if (!this.props.requestObserver.pending) {
|
||||
const {request} = this.props;
|
||||
if (request.ready || request.started || request.done || request.cancelled || request.observeOnly) {
|
||||
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cancel = () => {
|
||||
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
||||
@ -77,9 +62,10 @@ export default class VerificationRequestToast extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
accept = () => {
|
||||
accept = async () => {
|
||||
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
||||
const {event} = this.props.request;
|
||||
const {request} = this.props;
|
||||
const {event} = request;
|
||||
// no room id for to_device requests
|
||||
if (event.getRoomId()) {
|
||||
dis.dispatch({
|
||||
@ -88,18 +74,23 @@ export default class VerificationRequestToast extends React.PureComponent {
|
||||
should_peek: false,
|
||||
});
|
||||
}
|
||||
|
||||
const verifier = this.props.request.beginKeyVerification(verificationMethods.SAS);
|
||||
const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
|
||||
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
|
||||
verifier,
|
||||
}, null, /* priority = */ false, /* static = */ true);
|
||||
try {
|
||||
await request.accept();
|
||||
dis.dispatch({
|
||||
action: "set_right_panel_phase",
|
||||
phase: RIGHT_PANEL_PHASES.EncryptionPanel,
|
||||
refireParams: {verificationRequest: request},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const FormButton = sdk.getComponent("elements.FormButton");
|
||||
const {event} = this.props.request;
|
||||
const userId = event.getSender();
|
||||
const {request} = this.props;
|
||||
const {event} = request;
|
||||
const userId = request.otherUserId;
|
||||
let nameLabel = event.getRoomId() ? userLabelForEventRoom(userId, event) : userId;
|
||||
// for legacy to_device verification requests
|
||||
if (nameLabel === userId) {
|
||||
@ -121,6 +112,5 @@ export default class VerificationRequestToast extends React.PureComponent {
|
||||
|
||||
VerificationRequestToast.propTypes = {
|
||||
request: PropTypes.object.isRequired,
|
||||
requestObserver: PropTypes.instanceOf(KeyVerificationStateObserver),
|
||||
toastKey: PropTypes.string.isRequired,
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 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.
|
||||
@ -20,7 +21,7 @@ import * as sdk from './index';
|
||||
import { _t } from './languageHandler';
|
||||
import dis from "./dispatcher";
|
||||
import * as Rooms from "./Rooms";
|
||||
|
||||
import DMRoomMap from "./utils/DMRoomMap";
|
||||
import {getAddressType} from "./UserAddress";
|
||||
|
||||
/**
|
||||
@ -139,3 +140,23 @@ export default function createRoom(opts) {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
export async function ensureDMExists(client, userId) {
|
||||
const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId);
|
||||
const rooms = roomIds.map(id => client.getRoom(id));
|
||||
const suitableDMRooms = rooms.filter(r => {
|
||||
if (r && r.getMyMembership() === "join") {
|
||||
const member = r.getMember(userId);
|
||||
return member && (member.membership === "invite" || member.membership === "join");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
let roomId;
|
||||
if (suitableDMRooms.length) {
|
||||
const room = suitableDMRooms[0];
|
||||
roomId = room.roomId;
|
||||
} else {
|
||||
roomId = await createRoom({dmUserId: userId, spinner: false, andView: false});
|
||||
}
|
||||
return roomId;
|
||||
}
|
||||
|
@ -1117,6 +1117,10 @@
|
||||
"URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.",
|
||||
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.",
|
||||
"When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.",
|
||||
"Verify User": "Verify User",
|
||||
"For extra security, verify this user by checking a one-time code on both of your devices.": "For extra security, verify this user by checking a one-time code on both of your devices.",
|
||||
"For maximum security, do this in person.": "For maximum security, do this in person.",
|
||||
"Start Verification": "Start Verification",
|
||||
"Members": "Members",
|
||||
"Files": "Files",
|
||||
"Trusted": "Trusted",
|
||||
|
@ -123,7 +123,11 @@ export default class RightPanelStore extends Store {
|
||||
|
||||
if (payload.action === 'view_room' || payload.action === 'view_group') {
|
||||
// Reset to the member list if we're viewing member info
|
||||
const memberInfoPhases = [RIGHT_PANEL_PHASES.RoomMemberInfo, RIGHT_PANEL_PHASES.Room3pidMemberInfo];
|
||||
const memberInfoPhases = [
|
||||
RIGHT_PANEL_PHASES.RoomMemberInfo,
|
||||
RIGHT_PANEL_PHASES.Room3pidMemberInfo,
|
||||
RIGHT_PANEL_PHASES.EncryptionPanel,
|
||||
];
|
||||
if (memberInfoPhases.includes(this._state.lastRoomPhase)) {
|
||||
this._setState({lastRoomPhase: RIGHT_PANEL_PHASES.RoomMemberList, lastRoomPhaseParams: {}});
|
||||
}
|
||||
|
@ -21,8 +21,9 @@ export const RIGHT_PANEL_PHASES = Object.freeze({
|
||||
FilePanel: 'FilePanel',
|
||||
NotificationPanel: 'NotificationPanel',
|
||||
RoomMemberInfo: 'RoomMemberInfo',
|
||||
Room3pidMemberInfo: 'Room3pidMemberInfo',
|
||||
EncryptionPanel: 'EncryptionPanel',
|
||||
|
||||
Room3pidMemberInfo: 'Room3pidMemberInfo',
|
||||
// Group stuff
|
||||
GroupMemberList: 'GroupMemberList',
|
||||
GroupRoomList: 'GroupRoomList',
|
||||
|
@ -17,153 +17,6 @@ limitations under the License.
|
||||
import {MatrixClientPeg} from '../MatrixClientPeg';
|
||||
import { _t } from '../languageHandler';
|
||||
|
||||
const SUB_EVENT_TYPES_OF_INTEREST = ["start", "cancel", "done"];
|
||||
|
||||
export default class KeyVerificationStateObserver {
|
||||
constructor(requestEvent, client, updateCallback) {
|
||||
this._requestEvent = requestEvent;
|
||||
this._client = client;
|
||||
this._updateCallback = updateCallback;
|
||||
this.accepted = false;
|
||||
this.done = false;
|
||||
this.cancelled = false;
|
||||
this._updateVerificationState();
|
||||
}
|
||||
|
||||
get concluded() {
|
||||
return this.accepted || this.done || this.cancelled;
|
||||
}
|
||||
|
||||
get pending() {
|
||||
return !this.concluded;
|
||||
}
|
||||
|
||||
setCallback(callback) {
|
||||
this._updateCallback = callback;
|
||||
}
|
||||
|
||||
attach() {
|
||||
this._requestEvent.on("Event.relationsCreated", this._onRelationsCreated);
|
||||
for (const phaseName of SUB_EVENT_TYPES_OF_INTEREST) {
|
||||
this._tryListenOnRelationsForType(`m.key.verification.${phaseName}`);
|
||||
}
|
||||
}
|
||||
|
||||
detach() {
|
||||
const roomId = this._requestEvent.getRoomId();
|
||||
const room = this._client.getRoom(roomId);
|
||||
|
||||
for (const phaseName of SUB_EVENT_TYPES_OF_INTEREST) {
|
||||
const relations = room.getUnfilteredTimelineSet()
|
||||
.getRelationsForEvent(this._requestEvent.getId(), "m.reference", `m.key.verification.${phaseName}`);
|
||||
if (relations) {
|
||||
relations.removeListener("Relations.add", this._onRelationsUpdated);
|
||||
relations.removeListener("Relations.remove", this._onRelationsUpdated);
|
||||
relations.removeListener("Relations.redaction", this._onRelationsUpdated);
|
||||
}
|
||||
}
|
||||
this._requestEvent.removeListener("Event.relationsCreated", this._onRelationsCreated);
|
||||
}
|
||||
|
||||
_onRelationsCreated = (relationType, eventType) => {
|
||||
if (relationType !== "m.reference") {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
eventType !== "m.key.verification.start" &&
|
||||
eventType !== "m.key.verification.cancel" &&
|
||||
eventType !== "m.key.verification.done"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._tryListenOnRelationsForType(eventType);
|
||||
this._updateVerificationState();
|
||||
this._updateCallback();
|
||||
};
|
||||
|
||||
_tryListenOnRelationsForType(eventType) {
|
||||
const roomId = this._requestEvent.getRoomId();
|
||||
const room = this._client.getRoom(roomId);
|
||||
const relations = room.getUnfilteredTimelineSet()
|
||||
.getRelationsForEvent(this._requestEvent.getId(), "m.reference", eventType);
|
||||
if (relations) {
|
||||
relations.on("Relations.add", this._onRelationsUpdated);
|
||||
relations.on("Relations.remove", this._onRelationsUpdated);
|
||||
relations.on("Relations.redaction", this._onRelationsUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
_onRelationsUpdated = (event) => {
|
||||
this._updateVerificationState();
|
||||
this._updateCallback && this._updateCallback();
|
||||
};
|
||||
|
||||
_updateVerificationState() {
|
||||
const roomId = this._requestEvent.getRoomId();
|
||||
const room = this._client.getRoom(roomId);
|
||||
const timelineSet = room.getUnfilteredTimelineSet();
|
||||
const fromUserId = this._requestEvent.getSender();
|
||||
const content = this._requestEvent.getContent();
|
||||
const toUserId = content.to;
|
||||
|
||||
this.cancelled = false;
|
||||
this.done = false;
|
||||
this.accepted = false;
|
||||
this.otherPartyUserId = null;
|
||||
this.cancelPartyUserId = null;
|
||||
|
||||
const startRelations = timelineSet.getRelationsForEvent(
|
||||
this._requestEvent.getId(), "m.reference", "m.key.verification.start");
|
||||
if (startRelations) {
|
||||
for (const startEvent of startRelations.getRelations()) {
|
||||
if (startEvent.getSender() === toUserId) {
|
||||
this.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const doneRelations = timelineSet.getRelationsForEvent(
|
||||
this._requestEvent.getId(), "m.reference", "m.key.verification.done");
|
||||
if (doneRelations) {
|
||||
let senderDone = false;
|
||||
let receiverDone = false;
|
||||
for (const doneEvent of doneRelations.getRelations()) {
|
||||
if (doneEvent.getSender() === toUserId) {
|
||||
receiverDone = true;
|
||||
} else if (doneEvent.getSender() === fromUserId) {
|
||||
senderDone = true;
|
||||
}
|
||||
}
|
||||
if (senderDone && receiverDone) {
|
||||
this.done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.done) {
|
||||
const cancelRelations = timelineSet.getRelationsForEvent(
|
||||
this._requestEvent.getId(), "m.reference", "m.key.verification.cancel");
|
||||
|
||||
if (cancelRelations) {
|
||||
let earliestCancelEvent;
|
||||
for (const cancelEvent of cancelRelations.getRelations()) {
|
||||
// only accept cancellation from the users involved
|
||||
if (cancelEvent.getSender() === toUserId || cancelEvent.getSender() === fromUserId) {
|
||||
this.cancelled = true;
|
||||
if (!earliestCancelEvent || cancelEvent.getTs() < earliestCancelEvent.getTs()) {
|
||||
earliestCancelEvent = cancelEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (earliestCancelEvent) {
|
||||
this.cancelPartyUserId = earliestCancelEvent.getSender();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.otherPartyUserId = fromUserId === this._client.getUserId() ? toUserId : fromUserId;
|
||||
}
|
||||
}
|
||||
|
||||
export function getNameForEventRoom(userId, mxEvent) {
|
||||
const roomId = mxEvent.getRoomId();
|
||||
const client = MatrixClientPeg.get();
|
||||
|
Loading…
Reference in New Issue
Block a user