ServiceWorker の WebPush について


ServiceWorker の WebPush について、調べたことを学習のまとめとして記載します。


過去に実装したことをまとめた記事

前に、django-push-notifications で、WebPush 通知を登録のみ実装しました。


ServiceWorker の WebPush で通知を送るメリット

結構前から、ブラウザ上に通知を送るライブラリ、サービス等はありますが、ServiceWorkerから通知を送るメリットは、自サイトから離れた状態のユーザーにも、通知が送れることです。ブラウザとして全ての画面を閉じても起動さえしていれば、ServiceWorker が稼働しているため、通知を送付することができます。Webサイトと、ユーザーのコミニュケーション手段として、メール、RSS、電話、チャット等ありますが、その選択肢が1つ増えます。


WebPush サービスについて

自前でサーバーを建てるか、SaaSを使うかの選択肢があります。
以下、読んでためになった記事と、github repository へのリンクになります。

個人的に、使用するのであれば OneSignal が今のところ完全に無料なのと、Marketo 等の MA ツールとの連携ができたり、チャネルに対してメッセージを送信するバックオフィス機能があり実用性高そうに思いました。1


WebPush 通知のライフサイクル

WebPsuh 登録から考慮すること

VAPID を使う方式を試しました。

  1. WebPushの許可を求める
    Web Notifications API を使用して、通知の許可をユーザーに求めます。
    末許可の状態で、購読処理を行うと例外が発生します。
    許可を求めるタイミングは重要で、ページ読み込み時に表示しているサイト等もありますが、ユーザーの迷惑になる可能性があり、Lighthouse で警告が出力されます。以下、表示タイミングについて書かれた記事へのリンクです。

  2. WebPushの購読
    self.registration.pushManager.subscribe() で SerivceWorker 内でサブスクリプションを生成します。 生成したサブスクリプションを加工して、アプリケーションサーバーに送信します。

  3. WebPushの送信依頼
    アプリケーションサーバー、等 Push サーバーへの送信が可能なサーバからWebPush の送信を Push サーバーへ依頼します。
    言語的な縛りは特にないかと思います。個人的にはここは python が担っています。

  4. Pushサーバからクライアントへの送信
    Pushサーバーからクライアントへメッセージを送信します。
    ここは、FCM を使っているためブラックボックスです。

  5. クライアントで、メッセージを受信する
    ServiceWorker でメッセージを受信します。
    実装はなにかを参考にして、以下の実装にしました。

    // Push通知
    self.addEventListener('push', function(event) {
        let responseJson;
        let title;
        let message;
        let messageTag;
        try {
            // Push is a JSON
            responseJson = event.data.json();
            title = responseJson.title;
            message = responseJson.message;
            messageTag = responseJson.tag;
        } catch (err) {
            // Push is a simple text
            title = '';
            message = event.data.text();
            messageTag = '';
        }
        self.registration.showNotification(getTitle(title), getNotificationOptions(message, messageTag));
        // Optional: Comunicating with our js application. Send a signal
        self.clients.matchAll({includeUncontrolled: true, type: 'window'}).then(function(clients) {
            clients.forEach(function(client) {
                client.postMessage({
                    'data': messageTag,
                    'data_title': title,
                    'data_body': message,
                });
            });
        });
    });
    

  6. 通知の表示
    これはブラウザの仕事です。

  7. 通知のボタンクリック後のアクション
    ここも何かのサンプルそのままです。
    ServiceWorker でクライアントでメッセージ表示後の ボタンアクションを定義できます。

    // 通知クリック時の動作を定義
    self.addEventListener('notificationclick', function(event) {
        // Android doesn't close the notification when you click it
        // See http://crbug.com/463146
        event.notification.close();
        // Check if there's already a tab open with this URL.
        // If yes: focus on the tab.
        // If no: open a tab with the URL.
        event.waitUntil(self.clients.matchAll({type: 'window', includeUncontrolled: true}).then(function(windowClients) {
                for (let i = 0; i < windowClients.length; i++) {
                    let client = windowClients[i];
                    if ('focus' in client) {
                        return client.focus();
                    }
                }
            })
        );
    });
    

登録からの流れは以上です。


WebPush サブスクリプションの期限切れ

サブスクリプション には期限があり期限が切れます。
VAPID で且つ、Chrome の場合、現在期限が切れないようですが、他のブラウザについては切れる可能性があるようです。
サンプルが見つけられず、実際に発生するのか検証できていないので、他のイベントで発生時のFallbackをしつつ、pushsubscriptionchange が発火した際に、検知できるように GoogleAnalytics で event の送信を行うことを考えましたが、ライブラリは見つけたものの、実装方法がわからずでした。

importScripts('path/to/offline-google-analytics-import.js');
workbox.googleAnalytics.initialize();
self.addEventListener('pushsubscriptionchange', function() {
    // Offline GA 送信 をするコード   
    // 実装しようと思いましたが、実装サンプルがなく実装方法がわかりませんでした。     
    // オフラインでなければ、postMessage Window側にメッセージを返して、そこからga送信でもいいかもしれません。      
});

少なくとも、IEでは期限が切れる可能性があるので、期限切れ更新通知は実装しておいたほうがよさそうに思います。


WebPush サブスクリプションの解除

self.registration.pushManager.unsubscribe() で SerivceWorker でサブスクリプションの解除が行えます。
正直、WebPush の受け取りますか? は尋ねられるサイトはありますが、購読解除はあまり見かけない気がします。
自分で削除できるので、あまりうざいと思う人は、個人で削除はすると思いますが、マナーとしては実装しておくべきだと思います。
Blogだったら Aboutページとか記事単位で WebPush の解除できたらいいのでしょうか。


作成中のWebpush実装

作りかけですが、随時以下にUPしています。
mezzanine-theme-clean-blog/clean_blog_frontend/src/js at master · kemsakurai/mezzanine-theme-clean-blog

購読しますかは聞きますが、Push サーバーへの送信機能はなく、解除機能はない状態ですね....

以上です。


  1. 個人の実装は、OneSignal の存在を知らずに、FCM ベースで作成を行いました。切り替えが簡単なのであれば OneSignal に変えてしまおうかと考えています。 

コメント