Skip to content

Commit

Permalink
feat: app types, setup page (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
rolznz committed Jan 15, 2024
1 parent 9c8ddf4 commit cb5c8d8
Show file tree
Hide file tree
Showing 15 changed files with 200 additions and 41 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.DS_Store
.env
.env.local
*.db
*.macaroon
node_modules
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,17 @@ Generate a new OAuth client for <http://localhost:8080> from the [Alby developer

Go to `/frontend`

1. `yarn install`
2. `yarn dev`
1. `cp .env.example .env.local`
2. `yarn install`
3. `yarn dev`

### React Frontend (Alby Wallet API)

Follow standard LND instructions. After logging in, you will be redirected to the wrong port (8080), so manually re-open <http://localhost:5173>.

### Wails Frontend

`unset GTK_PATH`
`wails dev`

### Build and run locally
Expand Down
4 changes: 4 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@ func (svc *Service) ListApps(userApps *[]App, apps *[]api.App) error {
}
return nil
}

func (svc *Service) GetInfo(info *api.InfoResponse) {
info.BackendType = svc.cfg.LNBackendType
}
4 changes: 4 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ const (
LNDBackendType = "LND"
BreezBackendType = "BREEZ"
CookieName = "alby_nwc_session"

WailsAppType = "WAILS"
HttpAppType = "HTTP"
)

