ウェブアプリにプッシュ通知を追加する

1. 概要

プッシュ メッセージは、ユーザーに繰り返し働きかけるシンプルで効果的な方法です。この Codelab では、プッシュ通知をウェブアプリに追加する方法を学びます。

学習内容

  • プッシュ メッセージのユーザーを登録および登録解除する方法
  • 受信した push メッセージを処理する方法
  • 通知の表示方法
  • 通知のクリックに対応する方法

必要なもの

  • Chrome 52 以降
  • Chrome 用のウェブサーバーまたは任意のウェブサーバー
  • テキスト エディタ
  • HTML、CSS、JavaScript、Chrome DevTools に関する基本的な知識
  • サンプルコード(「設定」をご覧ください)

2. セットアップする

サンプルコードをダウンロードする

この Codelab のサンプルコードを取得するには、次の 2 つの方法があります。

  • git リポジトリのクローンを作成します。
git clone https://github.com/GoogleChrome/push-notifications.git
  • ZIP ファイルをダウンロードします。

ソースを ZIP ファイルとしてダウンロードした場合、解凍するとルートフォルダ push-notifications-master が作成されます。

ウェブサーバーをインストールして確認する

独自のウェブサーバーは自由に使用できますが、この Codelab は Web Server for Chrome アプリと連携するように設計されています。アプリをまだインストールしていない場合は、Chrome ウェブストアから入手できます。

Chrome 用ウェブサーバー アプリをインストールしたら、ブックマーク バーの [アプリ] ショートカットをクリックします。

946bcaaad66e5c8e.png

[アプリ] ウィンドウでウェブサーバー アイコンをクリックします。

9f3c21b2cf6cbfb5.png

次にこのダイアログが表示され、ローカル ウェブサーバーを構成できます。

73543edeb27c3d6f.png

[フォルダを選択] ボタンをクリックし、ダウンロードした push-notifications フォルダ内の app フォルダを選択します。これにより、ダイアログの [ウェブサーバーの URL] セクションに表示される URL から進行中の作業を表示できます。

[Options] で、[Automatically show index.html] の横のチェックボックスをオンにします(以下を参照)。

5ac11bca86ce7369.png

次に、[Web Server: STARTED] トグルを左にスライドしてから右にスライドして、サーバーを停止して再起動します。

d42f87972f9fec24.png

ウェブサーバーの URL をクリックして、ウェブブラウザでサイトにアクセスします。次のようなページが表示されますが、使用しているバージョンではアドレスが 127.0.0.1:8887 と表示されている場合があります。

00-push-codelab.png

常に Service Worker を更新する

開発中は、Service Worker を常に最新の状態に保ち、最新の変更を反映させることをおすすめします。

Chrome でこれを設定するには:

  1. [Codelab の push] タブに移動します。
  2. DevTools を開きます(Windows と Linux では Ctrl-Shift-I、macOS では Cmd-Option-I)。
  3. [アプリケーション] パネルを選択して [Service Worker] タブをクリックし、[再読み込み時に更新] チェックボックスをオンにします。このチェックボックスをオンにすると、ページが再読み込みされるたびに Service Worker が強制的に更新されます。

e7d384fb77885b99.png

3. Service Worker を登録する

完成したコード

app ディレクトリに sw.js という名前の空のファイルがあります。このファイルが Service Worker になります。現時点では、空のままで構いません。後でコードを追加します。

まず、このファイルを Service Worker として登録する必要があります。

app/index.html ページが scripts/main.js を読み込みます。この JavaScript ファイルに Service Worker を登録します。

scripts/main.js に次のコードを追加します。

if ('serviceWorker' in navigator && 'PushManager' in window) {
  console.log('Service Worker and Push are supported');

  navigator.serviceWorker.register('sw.js')
  .then(function(swReg) {
    console.log('Service Worker is registered', swReg);

    swRegistration = swReg;
  })
  .catch(function(error) {
    console.error('Service Worker Error', error);
  });
} else {
  console.warn('Push messaging is not supported');
  pushButton.textContent = 'Push Not Supported';
}

このコードは、ブラウザが Service Worker と push メッセージングをサポートしているかどうかを確認します。サポートされている場合、コードは sw.js ファイルを登録します。

実際に試す

ブラウザで [Push Codelab] タブを更新して、変更内容を確認します。

次のように、Chrome DevTools のコンソールで Service Worker is registered message を確認します。

