feat(portal): send account_slug in gateway init (#9653)

Adds the `account_slug` to the gateway's `init` message. When the
account slug is changed, the gateway's socket is disconnected using the
same mechanism as gateway deletion, which causes the gateway to
reconnect immediately and receive a new `init`.

Related: #9545
This commit is contained in:
Jamil
2025-06-24 11:35:06 -07:00
committed by GitHub
parent 27f482e061
commit 933d51e3d0
6 changed files with 56 additions and 1 deletions

View File

@@ -48,7 +48,10 @@ defmodule API.Gateway.Channel do
:ok = Enum.each(relays, &Domain.Relays.subscribe_to_relay_presence/1)
:ok = maybe_subscribe_for_relays_presence(relays, socket)
account = Domain.Accounts.fetch_account_by_id!(socket.assigns.gateway.account_id)
push(socket, "init", %{
account_slug: account.slug,
interface: Views.Interface.render(socket.assigns.gateway),
relays: Views.Relay.render_many(relays, relay_credentials_expire_at),
config: %{

View File

@@ -58,17 +58,23 @@ defmodule API.Gateway.ChannelTest do
assert is_number(online_at)
end
test "sends list of resources after join", %{
test "sends init message after join", %{
account: account,
gateway: gateway
} do
assert_push "init", %{
account_slug: account_slug,
interface: interface,
relays: relays,
config: %{
ipv4_masquerade_enabled: true,
ipv6_masquerade_enabled: true
}
}
assert account_slug == account.slug
assert relays == []
assert interface == %{
ipv4: gateway.ipv4,
ipv6: gateway.ipv6

View File

@@ -6,6 +6,17 @@ defmodule Domain.Events.Hooks.Accounts do
@impl true
def on_insert(_data), do: :ok
# Account slug changed - disconnect gateways for updated init
@impl true
def on_update(%{"slug" => old_slug}, %{"slug" => slug, "id" => account_id} = _data)
when old_slug != slug do
# Technically we could push a :slug_changed message to the Gateways here,
# but at the time of writing, disconnecting and reconnecting is safer to ensure
# all relevant state on the Gateway is updated correctly.
PubSub.Account.Gateways.disconnect(account_id)
end
# Account disabled - disconnect clients
@impl true
def on_update(

View File

@@ -10,6 +10,7 @@ defmodule Domain.Gateways.Presence do
with {:ok, _} <- __MODULE__.Group.track(gateway.group_id, gateway.id),
{:ok, _} <- __MODULE__.Account.track(gateway.account_id, gateway.id) do
:ok = PubSub.Gateway.subscribe(gateway.id)
:ok = PubSub.Account.Gateways.subscribe(gateway.account_id)
end
end

View File

@@ -84,6 +84,24 @@ defmodule Domain.PubSub do
end
end
defmodule Gateways do
def subscribe(account_id) do
account_id
|> topic()
|> Domain.PubSub.subscribe()
end
def disconnect(account_id) do
account_id
|> topic()
|> Domain.PubSub.broadcast("disconnect")
end
defp topic(account_id) do
Atom.to_string(__MODULE__) <> ":" <> account_id
end
end
defmodule Policies do
def subscribe(account_id) do
account_id

View File

@@ -13,10 +13,25 @@ defmodule Domain.Events.Hooks.AccountsTest do
end
describe "update/2" do
test "disconnects gateways if slug changes" do
account = Fixtures.Accounts.create_account()
gateway = Fixtures.Gateways.create_gateway(account: account)
:ok = Domain.Gateways.Presence.connect(gateway)
old_data = %{"slug" => "old"}
data = %{"slug" => "new", "id" => account.id}
assert :ok == on_update(old_data, data)
assert_receive "disconnect"
end
test "sends :config_changed if config changes" do
account = Fixtures.Accounts.create_account()
gateway = Fixtures.Gateways.create_gateway(account: account)
:ok = Domain.PubSub.Account.subscribe(account.id)
:ok = Domain.Gateways.Presence.connect(gateway)
old_data = %{
"id" => account.id,
@@ -33,6 +48,7 @@ defmodule Domain.Events.Hooks.AccountsTest do
assert :ok == on_update(old_data, data)
assert_receive :config_changed
refute_receive "disconnect"
end
test "does not send :config_changed if config does not change" do