refactor: 使用 sonner、zustand persist、partysocket 替换手写实现

This commit is contained in:
2026-03-02 06:57:45 +08:00
parent 08e5abe165
commit ab60db0dc5
9 changed files with 98 additions and 158 deletions

View File

@@ -1,4 +1,6 @@
import { WebSocket as ReconnectingWebSocket } from "partysocket";
import { useCallback, useEffect, useRef } from "react";
import { toast } from "sonner";
import { useAppStore } from "../stores/app-store";
function getWsUrl(): string {
@@ -9,13 +11,8 @@ function getWsUrl(): string {
return `${proto}//${location.host}/ws${q}`;
}
const WS_RECONNECT_BASE = 1000;
const WS_RECONNECT_MAX = 16000;
export function useWebSocket() {
const wsRef = useRef<WebSocket | null>(null);
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const reconnectDelayRef = useRef(WS_RECONNECT_BASE);
const wsRef = useRef<ReconnectingWebSocket | null>(null);
const sendJSON = useCallback((obj: Record<string, unknown>) => {
const ws = wsRef.current;
@@ -27,78 +24,59 @@ export function useWebSocket() {
const sendBinary = useCallback((data: Int16Array) => {
const ws = wsRef.current;
if (ws?.readyState === WebSocket.OPEN) {
ws.send(data.buffer);
ws.send(data.buffer as ArrayBuffer);
}
}, []);
useEffect(() => {
function connect() {
if (wsRef.current) return;
useAppStore.getState().setConnectionStatus("connecting");
useAppStore.getState().setConnectionStatus("connecting");
const ws = new WebSocket(getWsUrl());
ws.binaryType = "arraybuffer";
const ws = new ReconnectingWebSocket(getWsUrl(), undefined, {
minReconnectionDelay: 1000,
maxReconnectionDelay: 16000,
});
ws.binaryType = "arraybuffer";
ws.onopen = () => {
reconnectDelayRef.current = WS_RECONNECT_BASE;
useAppStore.getState().setConnectionStatus("connected");
};
ws.onopen = () => {
useAppStore.getState().setConnectionStatus("connected");
};
ws.onmessage = (e: MessageEvent) => {
if (typeof e.data !== "string") return;
try {
const msg = JSON.parse(e.data);
const store = useAppStore.getState();
switch (msg.type) {
case "partial":
store.setPreview(msg.text || "", false);
break;
case "final":
store.setPreview(msg.text || "", true);
if (msg.text) store.addHistory(msg.text);
break;
case "pasted":
store.showToast("✅ 已粘贴");
break;
case "error":
store.showToast(`${msg.message || "错误"}`);
break;
}
} catch {
// Ignore malformed messages
}
};
ws.onclose = () => {
wsRef.current = null;
ws.onmessage = (e: MessageEvent) => {
if (typeof e.data !== "string") return;
try {
const msg = JSON.parse(e.data);
const store = useAppStore.getState();
store.setConnectionStatus("disconnected");
if (store.recording) store.setRecording(false);
if (store.pendingStart) store.setPendingStart(false);
scheduleReconnect();
};
switch (msg.type) {
case "partial":
store.setPreview(msg.text || "", false);
break;
case "final":
store.setPreview(msg.text || "", true);
if (msg.text) store.addHistory(msg.text);
break;
case "pasted":
toast.success("已粘贴");
break;
case "error":
toast.error(msg.message || "错误");
break;
}
} catch {
// Ignore malformed messages
}
};
ws.onerror = () => ws.close();
wsRef.current = ws;
}
ws.onclose = () => {
const store = useAppStore.getState();
store.setConnectionStatus("disconnected");
if (store.recording) store.setRecording(false);
if (store.pendingStart) store.setPendingStart(false);
};
function scheduleReconnect() {
if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
reconnectTimerRef.current = setTimeout(
connect,
reconnectDelayRef.current,
);
reconnectDelayRef.current = Math.min(
reconnectDelayRef.current * 2,
WS_RECONNECT_MAX,
);
}
connect();
wsRef.current = ws;
return () => {
if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
wsRef.current?.close();
ws.close();
wsRef.current = null;
};
}, []);