Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

feat: add finish screen for setup #38

Merged
merged 6 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,32 +86,28 @@ _If you get a blank screen the first load, close the window and start the app ag

Breez SDK requires gcc to build the Breez bindings. Run `choco install mingw` and copy the breez SDK bindings file into the root of this directory (from your go installation directory) as per the [Breez SDK instructions](https://github.com/breez/breez-sdk-go?tab=readme-ov-file#windows). ALSO copy the bindings file into the output directory alongside the .exe in order to run it.

## Configuration parameters
## Optional configuration parameters

- `NOSTR_PRIVKEY`: the private key of this service. Should be a securely randomly generated 32 byte hex string.
- `CLIENT_NOSTR_PUBKEY`: if set, this service will only listen to events authored by this public key. You can set this to your own nostr public key.
- `RELAY`: default: "wss://relay.getalby.com/v1"
- `COOKIE_SECRET`: a randomly generated secret string. (only needed in http mode)
- `DATABASE_URI`: a sqlite filename. Default: .data/nwc.db
- `PORT`: the port on which the app should listen on (default: 8080)
- `LN_BACKEND_TYPE`: LND or BREEZ
- `WORK_DIR`: directory to store NWC data files. Default: .data
-

### LND Backend parameters

_For cert and macaroon, either hex or file options can be used._
Currently only LND can be configured via env. Other node types must be configured via the UI.

_To configure via env, the following parameters must be provided:_

- `LN_BACKEND_TYPE`: LND
- `LND_ADDRESS`: the LND gRPC address, eg. `localhost:10009` (used with the LND backend)
- `LND_CERT_FILE`: the location where LND's `tls.cert` file can be found (used with the LND backend)
- `LND_MACAROON_FILE`: the location where LND's `admin.macaroon` file can be found (used with the LND backend)

### BREEZ Backend parameters

- `BREEZ_MNEMONIC`: your bip39 mnemonic key phrase e.g. "define limit soccer guilt trim mechanic beyond outside best give south shine"
- `BREEZ_API_KEY`: contact breez for more info
- `GREENLIGHT_INVITE_CODE`: contact blockstream for more info

## Application deeplink options

### `/apps/new` deeplink options
Expand Down
4 changes: 2 additions & 2 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@ func (api *API) Setup(setupRequest *models.SetupRequest) error {
if setupRequest.BreezAPIKey != "" {
api.svc.cfg.SetUpdate("BreezAPIKey", setupRequest.BreezAPIKey, setupRequest.UnlockPassword)
}
if setupRequest.BreezMnemonic != "" {
api.svc.cfg.SetUpdate("BreezMnemonic", setupRequest.BreezMnemonic, setupRequest.UnlockPassword)
if setupRequest.Mnemonic != "" {
api.svc.cfg.SetUpdate("Mnemonic", setupRequest.Mnemonic, setupRequest.UnlockPassword)
}
if setupRequest.GreenlightInviteCode != "" {
api.svc.cfg.SetUpdate("GreenlightInviteCode", setupRequest.GreenlightInviteCode, setupRequest.UnlockPassword)
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { HomeRedirect } from "src/components/redirects/HomeRedirect";
import Unlock from "src/screens/Unlock";
import { SetupRedirect } from "src/components/redirects/SetupRedirect";
import { SetupMnemonic } from "src/screens/setup/SetupMnemonic";
import { SetupFinish } from "src/screens/setup/SetupFinish";

function App() {
return (
Expand All @@ -45,6 +46,7 @@ function App() {
<Route path="node" element={<SetupNode />} />
<Route path="wallet" element={<SetupWallet />} />
<Route path="mnemonic" element={<SetupMnemonic />} />
<Route path="finish" element={<SetupFinish />} />
</Route>
<Route path="apps" element={<AppsRedirect />}>
<Route index path="" element={<AppsList />} />
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/screens/Start.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ export default function Start() {
try {
setLoading(true);
if (!csrf) {
throw new Error("info not loaded");
throw new Error("csrf not loaded");
}
const res = await request("/api/start", {
await request("/api/start", {
method: "POST",
headers: {
"X-CSRF-Token": csrf,
Expand All @@ -32,7 +32,6 @@ export default function Start() {
unlockPassword,
}),
});
console.log({ res });
await refetchInfo();

navigate("/");
Expand Down
95 changes: 95 additions & 0 deletions frontend/src/screens/setup/SetupFinish.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import Container from "src/components/Container";
import { useInfo } from "src/hooks/useInfo";
import useSetupStore from "src/state/SetupStore";
import { useCSRF } from "src/hooks/useCSRF";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request";
import Loading from "src/components/Loading";
import { NodeInfo } from "src/types";
import React from "react";

export function SetupFinish() {
const navigate = useNavigate();
const { nodeInfo, unlockPassword } = useSetupStore();

const { mutate: refetchInfo } = useInfo();
const { data: csrf } = useCSRF();
const [connectionError, setConnectionError] = React.useState(false);
const hasFetchedRef = React.useRef(false);

useEffect(() => {
// ensure setup call is only called once
if (!csrf || hasFetchedRef.current) {
return;
}
hasFetchedRef.current = true;

(async () => {
const succeeded = await finishSetup(csrf, nodeInfo, unlockPassword);
if (succeeded) {
await refetchInfo();
navigate("/");
} else {
setConnectionError(true);
}
})();
}, [csrf, nodeInfo, refetchInfo, navigate, unlockPassword]);

if (connectionError) {
return (
<Container>
<h1 className="font-semibold text-lg font-headline mt-16 mb-8 dark:text-white">
Connection Failed
</h1>

<p>Navigate back to check your configuration, then try again.</p>
</Container>
);
}

return (
<Container>
<h1 className="font-semibold text-lg font-headline mt-16 mb-8 dark:text-white">
Connecting...
</h1>

<Loading />
</Container>
);
}

const finishSetup = async (
csrf: string,
nodeInfo: NodeInfo,
unlockPassword: string
): Promise<boolean> => {
try {
await request("/api/setup", {
method: "POST",
headers: {
"X-CSRF-Token": csrf,
"Content-Type": "application/json",
},
body: JSON.stringify({
...nodeInfo,
unlockPassword,
}),
});
await request("/api/start", {
method: "POST",
headers: {
"X-CSRF-Token": csrf,
"Content-Type": "application/json",
},
body: JSON.stringify({
unlockPassword,
}),
});
return true;
} catch (error) {
handleRequestError("Failed to connect", error);
return false;
}
};
49 changes: 10 additions & 39 deletions frontend/src/screens/setup/SetupMnemonic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,28 @@ import {
PopiconsShieldLine,
PopiconsTriangleExclamationLine,
} from "@popicons/react";
import * as bip39 from "@scure/bip39";
import { wordlist } from "@scure/bip39/wordlists/english";
import { useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";

import { useInfo } from "src/hooks/useInfo";
import * as bip39 from "@scure/bip39";
import { wordlist } from "@scure/bip39/wordlists/english";
import MnemonicInputs from "src/components/MnemonicInputs";
import ConnectButton from "src/components/ConnectButton";
import { useCSRF } from "src/hooks/useCSRF";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request";
import useSetupStore from "src/state/SetupStore";
import toast from "src/components/Toast";

export function SetupMnemonic() {
const navigate = useNavigate();
const { state, search } = useLocation();
const { search } = useLocation();
const setupStore = useSetupStore();
const params = new URLSearchParams(search);
const isNew = params.get("wallet") === "new";

const data = state as object;

const [mnemonic, setMnemonic] = useState<string>(
isNew ? bip39.generateMnemonic(wordlist, 128) : ""
setupStore.nodeInfo.mnemonic ||
(isNew ? bip39.generateMnemonic(wordlist, 128) : "")
);
const [backedUp, isBackedUp] = useState<boolean>(false);
const [isConnecting, setConnecting] = useState(false);

const { mutate: refetchInfo } = useInfo();
const { data: csrf } = useCSRF();

async function onSubmit(e: React.FormEvent) {
e.preventDefault();
Expand All @@ -43,30 +36,8 @@ export function SetupMnemonic() {
return;
}

try {
setConnecting(true);
if (!csrf) {
throw new Error("info not loaded");
}
await request("/api/setup", {
method: "POST",
headers: {
"X-CSRF-Token": csrf,
"Content-Type": "application/json",
},
body: JSON.stringify({
breezMnemonic: mnemonic,
...data,
}),
});

await refetchInfo();
navigate("/");
} catch (error) {
handleRequestError("Failed to connect", error);
} finally {
setConnecting(false);
}
setupStore.updateNodeInfo({ mnemonic });
navigate(`/setup/finish`);
}

return (
Expand Down Expand Up @@ -159,7 +130,7 @@ export function SetupMnemonic() {
<ConnectButton
submitText="Finish"
loadingText="Saving..."
isConnecting={isConnecting}
isConnecting={false}
disabled={isNew ? !backedUp : false}
/>
</form>
Expand Down
Loading
Loading