You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在同一作用域下让React PWA的Service Worker与FCM Worker协同工作?

React PWA集成FCM推送通知:合并Service Worker的解决方案

问题描述

我有一个service-worker.js文件,用来把ReactJS应用打包成PWA。现在想通过FCM添加推送通知功能,这要求在public文件夹下放firebase-messaging-sw.js文件。两者需要同作用域才能正常工作,但同一作用域下不能存在两个不同的Service Worker。我试过把service-worker.js重命名为firebase-messaging-sw.js但没生效,请问怎么让这两个文件协同工作?

原firebase-messaging-sw.js代码

// Scripts for firebase and firebase messaging
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");

 // Initialize the Firebase app in the service worker by passing the generated config
 const firebaseConfig = {
    apiKey: "xxxx",
    authDomain: "xxxx.firebaseapp.com",
    projectId: "xxxx",
    storageBucket: "xxxx",
    messagingSenderId: "xxxx",
    appId: "xxxx",
    measurementId: "xxxx"
}

 firebase.initializeApp(firebaseConfig);

 // Retrieve firebase messaging

const messaging = firebase.messaging();


self.addEventListener("notificationclick", function (event) {
  console.debug('SW notification click event', event)

  const url = event.notification.data.link
  event.waitUntil(
    clients.matchAll({type: 'window'}).then( windowClients => {
        // Check if there is already a window/tab open with the target URL
        for (var i = 0; i < windowClients.length; i++) {
            var client = windowClients[i];
            // If so, just focus it.
            if (client.url === url && 'focus' in client) {
                return client.focus();
            }
        }
        // If not, then open the target URL in a new window/tab.
        if (clients.openWindow) {
            return clients.openWindow(url);
        }
    })
);
})


messaging.onBackgroundMessage(async function(payload) {
  console.log("Received background message ", payload)

  const notificationTitle = payload.notification.title
  const notificationOptions = {
    body: payload.notification.body,
    icon: './logo192.png',
    badge: './notification-badgex24.png',
    data: {
        link: payload.data?.link
    }
  }

  self.registration.showNotification(notificationTitle, notificationOptions)

})

原service-worker.js代码

import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';

clientsClaim();

const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
  // Return false to exempt requests from being fulfilled by index.html.
  ({ request, url }) => {
    // If this isn't a navigation, skip.
    if (request.mode !== 'navigate') {
      return false;
    } // If this is a URL that starts with /_, skip.

    if (url.pathname.startsWith('/_')) {
      return false;
    } // If this looks like a URL for a resource, because it contains // a file extension, skip.

    if (url.pathname.match(fileExtensionRegexp)) {
      return false;
    } // Return true to signal that we want to use the handler.

    return true;
  },
  createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
);

registerRoute(
  // Add in any other file extensions or routing criteria as needed.
  ({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst.
  new StaleWhileRevalidate({
    cacheName: 'images',
    plugins: [
      // Ensure that once this runtime cache reaches a maximum size the
      // least-recently used images are removed.
      new ExpirationPlugin({ maxEntries: 50 }),
    ],
  })
);

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
  }
});

解决方案:合并两个Service Worker逻辑

核心思路是将Workbox的PWA缓存逻辑整合到firebase-messaging-sw.js中,因为FCM会优先查找该文件名的Service Worker,这样就能在同一个SW中同时处理PWA缓存和推送通知。

步骤1:修改firebase-messaging-sw.js引入Workbox脚本

在Firebase的importScripts之后,添加对应版本的Workbox CDN脚本(版本必须和项目依赖的Workbox版本一致):

importScripts("https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js");

步骤2:整合原service-worker.js的逻辑

把service-worker.js的所有代码,放到Firebase初始化和消息监听逻辑的后面,最终完整代码如下:

// Scripts for firebase and firebase messaging
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");
// 引入Workbox脚本(版本需与项目依赖一致)
importScripts("https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js");

// Initialize the Firebase app in the service worker
const firebaseConfig = {
    apiKey: "xxxx",
    authDomain: "xxxx.firebaseapp.com",
    projectId: "xxxx",
    storageBucket: "xxxx",
    messagingSenderId: "xxxx",
    appId: "xxxx",
    measurementId: "xxxx"
}

firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();

// FCM通知点击处理
self.addEventListener("notificationclick", function (event) {
  console.debug('SW notification click event', event)

  const url = event.notification.data.link
  event.waitUntil(
    clients.matchAll({type: 'window'}).then( windowClients => {
        for (var i = 0; i < windowClients.length; i++) {
            var client = windowClients[i];
            if (client.url === url && 'focus' in client) {
                return client.focus();
            }
        }
        if (clients.openWindow) {
            return clients.openWindow(url);
        }
    })
);
})

// FCM后台消息监听
messaging.onBackgroundMessage(async function(payload) {
  console.log("Received background message ", payload)

  const notificationTitle = payload.notification.title
  const notificationOptions = {
    body: payload.notification.body,
    icon: './logo192.png',
    badge: './notification-badgex24.png',
    data: {
        link: payload.data?.link
    }
  }

  self.registration.showNotification(notificationTitle, notificationOptions)

})

// -------------------------- 原service-worker.js逻辑开始 --------------------------
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';

clientsClaim();

const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
  ({ request, url }) => {
    if (request.mode !== 'navigate') return false;
    if (url.pathname.startsWith('/_')) return false;
    if (url.pathname.match(fileExtensionRegexp)) return false;
    return true;
  },
  createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
);

registerRoute(
  ({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'),
  new StaleWhileRevalidate({
    cacheName: 'images',
    plugins: [
      new ExpirationPlugin({ maxEntries: 50 }),
    ],
  })
);

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
  }
});
// -------------------------- 原service-worker.js逻辑结束 --------------------------

步骤3:清理冗余文件

删除public文件夹中的原service-worker.js,避免出现多个SW冲突。

步骤4:更新SW注册逻辑

在React应用的入口文件(如index.js)中,注册指定firebase-messaging-sw.js:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/firebase-messaging-sw.js')
      .then(registration => {
        console.log('SW registered: ', registration);
      })
      .catch(registrationError => {
        console.log('SW registration failed: ', registrationError);
      });
  });
}

关键注意事项

  • 版本匹配:Workbox的CDN版本必须和项目package.json中依赖的Workbox版本完全一致,否则会出现API兼容问题。
  • 作用域确保:firebase-messaging-sw.js必须放在public根目录,保证作用域覆盖整个应用。
  • 清除旧SW:测试前在浏览器开发者工具的Application > Service Workers中注销旧的Service Worker,刷新页面重新注册新SW,避免缓存异常。

内容的提问来源于stack exchange,提问作者Art

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 15:45:33