OpenWeb でのプッシュ通知

デベロッパーを集めてモバイル端末の機能にあってウェブにはないものは何かと質問すると、プッシュ通知は常に上位にあげられます。

プッシュ通知を使用すると、ユーザーは、お気に入りのサイトから最新情報を得られるようになります。一方、デベロッパー側は、関連性のあるコンテンツをカスタマイズして、効率的にユーザーのリピート率を高めることができます。

Chrome バージョン 42 以降では、Push APINotification API をデベロッパーが使用できます。

Chrome の Push API は、ウェブアプリ マニフェストService Worker など、いくつかの異なるテクノロジーに依存しています。この投稿では、これらのテクノロジーのそれぞれについて説明しますが、プッシュ メッセージを運用するために必要な最小限のもののみを説明します。マニフェストのその他の機能や、サービス ワーカーのオフライン機能について詳しくは、上記のリンクをご覧ください。

また、今後の Chrome のバージョンで API に追加される機能についても説明します。最後に、よくある質問を紹介します。

Chrome 用のプッシュ メッセージの実装

このセクションでは、ウェブアプリでプッシュ メッセージをサポートするために必要な手順について説明します。

Service Worker の登録

ウェブ用の push メッセージを実装するための Service Worker の依存関係があります。これは、push メッセージを受信すると、ブラウザはページを開かずにバックグラウンドで実行される Service Worker を起動し、イベントをディスパッチできるため、その push メッセージの処理方法を決定できるからです。

ウェブアプリにサービス ワーカーを登録する方法の例を以下に示します。登録が正常に完了すると、initialiseState() が呼び出されます。この関数については後で説明します。

var isPushEnabled = false;



window.addEventListener('load', function() {
    var pushButton = document.querySelector('.js-push-button');
    pushButton.addEventListener('click', function() {
    if (isPushEnabled) {
        unsubscribe();
    } else {
        subscribe();
    }
    });

    // Check that service workers are supported, if so, progressively
    // enhance and add push messaging support, otherwise continue without it.
    if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/service-worker.js')
    .then(initialiseState);
    } else {
    console.warn('Service workers aren\'t supported in this browser.');
    }
});

ボタンのクリック ハンドラは、プッシュ メッセージの登録または登録解除を行います。isPushEnabled は、プッシュ メッセージが現在登録されているかどうかを単に追跡するグローバル変数です。これらはコード スニペット全体で参照されます。

次に、Service Worker がサポートされていることを確認してから、プッシュ メッセージを処理するロジックを含む service-worker.js ファイルを登録します。ここでは、この JavaScript ファイルがサイトの Service Worker であることをブラウザに伝えています。

初期状態をセットアップする

Chrome でプッシュ メッセージの有効化と無効化を切り替えた場合の UX の例。

Service Worker を登録したら、UI の状態を設定する必要があります。

ユーザーは、サイトのプッシュ メッセージを有効または無効にするためのシンプルな UI を期待しています。また、変更が発生した場合は、その UI が最新の状態に保たれることを期待しています。つまり、ユーザーがサイトのプッシュ メッセージを有効にして、1 週間後にサイトに戻ってきた場合、プッシュ メッセージがすでに有効になっていることが UI にハイライト表示されます。

UX ガイドラインの一部については、こちらのドキュメントをご覧ください。この記事では、技術的な側面に焦点を当てます。

この時点では、有効または無効の 2 つの状態しか扱えないと思われるかもしれません。ただし、通知に関して考慮すべき他の状態もいくつかあります。

Chrome でのプッシュのさまざまな考慮事項とステータスを示す図

ボタンを有効にする前に確認する必要がある API がいくつかあります。すべてがサポートされている場合は、UI を有効にして、プッシュ メッセージが登録されているかどうかを示す初期状態を設定できます。

これらのチェックのほとんどは UI を無効にするため、初期状態を無効にする必要があります。また、JS ファイルをダウンロードできない、ユーザーが JavaScript を無効にしているなど、ページの JavaScript に問題が発生した場合の混乱も回避できます。

<button class="js-push-button" disabled>
    Enable Push Messages
</button>

この初期状態では、Service Worker の登録後に、上記のチェックを initialiseState() メソッドで実行できます。

