From 53be2b53501f58678e39c0e5c50a3137e26f3fb9 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Thu, 18 Jan 2024 20:39:25 +0700 Subject: [PATCH 1/5] feat: split http and wails into two entrypoints --- .vscode/settings.json | 5 +-- README.md | 29 +++++++-------- config.go | 3 +- frontend/.env.example | 2 -- frontend/package.json | 2 ++ frontend/src/utils/request.ts | 7 ++-- main.go | 68 +++++------------------------------ main_http.go | 68 +++++++++++++++++++++++++++++++++++ main_wails.go | 29 +++++++++++++++ wails.json | 4 +-- 10 files changed, 128 insertions(+), 89 deletions(-) delete mode 100644 frontend/.env.example create mode 100644 main_http.go create mode 100644 main_wails.go diff --git a/.vscode/settings.json b/.vscode/settings.json index f33eb35f..ccf872f2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,6 @@ "[go]": { "editor.defaultFormatter": "golang.go" }, - "editor.formatOnSave": true -} \ No newline at end of file + "editor.formatOnSave": true, + "go.buildTags": "http,wails" +} diff --git a/README.md b/README.md index 46ce6275..5035e787 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ Connect applications like [Damus](https://damus.io/) or [Amethyst](https://linkt ## Supported Backends -- [Alby](https://getalby.com) (see: alby.go) - LND (see: lnd.go) +- Breez (see: breez.go) - want more? please open an issue. ## Installation @@ -27,35 +27,30 @@ To get a new random Nostr key use `openssl rand -hex 32` or similar. ## Development -### Server (LND) +### Server (HTTP mode) 1. Create a Lightning Polar setup with two LND nodes and uncomment the Polar LND section in your `.env` file. 2. Compile the frontend or run `touch frontend/dist/tmp` to ensure there are embeddable files available. -3. `go run .` or `gow -e=go,mod,html,css run .` using [gow](https://github.com/mitranim/gow) +3. `go run .` -### Server (Alby Wallet API) - -Generate a new OAuth client for from the [Alby developer portal](https://getalby.com/developer) and set `ALBY_CLIENT_ID` and `ALBY_CLIENT_SECRET` in your .env file. - -### React Frontend (LND) +### React Frontend (HTTP mode) Go to `/frontend` -1. `cp .env.example .env.local` -2. `yarn install` -3. `yarn dev` +1. `yarn install` +2. `yarn dev` -### React Frontend (Alby Wallet API) +### Wails (Backend + Frontend) -Follow standard LND instructions. After logging in, you will be redirected to the wrong port (8080), so manually re-open . +`unset GTK_PATH && wails dev -tags "wails"` -### Wails (Backend + Frontend) +#### Wails Production build -`unset GTK_PATH && wails dev` +`wails build -tags "wails"` -### Build and run locally +### Build and run locally (HTTP mode) `mkdir tmp` `go build -o main` @@ -64,7 +59,7 @@ Follow standard LND instructions. After logging in, you will be redirected to th `cd tmp` `./main` -### Run dockerfile locally +### Run dockerfile locally (HTTP mode) `docker build . -t nwc-local` diff --git a/config.go b/config.go index 52739eee..f033b64c 100644 --- a/config.go +++ b/config.go @@ -17,12 +17,11 @@ type Config struct { // database config always takes preference. db.Config NostrSecretKey string `envconfig:"NOSTR_PRIVKEY"` - CookieSecret string `envconfig:"COOKIE_SECRET" required:"true"` + CookieSecret string `envconfig:"COOKIE_SECRET"` CookieDomain string `envconfig:"COOKIE_DOMAIN"` ClientPubkey string `envconfig:"CLIENT_NOSTR_PUBKEY"` Relay string `envconfig:"RELAY" default:"wss://relay.getalby.com/v1"` PublicRelay string `envconfig:"PUBLIC_RELAY"` - AppType string `envconfig:"APP_TYPE" default:"HTTP"` BreezAPIKey string `envconfig:"BREEZ_API_KEY"` BreezWorkdir string `envconfig:"BREEZ_WORK_DIR" default:".breez"` BasicAuthUser string `envconfig:"BASIC_AUTH_USER"` diff --git a/frontend/.env.example b/frontend/.env.example deleted file mode 100644 index d9c6f05d..00000000 --- a/frontend/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -VITE_APP_TYPE=HTTP -#VITE_APP_TYPE=WAILS \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index e2c02d24..b61ce8ee 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,7 +5,9 @@ "type": "module", "scripts": { "dev": "vite", + "dev:wails": "VITE_NWC_APP_TYPE=WAILS vite", "build": "tsc && vite build", + "build:wails": "tsc && VITE_NWC_APP_TYPE=WAILS vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts index f9839440..007ccc9b 100644 --- a/frontend/src/utils/request.ts +++ b/frontend/src/utils/request.ts @@ -7,8 +7,9 @@ export const request = async ( ...args: Parameters ): Promise => { try { + const appType = import.meta.env.VITE_NWC_APP_TYPE || "HTTP"; // TODO: can we use a different request file at build time so no conditional / env variable is needed? - switch (import.meta.env.VITE_APP_TYPE) { + switch (appType) { case "WAILS": { const res = await WailsRequestRouter( args[0].toString(), @@ -43,9 +44,7 @@ export const request = async ( return body; } default: - throw new Error( - "Unsupported app type: " + import.meta.env.VITE_APP_TYPE - ); + throw new Error("Unsupported app type: " + appType); } } catch (error) { console.error("Failed to fetch", error); diff --git a/main.go b/main.go index 551914f3..464aa1f2 100644 --- a/main.go +++ b/main.go @@ -4,21 +4,17 @@ import ( "context" "database/sql" "errors" - "fmt" - "net/http" "os" "os/signal" "strings" "sync" "time" - echologrus "github.com/davrux/echo-logrus/v4" "github.com/getAlby/nostr-wallet-connect/migrations" "github.com/getAlby/nostr-wallet-connect/models/db" "github.com/glebarez/sqlite" "github.com/joho/godotenv" "github.com/kelseyhightower/envconfig" - "github.com/labstack/echo/v4" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip19" log "github.com/sirupsen/logrus" @@ -31,8 +27,8 @@ import ( gormtrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/gorm.io/gorm.v1" ) -func main() { - +// TODO: move to service.go +func CreateService(wg *sync.WaitGroup) *Service { // Load config from environment variables / .env file godotenv.Load(".env") cfg := &Config{} @@ -148,59 +144,10 @@ func main() { logger.SetLevel(log.InfoLevel) svc.Logger = logger - var wg sync.WaitGroup - wg.Add(1) - // TODO: can we move these into two separate entrypoint files? - switch cfg.AppType { - case WailsAppType: - err := svc.launchLNBackend() - if err != nil { - // LN backend not needed immediately, just log errors - svc.Logger.Warnf("Failed to launch LN backend: %v", err) - } - go func() { - app := NewApp(svc) - LaunchWailsApp(app) - wg.Done() - svc.Logger.Info("Wails app exited") - }() - case HttpAppType: - // using echo - echologrus.Logger = logger - e := echo.New() - - // Alby backend only supported in HTTP app type - if svc.cfg.LNBackendType == AlbyBackendType { - oauthService, err := NewAlbyOauthService(svc, e) - if err != nil { - svc.Logger.Fatal(err) - } - svc.lnClient = oauthService - } else { - err := svc.launchLNBackend() - if err != nil { - // LN backend not needed immediately, just log errors - svc.Logger.Warnf("Failed to launch LN backend: %v", err) - } - } - - //register shared routes - svc.RegisterSharedRoutes(e) - //start Echo server - go func() { - if err := e.Start(fmt.Sprintf(":%v", svc.cfg.Port)); err != nil && err != http.ErrServerClosed { - e.Logger.Fatal("shutting down the server") - } - //handle graceful shutdown - <-ctx.Done() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - e.Shutdown(ctx) - svc.Logger.Info("Echo server exited") - wg.Done() - }() - default: - svc.Logger.Fatalf("Unknown app type: %s", cfg.AppType) + err = svc.launchLNBackend() + if err != nil { + // LN backend not needed immediately, just log errors + svc.Logger.Warnf("Failed to launch LN backend: %v", err) } go func() { @@ -246,7 +193,8 @@ func main() { svc.Logger.Info("Graceful shutdown completed. Goodbye.") wg.Done() }() - wg.Wait() + + return svc } func (svc *Service) setupDbConfig() error { diff --git a/main_http.go b/main_http.go new file mode 100644 index 00000000..67a101f9 --- /dev/null +++ b/main_http.go @@ -0,0 +1,68 @@ +//go:build !wails || http +// +build !wails http + +// (http tag above is simply to fix go language server issue and is not needed to build the app) + +package main + +import ( + "context" + "fmt" + "net/http" + "sync" + "time" + + echologrus "github.com/davrux/echo-logrus/v4" + "github.com/labstack/echo/v4" + log "github.com/sirupsen/logrus" +) + +// ignore this warning: we use build tags +// this function will only be executed if no wails tag is set +func main() { + log.Info("NWC Starting in HTTP mode") + var wg sync.WaitGroup + wg.Add(1) + + svc := CreateService(&wg) + + if svc.cfg.CookieSecret == "" { + svc.Logger.Fatalf("required key COOKIE_SECRET missing value") + } + + echologrus.Logger = svc.Logger + e := echo.New() + + // Alby backend only supported in HTTP app type + if svc.cfg.LNBackendType == AlbyBackendType { + oauthService, err := NewAlbyOauthService(svc, e) + if err != nil { + svc.Logger.Fatal(err) + } + svc.lnClient = oauthService + } else { + err := svc.launchLNBackend() + if err != nil { + // LN backend not needed immediately, just log errors + svc.Logger.Warnf("Failed to launch LN backend: %v", err) + } + } + + //register shared routes + svc.RegisterSharedRoutes(e) + //start Echo server + go func() { + if err := e.Start(fmt.Sprintf(":%v", svc.cfg.Port)); err != nil && err != http.ErrServerClosed { + e.Logger.Fatal("shutting down the server") + } + //handle graceful shutdown + <-svc.ctx.Done() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + e.Shutdown(ctx) + svc.Logger.Info("Echo server exited") + wg.Done() + }() + + wg.Wait() +} diff --git a/main_wails.go b/main_wails.go new file mode 100644 index 00000000..25d8a164 --- /dev/null +++ b/main_wails.go @@ -0,0 +1,29 @@ +//go:build wails +// +build wails + +package main + +import ( + "sync" + + log "github.com/sirupsen/logrus" +) + +// ignore this warning: we use build tags +// this function will only be executed if the wails tag is set +func main() { + log.Info("NWC Starting in WAILS mode") + var wg sync.WaitGroup + wg.Add(1) + + svc := CreateService(&wg) + + go func() { + app := NewApp(svc) + LaunchWailsApp(app) + wg.Done() + svc.Logger.Info("Wails app exited") + }() + + wg.Wait() +} diff --git a/wails.json b/wails.json index c9baa49e..d0be7f83 100644 --- a/wails.json +++ b/wails.json @@ -3,8 +3,8 @@ "name": "nostr-wallet-connect", "outputfilename": "Nostr-Wallet-Connect", "frontend:install": "yarn install", - "frontend:build": "yarn build", - "frontend:dev:watcher": "yarn dev", + "frontend:build": "yarn build:wails", + "frontend:dev:watcher": "yarn dev:wails", "frontend:dev:serverUrl": "auto", "author": { "name": "Alby Contributors", From 8c46ba1df42fe044dcd1b0168ac99a43a59cf79c Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Thu, 18 Jan 2024 21:13:15 +0700 Subject: [PATCH 2/5] fix: remove duplicate backend launch code --- main_http.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/main_http.go b/main_http.go index 67a101f9..f4a9dcf4 100644 --- a/main_http.go +++ b/main_http.go @@ -33,21 +33,6 @@ func main() { echologrus.Logger = svc.Logger e := echo.New() - // Alby backend only supported in HTTP app type - if svc.cfg.LNBackendType == AlbyBackendType { - oauthService, err := NewAlbyOauthService(svc, e) - if err != nil { - svc.Logger.Fatal(err) - } - svc.lnClient = oauthService - } else { - err := svc.launchLNBackend() - if err != nil { - // LN backend not needed immediately, just log errors - svc.Logger.Warnf("Failed to launch LN backend: %v", err) - } - } - //register shared routes svc.RegisterSharedRoutes(e) //start Echo server From c1687baece2faf56687696c9a35ac495d0ccb633 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Thu, 18 Jan 2024 21:13:41 +0700 Subject: [PATCH 3/5] chore: rename new service function --- main.go | 2 +- main_http.go | 2 +- main_wails.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 464aa1f2..db391d85 100644 --- a/main.go +++ b/main.go @@ -28,7 +28,7 @@ import ( ) // TODO: move to service.go -func CreateService(wg *sync.WaitGroup) *Service { +func NewService(wg *sync.WaitGroup) *Service { // Load config from environment variables / .env file godotenv.Load(".env") cfg := &Config{} diff --git a/main_http.go b/main_http.go index f4a9dcf4..2c2d25b4 100644 --- a/main_http.go +++ b/main_http.go @@ -24,7 +24,7 @@ func main() { var wg sync.WaitGroup wg.Add(1) - svc := CreateService(&wg) + svc := NewService(&wg) if svc.cfg.CookieSecret == "" { svc.Logger.Fatalf("required key COOKIE_SECRET missing value") diff --git a/main_wails.go b/main_wails.go index 25d8a164..8645e4ce 100644 --- a/main_wails.go +++ b/main_wails.go @@ -16,7 +16,7 @@ func main() { var wg sync.WaitGroup wg.Add(1) - svc := CreateService(&wg) + svc := NewService(&wg) go func() { app := NewApp(svc) From c9580842cca0306261b0f1e33cc8c2056addf592 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Fri, 19 Jan 2024 14:34:17 +0700 Subject: [PATCH 4/5] fix: graceful shutdown in http and wails modes --- README.md | 2 ++ main.go | 21 +++++++++++++++++---- main_http.go | 28 +++++++++++++--------------- main_wails.go | 24 ++++++++++++------------ service.go | 5 +++-- 5 files changed, 47 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 5035e787..a43bf5fc 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ Go to `/frontend` `unset GTK_PATH && wails dev -tags "wails"` +_If you get a blank screen the first load, close the window and start the app again_ + #### Wails Production build `wails build -tags "wails"` diff --git a/main.go b/main.go index db391d85..b2c9d393 100644 --- a/main.go +++ b/main.go @@ -28,7 +28,7 @@ import ( ) // TODO: move to service.go -func NewService(wg *sync.WaitGroup) *Service { +func NewService(ctx context.Context) *Service { // Load config from environment variables / .env file godotenv.Load(".env") cfg := &Config{} @@ -118,13 +118,16 @@ func NewService(wg *sync.WaitGroup) *Service { } log.Infof("Starting nostr-wallet-connect. npub: %s hex: %s", npub, identityPubkey) - ctx := context.Background() ctx, _ = signal.NotifyContext(ctx, os.Interrupt) + var wg sync.WaitGroup + wg.Add(1) + svc := &Service{ cfg: cfg, db: db, ctx: ctx, + wg: &wg, } err = svc.setupDbConfig() @@ -156,7 +159,9 @@ func NewService(wg *sync.WaitGroup) *Service { relay, err := nostr.RelayConnect(ctx, cfg.Relay, nostr.WithNoticeHandler(svc.noticeHandler)) if err != nil { - svc.Logger.Fatal(err) + svc.Logger.Errorf("Failed to connect to relay: %v", err) + wg.Done() + return } //publish event with NIP-47 info @@ -186,11 +191,19 @@ func NewService(wg *sync.WaitGroup) *Service { //err being nil means that the context was canceled and we should exit the program. break } + svc.Logger.Info("Disconnecting from relay...") err = relay.Close() if err != nil { svc.Logger.Error(err) } - svc.Logger.Info("Graceful shutdown completed. Goodbye.") + if svc.lnClient != nil { + svc.Logger.Info("Shutting down LN backend...") + err = svc.lnClient.Shutdown() + if err != nil { + svc.Logger.Error(err) + } + } + svc.Logger.Info("Relay subroutine ended") wg.Done() }() diff --git a/main_http.go b/main_http.go index 2c2d25b4..1897ceb8 100644 --- a/main_http.go +++ b/main_http.go @@ -9,7 +9,6 @@ import ( "context" "fmt" "net/http" - "sync" "time" echologrus "github.com/davrux/echo-logrus/v4" @@ -21,10 +20,8 @@ import ( // this function will only be executed if no wails tag is set func main() { log.Info("NWC Starting in HTTP mode") - var wg sync.WaitGroup - wg.Add(1) - - svc := NewService(&wg) + ctx := context.Background() + svc := NewService(ctx) if svc.cfg.CookieSecret == "" { svc.Logger.Fatalf("required key COOKIE_SECRET missing value") @@ -38,16 +35,17 @@ func main() { //start Echo server go func() { if err := e.Start(fmt.Sprintf(":%v", svc.cfg.Port)); err != nil && err != http.ErrServerClosed { - e.Logger.Fatal("shutting down the server") + e.Logger.Fatalf("shutting down the server: %v", err) } - //handle graceful shutdown - <-svc.ctx.Done() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - e.Shutdown(ctx) - svc.Logger.Info("Echo server exited") - wg.Done() }() - - wg.Wait() + //handle graceful shutdown + <-svc.ctx.Done() + svc.Logger.Infof("Shutting down echo server...") + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + e.Shutdown(ctx) + svc.Logger.Info("Echo server exited") + svc.Logger.Info("Waiting for service to exit...") + svc.wg.Wait() + svc.Logger.Info("Service exited") } diff --git a/main_wails.go b/main_wails.go index 8645e4ce..5192898f 100644 --- a/main_wails.go +++ b/main_wails.go @@ -4,7 +4,7 @@ package main import ( - "sync" + "context" log "github.com/sirupsen/logrus" ) @@ -13,17 +13,17 @@ import ( // this function will only be executed if the wails tag is set func main() { log.Info("NWC Starting in WAILS mode") - var wg sync.WaitGroup - wg.Add(1) + ctx, cancel := context.WithCancel(context.Background()) + svc := NewService(ctx) - svc := NewService(&wg) + app := NewApp(svc) + LaunchWailsApp(app) + svc.Logger.Info("Wails app exited") - go func() { - app := NewApp(svc) - LaunchWailsApp(app) - wg.Done() - svc.Logger.Info("Wails app exited") - }() - - wg.Wait() + svc.Logger.Info("Cancelling service context...") + // cancel the service context + cancel() + svc.Logger.Info("Waiting for service to exit...") + svc.wg.Wait() + svc.Logger.Info("Service exited") } diff --git a/service.go b/service.go index b7472994..65969d07 100644 --- a/service.go +++ b/service.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "strings" + "sync" "time" "github.com/labstack/echo-contrib/session" @@ -23,6 +24,7 @@ type Service struct { ReceivedEOS bool Logger *logrus.Logger ctx context.Context + wg *sync.WaitGroup } func (svc *Service) GetUser(c echo.Context) (user *User, err error) { @@ -123,7 +125,6 @@ func (svc *Service) StartSubscription(ctx context.Context, sub *nostr.Subscripti } }(event) } - svc.Logger.Info("Subscription ended") }() select { @@ -135,7 +136,7 @@ func (svc *Service) StartSubscription(ctx context.Context, sub *nostr.Subscripti svc.Logger.Errorf("Subscription error %v", ctx.Err()) return ctx.Err() } - svc.Logger.Info("Exiting subscription.") + svc.Logger.Info("Exiting subscription...") return nil } } From 73c4b75f7d6e1283018b19f6a1b57e87e40dd26c Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Fri, 19 Jan 2024 15:19:01 +0700 Subject: [PATCH 5/5] chore: remove unused code --- config.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/config.go b/config.go index 2ae04410..dcc8d100 100644 --- a/config.go +++ b/config.go @@ -7,9 +7,6 @@ const ( LNDBackendType = "LND" BreezBackendType = "BREEZ" CookieName = "alby_nwc_session" - - WailsAppType = "WAILS" - HttpAppType = "HTTP" ) type Config struct {