ユーザーをプッシュ通知に登録する

プッシュ メッセージを送信するには、まずユーザーから許可を得て、デバイスをプッシュ サービスに登録する必要があります。これには、JavaScript API を使用して PushSubscription オブジェクトを取得し、サーバーに送信することが含まれます。

JavaScript API はこのプロセスを簡単に管理します。このガイドでは、機能の検出、権限のリクエスト、定期購入プロセスの管理など、フロー全体について説明します。

特徴検出

まず、ブラウザがプッシュ メッセージをサポートしているかどうかを確認します。プッシュ通知のサポート状況は、次の 2 つのチェックで確認できます。

  • navigator オブジェクトで serviceWorker を確認します。
  • window オブジェクトで PushManager を確認します。
if (!('serviceWorker' in navigator)) {
  // Service Worker isn't supported on this browser, disable or hide UI.
  return;
}

if (!('PushManager' in window)) {
  // Push isn't supported on this browser, disable or hide UI.
  return;
}

サービス ワーカーとプッシュ メッセージングの両方に対するブラウザのサポートは拡大していますが、常に両方の機能を検出し、アプリケーションを段階的に強化してください。

Service Worker を登録する

機能検出後、Service Worker とプッシュ メッセージがサポートされていることがわかります。次に、サービス ワーカーを登録します。

サービス ワーカーを登録するときに、サービス ワーカー ファイルの場所をブラウザに伝えます。ファイルは JavaScript ファイルですが、ブラウザはプッシュ メッセージなどの Service Worker API へのアクセスを許可します。具体的には、ブラウザはサービス ワーカー環境でファイルを実行します。

Service Worker を登録するには、navigator.serviceWorker.register() を呼び出して、ファイルのパスを渡します。次に例を示します。

function registerServiceWorker() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      console.log('Service worker successfully registered.');
      return registration;
    })
    .catch(function (err) {
      console.error('Unable to register service worker.', err);
    });
}

この関数は、サービス ワーカー ファイルの場所をブラウザに伝えます。ここで、サービス ワーカー ファイルは /service-worker.js にあります。register() を呼び出すと、ブラウザは次の手順を実行します。

  1. サービス ワーカー ファイルをダウンロードします。

  2. JavaScript を実行します。

  3. ファイルがエラーなしで正しく実行されると、register() によって返された Promise が解決されます。エラーが発生すると、Promise は拒否されます。

: register() が拒否された場合は、Chrome DevTools で JavaScript のタイプミスやエラーを確認してください。

register() が解決されると、ServiceWorkerRegistration が返されます。この登録を使用して、PushManager API にアクセスします。

PushManager API のブラウザの互換性

Browser Support

  • Chrome: 42.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 16.

Source

権限のリクエスト

サービス ワーカーを登録して権限を取得したら、プッシュ メッセージを送信する権限をユーザーから取得します。

権限を取得するための API はシンプルです。ただし、API は最近、コールバックを受け取るのではなく Promise を返すように変更されました。ブラウザが実装する API バージョンを特定できないため、両方のバージョンを実装して処理する必要があります。

function askPermission() {
  return new Promise(function (resolve, reject) {
    const permissionResult = Notification.requestPermission(function (result) {
      resolve(result);
    });

    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  }).then(function (permissionResult) {
    if (permissionResult !== 'granted') {
      throw new Error("We weren't granted permission.");
    }
  });
}

上記のコードでは、Notification.requestPermission() の呼び出しによってユーザーにプロンプトが表示されます。

パソコンとモバイルの Chrome に表示される権限プロンプト。

ユーザーが [許可]、[ブロック] を選択するか、プロンプトを閉じて権限プロンプトを操作すると、結果が文字列('granted''default''denied')として返されます。

サンプルコードでは、権限が付与されている場合、askPermission() によって返される Promise は解決されます。それ以外の場合は、エラーがスローされ、Promise は拒否されます。

ユーザーが [ブロック] ボタンをクリックした場合の例外処理を行います。この場合、ウェブアプリはユーザーに権限を再度リクエストすることはできません。ユーザーは、設定パネルでアプリの権限の状態を変更して、アプリを手動でブロック解除する必要があります。ユーザーが [ブロック] をクリックすると、その決定を簡単に取り消すことはできないため、権限を求めるタイミングと方法を慎重に検討してください。

ほとんどのユーザーは、アプリが権限をリクエストする理由を理解すると、権限を付与します。

このドキュメントでは、一部の人気サイトが後で権限をリクエストする方法について説明します。

PushManager を使用してユーザーを登録する

サービス ワーカーを登録して権限を取得したら、registration.pushManager.subscribe() を呼び出してユーザーを登録できます。

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

subscribe() メソッドを呼び出すときは、必須パラメータとオプション パラメータで構成される options オブジェクトを渡します。

このセクションでは、渡すことができるオプションについて説明します。

userVisibleOnly オプション

