Phase 1.8:WebViewJavascriptBridge.js 协议源码入 Bundle
H5 端桥协议实现,与 BridgeBus 的消息格式 1:1 对齐:
H5 → Native 走 window.webkit.messageHandlers.WVJBHandler.postMessage(...),
Native → H5 走 window.WebViewJavascriptBridge._handleMessageFromObjC(base64)。
- 新增 ylgamehall/Resources/JS/WebViewJavascriptBridge.js(~130 行)
- bridge.registerHandler(name, fn):H5 注册等待 Native callHandler
- bridge.callHandler(name, data, responseCallback):H5 主动调 Native
- bridge._handleMessageFromObjC(b64):Native 入口,base64 → JSON parse
→ 派发到 H5 handler 或匹配响应 responseCallbacks
- base64 解码用 decodeURIComponent + atob 处理 UTF-8 多字节字符
- 兼容 marcuswestin 旧式 window.WVJBCallbacks 队列握手:注入后
立即 flush 队列(H5 业务代码无需修改)
- RootViewController 加 Phase 1.8 烟雾测试:Bundle.main.url 找到 JS 文件
并打印 bytes / head;synchronized group 会把 Resources/JS/ 子目录
扁平化到 bundle 根,注释里说明此约束
- BuildProject 通过,bundle/WebViewJavascriptBridge.js 实际存在
- 下一步 Phase 1.9 BridgedWebView 用 WKUserScript(.atDocumentStart) 注入
本文件到所有 H5 页面,桥真正生效
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* WebViewJavascriptBridge.js
|
||||
*
|
||||
* H5 端桥协议代码,与 Native BridgeBus(@MainActor BridgeProtocol 实现)
|
||||
* 一一对应。详见 docs/H5-Native-Implementation-Design.md §3.2 / §3.3。
|
||||
*
|
||||
* 在 BridgedWebView 构造时通过 WKUserScript(.atDocumentStart)注入到所有
|
||||
* 加载的 H5 页面。H5 业务代码运行时,window.WebViewJavascriptBridge 已就绪。
|
||||
*
|
||||
* 协议(Native ↔ H5 双向 JSON 消息):
|
||||
* - H5 → Native:window.webkit.messageHandlers.WVJBHandler.postMessage({...})
|
||||
* - Native → H5:window.WebViewJavascriptBridge._handleMessageFromObjC('<base64>')
|
||||
* - 单条消息:
|
||||
* { handlerName, data?, callbackId? } // 主动调用对端 handler
|
||||
* { responseId, responseData? } // 响应对端早前 callHandler 的 callbackId
|
||||
*/
|
||||
|
||||
;(function () {
|
||||
if (window.WebViewJavascriptBridge) { return; }
|
||||
|
||||
// ── 内部状态 ─────────────────────────────────────────────
|
||||
var messageHandlers = {}; // H5 注册的 handler:name → fn(data, responseCallback)
|
||||
var responseCallbacks = {}; // 等待 Native 响应的 JS 回调:callbackId → fn(responseData)
|
||||
var nextCallbackId = 1;
|
||||
|
||||
// ── 对外 API ─────────────────────────────────────────────
|
||||
var bridge = window.WebViewJavascriptBridge = {
|
||||
|
||||
/**
|
||||
* H5 注册 handler,等待 Native 主动 callHandler。重名覆盖。
|
||||
* @param {string} handlerName
|
||||
* @param {function(data, responseCallback)} handler
|
||||
*/
|
||||
registerHandler: function (handlerName, handler) {
|
||||
messageHandlers[handlerName] = handler;
|
||||
},
|
||||
|
||||
/**
|
||||
* H5 主动调 Native handler。
|
||||
* @param {string} handlerName
|
||||
* @param {*} data 任意 JSON 可序列化值
|
||||
* @param {function(responseData)} [responseCallback]
|
||||
*/
|
||||
callHandler: function (handlerName, data, responseCallback) {
|
||||
var callbackId = null;
|
||||
if (typeof responseCallback === 'function') {
|
||||
callbackId = 'cb_' + (nextCallbackId++) + '_' + Date.now();
|
||||
responseCallbacks[callbackId] = responseCallback;
|
||||
}
|
||||
var message = { handlerName: handlerName };
|
||||
if (data !== undefined && data !== null) {
|
||||
message.data = data;
|
||||
}
|
||||
if (callbackId) {
|
||||
message.callbackId = callbackId;
|
||||
}
|
||||
_postMessageToNative(message);
|
||||
},
|
||||
|
||||
/**
|
||||
* Native → H5 入口。BridgeBus.sendToJS 会注入:
|
||||
* window.WebViewJavascriptBridge._handleMessageFromObjC('<base64of JSON>')
|
||||
* base64 包装是为了避免 JSON 内容里的单引号 / 反斜杠扰乱 evaluateJavaScript 的字符串字面量。
|
||||
*/
|
||||
_handleMessageFromObjC: function (base64String) {
|
||||
try {
|
||||
var json = _decodeBase64UTF8(base64String);
|
||||
var message = JSON.parse(json);
|
||||
_dispatchFromNative(message);
|
||||
} catch (e) {
|
||||
console.error('[WVJB] handle message from native failed:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ── 内部 ─────────────────────────────────────────────────
|
||||
|
||||
function _postMessageToNative(message) {
|
||||
try {
|
||||
window.webkit.messageHandlers.WVJBHandler.postMessage(message);
|
||||
} catch (e) {
|
||||
console.error('[WVJB] post to native failed (WVJBHandler 通道未注册?):', e);
|
||||
}
|
||||
}
|
||||
|
||||
function _dispatchFromNative(message) {
|
||||
// Native 响应 H5 早前 callHandler
|
||||
if (message.responseId) {
|
||||
var cb = responseCallbacks[message.responseId];
|
||||
if (cb) {
|
||||
delete responseCallbacks[message.responseId];
|
||||
try { cb(message.responseData); }
|
||||
catch (e) { console.error('[WVJB] response callback threw:', e); }
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Native 主动调用 H5 handler
|
||||
var handler = messageHandlers[message.handlerName];
|
||||
if (!handler) {
|
||||
console.warn('[WVJB] no H5 handler registered for:', message.handlerName);
|
||||
return;
|
||||
}
|
||||
|
||||
var responseCallback;
|
||||
if (message.callbackId) {
|
||||
var responseId = message.callbackId;
|
||||
responseCallback = function (responseData) {
|
||||
_postMessageToNative({
|
||||
responseId: responseId,
|
||||
responseData: responseData
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
try { handler(message.data, responseCallback); }
|
||||
catch (e) { console.error('[WVJB] H5 handler "' + message.handlerName + '" threw:', e); }
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准 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(''));
|
||||
}
|
||||
|
||||
// ── marcuswestin 旧式握手兼容 ─────────────────────────────
|
||||
// 部分 H5 代码这样初始化 bridge:
|
||||
// function setupWebViewJavascriptBridge(callback) {
|
||||
// if (window.WebViewJavascriptBridge) return callback(WebViewJavascriptBridge);
|
||||
// if (window.WVJBCallbacks) return window.WVJBCallbacks.push(callback);
|
||||
// window.WVJBCallbacks = [callback];
|
||||
// /* iframe trick - WKWebView 模式不需要 */
|
||||
// }
|
||||
// setupWebViewJavascriptBridge(function (bridge) { bridge.registerHandler(...) });
|
||||
//
|
||||
// 我们在 documentStart 就注入 bridge,正常路径下 H5 拿到的 window.WebViewJavascriptBridge
|
||||
// 已存在,会立即同步 callback(bridge)。但若 H5 代码本身用了 WVJBCallbacks 队列模式
|
||||
// (Array.push 在 bridge 之前),需要在此处 flush 一次。
|
||||
if (Array.isArray(window.WVJBCallbacks)) {
|
||||
var pending = window.WVJBCallbacks;
|
||||
delete window.WVJBCallbacks;
|
||||
for (var i = 0; i < pending.length; i++) {
|
||||
try { pending[i](bridge); }
|
||||
catch (e) { console.error('[WVJB] pending WVJBCallback threw:', e); }
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -56,6 +56,22 @@ final class RootViewController: UIViewController {
|
||||
print("[ResourceUnzipper] ERROR: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 1.8 烟雾测试:确认 WebViewJavascriptBridge.js 已入 Bundle 且可读
|
||||
// 注:synchronized group 会把 ylgamehall/Resources/JS/ 子目录扁平化为 bundle 根,
|
||||
// 所以无需 subdirectory 参数。
|
||||
if let url = Bundle.main.url(forResource: "WebViewJavascriptBridge",
|
||||
withExtension: "js"),
|
||||
let content = try? String(contentsOf: url, encoding: .utf8) {
|
||||
print("""
|
||||
[WVJB.js] 已就绪
|
||||
path = \(url.path)
|
||||
bytes = \(content.utf8.count)
|
||||
head = \(content.prefix(60).replacingOccurrences(of: "\n", with: " ⏎ "))…
|
||||
""")
|
||||
} else {
|
||||
print("[WVJB.js] ERROR: 找不到 Bundle 内 JS/WebViewJavascriptBridge.js")
|
||||
}
|
||||
}
|
||||
|
||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||
|
||||
Reference in New Issue
Block a user