diff --git a/lib/phoenix/socket.ex b/lib/phoenix/socket.ex index 18a4c9eb99..990a800e03 100644 --- a/lib/phoenix/socket.ex +++ b/lib/phoenix/socket.ex @@ -556,7 +556,14 @@ defmodule Phoenix.Socket do end def __info__(%Broadcast{event: "disconnect"}, state) do - {:stop, {:shutdown, :disconnected}, state} + # Close code 1001 ("Going Away") signals the client that the connection + # is intentionally closed but a reconnect is expected — phoenix.js gates + # `reconnectTimer.scheduleTimeout()` on `closeCode !== 1000`, so without + # an explicit code here the default mapping of `{:shutdown, :disconnected}` + # (1000 in bandit ≥1.10.4) prevents the LiveSocket from reconnecting + # after a `phx.gen.auth`-style "log out everywhere" disconnect. + # See mtrudel/bandit#582. + {:stop, {:shutdown, :disconnected}, 1001, state} end def __info__(:socket_drain, state) do diff --git a/lib/phoenix/transports/long_poll_server.ex b/lib/phoenix/transports/long_poll_server.ex index e3a5f23d8d..6875efa4b6 100644 --- a/lib/phoenix/transports/long_poll_server.ex +++ b/lib/phoenix/transports/long_poll_server.ex @@ -117,6 +117,10 @@ defmodule Phoenix.Transports.LongPoll.Server do {:stop, reason, handler_state} -> state = %{state | handler: {handler, handler_state}} {:stop, reason, state} + + {:stop, reason, _code, handler_state} -> + state = %{state | handler: {handler, handler_state}} + {:stop, reason, state} end end diff --git a/test/phoenix/socket/socket_test.exs b/test/phoenix/socket/socket_test.exs index 03971cd2c0..01fd77eb75 100644 --- a/test/phoenix/socket/socket_test.exs +++ b/test/phoenix/socket/socket_test.exs @@ -137,4 +137,20 @@ defmodule Phoenix.SocketTest do assert DrainerSpecSocket.drainer_spec(drainer: false, endpoint: Endpoint) == :ignore end end + + describe "__info__/2" do + alias Phoenix.Socket.Broadcast + + test "disconnect broadcast emits close code 1001 so phoenix.js reconnects" do + # phoenix.js gates `reconnectTimer.scheduleTimeout()` on + # `closeCode !== 1000`. The default `{:shutdown, :disconnected}` + # mapping in bandit ≥1.10.4 is 1000, which suppresses reconnect; + # we pass 1001 ("Going Away") explicitly so the client retries. + state = make_ref() + msg = %Broadcast{topic: "t", event: "disconnect", payload: %{}} + + assert {:stop, {:shutdown, :disconnected}, 1001, ^state} = + Phoenix.Socket.__info__(msg, state) + end + end end