Add tile for verification

This commit is contained in:
RMidhunSuresh 2023-04-24 21:28:47 +05:30
parent 686c6092f8
commit 1c1a713ea2
11 changed files with 166 additions and 6 deletions

View File

@ -35,7 +35,7 @@ export type SegmentType = {
"members": true; "members": true;
"member": string; "member": string;
"device-verification": string | boolean; "device-verification": string | boolean;
"verification": boolean; "verification": string | boolean;
"join-room": true; "join-room": true;
}; };

View File

@ -70,9 +70,12 @@ export class RightPanelViewModel extends ViewModel {
} }
); );
this._hookUpdaterToSegment("verification", DeviceVerificationViewModel, () => { this._hookUpdaterToSegment("verification", DeviceVerificationViewModel, () => {
const id = this.navigation.path.get("verification").value;
const request = this._session?.crossSigning.get()?.receivedSASVerifications.get(id);
return { return {
session: this._session, session: this._session,
room: this._room, room: this._room,
request,
} }
}); });
} }

View File

@ -18,6 +18,7 @@ export enum TileShape {
Video = "video", Video = "video",
DateHeader = "date-header", DateHeader = "date-header",
Call = "call", Call = "call",
Verification = "verification",
} }
// TODO: should we imply inheriting from view model here? // TODO: should we imply inheriting from view model here?

View File

@ -0,0 +1,59 @@
/*
Copyright 2023 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 {SASRequest} from "../../../../../matrix/verification/SAS/SASRequest";
import {TileShape} from "./ITile";
import {SimpleTile} from "./SimpleTile";
import type {EventEntry} from "../../../../../matrix/room/timeline/entries/EventEntry.js";
import type {Options} from "./SimpleTile";
export class VerificationTile extends SimpleTile {
private request: SASRequest;
constructor(entry: EventEntry, options: Options) {
super(entry, options);
this.request = new SASRequest(this.lowerEntry);
}
get shape(): TileShape {
return TileShape.Verification;
}
get description(): string {
return this.i18n`${this.sender} wants to verify`;
}
accept(): void {
const crossSigning = this.getOption("session").crossSigning.get()
crossSigning.receivedSASVerifications.set(this.eventId, this.request);
this.openVerificationPanel(this.eventId);
}
async reject(): Promise<void> {
// create the SasVerification object and call abort() on it
await this.logAndCatch("VerificationTile.reject", async (log) => {
const crossSigning = this.getOption("session").crossSigning.get();
await this.request.reject(crossSigning, this._room, log);
});
}
private openVerificationPanel(eventId: string): void {
let path = this.navigation.path.until("room");
path = path.with(this.navigation.segment("right-panel", true))!;
path = path.with(this.navigation.segment("verification", eventId))!;
this.navigation.applyPath(path);
}
}

View File

@ -27,6 +27,7 @@ import {EncryptedEventTile} from "./EncryptedEventTile.js";
import {EncryptionEnabledTile} from "./EncryptionEnabledTile.js"; import {EncryptionEnabledTile} from "./EncryptionEnabledTile.js";
import {MissingAttachmentTile} from "./MissingAttachmentTile.js"; import {MissingAttachmentTile} from "./MissingAttachmentTile.js";
import {CallTile} from "./CallTile.js"; import {CallTile} from "./CallTile.js";
import {VerificationTile} from "./VerificationTile.js";
import type {ITile, TileShape} from "./ITile"; import type {ITile, TileShape} from "./ITile";
import type {Room} from "../../../../../matrix/room/Room"; import type {Room} from "../../../../../matrix/room/Room";
@ -73,6 +74,15 @@ export function tileClassForEntry(entry: TimelineEntry, options: Options): TileC
return FileTile; return FileTile;
case "m.location": case "m.location":
return LocationTile; return LocationTile;
case "m.key.verification.request":
const isCrossSigningEnabled = !options.session.features.crossSigning;
const userId = options.session.userId;
if (isCrossSigningEnabled||
entry.isLoadedFromStorage ||
entry.sender === userId) {
return undefined;
}
return VerificationTile as unknown as TileConstructor;
default: default:
// unknown msgtype not rendered // unknown msgtype not rendered
return undefined; return undefined;

View File

@ -132,6 +132,10 @@ export class Session {
return this._callHandler; return this._callHandler;
} }
get features() {
return this._features;
}
_setupCallHandler() { _setupCallHandler() {
this._callHandler = new CallHandler({ this._callHandler = new CallHandler({
clock: this._platform.clock, clock: this._platform.clock,

View File

@ -14,6 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import type {CrossSigning} from "../CrossSigning";
import type {Room} from "../../room/Room.js";
import type {ILogItem} from "../../../logging/types";
export class SASRequest { export class SASRequest {
constructor(public readonly startingMessage: any) {} constructor(public readonly startingMessage: any) {}
@ -26,6 +30,11 @@ export class SASRequest {
} }
get id(): string { get id(): string {
return this.startingMessage.content.transaction_id; return this.startingMessage.content.transaction_id ?? this.startingMessage.eventId;
}
async reject(crossSigning: CrossSigning, room: Room, log: ILogItem): Promise<void> {
const sas = crossSigning.startVerification(this, room, log);
await sas?.abort();
} }
} }

View File

@ -73,8 +73,9 @@ export class RoomChannel extends Disposables implements IChannel {
/** /**
* startingMessage may be the ready message or the start message. * startingMessage may be the ready message or the start message.
*/ */
this.id = startingMessage.content.transaction_id; this.id = startingMessage.id;
this.receivedMessages.set(startingMessage.type, startingMessage); const type = startingMessage.content?.msgtype ?? startingMessage.eventType;
this.receivedMessages.set(type, startingMessage);
this.otherUserDeviceId = startingMessage.content.from_device; this.otherUserDeviceId = startingMessage.content.from_device;
} }
} }

