add load bar home and emergency kit

This commit is contained in:
Paul Miller
2023-07-07 14:38:16 -05:00
parent d8467ca1bb
commit 6c717a7f06
8 changed files with 184 additions and 75 deletions

View File

@@ -15,6 +15,7 @@ import plusLogo from "~/assets/mutiny-plus-logo.png";
import { PendingNwc } from "./PendingNwc"; import { PendingNwc } from "./PendingNwc";
import { useI18n } from "~/i18n/context"; import { useI18n } from "~/i18n/context";
import { DecryptDialog } from "./DecryptDialog"; import { DecryptDialog } from "./DecryptDialog";
import { LoadingIndicator } from "./LoadingIndicator";
export default function App() { export default function App() {
const [state, _actions] = useMegaStore(); const [state, _actions] = useMegaStore();
@@ -23,6 +24,7 @@ export default function App() {
return ( return (
<SafeArea> <SafeArea>
<DefaultMain> <DefaultMain>
<LoadingIndicator />
<header class="w-full flex justify-between items-center mt-4 mb-2"> <header class="w-full flex justify-between items-center mt-4 mb-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Switch> <Switch>

View File

@@ -0,0 +1,67 @@
import { Progress } from "@kobalte/core";
import { Show } from "solid-js";
import { useMegaStore } from "~/state/megaStore";
export function LoadingBar(props: { value: number; max: number }) {
function valueToStage(value: number) {
switch (value) {
case 0:
return "Just getting started";
case 1:
return "Checking user status";
case 2:
return "Double checking something";
case 3:
return "Downloading";
case 4:
return "Setup";
case 5:
return "Done";
default:
return "Just getting started";
}
}
return (
<Progress.Root
value={props.value}
minValue={0}
maxValue={props.max}
getValueLabel={({ value }) => `Loading: ${valueToStage(value)}`}
class="w-full flex flex-col gap-2"
>
<Progress.ValueLabel class="text-sm text-m-grey-400" />
<Progress.Track class="h-6 bg-white/10 rounded">
<Progress.Fill class="bg-m-blue rounded h-full w-[var(--kb-progress-fill-width)] transition-[width]" />
</Progress.Track>
</Progress.Root>
);
}
export function LoadingIndicator() {
const [state, _actions] = useMegaStore();
const loadStageValue = () => {
switch (state.load_stage) {
case "fresh":
return 0;
case "checking_user":
return 1;
case "checking_double_init":
return 2;
case "downloading":
return 3;
case "setup":
return 4;
case "done":
return 5;
default:
return 0;
}
};
return (
<Show when={state.load_stage !== "done"}>
<LoadingBar value={loadStageValue()} max={5} />
</Show>
);
}

View File

@@ -1,7 +1,6 @@
/* @refresh reload */ /* @refresh reload */
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm"; import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import initWaila from "@mutinywallet/waila-wasm";
export type Network = "bitcoin" | "testnet" | "regtest" | "signet"; export type Network = "bitcoin" | "testnet" | "regtest" | "signet";
export type MutinyWalletSettingStrings = { export type MutinyWalletSettingStrings = {
@@ -105,10 +104,8 @@ export async function checkForWasm() {
} }
} }
export async function setupMutinyWallet( export async function doubleInitDefense() {
settings?: MutinyWalletSettingStrings, console.log("Starting init...");
password?: string
): Promise<MutinyWallet> {
// Ultimate defense against getting multiple instances of the wallet running. // Ultimate defense against getting multiple instances of the wallet running.
// If we detect that the wallet has already been initialized in this session, we'll reload the page. // If we detect that the wallet has already been initialized in this session, we'll reload the page.
// A successful stop of the wallet in onCleanup will clear this flag // A successful stop of the wallet in onCleanup will clear this flag
@@ -121,11 +118,17 @@ export async function setupMutinyWallet(
sessionStorage.removeItem("MUTINY_WALLET_INITIALIZED"); sessionStorage.removeItem("MUTINY_WALLET_INITIALIZED");
window.location.reload(); window.location.reload();
} }
}
export async function initializeWasm() {
// Actually intialize the WASM, this should be the first thing that requires the WASM blob to be downloaded
await initMutinyWallet(); await initMutinyWallet();
// Might as well init waila while we're at it }
await initWaila();
export async function setupMutinyWallet(
settings?: MutinyWalletSettingStrings,
password?: string
): Promise<MutinyWallet> {
console.log("Starting setup..."); console.log("Starting setup...");
const { network, proxy, esplora, rgs, lsp, auth, subscriptions } = const { network, proxy, esplora, rgs, lsp, auth, subscriptions } =
await setAndGetMutinySettings(settings); await setAndGetMutinySettings(settings);

58
src/logic/waila.ts Normal file
View File

@@ -0,0 +1,58 @@
import initWaila, { PaymentParams } from "@mutinywallet/waila-wasm";
import { Result } from "~/utils/typescript";
// Make sure we've initialzied waila before we try to use it
await initWaila();
export type ParsedParams = {
address?: string;
invoice?: string;
amount_sats?: bigint;
network?: string;
memo?: string;
node_pubkey?: string;
lnurl?: string;
};
export function toParsedParams(
str: string,
ourNetwork: string
): Result<ParsedParams> {
let params;
try {
params = new PaymentParams(str || "");
} catch (e) {
console.error(e);
return { ok: false, error: new Error("Invalid payment request") };
}
// If WAILA doesn't return a network we should default to our own
// If the networks is testnet and we're on signet we should use signet
const network = !params.network
? ourNetwork
: params.network === "testnet" && ourNetwork === "signet"
? "signet"
: params.network;
if (network !== ourNetwork) {
return {
ok: false,
error: new Error(
`Destination is for ${params.network} but you're on ${ourNetwork}`
)
};
}
return {
ok: true,
value: {
address: params.address,
invoice: params.invoice,
amount_sats: params.amount_sats,
network,
memo: params.memo,
node_pubkey: params.node_pubkey,
lnurl: params.lnurl
}
};
}

View File

@@ -2,63 +2,9 @@ import Reader from "~/components/Reader";
import { createEffect, createSignal } from "solid-js"; import { createEffect, createSignal } from "solid-js";
import { useNavigate } from "solid-start"; import { useNavigate } from "solid-start";
import { Button } from "~/components/layout"; import { Button } from "~/components/layout";
import { PaymentParams } from "@mutinywallet/waila-wasm";
import { showToast } from "~/components/Toaster"; import { showToast } from "~/components/Toaster";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
import { Result } from "~/utils/typescript"; import { toParsedParams } from "~/logic/waila";
export type ParsedParams = {
address?: string;
invoice?: string;
amount_sats?: bigint;
network?: string;
memo?: string;
node_pubkey?: string;
lnurl?: string;
};
export function toParsedParams(
str: string,
ourNetwork: string
): Result<ParsedParams> {
let params;
try {
params = new PaymentParams(str || "");
} catch (e) {
console.error(e);
return { ok: false, error: new Error("Invalid payment request") };
}
// If WAILA doesn't return a network we should default to our own
// If the networks is testnet and we're on signet we should use signet
const network = !params.network
? ourNetwork
: params.network === "testnet" && ourNetwork === "signet"
? "signet"
: params.network;
if (network !== ourNetwork) {
return {
ok: false,
error: new Error(
`Destination is for ${params.network} but you're on ${ourNetwork}`
)
};
}
return {
ok: true,
value: {
address: params.address,
invoice: params.invoice,
amount_sats: params.amount_sats,
network,
memo: params.memo,
node_pubkey: params.node_pubkey,
lnurl: params.lnurl
}
};
}
export default function Scanner() { export default function Scanner() {
const [state, actions] = useMegaStore(); const [state, actions] = useMegaStore();

View File

@@ -26,7 +26,6 @@ import { Scan } from "~/assets/svg/Scan";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
import { Contact, MutinyInvoice } from "@mutinywallet/mutiny-wasm"; import { Contact, MutinyInvoice } from "@mutinywallet/mutiny-wasm";
import { StyledRadioGroup } from "~/components/layout/Radio"; import { StyledRadioGroup } from "~/components/layout/Radio";
import { ParsedParams, toParsedParams } from "./Scanner";
import { showToast } from "~/components/Toaster"; import { showToast } from "~/components/Toaster";
import eify from "~/utils/eify"; import eify from "~/utils/eify";
import megacheck from "~/assets/icons/megacheck.png"; import megacheck from "~/assets/icons/megacheck.png";
@@ -44,6 +43,7 @@ import { SuccessModal } from "~/components/successfail/SuccessModal";
import { ExternalLink } from "~/components/layout/ExternalLink"; import { ExternalLink } from "~/components/layout/ExternalLink";
import { InfoBox } from "~/components/InfoBox"; import { InfoBox } from "~/components/InfoBox";
import { useI18n } from "~/i18n/context"; import { useI18n } from "~/i18n/context";
import { ParsedParams, toParsedParams } from "~/logic/waila";
export type SendSource = "lightning" | "onchain"; export type SendSource = "lightning" | "onchain";

View File

@@ -1,5 +1,6 @@
import { DeleteEverything } from "~/components/DeleteEverything"; import { DeleteEverything } from "~/components/DeleteEverything";
import { ImportExport } from "~/components/ImportExport"; import { ImportExport } from "~/components/ImportExport";
import { LoadingIndicator } from "~/components/LoadingIndicator";
import { Logs } from "~/components/Logs"; import { Logs } from "~/components/Logs";
import NavBar from "~/components/NavBar"; import NavBar from "~/components/NavBar";
import { import {
@@ -13,6 +14,19 @@ import {
import { BackLink } from "~/components/layout/BackLink"; import { BackLink } from "~/components/layout/BackLink";
import { ExternalLink } from "~/components/layout/ExternalLink"; import { ExternalLink } from "~/components/layout/ExternalLink";
function EmergencyStack() {
return (
<VStack>
<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>
);
}
export default function EmergencyKit() { export default function EmergencyKit() {
return ( return (
<SafeArea> <SafeArea>
@@ -20,6 +34,7 @@ export default function EmergencyKit() {
<BackLink href="/settings" title="Settings" /> <BackLink href="/settings" title="Settings" />
<LargeHeader>Emergency Kit</LargeHeader> <LargeHeader>Emergency Kit</LargeHeader>
<VStack> <VStack>
<LoadingIndicator />
<NiceP> <NiceP>
If your wallet seems broken, here are some tools to try If your wallet seems broken, here are some tools to try
to debug and repair it. to debug and repair it.
@@ -31,12 +46,7 @@ export default function EmergencyKit() {
reach out to us for support. reach out to us for support.
</ExternalLink> </ExternalLink>
</NiceP> </NiceP>
<ImportExport emergency /> <EmergencyStack />
<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> </VStack>
</DefaultMain> </DefaultMain>
<NavBar activeTab="settings" /> <NavBar activeTab="settings" />

View File

@@ -12,20 +12,30 @@ import {
import { createStore, reconcile } from "solid-js/store"; import { createStore, reconcile } from "solid-js/store";
import { import {
MutinyWalletSettingStrings, MutinyWalletSettingStrings,
doubleInitDefense,
initializeWasm,
setupMutinyWallet setupMutinyWallet
} from "~/logic/mutinyWalletSetup"; } from "~/logic/mutinyWalletSetup";
import { MutinyBalance, MutinyWallet } from "@mutinywallet/mutiny-wasm"; import { MutinyBalance, MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { ParsedParams } from "~/routes/Scanner";
import { MutinyTagItem } from "~/utils/tags"; import { MutinyTagItem } from "~/utils/tags";
import { checkBrowserCompatibility } from "~/logic/browserCompatibility"; import { checkBrowserCompatibility } from "~/logic/browserCompatibility";
import eify from "~/utils/eify"; import eify from "~/utils/eify";
import { timeout } from "~/utils/timeout"; import { timeout } from "~/utils/timeout";
import { ActivityItem } from "~/components/Activity"; import { ActivityItem } from "~/components/Activity";
import { ParsedParams } from "~/logic/waila";
const MegaStoreContext = createContext<MegaStore>(); const MegaStoreContext = createContext<MegaStore>();
type UserStatus = undefined | "new_here" | "waitlisted" | "approved"; type UserStatus = undefined | "new_here" | "waitlisted" | "approved";
export type LoadStage =
| "fresh"
| "checking_user"
| "checking_double_init"
| "downloading"
| "setup"
| "done";
export type MegaStore = [ export type MegaStore = [
{ {
already_approved?: boolean; already_approved?: boolean;
@@ -48,6 +58,7 @@ export type MegaStore = [
subscription_timestamp?: number; subscription_timestamp?: number;
readonly mutiny_plus: boolean; readonly mutiny_plus: boolean;
needs_password: boolean; needs_password: boolean;
load_stage: LoadStage;
}, },
{ {
fetchUserStatus(): Promise<UserStatus>; fetchUserStatus(): Promise<UserStatus>;
@@ -100,7 +111,8 @@ export const Provider: ParentComponent = (props) => {
return false; return false;
else return true; else return true;
}, },
needs_password: false needs_password: false,
load_stage: "fresh" as LoadStage
}); });
const actions = { const actions = {
@@ -175,7 +187,15 @@ export const Provider: ParentComponent = (props) => {
throw state.setup_error; throw state.setup_error;
} }
setState({ wallet_loading: true }); setState({
wallet_loading: true,
load_stage: "checking_double_init"
});
await doubleInitDefense();
setState({ load_stage: "downloading" });
await initializeWasm();
setState({ load_stage: "setup" });
const mutinyWallet = await setupMutinyWallet( const mutinyWallet = await setupMutinyWallet(
settings, settings,
@@ -185,9 +205,6 @@ export const Provider: ParentComponent = (props) => {
// If we get this far then we don't need the password anymore // If we get this far then we don't need the password anymore
setState({ needs_password: false }); setState({ needs_password: false });
// Get balance optimistically
const balance = await mutinyWallet.get_balance();
// Subscription stuff. Skip if it's not already in localstorage // Subscription stuff. Skip if it's not already in localstorage
let subscription_timestamp = undefined; let subscription_timestamp = undefined;
const stored_subscription_timestamp = localStorage.getItem( const stored_subscription_timestamp = localStorage.getItem(
@@ -214,10 +231,14 @@ export const Provider: ParentComponent = (props) => {
} }
} }
// Get balance optimistically
const balance = await mutinyWallet.get_balance();
setState({ setState({
mutiny_wallet: mutinyWallet, mutiny_wallet: mutinyWallet,
wallet_loading: false, wallet_loading: false,
subscription_timestamp: subscription_timestamp, subscription_timestamp: subscription_timestamp,
load_stage: "done",
balance balance
}); });
} catch (e) { } catch (e) {
@@ -318,6 +339,7 @@ export const Provider: ParentComponent = (props) => {
// Fetch status from remote on load // Fetch status from remote on load
onMount(() => { onMount(() => {
setState({ load_stage: "checking_user" });
// eslint-disable-next-line // eslint-disable-next-line
actions.fetchUserStatus().then((status) => { actions.fetchUserStatus().then((status) => {
setState({ user_status: status }); setState({ user_status: status });
@@ -341,6 +363,7 @@ export const Provider: ParentComponent = (props) => {
console.log("stopping mutiny_wallet"); console.log("stopping mutiny_wallet");
await state.mutiny_wallet?.stop(); await state.mutiny_wallet?.stop();
console.log("mutiny_wallet stopped"); console.log("mutiny_wallet stopped");
sessionStorage.removeItem("MUTINY_WALLET_INITIALIZED");
}; };
} }
} }