Chrome拡張機能からGoogle Apps Script (GAS)にアクセスして正常に通信できるようになった重要なポイントをまとめます。これを参考にすることで、今後同様の実装で問題を回避できるでしょう。
1. アーキテクチャの理解
Content ScriptとBackground Script (Service Worker)の役割分担
- Content Script: ウェブページのコンテキストで実行され、直接外部APIにアクセスする際にCORS制限がある
- Background Script: 拡張機能のコンテキストで実行され、manifest.jsonで設定した権限に基づいて外部APIにアクセス可能
正しい通信フロー
- Content Script → Background Script (メッセージ送信)
- Background Script → GAS API (fetch実行)
- GAS API → Background Script (レスポンス)
- Background Script → Content Script (結果返送)
2. GAS側の実装ポイント
適切なエンドポイント関数の実装
GET要求: doGet() 関数を実装
POST要求: doPost(e) 関数を実装(e.postData.contentsでPOSTデータを取得)
正しいレスポンス形式
return ContentService.createTextOutput(JSON.stringify(data))
.setMimeType(ContentService.MimeType.JSON);
デプロイ時の注意点
新しいデプロイ作成: コード変更後は「新しいデプロイ」を作成
最新バージョン選択: デプロイ時に最新バージョンを選択
アクセス権限設定: 「全員(匿名含む)」に設定
URLの確認: デプロイ後の正確なURLを使用(/execで終わるURL)
3. Chrome拡張機能側の実装ポイント
manifest.jsonの設定
{
"host_permissions": [
"https://script.google.com/*",
"https://script.googleusercontent.com/*"
],
"background": {
"service_worker": "service-worker.js"
}
}
Background Script (Service Worker)の実装
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'fetchGAS') {
fetch(message.url, {
method: message.method || 'GET',
headers: {
'Content-Type': 'application/json'
},
body: message.data ? JSON.stringify(message.data) : undefined
})
.then(response => response.text())
.then(text => {
sendResponse({success: true, data: text});
})
.catch(error => {
sendResponse({success: false, error: error.message});
});
return true; // 非同期レスポンスのために必須
}
});
Content Scriptからの呼び出し
const response = await new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
{
type: "fetchGAS",
url: gasUrl,
method: "POST", // または "GET"
data: dataToSend // POSTの場合のみ
},
response => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else if (!response.success) {
reject(new Error(response.error));
} else {
resolve(response.data);
}
}
);
});