type Config struct {
Expand All @@ -15,6 +18,7 @@ type Config struct {
Relay string `envconfig:"RELAY" default:"wss://relay.getalby.com/v1"`
PublicRelay string `envconfig:"PUBLIC_RELAY"`
LNBackendType string `envconfig:"LN_BACKEND_TYPE" default:"ALBY"`
AppType string `envconfig:"APP_TYPE" default:"HTTP"`
LNDAddress string `envconfig:"LND_ADDRESS"`
LNDCertFile string `envconfig:"LND_CERT_FILE"`
LNDMacaroonFile string `envconfig:"LND_MACAROON_FILE"`
Expand Down
6 changes: 3 additions & 3 deletions echo_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ func (svc *Service) CSRFHandler(c echo.Context) error {
}

func (svc *Service) InfoHandler(c echo.Context) error {
responseBody := &api.InfoResponse{}
responseBody.BackendType = svc.cfg.LNBackendType
return c.JSON(http.StatusOK, responseBody)
responseBody := api.InfoResponse{}
svc.GetInfo(&responseBody)
return c.JSON(http.StatusOK, &responseBody)
}

func (svc *Service) LogoutHandler(c echo.Context) error {
Expand Down
2 changes: 2 additions & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_APP_TYPE=HTTP
#VITE_APP_TYPE=WAILS
20 changes: 19 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,33 @@ import ShowApp from "src/screens/apps/ShowApp";
import NewApp from "src/screens/apps/NewApp";
import AppCreated from "src/screens/apps/AppCreated";
import NotFound from "src/screens/NotFound";
import { useInfo } from "./hooks/useInfo";
import Loading from "./components/Loading";
import { Setup } from "./screens/Setup";

function App() {
const { data: info } = useInfo();

if (!info) {
return <Loading />;
}

return (
<div className="bg:white dark:bg-black min-h-full">
<Toaster />
<BrowserRouter>
<Routes>
<Route path="/" element={<Navbar />}>
<Route index element={<Navigate to="/apps" replace />} />
<Route
index
element={
<Navigate
to={info.setupCompleted ? "/apps" : "/setup"}
replace
/>
}
/>
<Route path="setup" element={<Setup />} />
<Route path="apps" element={<AppsList />} />
<Route path="apps/:pubkey" element={<ShowApp />} />
<Route path="apps/new" element={<NewApp />} />
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ function Navbar() {
>
Connections
</a>
<a
className={`${linkStyles} ${
location.pathname === "/setup" && selectedLinkStyles
}`}
href="/setup"
>
Setup
</a>
<a
className={`${linkStyles} ${
location.pathname === "/about" && selectedLinkStyles
Expand Down
109 changes: 109 additions & 0 deletions frontend/src/screens/Setup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React from "react";
import { useCSRF } from "src/hooks/useCSRF";
import { BackendType } from "src/types";
import {
appFetch,
handleFetchError,
validateFetchResponse,
} from "src/utils/fetch";

export function Setup() {
const [backendType, setBackendType] = React.useState<BackendType>("BREEZ");
const [greenlightInviteCode, setGreenlightInviteCode] =
React.useState<string>("");
const [mnemonic, setMnemonic] = React.useState<string>("");
const { data: csrf } = useCSRF();

async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
try {
if (!greenlightInviteCode || !mnemonic) {
throw new Error("please fill out all fields");
}
if (!csrf) {
throw new Error("info not loaded");
}
const response = await appFetch("/api/setup", {
method: "POST",
headers: {
"X-CSRF-Token": csrf,
},
body: JSON.stringify({}),
});
await validateFetchResponse(response);
window.location.href = "/";
} catch (error) {
handleFetchError("Failed to connect", error);
}
}

return (
<>
<p className="mb-4">
Enter your node connection credentials to connect to your wallet.
</p>
<form onSubmit={handleSubmit}>
<label
htmlFor="backend-type"
className="block font-medium text-gray-900 dark:text-white"
>
Backend Type
</label>
<select
name="backend-type"
value={backendType}
onChange={(e) => setBackendType(e.target.value as BackendType)}
id="backend-type"
className="mb-4 bg-gray-50 border border-gray-300 text-gray-900 focus:ring-purple-700 dark:focus:ring-purple-600 dark:ring-offset-gray-800 focus:ring-2 text-sm rounded-lg block w-full p-2.5 dark:bg-surface-00dp dark:border-gray-700 dark:placeholder-gray-400 dark:text-white"
>
<option value={"BREEZ"}>Breez</option>
<option value={"LND"}>LND</option>
</select>

{backendType === "BREEZ" && (
<>
<label
htmlFor="greenlight-invite-code"
className="block font-medium text-gray-900 dark:text-white"
>
Greenlight Invite Code
</label>
<input
name="greenlight-invite-code"
onChange={(e) => setGreenlightInviteCode(e.target.value)}
value={greenlightInviteCode}
type="password"
id="greenlight-invite-code"
className="bg-gray-50 border border-gray-300 text-gray-900 focus:ring-purple-700 dark:focus:ring-purple-600 dark:ring-offset-gray-800 focus:ring-2 text-sm rounded-lg block w-full p-2.5 dark:bg-surface-00dp dark:border-gray-700 dark:placeholder-gray-400 dark:text-white"
/>
<label
htmlFor="greenlight-invite-code"
className="mt-4 block font-medium text-gray-900 dark:text-white"
>
BIP39 Mnemonic
</label>
<input
name="mnemonic"
onChange={(e) => setMnemonic(e.target.value)}
value={mnemonic}
type="password"
id="mnemonic"
className="bg-gray-50 border border-gray-300 text-gray-900 focus:ring-purple-700 dark:focus:ring-purple-600 dark:ring-offset-gray-800 focus:ring-2 text-sm rounded-lg block w-full p-2.5 dark:bg-surface-00dp dark:border-gray-700 dark:placeholder-gray-400 dark:text-white"
/>
</>
)}
{backendType === "LND" && (
<>
<p>Coming soon</p>
</>
)}
<button
type="submit"
className="mt-4 inline-flex w-full bg-purple-700 cursor-pointer dark:text-neutral-200 duration-150 focus-visible:ring-2 focus-visible:ring-offset-2 focus:outline-none font-medium hover:bg-purple-900 items-center justify-center px-5 py-3 rounded-md shadow text-white transition"
>
Connect
</button>
</form>
</>
);
}
3 changes: 2 additions & 1 deletion frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const NIP_47_MAKE_INVOICE_METHOD = "make_invoice";
export const NIP_47_LOOKUP_INVOICE_METHOD = "lookup_invoice";
export const NIP_47_LIST_TRANSACTIONS_METHOD = "list_transactions";

export type BackendType = "ALBY" | "LND";
export type BackendType = "ALBY" | "LND" | "BREEZ";

export type RequestMethodType =
| "pay_invoice"
Expand Down Expand Up @@ -103,6 +103,7 @@ export interface NostrEvent {

export interface InfoResponse {
backendType: BackendType;
setupCompleted: boolean;
}

export interface CreateAppResponse {
Expand Down
7 changes: 2 additions & 5 deletions frontend/src/utils/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import toast from "src/components/Toast";

// TODO: this should be passed as an environment variable
const FETCH_METHOD = "wails";

import { WailsRequestRouter } from "wailsjs/go/main/WailsApp";

export const appFetch = async (args: Parameters<typeof fetch>) => {
export const appFetch = async (...args: Parameters<typeof fetch>) => {
try {
if (FETCH_METHOD === "wails") {
if (import.meta.env.VITE_APP_TYPE === "WAILS") {
while (!("go" in window)) {
console.log("go not in window");
await new Promise((resolve) => setTimeout(resolve, 500));
Expand Down
15 changes: 14 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func main() {
ctx, _ = signal.NotifyContext(ctx, os.Interrupt)

// TODO: use a different check to run wails app
if cfg.LNBackendType == "BREEZ" {
if cfg.AppType == WailsAppType {
app := NewApp(svc)
LaunchWailsApp(app)

Expand All @@ -157,6 +157,7 @@ func main() {
e := echo.New()

switch cfg.LNBackendType {
// TODO: LND and Breez should only start if configured
case LNDBackendType:
lndClient, err := NewLNDService(ctx, svc, e)
if err != nil {
Expand All @@ -169,6 +170,12 @@ func main() {
svc.Logger.Fatal(err)
}
svc.lnClient = oauthService
case BreezBackendType:
/*breezSvc, err := NewBreezService(cfg.BreezMnemonic, cfg.BreezAPIKey, cfg.GreenlightInviteCode, cfg.BreezWorkdir)
if err != nil {
svc.Logger.Fatal(err)
}
svc.lnClient = breezSvc*/
default:
svc.Logger.Fatalf("Unsupported LNBackendType: %v", cfg.LNBackendType)
}
Expand All @@ -191,6 +198,10 @@ func main() {
}()
}

var wg sync.WaitGroup
if cfg.AppType == HttpAppType {
wg.Add(1)
}
go func() {

//connect to the relay
Expand Down Expand Up @@ -233,7 +244,9 @@ func main() {
svc.Logger.Error(err)
}
svc.Logger.Info("Graceful shutdown completed. Goodbye.")
wg.Done()
}()
wg.Wait()
}

func (svc *Service) createFilters() nostr.Filters {
Expand Down
3 changes: 2 additions & 1 deletion models/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ type User struct {
}

type InfoResponse struct {
BackendType string `json:"backendType"`
BackendType string `json:"backendType"`
SetupCompleted bool `json:"setupCompleted"`
}
27 changes: 0 additions & 27 deletions wails_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"embed"
"log"

"github.com/getAlby/nostr-wallet-connect/models/api"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
Expand All @@ -29,32 +28,6 @@ func (a *WailsApp) startup(ctx context.Context) {
a.ctx = ctx
}

func (a *WailsApp) WailsRequestRouter(route string) interface{} {

switch route {
case "/api/apps":
userApps := []App{}
a.svc.db.Find(&userApps)
apps := []api.App{}
a.svc.ListApps(&userApps, &apps)
a.svc.Logger.Infof("END WailsRequestRouter %v", len(apps))
return apps
case "/api/info":
// TODO: move to API
return api.InfoResponse{
BackendType: a.svc.cfg.LNBackendType,
}
case "/api/user/me":
// no user in this mode
return nil
case "/api/csrf":
// never used
return ""
}
a.svc.Logger.Fatalf("Unhandled route: %s", route)
return nil
}

func LaunchWailsApp(app *WailsApp) {
err := wails.Run(&options.App{
Title: "Nostr Wallet Connect",
Expand Down
26 changes: 26 additions & 0 deletions wails_handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import "github.com/getAlby/nostr-wallet-connect/models/api"

func (a *WailsApp) WailsRequestRouter(route string) interface{} {
switch route {
case "/api/apps":

userApps := []App{}
a.svc.db.Find(&userApps)
apps := []api.App{}
a.svc.ListApps(&userApps, &apps)
a.svc.Logger.Infof("END WailsRequestRouter %v", len(apps))
return apps
case "/api/info":
infoResponse := api.InfoResponse{}
a.svc.GetInfo(&infoResponse)
return infoResponse
case "/api/user/me":
return nil
case "/api/csrf":
return "dummy"
}
a.svc.Logger.Fatalf("Unhandled route: %s", route)
return nil
}

0 comments on commit cb5c8d8

Please sign in to comment.