// Once the service worker is registered set the initial state
function initialiseState() {
    // Are Notifications supported in the service worker?
    if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
    console.warn('Notifications aren\'t supported.');
    return;
    }

    // Check the current Notification permission.
    // If its denied, it's a permanent block until the
    // user changes the permission
    if (Notification.permission === 'denied') {
    console.warn('The user has blocked notifications.');
    return;
    }

    // Check if push messaging is supported
    if (!('PushManager' in window)) {
    console.warn('Push messaging isn\'t supported.');
    return;
    }

    // We need the service worker registration to check for a subscription
    navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
    // Do we already have a push message subscription?
    serviceWorkerRegistration.pushManager.getSubscription()
        .then(function(subscription) {
        // Enable any UI which subscribes / unsubscribes from
        // push messages.
        var pushButton = document.querySelector('.js-push-button');
        pushButton.disabled = false;

        if (!subscription) {
            // We aren't subscribed to push, so set UI
            // to allow the user to enable push
            return;
        }

        // Keep your server in sync with the latest subscriptionId
        sendSubscriptionToServer(subscription);

        // Set your UI to show they have subscribed for
        // push messages
        pushButton.textContent = 'Disable Push Messages';
        isPushEnabled = true;
        })
        .catch(function(err) {
        console.warn('Error during getSubscription()', err);
        });
    });
}

この手順の概要は次のとおりです。

  • ServiceWorkerRegistration プロトタイプで showNotification が使用可能であることを確認します。これがないと、プッシュ メッセージの受信時に Service Worker からの通知を表示できません。
  • 現在の Notification.permission"denied" でないことを確認します。権限が拒否された場合、ユーザーがブラウザで権限を手動で変更するまで、通知を表示できません。
  • プッシュ メッセージングがサポートされているかどうかを確認するには、PushManager が window オブジェクトで使用できることを確認します。
  • 最後に、pushManager.getSubscription() を使用して、すでに定期購入されているかどうかを確認します。有効になっている場合は、サブスクリプションの詳細をサーバーに送信して正しい情報があることを確認し、プッシュ メッセージがすでに有効になっているかどうかを示すように UI を設定します。サブスクリプション オブジェクトに存在する詳細情報については、この記事の後半で説明します。

サービス ワーカーがアクティブになって初めてプッシュ メッセージを実際にサブスクライブできるため、navigator.serviceWorker.ready が解決されるまで待機して、サブスクリプションを確認し、プッシュボタンを有効にします。

次のステップは、ユーザーがプッシュ メッセージを有効にしたい場合の処理です。この処理を行う前に、Google Developer Console プロジェクトを設定し、Firebase Cloud Messaging(FCM)(旧称 Google Cloud Messaging(GCM))を使用するようにマニフェストにパラメータを追加する必要があります。

Firebase Developer Console でプロジェクトを作成する

Chrome は FCM を使用してプッシュ メッセージの送信と配信を処理しますが、FCM API を使用するには、Firebase デベロッパー コンソールでプロジェクトを設定する必要があります。

以下の手順は、FCM を使用する Chrome、Android 版 Opera、Samsung ブラウザに固有の手順です。他のブラウザでこれがどのように機能するかについては、この記事の後半で説明します。

新しい Firebase デベロッパー プロジェクトを作成する

まず、https://console.firebase.google.com/ で [新しいプロジェクトを作成] をクリックして、新しいプロジェクトを作成する必要があります。

新しい Firebase プロジェクトのスクリーンショット

プロジェクト名を追加してプロジェクトを作成すると、プロジェクト ダッシュボードが表示されます。

Firebase プロジェクトのホーム

このダッシュボードで、左上のプロジェクト名の横にある歯車アイコンをクリックし、[プロジェクト設定] をクリックします。

Firebase プロジェクトの設定メニュー

設定ページで [Cloud Messaging] タブをクリックします。

Firebase プロジェクトの Cloud Messaging メニュー

このページには、後で使用するプッシュ メッセージの API キーと、次のセクションでウェブアプリ マニフェストに追加する必要がある送信者 ID が含まれています。

ウェブアプリ マニフェストを追加する

push サブスクリプションを成功させるには、gcm_sender_id フィールドを含むマニフェスト ファイルを追加する必要があります。このパラメータは、Chrome、Android 版 Opera、Samsung ブラウザでのみ、FCM / GCM を使用できるようにする必要があります。

