Back to blog
InspectionJanuary 1, 2024· 6 min read

Service Worker Detection: Is This Site Offline-Ready?

A practical Fusebox guide to service worker detection.

Service Worker Detection: Is This Site Offline-Ready?

Published: January 2024
Reading time: 8 minutes

Service Workers are the backbone of Progressive Web Apps (PWAs). They enable offline functionality, push notifications, and background sync. Here's how to detect them and understand what they reveal about a website's capabilities.

What Are Service Workers?

Service Workers are JavaScript files that run in the background, separate from web pages. They act as a proxy between your web app and the network, enabling:

  • Offline functionality - Cache and serve content
  • Push notifications - Engage users when app is closed
  • Background sync - Upload data when connection returns
  • Performance boost - Serve cached assets instantly

Detecting Service Workers

1. Quick Console Check

// Check if site has Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.getRegistrations().then(registrations => {
    if (registrations.length > 0) {
      console.log('✅ Service Workers found:', registrations.length);
      registrations.forEach((reg, index) => {
        console.log(`SW ${index + 1}:`, {
          scope: reg.scope,
          active: reg.active?.scriptURL,
          waiting: reg.waiting?.scriptURL,
          installing: reg.installing?.scriptURL,
          state: reg.active?.state
        });
      });
    } else {
      console.log('❌ No Service Workers registered');
    }
  });
} else {
  console.log('❌ Service Workers not supported');
}

2. Chrome DevTools Method

1. Open DevTools (F12)
2. Application tab
3. Service Workers section
4. See all registered workers
5. Check "Update on reload" for testing

3. Programmatic Detection

// Monitor Service Worker registration
const originalRegister = navigator.serviceWorker.register;

navigator.serviceWorker.register = function(scriptURL, options) {
  console.log('Service Worker registering:', {
    script: scriptURL,
    scope: options?.scope || '/'
  });
  
  return originalRegister.call(this, scriptURL, options)
    .then(registration => {
      console.log('Service Worker registered successfully');
      
      // Monitor lifecycle
      registration.addEventListener('updatefound', () => {
        console.log('New Service Worker version found');
      });
      
      return registration;
    })
    .catch(error => {
      console.error('Service Worker registration failed:', error);
      throw error;
    });
};

Common Service Worker Patterns

1. E-commerce PWA

// Service Worker: /sw.js
// Scope: /

Cache strategies detected:
- Static assets: Cache First (CSS, JS, fonts)
- Product images: Cache First with Network Fallback
- API calls: Network First with Cache Fallback
- Checkout: Network Only (no caching)

Features enabled:
✅ Offline browsing
✅ Add to home screen
✅ Push notifications for orders
✅ Background sync for cart

2. News Website

// Service Worker: /service-worker.js
// Scope: /

Caching detected:
- Articles: Stale While Revalidate
- Images: Cache First (30 day expiry)
- Homepage: Network First (5 minute cache)

Offline page: /offline.html
Push notifications: Enabled for breaking news

3. SaaS Application

// Multiple Service Workers:
// 1. /app/sw-main.js - Main app functionality
// 2. /api/sw-sync.js - Background sync
// 3. /chat/sw-notifications.js - Chat notifications

Advanced features:
- Periodic background sync
- Push notification groups
- Cache versioning (v2.3.1)
- Selective caching by user role

Service Worker Caching Strategies

1. Cache First (Offline First)

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetch(event.request))
  );
});

// Good for: Static assets, fonts, images
// Bad for: Dynamic content, API calls

2. Network First (Online First)

self.addEventListener('fetch', event => {
  event.respondWith(
    fetch(event.request)
      .catch(() => caches.match(event.request))
  );
});

// Good for: API calls, fresh content
// Bad for: Performance on slow networks

3. Stale While Revalidate

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(cachedResponse => {
      const fetchPromise = fetch(event.request).then(networkResponse => {
        cache.put(event.request, networkResponse.clone());
        return networkResponse;
      });
      return cachedResponse || fetchPromise;
    })
  );
});

// Good for: Balance of performance and freshness
// Used by: News sites, blogs

