> ## Documentation Index
> Fetch the complete documentation index at: https://docs.monyfix.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Веб сокеты

> Описание работы вебсокетов с раундами

## Как подключиться к раунду

Подключиться к раунду можно с помощью `webSocket` по API `/ws/rounds/{id}` по `ID` раунда.

<Note>
  При первом подключении, необходимо сразу отправить `json` объект с `access_token` юзера в параметре `token`.
</Note>

Пример:

```dart theme={null}
final wsUrl = Uri.parse('ws://api-dev.monyfix.tech/ws/rounds/$roundId');
    
// Создаем подключение
_channel = WebSocketChannel.connect(wsUrl);

// Отправляем токен сразу после подключения
_channel?.sink.add(jsonEncode({
  'type': 'token',
  'token': 'user jwt token'
}));
```

Если `токен` **не передан**, сервер вернет ошибку и завершит `websocket` соединение

```json theme={null}
{
  "type": "error.auth",
  "message": "Token not provided"
}
```

Если передан **некорректный** `токен`, сервер вернет ошибку и завершит `websocket` соединение

```json theme={null}
{
  "type": "error.auth",
  "message": "Invalid authentication data format"
}
```

Если передан **корректный** `токен`, но юзера в него не приглашали, сервер вернет ошибку и закроет `websocket` соединение

```json theme={null}
{
  "type": "error.round",
  "message": "Round access error"
}
```

Если передан **корректный** `токен`, сервер отправит ивент `participant_connected` и `participant_update`

```json theme={null}

// participant_connected

{
  "type": "participant_connected",
  "message": "Participant connected",
  "user_id": "user_id"
}

// participant_update

{
  "type": "participant_update",
  "message": "Participants updated",
  "participants": [
    {
      "id": "user_id",
      "phone": "user_phone",
      "status": "user_status",
      "name": "{first_name} {last_name}"
    }] // Participants List
}
```

В любом другом случае, сервер вернет

```json theme={null}
{
  "type": "error.auth",
  "message": "HTTPException.detail"
}
```

## Статусы и ивенты

### Статусы раундов и участников

<Frame>
  <img src="https://mintcdn.com/monyfix/Ot3f0zm5-vFv36hj/images/monyfix_statuses.png?fit=max&auto=format&n=Ot3f0zm5-vFv36hj&q=85&s=0fea589386f143ac461f7aef3c294f65" style={{ borderRadius: '0.5rem' }} width="1112" height="365" data-path="images/monyfix_statuses.png" />
</Frame>

### Основные ивенты

<Frame>
  <img src="https://mintcdn.com/monyfix/Ot3f0zm5-vFv36hj/images/monyfix_statuses_2.png?fit=max&auto=format&n=Ot3f0zm5-vFv36hj&q=85&s=87479a76294b87aa40633f34084bc782" style={{ borderRadius: '0.5rem' }} width="1167" height="476" data-path="images/monyfix_statuses_2.png" />
</Frame>

## Как отправлять ивенты на сервер

Каждая отправка ивента с клиента сопровождается `json` сообщением с `type` ивента и параметрами `data` с `token` и другими.

```json theme={null}
{
  "type": "participant_update|participant_answered", // тип события
  "data": {
    "token": "user access token",
    "status": "READY | NOT_READY", // для смены статуса
    "answer": "answer_id" // для ответа на вопрос
  }
}
```

## Как принимать ивенты с сервера

Каждая отправка ивента с сервера сопровождается `json` сообщением с `type` и `message`.

```json theme={null}
{
  "type": "participant_update|participant_disconnected...", // тип события
  "message": "message" // описание
}
```

## Ивенты, которые отправляет сервер

### Participant connected

Название ивента
`participant_connected`

Отправляется при первом подключении юзера. Отправляется всем участникам.

Отправляет `json`

```json theme={null}
{
  "type": "participant_connected",
  "message": "Participant connected",
  "user_id": "user_id"
}
```

и после этого отправляет ивент `participant_update`.

### Participant disconnected

Название ивента
`participant_disconnected`

Отправляется при разрыве `websocket` соединения. Отправляется всем участникам, кроме текущего.

1. Отключает юзера от `webscocket`
2. Изменяет статус юзера на `WAITING`
3. Отправляет ивент `participant_disconnected`
4. Отправляет ивент `participant_update`

