Встраивания зависли на «Загрузка обсуждения...»? Тема не создана? Вот решение

Вернувшись сегодня к работе, я обратил внимание на несколько моментов на страницах администратора. Вот мои мысли о том, что можно улучшить.

Настройки встраивания — /admin/customize/embedding/settings

Параметр allowed_internal_hosts имеет решающее значение для надежной работы встраивания в непубличных средах. Его необходимо явно указать как связанный параметр в этом разделе — его важность не вызывает никаких сомнений.

Хосты для встраивания — /admin/customize/embedding

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

Первый абзац

Текущий вариант:

Вставьте следующий HTML-код на свой сайт, чтобы создать и встроить темы Discourse. Замените EMBED_URL на канонический URL страницы, на которой вы размещаете этот код.

Альтернативный вариант:

Вставьте следующий HTML-код в то место страницы, где должны отображаться комментарии.
Параметр discourseEmbedUrl — это URL вашей страницы, на которую будут вести ссылки из Discourse. При первой загрузке вашей страницы Discourse попытается найти или создать тему для этого URL и установить ссылку на ваш контент.

Второй абзац

Текущий вариант:

Если вы хотите настроить стиль, раскомментируйте код и замените CLASS_NAME на CSS-класс, определенный в разделе Встроенный CSS вашей темы.

Альтернативный вариант:

Используйте свойство className, чтобы добавить пользовательские классы к тегу <html> внутри встроенного iframe. Для стилизации перейдите по адресу /admin/customize/themes, нажмите кнопку Изменить для вашей темы, затем кнопку Изменить код и включите опцию Показать расширенные настройки. Добавьте ваш пользовательский CSS в раздел Встроенный CSS.

Третий абзац

Текущий вариант:

Замените DISCOURSE_USERNAME на имя пользователя Discourse автора, который должен создать тему. Discourse автоматически найдет пользователя по атрибуту content тегов <meta> с атрибутом name, установленным в discourse-username или author. Параметр discourseUserName устарел и будет удален в версии Discourse 3.2.

Альтернативный вариант:

Примечание: Тема создается реальным пользователем Discourse, а не отображаемым именем или строкой автора. Это должен быть валидный, существующий аккаунт. Существует три способа определить, какой пользователь будет использован:

  1. Вариант по умолчанию — задается в разделе /admin/customize/embedding/posts_and_topics
  2. Переопределение для конкретного хоста — задается в разделе /admin/customize/embedding/
  3. Управление для конкретного URL — добавьте тег <meta name="discourse-username" content="USERNAME"> на вашу страницу с существующим именем пользователя Discourse USERNAME

Работает только имя пользователя существующего аккаунта Discourse. Если пользователь из мета-тега не найден, Discourse перейдет к варианту для хоста или глобальному варианту по умолчанию. Метод с использованием мета-тега, показанный здесь, позволяет осуществлять программное управление на уровне URL тем, какой пользователь Discourse используется для создания темы. Например, вы можете сопоставить авторов постов в блоге на вашем сайте с соответствующими учетными записями пользователей Discourse.

Раздел Фрагмент конфигурации

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

Я понимаю, что это может быть предметом споров — некоторые могут счесть интерфейс чистым и достаточным. Но я лично пропускал этот раздел слишком много раз, прежде чем понял, что его можно нажать. Это говорит о том, что возможности взаимодействия (affordance) можно улучшить, даже если немного. Более четкий визуальный сигнал или состояние по умолчанию без сворачивания могут значительно помочь, особенно новичкам, которые полагаются на примеры.

Фрагмент кода

Текущий вариант:

