Bladeren bron

chore: use swr subscription for layout traffic / memory (#1202)

* chore: update swr to 2

* refactor: use swr subscription for memory & traffic

* refactor: introduce `sockette`
Sukka 11 maanden geleden
bovenliggende
commit
1667856894
3 gewijzigde bestanden met toevoegingen van 112 en 39 verwijderingen
  1. 2 1
      package.json
  2. 30 5
      pnpm-lock.yaml
  3. 80 33
      src/components/layout/layout-traffic.tsx

+ 2 - 1
package.json

@@ -49,7 +49,8 @@
     "react-router-dom": "^6.23.1",
     "react-transition-group": "^4.4.5",
     "react-virtuoso": "^4.7.11",
-    "swr": "^1.3.0",
+    "sockette": "^2.0.6",
+    "swr": "^2.2.5",
     "tar": "^6.2.1",
     "types-pac": "^1.0.2"
   },

+ 30 - 5
pnpm-lock.yaml

@@ -100,9 +100,12 @@ importers:
       react-virtuoso:
         specifier: ^4.7.11
         version: 4.7.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      sockette:
+        specifier: ^2.0.6
+        version: 2.0.6
       swr:
-        specifier: ^1.3.0
-        version: 1.3.0(react@18.3.1)
+        specifier: ^2.2.5
+        version: 2.2.5(react@18.3.1)
       tar:
         specifier: ^6.2.1
         version: 6.2.1
@@ -4032,6 +4035,12 @@ packages:
         integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==,
       }
 
+  sockette@2.0.6:
+    resolution:
+      {
+        integrity: sha512-W6iG8RGV6Zife3Cj+FhuyHV447E6fqFM2hKmnaQrTvg3OydINV3Msj3WPFbX76blUlUxvQSMMMdrJxce8NqI5Q==,
+      }
+
   source-map-js@1.2.0:
     resolution:
       {
@@ -4110,10 +4119,10 @@ packages:
         integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==,
       }
 
-  swr@1.3.0:
+  swr@2.2.5:
     resolution:
       {
-        integrity: sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==,
+        integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==,
       }
     peerDependencies:
       react: ^16.11.0 || ^17.0.0 || ^18.0.0
@@ -4303,6 +4312,14 @@ packages:
     peerDependencies:
       browserslist: ">= 4.21.0"
 
