LoginSignup
22
23

More than 3 years have passed since last update.

AWS Amplify の ServiceWorker で Web Push 対応を実装してみる - クライアント編

Last updated at Posted at 2019-12-09

Web Push、送りたいことありますよね。

この記事は Web Push 通知を使うアプリを AWS Amplify で作ってみる話です。
特にクライアント側を AWS Amplify でどう実装するかを見てみます。
当初サーバー側もあわせて書こうとしたのですが、分量が多くなりそうだったので今回は一旦クライアント編として、サーバー編はまた Amplify の API カテゴリを絡めたものを後日書きます(たぶん)。

Web Push やりたいときにやらなきゃいけないこと

Web Push を運用しようとすると、けっこう色々やることがあります。

  1. Push を受け取るクライアント側
    1. Service Worker の実装
    2. 購読状態管理、Service Worker 登録等を行うフロントアプリケーションの実装
    3. 購読処理時に払い出されるエンドポイントをサーバーサイドに送信
  2. Push を送るサーバー側
    1. 秘密鍵・公開鍵の生成、管理
    2. クライアントから送られてきたエンドポイント情報をサービスのユーザーと紐付けて保存、管理
    3. Web Push 送信用ライブラリ を使って適切なタイミングで送信

等々です。Amplify の ServiceWorker クラスはこの中で、特に 1-2. 購読状態管理、Service Worker 登録等を行うフロントアプリケーションの実装 を助けてくれます。
また、その Amplify プロジェクトで Analytics カテゴリを有効にしている場合、ServiceWorker のライフサイクルイベントを自動で収集、可視化してくれる機能も Amplify が提供してくれます。

前提条件

仕様

今回は、

  • ブロック済でなければページを開いたときにプッシュ通知購読の許可を求める
  • 購読の停止、停止後の再購読を行う UI、機能は提供しない ※
  • 現在の購読状況(未購読、購読済み、ブロック済み)を表示する
  • Push 通知がサポートされていない環境(Safari 等)ではそう表示する
  • 購読済みの場合は、プッシュ通知に必要なサブスクリプション情報を表示する

というモノを実装することにします。

※ 実際のサービス運用時には、購読停止の際に Unsubscribe() の実行、サーバーサイドのデータベース更新等が必要になるでしょう

参考にした資料

この記事は以下2つのドキュメント・チュートリアルを参考にしています。

  1. Service Workers - Amplify JavaScript
  2. ウェブアプリへのプッシュ通知の追加 - developers.google.com

この記事内では AWS Amplify が関わる Web Push の登録・受信部分のみに触れますが、実際のアプリケーションではユーザーの体験を考える上でどんなときにどう購読 ON/OFF の UI を表示すべきかなど、もう少し考えることがあります。
上の 2. のチュートリアルはそういう面にも触れられているのでぜひご覧ください。

作業環境

今回は Vue project をベースにします。


$ node -v
v12.13.1
$ npm -v
6.9.0
$ npm install -g @vue/cli
...
$ vue --version
@vue/cli 4.1.1
$ npm install -g @aws-amplify/cli
...
$ amplify --version
4.5.0

使った環境、ブラウザは↓です。なお、Safari は現在 Push 通知に対応していません。
* macOS High Sierra 10.13.6
* Google Chrome ver.78
* Firefox ver.68.3

実装していき

Vue プロジェクト、Amplify プロジェクトの作成

$ vue create amplifywebpush
? Please pick a preset: (Use arrow keys)
❯ default (babel, eslint)

...

$ cd amplifywebpush
$ npm install aws-amplify aws-amplify-vue
$ amplify init
? Enter a name for the project amplifywebpush
? Enter a name for the environment dev
? Choose your default editor: Vim (via Terminal, Mac OS only)
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using vue
? Source Directory Path:  src
? Distribution Directory Path: dist
? Build Command:  npm run-script build
? Start Command: npm run-script serve
? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use yourprofilename
...

$ tree -L 1
.
├── README.md
├── amplify
├── babel.config.js
├── node_modules
├── package-lock.json
├── package.json
├── public
├── src
└── yarn.lock

4 directories, 5 files

はい

必要な Amplify のモジュール、カテゴリの追加

まず、ServiceWorker クラスは @aws-amplify/core パッケージに、Analytics は aws-amplify/analytics に入っているのでインストールします。

$ npm install @aws-amplify/core
$ npm install @aws-amplify/analytics

次に、Service Worker のライフタイムイベントを集計するため、アプリケーションに Analytics カテゴリを追加します。途中の選択肢は全てデフォルトで Enter を押しています。

$ amplify add analytics
$ amplify push
✔ Successfully pulled backend environment dev from the cloud.

