清除站点数据后Firebase Cloud Message无法获取Token的问题
使用Firebase + Vue 3实现通知功能,正常运行无异常,但清除站点数据(clear-site-data)后,首次启动应用无法获取通知Token,重新加载页面可解决该问题。
操作步骤
- 首次启动应用(运行正常)
- 打开控制台,进入Application面板
- 点击Storage后选择Clear Site Data
- 重新加载页面,触发错误
- 再次刷新页面,问题解决
错误信息
An error occurred while retrieving token. DOMException: Failed to execute 'subscribe' on 'PushManager': Subscription failed - no active Service Worker
相关代码
firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js') importScripts('https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js') firebase.initializeApp({...}) const messaging = firebase.messaging(); messaging.onBackgroundMessage((payload) => { console.log( '[firebase-messaging-sw.js] Received background message ', payload ); // Customize notification here const notificationTitle = payload.notification.title; const notificationOptions = { body: payload.notification.body, icon: payload.notification.image }; self.registration.showNotification(notificationTitle, notificationOptions); });
Vue组件代码
const approvedNotification = async () => { let permission = await Notification.requestPermission(); if (permission === "granted") { getToken(messaging, { vapidKey: '...' }).then((currentToken) => { if (currentToken) { token.value = currentToken console.log('token:', currentToken) // Send the token to your server and update the UI if necessary // ... } 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); // ... }); } else { // Handle denied permission } } onMounted(() => { approvedNotification() })
尝试将Firebase版本从8.10升级至新版本后,出现获取Token的错误,询问是否需要重写firebase-messaging-sw.js。
问题原因
清除站点数据后,浏览器会删除已注册的Service Worker。首次加载页面时,Vue的onMounted钩子会立即触发approvedNotification去获取Token,但此时Service Worker还处于注册/激活的异步过程中,尚未完全激活,导致PushManager.subscribe调用失败。第二次刷新时,Service Worker已经完成激活,因此能正常获取Token。
针对Firebase 8.x版本的修复
在调用getToken之前,确保Service Worker已经注册并激活。修改Vue组件代码,先等待Service Worker激活完成再执行Token获取逻辑:
// 封装等待Service Worker激活的函数 const waitForServiceWorkerActivation = async () => { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.register('/firebase-messaging-sw.js'); // 若Service Worker已激活,直接返回 if (registration.active) { return registration; } // 等待Service Worker完成激活 return new Promise((resolve) => { const updateListener = () => { registration.removeEventListener('updatefound', updateListener); registration.installing.addEventListener('statechange', () => { if (registration.installing.state === 'activated') { resolve(registration); } }); }; registration.addEventListener('updatefound', updateListener); }); } throw new Error('Service Worker is not supported'); } const approvedNotification = async () => { let permission = await Notification.requestPermission(); if (permission === "granted") { try { // 先等待Service Worker激活 await waitForServiceWorkerActivation(); const currentToken = await getToken(messaging, { vapidKey: '...' }); if (currentToken) { token.value = currentToken console.log('token:', currentToken) // 后续业务逻辑... } else { console.log('No registration token available. Request permission to generate one.'); } } catch (err) { console.log('An error occurred while retrieving token. ', err); } } else { // 处理权限拒绝逻辑 } } onMounted(() => { approvedNotification() })
升级到Firebase新版本(v9+模块化版本)的适配
Firebase v9+采用模块化架构,firebase-messaging-sw.js需要重写,同时前端代码也要调整为模块化导入方式:
重写后的firebase-messaging-sw.js
import { initializeApp } from 'https://www.gstatic.com/firebasejs/9.23.0/firebase-app.js'; import { getMessaging, onBackgroundMessage } from 'https://www.gstatic.com/firebasejs/9.23.0/firebase-messaging-sw.js'; const firebaseApp = initializeApp({ // 你的Firebase配置信息 }); const messaging = getMessaging(firebaseApp); onBackgroundMessage(messaging, (payload) => { console.log('[firebase-messaging-sw.js] Received background message ', payload); const notificationTitle = payload.notification.title; const notificationOptions = { body: payload.notification.body, icon: payload.notification.image }; self.registration.showNotification(notificationTitle, notificationOptions); });
Vue组件代码调整(v9+模块化)
import { initializeApp } from 'firebase/app'; import { getMessaging, getToken } from 'firebase/messaging'; // 初始化Firebase应用 const firebaseApp = initializeApp({ // 你的Firebase配置信息 }); const messaging = getMessaging(firebaseApp); const waitForServiceWorkerActivation = async () => { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.register('/firebase-messaging-sw.js'); if (registration.active) { return registration; } return new Promise((resolve) => { const updateListener = () => { registration.removeEventListener('updatefound', updateListener); registration.installing.addEventListener('statechange', () => { if (registration.installing.state === 'activated') { resolve(registration); } }); }; registration.addEventListener('updatefound', updateListener); }); } throw new Error('Service Worker is not supported'); } const approvedNotification = async () => { let permission = await Notification.requestPermission(); if (permission === "granted") { try { await waitForServiceWorkerActivation(); const currentToken = await getToken(messaging, { vapidKey: '你的VAPID密钥' }); if (currentToken) { token.value = currentToken console.log('token:', currentToken) // 后续业务逻辑... } else { console.log('No registration token available. Request permission to generate one.'); } } catch (err) { console.log('An error occurred while retrieving token. ', err); } } else { // 处理权限拒绝逻辑 } } onMounted(() => { approvedNotification() })
内容的提问来源于stack exchange,提问作者Portunis

