refactor: 引入 SWR 统一钱包余额查询与轮询逻辑

This commit is contained in:
2026-06-10 13:58:26 +08:00
parent 4707101da4
commit 20d2befa55
12 changed files with 255 additions and 268 deletions

22
package-lock.json generated
View File

@@ -27,6 +27,7 @@
"react-dom": "19.2.4",
"react-i18next": "^17.0.7",
"sonner": "^2.0.7",
"swr": "^2.4.1",
"tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0",
"zustand": "^5.0.13"
@@ -4382,6 +4383,14 @@
"node": ">= 0.8"
}
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://mirrors.cloud.tencent.com/npm/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"engines": {
"node": ">=6"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -9723,6 +9732,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/swr": {
"version": "2.4.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/swr/-/swr-2.4.1.tgz",
"integrity": "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/tagged-tag": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/tagged-tag/-/tagged-tag-1.0.0.tgz",

View File

@@ -28,6 +28,7 @@
"react-dom": "19.2.4",
"react-i18next": "^17.0.7",
"sonner": "^2.0.7",
"swr": "^2.4.1",
"tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0",
"zustand": "^5.0.13"

View File

@@ -2,8 +2,9 @@
import { Wallet } from "lucide-react";
import Image from "next/image";
import { useCallback, useEffect, useRef, useState } from "react";
import { useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import { useSWRConfig } from "swr";
import { getWalletBalance } from "@/api/wallet";
import { Skeleton } from "@/components/ui/skeleton";
@@ -12,76 +13,51 @@ import {
TransferOutDialog,
} from "@/features/wallet/wallet-transfer-dialogs";
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
import { useApiQuery } from "@/hooks/use-api-query";
import { formatMinorAsCurrency } from "@/lib/money";
import { PLAYER_CURRENCY_CHANGE_EVENT } from "@/lib/player-currency-preference";
import { cn } from "@/lib/utils";
import { useNetworkConnectionStore } from "@/stores/network-connection-store";
import type { WalletBalanceData } from "@/types/api/wallet-balance";
const BALANCE_KEY = (currency: string) => ["wallet/balance", currency];
export function HallWalletStrip() {
const mode = useNetworkConnectionStore((s) => s.mode);
const { t } = useTranslation("player");
const { activeCurrency } = useActivePlayerCurrency();
const [balance, setBalance] = useState<WalletBalanceData | null>(null);
const [loading, setLoading] = useState(true);
const { mutate } = useSWRConfig();
const degradedWalletPollRef = useRef<number | null>(null);
const currency = activeCurrency;
const isDegraded = mode === "polling" || mode === "offline";
const refresh = useCallback(async () => {
const b = await getWalletBalance({ currency: activeCurrency });
setBalance(b);
}, [activeCurrency]);
const { data: balance, isLoading: loading } = useApiQuery(
BALANCE_KEY(currency),
() => getWalletBalance({ currency }),
{
// 降级模式下 60 秒自动刷新SWR 内置定时器代替手动 setInterval
refreshInterval: isDegraded ? 60_000 : undefined,
},
);
// 全局事件触发刷新(下注后、币种切换等场景)
useEffect(() => {
let cancelled = false;
void (async () => {
setLoading(true);
try {
await refresh();
} finally {
if (!cancelled) setLoading(false);
}
})();
return () => {
cancelled = true;
};
}, [refresh]);
useEffect(() => {
const onRefresh = () => void refresh();
const onRefresh = () => void mutate(BALANCE_KEY(currency));
window.addEventListener("lottery-wallet-refresh", onRefresh);
window.addEventListener(PLAYER_CURRENCY_CHANGE_EVENT, onRefresh);
return () => {
window.removeEventListener("lottery-wallet-refresh", onRefresh);
window.removeEventListener(PLAYER_CURRENCY_CHANGE_EVENT, onRefresh);
};
}, [refresh]);
}, [mutate, currency]);
// 非降级模式时清理遗留计时器refreshInterval 已由 SWR 管理)
useEffect(() => {
if (mode !== "polling" && mode !== "offline") {
if (degradedWalletPollRef.current !== null) {
window.clearInterval(degradedWalletPollRef.current);
degradedWalletPollRef.current = null;
}
return;
if (!isDegraded && degradedWalletPollRef.current !== null) {
window.clearInterval(degradedWalletPollRef.current);
degradedWalletPollRef.current = null;
}
if (degradedWalletPollRef.current !== null) {
return;
}
degradedWalletPollRef.current = window.setInterval(() => {
void refresh();
}, 60_000);
return () => {
if (degradedWalletPollRef.current !== null) {
window.clearInterval(degradedWalletPollRef.current);
degradedWalletPollRef.current = null;
}
};
}, [mode, refresh]);
}, [isDegraded]);
const availableMinor = Number(balance?.available_balance ?? balance?.balance ?? 0);
const isCreditPlayer =
@@ -136,7 +112,7 @@ export function HallWalletStrip() {
currency={currency}
lotteryMinor={availableMinor}
mainMinor={mainMinor}
onSuccess={refresh}
onSuccess={async () => { await mutate(BALANCE_KEY(currency)); }}
/>
<TransferOutDialog
idPrefix="hall-"
@@ -145,7 +121,7 @@ export function HallWalletStrip() {
triggerClassName="h-12 rounded-lg text-base font-bold"
currency={currency}
availableMinor={availableMinor}
onSuccess={refresh}
onSuccess={async () => { await mutate(BALANCE_KEY(currency)); }}
/>
</div>
)}

