# Registered Route Request Flows

This document describes the three possible paths a request to a group registered route can take in the Acequia system.

## Overview

When a client makes a request to a registered route (pattern: `/groups/{groupId}/{instanceId}/*`), the system can handle it in three ways:

1. **Direct HTTP to Discovery Server** - Request goes to discovery server, which proxies via WebSocket to registered peer
2. **Service Worker Local Interception** - SW intercepts and sends postMessage to local client that is the registered peer
3. **Service Worker WebRTC Proxy** - SW intercepts and relays via WebRTC through another client to reach registered peer

The selection logic follows a fallback chain:
- **First attempt:** Local client (same browser)
- **Second attempt:** WebRTC peer connection
- **Third attempt:** Discovery server HTTP proxy

---

## Flow 1: Direct HTTP Request to Discovery Server

**When Used:** Client is not using service worker, or all local/WebRTC attempts failed

**Path:** Client → Discovery Server → WebSocket → Registered Peer

### Files Involved

1. **Client making request:**
   - Any HTTP client (fetch, browser, etc.)
   - No acequia.js involvement required

2. **localDiscovery/server.mjs**
   - Main discovery server
   - Handles HTTP requests and WebSocket connections

3. **localDiscovery/src/peerProxy.mjs**
   - Implements HTTP-to-WebSocket proxy
   - Routes requests to connected peers

4. **localDiscovery/src/groups.mjs**
   - Manages group memberships
   - Maintains route registry

5. **Registered peer (target instance):**
   - acequia/groups.js - Group class receives proxied request
   - acequia/routes.js - Router matches and executes handler

### Detailed Flow

```
┌─────────────┐
│   Client    │
│  (any HTTP) │
└──────┬──────┘
       │ HTTP GET/POST
       │ /groups/{groupId}/{instanceId}/some/path
       ▼
┌─────────────────────────────────────────┐
│  Discovery Server (server.mjs)          │
│                                         │
│  1. Receives HTTP request               │
│  2. Calls handleGroupRouteRequest()     │
│     (peerProxy.mjs)                     │
└──────┬──────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  Route Registry (groups.mjs)            │
│                                         │
│  3. Find route matching path pattern    │
│  4. Identify target instance by ID      │
│  5. Get WebSocket connection to target  │
└──────┬──────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  peerProxy.mjs                          │
│                                         │
│  6. Create proxiedRequest message       │
│  7. Send via WebSocket to peer          │
│  8. Wait for proxiedResponse            │
└──────┬──────────────────────────────────┘
       │ WebSocket message
       │ type: 'proxiedRequest'
       ▼
┌─────────────────────────────────────────┐
│  Registered Peer Instance               │
│  (Browser running acequia.js)           │
│                                         │
│  9. groups.js receives message          │
│                           │
│  10. router.matchRoute(data.path)       │
│                               │
│  11. Execute handler(data)              │
│                               │
│  12. Send proxiedResponse back          │
│                           │
└──────┬──────────────────────────────────┘
       │ WebSocket message
       │ type: 'proxiedResponse'
       ▼
┌─────────────────────────────────────────┐
│  peerProxy.mjs                          │
│                                         │
│  13. Convert response to HTTP format    │
│  14. Return HTTP response to client     │
└──────┬──────────────────────────────────┘
       │ HTTP Response
       ▼
┌─────────────┐
│   Client    │
│  (receives) │
└─────────────┘
```

### Key Functions

**server.mjs:**
```javascript
// Main HTTP request handler
app.all('/groups/:groupId/:instanceId/*', handleGroupRouteRequest)
```

**peerProxy.mjs:**
```javascript
export async function handleGroupRouteRequest(req, res, groupMan) {
    // 1. Extract groupId, instanceId, path from URL
    // 2. Look up route in registry
    // 3. Create proxiedRequest object
    // 4. Send via WebSocket to registered peer
    // 5. Wait for response (with timeout)
    // 6. Convert to HTTP response
}
```

**groups.mjs:**
```javascript
class GroupManager {
    // Store route registrations
    registerRoute(instanceId, pattern, metadata)

    // Find matching route
    findRoute(path)

    // Get WebSocket connection for instance
    getConnectionByInstanceId(instanceId)
}
```

