ユーザーの登録

まず、push メッセージを送信する権限をユーザーから取得します。次に、PushSubscription を入手します。

これを行うための JavaScript API は比較的単純なので、ロジックのフローを順を追って見ていきます。

機能検出

まず、現在のブラウザが実際にプッシュ メッセージングをサポートしているかどうかを確認する必要があります。push がサポートされているかどうかは、2 つの簡単なチェックでチェックできます。

  1. navigatorserviceWorker を確認します。
  2. windowPushManager を確認します。
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 と push メッセージングの両方でブラウザのサポートが急速に拡大していますが、両方の機能検出と段階的な機能強化を常に行うことをおすすめします。

Service Worker を登録する

Feature Detection では、Service Worker と push の両方がサポートされていることがわかります。次のステップでは、Service Worker を「登録」します。

Service Worker を登録すると、その Service Worker ファイルがどこにあるかがブラウザに通知されます。 ファイルは JavaScript のままですが、ブラウザは Service Worker API(push を含む)への「アクセス権を付与」します。正確には、ブラウザは Service Worker 環境でファイルを実行します。

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 ファイルがあることとその場所をブラウザに通知します。この場合、Service Worker ファイルは /service-worker.js にあります。バックグラウンドでは、ブラウザは register() を呼び出した後に次の処理を行います。

  1. Service Worker ファイルをダウンロードします。

  2. JavaScript を実行します。

  3. すべてが正しく実行され、エラーがなければ、register() によって返された Promise が解決されます。なんらかのエラーがある場合、Promise は拒否されます。

register() で拒否された場合は、Chrome DevTools で JavaScript に入力ミスやエラーがないか再度確認してください。

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

PushManager API ブラウザの互換性

対応ブラウザ

  • 42
  • 17
  • 44
  • 16

ソース

権限をリクエストしています

Service Worker を登録し、ユーザーを登録する準備ができました。次のステップでは、プッシュ メッセージを送信する権限をユーザーから取得します。

権限を取得するための 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 に表示される権限プロンプト。

ユーザーが [Allow] または [Block] を押すか、プロンプトを閉じるだけで権限プロンプトを操作すると、結果は 'granted''default''denied' の文字列として返されます。

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

対処する必要がある特殊なケースとして、ユーザーが [ブロック] ボタンをクリックした場合は、その場合、ウェブアプリはユーザーに権限を再度求めることができなくなります。権限の状態を変更してアプリを手動で「ブロック解除」する必要があります。これは設定パネルに埋め込まれています。ユーザーに許可を求める方法とタイミングについて慎重に検討してください。ユーザーが [ブロック] をクリックした場合、決定を覆すのは簡単ではないためです。

幸いなことに、ほとんどのユーザーは、なぜ権限が求められる理由を理解しているのであれば、喜んで権限を付与できます。

後で、いくつかの有名なサイトが許可を求める方法を見ていきます。

PushManager でユーザーをサブスクライブする

Service Worker を登録して権限を取得したら、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 オプション

ブラウザに push が最初に追加されたとき、デベロッパーがプッシュ メッセージを送信して通知を表示できなくすべきかについて、不確実な状況がありました。これは一般に「サイレント プッシュ」と呼ばれ、ユーザーはバックグラウンドで何かが発生したことを知らないためです。

ユーザーの現在地を継続的に追跡するなど、ユーザーが気付かないうちにデベロッパーが不当な行為を行う可能性があることが懸念されていました。

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

現時点では、値 true を渡す必要がありますuserVisibleOnly キーを含めない、または false を渡さないと、次のエラーが発生します。

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

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

applicationServerKey オプション

前のセクションで「アプリケーション サーバーキー」について簡単に説明しました。「アプリケーション サーバーキー」は、ユーザーを登録しているアプリを識別し、同じアプリがそのユーザーにメッセージを送信できるようにするために、push サービスによって使用されます。

アプリケーション サーバーキーは、アプリケーションに固有の公開鍵と秘密鍵のペアです。秘密鍵はアプリに対して非公開にする必要があります。公開鍵は自由に共有できます。

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

下の図にこの手順を示します。

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

サブスクライブ メソッドで使用される公開アプリケーション サーバー鍵のイラスト。

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

メッセージの送信時にアプリケーション サーバーの秘密鍵を使用する方法

技術的には、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 オブジェクトには、そのユーザーに push メッセージを送信するために必要なすべての情報が含まれています。JSON.stringify() を使用して内容を出力すると、次のように表示されます。

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

endpoint は push サービスの URL です。push メッセージをトリガーするには、この URL に POST リクエストを送信します。

keys オブジェクトには、push メッセージ(このセクションで後述)で送信されるメッセージ データを暗号化するために使用される値が含まれています。

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

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.');
      }
    });
}

ノードサーバーはこのリクエストを受け取り、後で使用できるようにデータをデータベースに保存します。

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 の詳細があることで、いつでもユーザーにメッセージを送信できます。

よくある質問

この時点でよくある質問をいくつか紹介します。

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

いいえ。push サービスはブラウザによって選択されます。subscribe() 呼び出しで確認したように、ブラウザは push サービスにネットワーク リクエストを行い、PushSubscription を構成する詳細を取得します。

ブラウザごとに使用するプッシュ サービスはブラウザによって異なり、API も異なるのではないでしょうか?

すべての push サービスは、同じ API を想定します。

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

パソコンで定期購入しているユーザーは、スマートフォンでも定期購入しているのでしょうか?

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

次のステップ

Codelab