mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2026-02-10 08:44:20 +01:00
224 lines
7.4 KiB
TypeScript
224 lines
7.4 KiB
TypeScript
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
|
|
import { createFileUploader } from "@solid-primitives/upload";
|
|
import { createSignal, Show } from "solid-js";
|
|
|
|
import {
|
|
Button,
|
|
ConfirmDialog,
|
|
InfoBox,
|
|
InnerCard,
|
|
NiceP,
|
|
showToast,
|
|
SimpleDialog,
|
|
TextField,
|
|
VStack
|
|
} from "~/components";
|
|
import { useI18n } from "~/i18n/context";
|
|
import { useMegaStore } from "~/state/megaStore";
|
|
import { downloadTextFile, eify } from "~/utils";
|
|
|
|
export function ImportExport(props: { emergency?: boolean }) {
|
|
const i18n = useI18n();
|
|
const [state, _] = useMegaStore();
|
|
|
|
const [error, setError] = createSignal<Error>();
|
|
const [exportDecrypt, setExportDecrypt] = createSignal(false);
|
|
const [password, setPassword] = createSignal("");
|
|
|
|
async function handleSave() {
|
|
try {
|
|
setError(undefined);
|
|
const json = await MutinyWallet.export_json();
|
|
await downloadTextFile(json || "", "mutiny-state.json");
|
|
} catch (e) {
|
|
console.error(e);
|
|
const err = eify(e);
|
|
if (err.message === "Incorrect password entered.") {
|
|
setExportDecrypt(true);
|
|
} else {
|
|
setError(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function savePassword(e: Event) {
|
|
e.preventDefault();
|
|
try {
|
|
setError(undefined);
|
|
if (!password()) {
|
|
throw new Error(
|
|
i18n.t(
|
|
"settings.emergency_kit.import_export.error_password"
|
|
)
|
|
);
|
|
}
|
|
const json = await MutinyWallet.export_json(password());
|
|
await downloadTextFile(json || "", "mutiny-state.json");
|
|
} catch (e) {
|
|
console.error(e);
|
|
setError(eify(e));
|
|
} finally {
|
|
setExportDecrypt(false);
|
|
setPassword("");
|
|
}
|
|
}
|
|
|
|
function noop() {
|
|
// noop
|
|
}
|
|
|
|
const { files, selectFiles } = createFileUploader();
|
|
|
|
async function importJson() {
|
|
setConfirmLoading(true);
|
|
const fileReader = new FileReader();
|
|
|
|
try {
|
|
setError(undefined);
|
|
const file: File = files()[0].file;
|
|
|
|
const text = await new Promise<string | null>((resolve, reject) => {
|
|
fileReader.onload = (e) => {
|
|
const result = e.target?.result?.toString();
|
|
if (result) {
|
|
resolve(result);
|
|
} else {
|
|
reject(
|
|
new Error(
|
|
i18n.t(
|
|
"settings.emergency_kit.import_export.error_no_text"
|
|
)
|
|
)
|
|
);
|
|
}
|
|
};
|
|
fileReader.onerror = (_e) =>
|
|
reject(
|
|
new Error(
|
|
i18n.t(
|
|
"settings.emergency_kit.import_export.error_read_file"
|
|
)
|
|
)
|
|
);
|
|
fileReader.readAsText(file, "UTF-8");
|
|
});
|
|
|
|
if (state.mutiny_wallet && !props.emergency) {
|
|
console.log("Mutiny wallet loaded, stopping");
|
|
try {
|
|
await state.mutiny_wallet.stop();
|
|
} catch (e) {
|
|
console.error(e);
|
|
setError(eify(e));
|
|
}
|
|
} else {
|
|
// If there's no mutiny wallet loaded we need to initialize WASM
|
|
console.log("Initializing WASM");
|
|
await initMutinyWallet();
|
|
}
|
|
|
|
// This should throw if there's a parse error, so we won't end up clearing
|
|
if (text) {
|
|
JSON.parse(text);
|
|
await MutinyWallet.import_json(text);
|
|
}
|
|
|
|
setTimeout(() => {
|
|
window.location.href = "/";
|
|
}, 1000);
|
|
} catch (e) {
|
|
showToast(eify(e));
|
|
} finally {
|
|
setConfirmOpen(false);
|
|
setConfirmLoading(false);
|
|
}
|
|
}
|
|
|
|
async function uploadFile() {
|
|
selectFiles(async (files) => {
|
|
if (files.length) {
|
|
setConfirmOpen(true);
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
|
|
const [confirmOpen, setConfirmOpen] = createSignal(false);
|
|
const [confirmLoading, setConfirmLoading] = createSignal(false);
|
|
|
|
return (
|
|
<>
|
|
<InnerCard
|
|
title={i18n.t("settings.emergency_kit.import_export.title")}
|
|
>
|
|
<NiceP>
|
|
{i18n.t("settings.emergency_kit.import_export.tip")}
|
|
</NiceP>
|
|
<NiceP>
|
|
<strong>
|
|
{i18n.t(
|
|
"settings.emergency_kit.import_export.caveat_header"
|
|
)}
|
|
</strong>{" "}
|
|
{i18n.t("settings.emergency_kit.import_export.caveat")}
|
|
</NiceP>
|
|
<div />
|
|
<Show when={error()}>
|
|
<InfoBox accent="red">{error()?.message}</InfoBox>
|
|
</Show>
|
|
<VStack>
|
|
<Button onClick={handleSave}>
|
|
{i18n.t(
|
|
"settings.emergency_kit.import_export.save_state"
|
|
)}
|
|
</Button>
|
|
<Button onClick={uploadFile}>
|
|
{i18n.t(
|
|
"settings.emergency_kit.import_export.import_state"
|
|
)}
|
|
</Button>
|
|
</VStack>
|
|
</InnerCard>
|
|
<ConfirmDialog
|
|
loading={confirmLoading()}
|
|
open={confirmOpen()}
|
|
onConfirm={importJson}
|
|
onCancel={() => setConfirmOpen(false)}
|
|
>
|
|
{i18n.t("settings.emergency_kit.import_export.confirm_replace")}{" "}
|
|
{files()[0].name}?
|
|
</ConfirmDialog>
|
|
{/* TODO: this is pretty redundant with the DecryptDialog, could make a shared component */}
|
|
<SimpleDialog
|
|
title={i18n.t(
|
|
"settings.emergency_kit.import_export.confirm_replace"
|
|
)}
|
|
open={exportDecrypt()}
|
|
>
|
|
<form onSubmit={savePassword}>
|
|
<div class="flex flex-col gap-4">
|
|
<TextField
|
|
name="password"
|
|
type="password"
|
|
ref={noop}
|
|
value={password()}
|
|
onInput={(e) => setPassword(e.currentTarget.value)}
|
|
error={""}
|
|
onBlur={noop}
|
|
onChange={noop}
|
|
/>
|
|
<Show when={error()}>
|
|
<InfoBox accent="red">{error()?.message}</InfoBox>
|
|
</Show>
|
|
<Button intent="blue" onClick={savePassword}>
|
|
{i18n.t(
|
|
"settings.emergency_kit.import_export.decrypt_wallet"
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</SimpleDialog>
|
|
</>
|
|
);
|
|
}
|