Firebaseの理解のためにGoogle Developers Codelabsのチュートリアルをやってみた。

## 本記事の内容
私自身、Firebaseってなんだろうというところから本記事で紹介するチュートリアルを実施しました。
Google Developers Codelabsで提供されているチュートリアルがとても良かったので、初学者の方に紹介できればと思い記事にさせていただきます。
内容は、チュートリアルを手順通りに進めただけですので、初心者 / 初学者向けとなっております。
実施したチュートリアルは、英語のため簡単な翻訳(Google翻訳で返ってきた日本語)を交えながら進めさせていただきます。

## 参考にしたサイトなど
- 本記事で実施したチュートリアル、参考にしたリファレンスサイト
  - [Google Developers Codelabs - Firebase Web Codelab](https://codelabs.developers.google.com/codelabs/firebase-web/)
  - [Firebase - ドキュメント](https://firebase.google.com/docs)
- そもそもFirebaseとは??
  - [Firebaseの始め方 - Qiita](https://qiita.com/kohashi/items/43ea22f61ade45972881)
- npmとかその辺のインストールについて
  - [Macにnode.jsをインストールする手順。 - Qiita](https://qiita.com/akakuro43/items/600e7e4695588ab2958d)

## 始める前に環境構築
### 開発環境
- macOS Mojave v10.14.4
- Homebrew 2.1.2
- node v12.2.0
- npm 6.9.0
- firebase 6.9.2

Nodeのインストール等については、すでにまとめている方がいるため割愛。

## いざチュートリアル!!
ここから以下の内容は、冒頭で紹介したGoogle Developers Codelabsで提供されている
チュートリアルの内容を翻訳しながら書いていきます。

#### 実施するチュートリアルについて
[Google Developers Codelabs - Firebase Web Codelab](https://codelabs.developers.google.com/codelabs/firebase-web/)
全15章からなります、所要時間は74分とのこと。
74分でサンプルコードが用意されてるとはいえ、本番公開までできちゃうのは驚きです。
早速始めていきます。

#### 1.Overview(概要)
https://codelabs.developers.google.com/codelabs/firebase-web/#0
この章ではチュートリアルの概要について説明しています。
何を使いどんなことを学ぶのか。
学ぶのに必要なものは何か説明されています。

<details><summary>以下、和訳。</summary><div>

Firebaseを使ってチャットクライアントを作ることで、webアプリケーションを簡単に作成する方法を学びます。

- 学べること
  - Cloud FirestoreとCloud Storageを使ったデータ同期
  - Authenticationを使用したユーザー認証
  - Hostingを使用したアプリケーションのデプロイ
  - Cloud Messagingを使用した通知機能の実装
- 必要なもの
  - お好みのIDE / テキストエディタ
  - Node.jsに付属しているパッケージマネージャー(npm)
  - コンソール
  - ウェブブラウザ
  - codelabのサンプルコード
    - 第2章で取得方法を確認して下さい。
</div></details>

#### 2.Get the sample code(サンプルコードの入手)
https://codelabs.developers.google.com/codelabs/firebase-web/#1
本チュートリアルは、コーディングやcssなどのスタイルの記述は行いません。
あくまでFirebaseって何ができるのかを学ぶチュートリアルになっています。
そのためhtmlなどのファイルはGoogle Developers Codelabsから提供されてます。
Gitリポジトリがあるためcloneしてデータを持ってくるか、gitが入っていない場合はzipファイルでDLが可能です。

<details><summary>以下、和訳。</summary><div>

コマンドを使ってCodelabsのリポジトリを複製します。

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

gitがインストールされていない場合はzipファイルをダウンロードすることもできます
> friendlychat-webリポジトリには、複数のプラットフォーム用のサンプルコードが含まれています。
> このチュートリアルでは、以下の2つのディレクトリを使用します。
> ・web-start => 本チュートリアルで編集するディレクトリ
> ・web       => 完成しているサンプルコード
> 完成したコードを実行する場合でも、Firebaseコンソールでプロジェクトを作成する必要があります。
> 手順については、プロジェクトの作成、アプリの設定のセクションを確認して下さい。

複製したリポジトリ内の`web-start`ディレクトリをIDEで開いて下さい。
このディレクトリには、完全に機能するwebチャットアプリの開始コードが含まれています。
</div></details>

#### 3.Create a Firebase project and Set up your project(プロジェクトの作成と設定)
https://codelabs.developers.google.com/codelabs/firebase-web/#2
この章では、以下のことを行います。

- プロジェクトの作成と設定
- 使用する製品の設定
  - Authentication
  - Cloud Firestore
  - Cloud Storage

主にFirebaseコンソールを使用し行う作業です。

<details><summary>以下、和訳。</summary><div>

##### 【プロジェクトを作成する】
(1)Firebaseコンソールで、「プロジェクトの追加」をクリック。「FriendlyChat」という名前を付けます。
※プロジェクトIDを覚えておいて下さい。
(2)「プロジェクトの作成」をクリックします。

> 重要：プロジェクトの名前は「FriendlyChat」になりますが、Firbaseが自動で一意なIDを割り当てます。
> IDでプロジェクトを識別しているため、プロジェクト名(「FriendlyChat」)は単なる表示名です。

これから作成するアプリケーションはFirebaseの以下の製品を使用しています。

- Authentication：ユーザーのアプリへのサインイン(認証)を簡単に設定できます。
- Cloud Firestore：構造化されたデータをクラウドに保存し、即座に通知します。
- Cloud Storage：クラウドにファイルを保存します。
- Hosting：アプリケーションをデプロイ(本番反映)できます。
- Cloud Messaging：プッシュ通知を設定し、ブラウザ通知を実現します。

これらの製品を使うには、特別な設定をするかFirebaseコンソールで有効にする必要があります。

##### 【AuthenticationでGoogleアカウント認証によるサインインを有効にする】
ユーザーが自分のGoogleアカウントでWebアプリにサインインできるようにします。
そのためには設定を有効にする必要があります。

(1)Firebaseコンソールの左側のナビゲーションにある「開発(Develop)」の項目を確認
(2)Authenticationをクリック。「ログイン方法」タブをクリック。
(3)Googleサインインを有効にして保存。

~~(4) (省略可能)アプリの一般公開名をフレンドリチャットに設定し、ドロップダウンメニューからプロジェクトサポートのメールアドレスを選択します。~~
=>2019/5/14時点でメールアドレスの設定は必須になっております。

##### 【Cloud Firestoreを有効にする】
Cloud Firestoreを使用してチャットメッセージを受信、保存します。
そのためにCloud Firestoreを有効にする必要があります。

(1)Firebaseコンソールの「開発(Develop)」の項目で、Databaseをクリック。
(2)Cloud Firestoreペインで「データベースの作成」をクリックします。
(3)「テストモードで開始」を選択。セキュリティ規則に関する免責事項を読み「有効にする」をクリック。
> 今回のチュートリアルでは「Realtime Database」ではなく「Cloud Firestore」を有効にするように気をつけて下さい。
> ２つの設定は同一ページ内にありますが、ページ上部にある「Cloud Firestore」を有効にします。

##### 【Cloud Storageを有効にする】
本アプリケーションは、Cloud Storageを使用して画像データの保存と共有を行います。
そのために、Cloud Storageを有効にする必要があります。

(1)Firebase consoleの「開発(Develop)」の項目で、Storageをクリック。
(2)「スタートガイド」をクリック
(3)Firebaseプロジェクトのセキュリティ規則に関する免責事項を読んでから、「OK」をクリックします。
</div></details>

#### 4.Install the Firebase command line interface(FirebaseCLIをインストール)
https://codelabs.developers.google.com/codelabs/firebase-web/#3
この章ではFirebase CLI(コマンドラインインターフェイス)のインストールを行います。
インストールには前述している通りnpmが必要です。
CLIをインストールすることでHostingの機能を使ったローカル環境の立ち上げや本番へのデプロイが容易になります。

<details><summary>以下、和訳。</summary><div>

FirebaseCLIを使用すると、Hostingを使用してwebアプリケーションをローカルに提供したり
Firebaseプロジェクトにデプロイすることができます。

> 注意：CLIのインストールにはNodeJS付属のnpmが必要になります。

(1)次のnpmコマンドを実行してCLIをインストールします。

```
npm -g install firebase-tools
```

> うまくいかない場合はnpmの権限を変更する必要があります。

(2)次のコマンドを実行し、CLIが正しくインストールされたことを確認します。

```
firebase --version
```

このとき、Firebase CLIのバージョンがv4.1.0以降であることを確認して下さい。

(3)次のコマンドを実行してFirebase CLIを承認します。

```
firebase login
```

Hosting用のアプリの設定をアプリのローカルディレクトリ(複製したリポジトリのweb-startディレクトリ)から取得するようにwebアプリテンプレートを設定しています。
しかし、その設定を反映させるためにローカルディレクトリとFirebaseプロジェクトを関連付ける必要があります。

(4)ローカルのアプリケーションディレクトリに移動して下さい。

```
cd ~/Project-dir/friendlychat-web/web-start
```

(5)次のコマンドを実行してローカルディレクトリとFirebaseプロジェクトを関連付けます。

```
firebase use --add
```

(6)プロンプトが表示されたら、プロジェクトIDを選択してから、Firebaseプロジェクトにエイリアスをつけます。

エイリアスは、複数の環境(本番環境、ステージング環境など)がある場合に役立ちます。
ただし、このコードラボでは、「default」とします。

(7)コマンドラインの残りの指示に従って下さい。

```
comandfirebase use --add 以降の内容
? Which project do you want to add? friendlychat-[your-id]
? What alias do you want to use for this project? (e.g. staging) default
```
</div></details>

#### 5.Run the starter app locally(ローカルでアプリを実行する)
https://codelabs.developers.google.com/codelabs/firebase-web/#4
この章ではFirebaseCLIを使用してローカルでアプリケーションを立ち上げます。

- `firebase serve --only hosting` でローカルでアプリを立ち上げる
- ブラウザにてlocalhostにアクセス

<details><summary>以下、和訳。</summary><div>

プロジェクトの作成や構成が完了したので、初めてアプリケーションを実行する準備が整いました。

(1)`web-start`ディレクトリ以下で次のコマンドを実行して下さい。

```
firebase serve --only hosting
```

(2)コマンドラインに次のような応答が表示されます。

```
hosting: Local server: http://localhost:5000
```

FirebaseのHostingのエミュレーターを使用することでローカルでアプリが提供されます。
`http://localhost:5000`から利用可能です。
`public`ディレクトリの下にある全てのファイルが提供されます。

(3)ブラウザで `http://localhost:5000` を開いてみます。

webチャットアプリのUIはまだ機能していません。
アプリは今は何もできませんが、UIをレイアウトしました。
それではリアルタイムチャットを作ろう!
</div></details>


#### 6.Import and configure Firebase(Firebaseをインポートして設定する)
https://codelabs.developers.google.com/codelabs/firebase-web/#5
この章ではアプリケーションにFirebaseをインポートする方法を学びます。
方法は使用する環境に応じて複数用意されています。(iOS、Android、ウェブなど)
本チュートリアルではウェブ用のインストール方法について説明しています。

<details><summary>以下、和訳。</summary><div>

##### 【Firebase SDKをインポート】
Firebase SDKをアプリにインポートする必要があります。
その方法はドキュメントに記載されている通り複数の方法があります。
https://firebase.google.com/docs/web/setup
例えば、CDNからライブラリをインポートすることもできます。
または、npmを使用してローカルにインストールし、Br​​owserifyを使用している場合はアプリにパッケージ化することもできます。

Hostingを使用してアプリの処理をしているのでindex.htmlにてローカルURLをインポートします。
cloneしてきたファイルにはすでに追加されているのでそれを確認することができます。

```~/project-dir/friendlychat-web/web-start/index.html
<script src="/__/firebase/5.6.0/firebase-app.js"></script>
<script src="/__/firebase/5.6.0/firebase-auth.js"></script>
<script src="/__/firebase/5.6.0/firebase-firestore.js"></script>
<script src="/__/firebase/5.6.0/firebase-storage.js"></script>
<script src="/__/firebase/5.6.0/firebase-messaging.js"></script>
```

> サンプルコードのfirebaseのバージョンは更新されるため新しい場合があります。

このチュートリアルでは、Cloud Firestore、Cloud Storage、Authentication、Cloud Messagingを使用するため
そのために必要なライブラリーを読み込んでいます。
アプリの読み込み時間短縮のために、必要なものだけが読み込まれていることを確認してください。

##### 【Firebaseを設定する】
Firebase SDKを設定し、使用しているFirebaseプロジェクトを特定する必要があります。
Hostingを使用しているため、その設定をする特別なスクリプトをインポートすることができます。

繰り返しになりますが、cloneしてきたファイルにはすでに追加されているのでそれを確認することができます。

```~/project-dir/friendlychat-web/web-start/index.html
<script src="/__/firebase/init.js"></script>
```

上記のスクリプトには、`firebase use --add`を実行したときに指定したFirebaseプロジェクトに基づいた設定が含まれています。

init.jsを調べて、プロジェクトの構成を確認するにはブラウザで`http://localhost:5000/__/firebase/init.js`を開いてください。
次のような内容が確認できるはずです。

```/__/firebase/init.js
if (typeof firebase === 'undefined') throw new Error('hosting/init-error: Firebase SDK not detected. You must include it before /__/firebase/init.js');
firebase.initializeApp({
  "apiKey": "qwertyuiop_asdfghjklzxcvbnm1234568_90",
  "databaseURL": "https://friendlychat-1234.firebaseio.com",
  "storageBucket": "friendlychat-1234.appspot.com",
  "authDomain": "friendlychat-1234.firebaseapp.com",
  "messagingSenderId": "1234567890",
  "projectId": "friendlychat-1234"
});
```

> 注意：Hostingによって生成されたローカルで使用可能な暗黙的な設定を使用する代わりに
> プロジェクトのFirebaseコンソールにある初期化コードをコピーすることもできます。
> （プロジェクトの設定 ＞ 全般 ＞ ウェブアプリに追加)
> Firebase Hostingではなく、独自のホスティングサーバーを使用している場合は特にこの方法を使用する必要があります。

</div></details>

#### 7.Set up user sign-in(ユーザーサインインの設定)
https://codelabs.developers.google.com/codelabs/firebase-web/#6
この章では、Authenticationを使ったユーザー認証を実装します。
htmlにはすでにjsの関数に対応するトリガーが設定されているので、`main.js`にてfunctionの設定をするのみです。
Authenticationの仕様については以下を参照すると良いです。
[JavaScript による Google ログインを使用した認証 | Firebase](https://firebase.google.com/docs/auth/web/google-signin)

<details><summary>以下、和訳。</summary><div>

Firebase SDKは、index.htmlでインポートおよび初期化されているので、使用する準備が整いました。
Authenticationを使用してユーザーサインインを実装します。

##### 【Googleサインインでユーザーを認証する】
このアプリでは、ユーザーはGoogleサインインをクリックすると、`signIn`というファンクション(機能)がトリガーされます。
(すでに設定されています。)
このコードラボでは、FirebaseがIDプロバイダーとしてGoogleを使用することを承認します。
ポップアップを使用しますが、他にもいくつかの方法があります。

1. `web-start` ディレクトリのサブディレクトリ `public/scripts/` で、main.jsを開きます。
2. `signIn` 関数を探します。
3. 関数全体を次のコードに置き換えます。

```main.js
// Signs-in Friendly Chat.
function signIn() {
  // Sign into Firebase using popup auth & Google as the identity provider.
  var provider = new firebase.auth.GoogleAuthProvider();
  firebase.auth().signInWithPopup(provider);
}
```

`signOut`関数は、ユーザーが「Sign-out」ボタンをクリックすると発生します。

1. `public/scripts/main.js`に戻ります
2. `signOut` 関数を探します。
3. 関数全体を次のコードに置き換えます。

```main.js
// Signs-out of Friendly Chat.
function signOut() {
  // Sign out of Firebase.
  firebase.auth().signOut();
}
```

##### 認証状態の追跡
ログインの状態に応じてUIを更新するにはユーザーがサインインまたはサインアウトしているか確認する必要があります。
Authenticationでは認証状態にオブザーバを登録でき、状態が変わるたびにトリガーされます。

1. `public/scripts/main.js`に戻ります
2. `initFirebaseAuth` 関数を探します。
3. 関数全体を次のコードに置き換えます。

```main.js
// Initiate Firebase Auth.
function initFirebaseAuth() {
  // Listen to auth state changes.
  firebase.auth().onAuthStateChanged(authStateObserver);
}
```

このコードは`authStateObserver関数`を認証状態オブザーバとして登録します。
認証状態が変わるたびにトリガーされます。
ここでサインボタンやサインアウトボタン、ユーザーのプロフィール写真の表示、非表示を切り替えます。
サインイン => サインアウトボタンを表示、プロフィール写真を表示
サインアウト => サインインボタンを表示、プロフィール写真を非表示

##### サインインしているユーザーの情報を表示
サインインしているユーザーのプロフィール写真とユーザー名をアプリのトップバーに表示します。

Firebaseでは、サインインしたユーザーのデータは常に`firebase.auth().currentUser`オブジェクトで利用できます。
ユーザーがサインインしたときにトリガーするように`authStateObserver関数`を設定して、UIがそれに応じて更新されるようにしました。
トリガーされると`getProfilePicUrl`と`getUserName`が呼び出されます。

1. `public/scripts/main.js`に戻ります
2. `getProfilePicUrl` 関数と`getUserName` 関数を探します。
3. 関数全体を次のコードに置き換えます。

```main.js
// Returns the signed-in user's profile pic URL.
function getProfilePicUrl() {
  return firebase.auth().currentUser.photoURL || '/images/profile_placeholder.png';
}

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

サインインしていないユーザーがメッセージを送信しようとすると、エラーメッセージが表示されます。
※ただし、試すことは可能です。
そのため、ユーザーがサインインしているかどうかを検出する必要があります。

1. `public/scripts/main.js`に戻ります
2. `isUserSignedIn` 関数を探します。
3. 関数全体を次のコードに置き換えます。

```main.js
// Returns true if a user is signed-in.
function isUserSignedIn() {
  return !!firebase.auth().currentUser;
}
```

##### アプリへのサインインをテストする
1. アプリケーションがローカルで提供されている状態なら一度リロードします。
それ以外の場合は`firebase serve`を実行し、localhostをブラウザで開きます。
2. サインボタンを使ってGoogleアカウントサインインします。
3. サインインするとユーザー名とプロフィール写真が表示されます。

</div></details>

#### 8.Write messages to Cloud Firestore(Cloud Firestoreにメッセージを書き込む)
https://codelabs.developers.google.com/codelabs/firebase-web/#7
この章では、アプリケーションからのCloud Firestoreへのデータの入力を実装します。
主にユーザーから送信されたメッセージをデータベースに登録できるようにします。
データベースへの登録のみ実装するため、データベースに登録されたメッセージの表示については次の章で実装します。

<details><summary>以下、和訳。</summary><div>

このセクションでは、Cloud Firestoreにデータを入力できるようにアプリのUIを設定していきます。
この設定はFirebaseコンソールから手動で行うことが可能ですが
基本的なCloud Firestoreの書き込みについて説明するためアプリ自体で行います。

##### 【データモデル】
Firestoreデータは、コレクション、ドキュメント、フィールド、およびサブコレクションに分割されています。
チャットの各メッセージは、メッセージと呼ばれるトップレベルのコレクションにドキュメントとして格納します。

> Tip：Firestoreデータモデルの詳細については、ドキュメントを参照して下さい。
[Cloud Firestore データモデル | Firebase](https://firebase.google.com/docs/firestore/data-model)

##### 【Firestoreにメッセージを追加する】
ユーザーが作成したチャットメッセージを保存するには、Cloud Firestoreを使用します。
このセクションでは、Cloud Firestoreに新しいメッセージを書き込むための機能をユーザーに追加します。
ユーザーが「SEND」ボタンをクリックすると、以下のコードスニペットが表示されます。
`messages`コレクション内のFirestoreインスタンスに、メッセージフィールドの内容を含むメッセージオブジェクトを追加します。
`add()`メソッドは、自動的に生成されたIDを持つ新しい文書をコレクションに追加します。

1. `public/scripts/main.js`に戻ります
2. `saveMessage` 関数を探します。
3. 関数全体を次のコードに置き換えます。

```main.js
// Saves a new message on the Cloud Firestore.
function saveMessage(messageText) {
  // 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);
  });
}
```

##### 【メッセージ送信テスト】
1. アプリケーションがローカルで提供されている状態なら一度リロードします。
それ以外の場合は`firebase serve`を実行し、localhostをブラウザで開きます。
2. サインインしたら、「Hey there！」などのメッセージを入力し、「SEND」をクリックします。
これでFirestoreに書き込みがされました。
ただし、データの取得を実装する必要があるため、実際のWebアプリケーションにはまだデータが表示されません
3. Firebaseコンソールで追加されたメッセージを確認することができます。
Firebaseコンソールの開発セクションにある「Database」をクリックします。
`messages`コレクションと一緒に追加したメッセージが表示されます。
</div></details>

#### 9.Read messages(メッセージを読む)
https://codelabs.developers.google.com/codelabs/firebase-web/#8
この章では8章でFirestoreに書き込んだメッセージをアプリケーション上に表示させるための処理を追加します。
この処理をすることでアプリケーションがFirestoreの内容を読み込むことができるようになります。

<details><summary>以下、和訳。</summary><div>

##### 【メッセージを同期する】
アプリでメッセージを読むには、データが変更されたときにトリガーするリスナーを追加して
新しいメッセージを表示するUI要素を作成する必要があります。

アプリから新しく追加されたメッセージをリッスンするコードを追加します。
このコードでは、データに加えられた変更を監視するリスナーを登録します。
ロード時に非常に長い履歴が表示されないように、チャットの最後の12メッセージのみを表示します。

1. `public/scripts/main.js`に戻ります
2. `loadMessages` 関数を探します。
3. 関数全体を次のコードに置き換えます。

```main.js
// Loads chat messages history and listens for upcoming ones.
function loadMessages() {
  // 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);
      }
    });
  });
}
```

データベース内のメッセージをリッスンするには、`.collection`関数を使用します。
コレクションに対してクエリを作成し、どのコレクションをリッスンするかデータを指定します。
上記のコードでは、チャットメッセージが保存されている場所である`messages`コレクション内の変更をリスニングしています。
また、`.limit(12)`を使用して最後の12個のメッセージのみを監視し
`.orderBy('timestamp', 'desc')`を使用して日付順にメッセージを並べ替えて
12個の最新のメッセージを取得することで制限を適用します。

```
var query = firebase.firestore()
            .collection('messages') <= どのコレクションをリッスンするか
            .orderBy('timestamp', 'desc') <= 日付順に並び替え
            .limit(12); <= 最新の12個
