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

React中如何在非public文件夹注册Service Worker并实现通知交互

解决Service Worker注册失败及通知按钮调用服务函数的问题

一、修复Service Worker注册失败的MIME类型错误

你遇到的DOMException: Failed to register a ServiceWorker... unsupported MIME type ('text/html')错误,核心原因是注册时请求的sw.js路径不对,服务器返回了HTML页面(比如404页面)而非JavaScript文件。结合你的场景,按以下步骤修复:

1. 修正注册路径

你的sw.js实际路径是src/services/notify/sw.js,但前端项目中src目录下的文件默认不会直接被浏览器访问(除非构建工具配置了静态资源映射)。首先要确保sw.js能被浏览器通过正确URL访问:

  • 如果是Create React App这类框架,建议把sw.js放在public/services/notify/目录下(这样构建后会被复制到输出目录的对应路径)。
  • 然后修改注册函数中的脚本路径为绝对路径:
export const registerServiceWorker = async () => {
    if ("serviceWorker" in navigator) {
        try {
            // 改为实际可访问的绝对路径
            const registration = await navigator.serviceWorker.register("/services/notify/sw.js", {
                scope: "/", // 需要全局作用域的话保持这个配置
            });
            if (registration.installing) {
                Logger.log("Service worker installing");
            } else if (registration.waiting) {
                Logger.log("Service worker installed");
            } else if (registration.active) {
                Logger.log("Service worker active");
            }
        } catch (error) {
            Logger.error(`Registration failed with ${error}`);
        }
    }
};

2. 处理全局作用域(scope: "/")的限制

当Service Worker脚本不在根目录,但需要全局作用域时,需要额外配置:

  • 方式1:添加Service-Worker-Allowed响应头
    在服务器返回sw.js时,添加HTTP响应头:Service-Worker-Allowed: /,告诉浏览器该脚本可以作用于根路径。
    • 如果是开发服务器(比如webpack-dev-server),可以通过devServer.headers配置添加;
    • 生产环境则在Nginx/Apache等服务器上配置响应头。
  • 方式2:将sw.js放在根目录,通过导入模块复用逻辑
    如果不想配置响应头,可以把sw.js放回根目录,然后在sw.js中导入src/services/notify/下的逻辑模块(需要确保模块能在Service Worker环境运行,无DOM依赖):
    // 根目录的sw.js
    import './services/notify/notificationHandler.js'; // 导入你的通知处理逻辑
    

3. 确保服务器返回正确的MIME类型

确保服务器对.js文件返回application/javascript的MIME类型:

  • 大多数现代开发服务器(如CRA、Vite)默认会处理,但如果是自定义服务器,需要配置mime映射(比如Nginx中添加types { application/javascript js; })。

二、实现通知按钮点击调用服务函数的方案

Service Worker是独立于页面的隔离环境,不能直接调用页面中的服务函数,推荐以下两种可行方案:

方案1:通过postMessage与页面通信

在Service Worker的通知点击事件中,向活跃的页面客户端发送消息,页面监听消息后调用服务函数:

  1. 修改Service Worker的notificationclick事件:
self.onnotificationclick = (event) => {
    event.notification.close();
    const action = event.action;
    
    event.waitUntil(
        clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
            // 找到活跃的页面客户端
            const targetClient = clientList[0] || clients.openWindow("/");
            
            if (targetClient) {
                // 向页面发送动作类型
                targetClient.postMessage({
                    type: "NOTIFICATION_ACTION",
                    action: action
                });
            }
        })
    );
};
  1. 在页面中监听消息并调用服务函数:
    在你的业务页面(比如login.jsx或App.jsx)中添加监听:
useEffect(() => {
    const handleMessage = (event) => {
        if (event.data.type === "NOTIFICATION_ACTION") {
            const { action } = event.data;
            // 调用你的服务函数
            if (action === 'answer') {
                // 比如调用answerService()
                answerService();
            } else if (action === 'decline') {
                declineService();
            }
        }
    };
    
    navigator.serviceWorker.addEventListener('message', handleMessage);
    return () => {
        navigator.serviceWorker.removeEventListener('message', handleMessage);
    };
}, []);

方案2:在Service Worker中直接导入服务模块

如果你的服务函数没有DOM依赖(比如只是纯逻辑或API调用),可以把服务函数抽成独立的模块,在sw.js中直接导入:

  1. 抽离服务函数为独立模块:
// src/services/notify/notificationActions.js
export const handleAnswerAction = async () => {
    // 这里写你的answer逻辑,比如调用API
    await fetch('/api/answer-call', { method: 'POST' });
};

export const handleDeclineAction = async () => {
    await fetch('/api/decline-call', { method: 'POST' });
};
  1. 在sw.js中导入并使用:
// src/services/notify/sw.js
import { handleAnswerAction, handleDeclineAction } from './notificationActions.js';

self.onnotificationclick = (event) => {
    event.notification.close();
    
    if (event.action === 'answer') {
        event.waitUntil(handleAnswerAction());
    } else if (event.action === 'decline') {
        event.waitUntil(handleDeclineAction());
    }
    
    // 页面聚焦逻辑保持不变
    event.waitUntil(
        clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
            if (clientList.length > 1) {
                clientList.forEach((client) => {
                    if (client.url === "/" && "focus" in client) return client.focus();
                })
                if (clients.openWindow) return clients.openWindow("/");
            } else {
                clientList[0]?.focus();
            }
        })
    );
};

注意:如果使用ES模块,需要在注册Service Worker时指定type: 'module':

navigator.serviceWorker.register("/services/notify/sw.js", {
    scope: "/",
    type: 'module' // 添加这个配置
});

内容的提问来源于stack exchange,提问作者Sai Barath R

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 12:48:10