5d7ad383d6f235d5.png

アプリケーション サーバーキーを取得する

この Codelab で作業するには、アプリケーション サーバーキーを生成する必要があります。これはコンパニオン サイト(web-push-codelab.glitch.me)で行うことができます。

ここで、公開鍵と秘密鍵のペアを生成できます。

push-codelab-04-companion.png

公開鍵を scripts/main.js にコピーし、<Your Public Key> の値を置き換えます。

const applicationServerPublicKey = '<Your Public Key>';

重要: 秘密鍵はウェブアプリに保存しないでください。

4. 状態を初期化する

完成したコード

現在、ウェブアプリの [有効にする] ボタンは無効になっており、クリックできません。プッシュ メッセージをデフォルトで無効にし、ブラウザがプッシュ メッセージに対応していることがわかっていて、ユーザーが現在メッセージに登録しているかどうかを確認できるようになったら、プッシュボタンを有効にすることをおすすめします。

scripts/main.js に次の 2 つの関数を作成する必要があります。

  • initializeUI: ユーザーが現在購読中かどうかを確認します。
  • updateBtn: ボタンを有効にし、ユーザーが購読しているかどうかに応じてテキストを変更します。

次のように initializeUI 関数を main.js に追加します。

function initializeUI() {
  // Set the initial subscription value
  swRegistration.pushManager.getSubscription()
  .then(function(subscription) {
    isSubscribed = !(subscription === null);

    if (isSubscribed) {
      console.log('User IS subscribed.');
    } else {
      console.log('User is NOT subscribed.');
    }

    updateBtn();
  });
}

新しいメソッドは、前のステップの swRegistration を使用し、そこから pushManager プロパティを取得して、getSubscription() を呼び出します。

pushManagergetSubscription() は、現在のサブスクリプションが存在する場合は、それで解決される Promise を返します。それ以外の場合は、null を返します。これにより、ユーザーがすでに購読中かどうかを確認し、isSubscribed の値を設定してから、updateBtn() を呼び出してボタンを更新できます。

updateBtn() 関数を main.js に追加します。

function updateBtn() {
  if (isSubscribed) {
    pushButton.textContent = 'Disable Push Messaging';
  } else {
    pushButton.textContent = 'Enable Push Messaging';
  }

  pushButton.disabled = false;
}

この関数によってボタンが有効になり、ユーザーが購読されているかどうかによってボタンのテキストが変更されます。

最後に、Service Worker が main.js に登録されているときに initializeUI() を呼び出します。

navigator.serviceWorker.register('sw.js')
.then(function(swReg) {
  console.log('Service Worker is registered', swReg);

  swRegistration = swReg;
  initializeUI();
})

実際に試す

[Push Codelab] タブを更新します。[Enable Push Messaging] ボタンが有効になり(クリック可能)、コンソールに User is NOT subscribed が表示されます。

a1553f4a0483d227.png

この Codelab の残りの部分を進めていくと、登録や登録解除を行うたびにボタンテキストが変わっていくはずです。

5. ユーザーを登録する

完成したコード

現時点では、[Enable Push Messaging] ボタンは機能しません。これを修正しましょう。

initializeUI() 関数に、ボタンのクリック リスナーを追加します。

function initializeUI() {
  pushButton.addEventListener('click', function() {
    pushButton.disabled = true;
    if (isSubscribed) {
      // TODO: Unsubscribe user
    } else {
      subscribeUser();
    }
  });

  // Set the initial subscription value
  swRegistration.pushManager.getSubscription()
  .then(function(subscription) {
    isSubscribed = !(subscription === null);

    updateSubscriptionOnServer(subscription);

    if (isSubscribed) {
      console.log('User IS subscribed.');
    } else {
      console.log('User is NOT subscribed.');
    }

    updateBtn();
  });
}

ユーザーがボタンをクリックしたときに、ユーザーが 2 回目のクリックできないようにして、ボタンを無効にします。これは、プッシュ メッセージへの登録には時間がかかる場合があるためです。

ユーザーがまだ登録していない場合は、subscribeUser() を呼び出します。そのためには、次のコードを scripts/main.js に貼り付ける必要があります。

function subscribeUser() {
  const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
  swRegistration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: applicationServerKey
  })
  .then(function(subscription) {
    console.log('User is subscribed.');

    updateSubscriptionOnServer(subscription);

    isSubscribed = true;

    updateBtn();
  })
  .catch(function(error) {
    console.error('Failed to subscribe the user: ', error);
    updateBtn();
  });
}

