114 lines
6.2 KiB
TypeScript
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 }); }
|
|
});
|
|
}
|
|
}
|