View File

@ -1510,3 +1510,31 @@ button.RoomDetailsView_row::after {
width: 100%; width: 100%;
display: inline-block; display: inline-block;
} }
.VerificationTileView,
.VerificationTileView__actions {
display: flex;
gap: 16px;
}
.VerificationTileView__description {
display: flex;
align-items: center;
font-weight: bold;
font-size: 1.4rem;
color: var(--text-color);
}
.VerificationTileView {
background: var(--background-color-primary--darker-5);
padding: 12px;
box-sizing: border-box;
border-radius: 8px;
}
.VerificationTileContainer {
display: flex;
justify-content: center;
padding: 16px;
box-sizing: border-box;
}

View File

@ -22,11 +22,12 @@ import {LocationView} from "./timeline/LocationView.js";
import {MissingAttachmentView} from "./timeline/MissingAttachmentView.js"; import {MissingAttachmentView} from "./timeline/MissingAttachmentView.js";
import {AnnouncementView} from "./timeline/AnnouncementView.js"; import {AnnouncementView} from "./timeline/AnnouncementView.js";
import {RedactedView} from "./timeline/RedactedView.js"; import {RedactedView} from "./timeline/RedactedView.js";
import {ITile, TileShape} from "../../../../../domain/session/room/timeline/tiles/ITile.js"; import {ITile, TileShape} from "../../../../../domain/session/room/timeline/tiles/ITile";
import {GapView} from "./timeline/GapView.js"; import {GapView} from "./timeline/GapView.js";
import {CallTileView} from "./timeline/CallTileView"; import {CallTileView} from "./timeline/CallTileView";
import {DateHeaderView} from "./timeline/DateHeaderView"; import {DateHeaderView} from "./timeline/DateHeaderView";
import type {TileViewConstructor, ViewClassForEntryFn} from "./TimelineView"; import type {TileViewConstructor} from "./TimelineView";
import {VerificationTileView} from "./timeline/VerificationTileView";
export function viewClassForTile(vm: ITile): TileViewConstructor { export function viewClassForTile(vm: ITile): TileViewConstructor {
switch (vm.shape) { switch (vm.shape) {
@ -53,6 +54,8 @@ export function viewClassForTile(vm: ITile): TileViewConstructor {
return CallTileView; return CallTileView;
case TileShape.DateHeader: case TileShape.DateHeader:
return DateHeaderView; return DateHeaderView;
case TileShape.Verification:
return VerificationTileView;
default: default:
throw new Error(`Tiles of shape "${vm.shape}" are not supported, check the tileClassForEntry function in the view model`); throw new Error(`Tiles of shape "${vm.shape}" are not supported, check the tileClassForEntry function in the view model`);
} }

View File

@ -0,0 +1,42 @@
/*
Copyright 2022 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 {Builder, TemplateView} from "../../../general/TemplateView";
import type {VerificationTile} from "../../../../../../domain/session/room/timeline/tiles/VerificationTile";
export class VerificationTileView extends TemplateView<VerificationTile> {
render(t: Builder<VerificationTile>, vm: VerificationTile) {
return t.div( { className: "VerificationTileContainer" },
t.div({ className: "VerificationTileView" }, [
t.div({className: "VerificationTileView__shield"}),
t.div({ className: "VerificationTileView__description" }, vm.description),
t.div({ className: "VerificationTileView__actions" }, [
t.button({ className: "VerificationTileView__accept button-action primary" }, "Accept"),
t.button({ className: "VerificationTileView__reject button-action secondary" }, "Reject"),
]),
])
);
}
/* This is called by the parent ListView, which just has 1 listener for the whole list */
onClick(evt) {
if (evt.target.classList.contains("VerificationTileView__accept")) {
this.value.accept();
} else if (evt.target.classList.contains("VerificationTileView__reject")) {
this.value.reject();
}
}
}