メインコンテンツまでスキップ

Webプッシュ通知についてブラウザ間の挙動の違いを調べてみた(フロントエンド編)

箕浦
箕浦
開発二部

こんにちは。開発 2 部 箕浦です。
現在、関わっている案件でWebプッシュ通知について調べる機会がありましたので
実装の仕方からブラウザ間の挙動の違いを調査した結果を紹介します。
本記事は、後編であるフロントエンド編になります。

今回はフロントエンドはReactで実装します。

Service Worker

Webプッシュ通知は、ユーザーがウェブサイトを見ている状態だけでなく、他のサイトを見ているときや、別のアプリケーションを動かしているときなどにも通知されてほしいです。
WebアプリケーションはReact(Javascript)でブラウザ上で動作するものなので、基本的にはユーザーがウェブサイトを閲覧していないときは動作しません。
そのため、Webプッシュ通知を受け取ったときの動作は、本体のWebアプリケーションとは別のプロセスで動かしておいて、プッシュ通知の処理を行う仕組みが必要となります。
その役割を担うのがService Workerです。
Service Workerは、プッシュ通知だけでなく、バックグラウンドで何か処理をさせたいときなど用途はいろいろあります。
ただし、ブラウザの種類やバージョンにより、サポート状況や挙動が変わることがありますので事前に動作確認が必要となります。

publicフォルダ配下に以下の service-worker.js ファイルを作成します。 簡単に説明すると、
push イベントに対して、タイトルやメッセージなどを受け取り、 showNotification を使って、デスクトップにプッシュ通知を表示します。
notificationclick イベントに対して、通知がクリックされた際に、指定したURLへ遷移するようにしています。

service-worker.js
addEventListener('push', (event) => {
console.log('[Service Worker] Push Received.');
console.log(`[Service Worker] Push had this data: "${event}"`);

if (!(self.Notification && self.Notification.permission === 'granted'))
return;

var data = {};
if (event.data)
data = event.data.json();

console.log(data)

self.clients.matchAll().then(clients => {
clients.forEach(client => {
client.postMessage({
data: data
});
});
});

var title = data.data['pinpoint.notification.title'];
var message = data.data['pinpoint.notification.body'];
var icon = data.data['pinpoint.notification.imageIconUrl'];
var image = data.data['pinpoint.notification.imageUrl'];
var options = {
body: message,
icon: icon,
image: image,
data: data,
};
event.waitUntil(self.registration.showNotification(title, options));
});

addEventListener('notificationclick', (event) => {
event.notification.close();
event.waitUntil(
clients.openWindow(event.notification.data.data['pinpoint.url'])
);
});

React

React側ではまず、firebase のライブラリをインストールします。

npm install firebase

App.jsに前編でFirebaseコンソールからコピーしたコードスニペットをそのまま貼り付けます。

src/App.js
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";

const firebaseConfig = {
apiKey: "APIキー",
authDomain: "webpush-c7bed.firebaseapp.com",
databaseURL: "https://webpush-c7bed.firebaseio.com",
projectId: "webpush-c7bed",
storageBucket: "webpush-c7bed.appspot.com",
messagingSenderId: "586895317040",
appId: "アプリ ID",
measurementId: "G-SPL1GEMWDJ"
};

const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);

同じく、以下のコードを追記します。
「サーバーキー」には、前編でFirebaseコンソールからコピーしたサーバーキーを設定します。
今回はサーバーキーや上記の設定は、ソースコードにハードコーディングしていますが、本番環境で使用する場合は、外から読み込むなどの仕組みが必要です。

src/App.js
import { getMessaging, getToken, isSupported } from "firebase/messaging";
const messaging = getMessaging(app);

const vapidKey = "サーバーキー"
var registeredServiceWorker;
navigator.serviceWorker.register('/service-worker.js').then((reg) => registeredServiceWorker = reg)

