From 18f0177fce748b577e0072ba4746568b07f0b167 Mon Sep 17 00:00:00 2001 From: Sukka Date: Mon, 17 Jun 2024 10:48:37 +0800 Subject: [PATCH] refactor(log): use swr subscription (#1220) --- src/components/layout/layout-traffic.tsx | 8 ++-- src/components/layout/use-log-setup.ts | 38 ---------------- src/hooks/use-log-data.ts | 57 ++++++++++++++++++++++++ src/main.tsx | 2 - src/pages/logs.tsx | 11 +++-- src/services/states.ts | 7 --- 6 files changed, 69 insertions(+), 54 deletions(-) delete mode 100644 src/components/layout/use-log-setup.ts create mode 100644 src/hooks/use-log-data.ts diff --git a/src/components/layout/layout-traffic.tsx b/src/components/layout/layout-traffic.tsx index c6a25c55..336c10e7 100644 --- a/src/components/layout/layout-traffic.tsx +++ b/src/components/layout/layout-traffic.tsx @@ -8,7 +8,7 @@ import { import { useClashInfo } from "@/hooks/use-clash"; import { useVerge } from "@/hooks/use-verge"; import { TrafficGraph, type TrafficRef } from "./traffic-graph"; -import { useLogSetup } from "./use-log-setup"; +import { useLogData } from "@/hooks/use-log-data"; import { useVisibility } from "@/hooks/use-visibility"; import parseTraffic from "@/utils/parse-traffic"; import useSWRSubscription from "swr/subscription"; @@ -30,8 +30,10 @@ export const LayoutTraffic = () => { const trafficRef = useRef(null); const pageVisible = useVisibility(); - // setup log ws during layout - useLogSetup(); + // https://swr.vercel.app/docs/subscription#deduplication + // useSWRSubscription auto deduplicates to one subscription per key per entire app + // So we can simply invoke it here acting as preconnect + useLogData(); const { data: traffic = { up: 0, down: 0 } } = useSWRSubscription< ITrafficItem, diff --git a/src/components/layout/use-log-setup.ts b/src/components/layout/use-log-setup.ts deleted file mode 100644 index 4a1e5679..00000000 --- a/src/components/layout/use-log-setup.ts +++ /dev/null @@ -1,38 +0,0 @@ -import dayjs from "dayjs"; -import { useEffect } from "react"; -import { getClashLogs } from "@/services/cmds"; -import { useClashInfo } from "@/hooks/use-clash"; -import { useEnableLog, useSetLogData } from "@/services/states"; -import { useWebsocket } from "@/hooks/use-websocket"; - -const MAX_LOG_NUM = 1000; - -// setup the log websocket -export const useLogSetup = () => { - const { clashInfo } = useClashInfo(); - - const [enableLog] = useEnableLog(); - const setLogData = useSetLogData(); - - const { connect, disconnect } = useWebsocket((event) => { - const data = JSON.parse(event.data) as ILogItem; - const time = dayjs().format("MM-DD HH:mm:ss"); - setLogData((l) => { - if (l.length >= MAX_LOG_NUM) l.shift(); - return [...l, { ...data, time }]; - }); - }); - - useEffect(() => { - if (!enableLog || !clashInfo) return; - - getClashLogs().then(setLogData); - - const { server = "", secret = "" } = clashInfo; - connect(`ws://${server}/logs?token=${encodeURIComponent(secret)}`); - - return () => { - disconnect(); - }; - }, [clashInfo, enableLog]); -}; diff --git a/src/hooks/use-log-data.ts b/src/hooks/use-log-data.ts new file mode 100644 index 00000000..cc3c50a5 --- /dev/null +++ b/src/hooks/use-log-data.ts @@ -0,0 +1,57 @@ +import useSWRSubscription from "swr/subscription"; +import { useEnableLog } from "../services/states"; +import { createSockette } from "../utils/websocket"; +import { useClashInfo } from "./use-clash"; +import dayjs from "dayjs"; +import { getClashLogs } from "../services/cmds"; + +const MAX_LOG_NUM = 1000; + +export const useLogData = () => { + const { clashInfo } = useClashInfo(); + + const [enableLog] = useEnableLog(); + !enableLog || !clashInfo; + + return useSWRSubscription( + enableLog && clashInfo ? "getClashLog" : null, + (_key, { next }) => { + const { server = "", secret = "" } = clashInfo!; + + // populate the initial logs + getClashLogs().then( + (logs) => next(null, logs), + (err) => next(err) + ); + + const s = createSockette( + `ws://${server}/logs?token=${encodeURIComponent(secret)}`, + { + onmessage(event) { + const data = JSON.parse(event.data) as ILogItem; + + // append new log item on socket message + next(null, (l = []) => { + const time = dayjs().format("MM-DD HH:mm:ss"); + + if (l.length >= MAX_LOG_NUM) l.shift(); + return [...l, { ...data, time }]; + }); + }, + onerror(event) { + this.close(); + next(event); + }, + } + ); + + return () => { + s.close(); + }; + }, + { + fallbackData: { up: 0, down: 0 }, + keepPreviousData: true, + } + ); +}; diff --git a/src/main.tsx b/src/main.tsx index efb3bc59..2b7b9467 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -16,7 +16,6 @@ import Layout from "./pages/_layout"; import "./services/i18n"; import { LoadingCacheProvider, - LogDataProvider, ThemeModeProvider, UpdateStateProvider, } from "./services/states"; @@ -45,7 +44,6 @@ document.addEventListener("keydown", (event) => { const contexts = [ , - , , , ]; diff --git a/src/pages/logs.tsx b/src/pages/logs.tsx index b9df1aba..c3bce9d4 100644 --- a/src/pages/logs.tsx +++ b/src/pages/logs.tsx @@ -6,17 +6,18 @@ import { PlayCircleOutlineRounded, PauseCircleOutlineRounded, } from "@mui/icons-material"; -import { useEnableLog, useLogData, useSetLogData } from "@/services/states"; +import { useLogData } from "@/hooks/use-log-data"; +import { useEnableLog } from "@/services/states"; import { BaseEmpty, BasePage } from "@/components/base"; import LogItem from "@/components/log/log-item"; import { useCustomTheme } from "@/components/layout/use-custom-theme"; import { BaseSearchBox } from "@/components/base/base-search-box"; import { BaseStyledSelect } from "@/components/base/base-styled-select"; +import { mutate } from "swr"; const LogPage = () => { const { t } = useTranslation(); - const logData = useLogData(); - const setLogData = useSetLogData(); + const { data: logData = [] } = useLogData(); const [enableLog, setEnableLog] = useEnableLog(); const { theme } = useCustomTheme(); const isDark = theme.palette.mode === "dark"; @@ -54,7 +55,9 @@ const LogPage = () => { diff --git a/src/services/states.ts b/src/services/states.ts index 3beddd00..4ea46298 100644 --- a/src/services/states.ts +++ b/src/services/states.ts @@ -5,10 +5,6 @@ const [ThemeModeProvider, useThemeMode, useSetThemeMode] = createContextState< "light" | "dark" >("light"); -const [LogDataProvider, useLogData, useSetLogData] = createContextState< - ILogItem[] ->([]); - export const useEnableLog = () => useLocalStorage("enable-log", true); interface IConnectionSetting { @@ -39,9 +35,6 @@ export { ThemeModeProvider, useThemeMode, useSetThemeMode, - LogDataProvider, - useLogData, - useSetLogData, LoadingCacheProvider, useLoadingCache, useSetLoadingCache,