Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LiveFlash (put_flash) doesn't work when push_navigateing across live_sessions #3686

Open
maxpohlmann opened this issue Feb 20, 2025 · 1 comment

Comments

@maxpohlmann
Copy link

Environment

  • Elixir version (elixir -v): 1.17.2
  • Phoenix version (mix deps): 1.7.19
  • Phoenix LiveView version (mix deps): 1.0.4
  • Operating system: MacOS 15
  • Browsers you attempted to reproduce this bug on (the more the merrier): Firefox
  • Does the problem persist after removing "assets/node_modules" and trying again? Yes/no: Yes

Actual behavior

When navigating across LiveViews in different live sessions with push_navigate, live flashes aren't being set and hence aren't being displayed on the redirected LiveView.

I take it that one isn't supposed to use push_navigate to navigate between LiveViews of different sessions anyways, since doing so yields the browser error unauthorized live_redirect. Falling back to page request as well as the server warning navigate event to [...] failed because you are redirecting across live_sessions. A full page reload will be performed instead. Furthermore, I can easily get the expected behaviour by replacing push_navigate with redirect. ...

Expected behavior

... However, since this fallback behaviour exists for this situation, I think live flashes should work properly as well, since otherwise it can be hard to figure out why they aren't being displayed. I.e. live flashes should be displayed even when using push_navigate across different live sessions.

Reproducing the bug

Here is a single-file application showing the bug:
A -> B: Using push_navigate within a live session => Flash is shown
B -> C: Using redirect across two live sessions => Flash is shown
C -> A: Using push_navigate across two live sessions => Flash is not shown

# main.exs
# Run with: elixir main.exs

Application.put_env(:sample, Example.Endpoint,
  http: [ip: {127, 0, 0, 1}, port: 5001],
  server: true,
  live_view: [signing_salt: "aaaaaaaa"],
  secret_key_base: String.duplicate("a", 64)
)

Mix.install([
  {:plug_cowboy, "~> 2.5"},
  {:jason, "~> 1.0"},
  {:phoenix, "~> 1.7"},
  {:phoenix_live_view,
   github: "phoenixframework/phoenix_live_view", branch: "main", override: true}
])

defmodule Example.CoreComponents do
  # default core components for live flashes
  use Phoenix.Component
  alias Phoenix.LiveView.JS

  attr :id, :string, doc: "the optional id of flash container"
  attr :flash, :map, default: %{}, doc: "the map of flash messages to display"
  attr :title, :string, default: nil
  attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup"
  attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container"

  slot :inner_block, doc: "the optional inner block that renders the flash message"

  def flash(assigns) do
    assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end)

    ~H"""
    <div
      :if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)}
      id={@id}
      role="alert"
      class={[
        "fixed top-2 right-2 mr-2 w-80 sm:w-96 z-50 rounded-lg p-3 ring-1",
        @kind == :info && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900",
        @kind == :error && "bg-rose-50 text-rose-900 shadow-md ring-rose-500 fill-rose-900"
      ]}
      {@rest}
    >
      <p :if={@title} class="flex items-center gap-1.5 text-sm font-semibold leading-6">
        {@title}
      </p>
      <p class="mt-2 text-sm leading-5">{msg}</p>
    </div>
    """
  end

  attr :flash, :map, required: true, doc: "the map of flash messages"
  attr :id, :string, default: "flash-group", doc: "the optional id of flash container"

  def flash_group(assigns) do
    ~H"""
    <div id={@id}>
      <.flash kind={:info} title="Success!" flash={@flash} />
      <.flash kind={:error} title="Error!" flash={@flash} />
    </div>
    """
  end
end

defmodule Example.Layouts do
  use Phoenix.LiveView

  def render("app.html", assigns) do
    ~H"""
    <script src="/assets/phoenix/phoenix.js">
    </script>
    <script src="/assets/phoenix_live_view/phoenix_live_view.js">
    </script>
    <script src="https://cdn.tailwindcss.com">
    </script>
    <script>
      let liveSocket = new window.LiveView.LiveSocket("/live", window.Phoenix.Socket)
      liveSocket.connect()
    </script>
    <style>
      * { font-size: 1.1em; }
    </style>
    <Example.CoreComponents.flash_group flash={@flash} />
    {@inner_content}
    """
  end
end

defmodule Example.ALive do
  use Phoenix.LiveView, layout: {Example.Layouts, :app}

  def render(assigns) do
    ~H"""
    <h1>A</h1>
    <button phx-click="go">To B</button>
    """
  end

  def handle_event("go", _unsigned_params, socket) do
    {:noreply, socket |> put_flash(:info, "Flash from A") |> push_navigate(to: "/b")}
  end
end

defmodule Example.BLive do
  use Phoenix.LiveView, layout: {Example.Layouts, :app}

  def render(assigns) do
    ~H"""
    <h1>B</h1>
    <button phx-click="go">To C</button>
    """
  end

  def handle_event("go", _unsigned_params, socket) do
    {:noreply, socket |> put_flash(:info, "Flash from B") |> redirect(to: "/c")}
  end
end

defmodule Example.CLive do
  use Phoenix.LiveView, layout: {Example.Layouts, :app}

  def render(assigns) do
    ~H"""
    <h1>C</h1>
    <button phx-click="go">To A</button>
    """
  end

  def handle_event("go", _unsigned_params, socket) do
    {:noreply, socket |> put_flash(:info, "Flash from C") |> push_navigate(to: "/a")}
  end
end

defmodule Example.Router do
  use Phoenix.Router, helpers: false

  import Plug.Conn
  import Phoenix.Controller
  import Phoenix.LiveView.Router

  pipeline :browser do
    plug(:accepts, ["html"])
    plug(:fetch_session)
    plug(:fetch_live_flash)
  end

  scope "/", Example do
    pipe_through(:browser)

    live_session :a_b_session do
      live("/a", ALive, :index)
      live("/b", BLive, :index)
    end

    live_session :c_session do
      live("/c", CLive, :index)
    end
  end
end

defmodule Example.Endpoint do
  use Phoenix.Endpoint, otp_app: :sample

  @session_options [
    store: :cookie,
    key: "_example_key",
    signing_salt: "De3iCH6m",
    same_site: "Lax"
  ]

  socket("/live", Phoenix.LiveView.Socket)

  plug(Plug.Static, from: {:phoenix, "priv/static"}, at: "/assets/phoenix")
  plug(Plug.Static, from: {:phoenix_live_view, "priv/static"}, at: "/assets/phoenix_live_view")
  plug(Plug.Session, @session_options)
  plug(Example.Router)
end

{:ok, _} = Supervisor.start_link([Example.Endpoint], strategy: :one_for_one)
Process.sleep(:infinity)
@Gazler
Copy link
Member

Gazler commented Feb 20, 2025

I don't believe this would be possible to "fix" unless we have something like #3672

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants