# Discovery Server Reference

The localDiscovery server (`localDiscovery/server.mjs`) handles peer discovery, route registration, and HTTP-to-WebSocket proxying. It runs on port 31313.

## WebSocket Protocol

### Connection

```
ws://localhost:31313/ws?group=groupname
```

The `group` query parameter determines which group the client joins.

### Connection Flow

1. Client connects with `?group=groupname`
2. Server sends `welcome` message
3. Client sends `register` with device info
4. Server broadcasts updated `peersList` to all group members
5. Server sends current `routeRegistry` to the new client
6. Client registers routes via `registerRoute` or `registerRoutes`
7. Server broadcasts updated `routeRegistry` to all group members

### Heartbeat

The server expects heartbeat messages every 20 seconds. Clients that don't send heartbeats for 60 seconds are reaped (removed from the group with their routes).

```javascript
// Server → Client (on connect)
{ "type": "welcome", "group": "mygroup", "heartbeat": 20000 }

// Client → Server (every 20s)
{ "type": "heartbeat", "deviceInfo": { "instanceId": "...", ... } }
```

## Message Types

### Client → Server

#### register
```json
{
    "type": "register",
    "deviceInfo": {
        "instanceId": "unique-per-tab",
        "deviceId": "persistent-per-device",
        "userId": "persistent-per-user",
        "displayName": "My App",
        "group": "groupname",
        "capabilities": ["camera", "editor"]
    }
}
```

#### registerRoute
```json
{
    "type": "registerRoute",
    "instanceId": "peer-instance-id",
    "pattern": "/{instanceId}/status",
    "metadata": { "description": "optional" }
}
```

Registers a single route. Broadcasts updated registry to all peers.

#### registerRoutes (batch)
```json
{
    "type": "registerRoutes",
    "instanceId": "peer-instance-id",
    "routes": [
        { "pattern": "/status", "metadata": {} },
        { "pattern": "/data", "metadata": {} }
    ]
}
```

Registers multiple routes with a single broadcast. More efficient for apps with many routes.

#### sendToPeer
```json
{
    "type": "sendToPeer",
    "peerId": "target-instance-id",
    "message": { "type": "signal", "data": "..." }
}
```

Relay a message to another peer (used for WebRTC signaling).

#### proxiedResponse
```json
{
    "type": "proxiedResponse",
    "response": {
        "requestId": "original-request-id",
        "data": {
            "status": 200,
            "headers": { "content-type": "application/json" },
            "body": "response body or base64",
            "bodyEncoding": "base64"
        }
    }
}
```

Response to a proxied HTTP request.

#### heartbeat
```json
{
    "type": "heartbeat",
    "deviceInfo": { "instanceId": "...", ... }
}
```

### Server → Client

#### welcome
```json
{
    "type": "welcome",
    "message": "Connected to WebSocket server",
    "group": "groupname",
    "heartbeat": 20000
}
```

#### peersList
```json
{
    "type": "peersList",
    "group": "groupname",
    "data": {
        "instance-1": { "instanceId": "instance-1", "displayName": "Device A", ... },
        "instance-2": { "instanceId": "instance-2", "displayName": "Device B", ... }
    }
}
```

#### routeRegistry
```json
{
    "type": "routeRegistry",
    "group": "groupname",
    "version": 5,
    "sessionId": "server-session-abc",
    "data": {
        "/instance-1/status": { "instanceId": "instance-1", "pattern": "/instance-1/status", "metadata": {} },
        "/instance-2/data": { "instanceId": "instance-2", "pattern": "/instance-2/data", "metadata": {} }
    }
}
```

#### messageFromPeer
```json
{
    "type": "messageFromPeer",
    "fromPeerId": "sender-instance-id",
    "message": { "type": "signal", "data": "..." }
}
```

#### proxiedRequest
```json
{
    "type": "proxiedRequest",
    "requestId": "unique-request-id",
    "method": "GET",
    "path": "/status",
    "remainingPath": "",
    "pathParams": {},
    "query": {},
    "headers": { ... },
    "body": null
}
```

#### error
```json
{
    "type": "error",
    "error": "description of what went wrong"
}
```

## HTTP Endpoints

### Peer Proxy

```
GET|POST|PUT|DELETE /groups/:groupId/*path
```

Proxies HTTP requests to the peer that owns the matching route. The request is serialized and sent over WebSocket; the peer's response is returned as the HTTP response.

- **Timeout:** 30 seconds
- **Body handling:** Binary-safe (raw buffer parsing for `/groups/` routes)
- **WebDAV support:** Full method and header support (PROPFIND, MKCOL, LOCK, etc.)
- **Binary responses:** Supports `bodyEncoding: 'base64'` for binary data

### Admin Endpoints

#### Health Check
```
GET /admin/health
```
```json
{
    "groups": ["camera", "scribbler"],
    "groupCount": 2,
    "totalClients": 5,
    "totalRoutes": 12,
    "uptime": 3600,
    "timestamp": 1708700000000
}
```

#### Route Registry
```
GET /admin/routes/:groupId
```
Returns all registered routes for a group with their owning instance and metadata.

#### Client List
```
GET /admin/clients/:groupId
```
Returns all connected clients with their device info and last-seen timestamps.

#### Combined State
```
GET /admin/state/:groupId
```
Returns both routes and clients for a group.

## Route Matching

The server uses Express-like pattern matching:

- `:param` — matches a single path segment (`[^/]+`)
- `*param` — matches everything including slashes (`.*`)

Patterns are tested sequentially; first match wins.

**Example:**
- Pattern: `/:instanceId/shot`
- Request: `/abc123/shot`
- Match: `{ instanceId: 'abc123' }`

## Stale Peer Reaper

Every 60 seconds, the server checks all peers across all groups:

1. Compare `lastSeen` timestamp to current time
2. If `> 60 seconds` since last heartbeat → remove peer
3. Unregister all routes owned by that peer
4. Broadcast updated peer list and route registry

## Error Types

| Error | Status | When |
|-------|--------|------|
| `RouteNotFoundError` | 404 | No pattern matches the request path |
| `GroupNotFoundError` | 404 | Group has no connected clients |
| `NodeUnavailableError` | 503 | Route owner not connected |
| `NodeTimeoutError` | 504 | Peer didn't respond within 30s |
| `RouteConflictError` | 409 | Pattern already registered (when overwrite not allowed) |
| `InvalidRoutePatternError` | 400 | Pattern syntax invalid |

## Configuration

`localDiscovery/config.mjs`:
```javascript
{
    version: "1.0.1",
    name: "local-discovery",
    defaultAuth: {
        type: "simple",
        username: "user",
        password: "l3n4str33t"
    }
}
```

Override with `config.local.mjs` for SSL or custom settings.

**Port:** Set via `PORT` environment variable (default: 31313).