程式開發

透過 Web Extension 提供物件給網頁

最近有著想要開發 LikeCoin Wallet 擴充插件的想法,類似於 MetaMask 的做法,將一個物件,例如 likecoin 提供到 window 下,這樣網頁就可以透過 likecoin.transfer 來轉帳。這篇文紀錄可能如何達成。

三種 Script

首先,先介紹 Web Extension 中三個主要的 Script。第一是 Background Script,顧名思義就是在後台常駐。第二種是 Content Script,這個則會在指定的頁面中執行,第三種是 Page Script,指網頁中的腳本。

其中,Background Script 和 Content Script 都不會受到跨域請求、CSP 的限制,給了非常大的操作空間。

而由於 Content Script 是和 Page Script 分開執行,因此直接在 Content Script 寫 window['likecoin'] = {} 是行不通的。(相關的概念似乎是叫做 Xray。)

因此,接下來的問題就成了如何讓網頁可以取用我們訂定的物件,並讓這個物件可以享有 Content Script 的優勢。

將物件提供給 Window

在許多相關的討論中,會開一個 inpage.js,並透過在 manifest.json 中加入 web_accessible_resources。如此一來,網頁就可以通過 <script src="extension://.../inpage.js"> 的形式來載入 inpage.js。此時,inpage.js 則成為 Page Script 的一部分,因此就可以被網站呼叫。

透過 Content Script 發起跨域請求

然而,既然成為 Page Script,那就無法享有前面提到的跨域請求和 CSP 的優勢。這時,我們可以透過 postMessagemessage 事件,將 inpage.js 中的請求轉交給 Content Script,Content Script 發起請求並獲得回應時再用相同方式回傳給 inpage.js

inpage.js:

const IDENTIFIER = 'demo-extension'
window.postMessage({
  url: 'https://example.com',
  _identifier: IDENTIFIER,
}, window.location.origin)

window.addEventListener('message', event => {
  if (!event.data || event.data._identifier !== IDENTIFIER) return
  const res = event.data.body
  // 使用回傳的內容
})

Content Script:

const IDENTIFIER = 'demo-extension'
window.addEventListener('message', event => {
  if (!event.data || event.data._identifier !== IDENTIFIER) return
  fetch(event.data.url).then(res => res.text()).then(data => {
    window.postMessage({
      body: data,
      _identifier: IDENTIFIER,
    }, window.location.origin)
  })
})

window.postMessage({
  url: 'https://example.com',
  _identifier: IDENTIFIER,
}, window.location.origin)

由於 Message 是對整個 window 發出,為了避免處理到來自其他 extension 或網頁本身的 Message,上面加入了 identifier。並限制 origin 來避免其他意外的操作,例如通過 iframe 的模擬。

然而必須留意的是網站也可以模擬插件的事件送出 message,因此若是隱私資料或資料更動,可能需要額外通過 popup 或其他手段來驗證。例如若收到轉帳的 message 而沒驗證的話,就有被盜取餘額的風險。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *