-
Notifications
You must be signed in to change notification settings - Fork 0
Service Worker
Coho uses a custom service worker for PWA functionality, offline resilience, and background work. This document describes the build, registration, caching strategies, and key features.
src/sw.ts ← Source of truth (TypeScript)
vite.config.ts ← Build plugins (dev-service-worker, build-sw)
public/sw.js ← Dev build output (gitignored)
dist/sw.js ← Production build output
index.html ← Registration script
The service worker is built separately from the main app using custom Vite plugins in vite.config.ts.
-
Trigger:
buildStartwhen Vite dev server starts (apply: 'serve') -
Output:
public/sw.js - Format: ES module, unminified
-
Version: Injects
__APP_VERSION__with an ISO timestamp
-
Trigger:
closeBundleafter the main build completes (apply: 'build') -
Output:
dist/sw.js - Format: ES module, minified with Terser
- Optimizations: Inlines all imports (required for SW scope)
Both builds inject __APP_VERSION__, which is used to version cache names.
The service worker is registered in index.html after a 15s delay to prioritize initial page load:
const swUrl = '/sw.js';
const swOptions = { scope: '/', type: 'module' };
navigator.serviceWorker.register(swUrl, swOptions);-
controllerchange: forces a page reload when a new SW takes control. -
SW_ACTIVATEDmessage: forces a reload when activation completes. -
pwa-update-availableevent: dispatched withupdateServiceWorker()to callSKIP_WAITING.
| Content Type | Strategy | Cache Name |
|---|---|---|
| Navigation requests | Stale-while-revalidate app shell | pages-{VERSION} |
| Scripts & styles | Cache-first | assets-{VERSION} |
| Images | Cache-first | images-{VERSION} |
| API GET requests | Network-first | pages-{VERSION} |
| Mutations | Network with background sync | — |
On install, the SW precaches:
//index.html/manifest.json
For Mastodon API mutations and other non-GET API requests, the SW attempts a network request. If it fails due to a network error, the request is queued and replayed later.
-
Sync tag:
mastodon-api-sync -
Queue key:
background-sync-queue(IndexedDB viaidb-keyval)
Queued requests are stored as:
interface QueuedRequest {
id: string;
url: string;
method: string;
headers: Record<string, string>;
body: string | null;
timestamp: number;
}- 2xx responses: removed from queue
- 4xx responses: removed from queue (won't succeed on retry)
- 5xx responses: kept for retry
- Network error: kept for retry
The following Mastodon API endpoints are queued when offline:
| Action | Endpoint Pattern |
|---|---|
| Create post | POST /api/v1/statuses |
| Edit/delete post | PUT/DELETE /api/v1/statuses/:id |
| Favorite/unfavorite | POST /api/v1/statuses/:id/(un)favourite |
| Boost/unboost | POST /api/v1/statuses/:id/(un)reblog |
| Bookmark/unbookmark | POST /api/v1/statuses/:id/(un)bookmark |
| Pin/unpin | POST /api/v1/statuses/:id/(un)pin |
| Follow/unfollow | POST /api/v1/accounts/:id/(un)follow |
| Block/unblock | POST /api/v1/accounts/:id/(un)block |
| Mute/unmute | POST /api/v1/accounts/:id/(un)mute |
| Vote in poll | POST /api/v1/polls/:id/votes |
| Clear/dismiss notifications | POST /api/v1/notifications/... |
/share POST requests are intercepted to support the Web Share Target flow.
- Shared files are cached in
shareTargetcache storage. - The user is redirected to
/homewith query parameters referencing cached files.
- Handles Mastodon push payloads, strips HTML, and shows rich notifications.
- Uses the Badging API when available.
- Notification actions include “Follow back” for follow events.
- Clears badge counts.
- Focuses or opens a window to the relevant route.
- Supports follow-back action by fetching the notification and issuing a follow request.
- Responds to
widgetinstalland updates widget templates viaself.widgets.
Uses periodicsync to keep content fresh:
-
get-notifications→ fetches notifications and triggers badges/notifications -
timeline-sync→ refreshes timeline cache in IndexedDB
The service worker cannot access localStorage, so credentials are synced to IndexedDB by the app:
const accessToken = await get('accessToken');
const server = await get('server');app-index.ts performs the sync on startup.
Old caches are deleted on activation, excluding the share-target cache:
if (!Object.values(CACHE_NAMES).includes(cacheName) && cacheName !== 'shareTarget') {
return caches.delete(cacheName);
}navigator.serviceWorker.controller;
navigator.serviceWorker.ready;DevTools → Application → Cache Storage
DevTools → Application → Service Workers → “Update on reload”
The SW logs its version on load:
[SW] Build version: 2026-02-04T18:32:00.000Z
Coho uses a custom SW for:
- Full control over caching, lifecycle, and background sync
- Smaller runtime footprint
- Custom features like share target handling and Mastodon-specific notifications
- Easier debugging without extra abstraction layers