Введение в Matrix

Материал из [matrix] на Русском
Перейти к: навигация, поиск

DISCLAIMER

Статья является переводом https://brendan.abolivier.bzh/enter-the-matrix/ автора @brendan:abolivier.bzh и распространяется по лицензии Creative Commons BY-SA (https://creativecommons.org/licenses/by-sa/4.0/). Перевод выполнили @airin:matrix.org, @commagray:matrix.org, @WaleSky:matrix.org. Все вопросы и предложение можно задать в комнате #perevodators:matrix.org.


Как вы можете знать, если вы некоторое время подписаны на меня в Twitter (или знаете меня в реальной жизни) (автора оригинала - прим. перевода), я очень люблю свободные программы и децентрализацию. Я люблю свободные программы. потому что они соответствуют философии, которой я живу, а децентрализацию -потому что она расширяет пользовательскую свободу и индивидуальность, и я считаю работу над децентрализованными сетями увлекательной. Они заставляют кардинально изменить способ проектирования систем, поскольку большая часть интернета сейчас состоит из централизованных сервисов, которые учат разрабатывать и проектировать только в этом плане (ключе) (=== централизованном).

Сегодня я хочу вам рассказать об одном из моих любимых свободных децентрализованных проектов - Matrix. Давайте уясним, что я не говорю об одной научно-фантастической франшизе или ночном клубе в Берлине. Matrix - это протокол для децентрализованных, федеративных и защищённых коммуникаций, созданный и поддерживаемый New Vector, компанией, базирующейся на территории Британии и Франции (в которую я присоединился для интернатуры в Лондоне прошлым летом). Он основан на RESTful HTTP/JSON API, документирован открытой спецификацией и спроектирован быть пригодным для чего угодно, что требует коммуникацию в реальном времени, он мгновенных сообщений до интернета вещей. Некоторые люди экспериментируют с Matrix, используя его в качестве блога, RSS-читалки и прочих неочевидных вещей, которых вы не ожидаете от такого проекта. Несмотря на это, однако, он в основном используется для мгновенных сообщений, в частности с клиентом Riot (который так же разработан New Vector).

Matrix так же дистанцируется от аргумента "ещё одна штука для сообщений" в своей философии: это не ещё один стандарт для коммуникаций, но инструмент для связывания всех сервисов коммуникации вместе, используя "мосты", интеграции и прочее. Например, у нас на CozyCloud есть комната, соединённая "мостом" с публичным IRC-каналом, и это значит, что все сообщения, отправленные в Matrix, будут направлены на канал в IRC, и наоборот. В свободное время я даже соединил "мостом" эту комнату с нашим сервером Mattermost, чтобы создать связку Mattermost<->Matrix<->IRC и позволить сообществу взаимодействовать с командой, не заставляя её членов запускать еще один чат-клиент и терять время глядя туда, в дополнение к внутренним коммуникациям.

Matrix-Bridges.jpg

Также в последнее время было немало шума вокруг Matrix после того, как правительство Франции анонсировало своё решение по полному переходу на Matrix для внутренних коммуникаций, используя форк Riot, который они в будущем выпустят как свободную программу для всего мира.

Что под капотом[править]

Первичное знакомство это замечательно, но, полагаю, от этой статьи вы ожидаете больших технических подробностей, поэтому давайте рассмотрим как Matrix работает. Небольшая оговорка: я не буду описывать это слишком подробно (потому что в таком случае пост получился бы слишком длинным и я бы не закончил его и за неделю), и остановлюсь главным образом на его основных принципах и базовом использовании.

Как я уже упомянул, Matrix децентрализованный и федеративный. Децентрализованность означает, что вы можете иметь свой собственный matrix сервер (как в принципе и другие сервисы, типа Mattermost), а федеративность означает, что два matrix сервера могут общаться друг с другом. Это значит, что если кто-то (пусть это будет Алиса) имеет свой сервер на matrix.alice.tld, и хочет поговорить с ее другом (назовём его Боб), у которого тоже есть свой сервер на matrix.bob.tld, это будет возможно, и matrix.alice.tld будет знать, как отправить сообщения Алисы Бобу на matrix.bob.tld.

