1. Home
  2. テクノロジー
  3. iOS16.4からiOSのSafariでもWebプッシュが受け取れるようになったのでAWSの機能でも試してみた! – 後編 (リモートでのWebプッシュ通知)

iOS16.4からiOSのSafariでもWebプッシュが受け取れるようになったのでAWSの機能でも試してみた! – 後編 (リモートでのWebプッシュ通知)

目次

はじめに

こんにちは!

普段は、ニフティ不動産のフロントエンド開発をメインで行なっているSoraY677です。

今回の記事では、自身が普段あまり触れることない技術を学んでみたいという思いから、iOS16.4からできるようになったWebプッシュ通知を、AWSの各機能を活用してリモートから送ってみようと思います!まだ世に出たばかりのリモートでのWebプッシュ通知機能を学び、アプリケーション開発等で活用いただければと思います!

また、この作業の前に、基本的な準備を行う前編での作業が必要です。まだ見ていないという方は先にそちらをご覧ください。

→ 前編はこちら

今回の作業

今回取り扱う、Webプッシュ通知をAWSの各機能を活用してリモートから送るために必要な作業は以下の通りです。

  • DBにWebプッシュ通知用情報を保管
  • LambdaでWebプッシュ通知

それでは早速始めましょう!

DBにWebプッシュ通知用情報を保管

これまではローカル上でWebプッシュ通知をしていましたが、今度はリモートでWebプッシュ通知に必要な情報を保持しておき、それを使って通知を行います。

以下のフローのように、AWSの各サービスを活用してWebプッシュ通知を送ります。

DBにデータを保管するまで

Webプッシュ通知を行うために必要な情報は以下の三つです。

  • endpoint:Webプッシュ通知を送信するために使用するサーバーのURL
  • p256dh:公開鍵
  • auth:暗号化を複雑にするための乱数

これらの保管に利用するDynamoDBとAPI Gatewayを設定しましょう。

DynamoDBの設定

新規テーブルの作成から以下の項目を設定し、他はデフォルトのままでテーブルを作成します。

  • テーブル名: web-push-notification
  • パーティションキー: id / 文字列

テーブルが作成されれば完了です。

API Gatewayを設定

今度は、API Gatewayの画面から「APIを作成」ボタンをクリックし、「REST API > 構築」を選択します。

プロトコルの選択として以下を設定し、「APIの作成」をクリックします。

  • プロトコルを選択する:「REST」を選択
  • 新しいAPIの作成:「新しいAPI」を選択
  • 名前と説明
    • API名:webpush-notification-api
    • エンドポイントタイプ:リージョン

次に「アクション > メソッドの作成 > POST」を選択し、APIのエンドポイントの詳細を定義していきます。

エンティティを以下に設定し「保存」をクリックします。

  • 信頼されたエンティティタイプ:「AWSのサービス」
  • ユースケース:「他のAWS上でサービスのユースケース」→「API Gateway」
  • AWSリージョン: ap-northeast-1
  • AWSサービス: DynamoDB
  • HTTPメソッド: POST
  • アクション: PutItem
  • 実行ロール: <DynamoDBへのアクセス権限を持った適切なロールを指定>

今度は、POSTの詳細な設定を行うため「統合リクエスト」をクリックします。

統合リクエストの設定として、マッピングテンプレートを追加します。

  • リクエスト本文のパススルー:「テンプレートが定義されていない場合」を選択
  • Content-Type: application/json

テンプレート内のJSONは、以下とします。

{
  "TableName": "web-push-notification",
  "Item": {
    "id": {
      "S": $input.json('$.id')
    },
    "endpoint": {
      "S": $input.json('$.endpoint')
    },
    "p256dh": {
      "S": $input.json('$.p256dh')
    },
    "auth": {
      "S": $input.json('$.auth')
    }
  }
}

設定したら保存します。

また、CORSの設定も行います。(今回はローカルやGitHubでホスティングしたページを使うため、何もしないとCORSに通信が弾かれてしまいます。)

メソッドの実行ページから「アクション > CORSの有効化」をクリックします。

設定画面ではデフォルトで以下の設定がされているはずなので、そのまま「CORSを有効にして既存のCORSヘッダーを置換」をクリックします。

  • Access-Control-Allow-Methods: OPTIONS, POST
  • Access-Control-Allow-Headers: 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'
  • Access-Control-Allow-Origin:‘*’

今回はオリジンをすべて対象にしていますが、実際のサービス等で使用する際は最適なオリジンのみにしてください。

確認が出てくるので、「はい、既存の値を置き換えます」をクリックします。

すると、リソースにOPTIONSが増えているのがわかると思います。これで設定は完了です。

テスト

メソッドの実行設定ページに戻り、今度は「テスト」をクリックします。

メソッドテストのページで、リクエスト本文に以下のテスト用のJSONを設定して「テスト」をクリックします。

{
    id: "api-gateway-test",
    endpoint: "https://sample.com",
    p256dh: "test",
    auth: "test"
}

