postMessage窗口间通信实现单点登录

postMessage窗口间通信实现单点登录

  • 方案一:iframe + postMessage + localStorage
  • 方案二:postMessage + window.open
  • 方案三:BroadcastChannel(推荐)

方案一:本地缓存:使用iframe + postMessage + localStorage

例如:网站1将数据发送到网站2
缺点:网站1和网站2的协议域名要相同
网站1(http://localhost:9090):

function open(src) {
  const iframe = document.createElement("iframe")
  iframe.style.display = "none"
  iframe.src = src
  document.body.append(iframe)

  iframe.onload = () => {
    iframe.contentWindow.postMessage('my value', src)
  }
  window.addEventListener('message', (event) => {
    if(event.origin === src) {
      iframe.remove()
    }
  })
}

open('http://localhost:2101')
// 可以通过任意方式打开网站2

网站2(http://localhost:2101):

window.addEventListener("message", (event) => {
  localStorage.setItem('win', event.data)
  event.source?.postMessage("false", { targetOrigin: event.origin })
}, false)

ps: 虽然看到很多文章都说使用上面这种方式可以让localStorage跨域共享,但是实测必须要网站1与网站2的[协议]://[域名]相同才行,端口可以不同,否则网站1的iframe(地址为网站2)的localStorage与网站2的localStorage是不共享的。所以上面这种方式并不是完全跨域
网站1
image
网站2
image

方案二:页面通信:postMessage + window.open

例如:网站1将数据发送到网站2
缺点:虽然可以实现任意两个网站通信,但是网站2只能在网站1通过window.open的方式打开才能获取消息
网站1(http://localhost:9090):

function open(src) {
    const win = window.open(src, "_blank")
    if(!win) {
      return
    }
    // 很多文章这里用的setTimeout,但是setTimeout存在一些问题
    // 1. 延迟时间长
    // 2. 存在漏收的情况
    // 所以这里使用轮询,每0.5s向另一个页面发送一次消息,当接收方接收到消息后终止发送
    const interval = setInterval(() => {
      win.postMessage("my value", src)
    }, 500)
    // 监听另一个页面的回传消息,接收到后终止定时器
    window.addEventListener('message', (event) => {
      if(event.origin === src) {
        clearInterval(interval)
      }
    })
}
open("http://192.168.10.112:2101")

网站2(http://192.168.10.112:2101):

let isclose = false
window.addEventListener("message", (event) => {
  // 只接收一次
  if(isclose) {
    return
  }
  isclose = true
  // 回传消息,终止发送
  event.source?.postMessage("false", { targetOrigin: event.origin })
  localStorage.setItem('data', event.data)
}, false)

注意事项:

otherWindow.postMessage(message, targetOrigin, [transfer]);

otherWindow为目的窗口的引用:iframe.contentWindow或window.open()的返回值
例如:向某个iframe页面发送消息需要用这个iframe的窗口引用进行发送,向某个通过window.open()方式打开的页面发送消息需要用window.open()返回的这个窗口引用进行发送

方案三:BroadcastChannel(推荐)-- 需要同源

BroadcastChannel 接口代理了一个命名频道,可以让指定 origin 下的任意 browsing context 来订阅它。它允许同源的不同浏览器窗口,Tab 页,frame 或者 iframe 下的不同文档之间相互通信。通过触发一个 message 事件,消息可以广播到所有监听了该频道的 BroadcastChannel 对象

点此查看BroadcastChannel详情
页面1:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>窗口通信1</h1>
    <button onclick="send()">SEND</button>

    <script>
        const broadCast = new BroadcastChannel("my_demo_client")

        function send() {
            console.log('send');
            broadCast.postMessage("client1")
        }

        broadCast.onmessage = e => {
            console.log(e);
        }
    </script>
</body>
</html>

页面2:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>窗口通信2</h1>
    <button onclick="send()">SEND</button>

    <script>
        const broadCast = new BroadcastChannel("my_demo_client")

        function send() {
            console.log('send');
            broadCast.postMessage("client1")
        }

        broadCast.onmessage = e => {
            console.log(e);
        }
    </script>
</body>
</html>

由于这两个页面都在my_demo_client频道,并且在同一个浏览器,可以直接通过频道进行双向通信
点击查看兼容性
如有错误,欢迎指正

posted @ 2024-02-29 17:47  梦羽微澜  阅读(66)  评论(0编辑  收藏  举报