プッシュ メッセージがブラウザに初めて追加されたとき、デベロッパーは通知を表示せずにプッシュ メッセージを送信することに不安を感じていました。ユーザーはバックグラウンドでイベントが発生したことを認識しないため、一般的にサイレント プッシュと呼ばれます。

懸念されていたのは、デベロッパーがユーザーの知らないうちにユーザーの位置情報を継続的にトラッキングできることでした。

このシナリオを回避し、仕様の作成者がこの機能をサポートする最適な方法を検討できるように、userVisibleOnly オプションが追加されました。値 true を渡すことは、ウェブアプリがプッシュ メッセージを受信するたびに通知を表示する(つまり、サイレント プッシュではない)というブラウザとのシンボリックな合意です。

true の値を渡す必要がありますuserVisibleOnly キーを含めない場合や false を渡さない場合は、次のエラーが返されます。

Chrome currently only supports the Push API for subscriptions that will result
in user-visible messages. You can indicate this by calling
`pushManager.subscribe({userVisibleOnly: true})` instead. See
[https://goo.gl/yqv4Q4](https://goo.gl/yqv4Q4) for more details.

Chrome は、ユーザーに表示されるメッセージを生成するサブスクリプションに対してのみ Push API をサポートしています。これを示すには、pushManager.subscribe({userVisibleOnly: true}) を呼び出します。詳しくは、https://goo.gl/yqv4Q4 をご覧ください。

Chrome では、一括サイレント プッシュは実装されないようです。代わりに、仕様の作成者は、ウェブアプリの使用状況に基づいて一定数のサイレント プッシュ メッセージをウェブアプリが送信できるバジェット API を検討しています。

applicationServerKey オプション

このドキュメントでは、以前にアプリケーション サーバーキーについて説明しました。プッシュ サービスは、アプリケーション サーバーキーを使用して、ユーザーを登録しているアプリケーションを特定し、同じアプリケーションがそのユーザーにメッセージを送信することを保証します。

アプリケーション サーバーキーは、アプリに固有の公開鍵と秘密鍵のペアです。秘密鍵はアプリケーションで非公開にし、公開鍵は自由に共有します。

subscribe() 呼び出しに渡される applicationServerKey オプションは、アプリの公開鍵です。ブラウザは、ユーザーを登録するときにこのキーをプッシュ サービスに渡します。これにより、プッシュ サービスはアプリの公開鍵をユーザーの PushSubscription に関連付けることができます。

以下の図は、これらの手順を表しています。

  1. ブラウザでウェブアプリを読み込み、公開アプリケーション サーバー鍵を渡して subscribe() を呼び出します。
  2. ブラウザはプッシュ サービスにネットワーク リクエストを送信します。プッシュ サービスはエンドポイントを生成し、このエンドポイントをアプリの公開鍵に関連付けて、エンドポイントをブラウザに返します。
  3. ブラウザは、このエンドポイントを PushSubscription に追加します。これは subscribe() プロミスが返すものです。

公開アプリケーション サーバー鍵が `subscribe()` メソッドで使用される仕組みを示す図。

プッシュ メッセージを送信するときは、アプリサーバーの秘密鍵で署名された情報を含む Authorization ヘッダーを作成します。プッシュ サービスがプッシュ メッセージを送信するリクエストを受け取ると、リクエストを受信するエンドポイントにリンクされた公開鍵を検索して、この署名付きの Authorization ヘッダーを検証します。署名が有効な場合、プッシュ サービスは、リクエストが一致する秘密鍵を持つアプリケーション サーバーから送信されたことを認識します。これは、他のユーザーがアプリのユーザーにメッセージを送信することを防ぐためのセキュリティ対策です。

メッセージの送信時にプライベート アプリケーション サーバーキーがどのように使用されるかを示す図。

厳密には、applicationServerKey は省略可能です。ただし、Chrome での最もシンプルな実装では必要であり、他のブラウザでも今後必要になる可能性があります。Firefox では省略可能です。

VAPID 仕様では、アプリケーション サーバーキーが定義されています。アプリケーション サーバーキーまたは VAPID キーという用語が使用されている場合は、同じものを指していることを覚えておいてください。

アプリケーション サーバーキーを作成する

web-push-codelab.glitch.me にアクセスするか、web-push コマンドラインを使用して、次のように鍵を生成することで、公開鍵と秘密鍵のセットを作成できます。

    $ npm install -g web-push
    $ web-push generate-vapid-keys

これらの鍵はアプリケーションに対して 1 回だけ作成し、秘密鍵を非公開にしてください。

権限と subscribe()

subscribe() の呼び出しには 1 つの副作用があります。subscribe() を呼び出したときにウェブアプリに通知を表示する権限がない場合、ブラウザが権限をリクエストします。これは、UI がこのフローで動作する場合に便利ですが、より詳細な制御が必要な場合(ほとんどのデベロッパーがそうであるように)、このドキュメントで前述した Notification.requestPermission() API を使用します。

PushSubscription の概要

subscribe() を呼び出し、オプションを渡すと、PushSubscription に解決される Promise が返されます。次に例を示します。

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

PushSubscription オブジェクトには、そのユーザーにプッシュ メッセージを送信するために必要なすべての情報が含まれています。JSON.stringify() を使用して内容を出力すると、次のようになります。

    {
      "endpoint": "https://some.pushservice.com/something-unique",
      "keys": {
        "p256dh":
    "BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
        "auth":"FPssNDTKnInHVndSTdbKFw=="
      }
    }

endpoint は、プッシュ サービスの URL です。プッシュ メッセージをトリガーするには、この URL に POST リクエストを行います。

keys オブジェクトには、プッシュ メッセージで送信されるメッセージ データの暗号化に使用される値が含まれています。(メッセージの暗号化については、このドキュメントの後半で説明します)。

サーバーに定期購入を送信する

プッシュ サブスクリプションを取得したら、サーバーに送信します。送信方法はユーザーが決定しますが、ヒントとして、JSON.stringify() を使用してサブスクリプション オブジェクトから必要なデータをすべて抽出することをおすすめします。または、次のように同じ結果を手動で組み立てることもできます。

const subscriptionObject = {
  endpoint: pushSubscription.endpoint,
  keys: {
    p256dh: pushSubscription.getKeys('p256dh'),
    auth: pushSubscription.getKeys('auth'),
  },
};

// The above is the same output as:

const subscriptionObjectToo = JSON.stringify(pushSubscription);

ウェブページからサブスクリプションを送信するには、次のコードを使用します。

function sendSubscriptionToBackEnd(subscription) {
  return fetch('/api/save-subscription/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(subscription),
  })
    .then(function (response) {
      if (!response.ok) {
        throw new Error('Bad status code from server.');
      }

      return response.json();
    })
    .then(function (responseData) {
      if (!(responseData.data && responseData.data.success)) {
        throw new Error('Bad response from server.');
      }
    });
}

