Firebase (FCM)を利用して Google Chrome にプッシュ通知を送信する

Firebase (Firebase Cloud Messaging(FCM))を利用して Google Chrome にプッシュ通知を送信する手順を紹介します。

事前準備

SSL対応したWebサーバーの準備

プッシュ通知の登録をするWebページはSSLのページ(https)である必要があります。SSLに対応したWebサーバーを用意します。

curl コマンド

テストのためにcurlコマンドを利用します。curlをインストールしておきます。curlのインストール手順は、こちらの記事を参照してください。

Firebaseの設定

Firebaseへのサインアップと、プロジェクトの作成をします。手順の詳細はこちらの記事を参照してください。

プログラム

Webサーバー

WebサーバーにHTMLファイル、マニフェストファイル、サービスワーカーのJavaScriptファイルを設置します。

index.html

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title></title>
  <meta charset="utf-8" />
  <link rel="manifest" href="manifest.json">
  <script type="text/javascript">

    var API_KEY = 'AIza..............................xw2ns';
    var GCM_ENDPOINT = 'https://android.googleapis.com/gcm/send';

    function onLoad() {
      if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('./service-worker.js').then(initialiseState);
      } else {
        window.Demo.debug.log('Service workers aren\'t supported in this browser.');
      }
    }
    
    function initialiseState() {
      if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
        window.Demo.debug.log('Notifications aren\'t supported.');
        return;
      }

      if (Notification.permission === 'denied') {
        window.Demo.debug.log('The user has blocked notifications.');
        return;
      }

      if (!('PushManager' in window)) {
        window.Demo.debug.log('Push messaging isn\'t supported.');
        return;
      }

      navigator.serviceWorker.ready.then(ServiceWorkerRegistInit);
    }

    function ServiceWorkerRegistInit(serviceWorkerRegistration) {
      serviceWorkerRegistration.pushManager.getSubscription().then(SubscriptionProcInit);
    }

    function SubscriptionProcInit(subscription) {
      if (!subscription) {
        return;
      }

      sendSubscriptionToServer(subscription);
      /* Push が利用可能 */
    }


    /* Subscribe */
    function SubscribePushNotification() {
      navigator.serviceWorker.ready.then(ServiceWorkerRegist);
    }

    function ServiceWorkerRegist(serviceWorkerRegistration) {
      /* Subscription 処理の開始前処理を記述 */

      var subscribe = serviceWorkerRegistration.pushManager.subscribe({ userVisibleOnly: true });
      subscribe.then(SubscriptionProc);
    }

    function SubscriptionProc(subscription) {
      /* Subscription 完了時の処理を記述 */

      return sendSubscriptionToServer(subscription);
    }

    function sendSubscriptionToServer(subscription) {
      console.log('TODO: Implement sendSubscriptionToServer()');
      var mergedEndpoint = endpointWorkaround(subscription);

      /* サーバーに各種情報を送信 (今回は画面表示のため、処理なし)*/

      /* 画面表示 */
      showCurlCommand(mergedEndpoint);
      showSubscriptionInfo(subscription);
    }

    /* エンドポイントの情報生成 */
    function endpointWorkaround(pushSubscription) {
      if (pushSubscription.endpoint.indexOf('https://android.googleapis.com/gcm/send') !== 0) {
        return pushSubscription.endpoint;
      }

      var mergedEndpoint = pushSubscription.endpoint;
      if (pushSubscription.subscriptionId &&
        pushSubscription.endpoint.indexOf(pushSubscription.subscriptionId) === -1) {
        mergedEndpoint = pushSubscription.endpoint + '/' +
          pushSubscription.subscriptionId;
      }
      return mergedEndpoint;
    }


    /* UnSubscribe */

    function UnSubscribePushNotification() {
      navigator.serviceWorker.ready.then(ServiceWorkerUnregist);
    }

    function ServiceWorkerUnregist(serviceWorkerRegistration) {
      /* UnSubscription 処理の開始前処理を記述 */

      var subscribe = serviceWorkerRegistration.pushManager.getSubscription();
      subscribe.then(UnSubscriptionProc);
    }

    function UnSubscriptionProc(subscription) {
      if (!subscription) {
        return;
      }
      subscription.unsubscribe().then(UnSubscriptionComplete);
    }

    function UnSubscriptionComplete() {
      /* UnSubscription 処理完了後の処理を記述 */
    }



    /* 情報の画面表示 */
    function showCurlCommand(mergedEndpoint) {
      if (mergedEndpoint.indexOf(GCM_ENDPOINT) !== 0) {
        window.Demo.debug.log('This browser isn\'t currently ' +
          'supported for this demo');
        return;
      }

      var endpointSections = mergedEndpoint.split('/');
      var subscriptionId = endpointSections[endpointSections.length - 1];

      var curlCommand = 'curl --header "Authorization: key=' + API_KEY +
        '" --header Content-Type:"application/json" ' + GCM_ENDPOINT +
        ' -d "{\\"registration_ids\\":[\\"' + subscriptionId + '\\"]}"';

      var frame = document.getElementById("command");
      frame.innerText = curlCommand;
    }

    function showSubscriptionInfo(subscription) {
      console.log(subscription);
      console.log(subscription.getKey('p256dh'));
      console.log(subscription.getKey('auth'));

      var frame = document.getElementById("info");
      frame.innerHTML = "Endpoint : " + subscription.endpoint + "<br/>";
      frame.innerHTML += "p256dh Key : " + btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('p256dh')))) + "<br/>";
      frame.innerHTML += "Authentication Secret : " + btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('auth')))) + "<br/>";
    }
  </script>
