Add tsx syntax to Unseal.
This commit is contained in:
parent
ed26eba220
commit
4930b8e727
|
@ -42,7 +42,6 @@
|
||||||
"webpack": "^5.37.1",
|
"webpack": "^5.37.1",
|
||||||
"webpack-cli": "^4.7.0",
|
"webpack-cli": "^4.7.0",
|
||||||
"webpack-dev-server": "^3.11.2",
|
"webpack-dev-server": "^3.11.2",
|
||||||
"z-makeelement": "^1.0.3",
|
|
||||||
"z-pagerouter": "^1.0.2"
|
"z-pagerouter": "^1.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Component, JSX, createRef } from "preact";
|
import { Component, JSX, createRef } from "preact";
|
||||||
import { MarginInline } from "./ReactMarginInline";
|
import { MarginInline } from "./MarginInline";
|
||||||
import { addClipboardNotifications } from "../pageUtils";
|
import { addClipboardNotifications } from "../pageUtils";
|
||||||
import ClipboardJS from "clipboard";
|
import ClipboardJS from "clipboard";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
/* eslint-disable */
|
|
||||||
import { ElementInfo, makeElement } from "z-makeelement";
|
|
||||||
/* eslint-enable */
|
|
||||||
|
|
||||||
export function Form(
|
|
||||||
children: Element[],
|
|
||||||
submit: (form: HTMLFormElement) => unknown,
|
|
||||||
options: Partial<ElementInfo> = {},
|
|
||||||
): HTMLFormElement {
|
|
||||||
const form = makeElement({
|
|
||||||
tag: "form",
|
|
||||||
children: children,
|
|
||||||
...options,
|
|
||||||
}) as HTMLFormElement;
|
|
||||||
|
|
||||||
form.addEventListener("submit", (e: Event) => {
|
|
||||||
e.preventDefault();
|
|
||||||
submit(form);
|
|
||||||
});
|
|
||||||
|
|
||||||
return form;
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { JSX } from "preact";
|
import { JSX } from "preact";
|
||||||
import { Margin } from "./ReactMargin";
|
import { Margin } from "./Margin";
|
||||||
|
|
||||||
export type InputWithTitleProps = {
|
export type InputWithTitleProps = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { makeElement } from "z-makeelement";
|
|
||||||
|
|
||||||
export function Margin(children: Element | Element[]): Element {
|
|
||||||
return makeElement({
|
|
||||||
tag: "div",
|
|
||||||
class: "uk-margin",
|
|
||||||
children: children,
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { makeElement } from "z-makeelement";
|
|
||||||
|
|
||||||
export function MarginInline(children: Element | Element[]): Element {
|
|
||||||
return makeElement({
|
|
||||||
tag: "div",
|
|
||||||
class: "uk-margin",
|
|
||||||
children: makeElement({
|
|
||||||
tag: "div",
|
|
||||||
class: "uk-inline",
|
|
||||||
children: children,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
import { Margin } from "./Margin";
|
|
||||||
import { makeElement } from "z-makeelement";
|
|
||||||
import QrScanner from "qr-scanner";
|
|
||||||
|
|
||||||
/* eslint-disable import/no-unresolved */
|
|
||||||
// @ts-ignore
|
|
||||||
import qrScannerWorkerSource from "!!raw-loader!qr-scanner/qr-scanner-worker.min.js";
|
|
||||||
QrScanner.WORKER_PATH = URL.createObjectURL(new Blob([qrScannerWorkerSource]));
|
|
||||||
|
|
||||||
export interface QRScannerType extends HTMLElement {
|
|
||||||
deinit(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function QRScanner(onScan: (code: string) => void): Promise<QRScannerType> {
|
|
||||||
const webcamVideo = makeElement({
|
|
||||||
tag: "video",
|
|
||||||
}) as HTMLVideoElement;
|
|
||||||
|
|
||||||
const QRInput = makeElement({
|
|
||||||
tag: "div",
|
|
||||||
children: [Margin(webcamVideo)],
|
|
||||||
}) as QRScannerType;
|
|
||||||
|
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({
|
|
||||||
video: {
|
|
||||||
facingMode: "environment",
|
|
||||||
},
|
|
||||||
audio: false,
|
|
||||||
});
|
|
||||||
webcamVideo.srcObject = stream;
|
|
||||||
const lastSeenValue = "";
|
|
||||||
const qrScanner = new QrScanner(webcamVideo, function (value) {
|
|
||||||
if (lastSeenValue == value) return;
|
|
||||||
onScan(value);
|
|
||||||
});
|
|
||||||
await qrScanner.start();
|
|
||||||
|
|
||||||
QRInput.deinit = () => {
|
|
||||||
try {
|
|
||||||
stream.getTracks().forEach(function (track) {
|
|
||||||
track.stop();
|
|
||||||
});
|
|
||||||
} catch (_) {
|
|
||||||
() => {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return QRInput;
|
|
||||||
}
|
|
58
src/elements/QRScanner.tsx
Normal file
58
src/elements/QRScanner.tsx
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/* eslint-disable import/no-unresolved */
|
||||||
|
// @ts-ignore
|
||||||
|
import qrScannerWorkerSource from "!!raw-loader!qr-scanner/qr-scanner-worker.min.js";
|
||||||
|
QrScanner.WORKER_PATH = URL.createObjectURL(new Blob([qrScannerWorkerSource]));
|
||||||
|
// end ignore
|
||||||
|
|
||||||
|
import { Component, JSX, createRef } from "preact";
|
||||||
|
import { Margin } from "./Margin";
|
||||||
|
import QrScanner from "qr-scanner";
|
||||||
|
|
||||||
|
export type QRScannerProps = {
|
||||||
|
onScan: (code: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class QRScanner extends Component<QRScannerProps, unknown> {
|
||||||
|
videoElement = createRef<HTMLVideoElement>();
|
||||||
|
|
||||||
|
stream: MediaStream;
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
void navigator.mediaDevices
|
||||||
|
.getUserMedia({
|
||||||
|
video: {
|
||||||
|
facingMode: "environment",
|
||||||
|
},
|
||||||
|
audio: false,
|
||||||
|
})
|
||||||
|
.then((stream) => {
|
||||||
|
this.stream = stream;
|
||||||
|
this.videoElement.current.srcObject = stream;
|
||||||
|
const lastSeenValue = "";
|
||||||
|
const qrScanner = new QrScanner(this.videoElement.current, (value) => {
|
||||||
|
if (lastSeenValue == value) return;
|
||||||
|
this.props.onScan(value);
|
||||||
|
});
|
||||||
|
void qrScanner.start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
try {
|
||||||
|
this.stream.getTracks().forEach(function (track) {
|
||||||
|
track.stop();
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
() => {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Margin>
|
||||||
|
<video ref={this.videoElement}></video>
|
||||||
|
</Margin>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { Page } from "../../types/Page";
|
import { Page } from "../../types/Page";
|
||||||
import { Tile } from "../../elements/ReactTile";
|
import { Tile } from "../../elements/Tile";
|
||||||
import { notImplemented, prePageChecks } from "../../pageUtils";
|
import { notImplemented, prePageChecks } from "../../pageUtils";
|
||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Form } from "../../../../elements/ReactForm";
|
import { Form } from "../../../../elements/Form";
|
||||||
import { InputWithTitle } from "../../../../elements/InputWithTitle";
|
import { InputWithTitle } from "../../../../elements/InputWithTitle";
|
||||||
import { MarginInline } from "../../../../elements/ReactMarginInline";
|
import { MarginInline } from "../../../../elements/MarginInline";
|
||||||
import { Page } from "../../../../types/Page";
|
import { Page } from "../../../../types/Page";
|
||||||
import { UserType } from "../../../../api/types/userpass/user";
|
import { UserType } from "../../../../api/types/userpass/user";
|
||||||
import { createOrUpdateUserPassUser } from "../../../../api/auth/userpass/createOrUpdateUserPassUser";
|
import { createOrUpdateUserPassUser } from "../../../../api/auth/userpass/createOrUpdateUserPassUser";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Form } from "../../../../elements/ReactForm";
|
import { Form } from "../../../../elements/Form";
|
||||||
import { Margin } from "../../../../elements/ReactMargin";
|
import { Margin } from "../../../../elements/Margin";
|
||||||
import { MarginInline } from "../../../../elements/ReactMarginInline";
|
import { MarginInline } from "../../../../elements/MarginInline";
|
||||||
import { Page } from "../../../../types/Page";
|
import { Page } from "../../../../types/Page";
|
||||||
import { UserType } from "../../../../api/types/userpass/user";
|
import { UserType } from "../../../../api/types/userpass/user";
|
||||||
import { createOrUpdateUserPassUser } from "../../../../api/auth/userpass/createOrUpdateUserPassUser";
|
import { createOrUpdateUserPassUser } from "../../../../api/auth/userpass/createOrUpdateUserPassUser";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Page } from "../types/Page";
|
import { Page } from "../types/Page";
|
||||||
import { Tile } from "../elements/ReactTile";
|
import { Tile } from "../elements/Tile";
|
||||||
import { TokenInfo } from "../api/types/token";
|
import { TokenInfo } from "../api/types/token";
|
||||||
import { lookupSelf } from "../api/sys/lookupSelf";
|
import { lookupSelf } from "../api/sys/lookupSelf";
|
||||||
import { prePageChecks, setErrorText } from "../pageUtils";
|
import { prePageChecks, setErrorText } from "../pageUtils";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Component, JSX, render } from "preact";
|
import { Component, JSX, render } from "preact";
|
||||||
import { Form } from "../elements/ReactForm";
|
import { Form } from "../elements/Form";
|
||||||
import { Margin } from "../elements/ReactMargin";
|
import { Margin } from "../elements/Margin";
|
||||||
import { MarginInline } from "../elements/ReactMarginInline";
|
import { MarginInline } from "../elements/MarginInline";
|
||||||
import { Page } from "../types/Page";
|
import { Page } from "../types/Page";
|
||||||
import { lookupSelf } from "../api/sys/lookupSelf";
|
import { lookupSelf } from "../api/sys/lookupSelf";
|
||||||
import { setErrorText } from "../pageUtils";
|
import { setErrorText } from "../pageUtils";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Component, JSX, createRef, render } from "preact";
|
import { Component, JSX, createRef, render } from "preact";
|
||||||
import { CopyableInputBox } from "../elements/CopyableInputBox";
|
import { CopyableInputBox } from "../elements/CopyableInputBox";
|
||||||
import { Form } from "../elements/ReactForm";
|
import { Form } from "../elements/Form";
|
||||||
import { Margin } from "../elements/ReactMargin";
|
import { Margin } from "../elements/Margin";
|
||||||
import { Page } from "../types/Page";
|
import { Page } from "../types/Page";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Form } from "../../../elements/ReactForm";
|
import { Form } from "../../../elements/Form";
|
||||||
import { Margin } from "../../../elements/ReactMargin";
|
import { Margin } from "../../../elements/Margin";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { SecretTitleElement } from "../SecretTitleElement";
|
import { SecretTitleElement } from "../SecretTitleElement";
|
||||||
import { createOrUpdateSecret } from "../../../api/kv/createOrUpdateSecret";
|
import { createOrUpdateSecret } from "../../../api/kv/createOrUpdateSecret";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Form } from "../../../elements/ReactForm";
|
import { Form } from "../../../elements/Form";
|
||||||
import { Margin } from "../../../elements/ReactMargin";
|
import { Margin } from "../../../elements/Margin";
|
||||||
import { MarginInline } from "../../../elements/ReactMarginInline";
|
import { MarginInline } from "../../../elements/MarginInline";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { newMount } from "../../../api/sys/newMount";
|
import { newMount } from "../../../api/sys/newMount";
|
||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Form } from "../../../elements/ReactForm";
|
import { Form } from "../../../elements/Form";
|
||||||
import { Margin } from "../../../elements/ReactMargin";
|
import { Margin } from "../../../elements/Margin";
|
||||||
import { MarginInline } from "../../../elements/ReactMarginInline";
|
import { MarginInline } from "../../../elements/MarginInline";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { newMount } from "../../../api/sys/newMount";
|
import { newMount } from "../../../api/sys/newMount";
|
||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Form } from "../../../elements/ReactForm";
|
import { Form } from "../../../elements/Form";
|
||||||
import { Margin } from "../../../elements/ReactMargin";
|
import { Margin } from "../../../elements/Margin";
|
||||||
import { MarginInline } from "../../../elements/ReactMarginInline";
|
import { MarginInline } from "../../../elements/MarginInline";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { newMount } from "../../../api/sys/newMount";
|
import { newMount } from "../../../api/sys/newMount";
|
||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Page } from "../../types/Page";
|
import { Page } from "../../types/Page";
|
||||||
import { Tile } from "../../elements/ReactTile";
|
import { Tile } from "../../elements/Tile";
|
||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Form } from "../../../elements/ReactForm";
|
import { Form } from "../../../elements/Form";
|
||||||
import { Margin } from "../../../elements/ReactMargin";
|
import { Margin } from "../../../elements/Margin";
|
||||||
import { MarginInline } from "../../../elements/ReactMarginInline";
|
import { MarginInline } from "../../../elements/MarginInline";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { SecretTitleElement } from "../SecretTitleElement";
|
import { SecretTitleElement } from "../SecretTitleElement";
|
||||||
import { addNewTOTP } from "../../../api/totp/addNewTOTP";
|
import { addNewTOTP } from "../../../api/totp/addNewTOTP";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Component, JSX, render } from "preact";
|
import { Component, JSX, render } from "preact";
|
||||||
import { CopyableInputBox } from "../../../elements/CopyableInputBox";
|
import { CopyableInputBox } from "../../../elements/CopyableInputBox";
|
||||||
import { DoesNotExistError } from "../../../types/internalErrors";
|
import { DoesNotExistError } from "../../../types/internalErrors";
|
||||||
import { MarginInline } from "../../../elements/ReactMarginInline";
|
import { MarginInline } from "../../../elements/MarginInline";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { PageRouter } from "z-pagerouter";
|
import { PageRouter } from "z-pagerouter";
|
||||||
import { PageState } from "../../../PageState";
|
import { PageState } from "../../../PageState";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Form } from "../../../elements/ReactForm";
|
import { Form } from "../../../elements/Form";
|
||||||
import { Margin } from "../../../elements/ReactMargin";
|
import { Margin } from "../../../elements/Margin";
|
||||||
import { MarginInline } from "../../../elements/ReactMarginInline";
|
import { MarginInline } from "../../../elements/MarginInline";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { SecretTitleElement } from "../SecretTitleElement";
|
import { SecretTitleElement } from "../SecretTitleElement";
|
||||||
import { newTransitKey } from "../../../api/transit/newTransitKey";
|
import { newTransitKey } from "../../../api/transit/newTransitKey";
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { CopyableModal } from "../../../elements/CopyableModal";
|
import { CopyableModal } from "../../../elements/CopyableModal";
|
||||||
import { FileUploadInput } from "../../../elements/FileUploadInput";
|
import { FileUploadInput } from "../../../elements/FileUploadInput";
|
||||||
import { Form } from "../../../elements/ReactForm";
|
import { Form } from "../../../elements/Form";
|
||||||
import { InputWithTitle } from "../../../elements/InputWithTitle";
|
import { InputWithTitle } from "../../../elements/InputWithTitle";
|
||||||
import { Margin } from "../../../elements/ReactMargin";
|
import { Margin } from "../../../elements/Margin";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { SecretTitleElement } from "../SecretTitleElement";
|
import { SecretTitleElement } from "../SecretTitleElement";
|
||||||
import { fileToBase64 } from "../../../htmlUtils";
|
import { fileToBase64 } from "../../../htmlUtils";
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { CopyableModal } from "../../../elements/CopyableModal";
|
import { CopyableModal } from "../../../elements/CopyableModal";
|
||||||
import { FileUploadInput } from "../../../elements/FileUploadInput";
|
import { FileUploadInput } from "../../../elements/FileUploadInput";
|
||||||
import { Form } from "../../../elements/ReactForm";
|
import { Form } from "../../../elements/Form";
|
||||||
import { InputWithTitle } from "../../../elements/InputWithTitle";
|
import { InputWithTitle } from "../../../elements/InputWithTitle";
|
||||||
import { Margin } from "../../../elements/ReactMargin";
|
import { Margin } from "../../../elements/Margin";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { SecretTitleElement } from "../SecretTitleElement";
|
import { SecretTitleElement } from "../SecretTitleElement";
|
||||||
import { fileToBase64 } from "../../../htmlUtils";
|
import { fileToBase64 } from "../../../htmlUtils";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { CopyableModal } from "../../../elements/CopyableModal";
|
import { CopyableModal } from "../../../elements/CopyableModal";
|
||||||
import { Form } from "../../../elements/ReactForm";
|
import { Form } from "../../../elements/Form";
|
||||||
import { Margin } from "../../../elements/ReactMargin";
|
import { Margin } from "../../../elements/Margin";
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { SecretTitleElement } from "../SecretTitleElement";
|
import { SecretTitleElement } from "../SecretTitleElement";
|
||||||
import { getTransitKey } from "../../../api/transit/getTransitKey";
|
import { getTransitKey } from "../../../api/transit/getTransitKey";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Page } from "../../../types/Page";
|
import { Page } from "../../../types/Page";
|
||||||
import { SecretTitleElement } from "../SecretTitleElement";
|
import { SecretTitleElement } from "../SecretTitleElement";
|
||||||
import { Tile } from "../../../elements/ReactTile";
|
import { Tile } from "../../../elements/Tile";
|
||||||
import { getTransitKey } from "../../../api/transit/getTransitKey";
|
import { getTransitKey } from "../../../api/transit/getTransitKey";
|
||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
import translations from "../translations/index.mjs";
|
import translations from "../translations/index.mjs";
|
||||||
// ts-unignore
|
// ts-unignore
|
||||||
|
|
||||||
import { Form } from "../elements/ReactForm";
|
import { Form } from "../elements/Form";
|
||||||
import { Margin } from "../elements/ReactMargin";
|
import { Margin } from "../elements/Margin";
|
||||||
import { MarginInline } from "../elements/ReactMarginInline";
|
import { MarginInline } from "../elements/MarginInline";
|
||||||
import { Page } from "../types/Page";
|
import { Page } from "../types/Page";
|
||||||
import { reloadNavBar } from "../elements/NavBar";
|
import { reloadNavBar } from "../elements/NavBar";
|
||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Form } from "../elements/ReactForm";
|
import { Form } from "../elements/Form";
|
||||||
import { Margin } from "../elements/ReactMargin";
|
import { Margin } from "../elements/Margin";
|
||||||
import { Page } from "../types/Page";
|
import { Page } from "../types/Page";
|
||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
|
|
||||||
|
|
|
@ -1,193 +0,0 @@
|
||||||
import { Form } from "../elements/Form";
|
|
||||||
import { MarginInline } from "../elements/MarginInline";
|
|
||||||
import { Page } from "../types/Page";
|
|
||||||
import { QRScanner, QRScannerType } from "../elements/QRScanner";
|
|
||||||
import { SealStatusType, getSealStatus } from "../api/sys/getSealStatus";
|
|
||||||
import { makeElement } from "z-makeelement";
|
|
||||||
import { setErrorText } from "../pageUtils";
|
|
||||||
import { submitUnsealKey } from "../api/sys/submitUnsealKey";
|
|
||||||
import i18next from "i18next";
|
|
||||||
|
|
||||||
const UnsealInputModes = {
|
|
||||||
FORM_INPUT: "FORM_INPUT",
|
|
||||||
QR_INPUT: "QR_INPUT",
|
|
||||||
};
|
|
||||||
|
|
||||||
export class UnsealPage extends Page {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.mode = UnsealInputModes.FORM_INPUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
mode: string;
|
|
||||||
refresher: number;
|
|
||||||
qrScanner: QRScannerType;
|
|
||||||
unsealProgress: HTMLProgressElement;
|
|
||||||
unsealProgressText: HTMLParagraphElement;
|
|
||||||
unsealInputContent: HTMLElement;
|
|
||||||
unsealKeyForm: HTMLFormElement;
|
|
||||||
|
|
||||||
async cleanup(): Promise<void> {
|
|
||||||
this.deinitWebcam();
|
|
||||||
clearInterval(this.refresher);
|
|
||||||
}
|
|
||||||
|
|
||||||
deinitWebcam(): void {
|
|
||||||
try {
|
|
||||||
this.qrScanner.deinit();
|
|
||||||
} catch (_) {
|
|
||||||
// Do Nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
makeRefresher(): void {
|
|
||||||
const id = setInterval(async () => {
|
|
||||||
await this.doRefresh();
|
|
||||||
return;
|
|
||||||
}, 1000);
|
|
||||||
this.refresher = id as unknown as number;
|
|
||||||
}
|
|
||||||
|
|
||||||
async doRefresh(): Promise<void> {
|
|
||||||
const status = await getSealStatus();
|
|
||||||
await this.updateSealProgress(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
async render(): Promise<void> {
|
|
||||||
this.unsealProgress = makeElement({
|
|
||||||
tag: "progress",
|
|
||||||
class: "uk-progress",
|
|
||||||
attributes: { value: "0", max: "0" },
|
|
||||||
}) as HTMLProgressElement;
|
|
||||||
this.unsealProgressText = makeElement({
|
|
||||||
tag: "p",
|
|
||||||
text: i18next.t("unseal_keys_progress", {
|
|
||||||
progress: "0",
|
|
||||||
keys_needed: "0",
|
|
||||||
}),
|
|
||||||
}) as HTMLParagraphElement;
|
|
||||||
this.unsealInputContent = makeElement({
|
|
||||||
tag: "div",
|
|
||||||
});
|
|
||||||
await this.router.setPageContent(
|
|
||||||
makeElement({
|
|
||||||
tag: "div",
|
|
||||||
children: [
|
|
||||||
this.unsealProgress,
|
|
||||||
makeElement({
|
|
||||||
tag: "p",
|
|
||||||
id: "errorText",
|
|
||||||
class: ["uk-text-danger", "uk-margin-top"],
|
|
||||||
}),
|
|
||||||
this.unsealProgressText,
|
|
||||||
this.unsealInputContent,
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
await this.switchInputMode(this.mode);
|
|
||||||
await this.updateSealProgress(await getSealStatus());
|
|
||||||
this.makeRefresher();
|
|
||||||
}
|
|
||||||
|
|
||||||
setButtons(method: string): void {
|
|
||||||
const newMethod: string =
|
|
||||||
method == UnsealInputModes.FORM_INPUT
|
|
||||||
? UnsealInputModes.QR_INPUT
|
|
||||||
: UnsealInputModes.FORM_INPUT;
|
|
||||||
const buttonText: string =
|
|
||||||
newMethod == UnsealInputModes.FORM_INPUT
|
|
||||||
? i18next.t("unseal_input_btn")
|
|
||||||
: i18next.t("unseal_qr_btn");
|
|
||||||
this.unsealInputContent.appendChild(
|
|
||||||
makeElement({
|
|
||||||
tag: "button",
|
|
||||||
class: ["uk-button", "uk-button-primary"],
|
|
||||||
text: buttonText,
|
|
||||||
onclick: async () => {
|
|
||||||
await this.switchInputMode(newMethod);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async switchInputMode(method: string): Promise<void> {
|
|
||||||
this.deinitWebcam();
|
|
||||||
this.unsealInputContent.querySelectorAll("*").forEach((n) => n.remove());
|
|
||||||
if (method == UnsealInputModes.FORM_INPUT) this.makeUnsealForm();
|
|
||||||
if (method == UnsealInputModes.QR_INPUT) await this.makeQRInput();
|
|
||||||
this.setButtons(method);
|
|
||||||
}
|
|
||||||
|
|
||||||
makeUnsealForm(): void {
|
|
||||||
this.unsealKeyForm = Form(
|
|
||||||
[
|
|
||||||
MarginInline(
|
|
||||||
makeElement({
|
|
||||||
tag: "input",
|
|
||||||
class: ["uk-input", "uk-form-width-medium"],
|
|
||||||
attributes: {
|
|
||||||
required: "true",
|
|
||||||
type: "password",
|
|
||||||
placeholder: i18next.t("key_input_placeholder"),
|
|
||||||
name: "key",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
MarginInline(
|
|
||||||
makeElement({
|
|
||||||
tag: "button",
|
|
||||||
class: ["uk-button", "uk-button-primary"],
|
|
||||||
text: i18next.t("submit_key_btn"),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
async (_) => {
|
|
||||||
await this.handleKeySubmit();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
this.unsealInputContent.appendChild(this.unsealKeyForm);
|
|
||||||
}
|
|
||||||
|
|
||||||
async makeQRInput(): Promise<void> {
|
|
||||||
this.qrScanner = await QRScanner(async (code: string) => {
|
|
||||||
await this.submitKey(code);
|
|
||||||
});
|
|
||||||
this.unsealInputContent.appendChild(this.qrScanner);
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateSealProgress(data: SealStatusType): Promise<void> {
|
|
||||||
const progress = data.progress;
|
|
||||||
const keysNeeded = data.t;
|
|
||||||
const text = this.unsealProgressText;
|
|
||||||
text.innerText = i18next.t("unseal_keys_progress", {
|
|
||||||
progress: String(progress),
|
|
||||||
keys_needed: String(keysNeeded),
|
|
||||||
});
|
|
||||||
const progressBar = this.unsealProgress;
|
|
||||||
progressBar.value = progress;
|
|
||||||
progressBar.max = keysNeeded;
|
|
||||||
if (!data.sealed) {
|
|
||||||
progressBar.value = keysNeeded;
|
|
||||||
await this.router.changePage("HOME");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async submitKey(key: string): Promise<void> {
|
|
||||||
try {
|
|
||||||
await submitUnsealKey(key);
|
|
||||||
await this.updateSealProgress(await getSealStatus());
|
|
||||||
} catch (e: unknown) {
|
|
||||||
const error = e as Error;
|
|
||||||
setErrorText(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleKeySubmit(): Promise<void> {
|
|
||||||
const formData = new FormData(this.unsealKeyForm);
|
|
||||||
|
|
||||||
await this.submitKey(formData.get("key") as string);
|
|
||||||
}
|
|
||||||
get name(): string {
|
|
||||||
return i18next.t("unseal_vault_text");
|
|
||||||
}
|
|
||||||
}
|
|
157
src/pages/Unseal.tsx
Normal file
157
src/pages/Unseal.tsx
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
import { Component, JSX } from "preact";
|
||||||
|
import { Form } from "../elements/Form";
|
||||||
|
import { MarginInline } from "../elements/MarginInline";
|
||||||
|
import { Page } from "../types/Page";
|
||||||
|
import { QRScanner } from "../elements/QRScanner";
|
||||||
|
import { getSealStatus } from "../api/sys/getSealStatus";
|
||||||
|
import { render } from "preact/compat";
|
||||||
|
import { setErrorText } from "../pageUtils";
|
||||||
|
import { submitUnsealKey } from "../api/sys/submitUnsealKey";
|
||||||
|
import { toStr } from "../utils";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
const UnsealInputModes = {
|
||||||
|
FORM_INPUT: "FORM_INPUT",
|
||||||
|
QR_INPUT: "QR_INPUT",
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UnsealFormInputProps = {
|
||||||
|
onSubmit: (code: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function UnsealFormInput(props: UnsealFormInputProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
onSubmit={(data: FormData) => {
|
||||||
|
props.onSubmit(data.get("unsealKey") as string);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MarginInline>
|
||||||
|
<input
|
||||||
|
class="uk-input uk-form-width-medium"
|
||||||
|
name="unsealKey"
|
||||||
|
type="password"
|
||||||
|
placeholder={i18next.t("key_input_placeholder")}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</MarginInline>
|
||||||
|
<MarginInline>
|
||||||
|
<button class="uk-button uk-button-primary" type="submit">
|
||||||
|
{i18next.t("submit_key_btn")}
|
||||||
|
</button>
|
||||||
|
</MarginInline>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnsealPageState = {
|
||||||
|
mode: string;
|
||||||
|
keys_submitted: number;
|
||||||
|
keys_needed: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class UnsealPageElement extends Component<{ page: Page }, UnsealPageState> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.state = {
|
||||||
|
mode: UnsealInputModes.FORM_INPUT,
|
||||||
|
keys_submitted: 0,
|
||||||
|
keys_needed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
timer: number;
|
||||||
|
|
||||||
|
async submitKey(key: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await submitUnsealKey(key);
|
||||||
|
this.updateStateWithSealStatus();
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const error = e as Error;
|
||||||
|
setErrorText(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStateWithSealStatus(): void {
|
||||||
|
void getSealStatus().then((data) => {
|
||||||
|
this.setState({
|
||||||
|
keys_submitted: data.progress,
|
||||||
|
keys_needed: data.t,
|
||||||
|
});
|
||||||
|
if (!data.sealed) {
|
||||||
|
void this.props.page.router.changePage("HOME");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.updateStateWithSealStatus();
|
||||||
|
this.timer = setInterval(() => {
|
||||||
|
this.updateStateWithSealStatus();
|
||||||
|
}, 500) as unknown as number;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<progress
|
||||||
|
class="uk-progress"
|
||||||
|
value={this.state.keys_submitted}
|
||||||
|
max={this.state.keys_needed}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p id="errorText" class="uk-text-danger uk-margin-top" />
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{i18next.t("unseal_keys_progress", {
|
||||||
|
progress: toStr(this.state.keys_submitted),
|
||||||
|
keys_needed: toStr(this.state.keys_needed),
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{this.state.mode == UnsealInputModes.FORM_INPUT && (
|
||||||
|
<UnsealFormInput onSubmit={(code) => this.submitKey(code)} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{this.state.mode == UnsealInputModes.QR_INPUT && (
|
||||||
|
<QRScanner onScan={(code) => this.submitKey(code)} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="uk-button uk-button-primary"
|
||||||
|
onClick={async () => {
|
||||||
|
let newMethod: string;
|
||||||
|
if (this.state.mode == UnsealInputModes.FORM_INPUT) {
|
||||||
|
newMethod = UnsealInputModes.QR_INPUT;
|
||||||
|
} else {
|
||||||
|
newMethod = UnsealInputModes.FORM_INPUT;
|
||||||
|
}
|
||||||
|
this.setState({ mode: newMethod });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.state.mode == UnsealInputModes.QR_INPUT
|
||||||
|
? i18next.t("unseal_input_btn")
|
||||||
|
: i18next.t("unseal_qr_btn")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnsealPage extends Page {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async render(): Promise<void> {
|
||||||
|
render(<UnsealPageElement page={this} />, this.router.pageContentElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
get name(): string {
|
||||||
|
return i18next.t("unseal_vault_text");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue