# API для разработчиков: DM, push и сигналы звонков

Документ описывает WebSocket-операции для подписанных личных сообщений, WebPush и realtime-сигналов звонков.

Логика личных сообщений дополнительно описана в `Dev_Docs/Personal_Messages/README.md`; этот файл фиксирует именно публичные `op`, поля запросов и поля ответов.

## 1. `UpsertPushToken`

Требует авторизации.

### Запрос

```json
{
  "op": "UpsertPushToken",
  "requestId": "push-upsert-001",
  "payload": {
    "sessionId": "SESSION_ID",
    "endpoint": "https://push.example/...",
    "p256dhKey": "BASE64",
    "authKey": "BASE64",
    "platform": "web",
    "userAgent": "Mozilla/5.0 ..."
  }
}
```

### Успешный ответ

```json
{
  "op": "UpsertPushToken",
  "requestId": "push-upsert-001",
  "status": 200,
  "ok": true,
  "payload": {
    "tokenId": "token-1",
    "updatedAtMs": 1774700000123
  }
}
```

---

## 2. `SendTestWebPush`

Требует авторизации. Если `login` передан, он должен совпадать с логином текущей сессии.

### Запрос

```json
{
  "op": "SendTestWebPush",
  "requestId": "push-test-001",
  "payload": {
    "login": "alice",
    "sessionId": "SESSION_ID",
    "title": "Test",
    "text": "Push body"
  }
}
```

### Успешный ответ

```json
{
  "op": "SendTestWebPush",
  "requestId": "push-test-001",
  "status": 200,
  "ok": true,
  "payload": {
    "targetLogin": "alice",
    "attemptedSessions": 1,
    "sessionsWithPushConfig": 1,
    "delivered": 1,
    "failed": 0,
    "sentAtMs": 1774700000123
  }
}
```

---

## 3. `SendDirectMessage`

Отправляет один подписанный DM-пакет.

### Запрос

```json
{
  "op": "SendDirectMessage",
  "requestId": "dm-001",
  "payload": {
    "blobB64": "BASE64_SIGNED_DM_PACKET"
  }
}
```

### Успешный ответ

```json
{
  "op": "SendDirectMessage",
  "requestId": "dm-001",
  "status": 200,
  "ok": true,
  "payload": {
    "messageId": "dm-1",
    "deliveredWsSessions": 1,
    "deliveredWebPushSessions": 0,
    "sessionNotFound": false
  }
}
```

---

## 4. `SendMessagePair` и `ReceiveOutcomingMessage`

`ReceiveOutcomingMessage` сейчас является алиасом `SendMessagePair` и использует тот же request/handler.

### Запрос

```json
{
  "op": "SendMessagePair",
  "requestId": "dm-pair-001",
  "payload": {
    "incomingBlobB64": "BASE64_INCOMING_SIGNED_BLOCK",
    "outgoingBlobB64": "BASE64_OUTGOING_SIGNED_BLOCK"
  }
}
```

### Успешный ответ

```json
{
  "op": "SendMessagePair",
  "requestId": "dm-pair-001",
  "status": 200,
  "ok": true,
  "payload": {
    "baseKey": "base-key",
    "incomingKey": "incoming-key",
    "outgoingKey": "outgoing-key",
    "deliveredWsSessions": 1,
    "deliveredWebPushSessions": 0
  }
}
```

---

## 5. `ReceiveIncomingMessage`

Принимает входящий подписанный DM-блок.

### Запрос

```json
{
  "op": "ReceiveIncomingMessage",
  "requestId": "dm-in-001",
  "payload": {
    "incomingBlobB64": "BASE64_INCOMING_SIGNED_BLOCK"
  }
}
```

### Успешный ответ

```json
{
  "op": "ReceiveIncomingMessage",
  "requestId": "dm-in-001",
  "status": 200,
  "ok": true,
  "payload": {
    "messageKey": "incoming-key",
    "baseKey": "base-key",
    "deliveredWsSessions": 1,
    "deliveredWebPushSessions": 0
  }
}
```

---

## 6. `AckSessionDelivery`

Требует авторизации. Подтверждает доставку сообщения в текущую сессию.

### Запрос

```json
{
  "op": "AckSessionDelivery",
  "requestId": "ack-001",
  "payload": {
    "messageKey": "incoming-key"
  }
}
```

### Успешный ответ

```json
{
  "op": "AckSessionDelivery",
  "requestId": "ack-001",
  "status": 200,
  "ok": true,
  "payload": {
    "messageKey": "incoming-key"
  }
}
```

---

## 7. `CallInviteBroadcast`

Требует авторизации. Отправляет приглашение к звонку на активные сессии пользователя `toLogin`.

### Запрос

```json
{
  "op": "CallInviteBroadcast",
  "requestId": "call-invite-001",
  "payload": {
    "toLogin": "bob",
    "callId": "call-1",
    "type": 100
  }
}
```

### Успешный ответ

```json
{
  "op": "CallInviteBroadcast",
  "requestId": "call-invite-001",
  "status": 200,
  "ok": true,
  "payload": {
    "callId": "call-1",
    "deliveredWsSessions": 1,
    "deliveredFcmSessions": 0,
    "deliveredWebPushSessions": 0
  }
}
```

---

## 8. `CallSignalToSession`

Требует авторизации. Отправляет сигнал звонка в конкретную сессию получателя.

### Запрос

```json
{
  "op": "CallSignalToSession",
  "requestId": "call-signal-001",
  "payload": {
    "toLogin": "bob",
    "targetSessionId": "SESSION_ID",
    "callId": "call-1",
    "type": 101,
    "data": "{\"sdp\":\"...\"}"
  }
}
```

### Успешный ответ

```json
{
  "op": "CallSignalToSession",
  "requestId": "call-signal-001",
  "status": 200,
  "ok": true,
  "payload": {
    "delivered": true
  }
}
```

Если целевая сессия не найдена или доставка не удалась, сервер может вернуть `404`.

## Типовые ошибки

- `422 / NOT_AUTHENTICATED` — требуется авторизация.
- `400 / BAD_FIELDS` — не заполнены обязательные поля.
- `404 / USER_NOT_FOUND` — пользователь не найден.
- `404 / SESSION_NOT_FOUND` — сессия не найдена.
- `422 / BAD_SIGNATURE` — подпись DM не прошла проверку.
- `422 / BAD_DEVICE_KEY` — некорректный device key отправителя.
- `422 / BAD_TIME_WINDOW` — время подписанного сообщения вне допустимого окна.
- `422 / REPLAY` — повторное сообщение заблокировано.
