NETWORK ENGINEER BLOG

Tips and Reviews for Engineers

Firebase web codelab について

はじめに

Firebase でチャットアプリを作成するチュートリアル(Firebase web codelab)を試してみました。
チュートリアルでは、以下のことが学べます。

  • Cloud Firestore と Cloud Storage のデータ同期
  • Firebase Authentication のユーザー認証
  • Web アプリケーションのデプロイ
  • Firebase Cloud Messaging の通知送信
  • Web アプリケーションのパフォーマンスデータ収集

環境

・Firebase 8.2.1
・openSUSE Tumbleweed 20201229
・Virtual Machine:2vCPU, 4GB Memory, 40GB Disk
・VMware(R) Workstation 15 Pro

事前準備

root ユーザー移行

sudo su -

git インストール

zypper install git

npm インストール

zypper install npm

サンプルコードの入手

codelab のリポジトリを複製

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

Firebase プロジェクト作成

Firebase にログインして、プロジェクトを作成します。
f:id:FriendsNow:20210101233700p:plain

Firebase Authentication の設定

ウェブアイコン をクリックして、Firebase Web アプリを作成します。
f:id:FriendsNow:20210101233914p:plain

左ペインの「Authentication」➡「Sign-in method」で「Google」をクリックして、有効化します。
f:id:FriendsNow:20210101235629p:plain

Google Cloud Console で OAuth 同意画面を設定します。*1
f:id:FriendsNow:20210102000111p:plain

Cloud Firestore の設定

左ペインの「Cloud FIrestore」から「データベース作成」をクリックして「テストモード」を有効化します。
f:id:FriendsNow:20210102000734p:plain

Firebase CLI インストール

CLI インストール

sudo su -
npm -g install firebase-tools

インストール確認

cd /home/hatkobelab/codelab-friendlychat-web/web-start/
firebase --version

Firebase CLI 承認

firebase login

※CLI をインストールした OS 上で実行する必要があります。それ以外の場合は、localhost に接続できずエラーとなります。本例では、以下のように openSUSE で実行しました。
f:id:FriendsNow:20210102001319p:plain

アプリと Firebase プロジェクトを紐づけ

firebase use --add
? Which project do you want to add? friendlychat-46e4f
? What alias do you want to use for this project? (e.g. staging) default

アプリをローカルで実行

アプリを実行

firebase serve --only hosting

※上述と同様に OS 上で実行します。

ブラウザから http://localhost:5000 に接続
f:id:FriendsNow:20210102001635p:plain
アプリの UI が接続されますが、認証がとおっていないため、まだ機能しません。

ユーザーサインイン設定

チュートリアルでは、Google サインインでユーザー認証するためのコードが用意されているので、対象の関数を置き換えます。ファイルは web-start/public/scripts/main.js です。

関数:signIn

// 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

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

関数:initFirebaseAuth

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

関数:getProfilePicUrl

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

関数:getUserName

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

関数:isUserSignedIn

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

サインインのテストは、上述と同様に OS 上で実行します。

firebase serve --only hosting

以下のように Google でサインインができるようになります。
f:id:FriendsNow:20210102002202p:plain

メッセージの書き込み設定

アプリケーションの UI にデータ入力ができるように Cloud Firestore にデータを書き込みます。ユーザーが作成したメッセージは、Cloud Firestore に保存されます。
チュートリアルでは、ユーザーが「Send」ボタンをクリックすると、データベースに保存するコードが用意されているので、対象の関数を置き換えます。ファイルは web-start/public/scripts/main.js です。

関数:saveMessage

// Saves a new message to your Cloud Firestore database.
function saveMessage(messageText) {
  // Add a new message entry to the 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 database', error);
  });
}

ブラウザから http://localhost:5000 に接続し「hello」などのメッセージを入力して「Send」ボタンをクリックすると、Firebase コンソールでメッセージを確認できます。
f:id:FriendsNow:20210102182214p:plain

メッセージの読み取り設定

アプリケーションでメッセージを読み取り、表示させます。
データベース内のメッセージが更新された際に、新しいメッセージをアプリケーションに表示させるように対象の関数を置き換えます。ファイルは web-start/public/scripts/main.js です。

