Adding a comment box with google firebase
Table of Contents
1. Why
I never played enough with firebase and felt like a good opportunity. The final result is far from perfect, without optimizations for loading too many comments, spam prevention and dynamically loading.
I had the idea after watching this unfinished series on youtube.
2. Code
2.1. Html
Nothing really special here.
<div id="comments"> <h2>Comments</h2> <form id="new-comment-form"> <label for="username">Post a new comment</label><br> <input type="text" id="comment-name" placeholder="Your name" minlength="4" maxlength="48" required> <textarea id="comment-box" placeholder="Your comment here" minlength="10" maxlength="512" rows="5" style="resize: none;" required></textarea> <button type="submit" id="post-comment">Post</button> </form> <div id="comments-section"> <h4 id="nocomment">Be the first to comment!</h4> <ul id="comment-list"> </ul> </div> </div> <script type="module" src="/comments.js"></script>
2.2. Css
This could be improved, hire a designer? I wont.
#comments { color-scheme: dark; margin: 10px; display: none; } #new-comment-form { padding: 5px; } #comment-list { padding: 5px; list-style: none; } #comment-box, #post { border: none; border-radius: 5px; boder-color: #3c81ba; margin: 10px 0 6px 0; } #comments-section { margin-top: 10%; overflow-y: auto; max-height: 500px; } #post-comment { float: right; margin-right: 5%; border-radius: 20%; } #post-comment:hover{ background-color: #a20a29; } div.comment { background-color: #212121; border-radius: 15px; margin-top: 20px; padding-left: 10px; padding-bottom: 5px; } div.comment span { color: #5ea6e1; } div.comment p { text-align: center; text-justify: inter-word; margin: 5px 12px; background: #373636; border: #3c81ba; border-width: 2px; border-radius: 5px; font-size: small; border-style: solid; } div.comment timestamp { float: right; padding-right: 50%; font-size: small; }
2.3. Javascript
Now here the magic happens. Ignore the cookie stuff. We initialize a firebase realtime database client as firebase will recommend us itself, choosing the cdn route.
// Import the functions you need from the SDKs you need import { initializeApp } from "https://www.gstatic.com/firebasejs/9.6.11/firebase-app.js"; import { getDatabase, ref, set, get, child } from "https://www.gstatic.com/firebasejs/9.6.11/firebase-database.js"; const setupComments = () => { if (!isOnPost) { return } const firebaseConfig = { apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", authDomain: "blog-comments-xxxxx.firebaseapp.com", databaseURL: "https://blog-comments-xxxxx-default-rtdb.firebaseio.com", projectId: "blog-comments-xxxxx", storageBucket: "blog-comments-xxxxx.appspot.com", messagingSenderId: "xxxxxxxxxxxx", appId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", measurementId: "G-xxxxxxxxxx" }; // Initialize Firebase const app = initializeApp(firebaseConfig); // Get a reference to the database service const database = getDatabase(app); // Page path const path = window.location.pathname.toString().split("/").slice(0, -1).join("/") String.prototype.hash = function() { var hash = 0; for (var i = 0; i < this.length; i++) { var code = this.charCodeAt(i); hash = ((hash << 5) - hash) + code; hash = hash & hash; // Convert to 32bit integer } return Math.abs(hash).toString(); } // DB functions async function postComment(name, text) { const time = Date.now() const fullpath = path.hash() + "-" + path.split("/").slice(-1) + "/" + name.hash() + time set(ref(database, fullpath), { name, text, time: time, }); } function checkType(obj, type) { return obj && typeof (obj) === type } // Fill with comments const fullpath = path.hash() + "-" + path.split("/").slice(-1) get(child(ref(database), fullpath)).then((snapshot) => { if (snapshot.exists()) { const comments = snapshot.val(); for (const cid of Object.keys(comments)) { const comment = comments[cid] if (checkType(comment.name, "string") && checkType(comment.text, "string") && checkType(comment.time, "number")) addCommentHtml(comment) } } document.getElementById("comments").style.display = "block" }).catch((error) => { console.error(error); document.getElementById("comments").style.display = "block" }) function addCommentHtml(commentObj) { document.getElementById("nocomment").style.display = "none" var li = document.createElement("li"); const div = document.createElement("div") div.classList.add("comment") const name = document.createTextNode(commentObj.name); const nameSpan = document.createElement("span") nameSpan.append(name) const comment = document.createTextNode(commentObj.text); const commentp = document.createElement("p") commentp.append(comment) const utctime = commentObj.time const timestamp_text = "in " + new Date(utctime).toLocaleDateString() + " " + new Date(utctime).toLocaleTimeString() const time = document.createTextNode(timestamp_text); const timestamp = document.createElement("timestamp") timestamp.append(time) div.appendChild(nameSpan); div.appendChild(timestamp); div.appendChild(commentp); li.appendChild(div) document.getElementById("comment-list").prepend(li); } function processUserCommentAdd() { const name = document.getElementById("comment-name"); const text = document.getElementById("comment-box"); addCommentHtml({ name: name.value, text: text.value, time: Date.now() }) postComment(name.value, text.value) setCookie('username', name.value); name.disabled = true text.value = "" } const form = document.getElementById("new-comment-form"); form.addEventListener("submit", function(e) { e.preventDefault() processUserCommentAdd() }); document.body.addEventListener('keydown', function(e) { if (!(e.key === 'Enter' && (e.metaKey || e.ctrlKey))) return; const target = e.target if (target.form) { e.preventDefault() if (form.checkValidity() === false) { form.reportValidity(); return; } processUserCommentAdd() } }) } setupComments();
2.4. Firebase rules
Basically only allow adding new content inside a /cat_id/post_id/, not updating or deleting allowed. The data must contain the fields name, text and time.
Then some type checking is applied and it is ensured that the timestamp is not from the future or older than 15 seconds.
{
"rules": {
"$category": {
".write": false,
".read": true,
"$postid": {
".read": true,
".write": "!data.exists()",
".validate": "$category.matches(/^\\d+-\\w+$/)",
"name": {".validate": "newData.isString() && newData.val().length <= 48 && newData.val().length >= 4"},
"text": {".validate": "newData.isString() && newData.val().length <= 512 && newData.val().length >= 10"},
"time": {".validate": "newData.isNumber() && newData.val() <= now && newData.val() >= now - 15000"},
"$other": {".validate": false }
}
}
}
}
3. What have I learned/noticed
- Firebase realtime database doesn't seem to be very configurable regarding spam prevention, ddos, etc, at first sight at least.
- It has authentication built in, which I didn't want to use.
- So… there is this weird bug if you leave the browser's developers tools open the firebase client wont authenticate. Is that a bug or some sort very bad "anti hacking" thing they try :P
- Firestore has more methods while the realtime database is simpler and more suitable for small projects. For example I had to give up on having a proof of work ddos prevention because I can't compute a hash on the realtime database rules. There is a very limited scripting you can do there.
You can check bellow my first tests of the this feature.




Comments
Be the first to comment!