gcm_sender_id は、FCM でユーザー デバイスをサブスクライブするときにこれらのブラウザによって使用されます。つまり、FCM はユーザーのデバイスを識別し、送信者 ID が対応する API キーと一致していること、ユーザーがサーバーにプッシュ メッセージを送信することを許可していることを確認できます。

非常にシンプルなマニフェスト ファイルを以下に示します。

{
    "name": "Push Demo",
    "short_name": "Push Demo",
    "icons": [{
        "src": "images/icon-192x192.png",
        "sizes": "192x192",
        "type": "image/png"
        }],
    "start_url": "/index.html?homescreen=1",
    "display": "standalone",
    "gcm_sender_id": "<Your Sender ID Here>"
}

gcm_sender_id の値を Firebase プロジェクトの送信者 ID に設定する必要があります。

マニフェスト ファイルをプロジェクトに保存したら(manifest.json が適切な名前)、ページの先頭に次のタグを付けて HTML から参照します。

<link rel="manifest" href="/manifest.json">

これらのパラメータを含むウェブ マニフェストを追加しないと、ユーザーをプッシュ メッセージに登録しようとしたときに、エラー "Registration failed - no sender id provided" または "Registration failed - permission denied" を含む例外が発生します。

プッシュ メッセージングに登録する

マニフェストの設定が完了したので、サイトの JavaScript に戻ります。

定期購入するには、PushManager オブジェクトの subscribe() メソッドを呼び出す必要があります。このオブジェクトには、ServiceWorkerRegistration を介してアクセスします。

これにより、プッシュ通知を送信する権限をオリジンに付与するようユーザーに求めるメッセージが表示されます。この権限がないと、正常に登録できません。

subscribe() メソッドによって返された Promise が解決すると、エンドポイントを含む PushSubscription オブジェクトが返されます。

後でプッシュ メッセージを送信するために、エンドポイントはユーザーごとにサーバーに保存する必要があります。

次のコードは、ユーザーをプッシュ メッセージに登録します。

function subscribe() {
    // Disable the button so it can't be changed while
    // we process the permission request
    var pushButton = document.querySelector('.js-push-button');
    pushButton.disabled = true;

    navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
    serviceWorkerRegistration.pushManager.subscribe()
        .then(function(subscription) {
        // The subscription was successful
        isPushEnabled = true;
        pushButton.textContent = 'Disable Push Messages';
        pushButton.disabled = false;

        // TODO: Send the subscription.endpoint to your server
        // and save it to send a push message at a later date
        return sendSubscriptionToServer(subscription);
        })
        .catch(function(e) {
        if (Notification.permission === 'denied') {
            // The user denied the notification permission which
            // means we failed to subscribe and the user will need
            // to manually change the notification permission to
            // subscribe to push messages
            console.warn('Permission for Notifications was denied');
            pushButton.disabled = true;
        } else {
            // A problem occurred with the subscription; common reasons
            // include network errors, and lacking gcm_sender_id and/or
            // gcm_user_visible_only in the manifest.
            console.error('Unable to subscribe to push.', e);
            pushButton.disabled = false;
            pushButton.textContent = 'Enable Push Messages';
        }
        });
    });
}

この時点で、ウェブアプリはプッシュ メッセージを受信する準備ができていますが、Service Worker ファイルにプッシュ イベント リスナーを追加するまで何も起こりません。

Service Worker プッシュ イベント リスナー

push メッセージを受信すると(実際に push メッセージを送信する方法については次のセクションで説明します)、Service Worker で push イベントがディスパッチされます。そこで、通知を表示する必要があります。

self.addEventListener('push', function(event) {
    console.log('Received a push message', event);

    var title = 'Yay a message.';
    var body = 'We have received a push message.';
    var icon = '/images/icon-192x192.png';
    var tag = 'simple-push-demo-notification-tag';

    event.waitUntil(
    self.registration.showNotification(title, {
        body: body,
        icon: icon,
        tag: tag
    })
    );
});

