Firebase + Google Cloud Platform (GCP) 環境でWebチャットを作成する。

#はじめに
　Firebase + Google Cloud Platform (GCP) 環境でWebチャットを作成する手順を紹介します。
　Googleが提供するサンプルアプリの[チュートリアル](https://codelabs.developers.google.com/codelabs/firebase-web/index.html#0)を元にしています。
　
　Webチャットの作成を通して、以下に示す内容を理解していきたいと思います。
　　(1) Firebaseの"Cloud Firestore"、"Cloud Storage"を使い、
　　　メッセージと画像を参照/追加する機能の実装方法
　　(2) Firebaseの"Firebase Auth"を使ったGoogle認証の実装方法
　　(3) Firebaseの"Firebase Hosting"を使って、Webアプリをインターネット上に公開する方法

　また、Appendixとして以下についても参考に載せたいと思います。
　　a) Firebaseの"Firebase Cloud Messaging"を使った通知機能の実装方法
　　b) 作成したWebアプリのパフォーマンスを確認する方法

#0. 前提条件
　Cloud Shellを使って進めていくので、事前にGoogleアカウントを作成下さい。
　* Cloud Shellでは、GCP上のクラウドリソースをブラウザのCUIから直接操作できます。

#1. サンプルアプリの取得
　Cloud Shellを使って、GCP上にサンプルアプリを配置していきます。

