115 lines
4.7 KiB
JavaScript
115 lines
4.7 KiB
JavaScript
/* ============================================================================
|
||
* pbl_overlay_loader.js —— 覆盖层脚本/CSS 按需注入器(M9)
|
||
*
|
||
* 为什么需要它:bricks .ui 页面禁止硬写 <script>/<link> 标签(会绕过 RBAC 或
|
||
* 触发 403)。本 loader 由页面壳(wwwroot/pbl_overlay/*.ui)的 bricks
|
||
* `script` actiontype 在用户点击「启用 PBL 覆盖层」时调用,动态注入同模块
|
||
* wwwroot/overlay 下的 JS/CSS,注入完成后调用 PblOverlayPlay.boot() 完成装配。
|
||
*
|
||
* RBAC 事实(QC #1 已补齐,与 scripts/load_path.py PATHS 表一一对应,禁止只改一边):
|
||
* - /pbl_scense_ext/overlay/pbl_overlay_loader.js → any (预鉴权页壳引导)
|
||
* - /pbl_scense_ext/overlay/pbl_overlay.css → any (页壳首屏样式)
|
||
* - /pbl_scense_ext/overlay/pbl_overlay_core.js → logined (业务内核)
|
||
* - /pbl_scense_ext/overlay/pbl_overlay_transport.js→ logined (广播/意图通道)
|
||
* - /pbl_scense_ext/overlay/pbl_overlay_play.js → logined (覆盖层装配)
|
||
* - /pbl_scense_ext/pbl_overlay/<page>.ui × 12 → logined (页面壳)
|
||
*
|
||
* 铁律:只注入本模块静态资源,不改写宿主(scense/scense_game)渲染层任何脚本。
|
||
* ==========================================================================*/
|
||
(function (global) {
|
||
'use strict';
|
||
|
||
var MODULE = 'pbl_scense_ext';
|
||
|
||
function apiBase() {
|
||
// 宿主显式注入优先;否则从当前 pathname 去掉模块路由尾部推应用根
|
||
var inj = String(global.__PBL_API_BASE__ || '');
|
||
if (inj) { return inj.replace(/\/$/, ''); }
|
||
return String(global.location && global.location.pathname || '')
|
||
.replace(/\/(pbl_overlay|overlay|game|scense_game|scense)\/.*$/, '')
|
||
.replace(/\/$/, '');
|
||
}
|
||
|
||
var DIR = apiBase() + '/' + MODULE + '/overlay/';
|
||
|
||
// 依赖顺序:core → transport → play(play 内部硬依赖前两者)
|
||
var FILES = ['pbl_overlay_core.js', 'pbl_overlay_transport.js', 'pbl_overlay_play.js'];
|
||
var CSS = 'pbl_overlay.css';
|
||
|
||
var injected = {};
|
||
var loading = null;
|
||
|
||
/* 注入失败时的最小可见降级(不依赖 core,避免「白屏无提示」)。
|
||
* 页面壳的 script 里也引用本函数:window.__pblOverlayFail(code, message) */
|
||
function degrade(code, message) {
|
||
try {
|
||
var host = document.querySelector('[data-pbl-overlay-root]') || document.body;
|
||
var box = document.getElementById('pbl-loader-degrade');
|
||
if (!box) {
|
||
box = document.createElement('div');
|
||
box.id = 'pbl-loader-degrade';
|
||
box.setAttribute('class', 'pbl-banner pbl-banner--error');
|
||
host.appendChild(box);
|
||
}
|
||
box.textContent = '[' + (code || 'PBL_E_NETWORK') + '] ' + (message || '覆盖层加载失败');
|
||
box.setAttribute('data-pbl-degrade', '1');
|
||
} catch (e) { /* DOM 不可用时静默,不抛出打断宿主 */ }
|
||
return { code: code || 'PBL_E_NETWORK', message: message || 'overlay load failed' };
|
||
}
|
||
|
||
function loadOne(src) {
|
||
return new Promise(function (resolve, reject) {
|
||
if (injected[src]) { return resolve(true); }
|
||
var s = document.createElement('script');
|
||
s.src = src;
|
||
s.async = false;
|
||
s.onload = function () { injected[src] = true; resolve(true); };
|
||
s.onerror = function () { reject({ code: 'PBL_E_NETWORK', message: '资源加载失败(403/404?) ' + src }); };
|
||
document.head.appendChild(s);
|
||
});
|
||
}
|
||
|
||
function ensureCss() {
|
||
var id = 'pbl-overlay-css';
|
||
if (document.getElementById(id)) { return; }
|
||
var l = document.createElement('link');
|
||
l.id = id;
|
||
l.rel = 'stylesheet';
|
||
l.href = DIR + CSS;
|
||
document.head.appendChild(l);
|
||
}
|
||
|
||
function load() {
|
||
ensureCss();
|
||
if (loading) { return loading; }
|
||
loading = FILES.reduce(function (p, f) {
|
||
return p.then(function () { return loadOne(DIR + f); });
|
||
}, Promise.resolve());
|
||
return loading;
|
||
}
|
||
|
||
// 幂等启动:已存在实例则复用(切换页面上下文时可 destroy 后重建)
|
||
function start(opts) {
|
||
return load().then(function () {
|
||
if (!global.PblOverlayPlay) { throw { code: 'PBL_E_NOT_FOUND', message: 'PblOverlayPlay 未注册' }; }
|
||
if (global.__PBL_OVERLAY__ && global.__PBL_OVERLAY__.mounted) { return global.__PBL_OVERLAY__; }
|
||
return global.PblOverlayPlay.boot(opts || {}) || global.__PBL_OVERLAY__;
|
||
}).catch(function (err) {
|
||
degrade((err && err.code) || 'PBL_E_LOAD_FAIL', (err && err.message) || String(err));
|
||
throw err;
|
||
});
|
||
}
|
||
|
||
global.PblOverlayLoader = {
|
||
MODULE: MODULE,
|
||
DIR: DIR,
|
||
FILES: FILES,
|
||
CSS: CSS,
|
||
apiBase: apiBase,
|
||
load: load,
|
||
start: start,
|
||
degrade: degrade
|
||
};
|
||
global.__pblOverlayFail = degrade;
|
||
})(window);
|