16 KiB
Друзья, сообщения и уведомления
Социальные функции работают между пользователями одного сервера. В web и desktop открывается раздел «Друзья и общение», на Android — вкладка «Друзья».
На ПК справа над плеером доступна мини-панель друзей и чата шириной до 600 px. Она позволяет переписываться без ухода с текущей страницы, сворачивается с сохранением черновика и открывает выбранную переписку в полном разделе. На экранах уже 1024 px используется полный раздел общения.
Поведение
- Поиск по логину и отображаемому имени начинается с двух символов, возвращает до 30 человек. Пароли, роли, настройки и другие приватные поля в результат не попадают.
- Заявка требует явного принятия получателем. Встречная заявка не принимает дружбу автоматически. Можно отменить свою заявку, отклонить входящую или удалить друга.
- Личные сообщения и отправка музыки доступны только текущим друзьям. Блокировка прекращает дружбу и закрывает общение в обоих направлениях. Разблокировка дружбу не восстанавливает.
- История сохраняется после удаления друга, но доступна снова только после новой взаимной дружбы. В первой странице чата — последние 50 сообщений; ранние подгружаются отдельно.
- В сообщении до 4000 символов. Музыкальное вложение — ID трека либо действующая ссылка общего доступа к плейлисту. Отзыв ссылки делает вложенный плейлист недоступным.
- «Поделиться» открывает выбор: скопировать ссылку, вызвать системную отправку (когда она поддерживается клиентом) или отправить конкретному другу одним нажатием. Отправка другу создаёт сообщение с музыкальным вложением. Повтор после сетевого сбоя использует тот же UUID.
Спокойные уведомления
Четыре типа: входящая заявка, принятие заявки, личное сообщение, музыка. Центр событий и счётчики сохраняются на сервере, отметки прочтения общие для устройств. Нет звуков, автоматических всплывающих окон и запроса разрешений ОС.
Список уведомлений возвращает до 100 событий, сначала непрочитанные. По мере подтверждения новых становятся доступны более старые непрочитанные события. Открытый видимый чат подтверждает сообщения только до последнего полученного номера; более поздние сообщения не теряются. В скрытой вкладке и в фоне Android автоматического подтверждения нет.
Сейчас это уведомления внутри работающего приложения, с обновлением центра каждые 15 секунд и открытого чата каждые 3 секунды. Web использует стандартную приостановку фонового polling React Query; Android останавливает polling при уходе приложения в фон. Уведомления ОС при закрытом клиенте (Web Push/FCM/нативный desktop) в эту реализацию не входят.
Реализация и проверки
Контракт: api/openapi.yaml, операции getSocial, searchPeople, socialAction,
getDirectMessages, sendDirectMessage. Модуль API: internal/social.
Миграция 00019_social.sql создаёт дружбу, блокировки, сообщения и уведомления.
Миграции применяются штатным запуском API.
Мутации пары сериализованы транзакционной advisory-блокировкой PostgreSQL. Отправка сообщения и создание уведомления атомарны. UUID сообщения глобально уникален; повтор с другим содержимым возвращает конфликт. Чужое уведомление нельзя отметить прочитанным. Поиск скрывает заблокированные аккаунты и личные блокировки, включая обратное направление.
Ограничения: до 50 исходящих ожидающих заявок, до 10 новых заявок за минуту, до 30 сообщений одному человеку за минуту; тело социального POST ограничено 32 KiB. Тексты выводятся обычным текстом, вложения формируют только внутренние маршруты каталога.
Проверки: go test ./internal/social,
TEST_DATABASE_URL=… go test -tags integration ./internal/social,
pnpm --filter @pipu/web test src/App.test.tsx -t 'social|playlist sharing',
pnpm --filter @pipu/web test:e2e e2e/social.spec.ts e2e/playlists.spec.ts.
Интеграционный сценарий включён в ci-db.
Активность и приватность
В разделе друзей настройка «Показывать мой статус» принимает значения «Никому» (по умолчанию), «Друзьям», «Всем». Сервер проверяет аудиторию, дружбу и блокировки. При «Никому» сведения о текущем прослушивании не сохраняются. Переключение на это значение удаляет активность всех устройств. Публикация имеет последовательный номер и срок жизни 90 секунд; пауза снимает статус, потерянное соединение не оставляет вечный статус.
Карточка показывает трек и контекст: «Моя волна», плейлист или обычное прослушивание. Трек открывается в каталоге. Плейлист доступен для чтения, пока активность видима: этот переход не создаёт постоянной ссылки общего доступа и не раскрывает существующую.
Совместное прослушивание
Статус «В сети» виден только текущим друзьям и независим от видимости музыки. Клиент поддерживает его штатным опросом состояния прослушивания, даже на паузе; просмотр социального раздела также обновляет собственный heartbeat. Сервер объединяет частые обновления одного пользователя до одного за 10 секунд. Если ни одно устройство не отвечает 45 секунд, статус истекает; UI обновляется при следующем опросе друзей. «Слушать вместе» отключено для друга не в сети. Сервер проверяет присутствие обоих участников при создании и принятии запроса, поэтому устаревшая кнопка не обходит правило. Блокировки и ограничения аудитории проверяются до выдачи ошибки об офлайн-статусе.
«Подключиться» отправляет запрос хозяину и оставляет управление у него. «Пригласить на мой стрим» создаёт собственную комнату: приглашённый становится слушателем. Один пользователь состоит в одной комнате; несколько слушателей могут подключиться к хозяину. Комната резервируется на время ожидания подтверждения. До принятия запроса слушатель не становится участником, а баннер совместного прослушивания не показывается. Исходное устройство ведущего поддерживает резервирование, не прерывая свою музыку. После успешной отправки окно закрывается; ожидающий запрос можно отменить в разделе общения. После отмены или истечения последнего запроса исходное устройство освобождает пустую комнату при следующем опросе, не прерывая свою музыку. Повторный запрос уже подключённого слушателя не создаёт нового ожидания. Второе устройство ведущего не включает звук автоматически и при закрытии не завершает стрим. Настройки приёма запросов/приглашений и автоматического принятия независимы, обе по умолчанию «Никто». Автопринятие применяется только внутри разрешённой аудитории. Дополнительная настройка управляет запуском звука при подключении.
Только исходное устройство хозяина публикует состояние комнаты. Слушатели получают трек, паузу и позицию с поправкой на серверное время каждые 2 секунды. Обычные кнопки управления, очередь и перемотка заблокированы; громкость, реакции и выход доступны. Это синхронизация собственных потоков из каталога с допуском дрейфа 1,5 секунды, а не ретрансляция аудиобайта хозяина. Браузер может потребовать нажатия «Включить звук». После 10 секунд потери связи слушатель ставится на паузу, комната истекает через 30 секунд без хозяина, отсутствующий слушатель удаляется через 60 секунд, запрос действует 2 минуты.
Чат
Web и desktop используют встроенный экран: список диалогов слева, переписка справа. На узком экране список и переписка открываются последовательно; кнопка назад возвращает к списку, а поле ввода остаётся над плеером. Android использует общую нижнюю панель. Поддерживаются текст и музыка, ответы с цитатой, шесть реакций, редактирование своих сообщений, удаление для всех, поиск по тексту, история, даты, прочтение и индикатор набора. Удаление очищает исходный текст и музыкальное вложение. Черновики разделены по серверу, пользователю и собеседнику. Скрытая переписка не отмечает сообщения прочитанными. Просмотр истории выше последних сообщений не вызывает принудительную прокрутку вниз. Поиск открывается по кнопке. Длинные сообщения можно развернуть, HTTP(S)-ссылки отображаются компактно без декодирования произвольного текста.
Список диалогов отсортирован по последнему сообщению; сервер отдаёт короткое превью только для текущих друзей, с повторной проверкой блокировок. Удалённый текст очищен. Вкладки «Друзья» и «Заявки» отделены от сообщений; приватность и настройки совместного прослушивания открываются из компактного индикатора статуса. Удаление друга и блокировка доступны через меню и требуют подтверждения.
Треки и плейлисты отображаются карточками с названием, обложкой и воспроизведением.
«Музыка из переписки» использует getDirectMessages?music=true с обычным before:
доступна вся история вложений, удалённые сообщения исключены, прочтение не подтверждается.
Недоступные треки и отозванные ссылки не запускаются; метаданные берутся из каталога
и публичной проекции плейлиста, а не из текста сообщения.
Анимации учитывают системное уменьшение движения. Фото, файлы и голосовые не входят в версию.
Миграции 00020–00022 добавляют активность, комнаты и возможности чата.
Уведомления дополнены запросом, приглашением и принятием совместного прослушивания.
Сигнал друзей в рекомендациях
В настройках рекомендаций можно отключить влияние друзей. Используется только их
текущая видимая активность с учётом аудитории и блокировок, без скрытой истории.
Go передаёт до 30 допустимых треков; Python добавляет до 10 кандидатов, небольшой бонус
с учётом выбранного контекста и не более одного кандидата с источником friends на выдачу.
Дизлайки, недоступные треки и недавние повторы исключаются прежними фильтрами.
При недоступном рекомендателе резервный подбор добавляет не более одного такого трека
в каждую вторую выдачу; тематические волны с seed сохраняют свой сценарий.