RailsでAmazonSNSを使ってiosにプッシュ通知を送る。


##目的
SwiftとRailsで作られているSNSアプリで以下の場合に通知を送れる様にしたい。
1.ユーザーの投稿に対していいね、コメントがついた時。
2.admin画面から、「 運営からのお知らせ 」を発行した時。

###用語が結構ややこしいのでそれの説明から（AmzonSNSの文脈で）、

####APNs
ApplePushNotificationサービスのことで、リモート通知機能の中核。
iosなどに開発者が情報を配信する為のサービス。

####デバイストークン
iphoneなどの実機を一意に識別するトークン。
デバイスからAPNsにリクエストを送り取得する。

####エンドポイント
AmazonSNSに登録される(user_idやデバイストークンなどの)、通知をどのユーザーに送るかを識別する情報。

####トピック
超簡単に説明すると、エンドポイントの集合。
複数のエンドポイントをまとめといて、トピックに通知を送ると登録されてるエンドポイント達に一斉送信できる。


####サブスクリプション
トピックとエンドポイントの関連の情報。
多対多のデータベースのつなぎ目的な。

####サブスクライブ
エンドポイントをトピックにサブスクライブする。みたいに使われる。

##全体の流れ
だいたいこんな感じ。

![push通知.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/664524/a95b862d-34b3-979d-0cea-fc5e2cbd2f89.png)

#APNsを利用する

APNsをAmazonSNSから利用する際に、
・APNs用証明書(.p12)　が必要になります。
以下の記事が参考になるかと思います。
https://qiita.com/3kuni/items/62c4739cf1316b2c2ef

#AmazonSNSへの登録
証明書が発行出来たら、AmazonSNSでアプリケーションの登録を行いましょう。
上から、
・アプリケーション名　　任意

・プッシュ通知プラットフォーム　　Apple　iOS/VoIP/Mac

・サンドボックスでの開発に使用される　開発環境用の場合チェックをいれる。

・証明書　　上で作ったファイル

を入力しアプリケーションプラットフォームを作成。
これでアプリケーションをAmazonSNSに登録することが出来ました。

#Rails側

Railsでは、以下の様なアクションを作って行こうかなと思います。

1.デバイストークンを受け取りそれをAmazonSNSに登録して、各種情報をDBに保存するcreate。

2.ログアウトの際などの為の1.を削除するdestroy。

```ruby:push_notification_controller.rb
 class Ios::PushNotificationController < ApiController
   def create
   
   end 

   def destroy
   end
 end
```

####まずは下準備から。


aws-sdkが入ってない場合はいれる。

```ruby:Gemfile
gem 'aws-sdk'
```

SNSのアクセス権限を付与したIAMユーザーを作り以下の様にクライアントをセットする。


```ruby:
 def set_aws_sns_client
          @client = Aws::SNS::Client.new(
            access_key_id:     ENV[ACCESS_KEY_ID],
            secret_access_key: ENV[SECRET_ACCESS_KEY],
            region:            "ENV[REGION]" ,
          )
  end
```
下準備完了。

##デバイストークンを受け取る処理

ここで目的を振り返る、今回は以下の場合に通知を送りたい。
1.ユーザーの投稿に対していいね、コメントがついた時。
2.admin画面から、「 運営からのお知らせ 」を発行した時。

1の時は投稿をしたユーザーに個別送信。
2の時は通知を許可している全てのユーザーに通知を送りたい。

これを実現するために、必要なことは

railsサーバーから繰り返しAmazonSNS経由で通知を送れるように、endpoint_arnやsubscription_arnをdbに保存できるようにする。 (a)

1 →　
各ユーザーの有効なエンドポイントをAmazonSNSに登録できること。(b)
プッシュ通知に失敗するとエンドポイントは無効になってしまうので、エンドポイントを更新出来るようにしておくこと。(d)
デバイストークンはAPNsで変更される可能性があるのでエンドポイントを更新できるようにしておく。(e)

2 →
全てのユーザーに一括送信したいので、AmazonSNSにコンソールからトピックを作成しユーザーがエンドポイントを登録するとともに作成したトピックにサブスクライブするようにする。(c)

###(a)dbへの保存
テーブル設計は以下のようにした。
notification_tokensテーブル

|id|device_token |endpoint_arn|subscription_arn|user_id|
|---|---|---|---|---|
|int|text|text  |text  |int|



###(b) 各ユーザーの有効なエンドポイントをAmazonSNSに登録できること。

iosから送られてきたトークンを受け取り、エンドポイントを登録。

``` ruby:
 device_token =  device_token_params
 response = @client.create_platform_endpoint(
             platform_application_arn: "arn:aws:sns:xxxxxxxxxxxxxxx",
             token: device_token,
             custom_user_data: {user_id: }.to_json,
            )

 def device_token_params
  params.permit(:device_token)
 end
```

### (c)トピックにサブスクライブする。

```ruby:
 response_topic = @client.subscribe(
              topic_arn:  'arn:aws:sns:XXXXXXXXXXXXXXXXXXX',
              protocol:   'application',
              endpoint:   response.endpoint_arn, #(a)のレスポンス
            )
```

これで全体通知が送れるようになる。

また(b)のレスポンスと合わせてdbに保存

```ruby:
NotificationToken.create!(user_id: @user.id,
                          device_token: device_token,
                          endpoint_arn: response.endpoint_arn,
                          subscription_arn: response_topic.subscription_arn
                                   )
```




###(d)(e)エンドポイントの更新
(d)(e)　どちらの場合も同じように更新するようにする。

``` ruby:
response = @client.set_endpoint_attributes({
              endpoint_arn: @user.notification_token.endpoint_arn,
              attributes: {
                  "Enabled" => "true",
                  "Token" => device_token
                },
              })
#トークンが変更されていたら、
current_user.notification_token.update_attributes(device_token:  device_token)
```

これでトークンが変更されていても、トークンが変更されていないが通知が何らかの原因で送れずエンドポイントが無効になっていてもここでios側から起動時に毎回更新するようにしておけば通知が送れなくなる心配はない。


##エンドポイントを削除する処理

AmazonSNSからエンドポイントを削除しdbを削除するだけで良いと言いたいところなのだが、SNSからエンドポイントを削除してもそのサブスクリプションまでは削除されない。
なのでサブスクリプションも削除する処理をかく。コードは以下。

```ruby:
@client.delete_endpoint(endpoint_arn:@user.notification_token.endpoint_arn)
@client.unsubscribe(subscription_arn:@user.notification_token.subscription_arn)
@user.notification_token.destroy
```


##通知を送るコード
個別通知

```ruby:
response = @client.publish(
      target_arn: self.endpoint_arn,
      message: "いいねされました！！"
    )
```
全体通知

```ruby:
 @client.publish(
        target_arn: 'arn:aws:sns:XXXXXXXXZXX',
        message: '運営からのお知らせです。',
 )
```
あとはこれを通知を送りたい場所に組み込んでいく。

##参考
https://53ningen.com/native-app-push-notification/
https://dev.classmethod.jp/articles/sns-mobile-token/
https://dev.classmethod.jp/articles/sns-push-filtering/
https://tech.medpeer.co.jp/entry/2018/03/15/080000
https://ppworks.hatenablog.jp/entry/2017/10/07/183506