ウェブプッシュ ライブラリでのメッセージの送信

ウェブプッシュを使用する際の課題の一つは、プッシュ メッセージのトリガーが非常に「扱いづらい」ことです。push メッセージをトリガーするには、アプリケーションがウェブ push プロトコルに沿って push サービスに POST リクエストを行う必要があります。すべてのブラウザでプッシュを使用するには、VAPID(アプリケーション サーバーキー)を使用する必要があります。この API では基本的に、アプリケーションがユーザーにメッセージを送信できることを証明する値を含むヘッダーを設定する必要があります。push メッセージでデータを送信するには、データを暗号化し、ブラウザがメッセージを正しく復号できるように、特定のヘッダーを追加する必要があります。

push をトリガーする主な問題は、問題が発生した場合に問題を診断するのが難しいことです。これは時間とともに対応ブラウザの拡大とともに改善されていますが、決して容易なことではありません。このため、push メッセージの暗号化、フォーマット、トリガーの処理にはライブラリを使用することを強くおすすめします。

ライブラリの機能について詳しく知りたい場合は、次のセクションで説明します。ここでは、サブスクリプションを管理し、既存のウェブ push ライブラリを使用して push リクエストを行う方法について説明します。

このセクションでは、web-push ノード ライブラリを使用します。他の言語にも違いはあるものの、それほど類似していないわけではありません。Node は JavaScript であり、読者にとって最もアクセスしやすいものであるため、こちらに注目しています。

手順は次のとおりです。

  1. バックエンドに定期購入を送信し、保存します。
  2. 保存済みのサブスクリプションを取得し、push メッセージをトリガーします。

定期購入を保存しています

データベースから PushSubscription を保存してクエリを実行する方法は、サーバー側の言語とデータベースの選択によって異なりますが、その方法の例を見ると役立つ場合があります。

デモのウェブページでは、シンプルな POST リクエストを行うことで、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.');
      }
    });
}

このデモの Express サーバーには、/api/save-subscription/ エンドポイントに対応するリクエスト リスナーがあります。

