(function (global) { 'use strict'; /** * 壳侧桥:与游戏 iframe 内 Agent 通信(postMessage;同域可走 contentWindow 快路径探测)。 * @constructor */ function WorkbenchBridge() { this.iframe = null; this.ready = false; this.hibernating = false; this._reqId = 1; this._pending = {}; this._listeners = {}; this._targetOrigin = '*'; this._boundMessage = this._onMessage.bind(this); this._crossOrigin = false; this._fallbackTried = false; } /** * @param {HTMLIFrameElement} iframe * @param {{crossOrigin?:boolean}} opts */ WorkbenchBridge.prototype.attach = function (iframe, opts) { this.iframe = iframe; this._crossOrigin = !!(opts && opts.crossOrigin); window.addEventListener('message', this._boundMessage); var self = this; iframe.addEventListener('load', function () { self.call('ping').then(function () { self.ready = true; }).catch(function () { /* wait agent.ready event */ }); }); }; WorkbenchBridge.prototype.detach = function () { window.removeEventListener('message', this._boundMessage); this.iframe = null; this.ready = false; }; /** * @param {string} name * @param {function(*):void} cb */ WorkbenchBridge.prototype.on = function (name, cb) { if (!this._listeners[name]) this._listeners[name] = []; this._listeners[name].push(cb); }; WorkbenchBridge.prototype.off = function (name, cb) { var list = this._listeners[name]; if (!list) return; var i = list.indexOf(cb); if (i >= 0) list.splice(i, 1); }; WorkbenchBridge.prototype._emit = function (name, data) { var list = this._listeners[name] || []; for (var i = 0; i < list.length; i++) { try { list[i](data); } catch (e) { console.warn('[Bridge] listener', name, e); } } }; WorkbenchBridge.prototype._onMessage = function (ev) { var msg = ev.data; if (!msg || msg.source !== 'pw-agent') return; if (msg.type === 'rpc-result' && msg.id != null && this._pending[msg.id]) { var p = this._pending[msg.id]; delete this._pending[msg.id]; if (msg.ok) p.resolve(msg.result); else p.reject(new Error(msg.error || 'rpc failed')); return; } if (msg.type === 'event') { if (msg.name === 'agent.ready') { this.ready = !!(msg.data && msg.data.ok); } if (msg.name === 'agent.hibernate') { this.hibernating = !!(msg.data && msg.data.on); } this._emit(msg.name, msg.data); } }; /** * @param {string} method * @param {object=} params * @returns {Promise<*>} */ WorkbenchBridge.prototype.call = function (method, params) { var self = this; return new Promise(function (resolve, reject) { if (!self.iframe || !self.iframe.contentWindow) { reject(new Error('iframe missing')); return; } // 同域快路径 if (!self._crossOrigin) { try { var agent = self.iframe.contentWindow.__PW_Agent; if (agent && typeof agent._dispatch === 'function') { var direct = agent._dispatch(method, params || {}); resolve(direct); return; } } catch (e) { /* fall through postMessage */ } } var id = self._reqId++; self._pending[id] = { resolve: resolve, reject: reject }; var target = self._crossOrigin ? self._targetOrigin : '*'; try { if (self.iframe.src) { var u = new URL(self.iframe.src, location.href); target = u.origin; self._targetOrigin = target; } } catch (e2) { /* */ } self.iframe.contentWindow.postMessage({ source: 'pw-bridge', id: id, method: method, params: params || {}, }, target); setTimeout(function () { if (self._pending[id]) { delete self._pending[id]; reject(new Error('rpc timeout: ' + method)); } }, 8000); }); }; WorkbenchBridge.prototype.subscribe = function (channel) { return this.call('subscribe', { channel: channel }); }; WorkbenchBridge.prototype.unsubscribe = function (channel) { return this.call('unsubscribe', { channel: channel }); }; WorkbenchBridge.prototype.hibernate = function () { return this.call('hibernate'); }; WorkbenchBridge.prototype.wake = function () { return this.call('wake'); }; /** * 构建游戏页 URL;优先 localhost↔127.0.0.1 跨域,失败标记后同域降级。 * @param {boolean=} forceSameOrigin * @returns {{url:string, crossOrigin:boolean}} */ WorkbenchBridge.buildGameUrl = function (forceSameOrigin) { var u = new URL(location.href); u.searchParams.set('pw', 'game'); var crossFail = false; try { crossFail = sessionStorage.getItem('pw-cross-fail') === '1'; } catch (e) { /* */ } var crossOrigin = false; if (!forceSameOrigin && !crossFail) { var host = location.hostname; if (host === 'localhost') { u.hostname = '127.0.0.1'; crossOrigin = true; } else if (host === '127.0.0.1') { u.hostname = 'localhost'; crossOrigin = true; } } return { url: u.toString(), crossOrigin: crossOrigin }; }; WorkbenchBridge.markCrossOriginFailed = function () { try { sessionStorage.setItem('pw-cross-fail', '1'); } catch (e) { /* */ } }; global.PWBridge = WorkbenchBridge; })(window);