このコードは、プッシュ イベント リスナーを登録し、事前定義されたタイトル、本文、アイコン、通知タグで通知を表示します。この例で注目すべき微妙な点は、event.waitUntil() メソッドです。このメソッドは Promise を受け取り、Promise が解決されるまでイベント ハンドラの有効期限を延長します(または、Service Worker を存続させていると考えることもできます)。この場合、event.waitUntil に渡される Promise は、showNotification() から返された Promise です。

通知タグは、一意の通知の識別子として機能します。同じエンドポイントに 2 つのプッシュ メッセージを短い間隔で送信し、同じタグで通知を表示すると、ブラウザは最初の通知を表示し、プッシュ メッセージが受信されると 2 番目の通知に置き換えます。

複数の通知を一度に表示する場合は、別のタグを使用するか、タグを使用しないようにします。通知を表示するより完全な例については、この投稿の後半で説明します。現時点では、シンプルにするため、プッシュ メッセージを送信してこの通知が表示されるかどうかを確認します。

プッシュ メッセージの送信

プッシュ メッセージをサブスクライブし、Service Worker が通知を表示する準備が整ったので、FCM 経由でプッシュ メッセージを送信します。

これは、FCM を使用しているブラウザにのみ適用されます。

PushSubscription.endpoint 変数をサーバーに送信する場合、FCM のエンドポイントは特別なものです。URL の末尾に registration_id というパラメータがあります。

エンドポイントの例を次に示します。

https://fcm.googleapis.com/fcm/send/APA91bHPffi8zclbIBDcToXN_LEpT6iA87pgR-J-MuuVVycM0SmptG-rXdCPKTM5pvKiHk2Ts-ukL1KV8exGOnurOAKdbvH9jcvg8h2gSi-zZJyToiiydjAJW6Fa9mE3_7vsNIgzF28KGspVmLUpMgYLBd1rxaVh-L4NDzD7HyTkhFOfwWiyVdKh__rEt15W9n2o6cZ8nxrP

FCM URL は次のとおりです。

https://fcm.googleapis.com/fcm/send

registration_id は次のようになります。

APA91bHPffi8zclbIBDcToXN_LEpT6iA87pgR-J-MuuVVycM0SmptG-rXdCPKTM5pvKiHk2Ts-ukL1KV8exGOnurOAKdbvH9jcvg8h2gSi-zZJyToiiydjAJW6Fa9mE3_7vsNIgzF28KGspVmLUpMgYLBd1rxaVh-L4NDzD7HyTkhFOfwWiyVdKh__rEt15W9n2o6cZ8nxrP

これは、FCM を使用するブラウザに固有のものです。通常のブラウザでは、エンドポイントを取得し、標準の方法でそのエンドポイントを呼び出すだけで、URL に関係なく機能します。

つまり、サーバー上で、エンドポイントが FCM 用かどうかを確認し、FCM 用の場合は registration_id を抽出する必要があります。Python でこれを行うには 次のようにすることができます

if endpoint.startswith('https://fcm.googleapis.com/fcm/send'):
    endpointParts = endpoint.split('/')
    registrationId = endpointParts[len(endpointParts) - 1]

    endpoint = 'https://fcm.googleapis.com/fcm/send'

登録 ID を取得したら、FCM API を呼び出すことができます。FCM API のリファレンス ドキュメントをご覧ください。

FCM を呼び出す際に留意すべき主な点は次のとおりです。

  • API を呼び出すときに、値が key=&lt;YOUR_API_KEY&gt;Authorization ヘッダーを設定する必要があります。ここで、&lt;YOUR_API_KEY&gt; は Firebase プロジェクトの API キーです。
    • API キーは、FCM で適切な送信者 ID を検索し、ユーザーからプロジェクトの権限を付与されていること、そして最後にサーバーの IP アドレスがそのプロジェクトの許可リストに登録されていることを確認するために使用されます。
  • データを JSON として送信するかフォームデータとして送信するかに応じて、適切な Content-Type ヘッダー(application/json または application/x-www-form-urlencoded;charset=UTF-8)。
  • registration_ids の配列 - ユーザーのエンドポイントから抽出する登録 ID です。

サーバーからプッシュ メッセージを送信する方法については、ドキュメントをご覧ください。Service Worker を簡単に確認するには、cURL を使用してブラウザにプッシュ メッセージを送信します。

