Newer
Older
smart-home-server / webclient / src / router / index.js
import { createRouter, createWebHashHistory } from "vue-router";
import { routes } from "./routes";
import { useAuthStore } from "../stores/auth.js";
import { isDebugEnabled } from "../utils/logger";
import { isNativeApp, hasServerUrl } from "../api/server-config";

const LOG_PREFIX = "[vue:router]";
const LOG_ENABLED = isDebugEnabled();

export const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

router.beforeEach(async (to, from, next) => {
  // Native app bootstrap: if server URL is not configured, force setup page
  if (isNativeApp() && to.name !== "mobile-setup") {
    try {
      const configured = await hasServerUrl();
      if (!configured) {
        if (LOG_ENABLED) {
          console.debug(LOG_PREFIX, "Redirect: native app missing server URL → mobile-setup");
        }
        next({ name: "mobile-setup" });
        return;
      }
    } catch {
      // If storage is unreadable, still allow navigation to mobile-setup
      if (to.name !== "mobile-setup") {
        next({ name: "mobile-setup" });
        return;
      }
    }
  }

  const authStore = useAuthStore();

  if (LOG_ENABLED) {
    console.debug(LOG_PREFIX, `Navigate: ${from.fullPath || "init"} → ${to.fullPath}`);
  }

  // Ensure auth initialization completes before any routing decision.
  // init() is cached: repeated calls are no-ops after the first resolution.
  await authStore.init();

  // Allow public routes unconditionally
  if (to.meta?.public) {
    if (to.name === "login" && authStore.isAuthenticated) {
      if (LOG_ENABLED) {
        console.debug(LOG_PREFIX, `Redirect: authenticated user at login → areas-favorites`);
      }
      next({ name: "areas-favorites" });
      return;
    }
    next();
    return;
  }

  // Require authentication
  if (!authStore.isAuthenticated) {
    if (LOG_ENABLED) {
      console.debug(LOG_PREFIX, `Redirect: unauthenticated → login`);
    }
    next({ name: "login" });
    return;
  }

  // Check route-level permission
  const required = to.meta?.permission;
  if (required && !authStore.hasPermission(required)) {
    if (LOG_ENABLED) {
      console.warn(LOG_PREFIX, `Forbidden: ${to.fullPath} requires ${required}`);
    }
    next({ name: "areas-favorites" });
    return;
  }

  next();
});