/* Copyright 2024 New Vector Ltd. Copyright 2022 The Matrix.org Foundation C.I.C. Copyright 2017 Vector Creations Ltd SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ import React, { createRef } from "react"; import { MatrixClient } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import * as MegolmExportEncryption from "../../../../utils/MegolmExportEncryption"; import { _t } from "../../../../languageHandler"; import BaseDialog from "../../../../components/views/dialogs/BaseDialog"; import Field from "../../../../components/views/elements/Field"; function readFileAsArrayBuffer(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { if (e.target?.result) { resolve(e.target.result as ArrayBuffer); } else { reject(new Error("Failed to read file due to unknown error")); } }; reader.onerror = reject; reader.readAsArrayBuffer(file); }); } enum Phase { Edit = "edit", Importing = "importing", } interface IProps { matrixClient: MatrixClient; onFinished(imported?: boolean): void; } interface IState { enableSubmit: boolean; phase: Phase; errStr: string | null; passphrase: string; } export default class ImportE2eKeysDialog extends React.Component { private unmounted = false; private file = createRef(); public constructor(props: IProps) { super(props); this.state = { enableSubmit: false, phase: Phase.Edit, errStr: null, passphrase: "", }; } public componentDidMount(): void { this.unmounted = false; } public componentWillUnmount(): void { this.unmounted = true; } private onFormChange = (): void => { const files = this.file.current?.files; this.setState({ enableSubmit: this.state.passphrase !== "" && !!files?.length, }); }; private onPassphraseChange = (ev: React.ChangeEvent): void => { this.setState({ passphrase: ev.target.value }, this.onFormChange); // update general form state too }; private onFormSubmit = (ev: React.FormEvent): boolean => { ev.preventDefault(); // noinspection JSIgnoredPromiseFromCall const file = this.file.current?.files?.[0]; if (file) { this.startImport(file, this.state.passphrase); } return false; }; private startImport(file: File, passphrase: string): Promise { this.setState({ errStr: null, phase: Phase.Importing, }); return readFileAsArrayBuffer(file) .then((arrayBuffer) => { return MegolmExportEncryption.decryptMegolmKeyFile(arrayBuffer, passphrase); }) .then((keys) => { return this.props.matrixClient.getCrypto()!.importRoomKeysAsJson(keys); }) .then(() => { // TODO: it would probably be nice to give some feedback about what we've imported here. this.props.onFinished(true); }) .catch((e) => { logger.error("Error importing e2e keys:", e); if (this.unmounted) { return; } const msg = e.friendlyText || _t("error|unknown"); this.setState({ errStr: msg, phase: Phase.Edit, }); }); } private onCancelClick = (ev: React.MouseEvent): boolean => { ev.preventDefault(); this.props.onFinished(false); return false; }; public render(): React.ReactNode { const disableForm = this.state.phase !== Phase.Edit; return (

{_t("settings|key_export_import|import_description_1")}

{_t("settings|key_export_import|import_description_2")}

{this.state.errStr}
); } }