🔥

Firebase とVanillaJavaScript で手書きノートアプリ作成(その2)

2020/05/06に公開

Firebase と VanillaJavaScript で手書きノートアプリを作成しました。
今回はその2ということで、認証関連のコードの説明、リファクタリング、その他についてまとめていきます。

その1:完成までの経緯や基本機能部分について画面とコードの説明はこちら

1. コードの説明(続き)

1.1. 認証関連のコード

ユーザーログインの状況を監視して、トップページの UI を構成するコード(重要)
if 文でユーザーがログインしている場合と、ログインしていない場合と切り分けて、UI の構成を変更します。
Firebase.auth の auth.onAuthStateChanged() や、Firestore の  onSnapShot()メソッドを使い状態の変化をリアルタイムに反映します。

./src/index.js
// listen for auth status changes
auth.onAuthStateChanged((user) => {
  if (user) {
    document.querySelector(".actions").style.display = "block";

    db.collection("notes")
      .where("createdBy", "==", user.uid)
      .onSnapshot(
        (snapshot) => {
          setupUI(user);

          if (document.getElementById("logout-msg")) {
            document.getElementById("logout-msg").innerHTML = ``;
          }

          let changes = snapshot.docChanges();
          setUpList(changes);

          document.querySelector(".footer").innerHTML = `
                <div class="footer">
                    <button id="createNote" class="bottomButton">CanvasNoteを作成する</button>
                </div>
            `;
          createNote(user);
        },
        function(error) {
          console.log(error.message);
        }
      );
  } else {
    setupUI();
    setUpList([]);

    document.querySelector(".actions").style.display = "none";

    document.getElementById("notes").innerHTML = `
            <h5 class="logout-msg" id="logout-msg" >Please login to add CanvasNote </h5>
        `;
    document.querySelector(".footer").innerHTML = ``;
  }
});

躓いた点
ユーザーのログイン状況に合わせて、表示するデータを切り分けるための設定を後程記述する Firestore のデータベースルールで記述し、FirestoreRules のシミュレーターでも問題ないことを確認しましたが、実際のアプリ上でエラーが発生し、なかなか原因がつかめませんでした。

(エラー内容)


index.cjs.js:14358 Uncaught Error in onSnapshot: FirebaseError: Missing or insufficient permissions.

原因は、Firestore のデータベースルール上で、ログインしたユーザーの情報しか見れないように設定しているにも関わらず、すべてのデータを onSnapshot()で取得しようとしているために発生していることがわかりました。
最終的には、db 接続構文の中に wehre 条件をいれて解決しました。

./src/index.js

db.collection('notes').where('createdBy', '==', user.uid).onSnapshot(snapshot => { //以降省略

同じエラーが、ソート等のイベントでも発生したので、同様に修正することでエラーを解消しました。

1.2. ログイン機能・モーダル画面

Firebase.auth の auth.signInWithEmailAndPassword(email,password) メソッドを使い、実装しました。ログイン成功時には、モーダル表示を消し、初期画面に自動的に遷移するようにし、ログイン失敗時には、ログイン画面に、ログイン失敗の表示をする設定としました。

./src/index.js
// login
loginForm.addEventListener("submit", (e) => {
  e.preventDefault();

  const email = loginForm["login-email"].value;
  const password = loginForm["login-password"].value;

  auth
    .signInWithEmailAndPassword(email, password)
    .then((cred) => {
      document.getElementById("modal-login").classList.remove("active");
      document.getElementById("mask").classList.remove("active");
      loginForm.reset();
    })
    .catch(() => {
      document.querySelector(".login-error").classList.add("error");
      document.getElementById("login-password").value = ``;
      console.log("login error");
    });
});

1.3. データ参照・編集権限設定の為のデータ項目の追加(createdBy)

ユーザー毎の編集権限切り替えの為に、createdBy の項目に user.uid 情報を格納するコードを createNote 関数に記述します。

これは、初期画面の CreateNote のボタンをクリックすると、動く処理です。
日付関連データと createdBy 以外は空のデータを登録し、登録が完了したら、ドキュメント ID を取得して、それを編集画面のリンク先として遷移する処理となっています。

./src/notes-function.js
// create createNote
const createNote = (user) => {
  document.querySelector("#createNote").addEventListener("click", (e) => {
    const timestamp = moment().valueOf();

    db.collection("notes")
      .add({
        title: "",
        body: "",
        canvas: [],
        createdAt: timestamp,
        updatedAt: timestamp,
        createdBy: user.uid,
      })
      .then((doc) => {
        location.assign(`edit.html#${doc.id}`);
      });
  });
};

1.4. Firestore ルールについて

ユーザー参照・編集権限の設定の為に、上記のフロントエンド側のコードと合わせて、Firestore 側のルール設定が必要となります。

新しいノートを作ることができるのは、ログインユーザーすべて、
read,update,delete は、ログイン認証されてかつ、認証したユーザー自身が作成した、ドキュメントのみとする設定です。

ポイントは、以下の構文部分。

request.auth.uid == resource.data.createdBy;

左辺が、firebase.auth がもつ uid の情報、右辺が firestore に格納済みの createdBy の情報(ここにはデータを作成者の uid 情報が入っている)

firestore.rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    match /{document=**} {

        // match docs
        match /notes/{noteID}{
          allow create: if request.auth.uid != null;
          allow read :  if request.auth.uid != null && request.auth.uid == resource.data.createdBy;
          allow update,delete :  if request.auth.uid != null && request.auth.uid == resource.data.createdBy ;
        }
    }
  }
}

