背景
有 2 个独立页面,主域相同,但是源不同。需要在 a 页面进行一定操作,使消息能够传递给 b 页面。
经过调研,bridge A 可以采用 postMessage,bridge B 可以使用 SharedWorker, LocalStorage, BroadcastChannel 等方法。
SharedWorker
iframe.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>message bridge</title>
</head>
<body>
<script>
// 创建一个 Shared Worker
const worker = new SharedWorker('worker.js', 'tabWorker');
function receiveMessageFromIndex(event) {
// 向 Shared Worker 发送消息
worker.port.postMessage(event?.data);
}
window.addEventListener('message', receiveMessageFromIndex, false);
</script>
</body>
</html>
worker.js
// 接收到消息时,向所有连接发送该消息
const connections = [];
onconnect = function (event) {
var port = event.ports[0];
connections.push(port);
port.onmessage = function (event) {
connections.forEach(function (conn) {
if (conn !== port) {
conn.postMessage(event.data);
}
});
};
port.start();
};
a.target.tsx
const postMessageIframeRef = useRef<HTMLIFrameElement>(null);
return <iframe
src="http://b.target.com/iframe.html"
style={{ display: 'none' }}
ref={postMessageIframeRef}
/>
b.target.tsx
useEffect(() => {
const worker = new SharedWorker('/worker.js', 'tabWorker');
worker.port.onmessage = function (event) {
console.log('接收到 event', event);
};
return () => {
worker.port.onmessage = null;
worker.port.close();
};
}, []);
LocalStorage
iframe.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>message bridge</title>
</head>
<body>
<script>
function receiveMessageFromIndex(event) {
window.localStorage.setItem('messageBridge', JSON.stringify(event?.data));
console.log('sent', event?.data);
}
window.addEventListener('message', receiveMessageFromIndex, false);
</script>
</body>
</html>
b.target.tsx
useEffect(() => {
const storageMessageBridge = (e: StorageEvent) => {
if (e.key === 'messageBridge') {
const data = JSON.parse(e.newValue || '{}');
console.log(data);
}
};
window.addEventListener('storage', storageMessageBridge);
return () => {
window.removeEventListener('storage', storageMessageBridge);
};
}, []);
a.target.tsx
同 1。
BroadcastChannel
iframe.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>message bridge</title>
</head>
<body>
<script>
const bc = new BroadcastChannel('messageBridge');
const send = (e) => bc.postMessage(e);
function receiveMessageFromIndex(event) {
send(event?.data);
console.log('sent');
}
window.addEventListener('message', receiveMessageFromIndex, false);
</script>
</body>
</html>
b.target.tsx
useEffect(() => {
const messageBridge = new BroadcastChannel('messageBridge');
messageBridge.onmessage = (e) => {
console.log(e?.data);
};
return () => {
messageBridge.onmessage = null;
};
}, []);
a.target.tsx
同 1。
浏览器局限性
跨主域时,以上方法都会出现限制,应该是 Chrome 出于安全考虑,禁用了跨主域标签页之间的通信(问了几位大佬确实如此,这个是定义好的安全模型,无法从正常途径破解)。
目前唯一已知可行的方案只有通过后端。
以下 2 种方案均不可行,测试浏览器版本 Chrome 123.0.6312.124(正式版本)(x86_64)。
相关讨论:Github - Allowing top-level communication for cross-origin isolated documents