114 lines
3.2 KiB
TypeScript
114 lines
3.2 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { toast } from "sonner";
|
|
|
|
import {
|
|
disconnectLotteryEcho,
|
|
getLotteryEcho,
|
|
} from "@/lib/lottery-echo";
|
|
import { parseJwtSessionVersion } from "@/lib/jwt-payload";
|
|
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
|
|
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
|
|
|
type BalanceUpdateWsEvent = {
|
|
player_id?: number;
|
|
currency_code?: string;
|
|
balance_minor?: number;
|
|
change_minor?: number;
|
|
change_formatted?: string;
|
|
reason?: string;
|
|
};
|
|
|
|
type SessionReplacedWsEvent = {
|
|
player_id?: number;
|
|
session_version?: number;
|
|
};
|
|
|
|
const REASON_I18N_KEY: Record<string, string> = {
|
|
transfer_in: "wallet.wsReason.transferIn",
|
|
transfer_out: "wallet.wsReason.transferOut",
|
|
bet: "wallet.wsReason.bet",
|
|
prize: "wallet.wsReason.prize",
|
|
refund: "wallet.wsReason.refund",
|
|
};
|
|
|
|
/**
|
|
* 订阅 `player.{id}` 私有频道的 `balance.update`,刷新余额并 Toast。
|
|
*/
|
|
export function usePlayerBalanceWs(): void {
|
|
const { t } = useTranslation("player");
|
|
const playerId = usePlayerSessionStore((state) => state.profile?.id);
|
|
const bearerToken = usePlayerSessionStore((state) => state.bearerToken);
|
|
const { activeCurrency } = useActivePlayerCurrency();
|
|
|
|
useEffect(() => {
|
|
if (!playerId || !bearerToken) {
|
|
return;
|
|
}
|
|
|
|
const echo = getLotteryEcho();
|
|
if (!echo) {
|
|
return;
|
|
}
|
|
|
|
const channelName = `player.${playerId}`;
|
|
const channel = echo.private(channelName);
|
|
|
|
const onBalanceUpdate = (evt: BalanceUpdateWsEvent): void => {
|
|
const currency = evt.currency_code?.trim().toUpperCase();
|
|
if (currency && currency !== activeCurrency.toUpperCase()) {
|
|
return;
|
|
}
|
|
|
|
window.dispatchEvent(new Event("lottery-wallet-refresh"));
|
|
|
|
const changeLabel =
|
|
typeof evt.change_formatted === "string" && evt.change_formatted !== ""
|
|
? evt.change_formatted
|
|
: evt.change_minor != null
|
|
? String(evt.change_minor)
|
|
: "";
|
|
|
|
const reasonKey =
|
|
evt.reason && REASON_I18N_KEY[evt.reason]
|
|
? REASON_I18N_KEY[evt.reason]
|
|
: "wallet.wsReason.unknown";
|
|
const reasonLabel = t(reasonKey);
|
|
|
|
toast.message(t("wallet.wsBalanceUpdated", { change: changeLabel, reason: reasonLabel }));
|
|
};
|
|
|
|
const onSessionReplaced = (evt: SessionReplacedWsEvent): void => {
|
|
const nextVersion = evt.session_version;
|
|
if (
|
|
evt.player_id !== playerId ||
|
|
typeof nextVersion !== "number" ||
|
|
!Number.isInteger(nextVersion)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const state = usePlayerSessionStore.getState();
|
|
const currentVersion = parseJwtSessionVersion(state.bearerToken) ?? 0;
|
|
if (nextVersion <= currentVersion) {
|
|
return;
|
|
}
|
|
|
|
disconnectLotteryEcho();
|
|
state.clearBearerToken();
|
|
window.location.replace("/login?session=replaced");
|
|
};
|
|
|
|
channel.listen(".balance.update", onBalanceUpdate);
|
|
channel.listen(".session.replaced", onSessionReplaced);
|
|
|
|
return () => {
|
|
channel.stopListening(".balance.update");
|
|
channel.stopListening(".session.replaced");
|
|
echo.leave(channelName);
|
|
};
|
|
}, [activeCurrency, bearerToken, playerId, t]);
|
|
}
|