</head>
<body onload="onLoad();">
  <p>Demo Page</p>
  <a href="#" onclick="SubscribePushNotification();">プッシュ通知の購読</a><br />
  <a href="#" onclick="UnSubscribePushNotification();">プッシュ通知の購読解除</a><br />
  <hr />
  <p id="command"></p>
  <hr />
  <p id="info"></p>
</body>
</html>

manifest.json

{
  "name": "Push Demo",
  "short_name": "Push Demo",
  "icons": [
    {
      "src": "/demo/push/non-data/images/icon-192x192.png",
      "sizes": "192x192"
    }
  ],
  "start_url": "./index.html",
  "display": "standalone",
  "gcm_sender_id": "540000000068"
}

service-worker.js

'use strict';

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  var title = 'メッセージのタイトル';
  var body = 'プッシュメッセージを受信';
  var icon = '/demo/push/non-data/images/icon-192x192.png';
  var tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );
});

self.addEventListener('notificationclick', function(event) {
  console.log('On notification click: ', event.notification.tag);
  event.notification.close();

  event.waitUntil(clients.matchAll({
    type: 'window'
  }).then(function(clientList) {
    for (var i = 0; i < clientList.length; i++) {
      var client = clientList[i];
      if (client.url === '/' && 'focus' in client) {
        return client.focus();
      }
    }
    if (clients.openWindow) {
      return clients.openWindow('/');
    }
  }));
});

キーの設定

index.html

var API_KEY = 'AIza..............................xw2ns';
の部分には、Firebase の [ウェブアプリに Firebase を追加]ダイアログで取得できるJavaScriptコードのapiKeyの値、(下記(1)の値)を記載します。

manifest.json

"gcm_sender_id": "540000000068"
の部分には、Firebase の [ウェブアプリに Firebase を追加]ダイアログで取得できるJavaScriptコードのmessagingSenderIdの値、(下記(2)の値)を記載します。


Firebaseの管理画面で取得できる 設定JavaScriptコード
<script src="https://www.gstatic.com/firebasejs/3.4.1/firebase.js"></script>
<script>
  // Initialize Firebase
  var config = {
    apiKey: " (1) ",
    authDomain: "ipentecdemo.firebaseapp.com",
    databaseURL: "https://ipentecdemo.firebaseio.com",
    storageBucket: "ipentecdemo.appspot.com",
    messagingSenderId: " (2) "
  };
  firebase.initializeApp(config);
</script>

解説

