RailsからFCM(FirebaseCloudMessaging)を使ってみる<OAuth2.0利用>


サボりがちな[あら便利カレンダー2018](https://benri2018.rnd.cloudpack.jp/)の記事アップします。
ちょっと立て込んでて...（言い訳）



# 内容について

クライアントの実装はほとんど触れません。
Railsで立てたAPIサーバーから、FCMへPush通知のメッセージ登録を行う部分に関する記事です。

# 経緯的な
スマホアプリにPush通知を実装するのにFirebaseを使うことになった。
僕は初Firebaseだったので色々調べながらだった。

ドキュメントを見る限り、HTTPを使って通知を出すものは以下の2つが存在するみたい。

(1) [FCM HTTP v1 API](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?hl=ja)
(2) [レガシー HTTP プロトコル](https://firebase.google.com/docs/cloud-messaging/http-server-ref?hl=ja)

これらは認証方法やscope、POSTするjsonとかが異なる。

FCMのやってみた系記事は(2)な実装をしている内容が多かったので、せっかくならナウい(死語)ほうで実装してみようかな、ということでやってみた。

# 環境

- ruby 2.5.0
- rails 5.2.0

# Gem
- [google-api-client](https://github.com/google/google-api-ruby-client)
    - [googleauth](https://github.com/google/google-auth-library-ruby)だけでも良いはず
- faraday
    - HTTPクライアント




# 準備
Railsでいろいろやる前に必要なことがあるのでまずそれから。

## [Firebase](https://console.firebase.google.com/)でプロジェクトの作成

「プロジェクトID」を控えておく。

<img width="536" alt="スクリーンショット.png" src="https://qiita-image-store.s3.amazonaws.com/0/213486/18aeaa39-6e87-90ac-cb83-a8aab397e0a4.png">


「プロジェクトIDなんだったっけ・・・」な場合は、ダッシュボードから「プロジェクトの設定」を開くと確認できます。


<img width="1184" alt="スクリーンショット.png" src="https://qiita-image-store.s3.amazonaws.com/0/213486/84a9fb4c-4593-51d0-6e5f-67e371223c1f.png">






## アプリの登録
例としてAndroidアプリを登録してみる。

クライアント側のアプリを作るのは脳が拒否し始めたので以下を利用。
[github : firebase/quickstart-android](https://github.com/firebase/quickstart-android/tree/master/messaging)

パッケージ名を入力して「アプリの登録」を押す。

<img width="689" alt="スクリーンショット.png" src="https://qiita-image-store.s3.amazonaws.com/0/213486/59f4c851-268b-cec2-1598-8dd3e8cad6b1.png">



設定ファイルがダウンロードできるのでファイルを控えておく。

<img width="675" alt="スクリーンショット 2018-05-23 16.57.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/213486/72c355a7-2b5b-5b59-15df-4d5c15e005ce.png">






# これで準備は整った

ので、以下の作業後に実行してみる

- 控えた「プロジェクトID」を、ソース中の変数`url`の`[プロジェクトID]`部分に突っ込む
- ダウンロードしたファイルをRailsのルートディレクトリに置く（ app/ とかと同じ階層）
- `token` にPush通知対象の端末IDを突っ込む
    - 前述した`quickstart-android`をビルドして実機で動かした時に、`LOG TOKEN`ボタンを押して取得できる値が端末IDに該当する





```ruby
scope = 'https://www.googleapis.com/auth/firebase.messaging'
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
    json_key_io: File.open("#{Rails.root.to_s}/google-services.json"),
    scope: scope
)

access_token = authorizer.fetch_access_token!

url = "https://fcm.googleapis.com/v1/projects/[ プロジェクトID ]/messages:send"

request_body = {
    message: {
        token: "[ 通知を出したい端末ID ]",
        data: {
            data1: "データ1",
            data2: "データ2",
            data3: "データ3"
        },
        notification: {
            title: "タイトル",
            body: "本文"
        }
    }
}

conn = Faraday.new(url: url)
response = conn.post do |req|
    req.headers['Content-Type'] = 'application/json'
    req.headers['Authorization'] = "Bearer " + access_token["access_token"].to_s
    req.body = request_body.to_json
end
```






# 解説
## 認証部分
APIサーバーなどからGoogleAPIを利用するのにはサービスアカウントを使う。

今回はFirebaseプロジェクトに紐づいたサービスアカウントを用いて、APIサーバーからFCMサーバーへのアクセス許可を得ている。

ダウンロードしたjsonファイルにサービスアカウントの情報が記載されているため、jsonファイルを読み込み、その情報を`scope`に投げることで、「よし！通れ！」的に有効期間が短いOAuth2.0トークンをくれる。

個人のGoogleアカウントを使ってOAuth2.0認証でよしなにする場合は、リフレッシュトークンを使ってトークンの更新を云々みたいなのがあるが、サービスアカウントを使う場合はそういうのはない。

## メッセージ登録部分
`url`にはFirebaseのプロジェクトIDが含まれていて、そのプロジェクトに対してメッセージを登録 -> 登録したアプリに通知出せる、みたいな感じ。

リクエストヘッダーには取得したアクセストークンを含める必要がある。
`Authorization : Bearer [取得したアクセストークン]` な形ですね。

`request_body` とかいうメッセージのオブジェクトに関してはこいつを見てくれ(つ´･ω･)つ[[ドキュメント]](https://firebase.google.com/docs/cloud-messaging/concept-options?hl=ja)






# アプリに組み込んだ時の具体的な使い方
以下のケースを想定してた

- グループチャットでの通知
    - ラインのグループ的なのを想定してほしい
    - 誰かが発言したら所属メンバーみんなに通知がいくやつ
- なんかお知らせ的な通知
    - 〜が開始されました、とか、運営からのお知らせ的なのとか


## 想定していたFCMの使い方

メッセージオブジェクトの中に`token`とあるが、通知対象の選択肢がいくつかあり、便利そうなのが`topic`。

Pub/Subな処理になるんだけど、ユーザー登録時にユーザーごとに一意なトピックを作成し、ログイン時にSub開始するようにすると、スマホだろうがタブレットだろうがユーザーが利用する端末を気にすることなく通知が出せる（はず）。

また、グループチャット的な機能があったとしたら、グループごとにトピックを作成して、所属するユーザーはグループのトピックもSubしておけば通知が受け取れる（はず）。

### 問題点
- トピックが作成されるまで**最大24時間**かかる
    - ちなみにテスト時は数時間かかった
- スマホアプリ側で常にいくつものトピックをSubしていることによる負荷
    - 試してないからどの程度かわからない

　
「トピック使うといい感じじゃん(ｳｷｳｷ」って感じだったんですが、最大24時間かかる時点で諦めました。
<img width="400" alt="スクリーンショット 2018-07-12 17.03.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/213486/aa540770-dab2-1d20-d768-9f7a31d61f6a.png">



## 想定していたケースを実現しようとすると
- お知らせの通知
    - ユーザー登録時に端末IDもユーザーに紐づけてDBに保存する
        - 複数端末で利用されるとすると、ログイン時に毎回端末IDを更新するとかしないといけない？
        - ただこれだと、複数端末で同時にアプリを開いていた場合は、最後にログインした端末にしか通知が届かない
        - ~~そしてうまくやりたいと思ってもやれないので、そのうち僕は考えるのをやめた~~


- グループチャットの通知
    - 誰かがメッセージを投げる度に、グループ所属メンバー一覧を取得し、各メンバーの端末IDを指定してメッセージ登録
    - 問題ないっちゃないが、複数端末の利用を考えると、~~うまくやりたいと思っても(略~~


## 補足
- リアルタイム性みたいなのが必要ないならトピックは使っても良いと思う（トピックはあらかじめ用意しておく）




# 最後に

FCMというかFirebaseがめっちゃ熱いっぽいので、一通りの機能使ってみたいと思います。
「最高や！！！」ってのがあればまた覚え書きとして残そうかな、と。