Analyzing Service Worker Capabilities

1. Cache Storage Analysis

// See what's cached
async function analyzeCaches() {
  const cacheNames = await caches.keys();
  console.log('Cache storages:', cacheNames);
  
  for (const cacheName of cacheNames) {
    const cache = await caches.open(cacheName);
    const requests = await cache.keys();
    
    console.log(`\nCache: ${cacheName}`);
    console.log(`Items: ${requests.length}`);
    
    // Categorize cached items
    const categories = {
      html: 0,
      css: 0,
      js: 0,
      images: 0,
      api: 0,
      other: 0
    };
    
    requests.forEach(request => {
      const url = new URL(request.url);
      if (url.pathname.endsWith('.html')) categories.html++;
      else if (url.pathname.endsWith('.css')) categories.css++;
      else if (url.pathname.endsWith('.js')) categories.js++;
      else if (url.pathname.match(/\.(jpg|jpeg|png|gif|webp)/)) categories.images++;
      else if (url.pathname.includes('/api/')) categories.api++;
      else categories.other++;
    });
    
    console.table(categories);
  }
}

analyzeCaches();

2. Push Notification Support

// Check push capabilities
async function checkPushSupport() {
  if (!('PushManager' in window)) {
    console.log('❌ Push notifications not supported');
    return;
  }
  
  const registration = await navigator.serviceWorker.ready;
  const subscription = await registration.pushManager.getSubscription();
  
  if (subscription) {
    console.log('✅ Push notifications active');
    console.log('Endpoint:', subscription.endpoint);
    console.log('Provider:', subscription.endpoint.includes('fcm') ? 'Firebase' : 
                             subscription.endpoint.includes('mozilla') ? 'Mozilla' : 
                             'Other');
  } else {
    console.log('⚠️ Push supported but not subscribed');
  }
  
  // Check permission
  const permission = await Notification.permission;
  console.log('Notification permission:', permission);
}

checkPushSupport();

3. Background Sync Detection

// Check for background sync
async function checkBackgroundSync() {
  if (!('sync' in ServiceWorkerRegistration.prototype)) {
    console.log('❌ Background sync not supported');
    return;
  }
  
  const registration = await navigator.serviceWorker.ready;
  
  // Check one-time sync
  try {
    await registration.sync.register('test-sync');
    console.log('✅ Background sync supported');
  } catch {
    console.log('⚠️ Background sync blocked');
  }
  
  // Check periodic sync
  if ('periodicSync' in ServiceWorkerRegistration.prototype) {
    try {
      const status = await navigator.permissions.query({
        name: 'periodic-background-sync',
      });
      console.log('Periodic sync permission:', status.state);
    } catch {
      console.log('❌ Periodic sync not available');
    }
  }
}

checkBackgroundSync();

Security and Privacy Implications

1. Scope Hijacking Risk

// Dangerous: Root scope Service Worker
navigator.serviceWorker.register('/sw.js', { scope: '/' });

// Can intercept ALL requests to the domain
// Including sensitive pages like /admin, /api

2. Cache Poisoning

// Service Worker could cache malicious responses
self.addEventListener('fetch', event => {
  if (event.request.url.includes('/api/user')) {
    // Attacker could cache fake user data
    event.respondWith(new Response('{"isAdmin": true}'));
  }
});

3. Privacy Concerns

// Service Workers can track offline behavior
self.addEventListener('fetch', event => {
  // Log all requests even when offline
  logToIndexedDB({
    url: event.request.url,
    time: Date.now(),
    offline: !navigator.onLine
  });
});

Performance Impact Analysis

1. Cache Hit Ratio

// Monitor cache effectiveness
let stats = {
  cacheHits: 0,
  cacheMisses: 0,
  networkErrors: 0
};

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      if (cached) {
        stats.cacheHits++;
        return cached;
      }
      
      stats.cacheMisses++;
      return fetch(event.request).catch(() => {
        stats.networkErrors++;
        throw error;
      });
    })
  );
});