Current Environment: dev

| Category  | Resource name   | Operation | Provider plugin   |
| --------- | --------------- | --------- | ----------------- |
| Auth      | cognito57ed5053 | Create    | awscloudformation |
| Analytics | amplifywebpush  | Create    | awscloudformation |
? Are you sure you want to continue? (Y/n) Yes

...

$ amplify status

src/main.js で Amplify を初期化します。

import Vue from 'vue';
import App from './App.vue';
import awsconfig from './aws-exports';
import Amplify, * as AmplifyModules from 'aws-amplify';
import { AmplifyPlugin } from 'aws-amplify-vue';

Amplify.configure(awsconfig);
Vue.use(AmplifyPlugin, AmplifyModules);
Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount('#app');

この時点では何も UI をいじっていないので、デフォルトの Vue プロジェクトの画面が表示されます。

$ amplify run

...

  App running at:
  - Local:   http://localhost:8080/
  - Network: http://192.168.0.1:8080/

  Note that the development build is not optimized.
  To create a production build, run yarn build.

vue.png

はい

ServiceWorker の実装

まず、public/service-worker.js を次のように実装してみます。

/**
 * Push 通知の受信時に発火するイベント
 */
addEventListener('push', (event) => {
  console.log('[Service Worker] Push Received.');
  console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);

  if (!(self.Notification && self.Notification.permission === 'granted'))
    return;

  let data = event.data ? event.data.json() : {};

  let title = data.title || "Web Push Notification";
  let message = data.message || "New Push Notification Received";
  let icon = "path/to/image";
  let badge = "path/to/image";
  let options = {
    body: message,
    icon: icon,
    badge: badge
  };
  event.waitUntil(self.registration.showNotification(title, options));
});

/**
 * 通知がクリックされたときに発火するイベント
 * 通知ごとに適切なクリック時の処理を記述。
 * 今回は Amplify JavaScript のドキュメントを開くという処理にする。
 */
addEventListener('notificationclick', (event) => {
  console.log('[Service Worker] Notification click: ', event);
  event.notification.close();
  event.waitUntil(
    clients.openWindow('https://aws-amplify.github.io/amplify-js')
  );
});

これはかなり最小限の実装ですが、色々な API やキャッシュ機能を使った例としては Example Service Worker - Amplify JavaScriptウェブアプリへのプッシュ通知の追加 - developers.google.com を参照するといいと思います。

ServiceWorker の登録

で、src/App.vue を次のように実装します。

<template>
  <div>
    <h2>Amplifyで作るWebPushアプリ</h2>
    <p>{{ state }}</p>
    <p>{{ endpointInfo }}</p>
  </div>
</template>

<script>
import { ServiceWorker } from 'aws-amplify';

const serviceWorker = new ServiceWorker();
const yourPublicKey = 'Paste your public key here';

export default {
  name: 'app',
  data(){
    return {
      registeredServiceWorker: null,
      state: '',
      endpointInfo: '',
    }
  },
  methods :{
    isPushSupported() {
      return ('serviceWorker' in navigator && 'PushManager' in window)
    },
    async updateUI() {
      if (!this.isPushSupported()) {
        this.state = 'Push 通知がサポートされていない環境';
        this.endpointInfo = '';
        return;
      }

      // 購読状況によって UI を変える
      if (Notification.permission == 'denied') {
        // すでに拒否されている
        this.state = 'ブロック済';
        this.endpointInfo = '';
      } else {
        var subscription = await this.registeredServiceWorker.pushManager.getSubscription();
        if (subscription) {
          // 購読済み
          this.state = '購読済';
          this.endpointInfo = JSON.stringify(subscription);
        } else {
          // 未購読
          this.state = '未購読';
          this.endpointInfo = '';
        }
      }
    },
  },
  async mounted(){
    this.registeredServiceWorker = await serviceWorker.register('/service-worker.js', '/');

    if ('permissions' in navigator) {
      let notificationPermission = await navigator.permissions.query({name:'notifications'});
      notificationPermission.onchange = () => {
        this.updateUI();
      };
    }

    if (Notification.permission !== 'denied') {
      await serviceWorker.enablePush(yourPublicKey);
    }
    this.updateUI();
  },
}
</script>

serviceWorker.enablePush(yourPublicKey) は Push 通知の購読をユーザーに確認し、許可されたら購読処理をすすめるメソッドです。サーバー側で生成する公開鍵を引数に渡しています。この値はあとで置き換えます。

これらの行は、割と Amplify が処理を隠蔽してくれているところで、例えば公開鍵は URL エンコードされた Base64 文字列を UInt8Array に変換してから使う必要がありますが、それは enablePush() メソッドが内包しています。
参考: 該当部分の実装 - github.com/aws-amplify/amplify-js

