标签页间跨源通信的尝试

背景

有 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


最后修改:2024 年 04 月 18 日 03 : 01 PM
如果觉得我的文章对你有用,请随意赞赏

发表评论