今回、自身の開発しているサービスをPWA化してプッシュ通知まで実装しようと頑張ったのですが、Vite環境で実装している記事が見つからなかったり、FCM(Firebase Cloud Messaging)の仕様が変更されていて記事が当てにならなかったりしたのでメモ程度に共有します。
誰かの助けになれば...(未来の自分の助けになっているかも)
前提
前提として、React+Viteのプロジェクトが一旦作成されていることを前提としています。
そこからわからなければ公式のドキュメント形を見て作成してください。
本記事における環境は
Vite: "^5.1.4" React: "^18.2.0" TypeScript: "^5.2.2" Firebase: "^10.8.1"
です。 また、同様にFirebaseのプロジェクトも作成されていること前提です。
実際の実装
通知を受け取るところの実装
/* JavaScript クライアントでメッセージを受信する | Firebase https://firebase.google.com/docs/cloud-messaging/js/receive?hl=ja */ // Give the service worker access to Firebase Messaging. // Note that you can only use Firebase Messaging here, other Firebase libraries // are not available in the service worker. importScripts('https://www.gstatic.com/firebasejs/8.2.1/firebase-app.js'); importScripts('https://www.gstatic.com/firebasejs/8.2.1/firebase-messaging.js'); // APIキーはコードに含めても問題ないみたい // https://firebase.google.com/docs/projects/api-keys?hl=ja firebase.initializeApp({ apiKey: "************************", authDomain: "******.firebaseapp.com", projectId: "******", storageBucket: "******.appspot.com", messagingSenderId: "************", appId: "****************************", measurementId: "**************" }); // バックグラウンドメッセージを処理できるようにFirebase Messagingのインスタンスを取得 const messaging = firebase.messaging(); messaging.onBackgroundMessage((payload) => { console.log('[firebase-messaging-sw.js] Received background message ', payload); // notificationがある場合は、自動的に表示されるので、ここで表示する必要はない // 2重で表示されるので注意 // const notificationTitle = payload.notification.title; // const notificationOptions = { // body: payload.notification.body, // icon: "/icon-512-any.png" // }; // return self.registration.showNotification(notificationTitle, // notificationOptions); }); // Workbox を使用してサービスワーカーにキャッシュリストを注入するためのプレースホルダーとして機能します。 // injectManifest 戦略を使う場合、Workbox はビルド時にこのプレースホルダーを見つけ、 // その位置に実際のキャッシュリスト(プレキャッシュマニフェスト)を注入 By ChatGPT console.log(self.__WB_MANIFEST);
import { getToken, onMessage, getMessaging } from "firebase/messaging" import { useEffect } from "react" import { messaging, useAuth } from "../contexts/AuthContext" import { userService } from "../services" const app = initializeApp(firebaseConfig) const messaging = getMessaging(app) export const Notification = () => { useEffect(() => { if (!user) return updateMessagingToken() onMessage(messaging, (payload) => { console.log(payload) }) }, [user]) // トークンをサーバー側へ通達する処理 const updateMessagingToken = async () => { try { const token = await getToken(messaging, { vapidKey: import.meta.env.VITE_FIREBASE_VAPID_KEY }) if (!token) return await userService.updateNotificationToken(token) } catch (error) { console.error(error) } } return ( <></> ) } export default Notification
通知を送信する実装
import firebase_admin.messaging as messaging def send_notification_to_user(user_token: str, title: str, body: str): if token is None: return message = messaging.Message( webpush=messaging.WebpushConfig( notification=messaging.WebpushNotification( title=title, body=body, icon="https://storage.googleapis.com/[projectId].appspot.com/icon.png", ), ), token=user_token, ) messaging.send(message)
詰まったところ
Service Workerの登録
Viteで構築していたので、VitePWAというプラグインでservice workerを自動注入していたが、firebaseのservice workerの挿入方法がわからなかった。
いろいろ調べた結果、プラグインの設定を重ね掛けすることで2つ挿入することに成功した。
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react-swc' import { VitePWA } from 'vite-plugin-pwa' export default defineConfig({ plugins: [ react(), VitePWA({ strategies: 'injectManifest', filename: 'firebase-messaging-sw.js', injectManifest: { swSrc: 'public/firebase-messaging-sw.js', swDest: 'dist/firebase-messaging-sw.js', }, workbox: { globPatterns: [], globIgnores: ['*'], }, }), VitePWA({ registerType: 'autoUpdate', injectRegister: 'auto', manifest: { ... } }) ], })
Https化
公式やいろんなサイトではhttps化、すなわちSSL通信でないと通知が送信されないと書いてあることが多いが、僕の環境だと普通にhttp://localhost
で立ち上げた開発環境のアプリに通知は飛んできた。
参考に楽なhttps化
FCMの仕様変更
いろいろな方が書いてくれてる記事のときからFCM(Firebase Cloud Messaging)の仕様が変更されていた。
https://firebase.google.com/docs/cloud-messaging/migrate-v1?hl=ja
僕が調べながらぶち当たった壁をいくつか
- POSTMANで試しにAPI送信してみたとき
- エンドポイントの変更
- Authorizationの方法が違う(
key=
=>Authorization Bearer
) - ペイロードの構造の変更(notificationがmessageに入ったり)
- firebase.initializeAppのときにサーバーキー?だけでは認証情報不足に
- notificationはフォアグラウンドのみ、PWAでバックグラウンド通知したい場合はwebpush煮含める
Firebaseのバージョン整合
ここが一番詰まったところ!
アプリを起動しているときのフォアグラウンドの通知には成功するのに、chrome://inspect/#service-workers
から確認できるバックグラウンドの通知が全然つかめなかった。
これで数日悩んだ結果、Reactからインポートしているfirebaseのバージョンとservice workerでインポートしているfirebaseのバージョンの互換性がないことが原因だった。
今回はこのバージョンの組み合わせで行けたので放置しているが、メジャーバージョンがどこまで違いが許容されるのかはあまり理解していない。(service workerの方をバージョン10にしてみたがないよ!って言われた)