Should session be empty on the second mount/3 call when using LiveView?

Hello,

I’m working on implementing LiveView (0.13+, phx 1.5.3) on a “news” page where SEO is important and we need to implement some liveview features.

The liveview is in router.ex:

scope "/", MyAppWeb do 
  pipe_through :browser
  ... 
  live "/news/:slug", ShowNewsLive
  ... 
end

The browser pipeline sets the session key “current_user_id” with the user, if present, and I would expect to rely on this in the liveview to load the user from the database.

In my liveview file (simplified):
ShowNewsLive.ex

mount(%{"slug" => slug}, %{"current_user_id" => user_id} = session, socket) do 
  # Load user and assign to socket. 
  socket = 
    socket 
    |> assign_user(session)
 
   {:ok, socket}
end

assign_user properly sets the current user to socket.assigns.current_user, and is available as @current_user in my template as I would expect based on the first time mount/3 is called on HTTP connect.

However, when the js fires and the websocket connects and mount/3 is called, the session field passed to mount/3 is an empty map. Is this expected? Does the websocket connection skip the router pipeline that the http request goes through?

Secondly, the socket passed to mount/3 the second time (on websocket connect) does not have the assigns that were assigned properly earlier on the HTTP-request triggered mount/3.

In my logs, I see something like this:

[info] Sent 200 in 5ms
[info] CONNECTED TO Phoenix.LiveView.Socket in 189µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" => "RB0GMy0wYhFeBwpPMHhFPB5bahlpKQUwtzekhTRFlQ3bzK6qPnYr8eDV", "_mounts" => "0", "vsn" => "2.0.0"}
The following is from mount(_params, session, socket) defined after mount/3 that pattern matches for 'current_user_id' in passed session
session:
%{}
socket:
%Phoenix.LiveView.Socket{
  assigns: %{flash: %{}, live_action: nil, live_module: MyAppWeb.ShowNewsLive},
  changed: %{},
  connected?: true,
  endpoint: MyAppWeb.Endpoint,
  fingerprints: {nil, %{}},
  host_uri: %URI{
    authority: nil,
    fragment: nil,
    host: "localhost",
    path: nil,
    port: 4000,
    query: nil,
    scheme: "http",
    userinfo: nil
  },
  id: "phx-FhTzcNm5Pubn5gdB",
  parent_pid: nil,
  private: %{
    assign_new: {%{}, []},
    connect_info: %{},
    connect_params: %{
      "_csrf_token" => "RB0GMy0wYhFeBwpPMHhFPB5bahlpKQUwtzekhTRFlQ3bzK6qPnYr8eDV",
      "_mounts" => 0
    }
  },
  redirected: nil,
  root_pid: #PID<0.819.0>,
  root_view: MyAppWeb.ShowNewsLive,
  router: MyAppWeb.Router,
  view: MyAppWeb.ShowNewsLive
}

[error] GenServer #PID<0.819.0> terminating
** (ArgumentError) assign @current_user not available in eex template.

I’ve blown away node_modules and mix.lock and re-installed those dependencies to no avail.

My package.json phoenix deps:

    "phoenix": "file:../deps/phoenix",
    "phoenix_html": "file:../deps/phoenix_html",
    "phoenix_live_view": "file:../deps/phoenix_live_view",

The bottom of my app.js:

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}});

liveSocket.connect()

What might be going wrong here?

Thanks

1 Like

Are you passing the @session_options to the socket path in your endpoint?

1 Like

@sfusato meant, do you have something like this:

defmodule MyAppWeb.Endpoint do
  use Phoenix.Endpoint

  socket "/live", Phoenix.LiveView.Socket,
    websocket: [connect_info: [session: @session_options]]

  ...
end
2 Likes

Oh, dang, I haven’t updated that part since updating to phx 1.5 on this app! That surely might be it - I will update and report back here. I suppose one of the strengths of LiveView is it still performs the initial render even if WS fails :slight_smile:

I currently have this style setup (likely outdated for phx 1.5 and liveview 0.13+):

  # Liveview Socket
  socket "/live", Phoenix.LiveView.Socket,
    websocket: [
      timeout: 45_000,
      check_origin: @valid_origins
      ],
    longpoll: [
      check_origin: @valid_origins
    ]

... 
  plug Plug.Session,
    store: :cookie,
    key: "_cogmint_key",
    signing_salt: "O5usGuLC"

where valid_origins is:

  @doc """
    The list of origins permitted to connect to the websocket transports
  """
  @valid_origins [
    "https://cogmint.com",
    "http://localhost:4000",
    "http://localhost:4001"
  ]

But I likely need to update to use the connect_info key as suggested:

socket "/live", Phoenix.LiveView.Socket,
    websocket: [connect_info: [session: @session_options]]

I’ll review the installation instructions (https://hexdocs.pm/phoenix_live_view/installation.html) and see if there’s anything else I might have missed.

1 Like

@PJextra and @sfusato and anyone else that comes across this - in my case it was because I was indeed not using the connect_info syntax and was instead using an older syntax.

The relevant parts of my endpoint.ex is now like so:

defmodule MyAwesomeAppWeb.Endpoint do 
  # Session options.
  @session_options [
    store: :cookie,
    key: "_myawesomeapp_key",
    signing_salt: "asdf123"
  ]

# ...

  # Liveview Socket
  socket "/live", Phoenix.LiveView.Socket,
    websocket: [
      connect_info: [session: @session_options],
      timeout: 45_000,
      check_origin: @valid_origins # list of URLs
      ],
    longpoll: [
      check_origin: @valid_origins
    ]

  # Phoenix Channels & Presence
  socket "/socket", MyAwesomeAppWeb.UserSocket,
    websocket: [
      connect_info: [session: @session_options],
      timeout: 45_000,
      check_origin: @valid_origins
      ],
    longpoll: [
      check_origin: @valid_origins
    ]

# ... 

  plug Plug.Session, @session_options 
# ... 

end

The session and socket now are passed as I’d expect :slight_smile:

Thank you both for being so helpful.

3 Likes

and if you would want to configure the session options at runtime, here’s how:

1 Like