**acequia/groups.js:**
```javascript
// Handle incoming proxied requests
this.server.addEventListener('message', async (event) => {
    let data = JSON.parse(event.data)

    if (data.type === 'proxiedRequest') {
        try {
            let match = this.router.matchRoute(data.path)
            this.sendProxiedResponse({
                requestId: data.requestId,
                data: await match.handler(data),
            })
        } catch (e) {
            this.sendProxiedResponse({
                requestId: data.requestId,
                data: createErrorResponse(e),
            })
        }
    }
})
```

---

## Flow 2: Service Worker Local Interception

**When Used:** Service worker is active and target instance is in same browser (different tab)

**Path:** Client → Service Worker → postMessage → Local Client (Registered Peer)

### Files Involved

1. **Client making request:**
   - Uses fetch() API
   - Service worker intercepts before network

2. **sw.js**
   - Intercepts fetch events
   - Maintains instanceMap of local clients
   - Routes via postMessage

3. **acequia/acequia.js**
   - Receives proxiedRequest messages
   - Routes to appropriate group

4. **acequia/groups.js**
   - Router matches route
   - Executes handler

### Detailed Flow

```
┌─────────────────────────────────────────┐
│  Client Tab A                           │
│  (making request)                       │
└──────┬──────────────────────────────────┘
       │ fetch('/groups/mygroup/instance123/data')
       ▼
┌─────────────────────────────────────────┐
│  Service Worker (sw.js)                 │
│                                         │
│  1. fetch event listener               │
│  2. Match /groups/ pattern              │
│  3. Call handleGroupRouteRequest()      │
└──────┬──────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  handleGroupRouteRequest()              │
│                                         │
│  4. Extract groupId, path from URL      │
│  5. Look up route in groupRoutes        │
│  6. Check if local instance exists      │
└──────┬──────────────────────────────────┘
       │ if (targetInstance.client)
       ▼
┌─────────────────────────────────────────┐
│  sendRequestToInstance()                │
│                                         │
│  7. Create proxiedRequest object        │
│     - requestId, path, method, headers  │
│     - body (if not GET/HEAD), cookies   │
│  8. Send via postMessage to client      │
│  9. Store in pendingRequests map        │
│  10. Set 30s timeout                    │
└──────┬──────────────────────────────────┘
       │ postMessage({type: 'proxiedRequest'})
       ▼
┌─────────────────────────────────────────┐
│  Client Tab B (Registered Peer)         │
│  acequia/acequia.js                     │
│                                         │
│  11. Service worker message listener    │
│  12. Check data.type === 'proxiedRequest'│
│  13. Call handleProxiedRequest()        │
└──────┬──────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  handleProxiedRequest() (acequia.js)    │
│                                         │
│  14. Iterate activeGroups Map           │
│  15. Find group with matching route     │
│  16. group.router.matchRoute(path)      │
│  17. Execute matched handler(request)   │
│  18. Post response back to SW           │
└──────┬──────────────────────────────────┘
       │ source.postMessage({type: 'proxiedResponse'})
       ▼
┌─────────────────────────────────────────┐
│  Service Worker (sw.js)                 │
│  Message listener                       │
│                                         │
│  19. Receive proxiedResponse            │
│  20. Find pendingRequests[requestId]    │
│  21. Call resolve(response)             │
│  22. Clear timeout                      │
└──────┬──────────────────────────────────┘
       │ Promise resolves
       ▼
┌─────────────────────────────────────────┐
│  handleGroupRouteRequest() (sw.js)      │
│                                         │
│  23. Create Response from response data │
│  24. Return Response object             │
└──────┬──────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  Client Tab A                           │
│  (receives fetch response)              │
└─────────────────────────────────────────┘
```

### Key Functions

**sw.js:**
```javascript
// Intercept all fetch requests
self.addEventListener('fetch', (event) => {
    // interceptFetch is the main fetch handler
    event.respondWith(interceptFetch(event))
})

// handleGroupRouteRequest checks for /groups/ pattern
async function handleGroupRouteRequest(ev) {
    const url = new URL(ev.request.url)
    if (!url.pathname.startsWith('/groups/')) return
    // ... route matching and handling
}

// Main routing logic
async function handleGroupRouteRequest(ev) {
    const url = new URL(ev.request.url)
    let groupMatchRes = matchGroupPath(url.pathname)
    if (!groupMatchRes) return

    const { groupId, path } = groupMatchRes
    const routes = groupRoutes[groupId]

    // Check for local instance
    if (targetInstance && targetInstance.client) {
        const proxiedRequest = { /* ... */ }
        response = await sendRequestToInstance(
            targetInstance.client,
            proxiedRequest
        )
    }
}

// Send to local client via postMessage
function sendRequestToInstance(client, request) {
    return new Promise((resolve, reject) => {
        const timeout = setTimeout(() => {
            delete pendingRequests[request.requestId]
            reject(new Error('Request timeout'))
        }, 30000) // 30 second timeout

        pendingRequests[request.requestId] = { resolve, reject, timeout }
        client.postMessage(request)
    })
}

// Receive responses (in message event handler)
self.addEventListener('message', async (e) => {
    const { data } = e
    // ...
    if (data?.type === 'proxiedResponse') {
        const { requestId, response } = data
        if (pendingRequests[requestId]) {
            const { resolve, reject, timeout } = pendingRequests[requestId]
            clearTimeout(timeout)
            delete pendingRequests[requestId]
            resolve(response)
        }
    }
})
```

**acequia/acequia.js:**
```javascript
// Listen for proxied requests from service worker
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.addEventListener('message', async (event) => {
        const { data } = event
        if (data?.type === 'proxiedRequest') {
            await handleProxiedRequest(data, event.source)
        } else if (data?.type === 'webrtcProxiedRequest') {
            await handleWebRTCProxiedRequest(data, event.source)
        }
    })
}

// Handle proxied request
async function handleProxiedRequest(request, source) {
    const { requestId, path } = request

    try {
        // Find a group that can handle this request
        for (const [groupId, group] of activeGroups) {
            const match = group.router.matchRoute(path)
            if (match) {
                const responseData = await match.handler(request)
                source.postMessage({
                    type: 'proxiedResponse',
                    requestId,
                    response: { data: responseData },
                })
                return
            }
        }

        // No matching route found
        source.postMessage({
            type: 'proxiedResponse',
            requestId,
            response: {
                data: {
                    status: 404,
                    statusText: 'Not Found',
                    body: 'No route handler found',
                    headers: { 'content-type': 'text/plain' },
                },
            },
        })
    } catch (error) {
        // Send error response
        source.postMessage({
            type: 'proxiedResponse',
            requestId,
            response: {
                data: {
                    status: 500,
                    statusText: 'Internal Server Error',
                    body: error.message,
                    headers: { 'content-type': 'text/plain' },
                },
            },
        })
    }
}
```

**acequia/routes.js:**
```javascript
export class Router {
    matchRoute(path) {
        // Find matching pattern
        for (const [pattern, { handler, metadata }] of this.routes) {
            const match = this.matchPattern(pattern, path)
            if (match) {
                return { handler, params: match.params, metadata }
            }
        }
        return null
    }
}
```

---

## Flow 3: Service Worker WebRTC Proxy

**When Used:** Target instance not in same browser, but reachable via WebRTC peer connection

**Path:** Client → Service Worker → postMessage → Local Client → WebRTC → Remote Peer (Registered Peer)

### Files Involved

1. **Client making request:**
   - Uses fetch() API
   - Service worker intercepts

2. **sw.js**
   - Intercepts fetch events
   - Finds local client in same group
   - Relays via postMessage

3. **acequia/acequia.js**
   - Receives webrtcProxiedRequest
   - Manages peer connections
   - Routes to group

4. **acequia/groups.js**
   - Manages peer connections
   - Sends request via WebRTC
   - Waits for response

5. **acequia/webrtc/webrtc.js**
   - Handles peer connections
   - Receives and processes requests
   - Routes to local handler

6. **acequia/webrtc/dataUtils.js**
   - Encodes/decodes binary data
   - Handles Blobs and ArrayBuffers

### Detailed Flow

```
┌─────────────────────────────────────────┐
│  Client Tab A                           │
│  (making request)                       │
└──────┬──────────────────────────────────┘
       │ fetch('/groups/mygroup/remoteInstance/data')
       ▼
┌─────────────────────────────────────────┐
│  Service Worker (sw.js)                 │
│                                         │
│  1. fetch event listener                │
│  2. Match /groups/ pattern              │
│  3. Call handleGroupRouteRequest()      │
└──────┬──────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  handleGroupRouteRequest()              │
│                                         │
│  4. Extract groupId, path from URL      │
│  5. Look up route in groupRoutes        │
│  6. Local instance NOT found            │
│  7. Try WebRTC fallback                 │
└──────┬──────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  sendRequestViaWebRTC()                 │
│                                         │
│  8. Find instances in same group        │
│     Filter instanceMap by groupId       │
│  9. Select first available instance     │
│     (relay instance)                    │
│  10. Create webrtcProxiedRequest        │
│  11. Store in pendingWebRTCRequests     │
│  12. Set 30s timeout                    │
│  13. Send postMessage to relay instance │
└──────┬──────────────────────────────────┘
       │ postMessage({type: 'webrtcProxiedRequest'})
       ▼
┌─────────────────────────────────────────┐
│  Client Tab B (Relay Instance)          │
│  acequia/acequia.js                     │
│                                         │
│  14. Service worker message listener    │
│  15. Check type === 'webrtcProxiedRequest'│
│  16. Call handleWebRTCProxiedRequest()  │
└──────┬──────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  handleWebRTCProxiedRequest()           │
│  (acequia.js)                           │
│                                         │
��  17. Extract targetInstanceId, request  │
│  18. Search activeGroups for peer       │
│      group.getPeerByInstanceId()        │
│  19. If no peer, try to connect         │
│      group.connectToPeer()              │
│  20. Wait for peer.ready                │
│  21. Call group.sendRequestViaPeer()    │
└──────┬──────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  groups.js - connectToPeer()            │
│                                         │
│  22. Check pendingPeerRequests          │
│  23. Call addPeer() from webrtc.js      │
│      (initiator: true)                  │
│  24. Wait for WebRTC connection         │
│  25. Return peer object                 │
└──────┬──────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  groups.js - sendRequestViaPeer()       │
│  ()                         ��
│                                         │
│  26. Create response handler            │
│      Listen for 'dataBig' event         │
│  27. Convert ArrayBuffer body to Blob   │
│  28. peer.sendBig({                     │
│         type: 'peerProxiedRequest',     │
│         requestId, path, method, etc.   │
���      })                                 │
│  29. Wait for response (30s timeout)    │
└──────┬──────────────────────────────────���
       │ WebRTC Data Channel
       │ MessagePack encoded chunks
       ▼
┌─────────────────────────────────────────┐
│  Remote Peer Instance                   │
│  webrtc/webrtc.js             │
│                                         │
│  30. peer.on('dataBig') listener        │
│  31. Check type === 'peerProxiedRequest'│
│  32. Call handlePeerProxiedRequest()    │
│                           │
└──────┬──────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  handlePeerProxiedRequest()             │
│  (webrtc.js)                   │
│                                         │
│  33. Get peer.group and router          │
│  34. group.router.matchRoute(path)      │
│  35. Execute match.handler(data)        │
│  36. peer.sendBig({                     │
│         type: 'peerProxiedResponse',    │
│         requestId, response             │
│      })                                 │
└──────┬──────────────────────────────────┘
       │ WebRTC Data Channel
       │ MessagePack encoded response
       ▼
┌─────────────────────────────────────────┐
│  Client Tab B (Relay Instance)          │
│  groups.js - sendRequestViaPeer()       │
│                                         │
│  37. responseHandler receives data      │
│  38. Check requestId matches            │
│  39. resolve(data.response)             │
└──────┬──────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  handleWebRTCProxiedRequest()           │
│  (acequia.js)                           │
│                                         │
│  40. Post response to service worker    │
│      source.postMessage({               │
│          type: 'webrtcProxiedResponse', │
│          requestId, response            │
│      })                                 │
└──────┬──────────────────────────────────┘
       │ postMessage
       ▼
┌─────────────────────────────────────────┐
│  Service Worker (sw.js)                 │
│  Message listener             │
│                                         │
│  41. Receive webrtcProxiedResponse      │
│  42. Find pendingWebRTCRequests[id]     │
│  43. Call resolve(response)             │
│  44. Clear timeout                      │
└──────┬──────────────────────────────────┘
       │ Promise resolves
       ▼
┌─────────────────────────────────────────┐
│  handleGroupRouteRequest() (sw.js)      │
│                                         │
│  45. createResponse(response)           │
│  46. Return Response object             │
└──────┬──────────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────────┐
│  Client Tab A                           │
│  (receives fetch response)              │
└─────────────────────────────────────────┘
```

### Key Functions

