Firebase概要まとめ JavaScriptサンプル付き

## はじめに
- PoC用にちょっとしたWebアプリをすばやく開発したくFirebaseについて調べ始めたのでまとめていこうと思います
  - Webアプリ(JavaScript)で使えないところはあまり詳しく調べていません
- Authentication、Cloud、Firestore、Cloud Storage、Cloud Messagingを使ったサンプルコードを書いてみました
  - ついでにHostingの手順も書きました
- Firebaseの設定方法も書いてあるのでこの記事を読めばFirebaseを使った開発を始められると思います
- 主に[Firebase公式サイト](https://firebase.google.com/)を読んでまとめているので一通り読まれた方にとっては新しい情報がないかもしれません

## Firebaseとは
- BaaS, mBaasと呼ばれるWebアプリやモバイルアプリに必要なバックエンド機能を提供してくれるサービスです
  - (mobile) Backend as a Serviceの略
- ログイン、プッシュ通知、DBなどの機能を提供してくれます
- 各機能はプロダクトと呼ばれる単位に分かれており必要な機能だけを選んで利用することができます

## 対応プラットフォーム（SDK）
- iOS
- Android
- Web(クライアントサイド)
- Unity
- C++
- Admin SDK(Node.js、Java、Python、Go、C#)
  - サーバサイド用途です
  - 秘密鍵を使って認証します
  - GCP内であればサービスアカウントも利用できるようです

## JavaScriptサンプルコード
- サンプルコードはCodeSandboxに置いてあります
- https://codesandbox.io/s/m5xzoz6ro9
- Authentication、Cloud Firestore、Cloud Storage、Cloud Messagingが試せます
  - Cloud MessagingはファイルをアップロードするとPush通知が飛んできます
    - WindowがフォアグランドかバッググランドかによってPush通知の受け取り方が変わります
- src/_firebase.jsの以下部分を部分を書き換えれば任意のプロジェクトで動かせると思います
- Firebaseのプロジェクトページから「アプリを追加」をクリックするとそのプロジェクトのキー情報などが取得できます
- バグっていたらすみません

```javascript:src/_firebase.js
var config = {
      apiKey: "",
      authDomain: "",
      databaseURL: "",
      projectId: "",
      storageBucket: "",
      messagingSenderId: ""
    };
```

---
## 料金プラン
- 制限や上限はありますが無料で全てのプロダクトを利用できます
- サービスの規模に応じて段階的にプランをアップグレードできます
- 詳細は[公式ページ](https://firebase.google.com/pricing/)を参照してください

### Sparkプラン
- 無料

### Flameプラン
- $25/月

### Blazeプラン
- 従量制


---
## Firebaseプロダクトの説明
### Authentication
- 認証サービスです
- FirebaseUIというUIも提供しています
- 認証方法は以下になります
    - メール/パスワード
    - フェデレーションID（Google、Facebook、Twitter、GitHub）
    - 電話番号
    - 既存認証システムとの統合
    - 匿名認証（プッシュ通知などに使えるらしい）
- JWT(JSON Web Token)を利用します
- 認可はCloud Firestoreなどの他プロダクト内でセキュリティルールを設定して行います

### Realtime Database
- NoSQLクラウドデータベースです
- オフラインサポートはiOS、Androidのみ
- クエリはシンプルなものしかないようです
- Cloud FirestoreとRealtime Databaseはどちらか一方しか使えません

### Cloud Firestore
- NoSQLクラウドデータベースです
- オフラインサポートはiOS、Android、Webとなるようです
- Realtime Databaseよりリッチなクエリを使えます
- オートスケーリングを備えています
- 今後はこちらが主流になるはず~~ですが、2018年12月時点ではBetaになっています~~
    - 2019年1月末にBetaが終わりGAとなりました！https://firebase.googleblog.com/2019/01/cloud-firestore-in-general-availability.html
- Cloud FirestoreとRealtime Databaseはどちらか一方しか使えません

### Hosting
- Webアプリのホスティングサービス
- CDNがついてくるようです
- SSLが提供されます
- 過去のデプロイにロールバックできる機能があります
- カスタムドメインに対応しています

### Cloud Functions for Firebase
- FaaS、AWSのLambdaみたいなやつです
  - トリガーの種類
      - Cloud Firestore
      - Realtime Database
      - Remote Config
      - Firebase Authentication
      - Google Analytics for Firebase
      - Crashlytics
      - Cloud Storage
      - Cloud Pub/Sub
      - HTTP
- 無料のSparkプランだと外部ネットワークにアクセスできません(外部APIなどを叩けません)

### Cloud Storage for Firebase
- クラウドストレージサービス、AWSのS3みたいなやつです
- Firebase SDKで直接アップロード、ダウンロードができます
- セキュリティルールを設定することでファイルの公開、非公開を制御できます
- GCPのサービスと統合されておりGCPのStorageからもアクセスできるようです

### ML Kit
- 機械学習サービスです
- 予め用意されたモデルと独自のTensorFlowモデルを利用できます
- 予め用意された以下モデルがあります(一部はクラウドのみでの提供となります)
  - テキスト認識（OCR）
  - 顔検出
  - バーコードスキャン
  - 画像のラベル付け
  - ロゴ認識
  - ランドマーク認識
  - 不適切なコンテンツの検出
  - 類似画像の検索
- カスタムされたTensorFLowモデルを利用することもできるようです

### Cloud Messaging(FCM)
- リモートPush通知です
- Pushの送り方は3パターンあります
  - 個々のデバイス
  - デバイスのグループ
  - 特定トピックの配信登録をしているデバイス
- WebはServiceWorker、Push APIを利用します
  - 一部のモダンブラウザでしか利用できません
  - iOSのブラウザは現時点ではPush APIに未対応です

### In-App Messaging
- Android、iOSのアプリ内Push通知です

### Crashlytics
- Android、iOSのクラッシュレポートです

### Performance Monitoring
- Android、iOSのパフォーマンスモニタリングです

### Test Lab
- Android、iOSのクラウドテスティングです

### Google Analytics for Firebase
- GAのモバイルアプリ版です

### Predictions
- ユーザの行動ログからクラスタリングしてアプリの挙動を変えられるサービスのようです
- アナリティクスデータに機械学習を適用し、予測された行動に基づいて動的にユーザーグループを作成できるようです
- Remote Configと組み合わせて使うものだと思われます

### A/B Testing
- Android、iOSのA/Bテストです
- アプリの見た目、動作、Push通知を切り替えられるようです
- Remote Configと組み合わせて使うものだと思われます

### Remote Config
- アプリの見た目、動作の設定を持つことができます
- アプリのアップデートなしに切り替えられます
- Predictions、A/B Testing、FCMと組み合わせて使うものだと思われます

### Dynamic Links
- アプリインストールの有無にかかわらず、共通で使えるリンクの提供してくれます
- インストールされていればアプリが開き、そうでなければブラウザが開きます

### App Indexing
- Google検索結果にアプリを表示することできるようになります
- アプリをインストールしていればアプリが開く、そうでなければアプリのインストールカードが表示されます

## プロダクトとSDKの対応表(2019年2月時点)

|    プロダクト    |    Beta    |    料金    |   iOS    |    Android    |    Web    |    Unity    |    C++    |    Admin    |
|:-------------:|:----------:|:----------:|:--------:|:-------------:|:---------:|:-----------:|:---------:|:-------------:|
| Authentication |  | 無料 | ○ | ○ | ○ | ○ | ○ | ○ |
| Cloud Firestore |  | 規模に応じて課金 | ○ | ○ | ○ |  |  | ○ |
| Realtime Database |  | 規模に応じて課金 | ○ | ○ | ○ | ○ | ○ | ○ |
| Hosting |  | 規模に応じて課金 |  |  | ○ |  |  |
| Cloud Functions for Firebase |  | 規模に応じて課金 | ○ | ○ | ○ | ○ | ○ | ○ |
| Cloud Storage for Firebase | | 規模に応じて課金 | ○ | ○ | ○ | ○ | ○ | ○ |
| ML Kit | ○ | 規模に応じて課金 | ○ | ○ |  |  |  |
| Cloud Messaging |  | 無料 | ○ | ○ | ○ | ○ | ○ | ○ |
| In-App Messaging |  | 無料 | ○ | ○ |  |  |  |
| Crashlytics |  | 無料 | ○ | ○ |  |  |  |
| Performance Monitoring |  | 無料 | ○ | ○ |  |  |  |
| Test Lab |  | 規模に応じて課金 | ○ | ○ |  |  |  |
| Google Analytics for Firebase |  | 無料 | ○ | ○ |  | ○ | ○ |  |
| Predictions | ○ | 無料 | ○ | ○ |  | ○ | ○ | |
| A/B Testing | ○ | 無料 | ○ | ○ |  |  |  |
| Remote Config |  | 無料 | ○ | ○ |  | ○ | ○ |  |
| Dynamic Links |  | 無料 | ○ | ○ | ○ | ○ |  |
| App Indexing |  | 無料 | ○ | ○ |  |  |  |

--- 

## Firebaseのプロジェクト設定方法
### プロジェクトの作成
- プロジェクトを作成します
![prj1.png](https://qiita-image-store.s3.amazonaws.com/0/3507/5c6662e1-b4f7-25bd-8e33-b616db2bb1a6.png)
- プロジェクトにアプリを追加するをクリックしてキー情報など控えておきます
  - この情報は第三者に見られても大丈夫なものです
![prj4.png](https://qiita-image-store.s3.amazonaws.com/0/3507/da32b510-69bc-c7f0-6fae-0d8223e6c875.png)

### Authentication
- Authenticationのページでログイン方法の設定をします
- メール/パスワードを有効にします
![auth1.png](https://qiita-image-store.s3.amazonaws.com/0/3507/f800826c-6b73-7d60-7735-deea7f1b259f.png)

### Firestore
- データベースの作成をクリックしてロックモードで開始を選択して有効にします
![firestore1.png](https://qiita-image-store.s3.amazonaws.com/0/3507/47cf785a-c3ec-e768-f3b3-43b44f92f3b2.png)

- セキュリティルールを設定します
![firestore2.png](https://qiita-image-store.s3.amazonaws.com/0/3507/69c4bf5b-e2f9-168c-a358-5957f90751ec.png)

- これはusersとmessage-tokensというデータのコレクションに対してセキュリティルールを設定しています
  - 更新、削除はデータを作成したユーザのみができるとなっています
  - データ追加は認証中のユーザのみができるとなっています

```
service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches the 'author_id' field
    // of the document
    match /users/{user} {
      allow read, update, delete: if request.auth.uid == resource.data.author_id;
      allow create: if request.auth.uid != null;
    }
    match /message-tokens/{user} {
      allow read, update, delete: if request.auth.uid == resource.data.author_id;
      allow create: if request.auth.uid != null;
    }
  }
}
```
- ルールを設定したら公開ボタンを押して有効にします

### Cloud Storage
- 同じようにセキュリティルールを設定します
![storage.png](https://qiita-image-store.s3.amazonaws.com/0/3507/92db87c9-54aa-3421-a371-afe53aad883f.png)

- これは/users/{ユーザID}/配下にファイルを読み書きできるのはそのユーザのみというルールを設定しています

```
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/{all=**}  {
      allow read, write: if request.auth.uid == userId;
    }
  }
}
```
- ルールを設定したら公開ボタンを押して有効にします

### Cloud Messaging
- Project Overviewの右にあるアイコンからプロジェクトの設定ページへ行きます
![fcm1.png](https://qiita-image-store.s3.amazonaws.com/0/3507/6cd8723c-e13d-1124-5eac-0c444316b8f7.png)

- 動作確認用にクラウドメッセージングの下にあるWebプッシュ証明書の鍵ペアを生成しておきます
![fcm3.png](https://qiita-image-store.s3.amazonaws.com/0/3507/d6a2f3f9-2fd8-29cc-6577-62e6547ef094.png)
- 表示されている鍵ペアの値をJavaScriptコードのmessaging.usePublicVapidKeyに渡す必要があります

- これは公開鍵なのでクライアントコードに書いてあっても大丈夫です（なぜかモザイクかけてしまいました）
  - 逆に上にあるサーバーキーは見られたらまずいやつです

```javascript:src/_messaging.js
messaging.usePublicVapidKey("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
```


- firebase-messaging-sw.jsのmessagingSenderIdに送信者IDに表示されている値を設定します

```javascript:manifest.json
firebase.initializeApp({
  messagingSenderId: "XXXXXXXXXXXXXXX"
});
```

- manifest.jsonにも同じようなID gcm_sender_idを設定する必要がありますが、これはFirebase全体で固定の値となるので変更したらだめなようです

```javascript:manifest.json
{
  "gcm_sender_id": "103953800507"
}
```
- またサーバキーを使ってcurlコマンドでPush通知が送れます
  - KEYとTOKENを設定する必要があります
  - サンプルコードに含まれるPush通知送信の準備が終わっている必要もあります（ServiceWorkerなど）

```bash
curl -X POST -H "Authorization: key=${SERVER_KEY}" -H "Content-Type: application/json" -d '{
"notification": {
 "title": "FCM Message",
 "body": "This is an FCM Message",
 "icon": "/images/icon.png",
 "click_action": "https://localshot:1234/"
},
"to": "${TOKEN}"
}' "https://fcm.googleapis.com/fcm/send"
```

### Cloud Function
- ここは黒い画面が必要になります
- npmは入っている前提で以下コマンドを実行します
- 色々聞かれると思いますが作成したプロジェクトを選択して後はデフォルトを選んでいきます

```bash
npm install -g firebase-tools
mkdir firebase
cd firebase
firebase login
firebase init functions
```

- 以下のようなコードを書きます
  - Cloudストレージのファイル作成をトリガーとして起動
  - FirestoreからPushメッセージのデバイストークンを取得（トークンはクライアント側からFirestoreに登録してある前提）
  - デバイスに向けてPush通知を送信

```
vi functions/index.js
```

```javascript:functions/index.js
const functions = require('firebase-functions');

function sendMessage(admin, registrationToken) {
  // See documentation on defining a message payload.
  var message = {
    "notification": {
      "title": "FCM Message",
      "body": "This is an FCM Message"
    },
    "token": registrationToken
  };
  console.log(message);
  admin.messaging().send(message)
    .then((response) => {
      // Response is a message ID string.
      console.log('Successfully sent message:', response);
      return true;
    })
    .catch((error) => {
      console.log('Error sending message:', error);
      return false;
    });
}


exports.sendPushNotification = functions.storage.object().onFinalize((object) => {
  console.log(object);
  console.log(object["name"]);
  const userId = object["name"].split("/")[1];

  const admin = require('firebase-admin');
  const functions = require('firebase-functions');
  if (!admin.apps.length) {
    admin.initializeApp(functions.config().firebase);
  }
  var db = admin.firestore();

  db.collection('message-tokens').doc(userId).get()
    .then(doc => {
      if (!doc.exists) {
        console.log('No such document!');
      } else {
        console.log('Document data:', doc.data());
        sendMessage(admin, doc.data()['message_token']);
      }
      return true;
    })
  .catch(err => {
    console.log('Error getting document', err);
    return false;
  });

  return 0; // See This Link: https://stackoverflow.com/questions/47128440/google-firebase-errorfunction-returned-undefined-expected-promise-or-value?rq=1
});
```

- 書き終わったらデプロイを行います

```
firebase deploy
```
- 以上でFirebaseのプロジェクト設定は終わりです


### Hosting
- 既にCodeSandboxでは動くようになっていたのですが ついでHostingの手順も試してみました
- CodeSanxboxからプロジェクトファイルをDLして展開したところからの手順になります

```bash
cd firebase-example
firebase init
# Hostingを選択
# 作成したプロジェクトを選択
# distを入力
# あとはデフォルトでOK
npm run build
cp firebase-messaging-sw.js manifest.json dist # Pushに必要ファイルをコピー
firebase deploy
```

- https://xxxxxxxxxx.firebaseapp.com/ のようなURLでFirebaseのホスティングしたページにアクセスできるようになります

## おわりに
- 間違いがあったら編集リクエストをもらえると助かります
- CodeSandboxのコードはたまにいじると思うのでタイミングによってバグりまくってるかもしれません
- 時間があったらReact/Vueのサンプルも書いてみたいですね