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 } = {}; constructor(private storageKey: string, private uid: string, private getState: () => any) { } request(action: string, params: any = {}, level = 0): Promise { 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 { 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 { 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 { 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 }); } }); } }