Same path for LiveView and controller depending on condition? E.g. authenticated users vs non-logged visitors

Hi! I’m considering LiveView for authenticated users and a normal controller for non-authenticated ones.

This is because the page might have many non-authenticated visitors who read content but probably won’t need LiveView’s features.

Does this make sense? Or is there a way in LiveView’s render to render the initial page but do not establish the socket connection? (for non-logged visitors or a different condition)

If I decided to use a normal controller for non-logged visitors and a LiveView for logged users, how could I do it in the Router? Is there a way to add a condition in the scope? or should I use a plug?

Thank you!

1 Like

I just saw this after a new like. I solved this by using LiveView and not connecting the socket for non-authenticated visitors — or any other condition.

Something like this:

app.js:

const main = document.getElementById("main")
if (!main || (main && main.getAttribute("data-connect-socket") != "false")) {
  liveSocket.connect()
}

live.html.heex

<main class="container" id="main" data-connect-socket={"#{assigns[:connect_socket] == nil || @connect_socket}"}>

And then in LiveView’s mount set @connect_socket to true or false.

This is quite a nasty approach, as every liveview route gets passed through a simple http route, you could filter that there. An addition to this, filtering on frontend is never a secure option, should I connect with a custom client, then I have access to your system?

Just use secured/unsecured routes, liveview is just an implementation detail, and it should be used whether you need interactive pages or not.

Thanks for your message @D4no0.

In my case, I used the same LiveView to share common behaviour but made sure to control access in the backend. So a custom client could connect the socket but wouldn’t be able to see or do anything restricted. Is this ok then? Thanks.

1 Like

We do something similar except loading different JS files for connected users (live.js) vs guests (basic.js, which includes some polyfills for LV hooks and phx-click) so guests don’t have to download the LiveView JS:

common.js

import "../../deps/phoenix_html"
import Alpine from "alpinejs"
window.Alpine = Alpine
Alpine.start()

basic.js

import "./common"
import { ImageHooks } from "./image"

let Hooks = {}; 
Object.assign(Hooks, ImageHooks);

// run LiveView Hooks without LiveView
(function () {
    [...document.querySelectorAll("[phx-hook]")].map((hookEl) => {
        let hookName = hookEl.getAttribute("phx-hook");
        let hook = Hooks[hookName];

        if (hook) {
            let mountedFn = hook.mounted.bind({ ...hook, el: hookEl });
            mountedFn();
        }
    });
}) ();

function phxClick(event) {
    let name = this.getAttribute("phx-click")
    if (name.charAt(0) == "[") {
        name = JSON.parse(name)[0][1]["event"]
    }
    // go to our fallback controller which supports some LV events, and otherwise shows an explanation error message
    window.location = "/LiveHandler/" + name + "?" + new URLSearchParams(getPhxValues(this)).toString()
} 

// attempt graceful degradation for LiveView events without LiveView
(function () {
    [...document.querySelectorAll("[phx-click]")].map((el) => {
        el.addEventListener('click', phxClick);
    });
})();

function getPhxValues(el) {
    console.log(el)
    return el
    .getAttributeNames()
    .filter(name => name.startsWith("phx-value-"))
    .reduce((obj, name) => ({
        ...obj,
        [name.substring(10)]: el.getAttribute(name)
    }), {})
}

live.js

import "./common"

// Semi-boilerplate Phoenix+LiveView...
import { Socket } from "../../deps/phoenix"
import {LiveSocket} from "../../deps/phoenix_live_view"
import NProgress from "nprogress"      

let Hooks = {};  
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {
    timeout: 60000,
    params: { _csrf_token: csrfToken }, 
    dom: {
      onBeforeElUpdated(from, to) {
        if (from._x_dataStack) { window.Alpine.clone(from, to) }
      }
    }, 
    hooks: Hooks
}) 

// Show progress bar on live navigation and form submits
window.addEventListener("phx:page-loading-start", info => NProgress.start())
window.addEventListener("phx:page-loading-stop", info => NProgress.done())

// connect if there are any LiveViews on the page
liveSocket.connect()

// expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000)  // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
 
window.liveSocket = liveSocket

import { ImageHooks } from "./image"
Object.assign(Hooks, ImageHooks);
Object.assign(liveSocket.hooks, Hooks);
1 Like

Nice approach to degrade phx-click and avoid importing LiveView when we don’t want to connect the socket.

Thanks for sharing!

1 Like