diff --git a/backend-mock/apps.js b/backend-mock/apps.js index 0ea3fc02..1b34cef8 100644 --- a/backend-mock/apps.js +++ b/backend-mock/apps.js @@ -1,6 +1,6 @@ const express = require("express"); const router = express.Router(); -const util = require("./util"); +const util = require("./sse/util"); router.post("/install/:id", (req, res) => { console.info("call to /api/v1/apps/install for app", req.params.id); diff --git a/backend-mock/auth.js b/backend-mock/auth.js index 13781d01..cdaa3606 100644 --- a/backend-mock/auth.js +++ b/backend-mock/auth.js @@ -4,7 +4,7 @@ require("dotenv").config(); const signToken = () => { console.info("call to signToken"); const token = jwt.sign( - { user_id: "admin", expires: Date.now() + 610_000 }, + { user_id: "admin", expires: Date.now() + 630_000 }, process.env.JWT_SECRET ); return token; diff --git a/backend-mock/index.js b/backend-mock/index.js index 1a6dc3fc..00057d73 100644 --- a/backend-mock/index.js +++ b/backend-mock/index.js @@ -1,12 +1,13 @@ const express = require("express"); const cors = require("cors"); -const btcInfo = require("./btc_info"); -const lnInfoLite = require("./ln_info_lite"); -const installedAppStatus = require("./installed_app_status"); -const systemInfo = require("./system_info"); -const hardwareInfo = require("./hardware_info"); -const walletBalance = require("./wallet_balance"); -const util = require("./util"); +const btcInfo = require("./sse/btc_info"); +const lnInfoLite = require("./sse/ln_info_lite"); +const installedAppStatus = require("./sse/installed_app_status"); +const systemInfo = require("./sse/system_info"); +const hardwareInfo = require("./sse/hardware_info"); +const walletBalance = require("./sse/wallet_balance"); +const systemStartupInfo = require("./sse/system_startup_info"); +const util = require("./sse/util"); const setup = require("./setup"); const system = require("./system"); const apps = require("./apps"); @@ -52,6 +53,7 @@ const eventsHandler = (request, response) => { response, }); + systemStartupInfo.systemStartupInfo(); systemInfo.systemInfo(); hardwareInfo.hardwareInfo(); btcInfo.btcInfo(); diff --git a/backend-mock/lightning.js b/backend-mock/lightning.js index b4d82254..99dc6542 100644 --- a/backend-mock/lightning.js +++ b/backend-mock/lightning.js @@ -1,6 +1,7 @@ const express = require("express"); const router = express.Router(); const transactions = require("./transactions"); +const util = require("./sse/util"); let WALLET_LOCKED = true; @@ -184,6 +185,14 @@ router.post("/unlock-wallet", (req, res) => { setTimeout(() => { if (req.body.password === "password") { WALLET_LOCKED = false; + setTimeout(() => { + util.sendSSE("system_startup_info", { + bitcoin: "done", + bitcoin_msg: "", + lightning: "done", + lightning_msg: "", + }); + }, 3000); return res.status(200).send(true); } return res.status(401).send(); diff --git a/backend-mock/btc_info.js b/backend-mock/sse/btc_info.js similarity index 100% rename from backend-mock/btc_info.js rename to backend-mock/sse/btc_info.js diff --git a/backend-mock/hardware_info.js b/backend-mock/sse/hardware_info.js similarity index 100% rename from backend-mock/hardware_info.js rename to backend-mock/sse/hardware_info.js diff --git a/backend-mock/installed_app_status.js b/backend-mock/sse/installed_app_status.js similarity index 100% rename from backend-mock/installed_app_status.js rename to backend-mock/sse/installed_app_status.js diff --git a/backend-mock/ln_info_lite.js b/backend-mock/sse/ln_info_lite.js similarity index 100% rename from backend-mock/ln_info_lite.js rename to backend-mock/sse/ln_info_lite.js diff --git a/backend-mock/system_info.js b/backend-mock/sse/system_info.js similarity index 100% rename from backend-mock/system_info.js rename to backend-mock/sse/system_info.js diff --git a/backend-mock/sse/system_startup_info.js b/backend-mock/sse/system_startup_info.js new file mode 100644 index 00000000..d3098ddb --- /dev/null +++ b/backend-mock/sse/system_startup_info.js @@ -0,0 +1,14 @@ +const util = require("./util"); + +const systemStartupInfo = () => { + console.info("sending system_startup_info"); + + util.sendSSE("system_startup_info", { + bitcoin: "done", + bitcoin_msg: "", + lightning: "locked", + lightning_msg: "Wallet locked, unlock it to enable full RPC access", + }); +}; + +module.exports = { systemStartupInfo }; diff --git a/backend-mock/util.js b/backend-mock/sse/util.js similarity index 100% rename from backend-mock/util.js rename to backend-mock/sse/util.js diff --git a/backend-mock/wallet_balance.js b/backend-mock/sse/wallet_balance.js similarity index 100% rename from backend-mock/wallet_balance.js rename to backend-mock/sse/wallet_balance.js diff --git a/src/components/Shared/UnlockModal/UnlockModal.tsx b/src/components/Shared/UnlockModal/UnlockModal.tsx index 27295df7..8ebe2f09 100644 --- a/src/components/Shared/UnlockModal/UnlockModal.tsx +++ b/src/components/Shared/UnlockModal/UnlockModal.tsx @@ -3,6 +3,7 @@ import { createPortal } from "react-dom"; import type { SubmitHandler } from "react-hook-form"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { ReactComponent as LockOpen } from "../../../assets/lock-open.svg"; import ModalDialog, { disableScroll, } from "../../../container/ModalDialog/ModalDialog"; @@ -11,7 +12,6 @@ import { instance } from "../../../util/interceptor"; import { MODAL_ROOT } from "../../../util/util"; import ButtonWithSpinner from "../ButtonWithSpinner/ButtonWithSpinner"; import InputField from "../InputField/InputField"; -import { ReactComponent as LockOpen } from "../../../assets/lock-open.svg"; interface IFormInputs { passwordInput: string; diff --git a/src/hooks/use-sse.tsx b/src/hooks/use-sse.tsx index 0fe72d25..6ec8692b 100644 --- a/src/hooks/use-sse.tsx +++ b/src/hooks/use-sse.tsx @@ -8,6 +8,7 @@ import { HardwareInfo } from "../models/hardware-info"; import { InstallAppData } from "../models/install-app"; import { LnInfoLite } from "../models/ln-info-lite"; import { SystemInfo } from "../models/system-info"; +import { SystemStartupInfo } from "../models/system-startup-info"; import { WalletBalance } from "../models/wallet-balance"; import { SSEContext, SSE_URL } from "../store/sse-context"; import { availableApps } from "../util/availableApps"; @@ -177,6 +178,17 @@ function useSSE() { }); }; + const setSystemStartupInfo = (event: MessageEvent) => { + sseCtx.setSystemStartupInfo((prev: SystemStartupInfo | null) => { + const message = JSON.parse(event.data); + + return { + ...prev, + ...message, + }; + }); + }; + if (evtSource) { evtSource.addEventListener("system_info", setSystemInfo); evtSource.addEventListener("btc_info", setBtcInfo); @@ -187,6 +199,7 @@ function useSSE() { evtSource.addEventListener("apps", setApps); evtSource.addEventListener("install", setInstall); evtSource.addEventListener("hardware_info", setHardwareInfo); + evtSource.addEventListener("system_startup_info", setSystemStartupInfo); } return () => { @@ -201,6 +214,10 @@ function useSSE() { evtSource.removeEventListener("apps", setApps); evtSource.removeEventListener("install", setInstall); evtSource.removeEventListener("hardware_info", setHardwareInfo); + evtSource.removeEventListener( + "system_startup_info", + setSystemStartupInfo + ); } }; }, [ @@ -213,6 +230,7 @@ function useSSE() { ]); return { + evtSource: sseCtx.evtSource, systemInfo: sseCtx.systemInfo, btcInfo: sseCtx.btcInfo, lnInfoLite: sseCtx.lnInfoLite, @@ -222,6 +240,7 @@ function useSSE() { availableApps: sseCtx.availableApps, installingApp: sseCtx.installingApp, hardwareInfo: sseCtx.hardwareInfo, + systemStartupInfo: sseCtx.systemStartupInfo, }; } diff --git a/src/models/system-startup-info.ts b/src/models/system-startup-info.ts new file mode 100644 index 00000000..82495359 --- /dev/null +++ b/src/models/system-startup-info.ts @@ -0,0 +1,6 @@ +export interface SystemStartupInfo { + bitcoin: "offline" | "done"; + bitcoin_msg: string; + lightning: "offline" | "bootstrapping" | "locked" | "done"; + lightning_msg: string; +} diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index f8cacde6..687ea81e 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -24,8 +24,15 @@ import { enableGutter } from "../util/util"; const Home: FC = () => { const { t } = useTranslation(); const { darkMode, walletLocked, setWalletLocked } = useContext(AppContext); - const { systemInfo, balance, btcInfo, lnInfoLite, appStatus, hardwareInfo } = - useSSE(); + const { + systemInfo, + balance, + btcInfo, + lnInfoLite, + appStatus, + hardwareInfo, + systemStartupInfo, + } = useSSE(); const [showSendModal, setShowSendModal] = useState(false); const [showReceiveModal, setShowReceiveModal] = useState(false); @@ -38,13 +45,15 @@ const Home: FC = () => { const theme = darkMode ? "dark" : "light"; const { implementation } = lnInfoLite; + const { lightning: lightningState } = systemStartupInfo || {}; + const isLnImplSelected = implementation !== null && implementation !== "" && implementation !== "NONE"; const getTransactions = useCallback(async () => { - if (!isLnImplSelected) { + if (!isLnImplSelected || (lightningState && lightningState !== "done")) { return; } try { @@ -54,6 +63,9 @@ const Home: FC = () => { }, }); setTransactions(tx.data); + if (tx.status === 200 && walletLocked) { + setWalletLocked(false); + } } catch (err: any) { if (err.response.status === 423) { setWalletLocked(true); @@ -67,15 +79,7 @@ const Home: FC = () => { ); } } - }, [isLnImplSelected, setWalletLocked, t]); - - useEffect(() => { - enableGutter(); - if (!walletLocked) { - setIsLoadingTransactions(true); - setTxError(""); - } - }, [t, walletLocked, setWalletLocked, setIsLoadingTransactions]); + }, [lightningState, isLnImplSelected, walletLocked, setWalletLocked, t]); useEffect(() => { if (isLnImplSelected && !walletLocked && isLoadingTransactions) { @@ -91,6 +95,25 @@ const Home: FC = () => { getTransactions, ]); + useEffect(() => { + enableGutter(); + + if (lightningState === "locked") { + setWalletLocked(true); + } + + if (!walletLocked) { + setIsLoadingTransactions(true); + setTxError(""); + } + }, [ + t, + lightningState, + walletLocked, + setWalletLocked, + setIsLoadingTransactions, + ]); + useInterval(getTransactions, 5000); const showSendModalHandler = useCallback(() => { @@ -152,10 +175,13 @@ const Home: FC = () => { const closeUnlockModal = useCallback( (unlocked: boolean) => { if (unlocked) { + if (systemStartupInfo) { + systemStartupInfo.lightning = "done"; + } toast.success(t("wallet.unlock_success"), { theme }); } }, - [t, theme] + [t, theme, systemStartupInfo] ); const unlockModal = walletLocked && ( @@ -167,11 +193,14 @@ const Home: FC = () => { if (implementation === null) { return ( -
- -
+ <> + {unlockModal} +
+ +
+ ); } diff --git a/src/store/sse-context.tsx b/src/store/sse-context.tsx index cf4f033f..6d104e33 100644 --- a/src/store/sse-context.tsx +++ b/src/store/sse-context.tsx @@ -6,6 +6,7 @@ import { BtcInfo } from "../models/btc-info"; import { HardwareInfo } from "../models/hardware-info"; import { LnInfoLite } from "../models/ln-info-lite"; import { SystemInfo } from "../models/system-info"; +import { SystemStartupInfo } from "../models/system-startup-info"; import { Transaction } from "../models/transaction.model"; import { WalletBalance } from "../models/wallet-balance"; @@ -31,6 +32,8 @@ interface SSEContextType { setInstallingApp: Dispatch>; hardwareInfo: HardwareInfo | null; setHardwareInfo: Dispatch>; + systemStartupInfo: SystemStartupInfo | null; + setSystemStartupInfo: Dispatch>; } export const SSEContext = createContext({ @@ -54,6 +57,8 @@ export const SSEContext = createContext({ setInstallingApp: () => {}, hardwareInfo: {} as HardwareInfo, setHardwareInfo: () => {}, + systemStartupInfo: {} as SystemStartupInfo, + setSystemStartupInfo: () => {}, }); // for personal development - change backend with .env file @@ -121,6 +126,8 @@ const SSEContextProvider: FC = (props) => { const [transactions, setTransactions] = useState([]); const [installingApp, setInstallingApp] = useState(null); const [hardwareInfo, setHardwareInfo] = useState(null); + const [systemStartupInfo, setSystemStartupInfo] = + useState(null); const contextValue: SSEContextType = { evtSource, @@ -143,6 +150,8 @@ const SSEContextProvider: FC = (props) => { setInstallingApp, hardwareInfo, setHardwareInfo, + systemStartupInfo, + setSystemStartupInfo, }; return (