thread - Replace O(n^2) origin-id/stanza-id lookups in AppModel with hash indexes and stop rebuilding the whole index on every unseen stanza-id, so archive application no longer grows quadratically with history size. - Run OMEMO decryption on a serial background queue instead of the main actor, and decrypt archived stanzas in small batches with a shorter yield, keeping the UI responsive during large archive catch-ups. - Show the "Синхронизация истории…" banner only for the one-page bootstrap window; incremental backlog catch-up continues silently in the background. - Fix MUC-MAM: publish decoded group mutations at the end of room catch-up; they were previously accumulated and then silently dropped. - Retry a failed catch-up pass automatically (bounded) and lengthen the query/apply timeouts so a single slow page no longer leaves history unloaded until the next app activation.
Luma
Luma — рабочий MVP XMPP-клиента для iOS, iPadOS, macOS и Apple Watch с интерфейсом в духе Telegram/iMessage. Клиент не привязан к одному провайдеру: он подключается к обычному XMPP-серверу по JID, включая Prosody.
Что уже реализовано
- вход по JID и паролю; пароль хранится в Apple Keychain;
- DNS SRV, STARTTLS и direct TLS, а также ручной host/port;
- roster, presence, личные чаты и групповые комнаты XEP-0045 (MUC), включая создание/вход, приглашения, автоподключение и число участников;
- отдельный раздел «Контакты»: люди синхронизируются с roster Prosody, а рядом показываются сохранённые MUC-комнаты; есть общий поиск, аватары и статусы;
- OMEMO для исходящих и входящих сообщений в личных и неанонимных MUC-чатах, постоянные ключи, TOFU и ручная проверка отпечатков устройств;
- глобальный переключатель OMEMO и отдельный режим для каждого чата: наследовать глобальную настройку, всегда шифровать или отправлять без OMEMO;
- XEP-0198 Stream Management, XEP-0280 Message Carbons и XEP-0313 MAM;
- delivery receipts и локальная история;
- редактирование текстовых сообщений через XEP-0308, включая исправления в OMEMO-чатах и синхронизацию через Carbons/MAM;
- ответы через XEP-0461 и XEP-0428: Luma показывает исходное сообщение
уменьшенным полупрозрачным bubble с дугой и открывает по нажатию фокус-режим
всей ветки в стиле iMessage; текстовые
>-цитаты старых Conversations/Monal также разбираются, а в MUC используется выданный комнатойstanza-id; - пересылка текста, геопозиции и медиа в существующий чат или на введённый JID; через пункт «Выбрать» можно отметить несколько сообщений и переслать их в исходном порядке; медиа скачивается/расшифровывается и загружается заново для получателя;
- «Удалить у всех» через XEP-0424 Message Retraction с tombstone в истории и отдельное постоянное «Удалить у меня», которое не возвращает сообщение после очередной синхронизации MAM; оба действия поддерживают несколько выбранных сообщений;
- съёмка фото прямо системной камерой iPhone, а также выбор фото и видео из медиатеки; фото, видео, музыка и файлы отправляются через XEP-0363 HTTP File Upload; при включённом OMEMO содержимое шифруется до загрузки, а исходное расширение имени сохраняется для совместимости с Conversations и Monal;
- множественный выбор до 20 фото/видео или файлов, полноэкранный предпросмотр фото и воспроизведение видео до отправки, удаление отдельных элементов и подпись; после сетевой ошибки неотправленные файлы остаются для повторной попытки, а параллельные загрузки не могут оставить интерфейс заблокированным;
- фото и видео показывают inline-превью и по нажатию открываются поверх всего окна приложения; полноэкранный просмотр закрывается вертикальным свайпом без отдельного крестика, увеличенное фото продолжает свободно перемещаться внутри, а видео использует системные элементы воспроизведения; музыка получает inline-плеер с названием файла, длительностью и перемоткой;
- каждое фото и видео помещается в отдельный скруглённый островок с внутренним
отступом и
aspectFit: миниатюра не обрезается, не выходит за границы и не слипается с соседними вложениями; - запись голосовых сообщений и круглых видеосообщений (до 60 секунд) с камеры; голосовые показывают waveform, поддерживают паузу, перемотку и скорости 1×/1.5×/2×, а видеосообщения воспроизводятся прямо в круглом bubble, который слегка увеличивается на время воспроизведения;
- явно видимый пункт «Отправить геопозицию» в меню
+, выбор текущей или произвольной точки на карте и отправка стандартногоgeo:URI, распознаваемого другими XMPP-клиентами; - аватары контактов и обновление собственного аватара через XEP-0084 PEP с совместимым fallback на vCard-temp;
- личные аудио- и видеозвонки через XMPP Jingle (XEP-0166/XEP-0167),
ICE-UDP/DTLS-SRTP и WebRTC; есть входящий экран, mute, динамик, камера и её
переключение, а STUN/TURN автоматически берутся через XEP-0215; некорректные
ICE-записи от сервера отбрасываются, затем используются публичный STUN и
прямые host candidates, поэтому они не блокируют запуск media engine;
исходящий
session-acceptнормализуется по порядку media-линий offer; Jingle Message Initiation сразу адресуется выбранному по presence/caps full JID; trickle ICE ждёт удалённый SDP, но больше не зависает в ожидании ресурса, поэтомуtransport-infoне попадает на другой endpoint и не остаётся в очереди до timeout; - завершённые аудио- и видеозвонки показываются в истории чата отдельными Telegram-подобными карточками: входящий/исходящий, длительность, отклонённый, пропущенный, отменённый, без ответа или неудачный; пропущенный входящий помечает чат непрочитанным;
- у обычных текстовых и файловых сообщений используются чистые скруглённые островки без «хвостиков»;
- единый заполненный
AppAssets.xcassets/AppIconсо всеми 42 размерами для iPhone, iPad, Mac и Apple Watch; один и тот же catalog явно подключён ко всем app-target’ам, а имя закреплено в build settings и Info.plist; - адаптивный SwiftUI-интерфейс для iPhone, iPad и Mac;
- companion-приложение watchOS: последние чаты, диктовка/ответы, а также запись
голосового до 60 секунд с отменой и предпрослушиванием;
.m4aпередаётся на iPhone фоновой файловой очередью WatchConnectivity, после чего iPhone отправляет его через обычный XEP-0363/OMEMO media-пайплайн и возвращает часам итоговый статус; это современный single-target watchOS app, поэтому SwiftUI-код и executable собираются в одном target без устаревшего WatchKit App/Extension контейнера; - системные уведомления для новых сообщений, пока приложение запущено в фоне или на экране; в открытом сейчас диалоге дублирующий banner не показывается.
Сборка
Нужны macOS, Xcode 16 или новее и XcodeGen.
brew install xcodegen
cd Luma
make project
open Luma.xcodeproj
После обновления архива обязательно снова выполните make project: новые
build settings AppIcon берутся из project.yml. Если устройство или Simulator
закэшировали старую пустую иконку, один раз удалите установленную Luma и
установите её заново; на macOS после новой сборки перезапустите Dock или сам Mac.
Можно вместо команд дважды нажать Regenerate-Luma-Project.command в корне
проекта (XcodeGen всё равно должен быть установлен).
При переходе с версии 0.8.7 один раз выполните в Xcode Product → Clean Build
Folder, чтобы удалить кэш старого application.watchapp2 target.
После открытия проекта:
- Выберите свою Apple Development Team для
Luma,LumaMacиLumaWatch. - При необходимости замените bundle identifiers
app.luma.chat*вproject.ymlна уникальные. - Дождитесь загрузки Swift packages.
- Запустите scheme
Lumaна iPhone/iPad илиLumaMacна Mac.
Для проверки из терминала на Mac:
make verify
Версии Martin, MartinOMEMO и WebRTC закреплены в project.yml. Зависимости
Martin имеют copyleft-лицензии, поэтому Luma распространяется под
AGPL-3.0-or-later.
Prosody
Минимально нужны pep, smacks, carbons, mam и MUC component. pep используется и для
OMEMO, и для XEP-0084 аватаров. Для медиа и вложений добавьте
компонент http_file_share; для надёжных звонков за NAT — coturn и
turn_external. Готовый пример и команды проверки находятся в
Docs/PROSODY.md.
Важные границы MVP
- OMEMO-модуль использует широко развёрнутый профиль с namespace
eu.siacs.conversations.axolotl, совместимый со многими версиями Monal и Conversations. Поддержку нового namespaceurn:xmpp:omemo:2следует добавить отдельным криптографическим адаптером до объявления полной совместимости с актуальным XEP-0384. - Надёжный push при выгруженном iOS-приложении не может работать только через XMPP-сокет. Для production нужны Apple Developer credentials, APNs provider, XEP-0357 push-компонент и регистрация приложения в этом компоненте.
- При первом запуске Luma запрашивает ограниченную последнюю страницу
серверного MAM-архива, чтобы огромная история не блокировала интерфейс, и
показывает индикатор «Синхронизация истории…» только на этом первичном этапе.
После успешного прохода сохраняются серверный UID последней записи и время;
следующие подключения продолжают RSM строго после UID. Временное перекрытие
используется только для старых snapshots без UID или один раз, если сервер
удалил сохранённый UID. Сырые stanza собираются вне main queue, а расшифровка
OMEMO выполняется на отдельной фоновой очереди — главный поток освобождается на
время Signal-криптографии, поэтому большая история не лагает интерфейс.
Инкрементальная догрузка backlog продолжается в фоне без вечного спиннера, а
сообщения применяются атомарными пачками с дедупликацией по
origin-id/stanza id через хеш-индексы. После сбоя отдельной страницы синхронизация автоматически повторяется ограниченное число раз, не дожидаясь повторного открытия приложения. - Клиент не вводит собственного ограничения размера вложения: фактический предел сообщает XEP-0363 upload-компонент сервера. Видеосообщение ограничено 60 секундами.
- XEP-0424 retract является запросом на удаление: федеративный XMPP не может гарантировать стирание уже полученной или скопированной версии в чужом клиенте. «Удалить у меня» влияет только на это устройство.
- Групповое OMEMO требует неанонимную MUC-комнату и OMEMO-устройство у каждого
получателя. Luma создаёт новые комнаты members-only/persistent с
muc#roomconfig_whois=anyone; для несовместимой чужой комнаты безопасная отправка блокируется без автоматического перехода на plaintext. Сервер также должен разрешать участникам читать списки member/admin/owner. - Звонки в MVP работают один-на-один, пока приложение запущено и XMPP-соединение активно. WebRTC защищает медиапоток DTLS-SRTP, но это отдельный механизм от OMEMO. Для надёжного входящего звонка после выгрузки iOS-приложения нужны PushKit/CallKit, APNs provider и серверный push-компонент. Карточка звонка хранится в локальной истории участвовавшего устройства Luma; она не отправляется собеседнику как фальшивое текстовое сообщение и поэтому не синхронизируется через MAM.
- Реакции и групповые звонки относятся к следующему этапу.
Архитектура описана в Docs/ARCHITECTURE.md, модель
угроз и ограничения хранения — в Docs/SECURITY.md.