@lenne.tech/nuxt-extensions 1.0.0 → 1.1.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.
Files changed (42) hide show
  1. package/LICENSE +1 -1
  2. package/dist/module.d.mts +4 -1
  3. package/dist/module.mjs +28 -9
  4. package/dist/runtime/components/transition/LtTransitionFade.vue +3 -1
  5. package/dist/runtime/components/transition/LtTransitionFadeScale.vue +3 -1
  6. package/dist/runtime/composables/auth/use-lt-auth.d.ts +1 -1
  7. package/dist/runtime/composables/auth/use-lt-auth.js +49 -13
  8. package/dist/runtime/composables/index.d.ts +6 -5
  9. package/dist/runtime/composables/index.js +1 -0
  10. package/dist/runtime/composables/use-lt-auth-client.d.ts +9 -1
  11. package/dist/runtime/composables/use-lt-auth-client.js +26 -2
  12. package/dist/runtime/composables/use-lt-error-translation.d.ts +33 -0
  13. package/dist/runtime/composables/use-lt-error-translation.js +133 -0
  14. package/dist/runtime/composables/use-lt-file.d.ts +1 -1
  15. package/dist/runtime/composables/use-lt-share.js +4 -1
  16. package/dist/runtime/composables/use-lt-tus-upload.d.ts +1 -1
  17. package/dist/runtime/composables/use-lt-tus-upload.js +7 -1
  18. package/dist/runtime/lib/auth-client.d.ts +1 -1
  19. package/dist/runtime/lib/auth-client.js +18 -6
  20. package/dist/runtime/lib/auth-state.d.ts +10 -1
  21. package/dist/runtime/lib/auth-state.js +17 -1
  22. package/dist/runtime/lib/index.d.ts +2 -2
  23. package/dist/runtime/locales/de.json +4 -0
  24. package/dist/runtime/locales/en.json +4 -0
  25. package/dist/runtime/plugins/auth-interceptor.client.d.ts +1 -1
  26. package/dist/runtime/plugins/auth-interceptor.client.js +12 -4
  27. package/dist/runtime/plugins/error-translation.client.d.ts +8 -0
  28. package/dist/runtime/plugins/error-translation.client.js +17 -0
  29. package/dist/runtime/server/tsconfig.json +1 -1
  30. package/dist/runtime/testing/index.d.ts +21 -0
  31. package/dist/runtime/testing/index.js +17 -0
  32. package/dist/runtime/testing/playwright-helpers.d.ts +296 -0
  33. package/dist/runtime/testing/playwright-helpers.js +127 -0
  34. package/dist/runtime/types/auth.d.ts +2 -2
  35. package/dist/runtime/types/error.d.ts +48 -0
  36. package/dist/runtime/types/error.js +0 -0
  37. package/dist/runtime/types/index.d.ts +4 -3
  38. package/dist/runtime/types/module.d.ts +17 -2
  39. package/dist/runtime/types/upload.d.ts +2 -2
  40. package/dist/runtime/utils/index.d.ts +2 -2
  41. package/dist/types.d.mts +7 -1
  42. package/package.json +18 -2
@@ -11,6 +11,10 @@
11
11
  "registerOptionsError": "Konnte Registrierungsoptionen nicht laden",
12
12
  "sessionExpired": "Sitzung abgelaufen"
13
13
  },
14
+ "error": {
15
+ "title": "Fehler",
16
+ "loadFailed": "Fehlerübersetzungen konnten nicht geladen werden"
17
+ },
14
18
  "share": {
15
19
  "copied": "Link kopiert",
16
20
  "copiedDescription": "Der Link wurde in die Zwischenablage kopiert."
@@ -11,6 +11,10 @@
11
11
  "registerOptionsError": "Could not load registration options",
12
12
  "sessionExpired": "Session expired"
13
13
  },