// Periodically log stats
setInterval(() => {
  const hitRate = (stats.cacheHits / (stats.cacheHits + stats.cacheMisses) * 100).toFixed(1);
  console.log(`Cache hit rate: ${hitRate}%`);
}, 60000);

2. Service Worker Overhead

// Measure SW boot time
let swStartTime;

self.addEventListener('activate', event => {
  swStartTime = performance.now();
});

self.addEventListener('fetch', event => {
  if (!swStartTime) return;
  
  const overhead = performance.now() - swStartTime;
  if (overhead > 1000) {
    console.warn('Slow Service Worker startup:', overhead + 'ms');
  }
});

Common Service Worker Issues

1. Update Problems

// Old Service Worker won't update
// Solution: Skip waiting
self.addEventListener('install', event => {
  self.skipWaiting(); // Force activation
});

self.addEventListener('activate', event => {
  event.waitUntil(clients.claim()); // Take control immediately
});

2. Cache Bloat

// Caches growing too large
async function cleanOldCaches() {
  const cacheWhitelist = ['cache-v2'];
  const cacheNames = await caches.keys();
  
  await Promise.all(
    cacheNames.map(cacheName => {
      if (!cacheWhitelist.includes(cacheName)) {
        console.log('Deleting old cache:', cacheName);
        return caches.delete(cacheName);
      }
    })
  );
}

3. Debugging Difficulties

// Add debug mode
const DEBUG = location.hostname === 'localhost';

function log(...args) {
  if (DEBUG) console.log('[SW]', ...args);
}

self.addEventListener('fetch', event => {
  log('Fetching:', event.request.url);
});

Quick Service Worker Audit

// Comprehensive SW audit
(async function auditServiceWorker() {
  console.log('🔍 Service Worker Audit\n');
  
  // 1. Check support
  if (!('serviceWorker' in navigator)) {
    console.log('❌ Service Workers not supported');
    return;
  }
  
  // 2. List registrations
  const registrations = await navigator.serviceWorker.getRegistrations();
  console.log(`📋 Registrations: ${registrations.length}`);
  
  for (const reg of registrations) {
    console.log('\n🔧 Service Worker:', reg.scope);
    console.log('Script:', reg.active?.scriptURL || 'None active');
    console.log('State:', reg.active?.state || 'Not active');
    
    // 3. Check update
    try {
      await reg.update();
      console.log('✅ Update check passed');
    } catch (e) {
      console.log('❌ Update check failed:', e.message);
    }
  }
  
  // 4. Cache analysis
  const cacheNames = await caches.keys();
  console.log(`\n💾 Caches: ${cacheNames.length}`);
  
  let totalSize = 0;
  for (const name of cacheNames) {
    const cache = await caches.open(name);
    const keys = await cache.keys();
    console.log(`  ${name}: ${keys.length} items`);
  }
  
  // 5. Capabilities
  console.log('\n🚀 Capabilities:');
  console.log('Push:', 'PushManager' in window ? '✅' : '❌');
  console.log('Sync:', 'sync' in ServiceWorkerRegistration.prototype ? '✅' : '❌');
  console.log('Periodic Sync:', 'periodicSync' in ServiceWorkerRegistration.prototype ? '✅' : '❌');
  
  // 6. PWA readiness
  const manifest = document.querySelector('link[rel="manifest"]');
  console.log('\n📱 PWA Status:');
  console.log('Manifest:', manifest ? '✅' : '❌');
  console.log('HTTPS:', location.protocol === 'https:' ? '✅' : '❌');
  console.log('Service Worker:', registrations.length > 0 ? '✅' : '❌');
})();

The Bottom Line

Service Workers reveal:

  • Offline capabilities - What works without internet
  • Performance strategy - Caching approach
  • User engagement - Push notifications, background sync
  • Technical sophistication - PWA implementation
  • Privacy approach - What's tracked offline

Modern web apps need Service Workers for competitive performance and user experience.


Detect Service Workers instantly: Fusebox shows all Service Workers, their caching strategies, and offline capabilities while you browse. Understand any site's PWA features. $29 one-time purchase.