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

プッシュ メッセージは、ユーザーとの再エンゲージメントを促すための簡単で効果的な方法です。この Codelab では、ウェブアプリにプッシュ通知を追加する方法を学びます。

学習内容

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

必要なもの

  • Chrome 52 以降
  • Web Server for Chrome、または任意のウェブサーバー
  • テキスト エディタ
  • HTML、CSS、JavaScript、Chrome DevTools の基本的な知識
  • サンプルコード(セットアップをご覧ください)

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

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

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

ソースコードをダウンロード

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

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

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

Chrome 用のウェブサーバーをインストールする

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

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

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

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

[オプション] で、次の図のように [index.html を自動的に表示] の横のチェックボックスをオンにします。

次に、[Web Server: STARTED] の切り替えを左にスライドしてから右に戻して、サーバーを停止して再起動します。

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

00-push-codelab.png

サービス ワーカーを常に更新する

開発中は、サービス ワーカーが常に最新の状態であり、最新の変更が反映されていることを確認すると便利です。

Chrome でこの機能を設定するには:

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

完成したコード

app ディレクトリに、sw.js という名前の空のファイルがあることを確認します。このファイルがサービス ワーカーになります。今のところは空のままで構いません。後でコードを追加します。

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

app/index.html ページが読み込まれますscripts/main.js。この JavaScript ファイルでサービス ワーカーを登録します。

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

このコードは、ブラウザがサービス ワーカーとプッシュ メッセージに対応しているかどうかを確認します。サポートされている場合、コードは sw.js ファイルを登録します。

試してみる

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

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

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

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

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

push-codelab-04-companion.png

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

const applicationServerPublicKey = '<Your Public Key>';

重要: 秘密鍵をウェブアプリに配置しないでください。

完成したコード

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

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;
}

この関数は、ユーザーが登録しているかどうかに応じてボタンを有効にし、ボタンのテキストを変更します。

最後に、サービス ワーカーが main.js に登録されたときに initializeUI() を呼び出します。

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

  swRegistration = swReg;
  initializeUI();
})

試してみる

Push Codelab タブを更新します。[プッシュ メッセージを有効にする] ボタンが有効(クリック可能)になり、コンソールに User is NOT subscribed が表示されるはずです。

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

完成したコード

現時点では、[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();
  });
}

ユーザーがボタンをクリックしたら、ボタンを無効にします。プッシュ メッセージの登録には時間がかかる場合があるため、ユーザーがボタンをもう一度クリックできないようにするためです。

ユーザーが現在定期購入していない場合は、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 の上部にあります。

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

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

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

subscribe() を呼び出すと、次の手順の後に解決される Promise が返されます。

  1. ユーザーが通知の表示を許可している。
  2. ブラウザがプッシュ サービスにネットワーク リクエストを送信し、PushSubscription の生成に必要なデータを取得しました。

これらの手順が成功した場合、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] タブに移動し、ページを更新してボタンをクリックします。次のような権限プロンプトが表示されます。

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

完成したコード

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

このシナリオを処理する場所は、明らかに 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 をクリックし、[通知] 権限を [グローバル デフォルトを使用(確認)] に変更する必要があります。

この設定を変更したら、ページを更新して [プッシュ メッセージを有効にする] ボタンをクリックし、権限ダイアログで [ブロック] を選択します。ボタンが無効になり、「プッシュ メッセージがブロックされました」というテキストが表示されます。

この変更により、考えられる権限のシナリオを考慮したうえで、ユーザーを登録できるようになりました。

完成したコード

バックエンドからプッシュ メッセージを送信する方法を学ぶ前に、登録したユーザーがプッシュ メッセージを受信したときに実際に何が起こるかを検討する必要があります。

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

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

このコードをステップ実行してみましょう。イベント リスナーを追加して、サービス ワーカーで push イベントをリッスンします。

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

(Web Worker を使用したことがない場合は、self はおそらく初めてでしょう。サービス ワーカー ファイルでは、self はサービス ワーカー自体を参照します)。

プッシュ メッセージを受信すると、イベント リスナーが呼び出され、サービス ワーカーの 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 を受け取ります。

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

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

プッシュ イベントのステップ実行が完了したので、プッシュ イベントをテストしてみましょう。

試してみる

サービス ワーカーのプッシュ イベント処理を使用すると、偽のプッシュ イベントをトリガーして、メッセージを受信したときに何が起こるかをテストできます。

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

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

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

完成したコード

これらの通知のいずれかをクリックしても、何も起こりません。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() は、新しいウィンドウまたはタブが表示される前にブラウザがサービス ワーカーを終了しないようにします。

試してみる

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

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

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

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

次に、コンパニオン サイトの [Subscription to Send To] テキスト ボックスに貼り付けます。

[送信するテキスト] に、プッシュ メッセージとともに送信する文字列を追加します。

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

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

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

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

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

完成したコード

プッシュ通知の登録を解除する機能がありません。これを行うには、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 が存在する場合は 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();
})

試してみる

ウェブアプリで [プッシュ メッセージを有効にする] または [プッシュ メッセージを無効にする] を押すと、ログにユーザーの登録と登録解除が表示されます。

これでこの Codelab は完了です。

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

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

参考資料

関連するブログ投稿