```

`.onSnapshot`関数は、1つのパラメータ、コールバック関数を取ります。
クエリに一致するドキュメントに変更があると、コールバック関数がトリガされます。
変更とはメッセージが削除、変更、追加された時です。
Cloud Firestoreのドキュメントでこれについてもっと読むことができます。
[Cloud Firestore でリアルタイム アップデートを入手する | Firebase](https://firebase.google.com/docs/firestore/query-data/listen)

##### 【同期メッセージをテストする】
1. アプリケーションがローカルで提供されている状態なら一度リロードします。
それ以外の場合は`firebase serve`を実行し、localhostをブラウザで開きます。
2. 先にデータストアに作成したメッセージは、FriendlyChat UIに表示されるはずです
新しいメッセージを書いても構いません、すぐに現れるはずです。
3. (オプション)Firebaseコンソールで直接メッセージの追加、変更、削除も可能です。
変更内容はUIに反映されます。

おめでとう、あなたのアプリでCloud Firestoreのドキュメントを読んでいます!
</div></details>

#### 10.Send images(画像を送る)
https://codelabs.developers.google.com/codelabs/firebase-web/#9
この章では画像送信の機能追加を行います。
送信された画像の保存先やチャット画面への表示など
jsにサンプルコードを追加しながら学んでいきます。

<details><summary>以下、和訳。</summary><div>

画像共有のための機能を追加します。

Cloud Firestoreは構造化データの保存に適していますが、Cloud Storageはファイルの保存に適しています。
Cloud Storageはファイル/BLOBストレージサービスであり
ユーザーが私たちのアプリを使って共有するすべての画像を保存するために使用します。

##### 【画像をCloud Storageに保存する】
このコードラボでは、ファイルピッカーダイアログをトリガーするボタンをすでに追加しています。
ファイルを選択すると、`saveImageMes​​sage`関数が呼び出され、選択したファイルへの参照を取得できます。
`saveImageMes​​sage`関数は次のことを行います。

1. チャットフィードに「プレースホルダ」チャットメッセージを作成。
これにより画像アップロード時の「読み込み中」のアニメーションが表示されます。
2. イメージファイルをCloud Storageのパスにアップロードします：`/<uid>/<messageId>/<file_name>`
3. 画像公開用のURLの生成
4. 一時的な読み込み画像の代わりに、新しくアップロードされた画像ファイルのURLでチャットメッセージを更新します。

実際にこの機能を実装します。

1. `public/scripts/main.js`に戻ります
2. `saveImageMessage` 関数を探します。
3. 関数全体を次のコードに置き換えます。

```main.js
// Saves a new message containing an image in Firebase.
// This first saves the image in Firebase storage.
function saveImageMessage(file) {
  // 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);
  });
}
```

##### 【画像送信テスト】
1. アプリケーションがローカルで提供されている状態なら一度リロードします。
それ以外の場合は`firebase serve`を実行し、localhostをブラウザで開きます。
2. サインイン後、画像アップロードボタンをクリックしてください。次にファイルピッカーを使って画像ファイルを選択します。
あなたがイメージを探しているならば、コーヒーカップのこの素敵な写真を使ってください。
[サンプル画像](https://www.pexels.com/photo/aroma-aromatic-art-artistic-434213/)
3. 選択した画像と共に新しいメッセージがアプリのUIに表示されます。

サインインしていないときに画像を追加しようとすると、Toastが表示され、画像を追加するにはサインインする必要があることがわかります。
</div></details>

#### 11.Show notifications(通知を表示)
https://codelabs.developers.google.com/codelabs/firebase-web/#10
新着のメッセージが投稿されたときにブラウザ通知が来るように設定します。
cURLコマンドを使ったHTTPリクエストの送信などを行います。

<details><summary>以下、和訳。</summary><div>

ブラウザ通知のサポートを追加します。
新しいメッセージがチャットに投稿されると、アプリはユーザーに通知します。
Cloud Messagingは、メッセージと通知を無料で確実に配信できるクロスプラットフォームのメッセージングソリューションです。

##### 【GCM送信者IDをホワイトリストに登録する】
[Webアプリマニフェスト](https://developers.google.com/web/fundamentals/web-app-manifest/)では、`gcm_sender_id`を指定する必要があります。
これは、FCMがこのアプリにメッセージを送信することを承認されていることを示すハードコードされた値です。

1. `web-start`ディレクトリの`public`ディレクトリで、`manifest.json`を開きます。
2. 以下に示すように、`gcm_sender_id`属性にブラウザの送信者IDの値を正確に追加します。
(以下に示す値から値を変更しないでください)

```manifest.json
{
  "name": "Friendly Chat",
  "short_name": "Friendly Chat",
  "start_url": "/index.html",
  "display": "standalone",
  "orientation": "portrait",
  "gcm_sender_id": "103953800507"
}
```

##### 【FCMサービスワーカーを追加する】
Webアプリには、Web通知を受信して​​表示する[Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)が必要です。

1. `web-start`ディレクトリから、`public`ディレクトリに`firebase-messaging-sw.js`という名前の新しいファイルを作成します。
2. その新しいファイルに次の内容を追加します。

```firebase-messaging-sw.js
importScripts('/__/firebase/5.6.0/firebase-app.js');
importScripts('/__/firebase/5.6.0/firebase-messaging.js');
importScripts('/__/firebase/init.js');

