fix: improve Reverb WebSocket connect and reconnect handling
Avoid premature polling fallback while Pusher is connecting, schedule reconnect after disconnect, and disable Pusher stats for Reverb. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { getWalletBalance } from "@/api/wallet";
|
||||
import { getActivePlayerCurrencyFromStore } from "@/lib/player-currency";
|
||||
import { getLotteryEcho } from "@/lib/lottery-echo";
|
||||
import { disconnectLotteryEcho, getLotteryEcho } from "@/lib/lottery-echo";
|
||||
import {
|
||||
useNetworkConnectionStore,
|
||||
type NetworkMode,
|
||||
@@ -15,6 +15,19 @@ const WALLET_POLLING_DURATION_MS = 2 * 60 * 1000; // 2分钟限时轮询
|
||||
const RECONNECT_INTERVAL_MS = 5_000; // 5秒重连间隔
|
||||
const MAX_RECONNECT_ATTEMPTS = 10; // 最大重连次数
|
||||
|
||||
/** Pusher 尚未连上前的中间态,不应立刻切降级 */
|
||||
const PUSHER_PENDING_STATES = new Set([
|
||||
"initialized",
|
||||
"connecting",
|
||||
"reconnecting",
|
||||
]);
|
||||
|
||||
type PusherConnection = {
|
||||
state?: string;
|
||||
bind: (event: string, callback: () => void) => void;
|
||||
unbind: (event: string, callback: () => void) => void;
|
||||
};
|
||||
|
||||
export type UseWebSocketManagerReturn = {
|
||||
/** 当前网络模式 */
|
||||
mode: NetworkMode;
|
||||
@@ -45,6 +58,8 @@ export function useWebSocketManager(): UseWebSocketManagerReturn {
|
||||
const store = useNetworkConnectionStore();
|
||||
const reconnectTimerRef = useRef<number | null>(null);
|
||||
const attemptReconnectRef = useRef<() => void>(() => {});
|
||||
/** 递增后重新创建 Echo 并绑定连接事件(供「恢复」与重连使用) */
|
||||
const [echoSession, setEchoSession] = useState(0);
|
||||
|
||||
const {
|
||||
mode,
|
||||
@@ -135,6 +150,7 @@ export function useWebSocketManager(): UseWebSocketManagerReturn {
|
||||
}
|
||||
|
||||
setReconnecting(true);
|
||||
attemptReconnectRef.current();
|
||||
}, [
|
||||
setWebSocketConnected,
|
||||
setLastDisconnectedAt,
|
||||
@@ -192,12 +208,17 @@ export function useWebSocketManager(): UseWebSocketManagerReturn {
|
||||
attemptReconnectRef.current = attemptReconnect;
|
||||
}, [attemptReconnect]);
|
||||
|
||||
// 手动重连
|
||||
// 手动重连:断开旧 Echo、重建连接(仅 reset 计数无法修复 failed 状态)
|
||||
const reconnect = useCallback(() => {
|
||||
disconnectLotteryEcho();
|
||||
resetReconnectAttempts();
|
||||
setReconnecting(true);
|
||||
attemptReconnect();
|
||||
}, [resetReconnectAttempts, setReconnecting, attemptReconnect]);
|
||||
if (reconnectTimerRef.current !== null) {
|
||||
window.clearTimeout(reconnectTimerRef.current);
|
||||
reconnectTimerRef.current = null;
|
||||
}
|
||||
setEchoSession((n) => n + 1);
|
||||
}, [resetReconnectAttempts, setReconnecting]);
|
||||
|
||||
// 监听网络状态变化
|
||||
useEffect(() => {
|
||||
@@ -232,13 +253,17 @@ export function useWebSocketManager(): UseWebSocketManagerReturn {
|
||||
|
||||
echo.channel("lottery-hall");
|
||||
|
||||
const pusher = echo.connector?.pusher;
|
||||
const connection = pusher?.connection;
|
||||
let connection = echo.connector?.pusher?.connection as
|
||||
| PusherConnection
|
||||
| undefined;
|
||||
let pendingTimer: number | null = null;
|
||||
|
||||
const syncConnectionState = () => {
|
||||
const state = connection?.state;
|
||||
if (state === "connected") {
|
||||
handleConnect();
|
||||
} else if (state && PUSHER_PENDING_STATES.has(state)) {
|
||||
setReconnecting(true);
|
||||
} else if (
|
||||
state === "disconnected" ||
|
||||
state === "unavailable" ||
|
||||
@@ -248,34 +273,55 @@ export function useWebSocketManager(): UseWebSocketManagerReturn {
|
||||
}
|
||||
};
|
||||
|
||||
if (connection) {
|
||||
connection.bind("connected", handleConnect);
|
||||
connection.bind("disconnected", handleDisconnect);
|
||||
connection.bind("unavailable", handleDisconnect);
|
||||
connection.bind("failed", handleDisconnect);
|
||||
const bindConnectionEvents = (conn: PusherConnection) => {
|
||||
conn.bind("connected", handleConnect);
|
||||
conn.bind("disconnected", handleDisconnect);
|
||||
conn.bind("unavailable", handleDisconnect);
|
||||
conn.bind("failed", handleDisconnect);
|
||||
conn.bind("state_change", syncConnectionState);
|
||||
syncConnectionState();
|
||||
};
|
||||
|
||||
if (connection) {
|
||||
bindConnectionEvents(connection);
|
||||
} else {
|
||||
// connector 可能下一 tick 才就绪;勿立刻切降级
|
||||
pendingTimer = window.setTimeout(() => {
|
||||
connection = echo.connector?.pusher?.connection as
|
||||
| PusherConnection
|
||||
| undefined;
|
||||
if (connection) {
|
||||
bindConnectionEvents(connection);
|
||||
} else if (isPusherConnected()) {
|
||||
handleConnect();
|
||||
} else {
|
||||
handleDisconnect();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (pendingTimer !== null) {
|
||||
window.clearTimeout(pendingTimer);
|
||||
}
|
||||
if (connection) {
|
||||
connection.unbind("connected", handleConnect);
|
||||
connection.unbind("disconnected", handleDisconnect);
|
||||
connection.unbind("unavailable", handleDisconnect);
|
||||
connection.unbind("failed", handleDisconnect);
|
||||
connection.unbind("state_change", syncConnectionState);
|
||||
}
|
||||
if (reconnectTimerRef.current) {
|
||||
window.clearTimeout(reconnectTimerRef.current);
|
||||
}
|
||||
};
|
||||
}, [
|
||||
echoSession,
|
||||
isPusherConnected,
|
||||
handleConnect,
|
||||
handleDisconnect,
|
||||
switchToPollingMode,
|
||||
setReconnecting,
|
||||
]);
|
||||
|
||||
// 仅挂载卸载时清理;勿依赖 stop*(否则每次 setInterval id 变化都会触发清理 → setState → 无线循环)
|
||||
|
||||
@@ -43,7 +43,8 @@ export function getLotteryEcho(): Echo<"reverb"> | null {
|
||||
wsPort: port,
|
||||
wssPort: port,
|
||||
forceTLS,
|
||||
enabledTransports: forceTLS ? ["wss"] : ["ws"],
|
||||
enabledTransports: forceTLS ? ["ws", "wss"] : ["ws"],
|
||||
disableStats: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user