JavaScript
Firebase

Firebaseで「いいねボタン」を実装してみよう!

good_btn.gif

Qiita でも無くてはならない「いいねボタン」をフロントだけで実装する方法です。
また今から紹介する内容は下記公式ドキュメントを元に紹介させていただきますので、予めご了承ください。
認証について : https://firebase.google.com/docs/auth/web/anonymous-auth?hl=ja
DB操作について : https://firebase.google.com/docs/database/web/read-and-write?hl=ja

Firebaseは料金プランそれぞれに認証やDBの接続数などが決まっていますので、気になる方は下記から参照してみてください。
https://firebase.google.com/pricing/?hl=ja

対象者

・下記仕様を承諾の上で、Firebaseで「いいねボタン」を実装したい方。

仕様

「いいねボタン」を押すのにユーザーはログインする操作は必要はなし
※今回匿名認証を用いており、ユーザーはログインする操作はしませんが裏ではログインを行ってます。

「いいねボタン」を各種ブラウザ1日に1回しか基本押せない
※「いいねボタン」を押したかどうかの情報は localStorage に保存します。
※ localStorage を意図的に消したり書きかえたりするとまた「いいねボタン」を押すことができます。
※ブラウザごとにlocalStorage は保存される為、ブラウザを変えてまた「いいねボタン」を押すことが出来ます。

実装の大きな手順

  1. Firebaseで匿名認証の設定
  2. JSの実装

1. Firebaseで匿名認証の設定する。

匿名認証について

Firebaseは認証設定なしでもデータを操作することは可能です。
ただし認証なしのデータ操作は不正アクセスに対して対策が出来ておらず、「いいねボタン」を実装するのみであっても何かしらの認証があった上でデータを操作したほうが安全です。
そこで Firebase では様々な認証方法を用意してあるのですが、その中で匿名認証を用いて今回は実装します。

まず匿名認証について前述したように、ユーザーがログイン操作を行っていなくてもJSで匿名としてログインを行わせる事が可能です。
Firebaseのドキュメントには「アプリに登録していないユーザーが、セキュリティ ルールで保護されているデータを使用できるようになります。」
と書かれてあるように、セキュリティルールを設けることができます。(引用元)

セキュリティルールとしてDBのアクセス制限だったり、IPのアクセス数の制限などを設けれたりします。
s1.png

それから匿名ログインについての豆知識として
匿名ログインを行うと user_id が発行されるのですが、こちらは匿名上なのでクッキーを消すと違う user_id に代わります。
匿名上なのでしょうがないのですが、Firebaseのログイン認証を使用してユーザーを常に特定したい場合などは他のログイン方法を使う必要が出てきます。

また匿名から永久アカウントに変えることもできます。
https://firebase.google.com/docs/auth/web/anonymous-auth?hl=ja#header_1

今回のケースでは、カウントのみを実装し特にユーザー情報を収集しないようなケースでは匿名認証は向いているかと思います。

匿名認証について大方説明したところで、Firebase上での匿名認証の設定方法について入ります。

匿名認証の設定方法

Firebaseで匿名認証を使用するには、匿名認証を許可しとく必要があります。

1.Firebaseにログイン
https://console.firebase.google.com/?hl=ja

2.プロジェクトを開き「Authentication」を開く。

3.「ログイン方法」のタブを開き、「匿名認証」を有効化します。
s2.png

これで匿名認証を使えるようになりました、簡単ですね。
匿名認証が使えるようになったので、いよいよ匿名認証に接続するプログラムを書いていきます。

2.JSの実装

概要については下記になります。

const config = {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: ""
  };
const app = firebase.initializeApp(config);
const db = app.database();

document.addEventListener('DOMContentLoaded', function() {
    firebase.auth().signInAnonymously().catch(function (error) {
        window.alert('ログイン認証エラー');
    });
    firebase.auth().onAuthStateChanged(function(user) {
        if (user) {
            let value = 0;
            db.ref('button').on('value', function(d) {                
                value = Number(d.val() || 0);
                document.getElementById('value').innerHTML = value;

            }, function(error) {
                window.alert('DB接続エラー');  
            });

            document.getElementsByTagName('button')[0].addEventListener('click',function(){
                const xhr = new XMLHttpRequest();
                xhr.open('GET','https://ntp-a1.nict.go.jp/cgi-bin/json');
                xhr.responseType = 'json';
                xhr.send();
                xhr.addEventListener('load',function(){
                    const TM = new Date(this.response.st * 1000);
                    const YEAR = Number(TM.getFullYear());
                    const MONTH = Number(TM.getMonth() + 1);
                    const TODATE = Number(TM.getDate());
                    const update_time = YEAR + '/' + MONTH + '/' + TODATE;

                    if ((localStorage.getItem('button') === null) || new Date(localStorage.getItem('button')) < new Date(update_time)) {
                        localStorage.setItem('button', update_time);
                        value += 1;

                        var obj = {};
                        obj['button'] = value;

                        db.ref('/').update(obj);
                    } else {
                        window.alert('既に今日はカウント済みです');
                    }
                });                    
            });
        }
    });
  });

まずFirebaseに接続し firebase.auth().signInAnonymously() にて匿名でのログインを行います。
匿名でのログイン後 firebase.auth().onAuthStateChanged() が呼び出されます。

そうしたらここにログイン後の処理を書きます。
最初に、DBから該当の button name に紐づいたカウント数を取得します。(なければ 0 を表示します)

db.ref('button').on('value', function(d) {                
    value = Number(d.val() || 0);

他にも once メソッドなどがあるのですが、onメソッドを使うことによりDBが変更になると自動でこのメソッドが呼び出されます。
まさに Realtime ですね。

そのあとボタンをクリックすると XMLHttpRequest で NICT インターネット時刻供給サービスを利用してサーバー時刻を取得しにいきます。

 document.getElementsByTagName('button')[0].addEventListener('click',function(){
    const xhr= new XMLHttpRequest();
    xhr.open('GET','https://ntp-a1.nict.go.jp/cgi-bin/json');
    xhr.responseType = 'json';
    xhr.send();
    xhr.addEventListener('load',function(){

時間を取得したら、前回ローカルストレージに保存されていない。
もしくは前回より日付が先に進んでいた場合は、カウント処理を行い button name の値が変更されます。

 if ((localStorage.getItem('button') === null) || new Date(localStorage.getItem('button')) < new Date(update_time)) {
    localStorage.setItem('button', update_time);
    value += 1;

    const obj = {};
    obj['button'] = value;

    db.ref('/').update(obj);
} else {
    window.alert('既に今日は投稿済みです');
}

これらが今回「いいねボタン」の実装内容になります。
いかがでしたか、プログラム自体はシンプルでわかりやすい内容だったと思います。

また補足として今回 DB のセキュリティルールを下記のようにしました。

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

これはログイン認証があれば、誰でもDBに接続できる仕様になっております。
DBにはカウントのみの情報しか置いてないために、特に制限かける必要はないと思ってますがユーザー情報などを置いた場合は、当然アクセス制限などが必要になってくると思います。

この辺りはまた次回、セキュリティルールを深堀して紹介できたらと思っています。
読んでいただきありがとうございました。