firebase.messaging();
```

Cloud Messaging SDKをロードして初期化するだけで、通知を表示します。

##### 【FCMデバイストークンを取得する】
通知がデバイスまたはブラウザで有効になると、デバイストークンが与えられます。
このデバイストークンは、特定のデバイスまたは特定のブラウザに通知を送信するために使用するものです。

ユーザーがサインインすると、`saveMessagingDeviceToken`関数が呼び出されます。
そこで、ブラウザからFCMデバイストークンを取得してCloudFirestoreに保存します。

1. `public/scripts/main.js`に戻ります
2. `saveMessagingDeviceToken` 関数を探します。
3. 関数全体を次のコードに置き換えます。

```main.js
// Saves the messaging device token to the datastore.
function saveMessagingDeviceToken() {
  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);
  });
}
```

ただし、このコードは最初は機能しません。
アプリがデバイストークンを取得できるようにするには、ユーザーはアプリに通知を表示する権限を付与する必要があります

1. `public/scripts/main.js`に戻ります
2. `requestNotificationsPermissions` 関数を探します。
3. 関数全体を次のコードに置き換えます。

```main.js
// Requests permission to show notifications.
function requestNotificationsPermissions() {
  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);
  });
}
```

##### 【デバイストークンを取得する】
1. アプリケーションがローカルで提供されている状態なら一度リロードします。
それ以外の場合は`firebase serve`を実行し、localhostをブラウザで開きます。
2. サインイン後、通知許可ダイアログが表示されます。
3. 許可をクリックします。
4. ブラウザのJavaScriptコンソールを開きます。次のようなメッセージが表示されます。
`Got FCM device token: cWL6w:APA91bHP...4jDPL_A-wPP06GJp1OuekTaTZI5K2Tu`
5. デバイストークンをコピーしてください。次の作業で必要です。

##### 【端末に通知を送る】
デバイストークンを取得したので、通知を送信できます。

(1)デバイストークンに加えて、Firebaseアプリの`Server Key`も必要になります。
このキーを取得するには、Firebaseコンソール > プロジェクト設定 > クラウドメッセージング に移動してから`Server Key`をコピーします。

通知を送信するには、次のHTTPリクエストを送信する必要があります。

```
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](https://curl.haxx.se/)コマンドを実行します。

> `YOUR_SERVER_KEY`と`YOUR_DEVICE_TOKEN`をサーバーキーとデバイストークンの値に置き換えることを忘れないでください。

```
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:5000"
           },
           "to": "YOUR_DEVICE_TOKEN"
         }' \
     https://fcm.googleapis.com/fcm/send
```

> あなたがcURLを持っていない、またはインストールするのが難しい(例えば、Windowsの上で)ならこのサービスを使用してください。
> [onlinecurl.com](https://onlinecurl.com/)
> サーバーキーをサードパーティのサービスと共有することは安全ではないかもしれないことに注意してください。
> テスト/開発後に必ずサーバーキーを更新してください。

FriendlyChatアプリがバックグラウンドにある場合にのみ通知が表示されます。
通知を表示するには、移動するか別のタブを表示する必要があります。
アプリがフォアグラウンドにあるとき、FCMによって送信されたメッセージをキャッチする方法があります。
[Firebase ドキュメント ウェブアプリがフォアグラウンドの場合にメッセージを処理する](https://firebase.google.com/docs/cloud-messaging/js/receive#handle_messages_when_your_web_app_is_in_the_foreground)

> フォローアップのコードラボ、[Firebase SDK for Cloud Functions](https://codelabs.developers.google.com/codelabs/firebase-cloud-functions/#0)では、チャットアプリケーションに投稿された新しいメッセージごとにバックエンドからの通知の送信を自動化する方法について説明します。

</div></details>

#### 12.Cloud Firestore security rules(Cloud Firestoreのセキュリティルール)
https://codelabs.developers.google.com/codelabs/firebase-web/#11
この章ではデータベース(Cloud Firestore)のセキュリティ設定を行っていきます。
何も設定していなければ全てのユーザーがコレクションに対して読み書きできる状態になっています。
設定方法はFirebaseコンソールから直接記入、Firebase CLIを使用したローカルファイルのアップの2つの方法があります。

<details><summary>以下、和訳。</summary><div>

##### 【データベースセキュリティルールを表示する】
Cloud Firestoreは特定のルール言語を使用してアクセス権、セキュリティ、およびデータ検証を定義します。

このコードラボの冒頭でFirebaseプロジェクトを設定するときには、データストアへのアクセスを制限しないように、「テストモード」のデフォルトセキュリティルールを使用することを選択しました。
Firebase console > Database > ルールタブ で、これらのルールを表示および変更できます。

現状は、データストアへのアクセスを制限しないデフォルトのルールが表示されるはずです。
これは、すべてのユーザーがデータストア内の任意のコレクションに対して読み書きできることを意味します。

```
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write;
    }
  }
}
```

以下のルールを使用して、少し制限するためにルールを更新します。

```firestore.rules
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;
    }
  }
}
```

> `request.auth`ルール変数は、認証されたユーザーに関する情報を含む特別な変数です。
> `request.resource`ルール変数は、書き込まれている新しいデータを指します。
> より多くの情報は[ドキュメント](https://firebase.google.com/docs/firestore/security/overview)にあります。

##### 【データベースセキュリティルールを更新する】
データベースセキュリティルールを編集するには、FirebaseコンソールまたはFirebase CLIを使用する2つの方法があります。

【Firebase consoleでセキュリティ規則を更新するには】

(1)左側のナビゲーションからDatabaseセクションに移動し、ルールタブをクリックします。
(2)すでにコンソールにあるデフォルトの規則を上記の規則で置き換えます。
(3)公開をクリックします。

【ローカルファイルからセキュリティルールを更新するには】

(1)`web-start`ディレクトリから`firestore.rules`を開きます。
(2)既にファイルに入っているデフォルトの規則を上記の規則で置き換えます。
(3)`web-start`ディレクトリから`firebase.json`を開きます。
(4)以下に示すように、`firestore.rules`を指す`firestore.rules`属性を追加します。
※ホスティング属性はすでにファイルに入っているはずです。

```firebase.json
{
  // Add this!
  "firestore": {
    "rules": "firestore.rules"
  },
  "hosting": {
    "public": "./public"
  }
}
```

(5)次のコマンドを実行して、Firebase CLIを使用してセキュリティルールを配置します。

```
firebase deploy --only firestore
```

(6)コマンドラインに次のような応答が表示されます。

```
=== Deploying to 'friendlychat-1234'...

i  deploying firestore
i  firestore: checking firestore.rules for compilation errors...
✔  firestore: rules file firestore.rules compiled successfully
i  firestore: uploading rules firestore.rules...
✔  firestore: released rules firestore.rules to cloud.firestore

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlychat-1234/overview
```

</div></details>

#### 13.Cloud Storage security rules(Cloud Storageのセキュリティルール)
https://codelabs.developers.google.com/codelabs/firebase-web/#12
前回の章ではデータベース(Cloud Firestore)のセキュリティルールを設定しました。
この章ではストレージ(Cloud Storage)に関するセキュリティルールを更新します。
設定方法は、12章の内容と似た感じです。

<details><summary>以下、和訳。</summary><div>

##### 【Cloud Storageのセキュリティルールを表示する】
Cloud Storageは、アクセス権、セキュリティ、およびデータ検証を定義するために特定のルール言語を使用します。
このコードラボの冒頭でFirebaseプロジェクトを設定するときは、認証されたユーザーにのみCloud Storageの使用を許可するデフォルトのCloud Storageセキュリティルールを使用することを選択しました。
Firebase console > Storage > ルール で、規則を表示および変更できます。
サインインしているすべてのユーザーがあなたのStorage bucket内のファイルを読み書きすることを許可するデフォルトのルールが確認できます。

```
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}
```

ルールを次のように更新します。

- 各ユーザーに自分の特定のフォルダへの書き込みのみを許可
- 誰でもクラウドストレージから読み取ることを許可
- アップロードされたファイルが画像であることを確認(バリデーション)
- アップロードできる画像のサイズを最大5MBに制限

これは、次のコードで実装できます。

```storage.rules
// 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;
    }
  }
}
```

> `request.auth`ルール変数は、認証されたユーザーに関する情報を含む特別な変数です。
> `request.resource`ルール変数には、アップロードされたファイルに関する情報が含まれています。
> より多くの情報は[ドキュメント](https://firebase.google.com/docs/storage/security/start)にあります。

##### 【Cloud Storageのセキュリティルールを更新する】
データベースセキュリティルールを編集するには、FirebaseコンソールまたはFirebase CLIを使用する2つの方法があります。

【Firebase consoleでセキュリティ規則を更新するには】

(1)左側のナビゲーションからStorageセクションに移動し、ルールタブをクリックします。
(2)すでにコンソールにあるデフォルトの規則を上記の規則で置き換えます。
(3)公開をクリックします。

【ローカルファイルからセキュリティルールを更新するには】

(1)`web-start`ディレクトリから`storage.rules`を開きます。
(2)既にファイルに入っているデフォルトの規則を上記の規則で置き換えます。
(3)`web-start`ディレクトリから`firebase.json`を開きます。
(4)以下に示すように、`storage.rules`を指す`storage.rules`属性を追加します。
※ホスティングとデータベースの属性はすでにファイルに入っているはずです。

```firebase.json
{
  // If you went through the "Cloud Firestore Security Rules" step.
  "firestore": {
    "rules": "firestore.rules"
  },
  // Add this!
  "storage": {
    "rules": "storage.rules"
  },
  "hosting": {
    "public": "./public"
  }
}
```

(5)次のコマンドを実行して、Firebase CLIを使用してセキュリティルールを配置します。

```
firebase deploy --only storage
```

(6)コマンドラインに次のような応答が表示されます。

```
=== Deploying to 'friendlychat-1234'...

i  deploying storage
i  storage: checking storage.rules for compilation errors...
✔  storage: rules file storage.rules compiled successfully
i  storage: uploading rules storage.rules...
✔  storage: released rules storage.rules to firebase.storage/friendlychat-1234.appspot.com

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlychat-1234/overview
```

</div></details>

#### 14.Deploy your app using Firebase Hosting(Hostingを使用してアプリをデプロイ)
https://codelabs.developers.google.com/codelabs/firebase-web/#13
この章では作成したアプリケーションをFirebase CLIを使用してデプロイし本番公開します。
`.web-start/firebase.json`に設定を追加しコマンドを打つだけで本番公開ができてしまいます。
また、Firebaseコンソール > Hosting にてバージョン管理や履歴の管理、ロールバックまでできます。

<details><summary>以下、和訳。</summary><div>

Firebaseはあなたのアセット/ Webアプリケーションを提供するホスティングサービスを提供しています。
Firebase CLIを使用して、ファイルをFirebase Hostingに配置できます。
デプロイする前に、どのローカルファイルをデプロイするかをfirebase.jsonファイルに指定する必要があります。
※このコードラボでは、ファイルを処理するために必要であったため、既に指定されています。
ホスティング設定は、ホスティング属性で指定されます。

```firebase.json
{
  // If you went through the "Cloud Firestore Security Rules" step.
  "firestore": {
    "rules": "firestore.rules"
  },
  // If you went through the "Storage Security Rules" step.
  "storage": {
    "rules": "storage.rules"
  },
  "hosting": {
    "public": "./public"
  }
}
```
これらの設定はCLIに`./public`ディレクトリ(`"public"： "./public"`)内のすべてのファイルをデプロイすることを伝えます。

(1)コマンドラインがアプリのローカル`web-start`ディレクトリにアクセスしていることを確認してください。
(2)次のコマンドを実行して、ファイルをFirebaseプロジェクトに配置します。

```
firebase deploy --except functions
```

(3)コンソールに次のように表示されます。

```
=== Deploying to 'friendlychat-1234'...

i  deploying firestore, storage, hosting
i  storage: checking storage.rules for compilation errors...
✔  storage: rules file storage.rules compiled successfully
i  firestore: checking firestore.rules for compilation errors...
✔  firestore: rules file firestore.rules compiled successfully
i  storage: uploading rules storage.rules...
i  firestore: uploading rules firestore.rules...
i  hosting[friendlychat-1234]: beginning deploy...
i  hosting[friendlychat-1234]: found 8 files in ./public
✔  hosting[friendlychat-1234]: file upload complete
✔  storage: released rules storage.rules to firebase.storage/friendlychat-1234.appspot.com
✔  firestore: released rules firestore.rules to cloud.firestore
i  hosting[friendlychat-1234]: finalizing version...
✔  hosting[friendlychat-1234]: version finalized
i  hosting[friendlychat-1234]: releasing new version...
✔  hosting[friendlychat-1234]: release complete

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlychat-1234/overview
Hosting URL: https://friendlychat-1234.firebaseapp.com
```

(4)あなた自身のFirebaseサブドメインでFirebase Hostingを使って完全にホスティングされているWebアプリにアクセスしてください。
`https://<firebase-project-id>.firebaseapp.com`
あるいは、コマンドラインで`firebase open hosting:site`を実行することもできます。

プロジェクトのFirebaseコンソールのHostingセクションに移動して、デプロイの履歴、以前のバージョンのアプリにロールバックする機能、カスタムドメインを設定するためのワークフローなど、便利なホスティング情報とツールを表示します。

</div></details>

#### 15.Congratulations!(おめでとうございます!)
https://codelabs.developers.google.com/codelabs/firebase-web/#14
14章までの内容でwebチャットアプリの作成は完了しました。
この章では、作成したwebチャットアプリを更に拡張する方法や、Cloud Firestoreの深い理解に役立つ
他のコードラボへの案内が記載されています。

<details><summary>以下、和訳。</summary><div>

Firebaseを使用してリアルタイムチャットWebアプリケーションを構築しました。
今回、使用したものは以下になります。

- Authentication
- Cloud Firestore
- Cloud Storage
- Cloud Messaging
- Hosting

次のステップ：

> Cloud Functionsの使用方法およびチャットアプリにバックエンドタスクを追加する方法については、[Cloud Functions for Firebaseコードラボ](https://codelabs.developers.google.com/codelabs/firebase-cloud-functions/#5)に進んでください。
> Firebaseプロジェクトはすでに設定されているので、そのcodelabの手順5から直接始めることができます。

> Cloud Firestoreについてもっと知りたいですか？サブコレクションとトランザクションについて学びます。
> Cloud Firestoreの詳細については、[Cloud Firestore Web Codelab](https://codelabs.developers.google.com/codelabs/firestore-web/#0)を参照してください。

もっと詳しく知る：
[firebase.google.com](https://firebase.google.com/)

</div></details>

## チュートリアルを終えて
### 思ったこと
- FirebaseってCUI、GUIでの操作に対応していて良い。
- DBの設計等、SQL文などを打つ必要が無いのでDB触ったことない人でもできそう。
- デプロイが手軽、ロールバックもGUIでできてしまうの難しい知識は不要。

### 今後について
他のコードラボもやってみようと思います。
また、今回の記事を執筆中にGoogleからFlutter for Webが発表されました。
こちらも気になるので勉強していこうと思います。
[Flutter - Beautiful native apps in record time](https://flutter.dev/)

## 最後に
本記事の内容は冒頭でも説明しましたが、[Google Developers Codelabs - Firebase Web Codelab](https://codelabs.developers.google.com/codelabs/firebase-web/)の内容を翻訳したものになります。
内容に誤字、脱字、不備などありましたらコメント等でお知らせいただけると幸いです。