How can get current uri in "sticky side" LiveView?

I worked on enabling people to see each other’s cursor positions on my blog.

You can test it here: How Elixir became a mature language in less than 10 years · Json Media

(As it shows other session’s cursor position, you should open two session for seeing it)

This feature is implemented in CursorLive and that LiveView is rendered as sticky side LiveView.

<main>
  <div class="max-w-6xl mx-auto px-8 py-4">
    <%= live_render(@socket, JsonCorpWeb.CursorLive, id: "cursor-live", sticky: true) %> # side LiveView
    <.flash_group flash={@flash} />
    <%= @inner_content %> # main LiveView
  </div>
</main>

codes: https://github.com/nallwhy/json_corp/blob/cursor/apps/json_corp_web/lib/json_corp_web/live/cursor_live.ex

However, the cursor positions are being shared regardless of the page the current user is viewing.

To solve this issue, I want to know the URI the current user is viewing in CursorLive, but I have no idea how to do that.

As CursorLive is a kind of child LiveView, it can’t implement handle_params/3 callback.
But is’s not a child LiveView, it’s socket.parent_pid is nil.

Is there a good way to solve this issue?

Thank you.

One idea is to send from LiveView with push_event/3 and have a JS hook catch it, then send it back to LiveView with pushEventTo/4 , which may allow communication solely with id, not requiring pid.

Can’t I send event between LiveView directly with id?

Hmm, not sure how much I like these, but here are a few more ideas off the top of my head:

  • register CursorLive’s pid and use LiveView.send_update/3 with that pid to inform it that the uri is changing by hooking into sibling/cousin LiveViews’ handle_params
  • nest both CursorLive and the other sibling/cousin LiveViews that use patch LiveView navigation under a parent LiveView that can co-ordinate them
  • set up a separate channel to communciate changes in current page or add that information to Presence metadata – that may get tricky if the same user is using multiple tabs or devices
1 Like

We’ve implemented something similar using the Presence approach, which works but indeed has that limitation. Using the csrf token as the key helps but still seems to still be shared between tabs, so still looking for a better way…

Ahh, that’s probably because Phoenix by default uses get_csrf_token/0 from Plug.CSRFProtection which returns an existing token cached in Plug.Session and sessions are generally shared across tabs in the same browser.

Have you tried a common parent/ancestor LiveView? If you want to stick with the token route, you could manually generate and sign a new token via Ecto.UUID.generate() and Phoenix.Token.sign() on every HTTP request as described in Using Channels with LiveView for Better UX: Sharing the Session UUID.

Hmm, first thought was that sounds neat. Moments later, another thought popped up about whether that could potentially introduce some security vulnerabilites. What if a malicious user manually intercepts and pushs a targeted event that, if not properly validated on the server, gives them some level of insight into or access to another LiveView? ¯\_(ツ)_/¯

2 Likes

Connect params in ws connect clientside, then just make random uuid. Equals unique per tab

2 Likes

@olivermt great idea, thanks!

@codeanpeace a common ancestor would be a hassle because that would mean embedding all LiveViews in the app (except the one that’s already a child LV) under a single parent.

Thanks @codeanpeace and @olivermt for your suggestions. Using Phoenix.LiveView.get_connect_params(socket)["_csrf_token"] in the child LiveView gives me the same token as used to connect the websocket which seems to not be reused between tabs so that solves it :slight_smile:

Edit: the code can be seen here:

1 Like

I did it with Registry + connect params and it works really nice :slight_smile: .

codes: Merge branch 'cursor' · nallwhy/json_corp@be85984 · GitHub
demo: How Elixir became a mature language in less than 10 years · Json Media

Thank you all!

1 Like