この cURL コマンドの &lt;YOUR_API_KEY&gt;&lt;YOUR_REGISTRATION_ID&gt; を独自のものに置き換えて、ターミナルから実行します。

輝かしい通知が表示されます。

    curl --header "Authorization: key=<YOUR_API_KEY>" --header
    "Content-Type: application/json" https://fcm.googleapis.com/fcm/send -d
    "{\"registration_ids\":[\"<YOUR_REGISTRATION_ID>\"]}"
Android 版 Chrome からのプッシュ メッセージの例。

バックエンド ロジックを開発する際は、Authorization ヘッダーと POST 本文の形式が FCM エンドポイントに固有のものであることを覚えておいてください。そのため、エンドポイントが FCM の場合を検出し、条件付きでヘッダーを追加して、POST 本文をフォーマットします。他のブラウザ(将来的には Chrome も)では、ウェブプッシュ プロトコルを実装する必要があります。

Chrome に現在実装されている Push API の欠点は、プッシュ メッセージでデータを送信できないことです。いや、なんでもない。これは、今後の実装では、ペイロード データをプッシュ メッセージ エンドポイントに送信する前に、サーバーで暗号化する必要が生じるためです。これにより、どのプッシュ プロバイダでも、エンドポイントがプッシュ メッセージの内容を簡単に確認できなくなります。また、HTTPS 証明書の不十分な検証や、サーバー間とプッシュ プロバイダ間の中間者攻撃などの他の脆弱性からも保護されます。ただし、この暗号化はまだサポートされていないため、当面はフェッチを実行して通知の入力に必要な情報を取得する必要があります。

より完全なプッシュ イベントの例

ここまで見てきた通知は非常に基本的なものです。サンプルとしては、実際のユースケースをカバーするには不十分です。

現実的には、ほとんどのユーザーは通知を表示する前にサーバーの情報を取得したいと考えています。たとえば、通知のタイトルとメッセージに特定の情報を入力するデータや、さらに一歩進んで、一部のページやデータをキャッシュに保存し、ユーザーが通知をクリックしたときに、ブラウザが開かれた時点ですべてがすぐに利用できるようにするデータなどです。その時点でネットワークが利用できない場合でも同様です。

次のコードでは、API からデータを取得し、レスポンスをオブジェクトに変換して、通知に入力します。

self.addEventListener('push', function(event) {
    // Since there is no payload data with the first version
    // of push messages, we'll grab some data from
    // an API and use it to populate a notification
    event.waitUntil(
    fetch(SOME_API_ENDPOINT).then(function(response) {
        if (response.status !== 200) {
        // Either show a message to the user explaining the error
        // or enter a generic message and handle the
        // onnotificationclick event to direct the user to a web page
        console.log('Looks like there was a problem. Status Code: ' + response.status);
        throw new Error();
        }

        // Examine the text in the response
        return response.json().then(function(data) {
        if (data.error || !data.notification) {
            console.error('The API returned an error.', data.error);
            throw new Error();
        }

        var title = data.notification.title;
        var message = data.notification.message;
        var icon = data.notification.icon;
        var notificationTag = data.notification.tag;

        return self.registration.showNotification(title, {
            body: message,
            icon: icon,
            tag: notificationTag
        });
        });
    }).catch(function(err) {
        console.error('Unable to retrieve data', err);

        var title = 'An error occurred';
        var message = 'We were unable to get the information for this push message';
        var icon = URL_TO_DEFAULT_ICON;
        var notificationTag = 'notification-error';
        return self.registration.showNotification(title, {
            body: message,
            icon: icon,
            tag: notificationTag
        });
    })
    );
});

繰り返しになりますが、event.waitUntil() が Promise を受け取ると、結果として showNotification() によって Promise が返されます。つまり、非同期の fetch() 呼び出しが完了するまでイベント リスナーは終了せず、通知が表示されるということです。

エラーが発生している場合でも通知が表示されます。これは、設定しないと Chrome 独自の汎用通知が表示されるためです。

ユーザーが通知をクリックしたときに URL を開く

ユーザーが通知をクリックすると、Service Worker で notificationclick イベントがディスパッチされます。ハンドラ内では、タブをフォーカスする、特定の URL のウィンドウを開くなど、適切なアクションを実行できます。

