LoginSignup
163
137

More than 3 years have passed since last update.

Firebaseの小ネタ集

Last updated at Posted at 2018-02-22

Firebase で開発する上で便利なテクニックをいくつか紹介します。

私は Firebase を Web でしか使っていないので,Android or iOS で使っている人に有用な情報はほとんどありません。予めご了承下さい。

Firebase JavaScript SDK の初期化用 API key をハードコーディングしない方法

Firebase JavaScript SDKを初期化するときに以下のようなコードを書くと思います。

// https://firebase.google.com/docs/web/setup?hl=ja#add_firebase_to_your_app
var config = {
  apiKey: "<API_KEY>",
  authDomain: "<PROJECT_ID>.firebaseapp.com",
  databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
  storageBucket: "<BUCKET>.appspot.com",
  messagingSenderId: "<SENDER_ID>",
};
firebase.initializeApp(config);

開発用と本番用で2つのFirebaseプロジェクトを使っている場合などで,API_KEY などをハードコーディングしたくない場合があります。このような場合にはwebpackの機能を使って API_KEY などを環境変数から流し込む方法もありますが,Firebase Hostingを使っている場合には,もっと簡単な方法があります。それは /__/firebase/init.json に設置されているJSONを使うことです。

このJSONはFirebase Hostingの本番環境と firebase serve で提供されています。たとえば
FirebaseUI Demo のページの場合だと https://fir-ui-demo-84a6c.firebaseapp.com/__/firebase/init.json にアクセスすることでSDKの初期化に必要な情報を取得することができます。例として,以下のように初期化することができます。

const resp = await fetch('/__/firebase/init.json');
const config = await resp.json();
firebase.initializeApp(config);

注意しなければならないのは,API_KEY をハードコーディングしていた場合には firebase.initializeApp(config) までに非同期的な処理が含まれなかったのに対し,fetchで /__/firebase/init.json を取ってくる場合には非同期的な処理となってしまうことです。SDKの初期化が必要な処理を firebase.initializeApp(config) を呼び出す前に行わないような設計にする必要があります。

ところで, API_KEY が他の人に見えないようにするために工夫している人をたまに見かけるのですが,Firebase Hostingを使っている限りは /__/firebase/init.json にアクセスするだけで手軽に見えてしまうので意味が無いです。

この情報は以下のページで紹介されています。

Firebase JavaScript SDK で必要な機能だけインクルードする

Firebase JavaScript SDKは非常にサイズが大きいので,全機能をインクルードしてwebpackやbrowserifyを使うと生成後のJavaScriptが1MBを超えてしまうこともあります。以下のように書くと,必要な機能だけインクルードすることができ,生成後のJavaScriptのサイズを小さくすることができるので活用しましょう。

import * as firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';

この情報は以下のページで紹介されています。

Cloud Functions から Cloud Storage にバケット名を省略して接続する

Cloud FunctionsからCloud Storageを使う場合には gcs.bucket(bucketName) のようにバケット名を指定しなければならないと思ってしまいがちですが(サンプル がそうなっているので),Cloud Functionsから使う場合にはAdmin SDKを初期化した時点でプロジェクトに紐付けられているバケットに簡単にアクセスできます。

import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
admin.initializeApp(functions.config().firebase);
const bucket = admin.storage().bucket();

Firebase Admin SDKのリファレンスをよく読むと,バケット名がoptionalになっています。

(非推奨)Cloud FunctionsからCloud Storage上のファイルの公開用URLをgetSignedUrlを使わずに取得する

Firebase のクライアント用の SDK では,Cloud Storage 上のパスを与えることで getDownloadURL() によって簡単に公開用URLを取得することができます。

// https://firebase.google.com/docs/storage/web/download-files?hl=ja#download_data_via_url
const url = await storageRef.child('images/stars.jpg').getDownloadURL()
document.querySelector('#myimg').src = url;

しかしながら,Cloud Functions用のAdmin SDKではクライアント用SDKほど簡単にURLを取得することができません。File#getSignedUrl というメソッドを呼ぶ必要があって,サービスアカウントの設定公開用URLの有効期限の設定 が必要です(チュートリアルとしては 公式のFirecasts がおすすめです)。

これはクライアント用SDKと比べてかなり面倒だし,サービスアカウントの管理も避けたいところですが,裏技として次のような方法があります。Cloud Storage for Firebase上のpublicなファイルのURLは以下のコードで取得することができます。

`https://firebasestorage.googleapis.com/v0/b/${bucketName}/o/${encodeURIComponent(filePath)}?alt=media`

この裏技はドキュメントに載っていないのであまりおすすめできませんが,便利なのは確かです。この裏技はStackOverflowの以下の投稿で知りました。

Cloud Storage にアップロードするときはメタデータを適切に設定しよう

Cloud Storageにアップロードしたファイルは,何もしないと 'Cache-Control' と 'Content-Type' が設定されていません。そのため,画像ファイルが全くキャッシュされずにスマホの通信料が増えてしまったり,画像ファイルのURLをクリックしたら 'Content-Type' がデフォルトの 'application/octet-stream' になっていてブラウザで表示されずにダウンロードされてしまったりします。

ファイルのメタデータは,クライアントから ref.put(file) するときに設定できたり,Cloud Functionsから bucket.upload するときに設定できたりします。

設定可能・取得可能なメタデータの一覧は以下のページで確認できます。

Cache-Control の適切な設定は,少し時間を確保してしっかり学習することをおすすめします。ISUCON7予選の解説記事なども参考になります。

Cloud Storage でファイルの変更・削除を禁止する

セキュリティルールを記述するときに,Firestore では allow XXX の部分に read, get, list, write, create, update, delete を指定することができる (Cloud Firestore Security Rules Reference) のに対し,Cloud Storage では read と write というざっくりとした指定しか用意されていません (Firebase Security Rules for Cloud Storage Reference)。

Cloud Storageのセキュリティルールでdeleteを禁止するには以下のように指定すれば良いようです。

allow write: if resource == null

Cloud Functions で multipart/form-data のリクエストを扱う

Cloud Functions の HTTP トリガーではリクエストはExpressのミドルウェア (body-parser) で処理されますが,multipart/form-data だけは処理してくれません。どうしても multipart/form-data を扱いたい場合には busboy というモジュールを使う方法があります。

やり方は Google Cloud Platform のほうの Cloud Functions のドキュメント に載っています。ほとんど Firebase のドキュメントと同じなのに,なぜかこの内容だけ Google Cloud Platform のほうのドキュメントにしか載っていないという罠にはまりました。

私の実験では数MB以上のファイルを Cloud Functions の HTTP トリガーで処理すると非常に不安定な状況になりました。ドキュメントにもあるように,このような場合にはCloud Storage経由でデータをアップロードするほうが適切なようです。

163
137
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
163
137