Немного терминологии:[править]
  • В спецификации Matrix есть несколько типов серверов. Хоум-серверы (HS) это серверы, реализующие клиент-серверный и федеративный АПИ, т.е. те, которые позволяют непосредственный обмен сообщениями между Алисой и Бобом. В моём примере выше "Matrix-серверы" matrix.alice.tld и matrix.bob.tld это хоум-серверы. Среди других типов серверов есть серверы идентификации (IS), которые позволяют использовать сторонние идентификаторы (например email или номер телефона) по которым люди могут друг друга найти, и сервисы приложений (AS), которые главным образом используются для создания мостов в другие системы (но не только). В этой статье я буду освещать только базовое использование хоум-серверов, поскольку для понимания принципов работы Matrix этого достаточно.
  • В спецификации Matrix, Алиса и Боб идентифицируются посредством Matrix ID, имеющего вид @имя:сервер. В нашем примере, их Matrix ID могут быть соответственно @Alice:matrix.alice.tld и @Bob:matrix.bob.tld. Matrix ID на самом деле следует более широкому формату наименований любой сущности в Matrix, а именно *имя:хоум-сервер, где "*" - символ, указывающий на тип адреса. "@" означает, что это Matrix ID.


Три жильца на трёх серверах[править]

Теперь, когда у нас есть два общающихся пользователя, давайте посмотрим, как третий (назовём его Чарли), у которого тоже есть хоум-сервер (matrix.charlie.tld) может присоединиться к ним. Это делается при помощи *комнат*, которые можно рассматривать как Matrix-эквивалент IRC каналов. Как любая сущность в Matrix, комнаты имеют идентификатор, начинающийся символом "!". Однако, не смотря на то, что он содержит в себе имя сервера, комнаты, в отличие от ID пользователя, не привязаны к серверам. Фактически, сервер в идентификаторе комнаты, это сервер пользователя, который её создал.

Когда Алиса хочет отправить сообщение в комнату, где находятся Боб и Чарли, её хоум-сервер находит в своёй локальной базе данных, какие еще серверы являются частью этой комнаты (в нашем примере, серверы Боба и Чарли), и отправляет сообщение каждому из них по отдельности (и каждый из них показывает сообщение своим юзерам в данной комнате, т.е. сервер Боба покажет сообщение Бобу). И каждый хоум-сервер сохранит это сообщение в своей локальной базе данных. Это означает:

  • Каждый хоум-сервер в комнате хранит содержимое её истории.
  • Если по любой причине какой-то хоум-сервер отключается, даже если это сервер, адрес которого прописан в названии комнаты, все остальные хоум-серверы в комнате могут продолжать общаться друг с другом.

В общих чертах, комнату можно изобразить следующим рисунком:

Room-schema.png

Это скриншот интерактивной демонстрации принципов работы Matrix под названием "How does it work?" с домашней страницы проекта, и я бы очень советовал вам её посмотреть. Поэтому Matrix ID и имена серверов не совпадают с моими примерами.

На самом деле я был немного неточен ранее, потому что в спецификации Матрикс чаты 1:1 это тоже комнаты. То есть строго говоря, Алиса и Боб были уже в комнате, до того как Чарли захотел поговорить с ними. Стоит также отметить, что комната может иметь неограниченноe число псевдонимов, выступающих её адресами, по которым пользователи могут зайти в неё, если она публична. Их синтакс имеет общую форму о которой мы говорили выше, с символом "#" в начале. Таким образом, !wbtZVAjTSFQzROqLrx:matrix.org становится #cozy:matrix.org, что, согласитесь, гораздо удобнее для чтения и запоминания. Так же как и с ID комнаты, его доменная часть это сервер пользователя, создавшего псевдоним, то есть я могу создать #cozycloud:matrix.trancendances.fr если обладаю достаточными правами, так как использую этот хоум-сервер.

Как я уже намекал, комната может быть либо публичной, либо приватной. В публичную комнату может зайти любой, кто знает один из её псевдонимов (или получил его в публичной директории хоум-сервера, если он был там опубликован), а приватные работают только по приглашениям. В обоих случаях, если пользователь еще не в комнате, его хоум-сервер свяжется с другим (будь это сервер, чьё имя есть в псевдониме комнаты или сервер, откуда пришло приглашение в приватную комнату) и попросит осуществить вход.