躓いた点としては、firestore ルールのシミュレータ(ルールプレイグランドの使い方)

ルールプレイグランドの場所のところに、/notes/{noteID}のように入力すると、request.auth.uid == resource.data.createdBy の右辺側、resource.data が空になるので、シミュレーターが動きません

null value のエラー内容


シミュレーションの実行中にエラーが発生しました - Error: simulator.rules line [10], column [76]. Null value error.

まちがった設定方法
ルール設定:間違い

正しくは、

場所:/notes/LWSQLz4jPULDhxxT1234

のように、実際のドキュメント ID を入れる必要があります。

そのほか、FirebaseUID の欄にも、実際の uid のデータをいれることで、シミュレーターが正しく動作します。

1.5. リファクタリングについて(webpack 導入)

Step1 で作成した、LocalStorage のコードをリファクタリングしました。

1)webpack の導入

HMTL ファイルが2つあるので、バンドルする js ファイルも 2 ファイルになるよう設定した。

webpack.config.js

    entry: {
        index: ['babel-polyfill','./src/index.js'],
        edit: ['babel-polyfill','./src/edit.js']
    },
    output: {
        path: path.resolve(__dirname,'public/scripts'),
        filename: '[name]-bundle.js'
    },

2)canvas 関連のコード整理

後から追加した canvas 関連の function を別ファイルで定義し、export/import で読み込む構成とした。

1.6. Firebase   Hosting について

deploy は FirebaseHosting を使用しました。
完成した Firebase アプリケーションを簡単に公開することができます。

注意点

  1. クラウド上で Firestore ルールを設定しても、標準の設定では、ローカルにある、firestore.rules の情報が優先して上書きされる。ただし、過去の設定にもどることは簡単なので焦らず対応すれば問題なし。

  2. 最新のコードを反映する為、Firebase deploy の前に npm run build を行う。

2. 今後の課題

  1. リファクタリング
    改善の余地があるので、「リーダブルコード」を読んでさらにブラッシュアップする。

  2. テストについて
     コードを修正したあと、以下のケースについて、網羅的に動作確認する必要がある。
    ・通常表示からの リンク、削除
    ・テキストフィルターからの リンク、削除
    ・ソートからの リンク、削除
    ・edit 画面からのデータ編集、削除 等
    手動でやっていると効率が悪いので、テストの自動化を検討する。

3. 参考リンク

Udemy The Modern JavaScript Bootcamp
https://www.udemy.com/course/modern-javascript/

Canvas に「戻る」や「進む」の機能を搭載してみた
https://www.kabanoki.net/1823/

Firestore rules tips
https://qiita.com/sgr-ksmt/items/1a731fdadf06119d35fc

Firebase Firestore Tutorial (全10本)
YouTubeのvideoIDが不正ですhttps://www.youtube.com/playlist?list=PL4cUxeGkcC9itfjle0ji1xOZ2cjRGY_WB

Firebase Auth Tutorial  (全24本)
YouTubeのvideoIDが不正ですhttps://www.youtube.com/playlist?list=PL4cUxeGkcC9jUPIes_B8vRjn1_GaplOPQ

Discussion