このコードの機能と、プッシュ メッセージング用にユーザーを登録する仕組みを詳しく見ていきましょう。

まず、アプリケーション サーバーの公開鍵(Base64 URL セーフにエンコードされたもの)を UInt8Array に変換します。これは、subscribe() 呼び出しで想定される入力であるためです。urlB64ToUint8Array() 関数は scripts/main.js の先頭にあります。

値を変換したら、Service Worker の pushManagersubscribe() メソッドを呼び出し、アプリケーション サーバーの公開鍵と値 userVisibleOnly: true を渡します。

const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
swRegistration.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: applicationServerKey
})

userVisibleOnly パラメータにより、プッシュ メッセージが送信されるたびに通知が表示されることが保証されます。現在、この値は必須で、true である必要があります。

subscribe() を呼び出すと Promise が返されます。この Promise は、次のステップの後に解決されます。

  1. ユーザーが通知を表示する権限を付与しています。
  2. ブラウザが、PushSubscription の生成に必要なデータを取得するために、ネットワーク リクエストを push サービスに送信しました。

これらのステップが成功した場合、subscribe() Promise は PushSubscription で解決されます。ユーザーが権限を付与しなかった場合、またはユーザーの登録で問題が発生した場合、Promise は拒否され、エラーが返されます。これにより、Codelab で次の Promise チェーンが得られます。

swRegistration.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: applicationServerKey
})
.then(function(subscription) {
  console.log('User is subscribed.');

  updateSubscriptionOnServer(subscription);

  isSubscribed = true;

  updateBtn();

})
.catch(function(err) {
  console.log('Failed to subscribe the user: ', err);
  updateBtn();
});

これにより、サブスクリプションを取得してユーザーをサブスクライブ済みとして扱うか、エラーを検出してコンソールにログ出力できます。どちらの場合も、updateBtn() を呼び出して、ボタンが再度有効になり、適切なテキストが表示されるようにします。

実際のアプリでは、関数 updateSubscriptionOnServer() でサブスクリプション データをバックエンドに送信しますが、この Codelab では UI にサブスクリプションを表示するだけです。scripts/main.js に次の関数を追加します。

function updateSubscriptionOnServer(subscription) {
  // TODO: Send subscription to application server

  const subscriptionJson = document.querySelector('.js-subscription-json');
  const subscriptionDetails =
    document.querySelector('.js-subscription-details');

  if (subscription) {
    subscriptionJson.textContent = JSON.stringify(subscription);
    subscriptionDetails.classList.remove('is-invisible');
  } else {
    subscriptionDetails.classList.add('is-invisible');
  }
}

実際に試す

[Push Codelab] タブに移動し、ページを更新してボタンをクリックします。次のような権限プロンプトが表示されます。

fcc61267a0194e81.png

権限を付与すると、コンソールに User is subscribed が記録されます。ボタンのテキストが [Disable Push Messaging] に変わり、ページの下部にサブスクリプションが JSON データとして表示されるようになります。

5c5505f2ead037c.png

6. 拒否された権限を処理する

完成したコード

まだ処理していないことの一つは、ユーザーが権限リクエストをブロックした場合です。ユーザーが権限をブロックすると、ウェブアプリで権限プロンプトを再表示できず、ユーザーを登録できなくなるため、この点については特別な考慮事項があります。少なくとも、プッシュボタンを無効にし、使用できないことをユーザーが認識できるようにする必要があります。

このシナリオを扱うには、updateBtn() 関数内で行います。必要な作業は、次のように Notification.permission 値を確認することだけです。

function updateBtn() {
  if (Notification.permission === 'denied') {
    pushButton.textContent = 'Push Messaging Blocked';
    pushButton.disabled = true;
    updateSubscriptionOnServer(null);
    return;
  }

  if (isSubscribed) {
    pushButton.textContent = 'Disable Push Messaging';
  } else {
    pushButton.textContent = 'Enable Push Messaging';
  }

  pushButton.disabled = false;
}

権限が denied の場合、ユーザーはサブスクライブできず、これ以上できることはないため、ボタンを永続的に無効にすることをおすすめします。

実際に試す

前のステップでウェブアプリへの権限をすでに付与しているので、URL バーで円で囲まれた i をクリックし、[通知] 権限を [グローバル デフォルトを使用(確認)] に変更する必要があります。

