added UI component for importing e2e keys
authorunknown <ajbura@gmail.com>
Sun, 1 Aug 2021 13:59:15 +0000 (19:29 +0530)
committerunknown <ajbura@gmail.com>
Sun, 1 Aug 2021 13:59:15 +0000 (19:29 +0530)
src/app/molecules/import-e2e-room-keys/ImportE2ERoomKeys.jsx [new file with mode: 0644]
src/app/molecules/import-e2e-room-keys/ImportE2ERoomKeys.scss [new file with mode: 0644]

diff --git a/src/app/molecules/import-e2e-room-keys/ImportE2ERoomKeys.jsx b/src/app/molecules/import-e2e-room-keys/ImportE2ERoomKeys.jsx
new file mode 100644 (file)
index 0000000..3542c01
--- /dev/null
@@ -0,0 +1,108 @@
+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;
diff --git a/src/app/molecules/import-e2e-room-keys/ImportE2ERoomKeys.scss b/src/app/molecules/import-e2e-room-keys/ImportE2ERoomKeys.scss
new file mode 100644 (file)
index 0000000..2c9e631
--- /dev/null
@@ -0,0 +1,63 @@
+
+.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