function App() {

useEffect(() => {

function requestPermission() {
isSupported().then((isSupport) => {
if (!isSupport) {
console.log('Messaging is not support.');
return
}

console.log('Requesting permission...');
Notification.requestPermission().then((permission) => {
if (permission === 'granted') {
console.log('Notification permission granted.');
getTokenRequest()
}
})
})
}

async function getTokenRequest() {
// Add the public key generated from the console here.
getToken(messaging, {
vapidKey: vapidKey,
serviceWorkerRegistration: registeredServiceWorker
})
.then((currentToken) => {
if (currentToken) {
// Send the token to your server and update the UI if necessary
// ...
console.log(`token: ${currentToken}`);
// ここで取得したトークンをサーバーへ送信する
} else {
// Show permission request UI
console.log('No registration token available. Request permission to generate one.');
// ...
}
}).catch((err) => {
console.log('An error occurred while retrieving token. ', err);
// ...
});
}
requestPermission();
}, []);

return (
<div className="App">
<div>
・・・
</div>
</div>
);
}

ここまでで実装は完了です。

プッシュ通知の送信

フロントエンドをローカルで起動します。

npm run start

ブラウザが起動すると、画面上部にプッシュ通知を許可もしくはブロックするがポップアップで表示されます。
ここで許可を選択しておきます。
ブロックを選択すると、プッシュ通知が届かなくなります。

permission

すると、トークンが払い出されます。 今回はわかりやすいようにトークンをデバッグコンソールに出力しています。

token

本来であれば、このトークンをサーバーへ何らかの方法で送り、サーバー側でユーザーと紐づけて管理しておきます。
今回はサーバーは省略しているので、このトークンをコピペすることにします。

今回はプッシュ通知の送信は簡単に確認するため、Amazon Pinpointのコンソール画面から送信します。
デバイストークンには先ほどコピーしたトークン
プッシュ通知サービスには「FCM」
を入力し、メッセージの内容を入れて、プッシュ通知を送信します。

pinpoint_1 pinpoint_2

送信を行うと、デスクトップの右下に無事プッシュ通知が表示されました!!

push-disp

ブラウザによる挙動

ここからは、ブラウザによるプッシュ通知の挙動を確認していきます。
今回はChrome、Edge、FireFoxの3種類で確認することにします。

デフォルトの状態でのプッシュ通知の表示

まずはデフォルトの状態でのプッシュ通知の表示は、

Chromeの場合

push-disp

Edgeの場合

push-edge

Firefoxの場合

push-firefox

こちらは、同じ結果となりました。

requireInteractionを有効

ユーザーがクリックするか閉じるかするまで、通知が自動的に閉じずに残る設定である requireInteraction を有効にした場合、

Chromeの場合

push-crome-with-button

Edgeの場合

push-edge

Firefoxの場合

push-firefox-with-button

こちらは、Edgeの場合は「閉じる」ボタンが表示されず、25秒ほどで通知の表示が消えました。 Edgeの場合、requireInteractionの設定だけではだめなようです。

プッシュ通知を閉じずに、連続して何回か送信した場合

プッシュ通知の「閉じる」ボタンを押さずに、連続して何回か送信した場合の動作を見てみます。

Chromeの場合

push-crome-with-button-3

Edgeの場合

push-edge

Firefoxの場合

push-firefox-with-button-3

ChromeとFirefoxでは、連続して送ると、3つまではデスクトップに表示されますが、それ以降は、 デスクトップに表示せず、通知管理に溜まっていく動作となっていました。
その状態で閉じるボタンで通知を閉じると通知管理に溜まっていたものが、 順番に表示されます。
Edgeでは、通知管理には溜まっていきますが、どれだけ送っても、デスクトップには1つまでの表示となっています。

ブラウザを起動していない状態でのプッシュ通知

ChromeとFirefoxでは、即時に通知表示されず、ブラウザを開いて、20秒ほど待っていると、 それまで溜まっていたものが一度に通知されてくる動作となっています。
それに対し、Edgeでは、ブラウザを起動していない状態でも、プッシュ通知が表示されました。

ブラウザによって、プッシュ通知のサポート状況や、実装は以後のバージョンアップで動作は変わる可能性がありますが、 現時点ではこのような差異がありました。
他にもいろいろな設定がありますので、今後も色々触ってみようと思います。