那些被忽略但很好用的 Web API / PostMessage

親像愛情的限時批~

各位有想過該如何跟其他頁面進行溝通嗎?如果在A頁面點擊了一個按鈕,能不能夠讓B頁面發生變化或執行動作呢?別說不可能,今天要介紹的 PostMessage 便能夠達成這樣的效果。


PostMessage

一般來說,不同的頁面要相互溝通的話,它們的 Domain 必須相同,然後通常就會使用 LocalStorage 搭配 storage 事件來進行溝通,但 PostMessage 神奇的地方就在於它可以越過這項限制,讓我們甚至可以跟不同源的網站頁面進行溝通,這樣就能解決前端最討厭的 CORS 問題了。

雖然 PostMessage 可以跨域溝通,不過當然也要對方願意且有撰寫對應的機制程式碼,要不然世界就大亂了 XD



# otherWindow.postMessage

PostMessage API 底下就是只有 postMessage 這個 method,非常的單純,卻也無比強大,其中 otherWindow 所指的是「目前分頁以外的 Window」,這個 Window 可以是利用 window.open 執行返回的 Window 物件,或是一個 iframe 元素的 contentWindow,而也就是訊息要送達的目的地。

另外 postMessage 還必須傳入兩個參數:

  • message: 第一個是你要傳送的訊息,任何型別格式都可以接受。
  • targetOrigin: 第二個則是設定「能夠接收本次訊息」的網頁 Origin,必須要是在這個 Domain 底下的頁面才能接收到訊息。
const url = "https://maxleebk.com//2021/09/28/webApi/webApi-15/";
const otherPage = window.open(url);
otherPage.postMessage("Hi,Max", "https://maxleebk.com/");

以上面的例子來說,我們先利用 window.open 打開了一個作者本人的部落格分頁,而該分頁的 Window 已經被儲存在 otherPage 中,再來我們就利用 postMessage 傳遞一個字傳,並且利用 targetOrigin 來確保一定要是 https://maxleebk.com/ 底下的頁面才能真正接受到訊息。

你該注意:targetOrigin 這個參數建議一定要傳入並填妥,不然訊息有可能會被刻意攔截,導致無法預期的安全性問題。



# Window:message Event

訊息有傳送的一方,自然也要有接收的一方,而接收方要順利接到訊息的話,只要在 window 上監聽 message 這個事件即可,而訊息的部分則會被放在 Event 物件的 data 屬性裡:

addEventListener("message", function (event) {
  console.log(event.data);
});

所以只要你想傳送的網站中,有打開一個這樣的通道,那你就可以透過 PostMessage 來與之溝通,不過接收方也不可能所有的訊息都照單全收,要是我們今天身為接收方,那應該要像下方這樣做一些防禦。

在 Event 物件中還有一個 origin 屬性,可以用來獲取傳送方的來源,所以我們可以利用它來過濾那些不在信任範圍的網址:

addEventListener("message", function (event) {
  // 如果訊息不是來自於 IT邦幫忙 那就不執行任何動作
  if (event.origin !== "https://ithelp.ithome.com.tw") return;
  console.log(event.data);
});

當然了,信息往來總不能只有單向,如果接收方要回信的話,只要利用 Event 物件中的 source 屬性就可以進行訊息的傳送:

addEventListener("message", function (event) {
  if (event.origin !== "https://ithelp.ithome.com.tw") return;
  console.log(event.data);
  event.source.postMessage("hi,IT邦幫忙", event.origin);
});

透過 PostMessage 訊息的相互傳遞,我們就可以不受同源政策的限制,向其他網域的網頁請求資料了,或利用訊息的格式判定來執行其他頁面的動作等等…

addEventListener("message", function (event) {
  axios.get("/user", event.data.id).then((res) => {
    event.source.postMessage(res, event.origin);
  });
});

addEventListener("message", function (event) {
  window[event.data.method](); // 執行指定名稱的全域函式
});



PostMessage API 是不是非常有趣又神奇呢?或許要遇到頁面溝通的情境並不多,但千萬忽略了它,等到遇到了,它會是非常強大的幫手,而且應用起來也相當簡單,大家如果有興趣,隨手就能寫個簡單的範例。


- 此篇文章為「iT 邦幫忙鐵人賽」參賽文章,同步發表於 iT 邦幫忙 -