Bladeren bron

refactor(log): use swr subscription (#1220)

Sukka 11 maanden geleden
bovenliggende
commit
1988aeb945

+ 5 - 3
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<TrafficRef>(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,

+ 0 - 38
src/components/layout/use-log-setup.ts

@@ -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]);
-};

+ 57 - 0
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<ILogItem[], any, "getClashLog" | null>(
+    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,
+    }
+  );
+};

+ 0 - 2
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 = [
   <ThemeModeProvider />,
-  <LogDataProvider />,
   <LoadingCacheProvider />,
   <UpdateStateProvider />,
 ];

+ 7 - 4
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 = () => {
           <Button
             size="small"
             variant="contained"
-            onClick={() => setLogData([])}
+            // useSWRSubscription adds a prefix "$sub$" to the cache key
+            // https://github.com/vercel/swr/blob/1585a3e37d90ad0df8097b099db38f1afb43c95d/src/subscription/index.ts#L37
+            onClick={() => mutate("$sub$getClashLog", [])}
           >
             {t("Clear")}
           </Button>

+ 0 - 7
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,