postMessage

window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

父容器代码

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
function receiveMessage(event: any) {
// 我们能信任信息来源吗?
// if (event.origin.indexOf('yitong.com') === -1 && event.origin.indexOf('allkids.com.cn') === -1) {
// return
// }
var messageData = {
type: ''
}
// "dataType":"json" 校验消息格式,防止收到其他消息导致报错
if (event.data && typeof event.data === 'string' && event.data.indexOf('"dataType":"json"') > -1) {
messageData = JSON.parse(event.data)
}
switch (messageData.type) {
case 'getToken':
event.source.postMessage(JSON.stringify({
dataType: 'json',
type: 'token',
data: 'this is token' // 这里给token字符串即可
}), event.origin)
break
case 'getUserInfo':
event.source.postMessage(JSON.stringify({
dataType: 'json',
type: 'userInfo',
data: { userId: '1234' } // 这里给用户信息,例如Id,name,role等等
}), event.origin)
break
default:
break
}
console.log('parent receive postMessage: ', event.data)
}
window.addEventListener('message', receiveMessage, false)

子页面代码

cms-bridge.ts

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
114
115
interface ImessageData {
type: string,
data?: string | any
}

class CMSBridge {
token = ''
userInfo!: any
constructor() {
window.addEventListener('message', this.receiveMessage.bind(this), false)
}

receiveMessage(event: any) {
// 上线后填写具体域名,防止被攻击
// if (event.origin.indexOf('yitong.com') === -1 && event.origin.indexOf('allkids.com.cn') === -1) {
// return
// }
// 回复消息 event.source.postMessage('JSON.Stringify', event.origin)
let messageData: ImessageData = {
type: ''
}
if (event.data && typeof event.data === 'string' && event.data.indexOf('"dataType":"json"') > -1) {
messageData = JSON.parse(event.data)
}
const locationHref = location.href.indexOf('//localhost') > -1
switch (messageData.type) {
case 'token':
this.token = messageData.data
// 接受token:messageData.data
event.source.postMessage(JSON.stringify({
dataType: 'json',
type: 'no reply',
data: 'thank you: ' + messageData.data
}), '*') // * 替换为线上域名
break
case 'userInfo':
this.userInfo = messageData.data
// 接受token:messageData.data
event.source.postMessage(JSON.stringify({
dataType: 'json',
type: 'no reply',
data: 'thank you: ' + messageData.data
}), '*') // * 替换为线上域名
break

// ----------------------本地调试专用-----------------------
case 'getToken':
locationHref && event.source.postMessage(JSON.stringify({
dataType: 'json',
type: 'token',
data: 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjcm0ueWl0b25nLmNvbSIsImF1ZCI6IjlBODZDMDA0OTUzRTQyNjI3RTZDREE0M0M3NTY3NTI4Iiwic3ViIjoiMTIxODgiLCJuYW1lIjoi5Y2O56ulIiwiaWF0IjoxNTk2MDA2MDg0NzAxLCJleHAiOjE1OTYwMTMyODQ3MDEsIk9QSUQiOiJZVDAxMzIxMCIsIk9QQ29tcGFueSI6IjE5OTgyIiwiT1BUeXBlIjoiQjAwMDIifQ.torRi4GK5P6zzn7oW2-vuXFL7VWM5MpiDZLD_Uqw2_-JG69Nkx56lz4NZ9dQI-Loy47XYw4m6BgD1ynT5tT8og' // 这里给token字符串即可
}), event.origin)
break
case 'getUserInfo':
locationHref && event.source.postMessage(JSON.stringify({
dataType: 'json',
type: 'userInfo',
data: { userId: '1234' } // 这里给用户信息,例如Id,name,role等等
}), event.origin)
break
default:
break
}
console.log('child receive postMessage:', event.data)
}

getToken() {
return new Promise((resolve, reject) => {
window.parent && window.parent.postMessage(JSON.stringify({
dataType: 'json',
type: 'getToken',
data: ''
}), '*') // * 替换为线上域名
let count = 0
let timer = setInterval(() => {
count += 10
if (this.token) {
if (timer) {
clearInterval(timer)
}
resolve(this.token)
}
if (count > 3000) {
reject(new Error('获取token失败!'))
}
}, 10)
})
}

getUserInfo() {
return new Promise((resolve, reject) => {
window.parent && window.parent.postMessage(JSON.stringify({
dataType: 'json',
type: 'getUserInfo',
data: ''
}), '*') // * 替换为线上域名
let count = 0
let timer = setInterval(() => {
count += 10
if (this.userInfo) {
if (timer) {
clearInterval(timer)
}
resolve(this.userInfo)
}
if (count > 3000) {
reject(new Error('获取userInfo失败'))
}
}, 10)
})
}
}

export default CMSBridge

使用

1
2
3
4
import CMSBridge from '@/api/cms-bridge'
const cms = new CMSBridge()
await cms.getToken()
await cms.getUserInfo()

参考资料

developer.mozilla.org


postMessage
http://example.com/20200728-postMessage/
作者
csorz
发布于
2020年7月28日
许可协议