**sw.js:**
```javascript
// Try WebRTC fallback when local instance not found
async function handleGroupRouteRequest(ev) {
    const targetInstance = instanceMap[targetInstanceId]

    if (targetInstance && targetInstance.client) {
        // Flow 2: Local instance
    } else {
        // Flow 3: WebRTC
        try {
            const requestBody = ev.request.method !== 'GET'
                ? await ev.request.clone().arrayBuffer()
                : null

            const proxiedRequest = { /* ... */ }
            response = await sendRequestViaWebRTC(
                groupId,
                targetInstanceId,
                proxiedRequest
            )
        } catch (webrtcError) {
            // Flow 1: Fallback to server
        }
    }
}

// Find relay instance and send request
async function sendRequestViaWebRTC(groupId, targetInstanceId, request) {
    return new Promise((resolve, reject) => {
        const timeout = setTimeout(() => {
            delete pendingWebRTCRequests[request.requestId]
            reject(new Error('WebRTC request timeout'))
        }, 30000)

        pendingWebRTCRequests[request.requestId] = {
            resolve,
            reject,
            timeout,
        }

        // Find instances in this group
        const groupInstances = Object.values(instanceMap).filter(
            (inst) => inst.groups && inst.groups.includes(groupId)
        )

        if (groupInstances.length === 0) {
            reject(new Error('No relay instances available'))
            return
        }

        // Use first instance to relay
        const relayInstance = groupInstances[0]
        relayInstance.client.postMessage({
            type: 'webrtcProxiedRequest',
            targetInstanceId,
            request,
        })
    })
}
```

**acequia/acequia.js:**
```javascript
// Handle WebRTC proxied requests
async function handleWebRTCProxiedRequest(data, source) {
    const { targetInstanceId, request } = data
    const { requestId } = request

    try {
        // Find existing peer
        let targetPeer = null
        let targetGroup = null

        for (const [groupId, group] of activeGroups) {
            const peer = group.getPeerByInstanceId?.(targetInstanceId)
            if (peer) {
                targetPeer = peer
                targetGroup = group
                break
            }
        }

        // If no peer, create connection
        if (!targetPeer) {
            for (const [groupId, group] of activeGroups) {
                const peerInfo =
                    group.getPeerInfoByInstanceId?.(targetInstanceId)
                if (peerInfo) {
                    targetPeer = await group.connectToPeer(targetInstanceId)
                    targetGroup = group
                    break
                }
            }
        }

        if (!targetPeer) {
            throw new Error('No peer connection available')
        }

        await targetPeer.ready

        const response = await targetGroup.sendRequestViaPeer(
            targetPeer,
            request
        )

        source.postMessage({
            type: 'webrtcProxiedResponse',
            requestId,
            response,
        })
    } catch (error) {
        source.postMessage({
            type: 'webrtcProxiedResponse',
            requestId,
            error: error.message,
        })
    }
}
```

**acequia/groups.js:**
```javascript
// Create peer connection on demand
async connectToPeer(instanceId) {
    const peerInfo = this.getPeerInfoByInstanceId(instanceId)
    if (!peerInfo) {
        throw new Error('No peer info found')
    }

    // Avoid duplicate connections
    if (this.pendingPeerRequests[instanceId]) {
        return this.pendingPeerRequests[instanceId]
    }

    const connectionPromise = (async () => {
        try {
            const peer = addPeer({
                peerId: instanceId,
                group: this,
                initiator: true,
                trickle: true,
                iceServers: acequia.config.ice,
            })
            await peer.ready
            delete this.pendingPeerRequests[instanceId]
            return peer
        } catch (error) {
            delete this.pendingPeerRequests[instanceId]
            throw error
        }
    })()

    this.pendingPeerRequests[instanceId] = connectionPromise
    return connectionPromise
}

// Send request via WebRTC and wait for response
async sendRequestViaPeer(peer, request) {
    return new Promise((resolve, reject) => {
        const timeout = setTimeout(() => {
            peer.off('dataBig', responseHandler)
            reject(new Error('Peer request timeout'))
        }, 30000)

        const responseHandler = (data) => {
            if (data.type === 'peerProxiedResponse' &&
                data.requestId === request.requestId) {
                clearTimeout(timeout)
                peer.off('dataBig', responseHandler)
                resolve(data.response)
            }
        }

        peer.on('dataBig', responseHandler)

        // Convert ArrayBuffer to Blob for WebRTC
        const requestToSend = { ...request }
        if (requestToSend.body instanceof ArrayBuffer) {
            requestToSend.body = new Blob([requestToSend.body])
        }

        delete requestToSend.type

        peer.sendBig({
            type: 'peerProxiedRequest',
            ...requestToSend,
        })
    })
}
```