События, события повсюду[править]

Теперь, когда мы знаем что такое комната, давайте поговорим о том, что происходит внутри нее. Ранее, я говорил про сообщения, которые на самом деле называются "события". С технической точки зрения событие в Matrix это JSON объект, который отправляется в комнату и рассылается всем участникам этой комнаты. Он конечно же имеет идентификатор, формируемый хоум-сервером пользователя, который отправил сообщение, и имеющий стандартную форму, о которой мы говорили выше, со спецсимволом "$" в начале.

{
 "origin_server_ts": 1526072700313,
 "sender": "@Alice:matrix.alice.tld",
 "event_id": "$1526072700393WQoZb:matrix.alice.tld",
 "unsigned": {
   "age": 97,
   "transaction_id": "m1526072700255.17"
 },
 "content": {
   "body": "Hello Bob and Charlie! Welcome to my room :-)",
   "msgtype": "m.text"
 },
 "type": "m.room.message",
 "room_id": "!TCnDZIwFBeQyBCciFD:matrix.alice.tld"
}

Выше приведён пример события, посланного Алисой Бобу и Чарли, в комнате, в которой они все находятся. Это сообщение, как следует из класса m.room.message в поле "type". Поле "content", которое должно быть объектом, содержит непосредственно контент этого события. В данном случае мы видим текст сообщения и его тип m.text. Такая скрупулёзность нужна потому что m.room.message может быть не только текстом, но и изображением, видео, уведомлением итд, согласно спецификации.

Поле unsigned здесь означает, что его содержимое не следует учитывать при вычислении и проверке криптографической подписи при передаче события от сервера к серверу.

Спецификация Matrix описывает 3 типа событий, которые могут циркулировать в комнате:

  • События ленты, например сообщения, формирующие ленту комнаты для всех хоум-серверов в данной комнате.
  • События состояния, которые содержат дополнительное поле state_key и формируют текущее состояние комнаты. Они могут описывать создание комнаты (m.room.create), смену топика (m.room.topic), правила входа (только по приглашению или публичная, m.room.join_rules), обновления членства (вход, выход, приглашение или бан, m.room.member с идентификатором пользователя, чьё членство изменяется, в поле state_key). Так же как и события ленты, они часть истории комнаты, но в отличие от них, последнее событие типа state_key легко извлекается, равно как и текущее состояние комнаты, являющиеся на самом деле JSON массивом, содержащим последние события для всех {type, state_key}. Matrix API позволяет также легко запросить полное состояние комнаты на момент публикации в ней определенного сообщения из ленты, и каждое событие состояния ссылается на родительское.
  • Эфемерные события, которые не включаются в ленту комнаты и служат для передачи информации, не сохраняющейся во времени, например уведомлении о наборе текста (”[…] is typing…“).

Что мне нравится в Matrix, так это то, что помимо базовой структуры события вы можете поместить туда практически всё, что захотите. Нет ограничений ни на имя класса (кроме того, что оно не может начинаться на m., так как это зарезервировано в спецификации), ни на его контент, так что вы свободны создавать свои собственные события по своему усмотрению, будь это события ленты, события состояния или всё вместе (не уверен насчет эфемерных событий). Так вы можете создавать целые системы, используя только Матрикс в качестве бэкенда.

События Matrix могут также быть отменены. Это аналог удаления, только события на самом деле не удаляются, а очищаются от содержимого, чтобы не создавать путаницу в ленте комнаты. Отмененное событие затем рассылается всем хоум-серверам в комнате, чтобы они могли отменить свою локальную копию события. Касательно редактирования содержимого события - это пока невозможно, но это очень востребованная функция, которая должна быть реализована в не очень далеком будущем.

Минимальный клиент[править]

Изучение основных принципов это замечательно, но не объясняет как пользоваться всей этой штукой. Думаю, вы хотите узнать как можно использовать Матрикс в вашем проекте.

Далее я предполагаю следующее:

  • Вы работаете с хоум-сервером matrix.project.tld, и его клиент-сервер АПИ доступно по HTTPS на порту 443
  • Вашего пользователя зовут Alice. Имейте в виду, что @Alice:matrix.org уже занято и вы должны изменить это для реальных тестов.
  • Ваш пароль 1L0v3M4tr!x.