<div id='discourse-comments'></div>
  <meta name='discourse-username' content='DISCOURSE_USERNAME'>

  <script type="text/javascript">
    DiscourseEmbed = {
      discourseUrl: 'https://discourse.your-site.com/',
      discourseEmbedUrl: 'EMBED_URL',
      // className: 'CLASS_NAME',
    };

    (function() {
      var d = document.createElement('script'); d.type = 'text/javascript'; d.async = true;
      d.src = DiscourseEmbed.discourseUrl + 'javascripts/embed.js';
      (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(d);
    })();
  </script>

Альтернативный вариант:

<div id="discourse-comments"></div>
<!-- Опционально: укажите, какая учетная запись пользователя Discourse создает тему -->
<!-- Если опущено, Discourse использует пользователя по умолчанию для хоста или глобального варианта -->
<meta name="discourse-username" content="DISCOURSE_USERNAME" />

<script type="text/javascript">
  DiscourseEmbed = {
    discourseUrl: 'https://discourse.mydomain.com/', // Требуется завершающий слэш
    discourseEmbedUrl: window.location.href, // Или жестко заданная каноническая строка URL
    // className: 'my-iframe-theme another-class',
    // discourseReferrerPolicy: 'strict-origin-when-cross-origin',
    // topicId: '1234',
  };

  (function() {
    const d = document.createElement('script');
    d.type = 'text/javascript';
    d.async = true;
    d.src = `${DiscourseEmbed.discourseUrl}javascripts/embed.js`;
    document.head.appendChild(d);
  })();
</script>

Параметры DiscourseEmbed — краткие описания

  • discourseUrlОбязательно
    Полный URL вашего экземпляра Discourse. Должен заканчиваться на завершающий слэш, например https://discourse.mydomain.com/

  • discourseEmbedUrlОбязательно
    Полный URL текущей страницы, на которую встраиваются комментарии. Именно так Discourse идентифицирует темы и связывает их с вашим контентом.

  • classNameОпционально
    Добавляет пользовательские CSS-классы к элементу <html> внутри iframe. Определите стили в разделе “Встроенный CSS” вашей темы Discourse.

  • discourseReferrerPolicyОпционально
    По умолчанию no-referrer-when-downgrade. См.: Referrer-Policy

  • topicIdОпционально
    Если установлено, Discourse будет использовать эту тему напрямую. В противном случае он будет искать тему, соответствующую discourseEmbedUrl, или создаст её, если такой не существует.

Контекстная документация для DiscourseEmbed

Параметр discourseEmbedUrl должен быть доступен для вашего сервера Discourse. При загрузке iframe Discourse загружает страницу по этому URL, чтобы создать или найти тему. Это работает безупречно для веб-сайтов, размещенных на публичных платформах.

Однако, если вы разрабатываете локально, встраивание может зависнуть на этапе “Загрузка обсуждения…” или вообще не появиться. Это происходит потому, что локальные URL разработки (например, localhost) могут блокироваться защитой Discourse от SSRF, опцией force_https или отсутствием хоста для встраивания.

Если вы разрабатываете новые функции для существующего сайта, одним из обходных путей является указание discourseEmbedUrl на производственный URL. Когда включена опция embed_any_origin, Discourse позволит встраиванию работать, даже если iframe обслуживается с другого источника. Комментарии загрузятся, если они существуют, или будет показана кнопка “Продолжить обсуждение”.

Альтернативно, если ваш локальный домен (например, localhost) добавлен в раздел Хосты для встраивания, вам может вообще не понадобиться embed_any_origin. Но вы все равно должны добавить localhost как хост для встраивания.

:warning: Один нюанс: если включена настройка force_https, а ваш сайт разработки не использует TLS, встраивание не удастся. В этом случае либо отключите force_https во время разработки, либо рассмотрите возможность запуска отдельного экземпляра Discourse для тестирования.

Примечание: Если discourseEmbedUrl доступен публично, но встраивание по-прежнему показывает “Загрузка обсуждения…” без создания темы, ваш домен может быть заблокирован защитой Discourse от SSRF.

Это часто происходит, когда ваш экземпляр Discourse работает в среде с локальным разрешением DNS — например, в Docker, Kubernetes или в LAN с внутренним DNS-сервером. В таких случаях Discourse может разрешить домен вашего сайта на локальный IP-адрес (например, 127.0.0.1 или 192.168.x.x) и считать его небезопасным.

Чтобы разрешить доступ, добавьте ваш домен в настройку сайта allowed_internal_hosts. Это явно пометит ваш домен как безопасный для загрузки, обходя фильтрацию SSRF.

Полный список заблокированных диапазонов IP-адресов доступен в исходном коде Discourse.

Разрешенные внутренние хосты — Описание настройки сайта

Текущий вариант:

Список внутренних хостов, которые Discourse может безопасно сканировать для однобоксинга и других целей

Альтернативный вариант:

Разрешает Discourse сканировать хосты, которые разрешаются во внутренние IP-адреса. Необходимо, если ваш сайт работает за локальным DNS (например, Docker, LAN, Kubernetes). Требуется для встраивания комментариев, создания тем и однобоксинга, когда защита от SSRF в противном случае заблокирует доступ.

Соображения

Некоторые из этих предложений могут быть лучше оформлены как ссылки на официальную документацию. Фактически, этот пост сам по себе может справиться с задачей, поскольку он должен индексироваться как любой другой. Другие могут потребовать полноценного pull request — до которого я, возможно, когда-нибудь доберусь, но не сегодня.

Тем не менее, в написанном мной могут быть технические неточности. Большая часть основана на практическом опыте, но я мог неверно интерпретировать какое-то поведение, быть обманутым кэшированием (о, кэширование…) или просто что-то упустить. В этом вопросе я полагаюсь на мнение опытных ветеранов Discourse.