Firebase とVanillaJavaScript で手書きノートアプリ作成(その2)
Firebase と VanillaJavaScript で手書きノートアプリを作成しました。
今回はその2ということで、認証関連のコードの説明、リファクタリング、その他についてまとめていきます。
その1:完成までの経緯や基本機能部分について画面とコードの説明はこちら
1. コードの説明(続き)
1.1. 認証関連のコード
ユーザーログインの状況を監視して、トップページの UI を構成するコード(重要)
if 文でユーザーがログインしている場合と、ログインしていない場合と切り分けて、UI の構成を変更します。
Firebase.auth の auth.onAuthStateChanged() や、Firestore の onSnapShot()メソッドを使い状態の変化をリアルタイムに反映します。
// 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 条件をいれて解決しました。
db.collection('notes').where('createdBy', '==', user.uid).onSnapshot(snapshot => { //以降省略
同じエラーが、ソート等のイベントでも発生したので、同様に修正することでエラーを解消しました。
1.2. ログイン機能・モーダル画面
Firebase.auth の auth.signInWithEmailAndPassword(email,password) メソッドを使い、実装しました。ログイン成功時には、モーダル表示を消し、初期画面に自動的に遷移するようにし、ログイン失敗時には、ログイン画面に、ログイン失敗の表示をする設定としました。
// 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 を取得して、それを編集画面のリンク先として遷移する処理となっています。
// 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 情報が入っている)
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 ファイルになるよう設定した。
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 アプリケーションを簡単に公開することができます。
注意点
-
クラウド上で Firestore ルールを設定しても、標準の設定では、ローカルにある、firestore.rules の情報が優先して上書きされる。ただし、過去の設定にもどることは簡単なので焦らず対応すれば問題なし。
-
最新のコードを反映する為、Firebase deploy の前に npm run build を行う。
2. 今後の課題
-
リファクタリング
改善の余地があるので、「リーダブルコード」を読んでさらにブラッシュアップする。 -
テストについて
コードを修正したあと、以下のケースについて、網羅的に動作確認する必要がある。
・通常表示からの リンク、削除
・テキストフィルターからの リンク、削除
・ソートからの リンク、削除
・edit 画面からのデータ編集、削除 等
手動でやっていると効率が悪いので、テストの自動化を検討する。
3. 参考リンク
Udemy The Modern JavaScript Bootcamp
Canvas に「戻る」や「進む」の機能を搭載してみた
Firestore rules tips
Firebase Firestore Tutorial (全10本)
YouTubeのvideoIDが不正ですFirebase Auth Tutorial (全24本)
YouTubeのvideoIDが不正です
Discussion