From c0af9b6d013c623aa9a3ddc437e1822ffa815c22 Mon Sep 17 00:00:00 2001 From: Derrick Reimer Date: Wed, 12 Jun 2024 10:56:25 -0500 Subject: [PATCH] Forward flashes and automatically pass via props (#13) --- CHANGELOG.md | 2 + lib/inertia/controller.ex | 6 ++ lib/inertia/plug.ex | 21 +++++ test/inertia_test.exs | 85 ++++++++++++++++--- .../my_app_web/controllers/page_controller.ex | 10 +++ test/support/my_app/lib/my_app_web/router.ex | 1 + 6 files changed, 114 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b2aad0..4d19572 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - Support for propagating errors via an `assign_errors` helper ([#10](https://github.com/svycal/inertia-phoenix/issues/10)) - Preservation of assigned errors across redirects ([#10](https://github.com/svycal/inertia-phoenix/issues/10)) - Setup external redirects properly for Inertia requests ([#11](https://github.com/svycal/inertia-phoenix/issues/11)) +- Forward flash contents across forced refreshes ([#13](https://github.com/svycal/inertia-phoenix/issues/13)) +- Automatically pass Phoenix flash data via the `flash` prop ## 0.4.0 diff --git a/lib/inertia/controller.ex b/lib/inertia/controller.ex index 5490894..396dc4b 100644 --- a/lib/inertia/controller.ex +++ b/lib/inertia/controller.ex @@ -137,6 +137,7 @@ defmodule Inertia.Controller do shared |> Map.merge(props) |> resolve_props(only: only, except: except) + |> maybe_put_flash(conn) conn |> put_private(:inertia_page, %{component: component, props: props}) @@ -220,6 +221,11 @@ defmodule Inertia.Controller do end end + # Skip putting flash in the props if there's already `:flash` key assigned. + # Otherwise, put the flash in the props. + defp maybe_put_flash(%{flash: _} = props, _conn), do: props + defp maybe_put_flash(props, conn), do: Map.put(props, :flash, conn.assigns.flash) + defp send_response(%{private: %{inertia_request: true}} = conn) do conn |> put_status(200) diff --git a/lib/inertia/plug.ex b/lib/inertia/plug.ex index a074d90..9b664ff 100644 --- a/lib/inertia/plug.ex +++ b/lib/inertia/plug.ex @@ -15,6 +15,7 @@ defmodule Inertia.Plug do |> assign(:inertia_head, []) |> put_private(:inertia_version, compute_version()) |> put_private(:inertia_error_bag, get_error_bag(conn)) + |> merge_forwarded_flash() |> fetch_inertia_errors() |> detect_inertia() end @@ -152,10 +153,30 @@ defmodule Inertia.Plug do conn |> put_resp_header("x-inertia-location", request_url(conn)) |> put_resp_content_type("text/html") + |> forward_flash() |> send_resp(:conflict, "") |> halt() end + defp forward_flash(%{assigns: %{flash: flash}} = conn) + when is_map(flash) and map_size(flash) > 0 do + put_session(conn, "inertia_flash", flash) + end + + defp forward_flash(conn), do: conn + + defp merge_forwarded_flash(conn) do + case get_session(conn, "inertia_flash") do + nil -> + conn + + flash -> + conn + |> delete_session("inertia_flash") + |> assign(:flash, Map.merge(conn.assigns.flash, flash)) + end + end + defp static_paths do Application.get_env(:inertia, :static_paths, []) end diff --git a/test/inertia_test.exs b/test/inertia_test.exs index 1d5c404..5474463 100644 --- a/test/inertia_test.exs +++ b/test/inertia_test.exs @@ -21,7 +21,7 @@ defmodule InertiaTest do assert %{ "component" => "Home", - "props" => %{"text" => "Hello World", "errors" => %{}}, + "props" => %{"text" => "Hello World", "errors" => %{}, "flash" => %{}}, "url" => "/", "version" => @current_version } = json_response(conn, 200) @@ -38,7 +38,12 @@ defmodule InertiaTest do assert %{ "component" => "Home", - "props" => %{"text" => "Hello World", "foo" => "bar", "errors" => %{}}, + "props" => %{ + "text" => "Hello World", + "foo" => "bar", + "errors" => %{}, + "flash" => %{} + }, "url" => "/shared", "version" => @current_version } = json_response(conn, 200) @@ -178,7 +183,8 @@ defmodule InertiaTest do "lazy_1" => "lazy_1", "lazy_3" => "lazy_3", "nested" => %{"lazy_2" => "lazy_2"}, - "errors" => %{} + "errors" => %{}, + "flash" => %{} }, "url" => "/lazy", "version" => @current_version @@ -196,7 +202,11 @@ defmodule InertiaTest do assert json_response(conn, 200) == %{ "component" => "Home", - "props" => %{"a" => %{"b" => %{"c" => "c", "e" => %{"f" => "f"}}}, "errors" => %{}}, + "props" => %{ + "a" => %{"b" => %{"c" => "c", "e" => %{"f" => "f"}}}, + "errors" => %{}, + "flash" => %{} + }, "url" => "/nested", "version" => @current_version } @@ -213,7 +223,7 @@ defmodule InertiaTest do assert json_response(conn, 200) == %{ "component" => "Home", - "props" => %{"a" => %{"b" => %{"c" => "c"}}, "errors" => %{}}, + "props" => %{"a" => %{"b" => %{"c" => "c"}}, "errors" => %{}, "flash" => %{}}, "url" => "/nested", "version" => @current_version } @@ -230,7 +240,7 @@ defmodule InertiaTest do assert json_response(conn, 200) == %{ "component" => "Home", - "props" => %{"a" => "a", "important" => "stuff", "errors" => %{}}, + "props" => %{"a" => "a", "important" => "stuff", "errors" => %{}, "flash" => %{}}, "url" => "/always", "version" => @current_version } @@ -249,7 +259,8 @@ defmodule InertiaTest do "component" => "Home", "props" => %{ "a" => %{"b" => %{"c" => "c", "e" => %{"f" => "f", "g" => "g"}, "d" => "d"}}, - "errors" => %{} + "errors" => %{}, + "flash" => %{} }, "url" => "/nested", "version" => @current_version @@ -265,7 +276,7 @@ defmodule InertiaTest do assert json_response(conn, 200) == %{ "component" => "Home", - "props" => %{"b" => "b", "errors" => %{}}, + "props" => %{"b" => "b", "errors" => %{}, "flash" => %{}}, "url" => "/tagged_lazy", "version" => @current_version } @@ -282,7 +293,7 @@ defmodule InertiaTest do assert json_response(conn, 200) == %{ "component" => "Home", - "props" => %{"a" => "a", "errors" => %{}}, + "props" => %{"a" => "a", "errors" => %{}, "flash" => %{}}, "url" => "/tagged_lazy", "version" => @current_version } @@ -298,7 +309,8 @@ defmodule InertiaTest do assert json_response(conn, 200) == %{ "component" => "Home", "props" => %{ - "errors" => %{"settings.theme" => "can't be blank", "name" => "can't be blank"} + "errors" => %{"settings.theme" => "can't be blank", "name" => "can't be blank"}, + "flash" => %{} }, "url" => "/changeset_errors", "version" => @current_version @@ -321,7 +333,8 @@ defmodule InertiaTest do "settings.theme" => "can't be blank", "name" => "can't be blank" } - } + }, + "flash" => %{} }, "url" => "/changeset_errors", "version" => @current_version @@ -365,6 +378,56 @@ defmodule InertiaTest do assert get_resp_header(conn, "x-inertia-location") == ["http://www.example.com/"] end + test "automatically includes flash in props", %{conn: conn} do + conn = + conn + |> patch(~p"/") + + assert html_response(conn, 302) + + conn = + conn + |> recycle() + |> get("/") + + assert html_response(conn, 200) =~ ~s("flash":{"info":"Patched") |> html_escape() + end + + test "does not clobber the flash prop if manually set", %{conn: conn} do + conn = + conn + |> get(~p"/overridden_flash") + + assert html_response(conn, 200) =~ ~s("flash":{"foo":"bar") |> html_escape() + end + + test "forwards flash across forced refreshes", %{conn: conn} do + conn = + conn + |> patch(~p"/") + + assert html_response(conn, 302) + + # The next redirect hop triggers a forced refresh... + conn = + conn + |> recycle() + |> put_req_header("x-inertia", "true") + |> put_req_header("x-inertia-version", "different") + |> get("/") + + assert html_response(conn, 409) + assert get_resp_header(conn, "x-inertia-location") == ["http://www.example.com/"] + + # After the hop, flash should be present in the props + conn = + conn + |> recycle() + |> get("/") + + assert html_response(conn, 200) =~ ~s("flash":{"info":"Patched") |> html_escape() + end + defp html_escape(content) do content |> Phoenix.HTML.html_escape() diff --git a/test/support/my_app/lib/my_app_web/controllers/page_controller.ex b/test/support/my_app/lib/my_app_web/controllers/page_controller.ex index d4e6f4e..ca2dc13 100644 --- a/test/support/my_app/lib/my_app_web/controllers/page_controller.ex +++ b/test/support/my_app/lib/my_app_web/controllers/page_controller.ex @@ -73,18 +73,28 @@ defmodule MyAppWeb.PageController do redirect(conn, external: "http://www.example.com/") end + def overridden_flash(conn, _params) do + conn + |> assign(:page_title, "Home") + |> assign_prop(:flash, %{foo: "bar"}) + |> render_inertia("Home") + end + def update(conn, _params) do conn + |> put_flash(:info, "Updated") |> redirect(to: "/") end def patch(conn, _params) do conn + |> put_flash(:info, "Patched") |> redirect(to: "/") end def delete(conn, _params) do conn + |> put_flash(:info, "Deleted") |> redirect(to: "/") end diff --git a/test/support/my_app/lib/my_app_web/router.ex b/test/support/my_app/lib/my_app_web/router.ex index 89e2063..9b1751a 100644 --- a/test/support/my_app/lib/my_app_web/router.ex +++ b/test/support/my_app/lib/my_app_web/router.ex @@ -24,6 +24,7 @@ defmodule MyAppWeb.Router do get "/redirect_on_error", PageController, :redirect_on_error get "/bad_error_map", PageController, :bad_error_map get "/external_redirect", PageController, :external_redirect + get "/overridden_flash", PageController, :overridden_flash put "/", PageController, :update patch "/", PageController, :patch delete "/", PageController, :delete