Service Workers
Overview
Formbird uses service workers to cache static assets for offline use. The service worker is built using Workbox, a Google library that simplifies service worker development. Service workers enable the application to load and function even when the device is offline.
Architecture
Service Worker Files
| File | Purpose |
|---|---|
ServiceWorker.ts |
Main service worker source (TypeScript) |
serviceWorker.js |
Built service worker in server/public |
ServiceWorkerStart.js |
Service worker registration and lifecycle |
ServiceWorkerUpdater.js |
Updates service worker when config changes |
Build Process
┌─────────────────────────────────────────────────────────────────────┐
│ Build Pipeline │
│ │
│ ServiceWorker.ts ──► Webpack ──► serviceWorker.js (unprocessed) │
│ │ │
│ ▼ │
│ Workbox injectManifest │
│ │ │
│ ▼ │
│ serviceWorker.js (with manifest) │
│ │
│ Manifest contains: │
│ - All static files (js, css, fonts, images) │
│ - Revision hashes for cache invalidation │
└─────────────────────────────────────────────────────────────────────┘
Workbox Configuration
Inject Manifest
The service worker uses Workbox's injectManifest to include the list of files to cache:
// gulp/tasks/service-worker.js
injectManifest({
"swDest": swDest,
"globDirectory": "server/public",
"globPatterns": [
"**/*.{gif,eot,svg,ttf,woff,woff2,ico,png,jpg,js,css,ijmap,html,wasm}"
],
"globIgnores": [
"vendor/custom-component-modules/**/*"
],
"swSrc": "client/app/scripts/modules/core/serviceWorkers/ServiceWorker.js",
"maximumFileSizeToCacheInBytes": 10 * 1024 * 1024 // 10mb
});
Glob Patterns
The globPatterns configuration determines which file types are cached:
| Extension | Type |
|---|---|
gif, png, jpg, ico |
Images |
js |
JavaScript bundles |
css |
Stylesheets |
woff, woff2, eot, ttf |
Fonts |
html |
HTML pages |
wasm |
WebAssembly modules |
ijmap |
Image map files |
Adding New File Types
To cache a new file type, add it to globPatterns:
"globPatterns": [
"**/*.{gif,eot,svg,ttf,woff,woff2,ico,png,jpg,js,css,ijmap,html,wasm,newtype}"
]
Service Worker Lifecycle
Registration
The service worker is registered when the application loads:
// ServiceWorkerStart.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/serviceWorker.js')
.then(registration => {
// Handle registration success
})
.catch(err => {
// Handle registration failure
});
}
States
| State | Description |
|---|---|
| Installing | Service worker being installed |
| Installed | Installation complete, waiting to activate |
| Activating | Taking over from previous version |
| Activated | Active and controlling pages |
| Redundant | Replaced by newer version |
Update Flow
1. Browser detects service worker file changed
2. New service worker starts installing
3. Old service worker continues serving requests
4. New service worker enters "waiting" state
5. When all tabs close, new version activates
6. App reloads with new service worker
Service Worker Updates
Automatic Updates
The service worker file is touched (modified timestamp updated) when:
- Configuration document changes
- Component is saved
- Vendor library is updated
- IndexedDB indexes change
This triggers the browser to check for updates.
ServiceWorkerUpdater
// ServiceWorkerUpdater.js
const serviceWorkerPath = './server/public/serviceWorker.js';
// Touch the file to trigger update
fs.utimesSync(serviceWorkerPath, new Date(), new Date());
Kubernetes Considerations
In Kubernetes deployments with multiple pods:
- Service worker must be shared between all pods
- Static files served from nginx (shared directory)
- All instances update the same service worker file
# Pods share the same public directory
volumes:
- name: public
persistentVolumeClaim:
claimName: web-app-public
Caching Strategies
Precaching (Static Assets)
Static assets are precached during service worker installation:
// Files listed in manifest are cached on install
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);
Runtime Caching (API Responses)
API responses can be cached at runtime:
// Cache-first for static data
workbox.routing.registerRoute(
/\/api\/getFile\//,
new workbox.strategies.CacheFirst()
);
// Network-first for dynamic data
workbox.routing.registerRoute(
/\/api\/documents/,
new workbox.strategies.NetworkFirst()
);
Vector Map Tile Caching
Map tiles are cached using runtime caching:
// Cache vector tiles from tile server
workbox.routing.registerRoute(
new RegExp('https://vmap.formbird.com/data/'),
new workbox.strategies.CacheFirst()
);
Vector Map Caching
Configuration
Configure tile server in clientConfiguration:
{
"clientConfiguration": {
"openLayers": {
"tileServerGL": {
"url": "https://vmap.formbird.com/data/v3/{z}/{x}/{y}.pbf"
}
}
}
}
Supported Tile Servers
| Server | URL Pattern |
|---|---|
| tileserver-gl | http://localhost:8080/data/v3/{z}/{x}/{y}.pbf |
| vmap.formbird.com | https://vmap.formbird.com/data/v3/{z}/{x}/{y}.pbf |
| MapBox | https://{a-d}.tiles.mapbox.com/v4/mapbox.streets-v6/{z}/{x}/{y}.vector.pbf |
| MapTiler | https://api.maptiler.com/tiles/v3-openmaptiles/{z}/{x}/{y}.pbf |
Caching Process
- User enables offline caching
- Service worker caches tiles for configured region
- Tiles stored in browser cache
- Map loads from cache when offline
Map Tile Storage Location
Map tiles are cached in "Cache Storage" under tiles-UUID, where UUID is unique for each device user. This allows multiple different Formbird logins to cache map tiles on the same browser without conflicts.
To view cached map tiles:
1. Open DevTools → Application → Cache Storage
2. Look for cache names starting with tiles-
Troubleshooting
Service Worker Not Installing
Symptoms: - White screen on load - App stuck at "Installing" - Service worker state stays at "installing"
Causes and Solutions:
| Cause | Solution |
|---|---|
| Slow connection | Increase timeout or improve network |
event.waitUntil blocking |
Check for stuck promises |
| Cache storage full | Clear browser storage |
| HTTPS required | Use localhost or HTTPS |
Service Worker Not Updating
Symptoms: - Old version still showing after deploy - Changes not appearing
Solutions:
1. Hard refresh: Shift + Ctrl + R (Windows) or Shift + Cmd + R (Mac)
2. Clear site data in browser dev tools
3. Unregister service worker manually
4. Check service worker file changed on server
Files Not Cached
Symptoms:
- Specific files fail to load offline
- net::ERR_FAILED errors
Solutions:
1. Verify file type in globPatterns
2. Check file size under maximumFileSizeToCacheInBytes
3. Ensure file exists in server/public
4. Check for globIgnores excluding the file
WASM Files Not Working Offline
WebAssembly files need to be included in caching:
// Ensure wasm is in globPatterns
"globPatterns": [
"**/*.{gif,eot,svg,ttf,woff,woff2,ico,png,jpg,js,css,ijmap,html,wasm}"
]
Version Mismatch Issues
The VersionService checks app version on startup:
// version.service.ts
if (appVersion !== serverAppVersion && appVersion !== undefined) {
// Only unregister if appVersion was previously set
await navigator.serviceWorker.getRegistration().then(reg => reg?.unregister());
}
Note: The appVersion !== undefined check prevents unregistering newly installed service workers on clean browsers.
Debugging
Chrome DevTools
- Open DevTools → Application tab
- Navigate to Service Workers section
- View registration status and logs
- Use "Update on reload" for development
Firefox DevTools
- Navigate to
about:debugging#/runtime/this-firefox - Find service worker in list
- Click "Inspect" to debug
Viewing Cached Files
- DevTools → Application → Cache Storage
- Expand cache names to see files
- Right-click to delete specific entries
Service Worker Console
// Enable verbose logging in service worker
Dexie.debug = true;
console.log('Service worker activating');
Related Documentation
- 101-Offline-Architecture.md - System overview
- 105-Map-and-Tile-Caching.md - Map caching details
- 106-Initial-Caching-Process.md - Caching workflow
- 109-Configuration-Reference.md - Configuration options