Skip to content

Commit

Permalink
feat(sidebar): refactor WebSocket integration for online user tracking
Browse files Browse the repository at this point in the history
- Replaced manual WebSocket implementation with a custom hook for better reusability and cleaner code.
- Updated the sidebar component to utilize the new useWebSocket hook for tracking online users.
- Incremented version number in tauri configuration to 2.15.9-beta.0.
  • Loading branch information
kiritoko1029 committed Dec 23, 2024
1 parent 90b8168 commit ba96c11
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 33 deletions.
34 changes: 2 additions & 32 deletions app/components/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useRef, useMemo, useState, Fragment } from "react";

import { useWebSocket } from "../hooks/useWebSocket";
import styles from "./home.module.scss";

import { IconButton } from "./button";
Expand Down Expand Up @@ -328,10 +328,10 @@ const SubTitle = function SubTitle(props: {}) {
const [hitokoto, setHitokoto] = useState("");
const [hitokoto_from, setHitokoto_from] = useState("");
const [from_who, setFrom_who] = useState("");
const [onlineUsers, setOnlineUsers] = useState(0);
const [showCopied, setShowCopied] = useState(false);
const refreshed = useRef(false);
const { hitokotoUrl } = useAccessStore();
const onlineUsers = useWebSocket();

const copyHitokoto = async (e: React.MouseEvent) => {
e.stopPropagation();
Expand All @@ -354,36 +354,6 @@ const SubTitle = function SubTitle(props: {}) {
});
}

useEffect(() => {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const ws = new WebSocket(`${protocol}//${window.location.host}/ws`);

ws.onopen = () => {
console.log("WebSocket connected");
ws.send(JSON.stringify({ type: "getOnline" }));
};

ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
console.log("Received message:", data);
if (data.type === "online") {
setOnlineUsers(data.count);
}
} catch (e) {
console.error("Failed to parse message:", e);
}
};

ws.onerror = (error) => {
console.error("WebSocket error:", error);
};

return () => {
ws.close();
};
}, []);

useEffect(() => {
if (hitokotoUrl) {
fetchHitokoto();
Expand Down
126 changes: 126 additions & 0 deletions app/hooks/useWebSocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { useEffect, useRef, useCallback } from "react";
import { create } from "zustand";

interface WebSocketStore {
onlineUsers: number;
setOnlineUsers: (count: number) => void;
}

export const useWebSocketStore = create<WebSocketStore>((set) => ({
onlineUsers: 0,
setOnlineUsers: (count) => set({ onlineUsers: count }),
}));

export function useWebSocket() {
const wsRef = useRef<WebSocket | null>(null);
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const reconnectAttemptsRef = useRef(0);
const MAX_RECONNECT_ATTEMPTS = 5;
const setOnlineUsers = useWebSocketStore((state) => state.setOnlineUsers);

const setupWebSocket = useCallback(() => {
// 如果已经有连接或正在重连,不要创建新连接
if (
wsRef.current?.readyState === WebSocket.OPEN ||
wsRef.current?.readyState === WebSocket.CONNECTING
) {
return;
}

// 清理现有连接
if (wsRef.current) {
wsRef.current.close();
wsRef.current = null;
}

// 清理重连定时器
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}

try {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const ws = new WebSocket(`${protocol}//${window.location.host}/ws`);
wsRef.current = ws;

ws.onopen = () => {
console.log("WebSocket connected");
reconnectAttemptsRef.current = 0; // 重置重连次数
ws.send(JSON.stringify({ type: "getOnline" }));
};

ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === "online") {
setOnlineUsers(data.count);
}
} catch (e) {
console.error("Failed to parse message:", e);
}
};

ws.onerror = (error) => {
console.error("WebSocket error:", error);
};

ws.onclose = (event) => {
// 只有在非正常关闭时才重连
if (event.code !== 1000 && event.code !== 1001) {
console.log(
"WebSocket closed unexpectedly, attempting to reconnect...",
);
reconnectAttemptsRef.current += 1;

// 如果重连次数未超过限制,则尝试重连
if (reconnectAttemptsRef.current <= MAX_RECONNECT_ATTEMPTS) {
const delay = Math.min(
1000 * Math.pow(2, reconnectAttemptsRef.current),
10000,
);
reconnectTimeoutRef.current = setTimeout(() => {
if (document.visibilityState === "visible") {
setupWebSocket();
}
}, delay);
} else {
console.log("Max reconnection attempts reached");
}
}
};
} catch (error) {
console.error("Failed to setup WebSocket:", error);
}
}, [setOnlineUsers]);

useEffect(() => {
setupWebSocket();

// 页面可见性变化时的处理
const handleVisibilityChange = () => {
if (document.visibilityState === "visible") {
reconnectAttemptsRef.current = 0; // 重置重连次数
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
setupWebSocket();
}
}
};

document.addEventListener("visibilitychange", handleVisibilityChange);

return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
if (wsRef.current) {
const ws = wsRef.current;
wsRef.current = null; // 立即清空引用
ws.close(1000); // 使用正常关闭代码
}
};
}, [setupWebSocket]);

return useWebSocketStore((state) => state.onlineUsers);
}
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
"package": {
"productName": "NextChat",
"version": "2.15.8"
"version": "2.15.9-beta.0"
},
"tauri": {
"allowlist": {
Expand Down

0 comments on commit ba96c11

Please sign in to comment.