前々回でFirestoreを利用してリアルタイムな通信が行えるチャットを、前回はユーザー認証を導入しました。
今回は自分が書き込んだログを削除する機能を追加してみます。
目次
サンプル
実行結果
- 書き込むためにはログインが必要です。閲覧は未ログイン状態でも行えます。
- 自分が書き込んだログは背景がピンク色になり、クリックすると削除できます。
- 自由に投稿していただいて問題ありませんが、公序良俗に反する書き込みはご遠慮ください。
- 適当なタイミングでお掃除します。
ソースコード
config.js
Firebaseのコンソールで表示される内容をそのままコピペしconfig.js
というファイル名で保存しました。これまでと同じです。
// コンソールの内容をそのままコピペ
var config = {
apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authDomain: "test-f76bc.firebaseapp.com",
databaseURL: "https://test-f76bc.firebaseio.com",
projectId: "test-f76bc",
storageBucket: "test-f76bc.appspot.com",
messagingSenderId: "1111111111111111"
};
firebase.initializeApp(config);
index.html
チャットの本体です。大まかな変更点は以下です。
- Firestoreへ書き込む際にFirebase AuthenticationのユーザーIDを記録 (
Chat.sendLogCreate()
) - Firestoreから発言を削除するメソッドを追加(
Chat.sendLogRemove()
) - 画面に発言を描画する際にユーザー情報を埋め込みつつ、イベントを定義(
Chat.addLog()
)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Firestore Chat3</title>
<style>
#chatlog{ width:520px; height:300px; border:1px solid gray; overflow-y:scroll; }
#pleaselogin{ width:540px; border:1px solid gray; padding:10px; background-color:lightgrey; }
#uname{ width:130px; float:left; margin-right:10px; padding-top:5px; text-align:center}
#msg{ width:300px; height:30px; margin-right:10px; font-size:12pt;}
#sbmt{ width:100px; height:30px; }
.hide{display:none}
.chatlog-item{cursor:default;}
.chatlog-item-mine{background-color:lightpink; cursor:pointer;}
</style>
</head>
<body>
<h1>Firestore Chat3</h1>
<!-- 発言が表示される領域 -->
<ul id="chatlog"></ul>
<!-- 入力フォーム -->
<div id="pleaselogin">
※チャットへの書き込みは<a href="/db/firestore4/login.html">ログイン</a>が必要です。
</div>
<form id="form1" class="hide">
<div id="uname"></div>
<input type="text" id="msg"><button type="button" id="sbmt">送信</button>
</form>
<script src="https://www.gstatic.com/firebasejs/5.8.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.8.1/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.8.1/firebase-firestore.js"></script>
<script src="/js/config.js"></script>
<script>
/**
* Chatオブジェクト
**/
var Chat = {
//----------------------------------------
// プロパティ
//----------------------------------------
user: {
uid: null,
name: null
},
db: null,
messagesRef: null,
//----------------------------------------
// メソッド
//----------------------------------------
/**
* 初期処理
**/
init: () => {
this.db = firebase.firestore();
this.messagesRef = this.db.collection("chatroom").doc("room3").collection("messages");
//---------------------
// 同期処理
//---------------------
this.messagesRef.orderBy("date", "asc").limit(20).onSnapshot( (snapshot) => {
snapshot.docChanges().forEach((change) => {
// 追加
if ( change.type === 'added' ) {
Chat.addLog(change.doc.id, change.doc.data());
}
// 更新
else if( change.type === 'modified' ){
Chat.modLog(change.doc.id, change.doc.data());
}
// 削除
else if ( change.type === 'removed' ) {
Chat.removeLog(change.doc.id);
}
});
});
//---------------------
// 送信ボタン
//---------------------
document.getElementById("sbmt").addEventListener("click", ()=>{
let msg = document.getElementById("msg").value;
if( msg.length !== 0 ){
Chat.sendLogCreate(msg);
}
});
// submitイベントは(いったん)無視する
document.getElementById("form1").addEventListener("submit", (e)=>{
e.preventDefault();
});
},
/**
* [Firestoreへ送信] ログを追加
*
* @param {string} str メッセージ
**/
sendLogCreate: (str)=>{
this.messagesRef.add({
name: Chat.user.name,
msg: str,
date: new Date().getTime(),
uid: Chat.user.uid
})
.then(()=>{
let msg = document.getElementById("msg");
msg.focus();
msg.value = "";
})
.catch((error) => {
console.log(`追加に失敗しました (${error})`);
});
},
/**
* [Firestoreへ送信] ログを削除
*
* @param {string} id
**/
sendLogRemove: (id)=>{
this.messagesRef.doc(id).delete()
.then(()=>{
console.log(`削除しました (${id})`);
})
.catch((error) => {
console.log(`削除に失敗しました (${error})`);
});
},
/**
* 描画エリアにログを追加
*
* @param {string} id
* @param {object} data
**/
addLog: (id, data)=>{
// 追加するHTMLを作成
let log = `${data.name}: ${data.msg} (${getStrTime(data.date)})`;
let li = document.createElement('li');
li.id = id; // id属性を作成
li.dataset.uid = data.uid; // data-uid属性を作成
li.classList.add( (data.uid===Chat.user.uid)? "chatlog-item-mine":"chatlog-item");
li.appendChild(document.createTextNode(log));
// 表示エリアへ追加
let chatlog = document.getElementById("chatlog");
chatlog.insertBefore(li, chatlog.firstChild);
// 削除機能をセット
document.getElementById(id).addEventListener("click", (e)=>{
let uid = e.target.dataset.uid;
let id = e.target.id;
if( uid === Chat.user.uid && confirm("削除しますか?") ){
Chat.sendLogRemove(id);
}
});
},
/**
* 描画エリアのログを変更
*
* @param {string} id
* @param {object} data
**/
modLog: (id, data)=>{
let log = document.getElementById(id);
if( log !== null ){
log.innerText = `${data.name}: ${data.msg} (${getStrTime(data.date)})`;
}
},
/**
* 描画エリアのログを削除
*
* @param {string} id
**/
removeLog: (id)=>{
let log = document.getElementById(id);
if( log !== null ){
log.parentNode.removeChild(log);
}
}
}; // Chat
/**
* 描画エリアのログを変更
*
* @param {string} id
* @param {object} data
**/
firebase.auth().onAuthStateChanged( (user) => {
// ログイン状態なら書き込みフォームを開放
if( user !== null ){
//隠す
document.getElementById("pleaselogin").classList.add("hide");
//表示
document.getElementById("chatlog").classList.remove("hide");
document.getElementById("form1").classList.remove("hide");
// ユーザー情報を確保
Chat.user.uid = user.uid;
Chat.user.name = user.displayName;
document.getElementById("uname").innerText = Chat.user.name;
}
// Firestore処理開始
Chat.init();
});
/**
* UNIX TIME => MM-DD hh:mm
**/
function getStrTime(time){
let t = new Date(time);
return(
("0" + (t.getMonth() + 1)).slice(-2) + "-" +
("0" + t.getDate() ).slice(-2) + " " +
("0" + t.getHours() ).slice(-2) + ":" +
("0" + t.getMinutes() ).slice(-2)
);
}
</script>
</body>
</html>
login.html
このファイルは前回からの大きな変更はありません。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Firestore Chat3</title>
<link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.css" />
<style>#auth{text-align: center;}</style>
</head>
<body>
<section id="auth">
<h1>Firestore Chat3</h1>
<p>ログインまたは新規登録をしてください</p>
<div id="firebaseui-auth-container"></div>
</section>
<script src="https://www.gstatic.com/firebasejs/5.8.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.8.1/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/ui/3.5.2/firebase-ui-auth__ja.js"></script>
<script src="/js/config.js"></script>
<script>
//----------------------------------------------
// Firebase UIの設定
//----------------------------------------------
var uiConfig = {
// ログイン完了時のリダイレクト先
signInSuccessUrl: '/db/firestore4/',
// 利用する認証機能
signInOptions: [
firebase.auth.EmailAuthProvider.PROVIDER_ID //メール認証
],
// 利用規約のURL(任意で設定)
tosUrl: 'http://example.com/kiyaku/',
// プライバシーポリシーのURL(任意で設定)
privacyPolicyUrl: 'http://example.com/privacy'
};
var ui = new firebaseui.auth.AuthUI(firebase.auth());
ui.start('#firebaseui-auth-container', uiConfig);
</script>
</body>
</html>
解説
Firestoreにセキュリティルールを追加する
Firebaseのコンソールにログインし、メニュー「Database」→「ルール」とたどり、前回は最終的に以下のような設定を行いました。
service cloud.firestore {
match /databases/{database}/documents {
match /chatroom/room2/messages/{document} {
allow get: if true;
allow list: if request.query.limit <= 20;
allow write: if request.auth.uid != null;
}
}
}
これを今回は以下のように書き換えます。
service cloud.firestore {
match /databases/{database}/documents {
match /chatroom/room3/messages/{document} {
allow get: if true;
allow list: if request.query.limit <= 20;
allow create: if request.auth.uid != null;
allow update, delete: if request.auth.uid == resource.data.uid;
}
}
}
read
と同様にwrite
も細かくばらして定義を行うことができます。ここでは以下のような定義を行いました。
- 新規に作成する際(
create
)には認証済みであること - 更新(
update
)と削除(delete
)は自分自身が作成したレコードであること
request
オブジェクトはFirestoreへの外部から来たリクエストを指し、resource
オブジェクトは操作対象とするFirestore内のデータを指します。ここではresource.data.uid
としていますが、これはuid
という名前でドキュメント内にフィールドを用意しているためです。
他者の発言を消そうとしてみる
index.html
の以下のif文を変更すれば自分以外の発言に対しても、Firestoreへ削除を命じることが可能になるので試しに実行してみます。
// befor
if( uid === Chat.user.uid && confirm("削除しますか?") ){
// after
if( confirm("削除しますか?") ){
すると、正常にルールの設定が行われていれば以下のようにPermission denied的なエラーとなります。
FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
セキュリティルールの設定を行っていない場合、他の人のデータも消したい放題ですので必ずルールが正しく設定されたかチェックすることをおすすめします。
続き
参考ページ
このブログを応援する
お寄せいただいたお気持ちは全額サーバ代や次の記事を執筆するための原資として活用させていただいております。この記事が参考になった場合などぜひご検討ください。
同じカテゴリの記事
- [Firebase] Cloud Functionsの実行結果をCDNにキャッシュする
- [Firebase] Functionsで環境変数を参照/設定する
- Firebaseからのメール「Firebase CLI lower than 7.7.0 will need to explicitly grant access through Cloud IAM」
- [Firebase] デプロイ対象を一部のサービスに限定する
- [Firebase] CloudFunctionsのRESTful APIを独自ドメインで利用する
- [Firebase] Authenticationで複数の認証プロバイダへ同時対応する (Web編)
- [Firebase] AuthenticationでSMS認証 (Web編)
- [Firebase] AuthenticationでGitHub認証 (Web編)