Node.js サーバーはこのリクエストを受け取り、後で使用するためにデータをデータベースに保存します。

app.post('/api/save-subscription/', function (req, res) {
  if (!isValidSaveRequest(req, res)) {
    return;
  }

  return saveSubscriptionToDatabase(req.body)
    .then(function (subscriptionId) {
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify({data: {success: true}}));
    })
    .catch(function (err) {
      res.status(500);
      res.setHeader('Content-Type', 'application/json');
      res.send(
        JSON.stringify({
          error: {
            id: 'unable-to-save-subscription',
            message:
              'The subscription was received but we were unable to save it to our database.',
          },
        }),
      );
    });
});

サーバーに PushSubscription の詳細情報があれば、いつでもユーザーにメッセージを送信できます。

定期的に再登録して有効期限切れを防ぐ

プッシュ通知を登録すると、多くの場合、nullPushSubscription.expirationTime が届きます。理論上、これはサブスクリプションの有効期限が切れないことを意味します。(一方、DOMHighResTimeStamp は正確な有効期限を示します)。ただし、実際には、ブラウザではサブスクリプションの有効期限が切れるのが一般的です。たとえば、プッシュ通知が長期間受信されない場合や、ブラウザがユーザーがプッシュ通知権限のあるアプリを使用していないことを検出した場合に、この状態になることがあります。これを防ぐ方法の 1 つは、次のスニペットに示すように、通知を受け取るたびにユーザーを再登録することです。そのため、ブラウザでサブスクリプションが自動的に期限切れにならないように、通知を十分な頻度で送信する必要があります。定期購入の有効期限切れを防ぐためだけにユーザーにスパムを送信することと、正当な通知の必要性との間で、メリットとデメリットを慎重に比較する必要があります。最終的には、ユーザーを長期間忘れていた通知登録から保護しようとするブラウザの取り組みを回避しようとすべきではありません。

/* In the Service Worker. */

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

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

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

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

よくある質問

よくある質問は次のとおりです。

ブラウザが使用するプッシュ サービスを変更できますか?

いいえ。ブラウザがプッシュ サービスを選択します。このドキュメントで subscribe() 呼び出しについて説明したように、ブラウザはプッシュ サービスにネットワーク リクエストを送信して、PushSubscription を構成する詳細情報を取得します。

プッシュ サービスによって使用する API は異なりますか?

すべてのプッシュ サービスは同じ API を想定しています。

この共通 API(Web Push Protocol)は、アプリがプッシュ メッセージをトリガーするために行うネットワーク リクエストを記述します。

パソコンでユーザーを登録した場合、スマートフォンでも登録されますか?

いいえ。ユーザーは、メッセージを受信する各ブラウザでプッシュ メッセージに登録する必要があります。また、ユーザーは各デバイスで権限を付与する必要があります。

次のステップ

Codelab