Safari web extensions CORSエラー回避してAPIリクエスト
はじめに
Safari web extensions (以降Safari拡張機能)を開発する機会があり、その中でAPIサーバへのリクエストが必要だったのでその方法の備忘録です。
開発の背景としては以下のような状況でした。
- Safari拡張機能から自社APIサーバにリクエストをしたい。
- APIサーバは、自社のフロントエンドからのみリクエストを許可するようにAccess-Control-Allow-Originを設定している。
Safari拡張機能の作成の仕方、概念は以下のサイトを参考にさせていただきました。
manifestのバージョンは3です。
結論
- manifest.json の host_permissions にドメインを設定する。
- background.jsからリクエストする。
// 一部抜粋
"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拡張にドメインのようなものが割り振られているみたいです。
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ページからのリクエストとみなされています。
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に処理を書いたところ正常にリクエストすることができました。
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
Discussion