You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

181 lines
6.1 KiB

(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);