add emergency kit and new error states

This commit is contained in:
Paul Miller
2023-06-21 13:57:23 -05:00
parent f807dc2d62
commit 7fd7fd580b
11 changed files with 216 additions and 51 deletions

View File

@@ -1,3 +1,4 @@
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { createSignal } from "solid-js"; import { createSignal } from "solid-js";
import { ConfirmDialog } from "~/components/Dialog"; import { ConfirmDialog } from "~/components/Dialog";
import { Button } from "~/components/layout"; import { Button } from "~/components/layout";
@@ -5,8 +6,8 @@ import { showToast } from "~/components/Toaster";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
import eify from "~/utils/eify"; import eify from "~/utils/eify";
export function DeleteEverything() { export function DeleteEverything(props: { emergency?: boolean }) {
const [_state, actions] = useMegaStore(); const [state, actions] = useMegaStore();
async function confirmReset() { async function confirmReset() {
setConfirmOpen(true); setConfirmOpen(true);
@@ -18,7 +19,21 @@ export function DeleteEverything() {
async function resetNode() { async function resetNode() {
try { try {
setConfirmLoading(true); setConfirmLoading(true);
await actions.deleteMutinyWallet(); // If we're in a context where the wallet is loaded we want to use the regular action to delete it
// Otherwise we just call the import_json method directly
if (state.mutiny_wallet && !props.emergency) {
try {
await actions.deleteMutinyWallet();
} catch (e) {
// If we can't stop we want to keep going
console.error(e);
}
} else {
// If there's no mutiny_wallet loaded we might need to initialize WASM
await initMutinyWallet();
await MutinyWallet.import_json("{}");
}
showToast({ title: "Deleted", description: `Deleted all data` }); showToast({ title: "Deleted", description: `Deleted all data` });
setTimeout(() => { setTimeout(() => {

View File

@@ -1,11 +1,13 @@
import { Title } from "solid-start"; import { A, Title } from "solid-start";
import { import {
Button, Button,
DefaultMain, DefaultMain,
LargeHeader, LargeHeader,
NiceP,
SafeArea, SafeArea,
SmallHeader SmallHeader
} from "~/components/layout"; } from "~/components/layout";
import { ExternalLink } from "./layout/ExternalLink";
export default function ErrorDisplay(props: { error: Error }) { export default function ErrorDisplay(props: { error: Error }) {
return ( return (
@@ -18,6 +20,17 @@ export default function ErrorDisplay(props: { error: Error }) {
<span class="font-bold">{props.error.name}</span>:{" "} <span class="font-bold">{props.error.name}</span>:{" "}
{props.error.message} {props.error.message}
</p> </p>
<NiceP>
Try reloading this page or clicking the "Dangit" button. If
you keep having problems,{" "}
<ExternalLink href="https://matrix.to/#/#mutiny-community:lightninghackers.com">
reach out to us for support.
</ExternalLink>
</NiceP>
<NiceP>
Getting desperate? Try the{" "}
<A href="/emergencykit">emergency kit.</A>
</NiceP>
<div class="h-full" /> <div class="h-full" />
<Button <Button
onClick={() => (window.location.href = "/")} onClick={() => (window.location.href = "/")}

View File

@@ -6,13 +6,13 @@ import { showToast } from "./Toaster";
import { downloadTextFile } from "~/utils/download"; import { downloadTextFile } from "~/utils/download";
import { createFileUploader } from "@solid-primitives/upload"; import { createFileUploader } from "@solid-primitives/upload";
import { ConfirmDialog } from "./Dialog"; import { ConfirmDialog } from "./Dialog";
import { MutinyWallet } from "@mutinywallet/mutiny-wasm"; import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
export function ImportExport() { export function ImportExport(props: { emergency?: boolean }) {
const [state, _] = useMegaStore(); const [state, _] = useMegaStore();
async function handleSave() { async function handleSave() {
const json = await state.mutiny_wallet?.export_json(); const json = await MutinyWallet.export_json();
downloadTextFile(json || "", "mutiny-state.json"); downloadTextFile(json || "", "mutiny-state.json");
} }
@@ -39,17 +39,28 @@ export function ImportExport() {
fileReader.readAsText(file, "UTF-8"); 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);
}
} 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 // This should throw if there's a parse error, so we won't end up clearing
if (text) { if (text) {
JSON.parse(text); JSON.parse(text);
MutinyWallet.import_json(text); await MutinyWallet.import_json(text);
} }
if (state.mutiny_wallet) { setTimeout(() => {
await state.mutiny_wallet.stop(); window.location.href = "/";
} }, 1000);
window.location.href = "/";
} catch (e) { } catch (e) {
showToast(eify(e)); showToast(eify(e));
} finally { } finally {

View File

@@ -1,13 +1,12 @@
import { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { Button, InnerCard, NiceP, VStack } from "~/components/layout"; import { Button, InnerCard, NiceP, VStack } from "~/components/layout";
import { useMegaStore } from "~/state/megaStore";
import { downloadTextFile } from "~/utils/download"; import { downloadTextFile } from "~/utils/download";
export function Logs() { export function Logs() {
const [state, _] = useMegaStore();
async function handleSave() { async function handleSave() {
try { try {
const logs = await state.mutiny_wallet?.get_logs(); const logs = await MutinyWallet.get_logs();
downloadTextFile( downloadTextFile(
logs.join("") || "", logs.join("") || "",
"mutiny-logs.txt", "mutiny-logs.txt",

View File

@@ -1,7 +1,32 @@
import { Title } from "solid-start"; import { Title } from "solid-start";
import { DefaultMain, LargeHeader, NiceP, SafeArea } from "~/components/layout"; import {
DefaultMain,
LargeHeader,
NiceP,
SafeArea,
SmallHeader
} from "~/components/layout";
import { ExternalLink } from "./layout/ExternalLink"; import { ExternalLink } from "./layout/ExternalLink";
import { Match, Switch } from "solid-js"; import { Match, Switch } from "solid-js";
import { ImportExport } from "./ImportExport";
import { Logs } from "./Logs";
import { DeleteEverything } from "./DeleteEverything";
function ErrorFooter() {
return (
<>
<div class="h-full" />
<p class="self-center text-neutral-500 mt-4">
Bugs? Feedback?{" "}
<span class="text-neutral-400">
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/issues">
Create an issue
</ExternalLink>
</span>
</p>
</>
);
}
export default function SetupErrorDisplay(props: { error: Error }) { export default function SetupErrorDisplay(props: { error: Error }) {
return ( return (
@@ -22,18 +47,10 @@ export default function SetupErrorDisplay(props: { error: Error }) {
this page, or close this tab and refresh the other this page, or close this tab and refresh the other
one. one.
</NiceP> </NiceP>
<div class="h-full" /> <ErrorFooter />
<p class="self-center text-neutral-500 mt-4">
Bugs? Feedback?{" "}
<span class="text-neutral-400">
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/issues">
Create an issue
</ExternalLink>
</span>
</p>
</DefaultMain> </DefaultMain>
</Match> </Match>
<Match when={true}> <Match when={props.error.message.startsWith("Browser error")}>
<Title>Incompatible browser</Title> <Title>Incompatible browser</Title>
<DefaultMain> <DefaultMain>
<LargeHeader>Incompatible browser detected</LargeHeader> <LargeHeader>Incompatible browser detected</LargeHeader>
@@ -61,15 +78,39 @@ export default function SetupErrorDisplay(props: { error: Error }) {
Supported Browsers Supported Browsers
</ExternalLink> </ExternalLink>
<div class="h-full" /> <ErrorFooter />
<p class="self-center text-neutral-500 mt-4"> </DefaultMain>
Bugs? Feedback?{" "} </Match>
<span class="text-neutral-400"> <Match when={true}>
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/issues"> <Title>Failed to load</Title>
Create an issue <DefaultMain>
</ExternalLink> <LargeHeader>Failed to load Mutiny</LargeHeader>
</span> <p class="bg-white/10 rounded-xl p-4 font-mono">
<span class="font-bold">{props.error.name}</span>:{" "}
{props.error.message}
</p> </p>
<NiceP>
Something went wrong while booting up Mutiny Wallet.
</NiceP>
<NiceP>
If your wallet seems broken, here are some tools to
try to debug and repair it.
</NiceP>
<NiceP>
If you have any questions on what these buttons do,
please{" "}
<ExternalLink href="https://matrix.to/#/#mutiny-community:lightninghackers.com">
reach out to us for support.
</ExternalLink>
</NiceP>
<ImportExport emergency />
<Logs />
<div class="rounded-xl p-4 flex flex-col gap-2 bg-m-red">
<SmallHeader>Danger zone</SmallHeader>
<DeleteEverything emergency />
</div>
<ErrorFooter />
</DefaultMain> </DefaultMain>
</Match> </Match>
</Switch> </Switch>

View File

@@ -1,4 +1,11 @@
import { JSX, ParentComponent, Show, Suspense, createResource } from "solid-js"; import {
JSX,
ParentComponent,
Show,
Suspense,
createResource,
createSignal
} from "solid-js";
import Linkify from "./Linkify"; import Linkify from "./Linkify";
import { Button, ButtonLink } from "./Button"; import { Button, ButtonLink } from "./Button";
import { Checkbox as KCheckbox, Separator } from "@kobalte/core"; import { Checkbox as KCheckbox, Separator } from "@kobalte/core";
@@ -7,6 +14,7 @@ import check from "~/assets/icons/check.svg";
import { MutinyTagItem } from "~/utils/tags"; import { MutinyTagItem } from "~/utils/tags";
import { generateGradient } from "~/utils/gradientHash"; import { generateGradient } from "~/utils/gradientHash";
import close from "~/assets/icons/close.svg"; import close from "~/assets/icons/close.svg";
import { A } from "solid-start";
export { Button, ButtonLink, Linkify }; export { Button, ButtonLink, Linkify };
@@ -84,9 +92,24 @@ export const DefaultMain: ParentComponent = (props) => {
}; };
export const FullscreenLoader = () => { export const FullscreenLoader = () => {
const [waitedTooLong, setWaitedTooLong] = createSignal(false);
setTimeout(() => {
setWaitedTooLong(true);
}, 10000);
return ( return (
<div class="w-full h-[100dvh] flex justify-center items-center"> <div class="w-full h-[100dvh] flex flex-col gap-4 justify-center items-center">
<LoadingSpinner wide /> <LoadingSpinner wide />
<Show when={waitedTooLong()}>
<p class="max-w-[20rem] text-neutral-400">
Stuck on this screen? Try reloading. If that doesn't work,
check out the{" "}
<A class="text-white" href="/emergencykit">
emergency kit.
</A>
</p>
</Show>
</div> </div>
); );
}; };

View File

@@ -6,7 +6,7 @@ export async function checkBrowserCompatibility(): Promise<boolean> {
localStorage.removeItem("test"); localStorage.removeItem("test");
} catch (e) { } catch (e) {
console.error(e); console.error(e);
throw new Error("LocalStorage is not supported."); throw new Error("Browser error: LocalStorage is not supported.");
} }
// Check if the browser supports WebAssembly // Check if the browser supports WebAssembly
@@ -17,7 +17,7 @@ export async function checkBrowserCompatibility(): Promise<boolean> {
Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00) Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)
) )
) { ) {
throw new Error("WebAssembly is not supported."); throw new Error("Browser error: WebAssembly is not supported.");
} }
console.debug("Checking indexedDB"); console.debug("Checking indexedDB");
@@ -26,7 +26,7 @@ export async function checkBrowserCompatibility(): Promise<boolean> {
await openDatabase(); await openDatabase();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
throw new Error("IndexedDB is not supported."); throw new Error("Browser error: IndexedDB is not supported.");
} }
return true; return true;

View File

@@ -117,15 +117,18 @@ export async function setupMutinyWallet(
console.log("Using esplora address", esplora); console.log("Using esplora address", esplora);
console.log("Using rgs address", rgs); console.log("Using rgs address", rgs);
console.log("Using lsp address", lsp); console.log("Using lsp address", lsp);
const mutinyWallet = await new MutinyWallet( const mutinyWallet = await new MutinyWallet(
// Password
"", "",
// Mnemonic
undefined, undefined,
proxy, proxy,
network, network,
esplora, esplora,
rgs, rgs,
lsp lsp,
// Do not connect peers
undefined
); );
const nodes = await mutinyWallet.list_nodes(); const nodes = await mutinyWallet.list_nodes();

View File

@@ -0,0 +1,45 @@
import { DeleteEverything } from "~/components/DeleteEverything";
import { ImportExport } from "~/components/ImportExport";
import { Logs } from "~/components/Logs";
import NavBar from "~/components/NavBar";
import {
DefaultMain,
LargeHeader,
NiceP,
SafeArea,
SmallHeader,
VStack
} from "~/components/layout";
import { BackLink } from "~/components/layout/BackLink";
import { ExternalLink } from "~/components/layout/ExternalLink";
export default function EmergencyKit() {
return (
<SafeArea>
<DefaultMain>
<BackLink />
<LargeHeader>Emergency Kit</LargeHeader>
<VStack>
<NiceP>
If your wallet seems broken, here are some tools to try
to debug and repair it.
</NiceP>
<NiceP>
If you have any questions on what these buttons do,
please{" "}
<ExternalLink href="https://matrix.to/#/#mutiny-community:lightninghackers.com">
reach out to us for support.
</ExternalLink>
</NiceP>
<ImportExport emergency />
<Logs />
<div class="rounded-xl p-4 flex flex-col gap-2 bg-m-red overflow-x-hidden">
<SmallHeader>Danger zone</SmallHeader>
<DeleteEverything emergency />
</div>
</VStack>
</DefaultMain>
<NavBar activeTab="none" />
</SafeArea>
);
}

View File

@@ -14,6 +14,8 @@ import { SeedWords } from "~/components/SeedWords";
import { SettingsStringsEditor } from "~/components/SettingsStringsEditor"; import { SettingsStringsEditor } from "~/components/SettingsStringsEditor";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
import { LiquidityMonitor } from "~/components/LiquidityMonitor"; import { LiquidityMonitor } from "~/components/LiquidityMonitor";
import { A } from "solid-start";
import { Suspense } from "solid-js";
export default function Settings() { export default function Settings() {
const [store, _actions] = useMegaStore(); const [store, _actions] = useMegaStore();
@@ -41,6 +43,13 @@ export default function Settings() {
</VStack> </VStack>
</Card> </Card>
<SettingsStringsEditor /> <SettingsStringsEditor />
<Card title="Emergency Kit">
<NiceP>
Having some serious problems with your wallet?
Check out the{" "}
<A href="/emergencykit">emergency kit.</A>
</NiceP>
</Card>
<Card title="If you know what you're doing"> <Card title="If you know what you're doing">
<VStack> <VStack>
<NiceP> <NiceP>

View File

@@ -151,17 +151,23 @@ export const Provider: ParentComponent = (props) => {
}); });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
setState({ setup_error: eify(e) });
} }
}, },
async deleteMutinyWallet(): Promise<void> { async deleteMutinyWallet(): Promise<void> {
await state.mutiny_wallet?.stop(); try {
setState((prevState) => ({ if (state.mutiny_wallet) {
...prevState, await state.mutiny_wallet?.stop();
mutiny_wallet: undefined, }
deleting: true setState((prevState) => ({
})); ...prevState,
MutinyWallet.import_json("{}"); mutiny_wallet: undefined,
localStorage.clear(); deleting: true
}));
MutinyWallet.import_json("{}");
} catch (e) {
console.error(e);
}
}, },
setWaitlistId(waitlist_id: string) { setWaitlistId(waitlist_id: string) {
setState({ waitlist_id }); setState({ waitlist_id });