MatchMaster/assets/mini_program_benefits/script/MiniProgramBenefitsReward.ts
2026-09-20 15:39:50 +08:00

114 lines
6.2 KiB
TypeScript

import Utils from "../../Script/module/Pay/Utils";
const TYPES = { favorite_entry: "favorite", desktop_entry: "desktop" };
/** Server authorizes each task once; the client grants the fixed reward and syncs totals. */
export default class MiniProgramBenefitsReward {
private static queues: { [key: string]: Promise<any> } = {};
constructor(private storageKey: string, private uid: string, private getState: () => any) { }
request(action: string, params: any = {}, level = 0): Promise<any> {
const previous = MiniProgramBenefitsReward.queues[this.storageKey] || Promise.resolve();
const result = previous.catch(() => {}).then(() => this.run(action, params, level));
const settled = result.then(() => {}, () => {});
MiniProgramBenefitsReward.queues[this.storageKey] = settled;
settled.then(() => {
if (MiniProgramBenefitsReward.queues[this.storageKey] === settled) delete MiniProgramBenefitsReward.queues[this.storageKey];
});
return result;
}
private assertAccount(): void {
if (!this.uid || String(cc.fx.GameConfig.GM_INFO.uid) !== this.uid) throw new Error("账号已切换,请重新打开福利");
}
private async run(action: string, params: any, level: number): Promise<any> {
this.assertAccount();
if (!["read", "claim", "ack"].includes(action)) throw new Error("真实奖励不支持模拟或重置");
if (action !== "read" && !Object.prototype.hasOwnProperty.call(TYPES, params.task)) throw new Error("未知福利任务");
const state = this.getState();
// Finish an authorized delivery before starting another one.
for (const task of Object.keys(state)) {
if (state[task].deliveryPending) await this.deliver(task, state[task]);
}
if (action === "claim") {
const item = state[params.task] || {};
if (!item.claimedAt) {
if (level < 1) throw new Error("通过第 1 关后才能领取");
if (!item.eligibleAt) throw new Error("请从对应入口进入后领取");
// Missing login contract must not silently turn into an unclaimed account.
const info: any = cc.fx.GameConfig.GM_INFO;
const flags = info.miniProgramWelfare;
if (!flags || typeof flags.favorite !== "boolean" || typeof flags.desktop !== "boolean") {
throw new Error("福利状态未同步,请重新登录后重试");
}
const type = TYPES[params.task];
this.totals();
await this.post("miniProgramWelfare", { uid: this.uid, type });
this.assertAccount();
// Authorization is consumed even if the following inventory save fails.
info.miniProgramWelfare = { ...flags, [type]: true };
state[params.task] = { ...item, claimedAt: Date.now(), receiptId: "welfare:" + this.uid + ":" + type, acknowledged: false, deliveryPending: true };
await this.deliver(params.task, state[params.task]);
}
} else if (action === "ack") {
const item = state[params.task];
if (!item || !item.claimedAt || item.receiptId !== params.receiptId) throw new Error("领奖回执不匹配");
item.acknowledged = true;
}
return { state, rewards: [2001, 2002, 2003].map(id => ({ id, amount: 1 })) };
}
private totals(): any {
const values: any = {};
["freezeAmount", "hammerAmount", "magicAmount"].forEach(key => {
const value = cc.fx.GameConfig.GM_INFO[key];
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0 || value >= Number.MAX_SAFE_INTEGER) throw new Error("道具数据尚未准备好,请稍后重试");
values[key] = value;
});
return values;
}
private async deliver(task: string, item: any): Promise<void> {
this.assertAccount();
const prop = cc.fx.StorageMessage.getStorage("prop") || {};
const credits = { ...(prop.welfareCredits || {}) };
if (!credits[item.receiptId]) {
const values = this.totals();
Object.keys(values).forEach(key => values[key] += 1);
credits[item.receiptId] = true;
cc.fx.StorageMessage.setStorage("prop", { ...prop, ...values, welfareCredits: credits, timestamp: Date.now() });
const saved = cc.fx.StorageMessage.getStorage("prop");
if (!saved || !saved.welfareCredits || !saved.welfareCredits[item.receiptId]) throw new Error("奖励保存失败,请重新打开福利重试");
Object.assign(cc.fx.GameConfig.GM_INFO, values);
}
// A retry only uploads current totals; it never adds the fixed reward twice.
const totals = this.totals();
await this.post("userProp", { uid: this.uid, action: "save", propType: 0,
propData: JSON.stringify({ freeze: totals.freezeAmount, hammer: totals.hammerAmount, magic_wand: totals.magicAmount }) });
this.assertAccount();
item.deliveryPending = false;
try {
cc.fx.GameTool.shushu_Track(task === "favorite_entry" ? "add_to_my" : "add_to_desktop", {});
} catch (error) {
cc.warn("小程序福利领取埋点上报失败", error);
}
}
private post(endpoint: string, params: any): Promise<any> {
return new Promise((resolve, reject) => {
let completed = false;
const finish = (response: any) => {
if (completed) return;
completed = true; clearTimeout(timeout);
try { this.assertAccount(); } catch (error) { reject(error); return; }
if (response && response.code === 1) resolve(response);
else reject(new Error(response && response.msg || "请求失败;如已扣发奖励,请重新登录确认领取状态"));
};
const timeout = setTimeout(() => finish({ msg: "领取结果待确认,请重新登录同步状态" }), 5500);
try { Utils.POST(endpoint, params, finish); }
catch (error) { finish({ msg: error && error.message }); }
});
}
}