View File

@@ -1,51 +1,40 @@
"use client";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect } from "react";
import { useSWRConfig } from "swr";
import { getWalletBalance } from "@/api/wallet";
import { TransferInPage } from "@/features/wallet/wallet-transfer-forms";
import { Skeleton } from "@/components/ui/skeleton";
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
import { useApiQuery } from "@/hooks/use-api-query";
import { PLAYER_CURRENCY_CHANGE_EVENT } from "@/lib/player-currency-preference";
import type { WalletBalanceData } from "@/types/api/wallet-balance";
const BALANCE_KEY = (currency: string) => ["wallet/balance", currency];
/** 独立路由 `/wallet/transfer-in` */
export function TransferInScreen() {
const router = useRouter();
const { activeCurrency: currency } = useActivePlayerCurrency();
const [balance, setBalance] = useState<WalletBalanceData | null>(null);
const [loading, setLoading] = useState(true);
const { mutate } = useSWRConfig();
const load = useCallback(async () => {
const b = await getWalletBalance({ currency });
setBalance(b);
}, [currency]);
const { data: balance, isLoading: loading } = useApiQuery(
BALANCE_KEY(currency),
() => getWalletBalance({ currency }),
);
// 币种切换时 SWR key 自动变化,此处仅处理全局事件触发的刷新
useEffect(() => {
let c = false;
void (async () => {
try {
await load();
} finally {
if (!c) setLoading(false);
}
})();
return () => {
c = true;
};
}, [load]);
useEffect(() => {
const onCurrencyChange = () => void load();
window.addEventListener(PLAYER_CURRENCY_CHANGE_EVENT, onCurrencyChange);
return () => window.removeEventListener(PLAYER_CURRENCY_CHANGE_EVENT, onCurrencyChange);
}, [load]);
const onRefresh = () => void mutate(BALANCE_KEY(currency));
window.addEventListener(PLAYER_CURRENCY_CHANGE_EVENT, onRefresh);
return () => window.removeEventListener(PLAYER_CURRENCY_CHANGE_EVENT, onRefresh);
}, [mutate, currency]);
const onSuccess = useCallback(async () => {
await load();
await mutate(BALANCE_KEY(currency));
router.push("/wallet");
}, [load, router]);
}, [mutate, currency, router]);
if (loading && !balance) {
return (

View File

@@ -1,51 +1,40 @@
"use client";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect } from "react";
import { useSWRConfig } from "swr";
import { getWalletBalance } from "@/api/wallet";
import { TransferOutPage } from "@/features/wallet/wallet-transfer-forms";
import { Skeleton } from "@/components/ui/skeleton";
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
import { useApiQuery } from "@/hooks/use-api-query";
import { PLAYER_CURRENCY_CHANGE_EVENT } from "@/lib/player-currency-preference";
import type { WalletBalanceData } from "@/types/api/wallet-balance";
const BALANCE_KEY = (currency: string) => ["wallet/balance", currency];
/** 独立路由 `/wallet/transfer-out` */
export function TransferOutScreen() {
const router = useRouter();
const { activeCurrency: currency } = useActivePlayerCurrency();
const [balance, setBalance] = useState<WalletBalanceData | null>(null);
const [loading, setLoading] = useState(true);
const { mutate } = useSWRConfig();
const load = useCallback(async () => {
const b = await getWalletBalance({ currency });
setBalance(b);
}, [currency]);
const { data: balance, isLoading: loading } = useApiQuery(
BALANCE_KEY(currency),
() => getWalletBalance({ currency }),
);
// 币种切换时 SWR key 自动变化,此处仅处理全局事件触发的刷新
useEffect(() => {
let c = false;
void (async () => {
try {
await load();
} finally {
if (!c) setLoading(false);
}
})();
return () => {
c = true;
};
}, [load]);
useEffect(() => {
const onCurrencyChange = () => void load();
window.addEventListener(PLAYER_CURRENCY_CHANGE_EVENT, onCurrencyChange);
return () => window.removeEventListener(PLAYER_CURRENCY_CHANGE_EVENT, onCurrencyChange);
}, [load]);
const onRefresh = () => void mutate(BALANCE_KEY(currency));
window.addEventListener(PLAYER_CURRENCY_CHANGE_EVENT, onRefresh);
return () => window.removeEventListener(PLAYER_CURRENCY_CHANGE_EVENT, onRefresh);
}, [mutate, currency]);
const onSuccess = useCallback(async () => {
await load();
await mutate(BALANCE_KEY(currency));
router.push("/wallet");
}, [load, router]);
}, [mutate, currency, router]);
if (loading && !balance) {
return (

View File

@@ -66,11 +66,13 @@ export function WalletScreen() {
setLogsLoading(true);
}
try {
const b = await getWalletBalance({ currency });
// 并行请求余额和日志,避免瀑布式串行等待
const [b, nextLogs] = await Promise.all([
getWalletBalance({ currency }),
loadLogs(1, false),
]);
if (cancelled) return;
setBalance(b);
const nextLogs = await loadLogs(1, false);
if (cancelled) return;
setLogs(nextLogs);
} catch (e) {
if (!cancelled) {
@@ -93,9 +95,12 @@ export function WalletScreen() {
setError(null);
setLogsLoading(true);
try {
const b = await getWalletBalance({ currency });
// 并行请求余额和日志
const [b] = await Promise.all([
getWalletBalance({ currency }),
loadLogs(1, false),
]);
setBalance(b);
await loadLogs(1, false);
} catch (e) {
setError(formatWalletClientError(e, t));
} finally {

View File

@@ -5,3 +5,4 @@ export { useWalletPolling, triggerWalletPollingAfterBet } from "./use-wallet-pol
export { useWebSocketManager } from "./use-websocket-manager";
export { usePlayerBalanceWs } from "./use-player-balance-ws";
export { usePlayEffectiveWs } from "./use-play-effective-ws";
export { useApiQuery } from "./use-api-query";

View File

@@ -0,0 +1,38 @@
"use client";
import useSWR, { type SWRConfiguration, type SWRResponse } from "swr";
/**
* 通用 API 查询 Hook —— 基于 SWR 的薄包装。
*
* @param key SWR 缓存键。传 `null` 表示暂不请求(条件请求)。
* @param fetcher 无参异步函数,通常由 `api/*.ts` 的函数包装而来。
* @param options SWR 配置覆盖。
*
* @example
* ```tsx
* // 简单用法
* const { data, error, isLoading } = useApiQuery(
* currency ? ["wallet/balance", currency] : null,
* () => getWalletBalance({ currency }),
* );
*
* // 带自定义刷新间隔
* const { data } = useApiQuery(
* "draw/current",
* () => getCurrentDraw(),
* { refreshInterval: 5_000 },
* );
* ```
*/
export function useApiQuery<Data = unknown, Error = unknown>(
key: string | readonly unknown[] | null,
fetcher: () => Promise<Data>,
options?: SWRConfiguration<Data, Error>,
): SWRResponse<Data, Error> {
return useSWR<Data, Error>(key, fetcher, {
revalidateOnFocus: false,
dedupingInterval: 2_000,
...options,
});
}

View File

@@ -2,13 +2,13 @@
import { useCallback, useEffect, useRef } from "react";
import { getWalletBalance } from "@/api/wallet";
import { getActivePlayerCurrencyFromStore } from "@/lib/player-currency";
import { startWalletRefreshBurst } from "@/lib/wallet-refresh-burst";
import { useNetworkConnectionStore } from "@/stores/network-connection-store";
const POLLING_INTERVAL_MS = 30_000; // 30秒轮询间隔
const LIMITED_POLLING_DURATION_MS = 2 * 60 * 1000; // 2分钟限时轮询
import {
refreshWalletNow,
startLimitedWalletPolling,
startPersistentWalletPolling,
stopWalletPolling,
} from "@/lib/wallet-polling-utils";
export type UseWalletPollingReturn = {
/** 开始钱包轮询(持续或限时) */
@@ -29,14 +29,10 @@ export type UseWalletPollingReturn = {
* 3. 开奖结果后的限时轮询2分钟
*/
export function useWalletPolling(): UseWalletPollingReturn {
const store = useNetworkConnectionStore();
const {
walletPollingIntervalId,
walletPollingExpiryAt,
setWalletPollingIntervalId,
setWalletPollingExpiryAt,
clearWalletPolling,
} = store;
// 使用 selector 避免整 store 订阅导致无谓重渲染
const walletPollingIntervalId = useNetworkConnectionStore(
(s) => s.walletPollingIntervalId,
);
const intervalIdRef = useRef<number | null>(walletPollingIntervalId);
@@ -45,79 +41,28 @@ export function useWalletPolling(): UseWalletPollingReturn {
intervalIdRef.current = walletPollingIntervalId;
}, [walletPollingIntervalId]);
// 刷新钱包余额
const refreshWallet = useCallback(async () => {
try {
await getWalletBalance({ currency: getActivePlayerCurrencyFromStore() });
// 触发全局刷新事件,让所有监听组件更新
window.dispatchEvent(new Event("lottery-wallet-refresh"));
} catch {
// 静默处理错误,避免频繁报错
}
}, []);
// 开始钱包轮询
// 开始钱包轮询(委托给共享工具函数)
const startWalletPolling = useCallback(
(options?: { limitedDuration?: boolean }) => {
const { limitedDuration = false } = options ?? {};
// 先停止现有的轮询
if (intervalIdRef.current) {
window.clearInterval(intervalIdRef.current);
intervalIdRef.current = null;
}
// 立即执行一次刷新
void refreshWallet();
// 设置轮询间隔
const intervalId = window.setInterval(() => {
// 检查限时轮询是否过期
if (limitedDuration && walletPollingExpiryAt) {
if (Date.now() > walletPollingExpiryAt) {
// 过期,停止轮询
clearWalletPolling();
intervalIdRef.current = null;
return;
}
}
void refreshWallet();
}, POLLING_INTERVAL_MS);
intervalIdRef.current = intervalId;
setWalletPollingIntervalId(intervalId);
// 设置限时轮询的过期时间
if (limitedDuration) {
const expiryAt = Date.now() + LIMITED_POLLING_DURATION_MS;
setWalletPollingExpiryAt(expiryAt);
// 设置一个定时器在过期时清理
window.setTimeout(() => {
if (intervalIdRef.current === intervalId) {
clearWalletPolling();
intervalIdRef.current = null;
}
}, LIMITED_POLLING_DURATION_MS + 1000); // 稍微延迟确保interval先执行检查
startLimitedWalletPolling();
} else {
startPersistentWalletPolling();
}
},
[
walletPollingExpiryAt,
refreshWallet,
setWalletPollingIntervalId,
setWalletPollingExpiryAt,
clearWalletPolling,
],
[],
);
// 停止钱包轮询
const stopWalletPolling = useCallback(() => {
const handleStop = useCallback(() => {
if (intervalIdRef.current) {
window.clearInterval(intervalIdRef.current);
intervalIdRef.current = null;
}
clearWalletPolling();
}, [clearWalletPolling]);
stopWalletPolling();
}, []);
// 组件卸载时清理
useEffect(() => {
@@ -130,8 +75,8 @@ export function useWalletPolling(): UseWalletPollingReturn {
return {
startWalletPolling,
stopWalletPolling,
refreshWallet,
stopWalletPolling: handleStop,
refreshWallet: refreshWalletNow,
isPolling: walletPollingIntervalId !== null,
};
}
@@ -144,7 +89,7 @@ export function triggerWalletPollingAfterBet(): void {
const store = useNetworkConnectionStore.getState();
if (store.mode === "polling" || store.mode === "offline") {
startWalletRefreshBurst();
startLimitedWalletPolling();
return;
}

View File

@@ -2,20 +2,22 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { getWalletBalance } from "@/api/wallet";
import { getActivePlayerCurrencyFromStore } from "@/lib/player-currency";
import {
disconnectLotteryEcho,
getLotteryEcho,
isLotteryReverbConfigured,
} from "@/lib/lottery-echo";
import {
refreshWalletNow,
startLimitedWalletPolling,
startPersistentWalletPolling,
stopWalletPolling as stopWalletPollingUtil,
} from "@/lib/wallet-polling-utils";
import {
useNetworkConnectionStore,
type NetworkMode,
} from "@/stores/network-connection-store";
const POLLING_INTERVAL_MS = 30_000; // 30秒轮询间隔
const WALLET_POLLING_DURATION_MS = 2 * 60 * 1000; // 2分钟限时轮询
const RECONNECT_INTERVAL_MS = 5_000; // 5秒重连间隔
const MAX_RECONNECT_ATTEMPTS = 10; // 最大重连次数
@@ -59,81 +61,45 @@ export type UseWebSocketManagerReturn = {
* 5. 可选钱包余额轮询(`startWalletPolling`,下注等场景)
*/
export function useWebSocketManager(): UseWebSocketManagerReturn {
const store = useNetworkConnectionStore();
// 使用 selector 避免整 store 订阅导致无谓重渲染
const mode = useNetworkConnectionStore((s) => s.mode);
const reconnectAttempts = useNetworkConnectionStore((s) => s.reconnectAttempts);
const setWebSocketConnected = useNetworkConnectionStore((s) => s.setWebSocketConnected);
const setReconnecting = useNetworkConnectionStore((s) => s.setReconnecting);
const incrementReconnectAttempts = useNetworkConnectionStore((s) => s.incrementReconnectAttempts);
const resetReconnectAttempts = useNetworkConnectionStore((s) => s.resetReconnectAttempts);
const setLastDisconnectedAt = useNetworkConnectionStore((s) => s.setLastDisconnectedAt);
const switchToPollingMode = useNetworkConnectionStore((s) => s.switchToPollingMode);
const switchToWebSocketMode = useNetworkConnectionStore((s) => s.switchToWebSocketMode);
const reconnectTimerRef = useRef<number | null>(null);
const attemptReconnectRef = useRef<() => void>(() => {});
/** 递增后重新创建 Echo 并绑定连接事件(供「恢复」与重连使用) */
const [echoSession, setEchoSession] = useState(0);
const {
mode,
reconnectAttempts,
setWebSocketConnected,
setReconnecting,
incrementReconnectAttempts,
resetReconnectAttempts,
setLastDisconnectedAt,
switchToPollingMode,
switchToWebSocketMode,
clearWalletPolling,
} = store;
// 刷新画作数据
const refreshDraw = useCallback(async () => {
// 由 useHallDrawLive 监听并拉取,避免与大厅重复请求 draw/current
window.dispatchEvent(new Event("lottery-hall-refresh"));
}, []);
// 刷新钱包余额
const refreshWallet = useCallback(async () => {
try {
await getWalletBalance({ currency: getActivePlayerCurrencyFromStore() });
// 触发全局刷新事件,让组件更新
window.dispatchEvent(new Event("lottery-wallet-refresh"));
} catch {
// 静默处理错误
}
}, []);
// 钱包轮询
// 钱包轮询(委托给共享工具函数,消除与 useWalletPolling 的重复逻辑)
const startWalletPolling = useCallback(
(options?: { limitedDuration?: boolean }) => {
const { limitedDuration = false } = options ?? {};
const s0 = useNetworkConnectionStore.getState();
const prevWalletId = s0.walletPollingIntervalId;
if (prevWalletId !== null) {
window.clearInterval(prevWalletId);
}
void refreshWallet();
const intervalId = window.setInterval(() => {
const s = useNetworkConnectionStore.getState();
if (
limitedDuration &&
s.walletPollingExpiryAt !== null &&
Date.now() > s.walletPollingExpiryAt
) {
s.clearWalletPolling();
return;
}
void refreshWallet();
}, POLLING_INTERVAL_MS);
s0.setWalletPollingIntervalId(intervalId);
if (limitedDuration) {
s0.setWalletPollingExpiryAt(Date.now() + WALLET_POLLING_DURATION_MS);
startLimitedWalletPolling();
} else {
startPersistentWalletPolling();
}
},
[refreshWallet],
[],
);
// 停止钱包轮询
const stopWalletPolling = useCallback(() => {
clearWalletPolling();
}, [clearWalletPolling]);
stopWalletPollingUtil();
}, []);
// 检测 Pusher 是否已真正 connected非仅订阅频道
const isPusherConnected = useCallback((): boolean => {
@@ -235,7 +201,7 @@ export function useWebSocketManager(): UseWebSocketManagerReturn {
const handleOffline = () => {
// 网络断开
store.setMode("offline");
useNetworkConnectionStore.getState().setMode("offline");
};
window.addEventListener("online", handleOnline);
@@ -245,7 +211,7 @@ export function useWebSocketManager(): UseWebSocketManagerReturn {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, [mode, reconnect, store]);
}, [mode, reconnect]);
// 初始化:绑定 Pusher 真实连接事件(勿仅凭 channel 对象存在判定已连接)
useEffect(() => {
@@ -349,6 +315,6 @@ export function useWebSocketManager(): UseWebSocketManagerReturn {
startWalletPolling,
stopWalletPolling,
refreshDraw,
refreshWallet,
refreshWallet: refreshWalletNow,
};
}

View File

@@ -0,0 +1,70 @@
import { getWalletBalance } from "@/api/wallet";
import { getActivePlayerCurrencyFromStore } from "@/lib/player-currency";
import { useNetworkConnectionStore } from "@/stores/network-connection-store";
const POLLING_INTERVAL_MS = 30_000; // 30秒轮询间隔
const LIMITED_POLLING_DURATION_MS = 2 * 60 * 1000; // 2分钟限时轮询
/**
* 非 Hook 钱包刷新工具 —— 供 Hook 与非 React 上下文共用。
*
* 立即拉取一次余额,并触发 `lottery-wallet-refresh` 全局事件。
*/
export async function refreshWalletNow(): Promise<void> {
try {
await getWalletBalance({ currency: getActivePlayerCurrencyFromStore() });
window.dispatchEvent(new Event("lottery-wallet-refresh"));
} catch {
// 静默处理错误,避免频繁报错
}
}
/**
* 启动钱包限时轮询2 分钟内每 30s 刷新一次)。
* 先清除已有的钱包轮询计时器,避免泄漏。
*/
export function startLimitedWalletPolling(): void {
const store = useNetworkConnectionStore.getState();
store.clearWalletPolling();
void refreshWalletNow();
const expiryAt = Date.now() + LIMITED_POLLING_DURATION_MS;
store.setWalletPollingExpiryAt(expiryAt);
const intervalId = window.setInterval(() => {
const s = useNetworkConnectionStore.getState();
if (
s.walletPollingExpiryAt !== null &&
Date.now() > s.walletPollingExpiryAt
) {
s.clearWalletPolling();
return;
}
void refreshWalletNow();
}, POLLING_INTERVAL_MS);
store.setWalletPollingIntervalId(intervalId);
}
/**
* 启动钱包持续轮询(直到手动停止)。
* 先清除已有的钱包轮询计时器,避免泄漏。
*/
export function startPersistentWalletPolling(): void {
const store = useNetworkConnectionStore.getState();
store.clearWalletPolling();
void refreshWalletNow();
const intervalId = window.setInterval(() => {
void refreshWalletNow();
}, POLLING_INTERVAL_MS);
store.setWalletPollingIntervalId(intervalId);
}
/** 停止钱包轮询 */
export function stopWalletPolling(): void {
useNetworkConnectionStore.getState().clearWalletPolling();
}

View File

@@ -1,27 +1,12 @@
import { useNetworkConnectionStore } from "@/stores/network-connection-store";
const WALLET_REFRESH_BURST_MS = 30_000;
const WALLET_REFRESH_BURST_DURATION_MS = 2 * 60 * 1000;
/**
* @deprecated 请直接使用 `@/lib/wallet-polling-utils` 的 `startLimitedWalletPolling`。
* 此文件保留向后兼容,内部已委托给共享工具函数。
*/
import { startLimitedWalletPolling } from "@/lib/wallet-polling-utils";
/**
* 开奖等场景:立即刷新余额,并在限时内每 30s 派发 `lottery-wallet-refresh`(先清旧定时器,避免泄漏)。
*/
export function startWalletRefreshBurst(): void {
const store = useNetworkConnectionStore.getState();
store.clearWalletPolling();
window.dispatchEvent(new Event("lottery-wallet-refresh"));
const expiryAt = Date.now() + WALLET_REFRESH_BURST_DURATION_MS;
store.setWalletPollingExpiryAt(expiryAt);
const intervalId = window.setInterval(() => {
if (Date.now() > expiryAt) {
useNetworkConnectionStore.getState().clearWalletPolling();
return;
}
window.dispatchEvent(new Event("lottery-wallet-refresh"));
}, WALLET_REFRESH_BURST_MS);
store.setWalletPollingIntervalId(intervalId);
startLimitedWalletPolling();
}