この状態で、amplify run してから Chrome や Firefox でページを開くと、おなじみ?の通知許可が求められます。 serviceWorker.enablePush(yourPublicKey) による動作です。

image.png

公開鍵の取得と模擬的なサーバーサイドの用意

冒頭で書いたとおり、今回はフロント側について書きたいので、バックエンドは手軽に鍵生成や Push 送信のテストができるコンパニオンサイト、Push Companionを使います。

Push Companionを開くと、Public KeyとPrivate Keyが表示されています。

image.png

Public Key をコピーして、src/App.vue に反映します。

[-]
const yourPublicKey = 'Paste your public key here';

[+]
const yourPublicKey = 'BDirXoCByCLittjLnybgMtlmAl1Oc52zE--QIgU378Z7ljkoiWDjy2F*****************************';

アプリケーションを起動して購読してみる

$ amplify run

...

  App running at:
  - Local:   http://localhost:8080/
  - Network: http://192.168.10.13:8080/

ブラウザで http://localhost:8080/ を開き、通知許可のダイアログを表示します。

image.png

ここで許可(Allow)すると、画面上に JSON 文字列が表示されます。

image.png

この JSON 文字列は Subscription 情報などと呼ばれ、サーバーサイドから Push を送信するときの宛先情報にあたるものです。

実際のサービスでは、この後 subscriptionInfo をサーバーサイドに送り、ユーザー情報と紐付けてデータベースに保存しておくなどして、必要なときに必要なユーザーに Push を送信することができるよう管理する必要があるでしょう。

Push を送信、受信してみる

では、いよいよ Push 通知を受信してみます。またコンパニオンサイトに頼ります。

"Subscription to Send To" に、先程の SubscriptionInfo 文字列をコピーして、"Text to Send" に好きなメッセージを書いて "SEND PUSH MESSAGE" をクリックします。単なる文字列を送ってもいいのですが、通常 JSON で送ることが一般的で、今回の Service Worker 側でもそれを期待しているので、次の内容を送ってみます。

{"title": "Hello Amplify!!!", "message": "Amplifyかわいい"}


image.png

うまく実装されていれば、次のような通知が表示されます。

image.png

Vue アプリケーションのタブを閉じていたり、別 Window を表示したりしていても通知が表示されるでしょうか?通知をクリックしたとき、ちゃんと service-worker.js で記述した処理が実行されているでしょうか?確認してみてください。

期待通りに動いていれば、これで Amplify を使ったクライアントサイドの Web Push 通知対応ができました。

Analytics の集計情報を見てみる

最初に、Service worker のイベントを集計するために Analytics カテゴリを追加してありました。ちゃんと動いているか見てみましょう。

$ amplify console analytics

Amazon Pinpoint のマネジメントコンソールが開いたら、 Analytics > Events を見てみます。
Filters を有効にすると、次のようなイベントが収集されています。

image.png

Amplify が自動的に Service worker のライフサイクルや状態変化、メッセージングなどのイベントを集計してくれています。

補足

開発中に購読状態を変更したいとき

開発中は、一度購読を許可した後にもう一度もとに戻したい、拒否を取り消したいときがたくさんあると思います。
Chrome のアドレスバーのアイコンをクリックすると、そのサイトに対する購読状態を変更することができます。

image.png

サーバーサイドの Push 送信実装について

今回はコンパニオンサイトに頼りましたが、実際にサーバーサイドからの Push 送信を自分で実装するときは Web Push 用のライブラリを使って実装することが一般的かと思います。

言語別のライブラリがあるので、必要のある方は見てみてください。

その辺の実装は、後日書くサーバー編で触れたいと思います。

その他考慮すること

前提条件にも書きましたが、今回の記事はクライアント側の Amplify に関わる部分にフォーカスしています。
サービスを運用する際は、これら以外に購読済みのユーザーが購読を停止するための UI や処理、ユーザーの Subscription 情報や状況を保存するバックエンドのデータベース等が必要になると思われます。
その辺も後日書くサーバー編に(ry

また、Appiterate による調べでは、不適切で不快な通知はユーザー離反の最も大きな要因になり得ます。ユーザー体験を十分に設計する必要があります。

image.png
[AWS Start-up ゼミ] よくある課題を一気に解説! 御社の技術レベルがアップする 2019 春期講習

まとめ

  • Amplify の ServiceWorker クラスは Web Push 購読処理を隠蔽して、ちょっと手軽にしてくれる
  • Amplify で Analytics カテゴリを有効にしていると、自動的に Service worker の挙動をトラッキングしてくれる

みんなも Amplify で Web Push 処理しましょう。

22
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
23