H5 拉起微信小程序流程解析
非个人主体并且已认证的(微信认证)小程序,使用云开发静态网站托管的网页,可以免鉴权跳转任意合法合规的小程序。即可以在微信内部浏览器的 H5 跳转小程序,也可以在微信外部浏览器或其他部分 App (如企业微信、QQ 等)跳转微信小程序(参见微信文档:静态网站 H5 跳小程序)。
微信浏览器内
由于 H5 拉起小程序是在微信浏览器封闭的系统内,微信有着较高的定制和管控能力。
在实现方案上,微信未提供直接拉起小程序的 API,而是提供了:供用户点击跳转小程序的开放标签 (从 2020 年中开始提供)。使用方法如下:
<wx-open-launch-weapp
id="launch-btn"
username="gh_xxxxxxxx"
path="pages/home/index?user=123&action=abc"
>
<template>
<style>.btn { padding: 12px }</style>
<button class="btn">打开小程序</button>
</template>
</wx-open-launch-weapp>
在需要用到跳转能力的页面中,首先引入微信 js-sdk,并在期望用户点击跳转的地方引入该开放标签即可。其中,gh_xxxxxxxx 为目标跳转的小程序原始 ID,线上小程序均可在”更多资料“一栏查看。
另外,在使用该标签的页面中,还需首先调用 wx.config 进行鉴权,否则该标签不展示。下面详细分析其流程。
获取 signature
首先通过服务端获取到了主体使用权的签名 signature。
(注意这里获取 signature 中使用的 appId 与开放标签中使用的 appId 不是同一个:这里用的是主体服务号的 appId,开放标签中使用的是跳转的目标小程序的 appId)。
签名校验
接着将 signature 作为参数供 wx.config 调用。 方法传入参数为:
wx.config({
// debug: true, // 调试时可开启
appId: "必填,公众号的唯一标识", // <!-- replace -->
timestamp: 0, // 必填,填任意数字即可
nonceStr: "nonceStr", // 必填,填任意非空字符串即可
signature: signature, // 必填,填服务端获取的签名
jsApiList: ["chooseImage"], // 必填,随意一个接口即可
openTagList: ["wx-open-launch-weapp"], // 填入打开小程序的开放标签名
});
调用该方法后,经过多处调用,最终进入下面的逻辑:
o.WeixinJSBridge
? WeixinJSBridge.invoke(n, x(e), function (e) {
A(n, e, i);
})
: B(n, i);
其中 X(e) 为拼接 signature 字段等。看 invoke 方法,其中 n 为 'preVerifyJSAPI' 方法。
invoke 方法在 js-sdk 中找不到定义,想到微信 app 可以加载执行 JS 代码,于是解压了微信 apk,找到 assets/wxjs.js,在里面找到了定义:
WeixinJSBridge.invoke = function _call(func, params, callback) {
// 省略
var msgObj = {
func: func,
params: params,
};
msgObj[_MESSAGE_TYPE] = "call";
msgObj[_CALLBACK_ID] = callbackID;
try {
_sendMessage(JSON.stringify(msgObj));
} catch (e) {
__initLog(__EL, "_call error", e);
}
};
核心为 _sendMessage 方法:
function _sendMessage(msg) {
// 省略 msg -> msgArrayString
__wx._sendMessage(msgArrayString);
}
可以看到最终是将传入的参数调用 __wx._sendMessage 方法,其中 __wx 对象从 window.__wx 上取得,大概率是 Native 上定义的对象,并注入到 JS 环境中。
(注:JS 调用 Native 方法可参见:H5 与 Native 的交互方案,看 wxjs.js 中,暂时用的 addJavascriptInterface 方案,但 iframe 发起 URL Schema 的方式也写了做备份。)
鉴权结果回调
鉴权结果要通知 JS 侧,需要 Native 调用 H5 的方法,来看其中的 _handleMessageFromWeixin 方法:
function _handleMessageFromWeixin(message) {
// 省略
switch (msgWrap[_MESSAGE_TYPE]) {
case "callback":
{
if (
typeof msgWrap[_CALLBACK_ID] === "string" &&
typeof _callback_map[msgWrap[_CALLBACK_ID]] === "function"
) {
var ret = _callback_map[msgWrap[_CALLBACK_ID]](msgWrap["__params"]);
delete _callback_map[msgWrap[_CALLBACK_ID]]; // can only call once
return JSON.stringify(ret);
}
return JSON.stringify({
__err_code: "cb404",
});
}
break;
case "event":
// 省略
}
}
可以看到在这里执行 callback() 回调,并传入 Native 处理结果,wx.config 的回调方法为 A(n, e, i),没有太多重要的操作。
在实际页面中发现,如果是鉴权不通过,那么开放标签不会显示,所以也研究了下微信的开放标签。
开放标签像是 webComponent,但很可惜没有在解压的微信 apk 中找到对应的定义,应该是从浏览器底层支持了。
小结
在微信内部浏览器中,通过鉴权的网站使用 <wx-open-launch-weapp>
标签即可拉起小程序。而通过上面流程的梳理发现,只要服务号主体配置了安全域名,那么理论上这个页面,可以在微信内置浏览器内跳转任意小程序。网上有相关尝试证实确实如此。暂时没发现有做其它管控。
其它浏览器
如前所述,<wx-open-launch-weapp>
是微信内提供的标签,在其它浏览器中无法使用,但官方也提供了其它浏览器拉起小程序的方法,即使用 URL Scheme
的方式。
外部网页使用: location.href = "weixin://dl/business/?t= *TICKET*"
的方式即可拉起小程序。
其中 TICKET 的获取方式与上文中 signature 类似,是在三方服务端通过 token 去请求小程序服务端获得,需要的参数是 appId 和 AppSecret ,在这里使用的 appId 和 AppSecret 即为将要拉起的目标小程序。可以选择到期失效,也可以选择永久有效。
所以三方媒体页面要拉起某个小程序,需要首先获得该小程序的许可,拿到其 TICKET 才行。但 TICKET 可以选择永久有效,所以从原理上说,如果是永久有效,存在被另外主体获取并盗用的可能。
获取 TICKET 的过程并没有用到安全域名配置的信息,所以任意的网站只要能拿到 TICKET 均可以调起小程序。(另外,URL Scheme 的方式也可用于从其它 app 中调起打开小程序。)
另外,由于是使用 location.href = "weixin://dl/business/?t= *TICKET*"
的方式调起小程序,所以理论上是网页可以自动拉起,实测也确实如此。
小结
外部浏览器通过 URL Scheme 的方式调起小程序,无论什么网站,只要能生成对应小程序的 TICKET 即可拉起该小程序,几乎没有管控。
总结
2020 年中,微信开放的拉起小程序的能力,在微信浏览器内做的管控为:需 为媒体网站配置安全域名 + 用户点击
方可跳转小程序;外部浏览器通过 URL Scheme
的方式做的管控为:需事先获取目标小程序的 TICKET,但整体来说该方式调起小程序则相对灵活,且可以由网页自动拉起小程序(需要说明的是,个人小程序,不具有获取 TICKET 或 signature 的权限)。
原文首发于:H5 拉起微信小程序流程解析 | 宜想悠然亭。