Firebase で iOS クライアントからプッシュ通知を送る


# Firebase、アツいですね
以前から横目でフォローしてたのですが、なんか突然 Google のサービス郡と密結合したのでびっくりしました。
Google Cloud Messaging も Firebase Cloud Messaging で上書きされそうで、Parse みたいに終了になったら名前どう処理すんだという感じですね！Google が最期まで面倒見てくれることを期待します。


# さて
Firebase と言えばリアルタイム DB がメインだったのですが、この度のメジャーバージョンアップでみんな大好きプッシュ通知機能も実装されて、Qiita の記事も順調に増えてきています。

まずは一番シンプルに、コンソールから送信する方法。キャンペーン情報やユーザー全体へのお知らせを、管理者が GUI を使って送信できます。
[【iOS】Firebase の Notifications でプッシュ通知を送る](http://qiita.com/koogawa/items/ca8cce019b4ff7ce2576)

次に、サーバーから API を叩いて送信する方法。
[Firebaseによるプッシュ通知のハマりどころ](http://qiita.com/mituoh/items/2dc87618edf234afd8f0)

# さらに次のステップへ

ここまでくると、さらに次のステップとして以下の疑問が湧いてくることでしょう。
**「チャットサービス的なアプリを作りたいんだけど、ユーザーがメッセージを送信したタイミングで相手にプッシュ通知を送るにはどうしたらいいの？」**

ということで、こちらからは「Firebase を使って iOS クライアントからプッシュ通知を送る」方法をお送りしていきたいと思います。

*※ FCM 的には配信されるのは「メッセージ」ですが、この記事ではわかりやすさを重視し、あえて「プッシュ通知」と表現します。*

# クライアントからプッシュ通知を送る3つの選択肢

まず前提として、Parse などのようにクライアントから直接プッシュ通知を作成して送信する機能は Firebase には用意されていません。
Parse 難民の方々には残念なお知らせですが、自前でサーバーを立てる必要があるようです。
[mituohさんの記事](http://qiita.com/mituoh/items/2dc87618edf234afd8f0) や [ドキュメント](https://firebase.google.com/docs/cloud-messaging/downstream) にあるように、自前サーバーから https://fcm.googleapis.com/fcm/send を叩くことでクライアントに対してプッシュ通知を配信します。


問題は何をトリガーにして配信処理を実行するか、でしょう。
調査を元に考察していくと、3つの手法が選択肢に挙がりました。

1. **自前サーバーに FCM Connection Server Protocol を実装し、クライアントから受信した Upstream Message をトリガーにする**
[ドキュメント](https://firebase.google.com/docs/cloud-messaging/upstream)に載っている方法です。XMPPを使って諸々処理するようです。

2. **自前サーバーにエンドポイントを用意し、クライアントから叩く**
そのままです。
チャットアプリでの例: ユーザーAがユーザーBに宛てたメッセージを Database に保存。同時にユーザーAは自前サーバーの特定のエンドポイントを適切なパラメータを持って叩きます。それによってユーザーBに対して適切な内容のプッシュ通知が作成され配信されます。

3. **自前サーバーで Firebase Database を監視し、データの追加をトリガーにする**
任意のエンティティの追加を監視し、クライアントからデータが追加されたタイミングで対応したプッシュ通知を配信します。
チャットアプリでの例: ユーザーAがユーザーBに宛てたメッセージを Database に保存すると、自前サーバーがメッセージの追加を検知、ユーザーBに対して適切な内容のプッシュ通知を作成し配信します。


# 今回は3番目のヤツでいきましょう

1番目は公式ドキュメントに載っている安心感がありますが、なんせ筆者が XMPP に明るくないもので、残念ながら今回は扱いません。とはいえうまく扱えれば強力な選択肢になるので、他の方が記事にしてくれるのを楽しみに待ちましょう :smile: 

2番目は非常にシンプルです。記事にするまでもないくらいなので、各自お試しください。

3番目のメリットは以下のとおりです。

- **オフライン対応**。Firebase Database の SDK はオフライン対応しているので、その強みをそのまま享受できます。圏外でメッセージを送信しても、圏内に移動すると自動でメッセージが Database に保存され、自前サーバーがプッシュ通知を送信してくれます。
- **通信を分割しなくてもよい**。2番目の方法は1つのメッセージを送信するのに2回通信が必要ですが、この方法だと1度で済みます。

Firebase Database にデータを追加するだけなので、もちろん Android やウェブからでも利用できます。
ということで、この記事では3番目の「**自前サーバーで Firebase Database を監視し、データの追加をトリガーにしてプッシュ通知を配信する方法**」を説明していきます。



# 手順

前置きは長かったですが、基礎の部分の手順は非常に簡単です。

## 1. iOS クライアントでプッシュ通知を受信できるよう設定する。
[koogawaさんの記事](http://qiita.com/koogawa/items/ca8cce019b4ff7ce2576) などを参考に、Firebase のコンソールから通知を配信して iOS 端末で受信できるようにしておきましょう。

## 2. Firebase Database を監視してプッシュ通知を配信するコードを書く。

以下が今回のキモとなる魔法のコードです。
まずはローカルで以下のコードを動かし...

```js:app.js
var https = require('https');
var firebase = require("firebase");

firebase.initializeApp({
	serviceAccount: "serviceAccountCredentials.json",
	databaseURL: "https://＊＊＊＊＊＊＊＊＊＊＊.firebaseio.com"
});

var db = firebase.database();
var ref = db.ref("messages/");
ref.once("value", function(snapshot) {
    var numChildren = snapshot.numChildren();
    var counter = 0;
    ref.on("child_added", function(addedSnapshot) {
        if( counter < numChildren ){
            // 既存のデータなのでスキップします
            counter++;
            return;
        }
        // 新着データなのでプッシュ通知を配信します
        sendNotification( addedSnapshot.val().body );
    });
});


function sendNotification( body ){
    var postData = JSON.stringify({
        "to": "/topics/news",
        "priority" : "high",
        "notification" : {
            "body" : body,
            "sound": "default",
        }
    });

    var options = {
		hostname: 'fcm.googleapis.com',
		path: '/fcm/send',
		method: 'POST',
		headers: {
			'Content-Type': 'application/json',
			"Authorization" : "key=**********************"
		}
    };

    var req = https.request(options, (res) => {
        console.log("OK");
    });

    req.on('error', (e) => {
		console.error("problem with request: ${e.message}");
    });

    req.write(postData);
    req.end();
}
```

Firebase Database にいくつかデータを追加してみましょう。
![Firebase.png](https://qiita-image-store.s3.amazonaws.com/0/29815/26aeb14a-2ffc-f148-d2be-6c4feb542caa.png)

すると、期待通りに iOS 端末にプッシュ通知が配信されました！
![IMG_5277.PNG](https://qiita-image-store.s3.amazonaws.com/0/29815/763d49b2-5470-1f8f-4a53-1a14f79fdae9.png)



## 3. Node.js サーバーを立ててデプロイする。

あとは上記のコードを自前の Node.js サーバーにデプロイするだけです。

サーバーは好きな様に立てれば良いのですが、FCM サーバーとも距離的に近そうなので、今回は以下の記事を参考に App Engine で立てました。
[Google App Engine Node.jsを試してみる。 GAE/Node.js](http://qiita.com/n0bisuke/items/fbea48562441a8314d45)

注意点としては、このコードが動くインスタンスは1台だけに制限しましょう。
複数台で同じコードを動かすと、設計上監視処理が重複し、プッシュ通知がインスタンスの数だけ配信されてしまいます。

デプロイできたら、もう一度コンソールからいくつかデータを追加してみて、端末にプッシュ通知が配信されることを確認しましょう。


# リアルワールド用の実装

ここまでが基礎の部分で、あとは実際に制作するアプリの仕様に従って実装しましょう。
例えば保存するデータに宛先のデバイスIDやトピック名を入れたり、サーバー側でそれをハンドリングする処理などが必要になってくると思います。
あと、Firebase Database のセキュリティルールは忘れずに設定しましょうね。

これで、Firebase を使って iOS クライアントからプッシュ通知を配信できるようになりました。


# サーバー立てるとかダルいよもっと楽でシンプルな方法は無いのかよ？
そうですよね、僕もそう思います。
おそらく、今後 Google Cloud Functions が上記の役割を担うようになってくるんじゃないでしょうか。

Firebase の人も、[「Google I/O には間に合わなかったけど Google Cloud Functions との連携は最優先で頑張ってるよ」](https://groups.google.com/d/msg/firebase-talk/LvHX3hMUWo4/jsNLxIMSFwAJ)とか、 [「Google Cloud Functions との連携がアナウンスされるまでは App Engine + nodejs 使っとけよ」](https://groups.google.com/d/msg/firebase-talk/MtskqCwsR-U/QF0u6AxeFwAJ) と言ってますね。

ただ Google Cloud Functions はまだベータ入りすらしていないステータスなので、しばらくはこの手法で行こうかなと思っています。

# 以上です
Firebase を本格的に使うために参考になれば幸いです。