Отправляет `json`

```json theme={null}

// participant_disconnected

{
  "type": "participant_disconnected",
  "message": "Participant disconnected",
  "user_id": "user_id"
}

// participant_update

{
  "type": "participant_update",
  "message": "Participants updated",
  "participants": [
    {
      "id": "user_id",
      "phone": "user_phone",
      "status": "user_status",
      "name": "{first_name} {last_name}"
    }] // Participants List
}
```

### Participant update

Название ивента
`participant_update`

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

Отправляет `json`

```json theme={null}
{
  "type": "participant_update",
  "message": "Participants updated",
  "participants": [
    {
      "id": "user_id",
      "phone": "user_phone",
      "status": "user_status",
      "name": "{first_name} {last_name}"
    }] // Participants List
}
```

### Round countdown

Название ивента
`round_countdown`

Отправляется, когда все участники имеют статус `READY`, для перехода на экран таймера. Отправляется всем участникам.

Отправляет `json`

```json theme={null}
{
  "type": "round_countdown",
  "message": "Countdown started: {countdown} seconds",
  "countdown": 60 // секунды
}
```

### Round question

Название ивента
`round_question`

Отправляется, когда время таймера истекло, для перехода на экран вопроса. Отправляется всем участникам.

Отправляет `json`

```json theme={null}
{
  "type": "round_question",
  "message": "Question started: {countdown_sec} seconds",
  "question_start": "dt_now.isoformat()",
  "question_end": "countdown_end.isoformat()",
  "countdown": 60, // отсчет для таймера в секундах
  "question": {
    "id": "int",
    "question_text": "str",
    "picture_url": "str",
    "question_type": "str",
    "answers": {
      "id": "int",
      "answer_text": "str",
      "is_correct": "bool"
    },
    "category": {
      "id": "int",
      "name": "str"
    }
  }
}
```

### Round results

Название ивента
`round_results`

Отправляется, когда все участники ответили на вопрос или таймер ответа на вопрос закончился, для перехода на экран результатов. Отправляется всем участникам.

Отправляет `json`

```json theme={null}
{
  "type": "round_results",
  "message": "Round results",
  "results": [
    {
        "id": "77eb8d8e-79ba-4871-9061-035f1321b22a", // id вопроса
        "name": "Nikolai Pavlov",
        "avatar": "mock_avatar_13.jpg",
        "answer_time": "43:25.312", // реальное время ответа строкой
        "answer_text": "Тихий", // вариант ответа текстом
        "is_correct": true,
        "total_time": 2605.312, // реальное время ответа в секундах
        "is_winner": true
    }
  ]
}
```

### Chat history

Название ивента
`chat_history`

Отправляется, когда участник открывает чат для загрузки сообщений в ответ на ивент `participant_open_chat`. Отправляется только конкретному участнику.

Отправляет `json`

```json theme={null}
{
  "type": "chat_history",
  "message": "Participant open chat",
  "messages": [
    {
      "user": {
        "id": "str",
        "name": "str",
        "avatar": "str"
      },
      "content": "str",
      "create_time": "datetime utc",
      "variant": "TEXT",
      "is_me": true // всегда true
    }
  ]
}
```

### New message

Название ивента
`new_message`

Отправляется, когда участник отправил сообщение в чат в ответ на ивент `chat_message`. Отправляется всем участникам.

Отправляет `json`

```json theme={null}
{
  "type": "new_message",
  "message": "Participant send new message",
  "messages": [
    {
      "user": {
        "id": "str",
        "name": "str",
        "avatar": "str"
      },
      "content": "str",
      "create_time": "datetime utc",
      "variant": "TEXT",
      "is_me": true // всегда true
    }
  ]
}
```

## Ивенты, которые отправляет клиент

### Participant update

Ивент, который вызывается для обновления основных данных участников, таких как: `name`, `phone`, `id`, `status`.
В основном срабатывает при первом подключении каждого пользователя и при смене статуса участника на `READY` или `NOT_READY`

<Note>
  Во всех ивентах, сервер ожидает `token` юзера в `data` параметре
</Note>

Название ивента
`participant_update`

JSON BODY

```json theme={null}
data: {
  "token": "user access token",
  "status": "READY | NOT_READY"
}
```

