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 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
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
56 changes: 48 additions & 8 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 @@ -11,18 +10,61 @@ import { useInfo } from "src/hooks/useInfo";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request";

const messages: string[] = [
"Unlocking",
"Starting the wallet",
"Connecting to the network",
"Syncing",
"Still syncing, please wait...",
];

export default function Start() {
const [unlockPassword, setUnlockPassword] = React.useState("");
const [loading, setLoading] = React.useState(false);
const navigate = useNavigate();
const [buttonText, setButtonText] = React.useState("Login");
useInfo(true); // poll the info endpoint to auto-redirect when app is running
const { data: csrf } = useCSRF();
const { mutate: refetchInfo } = useInfo();
const { toast } = useToast();

React.useEffect(() => {
if (!loading) {
return;
}
let messageIndex = 1;
const intervalId = setInterval(() => {
if (messageIndex < messages.length) {
setButtonText(messages[messageIndex]);
messageIndex++;
} else {
clearInterval(intervalId);
}
}, 5000);

const timeoutId = setTimeout(() => {
// if redirection didn't happen in 3 minutes info.running is false
toast({
title: "Failed to start",
description: "Please try starting the node again.",
variant: "destructive",
});

setLoading(false);
setButtonText("Login");
setUnlockPassword("");
return;
}, 180000); // wait for 3 minutes

return () => {
clearInterval(intervalId);
clearTimeout(timeoutId);
};
}, [loading, toast]);

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 +78,11 @@ export default function Start() {
unlockPassword,
}),
});
await refetchInfo();

navigate("/");
} catch (error) {
handleRequestError(toast, "Failed to connect", error);
} finally {
setLoading(false);
setButtonText("Login");
setUnlockPassword("");
}
}

Expand All @@ -68,7 +108,7 @@ export default function Start() {
/>
</div>
<LoadingButton type="submit" loading={loading}>
Login
{buttonText}
</LoadingButton>
</div>
</form>
Expand Down
35 changes: 24 additions & 11 deletions frontend/src/screens/setup/SetupFinish.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import { request } from "src/utils/request";
export function SetupFinish() {
const navigate = useNavigate();
const { nodeInfo, unlockPassword } = useSetupStore();

const { mutate: refetchInfo } = useInfo();
useInfo(true); // poll the info endpoint to auto-redirect when app is running
const { data: csrf } = useCSRF();
const [loading, setLoading] = React.useState(false);
const [connectionError, setConnectionError] = React.useState(false);
const hasFetchedRef = React.useRef(false);

Expand All @@ -30,6 +30,22 @@ export function SetupFinish() {
},
};

useEffect(() => {
if (!loading) {
return;
}
const timer = setTimeout(() => {
// SetupRedirect takes care of redirection once info.running is true
// if it still didn't redirect after 3 minutes, we show an error
setLoading(false);
setConnectionError(true);
}, 180000);

return () => {
clearTimeout(timer);
};
}, [loading]);

useEffect(() => {
// ensure setup call is only called once
if (!csrf || hasFetchedRef.current) {
Expand All @@ -38,26 +54,23 @@ export function SetupFinish() {
hasFetchedRef.current = true;

(async () => {
setLoading(true);
const succeeded = await finishSetup(csrf, nodeInfo, unlockPassword);
if (succeeded) {
const info = await refetchInfo();
if (!info) {
throw new Error("Failed to re-fetch info");
}
navigate("/");
} else {
// only setup call is successful as start is async
if (!succeeded) {
setLoading(false);
setConnectionError(true);
}
})();
}, [csrf, nodeInfo, refetchInfo, navigate, unlockPassword]);
}, [csrf, nodeInfo, navigate, unlockPassword]);

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
16 changes: 11 additions & 5 deletions http/http_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,21 +180,27 @@ 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)
err := httpSvc.saveSessionCookie(c)

if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to save session: %s", err.Error()),
})
}

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

return c.NoContent(http.StatusNoContent)
}

Expand Down
Loading