2021-11-16 16:58:22 +00:00
|
|
|
|
defmodule LiveBeatsWeb.Presence do
|
|
|
|
|
@moduledoc """
|
|
|
|
|
Provides presence tracking to channels and processes.
|
|
|
|
|
|
|
|
|
|
See the [`Phoenix.Presence`](http://hexdocs.pm/phoenix/Phoenix.Presence.html)
|
|
|
|
|
docs for more details.
|
|
|
|
|
"""
|
2022-01-11 19:57:06 +00:00
|
|
|
|
use Phoenix.Presence,
|
|
|
|
|
otp_app: :live_beats,
|
2022-03-21 18:09:27 +00:00
|
|
|
|
pubsub_server: LiveBeats.PubSub,
|
|
|
|
|
presence: __MODULE__
|
|
|
|
|
|
2022-04-06 16:20:23 +00:00
|
|
|
|
@pubsub LiveBeats.PubSub
|
2021-11-16 16:58:22 +00:00
|
|
|
|
|
2022-11-17 15:01:20 +00:00
|
|
|
|
use LiveBeatsWeb, :html
|
2021-11-16 16:58:22 +00:00
|
|
|
|
|
2022-01-12 17:27:30 +00:00
|
|
|
|
alias LiveBeats.{Accounts, MediaLibrary}
|
2022-01-31 19:27:06 +00:00
|
|
|
|
alias LiveBeatsWeb.Presence.BadgeComponent
|
2022-01-12 17:27:30 +00:00
|
|
|
|
|
2022-04-06 16:20:23 +00:00
|
|
|
|
def track_profile_user(%MediaLibrary.Profile{} = profile, current_user_id) do
|
|
|
|
|
track(
|
|
|
|
|
self(),
|
|
|
|
|
"proxy:" <> topic(profile),
|
|
|
|
|
current_user_id,
|
|
|
|
|
%{}
|
|
|
|
|
)
|
2022-03-21 18:09:27 +00:00
|
|
|
|
end
|
|
|
|
|
|
2022-04-06 16:20:23 +00:00
|
|
|
|
def untrack_profile_user(%MediaLibrary.Profile{} = profile, current_user_id) do
|
|
|
|
|
untrack(
|
|
|
|
|
self(),
|
|
|
|
|
"proxy:" <> topic(profile),
|
|
|
|
|
current_user_id
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def init(_opts) do
|
|
|
|
|
{:ok, %{}}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def handle_metas(topic, %{joins: joins, leaves: leaves}, presences, state) do
|
|
|
|
|
for {user_id, presence} <- joins do
|
|
|
|
|
user_data = %{user: presence.user, metas: Map.fetch!(presences, user_id)}
|
|
|
|
|
local_broadcast(topic, {__MODULE__, %{user_joined: user_data}})
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
for {user_id, presence} <- leaves do
|
|
|
|
|
metas =
|
|
|
|
|
case Map.fetch(presences, user_id) do
|
|
|
|
|
{:ok, presence_metas} -> presence_metas
|
|
|
|
|
:error -> []
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
user_data = %{user: presence.user, metas: metas}
|
|
|
|
|
|
|
|
|
|
local_broadcast(topic, {__MODULE__, %{user_left: user_data}})
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
{:ok, state}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def list_profile_users(%MediaLibrary.Profile{} = profile) do
|
|
|
|
|
list("proxy:" <> topic(profile))
|
2022-03-21 18:09:27 +00:00
|
|
|
|
end
|
|
|
|
|
|
2022-01-12 17:27:30 +00:00
|
|
|
|
def subscribe(%MediaLibrary.Profile{} = profile) do
|
2022-04-06 16:20:23 +00:00
|
|
|
|
Phoenix.PubSub.subscribe(@pubsub, topic(profile))
|
2022-01-12 17:27:30 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def fetch(_topic, presences) do
|
|
|
|
|
users =
|
|
|
|
|
presences
|
|
|
|
|
|> Map.keys()
|
|
|
|
|
|> Accounts.get_users_map()
|
|
|
|
|
|> Enum.into(%{})
|
|
|
|
|
|
|
|
|
|
for {key, %{metas: metas}} <- presences, into: %{} do
|
|
|
|
|
{key, %{metas: metas, user: users[String.to_integer(key)]}}
|
|
|
|
|
end
|
|
|
|
|
end
|
2022-01-31 13:21:27 +00:00
|
|
|
|
|
2022-01-31 19:27:06 +00:00
|
|
|
|
def listening_now(assigns) do
|
|
|
|
|
count = Enum.count(assigns.presence_ids)
|
|
|
|
|
|
|
|
|
|
assigns =
|
|
|
|
|
assigns
|
|
|
|
|
|> assign(:count, count)
|
|
|
|
|
|> assign_new(:total_count, fn -> count end)
|
2021-12-14 16:23:19 +00:00
|
|
|
|
|
2021-11-16 16:58:22 +00:00
|
|
|
|
~H"""
|
2022-08-03 13:40:11 +00:00
|
|
|
|
<div class="px-4 mt-6 sm:px-6 lg:px-8">
|
|
|
|
|
<!-- users -->
|
|
|
|
|
<h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide">
|
|
|
|
|
Listening now (<%= @count %>)
|
|
|
|
|
</h2>
|
2022-01-11 19:57:06 +00:00
|
|
|
|
<ul
|
|
|
|
|
id="listening-now"
|
|
|
|
|
role="list"
|
|
|
|
|
x-max="1"
|
|
|
|
|
class="grid grid-cols-1 gap-4 sm:gap-4 sm:grid-cols-2 xl:grid-cols-5 mt-3"
|
|
|
|
|
>
|
2022-01-31 19:27:06 +00:00
|
|
|
|
<%= for {id, _time} <- Enum.sort(@presence_ids, fn {_, t1}, {_, t2} -> t1 < t2 end) do %>
|
2022-08-03 13:40:11 +00:00
|
|
|
|
<.live_component id={id} module={BadgeComponent} presence={@presences[id]} />
|
2021-11-16 16:58:22 +00:00
|
|
|
|
<% end %>
|
|
|
|
|
</ul>
|
2022-01-31 19:27:06 +00:00
|
|
|
|
<%= if @total_count > @count do %>
|
|
|
|
|
<p>+ <%= @total_count - @count %> more</p>
|
|
|
|
|
<% end %>
|
2021-11-16 16:58:22 +00:00
|
|
|
|
</div>
|
|
|
|
|
"""
|
|
|
|
|
end
|
2022-04-06 16:20:23 +00:00
|
|
|
|
|
|
|
|
|
defp local_broadcast("proxy:" <> topic, payload) do
|
|
|
|
|
Phoenix.PubSub.local_broadcast(@pubsub, topic, payload)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp topic(%MediaLibrary.Profile{} = profile) do
|
|
|
|
|
"active_profiles:#{profile.user_id}"
|
|
|
|
|
end
|
2022-01-31 19:27:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defmodule LiveBeatsWeb.Presence.BadgeComponent do
|
|
|
|
|
use LiveBeatsWeb, :live_component
|
|
|
|
|
|
2022-08-03 13:40:11 +00:00
|
|
|
|
# https://fly.io/docs/reference/regions/
|
2022-02-03 12:11:56 +00:00
|
|
|
|
@region_names %{
|
|
|
|
|
"ams" => "Amsterdam, Netherlands",
|
|
|
|
|
"atl" => "Atlanta, Georgia (US)",
|
|
|
|
|
"cdg" => "Paris, France",
|
|
|
|
|
"dfw" => "Dallas, Texas (US)",
|
|
|
|
|
"ewr" => "Parsippany, NJ (US)",
|
|
|
|
|
"fra" => "Frankfurt, Germany",
|
|
|
|
|
"gru" => "Sao Paulo, Brazil",
|
|
|
|
|
"hkg" => "Hong Kong",
|
|
|
|
|
"iad" => "Ashburn, Virginia (US)",
|
|
|
|
|
"lax" => "Los Angeles, California (US)",
|
|
|
|
|
"lhr" => "London, United Kingdom",
|
|
|
|
|
"maa" => "Chennai (Madras), India",
|
|
|
|
|
"nrt" => "Tokyo, Japan",
|
|
|
|
|
"ord" => "Chicago, Illinois (US)",
|
|
|
|
|
"scl" => "Santiago, Chile",
|
|
|
|
|
"sea" => "Seattle, Washington (US)",
|
|
|
|
|
"sin" => "Singapore",
|
|
|
|
|
"sjc" => "Sunnyvale, California (US)",
|
|
|
|
|
"syd" => "Sydney, Australia",
|
|
|
|
|
"yyz" => "Toronto, Canada"
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-31 19:27:06 +00:00
|
|
|
|
def render(assigns) do
|
|
|
|
|
~H"""
|
|
|
|
|
<li id={"presence-#{@id}"} class="relative col-span-1 flex shadow-sm rounded-md overflow-hidden">
|
2022-08-03 13:40:11 +00:00
|
|
|
|
<.link
|
|
|
|
|
navigate={profile_path(@presence)}
|
|
|
|
|
class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate"
|
|
|
|
|
>
|
|
|
|
|
<img
|
|
|
|
|
class="w-12 h-12 flex-shrink-0 flex items-center justify-center rounded-l-md bg-purple-600"
|
|
|
|
|
src={@presence.avatar_url}
|
|
|
|
|
alt=""
|
|
|
|
|
/>
|
2022-01-31 19:27:06 +00:00
|
|
|
|
<div class="flex-1 flex items-center justify-between text-gray-900 text-sm font-medium hover:text-gray-600 pl-3">
|
|
|
|
|
<div class="flex-1 py-1 text-sm truncate">
|
|
|
|
|
<%= @presence.username %>
|
2024-06-04 21:27:51 +00:00
|
|
|
|
<p class="text-gray-400 text-xs">ping: <%= if @ping, do: "#{@ping}ms", else: "..." %></p>
|
|
|
|
|
<img
|
|
|
|
|
:if={@region}
|
|
|
|
|
class="inline w-7 h-7 absolute right-3 top-3"
|
|
|
|
|
src={"https://fly.io/phx/ui/images/#{@region}.svg"}
|
|
|
|
|
title={region_name(@region)}
|
|
|
|
|
/>
|
2022-01-31 19:27:06 +00:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</.link>
|
|
|
|
|
</li>
|
|
|
|
|
"""
|
|
|
|
|
end
|
2022-01-31 13:21:27 +00:00
|
|
|
|
|
|
|
|
|
def mount(socket) do
|
2022-01-31 19:27:06 +00:00
|
|
|
|
{:ok, socket, temporary_assigns: [presence: nil, ping: nil, region: nil]}
|
2022-01-31 13:21:27 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def update(%{action: {:ping, action}}, socket) do
|
|
|
|
|
%{user: user, ping: ping, region: region} = action
|
2022-02-01 14:40:58 +00:00
|
|
|
|
now = now_ms()
|
|
|
|
|
|
|
|
|
|
# debounce other tabs sending valid ping frequency
|
|
|
|
|
if now - socket.assigns.last_ping_at > 1000 do
|
|
|
|
|
{:ok, assign(socket, presence: user, ping: ping, region: region, last_ping_at: now)}
|
|
|
|
|
else
|
|
|
|
|
{:ok, socket}
|
|
|
|
|
end
|
2022-01-31 13:21:27 +00:00
|
|
|
|
end
|
|
|
|
|
|
2022-01-31 19:27:06 +00:00
|
|
|
|
def update(%{presence: nil}, socket), do: {:ok, socket}
|
|
|
|
|
|
2022-01-31 13:21:27 +00:00
|
|
|
|
def update(assigns, socket) do
|
|
|
|
|
{:ok,
|
|
|
|
|
socket
|
2022-01-31 19:27:06 +00:00
|
|
|
|
|> assign(id: assigns.id, presence: assigns.presence)
|
2022-01-31 13:21:27 +00:00
|
|
|
|
|> assign_new(:pings, fn -> %{} end)
|
2022-02-01 14:40:58 +00:00
|
|
|
|
|> assign_new(:regions, fn -> %{} end)
|
|
|
|
|
|> assign_new(:last_ping_at, fn -> now_ms() end)}
|
2022-01-31 13:21:27 +00:00
|
|
|
|
end
|
2022-02-01 14:40:58 +00:00
|
|
|
|
|
|
|
|
|
defp now_ms, do: System.system_time(:millisecond)
|
2022-02-03 12:11:56 +00:00
|
|
|
|
|
|
|
|
|
defp region_name(region), do: Map.get(@region_names, region)
|
2021-11-16 16:58:22 +00:00
|
|
|
|
end
|