Web Push、送りたいことありますよね。
この記事は Web Push 通知を使うアプリを AWS Amplify で作ってみる話です。
特にクライアント側を AWS Amplify でどう実装するかを見てみます。
当初サーバー側もあわせて書こうとしたのですが、分量が多くなりそうだったので今回は一旦クライアント編として、サーバー編はまた Amplify の API カテゴリを絡めたものを後日書きます(たぶん)。
Web Push やりたいときにやらなきゃいけないこと
Web Push を運用しようとすると、けっこう色々やることがあります。
- Push を受け取るクライアント側
- Service Worker の実装
- 購読状態管理、Service Worker 登録等を行うフロントアプリケーションの実装
- 購読処理時に払い出されるエンドポイントをサーバーサイドに送信
- Push を送るサーバー側
- 秘密鍵・公開鍵の生成、管理
- クライアントから送られてきたエンドポイント情報をサービスのユーザーと紐付けて保存、管理
- Web Push 送信用ライブラリ を使って適切なタイミングで送信
等々です。Amplify の ServiceWorker クラスはこの中で、特に 1-2. 購読状態管理、Service Worker 登録等を行うフロントアプリケーションの実装 を助けてくれます。
また、その Amplify プロジェクトで Analytics カテゴリを有効にしている場合、ServiceWorker のライフサイクルイベントを自動で収集、可視化してくれる機能も Amplify が提供してくれます。
前提条件
仕様
今回は、
- ブロック済でなければページを開いたときにプッシュ通知購読の許可を求める
- 購読の停止、停止後の再購読を行う UI、機能は提供しない ※
- 現在の購読状況(未購読、購読済み、ブロック済み)を表示する
- Push 通知がサポートされていない環境(Safari 等)ではそう表示する
- 購読済みの場合は、プッシュ通知に必要なサブスクリプション情報を表示する
というモノを実装することにします。
※ 実際のサービス運用時には、購読停止の際に Unsubscribe() の実行、サーバーサイドのデータベース更新等が必要になるでしょう
参考にした資料
この記事は以下2つのドキュメント・チュートリアルを参考にしています。
この記事内では 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.
はい
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)
による動作です。
公開鍵の取得と模擬的なサーバーサイドの用意
冒頭で書いたとおり、今回はフロント側について書きたいので、バックエンドは手軽に鍵生成や Push 送信のテストができるコンパニオンサイト、Push Companionを使います。
Push Companionを開くと、Public KeyとPrivate Keyが表示されています。
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/
を開き、通知許可のダイアログを表示します。
ここで許可(Allow)すると、画面上に JSON 文字列が表示されます。
この 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かわいい"}
うまく実装されていれば、次のような通知が表示されます。
Vue アプリケーションのタブを閉じていたり、別 Window を表示したりしていても通知が表示されるでしょうか?通知をクリックしたとき、ちゃんと service-worker.js
で記述した処理が実行されているでしょうか?確認してみてください。
期待通りに動いていれば、これで Amplify を使ったクライアントサイドの Web Push 通知対応ができました。
Analytics の集計情報を見てみる
最初に、Service worker のイベントを集計するために Analytics カテゴリを追加してありました。ちゃんと動いているか見てみましょう。
$ amplify console analytics
Amazon Pinpoint のマネジメントコンソールが開いたら、 Analytics > Events を見てみます。
Filters を有効にすると、次のようなイベントが収集されています。
Amplify が自動的に Service worker のライフサイクルや状態変化、メッセージングなどのイベントを集計してくれています。
補足
開発中に購読状態を変更したいとき
開発中は、一度購読を許可した後にもう一度もとに戻したい、拒否を取り消したいときがたくさんあると思います。
Chrome のアドレスバーのアイコンをクリックすると、そのサイトに対する購読状態を変更することができます。
サーバーサイドの Push 送信実装について
今回はコンパニオンサイトに頼りましたが、実際にサーバーサイドからの Push 送信を自分で実装するときは Web Push 用のライブラリを使って実装することが一般的かと思います。
言語別のライブラリがあるので、必要のある方は見てみてください。
その辺の実装は、後日書くサーバー編で触れたいと思います。
その他考慮すること
前提条件にも書きましたが、今回の記事はクライアント側の Amplify に関わる部分にフォーカスしています。
サービスを運用する際は、これら以外に購読済みのユーザーが購読を停止するための UI や処理、ユーザーの Subscription 情報や状況を保存するバックエンドのデータベース等が必要になると思われます。
その辺も後日書くサーバー編に(ry
また、Appiterate による調べでは、不適切で不快な通知はユーザー離反の最も大きな要因になり得ます。ユーザー体験を十分に設計する必要があります。
[AWS Start-up ゼミ] よくある課題を一気に解説! 御社の技術レベルがアップする 2019 春期講習
まとめ
- Amplify の
ServiceWorker
クラスは Web Push 購読処理を隠蔽して、ちょっと手軽にしてくれる - Amplify で
Analytics
カテゴリを有効にしていると、自動的に Service worker の挙動をトラッキングしてくれる
みんなも Amplify で Web Push 処理しましょう。