Я рассмотрю только самые основы клиент-серверной спецификации. Если вы хотите пойти дальше, вам нужно почитать полную спецификацию или спросить в комнате #matrix-dev. Я не буду здесь рассматривать установку хоум-сервера (хотя может я сделаю это в будущем посте). Моя цель - дать вам представление как работает клиент-сервер АПИ, а не создавать целую аппликацию, что заняло бы слишком много времени.

Стоит также заметить, что далее в этой статье названия всех конечных точек Matrix API кликабельны и ведут в соответствующий раздел спецификации, который вы можете почитать, если хотите более полной документации по конкретной конечной точке.

Регистрация[править]

Вашего пользователя еще не существует, поэтому давайте его зарегистрируем на сервере.

Точка подключения (endpoint) для регистрации - /_matrix/client/r0/register, и вы должны запросить её, используя POST HTTP request. В нашем примере, полный URL запроса будет таким: https://matrix.project.tld/_matrix/client/r0/register.

Все точки подключения в спецификации матрикс начинаются с /_matrix/.

Запрос содержит JSON следующей формы:

{
 "username": "Alice",
 "password": "1L0v3M4tr!x",
}

Здесь username и password это именно то, о чем вы думаете. Получаемый новым пользователем Matrix ID содержит в своей левой части указанный username.

Выполните этот запрос. Вы получите статус 401 и JSON, выглядящий примерно так:

{
   "flows": [
       {
           "stages": [
               "m.login.dummy"
           ]
       },
       {
           "stages": [
               "m.login.email.identity"
           ]
       }
   ],
   "params": {},
   "session": "HrvSksPaKpglatvIqJHVEfkd"
}

Эта точка подключения использует часть спецификации под названием "User-Interactive Authentication API". Она подразумевает, что аутентификация может быть представлена как набор цепочек последовательных этапов. Это именно то, что мы здесь имеем: две цепочки, каждый содержит один этап. Это очень простой пример, всё может быть значительно сложнее, например:

{
   "flows": [
       {
           "stages": [
               "m.login.recaptcha"
           ]
       },
       {
           "stages": [
               "m.login.email.identity",
               "m.login.recaptcha"
           ]
       }
   ],
   "params": {
       "m.login.recaptcha": {
           "public_key": "6Le31_kSAAAAAK-54VKccKamtr-MFA_3WS1d_fGV"
       }
   },
   "session": "qxATPqBPdTsaMBmOPkxZngXR"
}

Здесь мы видим 2 потока, один с одним этапом, другой с двумя. Присутствует также параметр в объекте params, используемом в цепочке m.login.recaptcha.

Поскольку я хочу сохранить всё как можно более простым, давайте вернёмся назад к нашему первому простому примеру и используем одноступенчатый поток. Единственный этап там - это m.login.dummy, и он будет успешно срабатывать всякий раз, когда будет получать от вас корректный JSON объект.

Для регистрации с использованием этой ступени, нам нужно добавить только несколько строк к нашему первичному JSON запросу:

{
 "auth": {
   "type": "m.login.dummy",
   "session": "HrvSksPaKpglatvIqJHVEfkd",
 },
 "username": "Alice",
 "password": "1L0v3M4tr!x",
}

Значение поля "session" во введенном объекте "auth" мы берем из ответа хоум-сервера на наш первичный запрос. Этот объект укажет серверу, что запрос является продолжением первичного запроса, использующего m.login.dummy. Хоум-сервер автоматически распознает поток, который мы используем и вернет успешный результат (потому что мы используем m.login.dummy), возвратив этот JSON со статусом 200:

{
 "access_token": "olic0yeVa1pore2Kie4Wohsh",
 "device_id": "FOZLAWNKLD",
 "home_server": "matrix.project.tld",
 "user_id": "@Alice:matrix.project.tld"
}

Посмотрим что мы здесь имеем:

  • Поле home_server содержит адрес хоум-сервера, на котором вы регистрируетесь. Это может показаться избыточным, но спецификация Matrix допускает отличие имени хоум-сервера от его адреса, поэтому оно упоминается там.
  • Поле user_id содержит только что созданный Matrix ID для нового пользователя.
  • Поле device_id содержит ID устройства, с которого вы регистрируетесь. Устройство привязывается к токену доступа и ключам E2E шифрования (которые мы не рассматриваем в этой статье).
  • Поле access_token содержит токен для аутентификации всех ваших запросов в рамках клиент-серверного АПИ Matrix. Обычно он значительно длиннее, чем тот, который показан в примере, я укоротил его для удобства.

