Мост для чата: добавьте чат форума на другие ваши сайты

Я веду небольшой форум и ещё три сайта, и я постоянно замечал одно и то же. Люди охотно общались в чате, оказавшись на форуме, но никто не заходит на форум, чтобы задать быстрый вопрос. Они спрашивают там, где уже находятся, или не спрашивают вовсе.

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

Репозиторий: GitHub - capodieci/discourse-chat-bridge: Allows to install discourse chat as a plugin in other websites · GitHub (MIT)

<script src="https://forum.example.com/chat-bridge/widget.js"
        data-site-key="your-site-key" defer></script>

Почему это плагин, а не сервис

Я начал с проектирования внешнего моста, который взаимодействовал бы с Discourse через API, а потом прочитал исходный код. Это полностью изменило дизайн, и причины могут быть полезны всем, кто рассматривает нечто подобное.

Плагин чата регистрирует ровно один гранулярный API-область доступа (scope), create_message. Всё, что выходит за рамки отправки сообщений — чтение каналов, получение истории, открытие личного сообщения, — требует глобального ключа области, а глобальный ключ для каждого пользователя — это огромная куча учётных данных, которые нужно хранить. По умолчанию лимиты запросов составляют 60 в минуту для административного сегмента и 20 для пользовательского, и клиент чата сжигает их, даже не стараясь. А вебхуки чата несут в себе только четыре события сообщений и ничего больше, поэтому реакции, присутствие и статус прочтения просто недоступны для пересылки.

Все эти проблемы исчезают, когда код выполняется внутри Discourse и может напрямую задавать вопросы Guardian. Никаких ключей, никакого потолка лимитов запросов, и один источник истины, а не кэш, который может расходиться с форумом.

Что работает

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

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

Всё рендерится внутри Shadow DOM. Это работает на страницах, которые я не контролирую, и CSS ни одной из сторон не должен иметь возможности сломать другую.

Что не работает и почему

Нет голосовых или видеозвонков. Это осознанное решение и выходит за рамки проекта.

Доставка не мгновенная. Сообщения приходят примерно через три секунды, пока панель открыта. Это заслуживает объяснения, потому что Discourse действительно публикует события чата в MessageBus, и кажется, что всё должно работать само.

Браузер на другом домене не может аутентифицироваться на эндпоинте MessageBus. Его политика CORS разрешает четыре заголовка запроса, и ни один из них не несёт bearer-токен, а маршрут через параметр запроса в аутентификацию Discourse ограничен эндпоинтами RSS и календаря. Единственный заголовок, который работает, X-Shared-Session-Key, разрешается в UserAuthToken и, следовательно, аутентифицирует любой запрос, несущий его, а не только запросы MessageBus. Передача его встраивающей странице превратила бы дыру в кросс-сайтовом скриптинге на маркетинговом сайте в полный захват аккаунта форума. Три секунды — лучший компромисс.

Существует более безопасный путь к реальному времени, описанный в репозитории, использующий канал MessageBus, чьё неугадываемое имя само по себе является правом доступа (capability), ограниченным одной сессией виджета, а не аккаунтом пользователя. Я не строил это. Если кто-то захочет, рассуждения находятся в docs/decisions.md.

То, что нужно понять перед установкой

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

Если скомпрометирован сайт, который вы зарегистрировали, злоумышленник, способный выполнять JavaScript там, может действовать от имени любого участника, который его посещает. Не только в чате. Любое действие, которое этот участник мог совершить на форуме.

Поэтому регистрируйте только те сайты, которые вы контролируете. Регистрация сайта партнёра или клиента означает принятие их безопасности как вашей собственной. Плагин говорит об этом на административной странице рядом с полем, где вы вводите источник (origin), а не в документе, который никто не открывает, а SECURITY.md разбирает, что плагин делает для ограничения масштаба последствий и что он намеренно не делает.

Установка

Добавьте его в определение контейнера и пересоберите один раз:

hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone --depth 1 https://github.com/capodieci/discourse-chat-bridge.git
env:
  DISCOURSE_ENABLE_CORS: true

Затем включите chat_bridge_enabled в Администрирование, Настройки, и всё остальное находится на одной странице /chat-bridge/admin, где вы добавляете сайт, и он выдаёт вам тег скрипта.

Две заметки, которые сэкономят кому-то полдня. Голосовым сообщениям нужны аудиоформаты в authorized_extensions, которые по умолчанию не содержат ничего, поэтому административная страница точно сообщает, каких не хватает. И chat_allowed_groups по умолчанию установлен на уровень доверия 1, поэтому совершенно новые аккаунты не могут общаться, пока не заработают его, и виджет объясняет это, а не молча падает.

Совместимость

Тестировалось против Discourse 2026.9.0 и 2026.8.0, а также в производственной среде на одном форуме.

Это опирается на объекты сервиса чата, которые не являются публичным API, поэтому выпуск Discourse может переместить их. В репозитории включён скрипт предварительной проверки только для чтения, который утверждает, что каждый из них всё ещё существует, и сообщает об этом за секунды, а не при первом запросе. Стоит запустить его после обновления.

Чего я хотел бы

Чтобы кто-то установил это на форуме, который не мой, и рассказал мне, что сломалось. Всё до сих пор проверено на одном Discourse, на одном сервере, одним человеком, и это самое слабое место.

Я также хотел бы услышать от любого, кто знает лучший ответ на проблему MessageBus, чем тот, на котором я остановился.

3 лайка

сразу убрал лайк, как только это заметил :smiling_face_with_tear:

2 лайка