Lambda (Node.js) から SNS 経由で IOSデバイス にプッシュ通知

# はじめに
LambdaからSNS経由でアプリをインストールしたIOSデバイスにプッシュ通知する方法についてまとめました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/dbd0f8cd-d591-6ae3-3ac9-78c0293b30de.png" width="600">

# 事前構築
- プッシュ通知用証明書（p12）は、発行済とする
- IOSデバイスのデバイストークンは、取得済みとする
    - 参考記事
    - https://kahoo.blog/howto-aws-sns-ios-push-notification/

# SNS作成
SNSでプッシュ通知の設定をします。
## プラットフォームアプリケーションの作成
`プラットフォームアプリケーションの作成`をクリックします。
![スクリーンショット 2022-10-14 16.00.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/2afc1cf1-a5ad-700e-71be-7e6741cccb9d.png)
- アプリケーション名：`適切な名前`
- プッシュ通知プラットフォーム：`Apple iOS/VoIP/MacOS`
- プッシュサービス：`IOS`
- 証明書：`作成済みのファイルをアップロード`
    - アップロード後、`認証情報をファイルから読み込み`をクリックする
![スクリーンショット 2022-10-14 16.08.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/8e208f0c-9bd3-031d-cb4b-49101644f68d.png)
- 配信ステータスのログ記録のIAMロールは、`新しいサービスロールの作成`を選択し、`新しいロールの作成`をクリックする
![スクリーンショット 2022-10-14 16.01.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/135094dd-1c5f-3b9e-a594-d98919c946e0.png)
    - 許可をクリックすると自動で作成される
![スクリーンショット 2022-10-14 16.02.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/b494a1bf-ac3c-b5f1-5107-126794b8837b.png)
    - IAMロールが作成されたことを確認し、作成をクリックする
![スクリーンショット 2022-10-14 16.02.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/dc567b85-7ce2-a038-6913-5c006df05119.png)
## SNSでプッシュ通知テスト
デバイストークンを元に、SNSからIOSデバイスにプッシュ通知できるか確認します。
デバイストークンは、取得しているものとします。

`アプリケーションエンドポイントの作成`をクリックします。
![スクリーンショット 2022-10-14 16.14.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/e467067b-7df5-8a99-068e-30fa54599f5b.png)
デバイストークンを入力し、作成をクリックします。
![スクリーンショット 2022-10-14 16.15.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/53386cc5-9cb0-491f-a976-49f84d79eebb.png)

メッセージの発行をクリックします。
![スクリーンショット 2022-10-14 16.16.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/724d983d-edf7-9be0-9f7f-7953f58673d5.png)
`配信プロトコルごとにカスタムペイロード。`を選択し、`メッセージの発行`をクリックすると、アプリを入れているデバイスにプッシュ通知が送られます。
![スクリーンショット 2022-10-14 16.16.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/62b354cf-30cc-6157-d5e0-e5f702bde774.png)

このSNSのプラットフォームアプリケーションのARNは、Lambdaで必要なため、メモしておきましょう。
![スクリーンショット 2022-10-14 16.22.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/945062de-196c-71f5-6870-6afc9ea218f8.png)

### プッシュ通知がデバイス側に来ない場合
以下の原因が考えられますので、もう一度設定を確認しましょう。

- デバイストークンが異なる
- プッシュ通知の証明書の設定ミス
- デバイス側でプッシュ通知の表示がオフの設定になっている
- SNSのエンドポイントのステータスが無効になっている

配信失敗時、CloudWatch Logsの`sns/ap-northeast-1/アカウントID/app/APNS/test/Failure`にエラーログが出力されていますので、そちらも参考になります。

ちなみに、`SNSのエンドポイントのステータスが無効になっている`に関して、エンドポイントの編集で設定変更が可能です。
![スクリーンショット 2022-10-14 16.20.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/9b1a8322-6bf7-a008-6e85-218fba088ccf.png)

# Lambda作成
Lambdaを作成します。ランタイムをNode.jsにし、他はデフォルトのままにします。
## IAMロール
`AmazonSNSFullAccess`をアタッチします。
## コード
**環境変数**

- PLATFORM_APPLICATION_ARN：`先程作成したSNSのARN`（arn:aws:sns:ap-northeast-1:アカウントID:app/APNS/test）
- APN_TYPE：`APNS`(サンドボックスの場合、`APNS_SANDBOX`)


```js
const AWS = require('aws-sdk');
const sns = new AWS.SNS();

const platformApplicationArn = process.env.PLATFORM_APPLICATION_ARN;
const apnType = process.env.APN_TYPE;

const pushParams = (endPointArn) => {
  const params = {
    TargetArn: endPointArn,
    Message: JSON.stringify({
      [apnType]: JSON.stringify({
        aps: {
          alert: {
            title: 'タイトルです',
            body: '本文です',
          },
          sound: 'default',
          // アプリ側に渡したい値がある場合、以下などを記載してもよい
          value: {
            status: 'ok',
          },
        },
      }),
    }),
    MessageStructure: 'json',
  };
  console.log('Received pushParams:', JSON.stringify(params, null, 2));
  return params;
};

const pushNotification = async (deviceTokens) => {
  for (const deviceToken of deviceTokens) {
    const platformApplicationArnParams = {
      PlatformApplicationArn: platformApplicationArn,
      Token: deviceToken,
    };
    // PlatformEndpointを作成。作成済みの場合、新たに作成せず、PlatformEndpointのARNを返す
    const createEndpoint = await sns.createPlatformEndpoint(platformApplicationArnParams).promise();
    const endPointArn = createEndpoint.EndpointArn;
    console.log('Received endPointArn:', JSON.stringify(endPointArn, null, 2));

    const enableEndpointParams = {
      EndpointArn: endPointArn,
      Attributes: {
        Enabled: 'true',
      },
    };
    // PlatformEndpointのARNを元に、PlatformEndpoint を Enabled の状態にする。元々Enabledの状態でも問題なし。
    await sns.setEndpointAttributes(enableEndpointParams).promise();

    // プッシュ通知する
    await sns.publish(pushParams(endPointArn)).promise();
  }
};

exports.handler = async (event, context, callback) => {
  const deviceTokens = ['deviceToken-1', 'deviceToken-2', 'deviceToken-3'];
  await pushNotification(deviceTokens);
};

```


- setEndpointAttributes

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SNS.html#setEndpointAttributes-property

- createPlatformEndpoint

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SNS.html#createPlatformEndpoint-property

- publishメソッド

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SNS.html#publish-property

今回使用した上記の３つのメソッドの役割について、以下の記事が参考になりました。

https://dev.classmethod.jp/articles/sns-mobile-token/

# 参考

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-handler.html



https://qiita.com/Fujimon_fn/items/740ecfdd9328375c1616

https://qiita.com/takmot/items/664b57e8be6a41b33ab9