如何检测用户从DevTools手动注销Service Worker(无需调用自定义Unsubscribe函数)
Got it, so the problem is that users are bypassing your custom Unsubscribe() function by manually unregistering the Service Worker through DevTools, which leaves stale push endpoints lingering in your database. Let’s walk through practical solutions to catch this scenario:
1. Listen for Controller Changes in the Main Page
When a user unregisters the Service Worker, the page’s active controller (the Service Worker controlling the page) will become null. You can listen for the controllerchange event to detect this shift, then validate the current push subscription state and clean up your database if needed:
// Set up the listener when your page initializes navigator.serviceWorker.addEventListener('controllerchange', async () => { // Check if any Service Worker registrations exist const registrations = await navigator.serviceWorker.getRegistrations(); if (registrations.length === 0) { // No active registrations = Service Worker was unregistered const storedSubscription = JSON.parse(localStorage.getItem('pushSubscription')); if (storedSubscription) { // Call your backend to remove the stale endpoint await fetch('api/push/Unregister', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(storedSubscription) }); // Clear local storage to avoid duplicate cleanup localStorage.removeItem('pushSubscription'); } } });
2. Validate Subscription State on Page Load
Every time the page loads, proactively check if the stored push subscription still matches the active one (if any). If there’s a mismatch or no active subscription exists, sync this with your database:
window.addEventListener('load', async () => { const storedSubscription = JSON.parse(localStorage.getItem('pushSubscription')); if (!storedSubscription) return; try { const swReg = await navigator.serviceWorker.ready; const currentSubscription = await swReg.pushManager.getSubscription(); // If no active subscription exists, or endpoints don't match if (!currentSubscription || currentSubscription.endpoint !== storedSubscription.endpoint) { await fetch('api/push/Unregister', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(storedSubscription) }); localStorage.removeItem('pushSubscription'); } } catch (error) { // navigator.serviceWorker.ready failed = no active Service Worker await fetch('api/push/Unregister', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(storedSubscription) }); localStorage.removeItem('pushSubscription'); } });
3. Bonus: Backend-Side Validation
To cover edge cases (like users clearing local storage), add a validation step on your backend when sending push notifications. If a push request fails with a "410 Gone" or similar error (indicating the endpoint is invalid), automatically remove that endpoint from your database. This acts as a safety net for any missed client-side detections.
Key Notes
- Store subscriptions locally: Keep a copy of the push subscription in
localStorageorsessionStorageso you can reference it when detecting unregistration. - Idempotent backend endpoints: Ensure your
UnregisterAPI can handle duplicate requests without causing errors (since cleanup might trigger multiple times). - Real-time limitations: These client-side methods can’t detect unregistration the exact second it happens (since DevTools unregistration doesn’t fire a direct event), but they’ll catch it on the next page load or controller change—covering most user interactions.
内容的提问来源于stack exchange,提问作者Steinar

