@lenne.tech/nuxt-extensions 1.2.12 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.d.mts CHANGED
@@ -6,7 +6,7 @@ export { LtAuthModuleOptions, LtErrorTranslationModuleOptions, LtExtensionsModul
6
6
  export { LtErrorTranslationResponse, LtParsedError, UseLtErrorTranslationReturn } from '../dist/runtime/types/error.js';
7
7
 
8
8
  declare const name = "@lenne.tech/nuxt-extensions";
9
- declare const version = "1.0.0";
9
+ declare const version = "1.3.0";
10
10
  declare const configKey = "ltExtensions";
11
11
  declare const _default: _nuxt_schema.NuxtModule<LtExtensionsModuleOptions, LtExtensionsModuleOptions, false>;
12
12
 
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "configKey": "ltExtensions",
3
3
  "name": "@lenne.tech/nuxt-extensions",
4
- "version": "1.0.0",
4
+ "version": "1.3.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { defineNuxtModule, createResolver, addImports, addComponent, addPlugin, addRouteMiddleware } from '@nuxt/kit';
2
2
 
3
3
  const name = "@lenne.tech/nuxt-extensions";
4
- const version = "1.0.0";
4
+ const version = "1.3.0";
5
5
  const configKey = "ltExtensions";
6
6
  const defaultOptions = {
7
7
  auth: {
@@ -111,6 +111,8 @@ const module$1 = defineNuxtModule({
111
111
  { name: "getLtJwtToken", from: resolve("./runtime/lib/auth-state") },
112
112
  { name: "setLtJwtToken", from: resolve("./runtime/lib/auth-state") },
113
113
  { name: "getLtApiBase", from: resolve("./runtime/lib/auth-state") },
114
+ { name: "buildLtApiUrl", from: resolve("./runtime/lib/auth-state") },
115
+ { name: "isLocalDevApiProxy", from: resolve("./runtime/lib/auth-state") },
114
116
  { name: "attemptLtJwtSwitch", from: resolve("./runtime/lib/auth-state") },
115
117
  { name: "isLtAuthenticated", from: resolve("./runtime/lib/auth-state") },
116
118
  { name: "createLtAuthFetch", from: resolve("./runtime/lib/auth-state") },
@@ -5,6 +5,8 @@
5
5
  * and provides a method to perform that setup.
6
6
  *
7
7
  * Uses useState for SSR-compatible state management.
8
+ *
9
+ * URL handling is delegated to {@link buildLtApiUrl} (SSR / proxy / direct).
8
10
  */
9
11
  import type { ComputedRef } from "vue";
10
12
  export interface UseSystemSetupReturn {
@@ -1,19 +1,12 @@
1
- import { computed, useRuntimeConfig, useState } from "#imports";
2
- import { getLtApiBase } from "../../lib/auth-state.js";
1
+ import { computed, useState } from "#imports";
3
2
  import { ltSha256 } from "../../utils/crypto.js";
3
+ import { buildLtApiUrl } from "../../lib/auth-state.js";
4
4
  export function useSystemSetup() {
5
- const runtimeConfig = useRuntimeConfig();
6
5
  const needsSetupState = useState("lt-needs-setup", () => null);
7
6
  const needsSetup = computed(() => needsSetupState.value);
8
7
  async function checkSetupStatus() {
9
8
  try {
10
- let url;
11
- if (import.meta.server) {
12
- const apiUrl = runtimeConfig.apiUrl || "http://localhost:3000";
13
- url = `${apiUrl}/api/system-setup/status`;
14
- } else {
15
- url = "/api/system-setup/status";
16
- }
9
+ const url = buildLtApiUrl("/system-setup/status");
17
10
  const data = await $fetch(url);
18
11
  needsSetupState.value = data.needsSetup;
19
12
  return data.needsSetup;
@@ -23,9 +16,9 @@ export function useSystemSetup() {
23
16
  }
24
17
  }
25
18
  async function initSetup(params) {
26
- const apiBase = getLtApiBase();
19
+ const url = buildLtApiUrl("/system-setup/init");
27
20
  const hashedPassword = await ltSha256(params.password);
28
- await $fetch(`${apiBase}/system-setup/init`, {
21
+ await $fetch(url, {
29
22
  method: "POST",
30
23
  body: {
31
24
  email: params.email,
@@ -27,9 +27,10 @@ export declare function resetLtAuthClient(): void;
27
27
  * The client is created once and reused across all calls.
28
28
  * Configuration is read from RuntimeConfig on first call.
29
29
  *
30
- * IMPORTANT: In dev mode, the basePath is automatically prefixed with '/api'
31
- * to leverage Nuxt's server proxy. This is required for WebAuthn/Passkey
32
- * to work correctly (same-origin policy).
30
+ * IMPORTANT: When the API proxy is enabled (NUXT_PUBLIC_API_PROXY=true),
31
+ * the basePath is automatically prefixed with '/api' so the Vite dev proxy
32
+ * can forward requests to the backend. This is required for same-origin
33
+ * cookies and WebAuthn/Passkey to work correctly.
33
34
  */
34
35
  export declare function useLtAuthClient(): LtAuthClient;
35
36
  export declare const ltAuthClient: {
@@ -3,7 +3,7 @@ import {
3
3
  getOrCreateLtAuthClient,
4
4
  resetLtAuthClientSingleton
5
5
  } from "../lib/auth-client.js";
6
- import { isLtDevMode } from "../lib/auth-state.js";
6
+ import { isLocalDevApiProxy } from "../lib/auth-state.js";
7
7
  export function resetLtAuthClient() {
8
8
  resetLtAuthClientSingleton();
9
9
  }
@@ -11,13 +11,13 @@ export function useLtAuthClient() {
11
11
  try {
12
12
  const nuxtApp = useNuxtApp();
13
13
  const config = nuxtApp.$config?.public?.ltExtensions?.auth || {};
14
- const isDev = isLtDevMode();
14
+ const useProxy = isLocalDevApiProxy();
15
15
  let basePath = config.basePath || "/iam";
16
- if (isDev && basePath && !basePath.startsWith("/api")) {
16
+ if (useProxy && basePath && !basePath.startsWith("/api")) {
17
17
  basePath = `/api${basePath}`;
18
18
  }
19
19
  return getOrCreateLtAuthClient({
20
- baseURL: isDev ? "" : config.baseURL,
20
+ baseURL: useProxy ? "" : config.baseURL,
21
21
  basePath,
22
22
  twoFactorRedirectPath: config.twoFactorRedirectPath,
23
23
  enableAdmin: config.enableAdmin,
@@ -5,7 +5,9 @@
5
5
  * Works with or without @nuxtjs/i18n.
6
6
  *
7
7
  * Backend error format: "#LTNS_0100: Unauthorized - User is not logged in"
8
- * Translations loaded from: GET /api/i18n/errors/:locale
8
+ * Translations loaded from: GET /i18n/errors/:locale
9
+ *
10
+ * URL handling is delegated to {@link buildLtApiUrl} (SSR / proxy / direct).
9
11
  */
10
12
  import type { UseLtErrorTranslationReturn } from "../types/error.js";
11
13
  /**
@@ -1,4 +1,5 @@
1
1
  import { computed, ref, useState, useNuxtApp, useRuntimeConfig } from "#imports";
2
+ import { buildLtApiUrl } from "../lib/auth-state.js";
2
3
  const ERROR_CODE_REGEX = /^#([A-Z_]+_\d+):\s*(.+)$/;
3
4
  export function useLtErrorTranslation() {
4
5
  const nuxtApp = useNuxtApp();
@@ -29,8 +30,8 @@ export function useLtErrorTranslation() {
29
30
  }
30
31
  return config?.defaultLocale || "de";
31
32
  }
32
- function getApiBase() {
33
- return runtimeConfig.public?.ltExtensions?.auth?.baseURL || "";
33
+ function buildErrorUrl(locale) {
34
+ return buildLtApiUrl(`/i18n/errors/${locale}`);
34
35
  }
35
36
  async function loadTranslations(locale) {
36
37
  const targetLocale = locale || detectLocale();
@@ -42,10 +43,8 @@ export function useLtErrorTranslation() {
42
43
  }
43
44
  isLoading.value = true;
44
45
  try {
45
- const apiBase = getApiBase();
46
- const response = await $fetch(
47
- `${apiBase}/api/i18n/errors/${targetLocale}`
48
- );
46
+ const url = buildErrorUrl(targetLocale);
47
+ const response = await $fetch(url);
49
48
  if (response?.errors) {
50
49
  translations.value = {
51
50
  ...translations.value,
@@ -3,7 +3,7 @@ import { adminClient, twoFactorClient } from "better-auth/client/plugins";
3
3
  import { createAuthClient } from "better-auth/vue";
4
4
  import { navigateTo } from "#imports";
5
5
  import { ltSha256 } from "../utils/crypto.js";
6
- import { createLtAuthFetch, isLtDevMode } from "./auth-state.js";
6
+ import { createLtAuthFetch, isLocalDevApiProxy } from "./auth-state.js";
7
7
  let _ltAuthPluginRegistry = [];
8
8
  let _pluginsChangedAfterCreation = false;
9
9
  let _authClientSingleton = null;
@@ -38,9 +38,9 @@ export function getOrCreateLtAuthClient(config) {
38
38
  return _authClientSingleton;
39
39
  }
40
40
  export function createLtAuthClient(config = {}) {
41
- const isDev = isLtDevMode();
42
- const defaultBaseURL = isDev ? "" : import.meta.env?.VITE_API_URL || process.env.API_URL || "http://localhost:3000";
43
- const defaultBasePath = isDev ? "/api/iam" : "/iam";
41
+ const useProxy = isLocalDevApiProxy();
42
+ const defaultBaseURL = useProxy ? "" : import.meta.env?.VITE_API_URL || process.env.API_URL || "http://localhost:3000";
43
+ const defaultBasePath = useProxy ? "/api/iam" : "/iam";
44
44
  const {
45
45
  baseURL = defaultBaseURL,
46
46
  basePath = defaultBasePath,
@@ -13,14 +13,49 @@
13
13
  */
14
14
  import type { LtAuthMode } from "../types/index.js";
15
15
  /**
16
- * Detects if we're running in development mode at runtime.
16
+ * Determines if HTTP requests should use the Nuxt Vite dev proxy.
17
17
  *
18
- * Note: `import.meta.dev` is evaluated at build time and doesn't work
19
- * correctly for pre-built modules. This function uses runtime checks instead.
18
+ * When `true`, client-side requests are prefixed with `/api/` so the
19
+ * Vite dev server can forward them to the backend. The proxy strips
20
+ * the `/api/` prefix before forwarding, so the backend receives the
21
+ * original path (e.g., `/iam/sign-in`, `/i18n/errors/de`).
20
22
  *
21
- * @returns true if running in development mode
23
+ * **Why a proxy?**
24
+ * In local development the frontend (localhost:3001) and backend
25
+ * (localhost:3000) run on different ports. Browsers enforce same-origin
26
+ * policy for cookies, which breaks session-based authentication.
27
+ * The Vite proxy makes all requests appear same-origin.
28
+ *
29
+ * **How is this controlled?**
30
+ * Set `NUXT_PUBLIC_API_PROXY=true` in your `.env` file to enable the proxy.
31
+ * Nuxt auto-maps this to `runtimeConfig.public.apiProxy`.
32
+ * This should ONLY be enabled for local development (`nuxt dev`).
33
+ * On deployed stages (develop, test, preview, production) the proxy
34
+ * must NOT be enabled — requests go directly to the backend.
35
+ *
36
+ * **Fallback:** If `apiProxy` is not configured, the function checks
37
+ * whether the app runs under `nuxt dev` (buildId === 'dev'). If so,
38
+ * the proxy is activated with a prominent console warning so the
39
+ * implicit activation is immediately visible.
40
+ *
41
+ * **SSR never uses the proxy** — server-side requests call the backend
42
+ * directly via `runtimeConfig.apiUrl`.
43
+ *
44
+ * @returns true if the `/api/` proxy prefix should be used for client requests
45
+ *
46
+ * @see https://github.com/lenneTech/nuxt-base-starter — Vite proxy configuration
22
47
  */
23
- export declare function isLtDevMode(): boolean;
48
+ export declare function isLocalDevApiProxy(): boolean;
49
+ /**
50
+ * Build a full API URL for a given path, handling SSR, proxy, and direct modes.
51
+ *
52
+ * - **SSR**: `runtimeConfig.apiUrl` + path (direct backend call)
53
+ * - **Client + Proxy** (`NUXT_PUBLIC_API_PROXY=true`): `/api` + path (Vite proxy)
54
+ * - **Client direct**: `runtimeConfig.public.apiUrl` + path
55
+ *
56
+ * @param path - The API path (e.g., `/system-setup/status`, `/i18n/errors/de`)
57
+ */
58
+ export declare function buildLtApiUrl(path: string): string;
24
59
  /**
25
60
  * Get the current auth mode from cookie
26
61
  */
@@ -38,7 +73,13 @@ export declare function setLtJwtToken(token: string | null): void;
38
73
  */
39
74
  export declare function setLtAuthMode(mode: LtAuthMode): void;
40
75
  /**
41
- * Get the API base URL from runtime config
76
+ * Get the API base URL for auth (IAM) requests.
77
+ *
78
+ * Uses {@link isLocalDevApiProxy} to determine the URL strategy:
79
+ * - **Proxy mode** (`NUXT_PUBLIC_API_PROXY=true`): Returns `/api{basePath}`
80
+ * (e.g., `/api/iam`) so the Vite dev proxy forwards the request to the backend.
81
+ * - **Direct mode** (proxy disabled or not set): Returns `{baseURL}{basePath}`
82
+ * (e.g., `https://api.example.com/iam`) for direct backend calls.
42
83
  *
43
84
  * @param basePath - The auth API base path. If not provided, reads from runtime config (default: '/iam')
44
85
  */
@@ -1,18 +1,45 @@
1
- export function isLtDevMode() {
1
+ import { useRuntimeConfig } from "#imports";
2
+ let _proxyFallbackWarned = false;
3
+ export function isLocalDevApiProxy() {
2
4
  if (import.meta.server) {
3
- return process.env.NODE_ENV !== "production";
5
+ return false;
4
6
  }
5
- if (typeof window !== "undefined") {
6
- const buildId = window.__NUXT__?.config?.app?.buildId;
7
+ try {
8
+ const runtimeConfig = useRuntimeConfig();
9
+ const apiProxy = runtimeConfig.public?.apiProxy;
10
+ if (apiProxy !== void 0 && apiProxy !== null && apiProxy !== "") {
11
+ return apiProxy === true || apiProxy === "true";
12
+ }
13
+ const buildId = runtimeConfig?.app && runtimeConfig.app?.buildId;
7
14
  if (buildId === "dev") {
15
+ if (!_proxyFallbackWarned) {
16
+ _proxyFallbackWarned = true;
17
+ console.warn(
18
+ "\n\u26A0\uFE0F [LtExtensions] API proxy activated implicitly because nuxt dev was detected.\n To make this explicit, add NUXT_PUBLIC_API_PROXY=true to your .env file.\n If you do NOT want the proxy, set NUXT_PUBLIC_API_PROXY=false.\n"
19
+ );
20
+ }
8
21
  return true;
9
22
  }
10
- const hostname = window.location?.hostname;
11
- if (hostname === "localhost" || hostname === "127.0.0.1") {
12
- return true;
23
+ return false;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+ export function buildLtApiUrl(path) {
29
+ try {
30
+ const runtimeConfig = useRuntimeConfig();
31
+ if (import.meta.server) {
32
+ const apiUrl2 = runtimeConfig.apiUrl || "http://localhost:3000";
33
+ return `${apiUrl2}${path}`;
13
34
  }
35
+ if (isLocalDevApiProxy()) {
36
+ return `/api${path}`;
37
+ }
38
+ const apiUrl = runtimeConfig.public.apiUrl || runtimeConfig.public?.ltExtensions?.auth?.baseURL || "http://localhost:3000";
39
+ return `${apiUrl}${path}`;
40
+ } catch {
41
+ return `http://localhost:3000${path}`;
14
42
  }
15
- return false;
16
43
  }
17
44
  export function getLtAuthMode() {
18
45
  if (import.meta.server) return "cookie";
@@ -69,18 +96,14 @@ export function setLtAuthMode(mode) {
69
96
  }
70
97
  }
71
98
  export function getLtApiBase(basePath) {
72
- if (!basePath && typeof window !== "undefined") {
73
- basePath = window.__NUXT__?.config?.public?.ltExtensions?.auth?.basePath;
74
- }
75
- basePath = basePath || "/iam";
76
- const isDev = isLtDevMode();
77
- if (isDev) {
78
- return `/api${basePath}`;
79
- }
80
- if (typeof window !== "undefined" && window.__NUXT__?.config?.public?.ltExtensions?.auth?.baseURL) {
81
- return `${window.__NUXT__.config.public.ltExtensions.auth.baseURL}${basePath}`;
99
+ if (!basePath) {
100
+ try {
101
+ const runtimeConfig = useRuntimeConfig();
102
+ basePath = runtimeConfig.public?.ltExtensions?.auth?.basePath;
103
+ } catch {
104
+ }
82
105
  }
83
- return `http://localhost:3000${basePath}`;
106
+ return buildLtApiUrl(basePath || "/iam");
84
107
  }
85
108
  export async function attemptLtJwtSwitch(basePath = "/iam") {
86
109
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nuxt-extensions",
3
- "version": "1.2.12",
3
+ "version": "1.3.0",
4
4
  "description": "Reusable Nuxt 4 composables, components, and Better-Auth integration for lenne.tech projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -47,7 +47,7 @@
47
47
  ],
48
48
  "scripts": {
49
49
  "prepare": "git config core.hooksPath .githooks",
50
- "prepack": "nuxt-module-build build",
50
+ "prepack": "node scripts/sync-module-version.mjs && nuxt-module-build build",
51
51
  "c": "pnpm run check",
52
52
  "check": "pnpm audit && pnpm run format:check && pnpm run lint && pnpm test:types && pnpm test && pnpm run build",
53
53
  "check:fix": "pnpm install && pnpm audit --fix && pnpm run format && pnpm run lint:fix && pnpm test:types && pnpm test && pnpm run build",