私は小さなフォーラムと他の3つのサイトを運営しており、いつも同じことに気づいていました。人々はフォーラムにいるとチャットで話したがりますが、ちょっとした質問をするためにフォーラムまで行く人はいません。彼らはすでにいる場所で質問するか、そもそも質問しません。
そこで、フォーラムのチャットを他のサイトに配置するプラグインを作りました。スクリプトタグを1つ追加するだけで、隅にバブルが表示されます。訪問者はすでに持っているフォーラムアカウントで、フォーラム独自のログイン画面を通じてサインインし、フォーラムで見られるのと同じチャンネルで会話できます。彼らが送信したメッセージは、他のメッセージと同様にフォーラムのチャットに届きます。なぜなら、それが本来の姿だからです。
リポジトリ: 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と通信する外部ブリッジを設計しましたが、ソースコードを読んだことで設計が完全に変わりました。類似のものを検討している方にとって、その理由は参考になるかもしれません。
チャットプラグインは、create_message という1つの細粒度のAPIスコープのみを登録します。投稿を超える動作、つまりチャンネルの読み取り、履歴の取得、ダイレクトメッセージの開始には、グローバルスコープキーが必要です。すべてのユーザーにグローバルスコープキーを発行すると、管理すべき認証情報の山ができてしまいます。また、デフォルトのレート制限は管理者バケットで分60リクエスト、ユーザーバケットで分20リクエストですが、チャットクライアントは意図せずこれをすぐ使い切ります。さらに、チャットのWebhookは4つのメッセージイベントしか持たず、リアクション、オンライン状態、既読状態などは転送可能な情報として存在しません。
これらの問題はすべて、コードがDiscourse内部で実行され、Guardian に直接質問できることで消えます。キーも不要、レート制限の上限もなく、フォーラムと食い違う可能性のあるキャッシュではなく、単一の信頼できる情報源を持つことになります。
動作する機能
チャンネル、メッセージ履歴、送信、人々の検索付きダイレクトメッセージ、ブラウザで録音されたボイスメッセージです。ボイスメッセージは、フォーラムのチャットを読むメンバーに対して通常のオーディオプレーヤーとして表示され、ダウンロードリンクではありません。これを正しく実装するには多少の手間がかかりました。
各ウェブサイトには独自のアクセントカラー、配置場所、パネルタイトルが割り当てられるため、3つのサイトが同じウィジェットのコピーではなく、3つの異なるプロダクトのように見えます。訪問者はオンオフ可能な通知音、ライト/ダークモードのオーバーライド、会話のミュート機能(これは実際のDiscourseメンバーシップに書き込まれ、フォーラムに戻った際も維持されます)を利用できます。
すべてはShadow DOM内でレンダリングされます。これは自分が管理していないページで動作するため、どちらのCSSも相手を壊してはなりません。
動作しないこと、そしてその理由
通話(音声・ビデオ)はありません。これは意図的なもので、スコープ外です。
配信は即時ではありません。パネルが開いている間、メッセージは約3秒で届きます。DiscourseはチャットイベントをMessageBusに公開しており、動作するはずに見えるため、これには説明が必要です。
別のドメイン上のブラウザは、MessageBusエンドポイントに認証できません。そのCORSポリシーは4つのリクエストヘッダーのみを許可し、その中にはベアラートークンを持つものはありません。また、Discourseの認証へのクエリパラメータ経由のルートは、RSSとカレンダーエンドポイントに制限されています。唯一機能するヘッダー X-Shared-Session-Key は UserAuthToken に解決され、したがってそれを保持するすべてのリクエスト(MessageBusのリクエストに限らず)を認証します。これを埋め込みページに渡すと、マーケティングサイトでのクロスサイトスクリプティング(XSS)の穴が、フォーラムアカウントの完全な乗っ取りに変わってしまいます。3秒の遅延の方が良いトレードオフです。
リポジトリには、推測しにくいチャンネル名自体が権限となるMessageBusチャンネルを使用した、より安全なリアルタイム接続の手法が記載されています。1つのウィジェットセッションにスコープされ、ユーザーアカウント全体にはスコープされません。私はこれを実装していません。もし誰かが実装したければ、その根拠は docs/decisions.md にあります。
インストール前に理解すべきこと
ウェブサイトを登録すると、そのウェブサイトはあなたのメンバーの認証情報を伴って、あなたのフォーラムへのクロスオリジンアクセス権を得ます。これは副作用ではなく、仕組みそのものです。
登録したサイトが侵害された場合、そこでJavaScriptを実行できる攻撃者は、そこを訪問する任意のメンバーとして行動できます。チャットだけでなく、そのメンバーがフォーラムで実行できるすべてのことです。
したがって、自分が管理するサイトのみを登録してください。パートナーやクライアントのサイトを登録することは、彼らのセキュリティを自分のものとして受け入れることを意味します。このプラグインは、誰も開かないドキュメントではなく、オリジンをタイプするフィールドの横にある管理ページでこれを明示しています。また、SECURITY.md には、プラグインが被害範囲を制限するために何をし、意図的に何を行わないかが記載されています。
インストール
コンテナ定義に追加し、1回再ビルドします:
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
次に、Admin、Settingsで chat_bridge_enabled をオンにし、それ以外は /chat-bridge/admin の1ページに集約されています。ここでウェブサイトを追加すると、スクリプトタグが手渡されます。
誰かの午後を救う2つの注意点があります。ボイスメッセージには authorized_extensions 内のオーディオ形式が必要です。デフォルトでは何も含まれていないため、管理ページはどの形式が欠けているかを正確に示します。また、chat_allowed_groups はデフォルトでトラストレベル1に設定されているため、新しいアカウントはそれを獲得するまでチャットできません。ウィジェットはこれを明示的に説明し、静かに失敗することはありません。
互換性
Discourse 2026.9.0 と 2026.8.0 に対してテスト済み、および1つのフォーラムでの本番環境での使用実績があります。
これは公開APIではないチャットサービスオブジェクトに依存しているため、Discourseのリリースでこれらが移動する可能性があります。リポジトリには、それらがすべてまだ存在することをアサートし、最初のリクエスト時ではなく数秒で報告する、読み取り専用の事前チェックスクリプトが含まれています。アップグレード後に実行する価値があります。
望んでいること
私のものではないフォーラムにインストールして、何が壊れたかを教えてくれる人がいることです。これまではすべて、1人の人物によって、1つのサーバー上の1つのDiscourseに対して検証されており、それが最も弱い点です。
また、私が決めたものよりもMessageBus問題に対するより良い答えを知っている人からの意見も聞きたいです。