app.post('/api/save-subscription/', function (req, res) {

このルートでは、リクエストに問題がなく、ガベージでいっぱいでないことを確認するためだけに、サブスクリプションを検証します。

const isValidSaveRequest = (req, res) => {
  // Check the request body has at least an endpoint.
  if (!req.body || !req.body.endpoint) {
    // Not a valid subscription.
    res.status(400);
    res.setHeader('Content-Type', 'application/json');
    res.send(
      JSON.stringify({
        error: {
          id: 'no-endpoint',
          message: 'Subscription must have an endpoint.',
        },
      }),
    );
    return false;
  }
  return true;
};

定期購入が有効な場合は、保存して、適切な JSON レスポンスを返す必要があります。

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

このデモでは、nedb を使用してサブスクリプションを保存します。これは単純なファイルベースのデータベースですが、任意のデータベースを使用できます。ここではセットアップが不要なため こちらのみを使用します本番環境では、より信頼性の高いものを使用する必要があります。(私は古い MySQL を使い続ける傾向があります)。

function saveSubscriptionToDatabase(subscription) {
  return new Promise(function (resolve, reject) {
    db.insert(subscription, function (err, newDoc) {
      if (err) {
        reject(err);
        return;
      }

      resolve(newDoc._id);
    });
  });
}

push メッセージの送信

push メッセージを送信するには、最終的にユーザーにメッセージを送信するプロセスをトリガーするイベントが必要です。一般的な方法は、push メッセージを構成してトリガーできる管理ページを作成することです。ただし、ローカルで実行するプログラムを作成したり、PushSubscription のリストにアクセスしてコードを実行して push メッセージをトリガーしたりすることもできます。

デモには「admin like」ページがあり、そこからプッシュをトリガーできます。これは単なるデモであるため 公開ページです

デモを動作させるための手順を 1 つずつ説明していきます。これは、Node を初めて使用する人も含め、誰でも従うことができるベビーステップです。

ユーザーの登録については、subscribe() オプションに applicationServerKey を追加する方法を説明しました。この秘密鍵はバックエンドで必要になります。

このデモでは、次のようにこれらの値を Node アプリに追加します(退屈なコードは知っていましたが、魔法のようなものはないことを知っておいてください)。

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

次に、Node サーバー用の web-push モジュールをインストールする必要があります。

npm install web-push --save

次に、Node スクリプトで、次のように web-push モジュールを要求します。

const webpush = require('web-push');

これで、web-push モジュールの使用を開始できるようになりました。まず、web-push モジュールにアプリケーション サーバーキーを伝える必要があります。(仕様の名前であるため、この鍵は VAPID キーとも呼ばれます)。

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

webpush.setVapidDetails(
  'mailto:web-push-book@gauntface.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey,
);

「mailto:」文字列も入力されています。この文字列は、URL または mailto メールアドレスにする必要があります。この情報は、実際には push をトリガーするリクエストの一部としてウェブ push サービスに送信されます。これは、ウェブプッシュ サービスがセンダーと通信する必要がある場合に、それを可能にする情報を持っているためです。

これで、web-push モジュールを使用する準備が整いました。次のステップとして、push メッセージをトリガーします。

このデモでは、偽装管理パネルを使用してプッシュ メッセージをトリガーします。

管理ページのスクリーンショット。

[push メッセージをトリガー] ボタンをクリックすると、/api/trigger-push-msg/ に POST リクエストが送信されます。これは、バックエンドがプッシュ メッセージを送信するためのシグナルです。このエンドポイントのエクスプレス ルートを作成します。

app.post('/api/trigger-push-msg/', function (req, res) {

このリクエストを受信すると、データベースからサブスクリプションを取得し、それぞれに対して push メッセージをトリガーします。

return getSubscriptionsFromDatabase().then(function (subscriptions) {
  let promiseChain = Promise.resolve();

  for (let i = 0; i < subscriptions.length; i++) {
    const subscription = subscriptions[i];
    promiseChain = promiseChain.then(() => {
      return triggerPushMsg(subscription, dataToSend);
    });
  }

  return promiseChain;
});

その後、関数 triggerPushMsg() で web-push ライブラリを使用して、指定されたサブスクリプションにメッセージを送信できます。

const triggerPushMsg = function (subscription, dataToSend) {
  return webpush.sendNotification(subscription, dataToSend).catch((err) => {
    if (err.statusCode === 404 || err.statusCode === 410) {
      console.log('Subscription has expired or is no longer valid: ', err);
      return deleteSubscriptionFromDatabase(subscription._id);
    } else {
      throw err;
    }
  });
};

webpush.sendNotification() を呼び出すと、Promise が返されます。メッセージが正常に送信された場合、Promise は解決され、何もする必要はありません。Promise が拒否された場合は、エラーを調べて、PushSubscription がまだ有効かどうかを確認できます。

push サービスで発生したエラーの種類を判断するには、ステータス コードを確認することをおすすめします。エラー メッセージは push サービスによって異なり、他よりも役に立つメッセージもあります。

この例では、ステータス コード 404410(Not Found と Gone の HTTP ステータス コード)をチェックします。いずれかの通知を受け取った場合、定期購入が期限切れになっているか、無効になっていることを意味します。このような場合は、データベースからサブスクリプションを削除する必要があります。

その他のエラーが発生した場合は、throw err のみを行います。これにより、triggerPushMsg() から返された Promise が拒否されます。

他のステータス コードについては、次のセクションでウェブ push プロトコルについて詳しく見ていきます。

サブスクリプションをループした後、JSON レスポンスを返す必要があります。

.then(() => {
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-send-messages',
    message: `We were unable to send messages to all subscriptions : ` +
        `'${err.message}'`
    }
}));
});

ここまで、主な実装手順について説明してきました。

  1. ウェブページからバックエンドに定期購入を送信し、データベースに保存できるようにする API を作成します。
  2. push メッセージの送信をトリガーする API を作成します(この場合は、偽装された管理パネルから呼び出される API)。
  3. バックエンドからすべてのサブスクリプションを取得し、web-push ライブラリのいずれかを使用して各サブスクリプションにメッセージを送信します。

バックエンド(Node、PHP、Python など)に関係なく、push の実装手順は同じです。

次は、これらのウェブ push ライブラリが何をしてくれるのでしょうか。

次のステップ

Codelab