概要
ServiceWorkerを使ってWebブラウザにユーザ個別の内容でプッシュ通知を送信するとき、現状ではプッシュ通知を受信したあと、Fetch APIによってサーバに詳細内容を問い合わせる一手間が必要なようです。
サーバ側で詳細内容を返すAPIの実装について、その詳細を解説している情報源があまり見つからなかったので、自分が作成したときの記録を残しておきたいと思います。
ServiceWorkerのクライアント側実装については、他の情報源を確認してください。
実現したいこと
Webブラウザアプリで、ユーザ個別の内容でプッシュ通知を送信したい。
そのために、プッシュ通知内容を返すAPIをサーバ側に作成する。
環境
Ruby 2.2.2
Rails 4.2.6
MySQL
Nginx
Unicorn
関連技術
- Service Worker
-
Google Cloud Messaging
- Firebase Cloud Messagingに移行することが推奨されています
問題
メッセージを暗号化しない限り、プッシュ通知自体にテキストやURLなどのカスタマイズした情報を含めることができない(調査当時)ようです。
引用:ブラウザプッシュ通知とユーザー個別に内容を送信する実装方法 in GAMY
ブラウザのプッシュではネイティブアプリのように、プッシュにテキストやURLなどの情報を入れることはメッセージを暗号化しないとできません。
つまりブラウザのプッシュではプッシュが来たということしかわからないという仕様です。
なのでプッシュが来てからFetch API(XHRの新しくて使いやすいやつ)でサーバーに詳細内容を問い合わせて、
取得した結果を表示させるという処理が必要になります。
解決策
「Fetch APIでサーバにプッシュ通知の内容を問い合わせる」というのは、現状では一つの解決策のようです。
上記の引用ページでは、以下のようなサーバ側APIがある想定で、Fetch APIの使い方が丁寧に解説されており、参考にさせていただきました。
{
"title": "GAMY",
"icon": "/icon.png",
"body": "◯◯さんがあなたのコメントに返信しました。",
"url": "https://gamy.jp/notifications"
}
今回は、このサーバ側APIの実装方法を、Railsを例にしてまとめたいと思います。
サーバ側API実装の一例
APIのルーティングを設定
# API
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
#resource :devices
resource :notifications, :only => [:show]
end
end
Deviceテーブルを定義
今回の例では、userに紐づくdeviceモデルを定義する(一対多)ことにします。
push_timeは、ここではユーザがプッシュしてほしい時間を指定するための項目と考えてください(アプリの仕様次第なので必須ではありません)。
rails g model Device uuid:string registration_id:string user:references is_logged_in:boolean push_time:time
migrationファイルを適宜編集します。
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コントローラを作成します。
rails g controller api/v1/Notifications
ブラウザプッシュ通知とユーザー個別に内容を送信する実装方法 in GAMYなどを参考に、クライアントのJavaScriptからFetch APIでリクエストを投げられるように実装しておきます。
/notifications?endpoint=XXXXXXXX
コントローラ内は、Google Cloud Messaging(GCM)に特化した実装になってしまっていますが、
Fetch APIでリクエストがあると、config/routes.rbの設定内容に従ってshowアクションが呼ばれ、
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がクライアントごとに個別なので、一応)。
{
"title": "Greeting from controller",
"icon": "/icon.png",
"body": "your registration_id: " + registration_id,
"url": "/"
}
参考までに、rpushでDBに保存されるプッシュ通知の内容を取得するなら、Rpush_notificationモデルを作成して、notificationsコントローラからそれを呼べば、取得できました。
個別の内容を加えたければ、@notificationを少しいじればできるはず。
class Rpush_notification < ActiveRecord::Base
end
@notification = Rpush_notification.where("registration_ids like '%" + registration_id + "%'").order("created_at DESC").first
rpush
rpushは、公式ページによると
The push notification service for Ruby.
ということで、Rubyでプッシュ通知を実現する際に、手軽に使えるサービスです。
導入
gem 'rpush'
bundle install
rpush init
設定
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 オプションで環境を指定できます。
rpush status -e development
rpush start -e development
rpush stop -e development
APIのApplicationController
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にデバイス投入
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コントローラでユーザに合わせた内容に加工して、クライアントに返せます。
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
定期的なジョブでプッシュ通知を送信
rpush status -e development
rpush start -e development
date; bundle exec rake notification:send_notification RAILS_ENV=development
rakeタスクを実行すれば、プッシュ通知が送信されます。
必要に応じて、これをcronで実行したりできそうです(whenever gemを使ってみようと思います)。