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

Commit

Permalink
feat: update start api to be async
Browse files Browse the repository at this point in the history
  • Loading branch information
im-adithya committed Jul 2, 2024
1 parent 202daef commit 2a4e761
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 29 deletions.
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) {
// 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
33 changes: 24 additions & 9 deletions frontend/src/screens/setup/SetupFinish.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@ import { toast } from "src/components/ui/use-toast";
import { useCSRF } from "src/hooks/useCSRF";
import { useInfo } from "src/hooks/useInfo";
import useSetupStore from "src/state/SetupStore";
import { SetupNodeInfo } from "src/types";
import { SetupNodeInfo, 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 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 [loadingMessage, setLoadingMessage] = React.useState(
"Setting up your Hub..."
);
const hasFetchedRef = React.useRef(false);

const defaultOptions = {
Expand All @@ -40,24 +44,35 @@ 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
let messageIndex = 1;
const intervalId = setInterval(() => {
// we don't check for info.running as HomeRedirect takes care of it
if (messageIndex < messages.length) {
setLoadingMessage(messages[messageIndex]);
messageIndex++;
} else {
clearInterval(intervalId);
}
}, 5000);

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, loadingMessage]);

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 All @@ -76,7 +91,7 @@ export function SetupFinish() {
<div className="flex flex-col gap-5 justify-center text-center">
<Lottie options={defaultOptions} height={400} width={400} />
<h1 className="font-semibold text-lg font-headline">
Setting up your Hub...
{loadingMessage}
</h1>
</div>
</Container>
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[] = [
"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() {
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

0 comments on commit 2a4e761

Please sign in to comment.