##1-1. GCPにログイン
　[GCP](https://console.cloud.google.com)にログインして下さい。

##1-2. Cloud Shellの起動
　「Cloud Shellをアクティブにする」ボタンをクリックして下さい。
　<img width="901" alt="1-1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/b4f8101e-7cb6-7bd9-fc90-66a216f5e162.png">

　画面下部にCloud Shellが立ち上がり、
　"Welcome to Cloud Shell! Type "help" to get started."と
　メッセージが表示されます。

##1-3. サンプルアプリのコードを取得
　任意のパスにサンプルアプリのコードをクローンします。
　以下のコマンドを実行して下さい。

```php
$ git clone https://github.com/firebase/friendlychat-web
```

　GCP上に"friendlychat-web"が出来上がります。

#2. Firebaseプロジェクトの作成と設定
##2-1. Firebaseプロジェクトの作成
　(1) Firebaseのコンソールにログイン
　　[Firebaseのコンソール](https://console.firebase.google.com/?pli=1)にログインして下さい。

　　下記のようなページが開きます。
　　　![2-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/6ccb8a15-118e-4e57-9766-d5807fadc9bf.png)

　(2) 「プロジェクトを作成」ボタンをクリックして下さい。
　(3) プロジェクトの追加画面が開きます。
　　① プロジェクト名に"FriendlyChat"を入力して下さい。
　　② プロジェクト IDが自動で割り振られるので、その値をメモしておいて下さい。
　　③ 「測定管理者間のデータ保護条項に同意します。」にチェックを入れて下さい。
　　④ 「プロジェクトを作成」ボタンをクリックして下さい。
　(4) 次の画面が開きます。
　　① 「次へ」ボタンをクリックして下さい。
　　　<img width="500" alt="2-2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/f02af9b6-a36b-b138-e6b9-3fd200bf0470.png">

　(5) Firebaseプロジェクト "FriendlyChat"が作成されます。
　　<img width="500" alt="2-3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/31f51316-4f3e-de24-f2c5-7cc5ba66897d.png">

##2-2. Firebaseウェブアプリの作成
　(1) 「ウェブ」ボタンをクリックします。
　　<img width="500" alt="2-4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/c24083d1-2ccd-7dab-9668-a46ab0326da9.png">

　(2) ウェブアプリへの Firebase の追加画面が開きます。
　　① アプリのニックネームに"Friendly Chat"を入力して下さい。
　　② 「このアプリのFirebase Hostingも設定します」にチェックを入れて下さい。
　　③ 「アプリを登録」ボタンをクリックして下さい。
 
　Firebase SDK の追加、Firebase CLI のインストール、
　及びFirebase Hosting へのデプロイについては、Skipします。
　
　ウェブアプリへの Firebase の追加画面を閉じます。
　　<img width="500" alt="2-5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/5361a371-b127-d060-50fe-24ba9e2261f5.png">

##2-3. Google認証の有効化
　(1) 左ナビゲーションの開発 > Authenticationをクリックして下さい。
　　<img width="246" alt="2-6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/34905e36-47ed-5ead-9864-15cddb409719.png">

　(2) 「ログイン方法を設定」ボタンをクリックして下さい。
　(3) プロバイダから"Google"を選択して下さい。
　(4) Googleの設定画面が開きます。
　　① 「有効にする」をスライドして下さい。
　　② プロジェクトの公開名に"Friendly Chat"を入力して下さい。
　　③ プロジェクトのサポートメールに自身のメールアドレス（Gmail）を入力して下さい。
　　④ 「保存」ボタンをクリックして下さい。

##2-4. Cloud Firestoreの有効化
　Cloud Firestore を使ってチャットメッセージの保存と新しいチャットメッセージの受信を行います。

　(1) 左ナビゲーションの開発 > Databaseをクリックして下さい。
　　<img width="242" alt="2-7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/de6f3ccd-501c-e6ac-c27e-ed542b2c1e35.png">

　(2) 「データベースの作成」ボタンをクリックして下さい。
　　<img width="500" alt="2-8.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/54389196-5333-e504-43bb-4a3773e6582c.png">

　(3) データベースの作成画面（セキュリティ保護ルールタブ）が開きます。
　　① テストモードで開始を選択します。
　　② 「次へ」ボタンをクリックして下さい。

　(4) データベースの作成画面（ロケーションタブ）が開きます。
　　① Cloud Firestore のロケーションで任意のロケーションを選択します。
　　② 「完了」ボタンをクリックして下さい。
 
##2-5. Cloud Storageの有効化
　Cloud Storage を使って、写真の保持、アップロード及び共有を行います。

　(1) 左ナビゲーションの開発 > Storageをクリックして下さい。
　　<img width="242" alt="2-9.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/a6e0760e-4f18-b825-d5bf-d5437733330c.png">

　(2) 「スタートガイド」ボタンをクリックして下さい。
　(3) 「次へ」ボタンをクリックして下さい。
　(4) 「完了」ボタンをクリックして下さい。

#3. Firebase CLIのインストール
　Firebase CLI を使って、ローカルに作成したFirebaseプロジェクトのWebアプリを
　Firebase Hostingにアップロードします.

　(1) Cloud Shellを開きます。
　(2) Firebase CLIをインストールします。
　　以下のコマンドを実行して下さい。

```php
$ npm -g install firebase-tools
```

　(3) Firebase CLIのバージョンを確認します。
　　以下のコマンドを実行して下さい。

```php
$ firebase --version
```

　(4) 1-3.で取得したサンプルアプリ"friendlychat-web"の"web-start"に移動します。
　　以下のコマンドを実行して下さい。

```php
$ cd friendlychat-web/web-start
```

　(5) Firebaseにログインします。
　　① 以下のコマンドを実行して下さい。

```php
$ firebase login --no-localhost
```

　② 以下のメッセージが出力されるので、"Y"を入力して下さい。

```php
? Allow Firebase to collect anonymous CLI usage and error reporting information? (Y/n)
```

　③ "Visit this URL on any device to log in:"というメッセージの下に
　　記載されているURLをコピーし、アクセスして下さい。
　④ Googleアカウントを選択すると"Firebase CLI が 
　　Google アカウントへのアクセスをリクエストしています"と表示されるので、
　　「許可」ボタンをクリックして下さい。
　⑤ コードが表示されるので、コピーします。
　⑥ Cloud Shellに戻り、"? Paste authorization code here"の部分に
　　コピーしたコードを入力します。
　　以下のメッセージが出力されれば成功です。

```php
　✔  Success!
```

　(6) Firebaseに"web-start"を追加します。
　　① 以下のコマンドを実行して下さい。

```php
$ firebase use --add
```

　　② プロジェクト IDの入力
　　　『2-1. Firebaseプロジェクトの作成』でメモしたプロジェクト IDを入力して下さい。
　　③ aliasには"default"を入力して下さい。
　　④ 以下のコマンドを入力し、"FriendlyChat (current)"と表示されれば成功です。

```php
$ firebase list
```

#4. Firebase プロジェクトのローカル実行
　(1) 以下のコマンドを実行して下さい。

```php
$ firebase serve --only hosting --port 8080
```

　　以下のメッセージが出力されます。

```php
i  hosting: Serving hosting files from: ./public
✔  hosting: Local server: http://localhost:8080
```

　(2) http\://localhost:8080にアクセスします。
　　① Cloud Shell の「ウェブでプレビュー」ボタンをクリックします。
　　　<img width="500" alt="2-10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/af307d17-3de2-d801-abed-d641cde643b9.png">

　　② 下記の画面が出力されれば成功です。
　　　<img width="500" alt="2-11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/0a9ff457-f1a6-42b6-7e19-a9ca37b1af51.png">

　ここまでの作業で静的ファイルの確認まで出来ました。
　残りの作業でチャット機能を実装していきます。

#5. ユーザ認証の実装
　Firebase Authを使って、Google認証を実装します。

##5-1. ログイン機能の実装
　(1) Cloud Shell の「コードエディタ（ベータ版）の起動」ボタンをクリックします。
　　<img width="368" alt="2-12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/f2f5913d-2229-2d07-d91b-ff8ea65fd2a4.png">

　(2) "web-start/public/scripts/main.js"を開きます。
　　<img width="500" alt="2-13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/6392d876-eb50-cedf-ac12-39bb626d1d3b.png">

　(3) signIn() に以下の修正を行います。
　　【変更前】

```php
// Signs-in Friendly Chat.
function signIn() {
  // TODO 1: Sign in Firebase with credential from the Google user.
}
```

　　【変更後】

```php
// Signs-in Friendly Chat.
function signIn() {
  // TODO 1: Sign in Firebase with credential from the Google user.
  var provider = new firebase.auth.GoogleAuthProvider();
  firebase.auth().signInWithPopup(provider);
}
```

##5-2. ログアウト機能の実装
　引き続き、"web-start/public/scripts/main.js"に修正を加えます。

　(1) signOut() に以下の修正を行います。
　　【変更前】

```php
// Signs-out of Friendly Chat.
function signOut() {
  // TODO 2: Sign out of Firebase.
}
```

　　【変更後】

```php
// Signs-out of Friendly Chat.
function signOut() {
  // TODO 2: Sign out of Firebase.
  firebase.auth().signOut();
}
```

##5-3. 認証状態を確認する機能の実装
　引き続き、"web-start/public/scripts/main.js"に修正を加えます。

　(1) initFirebaseAuth() に以下の修正を行います。
　　【変更前】

```php
// Initiate firebase auth.
function initFirebaseAuth() {
  // TODO 3: Initialize Firebase.
}
```

　　【変更後】

```php
// Initiate firebase auth.
function initFirebaseAuth() {
  // TODO 3: Initialize Firebase.
  firebase.auth().onAuthStateChanged(authStateObserver);
}
```

##5-4. ログインユーザの情報を表示する機能の実装
　引き続き、"web-start/public/scripts/main.js"に修正を加えます。

　(1) getProfilePicUrl() に以下の修正を行います。
　　【変更前】

```php
// Returns the signed-in user's profile Pic URL.
function getProfilePicUrl() {
  // TODO 4: Return the user's profile pic URL.
}
```

　　【変更後】

```php
// Returns the signed-in user's profile Pic URL.
function getProfilePicUrl() {
  // TODO 4: Return the user's profile pic URL.
  return firebase.auth().currentUser.photoURL || '/images/profile_placeholder.png';
}
```

　(2) getUserName() に以下の修正を行います。
　　【変更前】

```php
// Returns the signed-in user's display name.
function getUserName() {
  // TODO 5: Return the user's display name.
}
```

　　【変更後】

```php
// Returns the signed-in user's display name.
function getUserName() {
  // TODO 5: Return the user's display name.
  return firebase.auth().currentUser.displayName;
}
```

　(3) isUserSignedIn() に以下の修正を行います。
　　【変更前】

```php
// Returns true if a user is signed-in.
function isUserSignedIn() {
  // TODO 6: Return true if a user is signed-in.
}
```

　　【変更後】

```php
// Returns true if a user is signed-in.
function isUserSignedIn() {
  // TODO 6: Return true if a user is signed-in.
  return !!firebase.auth().currentUser;
}
```

##5-5. Google認証の修正
　『2-3. Google認証の有効化』でGogle認証を有効化しましたが、
　GCPのプレビューでGoogle認証の動作を確認する場合、
　"8080-dot-XXXXXXX-dot-devshell.appspot.com"を承認済みドメインに設定する必要があります。

　(1) Firebaseのコンソールを開き、『2-3. Google認証の有効化』と同様
　　左ナビゲーションの開発 > Authenticationをクリックして下さい。
　(2) 「ログイン方法を設定」ボタンをクリックして下さい。
　(3) 承認済みドメインで「ドメインの追加」ボタンをクリックして下さい。
　(4) ドメインの追加画面が開くので、"8080-dot-XXXXXXX-dot-devshell.appspot.com"を入力し、
　　「追加」ボタンをクリックして下さい。
　　* XXXXXXXの箇所はご自身の環境に合わせて変更下さい。

##5-6. 動作確認
　(1) Firebase プロジェクトのローカル実行で開いている画面を閉じて下さい。
　　* 開いていない場合、次の手順にSkipして下さい。

　(2) 以下のコマンドを実行して下さい。

```php
$ firebase serve --only hosting --port 8080
```

　(3) http\://localhost:8080にアクセスします。
　　① 下記の画面が出力されます。
　　　<img width="500" alt="5-2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/d1801108-226b-9fb0-9d70-f1dd7d610c7c.png">

　　② 「SIGN-IN WITH GOOGLE」ボタンをクリックし、
　　　Google認証が正しく動作するか確認下さい。

　【補足】
　　Google認証に失敗し、403エラーが発生した場合、
　　以下の作業を試して下さい。
　　　(1) GCPを開いて下さい。
　　　(2) プロジェクトに"FriendlyChat"を選択して下さい。
　　　　<img width="416" alt="5-0.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/ba5153e1-6521-2d10-6e88-9f657406de61.png">

　　　(3) 左ナビゲーションのAPIとサービス > 認証情報をクリックして下さい。
　　　　<img width="500" alt="5-1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/ad176321-8b43-26de-7dcb-ceb70b07929c.png">

　　　(4) OAuth 同意画面タブを選択して下さい。
　　　(5) 承認済みドメインに"8080-dot-XXXXXXX-dot-devshell.appspot.com"を入力し、
　　　　「保存」ボタンをクリックして下さい。

#6. Cloud Firestoreにメッセージを保持する。
　チャットメッセージをCloud Firestoreに保持するための機能を実装します。

##6-1. Cloud Firestoreにメッセージを書き込む。
　Cloud Shell のコードエディタを開き、"web-start/public/scripts/main.js"に修正を加えます。

　(1) saveMessage() に以下の修正を行います。
　　【変更前】

```php
// Saves a new message on the Firebase DB.
function saveMessage(messageText) {
  // TODO 7: Push a new message to Firebase.
}
```

　　【変更後】

```php
// Saves a new message on the Firebase DB.
function saveMessage(messageText) {
  // TODO 7: Push a new message to Firebase.
  // Add a new message entry to the Firebase database.
  return firebase.firestore().collection('messages').add({
      name: getUserName(),
      text: messageText,
      profilePicUrl: getProfilePicUrl(),
      timestamp: firebase.firestore.FieldValue.serverTimestamp()
  }).catch(function(error) {
      console.error('Error writing new message to Firebase Database', error);
  });
}
```

##6-2. 動作確認
　(1) Firebase プロジェクトのローカル実行で開いている画面を閉じて下さい。
　　* 開いていない場合、次の手順にSkipして下さい。

　(2) 以下のコマンドを実行して下さい。

```php
$ firebase serve --only hosting --port 8080
```

　(3) http\://localhost:8080にアクセスします。
　(4) Message欄に任意のメッセージを入力し、「SEND」ボタンをクリックして下さい。
　　<img width="483" alt="6-1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/7b6b81ad-cad8-d815-fcff-53f101453ee9.png">

　(5) Firebaseのコンソールを開き、『2-4. Cloud Firestoreの有効化』と同様
　　左ナビゲーションの開発 > Databaseをクリックして下さい。
　　"messages"に上記で入力したメッセージが確認できます。

#7. Cloud Firestoreのメッセージを参照する。
　Cloud Firestoreからチャットメッセージを参照するための機能を実装します。

##7-1. Cloud Firestoreのメッセージを読み込む。
　Cloud Shell のコードエディタを開き、"web-start/public/scripts/main.js"に修正を加えます。

　(1) loadMessages() に以下の修正を行います。
　　【変更前】

```php
// Loads chat messages history and listens for upcoming ones.
function loadMessages() {
  // TODO 8: Load and listens for new messages.
}
```

　　【変更後】

```php
// Loads chat messages history and listens for upcoming ones.
function loadMessages() {
  // TODO 8: Load and listens for new messages.
  // Create the query to load the last 12 messages and listen for new ones.
  var query = firebase.firestore().collection('messages').orderBy('timestamp', 'desc').limit(12);
  
  // Start listening to the query.
  query.onSnapshot(function(snapshot) {
      snapshot.docChanges().forEach(function(change) {
          if (change.type === 'removed') {
              deleteMessage(change.doc.id);
          } else {
              var message = change.doc.data();
              displayMessage(change.doc.id, message.timestamp, message.name,
                message.text, message.profilePicUrl, message.imageUrl);
          }
      });
  });
}
```

##7-2. 動作確認
　(1) Firebase プロジェクトのローカル実行で開いている画面を閉じて下さい。
　　* 開いていない場合、次の手順にSkipして下さい。

　(2) 以下のコマンドを実行して下さい。

```php
$ firebase serve --only hosting --port 8080
```

　(3) http\://localhost:8080にアクセスします。
　(4) 『6. Cloud Firestoreにメッセージを保持する。』で入力したメッセージが確認出来ます。
　　また、再度、入力したメッセージが即時に更新されることも確認できます。

#8. Cloud Storageを使って、画像の共有を行う。
　Cloud Storageへの画像のアップロードと参照を行うための機能を実装します。

##8-1. Cloud Storageへの画像アップロードと読み込み。
　Cloud Shell のコードエディタを開き、"web-start/public/scripts/main.js"に修正を加えます。

　(1) saveImageMessage() に以下の修正を行います。
　　【変更前】

```php
// Saves a new message containing an image in Firebase.
// This first saves the image in Firebase storage.
function saveImageMessage(file) {
  // TODO 9: Posts a new image as a message.
}
```

　　【変更後】

```php
// Saves a new message containing an image in Firebase.
// This first saves the image in Firebase storage.
function saveImageMessage(file) {
  // TODO 9: Posts a new image as a message.
  // 1 - We add a message with a loading icon that will get updated with the shared image.
  firebase.firestore().collection('messages').add({
      name: getUserName(),
      imageUrl: LOADING_IMAGE_URL,
      profilePicUrl: getProfilePicUrl(),
      timestamp: firebase.firestore.FieldValue.serverTimestamp()
  }).then(function(messageRef) {
      // 2 - Upload the image to Cloud Storage.
      var filePath = firebase.auth().currentUser.uid + '/' + messageRef.id + '/' + file.name;
      return firebase.storage().ref(filePath).put(file).then(function(fileSnapshot) {
          // 3 - Generate a public URL for the file.
          return fileSnapshot.ref.getDownloadURL().then((url) => {
              // 4 - Update the chat message placeholder with the image's URL.
              return messageRef.update({
                  imageUrl: url,
                  storageUri: fileSnapshot.metadata.fullPath
              });
          });
      });
  }).catch(function(error) {
      console.error('There was an error uploading a file to Cloud Storage:', error);
  });
}
```

##8-2. 動作確認
　(1) Firebase プロジェクトのローカル実行で開いている画面を閉じて下さい。
　　* 開いていない場合、次の手順にSkipして下さい。

　(2) 以下のコマンドを実行して下さい。

```php
$ firebase serve --only hosting --port 8080
```

　(3) http\://localhost:8080にアクセスします。
　(4) 「Image」ボタンをクリックし、任意の画像をアップロードして下さい。
　　下記のようにアップロードした画像が確認できれば成功です。
　　　<img width="500" alt="7-1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/6eee51ea-0239-84be-f599-d49d197f65af.png">

　(5) Firebaseのコンソールを開き、『2-4. Cloud Firestoreの有効化』と同様
　　左ナビゲーションの開発 > Databaseをクリックして下さい。
　　"messages"に上記でアップロードした画像についてもメッセージとしてが確認できます。

　(6) Firebaseのコンソールを開き、『2-5. Cloud Storageの有効化』と同様
　　左ナビゲーションの開発 > Storageをクリックして下さい。
　　上記でアップロードした画像が保持されていることが確認できます。

#9. Cloud Firestore のセキュリティルール変更
　『2-4. Cloud Firestoreの有効化』の(3)でテストモードを選択している為、
　全てのユーザが作成したCloud Firestoreに対し、参照/追加ができる状態になっています。
　セキュリティルールの修正方法としては、2通りありますが、
　ここでは、Firebase CLIを使って修正を行います。

　(1) Cloud Shellを開き、 web-startに移動して下さい。
　(2) web-start配下にfirestore.rulesが存在しない為、
　　web配下のfirestore.rulesをコピーします。
　　以下のコマンドを実行します。

```php
$ cp ../web/firestore.rules .
```

　(3) firebase.jsonを開き、以下の修正を行います。

　　【変更前】

```php
{
  "hosting": {
    "public": "./public",
    "headers": [{
      "source" : "**/*.@(js|html)",
      "headers" : [ {
        "key" : "Cache-Control",
        "value" : "max-age=0"
      } ]
    }]
  }
}
```

　　【変更後】

```php
{
  "firestore": {
    "rules": "firestore.rules"
  },
  "hosting": {
    "public": "./public",
    "headers": [{
      "source" : "**/*.@(js|html)",
      "headers" : [ {
        "key" : "Cache-Control",
        "value" : "max-age=0"
      } ]
    }]
  }
}
```

　(4) 以下のコマンドを実行します。

```php
$ firebase deploy --only firestore
```

　(5) Firebase consoleを開き、左ナビゲーションの開発 > Databaseをクリックして下さい。
　(6) タブからルールをクリックして下さい。
　　以下のようにルールが書き換わっていれば成功です。

```php
service cloud.firestore {
  match /databases/{database}/documents {
    // Messages:
    //   - Anyone can read.
    //   - Authenticated users can add and edit messages.
    //   - Validation: Check name is same as auth token and text length below 300 char or that imageUrl is a URL.
    //   - Deletes are not allowed.
    match /messages/{messageId} {
      allow read;
      allow create, update: if request.auth != null
                    && request.resource.data.name == request.auth.token.name
                    && (request.resource.data.text is string
                      && request.resource.data.text.size() <= 300
                      || request.resource.data.imageUrl is string
                      && request.resource.data.imageUrl.matches('https?://.*'));
      allow delete: if false;
    }
    // FCM Tokens:
    //   - Anyone can write their token.
    //   - Reading list of tokens is not allowed.
    match /fcmTokens/{token} {
      allow read: if false;
      allow write;
    }
  }
}
```

#10. Cloud Storage のセキュリティルール変更
　Cloud Firestoreと同様、セキュリティルールを修正します。

　(1) Cloud Shellを開き、 web-startに移動して下さい。
　(2) web-start配下にstorage.rulesが存在しない為、
　　web配下のstorage.rulesをコピーします。
　　以下のコマンドを実行します。

```php
$ cp ../web/storage.rules .
```

　(3) firebase.jsonを開き、以下の修正を行います。

　　【変更前】

```php
{
  "firestore": {
    "rules": "firestore.rules"
  },
  "hosting": {
    "public": "./public",
    "headers": [{
      "source" : "**/*.@(js|html)",
      "headers" : [ {
        "key" : "Cache-Control",
        "value" : "max-age=0"
      } ]
    }]
  }
}
```

　　【変更後】

```php
{
  "firestore": {
    "rules": "firestore.rules"
  },
  "storage": {
    "rules": "storage.rules"
  },
  "hosting": {
    "public": "./public",
    "headers": [{
      "source" : "**/*.@(js|html)",
      "headers" : [ {
        "key" : "Cache-Control",
        "value" : "max-age=0"
      } ]
    }]
  }
}
```

　(4) 以下のコマンドを実行します。

```php
$ firebase deploy --only storage
```

　(5) Firebase consoleを開き、左ナビゲーションの開発 > Storageをクリックして下さい。
　(6) タブからルールをクリックして下さい。
　　以下のようにルールが書き換わっていれば成功です。

```php
// Returns true if the uploaded file is an image and its size is below the given number of MB.
function isImageBelowMaxSize(maxSizeMB) {
  return request.resource.size < maxSizeMB * 1024 * 1024
      && request.resource.contentType.matches('image/.*');
}

service firebase.storage {
  match /b/{bucket}/o {
    match /{userId}/{messageId}/{fileName} {
      allow write: if request.auth != null && request.auth.uid == userId && isImageBelowMaxSize(5);
      allow read;
    }
  }
}
```

#11. Firebase Hosting にWebアプリをデプロイ
　Firebase CLIを使って、"Friendly Chat"をFirebase Hostingにデプロイします。

　(1) Cloud Shellを開き、 web-startに移動して下さい。
　(2) 以下のコマンドを実行します。

```php
$ firebase deploy --except functions
```

　　コマンドの結果のHosting URLに
　　https\://\<firebase-projectId\>.firebaseapp.com
　　が出力されているので、アクセスして下さい。
　　携帯等からもチャットメッセージの参照、メッセージの追加（Googleログイン後）が
　　出来ていれば成功です。

以上となります。
Firebase + Google Cloud Platform (GCP) 環境を利用することで、
簡単にWebアプリ（Google認証付き）のインターネット公開が可能となりました。
今回説明できなかったFirebase Functionsなども試し、
より便利なWebアプリを作成してみて下さい。

Appendixでは、同じくチュートリアルに紹介されている、
通知機能とパフォーマンスの確認方法について補足説明します。

#Appendix
#a) 通知設定
　Firebase Cloud Messaging (FCM)を使って、新しいメッセージが
　チャットに書き込まれた時にブラウザに通知する機能を実装します。

##a)-1 manifest.jsonにgcm_sender_idを追加する。
　固定値で"103953800507"を追加します。

　(1) "web-start/public/manifest.json"を開きます。
　(2) 以下の修正を加えます。

　　【変更前】

```php
{
  "name": "Friendly Chat",
  "short_name": "Friendly Chat",
  "start_url": "/index.html",
  "display": "standalone",
  "orientation": "portrait"
}
```

　　【変更後】

```php
{
  "name": "Friendly Chat",
  "short_name": "Friendly Chat",
  "start_url": "/index.html",
  "display": "standalone",
  "orientation": "portrait",
  "gcm_sender_id": "103953800507"
}
```
　
##a)-2 "Service Worker"の作成
　通知を受け取って表示するための"Service Worker"を作成します。
　(1) "web-start/public/firebase-messaging-sw.js"を新規に作成します。
　(2) "web-start/public/firebase-messaging-sw.js"に
　　以下の内容を入力します。
　　* ６．３．０の箇所は各自のバージョンに合わせて修正して下さい。
　　　"web-start/public/index.html"でも同様の.jsを参照しているため、
　　　バージョンが分からない場合、参考にして下さい。

```php
importScripts('/__/firebase/6.3.0/firebase-app.js');
importScripts('/__/firebase/6.3.0/firebase-messaging.js');
importScripts('/__/firebase/init.js');

firebase.messaging();
```

##a)-3 "FCM device token"の保持
　ブラウザから受け取った"FCM device token"を、Cloud Firestoreに保持します。　
　Cloud Shell のコードエディタを開き、"web-start/public/scripts/main.js"に修正を加えます。

　(1) saveMessagingDeviceToken() に以下の修正を行います。
　　【変更前】

```php
// Saves the messaging device token to the datastore.
function saveMessagingDeviceToken() {
  // TODO 10: Save the device token in the realtime datastore
}
```

　　【変更後】

```php
// Saves the messaging device token to the datastore.
function saveMessagingDeviceToken() {
  // TODO 10: Save the device token in the realtime datastore
  firebase.messaging().getToken().then(function(currentToken) {
      if (currentToken) {
          console.log('Got FCM device token:', currentToken);
          // Saving the Device Token to the datastore.
          firebase.firestore().collection('fcmTokens').doc(currentToken)
          .set({uid: firebase.auth().currentUser.uid});
      } else {
          // Need to request permissions to show notifications.
      requestNotificationsPermissions();
      }
  }).catch(function(error){
      console.error('Unable to get messaging token.', error);
  });
}
```

##a)-4 通知を表示するための権限設定
　通知を表示する権限がない場合、"FCM device token"を受け取れない為、
　通知を表示する権限を与えてもいいか確認するダイアログを表示する機能を加えます。

　引き続き、"web-start/public/scripts/main.js"に修正を加えます。

　(1) requestNotificationsPermissions() に以下の修正を行います。
　　【変更前】

```php
// Requests permissions to show notifications.
function requestNotificationsPermissions() {
  // TODO 11: Request permissions to send notifications.
}
```

　　【変更後】

```php
// Requests permissions to show notifications.
function requestNotificationsPermissions() {
  // TODO 11: Request permissions to send notifications.
  console.log('Requesting notifications permission...');
  firebase.messaging().requestPermission().then(function() {
      // Notification permission granted.
      saveMessagingDeviceToken();
  }).catch(function(error) {
      console.error('Unable to get permission to notify.', error);
  });
}
```

##a)-5 "FCM device token"の取得
　(1) Firebase プロジェクトのローカル実行で開いている画面を閉じて下さい。
　　* 開いていない場合、次の手順にSkipして下さい。

　(2) 以下のコマンドを実行して下さい。

```php
$ firebase serve --only hosting --port 8080
```

　(3) http\://localhost:8080にアクセスします。
　(4) 下記のようにダイアログが表示されるので、「許可」ボタンをクリックして下さい。
　　<img width="315" alt="9-1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/be328480-bc74-1226-57f8-ece99c82e1ee.png">

　(5) ブラウザのJavaScript consoleを開き、Got FCM device tokenの部分を確認して下さい。
　　"FCM device token"が表示されていますのでコピーして下さい。
　　　<img width="309" alt="9-2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/8714989a-c064-85b0-5ec9-1780997e04b1.png">

##a)-6 "Server Key"の取得
　(1) Firebaseのコンソールから、プロジェクトの設定を選択します。
　　<img width="414" alt="9-3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/940f1a4a-385b-f972-5646-695d351ec309.png">

　(2) Cloud Messagingタブを開き、サーバーキーをコピーして下さい。
　　<img width="258" alt="9-4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/9eb62ae4-39ad-8ac0-a3e2-9f40cd1e7958.png">

##a)-7 curlコマンドで通知の送信
　FriendlyChatに設定した通知機能の動作確認を行います。

　(1) 以下のHTTPリクエストを送信します。

```php
POST /fcm/send HTTP/1.1
Host: fcm.googleapis.com
Content-Type: application/json
Authorization: key=YOUR_SERVER_KEY

{
  "notification": {
    "title": "New chat message!",
    "body": "There is a new message in FriendlyChat",
    "icon": "/images/profile_placeholder.png",
    "click_action": "http://localhost:5000"
  },
  "to":"YOUR_DEVICE_TOKEN"
}
```

　(2) curlコマンドで以下を実行します。
　　Cloud Shellで実行します。

```php
curl -H "Content-Type: application/json" \
     -H "Authorization: key=YOUR_SERVER_KEY" \
     -d '{
           "notification": {
             "title": "New chat message!",
             "body": "There is a new message in FriendlyChat",
             "icon": "/images/profile_placeholder.png",
             "click_action": "http://localhost:8080"
           },
           "to": "YOUR_DEVICE_TOKEN"
         }' \
     https://fcm.googleapis.com/fcm/send
```

* YOUR_DEVICE_TOKEN：『9-5. "FCM device token"の取得』で取得した
　"FCM device token"に書き換えて下さい。
* YOUR_SERVER_KEY：『9-6. "Server Key"の取得』で取得した
　"Server Key"に書き換えて下さい。

　(3) "New chat message!"とブラウザに出力されれば、成功です。

#b) Webアプリのパフォーマンス確認
　Performance Monitoring SDKを使って、Webアプリのパフォーマンスを確認します。

##b)-1 Performance Monitoring SDKの追加
　Performance Monitoring SDKの追加方法は、幾つかありますが、
　本記事では、"Firebase Hosting"を使っているので、"Hosting URL"から
　取得する方法を選択します。
　* SDKを追加すると、Firebaseは自動トレースと
　　HTTP/S ネットワーク リクエストのモニタリングを開始します。
　* 本記事で使用しているサンプルアプリの"web-start/public/index.html"には、
　　既にPerformance Monitoring SDKが追加されています。
　　以下の部分が該当します。

```php
<script src="/__/firebase/6.3.0/firebase-performance.js"></script>
```

##b)-2 FirebaseとPerformance Monitoringの初期化
　(1) Firebaseの初期化
　　サンプルアプリの"web-start/public/index.html"には、
　　Firebaseの初期化のスクリプトが既に追加されています。
　　以下の部分が該当します。

```php
<script src="/__/firebase/init.js"></script>
```

　(2) Performance Monitoringの初期化
　　以下を(1)の init.js の下に追記します。

　　【変更前】

```php
<script src="/__/firebase/init.js"></script>
```

　　【変更後】

```php
<script src="/__/firebase/init.js"></script>

<!-- Initialize Performance Monitoring and get a reference to the service -->
<script>
    var perf = firebase.performance();
</script>
```

##b)-3 "First input delay"の設定（オプション）
　ユーザーがページで操作（ボタンやリンクのクリック）を行ってから、
　ブラウザがその入力に応答できるまでの時間を測定するためのオプションです。

　利用するためには、以下の作業を実施します。

　(1) "web-start/public/index.html"を開きます。
　(2) 以下のコメントを外します。

　　【変更前】

```php
<!-- TODO: Enable First Input Delay polyfill library. -->
<!--  <script type="text/javascript">!function(n,e){var t,o,i,c=[],f={passive:!0,capture:!0},r=new Date,a="pointerup",u="pointercancel";function p(n,c){t||(t=c,o=n,i=new Date,w(e),s())}function s(){o>=0&&o<i-r&&(c.forEach(function(n){n(o,t)}),c=[])}function l(t){if(t.cancelable){var o=(t.timeStamp>1e12?new Date:performance.now())-t.timeStamp;"pointerdown"==t.type?function(t,o){function i(){p(t,o),r()}function c(){r()}function r(){e(a,i,f),e(u,c,f)}n(a,i,f),n(u,c,f)}(o,t):p(o,t)}}function w(n){["click","mousedown","keydown","touchstart","pointerdown"].forEach(function(e){n(e,l,f)})}w(n),self.perfMetrics=self.perfMetrics||{},self.perfMetrics.onFirstInputDelay=function(n){c.push(n),s()}}(addEventListener,removeEventListener);</script> -->
```

　　【変更後】

```php
<!-- TODO: Enable First Input Delay polyfill library. -->
<script type="text/javascript">!function(n,e){var t,o,i,c=[],f={passive:!0,capture:!0},r=new Date,a="pointerup",u="pointercancel";function p(n,c){t||(t=c,o=n,i=new Date,w(e),s())}function s(){o>=0&&o<i-r&&(c.forEach(function(n){n(o,t)}),c=[])}function l(t){if(t.cancelable){var o=(t.timeStamp>1e12?new Date:performance.now())-t.timeStamp;"pointerdown"==t.type?function(t,o){function i(){p(t,o),r()}function c(){r()}function r(){e(a,i,f),e(u,c,f)}n(a,i,f),n(u,c,f)}(o,t):p(o,t)}}function w(n){["click","mousedown","keydown","touchstart","pointerdown"].forEach(function(e){n(e,l,f)})}w(n),self.perfMetrics=self.perfMetrics||{},self.perfMetrics.onFirstInputDelay=function(n){c.push(n),s()}}(addEventListener,removeEventListener);</script>
```

##b)-4 パフォーマンスデータの確認
　(1) Webチャットでチャットメッセージを追加します。
　(2) Firebaseのコンソールを開き、
　　左ナビゲーションの開発 > Performanceをクリックして下さい。
　　* 通常は、12時間以内にパフォーマンスの結果が更新されます。
　　
　　<img width="227" alt="b-1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/4b5ec626-3afd-9e49-d0c8-2ec61e5d5e6b.png">

　(3) パフォーマンスデータの確認が出来ます。
　　<img width="500" alt="b-2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165180/05f3bfb6-64e6-285e-e050-8f60aad2bd41.png">

#参考元
[Firebaseのチュートリアル](https://codelabs.developers.google.com/codelabs/firebase-web/index.html#0)
[FirebaseでWebチャットアプリをデプロイするまで（1時間コース）](https://qiita.com/st5757/items/9e651e8cffaa90681426)