2024-08-14 18:49:18 +00:00
#pagebreak(weak: true)
#set text(size: 13pt)
#align(center, text([Протокол для взаимодействия веб-клиента и сервера iu9-ca-chat],
size: 30pt))
#let Heading(c, d) = {
heading(c, depth: d, numbering: "1.");
}
#let bold(txt) = text(txt, weight: "bold")
#let funnyArrow = $~>$
#Heading([Руководство], 1)
Обозначается множество запросов,
которые клиент может посыдать серверу для передачи и
получения данных. Н а один запрос следует один ответ (как минимум что бы сообщить о б
успехе запроса).
Для каждого запроса есть свой URI адрес на сервере. Для сохранения порядка адреса всех
запросов начинаются в #raw("/internalapi"). Объявление запроса состоит из указания е г о
адреса (выступающего чем-то вроде имени), формата отправляемых (на сервер) данных и
формата принимаемых данных. После объявления (в прямоугольнике) идёт описание.
Данные запроса и ответа передаются в формате JSON. Ключевое слово #bold[Sent] обозначает
отправляемый JSON-объект. Ключевое слово #bold[Recv] обозначает получаемый JSON-объект.
#Heading([Структура], 1)
У каждого чата поддерживается текущий HistoryID. Изначально эта переменная (на сервере)
равна нулю, но с удалением, изменением и написанием новых сообщений, с добавлением
и удалением пользователей из группы, изменяется состояние чата. Эти изменения называются
событиями. Кадое новое событие инкрементирует HistoryID. Клиент так же поддерживает
для каждого чата LocalHistoryID, который соответствует текущему состоянию чата в памяти
клиента. При запросе новых событий, клиент отправляет свой LocalHistoryID у
запрашиваемых чатов и получает события, знание которых необходимо для обновления
состояния "от LocalHistoryID до HistoryID", а так же этот самый новый HistoryID, который
олицетворяет собой состояние чата с учетом новых полученных событий. После прочтения
списка событий клиент заменяет свой LocalHistoryID на полученный HistoryID.
И так далее, по циклу. Любой запрос возвращает обновления.
Н е только чаты могут получать события. Здесь "сущностями с историей" я называю чаты и
список чатов пользователя.
Определим особую структуру hist_entity_request такого формата:
`hist_entity_request["type"]` = `"chat"` или `"chatlist"`
Если запрашивается чат, то нужно указать е г о ID.
`hist_entity_request["chatId"]` = Число
`hist_entity_request["LocalHistoryId"]` = Число #funnyArrow указывает версию чата в памяти
клиента
Пример: ```json {"type": "chat", "chatId": 2, "LocalHistoryId": 12312321}```
```json {"type": "chatlist", "LocalHistoryId": 31231}```
Определим особую структуру hist_entity_response такого формата:
`hist_entity_response["type"]` = `"chat"` или `"chatlist"`
Если запрашивается чат, то нужно указать е г о ID.
`hist_entity_response["chatId"]` = Число
`hist_entity_response["HistoryId"]` = Число. Это новый HistoryId для этой сущности с историей.
`history_entity_response["events"]` = Список новых событий.
Событие - это словарь. Обозначим е г о ключемым словом #bold[event]:
`event["type"]` = Строка #funnyArrow Это тип события, которые бывают нескольких типов:
- `"newMessage"` - приходит новое сообщение в чат
- `"addedMember"` - в чат добавили нового участника
- `"removedMember"` - из чата выкинули участника
- `"addedChat"` - клиента добавили в чат
- `"removedChat"` - клиента выкинули из чата
#Heading([Событие `newMessage`], 2) <ev_newMessage>
```json
{
"type" : "newMessage",
"previous": 111111 // Id of previous message
"id": 1112 // Id of the message
"content": {
"isSystem": false // Булевое значение, true если это системное сообщение вроде "поприветствуйте нового пользователя". Такие должны выделяться особым цветом.
"text": "<text of the message>" // Text of the message
"sender": 1000 // User ID of sender
},
}
```
#Heading([Событие `addedMember`], 2)
```json
{
"type": "addedMember",
"member": 1000 // User ID добавленного человека
"content": {
"name": "<users name>",
"nickname": "<users nickname>"
2024-08-25 14:46:37 +00:00
"role": "regular" // Роль это либо admin, либо regular, либо read-only
2024-08-14 18:49:18 +00:00
}
}
```
#Heading([Событие `removedMember`], 2)
```json
{
"type": "removedMember",
"member": 1000 // User ID удалённого человека (или покинувшего чат человека)
}
```
#Heading([Событие `addedChat`], 2)
```json
{
"type": "addedChat",
"id": 228, // Chat ID
"content": {
"name": "<имя чата>",
"nickname": "<nickname чата>",
2024-08-25 14:46:37 +00:00
"lastMsgId": 1212, // Id последнего сообщения. -1 Если чат пуст
"roleHere": "regular" // Роль текущего пользователя в этом чате
2024-08-14 18:49:18 +00:00
}
}
```
#Heading([Событие `removedChat`], 2)
```json
{
2024-08-25 14:46:37 +00:00
"type": "removedChat",
2024-08-14 18:49:18 +00:00
"id": 228, // Chat ID того чата, из которого клиента удалили / из которого клиент вышел
}
```
Пример объекта hist_entity_response:
```json
{
"type": "chat", "chatId": 2, "HistoryId": 12312325,
"events": [
{
"type": "newMessage",
"previous": 111111,
"id": 111115,
"content": {
"isSystem": false,
"text": "私は頭骨を劇場から盗んだ",
"sender": 666
}
},
{
"type": "newMessage",
"previous": 111115,
"id": 1000000,
"content": {
"isSystem": true,
"text": "Father mushroom left chat",
"dender": 0
}
},
{
"type": "removedMember",
"member": 666
},
{
"type": "addedMember",
"member": 777,
"content": {
"name": "'Definitely not theatre looter'",
2024-08-25 14:46:37 +00:00
"nickname": "father-mushroom-2",
"role": "regular"
2024-08-14 18:49:18 +00:00
}
},
{
"type": "newMessage",
"previous": 1000000,
"id": 1000002,
"content": {
"isSystem": true,
"text": "'Definitely not theatre looter' joined chat",
"sender": 0
}
}
]
}
```
#Heading([Запросы], 1)
`Recv["status"] = Integer` #funnyArrow Это поле присутствуент во всех ответах, е г о значение
0 в случае успеха запроса и отрицательно если произошла ошибка. Клиент должен быть написан так, что
получение ошибки от сервера не вызывает сбоя. Так как это поле есть везде, в описании
запросов оно умалчивается.
Так же, в ответах может присутствовать поле `Recv["error"]`, которое описывает ошибку.
Н о оно не обязательно даже в случае когда ошибка есть, когда ошибки нет оно бесполезно.
#let bigrect(cnt) = rect(stroke: 1pt, width: 100%, cnt)
#let uritxt(string) = text(fill: rgb(12, 10, 255, 255), raw(string))
#Heading([Read-only запросы], 2)
#Heading([Получение событий], 3) <api_pollEvents>
#bigrect[
- URI: #uritxt("/internalapi/pollEvents") \
- Отправить: \
`Sent["scope"]` = [hist_entity_request, ..., hist_entity_request] #funnyArrow Список
сущностей, по которым клиент хочет получить обновления. \
- Получить: \
`Recv["update"]` = [hist_entity_response, ..., hist_entity_response] #funnyArrow Список
сущностей, для которых пришли изменения.
]
В списке `Recv["update"]` будут предоставлены обновления только для тех сущностей,
которые были запрошены.
#Heading([Получение списка чатов где пользователь состоит], 3)
#bigrect[
- URI: #uritxt("/internalapi/getChatList") \
- Отправить: \
`Sent = {}` #funnyArrow Да, мы просто отправляем пустой словарь \
- Получить: \
`Recv["chats"] = Array` \
`Recv["chats"][i]["id"] = Integer` #funnyArrow Chat ID \
`Recv["chats"][i]["content"]["name"] = "<chat name>"` \
`Recv["chats"][i]["content"]["nickname"] = "<chat nickname>"` \
`Recv["chats"][i]["content"]["lastMsgId"]` = Integer #funnyArrow Id последнего сообщения в чате
]
#Heading([Получение информации о чате], 3)
#bigrect[
- URI: #uritxt("/internalapi/getChatInfo") \
- Отправить: \
`Sent["id"] = Integer` Просто отправляем id чата \
- Получить: \
`Recv["name"] = "<chat name>"` \
`Recv["nickname"] = "<chat nickname>"` \
`Recv["lastMsgId"] = Integer` #funnyArrow ID последнего сообщения в чате.
]
Если чат пуст, то lastMsgId равен -1.
#Heading([Получение списка участников чата], 3)
#bigrect[
- URI: #uritxt("/internalapi/getChatMemberList") \
- Отправить: \
`Sent["chatId"] = Integer` Это id чата \
- Получить: \
`Recv["members"] = Array` Получаем мы только список участников чата. \
Вот какие структуры находятся в этом списке: \
`Recv["members"]["id"] = Integer` ID Пользователя \
`Recv["members"]["content"]["name"] = "<users name>"` \
`Recv["members"]["content"]["nickname"] = "<users nickname>"` \
`Recv["members"]["content"]["role"] = "<Роль этого участника чата>"`
]
2024-08-25 14:46:37 +00:00
Роль учатника это либо `"admin"`, либо `"regular"`, либо `"read-only"`. Думаю, семантика их ясна из
2024-08-14 18:49:18 +00:00
названий.
#Heading([Получение информации о пользователе], 3)
#bigrect[
- URI: #uritxt("/internalapi/getUserInfo") \
- Отправить: \
`Sent["id"] = Integer` #funnyArrow Id пользователя о котором хотим получить инфу. \
- Получить: \
`Recv["content"]["name"] = "<user name>"` \
`Recv["content"]["nickname"] = "<user nickname>"` \
]
#Heading([Получение информации о сообщении], 3)
#bigrect[
- URI: #uritxt("/internalapi/getMessageInfo") \
- Отправить: \
`Sent["chatId"] = Integer` \
`Sent["id"] = Integer` \
- Получить: \
`Sent["content"]["text"] = "<text of message>"` \
`Sent["content"]["isSystem"] = Boolean` \
`Sent["content"]["sender"] = 1100` #funnyArrow User ID отправителя \
]
#Heading([Получение соседей сообщения], 3)
#bigrect[
- URI: #uritxt("/internalapi/getMessageNeighboursInfo") \
- Отправить: \
`Sent["chatId"] = Integer` \
`Sent["id"] = Integer` #funnyArrow Выбираем нужное сообщение в чате \
`Sent["direction"] = "backward" / "forward"` \
`Sent["amount"] = Integer` \
- Получить: \
`Recv["messages"] = Array` \
Е г о элементы это ID сообщения и контент сообщения. \
`Recv["messages"][i]["id"]` \
`Recv["messages"][i]["previous"]` \
`Recv["messages"][i]["content"]`
]
Структура контента сообщения описана в @ev_newMessage.
Можно узнать соседей сообщения "сверху и снизу". Направление `"backward"` покажет $n$
сообщений до переданного сообщения (они будут расположены в списке в обратном порядке),
направление `"forward"` покажет $n$ сообщений до указанного. Здесь $n$ это выбранное клиентом
количество желаемых сообщений (`Sent["amount"]`). Сервер ОБЯЗАН вернуть ровно $n$ сообщений,
если они есть и ОБЯЗАН вернуть все сообщения до определённого края истории чата, если край достигнут.
#Heading([Запросы изменения состояния одного чата], 2)
#Heading([Отправка сообщения], 3)
#bigrect[
- URI: #uritxt("/internalapi/sendMessage") \
- Отправить: \
`Sent["chatId"] = Integer` #funnyArrow В какой чат мы хотим отправить сообщение \
`Sent["LocalHistoryId"] = Integer` #funnyArrow HistoryID для состояния ЭТОГО чата (в памяти клиента) \
`Sent["content"]["text"] = "<text of message>"` \
- Получить: \
`Recv["update"] = Array`
]
Здесь поле `Recv["update"]` означает то же самое, что и в
#uritxt("/internalapi/pollEvents") (см. @api_pollEvents). Там
содержатся только один hist_entity_response - указывающий изменения в этом чате.
Клиент ОБЯЗАН отображать отправленное пользователем сообщение только тогда, когда
он найдёт сообщение с сервера о событии с отправкой этого сообщения.
#Heading([Редактирование сообщения], 3)
#bigrect[
- URI: #uritxt("/internalapi/editMessage") \
- Отправить: \
`Sent["chatId"] = Integer` \
`Sent["LocalHistoryId"] = Integer` #funnyArrow HistoryID для состояния ЭТОГО чата \
`Sent["id"] = Integer` \
`Sent["content"]["text"] = "<text of message>"` #funnyArrow Новый текст сообщения
- Получить: \
`Recv["update"] = Array of hist_entity_response` #funnyArrow Всё то же самое (См. @api_pollEvents)
]
Обновление получает
#Heading([Добавление участника в чат], 3)
#bigrect[
- URI: #uritxt("/internalapi/addMemberToChat") \
- Отправить: \
`Sent["chatId"] = Integer` #funnyArrow К какому чату добавляем \
`Sent["LocalHistoryId"] = Integer` #funnyArrow HistoryID для состояния ЭТОГО чата \
`Sent["userId"] = Integer` #funnyArrow ID добавляемого пользователя \
- Получить: \
`Recv["update"]`
]
#Heading([Удаление участнка из чата], 3)
#bigrect[
- URI: #uritxt("/internalapi/removeMemberFromChat") \
- Отправить:
`Sent["chatId"] = Integer` #funnyArrow Из какого чата удалить \
`Sent["LocalHistoryId"] = Integer` #funnyArrow HistoryID для состояния ЭТОГО чата \
`Sent["userId"] = Integer` #funnyArrow ID удаляемого пользователя \
- Получить: \
`Recv["update"]`
]
Как можно заметить, все запросы, изменяющие состояние чата имеют побочную функцию: обновление.
Она обязательна, клиент ОБЯЗАН указывать LocalHistoryID,
в запросе приутствует мнимый hist_entity_requet. Он аддресует тот чат, к котороу применим запрос
#Heading([Запросы изменения состояния списка чатов пользователя], 2)
В запросах этой категории есть ключ `"LocalHistoryId"`, а в ответах на них есть ключ `"update"`,
но они нужны для поллинга событий не какого-то чата, а списка чатов где есть пользователь.
#Heading([Создание чата], 3)
#bigrect[
- URI: #uritxt("/internalapi/createChat")
- Отправить: \
`Sent["LocalHistoryId"] = Integer` #funnyArrow Это ID состояния списка чатов пользователя \
`Sent["content"]["name"] = "<name of chat>"` \
`Sent["content"]["nickname"] = "<nickname of chat>"` \
- Получить: \
`Sent["update"]` #funnyArrow События, произошедшие cо списком чатов пользователя.
`Sent["chatId"] = Integer`
]
`Sent["chatId"]` это id созданного по этому запросу чата
#Heading([Удаление чата], 3)
#bigrect[
2024-08-25 14:46:37 +00:00
- URI: #uritxt("/internalapi/leaveChat")
2024-08-14 18:49:18 +00:00
- Отправить: \
`Sent["LocalHistoryId"] = Integer` #funnyArrow Это ID состояния списка чатов пользователя \
`Sent["id"] = Integer` #funnyArrow Chat Id того чата, который должен быть удалён \
- Получить: \
`Sent["update"]` #funnyArrow События, произошедшие cо списком чатов пользователя.
]
#Heading([Расположение ресурсов веб-чата по URI], 1)
Эта секция не касается internalApi, она о самом веб-чате.
#Heading([Заглавная страница], 2)
По адресу #uritxt("/") показывается список чатов пользователя. Еезалогиненный пользователь,
зашедший сюда, будет переслан на #uritxt("/login")
#Heading([Логин], 2)
Адрес: #uritxt("/login"). Н а этой странице нет ничего кроме формы для входа.
Форма отправляет данные на #uritxt("/login") POST запросом.
Успешный вход пересылает на #uritxt("/"), неуспешный показывает ошибку на этой же странице.
#Heading([Создание чата], 2)
Форма создания чата отправляет данные на #uritxt("/createChat") POST запросом.
При успехе пользователь пересылается на страницу чата (#uritxt("/chatRoom/..."))
Где находится форма - сами решайте.
#Heading([Редактирование профиля], 2)
2024-08-25 14:46:37 +00:00
Свой профиль редактируется на странице #uritxt("/mirror"). Туда же отправляются данные формы
2024-08-14 18:49:18 +00:00
(POST запросом). При успехе пользователь пересылается на главную страницу,
иначе, остаётся на прежней.
#Heading([Просмотр профиля], 2)
Профиль любого человека можно посмотреть на странице #uritxt("/user/<nickname>")
#Heading([Страница чата], 2)
Адрес: #uritxt("/chat/<nickname>"). О б остальных страницах договаривайтесь сами.
#Heading([Замечания], 1)
Эта спецификация не утверждает, что Id для одних и тех же чатов и сообщений
сохраняется междй пользователями. Так же не утверждается, что Id сообщений
сохраняются между чатами. Для именования чатов и пользователей,
пользователи должны использовать никнеймы.
Когда чат или пользователь занимают никнейм, а потом заменяют е г о на новый,
старый не перестаёт им пренадлежать, и не может уже никогда быть занятым другим
чем-либо. Имя чата или пользователя может содержать любые символы. Никнейм
отличается тем, что в нём допустимы лишь цифры, буквы латинского алфавита, дефис.