Регистрация пользователя тут же производит его логин, так что вам не нужно делать это прямо сейчас. Если, по какой-то причине, вы выходите из системы, вы можете подключиться обратно, используя описанную здесь точку входа.

Создание нашей первой комнаты[править]

Теперь, когда у нас есть аутентифицированный пользователь на сервере, давайте создадим комнату. Это делается посылкой POST запроса на точку подключения /_matrix/client/r0/createRoom. В нашем примере, полный URL запроса будет: https://matrix.project.tld/_matrix/client/r0/createRoom?access_token=olic0yeVa1pore2Kie4Wohsh Обратите внимание на параметр access_token, который должен содержать токен доступа, выданным нам сервером ранее.

Есть несколько JSON параметров, которые я не буду здесь затрагивать, поскольку ни один из них не является обязательным для выполнения запроса. Давайте отправим запрос с пустым объектом ({}) в качестве содержимого.

Перед ответом, хоум-сервер создаст комнату, заполнит её несколькими событиями состояния (такими, как первичное событие m.room.create или событие входа для вашего пользователя). Затем он должен вернуть в ответ статус 200 и JSON, выглядящий примерно так:

{
   "room_id": "!RtZiWTovChPysCUIgn:matrix.project.tld"
}

Вот вы и создали свою самую первую комнату! Как вы наверное догадались, значениe поля room_id это ID свежесозданной комнаты.

Копаемся в состоянии комнаты[править]

Смотреть состояние комнаты на данном этапе практически бесполезно, но давайте сделаем это тем не менее. Вызов полного состояния комнаты, к примеру, не сложнее обычного GET запроса к точке входа [_matrix /_matrix/client/r0/rooms/{roomId}/state], где {roomId} это ID комнаты. Если вы выполняете эти шаги используя curl в bash, вам понадобится заменить "!" в ID комнаты на его URL-закодированный вариант (%21). Не забудьте добавить свой токен доступа к полному URL, как показано выше.

Запрос должен вернуть JSON-массив, содержащий события состояния, такие как:

{
 "age": 654742,
 "content": {
   "join_rule": "public"
 },
 "event_id": "$1526078716401exXBQ:matrix.project.tld",
 "origin_server_ts": 1526078716874,
 "room_id": "!RtZiWTovChPysCUIgn:matrix.project.tld",
 "sender": "@Alice:matrix.project.tld",
 "state_key": "",
 "type": "m.room.join_rules",
 "unsigned": {
   "age": 654742
 }
}

Теперь давайте попробуем отправить своё собственное событие состояния в комнату. Чтобы это сделать, вам потребуется отправить PUT-запрос на точку /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}, заменив ID комнаты, тип события и state_key на нужные значения. Если ваш state key - пустая строка, вы можете исключить его из URL запроса. Еще раз, не забудьте добавить ваш токен доступа!

Полезная нагрузка события в теле запроса передается объектом content.

Давайте создадим событие tld.project.foo с ключом состояния "bar" и {"baz": "qux"} в качестве содержимого. Для этого отправим запрос PUT по адресу /_matrix/client/r0/rooms/!RtZiWTovChPysCUIgn:matrix.project.tld/state/tld.project.foo/bar?access_token=olic0yeVa1pore2Kie4Wohsh (из которого я убрал схему протокола и доменное имя, чтобы он не был слишком большим) со следующим содержимым:

{
 "baz": "qux"
}

Хоум-сервер после этого возвращает нам объект с единственным полем event_id, содержащим ID только что созданного события состояния.

Если мы повторим запрос, который делали ранее, чтобы получить полное состояние комнаты, мы теперь увидим наше событие:

{
   "age": 58357,
   "content": {
       "baz": "qux"
   },
   "event_id": "$1526080218403sbpku:matrix.project.tld",
   "origin_server_ts": 1526080218639,
   "room_id": "!RtZiWTovChPysCUIgn:matrix.project.tld",
   "sender": "@Alice:matrix.project.tld",
   "state_key": "bar",
   "type": "tld.project.foo",
   "unsigned": {
       "age": 58357
   }
}

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

