--- /dev/null
+import React, { useState, useEffect, useRef } from 'react';
+import './ImportE2ERoomKeys.scss';
+import EventEmitter from 'events';
+
+import initMatrix from '../../../client/initMatrix';
+import decryptMegolmKeyFile from '../../../util/decryptE2ERoomKeys';
+
+import Text from '../../atoms/text/Text';
+import IconButton from '../../atoms/button/IconButton';
+import Button from '../../atoms/button/Button';
+import Input from '../../atoms/input/Input';
+import Spinner from '../../atoms/spinner/Spinner';
+
+import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg';
+
+const viewEvent = new EventEmitter();
+
+async function tryDecrypt(file, password) {
+ try {
+ const arrayBuffer = await file.arrayBuffer();
+ viewEvent.emit('importing', true);
+ viewEvent.emit('status', 'Decrypting file...');
+ const keys = await decryptMegolmKeyFile(arrayBuffer, password);
+
+ viewEvent.emit('status', 'Decrypting messages...');
+ await initMatrix.matrixClient.importRoomKeys(JSON.parse(keys));
+
+ viewEvent.emit('status', null);
+ viewEvent.emit('importing', false);
+ } catch (e) {
+ viewEvent.emit('status', e.friendlyText || 'Something went wrong!');
+ viewEvent.emit('importing', false);
+ }
+}
+
+function ImportE2ERoomKeys() {
+ const [keyFile, setKeyFile] = useState(null);
+ const [status, setStatus] = useState(null);
+ const [isImporting, setIsImporting] = useState(false);
+ const inputRef = useRef(null);
+ const passwordRef = useRef(null);
+
+ useEffect(() => {
+ const handleIsImporting = (isImp) => setIsImporting(isImp);
+ const handleStatus = (msg) => setStatus(msg);
+ viewEvent.on('importing', handleIsImporting);
+ viewEvent.on('status', handleStatus);
+
+ return () => {
+ viewEvent.removeListener('importing', handleIsImporting);
+ viewEvent.removeListener('status', handleStatus);
+ }
+ }, []);
+
+ function importE2ERoomKeys() {
+ const password = passwordRef.current.value;
+ if (password === '' || keyFile === null) return;
+ if (isImporting) return;
+
+ tryDecrypt(keyFile, password);
+ }
+
+ function handleFileChange(e) {
+ const file = e.target.files.item(0);
+ passwordRef.current.value = '';
+ setKeyFile(file);
+ setStatus(null);
+ }
+ function removeImportKeysFile() {
+ inputRef.current.value = null;
+ passwordRef.current.value = null;
+ setKeyFile(null);
+ setStatus(null);
+ }
+
+ useEffect(() => {
+ if (!isImporting && status === null) {
+ removeImportKeysFile();
+ }
+ }, [isImporting, status]);
+
+ return (
+ <div className="import-e2e-room-keys">
+ <input ref={inputRef} onChange={handleFileChange} style={{ display: 'none' }} type="file" />
+
+ <form className="import-e2e-room-keys__form" onSubmit={(e) => { e.preventDefault(); importE2ERoomKeys(); }}>
+ { keyFile !== null && (
+ <div className="import-e2e-room-keys__file">
+ <IconButton onClick={removeImportKeysFile} src={CirclePlusIC} tooltip="Remove file" />
+ <Text>{keyFile.name}</Text>
+ </div>
+ )}
+ {keyFile === null && <Button onClick={() => inputRef.current.click()}>Import keys</Button>}
+ <Input forwardRef={passwordRef} type="password" placeholder="password" required />
+ <Button disabled={isImporting} variant="primary" type="submit">Decrypt</Button>
+ </form>
+ { isImporting && status !== null && (
+ <div className="import-e2e-room-keys__process">
+ <Spinner size="small" />
+ <Text variant="b2">{status}</Text>
+ </div>
+ )}
+ {!isImporting && status !== null && <Text className="import-e2e-room-keys__error" variant="b2">{status}</Text>}
+ </div>
+ );
+}
+
+export default ImportE2ERoomKeys;
--- /dev/null
+
+.import-e2e-room-keys {
+ &__file {
+ display: inline-flex;
+ align-items: center;
+ background: var(--bg-surface-low);
+ border-radius: var(--bo-radius);
+ box-shadow: var(--bs-surface-border);
+
+ & button {
+ --parent-height: 46px;
+ width: var(--parent-height);
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ & .ic-raw {
+ background-color: var(--bg-caution);
+ transform: rotate(45deg);
+ }
+
+ & .text {
+ margin-left: var(--sp-tight);
+ margin-right: var(--sp-loose);
+ max-width: 86px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ [dir=rtl] {
+ margin-right: var(--sp-tight);
+ margin-left: var(--sp-loose);
+ }
+ }
+ }
+
+ &__form {
+ display: flex;
+ margin-top: var(--sp-extra-tight);
+
+
+ & .input-container {
+ flex: 1;
+ margin: 0 var(--sp-tight);
+ }
+ }
+
+ &__process {
+ margin-top: var(--sp-tight);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ & .text {
+ margin: 0 var(--sp-tight);
+ }
+ }
+ &__error {
+ margin-top: var(--sp-tight);
+ color: var(--bg-danger);
+ }
+}
\ No newline at end of file