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

プッシュ メッセージを送信するには、まずユーザーから許可を得て、ユーザーのデバイスをプッシュ サービスに登録する必要があります。これには、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;
}

サービス 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() promise が返します。

`subscribe()` メソッドで公開アプリケーション サーバーキーがどのように使用されるかを示す図。

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

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

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

VAPID 仕様では、アプリケーション サーバーキーが定義されています。アプリケーション サーバーキーと VAPID キーは同じものです。

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

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 は、push サービスの URL です。push メッセージをトリガーするには、この URL に POST リクエストを送信します。

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

サーバーにサブスクリプションを送信する

プッシュ サブスクリプションを取得したら、サーバーに送信します。送信方法は任意ですが、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 の詳細があれば、いつでもユーザーにメッセージを送信できます。

有効期限切れを防ぐために定期的に再登録する

プッシュ通知に登録すると、多くの場合、null の PushSubscription.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);
    });
}

よくある質問

よくある質問を以下に示します。

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

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

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

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

この共通 API は Web Push プロトコルと呼ばれ、プッシュ メッセージをトリガーするためにアプリケーションが行うネットワーク リクエストを記述します。

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

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

次のステップ

Codelab