// Keep the native Page lifecycle, but hydrate the correct account before any // story page reads/writes storage. This also covers cold starts from shares. const bridge = require('./platformBridge') const LIFECYCLE_METHODS = new Set(['onLoad', 'onShow', 'onReady', 'onHide', 'onUnload']) const CLEANUP_METHOD = /^(?:destroy|clear|unbind|pause|invalidate|beginPauseLock)/ module.exports = function registerTangPage(options) { function isCurrent(page) { return !page.__tangDead && !page.__tangOriginalUnloaded && page.__tangVisible && page.__tangScope === bridge.getScope() } function returnHome(page) { if (page.__tangRedirected || page.__tangDead) return page.__tangRedirected = true wx.reLaunch({ url: '/tang-detective/pages/home/home' }) } function cleanUp(page, name) { if (!page.__tangLoaded || typeof options[name] !== 'function') return if (name !== 'onUnload' && page.__tangOriginalUnloaded) return if (name === 'onUnload') { if (page.__tangOriginalUnloaded) return page.__tangOriginalUnloaded = true } page.__tangCleaning = true try { options[name].call(page) } finally { // This exception is synchronous only; pending timers and audio callbacks // regain the normal visible/account/dead guards as soon as cleanup ends. page.__tangCleaning = false } } function deliverReady(page) { if (!isCurrent(page) || !page.__tangLoaded || !page.__tangHasShown || !page.__tangReadyRequested || page.__tangReadyDelivered) return page.__tangReadyDelivered = true if (typeof options.onReady === 'function') options.onReady.call(page) } const guarded = { ...options } Object.keys(options).forEach(key => { // Event handlers such as onAudioTimeUpdate are ordinary guarded methods, // even though their names begin with "on". if (typeof options[key] !== 'function' || LIFECYCLE_METHODS.has(key)) return guarded[key] = function (...args) { if (this.__tangCleaning) { // Release resources across an account change, but do not run helpers // such as saveCurrentAudioTime against the newly selected account. if (this.__tangScope === bridge.getScope() || CLEANUP_METHOD.test(key)) { return options[key].apply(this, args) } return } if (this.__tangDead || this.__tangOriginalUnloaded || !this.__tangLoaded || !this.__tangVisible) return if (this.__tangScope && this.__tangScope !== bridge.getScope()) { returnHome(this) return } return options[key].apply(this, args) } }) Page({ ...guarded, data: { ...(options.data || {}), tangBootPending: true }, tangIgnoreBootTap() {}, onLoad(query) { this.__tangQuery = query this.__tangScope = bridge.getScope() this.__tangDead = false this.__tangVisible = false this.__tangLoaded = false this.__tangCleaning = false this.__tangOriginalUnloaded = false this.__tangRedirected = false this.__tangShowGeneration = 0 this.__tangReadyRequested = false this.__tangReadyDelivered = false this.__tangHasShown = false const nativeSetData = this.setData this.setData = function (...args) { if (this.__tangScope !== bridge.getScope()) return if (!this.__tangCleaning && !isCurrent(this)) return return nativeSetData.apply(this, args) } this.__tangBoot = bridge.open() }, onShow() { if (this.__tangDead) return this.__tangVisible = true const generation = ++this.__tangShowGeneration const scope = bridge.getScope() if (this.__tangOriginalUnloaded || scope !== this.__tangScope) { returnHome(this) return } Promise.resolve(this.__tangBoot).then(() => { if (this.__tangDead || !this.__tangVisible || generation !== this.__tangShowGeneration) return if (bridge.getScope() !== scope) { returnHome(this) return } if (!this.__tangLoaded) { this.__tangLoaded = true if (options.onLoad) options.onLoad.call(this, this.__tangQuery) if (!isCurrent(this)) return this.setData({ tangBootPending: false }) } if (options.onShow) options.onShow.call(this) this.__tangHasShown = true deliverReady(this) }).catch(() => { if (isCurrent(this) && generation === this.__tangShowGeneration) { wx.showToast({ title: '故事暂时未能打开,请返回重试', icon: 'none' }) } }) }, onReady() { this.__tangReadyRequested = true deliverReady(this) }, onHide() { this.__tangVisible = false ++this.__tangShowGeneration try { cleanUp(this, 'onHide') } finally { // A hidden page from an obsolete account must not retain contexts with // callbacks that directly reference storage instead of guarded methods. try { if (this.__tangScope !== bridge.getScope()) cleanUp(this, 'onUnload') } finally { bridge.flush() } } }, onUnload() { this.__tangDead = true this.__tangVisible = false ++this.__tangShowGeneration try { cleanUp(this, 'onUnload') } finally { bridge.flush() } }, }) }