+  use-sync-external-store@1.2.2:
+    resolution:
+      {
+        integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==,
+      }
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
   vfile-message@4.0.2:
     resolution:
       {
@@ -7096,6 +7113,8 @@ snapshots:
       dot-case: 3.0.4
       tslib: 2.6.3
 
+  sockette@2.0.6: {}
+
   source-map-js@1.2.0: {}
 
   source-map-support@0.5.21:
@@ -7130,9 +7149,11 @@ snapshots:
 
   svg-parser@2.0.4: {}
 
-  swr@1.3.0(react@18.3.1):
+  swr@2.2.5(react@18.3.1):
     dependencies:
+      client-only: 0.0.1
       react: 18.3.1
+      use-sync-external-store: 1.2.2(react@18.3.1)
 
   systemjs@6.15.1: {}
 
@@ -7237,6 +7258,10 @@ snapshots:
       escalade: 3.1.2
       picocolors: 1.0.1
 
+  use-sync-external-store@1.2.2(react@18.3.1):
+    dependencies:
+      react: 18.3.1
+
   vfile-message@4.0.2:
     dependencies:
       "@types/unist": 3.0.2

+ 80 - 33
src/components/layout/layout-traffic.tsx

@@ -1,4 +1,4 @@
-import { useEffect, useRef, useState } from "react";
+import { useRef } from "react";
 import { Box, Typography } from "@mui/material";
 import {
   ArrowDownward,
@@ -10,8 +10,14 @@ import { useVerge } from "@/hooks/use-verge";
 import { TrafficGraph, type TrafficRef } from "./traffic-graph";
 import { useLogSetup } from "./use-log-setup";
 import { useVisibility } from "@/hooks/use-visibility";
-import { useWebsocket } from "@/hooks/use-websocket";
 import parseTraffic from "@/utils/parse-traffic";
+import useSWRSubscription from "swr/subscription";
+import Sockette from "sockette";
+
+interface MemoryUsage {
+  inuse: number;
+  oslimit?: number;
+}
 
 // setup the traffic
 export const LayoutTraffic = () => {
@@ -22,52 +28,93 @@ export const LayoutTraffic = () => {
   const trafficGraph = verge?.traffic_graph ?? true;
 
   const trafficRef = useRef<TrafficRef>(null);
-  const [traffic, setTraffic] = useState({ up: 0, down: 0 });
-  const [memory, setMemory] = useState({ inuse: 0 });
   const pageVisible = useVisibility();
 
   // setup log ws during layout
   useLogSetup();
 
-  const trafficWs = useWebsocket(
-    (event) => {
-      const data = JSON.parse(event.data) as ITrafficItem;
-      trafficRef.current?.appendData(data);
-      setTraffic(data);
+  const { data: traffic = { up: 0, down: 0 } } = useSWRSubscription<
+    ITrafficItem,
+    any,
+    "getRealtimeTraffic" | null
+  >(
+    clashInfo && pageVisible ? "getRealtimeTraffic" : null,
+    (_key, { next }) => {
+      const { server = "", secret = "" } = clashInfo!;
+
+      let errorCount = 10;
+
+      const s = new Sockette(
+        `ws://${server}/traffic?token=${encodeURIComponent(secret)}`,
+        {
+          onmessage(event) {
+            errorCount = 0; // reset counter
+            const data = JSON.parse(event.data) as ITrafficItem;
+            trafficRef.current?.appendData(data);
+            next(null, data);
+          },
+          onerror(event) {
+            errorCount -= 1;
+
+            if (errorCount <= 0) {
+              this.close();
+              next(event, { up: 0, down: 0 });
+            }
+          },
+        }
+      );
+
+      return () => {
+        s.close();
+      };
     },
-    { onError: () => setTraffic({ up: 0, down: 0 }), errorCount: 10 }
+    {
+      fallbackData: { up: 0, down: 0 },
+      keepPreviousData: true,
+    }
   );
 
-  useEffect(() => {
-    if (!clashInfo || !pageVisible) return;
-
-    const { server = "", secret = "" } = clashInfo;
-    trafficWs.connect(
-      `ws://${server}/traffic?token=${encodeURIComponent(secret)}`
-    );
-    return () => trafficWs.disconnect();
-  }, [clashInfo, pageVisible]);
-
   /* --------- meta memory information --------- */
   const isMetaCore = verge?.clash_core?.includes("clash-meta");
   const displayMemory = isMetaCore && (verge?.enable_memory_usage ?? true);
 
-  const memoryWs = useWebsocket(
-    (event) => {
-      setMemory(JSON.parse(event.data));
+  const { data: memory = { inuse: 0 } } = useSWRSubscription<
+    MemoryUsage,
+    any,
+    "getRealtimeMemory" | null
+  >(
+    clashInfo && pageVisible && displayMemory ? "getRealtimeMemory" : null,
+    (_key, { next }) => {
+      const { server = "", secret = "" } = clashInfo!;
+      const ws = new WebSocket(
+        `ws://${server}/memory?token=${encodeURIComponent(secret)}`
+      );
+
+      let errorCount = 10;
+
+      ws.addEventListener("message", (event) => {
+        errorCount = 0; // reset counter
+        next(null, JSON.parse(event.data));
+      });
+      ws.addEventListener("error", (event) => {
+        errorCount -= 1;
+
+        if (errorCount <= 0) {
+          ws.close();
+          next(event, { inuse: 0 });
+        }
+      });
+
+      return () => {
+        ws.close();
+      };
     },
-    { onError: () => setMemory({ inuse: 0 }), errorCount: 10 }
+    {
+      fallbackData: { inuse: 0 },
+      keepPreviousData: true,
+    }
   );
 
-  useEffect(() => {
-    if (!clashInfo || !pageVisible || !displayMemory) return;
-    const { server = "", secret = "" } = clashInfo;
-    memoryWs.connect(
-      `ws://${server}/memory?token=${encodeURIComponent(secret)}`
-    );
-    return () => memoryWs.disconnect();
-  }, [clashInfo, pageVisible, displayMemory]);
-
   const [up, upUnit] = parseTraffic(traffic.up);
   const [down, downUnit] = parseTraffic(traffic.down);
   const [inuse, inuseUnit] = parseTraffic(memory.inuse);