54495592074f10ae.png

この設定を変更したら、ページを更新して [Enable Push Messaging] ボタンをクリックし、権限ダイアログで [Block] を選択します。ボタンが無効になり、[Push Messaging Blocked] というメッセージが表示されます。

d4cf22468f6defda.png

この変更により、考えられる権限のシナリオに対処して、ユーザーをサブスクライブできるようになりました。

7. push イベントを処理する

完成したコード

バックエンドからプッシュ メッセージを送信する方法を学ぶ前に、サブスクライブしたユーザーがプッシュ メッセージを受信すると実際にどうなるかを考慮する必要があります。

プッシュ メッセージをトリガーすると、ブラウザはプッシュ メッセージを受信してプッシュ対象の Service Worker を特定し、その Service Worker を復帰させ、プッシュ イベントをディスパッチします。このイベントをリッスンし、結果として通知を表示する必要があります。

sw.js ファイルに次のコードを追加します。

self.addEventListener('push', function(event) {
  console.log('[Service Worker] Push Received.');
  console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);

  const title = 'Push Codelab';
  const options = {
    body: 'Yay it works.',
    icon: 'images/icon.png',
    badge: 'images/badge.png'
  };

  event.waitUntil(self.registration.showNotification(title, options));
});

このコードを順を追って説明します。イベント リスナーを追加して、Service Worker で push イベントをリッスンしています。

self.addEventListener('push', ... );

(ウェブ ワーカーを使ったことがない場合でも、self はおそらく初めて使用するでしょう。Service Worker ファイルでは、self が Service Worker 自体を参照します)。

push メッセージを受信するとイベント リスナーが呼び出され、Service Worker の registration プロパティに対して showNotification() を呼び出して通知を作成します。showNotification() には title が必要です。options オブジェクトを指定して、本文のメッセージ、アイコン、バッジを設定することもできます。(このバッジは本稿執筆時点で Android でのみ使用されています)。

const title = 'Push Codelab';
const options = {
  body: 'Yay it works.',
  icon: 'images/icon.png',
  badge: 'images/badge.png'
};
self.registration.showNotification(title, options);

push イベント処理で最後に説明するのは event.waitUntil() です。このメソッドは Promise を受け取り、渡された Promise が解決されるまでブラウザが Service Worker を存続させて実行し続けるようにします。

上記のコードを少しわかりやすくするために、次のように書き換えます。

const notificationPromise = self.registration.showNotification(title, options);
event.waitUntil(notificationPromise);

push イベントをステップ実行したところで、push イベントをテストしてみましょう。

実際に試す

Service Worker の push イベント処理を使用すると、疑似 push イベントをトリガーして、メッセージの受信時の動作をテストできます。

ウェブアプリで push メッセージングをサブスクライブし、コンソールに User IS subscribed が表示されていることを確認します。DevTools の [Application] パネルの [Service Workers] タブで、[Push] ボタンをクリックします。

1ee499267eeccd1c.png

[Push] をクリックすると、次のような通知が表示されます。

379105dfb0ea56d8.png

注: このステップでうまくいかない場合は、DevTools の [Application] パネルの [Unregister] リンクから Service Worker の登録を解除し、Service Worker が停止するのを待ってから、ページを再読み込みしてください。

8. 通知のクリック

完成したコード

これらの通知のいずれかをクリックしても、何も起こりません。通知のクリックを処理するには、Service Worker で notificationclick イベントをリッスンします。

まず、sw.jsnotificationclick リスナーを追加します。

self.addEventListener('notificationclick', function(event) {
  console.log('[Service Worker] Notification click received.');

  event.notification.close();

  event.waitUntil(
    clients.openWindow('https://developers.google.com/web')
  );
});

ユーザーが通知をクリックすると、notificationclick イベント リスナーが呼び出されます。

このコードは、まずクリックされた通知を閉じます。

event.notification.close();

その後、新しいウィンドウまたはタブが開き、URL https://developers.google.com/web が読み込まれます。この値は自由に変更してください。

event.waitUntil(
    clients.openWindow('https://developers.google.com/web/')
  );

event.waitUntil() は、新しいウィンドウやタブが表示される前にブラウザが Service Worker を終了しないようにします。

実際に試す

DevTools でプッシュ メッセージをもう一度トリガーして、通知をクリックします。通知が閉じ、新しいタブが開きます。

9. プッシュ メッセージを送信する

