iu9-ca-chat-api/api.typ

346 lines
22 KiB
Plaintext
Raw Normal View History

#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 = $~>$
2024-08-27 08:58:52 +00:00
#let bigrect(cnt) = rect(stroke: 1pt, width: 100%, cnt)
#let uritxt(string) = text(fill: rgb(12, 10, 255, 255), raw(string))
#Heading([Руководство], 1)
Обозначается множество запросов,
которые клиент может посыдать серверу для передачи и
2024-08-27 08:58:52 +00:00
получения данных. На один запрос следует один ответ.
Обозначаются переменные, которые сервер передаёт nytl шаблонизатору, который в свою очередь
засовывает в html тег ```html <script> ```, что бы при инициализации клиентсайдовой жс-программы,
у неё были эти переменные.
Для каждого запроса есть свой URI адрес на сервере. Для сохранения порядка адреса всех
2024-08-27 08:58:52 +00:00
запросы начинаются с #uritxt("/api/"). Объявление запроса состоит из указания его
адреса (выступающего чем-то вроде имени), формата отправляемых (на сервер) данных и
формата принимаемых данных. После объявления (в прямоугольнике) идёт описание.
Данные запроса и ответа передаются в формате JSON. Ключевое слово #bold[Sent] обозначает
отправляемый JSON-объект. Ключевое слово #bold[Recv] обозначает получаемый JSON-объект.
#Heading([Структура], 1)
2024-08-27 08:58:52 +00:00
Здесь и далее слово "чат" означает то же самое что и комната.
У каждого чата поддерживается текущий HistoryID. Изначально эта переменная (на сервере)
равна нулю, но с удалением, изменением и написанием новых сообщений, с добавлением
и удалением пользователей из группы, изменяется состояние чата. Эти изменения называются
событиями. Кадое новое событие инкрементирует HistoryID. Клиент так же поддерживает
для каждого чата LocalHistoryID, который соответствует текущему состоянию чата в памяти
клиента. При запросе новых событий, клиент отправляет свой LocalHistoryID у
запрашиваемых чатов и получает события, знание которых необходимо для обновления
состояния "от LocalHistoryID до HistoryID", а так же этот самый новый HistoryID, который
олицетворяет собой состояние чата с учетом новых полученных событий. После прочтения
списка событий клиент заменяет свой LocalHistoryID на полученный HistoryID.
И так далее, по циклу. Любой запрос возвращает обновления.
2024-08-27 08:58:52 +00:00
Не только чаты могут получать события.
Список чатов, в которые входит пользователь, тоже может.
Здесь "сущностями с историей" я называю чаты и список чатов пользователя.
Запросов много, но все они обновляют какую-то из "сущностей с историей".
Например, #uritxt("/api/sendMessage") обновляет историю сообщений чата,
в который новое сообщение было отправлено, а #uritxt("/api/createChat") обновляет
список чатов пользователя, который вызвал этот запрос.
Значит, для каждого запроса нужен свой способ указать нужную сущность с историей и её
LocalHistoryId, соответствующий сущности, изменяемой запросом, а также нужно уметь
принимать ответ с сервера об изменениях, произошедших с сущностью, что бы уметь
обновлять её состояние.
#Heading([Чаты], 2) <update_chat>
Запросы, изменяющие чат, содержат поле `"chatUpdReq"`, в котором хранится объект с полями
chatId и LocalHistoryId,
а получают ответ с полем `"chatUpdResp`с полями HistoryId (обновлённое), обновлённое lastMsgId,
полем-массивом messages, полем-массивом `members` (о них расскажу позже).
`Sent.chatUpdReq.chatId = Integer` - указывает чат \
`Sent.chatUpdReq.LocalHistoryId = Integer` - через это поле клиент сообщает свой
(возможно устаревший) LocalHistoryId. \
`Recv.chatUpdResp.HistoryId = Integer` - это новое значение HistoryId, прешедшее на замену LocalHistoryId \
`Recv.chatUpdResp.lastMsgId = Integer` - Номер последнего сообщения в чате. -1 если чат пуст. \
`Recv.chatUpdResp.messages = Array` - массив объектов типа `messageSt`. Они выглядят так так: \
`messageSt.id = Integer` - id сообщения (в этом чате). \
`messageSt.isSystem = Boolean` \
`messageSt.exists = Boolean` \
`messageSt.text` \
`messageSt.senderUserId` \
Это всё поля, определяющие внутренности сообщения. Структура messageSt не обязательно означает, что
за время обновления чата появилось такое сообщение, она говорит, что в момент `HistoryId` было такое сообщение
с таким id.\
`Recv.chatUpdResp.members` - массив объектов типа `memberSt`. Они выглядят так: \
`memberSt.userId = Integer` - id пользователя, о членстве которого в этом чате говорит этот объект \
`memberSt.nickname = String` \
`memberSt.name = String` \
`memberSt.roleHere = String` \
`roleHere` это что-то из "admin", "regular", "read-only", "not-a-member".
Самое главное - различать роль "not-a-member" от остальных. Ведь она означает что человек
больше не состоит в чате, и если он отображался в списке, то
его надо удалить. Различие остальных ролей не так принципиально.
#Heading([Список чатов, где есть пользователь], 2) <update_chatList>
Запросы, изменяющеи список чатов залогиненного пользователя содержат поле `"chatListUpdReq"`,
который содержит объект с полем `LocalHistoryId` (сами понимаете для чего),
а `"chatListUpdResp"`с полем HistoryId (обновлённое), массивом `myChats`
`Recv.chatListUpdResp.myChats` - массив объектов типа `myMembershipSt`.
Каждый такой объект говорит о состоянии "нахождения пользователя (залогиненного) в чате" в момент HistoryId.
Он может говорить как о нахождении пользователя в чате, так и о его уходе оттуда.
Он выглядит так:
`myMembershipSt.chatId = Integer` - id чата \
`myMembershipSt.chatName = String` \
`myMembershipSt.chatNickname = String` \
`myMembershipSt.myRoleHere = String` \
`myRoleHere` это что-то из "admin", "regular", "read-only", "not-a-member".
Самое главное - различать роль "not-a-member" от остальных. Ведь она означает что
чат из списка чатов надо удалить. Различие остальных ролей не так принципиально.
#Heading([Запросы], 1)
`Recv["status"] = Integer` #funnyArrow Это поле присутствуент во всех ответах, его значение
0 в случае успеха запроса и отрицательно если произошла ошибка. Клиент должен быть написан так, что
получение ошибки от сервера не вызывает сбоя. Так как это поле есть везде, в описании
запросов оно умалчивается.
Так же, в ответах может присутствовать поле `Recv["error"]`, которое описывает ошибку.
Но оно не обязательно даже в случае когда ошибка есть, когда ошибки нет оно бесполезно.
#Heading([Read-only запросы], 2)
2024-08-27 08:58:52 +00:00
#Heading([Получение событий в чате], 3)
#bigrect[
2024-08-27 08:58:52 +00:00
- URI: #uritxt("/api/chatPollEvents") \
- Отправить: \
2024-08-27 08:58:52 +00:00
`Sent.chatUpdReq` - про это поле говорилось в @update_chat \
- Получить: \
2024-08-27 08:58:52 +00:00
`Recv.chatUpdResp` - про это поле тоже говорилось выше
]
2024-08-27 08:58:52 +00:00
Эта функция может только обновлять чат и только один чат.
2024-08-27 08:58:52 +00:00
#Heading([Получение событий в списке чатов вошедшего пользователя], 3)
#bigrect[
2024-08-27 08:58:52 +00:00
- URI: #uritxt("/api/chatListPollEvents") \
- Отправить: \
`Sent.chatListUpdReq` - про это поле говорилось в @update_chatList \
- Получить: \
`Recv.chatListUpdResp` - про это поле тоже говорилось выше
]
#Heading([Получение соседей сообщения], 3)
#bigrect[
2024-08-27 08:58:52 +00:00
- URI: #uritxt("/api/getMessageNeighbours") \
- Отправить: \
2024-08-27 08:58:52 +00:00
`Sent.chatUpdReq` - это поле везде значит одно и то же. Отсюда берётся `chatId`,
что бы значть о каком чате идёт речь \
`Sent.msgId` - id сообщения, с которого начинается отсчет. Можно указать -1 при backward-запросе
и отсчет пойдёт "со дна" чата \
`Sent.direction = String` - это либо `"backward"`, либо `"forward"` \
`Sent.amount = Integer` - Либит на пролистывание сообщений \
- Получить: \
2024-08-27 08:58:52 +00:00
`Sent.chatUpdResp`
]
2024-08-27 08:58:52 +00:00
Можно получать полную информацию об $n$ сообщения до или после определённого сообщения.
$n = "Sent"."amount"$.
Можно заметить, что в ответе мы не получаем никакого списка сообщений, только класический `chatUpdResp`.
Дело в том, что поля `msgId`, `direction`, `amount` лишь говорят обнови состояние чата, но
ОБ ЭТИХ ВОТ
сообщениях пришли мне всю информацию, что только есть, даже если они не новые и никак не менялись.
2024-08-27 08:58:52 +00:00
Рекомендуется держать сообщения в разреженном массиве.
#Heading([Запросы изменения состояния одного чата], 2)
#Heading([Отправка сообщения], 3)
#bigrect[
2024-08-27 08:58:52 +00:00
- URI: #uritxt("/api/sendMessage") \
- Отправить: \
2024-08-27 08:58:52 +00:00
`Sent.chatUpdReq` - ... \
`Sent.content.text = "<text of message>"` \
- Получить: \
2024-08-27 08:58:52 +00:00
`Recv.chatUpdResp = Array`
]
Клиент ОБЯЗАН отображать отправленное пользователем сообщение только тогда, когда
он найдёт сообщение с сервера о событии с отправкой этого сообщения.
2024-08-26 09:44:58 +00:00
#Heading([Удаление сообщения], 3)
#bigrect[
2024-08-27 08:58:52 +00:00
- URI: #uritxt("/api/deleteMessage") \
- Отправить: \
2024-08-27 08:58:52 +00:00
`Sent.chatUpdReq` \
2024-09-02 07:31:48 +00:00
`Sent.id = Integer` - Id выбранного на расстрел сообщения
- Получить: \
2024-08-27 08:58:52 +00:00
`Recv.chatUpdResp`
]
#Heading([Добавление участника в чат], 3)
#bigrect[
2024-08-27 08:58:52 +00:00
- URI: #uritxt("/api/addMemberToChat") \
- Отправить: \
2024-08-27 08:58:52 +00:00
`Sent.chatUpdReq` \
2024-09-02 07:31:48 +00:00
`Sent.nickname = String` - никнейм того участника, которого мы хотим добавить. \
`Sent.makeReadOnly = Boolean` - true если придобавлении человеку выдаётся роль read-only
- Получить: \
2024-08-27 08:58:52 +00:00
`Recv.chatUpdResp`
]
2024-08-27 08:58:52 +00:00
Никнейм добавляемого участника должен быть введён безошибочно, иначе вернётся ошибка.
Выполняется только администратором.
#Heading([Удаление участнка из чата], 3)
#bigrect[
2024-08-27 08:58:52 +00:00
- URI: #uritxt("/api/removeMemberFromChat") \
2024-09-02 07:31:48 +00:00
- Отправить: \
2024-08-27 08:58:52 +00:00
`Sent.chatUpdReq` \
`Sent.userId` - id пользователя, которого мы хотим удалить из чата
- Получить: \
2024-08-27 08:58:52 +00:00
`Recv.chatUpdResp`
]
Как можно заметить, все запросы, изменяющие состояние чата имеют побочную функцию: обновление.
2024-08-27 08:58:52 +00:00
Она обязательна, клиент ОБЯЗАН указывать chatUpdReq.
Выполняется только администратором.
#Heading([Запросы изменения состояния списка чатов пользователя], 2)
#Heading([Создание чата], 3)
#bigrect[
2024-08-27 08:58:52 +00:00
- URI: #uritxt("/api/createChat")
- Отправить: \
2024-08-27 08:58:52 +00:00
`Sent.chatListUpdReq` \
`Sent.content.name = String` \
`Sent.content.nickname = String` \
- Получить: \
2024-08-27 08:58:52 +00:00
`Recv.chatListUpdResp`
]
2024-08-27 08:58:52 +00:00
`Sent.content.name` и `Sent.content.nickname` это выбранные названия для нашего чата.
Если введены невозможные названия (слишком длинные или занятый никнейм), то вернётся ошибка
Пользователь, создавший чат, мгновенно вступает в него в качестве администратора.
2024-08-27 08:58:52 +00:00
#Heading([Удаление чата (Уйти из чата)], 3)
#bigrect[
2024-08-27 08:58:52 +00:00
- URI: #uritxt("/api/leaveChat")
- Отправить: \
2024-09-02 07:31:48 +00:00
`Sent.chatListUpdReq` \
2024-08-27 08:58:52 +00:00
`Sent.chatId` - id чата, который мы покидаем.
- Получить: \
2024-08-27 08:58:52 +00:00
`chatListUpdResp`
]
2024-08-27 08:58:52 +00:00
#Heading([Инициализация списка чатов], 1)
Возникает вопрос "А как программа-клиент узнаёт с какого состояния чата начинать работу,
если мы можем запрашивать только обновление сущностей с историей? Откуда брать 'стартовые данные'?"
Эту информацию сервер динамически вставляет в отправляемую страничку (в тег ```html <script>```) с помощью
New York Transit Line.
Вот какие переменные доступны жс-коду на странице списка чатов:
`pres` - локаль (описана в config.json, поле "presentation". (pres - от слова presentation)) \
`userinfo.id`, `userinfo.nickname`, `userinfo.name` - это всё информация о вошедшем пользователе.
(О себе).
`initial_chatListUpdResp` - этот объект имеет такую же структуру, как и
`Recv.chatListUpdResp` из всех тех запросов к списку чатов, отсюда и название.
В `initial_chatListUpdResp.HistoryId` хранится то значение LocalHistoryId, с которого мы начинаем,
а каждый объект типа `myMembershipSt` в `initial_chatListUpdResp.myChats` говорит об одном чате,
в котором мы членствуем.
Эти данные эквивалентны запросу #uritxt("/api/chatListPollEvents") с LocalHistoryId = 0,
но сейчас вы увидете, что в случае инициализации ЧАТА подобный подход НЕОБХОДИМ.
#Heading([Инициализация чата], 1)
Вот какие переменные доступны жс-коду на странице списка чатов:
`pres` - локаль (описана в config.json, поле "presentation". (pres - от слова presentation)) \
`userinfo.id`, `userinfo.nickname`, `userinfo.name` - это всё информация о вошедшем пользователе.
(О себе). Это всё было и в list-rooms, но теперь добавляются такие переменные:
2024-09-02 07:31:48 +00:00
`openedchat.id`, `openedchat.name`, `openedchat.nickname` - это всё информация о том чате, что мы открыли.
2024-08-27 08:58:52 +00:00
`initial_chatUpdResp` - этот объект имеет такую же структуру, как и `Recv.chatUpdResp`
из всех тех запросов к чату, отсюда и название.
Здесь поля `.HistoryId`, `.members` и `.lastMsgId` имеют то же значение, что и при обычном запросе обновления
(в массиве `members` будет информация о ВСЕХ участниках чата (в начале выполнения жс-кода)),
но массив `.messages` немного хитрее. Если мы зашли по ссылке #uritxt("/chat/<nickanme>"), то
мы просматриваем чат "с конца". И ".messages" будет пустовать. Если же мы зашли по ссылке
#uritxt("/chat/<nickname>/m/<msgId>"), то массив .messages будет содержать ровно один объект типа `messageSt`:
о том самом сообщении, с которого начался просмотр чата. Т.е. отличие от запроса #uritxt("/api/chatPollEvents")
в том, что мы НЕ ХОТИМ ПОЛУЧАТЬ СРАЗУ ВСЕ СООБЩЕНИЯ ИЗ ЧАТА. Мы хотим начать только с одного.
#Heading([Расположение ресурсов веб-чата по URI], 1)
2024-08-27 08:58:52 +00:00
Эта секция не касается API, она о самом веб-чате.
#Heading([Заглавная страница], 2)
2024-08-27 08:58:52 +00:00
По адресу #uritxt("/") показывается список чатов пользователя. Незалогиненный пользователь,
зашедший сюда, будет переслан на #uritxt("/login")
2024-08-27 08:58:52 +00:00
#uritxt("/list-rooms") это алиас для #uritxt("/")
#Heading([Логин], 2)
Адрес: #uritxt("/login"). На этой странице нет ничего кроме формы для входа.
Форма отправляет данные на #uritxt("/login") POST запросом.
Успешный вход пересылает на #uritxt("/"), неуспешный показывает ошибку на этой же странице.
#Heading([Редактирование профиля], 2)
2024-08-27 08:58:52 +00:00
Свой профиль редактируется на странице #uritxt("/user/<your nickname>"). Туда же отправляются данные формы
(POST запросом). При успехе пользователь пересылается на главную страницу,
2024-08-27 08:58:52 +00:00
иначе, остаётся на прежней и высвечиваются красные коробочки с ошибкой.
#Heading([Просмотр профиля], 2)
Профиль любого человека можно посмотреть на странице #uritxt("/user/<nickname>")
#Heading([Страница чата], 2)
2024-08-27 08:58:52 +00:00
Адрес: #uritxt("/chat/<nickname>").
2024-08-27 08:58:52 +00:00
Можно при открытии чата сразу перейти к сообщению с заранее известным id:
#uritxt("/chat/<nickname>/m/<id of message in chat>")
2024-09-02 07:31:48 +00:00
Просмотр списка пользователей на странице #uritxt("/chat-members/<nickname>").
2024-08-27 08:58:52 +00:00
Здесь же администратор может добавлять и удалять участников чата (через api запросы)
#Heading([Замечания], 1)
2024-08-27 08:58:52 +00:00
Утверждается, что id сообщений в чате последовательны (от 1 до какого-то натурального числа).
Т.е. все участники чата видят одни и те же номера ссобщений.
Эта спецификация не утверждает, что id для одних и тех же чатов и людей сохраняется между
сессиями. Для именования чатов и пользователей, пользователи должны использовать никнеймы.
Когда чат или пользователь занимают никнейм, а потом заменяют его на новый,
старый не перестаёт им пренадлежать, и не может уже никогда быть занятым другим
чем-либо. Имя чата или пользователя может содержать любые символы. Никнейм
отличается тем, что в нём допустимы лишь цифры, буквы латинского алфавита, дефис.