Some checks failed
Build and Push / Build and Push Docker Image (push) Failing after 2s
- State (177 lines): mount assigns, defaults, slideover, connection status - Events (540 lines): 22 handle_event handlers, rate limiting, URL helpers - Subscriptions (122 lines): PubSub setup, spatial registration, cleanup - BoundsUpdater (172 lines): bounds pipeline, validation, debouncing - index.ex: 2,062 -> 1,202 lines (42% reduction) - Credo: initial_historical_completed, bounds_update_timer added to ignored_assigns (consumed by extracted modules, invisible to credo) - All 361 map_live tests pass across multiple random seeds - Handoff: marked maintainability #1 as done
122 lines
3.9 KiB
Elixir
122 lines
3.9 KiB
Elixir
defmodule AprsmeWeb.MapLive.Subscriptions do
|
|
@moduledoc """
|
|
Manages Phoenix.PubSub subscriptions and connection lifecycle for the map LiveView.
|
|
Extracted from Index to reduce module size.
|
|
"""
|
|
|
|
import Phoenix.Component, only: [assign: 3]
|
|
import Phoenix.LiveView, only: [connected?: 1, push_event: 3, put_flash: 3]
|
|
|
|
alias Phoenix.LiveView.Socket
|
|
|
|
@doc """
|
|
Set up spatial and PubSub subscriptions for a newly mounted socket.
|
|
Only creates subscriptions when the socket is connected (LiveSocket).
|
|
"""
|
|
@spec setup(Socket.t(), map()) :: Socket.t()
|
|
def setup(socket, initial_bounds) do
|
|
do_setup(socket, initial_bounds, connected?(socket))
|
|
end
|
|
|
|
@doc """
|
|
Clean up all subscriptions and timers when the LiveView terminates.
|
|
"""
|
|
@spec teardown(Socket.t()) :: :ok
|
|
def teardown(socket) do
|
|
_teardown_connection_monitor(socket)
|
|
_teardown_spatial(socket)
|
|
_teardown_timers(socket)
|
|
_teardown_batch_tasks(socket)
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Update the spatial viewport bounds for an existing subscription.
|
|
"""
|
|
@spec update_viewport(Socket.t(), map()) :: :ok | nil
|
|
def update_viewport(socket, map_bounds) do
|
|
if socket.assigns[:spatial_client_id] do
|
|
Aprsme.SpatialPubSub.update_viewport(socket.assigns.spatial_client_id, map_bounds)
|
|
end
|
|
end
|
|
|
|
# --- Private helpers ---
|
|
|
|
defp do_setup(socket, _initial_bounds, false), do: socket
|
|
|
|
defp do_setup(socket, initial_bounds, true) do
|
|
# Check if we should accept new connections
|
|
if Application.get_env(:aprsme, :cluster_enabled, false) and
|
|
not Aprsme.ConnectionMonitor.accepting_connections?() do
|
|
socket
|
|
|> put_flash(:info, "This server is currently at capacity. Please try again in a moment.")
|
|
|> push_event("redirect_to_least_loaded", %{})
|
|
|> assign(:connection_draining, true)
|
|
else
|
|
# Generate a unique client ID for this LiveView instance
|
|
client_id = "liveview_#{:erlang.phash2(self())}"
|
|
|
|
# Register with connection monitor
|
|
_ =
|
|
if Application.get_env(:aprsme, :cluster_enabled, false) do
|
|
_ = Aprsme.ConnectionMonitor.register_connection()
|
|
# Subscribe to drain events for this node
|
|
Phoenix.PubSub.subscribe(Aprsme.PubSub, "connection:drain:#{Node.self()}")
|
|
end
|
|
|
|
# Register the actual initial viewport.
|
|
{:ok, spatial_topic} = Aprsme.SpatialPubSub.register_viewport(client_id, initial_bounds)
|
|
|
|
# Subscribe to the spatial topic for this client
|
|
:ok = Phoenix.PubSub.subscribe(Aprsme.PubSub, spatial_topic)
|
|
|
|
# Still subscribe to bad packets (they don't have location)
|
|
:ok = Phoenix.PubSub.subscribe(Aprsme.PubSub, "bad_packets")
|
|
|
|
# Subscribe to deployment events
|
|
:ok = Phoenix.PubSub.subscribe(Aprsme.PubSub, "deployment_events")
|
|
|
|
_ = Process.send_after(self(), :cleanup_old_packets, 60_000)
|
|
|
|
socket
|
|
|> assign(:spatial_client_id, client_id)
|
|
|> assign(:connection_draining, false)
|
|
end
|
|
end
|
|
|
|
defp _teardown_connection_monitor(socket) do
|
|
_ =
|
|
if Application.get_env(:aprsme, :cluster_enabled, false) and
|
|
not socket.assigns[:connection_draining] do
|
|
Aprsme.ConnectionMonitor.unregister_connection()
|
|
end
|
|
end
|
|
|
|
defp _teardown_spatial(socket) do
|
|
_ =
|
|
if socket.assigns[:spatial_client_id] do
|
|
Aprsme.SpatialPubSub.unregister_client(socket.assigns.spatial_client_id)
|
|
end
|
|
end
|
|
|
|
defp _teardown_timers(socket) do
|
|
_ = if socket.assigns.buffer_timer, do: Process.cancel_timer(socket.assigns.buffer_timer)
|
|
|
|
_ =
|
|
if socket.assigns[:bounds_update_timer] do
|
|
Process.cancel_timer(socket.assigns.bounds_update_timer)
|
|
end
|
|
|
|
_ =
|
|
if socket.assigns[:hover_end_timer] do
|
|
Process.cancel_timer(socket.assigns.hover_end_timer)
|
|
end
|
|
end
|
|
|
|
defp _teardown_batch_tasks(socket) do
|
|
_ =
|
|
if socket.assigns[:pending_batch_tasks] do
|
|
Enum.each(socket.assigns.pending_batch_tasks, &Process.cancel_timer/1)
|
|
end
|
|
end
|
|
end
|