14
+ "error": {
15
+ "title": "Error",
16
+ "loadFailed": "Failed to load error translations"
17
+ },
14
18
  "share": {
15
19
  "copied": "Link copied",
16
20
  "copiedDescription": "The link has been copied to clipboard."
@@ -9,6 +9,6 @@
9
9
  * Note: This is a client-only plugin (.client.ts) since auth state
10
10
  * management only makes sense in the browser context.
11
11
  */
12
- import type { NuxtApp } from '#app';
12
+ import type { NuxtApp } from "#app";
13
13
  declare const _default: (nuxtApp: NuxtApp) => void;
14
14
  export default _default;
@@ -6,11 +6,18 @@ export default (nuxtApp) => {
6
6
  const loginPath = runtimeConfig.loginPath || "/auth/login";
7
7
  const configuredPublicPaths = runtimeConfig.interceptor?.publicPaths || [];
8
8
  let isHandling401 = false;
9
- const defaultPublicPaths = ["/auth/login", "/auth/register", "/auth/forgot-password", "/auth/reset-password", "/auth/2fa"];
9
+ const defaultPublicPaths = [
10
+ "/auth/login",
11
+ "/auth/register",
12
+ "/auth/forgot-password",
13
+ "/auth/reset-password",
14
+ "/auth/2fa"
15
+ ];
10
16
  const publicAuthPaths = [.../* @__PURE__ */ new Set([...defaultPublicPaths, ...configuredPublicPaths])];
11
17
  function isPublicAuthRoute() {
12
- const route = nuxtApp.$router?.currentRoute?.value;
13
- if (!route) return false;
18
+ const router = nuxtApp.$router;
19
+ const route = router?.currentRoute?.value;
20
+ if (!route?.path) return false;
14
21
  return publicAuthPaths.some((path) => route.path.startsWith(path));
15
22
  }
16
23
  function isAuthEndpoint(url) {
@@ -50,7 +57,8 @@ export default (nuxtApp) => {
50
57
  if (isAuthenticated.value) {
51
58
  console.debug("[LtAuth Interceptor] Session expired, logging out...");
52
59
  clearUser();
53
- const currentPath = nuxtApp.$router?.currentRoute?.value?.fullPath;
60
+ const router = nuxtApp.$router;
61
+ const currentPath = router?.currentRoute?.value?.fullPath;
54
62
  const redirectQuery = currentPath && currentPath !== loginPath ? `?redirect=${encodeURIComponent(currentPath)}` : "";
55
63
  window.location.href = loginPath + redirectQuery;
56
64
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Error Translation Plugin
3
+ *
4
+ * Automatically loads error translations on app start.
5
+ * Provides global helper methods: $ltTranslateError, $ltShowErrorToast
6
+ */
7
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
8
+ export default _default;
@@ -0,0 +1,17 @@
1
+ import { defineNuxtPlugin, useRuntimeConfig } from "#imports";
2
+ import { useLtErrorTranslation } from "../composables/use-lt-error-translation.js";
3
+ export default defineNuxtPlugin(async (nuxtApp) => {
4
+ const runtimeConfig = useRuntimeConfig();
5
+ const config = runtimeConfig.public?.ltExtensions?.errorTranslation;
6
+ if (config?.enabled === false) {
7
+ return;
8
+ }
9
+ const { loadTranslations, translateError, showErrorToast } = useLtErrorTranslation();
10
+ try {
11
+ await loadTranslations();
12
+ } catch (error) {
13
+ console.warn("[LtErrorTranslation] Initial load failed:", error);
14
+ }
15
+ nuxtApp.provide("ltTranslateError", translateError);
16
+ nuxtApp.provide("ltShowErrorToast", showErrorToast);
17
+ });
@@ -1,3 +1,3 @@
1
1
  {
2
- "extends": "../../../.nuxt/tsconfig.server.json",
2
+ "extends": "../../../.nuxt/tsconfig.server.json"
3
3
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Testing Utilities for @lenne.tech/nuxt-extensions
3
+ *
4
+ * This module exports Playwright test helpers for E2E testing
5
+ * of Nuxt applications using @lenne.tech/nuxt-extensions.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // In your test file
10
+ * import {
11
+ * waitForHydration,
12
+ * gotoAndWaitForHydration,
13
+ * fillInput,
14
+ * generateTestUser,
15
+ * generateTOTP,
16
+ * } from '@lenne.tech/nuxt-extensions/testing';
17
+ * ```
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+ export { waitForHydration, gotoAndWaitForHydration, waitForURLAndHydration, DEFAULT_HYDRATION_TIMEOUT, fillInput, fillInputs, generateTestUser, generateRandomString, generateTOTP, extractTOTPSecret, parseTOTPUrl, waitForElement, waitForNetworkIdle, hasText, getAllText, type TestUser, } from "./playwright-helpers.js";
@@ -0,0 +1,17 @@
1
+ export {
2
+ waitForHydration,
3
+ gotoAndWaitForHydration,
4
+ waitForURLAndHydration,
5
+ DEFAULT_HYDRATION_TIMEOUT,
6
+ fillInput,
7
+ fillInputs,
8
+ generateTestUser,
9
+ generateRandomString,
10
+ generateTOTP,
11
+ extractTOTPSecret,
12
+ parseTOTPUrl,
13
+ waitForElement,
14
+ waitForNetworkIdle,
15
+ hasText,
16
+ getAllText
17
+ } from "./playwright-helpers.js";
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Playwright Test Helpers for Nuxt Applications
3
+ *
4
+ * This module provides reusable test utilities for E2E testing
5
+ * with Playwright in Nuxt projects using @lenne.tech/nuxt-extensions.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import {
10
+ * waitForHydration,
11
+ * gotoAndWaitForHydration,
12
+ * fillInput,
13
+ * generateTestUser,
14
+ * generateTOTP,
15
+ * } from '@lenne.tech/nuxt-extensions/testing';
16
+ *
17
+ * test('my test', async ({ page }) => {
18
+ * await gotoAndWaitForHydration(page, '/auth/login');
19
+ * await fillInput(page, 'input[name="email"]', 'test@example.com');
20
+ * });
21
+ * ```
22
+ */
23
+ import type { Page } from "@playwright/test";
24
+ /**
25
+ * Default timeout for hydration waiting (in milliseconds)
26
+ */
27
+ export declare const DEFAULT_HYDRATION_TIMEOUT = 15000;
28
+ /**
29
+ * Wait for Nuxt hydration to complete
30
+ *
31
+ * This is essential for Nuxt SSR applications where Vue components
32
+ * need to be hydrated before they can respond to interactions.
33
+ *
34
+ * @param page - Playwright page object
35
+ * @param options - Configuration options
36
+ * @param options.timeout - Maximum time to wait (default: 15000ms)
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * await waitForHydration(page);
41
+ * await page.click('button'); // Now safe to interact
42
+ * ```
43
+ */
44
+ export declare function waitForHydration(page: Page, options?: {
45
+ timeout?: number;
46
+ }): Promise<void>;
47
+ /**
48
+ * Navigate to URL and wait for Nuxt hydration to complete
49
+ *
50
+ * Combines page.goto() with hydration waiting. Use this instead of
51
+ * page.goto() when you need to interact with Vue components immediately.
52
+ *
53
+ * @param page - Playwright page object
54
+ * @param url - URL to navigate to (relative or absolute)
55
+ * @param options - Configuration options
56
+ * @param options.timeout - Maximum time to wait for hydration (default: 15000ms)
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * await gotoAndWaitForHydration(page, '/auth/login');
61
+ * // Page is now hydrated and ready for interaction
62
+ * ```
63
+ */
64
+ export declare function gotoAndWaitForHydration(page: Page, url: string, options?: {
65
+ timeout?: number;
66
+ }): Promise<void>;
67
+ /**
68
+ * Wait for URL navigation and then Nuxt hydration
69
+ *
70
+ * Use after form submissions or actions that trigger navigation.
71
+ * Ensures both the navigation completes AND the new page is hydrated.
72
+ *
73
+ * @param page - Playwright page object
74
+ * @param url - URL pattern to wait for (string or RegExp)
75
+ * @param options - Configuration options
76
+ * @param options.timeout - Maximum time to wait for URL (default: 30000ms)
77
+ * @param options.hydrationTimeout - Maximum time to wait for hydration (default: 15000ms)
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * await page.click('button[type="submit"]');
82
+ * await waitForURLAndHydration(page, /\/dashboard/);
83
+ * ```
84
+ */
85
+ export declare function waitForURLAndHydration(page: Page, url: string | RegExp, options?: {
86
+ timeout?: number;
87
+ hydrationTimeout?: number;
88
+ }): Promise<void>;
89
+ /**
90
+ * Fill an input field with proper Vue 3 reactivity support
91
+ *
92
+ * Uses keyboard typing instead of Playwright's fill() to properly
93
+ * trigger Vue's v-model updates. This is necessary because fill()
94
+ * sets the value directly without firing input events in some cases.
95
+ *
96
+ * @param page - Playwright page object
97
+ * @param selector - CSS selector for the input element
98
+ * @param value - Value to type into the input
99
+ * @param options - Configuration options
100
+ * @param options.delay - Delay between keystrokes in ms (default: 5)
101
+ * @param options.clear - Whether to clear existing content (default: true)
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * await fillInput(page, 'input[name="email"]', 'test@example.com');
106
+ * await fillInput(page, '#password', 'secret', { delay: 10 });
107
+ * ```
108
+ */
109
+ export declare function fillInput(page: Page, selector: string, value: string, options?: {
110
+ delay?: number;
111
+ clear?: boolean;
112
+ }): Promise<void>;
113
+ /**
114
+ * Fill multiple input fields at once
115
+ *
116
+ * Convenience function for filling multiple form fields.
117
+ *
118
+ * @param page - Playwright page object
119
+ * @param fields - Object mapping selectors to values
120
+ * @param options - Configuration options passed to fillInput
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * await fillInputs(page, {
125
+ * 'input[name="email"]': 'test@example.com',
126
+ * 'input[name="password"]': 'secret123',
127
+ * });
128
+ * ```
129
+ */
130
+ export declare function fillInputs(page: Page, fields: Record<string, string>, options?: {
131
+ delay?: number;
132
+ clear?: boolean;
133
+ }): Promise<void>;
134
+ /**
135
+ * Test user credentials interface
136
+ */
137
+ export interface TestUser {
138
+ email: string;
139
+ password: string;
140
+ name: string;
141
+ }
142
+ /**
143
+ * Generate unique test user credentials
144
+ *
145
+ * Creates unique email, password, and name for test isolation.
146
+ * Each call generates different credentials to avoid test conflicts.
147
+ *
148
+ * @param prefix - Prefix for the email address (default: 'e2e')
149
+ * @returns Object with email, password, and name
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * const user = generateTestUser('auth-test');
154
+ * // { email: 'auth-test-abc123@test.com', password: 'TestPassabc123!', name: 'E2E Test User auth-test' }
155
+ * ```
156
+ */
157
+ export declare function generateTestUser(prefix?: string): TestUser;
158
+ /**
159
+ * Generate a random string for test data
160
+ *
161
+ * @param length - Length of the string (default: 8)
162
+ * @param charset - Characters to use (default: alphanumeric)
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * const id = generateRandomString(12);
167
+ * const code = generateRandomString(6, '0123456789');
168
+ * ```
169
+ */
170
+ export declare function generateRandomString(length?: number, charset?: string): string;
171
+ /**
172
+ * Generate a TOTP code from a secret (RFC 6238)
173
+ *
174
+ * Implements the Time-based One-Time Password algorithm for testing
175
+ * 2FA functionality. Compatible with Google Authenticator and similar apps.
176
+ *
177
+ * @param secret - Base32-encoded TOTP secret
178
+ * @param options - Configuration options
179
+ * @param options.digits - Number of digits in the code (default: 6)
180
+ * @param options.period - Time period in seconds (default: 30)
181
+ * @param options.algorithm - Hash algorithm (default: 'sha1')
182
+ * @returns 6-digit TOTP code as string
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * const secret = 'JBSWY3DPEHPK3PXP';
187
+ * const code = generateTOTP(secret);
188
+ * await page.fill('input[name="code"]', code);
189
+ * ```
190
+ */
191
+ export declare function generateTOTP(secret: string, options?: {
192
+ digits?: number;
193
+ period?: number;
194
+ algorithm?: "sha1" | "sha256" | "sha512";
195
+ }): string;
196
+ /**
197
+ * Extract TOTP secret from otpauth:// URL
198
+ *
199
+ * Parses the secret parameter from a TOTP QR code URL.
200
+ *
201
+ * @param otpauthUrl - The otpauth:// URL (or data URL containing it)
202
+ * @returns The extracted secret, or null if not found
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * const qrSrc = await page.locator('img[alt="QR Code"]').getAttribute('src');
207
+ * const secret = extractTOTPSecret(qrSrc);
208
+ * const code = generateTOTP(secret);
209
+ * ```
210
+ */
211
+ export declare function extractTOTPSecret(otpauthUrl: string): string | null;
212
+ /**
213
+ * Extract all parameters from otpauth:// URL
214
+ *
215
+ * @param otpauthUrl - The otpauth:// URL
216
+ * @returns Object with issuer, account, secret, algorithm, digits, period
217
+ *
218
+ * @example
219
+ * ```typescript
220
+ * const params = parseTOTPUrl(otpauthUrl);
221
+ * console.log(params.issuer, params.secret);
222
+ * ```
223
+ */
224
+ export declare function parseTOTPUrl(otpauthUrl: string): {
225
+ issuer: string | null;
226
+ account: string | null;
227
+ secret: string | null;
228
+ algorithm: string;
229
+ digits: number;
230
+ period: number;
231
+ };
232
+ /**
233
+ * Wait for an element to be visible and stable
234
+ *
235
+ * Useful for waiting for animations to complete or elements to settle.
236
+ *
237
+ * @param page - Playwright page object
238
+ * @param selector - CSS selector for the element
239
+ * @param options - Configuration options
240
+ * @param options.timeout - Maximum time to wait (default: 10000ms)
241
+ * @param options.stable - Wait for element to be stable (default: true)
242
+ *
243
+ * @example
244
+ * ```typescript
245
+ * await waitForElement(page, '.modal-content');
246
+ * ```
247
+ */
248
+ export declare function waitForElement(page: Page, selector: string, options?: {
249
+ timeout?: number;
250
+ stable?: boolean;
251
+ }): Promise<void>;
252
+ /**
253
+ * Wait for network to be idle
254
+ *
255
+ * Useful for waiting for all API calls to complete.
256
+ *
257
+ * @param page - Playwright page object
258
+ * @param options - Configuration options
259
+ * @param options.timeout - Maximum time to wait (default: 30000ms)
260
+ *
261
+ * @example
262
+ * ```typescript
263
+ * await page.click('button[type="submit"]');
264
+ * await waitForNetworkIdle(page);
265
+ * ```
266
+ */
267
+ export declare function waitForNetworkIdle(page: Page, options?: {
268
+ timeout?: number;
269
+ }): Promise<void>;
270
+ /**
271
+ * Check if an element contains specific text
272
+ *
273
+ * @param page - Playwright page object
274
+ * @param selector - CSS selector for the element
275
+ * @param text - Text to search for
276
+ * @returns true if text is found
277
+ *
278
+ * @example
279
+ * ```typescript
280
+ * const hasError = await hasText(page, '.error', 'Invalid email');
281
+ * ```
282
+ */
283
+ export declare function hasText(page: Page, selector: string, text: string): Promise<boolean>;
284
+ /**
285
+ * Get all text content from matching elements
286
+ *
287
+ * @param page - Playwright page object
288
+ * @param selector - CSS selector for the elements
289
+ * @returns Array of text content
290
+ *
291
+ * @example
292
+ * ```typescript
293
+ * const errors = await getAllText(page, '.error-message');
294
+ * ```
295
+ */
296
+ export declare function getAllText(page: Page, selector: string): Promise<string[]>;
@@ -0,0 +1,127 @@
1
+ import * as crypto from "crypto";
2
+ export const DEFAULT_HYDRATION_TIMEOUT = 15e3;
3
+ export async function waitForHydration(page, options = {}) {
4
+ const timeout = options.timeout ?? DEFAULT_HYDRATION_TIMEOUT;
5
+ await page.waitForFunction(() => window.useNuxtApp?.()?.isHydrating === false, {
6
+ timeout
7
+ });
8
+ }
9
+ export async function gotoAndWaitForHydration(page, url, options = {}) {
10
+ await page.goto(url);
11
+ await waitForHydration(page, options);
12
+ }
13
+ export async function waitForURLAndHydration(page, url, options = {}) {
14
+ const { timeout = 3e4, hydrationTimeout } = options;
15
+ await page.waitForURL(url, { timeout });
16
+ await waitForHydration(page, { timeout: hydrationTimeout });
17
+ }
18
+ export async function fillInput(page, selector, value, options = {}) {
19
+ const { delay = 5, clear = true } = options;
20
+ const locator = page.locator(selector);
21
+ await locator.click();
22
+ if (clear) {
23
+ await page.keyboard.press("Control+a");
24
+ await page.keyboard.press("Backspace");
25
+ }
26
+ await page.keyboard.type(value, { delay });
27
+ }
28
+ export async function fillInputs(page, fields, options = {}) {
29
+ for (const [selector, value] of Object.entries(fields)) {
30
+ await fillInput(page, selector, value, options);
31
+ }
32
+ }
33
+ export function generateTestUser(prefix = "e2e") {
34
+ const testId = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
35
+ return {
36
+ email: `${prefix}-${testId}@test.com`,
37
+ password: `TestPass${testId}!`,
38
+ name: `E2E Test User ${prefix}`
39
+ };
40
+ }
41
+ export function generateRandomString(length = 8, charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") {
42
+ let result = "";
43
+ for (let i = 0; i < length; i++) {
44
+ result += charset.charAt(Math.floor(Math.random() * charset.length));
45
+ }
46
+ return result;
47
+ }
48
+ export function generateTOTP(secret, options = {}) {
49
+ const { digits = 6, period = 30, algorithm = "sha1" } = options;
50
+ const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
51
+ let bits = "";
52
+ for (const char of secret.toUpperCase().replace(/=/g, "")) {
53
+ const val = base32Chars.indexOf(char);
54
+ if (val === -1) continue;
55
+ bits += val.toString(2).padStart(5, "0");
56
+ }
57
+ const keyBytes = Buffer.alloc(Math.floor(bits.length / 8));
58
+ for (let i = 0; i < keyBytes.length; i++) {
59
+ keyBytes[i] = parseInt(bits.slice(i * 8, (i + 1) * 8), 2);
60
+ }
61
+ const counter = Math.floor(Date.now() / 1e3 / period);
62
+ const counterBuffer = Buffer.alloc(8);
63
+ counterBuffer.writeBigUInt64BE(BigInt(counter));
64
+ const hmac = crypto.createHmac(algorithm, keyBytes);
65
+ hmac.update(counterBuffer);
66
+ const hash = hmac.digest();
67
+ const offset = (hash[hash.length - 1] ?? 0) & 15;
68
+ const code = ((hash[offset] ?? 0) & 127) << 24 | ((hash[offset + 1] ?? 0) & 255) << 16 | ((hash[offset + 2] ?? 0) & 255) << 8 | (hash[offset + 3] ?? 0) & 255;
69
+ const mod = Math.pow(10, digits);
70
+ return (code % mod).toString().padStart(digits, "0");
71
+ }
72
+ export function extractTOTPSecret(otpauthUrl) {
73
+ const decoded = decodeURIComponent(otpauthUrl);
74
+ const match = decoded.match(/secret=([A-Z2-7]+)/i);
75
+ return match?.[1]?.toUpperCase() ?? null;
76
+ }
77
+ export function parseTOTPUrl(otpauthUrl) {
78
+ const decoded = decodeURIComponent(otpauthUrl);
79
+ const pathMatch = decoded.match(/otpauth:\/\/totp\/(?:([^:]+):)?([^?]+)/);
80
+ const getParam = (name) => {
81
+ const match = decoded.match(new RegExp(`${name}=([^&]+)`, "i"));
82
+ return match?.[1] ?? null;
83
+ };
84
+ return {
85
+ issuer: getParam("issuer") ?? pathMatch?.[1] ?? null,
86
+ account: pathMatch?.[2] ?? null,
87
+ secret: getParam("secret")?.toUpperCase() ?? null,
88
+ algorithm: getParam("algorithm") || "SHA1",
89
+ digits: parseInt(getParam("digits") || "6", 10),
90
+ period: parseInt(getParam("period") || "30", 10)
91
+ };
92
+ }
93
+ export async function waitForElement(page, selector, options = {}) {
94
+ const { timeout = 1e4, stable = true } = options;
95
+ const locator = page.locator(selector);
96
+ await locator.waitFor({ state: "visible", timeout });
97
+ if (stable) {
98
+ await locator.evaluate((el) => {
99
+ return new Promise((resolve) => {
100
+ if (!el.getAnimations || el.getAnimations().length === 0) {
101
+ resolve();
102
+ return;
103
+ }
104
+ Promise.all(el.getAnimations().map((a) => a.finished)).then(() => resolve());
105
+ });
106
+ });
107
+ }
108
+ }
109
+ export async function waitForNetworkIdle(page, options = {}) {
110
+ const { timeout = 3e4 } = options;
111
+ await page.waitForLoadState("networkidle", { timeout });
112
+ }
113
+ export async function hasText(page, selector, text) {
114
+ const locator = page.locator(selector);
115
+ const content = await locator.textContent();
116
+ return content?.includes(text) ?? false;
117
+ }
118
+ export async function getAllText(page, selector) {
119
+ const locators = page.locator(selector);
120
+ const count = await locators.count();
121
+ const texts = [];
122
+ for (let i = 0; i < count; i++) {
123
+ const text = await locators.nth(i).textContent();
124
+ if (text) texts.push(text.trim());
125
+ }
126
+ return texts;
127
+ }
@@ -1,4 +1,4 @@
1
- import type { ComputedRef, Ref } from 'vue';
1
+ import type { ComputedRef, Ref } from "vue";
2
2
  /**
3
3
  * User type for Better Auth session
4
4
  * Compatible with @lenne.tech/nest-server IAM module
@@ -20,7 +20,7 @@ export interface LtUser {
20
20
  * - 'cookie': Primary mode using HttpOnly session cookies (more secure)
21
21
  * - 'jwt': Fallback mode using JWT tokens in Authorization header
22
22
  */
23
- export type LtAuthMode = 'cookie' | 'jwt';
23
+ export type LtAuthMode = "cookie" | "jwt";
24
24
  /**
25
25
  * Stored auth state (persisted in cookie for SSR compatibility)
26
26
  */
@@ -0,0 +1,48 @@
1
+ import type { ComputedRef, Ref } from "vue";
2
+ /**
3
+ * Backend error translation response format
4
+ * Matches the response from GET /api/i18n/errors/:locale
5
+ */
6
+ export interface LtErrorTranslationResponse {
7
+ errors: Record<string, string>;
8
+ }
9
+ /**
10
+ * Parsed error from backend
11
+ * Contains both the original and translated messages
12
+ */
13
+ export interface LtParsedError {
14
+ /** Raw error code (e.g., LTNS_0100) or null if no code found */
15
+ code: string | null;
16
+ /** Original developer message from the backend */
17
+ developerMessage: string;
18
+ /** Translated user-friendly message */
19
+ translatedMessage: string;
20
+ }
21
+ /**
22
+ * Error translation module options
23
+ */
24
+ export interface LtErrorTranslationModuleOptions {
25
+ /** Enable error translation feature (default: true) */
26
+ enabled?: boolean;
27
+ /** Default locale if not detected (default: 'de') */
28
+ defaultLocale?: string;
29
+ }
30
+ /**
31
+ * Return type for useLtErrorTranslation composable
32
+ */
33
+ export interface UseLtErrorTranslationReturn {
34
+ /** Translate an error message or code to user-friendly message */
35
+ translateError: (errorOrMessage: unknown) => string;
36
+ /** Parse a backend error to extract code and messages */
37
+ parseError: (errorOrMessage: unknown) => LtParsedError;
38
+ /** Show translated error as toast notification */
39
+ showErrorToast: (errorOrMessage: unknown, title?: string) => void;
40
+ /** Manually load translations for a locale */
41
+ loadTranslations: (locale?: string) => Promise<void>;
42
+ /** Check if translations are loaded for current locale */
43
+ isLoaded: ComputedRef<boolean>;
44
+ /** Check if translations are currently loading */
45
+ isLoading: Ref<boolean>;
46
+ /** Current detected locale */
47
+ currentLocale: ComputedRef<string>;
48
+ }
File without changes
@@ -1,3 +1,4 @@
1
- export type { LtAuthClientConfig, LtAuthMode, LtAuthResponse, LtAuthState, LtPasskeyAuthResult, LtPasskeyRegisterResult, LtUser, UseLtAuthReturn, } from './auth.js';
2
- export type { LtFileInfo, LtUploadItem, LtUploadOptions, LtUploadProgress, LtUploadStatus, UseLtFileReturn, UseLtTusUploadReturn, } from './upload.js';
3
- export type { LtAuthModuleOptions, LtExtensionsModuleOptions, LtExtensionsPublicRuntimeConfig, LtI18nModuleOptions, LtTusModuleOptions, } from './module.js';
1
+ export type { LtAuthClientConfig, LtAuthMode, LtAuthResponse, LtAuthState, LtPasskeyAuthResult, LtPasskeyRegisterResult, LtUser, UseLtAuthReturn, } from "./auth.js";
2
+ export type { LtFileInfo, LtUploadItem, LtUploadOptions, LtUploadProgress, LtUploadStatus, UseLtFileReturn, UseLtTusUploadReturn, } from "./upload.js";
3
+ export type { LtAuthModuleOptions, LtErrorTranslationModuleOptions, LtExtensionsModuleOptions, LtExtensionsPublicRuntimeConfig, LtI18nModuleOptions, LtTusModuleOptions, } from "./module.js";
4
+ export type { LtErrorTranslationResponse, LtParsedError, UseLtErrorTranslationReturn, } from "./error.js";