# WebSocket Sync API

Real-time bidirectional sync API for live updates and notifications.

**Endpoint**: `WS /ws/sync`
**Auth**: Required (JWT token or Device authentication)

## Connection

Connect to the WebSocket endpoint with authentication:

```javascript
const ws = new WebSocket("wss://bookhoard.example/ws/sync?token=eyJhbGci...");

// Or with device authentication
const ws = new WebSocket(
  "wss://bookhoard.example/ws/sync?device_id=uuid&device_key=key",
);
```

## Message Format

All messages are JSON:

```json
{
  "type": "message_type",
  "data": { ... }
}
```

## Client→Server Messages

### Subscribe to Progress Updates

```json
{
  "type": "subscribe",
  "data": {
    "topic": "progress",
    "device_id": "device-uuid"
  }
}
```

### Unsubscribe

```json
{
  "type": "unsubscribe",
  "data": {
    "topic": "progress"
  }
}
```

### Heartbeat/Ping

```json
{
  "type": "ping",
  "data": {
    "timestamp": "2026-02-08T10:00:00Z"
  }
}
```

## Server→Client Messages

### Progress Updated

```json
{
  "type": "progress_updated",
  "data": {
    "media_item_id": "uuid",
    "device_id": "device-uuid",
    "percentage": 75.5,
    "position": 1234,
    "updated_at": "2026-02-08T10:00:00Z"
  }
}
```

### Conflict Detected

```json
{
  "type": "conflict_detected",
  "data": {
    "conflict_id": "uuid",
    "media_item_id": "uuid",
    "severity": "high",
    "created_at": "2026-02-08T10:00:00Z"
  }
}
```

### Scan Progress

```json
{
  "type": "scan_progress",
  "data": {
    "job_id": "uuid",
    "library_id": "uuid",
    "percentage": 45.5,
    "files_processed": 850,
    "total_files": 1523,
    "status": "in_progress"
  }
}
```

### Scan Complete

```json
{
  "type": "scan_complete",
  "data": {
    "job_id": "uuid",
    "library_id": "uuid",
    "files_added": 125,
    "files_updated": 45,
    "files_failed": 3,
    "completed_at": "2026-02-08T10:00:00Z"
  }
}
```

### Device Connected

```json
{
  "type": "device_connected",
  "data": {
    "device_id": "uuid",
    "device_name": "My Kobo",
    "connected_at": "2026-02-08T10:00:00Z"
  }
}
```

### Device Disconnected

```json
{
  "type": "device_disconnected",
  "data": {
    "device_id": "uuid",
    "disconnected_at": "2026-02-08T10:00:00Z"
  }
}
```

### Pong Response

```json
{
  "type": "pong",
  "data": {
    "timestamp": "2026-02-08T10:00:00Z"
  }
}
```

## Connection Lifecycle

1. **Connect** - WebSocket connection established
2. **Authenticate** - Send JWT token or device credentials via query string
3. **Subscribed** - Server confirms authentication and subscribes to relevant topics
4. **Messages** - Server pushes real-time updates
5. **Heartbeat** - Client sends ping every 30 seconds
6. **Disconnect** - Connection closed

## Authentication

### User Authentication

```
wss://bookhoard.example/ws/sync?token=<jwt_token>
```

### Device Authentication

```
wss://bookhoard.example/ws/sync?device_id=<uuid>&device_key=<key>
```

## Error Messages

### Authentication Failed

```json
{
  "type": "error",
  "data": {
    "code": "auth_failed",
    "message": "Invalid or expired token"
  }
}
```

### Subscription Failed

```json
{
  "type": "error",
  "data": {
    "code": "subscription_failed",
    "message": "Cannot subscribe to topic"
  }
}
```

## Usage Example

```javascript
const ws = new WebSocket("wss://bookhoard.example/ws/sync?token=eyJhbGci...");

ws.onopen = () => {
  console.log("Connected to sync WebSocket");

  // Subscribe to progress updates
  ws.send(
    JSON.stringify({
      type: "subscribe",
      data: { topic: "progress", device_id: "device-uuid" },
    }),
  );

  // Start heartbeat
  setInterval(() => {
    ws.send(
      JSON.stringify({
        type: "ping",
        data: { timestamp: new Date().toISOString() },
      }),
    );
  }, 30000);
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);

  switch (message.type) {
    case "progress_updated":
      console.log("Progress updated:", message.data);
      break;
    case "conflict_detected":
      console.log("New conflict detected:", message.data);
      break;
    case "scan_complete":
      console.log("Scan complete:", message.data);
      break;
    case "pong":
      console.log("Pong received");
      break;
    default:
      console.log("Unknown message type:", message.type);
  }
};

ws.onerror = (error) => {
  console.error("WebSocket error:", error);
};

ws.onclose = () => {
  console.log("WebSocket connection closed");
};
```

## Topics

| Topic     | Description              | Events                                 |
| --------- | ------------------------ | -------------------------------------- |
| progress  | Reading progress updates | progress_updated                       |
| conflicts | Sync conflict events     | conflict_detected, conflict_resolved   |
| scanner   | Library scan events      | scan_progress, scan_complete           |
| devices   | Device connection events | device_connected, device_disconnected  |
| queue     | Sync queue events        | queue_item_added, queue_item_processed |

## Best Practices

1. **Heartbeat**: Send ping every 30 seconds to keep connection alive
2. **Reconnect**: Implement exponential backoff for reconnection
3. **Message Queue**: Queue messages when disconnected and replay on reconnect
4. **Error Handling**: Handle all error types gracefully
5. **Cleanup**: Unsubscribe from topics when no longer needed
