# Service Worker Reference

The Acequia service worker (`sw.js`) intercepts all fetch requests and routes them to the appropriate handler. It manages the route registry, transport selection, caching, and the virtual filesystem.

## Lifecycle

### Install

```javascript
self.addEventListener('install', () => {
    self.skipWaiting() // Activate immediately, don't wait for old tabs to close
})
```

### Activate

```javascript
self.addEventListener('activate', (event) => {
    event.waitUntil(clients.claim())  // Control all open tabs
    // Clean up old caches
    // Broadcast REQUEST_ROUTE_REGISTRY to all clients
})
```

On activation, the SW asks all controlled tabs to re-send their route registrations. This handles SW updates gracefully — routes are restored without page reload.

## Fetch Handler Chain

Every fetch request passes through handlers in order. First handler to return a response wins.

### 1. handleGroupRouteRequest

**Matches:** `/groups/{groupId}/...`

Routes requests to the peer that owns the matching route pattern.

**Flow:**
1. Parse `groupId` from URL
2. Look up routes for that group in `groupRoutes`
3. Find matching route pattern via `matchRoute()`
4. Find the instance that owns this route
5. Choose transport (see Transport Selection below)
6. Return response from handler

**Transport Selection:**

```
Is the route handler in a local tab?
  ├─ Yes → Is the tab still alive (clients.matchAll)?
  │   ├─ Yes → Send via postMessage, wait 30s for response
  │   └─ No → Fall through to WebRTC
  └─ No → Is there a WebRTC peer with this route?
      ├─ Yes → Send via peer.sendBig(), wait 30s
      └─ No → Proxy via HTTP to discovery server
```

### 2. handleWebDAVRequest

**Matches:** `/acq/...`

Proxies requests to WebDAV servers via the virtual filesystem mount system.

**Flow:**
1. Strip `/acq` prefix
2. Look up mount point in IndexedDB (`acequiaMounts` store)
3. Find longest matching path prefix
4. Rewrite URL to actual WebDAV server URL
5. Look up credentials in IndexedDB (`acequiaCredentials` store)
6. Proxy request with auth headers
7. Rewrite response paths back to `/acq/` format

**If no mount point found:** Returns 404 immediately. Does NOT fall through.

### 3. alwaysFetchHandler

**Matches:** URLs matching patterns in `config.alwaysFetch`

Matching uses `String.search()`, so `'/'` matches ALL URLs. This is the current default config.

Always fetches from network. Never serves from cache.

### 4. cacheHandler

**Matches:** Everything else (fallback)

Serves from cache if available, otherwise fetches from network and caches the response.

## Global State

```javascript
const instanceMap = {}
// { instanceId: { client: ClientId, meta: {}, groups: [...], lastUpdate: timestamp } }

const groupRoutes = {}
// { groupId: { pattern: { instanceId, pattern, metadata } } }

const groupRouteVersions = {}
// { groupId: { version: number, sessionId: string } }

const pendingWebRTCRequests = {}
// { requestId: { resolve, reject, timeout } }
```

## Message Types

### Browser → Service Worker

| Type | Purpose | Payload |
|------|---------|---------|
| `SKIP_WAITING` | Force SW activation | — |
| `CLAIM_CLIENTS` | Take control of pages | — |
| `REGISTER_INSTANCE` | Register a tab | `{ instanceId, groups, meta }` |
| `UPDATE_GROUPS` | Update group memberships | `{ instanceId, groups }` |
| `ROUTE_REGISTRY` | Route registry update | `{ group, version, sessionId, data }` |
| `proxiedResponse` | Response from local handler | `{ requestId, data: { status, headers, body } }` |
| `webrtcProxiedResponse` | Response from WebRTC peer | `{ requestId, data }` |
| `BROADCAST` | Broadcast to all clients | `{ message }` |
| `BROADCAST_STATUS` | Request instance status | — |
| `GET_SW_STATE` | Get introspection snapshot | — |

### Service Worker → Browser

| Type | Purpose | Payload |
|------|---------|---------|
| `proxiedRequest` | Forward request to handler | `{ requestId, path, method, headers, body }` |
| `webrtcProxiedRequest` | Forward via WebRTC | `{ requestId, path, method, headers, body }` |
| `REQUEST_ROUTE_REGISTRY` | Ask clients to re-register | — |
| `SW_STATE` | Introspection response | `{ groupRoutes, instanceMap, ... }` |

## Route Registry Versioning

The route registry uses versioning to prevent stale updates:

```javascript
// On receiving ROUTE_REGISTRY message:
const current = groupRouteVersions[groupId]
const incoming = { version, sessionId }

if (current && incoming.sessionId !== current.sessionId) {
    // Server restarted — accept update, reset version tracking
    current.version = -1
    current.sessionId = incoming.sessionId
}

if (current && incoming.version < current.version) {
    // Stale update from before reconnect — reject
    return
}

// Accept update
groupRoutes[groupId] = data
groupRouteVersions[groupId] = incoming
```

This handles:
- **Out-of-order messages:** Old version numbers are rejected
- **Server restarts:** New `sessionId` resets the version counter
- **WebSocket reconnects:** Routes re-registered on reconnect, version bumps

## Instance Reaper

Every 60 seconds, the SW cleans up stale instances:

```javascript
// For each instance in instanceMap:
// 1. Check if client still exists via clients.matchAll()
// 2. Check if lastUpdate > 5 minutes ago
// 3. Remove if dead or stale
```

This prevents routing to closed tabs and avoids 30-second timeouts.

## Cache Management

| Cache Name | Key | Contents |
|------------|-----|----------|
| `source` | Fixed | Application scripts (if `cacheSource: true`) |
| `external` | Fixed | External resources (CDN, etc.) |
| `scripts-{version}` | Versioned | Data cache |

On activation, old versioned caches are deleted.

## Virtual Filesystem (/acq/)

See [VIRTUAL_FILESYSTEM.md](../../localWebDAV/docs/VIRTUAL_FILESYSTEM.md) for the mount point system.

### Mount Point Resolution

1. Request: `/acq/events/2024/fire.json`
2. Strip prefix: `/events/2024/fire.json`
3. Check mount points for longest match:
   - `/events` → match, `subPath: '/v2/events'`
4. Rewrite: `https://server.com/v2/events/2024/fire.json`
5. Add auth from credentials store
6. Proxy and return

### Credential Types

| Type | Auth Method |
|------|-------------|
| `acequiaToken` | Auto-generates JWT token |
| `simple` | Basic auth (username/password) |

## Debugging

### Get SW State

```javascript
// From browser console:
const state = await acequia.diagnoseRoutes()
console.log(state.groupRoutes)    // All registered routes
console.log(state.instanceMap)     // All registered instances
console.log(state.groupRouteVersions) // Version tracking
```

### Force SW Update

```javascript
// From browser console:
navigator.serviceWorker.controller.postMessage({ type: 'SKIP_WAITING' })
```

### Bypass SW

In DevTools > Application > Service Workers, check "Bypass for network" to skip the SW entirely. Useful for debugging whether an issue is in the SW or the app.