declare const wx: any; /** A terminal gate for this process. Only a real package restart clears it. */ export default class ClientUpdate { private static initialized = false; private static manager: any = null; private static state: "checking" | "downloading" | "ready" | "unavailable" = "checking"; private static rejection: any = null; private static dialogOpen = false; private static waitTimer: any = null; static get blocked(): boolean { return this.rejection !== null; } static get blockedResponse(): any { return this.rejection; } static initialize(): void { if (this.initialized) return; this.initialized = true; if (typeof wx !== "undefined" && typeof wx.getUpdateManager === "function") { try { this.manager = wx.getUpdateManager(); this.manager.onCheckForUpdate(result => { if (this.state === "ready") return; this.state = result.hasUpdate ? "downloading" : "unavailable"; if (this.blocked) this.showPrompt(); }); this.manager.onUpdateReady(() => { this.state = "ready"; if (this.blocked) this.showPrompt(); }); this.manager.onUpdateFailed(() => { this.state = "unavailable"; if (this.blocked) this.showPrompt(); }); } catch (_) { this.state = "unavailable"; } } else { this.state = "unavailable"; } } /** Also converts late successful responses to the same error after another request blocks us. */ static handleResponse(response: any): any { if (!this.blocked && response?.errorCode === "CLIENT_UPDATE_REQUIRED") { this.initialize(); this.rejection = response; this.freezeGame(); this.showPrompt(); } return this.rejection || response; } /** GameManager calls this before its normal foreground resume. */ static onShow(): boolean { if (!this.blocked) return false; this.freezeGame(); this.showPrompt(); return true; } private static freezeGame(): void { cc.fx.GameConfig.GM_INFO.gameState = false; const scene = cc.director.getScene(); if (scene) scene.pauseSystemEvents(true); cc.audioEngine.pauseAll(); cc.game.pause(); } private static clearWait(): void { if (this.waitTimer !== null) clearTimeout(this.waitTimer); this.waitTimer = null; } private static showPrompt(): void { if (typeof wx === "undefined" || typeof wx.showModal !== "function") { return; } if (this.state === "checking" || this.state === "downloading") { if (this.dialogOpen) return; wx.showLoading({ title: "正在准备更新", mask: true }); if (this.waitTimer === null) { this.waitTimer = setTimeout(() => { this.waitTimer = null; if (this.state !== "ready") { this.state = "unavailable"; this.showPrompt(); } }, 30000); } return; } this.clearWait(); if (this.dialogOpen) return; wx.hideLoading(); const promptState = this.state; const ready = promptState === "ready"; this.dialogOpen = true; wx.showModal({ title: "游戏需要更新", content: ready ? "新版本已准备好,请重启更新。" : "当前版本已无法继续游戏,暂未取得可用的新版本。请退出后重新进入;仍有问题请稍后再试。", showCancel: false, confirmText: ready ? "重启更新" : "退出游戏", success: result => { if (!result.confirm || this.state !== promptState) return; if (ready) { try { this.manager.applyUpdate(); } catch (_) { this.state = "unavailable"; } } else if (typeof wx.exitMiniProgram === "function") { wx.exitMiniProgram({ fail: () => { wx.showToast({ title: "请手动退出后重新进入", icon: "none", duration: 3000 }); } }); } }, complete: () => { this.dialogOpen = false; if (this.state !== promptState) { setTimeout(() => this.showPrompt(), 0); } else { // Keep a visible mask even if the platform refuses to exit/restart or dismisses the modal. wx.showLoading({ title: ready ? "请重启游戏" : "请重新进入游戏", mask: true }); } }, }); } }