鸿蒙Mpaas中AlipayJSBridge的探究

264 阅读4分钟

很多金融项目中都接入了Mpaas的HRiver容器用于客户端快速打通H5业务,mpaas容器提供了离线包,jsbridge插件等功能,在项目开发中,经常有遇到一些疑问

AlipayJSBridge.call的实现在哪里?

在与前端联调中,前端只调用了AlipayJSBridge.call就可以与客户端产生交互,这个问题问了前端他们没有相关代码的实现和引入,那么这个js的代码应该是在客户端加载的,如果使用了mpaas的容器,那么在容器打开页面的时候可以通过webview的生命周期方法挂载项目本地js方法,通过猜想后在项目的mpass的编译后文件中找到了该JSBridge的文件。

截屏2025-02-17 10.10.41.png

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中代码进行分析

截屏2025-02-17 10.17.53.png 找到了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文件中的实现了。