mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2025-02-02 07:31:38 +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 };
|
||||
}
|
||||
|
@ -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 () => {
|
||||
return t.div([
|
||||
t.button(
|
||||
{
|
||||
onClick: disableTargetCallback(async (evt) => {
|
||||
await vm.signOwnDevice();
|
||||
})
|
||||
}, "Sign own device");
|
||||
}),
|
||||
},
|
||||
"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