**acequia/webrtc/webrtc.js:**
```javascript
// Receive incoming WebRTC requests
peer.on('dataBig', async (data) => {
    if (data.type === 'peerProxiedRequest') {
        await handlePeerProxiedRequest(data, peer)
    }
})

// Handle incoming request and execute route
async function handlePeerProxiedRequest(data, peer) {
    const { requestId } = data

    try {
        const group = peer.group
        if (!group || !group.router) {
            throw new Error('No group or router available')
        }

        // Match route
        const match = group.router.matchRoute(data.path)
        if (!match) {
            throw new Error(`No route found for path: ${data.path}`)
        }

        // Execute handler
        const responseData = await match.handler(data)

        // Send response via WebRTC
        peer.sendBig({
            type: 'peerProxiedResponse',
            requestId,
            response: {
                data: responseData,
            },
        })
    } catch (error) {
        // Send error response
        peer.sendBig({
            type: 'peerProxiedResponse',
            requestId,
            response: {
                data: {
                    status: 500,
                    statusText: 'Internal Server Error',
                    body: JSON.stringify({
                        error: error.message,
                        stack: error.stack,
                    }),
                    headers: { 'content-type': 'application/json' },
                },
            },
        })
    }
}
```

**acequia/webrtc/dataUtils.js:**
```javascript
// Binary data encoding for WebRTC
export async function generateWebRTCpayload(obj) {
    let deBlobbed = await recursivelyEncodeBlobs(obj)
    return _generateWebRTCpayload(deBlobbed)
}

export async function recursivelyEncodeBlobs(obj, depth = 0) {
    if (obj === undefined || obj === null) return obj

    if ((typeof Blob !== 'undefined' && obj.constructor == Blob) ||
        (typeof File !== 'undefined' && obj.constructor == File)) {
        return await deBlob(obj)
    } else if (obj.constructor == Object) {
        let res = {}
        for (let i in obj) {
            if (obj[i] !== undefined) {
                res[i] = await recursivelyEncodeBlobs(obj[i], depth + 1)
            }
        }
        return res
    }
    return obj
}

export async function recursivelyDecodeBlobs(obj, depth = 0) {
    if (obj === undefined || obj === null) return obj

    if (obj.constructor == Object && obj.type && obj.isBlob) {
        if (typeof Blob !== 'undefined') {
            return new Blob([obj.view], { type: obj.type })
        }
    } else if (obj.constructor == Object) {
        let res = {}
        for (let i in obj) {
            res[i] = await recursivelyDecodeBlobs(obj[i], depth + 1)
        }
        return res
    }
    return obj
}
```

---

## Decision Flow Diagram

```
                                    ┌─────────────────┐
                                    │  Client Request │
                                    │  fetch(url)     │
                                    └────────┬────────┘
                                             │
                                             ▼
                              ┌──────────────────────────────┐
                              │  Is Service Worker Active?   │
                              └──────────┬───────────────────┘
                                         │
                         ┌───────────────┼───────────────┐
                         │ YES                           │ NO
                         ▼                               ▼
             ┌───────────────────────┐         ┌─────────────────┐
             │ SW Intercepts Request │         │  Direct HTTP    │
             │  (sw.js)              │         │  to Discovery   │
             └───────────┬───────────┘         │  Server         │
                         │                     │  (Flow 1)       │
                         ▼                     └─────────────────┘
         ┌───────────────────────────────┐
         │ Match /groups/{groupId}/      │
         │ {instanceId}/* pattern?       │
         └───────────┬───────────────────┘
                     │
                     ▼ YES
     ┌───────────────────────────────────────┐
     │ Is target instance in instanceMap?    │
     │ (same browser, different tab)         │
     └───────────┬───────────────────────────┘
                 │
      ┌──────────┼──────────┐
      │ YES                 │ NO
      ▼                     ▼
┌─────────────────┐   ┌──────────────────────────────┐
│ Local Instance  │   │ Are there other instances    │
│ postMessage     │   │ in the same group?           │
│ (Flow 2)        │   └────────────┬─────────────────┘
└─────────────────┘                │
                         ┌──────────┼──────────┐
                         │ YES                 │ NO
                         ▼                     ▼
              ┌──────────────────────┐   ┌─────────────────┐
              │ Try WebRTC via relay │   │  Direct HTTP    │
              │ instance             │   │  to Discovery   │
              │ (Flow 3)             │   │  Server         │
              └──────────┬───────────┘   │  (Flow 1)       │
                         │               └─────────────────┘
                         ▼
              ┌──────────────────────┐
              │ Does peer connection │
              │ exist or can connect?│
              └──────────┬───────────┘
                         │
              ┌──────────┼──────────┐
              │ YES                 │ NO
              ▼                     ▼
    ┌─────────────────┐      ┌─────────────────┐
    │ Send via WebRTC │      │  Direct HTTP    │
    │ (Flow 3)        │      │  to Discovery   │
    │                 │      │  Server         │
    └─────────────────┘      │  (Flow 1)       │
                             └─────────────────┘
```

