ウェブアプリへのプッシュ通知の追加

プッシュ メッセージングは、ユーザーに再アプローチするためのシンプルで効果的な手段です。この Codelab では、ウェブアプリにプッシュ通知を追加する方法について説明します。

ラボの内容

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

必要なもの

  • Chrome 52 以降
  • 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 Server ウェブサーバーと適切に連携するように設計されています。このアプリをまだインストールしていない場合は、Chrome ウェブストアから入手できます。

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

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

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

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

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

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

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

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

00-push-codelab.png

常に Service Worker を更新する

開発時に、Service Worker を常に最新の状態に保ち、最新の変更を行うと効果的です。

Chrome で設定するには:

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

完成したコード

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

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

app/index.html ページに scripts/main.js が読み込まれます。Service Worker をこの 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';
}

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

お試しください

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

Chrome DevTools で、次のような Service Worker is registered message を確認します。

アプリケーション サーバーキーの取得

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

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

push-codelab-04-companion.png

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

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

ユーザーが定期購入しているかどうかによって、ボタンを有効にし、ボタンのテキストを変更します。

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

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

  swRegistration = swReg;
  initializeUI();
})

お試しください

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

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

完成したコード

現時点では、[プッシュ メッセージングを有効にする] ボタンはほとんど機能しません。この問題を修正しましょう。

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 の先頭にあります。

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

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

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

subscribe() を呼び出すと、次の手順で解決される 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');
  }
}

お試しください

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

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

完成したコード

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

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

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

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

完成したコード

バックエンドから push メッセージを送信する方法について学習する前に、登録済みユーザーが push メッセージを受信したときに実際に何が起きるかを検討する必要があります。

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

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 が解決されるまで、ブラウザで 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] ボタンをクリックします。

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

注: この手順で問題が解決しない場合は、DevTools の [アプリケーション] パネルの [登録解除] リンクで 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() は、新しいウィンドウまたはタブが表示される前に、ブラウザが Service Worker を終了しないようにします。

お試しください

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

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

通常は、ウェブページからバックエンドに定期購入を送信する必要があります。その後、バックエンドは、サブスクリプション内のエンドポイントに API 呼び出しを行って push メッセージをトリガーします。

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

次に、[登録先] テキスト領域のコンパニオン サイトに貼り付けます。

[Text to Send] に、push メッセージで送信する文字列を追加します。

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

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

これによって、データの送受信をテストし、その結果として通知を操作する機会が得られます。

コンパニオン アプリは、web-push ライブラリを使用してメッセージを送信している単なるノードサーバーです。GitHub の web-push-libs 組織を参照して、プッシュ メッセージの送信に使用できるライブラリを確認する価値があります。これにより、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 で解決され、存在しない場合は Promise が返されます。サブスクリプションがある場合、その 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();
})

お試しください

ウェブアプリで [Push Messaging を有効にする] または [Push Messaging を無効にする] が押されている必要があります。ログには、ユーザーが登録または登録解除したことが表示されます。

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

この Codelab では、ウェブアプリにプッシュ通知を追加して利用を開始する方法を確認しました。ウェブ通知でできることについて詳しくは、各種ドキュメントをご覧ください。

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

参考資料

関連するブログ投稿