Fetch APIを使ってユーザ個別の内容でWebブラウザプッシュ通知：サーバ側実装

# 概要
ServiceWorkerを使ってWebブラウザにユーザ個別の内容でプッシュ通知を送信するとき、現状ではプッシュ通知を受信したあと、Fetch APIによってサーバに詳細内容を問い合わせる一手間が必要なようです。

サーバ側で詳細内容を返すAPIの実装について、その詳細を解説している情報源があまり見つからなかったので、自分が作成したときの記録を残しておきたいと思います。

ServiceWorkerのクライアント側実装については、他の情報源を確認してください。


# 実現したいこと
Webブラウザアプリで、ユーザ個別の内容でプッシュ通知を送信したい。
そのために、プッシュ通知内容を返すAPIをサーバ側に作成する。

# 環境
Ruby 2.2.2
Rails 4.2.6
MySQL
Nginx
Unicorn

# 関連技術

- [Service Worker](http://www.html5rocks.com/ja/tutorials/service-worker/introduction/)
- [Google Cloud Messaging](https://developers.google.com/cloud-messaging/)
 - [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/)に移行することが推奨されています


# 問題

メッセージを暗号化しない限り、プッシュ通知自体にテキストやURLなどのカスタマイズした情報を含めることができない（調査当時）ようです。

引用：[ブラウザプッシュ通知とユーザー個別に内容を送信する実装方法 in GAMY](http://qiita.com/narikei/items/0f26b30d347ae19d9559)
>ブラウザのプッシュではネイティブアプリのように、プッシュにテキストやURLなどの情報を入れることはメッセージを暗号化しないとできません。
つまりブラウザのプッシュではプッシュが来たということしかわからないという仕様です。
なのでプッシュが来てからFetch API（XHRの新しくて使いやすいやつ）でサーバーに詳細内容を問い合わせて、
取得した結果を表示させるという処理が必要になります。


# 解決策

「Fetch APIでサーバにプッシュ通知の内容を問い合わせる」というのは、現状では一つの解決策のようです。

上記の引用ページでは、以下のようなサーバ側APIがある想定で、Fetch APIの使い方が丁寧に解説されており、参考にさせていただきました。

```json:/notifications.json?endpoint=***********
{
  "title": "GAMY",
  "icon": "/icon.png",
  "body": "◯◯さんがあなたのコメントに返信しました。",
  "url": "https://gamy.jp/notifications"
}
```

今回は、このサーバ側APIの実装方法を、Railsを例にしてまとめたいと思います。

# サーバ側API実装の一例

## APIのルーティングを設定

```rb:config/routes.rb
  # API
  namespace :api, defaults: {format: 'json'} do
    namespace :v1 do
      #resource :devices
      resource :notifications, :only => [:show]
    end
  end
```

## Deviceテーブルを定義

今回の例では、userに紐づくdeviceモデルを定義する（一対多）ことにします。
push_timeは、ここではユーザがプッシュしてほしい時間を指定するための項目と考えてください（アプリの仕様次第なので必須ではありません）。

```bash:
rails g model Device uuid:string registration_id:string user:references is_logged_in:boolean push_time:time
```

migrationファイルを適宜編集します。

```rb:db/migrate/XXXXX_create_device.rb
class CreateDevices < ActiveRecord::Migration
  def change
    create_table :devices do |t|
      t.string :uuid, limit: 36, null: false
      t.string :registration_id
      t.references :user, index: true, foreign_key: true
      t.boolean :is_logged_in
      t.time :push_time

      t.timestamps null: false
    end
    add_index :devices, :registration_id
  end
end
```

## Notificationsコントローラを作成

Notificationsコントローラを作成します。

```bash:
rails g controller api/v1/Notifications
```

[ブラウザプッシュ通知とユーザー個別に内容を送信する実装方法 in GAMY](http://qiita.com/narikei/items/0f26b30d347ae19d9559)などを参考に、クライアントのJavaScriptからFetch APIでリクエストを投げられるように実装しておきます。

```:FetchAPIでリクエスト
/notifications?endpoint=XXXXXXXX
```

コントローラ内は、Google Cloud Messaging(GCM)に特化した実装になってしまっていますが、
Fetch APIでリクエストがあると、config/routes.rbの設定内容に従ってshowアクションが呼ばれ、

```rb:app/controllers/api/v1/notifications_controller.rb
class Api::V1::NotificationsController < ApplicationController

  def show
    gcm_domain = "https://android.googleapis.com/gcm/send/"
    registration_id = params[:endpoint].split(gcm_domain)[1]

    @notification = {other_column: 'Hi!', notification: {"title": "Greeting from controller", "icon": "/icon.png", "body": "your registration_id: " + registration_id, "url": "/"}}
    render json: @notification.notification
  end

end
```

jsonとして、ユーザ個別の内容を返すことができます（GCMのregistration_idがクライアントごとに個別なので、一応）。

```:/notifications?endpoint=XXXXXXXX
{
  "title": "Greeting from controller",
  "icon": "/icon.png",
  "body": "your registration_id: " + registration_id,
  "url": "/"
}
```

参考までに、rpushでDBに保存されるプッシュ通知の内容を取得するなら、Rpush_notificationモデルを作成して、notificationsコントローラからそれを呼べば、取得できました。

個別の内容を加えたければ、@notificationを少しいじればできるはず。

```rb:app/models/rpush_notification.rb 
class Rpush_notification < ActiveRecord::Base
end
```

```rb:app/controllers/api/v1/notifications_controller.rb
@notification = Rpush_notification.where("registration_ids like '%" + registration_id + "%'").order("created_at DESC").first
```


## rpush

rpushは、[公式ページ](https://github.com/rpush/rpush)によると
>The push notification service for Ruby.

ということで、Rubyでプッシュ通知を実現する際に、手軽に使えるサービスです。

### 導入

```:Gemfile
gem 'rpush'
```

```bash:
bundle install
rpush init
```

### 設定

https://github.com/rpush/rpush/wiki/Canonical-IDs

```rb:config/initializers/rpush.rb
Rpush.reflect do |on|
    (...)
    # Called when the GCM returns a canonical registration ID.
    # You will need to replace old_id with canonical_id in your records.
    on.gcm_canonical_id do |old_id, canonical_id|
      d = Device.find_by_registration_id(old_id)
      d.update_attributes(registration_id: canonical_id)
    end
    (...)
end
```

### 状態確認・開始・停止

-e オプションで環境を指定できます。

```bash:
rpush status -e development
rpush start -e development
rpush stop -e development
```


## APIのApplicationController

```:app/controllers/api/v1/application_controller.rb
class Api::V1::ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
end
```




## DB反映

```
bundle exec rake db:create   RAILS_ENV=development
bundle exec rake db:migrate  RAILS_ENV=development
```



## DBにデバイス投入

```sql:
mysql> select * from devices\G
*************************** 1. row ***************************
             id: 1
           uuid: XXXXX-XXXXX-XXXX...
registration_id: YYYYYYYYYYYYYY.....
        user_id: 1
   is_logged_in: 1
      push_time: 05:55:00
     created_at: 2016-08-04 13:51:00
     updated_at: 2016-08-04 13:52:00
1 row in set (0.00 sec)
```

## Notificationモデルを作成

rakeタスク作成のために作ったモデルであって、DBと紐付いたモデルではないので、ActiveRecord::Baseを継承する必要はなさそうです。

send_gcm_notificationメソッドを定義して、rakeタスクから利用できるようにします。

以下の例では、ログインしていて、push_timeが現在時刻〜1時間以内に含まれているデバイスを対象に、プッシュ通知を送信します。

```
n.save!
```
によって、rpush_notificationsテーブルに挿入され、rpush経由でプッシュ通知が送信されます。

通知の内容は、先に述べたNotificationsコントローラでユーザに合わせた内容に加工して、クライアントに返せます。

```rb:app/model/notification.rb
class Notification #< ActiveRecord::Base

  def send_gcm_notification
    if !Rpush::Gcm::App.find_by_name(ENV["RPUSH_APP_GCM"])
      app = Rpush::Gcm::App.new
      app.name = ENV["RPUSH_APP_GCM"]
      app.auth_key = ENV["RPUSH_AUTH_KEY_GCM"]
      app.connections = 1
      app.save!
    end

    n = Rpush::Gcm::Notification.new 
    n.app = Rpush::Gcm::App.find_by_name(ENV["RPUSH_APP_GCM"])
 
    #n.registration_ids = Device.all.map{|device| device.registration_id}
    # times are stored as UTC in DB
    n.registration_ids = Device.where(is_logged_in: true).where(push_time: Time.now.to_s(:time)..(Time.now+1.hour).to_s(:time)).map{|device| device.registration_id}

    n.data = { message: "hello message" }
    n.notification = { 
      body: 'How are you?',
      title: 'Greeting from controller',
      icon: 'myicon'
    }

    n.save!
  end

end
```


## 定期的なジョブでプッシュ通知を送信

```bash:
rpush status -e development
rpush start -e development
date; bundle exec rake notification:send_notification RAILS_ENV=development
```

rakeタスクを実行すれば、プッシュ通知が送信されます。
必要に応じて、これをcronで実行したりできそうです(whenever gemを使ってみようと思います)。