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. """ use Phoenix.Presence, otp_app: :live_beats, pubsub_server: LiveBeats.PubSub, presence: __MODULE__ import Phoenix.LiveView.Helpers import LiveBeatsWeb.LiveHelpers alias LiveBeats.{Accounts, MediaLibrary} alias LiveBeatsWeb.Presence.BadgeComponent def init(state) do LiveBeats.PresenceClient.init(state) end def handle_metas(topic, presences_diff, presences, state) do LiveBeats.PresenceClient.handle_metas(topic, presences_diff, presences, state) end def subscribe(%MediaLibrary.Profile{} = profile) do LiveBeats.PresenceClient.subscribe(profile) 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 def listening_now(assigns) do import Phoenix.LiveView count = Enum.count(assigns.presence_ids) assigns = assigns |> assign(:count, count) |> assign_new(:total_count, fn -> count end) ~H"""

Listening now (<%= @count %>)

<%= if @total_count > @count do %>

+ <%= @total_count - @count %> more

<% end %>
""" end end defmodule LiveBeatsWeb.Presence.BadgeComponent do use LiveBeatsWeb, :live_component # https://fly.io/docs/reference/regions/ @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" } def render(assigns) do ~H"""
  • <.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">
    <%= @presence.username %> <%= if @ping do %>

    ping: <%= @ping %>ms

    <%= if @region do %><% end %> <% end %>
  • """ end def mount(socket) do {:ok, socket, temporary_assigns: [presence: nil, ping: nil, region: nil]} end def update(%{action: {:ping, action}}, socket) do %{user: user, ping: ping, region: region} = action 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 end def update(%{presence: nil}, socket), do: {:ok, socket} def update(assigns, socket) do {:ok, socket |> assign(id: assigns.id, presence: assigns.presence) |> assign_new(:pings, fn -> %{} end) |> assign_new(:regions, fn -> %{} end) |> assign_new(:last_ping_at, fn -> now_ms() end)} end defp now_ms, do: System.system_time(:millisecond) defp region_name(region), do: Map.get(@region_names, region) end