self.addEventListener('notificationclick', function(event) {
    console.log('On notification click: ', event.notification.tag);
    // Android doesn't close the notification when you click on it
    // See: http://crbug.com/463146
    event.notification.close();

    // This looks to see if the current is already open and
    // focuses if it is
    event.waitUntil(
    clients.matchAll({
        type: "window"
    })
    .then(function(clientList) {
        for (var i = 0; i < clientList.length; i++) {
        var client = clientList[i];
        if (client.url == '/' && 'focus' in client)
            return client.focus();
        }
        if (clients.openWindow) {
        return clients.openWindow('/');
        }
    })
    );
});

この例では、既存の同じオリジンのタブが存在する場合はフォーカスし、存在しない場合は新しいタブを開いて、サイトのオリジンのルートにブラウザを開きます。

Notification API でできることに関する投稿もご覧ください。

ユーザーのデバイスの登録を解除する

ユーザーのデバイスを登録し、プッシュ メッセージを受信しているが、登録を解除するにはどうすればよいですか?

ユーザーのデバイスの登録を解除するために必要な主な処理は、PushSubscription オブジェクトで unsubscribe() メソッドを呼び出し、サーバーからエンドポイントを削除することです(受信されないことが明らかなプッシュ メッセージを送信しないようにするためです)。次のコードはまさにこれを行います。

function unsubscribe() {
    var pushButton = document.querySelector('.js-push-button');
    pushButton.disabled = true;

    navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
    // To unsubscribe from push messaging, you need get the
    // subscription object, which you can call unsubscribe() on.
    serviceWorkerRegistration.pushManager.getSubscription().then(
        function(pushSubscription) {
        // Check we have a subscription to unsubscribe
        if (!pushSubscription) {
            // No subscription object, so set the state
            // to allow the user to subscribe to push
            isPushEnabled = false;
            pushButton.disabled = false;
            pushButton.textContent = 'Enable Push Messages';
            return;
        }

        var subscriptionId = pushSubscription.subscriptionId;
        // TODO: Make a request to your server to remove
        // the subscriptionId from your data store so you
        // don't attempt to send them push messages anymore

        // We have a subscription, so call unsubscribe on it
        pushSubscription.unsubscribe().then(function(successful) {
            pushButton.disabled = false;
            pushButton.textContent = 'Enable Push Messages';
            isPushEnabled = false;
        }).catch(function(e) {
            // We failed to unsubscribe, this can lead to
            // an unusual state, so may be best to remove
            // the users data from your data store and
            // inform the user that you have done so

            console.log('Unsubscription error: ', e);
            pushButton.disabled = false;
            pushButton.textContent = 'Enable Push Messages';
        });
        }).catch(function(e) {
        console.error('Error thrown while unsubscribing from push messaging.', e);
        });
    });
}

購読を最新の状態に保つ

FCM とサーバーの間でサブスクリプションが非同期になる可能性があります。FCM のドキュメントに記載されているように、サーバーが FCM API の send POST のレスポンス本文を解析し、error:NotRegisteredcanonical_id の結果を探していることを確認します。

また、Service Worker とサーバーの間でサブスクリプションが非同期になることもあります。たとえば、サブスクライブまたはサブスクライブ解除が成功した後に、ネットワーク接続が不安定でサーバーを更新できない場合や、ユーザーが通知権限を取り消して自動登録解除をトリガーする場合があります。このようなケースを処理するには、serviceWorkerRegistration.pushManager.getSubscription() の結果を定期的に(ページの読み込み時など)確認し、サーバーと同期します。サブスクリプションが解約され、Notification.permission == 'granted' の場合は、自動的に再サブスクライブすることもできます。

sendSubscriptionToServer() では、endpoint を更新するときに、失敗したネットワーク リクエストをどのように処理するかを検討する必要があります。1 つの解決策は、Cookie 内の endpoint の状態を追跡して、サーバーに最新の詳細情報が必要かどうかを判断することです。

上記のすべての手順を完了すると、Chrome 46 でウェブ上のプッシュ メッセージングが完全に実装されます。まだ、作業を容易にする仕様化された機能(プッシュ メッセージをトリガーする標準 API など)がありますが、このリリースでは、ウェブアプリにプッシュ メッセージを組み込むことができます。