ウェブアプリで DevTools を使用して通知を表示できることと、ワンクリックで通知を閉じる方法を確認しました。次のステップでは、実際のプッシュ メッセージを送信します。

通常、これにはウェブページからバックエンドに購読を送信する必要があります。この場合、バックエンドはサブスクリプションのエンドポイントに対して API 呼び出しを行うことで、push メッセージをトリガーします。

これはこの Codelab の範囲外ですが、コンパニオン サイト(web-push-codelab.glitch.me)を使用して実際のプッシュ メッセージをトリガーできます。ページの下部にサブスクリプションを貼り付けます。

bb202867962a0249.png

次に、コンパニオン サイトの [Subscription to Send To] テキスト領域にこれを貼り付けます。

a0dd93bc33a9e8cf.png

[Text to Send] に、プッシュ メッセージと一緒に送信する文字列を追加します。

[プッシュ メッセージを送信] ボタンをクリックします。

a5e8e89411ec034.png

プッシュ メッセージを受信します。使用したテキストはコンソールに記録されます。

f6815a356d4f9aaa.png

これにより、データの送受信をテストし、結果として通知を操作できるようになります。

コンパニオン アプリは、web-push ライブラリを使用してメッセージを送信する単なるノードサーバーです。GitHub の web-push-libs 組織を参照して、プッシュ メッセージの送信に使用できるライブラリを確認することをおすすめします。これにより、push メッセージをトリガーするための多くの詳細が処理されます。

コンパニオン サイトのすべてのコードは、こちらで確認できます。

10. ユーザーの登録を解除する

完成したコード

欠けているのが、ユーザーの push 登録を解除できる機能です。そのためには、PushSubscriptionunsubscribe() を呼び出す必要があります。

scripts/main.js ファイルに戻り、initializeUI()pushButton クリック リスナーを次のように変更します。

pushButton.addEventListener('click', function() {
  pushButton.disabled = true;
  if (isSubscribed) {
    unsubscribeUser();
  } else {
    subscribeUser();
  }
});

ここでは、新しい関数 unsubscribeUser() を呼び出します。この関数では、現在のサブスクリプションを取得し、それに対して unsubscribe() を呼び出します。以下のコードを scripts/main.js に追加します。

function unsubscribeUser() {
  swRegistration.pushManager.getSubscription()
  .then(function(subscription) {
    if (subscription) {
      return subscription.unsubscribe();
    }
  })
  .catch(function(error) {
    console.log('Error unsubscribing', error);
  })
  .then(function() {
    updateSubscriptionOnServer(null);

    console.log('User is unsubscribed.');
    isSubscribed = false;

    updateBtn();
  });
}

この関数を詳しく見ていきましょう。

まず、getSubscription() を呼び出して現在のサブスクリプションを取得します。

swRegistration.pushManager.getSubscription()

PushSubscription が存在する場合は、それで解決される Promise を返します。それ以外の場合は null を返します。サブスクリプションがある場合は、そのサブスクリプションに対して unsubscribe() を呼び出すと、PushSubscription が無効になります。

swRegistration.pushManager.getSubscription()
.then(function(subscription) {
  if (subscription) {
    // TODO: Tell application server to delete subscription
    return subscription.unsubscribe();
  }
})
.catch(function(error) {
  console.log('Error unsubscribing', error);
})

完了までに時間がかかることがあるため、unsubscribe() を呼び出すと Promise が返されます。その Promise を返すと、チェーン内の次の then()unsubscribe() が完了するまで待機します。また、unsubscribe() を呼び出してエラーが発生した場合に備えて、キャッチ ハンドラも追加します。その後、UI を更新できます。

.then(function() {
  updateSubscriptionOnServer(null);

  console.log('User is unsubscribed.');
  isSubscribed = false;

  updateBtn();
})

実際に試す

ウェブアプリで [Enable Push Messaging] または [Disable Push Messaging] をクリックできるようになります。登録および登録解除されたことがログに表示されます。

81a07119235b53da.png

11. 終了

以上で、この Codelab は終了です。

この Codelab では、ウェブアプリにプッシュ通知を追加して実行する方法を紹介しました。ウェブ通知の機能について詳しくは、こちらのドキュメントをご覧ください

サイトにプッシュ通知を導入する場合は、古いブラウザや、GCM を使用する標準に準拠していないブラウザに対するサポートを追加することをおすすめします。詳しくはこちらをご覧ください

参考資料

関連するブログ投稿