问题描述
当在鸿蒙系统中使用微信网页 JS-SDK 时,如果页面中存在 iframe,且 iframe 加载与当前页面不同的 url,会导致微信 JS-SDK 报错 invalid signature
或 permission denied
。
此问题的根源在于鸿蒙微信的 bug,已向官方反馈问题,详见。
临时解决方案
通过分析问题的成因,可以发现微信 JS-SDK 的签名验证依赖于当前页面的 url,而在鸿蒙系统微信获取的当前页面 url 会因为 iframe 的加载而改变,导致签名验证失败。故我们可以在调用失败后插入一个与当前页面 url 相同的 iframe 来欺骗微信 🤡。
1. 劫持微信 JS-SDK 的调用
通过 navigator.userAgent
是否同时包含 'ArkWeb'
和 MicroMessenger'
判断当前网页应用的运行环境是否为鸿蒙微信。
如果是鸿蒙微信,那么在引入微信 JS-SDK 后,添加下面的代码劫持微信 JS-SDK 的调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
|
export const ENABLED_WECHAT_JSSDK_API_LIST = [ 'getLocation', 'onMenuShareAppMessage', 'onMenuShareQQ', 'chooseImage', 'uploadImage', 'previewImage', 'hideOptionMenu', 'showOptionMenu', 'hideMenuItems', 'showMenuItems', 'hideAllNonBaseMenuItem', 'showAllNonBaseMenuItem', 'scanQRCode', ]
const _wx = window.wx
const HACK_RETRY_CALL_TAG = Symbol()
let hackPending let hackPendingResolve
function hackWechat() { return new Promise((resolve) => { const iframe = document.createElement('iframe') iframe.src = window.location.href.split('#')[0] + '#hack-wechat-harmonyos' iframe.style.display = 'none'
window.addEventListener('message', function handler(event) { if (event.data?.type !== 'hackWechatHarmonyosReady') return
iframe.remove() window.removeEventListener('message', handler) setTimeout(resolve, 10) })
document.body.append(iframe) }) }
window.wx = new Proxy( {}, { get(_, prop, receiver) { if (!ENABLED_WECHAT_JSSDK_API_LIST.includes(prop)) { return _wx[prop] }
return async (options) => { options ??= {}
if (!options[HACK_RETRY_CALL_TAG]) { await hackPending }
const hackOptions = { ...options, success(...args) { if (options[HACK_RETRY_CALL_TAG]) { hackPending = null hackPendingResolve?.() } options.success?.(...args) }, async fail(...args) { const errMsg = args[0]?.errMsg if ( !errMsg.endsWith(':fail invalid signature') && !errMsg.endsWith(':permission denied') ) { options.fail?.(...args) return }
if (hackPending && !options[HACK_RETRY_CALL_TAG]) { await hackPending receiver[prop](options) return }
hackPending = new Promise((resolve) => { hackPendingResolve = resolve })
await hackWechat() receiver[prop]({ ...options, [HACK_RETRY_CALL_TAG]: true, }) }, }
_wx[prop](hackOptions) } }, }, )
|
⚠️ 当前代码未设置最大重试次数,可能会陷入死循环(遇到非 bug 导致的 fail),如有需要可自行添加
2. 减少插入额外 iframe 导致的性能开销
插入与当前页面 url 相同的 iframe 会导致额外的性能开销及资源浪费,那么我们怎么能将其影响降到最低呢?以我实际项目为例:
项目介绍
项目为使用 Vue
开发的单页应用,所有的 url 都会通过 index.html
来处理。
解决方案
在 index.html
中添加如下代码,当检测到当前页面是 hack iframe 时(通过判断 hash
是否为 #hack-wechat-harmonyos
),使用 window.stop() 停止后续解析并通过 postMessage
通知父窗口已准备就绪。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8" />
<script> if (window.parent !== window && location.hash === '#hack-wechat-harmonyos') { window.stop(); window.parent.postMessage({ type: 'hackWechatHarmonyosReady' }, '*'); } </script>
...
|