feat: embed optional stream chat

Declare chat@1 as an optional enhancement and show an Aether chat iframe beside the Sender player when a chat provider is installed.
This commit is contained in:
2026-05-25 05:44:06 +02:00
parent a5ec731ad3
commit 38ff2fa2b2
4 changed files with 100 additions and 9 deletions
+36
View File
@@ -0,0 +1,36 @@
defmodule Sender.ChatIntegration do
@moduledoc false
@chat_capability "chat@1"
@aether_embed_prefix "/aether/chat/embed/"
def embed_path(stream_id \\ "default") when is_binary(stream_id) do
if chat_available?() do
{:ok, @aether_embed_prefix <> channel_slug(stream_id)}
else
:unavailable
end
end
def chat_available? do
Code.ensure_loaded?(Tribes.PluginRegistry) &&
Tribes.PluginRegistry.provides?(@chat_capability)
rescue
_error -> false
end
defp channel_slug(stream_id) do
"sender-stream-" <> slugify(stream_id)
end
defp slugify(value) do
value
|> String.downcase()
|> String.replace(~r/[^a-z0-9]+/u, "-")
|> String.trim("-")
|> case do
"" -> "default"
slug -> slug
end
end
end
+31 -8
View File
@@ -14,10 +14,17 @@ defmodule SenderWeb.HomeLive do
on_mount({Tribes.Plugin.LiveUserAuth, :live_user_optional})
alias Sender.ChatIntegration
alias Tribes.Plugin.Layouts
def mount(_params, _session, socket) do
{:ok, assign(socket, :page_title, "Streaming")}
chat_embed_path =
case ChatIntegration.embed_path() do
{:ok, path} -> path
:unavailable -> nil
end
{:ok, socket |> assign(:page_title, "Streaming") |> assign(:chat_embed_path, chat_embed_path)}
end
def render(assigns) do
@@ -34,13 +41,29 @@ defmodule SenderWeb.HomeLive do
</p>
</section>
<section
id="sender-player"
class="sender-player rounded-box border border-base-300 bg-base-200/60 p-6"
phx-update="ignore"
>
<p class="text-sm text-base-content/60">No live stream selected.</p>
</section>
<div class={[
"grid gap-4",
@chat_embed_path && "xl:grid-cols-[minmax(0,1fr)_24rem]",
!@chat_embed_path && "grid-cols-1"
]}>
<section
id="sender-player"
class="sender-player rounded-box min-h-[24rem] border border-base-300 bg-base-200/60 p-6"
phx-update="ignore"
>
<p class="text-sm text-base-content/60">No live stream selected.</p>
</section>
<section :if={@chat_embed_path} id="sender-stream-chat" class="min-h-[32rem] overflow-hidden rounded-box border border-base-300 bg-base-100">
<iframe
id="sender-chat-frame"
title="Stream chat"
src={@chat_embed_path}
class="h-full min-h-[32rem] w-full border-0"
>
</iframe>
</section>
</div>
</div>
</Layouts.app>
"""
+1 -1
View File
@@ -7,7 +7,7 @@
"otp_app": "sender",
"provides": ["sender@1"],
"requires": ["ui@1"],
"enhances_with": [],
"enhances_with": ["chat@1"],
"assets": {
"global_js": ["sender.js"],
"global_css": ["sender.css"]
+32
View File
@@ -9,9 +9,41 @@ defmodule Sender.HomePageTest do
assert has_element?(view, "#plugin-nav-streaming", "Streaming")
end
test "embeds stream chat when a chat provider is installed", %{conn: conn} do
register_chat_provider!()
{:ok, view, _html} = live(conn, "/sender")
assert has_element?(view, "#sender-stream-chat")
assert has_element?(
view,
~s|#sender-chat-frame[src="/aether/chat/embed/sender-stream-default"]|
)
end
test "dispatches top-level subpaths to the plugin page", %{conn: conn} do
{:ok, _view, html} = live(conn, "/sender/example")
assert html =~ "Streaming"
end
defp register_chat_provider! do
name = "sender_chat_test"
_ = Tribes.PluginRegistry.unregister_plugin(name)
:ok =
Tribes.PluginRegistry.register_plugin(
name,
%{
name: name,
version: "0.1.0",
provides: ["chat@1"],
provider_priority: -100
},
File.cwd!()
)
on_exit(fn -> _ = Tribes.PluginRegistry.unregister_plugin(name) end)
end
end