便利だけどドキュメントが残念なMobileBackendについて書いてみる。

## はじめに
`MobileBackend`というのは`Android`の`GCM`や`iPhone`の`APNs`に対してプッシュ通知を送ってくれるサービスである。

サイトはこちら。
[http://mb.cloud.nifty.com/](http://mb.cloud.nifty.com/)

僕はプッシュ通知を送るために通知用のサーバーを`Rack`を使って`Ruby`で作ってました。
自前で作っただけあって、たまにプッシュ通知が届かないと言う事態も発生して困ったりしてました。

そうこうしているうちに`AmazonSNS`と言うサービスが出てきたのですが、色々あって結局移行できずにその現場を去ってしまいました。

で、今回もプッシュ通知を送る必要が出てきたので`MobileBackend`を使ったのですが・・・

と言うお話です。
なお、クライアントサイドの話ではなくプッシュ通知を送るサーバーサイドの話です。

## 困った点
`MobileBackend`は非常に便利なサービスだったのですが、タイトルにも書いているようにドキュメントがとても少ないです。

これといった`Gem`も無いためサーバーサイドで実装するためには、自前でプログラムを組む必要がありました。
しかし、ドキュメントがあまり無いためサーバーサイドを実装するのに困りました・・・。

## 自前でプッシュ通知を送る場合
自分でプッシュ通知を送る場合は以下の手順が必要になります。

1. プッシュサーバー構築を行う。
2. プッシュ通知を行うプログラムを書く
3. サーバーにGCMのキーとAPNsの証明書を設定する。
4. クライアント（Android/iPhone）からトークン(デバイストークン/Registration ID)をサーバーに送ってもらう。
5. `4`で送られたトークンとユーザー情報を紐づけてデータとして保存する。
6. 必要に応じて`2`のプログラムで送信する。

僕は`iOS`には`houston`、`Android`には`APNS`で送ってたので、AndroidとiOSで送信プログラムが違い似たような処理が二つできてました。
また、通知エラーの取得方法がAndroidとiOSで違うため変な実装が出来上がってしまいました。（iOSは定期的にpollingしてエラー情報を取りに行かなければならない。）

証明書が切れるとサーバー内に入って証明書を切り替える必要がありました。

APNs/GCMのレスポンスに時間がかかってサーバーリソースを喰わないようにするために、送信データを一時的にキュー(Redis)にためて送ってました。

大変でした・・・（初めて作ったので・・・）。

## MobileBackendとは
`MobileBackend`はこれらのことを代替えしてくれるサービスです。
トークンの保持も証明書の管理も全て`MobileBackend`が行ってくれます。
当然、テスト用のプッシュ通知も簡単に飛ばせます。

非常に便利なのですが、、、とにかくドキュメントが少ないです。

## 実装方法
ドキュメントが少ない中で以下のサイトを見つけれたのは幸運でした。

[ニフティクラウドmobile backendでPush通知できるRubyスクリプト](http://kakakikikeke.blogspot.jp/2015/03/ncmb-ruby-push.html)

このプログラムコードがなければ`Rest通信`をしてゴリゴリ書くしかなかったです。
上記のプログラムをRailsで使用したコードがこちらです。

```rb:Gemfile
gem "rest-client", ">= 1.7.3"
gem "ruby-hmac", ">= 0.4.0"
```

上記のGemを入れた後に`Model`に以下のコードを追加しました。

```rb
# プッシュ通知送信処理
class PushNotifications
  # MobileBackendへ送信するために使用
  require 'rest_client'
  require 'json'
  require 'time'
  require 'hmac'
  require 'hmac-sha2'
  require 'base64'
  require 'openssl'

  # MobileBackendで使用する固定パラメータ
  ENDPOINT='mb.api.cloud.nifty.com'
  VERSION='2013-09-01'
  SIG_METHOD='HmacSHA256'
  SIG_VERSION=2
  PATH='/push'

  # iOS/Androidを別々に送らないと送信できなかったためクッション用のメソッドを準備
  # 引数なしで呼び出すと全端末へサイレントプッシュが送れる。
  def self.send_mobile_backend(message = nil, channel = nil)
    begin
      # チャンネルを指定して、対象の端末にプッシュ通知を送る。
      self.send_mobile_backend_target(message, channel, "ios")
    rescue => e
      Rails.logger.error "プッシュ通知の送信に失敗しました。#{e}"
    end

    begin
      self.send_mobile_backend_target(message, channel, "android")
    rescue => e
      Rails.logger.error "プッシュ通知の送信に失敗しました。#{e}"
    end
  end

  # 実際のPUSH通知を行います。
  def self.send_mobile_backend_target(message = nil, channel = nil, target)
    # モバイルバックエンドのAPPキーを取得
    application_key = AppConfig.get(:env, Rails.env, :mobile_backend, :app_key)
    # モバイルバックエンドのクライアントキーを取得
    client_key = AppConfig.get(:env, Rails.env, :mobile_backend, :client_key)
    timestamp = PushNotifications.get_timestamp
    signature = PushNotifications.create_signature(
        "POST",
        ENDPOINT,
        VERSION,
        PATH,
        client_key,
        "X-NCMB-Application-Key" => application_key,
        "X-NCMB-Timestamp" => timestamp,
        "SignatureMethod" => SIG_METHOD,
        "SignatureVersion" => SIG_VERSION,
    )

    # 即時配信を指定して、iOS/Androidの指定を行います。
    params = {
        'immediateDeliveryFlag' => true,
        'target' => [
            target
        ],
    }

    # メッセージの指定がある場合はメッセージを追加します。
    params = params.merge('message' => message) if message

    # メッセージがあって、iOSの場合はサウンドとバッチをつけます。
    # Androidの場合に送るとエラーが発生します。
    params = params.merge('sound' => 'default', "badgeIncrementFlag" => false, "badgeSetting" => 1) if message && target == "ios"

    # クライアントへ渡したいデータがある場合は以下のように指定できます。
    params = params.merge("userSettingValue" => {"hoge" => "foo"})

    # チャンネルの指定がある場合にはチャンネルを指定します。
    # こんなコードにしているのはsearchConditionが無いとおちるかもと思って、つけてます。（思考錯誤した後に検証してなかった・・・）
    search_condition = {"searchCondition" => {}}
    search_condition = {"searchCondition" => {"channels" => channel}} if channel
    params = params.merge(search_condition)

    # 実際に送信します。
    response = RestClient.post( "https://#{ENDPOINT}/#{VERSION}#{PATH}",
                                params.to_json,
                                {
                                    :content_type => 'application/json',
                                    'X-NCMB-Application-Key' => application_key,
                                    'X-NCMB-Timestamp' => timestamp,
                                    'X-NCMB-Signature' => signature
                                })
    # 送信結果をログに出します。
    json = JSON.parse(response)
    Rails.logger.info json
  end

  # 以下は上記のURLの通り実装。
  def self.create_signature(method, endpoint, version, path, client_key, options = {})
    canonical_querystring = options.sort.collect { |key, value| [key, value].join('=') }.join('&')
    string_to_sign = "#{method}\n#{endpoint}\n/#{version}#{path}\n#{canonical_querystring}"
    hmac = HMAC::SHA256.new(client_key)
    hmac.update(string_to_sign)
    Base64.encode64(hmac.digest.to_s).chomp
  end

  def self.get_timestamp
    Time.now.utc.xmlschema.gsub("Z", ".456Z")
  end
end
```

`kakakikikeke`さんのおかげで無事に送ることができました。

なお、ドキュメントは以下になります。
[REST API リファレンス](http://mb.cloud.nifty.com/doc/current/rest/push/pushRegistration.html)

iOS特有の項目などに関しては一切記述がありませんが、`sound`や`badgeSetting`をAndroidに向けて送信するとエラーが出ます。
しかも、なんでエラーが出たかわかりません・・・。

なので一気にパラメータを追加して送るのではなく、パラメータを追加しては試しながら進めると良いと思います。

## まとめ
チャンネルを使って送信できるので、例えば特定のカテゴリーに所属している人だけに送る場合は「`"category_#{category_id}"`のようなチャンネルに送る」といったように使えます。
とても便利です。

便利ですが・・・使うためにはコツがいります。
ということで、MobildeBackendを使う方の参考になればと思います。

最後になりますが、`kakakikikeke`さんありがとうございました。