HTMLファイルでは、ページが読み込まれると、onLoad()関数が実行され、サービスワーカーを登録します。navigator.serviceWorker.register()メソッドの呼び出しで、登録します。登録完了後、initialiseState() 関数で、パーミッションチェックや動作対応環境かのチェックをします。問題が無ければ、navigator.serviceWorker.ready()メソッドを呼び出し、ワーカーを待機状態にします。待機状態完了後にServiceWorkerRegistInit()関数が呼び出されます。ServiceWorkerRegistInit()では、pushManager.getSubscription()メソッドを呼び出し、subscriptionオブジェクトを取得します。getSubscription()メソッドの完了時に、SubscriptionProcInit()関数が呼び出され、引数に取得したsubscriptionオブジェクトが与えられます。
subscriptionオブジェクトがnullであれば、getSubscription()メソッドでサブスクリプションオブジェクトが取得できなかったため、購読状態にはなっていないことが確認できます。subscriptionオブジェクトが有効であれば、sendSubscriptionToServer()関数を呼び出し、エンドポイントやAPIキーなどをサーバーに送信します。(今回の実装では画面に表示するのみです。)

[プッシュ通知の購読]リンクがクリックされた場合は、navigator.serviceWorker.ready()メソッドを呼び出し、ワーカーを待機状態にします。待機完了時点でServiceWorkerRegist()関数が呼び出されますので、pushManager.subscribe()メソッドを呼び出し、このプッシュ通知を購読状態に設定します。購読状態に設定できた時点で、SubscriptionProc()関数が呼び出され、sendSubscriptionToServer()関数を呼び出し、エンドポイントやAPIキーなどをサーバーに送信します。(今回の実装では画面に表示するのみです。)

一方[プッシュ通知の購読解除]がクリックされた場合は、navigator.serviceWorker.ready()メソッドを呼び出し、ワーカーを待機状態にします。待機完了時点でServiceWorkerUnregist()関数が呼び出されますので、pushManager.getSubscription() メソッドにより、subscriptionオブジェクトを取得します。subscriptionオブジェクトの取得が完了した時点で、UnSubscriptionProc()関数が呼び出されます。subscriptionオブジェクトが取得できていれば購読状態であるため、subscriptionオブジェクトのunsubscribe()メソッドを呼び出し、購読状態を解除します。購読状態の解除が完了すると、UnSubscriptionComplete()関数が実行されます。

sendSubscriptionToServer()はエンドポイントやAPIキーなどの情報をサーバーに送信するための関数ですが、今回はサーバーへの送信はせずに、画面に表示する動作にします。サーバーへのキーの設定は手動で実施します。
showCurlCommand() 関数はプッシュ通知を実行するためcurlのコマンド文字列を画面に表示します。showSubscriptionInfo()関数は、エンドポイントやAPIキーなどそれぞれの情報を画面に表示します。

配置

配置は、通常のHTMLファイルと同様にサーバーにアップロードするのみです。IISの初期設定では、json拡張子のMIMEが定義されていないため、MIMEの設定を追加します。

配置は https://www.ipentec.com/demo/push/non-data/ のURLとなるディレクトリに配置します。

実行結果

上記のHTMLファイル(index.html)をWebブラウザで表示します。下図のウィンドウが表示されます。


画面の[プッシュ通知の購読]リンクをクリックします。下図の"通知の表示"の許可ダイアログが表示されます。[許可]ボタンをクリックします。


処理ができると、画面の下部に情報が表示されます。上部の枠が、プッシュを送信するための curl のコマンドになります。下部の枠は、プッシュのためのEndpoint, p256dh Key, Authentication Secret の値が表示されます。


curl コマンドを利用して、プッシュ通知を送信します。先の画面のcurl コマンドをコピーして、コマンドプロンプトに貼りつけて実行します。


正常に実行できると、下図の画面が表示されます。"success"の値が"1"になっていることが確認できます。


Chromeで先のページを表示しているPCの画面の右下に、プッシュ通知のポップアップウィンドウが表示されます。


ポップアップウィンドウをクリックするとWebブラウザのウィンドウが開き、サイトに遷移します。

プッシュ配信サーバー

curlコマンドを実行するスクリプトを配置して実行することでプッシュ配信サーバーを作成できますが、今回は、C#のアプリケーションでプッシュを配信するプログラムも作成します。

