Rails + Swiftのプッシュ通知をAmazonSNSで実現する

SNSアプリでいいねを押した時に相手に通知されるような機能を実装したいと考えました。

なんだかすごく難しそうに思えますが、プッシュ通知を送ることができるのは大きなアドバンテージなので、ぜひ実装したいところです💪🏻説明不足なところや間違っているところがあればぜひぜひコメントください😄

## 実装してみた感想
- 証明書の発行をする時、いま自分が何の作業をしているのかわからないのが不安でした。
- 証明書関連の発行と全体像の学習に時間を使いましたが、SwiftとRailsの実装はそこまで難しくなかったです。
- 送った通知が届くのはめっちゃうれしい🎉

## 全体像
それぞれのiOSデバイスが持っているデバイストークンをRailsに送ります。それをAmazonSNSにエンドポイントとして登録。Rails側ではユーザのエンドポイントをModelに保存しておきます。何かのアクションをトリガーにして、Railsでエンドポイントを叩くとiOSデバイスに通知が届くという流れです。

作業の手順としては

1. 通知に関する証明書の発行
2. AmazonSNSへの登録
3. Swiftでの実装
4. Railsでの実装

の順で進めました。

## 1. 通知に関する証明書の発行
こちらを参考に、①〜⑦の手順で作成します。
[【iOS】プッシュ通知の受信に必要な証明書の作り方(開発用)](https://github.com/natsumo/iOS_Certificate)

それぞれ何をやっているかは、こちらの説明がとてもわかりやすかったです。
[ iOSアプリのプロビジョニング周りを図にしてみる ](https://qiita.com/fujisan3/items/d037e3c40a0acc46f618)

以下、補足や注意点などです。

### ①CSRの作成
Mac上での作業です。すでに作成済みの場合不要で、もし名前の同じファイルを作ってしまうとかえって混乱の元になるので注意。

>⚠️注意:通称に日本語を入力してはいけない

通称に日本語を使っていると、AmazonSNSでの登録ができません。

CSR(Certificate Signing Request)とは、Appleに「証明書を発行してください」という要求を記述したファイルです。市役所で住民票が欲しい時に「申請書」を書くようなイメージでしょうか。

### ②.cerの作成
[Apple Developer Programメンバーセンター](https://developer.apple.com/account/)での作業です。すでに作成済みの場合不要です。名前の同じファイルを作ってしまうとかえって混乱の元になるので注意。

Apple側が「要求があったので証明します」ということで発行されます。発行されたものはファイルとしてダウンロードします。

### ③AppIDの作成
[Apple Developer Programメンバーセンター](https://developer.apple.com/account/)での作業です。`XC xxxx xxxx `というApp IDが自動的に作成されている場合は省略できます。上２つの証明書とは別の、アプリ自体のIDです。アプリケーションを識別するために使われます。何かファイルが作成されるわけではなく、登録するだけで完了です。

### ④端末の登録
動作確認のために使う端末を登録します。

[Apple Developer Programメンバーセンター](https://developer.apple.com/account/)での作業です。すでに作成済みの場合不要です。登録するだけで完了です。

### ⑤プロビジョニングプロファイルの作成
[Apple Developer Programメンバーセンター](https://developer.apple.com/account/)での作業です。

プロビジョニングプロファイルとは、iPhone,Padなどの実機にインストールするために必要な証明書です。ファイルをダウンロードして、ダブルクリックしておきます。

### ⑥APNs用証明書(.cer)sの作成
[Apple Developer Programメンバーセンター](https://developer.apple.com/account/)での作業です。

通知を送るためにはApple Push Notification serviceというサービスを経由することになります。APNs用証明書は、そのプッシュ通知サービスを利用するための証明書です。

### ⑦APNs用証明書(.p12)の作成
Mac上での作業です。

⑥で作成した.cerは証明書のみのファイルですが、.p12は証明書と秘密鍵を書き出したファイルです。

>⚠️注意: 「証明書」の上で右クリックする
⚠️注意:  ファイル名に日本語を使わない 
⚠️注意:  パスワードを設定しない


## 2. AmazonSNSへの登録
AmazonSNSとは、Amazon Simple Notification Serviceの略称です。FacebookなどのSNSとは関係ない模様。

まずは登録します。
[ Amazon Simple Notification Service ](https://aws.amazon.com/jp/sns/)

### Amazon SNSにプラットフォームアプリケーションを作成
まず、アプリを登録します。

`SNSダッシュボード -> アプリケーション -> プラットフォームアプリケーションの作成`
を開きます。

`プッシュ通知プラットフォーム`をApple ProductionまたはApple Developmentを選択。`P12 ファイルの選択`から.p12ファイルをアップロードします。`認証情報をファイルから読み込み`をクリックして、プラットフォームアプリケーションの作成ボタンをクリック。

作成が完了すると、ARNというURIのようなものが発行されます。このARNによってどのアプリから通知を送っているか識別できるようになります。

ちなみに、私はこの段階で詰まりました。原因は、①CSRファイル作成の時に日本語を使っていたからです😢



### 実機からデバイストークンを取得する
ここで一旦、実機からデバイストークンを取得するためにSwiftを書きます。

Xcodeの左側のナビゲーターから、プロジェクト名をクリック。
`TARGETS -> Capabilities -> Push Notifications` をONにする。

```AppDelegate.swift
import UserNotifications  // インポートする
```

```AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { // Delegateの追加
```

```AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
		
		// 通知の可否を問う
		if #available(iOS 10.0, *) {
			//ios10
			let center = UNUserNotificationCenter.current()
			center.requestAuthorization(options: [.badge, .sound, .alert], completionHandler: { (granted, error) in
				if error != nil {
					return
				}
				if granted {
					debugPrint("通知許可")
					center.delegate = self
					DispatchQueue.main.async(execute: {
						application.registerForRemoteNotifications()
					})
				} else {
					debugPrint("通知拒否")
				}
			})
		} else {
			// ios9
			let settings = UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil)
			UIApplication.shared.registerUserNotificationSettings(settings)
			UIApplication.shared.registerForRemoteNotifications()
		}
		
		return true
	}
```

```AppDelegate.swift
	// リモート通知を許可したとき
	func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
		// 文字列の処理をする
		let deviceTokenString: String = deviceToken.map { String(format: "%.2hhx", $0) }.joined()
		print("deviceTokenString \(deviceTokenString)")
	}
	
	// リモート通知を拒否したとき
	func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
		
		debugPrint("リモート通知の設定は拒否されました")
	}
```

これでビルドすると、デバイストークンが表示されるのでコピーします。


### エンドポイントの作成
それぞれのiOSデバイスに通知を送るために必要なエンドポイントを作成します。エンドポイントは、iOSデバイスのデバイストークン、ARN(URIのようなもの)、ユーザーデータ(idなど)から構成されています。

AmazonSNSを開きます。
`SNSダッシュボード -> アプリケーション -> 作成したプラットフォームアプリケーションのARN` をクリック。`プラットフォームエンドポイントの作成`をクリック。さきほどコピーしたデバイストークンをペーストします。ユーザーデータはエンドポイントを識別するためにあるので、適切な名前を入れておくとよいです。


### 通知を送ってみる
エンドポイントが登録されたら、選択して、エンドポイントへの発行。`JSON message generator`を使って、通知する本文を入力します。これでメッセージの発行をするとプッシュ通知が届きます👍🏻

これで個別にプッシュ通知を送る設定が完了しました。個別に送るというのは、「Aさんがあなたの投稿にいいね！しました」のような通知のことです。

「新しいバージョンが公開されました」のような一斉に配信される通知は、下記のトピックという機能を使います。

### トピックの作成
`SNSダッシュボード -> トピック -> 新しいトピックの作成` をクリックします。適切なトピック名と表示名を入力します。

トピックが作成できたら、ARNをクリックして、トピックARNをコピーしておきます。

続いて、作成したトピックに個々のデバイスを登録(サブスクリプション)していきます。
`SNSダッシュボード -> サブスクリプション -> サブスクリプションの作成` をクリック。トピックARNにさきほどコピーしたARNをペーストします。プロトコルはApplication、エンドポイントは登録したいエンドポイントのARNを入力します。

### トピックに通知を配信する
`SNSダッシュボード -> トピック -> 通知をしたいトピック -> トピックに発行`

個別に送った時と同じ手順で、通知を送ることができます💪🏻 

## 3. Swift側での実装
RailsにJSONで送ります。AlamofireとSwiftyJSONがスタンダードでしょうか。

[ AlamofireとSwiftyJSONでAPIを叩くチュートリアル ](https://qiita.com/yutat93/items/1b6dfe34fa8537cf3329)

## 4. Railsでの実装

Railsからは、`aws-sdk` というgemを使って簡単にプッシュ通知が送ることができます。

この記事を参考にしました。
[ Ruby/RailsでAmazon SNSを使用してiOSとAndroidにpush通知を送る方法 ](https://qiita.com/arumi8go/items/11c713186a055bd7fc86)

AmazonSNSのウェブコンソールからやったように、
1. エンドポイントの登録
2. トピックにサブスクリプション
3. 送信
の手順を踏んでいきます。

まずはgemをインストール

```ruby:Gemfile
gem 'aws-sdk', '>= 2.3.22'
```

```ruby:ターミナル
bundle install
```

### AWSのIDやKeyを取得して設定
aws-sdkを使う際にAWSのAccessKeyID 、 SecretAccessKey 、 Regionが必要になります。AccessKeyID 、 SecretAccessKey を以下にアクセスして確認します。 `アクセスキー（アクセスキー ID とシークレットアクセスキー）`から新しいアクセスキーを作成します。
[https://console.aws.amazon.com/iam/home?#/security\_credential](https://console.aws.amazon.com/iam/home?#/security_credential)

Regionは`Amazon SNS  -> ダッシュボード -> リソース`を参照してみると、`\{\*\*\*\} リージョンで次の Amazon SNS リソースを使用しています。`という表記があるので参考にします。

```ruby
@client = Aws::SNS::Client.new(
	access_key_id:     ENV['ACCESS_KEY_ID'],
	secret_access_key: ENV['SECRET_ACCESS_KEY'],
	region:            ENV['REGION'],
  )
```

### エンドポイントの作成
```ruby

	# エンドポイントの作成
	response = @client.create_platform_endpoint(
	  platform_application_arn: "arn:aws:sns:xx-xxx-x:000000000000:app/APNS_SANDBOX/xxxx00000sandbox",
	  token: device_token,
	  custom_user_data: user.id.to_s, # ユーザidや名前
	)
```

### トピックに登録
上記のresponseにはエンドポイントのARNなどが含まれているので、それを利用します。

```ruby

	# エンドポイントのレスポンスからトピックに登録
	if response
	  arn = response.endpoint_arn
	  PushNotificationSetting.create_settings(device_token,arn,user)

	  @client.subscribe(
		topic_arn:  "arn:aws:sns:xx-xxx-0:00000000:xxxxxx”,
		protocol:   'application', 
		endpoint:  arn,
	  )
	end
```

### 通知を送る
```ruby
	aps = {alert: “foo”, badge: 1, sound: 'default', 'mutable-content': 1}

	json = {
	  default: "defalut",
	  #APNS: "{\"aps\":{\"alert\": \"apns\"} }",
	  APNS_SANDBOX: {aps: aps}.to_json,
	}.to_json
	@client.publish(target_arn: "arn:aws:sns:xx-xxx-0:0000000000:endpoint/APNS_SANDBOX/xxxx0000sandbox/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx”, message: json, message_structure: 'json')
```

>⚠️注意:default: にメッセージを設定しないと送信できません

## おわりに
これでRailsからiOSアプリにプッシュ通知を送ることができるようになりました！😄