Gumshoes
If we look at push_navigate
and redirect
they both attach a redirected command tuple
to the socket:
def push_navigate(%Socket{} = socket, opts) do
opts = push_opts!(opts, "push_navigate/2")
put_redirect(socket, {:live, :redirect, opts})
end
phoenix_live_view/phoenix_live_view.ex at ce0986071a1c469e580cb97808168a27109e659f · phoenixframework/phoenix_live_view · GitHub
def redirect(%Socket{} = socket, to: url) do
validate_local_url!(url, "redirect/2")
put_redirect(socket, {:redirect, %{to: url}})
end
phoenix_live_view/phoenix_live_view.ex at ce0986071a1c469e580cb97808168a27109e659f · phoenixframework/phoenix_live_view · GitHub
via put_redirect
defp put_redirect(%Socket{redirected: nil} = socket, command) do
%Socket{socket | redirected: command}
end
phoenix_live_view/phoenix_live_view.ex at ce0986071a1c469e580cb97808168a27109e659f · phoenixframework/phoenix_live_view · GitHub
I think this should then pass into handle_changed
in channel.ex
defp handle_changed(state, %Socket{} = new_socket, ref, pending_live_patch \\ nil) do
new_state = %{state | socket: new_socket}
case maybe_diff(new_state, false) do
{:diff, diff, new_state} ->
{:noreply,
new_state
|> push_live_patch(pending_live_patch)
|> push_diff(diff, ref)}
result ->
handle_redirect(new_state, result, Utils.changed_flash(new_socket), ref)
end
end
phoenix_live_view/channel.ex at ce0986071a1c469e580cb97808168a27109e659f · phoenixframework/phoenix_live_view · GitHub
where maybe_diff
does a small check if there is a redirected command:
defp maybe_diff(%{socket: socket} = state, force?) do
socket.redirected || render_diff(state, socket, force?)
end
phoenix_live_view/channel.ex at ce0986071a1c469e580cb97808168a27109e659f · phoenixframework/phoenix_live_view · GitHub
So we should kick down to handle_redirect
with the command. In both cases we copy the flash and then call push_redirect
or push_live_redirect
{:redirect, %{to: _to} = opts} ->
opts = copy_flash(new_state, flash, opts)
new_state
|> push_redirect(opts, ref)
|> stop_shutdown_redirect(:redirect, opts)
{:live, :redirect, %{to: _to} = opts} ->
opts = copy_flash(new_state, flash, opts)
new_state
|> push_live_redirect(opts, ref, pending_diff_ack)
|> stop_shutdown_redirect(:live_redirect, opts)
phoenix_live_view/channel.ex at ce0986071a1c469e580cb97808168a27109e659f · phoenixframework/phoenix_live_view · GitHub
Push redirect and push_live_redirect kick out to the JS client with a different key:
defp push_redirect(state, opts, nil = _ref) do
push(state, "redirect", opts)
end
defp push_redirect(state, opts, ref) do
reply(state, ref, :ok, %{redirect: opts})
end
defp push_live_redirect(state, opts, nil = _ref, {_diff, ack_ref}) do
reply(state, ack_ref, :ok, %{live_redirect: opts})
end
defp push_live_redirect(state, opts, nil = _ref, _pending_diff_ack) do
push(state, "live_redirect", opts)
end
phoenix_live_view/channel.ex at ce0986071a1c469e580cb97808168a27109e659f · phoenixframework/phoenix_live_view · GitHub
If we look around the JS, the redirect handlers are very similar, but…
Since we cross the session boundary, we get forcibly dropped as we’d expect (and want!) but this means the “fall back to page request” is a new one without the flash copied over, vs redirect
where we intentionally attach some flash data and reload the whole page, which may “cross a session boundary” but since it’s not navigation inside the session, it doesn’t hit the same check and the flash is retained