FormSimplePush

下図のUIを作成します。

FormSimplePush.cs

下記のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;

namespace PushServerApplication
{
  public partial class FormSimplePush : Form
  {
    public FormSimplePush()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      SendNotification(textBox_endpoint_service.Text, textBox_endpoint.Text, textBox_apikey.Text);
    }

    public static bool SendNotification(string endpoint_service, string endpoint, string apikey)
    {
      HttpRequestMessage Request = new HttpRequestMessage(HttpMethod.Post, endpoint_service);

      Request.Headers.TryAddWithoutValidation("Authorization", "key=" + apikey);

      string[] endpoint_split = endpoint.Split('/');
      string registrationID = endpoint_split[endpoint_split.Length - 1].Trim();
      string contentStr = "{\"registration_ids\":[\"" + registrationID + "\"]}";
      Request.Content = new StringContent(contentStr);
      Request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
      Request.Content.Headers.ContentLength = contentStr.Length;

      HttpClient HC = new HttpClient();
      HttpResponseMessage hrm = HC.SendAsync(Request).Result;
      return hrm.StatusCode == HttpStatusCode.Created;
    }
  }
}

解説

SendNotification()メソッドでプッシュ通知を送信します。
endpoint_serviceで指定したURL (今回の場合は、'https://android.googleapis.com/gcm/send') にアクセスします。アクセスメソッドはPOSTを指定します。
リクエストヘッダには、Authorizationのキーに、key=(APIキー)の値を設定します。POSTで送信するコンテンツは、
{"registration_ids":["(登録ID)"]}
形式のJSONを送信します。(登録ID)の部分は、エンドポイントURLの https://android.googleapis.com/gcm/send/ 以下の文字列になります。
https://android.googleapis.com/gcm/send/ezrpSNyJu4c:APA91bHCOy5uwfACvz4PvbF_FJfuQoGwPGoMds12hQLw6Cot4OMuAnlCzeRq8gugoyOXYkPBlA17C4naGfPS_nixpaXmPGqnKBR5ypsKWvSNqOacNiPnL6AsSiqe1cmokav3krB3YBG_
エンドポイントのURLが上記であれば、(登録ID)は
ezrpSNyJu4c:APA91bHCOy5uwfACvz4PvbF_FJfuQoGwPGoMds12hQLw6Cot4OMuAnlCzeRq8gugoyOXYkPBlA17C4naGfPS_nixpaXmPGqnKBR5ypsKWvSNqOacNiPnL6AsSiqe1cmokav3krB3YBG_
になります。

ContentType は"application/json"を指定し、ContentLengthの値も設定して送信します。

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


APIキーとエンドポイントのURLを入力します。APIキーはindex.htmlの"var API_KEY" に指定した値と同じもの入力します。 エンドポイントは、curlコマンドを取得した画面で下部に表示された、エンドポイントのURLを入力します。入力後、[Push]ボタンをクリックします。


正常に処理できると、デスクトップ画面の右下に通知ウィンドウが表示されます。


サーバーからChromeにプッシュ通知を送信することができました。

注意

Chromeのウィンドウが1枚も開いていない場合は、Chromeが起動するまでプッシュ通知のウィンドウは表示されません。リアルタイムに通知を受けとるには、Chromeが起動している必要があります。

補足1:通知許可ダイアログの再表示、拒否の解除

通知許可ダイアログで、
  • [拒否]ボタンを押してしまった
  • 動作確認のため、再度、通知許可ダイアログを表示させたい
といった場合があります。通知許可ダイアログは一度設定すると、その設定で固定され、以後確認ダイアログや設定変更の確認は無くなります。
通知の設定を解除して初期化したい場合や、設定を変更したい場合は、こちらの記事の手順で初期化、変更できます。

補足2:メッセージ本文をプッシュで送信したい

今回のプログラムでは、プッシュ通知があった際に、あらかじめ設定されている文言を通知ウィンドウに表示しています。通知メッセージ本文をサーバー側から指定する場合はこちらの記事を参照してください。

著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2024-01-07
作成日: 2016-10-07
iPentec all rights reserverd.