テストを実行すると右側にログが現れます。同時にDynamoDBのテーブルにレコードが追加されているはずです。

デプロイ

実際にサイトからAPIにアクセスできるようにします。

メソッドの実行ページまで戻り、「アクション > APIのデプロイ」をクリックします。

APIのデプロイ設定を行い、「デプロイ」をクリックします。

  • デプロイされるステージ:[新しいステージ]
  • ステージ: dev(自由に命名してOK)

ステージエディターのページに移動するので、一番上の「URLの呼び出し」に書いてあるエンドポイントを記録しておきます。

これで、アプリケーションからAPIに対してPOSTした情報を、DBに保存するという設定ができました。

サイトからWebプッシュ通知の設定をPOSTする

必要な情報をアプリケーション側からPOSTするように修正します。

この作業にあたり、まずは秘密鍵と公開鍵を生成する作業が必須になります。鍵の生成はコマンドラインからssh-keygenで簡単に行うことができます。

鍵生成が出来たら記録しておき、index.htmlのコードを以下に変更します。生成した公開鍵と先ほど作成したAPIのエンドポイントはそれぞれ置き換えます。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="manifest" href="manifest.json" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Webプッシュ通知</title>
  </head>
  <body>
    <button id="subscribe-button">リモートでの通知を許可する</button>
    <script>
      const publicKey = "<生成した公開鍵>";

      // 通知の登録を行う関数
      async function subscribeNotification(reg) {
        if (!reg) return;

        let result = null;
        try {
          result = await reg.pushManager.subscribe({
            userVisibleOnly: true,
            applicationServerKey: publicKey,
          });
        } catch (e) {}
        return result;
      }

      async function postSubscription(endpoint, p256dh, auth) {
        const result = await fetch(
          "<APIのエンドポイント>",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              id: "web-test",
              endpoint: endpoint,
              p256dh: p256dh,
              auth: auth,
            }),
          }
        );
      }

      // ArrayBufferをStringに変換する関数
      function ArrayBufferToString(arrayBuffer) {
        return btoa(
          String.fromCharCode.apply(null, new Uint8Array(arrayBuffer))
        )
          .replace(/\+/g, "-")
          .replace(/\//g, "_");
      }

      window.onload = async () => {
        navigator.serviceWorker.register("./service-worker.js");

        navigator.serviceWorker.ready.then((reg) => {
          document
            .getElementById("subscribe-button")
            .addEventListener("click", async () => {
              const subscription = await subscribeNotification(reg);
              if (!subscription) return;

              const endpoint = subscription.endpoint;
              const p256dh = ArrayBufferToString(subscription.getKey("p256dh"));
              const auth = ArrayBufferToString(subscription.getKey("auth"));

              await postSubscription(endpoint, p256dh, auth);
            });
        });
      };
    </script>
  </body>
</html>

このコードを少し解説すると、サービスワーカーの登録時に提供される機能(reg;サービスワーカーの機能が詰まったオブジェクト)を用いて、Webプッシュ通知の許可を行なっています。

const subscription = await reg.pushManager.subscribe({
    userVisibleOnly: true, // ユーザにプッシュ表示するだけか否かフラグ
    applicationServerKey: publicKey,
});

この実行結果としてendpointp256dhauthが返されます。

subscription.endpoint
subscription.getKey("p256dh")
subscription.getKey("auth")

ただし、p256dhauthはArrayBufferとして返されます。そのため、独自に定義したArrayBufferToString関数によって文字列に変換しているのです。

では、試しにローカルからVisual Studio CodeのLive Serverを起動して、立ち上がったページにアクセスしてみましょう。

サイトの「リモートでの通知を許可する」ボタンをクリックします。正しく動作すれば、DynamoDBにレコードが追加されているはずです。

実際にPWAからも登録を行いたいので、このテーブルのレコードを一度削除し、コードの修正内容をGitHubにプッシュします。(さきほど作業していたブランチにプッシュするようお願いします。公開されているGitHub Pagesが自動で更新されるためです。)

また3~5分程度待ち、公開されているGitHub Pagesにアクセスします。(ボタンの文言が「リモートでの通知を許可する」に変わっているはずです。)

手持ちのiOS端末で、先程追加したPWAをホーム画面から削除します。その後PWA化を再度行い、起動して通知を許可します。すると、DynamoDBの方にWebPush通知に必要な情報が入っているはずです。

LambdaでWebプッシュ通知

LambdaでWebプッシュ通知を送るにあたり、Node.jsパッケージの一つであるweb-pushを利用して実現していきます。

Lambdaの関数を新規作成

まずは土台となる関数の作成を行います。「関数の作成」ボタンから以下のように設定します。

Node.jsのパッケージをLambdaレイヤーとして登録

LambdaでNode.jsパッケージを利用するために、その設定を一つにまとめたnodejs.zipを用意してLambdaレイヤーとして登録する方法を今回は採用します。

現在のディレクトリ構成に対し、以下のように各ファイルを追加します。

project
 ├ nodejs < new!
 ├ .gitignore < new!
 ├ index.html
 ├ manifest.json
 └ service-worker.js

.gitignoreは以下のように設定します。

node_modules
nodejs.zip

続いて、nodejsのディレクトリ配下で以下のコマンドを実行し、パッケージをインストールします。

npm init
# ... (色々聞かれますが、全てEnterでOKです)
npm install web-push
npm install aws-sdk

これで、現在のディレクトリ構成は以下となったはずです。

project
 ├ nodejs < new!
 │ ├ node_modules < new!
 │ │ └ ... < new!
 │ ├ package-lock.json < new!
 │ └ package.json < new!
 ├ .gitignore < new!
 ├ index.html
 ├ manifest.json
 └ service-worker.js

package.json及びpackage-lock.jsonに設定が書き込まれ、実際のパッケージはnode_modules配下に追加されました。

これをnodejs.zipに圧縮します。構成は以下となるように注意します。(この構成でないとLambdaは正しく読み込んでくれません。)

nodejs.zip
 └ nodejs
   ├ node_modules
   │ └ ...
   ├ package-lock.json
   └ package.json

作成出来たら、続いてweb-pushパッケージが使えるようにレイヤーを設定します。

左の「レイヤー > レイヤーの作成」をクリックします。

レイヤーの作成画面にて好きな名前をつけ、先ほど作成したnodejs.zipをアップロードします。互換性のあるアーキテクチャはx86_64を選択します。

作成出来たら、バージョンARNを記録しておきます。

Lambdaに戻ってレイヤーを設定します。ページ下の「レイヤー > レイヤーの追加」をクリックします。

レイヤー追加の画面から、先程のレイヤーのバージョンARNを指定し、「追加」をクリックします。

これで web-pushパッケージが使えるようになりました。

Lambdaのコードを設定

Lambdaのコードは以下とします。コード上の公開鍵と秘密鍵は、先程生成したもので差し替えてください。

import webpush from 'web-push';
webpush.setVapidDetails(
    'mailto:test@example.com', // mailto:~~ の形である必要がある
    '<生成した公開鍵>',
    '<生成した秘密鍵>'
);
import aws from 'aws-sdk'
const dynamo = new aws.DynamoDB();

function getDynamoItem(){
    return new Promise((resolve, reject) => {
        const params = {
            TableName: 'web-push-notification',
            Key: {
                'id': {S: 'web-test'} // id='web-test'のもののみ対象
            }
        };
        dynamo.getItem(params,function(err, data) {
            if (err) {
                reject(err);
            } else {
                resolve(data.Item);
            }
        });
    });
}

export const handler = async (event) => {
    const item = await getDynamoItem()
  
    const title = "リモートWebPush通知テスト!";
    const text = "LambdaからのWebPush通知テストです!";
    const pushSubscription = {
      endpoint: item.endpoint.S,
      keys: {
        auth: item.auth.S,
        p256dh: item.p256dh.S
      }
    };
    await webpush.sendNotification(pushSubscription, JSON.stringify({ title, text }));
};

ここまで出来たら準備完了です。

自分の端末に届くかテスト

DynamoDBのコード上から「Deploy」をクリックした後、「Test」をクリックします。以下のようにiOS端末の方にWebプッシュ通知が来ると思います!

手順は多かったですが、これでWebプッシュ通知を一から実装する方法が学べました!

終わりに

今回の記事では、新しく使えるようになったiOS16.4でのWebプッシュ通知を実際に試してみました。伝えたい情報をより強調してお届けできるため、今後のサービス開発に盛り込んで「ユーザーにいかに想起してもらうか」といった課題の解決に役立てたいと思いました。

別記事の「iOS16.4からiOSのSafariでもWebプッシュが受け取れる様になったので試してみた!」と比較すると手間が多く、アーキテクチャ全体を考えなければならないのがデメリットに感じました。ですが、Node.jsが動作すれば良いため、普段から利用しているサーバにも導入しやすく、Firebaseの導入に対して、「現状学習できる時間がない」・「要件として既存の環境でWebプッシュ通知を実現する必要がある」などといった場合でも対応できるのがこの方法のメリットだと思います。

まだ色々な制約やPWAの浸透率の低さもあり、Webプッシュ通知の活用自体できる場面は少ないかもしれませんが、将来的に注目を浴びる技術だと私は思っています。

この記事を読んでいただいた方も、ぜひ手を動かしながら実際に新たなスマホアプリの可能性となり得るWebプッシュ通知をお試しいただければと思います!

前編:iOS16.4からiOSのSafariでもWebプッシュが受け取れるようになったのでAWSの機能でも試してみた! – 前編 (ローカルでのプッシュ通知とPWA化)

この記事をシェア

掲載内容は、記事執筆時点の情報をもとにしています。