ウェブアプリをデバッグする方法

プッシュ メッセージを実装する際にバグが発生した場合、そのバグはページまたは Service Worker のいずれかに存在します。

ページのバグは DevTools を使用してデバッグできます。Service Worker の問題をデバッグするには、次の 2 つの方法があります。

  1. chrome://inspect > Service workers に移動します。このビューには、現在実行中の Service Worker 以外の情報はほとんど表示されません。
  2. chrome://serviceworker-internals に移動すると、Service Worker の状態と、エラーがある場合はエラーを確認できます。このページは、DevTools に同様の機能セットが追加されるまでの一時的なものです。

Service Worker を初めて使用する方には、[Open DevTools window and pause JavaScript execution on service worker startup for debugging] というチェックボックスを使用することをおすすめします。このチェックボックスをオンにすると、Service Worker の開始位置にブレークポイントが追加され、実行が一時停止します。これにより、Service Worker スクリプトを再開またはステップ実行して、問題が発生していないか確認できます。

serviceworker-internals で実行の一時停止チェックボックスが表示されている場所を示すスクリーンショット。

FCM とサービス ワーカーのプッシュ イベントの間に問題があると思われる場合、Chrome が何かを受信したかどうかを確認する方法がないため、問題をデバッグするためにできることはほとんどありません。重要なのは、サーバーが API 呼び出しを行うときに FCM からのレスポンスが成功することです。たとえば、次のようになります。

