Progressive Web Apps (PWA)

Service Workers Fundamentals

20 min Lesson 3 of 30

What are Service Workers?

Service Workers are JavaScript files that run separately from the main browser thread, acting as a programmable network proxy. They intercept network requests, cache resources, and enable offline functionality, making them the backbone of Progressive Web Apps.

Key Characteristics:
  • Runs in background: Separate from web pages, can't access DOM directly
  • Event-driven: Responds to events (install, activate, fetch, push, sync)
  • Programmable cache: Full control over caching strategies
  • HTTPS required: Must be served over secure connection (except localhost)
  • Scope-based: Controls pages within its scope path
  • Lifecycle-managed: Install, activate, and update phases

Service Worker Capabilities

What Service Workers CAN do: ✓ Intercept and handle network requests ✓ Cache resources (HTML, CSS, JS, images, API responses) ✓ Enable offline functionality ✓ Background sync (retry failed requests) ✓ Push notifications ✓ Manage multiple caches ✓ Update cached content intelligently What Service Workers CANNOT do: ✗ Access the DOM directly ✗ Use synchronous APIs (localStorage) ✗ Access cookies directly (use fetch with credentials) ✗ Run without being registered from a page

Service Worker Lifecycle

The Complete Lifecycle:
  1. Registration: Page registers the service worker file
  2. Installation: Service worker downloads and installs (happens once)
  3. Activation: Service worker takes control (happens after install)
  4. Idle: Waits for events (fetch, push, sync)
  5. Fetch/Event handling: Responds to network requests and events
  6. Termination: Browser can stop idle service workers to save memory
  7. Update: New version triggers new install/activate cycle

Service Worker Lifecycle Diagram

┌─────────────────┐ │ Page Loads │ │ Registers SW │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ INSTALLING │ ← First time only │ (install event) │ Cache static assets └────────┬────────┘ │ ▼ ┌─────────────────┐ │ INSTALLED │ │ (waiting) │ ← Waits for old SW to stop └────────┬────────┘ │ ▼ ┌─────────────────┐ │ ACTIVATING │ │ (activate event)│ ← Clean up old caches └────────┬────────┘ │ ▼ ┌─────────────────┐ │ ACTIVATED │ │ (idle) │ ← Controls pages in scope └────────┬────────┘ │ ▼ ┌─────────────────┐ │ FETCH/EVENTS │ ← Intercepts requests │ (fetch event) │ Handles push, sync └─────────────────┘

Browser Support

Browser Support Since Version
Chrome ✓ Full Support v40 (2015)
Firefox ✓ Full Support v44 (2016)
Safari ✓ Full Support v11.1 (2018)
Edge ✓ Full Support v17 (2018)
Opera ✓ Full Support v27 (2015)
Samsung Internet ✓ Full Support v4 (2016)
Browser Support Check:
if ('serviceWorker' in navigator) {
  console.log('Service Worker supported!');
} else {
  console.log('Service Worker not supported');
}

HTTPS Requirement

Security Requirement:

Service Workers MUST be served over HTTPS in production because they can intercept network requests and modify responses. This prevents man-in-the-middle attacks.

Exceptions:
  • localhost and 127.0.0.1 (for local development)
  • Local network addresses (e.g., 192.168.x.x) in some browsers
For Development:
  • Use localhost during development
  • Use tools like ngrok for HTTPS tunneling
  • Use Let's Encrypt for free SSL certificates in production

Service Worker Scope

Understanding Scope:

The scope determines which pages the service worker controls. By default, the scope is the directory where the service worker file is located.

// Service worker at: /sw.js // Default scope: / (entire site) navigator.serviceWorker.register('/sw.js'); // Service worker at: /app/sw.js // Default scope: /app/ (only pages under /app/) navigator.serviceWorker.register('/app/sw.js'); // Custom scope (must be within service worker directory or higher) navigator.serviceWorker.register('/sw.js', { scope: '/app/' // Only controls /app/* pages }); // INVALID: Can't control pages outside service worker's directory // Service worker at: /app/sw.js navigator.serviceWorker.register('/app/sw.js', { scope: '/' // ✗ Error: Scope outside service worker path });
Scope Best Practices:
  • Place service worker at root (/) for site-wide control
  • Use custom scope for controlling specific sections
  • Multiple service workers can control different scopes
  • More specific scopes take precedence over general ones

Debugging Service Workers

Chrome DevTools

1. Open DevTools (F12) 2. Go to Application tab 3. Click Service Workers in sidebar You can: ✓ See registered service workers ✓ Unregister service workers ✓ Force update/skip waiting ✓ Simulate offline mode ✓ View service worker source ✓ See active/waiting workers ✓ Check scope and status

Firefox DevTools

1. Open DevTools (F12) 2. Go to Application tab (or about:debugging#/runtime/this-firefox) 3. Click Service Workers Firefox-specific: ✓ See all service workers across all sites ✓ Debug service workers ✓ Start/stop service workers ✓ View push subscriptions

Safari DevTools

1. Enable Develop menu (Safari → Preferences → Advanced) 2. Develop → Service Workers 3. Select your site Safari-specific: ✓ List all service workers ✓ Inspect service worker console ✓ View service worker status

Service Worker Events

Core Events:
install Fired when service worker is first installed
activate Fired when service worker becomes active
fetch Fired for every network request in scope
message Communication between page and service worker
sync Background sync when connection restored
push Push notification received

Basic Service Worker Structure

// sw.js - Basic service worker file // Install event - Cache static assets self.addEventListener('install', event => { console.log('Service Worker: Installing...'); // Caching logic goes here }); // Activate event - Clean up old caches self.addEventListener('activate', event => { console.log('Service Worker: Activated'); // Cleanup logic goes here }); // Fetch event - Intercept network requests self.addEventListener('fetch', event => { console.log('Service Worker: Fetching', event.request.url); // Request handling logic goes here });
Common Pitfalls:
  • Not checking browser support: Always check if ('serviceWorker' in navigator)
  • Forgetting HTTPS: Service workers won't register on HTTP (except localhost)
  • Wrong scope: Service worker can't control pages outside its directory
  • Not updating: Old service workers can stay active indefinitely without update logic
  • Blocking activation: New service workers wait until all pages using old one close
  • Cache without cleanup: Old caches accumulate and waste storage

Service Worker Lifecycle Example

// Page script (main.js) if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js') .then(registration => { console.log('SW registered:', registration.scope); // Check for updates periodically registration.update(); // Listen for updates registration.addEventListener('updatefound', () => { const newWorker = registration.installing; newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { // New service worker available console.log('New content available, please refresh'); } }); }); }) .catch(error => { console.error('SW registration failed:', error); }); }
Exercise:
  1. Open Chrome DevTools → Application → Service Workers
  2. Visit a PWA (like twitter.com or pinterest.com) and inspect its service worker
  3. Check the service worker status, scope, and source code
  4. Use "Offline" checkbox to simulate offline mode
  5. Reload the page and observe how it works offline
  6. Click "Unregister" and reload - see the difference
  7. Create a simple HTML page and try registering a basic service worker
  8. Inspect the service worker lifecycle in DevTools