Пример

```dart theme={null}
void sendMessage(String type, Map<String, dynamic> data) {
  if (_channel == null) return;
  
  final message = {
    'type': type,
    'data': data
  };
  
  _channel?.sink.add(jsonEncode(message));
}

ws.sendMessage('participant_update', {
  'token': 'user access token',
  'status': 'READY'
});
```

### Round Countdown End

Отправляется, когда время таймера истекло.

Название ивента
`round_countdown_end`

Пример

```dart theme={null}
ws.sendMessage('round_countdown_end', {
  'token': 'user access token'
});
```

### Round Question End

Отправляется, когда время таймера для ответа на вопрос истекло.

Название ивента
`round_question_end`

Пример

```dart theme={null}
ws.sendMessage('round_question_end', {
  'token': 'user access token'
});
```

### Participant answered

Отправляется, когда участник ответил на вопрос в викторине.

<Note>
  Время ответа участника передается строго в формате **mm:ss.SSS** (22:45.343)  с учётом времени до тысячных долей секунды.
</Note>

Название ивента
`participant_answered`

JSON BODY

```json theme={null}
data: {
  "token": "user access token",
  "answer_id": "answer_id", // указывается id ответа из ранее полученного вопроса с ответами
  "response_time": "22:45.343" // время таймера в момент ответа с миллисекундами (3 символа после запятой)
}
```

Пример

```dart theme={null}
ws.sendMessage('participant_answered', {
  'token': 'user access token',
  'answer_id': '5',
  'response_time': "22:45.343"
});
```

В случае, если ответ участника был последним (5 из 5), сервер пришлет ивент `round_results` с результатами раунда для перехода на экран результатов.

### Participant open chat

Отправляется конкретному участнику, когда он открыл окно чата для получения сообщений.

Название ивента
`participant_open_chat`

Пример

```dart theme={null}
ws.sendMessage('participant_open_chat', {
  'token': 'user access token'
});
```

В ответ сервер отправит ивент `chat_history` с массивом сообщений:

```json theme={null}
{
  "type": "participant_open_chat",
  "message": "Participants open chat",
  "messages": [
    {
      "user": {
        "id": "str",
        "name": "str",
        "avatar": "str"
      },
      "content": "str",
      "create_time": "datetime utc",
      "variant": "TEXT",
      "is_me": true // true, если это сообщение юзера, который отправил запрос. false, если сообщение не его
    }
  ]
}
```

### Chat message

Отправляется всем участникам, когда один из них отправляет сообщение в чат.

Название ивента
`chat_message`

Пример

```dart theme={null}
ws.sendMessage('chat_message', {
  'token': 'user access token',
  'content': 'user message',
  'type': 'TEXT' // пока статика, будет меняться в зависимости от типа сообщения (изображение, видео и т.д.)
});
```

В ответ сервер отправит ивент всем участникам `new_message` с массивом сообщений:

```json theme={null}
{
  "type": "new_message",
  "message": "Participant send new message",
  "messages": [
    {
      "user": {
        "id": "str",
        "name": "str",
        "avatar": "str"
      },
      "content": "str",
      "create_time": "datetime utc",
      "variant": "TEXT",
      "is_me": true // всегда true
    }
  ]
}
```

## Коды ошибок

Сервер может отправлять ошибки в виде сообщений с типом `error`, а также закрывать соединения с определенными `WebSocket` кодами. Вот список возможных кодов и ошибок:

| Код ошибки                  | Описание                                            | Когда возникает                                                             |
| --------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------- |
| `WS_1008_POLICY_VIOLATION	` | Политика безопасности нарушена (некорректный токен) | Токен аутентификации отсутствует, неверный или не передан в нужном формате  |
| `WS_1011_INTERNAL_ERROR	`   | Внутренняя ошибка сервера                           | В случае непредвиденной ошибки на сервере                                   |
| `error`                     | Общие ошибки                                        | В случаях непредвиденной ошибки                                             |
| `error.round`               | Ошибки с доступом к раунду                          | При подключении юзера                                                       |
| `error.auth`                | Ошибки авторизации                                  | В случае некорректного токена или его отсутствия                            |
| `error.chat`                | Ошибки чата                                         | В случае отсутствия параметров `content` или `type` в ивенте `chat_message` |
