From 7b04a894ed662e37e69b2e6fea75fd981fc7d7ae Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Mon, 28 Oct 2024 13:56:54 -0400 Subject: [PATCH] Allow to patch (navigate patching the content) to another site (#621) --- lib/beacon/private.ex | 39 +++++++++++++++++++ lib/beacon/pub_sub.ex | 4 ++ lib/beacon/web/live/page_live.ex | 52 ++++++++++++++++--------- test/beacon_web/live/page_live_test.exs | 19 +++++++++ test/support/router.ex | 1 + 5 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 lib/beacon/private.ex diff --git a/lib/beacon/private.ex b/lib/beacon/private.ex new file mode 100644 index 00000000..00cb8f3f --- /dev/null +++ b/lib/beacon/private.ex @@ -0,0 +1,39 @@ +defmodule Beacon.Private do + @moduledoc false + + # Concentrate calls to private APIs so it's easier to track breaking changes and document them, + # in case we need to make changes or understand why we had to call such APIs. + + # Should be avoided as much as possible. + + @doc """ + On page navigation, the request might actually hit a different site than the one defined by the current session. + + That's the case for a live patch to a full URL, for eg: + + User is in https://sitea.com/page1 and clicks on a link to https://siteb.com/page2 through a `<.link patch={...}>` component, + and since LV allows such navigation, we must do it as well but we need fetch the session defined for the requested URL and update the state accordingly, + otherwise the page will either not be found or worse could render the wrong content. + + Relative paths are not supported because there's no safe way to know if a `/relative` path belongs to site A or B, that's a LV contraint. + + We use the private function `Phoenix.LiveView.Route.live_link_info/3` in order to keep the same behavior as LV. + """ + def site_from_session(endpoint, router, url, view) do + case Phoenix.LiveView.Route.live_link_info(endpoint, router, url) do + {_, + %{ + view: ^view, + live_session: %{ + extra: %{ + session: %{"beacon_site" => site} + } + } + }} -> + site + + _ -> + nil + end + end +end diff --git a/lib/beacon/pub_sub.ex b/lib/beacon/pub_sub.ex index cfa5f60a..b999259f 100644 --- a/lib/beacon/pub_sub.ex +++ b/lib/beacon/pub_sub.ex @@ -56,6 +56,10 @@ defmodule Beacon.PubSub do Phoenix.PubSub.subscribe(@pubsub, topic_page(site, path)) end + def unsubscribe_to_page(site, path) do + Phoenix.PubSub.unsubscribe(@pubsub, topic_page(site, path)) + end + def page_loaded(%Content.Page{} = page) do page.site |> topic_page(page.path) diff --git a/lib/beacon/web/live/page_live.ex b/lib/beacon/web/live/page_live.ex index a6a8d47e..bca509d2 100644 --- a/lib/beacon/web/live/page_live.ex +++ b/lib/beacon/web/live/page_live.ex @@ -96,25 +96,39 @@ defmodule Beacon.Web.PageLive do end end - def handle_params(params, _url, socket) do - %{beacon: %{site: site}} = socket.assigns - %{"path" => path_info} = params - page = RouterServer.lookup_page!(site, path_info) - live_data = Beacon.Web.DataSource.live_data(site, path_info, Map.drop(params, ["path"])) - beacon_assigns = BeaconAssigns.new(site, page, live_data, path_info, params) - - socket = - socket - |> Component.assign(live_data) - # TODO: remove deprecated @beacon_live_data - |> Component.assign(:beacon_live_data, live_data) - # TODO: remove deprecated @beacon_path_params - |> Component.assign(:beacon_path_params, beacon_assigns.path_params) - # TODO: remove deprecated @beacon_query_params - |> Component.assign(:beacon_query_params, beacon_assigns.query_params) - |> Component.assign(:beacon, beacon_assigns) - - {:noreply, push_event(socket, "beacon:page-updated", %{meta_tags: Beacon.Web.DataSource.meta_tags(socket.assigns)})} + def handle_params(params, url, socket) do + case Beacon.Private.site_from_session(socket.endpoint, socket.router, url, __MODULE__) do + nil -> + raise Beacon.Web.NotFoundError, """ + no page was found for url #{url} + + Make sure a page was created for that url. + """ + + site -> + %{"path" => path_info} = params + page = RouterServer.lookup_page!(site, path_info) + live_data = Beacon.Web.DataSource.live_data(site, path_info, Map.drop(params, ["path"])) + beacon_assigns = BeaconAssigns.new(site, page, live_data, path_info, params) + + if socket.assigns.beacon.site != site do + Beacon.PubSub.unsubscribe_to_page(socket.assigns.beacon.site, path_info) + Beacon.PubSub.subscribe_to_page(site, path_info) + end + + socket = + socket + |> Component.assign(live_data) + # TODO: remove deprecated @beacon_live_data + |> Component.assign(:beacon_live_data, live_data) + # TODO: remove deprecated @beacon_path_params + |> Component.assign(:beacon_path_params, beacon_assigns.path_params) + # TODO: remove deprecated @beacon_query_params + |> Component.assign(:beacon_query_params, beacon_assigns.query_params) + |> Component.assign(:beacon, beacon_assigns) + + {:noreply, push_event(socket, "beacon:page-updated", %{meta_tags: Beacon.Web.DataSource.meta_tags(socket.assigns)})} + end end @doc false diff --git a/test/beacon_web/live/page_live_test.exs b/test/beacon_web/live/page_live_test.exs index 6c905dea..08d8d3f6 100644 --- a/test/beacon_web/live/page_live_test.exs +++ b/test/beacon_web/live/page_live_test.exs @@ -68,6 +68,7 @@ defmodule Beacon.Web.Live.PageLiveTest do @beacon.query_params=<%= @beacon.query_params["query"] %> <.page_link path="/about">go_to_about_page + <.link patch={"#{Beacon.BeaconTest.Endpoint.url()}/other"}>go_to_other_site <.form :let={f} for={%{}} as={:greeting} phx-submit="hello"> Name: <%= text_input f, :name %> @@ -131,6 +132,14 @@ defmodule Beacon.Web.Live.PageLiveTest do meta_tags: nil ) + beacon_published_page_fixture( + site: :not_booted, + path: "/", + template: """ +

<%= @beacon.site %>

+ """ + ) + [layout: layout] end @@ -157,6 +166,16 @@ defmodule Beacon.Web.Live.PageLiveTest do |> render_click() =~ "about_page" end + test "patch to another site resets site data", %{conn: conn} do + {:ok, view, _html} = live(conn, "/home/hello") + + view + |> element("a", "go_to_other_site") + |> render_click() + + assert has_element?(view, "h1", "not_booted") + end + describe "meta tags" do test "merge layout, page, and site", %{conn: conn} do {:ok, _view, html} = live(conn, "/home/hello") diff --git a/test/support/router.ex b/test/support/router.ex index 178aefe3..3f3cedf9 100644 --- a/test/support/router.ex +++ b/test/support/router.ex @@ -17,6 +17,7 @@ defmodule Beacon.BeaconTest.Router do scope "/" do pipe_through :browser + beacon_site "/other", site: :not_booted beacon_site "/", site: :my_site end end