저는 작은 포럼과 다른 웹사이트 세 개를 운영하고 있으며, 항상 같은 현상을 목격했습니다. 사람들이 포럼에 들어와서 채팅을 할 때는 기꺼이 대화에 참여하지만, 빠른 질문을 하려고 포럼에 오는 사람은 없습니다. 그들은 이미 있는 곳에서 질문하거나, 아예 질문하지 않습니다.
그래서 저는 포럼의 채팅을 그 다른 웹사이트들에 보여주는 플러그인을 만들었습니다. 스크립트 태그 하나만 추가하면 모서리에 버블이 나타납니다. 방문객들은 이미 보유한 포럼 계정으로 포럼 자체의 로그인 화면을 통해 로그인하고, 포럼에서 보게 될 것과 동일한 채널에서 대화합니다. 그들이 보내는 메시지는 포럼의 채팅에 다른 모든 메시지와 동일하게 도착합니다. 왜냐하면 그것이 실제로 그렇기 때문입니다.
레포지토리: 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>
왜 서비스而不是 플러그인인가
처음에는 API를 통해 Discourse와 통신하는 외부 브리지를 설계하기 시작했습니다. 하지만 소스 코드를 읽은 후 설계가 완전히 바뀌었습니다. 비슷한 것을 고려하는 사람에게 그 이유들이 유용할 수 있습니다.
채팅 플러그인은 정확히 하나의 세분화된 API 스코프인 create_message만 등록합니다. 게시를 넘어선 것, 즉 채널 읽기, 히스토리 가져오기, 다이렉트 메시지 열기 등은 글로벌 스코프 키가 필요하며, 모든 사용자에게 글로벌 스코프 키를 발급하는 것은 관리해야 할 자격 증명一大堆가 됩니다. 기본 속도 제한은 관리자 버킷에서 분당 60회, 사용자 버킷에서 20회로 설정되어 있으며, 채팅 클라이언트는 의도하지 않아도 이 한도를 쉽게 소진합니다. 그리고 채팅 웹훅은 네 가지 메시지 이벤트만 포함하고 다른 것은 없으므로, 반응, 접속 상태, 읽음 상태는 전달할 수 있는 것이 아닙니다.
코드 Discourse 내부에서 실행되어 Guardian에게 직접 질문을 할 수 있게 되면, 이 모든 문제가 사라집니다. 키가 필요 없고, 속도 제한 상한이 없으며, 포럼과 불일치할 수 있는 캐시가 아니라 단일 진실 공급원이 됩니다.
작동하는 것들
채널, 메시지 히스토리, 전송, 사람 검색을 통한 다이렉트 메시지, 그리고 브라우저에서 녹음된 음성 메시지. 음성 메시지는 포럼 자체의 채팅을 읽는 멤버들에게 다운로드 링크가 아닌 일반적인 오디오 플레이어로서 표시됩니다. 이를 올바르게 구현하는 데 상당한 주의를 기울였습니다.
각 웹사이트는 고유한 액센트 색상, 모서리 위치, 패널 제목을 가지므로, 세 개의 사이트가 동일한 위젯의 세 개의 사본이 아니라 세 가지 다른 제품처럼 보일 수 있습니다. 방문객들은 끌 수 있는 알림 소리, 라이트 또는 다크 모드 오버라이드, 그리고 대화를 음소거할 수 있는 기능을 제공받습니다. 이는 실제 Discourse 멤버십에 기록되어 포럼으로 돌아갈 때에도 따라옵니다.
모든 것은 Shadow DOM 내에서 렌더링됩니다. 이는 제가 제어하지 않는 페이지에서 실행되며, 어느 쪽의 CSS도 다른 쪽을 깨뜨릴 수 없어야 합니다.
작동하지 않는 것과 그 이유
음성 또는 비디오 통화는 없습니다. 이는 의도적인 것이며 범위를 벗어납니다.
전달은 즉각적이지 않습니다. 패널이 열려 있는 동안 메시지는 약 3초 후에 도착합니다. Discourse가 채팅 이벤트를 MessageBus에 게시하므로 작동할 것 같아 보이지만, 설명이 필요합니다.
다른 도메인의 브라우저는 MessageBus 엔드포인트에 인증할 수 없습니다. 그 CORS 정책은 네 가지 요청 헤더만 허용하며, 그중 어느 것도 베어러 토큰을 전달하지 않습니다. 그리고 Discourse 인증으로의 쿼리 파라미터 경로는 RSS와 캘린더 엔드포인트로 제한됩니다. 작동하는 유일한 헤더인 X-Shared-Session-Key는 UserAuthToken으로 해석되므로, MessageBus 요청만이 아니라 그것을 포함하는 모든 요청을 인증합니다. 이를 임베딩 페이지에 전달하면 마케팅 사이트의 크로스 사이트 스크립팅 취약점이 포럼 계정 전체 탈취로 이어집니다. 3초는 더 나은 타협점입니다.
레포지토리에는 MessageBus 채널의 추측할 수 없는 이름 자체가 권한(capability)이 되는, 사용자 계정이 아닌 하나의 위젯 세션으로 범위가 제한된 더 안전한 실시간 경로를 기록해 두었습니다. 저는 이를 구현하지 않았습니다. 누군가가 원한다면, 그 근거는 docs/decisions.md에 있습니다.
설치 전에 이해해야 할 것
웹사이트를 등록하면 해당 웹사이트가 멤버들의 자격 증명을 가지고 포럼에 대한 교차 원본(cross-origin) 접근 권한을 얻게 됩니다. 이는 부수적인 효과가 아니라, 메커니즘 자체입니다.
등록한 사이트가 침해되면, 그곳에서 JavaScript를 실행할 수 있는 공격자는 그곳을 방문하는 모든 멤버로 행동할 수 있습니다. 채팅에서만이 아니라, 그 멤버가 포럼에서 할 수 있는 모든 것에 대해 말입니다.
따라서 제가 제어하는 사이트만 등록하십시오. 파트너나 고객의 사이트를 등록하는 것은 그들의 보안을 제 자신의 것으로 수용하는 것을 의미합니다. 플러그인은 아무도 열지 않는 문서가 아니라, 출처를 입력하는 필드 옆에 있는 관리자 페이지에서 이를 명시합니다. 그리고 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 문제에 대한 더 나은 답을 아는 분의 의견을 듣고 싶습니다.
