まず、push メッセージを送信する権限をユーザーから取得します。次に、PushSubscription
を入手します。
これを行うための JavaScript API は比較的単純なので、ロジックのフローを順を追って見ていきます。
機能検出
まず、現在のブラウザが実際にプッシュ メッセージングをサポートしているかどうかを確認する必要があります。push がサポートされているかどうかは、2 つの簡単なチェックでチェックできます。
- navigator で serviceWorker を確認します。
- window で PushManager を確認します。
if (!('serviceWorker' in navigator)) {
// Service Worker isn't supported on this browser, disable or hide UI.
return;
}
if (!('PushManager' in window)) {
// Push isn't supported on this browser, disable or hide UI.
return;
}
Service Worker と push メッセージングの両方でブラウザのサポートが急速に拡大していますが、両方の機能検出と段階的な機能強化を常に行うことをおすすめします。
Service Worker を登録する
Feature Detection では、Service Worker と push の両方がサポートされていることがわかります。次のステップでは、Service Worker を「登録」します。
Service Worker を登録すると、その Service Worker ファイルがどこにあるかがブラウザに通知されます。 ファイルは JavaScript のままですが、ブラウザは Service Worker API(push を含む)への「アクセス権を付与」します。正確には、ブラウザは Service Worker 環境でファイルを実行します。
Service Worker を登録するには、navigator.serviceWorker.register()
を呼び出してファイルのパスを渡します。次のようになります。
function registerServiceWorker() {
return navigator.serviceWorker
.register('/service-worker.js')
.then(function (registration) {
console.log('Service worker successfully registered.');
return registration;
})
.catch(function (err) {
console.error('Unable to register service worker.', err);
});
}
この関数は、Service Worker ファイルがあることとその場所をブラウザに通知します。この場合、Service Worker ファイルは /service-worker.js
にあります。バックグラウンドでは、ブラウザは register()
を呼び出した後に次の処理を行います。
Service Worker ファイルをダウンロードします。
JavaScript を実行します。
すべてが正しく実行され、エラーがなければ、
register()
によって返された Promise が解決されます。なんらかのエラーがある場合、Promise は拒否されます。
register()
で拒否された場合は、Chrome DevTools で JavaScript に入力ミスやエラーがないか再度確認してください。
register()
が解決されると、ServiceWorkerRegistration
が返されます。この登録を使用して PushManager API にアクセスします。
PushManager API ブラウザの互換性
権限をリクエストしています
Service Worker を登録し、ユーザーを登録する準備ができました。次のステップでは、プッシュ メッセージを送信する権限をユーザーから取得します。
権限を取得するための API は比較的シンプルですが、欠点は、API が最近コールバックの取得から Promise を返すように変更されたことです。この問題は、現在のブラウザで実装されている API のバージョンがわからないため、両方を実装して両方を処理する必要があります。
function askPermission() {
return new Promise(function (resolve, reject) {
const permissionResult = Notification.requestPermission(function (result) {
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
}).then(function (permissionResult) {
if (permissionResult !== 'granted') {
throw new Error("We weren't granted permission.");
}
});
}
上記のコードでは、重要なコード スニペットは Notification.requestPermission()
の呼び出しです。このメソッドは、ユーザーにプロンプトを表示します。
ユーザーが [Allow] または [Block] を押すか、プロンプトを閉じるだけで権限プロンプトを操作すると、結果は 'granted'
、'default'
、'denied'
の文字列として返されます。
上記のサンプルコードでは、権限が付与されると askPermission()
によって返される Promise は解決され、権限が付与されるとエラーがスローされ、Promise が拒否されます。
対処する必要がある特殊なケースとして、ユーザーが [ブロック] ボタンをクリックした場合は、その場合、ウェブアプリはユーザーに権限を再度求めることができなくなります。権限の状態を変更してアプリを手動で「ブロック解除」する必要があります。これは設定パネルに埋め込まれています。ユーザーに許可を求める方法とタイミングについて慎重に検討してください。ユーザーが [ブロック] をクリックした場合、決定を覆すのは簡単ではないためです。
幸いなことに、ほとんどのユーザーは、なぜ権限が求められる理由を理解しているのであれば、喜んで権限を付与できます。
後で、いくつかの有名なサイトが許可を求める方法を見ていきます。
PushManager でユーザーをサブスクライブする
Service Worker を登録して権限を取得したら、registration.pushManager.subscribe()
を呼び出してユーザーを登録できます。
function subscribeUserToPush() {
return navigator.serviceWorker
.register('/service-worker.js')
.then(function (registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
),
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function (pushSubscription) {
console.log(
'Received PushSubscription: ',
JSON.stringify(pushSubscription),
);
return pushSubscription;
});
}
subscribe()
メソッドを呼び出すときは、必須パラメータとオプション パラメータの両方で構成される options オブジェクトを渡します。
渡すことができるすべてのオプションを見てみましょう。
uservisibleOnly オプション
ブラウザに push が最初に追加されたとき、デベロッパーがプッシュ メッセージを送信して通知を表示できなくすべきかについて、不確実な状況がありました。これは一般に「サイレント プッシュ」と呼ばれ、ユーザーはバックグラウンドで何かが発生したことを知らないためです。
ユーザーの現在地を継続的に追跡するなど、ユーザーが気付かないうちにデベロッパーが不当な行為を行う可能性があることが懸念されていました。
このような状況を回避し、仕様作成者がこの機能をサポートする最適な方法を検討するための時間を確保するために、userVisibleOnly
オプションが追加されました。これは、ブラウザで値 true
を渡すことで、プッシュ通知が受信されるたびにウェブアプリが通知を表示する(サイレント プッシュがない)ことを意味します。
現時点では、値 true
を渡す必要があります。userVisibleOnly
キーを含めない、または false
を渡さないと、次のエラーが発生します。
Chrome では現在、ユーザーに表示されるメッセージを表示するサブスクリプションの Push API のみをサポートしています。これを示すには、代わりに pushManager.subscribe({userVisibleOnly: true})
を呼び出します。詳しくは https://goo.gl/yqv4Q4 をご覧ください。
現時点では、Chrome には包括的なサイレント プッシュは実装されないようです。代わりに、仕様作成者は、ウェブアプリの使用状況に基づいて特定の数のサイレント プッシュ メッセージをウェブアプリに許可するバジェット API の概念を検討しています。
applicationServerKey オプション
前のセクションで「アプリケーション サーバーキー」について簡単に説明しました。「アプリケーション サーバーキー」は、ユーザーを登録しているアプリを識別し、同じアプリがそのユーザーにメッセージを送信できるようにするために、push サービスによって使用されます。
アプリケーション サーバーキーは、アプリケーションに固有の公開鍵と秘密鍵のペアです。秘密鍵はアプリに対して非公開にする必要があります。公開鍵は自由に共有できます。
subscribe()
呼び出しに渡される applicationServerKey
オプションは、アプリの公開鍵です。ブラウザは、ユーザーをサブスクライブするときにこれを push サービスに渡します。つまり、push サービスはアプリケーションの公開鍵をユーザーの PushSubscription
に関連付けることができます。
下の図にこの手順を示します。
- ウェブアプリがブラウザに読み込まれ、
subscribe()
を呼び出して公開アプリケーション サーバーキーを渡します。 - 次に、ブラウザは push サービスにネットワーク リクエストを行います。push サービスはエンドポイントを生成し、このエンドポイントをアプリケーションの公開鍵に関連付けて、エンドポイントをブラウザに返します。
- ブラウザは、このエンドポイントを
PushSubscription
に追加します。これは、subscribe()
Promise を介して返されます。
後で push メッセージを送信する場合は、Authorization ヘッダーを作成する必要があります。このヘッダーには、アプリケーション サーバーの秘密鍵で署名された情報が含まれています。push サービスは、push メッセージの送信リクエストを受信すると、リクエストを受信するエンドポイントにリンクされている公開鍵を検索することで、この署名付きの Authorization ヘッダーを検証できます。署名が正しい場合、push サービスは、一致する秘密鍵を持つアプリケーション サーバーからの署名でなければならないことを認識します。これは基本的に、第三者がアプリケーションのユーザーにメッセージを送信できないようにするセキュリティ対策です。
技術的には、applicationServerKey
は省略可能です。ただし、Chrome での最も簡単な実装には必須であり、他のブラウザで今後必要になる可能性があります。Firefox では省略可能です。
アプリケーション サーバーキーを何にするかを定義する仕様は VAPID 仕様です。「アプリケーション サーバーキー」または「VAPID キー」を参照しているときは、これらは同じものであることに留意してください。
アプリケーション サーバーキーの作成方法
web-push-codelab.glitch.me にアクセスして、アプリケーション サーバー鍵の公開と非公開のセットを作成するか、web-push コマンドラインを使用して鍵を生成します。手順は次のとおりです。
$ npm install -g web-push
$ web-push generate-vapid-keys
アプリケーションに対してこれらの鍵を作成する必要があるのは 1 回だけです。秘密鍵は非公開にしておいてください。(うん、さっき言ったよね)
権限と subscribe()
subscribe()
の呼び出しには副作用が 1 つあります。subscribe()
の呼び出し時にウェブアプリに通知を表示する権限がない場合、ブラウザはユーザーに権限をリクエストします。これは、UI がこのフローで機能する場合に便利ですが、より詳細な制御が必要な場合は(ほとんどのデベロッパーがそうすると思います)、前に使用した Notification.requestPermission()
API を使用してください。
PushSubscription とは
subscribe()
を呼び出し、いくつかのオプションを渡して、PushSubscription
に解決される Promise を取得します。その結果、次のようなコードになります。
function subscribeUserToPush() {
return navigator.serviceWorker
.register('/service-worker.js')
.then(function (registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
),
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function (pushSubscription) {
console.log(
'Received PushSubscription: ',
JSON.stringify(pushSubscription),
);
return pushSubscription;
});
}
PushSubscription
オブジェクトには、そのユーザーに push メッセージを送信するために必要なすべての情報が含まれています。JSON.stringify()
を使用して内容を出力すると、次のように表示されます。
{
"endpoint": "https://some.pushservice.com/something-unique",
"keys": {
"p256dh":
"BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
"auth":"FPssNDTKnInHVndSTdbKFw=="
}
}
endpoint
は push サービスの URL です。push メッセージをトリガーするには、この URL に POST リクエストを送信します。
keys
オブジェクトには、push メッセージ(このセクションで後述)で送信されるメッセージ データを暗号化するために使用される値が含まれています。
期限切れにならないよう定期的な再定期購入
プッシュ通知に登録すると、多くの場合、null
の PushSubscription.expirationTime
が届きます。理論的には、これはサブスクリプションが期限切れになることはないことを意味します(これは、サブスクリプションが期限切れになった正確な時点を示す DOMHighResTimeStamp
を受け取った場合とは対照的です)。ただし、プッシュ通知を長期間受信していない場合や、ユーザーがプッシュ通知権限のあるアプリを使用していないことをブラウザが検出した場合などは、ブラウザではサブスクリプションの有効期限が切れることが一般的です。これを防ぐパターンの一つとして、以下のスニペットに示すように、通知を受け取るたびにユーザーを再登録します。この場合、ブラウザが購読を自動的に期限切れにならないように、通知を頻繁に送信する必要があります。また、購読が期限切れにならないようにするために、ユーザーに無断でスパムを送信することに対して、正当な通知のメリットとデメリットを慎重に検討する必要があります。結局のところ、忘れられていた通知の登録からユーザーを守るために、ブラウザと戦うべきではありません。
/* In the Service Worker. */
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
// Display notification or handle data
// Example: show a notification
const title = 'New Notification';
const body = 'You have new updates!';
const icon = '/images/icon.png';
const tag = 'simple-push-demo-notification-tag';
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
// Attempt to resubscribe after receiving a notification
event.waitUntil(resubscribeToPush());
});
function resubscribeToPush() {
return self.registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription.unsubscribe();
}
})
.then(function() {
return self.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
});
})
.then(function(subscription) {
console.log('Resubscribed to push notifications:', subscription);
// Optionally, send new subscription details to your server
})
.catch(function(error) {
console.error('Failed to resubscribe:', error);
});
}
サーバーにサブスクリプションを送信する
push サブスクリプションを作成したら、サーバーに送信する必要があります。その方法は自由に決めることができますが、JSON.stringify()
を使用して、サブスクリプション オブジェクトから必要なデータをすべて取得することをおすすめします。または、次のように手動で同じ結果を組み合わせることもできます。
const subscriptionObject = {
endpoint: pushSubscription.endpoint,
keys: {
p256dh: pushSubscription.getKeys('p256dh'),
auth: pushSubscription.getKeys('auth'),
},
};
// The above is the same output as:
const subscriptionObjectToo = JSON.stringify(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.');
}
});
}
ノードサーバーはこのリクエストを受け取り、後で使用できるようにデータをデータベースに保存します。
app.post('/api/save-subscription/', function (req, res) {
if (!isValidSaveRequest(req, res)) {
return;
}
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.',
},
}),
);
});
});
サーバーに PushSubscription
の詳細があることで、いつでもユーザーにメッセージを送信できます。
期限切れにならないよう定期的な再定期購入
プッシュ通知に登録すると、多くの場合、null
の PushSubscription.expirationTime
が届きます。理論的には、これはサブスクリプションが期限切れになることはないことを意味します(これは、サブスクリプションが期限切れになった正確な時点を示す DOMHighResTimeStamp
を受け取った場合とは対照的です)。ただし、プッシュ通知を長期間受信していない場合や、プッシュ通知の権限を持つアプリをユーザーが使用していないことをブラウザが検出した場合などは、ブラウザではサブスクリプションの有効期限が切れることが一般的です。これを防ぐパターンの一つとして、以下のスニペットに示すように、通知を受け取るたびにユーザーを再登録します。この場合、ブラウザが購読を自動的に期限切れにならないように、通知を頻繁に送信する必要があります。また、購読が期限切れにならないように、ユーザーにスパム行為を行うことに対して、正当な通知のメリットとデメリットを慎重に検討する必要があります。結局のところ、忘れられていた通知の登録からユーザーを守るために、ブラウザと戦うべきではありません。
/* In the Service Worker. */
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
// Display notification or handle data
// Example: show a notification
const title = 'New Notification';
const body = 'You have new updates!';
const icon = '/images/icon.png';
const tag = 'simple-push-demo-notification-tag';
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
// Attempt to resubscribe after receiving a notification
event.waitUntil(resubscribeToPush());
});
function resubscribeToPush() {
return self.registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription.unsubscribe();
}
})
.then(function() {
return self.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
});
})
.then(function(subscription) {
console.log('Resubscribed to push notifications:', subscription);
// Optionally, send new subscription details to your server
})
.catch(function(error) {
console.error('Failed to resubscribe:', error);
});
}
よくある質問
この時点でよくある質問をいくつか紹介します。
ブラウザが使用するプッシュ サービスを変更できますか?
いいえ。push サービスはブラウザによって選択されます。subscribe()
呼び出しで確認したように、ブラウザは push サービスにネットワーク リクエストを行い、PushSubscription を構成する詳細を取得します。
ブラウザごとに使用するプッシュ サービスはブラウザによって異なり、API も異なるのではないでしょうか?
すべての push サービスは、同じ API を想定します。
この共通 API はウェブプッシュ プロトコルと呼ばれ、プッシュ メッセージをトリガーするためにアプリケーションが行う必要があるネットワーク リクエストを記述します。
パソコンで定期購入しているユーザーは、スマートフォンでも定期購入しているのでしょうか?
いいえ。ユーザーは、メッセージを受信するブラウザごとにプッシュに登録する必要があります。また、これを行うには、ユーザーが各デバイスで権限を付与する必要があります。
次のステップ
- ウェブでのプッシュ通知の概要
- プッシュの仕組み
- ユーザーの登録
- 権限の UX
- ウェブプッシュ ライブラリを使用したメッセージの送信
- ウェブのプッシュ プロトコル
- push イベントの処理
- 通知の表示
- 通知の動作
- 一般的な通知パターン
- プッシュ通知に関するよくある質問
- 一般的な問題とバグの報告