Skip to content

Commit

Permalink
feat: show startup error (#571)
Browse files Browse the repository at this point in the history
* feat: show startup error

* chore: move startup error var to api

* chore: simplify start error display

* fix: startup error on setup page

---------

Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
  • Loading branch information
im-adithya and rolznz authored Sep 4, 2024
1 parent ae43099 commit da27ed2
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 45 deletions.
32 changes: 24 additions & 8 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ import (
)

type api struct {
db *gorm.DB
dbSvc db.DBService
cfg config.Config
svc service.Service
permissionsSvc permissions.PermissionsService
keys keys.Keys
albyOAuthSvc alby.AlbyOAuthService
db *gorm.DB
dbSvc db.DBService
cfg config.Config
svc service.Service
permissionsSvc permissions.PermissionsService
keys keys.Keys
albyOAuthSvc alby.AlbyOAuthService
startupError error
startupErrorTime time.Time
}

func NewAPI(svc service.Service, gormDB *gorm.DB, config config.Config, keys keys.Keys, albyOAuthSvc alby.AlbyOAuthService, eventPublisher events.EventPublisher) *api {
Expand Down Expand Up @@ -648,6 +650,10 @@ func (api *api) GetInfo(ctx context.Context) (*InfoResponse, error) {
info := InfoResponse{}
backendType, _ := api.cfg.Get("LNBackendType", "")
info.SetupCompleted = api.cfg.SetupCompleted()
if api.startupError != nil {
info.StartupError = api.startupError.Error()
info.StartupErrorTime = api.startupErrorTime
}
info.Running = api.svc.GetLNClient() != nil
info.BackendType = backendType
info.AlbyAuthUrl = api.albyOAuthSvc.GetAuthUrl()
Expand Down Expand Up @@ -700,7 +706,17 @@ func (api *api) SetNextBackupReminder(backupReminderRequest *BackupReminderReque

var startMutex sync.Mutex

func (api *api) Start(startRequest *StartRequest) error {
func (api *api) Start(startRequest *StartRequest) {
api.startupError = nil
err := api.StartInternal(startRequest)
if err != nil {
logger.Logger.WithError(err).Error("Failed to start node")
api.startupError = err
api.startupErrorTime = time.Now()
}
}

func (api *api) StartInternal(startRequest *StartRequest) (err error) {
if !startMutex.TryLock() {
// do not allow to start twice in case this is somehow called twice
return errors.New("app is already starting")
Expand Down
28 changes: 15 additions & 13 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type API interface {
GetInfo(ctx context.Context) (*InfoResponse, error)
GetMnemonic(unlockPassword string) (*MnemonicResponse, error)
SetNextBackupReminder(backupReminderRequest *BackupReminderRequest) error
Start(startRequest *StartRequest) error
Start(startRequest *StartRequest)
Setup(ctx context.Context, setupRequest *SetupRequest) error
SendPaymentProbes(ctx context.Context, sendPaymentProbesRequest *SendPaymentProbesRequest) (*SendPaymentProbesResponse, error)
SendSpontaneousPaymentProbes(ctx context.Context, sendSpontaneousPaymentProbesRequest *SendSpontaneousPaymentProbesRequest) (*SendSpontaneousPaymentProbesResponse, error)
Expand Down Expand Up @@ -152,18 +152,20 @@ type User struct {
}

type InfoResponse struct {
BackendType string `json:"backendType"`
SetupCompleted bool `json:"setupCompleted"`
OAuthRedirect bool `json:"oauthRedirect"`
Running bool `json:"running"`
Unlocked bool `json:"unlocked"`
AlbyAuthUrl string `json:"albyAuthUrl"`
NextBackupReminder string `json:"nextBackupReminder"`
AlbyUserIdentifier string `json:"albyUserIdentifier"`
AlbyAccountConnected bool `json:"albyAccountConnected"`
Version string `json:"version"`
Network string `json:"network"`
EnableAdvancedSetup bool `json:"enableAdvancedSetup"`
BackendType string `json:"backendType"`
SetupCompleted bool `json:"setupCompleted"`
OAuthRedirect bool `json:"oauthRedirect"`
Running bool `json:"running"`
Unlocked bool `json:"unlocked"`
AlbyAuthUrl string `json:"albyAuthUrl"`
NextBackupReminder string `json:"nextBackupReminder"`
AlbyUserIdentifier string `json:"albyUserIdentifier"`
AlbyAccountConnected bool `json:"albyAccountConnected"`
Version string `json:"version"`
Network string `json:"network"`
EnableAdvancedSetup bool `json:"enableAdvancedSetup"`
StartupError string `json:"startupError"`
StartupErrorTime time.Time `json:"startupErrorTime"`
}

type MnemonicRequest struct {
Expand Down
21 changes: 19 additions & 2 deletions frontend/src/screens/Start.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,27 @@ export default function Start() {
const [unlockPassword, setUnlockPassword] = React.useState("");
const [loading, setLoading] = React.useState(false);
const [buttonText, setButtonText] = React.useState("Login");
useInfo(true); // poll the info endpoint to auto-redirect when app is running

const { data: info } = useInfo(true); // poll the info endpoint to auto-redirect when app is running

const { toast } = useToast();

const startupError = info?.startupError;
const startupErrorTime = info?.startupErrorTime;

React.useEffect(() => {
if (startupError && startupErrorTime) {
toast({
title: "Failed to start",
description: startupError,
variant: "destructive",
});
setLoading(false);
setButtonText("Login");
setUnlockPassword("");
}
}, [startupError, toast, startupErrorTime]);

React.useEffect(() => {
if (!loading) {
return;
Expand All @@ -54,7 +71,7 @@ export default function Start() {
setButtonText("Login");
setUnlockPassword("");
return;
}, 180000); // wait for 3 minutes
}, 30000); // wait for 30 seconds

return () => {
clearInterval(intervalId);
Expand Down
25 changes: 24 additions & 1 deletion frontend/src/screens/setup/SetupFinish.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import { AuthTokenResponse, SetupNodeInfo } from "src/types";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request";

let lastStartupErrorTime: string;
export function SetupFinish() {
const navigate = useNavigate();
const { nodeInfo, unlockPassword } = useSetupStore();
const { toast } = useToast();
useInfo(true); // poll the info endpoint to auto-redirect when app is running
const { data: info } = useInfo(true); // poll the info endpoint to auto-redirect when app is running

const [loading, setLoading] = React.useState(false);
const [connectionError, setConnectionError] = React.useState(false);
Expand All @@ -32,6 +33,28 @@ export function SetupFinish() {
},
};

const startupError = info?.startupError;
const startupErrorTime = info?.startupErrorTime;

React.useEffect(() => {
// lastStartupErrorTime check is required because user may leave page and come back
// after re-configuring settings
if (
startupError &&
startupErrorTime &&
startupErrorTime !== lastStartupErrorTime
) {
lastStartupErrorTime = startupErrorTime;
toast({
title: "Failed to start",
description: startupError,
variant: "destructive",
});
setLoading(false);
setConnectionError(true);
}
}, [startupError, toast, startupErrorTime]);

useEffect(() => {
if (!loading) {
return;
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ export interface InfoResponse {
version: string;
unlocked: boolean;
enableAdvancedSetup: boolean;
startupError: string;
startupErrorTime: string;
}

export type Network = "bitcoin" | "testnet" | "signet";
Expand Down
13 changes: 1 addition & 12 deletions http/http_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,12 +223,6 @@ func (httpSvc *HttpService) startHandler(c echo.Context) error {
})
}

if !httpSvc.cfg.CheckUnlockPassword(startRequest.UnlockPassword) {
return c.JSON(http.StatusUnauthorized, ErrorResponse{
Message: "Invalid password",
})
}

token, err := httpSvc.createJWT(nil)

if err != nil {
Expand All @@ -237,12 +231,7 @@ func (httpSvc *HttpService) startHandler(c echo.Context) error {
})
}

go func() {
err := httpSvc.api.Start(&startRequest)
if err != nil {
logger.Logger.WithError(err).Error("Failed to start node")
}
}()
go httpSvc.api.Start(&startRequest)

return c.JSON(http.StatusOK, &authTokenResponse{
Token: token,
Expand Down
12 changes: 3 additions & 9 deletions wails/wails_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -641,15 +641,9 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
}).WithError(err).Error("Failed to decode request to wails router")
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
err = app.api.Start(startRequest)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
}).WithError(err).Error("Failed to setup node")
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}

go app.api.Start(startRequest)

return WailsRequestRouterResponse{Body: nil, Error: ""}
case "/api/setup":
setupRequest := &api.SetupRequest{}
Expand Down

0 comments on commit da27ed2

Please sign in to comment.