{"multicast_id":1234567890,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1234567890"}]}

"success": 1 レスポンスに注目してください。失敗した場合は、FCM 登録 ID に問題があり、プッシュ メッセージが Chrome に送信されていないことを示しています。

Chrome for Android での Service Worker のデバッグ

現時点では、Android 版 Chrome でサービス ワーカーをデバッグするのは簡単ではありません。chrome://inspect に移動し、デバイスを見つけて、Service Worker の URL を含む「Worker pid:....」という名前のリストアイテムを探します。

Chrome Inspect でサービス ワーカーが配置されている場所を示すスクリーンショット

プッシュ通知の UX

Chrome チームは、プッシュ通知の UX に関するベスト プラクティスのドキュメントと、プッシュ通知を扱う際のエッジケースに関するドキュメントを作成しています。

Chrome とオープンウェブでのプッシュ メッセージングの将来

このセクションでは、この実装の Chrome 固有の部分について、注意すべき点と他のブラウザの実装との違いについて詳しく説明します。

ウェブ push プロトコルとエンドポイント

Push API 標準の利点は、エンドポイントを取得してサーバーに渡し、ウェブ プッシュ プロトコルを実装してプッシュ メッセージを送信できることです。

ウェブプッシュ プロトコルは、プッシュ プロバイダが実装できる新しい標準です。これにより、デベロッパーはプッシュ プロバイダを気にする必要がなくなります。これにより、FCM の場合のように API キーに登録して特殊な形式のデータを送信する必要がなくなります。

Chrome は Push API を実装した最初のブラウザであり、FCM はウェブ プッシュ プロトコルをサポートしていません。そのため、Chrome では gcm_sender_id が必要になり、FCM では RESTful API を使用する必要があります。

Chrome の最終的な目標は、Chrome と FCM でウェブ プッシュ プロトコルを使用することです。

それまでは、エンドポイント「https://fcm.googleapis.com/fcm/send」を検出して、他のエンドポイントとは別に処理する必要があります。つまり、ペイロード データを特定の方法でフォーマットし、認可キーを追加する必要があります。

ウェブ push プロトコルを実装する方法

Firefox Nightly は現在プッシュに取り組んでおり、Web Push プロトコルを最初に実装するブラウザになる可能性があります。

よくある質問

仕様はどこにありますか?

https://slightlyoff.github.io/ServiceWorker/spec/service_worker/ https://w3c.github.io/push-api/ https://notifications.spec.whatwg.org/

ウェブ プレゼンスに複数のオリジンがある場合や、ウェブ プレゼンスとネイティブ プレゼンスの両方がある場合、通知の重複を防ぐことはできますか?

現時点では解決策はありませんが、Chromium で進捗状況を追跡できます。

理想的なシナリオでは、ユーザー デバイス用になんらかの ID を用意し、サーバー側でネイティブ アプリとウェブアプリのサブスクリプション ID を照合して、プッシュ メッセージの送信先を決定することです。画面サイズやデバイスモデル、生成された鍵をウェブアプリとネイティブ アプリ間で共有する方法で調べることができますが、それぞれの方法に長所と短所があります。

gcm_sender_id が必要な理由

これは、Chrome、Android 版 Opera、Samsung ブラウザが Firebase Cloud Messaging(FCM)API を使用できるようにするために必要です。目標は、標準が確定し、FCM がサポートできるようになったら、Web Push プロトコルを使用することです。

ウェブ ソケットやサーバー送信イベント(EventSource)を使用しないのはなぜですか?

プッシュ メッセージを使用するメリットは、ページが閉じている場合でも Service Worker が起動され、通知を表示できることです。ページまたはブラウザが閉じると、Web Sockets と EventSource の接続は終了します。

バックグラウンド イベントの配信が不要な場合はどうすればよいですか?

バックグラウンドでの配信が不要な場合は、Web Sockets が適しています。

通知を表示せずにプッシュ機能(サイレント バックグラウンド プッシュなど)を使用できるのはどのような場合ですか?

利用可能になる時期はまだ決まっていませんが、バックグラウンド同期を実装する予定があります。まだ決定や仕様化はされていませんが、バックグラウンド同期でサイレント プッシュを有効にすることについて議論されています。

HTTPS が必要な理由開発中にこの問題を回避するにはどうすればよいですか?

Service Worker は、Service Worker スクリプトが意図された送信元からのものであり、中間者攻撃によって実行されていないことを確認するために、安全な送信元を必要とします。現在のところ、これは本番環境で HTTPS を使用することを意味します。ただし、開発中はローカルホストを使用できます。

ブラウザのサポート内容

Chrome は安定版でサポートしており、Mozilla は Firefox Nightly で push を進めています。詳細については、Push API の実装のバグをご覧ください。また、こちらで通知の実装を追跡できます。

一定の期間が経過した通知を削除できますか?

現時点ではできませんが、現在表示されている通知のリストを取得できるようにサポートする予定です。通知の表示後に有効期限を設定するユースケースがある場合は、その旨をお知らせいただければ幸いです。コメントを追加していただければ、Chrome チームに報告いたします。

プッシュ通知がユーザーに送信されるのを特定の期間後に停止するだけで、通知が表示される期間は気にしない場合は、FCM の有効期間(TTL)パラメータを使用できます。詳細

Chrome のプッシュ メッセージ機能にはどのような制限がありますか?

この投稿では、いくつかの制限事項について概説しています。

  • Chrome で CCM をプッシュ サービスとして使用すると、多くの独自要件が発生します。今後、問題の一部を解除できるかどうか、Google と共同で検討しています。
  • プッシュ メッセージを受信したときに通知を表示する必要があります。
  • パソコン版 Chrome では、Chrome が実行されていないとプッシュ メッセージが受信されないという制限があります。これは、push メッセージが常に受信される ChromeOS や Android とは異なります。

Permissions API を使用する必要はないでしょうか?

Permission API は Chrome で実装されていますが、すべてのブラウザで利用できるとは限りません。詳しくはこちらをご覧ください

通知をクリックしても Chrome で前のタブが開かないのはなぜですか?

この問題は、現在 Service Worker によって制御されていないページにのみ影響します。詳しくはこちらをご覧ください

ユーザーのデバイスがプッシュを受信した時点で通知が古くなっている場合はどうなりますか?

プッシュ メッセージを受信したときに、常に通知を表示する必要があります。通知を送信するが、その通知が有効なのは一定期間のみであるというシナリオでは、CCM で time_to_live パラメータを使用して、有効期限が切れた場合に FCM がプッシュ メッセージを送信しないようにします。

詳しくは、こちらをご覧ください

10 個のプッシュ メッセージを送信し、デバイスに 1 つだけ受信させたい場合はどうなりますか?

FCM には「collapse_key」パラメータがあり、これを使用して、同じ「collapse_key」を持つ保留中のメッセージを新しいメッセージに置き換えるよう FCM に指示できます。

詳しくは、こちらをご覧ください