Files
youle_app_ios_v2/ylgamehall/Resources/JS/WebViewJavascriptBridge.js
T
2026-08-07 12:18:44 +08:00

206 lines
9.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* WebViewJavascriptBridge.js
*
* H5 端桥协议代码,与 Native BridgeBus@MainActor BridgeProtocol 实现)
* 一一对应。详见 docs/H5-Native-Implementation-Design.md §3.2 / §3.3。
*
* ⚠️ 本文件的**语义**必须与原工程 msext 的 `WebViewJavascriptBridge_JS.m`
* marcuswestin 原版,daoqi/msext/Class/WebViewJavascriptBridge/)保持一致。
* 传输层可以现代化(msext 用 iframe + _fetchQueue 拉取,我们用
* WKScriptMessageHandler 直推),但**派发时机与消息字段必须逐项对齐**,
* 否则 H5 侧同一份代码在两个壳里的执行顺序不同 → 行为不一致。
*
* 已对齐的 msext 语义(每一条都曾经不一致,是排查「子游戏定位拿不到」时发现的):
*
* 1. **异步派发**msext `dispatchMessagesWithTimeoutSafety = true`,且
* `disableJavscriptAlertBoxSafetyTimeout` 在整个 msext 工程里**从未被调用**,
* 所以 Native → H5 的每一条消息都走 `setTimeout(_doDispatch)`,在**新的
* macrotask** 上执行。早前本文件是同步派发(在 evaluateJavaScript 内直接
* 调 handler),H5 handler 相对自身 pending 脚本/微任务的顺序与 msext 不同。
* 2. **WVJBCallbacks 用 `setTimeout(..., 0)` flush**msext `_callWVJBCallbacks`),
* 不是同步 flush —— 决定 H5 的 `registerHandler` 何时生效。
* 3. **H5 → Native 的 response 消息带 `handlerName`**msext `_doSend({handlerName,
* responseId, responseData})`)。
* 4. `_disableJavascriptAlertBoxSafetyTimeout` 作为 H5 侧 handler 注册(msext 有)。
* 5. 缺省安装 `window.onerror`msext 在 H5 未装时兜底装一个)。
* 6. 「收到 Native 消息但没有对应 handler」用 `console.log`msext 原文),不是 warn。
*
* 协议(Native ↔ H5 双向 JSON 消息):
* - H5 → Nativewindow.webkit.messageHandlers.WVJBHandler.postMessage({...})
* - Native → H5window.WebViewJavascriptBridge._handleMessageFromObjC('<base64>')
* - 单条消息:
* { handlerName, data?, callbackId? } // 主动调用对端 handler
* { responseId, responseData? } // 响应对端早前 callHandler 的 callbackId
*/
;(function () {
if (window.WebViewJavascriptBridge) { return; }
// msext 同款:H5 没装 onerror 时兜底装一个
if (!window.onerror) {
window.onerror = function (msg, url, line) {
console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
};
}
// ── 内部状态 ─────────────────────────────────────────────
var messageHandlers = {}; // H5 注册的 handlername → fn(data, responseCallback)
var responseCallbacks = {}; // 等待 Native 响应的 JS 回调:callbackId → fn(responseData)
var nextCallbackId = 1;
// msext `dispatchMessagesWithTimeoutSafety`:默认 true,仅在 Native 下发
// `_disableJavascriptAlertBoxSafetyTimeout` 时置 falsemsext 从不下发)。
var dispatchMessagesWithTimeoutSafety = true;
// ── 对外 API ─────────────────────────────────────────────
var bridge = window.WebViewJavascriptBridge = {
/**
* H5 注册 handler,等待 Native 主动 callHandler。重名覆盖。
*/
registerHandler: function (handlerName, handler) {
messageHandlers[handlerName] = handler;
},
/**
* H5 主动调 Native handler。
* msext `callHandler` 支持 2 参形式(第二个参数是函数时视为 responseCallback)。
*/
callHandler: function (handlerName, data, responseCallback) {
if (arguments.length === 2 && typeof data === 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName: handlerName, data: data }, responseCallback);
},
disableJavscriptAlertBoxSafetyTimeout: function () {
dispatchMessagesWithTimeoutSafety = false;
},
/**
* Native → H5 入口。BridgeBus.sendToJS 会注入:
* window.WebViewJavascriptBridge._handleMessageFromObjC('<base64of JSON>')
* base64 包装是为了避免 JSON 内容里的单引号 / 反斜杠扰乱 evaluateJavaScript
* 的字符串字面量(msext 走的是逐个 escape,等价)。
*
* ⚠️ 无返回值:msext 的派发是 setTimeout 异步的,拿不到「H5 有没有跑到
* handler」的同步结果。原生侧不要依赖它的返回值做诊断。
*/
_handleMessageFromObjC: function (base64String) {
_dispatchMessageFromObjC(base64String);
}
};
// ── 内部 ─────────────────────────────────────────────────
function _postMessageToNative(message) {
try {
window.webkit.messageHandlers.WVJBHandler.postMessage(message);
} catch (e) {
console.log('[WVJB] post to native failed (WVJBHandler 通道未注册?):', e);
}
}
function _doSend(message, responseCallback) {
if (typeof responseCallback === 'function') {
var callbackId = 'cb_' + (nextCallbackId++) + '_' + new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
message['callbackId'] = callbackId;
}
_postMessageToNative(message);
}
/// msext `_dispatchMessageFromObjC` 逐句等价:默认走 setTimeout(新 macrotask)。
function _dispatchMessageFromObjC(base64String) {
if (dispatchMessagesWithTimeoutSafety) {
setTimeout(_doDispatchMessageFromObjC);
} else {
_doDispatchMessageFromObjC();
}
function _doDispatchMessageFromObjC() {
var message;
try {
message = JSON.parse(_decodeBase64UTF8(base64String));
} catch (e) {
console.log('[WVJB] handle message from native failed:', e);
return;
}
var responseCallback;
if (message.responseId) {
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) { return; }
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
return;
}
if (message.callbackId) {
var callbackResponseId = message.callbackId;
responseCallback = function (responseData) {
// msext 同款:response 消息里**带 handlerName**
_doSend({
handlerName: message.handlerName,
responseId: callbackResponseId,
responseData: responseData
});
};
}
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
// 诊断上报(不改 msext 语义,只是额外往自家通道发一条原生侧忽略的消息)
_postMessageToNative({ diagDispatched: message.handlerName, hadHandler: false });
} else {
var threw = null;
try { handler(message.data, responseCallback); }
catch (e) { threw = String(e); }
_postMessageToNative({
diagDispatched: message.handlerName,
hadHandler: true,
threw: threw
});
// msext 不吞异常(handler 抛错会冒到 setTimeout 的全局 onerror);
// 这里把它原样重抛,保持行为一致,只是先记录了一笔。
if (threw !== null) { throw new Error(threw); }
}
}
}
/**
* 标准 base64 → UTF-8 字符串。
* 直接 atob() 只能处理 latin1;这里用 decodeURIComponent + escape 处理多字节字符。
*/
function _decodeBase64UTF8(b64) {
return decodeURIComponent(Array.prototype.map.call(atob(b64), function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
// msext 同款:把 Native 可下发的 safety-timeout 开关注册成 H5 侧 handler
bridge.registerHandler("_disableJavascriptAlertBoxSafetyTimeout",
bridge.disableJavscriptAlertBoxSafetyTimeout);
// ── 告知原生「桥已就绪」──────────────────────────────────
// 等价 msext 的 `__bridge_loaded__` iframe 回调 → `injectJavascriptFile` →
// flush `startupMessageQueue`。原生在收到本消息前发出的 callHandler 必须排队,
// 不能丢(msext 有 startupMessageQueue,早前我们是直接丢弃)。
_postMessageToNative({ bridgeReady: true });
// ── marcuswestin 旧式握手兼容 ─────────────────────────────
// msext `_callWVJBCallbacks` 用 setTimeout(..., 0)**不是同步 flush**
// 这决定 H5 的 registerHandler 相对页面其它顶层脚本的生效时机。
setTimeout(function () {
var callbacks = window.WVJBCallbacks;
if (!callbacks) { return; }
delete window.WVJBCallbacks;
for (var i = 0; i < callbacks.length; i++) {
try { callbacks[i](bridge); }
catch (e) { console.log('[WVJB] pending WVJBCallback threw:', e); }
}
}, 0);
})();