---

## Summary Table

| Flow | Trigger | Relay Method | Latency | Use Case |
|------|---------|--------------|---------|----------|
| **1. Direct HTTP** | No SW, or fallback | HTTP → WebSocket | Highest | Cross-network, fallback |
| **2. Local postMessage** | Same browser tabs | postMessage | Lowest | Multi-tab same browser |
| **3. WebRTC Proxy** | Different browsers | postMessage → WebRTC | Medium | P2P cross-browser |

---

## Configuration and Setup

### Route Registration

Routes must be registered before they can be intercepted:

```javascript
import { Group } from './acequia/groups.js'

const group = new Group('mygroup', {
    discoveryURL: 'wss://discovery.example.com',
})

// Register a route
group.registerRoute('/data/:id', async (request) => {
    return {
        status: 200,
        statusText: 'OK',
        body: JSON.stringify({ id: request.params.id }),
        headers: { 'content-type': 'application/json' },
    }
})
```

### Service Worker Registration

Service worker must be registered and active:

```javascript
// acequia.js automatically registers SW
import * as acequia from './build/acequia.js'

await acequia.acequiaReady()
```

### Group Membership

Instances must notify SW of group membership:

```javascript
// Automatically called in groups.js:237
updateServiceWorkerGroups() {
    const instanceId = acequia.getInstanceId()
    const groups = Array.from(acequia.activeGroups.keys())

    acequia.postToSW({
        type: 'UPDATE_GROUPS',
        instanceId,
        groups,
    })
}
```

---

## Debugging

Enable debug logging:

```javascript
// config.js
export const config = {
    debug: true,
    // ...
}
```

Key debug logs to watch:

**Flow 2 (Local):**
- sw.js: "Routing to local instance: {instanceId}"
- acequia.js: "handleProxiedRequest called"

**Flow 3 (WebRTC):**
- sw.js: "Local instance not found, trying WebRTC"
- sw.js: "instanceMap for WebRTC relay: {instances}"
- acequia.js: "handleWebRTCProxiedRequest"
- groups.js: "making blob request"
- webrtc.js: "peer received dataBig: peerProxiedRequest"
- webrtc.js: "handlePeerProxiedRequest called"

**Flow 1 (Server fallback):**
- sw.js: "WebRTC failed, will fall back to server"
- server.mjs: "Handling group route request"
- peerProxy.mjs: "Proxying request to peer"

---

## Performance Considerations

1. **Flow 2 (Local)** is fastest: ~1-5ms
2. **Flow 3 (WebRTC)** is medium: ~10-50ms (after connection established)
3. **Flow 1 (Server)** is slowest: ~100-500ms (network dependent)

### WebRTC Connection Caching

Peer connections are cached and reused:
- First request to new peer: ~2-5 seconds (ICE negotiation)
- Subsequent requests: ~10-50ms (connection already established)

### Request Timeouts

All flows use 30-second timeouts:
- sw.js sendRequestToInstance() - Local requests (30s)
- sw.js sendRequestViaWebRTC() - WebRTC requests (30s)
- groups.js sendRequestViaPeer() - Peer requests (30s)
- peerProxy.mjs - Server requests

---

## Error Handling

All flows implement graceful degradation:

1. **Try local** (Flow 2) - immediate postMessage
2. **Try WebRTC** (Flow 3) - if local fails
3. **Fallback to server** (Flow 1) - if WebRTC fails

Errors are returned as HTTP responses:

```javascript
{
    status: 500,
    statusText: 'Internal Server Error',
    body: JSON.stringify({
        error: 'Error message',
        stack: 'Stack trace',
    }),
    headers: { 'content-type': 'application/json' }
}
```
