很多金融项目中都接入了Mpaas的HRiver容器用于客户端快速打通H5业务,mpaas容器提供了离线包,jsbridge插件等功能,在项目开发中,经常有遇到一些疑问
AlipayJSBridge.call的实现在哪里?
在与前端联调中,前端只调用了AlipayJSBridge.call就可以与客户端产生交互,这个问题问了前端他们没有相关代码的实现和引入,那么这个js的代码应该是在客户端加载的,如果使用了mpaas的容器,那么在容器打开页面的时候可以通过webview的生命周期方法挂载项目本地js方法,通过猜想后在项目的mpass的编译后文件中找到了该JSBridge的文件。
window.AlipayJSBridge||function(){console.log("begin load AlipayJSBridge");var messenger=window.__alipayConsole__||window.console,log=messenger.log,e=function(e){log.call(messenger,"{bridge_token}h5container.message: "+JSON.stringify(e))},a=null,n=null;window.addEventListener("message",function(e){if(e&&"__RENDER_WORKER_IPC_MP__"===e.data&&null===n&&e.ports&&1===e.ports.length){(n=e.ports[0]).onmessage=function(e){try{var a=JSON.parse(e.data),n='postMessage'===a.handlerName?'message':a.handlerName;if(a.responseId){var l=t[a.responseId];'boolean'==typeof a.keepCallback&&a.keepCallback||delete t[a.responseId];'function'==typeof l&&setTimeout(function(){l(a.responseData)},0)}else n&&AlipayJSBridge.trigger(n,a,a.callbackId)}catch(a){log.call(messenger,"failed to process message: "+e.data);log.call(messenger,a)}};a=function(e){e.__FastPath__=1;var a=JSON.stringify(e);n.postMessage(a)}}});var t={};window.AlipayJSBridge={call:function(n,l,i){if('string'==typeof n){if('function'==typeof l){i=l;l=null}else'object'!=typeof l&&(l=null);var o=''+(new Date).getTime()+Math.random();'function'==typeof i&&(t[o]=i);var c={func:n,param:l,msgType:'call',clientId:o};null!==a&&'postMessage'===n?a(c):e(c)}},callback:function(n,t){var l={clientId:n,param:t};null!==a&&'string'==typeof n&&'native_'!==n.substring(0,7)?a(l):e(l)},trigger:function(e,a,n){if(e){var t=document.createEvent('Events');t.initEvent(e,!1,!0);t.syncJsApis=[];if('object'==typeof a)for(var l in a)t[l]=a[l];t.clientId=n;var i=!document.dispatchEvent(t);n&&'back'===e&&AlipayJSBridge.callback(n,{prevent:i});n&&'closeWindow'===e&&AlipayJSBridge.callback(n,{prevent:i});n&&'firePullToRefresh'===e&&AlipayJSBridge.callback(n,{prevent:i});n&&'onShare'===e&&AlipayJSBridge.callback(n,{prevent:i});n&&'collectDestroyJsApi'===e&&AlipayJSBridge.callback(n,{syncJsApis:t.syncJsApis});n&&'pullIntercept'===e&&AlipayJSBridge.callback(n,{prevent:i});n&&'segControlClick'===e&&AlipayJSBridge.callback(n,{prevent:i})}},_invokeJS:function(e){e=JSON.parse(e);console.log("invokeJS msgType "+e.msgType+" func "+e.func);if('callback'===e.msgType){var a=t[e.clientId];'boolean'==typeof e.keepCallback&&e.keepCallback||delete t[e.clientId];'function'==typeof a&&setTimeout(function(){a(e.param)},1)}else'call'===e.msgType&&e.func&&this.trigger(e.func,e.param,e.clientId)},devPerformance4Test:""};AlipayJSBridge.startupParams='{startupParams}';window.APVIEWID='{APVIEWID}';var l=document.createEvent('Events');l.initEvent('AlipayJSBridgeReady',!1,!1);var i=document.addEventListener;document.addEventListener=function(e,a){e===l.type&&setTimeout(function(){a(l)},1);i.apply(document,arguments)};document.dispatchEvent(l);console.log("load AlipayJSBridge dispatchEvent AlipayJSBridgeReady")}(); // do not modify
AlipayJSBridge交互方式
对h5_bridge.js中代码进行分析
找到了jsbridge的定义和call的方法实现
call中非postMessage的现实调用e方法函数,e方法的实现
var messenger = window.__alipayConsole__ || window.console;
var log = messenger.log;
var e = function(e){
log.call(messenger, "{bridge_token}h5container.message: " + JSON.stringify(e));
};
传统的jsbridge交互方式是自定义urlscheme的拦截,这里能看到这个jsbridge是使用console.log的方式进行的,后面我们通过mpaas插件的调用断点的方式,找到了相关代码
Web.onConsole((event) => {
if (event && event.message) {
let h41 = event.message;
this.handleMsgFromJs(h41.getMessage());
}
return false;
});
对webview的console进行了拦截,主要逻辑在handleMsgFromJs中做message的处理工作,大概就是判断是否js交互的方法,需要通过{bridge_token}h5container.message:对message的string进行判断,js的message输出为{bridge_token}h5container.message: {"func":"doAction","param":{},"msgType":"call","clientId":"17397564556440.20416475185463212"}如果匹配那么需要对message内容做处理。其中bridge_token是webview的运行processID进行的拼接。
jsbridge是什么时候注入的
首先想到在web.onControllerAttached的挂载回调中进行本地js的加载,发现mpaas并没有这么做,而是在
Web.javaScriptOnDocumentStart(this.scriptLoader?.e20());
后来尝试用web.onControllerAttached回调中runjavascript发现模拟器中可以成功注入,真机不行,因为onControllerAttached的回调网页还没有被加载。鸿蒙有专门加载注入js的方法
- javaScriptOnDocumentStart() 该脚本将在页面的任何JavaScript代码之前运行,并且DOM树此时可能尚未加载、渲染完毕。这个方法更为合适
AlipayJSBridge回调方式
首先插件通过调用H5BridgeContext基类的sendBridgeResult方法,通过断点跟踪
sendBridgeResult(w53) {
return this.s26(w53);
}
s26(param, u53, rawData) {
if (!this.id || this.id.length == 0) {
g1(h1, "client id not specified " + this.action);
return false;
}
if (this.id.startsWith("native_")) {
g1(h1, "ignore native fired event " + this.action);
return false;
}
if (this.r26) {
let event = new H5Event();
event.id = this.id;
event.action = this.action;
event.keepCallback = u53 || false;
event.param = param;
if (rawData != undefined && rawData) {
event.rawData = rawData;
}
event.msgType = H5Event.TYPE_CALL_BACK;
this.r26.sendToWeb(event);
}
else {
g1(h1, "[FATAL ERROR] in sendBack() bridge is null");
return false;
}
return true;
}
关键代码
sendToWeb(event) {
if (!event || this.l27) {
return;
}
let eventId = event.id;
let action = event.action;
let param = event.param;
let type = event.msgType;
let u55 = event.keepCallback;
let start = "AlipayJSBridge._invokeJS(";
let end = ")";
if (event.rawData != undefined && event.rawData) {
let y55 = `"{\"clientId\":\"${eventId}\",\"func\":\"${action}\",\"param\":{\"data\":\"${param}\"},\"msgType\":\"${type}\",\"keepCallback\":${u55}}"`;
g1(h1, 'rawData: ' + param.length + " " + new Date().getTime());
let z55 = start + y55 + end;
if (this.n27) {
this.n27.runJavaScript("javascript:(function(){if(typeof AlipayJSBridge === 'object'){" + z55
+ "}})();");
g1(h1, "jsapi rep:" + z55.length);
}
}
else {
let v55 = {};
v55["clientId"] = eventId;
v55["func"] = action;
v55["param"] = param;
v55["msgType"] = type;
v55["keepCallback"] = u55;
let msg = JSON.stringify(v55);
let w55 = JSON.stringify(msg);
let x55 = start + w55 + end;
if (this.n27) {
this.n27.runJavaScript("javascript:(function(){if(typeof AlipayJSBridge === 'object'){" + x55
+ "}})();");
g1(h1, "jsapi rep:" + x55);
}
else {
g1(h1, "jsapi error webController undefined: " + x55);
}
}
}
实质是webController方法中的runJavaScript调用bridge文件中的_invokeJS方法, 更多细节在bridge文件中的实现了。