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

feat: update start api to be async #558

Merged
merged 7 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
10 changes: 1 addition & 9 deletions frontend/src/components/redirects/SetupRedirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Outlet, useLocation, useNavigate } from "react-router-dom";
import Loading from "src/components/Loading";
import { useInfo } from "src/hooks/useInfo";

let didSetupThisSession = false;
export function SetupRedirect() {
const { data: info } = useInfo();
const location = useLocation();
Expand All @@ -13,17 +12,10 @@ export function SetupRedirect() {
if (!info) {
return;
}
if (didSetupThisSession) {
im-adithya marked this conversation as resolved.
Show resolved Hide resolved
// ensure redirect does not happen as node may still be starting
// which would then incorrectly redirect to the login page
console.info("Skipping setup redirect on initial setup");
return;
}
if (info.setupCompleted) {
if (info.setupCompleted && info.running) {
navigate("/");
return;
}
didSetupThisSession = true;
}, [info, location, navigate]);

if (!info) {
Expand Down
33 changes: 27 additions & 6 deletions frontend/src/screens/Start.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import Container from "src/components/Container";
import TwoColumnLayoutHeader from "src/components/TwoColumnLayoutHeader";
import { Input } from "src/components/ui/input";
Expand All @@ -8,21 +7,24 @@ import { LoadingButton } from "src/components/ui/loading-button";
import { useToast } from "src/components/ui/use-toast";
import { useCSRF } from "src/hooks/useCSRF";
import { useInfo } from "src/hooks/useInfo";
import { startupMessages as messages } from "src/types";
import { asyncTimeout } from "src/utils/asyncTimeout";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request";

export default function Start() {
const [unlockPassword, setUnlockPassword] = React.useState("");
const [loading, setLoading] = React.useState(false);
const navigate = useNavigate();
const [buttonText, setButtonText] = React.useState("Login");
const { data: csrf } = useCSRF();
const { mutate: refetchInfo } = useInfo();
const { data: info } = useInfo(true);
const { toast } = useToast();

async function onSubmit(e: React.FormEvent) {
e.preventDefault();
try {
setLoading(true);
setButtonText(messages[0]);
if (!csrf) {
throw new Error("csrf not loaded");
}
Expand All @@ -36,13 +38,32 @@ export default function Start() {
unlockPassword,
}),
});
await refetchInfo();

navigate("/");
let messageIndex = 1;
const intervalId = setInterval(() => {
// we don't check for info.running as HomeRedirect takes care of it
if (messageIndex < messages.length) {
setButtonText(messages[messageIndex]);
messageIndex++;
} else {
clearInterval(intervalId);
}
}, 5000);

await asyncTimeout(180000); // wait for 3 minutes
if (!info?.running) {
toast({
title: "Failed to start",
description: "Please try starting the node again.",
variant: "destructive",
});
}
} catch (error) {
handleRequestError(toast, "Failed to connect", error);
} finally {
setLoading(false);
setButtonText("Login");
setUnlockPassword("");
}
}

Expand All @@ -68,7 +89,7 @@ export default function Start() {
/>
</div>
<LoadingButton type="submit" loading={loading}>
Login
{buttonText}
</LoadingButton>
</div>
</form>
Expand Down
15 changes: 8 additions & 7 deletions frontend/src/screens/setup/SetupFinish.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import { useCSRF } from "src/hooks/useCSRF";
import { useInfo } from "src/hooks/useInfo";
import useSetupStore from "src/state/SetupStore";
import { SetupNodeInfo } from "src/types";
import { asyncTimeout } from "src/utils/asyncTimeout";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request";

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

const { mutate: refetchInfo } = useInfo();
const { data: info } = useInfo(true);
const { data: csrf } = useCSRF();
const [connectionError, setConnectionError] = React.useState(false);
const hasFetchedRef = React.useRef(false);
Expand All @@ -40,24 +41,24 @@ export function SetupFinish() {
(async () => {
const succeeded = await finishSetup(csrf, nodeInfo, unlockPassword);
if (succeeded) {
const info = await refetchInfo();
if (!info) {
throw new Error("Failed to re-fetch info");
// only setup call is successful as start is async
await asyncTimeout(180000); // wait for 3 minutes
if (!info?.running) {
setConnectionError(true);
}
navigate("/");
} else {
setConnectionError(true);
}
})();
}, [csrf, nodeInfo, refetchInfo, navigate, unlockPassword]);
}, [csrf, nodeInfo, info, navigate, unlockPassword]);
im-adithya marked this conversation as resolved.
Show resolved Hide resolved

if (connectionError) {
return (
<Container>
<div className="flex flex-col gap-5 text-center items-center">
<div className="grid gap-2">
<h1 className="font-semibold text-lg">Connection Failed</h1>
<p>Please check your node configuration.</p>
<p>Please check your node configuration and try again.</p>
</div>
<Button
onClick={() => {
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ export const budgetOptions: Record<string, number> = {
Unlimited: 0,
};

export const startupMessages: string[] = [
im-adithya marked this conversation as resolved.
Show resolved Hide resolved
"Unlocking",
"Starting the wallet",
"Connecting to the network",
"Syncing",
"Still syncing, please wait...",
];

export interface ErrorResponse {
message: string;
}
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/utils/asyncTimeout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const asyncTimeout = (ms: number) => {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};
16 changes: 11 additions & 5 deletions http/http_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,20 @@ func (httpSvc *HttpService) startHandler(c echo.Context) error {
})
}

err := httpSvc.api.Start(&startRequest)
if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to start node: %s", err.Error()),
if !httpSvc.cfg.CheckUnlockPassword(startRequest.UnlockPassword) {
return c.JSON(http.StatusUnauthorized, ErrorResponse{
Message: "Invalid password",
})
}

err = httpSvc.saveSessionCookie(c)
go func() {
im-adithya marked this conversation as resolved.
Show resolved Hide resolved
err := httpSvc.api.Start(&startRequest)
if err != nil {
logger.Logger.WithError(err).Error("Failed to start node")
}
}()

err := httpSvc.saveSessionCookie(c)

if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Expand Down
Loading