fc0dcb0782
refactor: - 主站复用game agent的zustand store fix: - 修复旧请求覆盖问题 - 修复账号切换问题 - 主站原本已经每 4 秒轮询 external generation 任务状态, 在这里补充触发余额刷新的时机 --------- Co-authored-by: 段舒康 <kdletters@qq.com> Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/138 Co-authored-by: 王德宇 <kvtodev@outlook.com> Co-committed-by: 王德宇 <kvtodev@outlook.com>
239 lines
7.2 KiB
TypeScript
239 lines
7.2 KiB
TypeScript
import { create, type StoreApi, type UseBoundStore } from 'zustand';
|
|
|
|
import type {
|
|
ProfileMudPointBalance,
|
|
ProfileRechargeCenterResponse,
|
|
} from '../contracts/runtime';
|
|
|
|
export type MudPointBalanceStatus = 'idle' | 'loading' | 'ready' | 'error';
|
|
|
|
export type ProfileWalletApi = {
|
|
getRechargeCenter(
|
|
signal?: AbortSignal,
|
|
): Promise<ProfileRechargeCenterResponse>;
|
|
};
|
|
|
|
export type ProfileWalletBalanceSnapshot = Readonly<{
|
|
ownerUserId: string;
|
|
ownerVersion: number;
|
|
invalidationVersion: number;
|
|
operationSequence: number;
|
|
}>;
|
|
|
|
export type ProfileWalletStore = {
|
|
ownerUserId: string | null;
|
|
mudPointBalance: ProfileMudPointBalance | null;
|
|
legacyWalletBalance: number | null;
|
|
mudPointBalanceStatus: MudPointBalanceStatus;
|
|
mudPointBalanceError: string;
|
|
setWalletOwner: (userId: string | null) => void;
|
|
captureWalletBalanceSnapshot: (
|
|
ownerUserId: string,
|
|
) => ProfileWalletBalanceSnapshot | null;
|
|
applyWalletBalanceSnapshot: (
|
|
snapshot: ProfileWalletBalanceSnapshot,
|
|
balance: ProfileMudPointBalance,
|
|
) => boolean;
|
|
applyLegacyWalletBalanceSnapshot: (
|
|
snapshot: ProfileWalletBalanceSnapshot,
|
|
balance: number,
|
|
) => boolean;
|
|
onWalletBalanceMayHaveChanged: () => Promise<void>;
|
|
resetWalletBalance: () => void;
|
|
};
|
|
|
|
const EMPTY_WALLET_STATE = {
|
|
mudPointBalance: null,
|
|
legacyWalletBalance: null,
|
|
mudPointBalanceStatus: 'idle',
|
|
mudPointBalanceError: '',
|
|
} as const;
|
|
|
|
export function createProfileWalletStore(
|
|
api: ProfileWalletApi,
|
|
): UseBoundStore<StoreApi<ProfileWalletStore>> {
|
|
let refreshVersion = 0;
|
|
let settledRefreshVersion = 0;
|
|
let ownerVersion = 0;
|
|
let operationSequence = 0;
|
|
let latestAppliedOperationSequence = 0;
|
|
let requestGeneration = 0;
|
|
let activeRefresh: Promise<void> | null = null;
|
|
let activeAbortController: AbortController | null = null;
|
|
|
|
const invalidateActiveRefresh = () => {
|
|
requestGeneration += 1;
|
|
activeAbortController?.abort();
|
|
activeAbortController = null;
|
|
activeRefresh = null;
|
|
};
|
|
|
|
return create<ProfileWalletStore>((set, get) => ({
|
|
ownerUserId: null,
|
|
...EMPTY_WALLET_STATE,
|
|
setWalletOwner: (userId) => {
|
|
const normalizedUserId = userId?.trim() || null;
|
|
if (get().ownerUserId === normalizedUserId) {
|
|
return;
|
|
}
|
|
|
|
ownerVersion += 1;
|
|
latestAppliedOperationSequence = 0;
|
|
invalidateActiveRefresh();
|
|
settledRefreshVersion = refreshVersion;
|
|
set({
|
|
ownerUserId: normalizedUserId,
|
|
...EMPTY_WALLET_STATE,
|
|
});
|
|
},
|
|
captureWalletBalanceSnapshot: (ownerUserId) => {
|
|
const normalizedOwnerUserId = ownerUserId.trim();
|
|
if (
|
|
!normalizedOwnerUserId ||
|
|
get().ownerUserId !== normalizedOwnerUserId
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
ownerUserId: normalizedOwnerUserId,
|
|
ownerVersion,
|
|
invalidationVersion: refreshVersion,
|
|
operationSequence: ++operationSequence,
|
|
};
|
|
},
|
|
applyWalletBalanceSnapshot: (snapshot, balance) => {
|
|
if (
|
|
get().ownerUserId !== snapshot.ownerUserId ||
|
|
ownerVersion !== snapshot.ownerVersion ||
|
|
snapshot.invalidationVersion < refreshVersion ||
|
|
snapshot.invalidationVersion < settledRefreshVersion ||
|
|
snapshot.operationSequence < latestAppliedOperationSequence
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
invalidateActiveRefresh();
|
|
latestAppliedOperationSequence = Math.max(
|
|
latestAppliedOperationSequence,
|
|
snapshot.operationSequence,
|
|
);
|
|
settledRefreshVersion = snapshot.invalidationVersion;
|
|
set({
|
|
mudPointBalance: balance,
|
|
legacyWalletBalance: balance.totalPoints,
|
|
mudPointBalanceStatus: 'ready',
|
|
mudPointBalanceError: '',
|
|
});
|
|
return true;
|
|
},
|
|
applyLegacyWalletBalanceSnapshot: (snapshot, balance) => {
|
|
if (
|
|
!Number.isFinite(balance) ||
|
|
get().ownerUserId !== snapshot.ownerUserId ||
|
|
ownerVersion !== snapshot.ownerVersion ||
|
|
snapshot.invalidationVersion < refreshVersion ||
|
|
snapshot.invalidationVersion < settledRefreshVersion ||
|
|
snapshot.operationSequence < latestAppliedOperationSequence
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
invalidateActiveRefresh();
|
|
latestAppliedOperationSequence = Math.max(
|
|
latestAppliedOperationSequence,
|
|
snapshot.operationSequence,
|
|
);
|
|
settledRefreshVersion = snapshot.invalidationVersion;
|
|
set({
|
|
mudPointBalance: null,
|
|
legacyWalletBalance: balance,
|
|
mudPointBalanceStatus: 'ready',
|
|
mudPointBalanceError: '',
|
|
});
|
|
return true;
|
|
},
|
|
onWalletBalanceMayHaveChanged: () => {
|
|
refreshVersion += 1;
|
|
if (activeRefresh) {
|
|
return activeRefresh;
|
|
}
|
|
|
|
const abortController = new AbortController();
|
|
const refresh = (async () => {
|
|
while (settledRefreshVersion < refreshVersion) {
|
|
const ownerUserId = get().ownerUserId;
|
|
const requestedVersion = refreshVersion;
|
|
const generation = requestGeneration;
|
|
if (!ownerUserId) {
|
|
settledRefreshVersion = requestedVersion;
|
|
return;
|
|
}
|
|
|
|
set({ mudPointBalanceStatus: 'loading', mudPointBalanceError: '' });
|
|
try {
|
|
const center = await api.getRechargeCenter(abortController.signal);
|
|
if (generation !== requestGeneration) {
|
|
return;
|
|
}
|
|
if (requestedVersion !== refreshVersion) {
|
|
settledRefreshVersion = requestedVersion;
|
|
continue;
|
|
}
|
|
if (!center.mudPointBalance) {
|
|
if (Number.isFinite(center.walletBalance)) {
|
|
set({
|
|
mudPointBalance: null,
|
|
legacyWalletBalance: center.walletBalance,
|
|
});
|
|
}
|
|
throw new Error('充值中心响应缺少泥点余额');
|
|
}
|
|
|
|
settledRefreshVersion = requestedVersion;
|
|
set({
|
|
mudPointBalance: center.mudPointBalance,
|
|
legacyWalletBalance: center.mudPointBalance.totalPoints,
|
|
mudPointBalanceStatus: 'ready',
|
|
mudPointBalanceError: '',
|
|
});
|
|
} catch {
|
|
if (generation !== requestGeneration) {
|
|
return;
|
|
}
|
|
if (requestedVersion !== refreshVersion) {
|
|
settledRefreshVersion = requestedVersion;
|
|
continue;
|
|
}
|
|
|
|
settledRefreshVersion = requestedVersion;
|
|
set({
|
|
mudPointBalanceStatus: 'error',
|
|
mudPointBalanceError: '泥点明细读取失败',
|
|
});
|
|
}
|
|
}
|
|
})();
|
|
activeRefresh = refresh;
|
|
activeAbortController = abortController;
|
|
const clearActiveRefresh = () => {
|
|
if (activeRefresh === refresh) {
|
|
activeRefresh = null;
|
|
}
|
|
if (activeAbortController === abortController) {
|
|
activeAbortController = null;
|
|
}
|
|
};
|
|
void refresh.then(clearActiveRefresh, clearActiveRefresh);
|
|
return refresh;
|
|
},
|
|
resetWalletBalance: () => {
|
|
ownerVersion += 1;
|
|
latestAppliedOperationSequence = 0;
|
|
invalidateActiveRefresh();
|
|
settledRefreshVersion = refreshVersion;
|
|
set({ ownerUserId: null, ...EMPTY_WALLET_STATE });
|
|
},
|
|
}));
|
|
}
|