Firebaseを使ってHTMLコピペだけで作るチャットサンプル


無料BaaSだと[Milkcocoa](https://mlkcca.com/)のサクッと作れる使い勝手が好きでMilkcocoaをよく使ってましたが、Firebaseでも同じものを作れないかやってみました。

2年前くらいのFirebaseは割とサクッと使えた印象なのですが、Google傘下になってからのFirebaseは機能も増えてサクッと作れるサンプルがあまり見当たらない感覚があったので作ってみます。

## 作ったもの

こんな感じでリアルタイムなチャットアプリケーションです。 
(Mac Chrome 58 / iOS10 safariで動作確認)

https://github.com/n0bisuke/firebase_chat

![](https://camo.githubusercontent.com/bcf6d9a00e116c23df50c6438a946b9587b68af5/68747470733a2f2f692e6779617a6f2e636f6d2f32373264353165346133613066383139363237353234663230373734353063632e676966)

みたことある人がいるかもしれませんがMilkcocoaのチャットサンプルをフォークしています。

> https://github.com/milk-cocoa/chat

## コード (1ファイル完結)

これだけでコピペで動くものにしたかったので、CSSやJSも全ていれこんでいます。
Firebase歴数時間なのでもっと効率の良い書き方あれば教えてください。

```index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
    <meta property="og:title" content="Firebaseで作ったリアルタイムチャット" />
    <meta property="og:type" content="chat" />
    <meta property="og:description" content="BaaS（Firebase）で作られたリアルタイムチャット" />
    <title>Firebaseで作ったリアルタイムチャット</title>
    <style>html{-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{font-family:游ゴシック,YuGothic,ヒラギノ角ゴ ProN W3,Hiragino Kaku Gothic ProN,メイリオ,Meiryo,Arial,sans-serif;font-size:15px;background:#f7f7f7;min-width:20pc;line-height:1.6;color:#3a342e}body,h1{margin:0}h1{font-size:2em;line-height:1.2}p{margin:1em 0 1.6em}a{color:#222;background:transparent}a:active,a:hover{color:#154be5;text-decoration:none;outline:0}a:focus{outline:thin dotted}textarea{overflow:auto;vertical-align:top}.cf:after,.cf:before{content:" ";display:table}.cf:after{clear:both}.cf{*zoom:1}.header{background-color:#1ba39c;width:100%;padding:.75em 0}.header .logo{font-family:Arial Black;font-weight:900;color:#fff;max-width:60pc;margin:0 auto;padding:0 .7em}.header .logo a{color:#fff}.container{width:100%;background-color:#45bfb6}.container .postarea{max-width:45pc;margin:0 auto;padding:2em 1.3em}.container .postarea .postarea-text{width:100%}.container .postarea .postarea-text textarea{font-size:1em;width:100%;border:1px solid #ededed;border-radius:5px;height:34px;resize:vertical;color:#3a342e;padding:.5em;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.container .postarea .postarea-share{float:left;width:9.5em}.container .postarea .postarea-share p{white-space:nowrap;color:#fff;font-size:.72em;margin:.5em 0 .1em}.container .postarea .postarea-share .postarea-share-button{width:100%;margin-right:-2em}.container .postarea .postarea-share .postarea-share-button a{font-size:1.5em;float:left;display:block;width:2.9em;text-align:center;background:#f7f7f7;color:#848484;text-decoration:none;height:1.8em;line-height:1.8em;border-radius:3px;border:1px solid #ededed;vertical-align:middle}.container .postarea .postarea-share .postarea-share-button .postarea-share-facebook{margin-right:.3em}.container .postarea .postarea-button{font-weight:700;margin-top:.6em;font-size:1em;height:3.85em;background:#f7f7f7;color:#848484;cursor:pointer;line-height:3.85em;border-radius:3px;text-align:center;border:1px solid #ededed;text-decoration:none;float:right;width:8.8em}.content{max-width:760px;margin:3em auto}.content .post{margin-top:2em;border-bottom:1px dotted #ccc;padding:0 1em 1em}.content .post .post-date{font-size:.7em;color:#848484;text-align:right;border:0;margin:0}.footer{font-size:.8em;color:#848484;max-width:760px;margin:2em auto 0;padding:0 1em 3em}.content .post .post-text{margin:0}.container .postarea .postarea-button:hover,.container .postarea .postarea-share .postarea-share-button a:hover{background:#e6e6e6}</style>
    <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
    <script src="https://www.gstatic.com/firebasejs/4.1.2/firebase.js"></script>
</head>

<body>
    <div class="header">
        <h1 class="logo">CHAT</h1>
    </div>

    <div class="container">
        <div class="postarea cf">

            <div class="postarea-text">
                <textarea name="" id="content" cols="30" rows="10" placeholder="Enterで投稿"></textarea>
            </div>

            <div class="postarea-share cf">
                <p>このチャットをシェアする！</p>
                <div class="postarea-share-button cf">
                    <script>
                    var url = encodeURIComponent(location.href);
                    document.write('<iframe src="//www.facebook.com/plugins/like.php?href='+url+'&amp;width&amp;layout=button&amp;action=like&amp;show_faces=false&amp;share=true&amp;height=35" scrolling="no" frameborder="0" style="border:none; overflow:hidden; height:20px;" allowTransparency="true"></iframe>');
                    </script>
                    <a href="https://twitter.com/share" class="twitter-share-button" data-lang="ja" data-count="none">ツイート</a>
                    <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
                </div>
            </div>

            <button id="post" class="postarea-button">投稿する</button>
        </div>
    </div>

    <div id="messages" class="content">
        <div id="dummy"></div>
    </div>

    <p class="footer"><strong>Forked by <a href="https://github.com/milk-cocoa/chat">Milkcocoa Chat</a></strong></p>

    <script>
        'use strict';
        //Firebaseの初期化
        firebase.initializeApp({databaseURL: "https://<YOUR DATABASE NAME>.firebaseio.com"});
        const database = firebase.database();
        const ref = database.ref('messages');
        let last_message = "dummy";

        //初期読み込み & pushイベント検知
        ref.on("child_added", (snapshot) => {
            renderMessage({
                id: snapshot.key,
                value: snapshot.val()
            });
        });

        //インジェクション対策
        const escapeHTML = (val) => $('<div>').text(val).html();

        //投稿処理
        const postAction = () => {
            const content = escapeHTML($("#content").val());
            if(content && content !== "") {
                ref.push({
                    title: 'タイトル',
                    content: content,
                    date: new Date().getTime()
                })
                .then((res)=>{
                    console.log(res);
                });
            }
            $("#content").val("");
        };

        //メッセージをDOM追加
        const renderMessage = (message) => {
            const message_html = `<p class="post-text">${escapeHTML(message.value.content)}</p>`;
            let date_html = '';
            if(message.value.date) {
                date_html = `<p class="post-date">${escapeHTML(new Date(message.value.date).toLocaleString())}</p>`;
            }
            $("#"+last_message).before(`<div id="${message.id}" class="post">${message_html}${date_html}</div>`);
            last_message = message.id;
        }

        //クリック時の処理
        $('#post').click(() => postAction());
        //エンターキータイプ時の処理
        $('#content').keydown((e) => {
            if(e.which == 13){
                postAction();
                return false;
            }
        });
    </script>
</body>
</html>
```

基本、これだけで動きます。

一箇所だけ以下の箇所でデータベース名を自分のものに指定して、後述するルール変更をする必要があります。

`firebase.initializeApp({databaseURL: "https://<YOUR DATABASE NAME>.firebaseio.com"});`

## Firebase側の設定

https://console.firebase.google.com/u/0/ からログインします。

`プロジェクトを作成`から

* プロジェクト名 (※ここではn0bisukeとしました)
* 国/地域

を入力&選択し、プロジェクトを作成します。


![](https://i.gyazo.com/c9d638662e41bc171700396ea069a30d.png)

プロジェクトが出来上がると管理画面に遷移します。サイドバーの`Database`を選択すると、URLが表示されます。

ここでは`https://n0bisuke-c2cfc.firebaseio.com`が表示されましたがこのサブドメイン部分の`n0bisuke-c2cfc`がデータベース名になります。これをコピーして、先ほどのコードの`firebase.initializeApp({databaseURL: "https://<YOUR DATABASE NAME>.firebaseio.com"});`部分を書き換えましょう。

![](https://i.gyazo.com/ac7ce6fc95781637c280a1b55c09d60d.png)

最後にルールタブからセキュリティルール変更です。デフォルトだとアクセス制限が掛かっているので以下の内容に書き換えます。

![](https://i.gyazo.com/b2711f01aa5283e57121d774cf11966f.png)

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

これでチャットが動きます。試してみてください。
ちなみにどこからでもアクセスできる形になるのでセキュリティ的にはアウトです（警告メッセージもでますね）。実運用を考える際は注意して下さい。

## 所感

ちなみにプランは無料だとこんな感じみたいです。

![](https://i.gyazo.com/d847779e5646fd722e35307b257dbb58.png)

チャットサンプル作って遊ぶ程度なら十分そうですね。

この手順くらいならMilkcocoaのチャットと同様くらいの時間で作れそうな印象です。

主観ですが、クライアントからチャットを送信して、他のクライアントに同期されるまでの時間がMilkcocoaよりも遅いかもしれません。その辺は、Milkcocoaの次のバージョンが出るかもって噂があるので、少し期待ですね。



## 参考

* https://github.com/devm33/firebase-minichat/blob/master/main.js
* [javascript - Firebase Permission Denied - Stack Overflow](https://stackoverflow.com/questions/37403747/firebase-permission-denied)