Firebase Functions + TypeScript で CORS を使用する

Web API のプロトタイプを Firebase Functions の http ハンドラーを使って実装して、それを JavaScript から呼び出したら、 No 'Access-Control-Allow-Origin' header のエラーが出たので、それに対応した手順を書きます。

まず

Firebase Functions を REST API のように 即時に応答を要求する用途 に使うのはほとんどの場合間違っています。一般的に、クラウドプラットフォームで提供される "Functions" と呼ばれる機能は、起動は遅いと考えたほうがよいです。

Functions

次のような say 関数を作りました、TypeScript で。

import * as functions from 'firebase-functions';
export const say = functions.https.onRequest((request, response) => {
    response.send("Hello from Firebase!");
});

サンプルまんまです。

firebase serve をして、ローカルで動作させると、 http://localhost:5001/<project名>/us-central1/say のような URL で起動できます。

HTML

これを呼び出す HTML を次のように書きました。 この index.html は Firebase Hosting に配置するので、 public ディレクトリに置きます。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Firebase Hosting</title>
    <script type="text/javascript">
    function loadHello() {
      var label = document.getElementById("label");

      var req = new XMLHttpRequest();
      req.onreadystatechange = function() {
        if (req.readyState == 4) { 
          if (req.status == 200) {
            label.innerHTML = req.responseText;
          }
        } else {
          label.innerHTML = "通信中...";
        }
      }
      
      req.open('GET', 'http://localhost:5001/<プロジェクト名>/us-central1/say', true);
      req.send(null);
    }
    </script>
  </head>
  <body onLoad="loadHello()">
    <H1>Firebase Hosting</H1>
    <div id="label">loading..</div>
  </body>
</html>

こちらも firebase serve しているときに http://localhost:5000/ でアクセスできます。

*No Access-Control-Allow-Origin エラー

index.html は、読み込み時に Functions の /say を呼び出して、そのレスポンスを id=label に表示する、というものですが、読み込み時に Console にエラーが出ます。

Failed to load http://localhost:5001/xxxx/us-central1/say: 
No 'Access-Control-Allow-Origin' header is present on the requested resource. 
Origin 'http://localhost:5000' is therefore not allowed access.

よくあるやつです。

Functions で CORS を使う

Functions を CORS に対応させる(別ドメインからの呼び出しを許可する)には、expressjs/cors: Node.js CORS middleware という node.js 用ライブラリを使います。今回は TypeScript なので、これの type definitions である

を使います。

まずは cors と @types/cors をインストールします。

npm install --save cors
npm install --save-dev @types/cors

functions/package.json は次のようになっています。

{
  "name": "functions",
  "scripts": {
    "lint": "tslint --project tsconfig.json",
    "build": "tsc",
    "serve": "npm run build && firebase serve --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "main": "lib/index.js",
  "dependencies": {
    "cors": "^2.8.4",
    "firebase-admin": "~5.12.1",
    "firebase-functions": "^1.0.3"
  },
  "devDependencies": {
    "@types/cors": "^2.8.4",
    "tslint": "^5.8.0",
    "typescript": "^2.5.3"
  },
  "private": true
}

続いて Functions を次のように書き換えます。

import * as functions from 'firebase-functions';
import * as corsLib from 'cors';
const cors = corsLib();

export const say = functions.https.onRequest((request, response) => {
    return cors(request, response, () => {
        response.send("Hello from Firebase!");
    })
});

cors を import して関数として実行。 さらに onRequest の中を、 cors(req, res, ()=>{ }) で包んじゃいます。

これで html を表示させると、エラーは消え、次のような画面が表示できます。

image.png