mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2025-02-01 23:21:40 +01:00
WIP: Add views/view-models
This commit is contained in:
parent
c92fd6069d
commit
0f7ef6912f
@ -34,6 +34,8 @@ export type SegmentType = {
|
||||
"details": true;
|
||||
"members": true;
|
||||
"member": string;
|
||||
"device-verification": true;
|
||||
"join-room": true;
|
||||
};
|
||||
|
||||
export function createNavigation(): Navigation<SegmentType> {
|
||||
@ -51,7 +53,7 @@ function allowsChild(parent: Segment<SegmentType> | undefined, child: Segment<Se
|
||||
// allowed root segments
|
||||
return type === "login" || type === "session" || type === "sso" || type === "logout";
|
||||
case "session":
|
||||
return type === "room" || type === "rooms" || type === "settings" || type === "create-room" || type === "join-room";
|
||||
return type === "room" || type === "rooms" || type === "settings" || type === "create-room" || type === "join-room" || type === "device-verification";
|
||||
case "rooms":
|
||||
// downside of the approach: both of these will control which tile is selected
|
||||
return type === "room" || type === "empty-grid-tile";
|
||||
|
@ -26,6 +26,7 @@ import {RoomGridViewModel} from "./RoomGridViewModel.js";
|
||||
import {SettingsViewModel} from "./settings/SettingsViewModel.js";
|
||||
import {CreateRoomViewModel} from "./CreateRoomViewModel.js";
|
||||
import {JoinRoomViewModel} from "./JoinRoomViewModel";
|
||||
import {DeviceVerificationViewModel} from "./verification/DeviceVerificationViewModel";
|
||||
import {ViewModel} from "../ViewModel";
|
||||
import {RoomViewModelObservable} from "./RoomViewModelObservable.js";
|
||||
import {RightPanelViewModel} from "./rightpanel/RightPanelViewModel.js";
|
||||
@ -48,6 +49,7 @@ export class SessionViewModel extends ViewModel {
|
||||
this._gridViewModel = null;
|
||||
this._createRoomViewModel = null;
|
||||
this._joinRoomViewModel = null;
|
||||
this._verificationViewModel = null;
|
||||
this._toastCollectionViewModel = this.track(new ToastCollectionViewModel(this.childOptions({
|
||||
session: this._client.session,
|
||||
})));
|
||||
@ -95,6 +97,12 @@ export class SessionViewModel extends ViewModel {
|
||||
}));
|
||||
this._updateJoinRoom(joinRoom.get());
|
||||
|
||||
const verification = this.navigation.observe("device-verification");
|
||||
this.track(verification.subscribe((verificationOpen) => {
|
||||
this._updateVerification(verificationOpen);
|
||||
}));
|
||||
this._updateVerification(verification.get());
|
||||
|
||||
const lightbox = this.navigation.observe("lightbox");
|
||||
this.track(lightbox.subscribe(eventId => {
|
||||
this._updateLightbox(eventId);
|
||||
@ -143,7 +151,8 @@ export class SessionViewModel extends ViewModel {
|
||||
this._gridViewModel ||
|
||||
this._settingsViewModel ||
|
||||
this._createRoomViewModel ||
|
||||
this._joinRoomViewModel
|
||||
this._joinRoomViewModel ||
|
||||
this._verificationViewModel
|
||||
);
|
||||
}
|
||||
|
||||
@ -179,6 +188,10 @@ export class SessionViewModel extends ViewModel {
|
||||
return this._joinRoomViewModel;
|
||||
}
|
||||
|
||||
get verificationViewModel() {
|
||||
return this._verificationViewModel;
|
||||
}
|
||||
|
||||
get toastCollectionViewModel() {
|
||||
return this._toastCollectionViewModel;
|
||||
}
|
||||
@ -327,6 +340,16 @@ export class SessionViewModel extends ViewModel {
|
||||
this.emitChange("activeMiddleViewModel");
|
||||
}
|
||||
|
||||
_updateVerification(verificationOpen) {
|
||||
if (this._verificationViewModel) {
|
||||
this._verificationViewModel = this.disposeTracked(this._verificationViewModel);
|
||||
}
|
||||
if (verificationOpen) {
|
||||
this._verificationViewModel = this.track(new DeviceVerificationViewModel(this.childOptions({ session: this._client.session })));
|
||||
}
|
||||
this.emitChange("activeMiddleViewModel");
|
||||
}
|
||||
|
||||
_updateLightbox(eventId) {
|
||||
if (this._lightboxViewModel) {
|
||||
this._lightboxViewModel = this.disposeTracked(this._lightboxViewModel);
|
||||
|
@ -157,6 +157,10 @@ export class KeyBackupViewModel extends ViewModel<SegmentType, Options> {
|
||||
}
|
||||
}
|
||||
|
||||
navigateToVerification(): void {
|
||||
this.navigation.push("device-verification", true);
|
||||
}
|
||||
|
||||
get backupWriteStatus(): BackupWriteStatus {
|
||||
const keyBackup = this._keyBackup;
|
||||
if (!keyBackup || keyBackup.version === undefined) {
|
||||
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
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 {Options as BaseOptions} from "../../ViewModel";
|
||||
import {SegmentType} from "../../navigation/index";
|
||||
import {ErrorReportViewModel} from "../../ErrorReportViewModel";
|
||||
import {WaitingForOtherUserViewModel} from "./stages/WaitingForOtherUserViewModel";
|
||||
import {VerificationCancelledViewModel} from "./stages/VerificationCancelledViewModel";
|
||||
import {SelectMethodViewModel} from "./stages/SelectMethodViewModel";
|
||||
import {VerifyEmojisViewModel} from "./stages/VerifyEmojisViewModel";
|
||||
import {VerificationCompleteViewModel} from "./stages/VerificationCompleteViewModel";
|
||||
import type {Session} from "../../../matrix/Session.js";
|
||||
import type {SASVerification} from "../../../matrix/verification/SAS/SASVerification";
|
||||
|
||||
type Options = BaseOptions & {
|
||||
session: Session;
|
||||
};
|
||||
|
||||
export class DeviceVerificationViewModel extends ErrorReportViewModel<SegmentType, Options> {
|
||||
private session: Session;
|
||||
private sas: SASVerification;
|
||||
private _currentStageViewModel: any;
|
||||
|
||||
constructor(options: Readonly<Options>) {
|
||||
super(options);
|
||||
this.session = options.session;
|
||||
this.createAndStartSasVerification();
|
||||
this._currentStageViewModel = this.track(
|
||||
new WaitingForOtherUserViewModel(
|
||||
this.childOptions({ sas: this.sas })
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async createAndStartSasVerification(): Promise<void> {
|
||||
await this.logAndCatch("DeviceVerificationViewModel.createAndStartSasVerification", (log) => {
|
||||
// todo: can crossSigning be undefined?
|
||||
const crossSigning = this.session.crossSigning;
|
||||
// todo: should be called createSasVerification
|
||||
this.sas = crossSigning.startVerification(this.session.userId, undefined, log);
|
||||
const emitter = this.sas.eventEmitter;
|
||||
this.track(emitter.disposableOn("SelectVerificationStage", (stage) => {
|
||||
this.createViewModelAndEmit(
|
||||
new SelectMethodViewModel(this.childOptions({ sas: this.sas, stage: stage!, }))
|
||||
);
|
||||
}));
|
||||
this.track(emitter.disposableOn("EmojiGenerated", (stage) => {
|
||||
this.createViewModelAndEmit(
|
||||
new VerifyEmojisViewModel(this.childOptions({ stage: stage!, }))
|
||||
);
|
||||
}));
|
||||
this.track(emitter.disposableOn("VerificationCancelled", (cancellation) => {
|
||||
this.createViewModelAndEmit(
|
||||
new VerificationCancelledViewModel(
|
||||
this.childOptions({ cancellationCode: cancellation!.code, cancelledByUs: cancellation!.cancelledByUs, })
|
||||
));
|
||||
}));
|
||||
this.track(emitter.disposableOn("VerificationCompleted", (deviceId) => {
|
||||
this.createViewModelAndEmit(
|
||||
new VerificationCompleteViewModel(this.childOptions({ deviceId: deviceId! }))
|
||||
);
|
||||
}));
|
||||
return this.sas.start();
|
||||
});
|
||||
}
|
||||
|
||||
private createViewModelAndEmit(vm) {
|
||||
this._currentStageViewModel = this.disposeTracked(this._currentStageViewModel);
|
||||
this._currentStageViewModel = this.track(vm);
|
||||
this.emitChange("currentStageViewModel");
|
||||
}
|
||||
|
||||
get currentStageViewModel() {
|
||||
return this._currentStageViewModel;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
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 {SegmentType} from "../../../navigation/index";
|
||||
import {ErrorReportViewModel} from "../../../ErrorReportViewModel";
|
||||
import type {Options as BaseOptions} from "../../../ViewModel";
|
||||
import type {Session} from "../../../../matrix/Session.js";
|
||||
import type {SASVerification} from "../../../../matrix/verification/SAS/SASVerification";
|
||||
import type {SelectVerificationMethodStage} from "../../../../matrix/verification/SAS/stages/SelectVerificationMethodStage";
|
||||
|
||||
type Options = BaseOptions & {
|
||||
sas: SASVerification;
|
||||
stage: SelectVerificationMethodStage;
|
||||
session: Session;
|
||||
};
|
||||
|
||||
export class SelectMethodViewModel extends ErrorReportViewModel<SegmentType, Options> {
|
||||
public hasProceeded: boolean = false;
|
||||
|
||||
async proceed() {
|
||||
await this.logAndCatch("SelectMethodViewModel.proceed", async (log) => {
|
||||
await this.options.stage.selectEmojiMethod(log);
|
||||
this.hasProceeded = true;
|
||||
this.emitChange("hasProceeded");
|
||||
});
|
||||
}
|
||||
|
||||
async cancel() {
|
||||
await this.logAndCatch("SelectMethodViewModel.cancel", async () => {
|
||||
await this.options.sas.abort();
|
||||
});
|
||||
}
|
||||
|
||||
get deviceName() {
|
||||
return this.options.stage.otherDeviceName;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
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 {ViewModel, Options as BaseOptions} from "../../../ViewModel";
|
||||
import {SegmentType} from "../../../navigation/index";
|
||||
import {CancelTypes} from "../../../../matrix/verification/SAS/channel/types";
|
||||
|
||||
type Options = BaseOptions & {
|
||||
cancellationCode: CancelTypes;
|
||||
cancelledByUs: boolean;
|
||||
};
|
||||
|
||||
export class VerificationCancelledViewModel extends ViewModel<SegmentType, Options> {
|
||||
get cancelCode(): CancelTypes {
|
||||
return this.options.cancellationCode;
|
||||
}
|
||||
|
||||
get isCancelledByUs(): boolean {
|
||||
return this.options.cancelledByUs;
|
||||
}
|
||||
|
||||
gotoSettings() {
|
||||
this.navigation.push("settings", true);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
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 {SegmentType} from "../../../navigation/index";
|
||||
import {ErrorReportViewModel} from "../../../ErrorReportViewModel";
|
||||
import type {Options as BaseOptions} from "../../../ViewModel";
|
||||
import type {Session} from "../../../../matrix/Session.js";
|
||||
|
||||
type Options = BaseOptions & {
|
||||
deviceId: string;
|
||||
session: Session;
|
||||
};
|
||||
|
||||
export class VerificationCompleteViewModel extends ErrorReportViewModel<SegmentType, Options> {
|
||||
get otherDeviceId(): string {
|
||||
return this.options.deviceId;
|
||||
}
|
||||
|
||||
gotoSettings() {
|
||||
this.navigation.push("settings", true);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
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 {SegmentType} from "../../../navigation/index";
|
||||
import {ErrorReportViewModel} from "../../../ErrorReportViewModel";
|
||||
import type {Options as BaseOptions} from "../../../ViewModel";
|
||||
import type {Session} from "../../../../matrix/Session.js";
|
||||
import type {CalculateSASStage} from "../../../../matrix/verification/SAS/stages/CalculateSASStage";
|
||||
|
||||
type Options = BaseOptions & {
|
||||
stage: CalculateSASStage;
|
||||
session: Session;
|
||||
};
|
||||
|
||||
export class VerifyEmojisViewModel extends ErrorReportViewModel<SegmentType, Options> {
|
||||
public isWaiting: boolean = false;
|
||||
|
||||
async setEmojiMatch(match: boolean) {
|
||||
await this.logAndCatch("VerifyEmojisViewModel.setEmojiMatch", async () => {
|
||||
await this.options.stage.setEmojiMatch(match);
|
||||
this.isWaiting = true;
|
||||
this.emitChange("isWaiting");
|
||||
});
|
||||
}
|
||||
|
||||
get emojis() {
|
||||
return this.options.stage.emoji;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
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 {ViewModel, Options as BaseOptions} from "../../../ViewModel";
|
||||
import {SegmentType} from "../../../navigation/index";
|
||||
import type {SASVerification} from "../../../../matrix/verification/SAS/SASVerification";
|
||||
|
||||
type Options = BaseOptions & {
|
||||
sas: SASVerification;
|
||||
};
|
||||
|
||||
export class WaitingForOtherUserViewModel extends ViewModel<SegmentType, Options> {
|
||||
async cancel() {
|
||||
await this.options.sas.abort();
|
||||
}
|
||||
}
|
@ -84,6 +84,10 @@ export class SASVerification extends EventEmitter<SASProgressEvents> {
|
||||
}
|
||||
}
|
||||
|
||||
async abort() {
|
||||
await this.channel.cancelVerification(CancelTypes.UserCancelled);
|
||||
}
|
||||
|
||||
async start() {
|
||||
try {
|
||||
let stage = this.startStage;
|
||||
@ -98,6 +102,9 @@ export class SASVerification extends EventEmitter<SASProgressEvents> {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (this.channel.isCancelled) {
|
||||
this.eventEmitter.emit("VerificationCancelled", this.channel.cancellation);
|
||||
}
|
||||
this.olmSas.free();
|
||||
this.timeout.abort();
|
||||
this.finished = true;
|
||||
|
@ -49,6 +49,8 @@ export interface IChannel {
|
||||
acceptMessage: any;
|
||||
startMessage: any;
|
||||
initiatedByUs: boolean;
|
||||
isCancelled: boolean;
|
||||
cancellation: { code: CancelTypes, cancelledByUs: boolean };
|
||||
id: string;
|
||||
otherUserDeviceId: string;
|
||||
}
|
||||
@ -78,7 +80,7 @@ export class ToDeviceChannel extends Disposables implements IChannel {
|
||||
public startMessage: any;
|
||||
public id: string;
|
||||
private _initiatedByUs: boolean;
|
||||
private _isCancelled = false;
|
||||
private _cancellation: { code: CancelTypes, cancelledByUs: boolean };
|
||||
|
||||
/**
|
||||
*
|
||||
@ -116,8 +118,12 @@ export class ToDeviceChannel extends Disposables implements IChannel {
|
||||
}
|
||||
}
|
||||
|
||||
get cancellation() {
|
||||
return this._cancellation;
|
||||
};
|
||||
|
||||
get isCancelled(): boolean {
|
||||
return this._isCancelled;
|
||||
return !!this._cancellation;
|
||||
}
|
||||
|
||||
async send(eventType: VerificationEventType, content: any, log: ILogItem): Promise<void> {
|
||||
@ -198,8 +204,8 @@ export class ToDeviceChannel extends Disposables implements IChannel {
|
||||
this.handleReadyMessage(event, log);
|
||||
return;
|
||||
}
|
||||
if (event.type === VerificationEventType.Cancel) {
|
||||
this._isCancelled = true;
|
||||
if (event.type === VerificationEventTypes.Cancel) {
|
||||
this._cancellation = { code: event.content.code, cancelledByUs: false };
|
||||
this.dispose();
|
||||
return;
|
||||
}
|
||||
@ -234,7 +240,7 @@ export class ToDeviceChannel extends Disposables implements IChannel {
|
||||
const payload = {
|
||||
messages: {
|
||||
[this.otherUserId]: {
|
||||
[this.otherUserDeviceId]: {
|
||||
[this.otherUserDeviceId ?? "*"]: {
|
||||
code: cancellationType,
|
||||
reason: messageFromErrorType[cancellationType],
|
||||
transaction_id: this.id,
|
||||
@ -242,8 +248,8 @@ export class ToDeviceChannel extends Disposables implements IChannel {
|
||||
}
|
||||
}
|
||||
}
|
||||
await this.hsApi.sendToDevice(VerificationEventType.Cancel, payload, makeTxnId(), { log }).response();
|
||||
this._isCancelled = true;
|
||||
await this.hsApi.sendToDevice(VerificationEventTypes.Cancel, payload, makeTxnId(), { log }).response();
|
||||
this._cancellation = { code: cancellationType, cancelledByUs: true };
|
||||
this.dispose();
|
||||
});
|
||||
}
|
||||
@ -257,8 +263,8 @@ export class ToDeviceChannel extends Disposables implements IChannel {
|
||||
}
|
||||
}
|
||||
|
||||
waitForEvent(eventType: VerificationEventType): Promise<any> {
|
||||
if (this._isCancelled) {
|
||||
waitForEvent(eventType: VerificationEventTypes): Promise<any> {
|
||||
if (this.isCancelled) {
|
||||
throw new VerificationCancelledError();
|
||||
}
|
||||
// Check if we already received the message
|
||||
|
@ -17,6 +17,7 @@ export class MockChannel implements ITestChannel {
|
||||
public initiatedByUs: boolean;
|
||||
public startMessage: any;
|
||||
public isCancelled: boolean = false;
|
||||
public cancellation: { code: CancelTypes; cancelledByUs: boolean; };
|
||||
private olmSas: any;
|
||||
|
||||
constructor(
|
||||
|
@ -23,9 +23,11 @@ import type {ILogItem} from "../../../../logging/types";
|
||||
export class SelectVerificationMethodStage extends BaseSASVerificationStage {
|
||||
private hasSentStartMessage = false;
|
||||
private allowSelection = true;
|
||||
public otherDeviceName: string;
|
||||
|
||||
async completeStage() {
|
||||
await this.log.wrap("SelectVerificationMethodStage.completeStage", async (log) => {
|
||||
await this.findDeviceName(log);
|
||||
this.eventEmitter.emit("SelectVerificationStage", this);
|
||||
const startMessage = this.channel.waitForEvent(VerificationEventType.Start);
|
||||
const acceptMessage = this.channel.waitForEvent(VerificationEventType.Accept);
|
||||
@ -81,6 +83,13 @@ export class SelectVerificationMethodStage extends BaseSASVerificationStage {
|
||||
});
|
||||
}
|
||||
|
||||
private async findDeviceName(log: ILogItem) {
|
||||
await log.wrap("SelectVerificationMethodStage.findDeviceName", async () => {
|
||||
const device = await this.options.deviceTracker.deviceForId(this.otherUserId, this.otherUserDeviceId, this.options.hsApi, log);
|
||||
this.otherDeviceName = device.displayName;
|
||||
})
|
||||
}
|
||||
|
||||
async selectEmojiMethod(log: ILogItem) {
|
||||
if (!this.allowSelection) { return; }
|
||||
const content = {
|
||||
|
@ -19,7 +19,8 @@ import {VerificationEventType} from "../channel/types";
|
||||
export class SendDoneStage extends BaseSASVerificationStage {
|
||||
async completeStage() {
|
||||
await this.log.wrap("SendDoneStage.completeStage", async (log) => {
|
||||
await this.channel.send(VerificationEventType.Done, {}, log);
|
||||
this.eventEmitter.emit("VerificationCompleted", this.otherUserDeviceId);
|
||||
await this.channel.send(VerificationEventTypes.Done, {}, log);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -13,10 +13,13 @@ 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 {CancelTypes} from "./channel/types";
|
||||
import {CalculateSASStage} from "./stages/CalculateSASStage";
|
||||
import {SelectVerificationMethodStage} from "./stages/SelectVerificationMethodStage";
|
||||
|
||||
export type SASProgressEvents = {
|
||||
SelectVerificationStage: SelectVerificationMethodStage;
|
||||
EmojiGenerated: CalculateSASStage;
|
||||
VerificationCompleted: string;
|
||||
VerificationCancelled: { code: CancelTypes, cancelledByUs: boolean };
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 3.05V9.27C2 15.63 9 17 9 17C9 17 16 15.63 16 9.27V3.05L9 1L2 3.05ZM11.9405 5.5196C12.1305 5.3396 12.4305 5.3496 12.6105 5.5396C12.7705 5.7196 12.7705 5.9896 12.6305 6.1696L8.41047 11.2796L8.38047 11.3196C8.10047 11.6596 7.59047 11.7096 7.25047 11.4296C7.22027 11.4145 7.19577 11.388 7.17266 11.363C7.16517 11.3549 7.15782 11.347 7.15047 11.3396L5.34047 9.2596C5.14047 9.0196 5.16047 8.6596 5.40047 8.4596C5.60047 8.2796 5.89047 8.2796 6.10047 8.4196L7.67047 9.5196L11.9405 5.5196Z" fill="#ff00ff"/>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 657 B After Width: | Height: | Size: 658 B |
@ -1354,3 +1354,91 @@ button.RoomDetailsView_row::after {
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.VerificationCompleteView,
|
||||
.DeviceVerificationView,
|
||||
.SelectMethodView {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.VerificationCompleteView__heading,
|
||||
.VerifyEmojisView__heading,
|
||||
.SelectMethodView__heading,
|
||||
.WaitingForOtherUserView__heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.VerificationCompleteView>*,
|
||||
.SelectMethodView>*,
|
||||
.VerifyEmojisView>*,
|
||||
.WaitingForOtherUserView>* {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.VerificationCompleteView__title,
|
||||
.VerifyEmojisView__title,
|
||||
.SelectMethodView__title,
|
||||
.WaitingForOtherUserView__title {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.VerificationCancelledView__description,
|
||||
.VerificationCompleteView__description,
|
||||
.VerifyEmojisView__description,
|
||||
.SelectMethodView__description,
|
||||
.WaitingForOtherUserView__description {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.VerificationCancelledView__actions,
|
||||
.SelectMethodView__actions,
|
||||
.VerifyEmojisView__actions,
|
||||
.WaitingForOtherUserView__actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.EmojiCollection {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.EmojiContainer__emoji {
|
||||
font-size: 3.2rem;
|
||||
}
|
||||
|
||||
.VerifyEmojisView__waiting,
|
||||
.EmojiContainer__name,
|
||||
.EmojiContainer__emoji {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.EmojiContainer__name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.VerifyEmojisView__waiting {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.VerificationCompleteView__icon {
|
||||
background: url("./icons//verified.svg?primary=accent-color") no-repeat;
|
||||
background-size: contain;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import {CreateRoomView} from "./CreateRoomView.js";
|
||||
import {RightPanelView} from "./rightpanel/RightPanelView.js";
|
||||
import {viewClassForTile} from "./room/common";
|
||||
import {JoinRoomView} from "./JoinRoomView";
|
||||
import {DeviceVerificationView} from "./verification/DeviceVerificationView";
|
||||
import {ToastCollectionView} from "./toast/ToastCollectionView";
|
||||
|
||||
export class SessionView extends TemplateView {
|
||||
@ -53,6 +54,8 @@ export class SessionView extends TemplateView {
|
||||
return new CreateRoomView(vm.createRoomViewModel);
|
||||
} else if (vm.joinRoomViewModel) {
|
||||
return new JoinRoomView(vm.joinRoomViewModel);
|
||||
} else if (vm.verificationViewModel) {
|
||||
return new DeviceVerificationView(vm.verificationViewModel);
|
||||
} else if (vm.currentRoomViewModel) {
|
||||
if (vm.currentRoomViewModel.kind === "invite") {
|
||||
return new InviteView(vm.currentRoomViewModel);
|
||||
|
@ -62,11 +62,24 @@ export class KeyBackupSettingsView extends TemplateView<KeyBackupViewModel> {
|
||||
return t.p("Cross-signing master key found and trusted.")
|
||||
}),
|
||||
t.if(vm => vm.canSignOwnDevice, t => {
|
||||
return t.button({
|
||||
onClick: disableTargetCallback(async () => {
|
||||
await vm.signOwnDevice();
|
||||
})
|
||||
}, "Sign own device");
|
||||
return t.div([
|
||||
t.button(
|
||||
{
|
||||
onClick: disableTargetCallback(async (evt) => {
|
||||
await vm.signOwnDevice();
|
||||
}),
|
||||
},
|
||||
"Sign own device"
|
||||
),
|
||||
t.button(
|
||||
{
|
||||
onClick: disableTargetCallback(async () => {
|
||||
vm.navigateToVerification();
|
||||
}),
|
||||
},
|
||||
"Verify by emoji"
|
||||
),
|
||||
]);
|
||||
}),
|
||||
|
||||
]);
|
||||
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
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 {TemplateView} from "../../general/TemplateView";
|
||||
import {WaitingForOtherUserViewModel} from "../../../../../domain/session/verification/stages/WaitingForOtherUserViewModel";
|
||||
import {DeviceVerificationViewModel} from "../../../../../domain/session/verification/DeviceVerificationViewModel";
|
||||
import {VerificationCancelledViewModel} from "../../../../../domain/session/verification/stages/VerificationCancelledViewModel";
|
||||
import {WaitingForOtherUserView} from "./stages/WaitingForOtherUserView";
|
||||
import {VerificationCancelledView} from "./stages/VerificationCancelledView";
|
||||
import {SelectMethodViewModel} from "../../../../../domain/session/verification/stages/SelectMethodViewModel";
|
||||
import {SelectMethodView} from "./stages/SelectMethodView";
|
||||
import {VerifyEmojisViewModel} from "../../../../../domain/session/verification/stages/VerifyEmojisViewModel";
|
||||
import {VerifyEmojisView} from "./stages/VerifyEmojisView";
|
||||
import {VerificationCompleteViewModel} from "../../../../../domain/session/verification/stages/VerificationCompleteViewModel";
|
||||
import {VerificationCompleteView} from "./stages/VerificationCompleteView";
|
||||
|
||||
export class DeviceVerificationView extends TemplateView<DeviceVerificationViewModel> {
|
||||
render(t, vm) {
|
||||
return t.div({
|
||||
className: {
|
||||
"middle": true,
|
||||
"DeviceVerificationView": true,
|
||||
}
|
||||
}, [
|
||||
t.mapView(vm => vm.currentStageViewModel, (stageVm) => {
|
||||
if (stageVm instanceof WaitingForOtherUserViewModel) {
|
||||
return new WaitingForOtherUserView(stageVm);
|
||||
}
|
||||
else if (stageVm instanceof VerificationCancelledViewModel) {
|
||||
return new VerificationCancelledView(stageVm);
|
||||
}
|
||||
else if (stageVm instanceof SelectMethodViewModel) {
|
||||
return new SelectMethodView(stageVm);
|
||||
}
|
||||
else if (stageVm instanceof VerifyEmojisViewModel) {
|
||||
return new VerifyEmojisView(stageVm);
|
||||
}
|
||||
else if (stageVm instanceof VerificationCompleteViewModel) {
|
||||
return new VerificationCompleteView(stageVm);
|
||||
}
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
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 {TemplateView} from "../../../general/TemplateView";
|
||||
import {spinner} from "../../../common.js"
|
||||
import type {SelectMethodViewModel} from "../../../../../../domain/session/verification/stages/SelectMethodViewModel";
|
||||
|
||||
export class SelectMethodView extends TemplateView<SelectMethodViewModel> {
|
||||
render(t) {
|
||||
return t.div({ className: "SelectMethodView" }, [
|
||||
t.map(vm => vm.hasProceeded, (hasProceeded, t, vm) => {
|
||||
if (hasProceeded) {
|
||||
return spinner(t);
|
||||
}
|
||||
else return t.div([
|
||||
t.div({ className: "SelectMethodView__heading" }, [
|
||||
t.h2( { className: "SelectMethodView__title" }, vm.i18n`Verify device '${vm.deviceName}' by comparing emojis?`),
|
||||
]),
|
||||
t.p({ className: "SelectMethodView__description" },
|
||||
vm.i18n`You are about to verify your other device by comparing emojis.`
|
||||
),
|
||||
t.div({ className: "SelectMethodView__actions" }, [
|
||||
t.button(
|
||||
{
|
||||
className: {
|
||||
"button-action": true,
|
||||
primary: true,
|
||||
destructive: true,
|
||||
},
|
||||
onclick: () => vm.cancel(),
|
||||
},
|
||||
"Cancel"
|
||||
),
|
||||
t.button(
|
||||
{
|
||||
className: {
|
||||
"button-action": true,
|
||||
primary: true,
|
||||
},
|
||||
onclick: () => vm.proceed(),
|
||||
},
|
||||
"Proceed"
|
||||
),
|
||||
]),
|
||||
]);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
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 {TemplateView} from "../../../general/TemplateView";
|
||||
import {VerificationCancelledViewModel} from "../../../../../../domain/session/verification/stages/VerificationCancelledViewModel";
|
||||
import {CancelTypes} from "../../../../../../matrix/verification/SAS/channel/types";
|
||||
|
||||
export class VerificationCancelledView extends TemplateView<VerificationCancelledViewModel> {
|
||||
render(t, vm: VerificationCancelledViewModel) {
|
||||
const headerTextStart = vm.isCancelledByUs ? "You" : "The other device";
|
||||
|
||||
return t.div(
|
||||
{
|
||||
className: "VerificationCancelledView",
|
||||
},
|
||||
[
|
||||
t.h2(
|
||||
{ className: "VerificationCancelledView__title" },
|
||||
vm.i18n`${headerTextStart} cancelled the verification!`
|
||||
),
|
||||
t.p(
|
||||
{ className: "VerificationCancelledView__description" },
|
||||
vm.i18n`${this.getDescriptionFromCancellationCode(vm.cancelCode, vm.isCancelledByUs)}`
|
||||
),
|
||||
t.div({ className: "VerificationCancelledView__actions" }, [
|
||||
t.button({
|
||||
className: {
|
||||
"button-action": true,
|
||||
"primary": true,
|
||||
},
|
||||
onclick: () => vm.gotoSettings(),
|
||||
}, "Got it")
|
||||
]),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
getDescriptionFromCancellationCode(code: CancelTypes, isCancelledByUs: boolean): string {
|
||||
const descriptionsWhenWeCancelled = {
|
||||
// [CancelTypes.UserCancelled]: NO_NEED_FOR_DESCRIPTION_HERE
|
||||
[CancelTypes.InvalidMessage]: "You other device sent an invalid message.",
|
||||
[CancelTypes.KeyMismatch]: "The key could not be verified.",
|
||||
// [CancelTypes.OtherDeviceAccepted]: "Another device has accepted this request.",
|
||||
[CancelTypes.TimedOut]: "The verification process timed out.",
|
||||
[CancelTypes.UnexpectedMessage]: "Your other device sent an unexpected message.",
|
||||
[CancelTypes.UnknownMethod]: "Your other device is using an unknown method for verification.",
|
||||
[CancelTypes.UnknownTransaction]: "Your other device sent a message with an unknown transaction id.",
|
||||
[CancelTypes.UserMismatch]: "The expected user did not match the user verified.",
|
||||
[CancelTypes.MismatchedCommitment]: "The hash commitment does not match.",
|
||||
[CancelTypes.MismatchedSAS]: "The emoji/decimal did not match.",
|
||||
}
|
||||
const descriptionsWhenTheyCancelled = {
|
||||
[CancelTypes.UserCancelled]: "Your other device cancelled the verification!",
|
||||
[CancelTypes.InvalidMessage]: "Invalid message sent to the other device.",
|
||||
[CancelTypes.KeyMismatch]: "The other device could not verify our keys",
|
||||
// [CancelTypes.OtherDeviceAccepted]: "Another device has accepted this request.",
|
||||
[CancelTypes.TimedOut]: "The verification process timed out.",
|
||||
[CancelTypes.UnexpectedMessage]: "Unexpected message sent to the other device.",
|
||||
[CancelTypes.UnknownMethod]: "Your other device does not understand the method you chose",
|
||||
[CancelTypes.UnknownTransaction]: "Your other device rejected our message.",
|
||||
[CancelTypes.UserMismatch]: "The expected user did not match the user verified.",
|
||||
[CancelTypes.MismatchedCommitment]: "Your other device was not able to verify the hash commitment",
|
||||
[CancelTypes.MismatchedSAS]: "The emoji/decimal did not match.",
|
||||
}
|
||||
const map = isCancelledByUs ? descriptionsWhenWeCancelled : descriptionsWhenTheyCancelled;
|
||||
return map[code] ?? "";
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
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 {TemplateView} from "../../../general/TemplateView";
|
||||
import type {VerificationCompleteViewModel} from "../../../../../../domain/session/verification/stages/VerificationCompleteViewModel";
|
||||
|
||||
export class VerificationCompleteView extends TemplateView<VerificationCompleteViewModel> {
|
||||
render(t, vm: VerificationCompleteViewModel) {
|
||||
return t.div({ className: "VerificationCompleteView" }, [
|
||||
t.div({className: "VerificationCompleteView__icon"}),
|
||||
t.div({ className: "VerificationCompleteView__heading" }, [
|
||||
t.h2(
|
||||
{ className: "VerificationCompleteView__title" },
|
||||
vm.i18n`Verification completed successfully!`
|
||||
),
|
||||
]),
|
||||
t.p(
|
||||
{ className: "VerificationCompleteView__description" },
|
||||
vm.i18n`You successfully verified device ${vm.otherDeviceId}`
|
||||
),
|
||||
t.div({ className: "VerificationCompleteView__actions" }, [
|
||||
t.button({
|
||||
className: {
|
||||
"button-action": true,
|
||||
"primary": true,
|
||||
},
|
||||
onclick: () => vm.gotoSettings(),
|
||||
}, "Got it")
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
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 {TemplateView} from "../../../general/TemplateView";
|
||||
import {spinner} from "../../../common.js"
|
||||
import type {VerifyEmojisViewModel} from "../../../../../../domain/session/verification/stages/VerifyEmojisViewModel";
|
||||
|
||||
export class VerifyEmojisView extends TemplateView<VerifyEmojisViewModel> {
|
||||
render(t, vm: VerifyEmojisViewModel) {
|
||||
const emojiList = vm.emojis.reduce((acc, [emoji, name]) => {
|
||||
const e = t.div({ className: "EmojiContainer" }, [
|
||||
t.div({ className: "EmojiContainer__emoji" }, emoji),
|
||||
t.div({ className: "EmojiContainer__name" }, name),
|
||||
]);
|
||||
acc.push(e);
|
||||
return acc;
|
||||
}, [] as any);
|
||||
const emojiCollection = t.div({ className: "EmojiCollection" }, emojiList);
|
||||
return t.div({ className: "VerifyEmojisView" }, [
|
||||
t.div({ className: "VerifyEmojisView__heading" }, [
|
||||
t.h2(
|
||||
{ className: "VerifyEmojisView__title" },
|
||||
vm.i18n`Do the emojis match?`
|
||||
),
|
||||
]),
|
||||
t.p(
|
||||
{ className: "VerifyEmojisView__description" },
|
||||
vm.i18n`Confirm the emoji below are displayed on both devices, in the same order:`
|
||||
),
|
||||
t.div({ className: "VerifyEmojisView__emojis" }, emojiCollection),
|
||||
t.map(vm => vm.isWaiting, (isWaiting, t, vm) => {
|
||||
if (isWaiting) {
|
||||
return t.div({ className: "VerifyEmojisView__waiting" }, [
|
||||
spinner(t),
|
||||
t.span(vm.i18n`Waiting for you to verify on your other device`),
|
||||
]);
|
||||
}
|
||||
else {
|
||||
return t.div({ className: "VerifyEmojisView__actions" }, [
|
||||
t.button(
|
||||
{
|
||||
className: {
|
||||
"button-action": true,
|
||||
primary: true,
|
||||
destructive: true,
|
||||
},
|
||||
onclick: () => vm.setEmojiMatch(false),
|
||||
},
|
||||
vm.i18n`They don't match`
|
||||
),
|
||||
t.button(
|
||||
{
|
||||
className: {
|
||||
"button-action": true,
|
||||
primary: true,
|
||||
},
|
||||
onclick: () => vm.setEmojiMatch(true),
|
||||
},
|
||||
vm.i18n`They match`
|
||||
),
|
||||
]);
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
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 {TemplateView} from "../../../general/TemplateView";
|
||||
import {spinner} from "../../../common.js";
|
||||
import {WaitingForOtherUserViewModel} from "../../../../../../domain/session/verification/stages/WaitingForOtherUserViewModel";
|
||||
|
||||
export class WaitingForOtherUserView extends TemplateView<WaitingForOtherUserViewModel> {
|
||||
render(t, vm) {
|
||||
return t.div({ className: "WaitingForOtherUserView" }, [
|
||||
t.div({ className: "WaitingForOtherUserView__heading" }, [
|
||||
spinner(t),
|
||||
t.h2(
|
||||
{ className: "WaitingForOtherUserView__title" },
|
||||
vm.i18n`Waiting for any of your device to accept the verification request`
|
||||
),
|
||||
]),
|
||||
t.p({ className: "WaitingForOtherUserView__description" },
|
||||
vm.i18n`Accept the request from the device you wish to verify!`
|
||||
),
|
||||
t.div({ className: "WaitingForOtherUserView__actions" },
|
||||
t.button({
|
||||
className: {
|
||||
"button-action": true,
|
||||
"primary": true,
|
||||
"destructive": true,
|
||||
},
|
||||
onclick: () => vm.cancel(),
|
||||
}, "Cancel")
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user