鸿蒙系统中微信网页 JS-SDK 由 iframe 导致 invalid signature 和 permission denied 报错

问题描述

当在鸿蒙系统中使用微信网页 JS-SDK 时,如果页面中存在 iframe,且 iframe 加载与当前页面不同的 url,会导致微信 JS-SDK 报错 invalid signaturepermission 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
/*
hack: https://developers.weixin.qq.com/community/develop/doc/000c480314c5f0b8368360ed161400?highLine=%25E9%25B8%25BF%25E8%2592%2599%25E9%2580%2582%25E9%2585%258D
纯血鸿蒙微信存在 bug, iframe 加载与当前 url 不同的页面会影响签名校验
这里通过在调用失败后插入一个与当前 url 相同的 iframe 来欺骗微信
*/

// 这里只列出了部分 api,实际项目中请根据需要修改,此列表可直接在 JS-SDK 初始化时传给 jsApiList 配置项
export const ENABLED_WECHAT_JSSDK_API_LIST = [
'getLocation',
'onMenuShareAppMessage',
'onMenuShareQQ',
'chooseImage',
'uploadImage',
'previewImage',
'hideOptionMenu',
'showOptionMenu',
'hideMenuItems',
'showMenuItems',
'hideAllNonBaseMenuItem',
'showAllNonBaseMenuItem',
'scanQRCode',
]

const _wx = window.wx

// 标记 hack 后重试调用
const HACK_RETRY_CALL_TAG = Symbol()
// 处理并发调用,插入 hack iframe 时让后续调用等待
let hackPending
let hackPendingResolve

function hackWechat() {
return new Promise((resolve) => {
const iframe = document.createElement('iframe')
// #hack-wechat-harmonyos 用于标记为 hack iframe 加载,提前停止解析避免资源浪费
iframe.src =
window.location.href.split('#')[0] + '#hack-wechat-harmonyos'
iframe.style.display = 'none'

// 不能使用 iframe.onload 事件来判断 iframe 加载完成,因为页面中会通过 window.stop() 阻止后续解析
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 ??= {}

// 等待 hack 完成,放过 hack 后重试调用
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
}

// 等待 hack 完成,放过 hack 后重试调用
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>

...

鸿蒙系统中微信网页 JS-SDK 由 iframe 导致 invalid signature 和 permission denied 报错
https://sun79.github.io/2025/06/25/harmonyos-wechat-iframe-bug/
作者
Sun79
发布于
2025年6月25日
许可协议