関数:loadMessages

// 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);
      }
    });
  });
}

画像送信設定

画像を共有する機能を追加します。
Cloud Firestore は、構造化データの保存に適していますが、Cloud Storage はファイルの保存に適しています。
ユーザーがアプリケーションを使用して共有する画像を Cloud Storage に保存するように対象の関数を置き換えます。ファイルは web-start/public/scripts/main.js です。

関数:saveImageMessage

// 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);
  });
}

ブラウザから http://localhost:5000 に接続し「hello」などのメッセージを入力して「Send」ボタンをクリックすると、アプリケーションに表示されます。また、画像をアプリケーションにアップロードすることもできます。
f:id:FriendsNow:20210102221928p:plain

パフォーマンスデータ収集

Performance Monitoring SDK を使用して、アプリケーションからパフォーマンスデータを収集し、Firebase コンソールでデータの確認および分析ができます。

自動トレースの設定

アプリケーション利用時に自動的にパフォーマンスデータ監視を行うためには、main.js に以下を追加します。

// TODO: Initialize Firebase Performance Monitoring.
firebase.performance();

パフォーマンスデータの表示

Firebase コンソールからパフォーマンスデータを確認できます。通常は、この後の手順で実施するデプロイ後、12時間以内に表示されます。
f:id:FriendsNow:20210102223620p:plain

アプリケーションのデプロイ

Firebase Hosting を使用してアプリケーションを Firebase プロジェクトにデプロイします。
コマンドは以下を使用します。

> cd /home/hatkobelab/codelab-friendlychat-web/web-start/
> firebase deploy --except functions

デプロイ後、Firebase Hosting の URL(以下の2つの Firebase 独自サブドメイン)から、ウェブアプリケーションにアクセスできるようになります。

https://<firebase-projectId>.firebaseapp.com
https://<firebase-projectId>.web.app

f:id:FriendsNow:20210102225510p:plain

通知設定

Firebase Cloud Messaging を使用して、ウェブアプリに通知を配信することができます。

送信者 ID 許可

アプリケーション側で、通知を許可する送信 ID を設定する必要があります。 web-start/public/manifest.jsongcm_sender_id で設定します。※固定値で変更できません。

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

Service Worker の追加

通知をアプリケーションに表示させるために必要な Service Worker を追加します。 web-start/public ディレクトリに irebase-messaging-sw.js という名前のファイルを作成して、以下のコンテンツを追加します。*2

// Import and configure the Firebase SDK
// These scripts are made available when the app is served or deployed on Firebase Hosting
// If you do not want to serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup
importScripts('/__/firebase/8.2.1/firebase-app.js');
importScripts('/__/firebase/8.2.1/firebase-messaging.js');
importScripts('/__/firebase/init.js');

firebase.messaging();

デバイストークンの取得

デバイスまたはブラウザで通知が有効になると、デバイストークンが提供されます。
ユーザーがサインインすると、ブラウザからデバイストークンを取得し、Cloud Firestore に保存するように設定します。ファイルは web-start/public/scripts/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);
  });
}

通知権限のリクエスト

ユーザーに通知許可を求めるダイアログをブラウザに表示させます。ユーザーで許可していない場合、デバイストークンは提供されません。

// 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);
  });
}

サインイン後、通知許可ダイアログが表示されます。
f:id:FriendsNow:20210102234510p:plain
「許可」をクリックすると、ブラウザの JavaScript コンソールに次のメッセージが表示されます。

Got FCM device token: cWL6w:APA91bHP...4jDPL_A-wPP06GJp1OuekTaTZI5K2Tu

ブラウザに通知を送信

デバイストークンと、サーバーキーを設定し、次の cURL コマンドを実行します。
サーバーキーは、Firebase コンソール から「プロジェクトの概要」➡「プロジェクトを設定」➡「Cloud Messaging」 で確認できます。

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

アプリケーションがバックグラウンドにある場合、次のようにブラウザに通知が表示されます。
f:id:FriendsNow:20210102235944p:plain

以上

*1:ロゴのアップロードが必須なので注意

*2:8.2.1 は Firebase のバージョンで最新化が必要です。最新のバージョンはこちら