🦒

Safari web extensions CORSエラー回避してAPIリクエスト

2023/12/19に公開

はじめに

Safari web extensions (以降Safari拡張機能)を開発する機会があり、その中でAPIサーバへのリクエストが必要だったのでその方法の備忘録です。

開発の背景としては以下のような状況でした。

  • Safari拡張機能から自社APIサーバにリクエストをしたい。
  • APIサーバは、自社のフロントエンドからのみリクエストを許可するようにAccess-Control-Allow-Originを設定している。

Safari拡張機能の作成の仕方、概念は以下のサイトを参考にさせていただきました。

manifestのバージョンは3です。
https://developer.apple.com/documentation/safariservices/safari_web_extensions/creating_a_safari_web_extension
https://zenn.dev/h1d3mun3/articles/0bbcdcef81223c
https://qiita.com/oreo/items/c2c99c6fed7bec18c609
https://qiita.com/syu3/items/77ebb4e6193ec441a88d

結論

  1. manifest.json の host_permissions にドメインを設定する。
  2. background.jsからリクエストする。
manifest.json
// 一部抜粋
    "host_permissions" : [
        "*://api.example.com/*"
    ]
manifest.json全体
{
    "manifest_version": 3,
    "default_locale": "en",

    "name": "__MSG_extension_name__",
    "description": "__MSG_extension_description__",
    "version": "1.0",

    "icons": {
        "48": "images/icon-48.png",
        "96": "images/icon-96.png",
        "128": "images/icon-128.png",
        "256": "images/icon-256.png",
        "512": "images/icon-512.png"
    },

    "background": {
        "service_worker": "background.js"
    },

    "content_scripts": [{
        "js": [ "content.js" ],
        "matches": [ "*://example.com/*" ]
    }],

    "action": {
        "default_popup": "popup.html",
        "default_icon": {
            "16": "images/toolbar-icon-16.png",
            "19": "images/toolbar-icon-19.png",
            "32": "images/toolbar-icon-32.png",
            "38": "images/toolbar-icon-38.png",
            "48": "images/toolbar-icon-48.png",
            "72": "images/toolbar-icon-72.png"
        }
    },

    "permissions": [ ],
    "host_permissions" : [
        "*://api.example.com/*"
    ]
}

補足

まず、Safari拡張機能の開発においてJSを実行できる箇所は以下の3箇所になります。

  • ポップアップ領域 (popup.js)
    Safariメニューから拡張機能を表示した際に実行される。

  • コンテントスクリプト (content.js)
    指定したURLのWEBページに対してスクリプトを挿入できる。
    DOM API を使用してページのコンテンツを読み取り、変更することができる。

  • サービスワーカー (background.js)
    iosでSafari拡張をONにしている間、バックグラウンドで実行される。

※ popup.js、content.js、background.jsはSafari拡張機能の雛形作成時にデフォルトで作成されるファイル名です。manifest.jsonに定義してあげれば別のファイル名を使用することができます。

popup.js で実行

popup.jsからリクエストするとCORSエラーになりました。
Safari拡張にドメインのようなものが割り振られているみたいです。

popup.js
console.log("Hello World!", browser);

const fetchData = async () => {
  const res = await fetch('https://api.example.com');
  const data = await res.json();
  return data;
}

setTimeout(async () => {
  const data = await fetchData();
  console.log(data); // Origin safari-web-extension://xxxxxxxx-0000-1111-2222-xxxxxxxxxxxx is not allowed by Access-Control-Allow-Origin. 
}, 10000) // 開発者ツールのコンソールでログを見るために10秒後にAPIリクエストするようにしてます。

content.js で実行

content.jsにからリクエストした場合もCORSエラーになりました。
スクリプトを挿入したWEBページからのリクエストとみなされています。

content.js
browser.runtime.sendMessage({ greeting: "hello" }).then(async (response) => {
  console.log("Received response: ", response);
  const data = await fetchData();
  console.log(data); // Origin https://example.com is not allowed by Access-Control-Allow-Origin
});

const fetchData = async () => {
  const res = await fetch('https://api.example.com');
  const data = await res.json();
  return data;
}

background.js で実行

manifest.jsonでAPIサーバのドメインを指定して、background.jsに処理を書いたところ正常にリクエストすることができました。

background.js
browser.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
  console.log("Received request: ", request);
  const data = await fetchData();
  console.log(data); // json object is displayed.

  if (request.greeting === "hello")
      sendResponse({ farewell: "goodbye" });
});

const fetchData = async () => {
  const res = await fetch('https://api.example.com');
  const data = await res.json();
  return data;
}

background.jsが起動している(であろう)サービスワーカーに関して
あまり理解できていないですが、WEBページの裏側で動かせるイベント駆動のJavaScript環境のようです。
実行される環境が通常のWEBページと異なるためCORSの影響は受けない。程度の認識です。
正確な説明ができる方ご教授ください。

補足2

サービスワーカーではXMLHttpRequestは使用できないみたいです。
実際に使用を試したところ以下のエラーが出力されました。

const xhr = new XMLHttpRequest(); //ReferenceError: Can't find variable: XMLHttpRequest

https://stackoverflow.com/questions/37112425/xmlhttprequest-within-service-worker
https://xhr.spec.whatwg.org/#xmlhttprequest

Discussion