firebase emulatorの躓きポイント
Q. デモプロジェクトの利用を推奨しますとドキュメントに書かれているがデモプロジェクトって何?
A. firebase emulators:start --project demo-xxxxのようにemulator起動時にdemo-をつけると、実際のprojectがgcp上に存在しなくてもemulatorを起動することができる。これによって、firebaseプロジェクトを実際に用意しなくても開発を始めることができる(が、このproject idによってemulatorの設定項目が連動する箇所があるので、ある程度マニュアル設定が必要になる。)
Q. websdkのfunctionsがlocalhostではなくapiのエンドポイントを見に行ってしまうんだが?
A. connectFunctionsEmulatorだけfunctionsの設定がregionにも依存していることに注意、なので、ドキュメントにもconst app = getApp(); connectFunctionsEmulator(getFunctions(app))のようにappをわざわざ取得して初期化するように優しく(?)書いている。ただし、regionの引数を入れない場合、defaultの関数がus-east1になるので、getFunctionsの第二引数にregionを入れよう。取得する関数のregionが正しく一致していないとgoogle cloudのエンドポイントにリクエストが飛んでしまうぞ。httpsOnCallの取得前にconnectFunctionsEmulatorを実行しよう。実行後getFunctionsで取得されるインスタンスのemulatorOriginがlocalhostになった状態でhttpsOnCallでクライアントAPIを取得するとエミュレータにリクエストが飛ぶようになる。
Q. Firebase Emulatorのstorageやfirestoreのダッシュボードにアクセスできないよ〜!
A. rulesにエラーだけでなく、warningがあるだけでもruleのエクスポートが失敗する。起動時のログをよく見てみよう。さらにunused valiableやunused rule functionでもwarningに引っかかるので 本番環境にデプロイできているルールが正しく読み込まれるとは限らないぞ! 注意しよう。
Q. デモプロジェクトを使っている時に実際のプロジェクトの認証情報を使ってweb sdkやadmin sdkを初期化すると、realtime databaseのレコードがデモプロジェクトのdefault-rtdbじゃなくて、違うDBが作成されてしまいます。
A. initializeAppに実際の認証情報を入れて初期化すると、デモプロジェクトのデフォルトDBではなく、認証情報のdatabaseUrlを元に操作先のDBを決めてしまうぞ。なのにこれtypescriptのrequired パラメータになっているので、emulatorを使用したい場合でinitializeAppの仕方を条件分岐して、projectIdとdatabaseUrlを書き換えてしまおう。
Q. rulesが読み込まれているのにDBに反映されていない旨のエラーがブラウザで出ます。
A. デモプロジェクトを使っているなどの理由で、エミュレータのrulesが適用されているDBがデフォルトDBになっているけど、認証情報を読み込んでいるせいで接続先がデフォルトDBになっていない可能性があるぞ!curl 'localhost:9000/.settings/rules.json?ns=dbname' -H 'Authorization: Bearer owner'でDBに適用されているルールを調べてみよう。
Q. pulishTopicしたい場合はどうしたらいい?
A. firebase emulatorのpub subエミュレータはgloudのpubsubエミュレータと同じものらしい。
なので
export PUBSUB_EMULATOR_HOST="localhost:8085" # pubsub
を設定した状態で@google-cloud/pubsubのAPIを呼び出すとpubsubエミュレータに向けて操作ができるぞ。君の目で確かめてみてくれ!
Q. デモプロジェクト使用時にhttpOnCall関数が呼べません。
A. もしかして実在の認証情報でinitializeAppしているのではないでしょうか。その場合、proojectIdがデモプロジェクトではなく、localhost:5001(等)/(initializeされたprojectId)/エミュレータのapiごとのURLというパスにリクエストが飛んでいるはず。ブラウザのnetworkタブからリクエスト先のURLを確認してくれ。これが原因であれば、initializeApp時のprojectIdにデモプロジェクトのidを入れれば解決します。ブラウザにはCORSエラーと出るので分かりづらい。
Q. メールリンク認証で作ったアカウントがAuthのダッシュボードに表示されない
A. version(10.8.0)時点での仕様です。実際にパスワード認証でアカウントを作らない限りユーザーは表示されません。さらにパスワード認証で作成されたユーザーと、このメールリンクを送るために作成したユーザーのuidは別のものが生成され、customClaimsも引き継がれません!残念だったな!!!
パスワードがないユーザーは内部ではエミュレータが終了するまで保持されるが、exportもされないぞ。メールリンクのログインを行うためにsendSignInMailLinkを実行するとサーバのコンソールにメール送信の代わりにメールログインのリンクが生成されるからこのリンクをブラウザに打ち込んでAuthフローを行うと、ユーザー作成までのcallback urlのハンドリングは適切に行なってくれる。
メールリンクログインの検証のために作成したアカウントはあくまでメールリンクのログインフローを確かめるために使うもので、パスワードを発行しないと消えてしまうので、実際にログインしてアプリケーションのテストを行うためのアカウントはパスワードを生成しておこう。メールリンクでしかログインを許可していないアプリケーションを扱う場合はどうしたらいいの?わかりません。
デモプロジェクト推奨なのに罠が多すぎ...。
開発環境を後からエミュレータに移行しようとすると結構大変かつ微妙に実際の環境と等価ではなかったり、(cloud task)のような他のGCPサービスや(Algoliaのような)外部サービスを使っているとモックしないとローカルで開発を完結させるのが難しいケースもあるので、無理に移行をせずにテストにだけ用いるなどの割り切りをしてもいいんじゃないかなと思います。
admin-sdkの場合は環境変数をいれて制御するのがおすすめ。
export GCLOUD_PROJECT="demo-xxxx"
export FIREBASE_STORAGE_EMULATOR_HOST="localhost:9199" # cloud storage
export FIREBASE_AUTH_EMULATOR_HOST="localhost:9099" # auth
export FIREBASE_DATABASE_EMULATOR_HOST="localhost:9000" # realtime database
export FIRESTORE_EMULATOR_HOST="localhost:8080" # firestore
export PUBSUB_EMULATOR_HOST="localhost:8085" # pubsub
GCPマネージドサービス以外の環境でadimin-sdkを実行する必要がある場合のエミュレータの初期化は以下のようにdemoプロジェクトのデフォルトのstorageとdatabaseUrlなどを設定しておくと良い。
if (process.env.GCLOUD_PROJECT?.startsWith('demo-')) {
initializeApp({
storageBucket: `${process.env.GCLOUD_PROJECT}.appspot.com`,
databaseURL: `https://${process.env.GCLOUD_PROJECT}.firebaseio.com`,
})
}
web sdkではAPI Keyを入れないとエラーになるが、実際は接続先だけ設定できれば良いので、適当な値を入れ、環境変数などで条件分岐を行い、認証情報が使われないように初期化処理を分けておくと良い。
initializeApp({
apiKey: 'dummy',
projectId: `demo-${projectId}`,
databaseURL: `https://demo-${projectId}-default-rtdb.firebaseio.com`,
})
エミュレータのscheduler名はregionが省略される。
本番環境のschedulerを強制的にpublishMessageするような処理をテストしたい場合、トピック名が変更されている可能性があることに注意する。
cloud上
firebase-schedule-backend-eventTrigger-scheduler-sendIndex-asia-northeast1
ローカルエミュレータ
firebase-schedule-backend-eventTrigger-scheduler-sendIndex
emulatorはgetSignedUrlに対応していない。
これを利用しているエンドポイントはgetPublicUrlに差し替える必要がある。FIREBASE_STORAGE_EMULATOR_HOSTの有無で判断するのが良さげ。