Отправка сообщений[править]

Отправка событий ленты почти не отличается от отправки событий состояния, за исключением того, что это делается через точку входа /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}, и содержит один параметр, который мы прежде не видели: txnId — ID транзакции. Это просто уникальный ID, позволяющий идентифицировать конкретный запрос среди всех других запросов для того же токена доступа. Вы можете писать туда что угодно, пока вы не используете одно и то же значение дважды в пределах одного токена доступа.

Повторюсь, что тело запроса это контент события.

Выборка событий ленты, однако, немного более сложный процесс. Он производится запросом GET к точке /_matrix/client/r0/sync. Хитрость в том, что эта точка входа не специфична для конкретной комнаты, и возвращает все события, полученные во всех комнатах, в которых вы находитесь, вместе с некоторыми событиями присутствия, приглашениями итд.

После такого запроса (с добавленным к нему токеном доступа) вы можете найти в JSON-ответе события ленты интересующей вас комнаты обратив внимание на объект rooms, который содержит объект join, который содержит по одному объекту на каждую вашу комнату. Найдите комнату !RtZiWTovChPysCUIgn:matrix.project.tld (ту, что мы создали ранее), и в соответствующем объекте вы увидите события состояния, ленты и эфемерные события для данной комнаты.

Приглашение народа[править]

Алиса зарегистрировалась на серверe и создала свою комнату, но чувствует себя довольно одинокой. Давайте пригласим туда Боба и порадуем её.

Приглашение в комнату довольно простая операция и требует только POST запроса на точку /_matrix/client/r0/rooms/{roomId}/invite. Тело запроса должно содержать Matrix ID приглашаемого, например:

{
 "user_id": "@Bob:matrix.bob.tld"
}

Подобный же запрос был бы, если бы Боб зарегистрировался на том же сервере, что и Алиса.

Если всё прошло нормально, хоум-сервер должен вернуть статус 200 и пустой JSON объект ({}).


При следующем обращении к точке /_matrix/client/r0/sync Боб увидит среди комнат объект "invite", содержащий приглашение, которое Алиса ему прислала и состоящий из нескольких событий, в т.ч. события приглашения:

{
 "invite": {
   "!RtZiWTovChPysCUIgn:matrix.project.tld": {
     "invite_state": {
       "events": [
         {
           "sender": "@Alice:matrix.project.tld",
           "type": "m.room.name",
           "state_key": "",
           "content": {
             "name": "My very cool room"
           }
         },
         {
           "sender": "@Alice:matrix.project.tld",
           "type": "m.room.member",
           "state_key": "@Bob:matrix.bob.tld",
           "content": {
             "membership": "invite"
           }
         }
       ]
     }
   }
 }
}

Теперь Боб сможет присоединиться к комнате посылкой простого POST запроса на точку /_matrix/client/r0/rooms/{roomId}/join.

Алиса встречает Боба[править]

Итак, у нас теперь есть свежая комната, где Алиса и Боб могут общаться между собой, и всё проделано при помощи HTTP запросов, которые вы можете слать curl`ом из терминала. Разумеется, вам не нужно всякий раз делать это настолько вручную, существуют также Matrix SDK для разных языков и платформ, включая JavaScript, Go, Python, Android, iOS и многое другое. Полный список доступен здесь.

Если вы хотите вникнуть немного глубже в Matrix API, я бы советовал вам взглянуть на спецификацию (хотя она и требует еще много работы) и проекты сообщества на странице Try Matrix Now! на сайте Matrix.

Надеюсь вам понравилось это путешествие в Matrix API, как и мне, когда я впервые услышал об этом проекте. Matrix это определённо то, с чем я буду играться, и может у меня появятся значительные новости касательно Matrix-проектов, над которыми я работаю.

Как обычно, хочу поблагодарить Thibaut за вычитку этого поста и полезный первичный фидбек. Если вы захотите оставить свой фидбек об этом посте, не стесняйтесь сделать это в Твиттере или через Matrix, мой личный Matrix ID: @Brendan:matrix.trancendances.fr!