@lenne.tech/ng-base 22.0.4 → 22.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.
- package/CLAUDE.md +5 -0
- package/README.md +34 -7
- package/fesm2022/lenne.tech-ng-base-ai.mjs +27 -27
- package/fesm2022/lenne.tech-ng-base-auth-provide.mjs +15 -14
- package/fesm2022/lenne.tech-ng-base-auth-provide.mjs.map +1 -1
- package/fesm2022/lenne.tech-ng-base-auth.mjs +17 -13
- package/fesm2022/lenne.tech-ng-base-auth.mjs.map +1 -1
- package/fesm2022/{lenne.tech-ng-base-bug-bug-report-dialog-BRzVmKJr.mjs → lenne.tech-ng-base-bug-bug-report-dialog-f6VNOWnW.mjs} +11 -11
- package/fesm2022/{lenne.tech-ng-base-bug-bug-report-dialog-BRzVmKJr.mjs.map → lenne.tech-ng-base-bug-bug-report-dialog-f6VNOWnW.mjs.map} +1 -1
- package/fesm2022/{lenne.tech-ng-base-bug-bug-report-service-DWoTb_iw.mjs → lenne.tech-ng-base-bug-bug-report-service-D6slR3GD.mjs} +4 -4
- package/fesm2022/{lenne.tech-ng-base-bug-bug-report-service-DWoTb_iw.mjs.map → lenne.tech-ng-base-bug-bug-report-service-D6slR3GD.mjs.map} +1 -1
- package/fesm2022/lenne.tech-ng-base-bug.mjs +5 -5
- package/fesm2022/lenne.tech-ng-base-files-ui.mjs +3 -3
- package/fesm2022/lenne.tech-ng-base-files.mjs +8 -4
- package/fesm2022/lenne.tech-ng-base-files.mjs.map +1 -1
- package/fesm2022/lenne.tech-ng-base-share.mjs +3 -3
- package/fesm2022/lenne.tech-ng-base-ui-provide.mjs +3 -3
- package/fesm2022/lenne.tech-ng-base-ui.mjs +1206 -1216
- package/fesm2022/lenne.tech-ng-base-ui.mjs.map +1 -1
- package/fesm2022/lenne.tech-ng-base-utils.mjs +9 -9
- package/fesm2022/lenne.tech-ng-base.mjs +27 -24
- package/fesm2022/lenne.tech-ng-base.mjs.map +1 -1
- package/i18n/auth/de.json +1 -3
- package/i18n/auth/en.json +1 -3
- package/i18n/de.json +1 -2
- package/i18n/en.json +1 -2
- package/i18n/ui/de.json +0 -5
- package/i18n/ui/en.json +0 -5
- package/package.json +5 -1
- package/types/lenne.tech-ng-base-auth-provide.d.ts +2 -3
- package/types/lenne.tech-ng-base-auth.d.ts +4 -1
- package/types/lenne.tech-ng-base-files.d.ts +5 -1
- package/types/lenne.tech-ng-base.d.ts +8 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lenne.tech-ng-base-auth.mjs","names":[],"sources":["../../../projects/ng-base/auth/src/lib/auth-client.ts","../tmp-esm2022/auth/lib/i18n/de.json","../tmp-esm2022/auth/lib/i18n/en.json","../../../projects/ng-base/auth/src/lib/i18n.ts","../../../projects/ng-base/auth/src/lib/auth-service.ts"],"sourcesContent":["import type { createAuthClient } from 'better-auth/client';\nimport { ltSha256, type LtApiUrlResolution, type LtFetch } from '@lenne.tech/ng-base';\n\nimport type { LtAuthCallOptions, LtAuthResponse, LtNoStrayCredential, LtPasskey, LtTwoFactorEnableResult, LtUser } from '@lenne.tech/ng-base/auth/provide';\n\n/** Configuration of {@link createLtAuthClient}. */\nexport interface LtAuthClientConfig {\n /** API origin (`''` for same-origin / proxy). */\n baseURL: string;\n /** Better Auth base path, e.g. `/iam` or `/api/iam` behind the dev proxy. */\n basePath: string;\n /** Register `adminClient()` (default `false`, see `LtAuthOptions.enableAdmin`). */\n enableAdmin?: boolean;\n /** Register `twoFactorClient()` (default `true`). */\n enableTwoFactor?: boolean;\n /** Fetch implementation (the auth-aware `LT_FETCH`). */\n fetch?: LtFetch;\n /** Called when a sign-in answers `twoFactorRedirect` (navigate to the 2FA page). */\n onTwoFactorRedirect?: () => void | Promise<void>;\n /** Additional Better Auth client plugins (e.g. `passkeyClient()` from `@better-auth/passkey/client`). */\n plugins?: unknown[];\n}\n\n/**\n * The Better Auth client surface ng-base exposes. Every method that sends a credential hashes it with SHA-256 first\n * (`{ ...params, field: sha256(field) }`, never a whitelist), so a plaintext password never leaves the browser.\n */\nexport interface LtAuthClient {\n /** The underlying Better Auth client (escape hatch; its methods do NOT hash). */\n readonly raw: ReturnType<typeof createAuthClient>;\n changePassword<T extends { currentPassword: string; newPassword: string; revokeOtherSessions?: boolean }>(\n params: LtNoStrayCredential<T, 'currentPassword' | 'newPassword'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse>;\n getSession(options?: LtAuthCallOptions): Promise<LtAuthResponse<{ session: Record<string, unknown>; user: LtUser } | null>>;\n passkey: {\n deletePasskey(params: { id: string }, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n listUserPasskeys(options?: LtAuthCallOptions): Promise<LtAuthResponse<LtPasskey[]>>;\n updatePasskey(params: { id: string; name: string }, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n };\n requestPasswordReset<T extends { email: string; redirectTo?: string }>(params: LtNoStrayCredential<T, never>, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n resetPassword<T extends { newPassword: string; token: string }>(params: LtNoStrayCredential<T, 'newPassword'>, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n sendVerificationEmail<T extends { callbackURL?: string; email: string }>(params: LtNoStrayCredential<T, never>, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n signIn: {\n email<T extends { callbackURL?: string; email: string; password: string; rememberMe?: boolean }>(\n params: LtNoStrayCredential<T, 'password'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse>;\n };\n signOut(options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n signUp: {\n email<T extends { callbackURL?: string; email: string; name: string; password: string }>(\n params: LtNoStrayCredential<T, 'password'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse>;\n };\n twoFactor: {\n disable<T extends { password: string }>(params: LtNoStrayCredential<T, 'password'>, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n enable<T extends { issuer?: string; password: string }>(\n params: LtNoStrayCredential<T, 'password'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse<LtTwoFactorEnableResult>>;\n generateBackupCodes<T extends { password: string }>(\n params: LtNoStrayCredential<T, 'password'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse<{ backupCodes: string[] }>>;\n verifyBackupCode(params: { code: string; disableSession?: boolean; trustDevice?: boolean }, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n verifyTotp(params: { code: string; trustDevice?: boolean }, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n };\n verifyEmail(params: { callbackURL?: string; token: string }, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n}\n\nconst pluginRegistry: unknown[] = [];\n\n/** Register extra Better Auth client plugins for every client created afterwards (prefer `provideLtAuth({ plugins })`). */\nexport function registerLtAuthPlugins(plugins: unknown[]): void {\n pluginRegistry.push(...plugins);\n}\n\n/** Plugins registered via {@link registerLtAuthPlugins}. */\nexport function getLtAuthPluginRegistry(): readonly unknown[] {\n return [...pluginRegistry];\n}\n\n/** Clear the plugin registry (tests). */\nexport function clearLtAuthPluginRegistry(): void {\n pluginRegistry.length = 0;\n}\n\n/**\n * `baseURL` / `basePath` of the Better Auth client for an API URL resolution: behind the dev proxy the origin is the\n * app itself (`''`) and the path gets the `/api` prefix (`/iam` → `/api/iam`), otherwise the API base URL with the\n * configured path.\n */\nexport function resolveLtAuthClientBase(resolution: Pick<LtApiUrlResolution, 'baseUrl' | 'proxy'>, basePath: string): { basePath: string; baseURL: string } {\n const trimmed = (basePath || '/iam').replace(/\\/+$/, '');\n const path = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;\n if (resolution.proxy) {\n return { basePath: path === '/api' || path.startsWith('/api/') ? path : `/api${path}`, baseURL: '' };\n }\n return { basePath: path, baseURL: resolution.baseUrl };\n}\n\n/**\n * Absolute Better Auth URL for `baseURL` + `basePath`.\n *\n * Better Auth rejects a relative `baseURL` (\"Invalid base URL\"), so an empty origin (same-origin deployment, dev proxy\n * `/api/iam`) is resolved against the browser origin. Without a usable origin (server without API URL) the path is\n * returned unchanged; such a client cannot reach the backend anyway.\n */\nexport function resolveLtAuthClientUrl(baseURL: string, basePath: string): string {\n const path = basePath ? (basePath.startsWith('/') ? basePath : `/${basePath}`) : '';\n if (baseURL) {\n return `${baseURL.replace(/\\/+$/, '')}${path}`;\n }\n const origin = typeof location !== 'undefined' ? location.origin : '';\n return origin && origin !== 'null' ? `${origin}${path}` : path;\n}\n\n/**\n * Load `better-auth/client` and its plugins on demand. Dynamic imports keep Better Auth (and better-fetch) out of the\n * initial bundle: esbuild puts them into a lazy chunk that is fetched with the first auth action.\n */\nfunction loadBetterAuth(): Promise<[typeof import('better-auth/client'), typeof import('better-auth/client/plugins')]> {\n return Promise.all([import('better-auth/client'), import('better-auth/client/plugins')]);\n}\n\n/**\n * Create the hashing Better Auth client wrapper.\n *\n * Uses the framework-agnostic `better-auth/client` (`createAuthClient`) with `twoFactorClient()` (and `adminClient()` when\n * enabled), the plugins of `config.plugins` and the registry, and the given fetch. Async because Better Auth is loaded\n * on first use (see `LtAuthService.getClient()`, which shares one client per app).\n */\nexport async function createLtAuthClient(config: LtAuthClientConfig): Promise<LtAuthClient> {\n const [{ createAuthClient }, { adminClient, twoFactorClient }] = await loadBetterAuth();\n const plugins: unknown[] = [];\n if (config.enableTwoFactor !== false) {\n plugins.push(twoFactorClient({ onTwoFactorRedirect: () => config.onTwoFactorRedirect?.() }));\n }\n if (config.enableAdmin) {\n plugins.push(adminClient());\n }\n plugins.push(...(config.plugins ?? []), ...pluginRegistry);\n const fetchImpl = config.fetch;\n const raw = createAuthClient({\n baseURL: resolveLtAuthClientUrl(config.baseURL, config.basePath),\n fetchOptions: {\n credentials: 'include',\n ...(fetchImpl ? { customFetchImpl: (input: RequestInfo | URL, init?: RequestInit) => fetchImpl(input, init) } : {}),\n },\n plugins: plugins as never[],\n });\n // Better Auth's client is a Proxy without own keys: every member is listed explicitly, never spread.\n const client = raw as unknown as Record<string, any>;\n const call = (path: string[], args: unknown[]): Promise<LtAuthResponse<any>> => {\n let target: any = client;\n for (const key of path) {\n target = target[key];\n }\n return target(...args);\n };\n const hashed = async <T extends Record<string, unknown>>(params: T, fields: string[], method: string): Promise<T> => {\n const result: Record<string, unknown> = { ...params };\n for (const field of fields) {\n const value = params[field];\n if (typeof value !== 'string' || !value) {\n throw new Error(`[@lenne.tech/ng-base] ${method}: \"${field}\" is required`);\n }\n result[field] = await ltSha256(value);\n }\n return result as T;\n };\n const withOptions = (options?: LtAuthCallOptions) => (options ? [{ fetchOptions: options }] : []);\n\n return {\n raw,\n changePassword: async (params, options) =>\n call(['changePassword'], [await hashed(params as Record<string, unknown>, ['currentPassword', 'newPassword'], 'changePassword'), ...withOptions(options)]),\n getSession: (options) => call(['getSession'], options ? [{ fetchOptions: options }] : []),\n passkey: {\n deletePasskey: (params, options) => call(['$fetch'], ['/passkey/delete-passkey', { body: params, method: 'POST', ...options }]),\n listUserPasskeys: (options) => call(['$fetch'], ['/passkey/list-user-passkeys', { method: 'GET', ...options }]),\n updatePasskey: (params, options) => call(['$fetch'], ['/passkey/update-passkey', { body: params, method: 'POST', ...options }]),\n },\n requestPasswordReset: (params, options) => call(['requestPasswordReset'], [params, ...withOptions(options)]),\n resetPassword: async (params, options) => call(['resetPassword'], [await hashed(params as Record<string, unknown>, ['newPassword'], 'resetPassword'), ...withOptions(options)]),\n sendVerificationEmail: (params, options) => call(['sendVerificationEmail'], [params, ...withOptions(options)]),\n signIn: {\n email: async (params, options) => call(['signIn', 'email'], [await hashed(params as Record<string, unknown>, ['password'], 'signIn.email'), ...withOptions(options)]),\n },\n signOut: (options) => call(['signOut'], options ? [{ fetchOptions: options }] : []),\n signUp: {\n email: async (params, options) => call(['signUp', 'email'], [await hashed(params as Record<string, unknown>, ['password'], 'signUp.email'), ...withOptions(options)]),\n },\n twoFactor: {\n disable: async (params, options) =>\n call(['twoFactor', 'disable'], [await hashed(params as Record<string, unknown>, ['password'], 'twoFactor.disable'), ...withOptions(options)]),\n enable: async (params, options) =>\n call(['twoFactor', 'enable'], [await hashed(params as Record<string, unknown>, ['password'], 'twoFactor.enable'), ...withOptions(options)]),\n generateBackupCodes: async (params, options) =>\n call(['twoFactor', 'generateBackupCodes'], [await hashed(params as Record<string, unknown>, ['password'], 'twoFactor.generateBackupCodes'), ...withOptions(options)]),\n verifyBackupCode: (params, options) => call(['twoFactor', 'verifyBackupCode'], [params, ...withOptions(options)]),\n verifyTotp: (params, options) => call(['twoFactor', 'verifyTotp'], [params, ...withOptions(options)]),\n },\n verifyEmail: (params, options) => call(['verifyEmail'], [{ query: params }, ...withOptions(options)]),\n };\n}\n","","","import { inject } from '@angular/core';\nimport { LtI18nService } from '@lenne.tech/ng-base';\n\nimport de from './i18n/de.json';\nimport en from './i18n/en.json';\n\n/** The `lt.auth.*` texts (also shipped as `i18n/auth/{de,en}.json`). */\nexport const LT_AUTH_MESSAGES = { de, en } as const;\n\n/** `LtI18nService` with the `lt.auth.*` texts registered (injection context). */\nexport function injectLtAuthI18n(): LtI18nService {\n const i18n = inject(LtI18nService);\n i18n.registerLibraryMessages('auth', LT_AUTH_MESSAGES);\n return i18n;\n}\n","import { inject, Service, type Signal } from '@angular/core';\nimport { LtApiUrlService, ltArrayBufferToBase64Url, ltBase64UrlToUint8Array } from '@lenne.tech/ng-base';\nimport {\n LT_AUTH_OPTIONS,\n LtAuthStore,\n readLtAuthAnswer,\n type LtAuthCallOptions,\n type LtAuthFeatures,\n type LtAuthMode,\n type LtAuthResponse,\n type LtNoStrayCredential,\n type LtPasskey,\n type LtPasskeyAuthResult,\n type LtPasskeyRegisterResult,\n type LtTwoFactorEnableResult,\n type LtUser,\n type LtUserInput,\n} from '@lenne.tech/ng-base/auth/provide';\n\nimport { createLtAuthClient, resolveLtAuthClientBase, type LtAuthClient } from './auth-client';\nimport { injectLtAuthI18n } from './i18n';\n\n/** Passkey authentication options as nest-server sends them (base64url strings, plus the `challengeId` extension). */\ninterface LtPasskeyAuthenticationOptions {\n allowCredentials?: { id: string; transports?: string[]; type: string }[];\n challenge: string;\n challengeId?: string;\n rpId?: string;\n timeout?: number;\n userVerification?: UserVerificationRequirement;\n}\n\n/** Passkey registration options as nest-server sends them. */\ninterface LtPasskeyRegistrationOptions {\n attestation?: AttestationConveyancePreference;\n authenticatorSelection?: AuthenticatorSelectionCriteria;\n challenge: string;\n challengeId?: string;\n excludeCredentials?: { id: string; transports?: string[]; type: string }[];\n pubKeyCredParams: PublicKeyCredentialParameters[];\n rp: PublicKeyCredentialRpEntity;\n timeout?: number;\n user: { displayName: string; id: string; name: string };\n}\n\n/**\n * Authentication against nest-server's Better Auth integration (`/iam`), the Angular counterpart of `useLtAuth()`.\n *\n * A facade over {@link LtAuthStore} (`@lenne.tech/ng-base/auth/provide`): state, transport, session validation and\n * sign-out are the store's (same signals, same methods), this service adds the flows that need Better Auth or WebAuthn\n * — sign-in/up, 2FA, passkeys, password and email. Guards, the interceptor and `provideLtAuth()` use the store only, so\n * this service (and Better Auth, loaded on first use) stays out of the initial bundle until a page injects it.\n *\n * State is signal-based and backed by the client-readable `lt-auth-state` cookie, so SSR renders the signed-in shell\n * from the request cookie (UI gating only; the session itself is the httpOnly `iam.session_token`).\n *\n * Transport: session cookie first; when cookies do not work (401 while the state says signed in) the service switches\n * to JWT mode (`GET /iam/token`, Bearer header) and refreshes a stale JWT once per burst. All credentials are SHA-256\n * hashed before they leave the browser.\n */\n@Service()\nexport class LtAuthService {\n private readonly store = inject(LtAuthStore);\n private readonly api = inject(LtApiUrlService);\n private readonly options = inject(LT_AUTH_OPTIONS);\n private readonly i18n = injectLtAuthI18n();\n private clientRequest: Promise<LtAuthClient> | null = null;\n\n // ── state (the store's signals) ────────────────────────────────────────────────────────────────────────────────────\n /** Signed-in user (from the auth-state cookie, refreshed by `validateSession()` and every sign-in). */\n readonly user: Signal<LtUser | null> = this.store.user;\n /** Whether a user is signed in (UI gating; the backend decides). */\n readonly isAuthenticated: Signal<boolean> = this.store.isAuthenticated;\n /** Current transport. */\n readonly authMode: Signal<LtAuthMode> = this.store.authMode;\n /** Whether the JWT fallback is active. */\n readonly isJwtMode: Signal<boolean> = this.store.isJwtMode;\n /** Whether a sign-in/up/out or passkey flow is running. */\n readonly isLoading: Signal<boolean> = this.store.isLoading;\n /** Whether the user has the `admin` role (UX gate only). */\n readonly isAdmin: Signal<boolean> = this.store.isAdmin;\n /** Whether two-factor authentication is enabled for the user. */\n readonly is2FAEnabled: Signal<boolean> = this.store.is2FAEnabled;\n /** nest-server feature flags (`GET /iam/features`); fetched on the server and transferred to the browser. */\n readonly features: Signal<LtAuthFeatures> = this.store.features;\n /** Whether passkeys can be used: enabled in the options, backend feature on and WebAuthn available in this browser. */\n readonly passkeySupported: Signal<boolean> = this.store.passkeySupported;\n\n /**\n * The hashing Better Auth client (see {@link LtAuthClient}), created once per app on first use. Better Auth is loaded\n * lazily (dynamic import), so it is not part of the initial bundle; session state, guards, the interceptor and\n * {@link fetchWithAuth} never need it. The client sends every request through {@link fetchWithAuth} (the same\n * auth-aware fetch as `LT_FETCH`). During SSR it never carries the request's cookies, so do not use it on the\n * server for anything that needs the user's session. A failed load is retried on the next call.\n */\n getClient(): Promise<LtAuthClient> {\n if (!this.clientRequest) {\n this.clientRequest = createLtAuthClient({\n ...resolveLtAuthClientBase(this.api.resolve(), this.options.basePath),\n enableAdmin: this.options.enableAdmin,\n enableTwoFactor: this.options.enableTwoFactor,\n // No onTwoFactorRedirect: nest-server answers `requiresTwoFactor`, Better Auth's plugin only sees\n // `twoFactorRedirect`, so `signIn.email` recognises both itself and navigates once.\n fetch: (input, init) => this.store.fetchWithAuth(input, init),\n plugins: this.options.plugins,\n });\n this.clientRequest.catch(() => {\n this.clientRequest = null;\n });\n }\n return this.clientRequest;\n }\n\n /** The current JWT (JWT mode only), or `null`. */\n jwtToken(): string | null {\n return this.store.jwtToken();\n }\n\n // ── roles ──────────────────────────────────────────────────────────────────────────────────────────────────────────\n /** `user.role === role` or `user.roles` contains it (case-sensitive). */\n hasRole(role: string): boolean {\n return this.store.hasRole(role);\n }\n\n /** Whether the user has any of the roles. */\n hasAnyRole(...roles: string[]): boolean {\n return this.store.hasAnyRole(...roles);\n }\n\n // ── features ───────────────────────────────────────────────────────────────────────────────────────────────────────\n /** Fetch the feature flags (`GET /iam/features`, see {@link LtAuthStore.fetchFeatures}). */\n fetchFeatures(): Promise<LtAuthFeatures> {\n return this.store.fetchFeatures();\n }\n\n /** The feature flags, fetched at most once (transferred from SSR, or fetched on first call). */\n ensureFeatures(): Promise<LtAuthFeatures> {\n return this.store.ensureFeatures();\n }\n\n // ── sign-in / sign-up / sign-out ───────────────────────────────────────────────────────────────────────────────────\n /**\n * Email sign-in (password hashed). A second factor (`requiresTwoFactor` from nest-server or `twoFactorRedirect` from\n * Better Auth) navigates to `twoFactorRedirectPath` (keeping the current `redirect` query parameter) and sets no user. A JWT-shaped body token (nest-server\n * `cookies: false`) switches to JWT mode; otherwise the session cookie is used and a JWT is fetched in the background.\n */\n readonly signIn = {\n email: async <T extends { callbackURL?: string; email: string; password: string; rememberMe?: boolean }>(\n params: LtNoStrayCredential<T, 'password'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse> =>\n this.store.withLoading(async () => {\n const result = await (await this.getClient()).signIn.email(params, options);\n if (readLtAuthAnswer(result).twoFactor) {\n this.store.navigateKeepingRedirect(this.options.twoFactorRedirectPath);\n } else {\n this.store.applyAuthResult(result);\n }\n return result;\n }),\n /** Passkey sign-in (same as {@link authenticateWithPasskey}). */\n passkey: (): Promise<LtPasskeyAuthResult> => this.authenticateWithPasskey(),\n };\n\n /**\n * Email sign-up (password hashed), same token handling as sign-in. With email verification nest-server answers\n * `emailVerificationRequired: true` and revokes the session: no user is set, the caller leads to the verify page.\n */\n readonly signUp = {\n email: async <T extends { callbackURL?: string; email: string; name: string; password: string }>(\n params: LtNoStrayCredential<T, 'password'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse> =>\n this.store.withLoading(async () => {\n const result = await (await this.getClient()).signUp.email(params, options);\n this.store.applyAuthResult(result);\n return result;\n }),\n };\n\n /**\n * Sign out: sign-out hooks (offline sync flush), `POST /iam/sign-out`, local state cleared, after-hooks (sync wipe).\n * The local state is cleared even when the request fails (offline device). See {@link LtAuthStore.signOut}.\n */\n signOut(options?: LtAuthCallOptions): Promise<void> {\n return this.store.signOut(options);\n }\n\n // ── session ────────────────────────────────────────────────────────────────────────────────────────────────────────\n /** Store the user in state and cookie (browser always; SSR never writes). `image: null` becomes `undefined`. */\n setUser(user: LtUserInput | null, mode: LtAuthMode = 'cookie'): void {\n this.store.setUser(user, mode);\n }\n\n /** Clear local auth state and cookies (browser). Does not delete offline data (use `signOut()` for that). */\n clearUser(): void {\n this.store.clearUser();\n }\n\n /**\n * Validate the session (app start / hard reload). Server: answers from the cookie, no backend call. Browser:\n * `GET /iam/get-session` (≤ 10 s); a user is merged with the cached one (nest-server-only fields kept, authz keys\n * fail-closed); no user but a cached one → `true` (e.g. right after 2FA); else `false`.\n */\n validateSession(): Promise<boolean> {\n return this.store.validateSession();\n }\n\n /** The auth-aware fetch, also provided as `LT_FETCH` (see {@link LtAuthStore.fetchWithAuth}). */\n fetchWithAuth(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {\n return this.store.fetchWithAuth(input, init);\n }\n\n /** Token to retry a request with after a 401 (see {@link LtAuthStore.recoverFrom401}). */\n recoverFrom401(url: string, sentToken: string | null): Promise<string | null> {\n return this.store.recoverFrom401(url, sentToken);\n }\n\n /** Session-loss handling for a 401 from `url` (see `LtUnauthorizedHandler`). */\n handleUnauthorized(url?: string): Promise<boolean> {\n return this.store.handleUnauthorized(url);\n }\n\n /** `GET /iam/token` → store the JWT and switch to JWT mode. One request per burst. Browser only. */\n switchToJwtMode(): Promise<boolean> {\n return this.store.switchToJwtMode();\n }\n\n /** Re-mint the JWT (JWT mode only). */\n refreshJwtToken(): Promise<boolean> {\n return this.store.refreshJwtToken();\n }\n\n // ── passkeys (WebAuthn, nest-server challengeId) ───────────────────────────────────────────────────────────────────\n /** Passkey sign-in: options → `navigator.credentials.get` → `verify-authentication` (`challengeId`) → user set. */\n async authenticateWithPasskey(): Promise<LtPasskeyAuthResult> {\n if (!hasWebAuthn()) {\n return { error: this.i18n.t('lt.auth.passkeyUnsupported'), success: false };\n }\n return this.store.withLoading(async () => {\n try {\n const optionsResponse = await this.store.fetchWithAuth(this.store.authUrl('/passkey/generate-authenticate-options'), { method: 'GET' });\n if (!optionsResponse.ok) {\n return { error: this.i18n.t('lt.auth.passkeyError'), success: false };\n }\n const options = (await optionsResponse.json()) as LtPasskeyAuthenticationOptions;\n const credential = (await navigator.credentials.get({\n publicKey: {\n allowCredentials: (options.allowCredentials ?? []).map((descriptor) => ({\n ...descriptor,\n id: typeof descriptor.id === 'string' ? ltBase64UrlToUint8Array(descriptor.id).buffer : descriptor.id,\n })) as PublicKeyCredentialDescriptor[],\n challenge: ltBase64UrlToUint8Array(options.challenge).buffer,\n rpId: options.rpId,\n timeout: options.timeout,\n userVerification: options.userVerification,\n },\n })) as PublicKeyCredential | null;\n if (!credential) {\n return { error: this.i18n.t('lt.auth.noPasskeySelected'), success: false };\n }\n const assertion = credential.response as AuthenticatorAssertionResponse;\n const authResponse = await this.store.fetchWithAuth(this.store.authUrl('/passkey/verify-authentication'), {\n body: JSON.stringify({\n challengeId: options.challengeId,\n response: {\n clientExtensionResults: credential.getClientExtensionResults?.() ?? {},\n id: credential.id,\n rawId: ltArrayBufferToBase64Url(credential.rawId),\n response: {\n authenticatorData: ltArrayBufferToBase64Url(assertion.authenticatorData),\n clientDataJSON: ltArrayBufferToBase64Url(assertion.clientDataJSON),\n signature: ltArrayBufferToBase64Url(assertion.signature),\n userHandle: assertion.userHandle ? ltArrayBufferToBase64Url(assertion.userHandle) : null,\n },\n type: credential.type,\n },\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n });\n const result = ((await authResponse.json().catch(() => ({}))) ?? {}) as { message?: string; session?: { token: string }; user?: LtUserInput };\n if (!authResponse.ok) {\n return { error: result.message || this.i18n.t('lt.auth.passkeyFailed'), success: false };\n }\n // A JWT in the answer (nest-server with `cookies: false`) → JWT mode; otherwise the verification set the session\n // cookie. The opaque session token (`session.token`, or `token` from older servers) is never stored as JWT (it\n // would land in a script-readable cookie). Without user in the answer it is loaded from the session.\n const user = await this.store.adoptVerifiedSession(result);\n if (!user) {\n return { error: this.i18n.t('lt.auth.passkeyFailed'), success: false };\n }\n return { session: result.session, success: true, user };\n } catch (error) {\n if (error instanceof Error && error.name === 'NotAllowedError') {\n return { error: this.i18n.t('lt.auth.passkeyAborted'), success: false };\n }\n return { error: error instanceof Error && error.message ? error.message : this.i18n.t('lt.auth.passkeyFailed'), success: false };\n }\n });\n }\n\n /** Register a passkey for the signed-in user (`challengeId` body as nest-server expects). */\n async registerPasskey(name?: string): Promise<LtPasskeyRegisterResult> {\n if (!hasWebAuthn()) {\n return { error: this.i18n.t('lt.auth.passkeyUnsupported'), success: false };\n }\n return this.store.withLoading(async () => {\n try {\n const optionsResponse = await this.store.fetchWithAuth(this.store.authUrl('/passkey/generate-register-options'), { method: 'GET' });\n if (!optionsResponse.ok) {\n const body = (await optionsResponse.json().catch(() => ({}))) as { message?: string } | null;\n return { error: body?.message || this.i18n.t('lt.auth.registerOptionsError'), success: false };\n }\n const options = (await optionsResponse.json()) as LtPasskeyRegistrationOptions;\n const credential = (await navigator.credentials.create({\n publicKey: {\n attestation: options.attestation,\n authenticatorSelection: options.authenticatorSelection,\n challenge: ltBase64UrlToUint8Array(options.challenge).buffer,\n excludeCredentials: (options.excludeCredentials ?? []).map((descriptor) => ({\n ...descriptor,\n id: ltBase64UrlToUint8Array(descriptor.id).buffer,\n })) as PublicKeyCredentialDescriptor[],\n pubKeyCredParams: options.pubKeyCredParams,\n rp: options.rp,\n timeout: options.timeout,\n user: { ...options.user, id: ltBase64UrlToUint8Array(options.user.id).buffer },\n },\n })) as PublicKeyCredential | null;\n if (!credential) {\n return { error: this.i18n.t('lt.auth.passkeyCreationAborted'), success: false };\n }\n const attestation = credential.response as AuthenticatorAttestationResponse;\n const registerResponse = await this.store.fetchWithAuth(this.store.authUrl('/passkey/verify-registration'), {\n body: JSON.stringify({\n challengeId: options.challengeId,\n name,\n response: {\n clientExtensionResults: credential.getClientExtensionResults?.() ?? {},\n id: credential.id,\n rawId: ltArrayBufferToBase64Url(credential.rawId),\n response: {\n attestationObject: ltArrayBufferToBase64Url(attestation.attestationObject),\n clientDataJSON: ltArrayBufferToBase64Url(attestation.clientDataJSON),\n transports: attestation.getTransports?.() ?? [],\n },\n type: credential.type,\n },\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n });\n const result = (await registerResponse.json().catch(() => ({}))) as { message?: string } | null;\n if (!registerResponse.ok) {\n return { error: result?.message || this.i18n.t('lt.auth.passkeyRegisterFailed'), success: false };\n }\n return { passkey: result, success: true };\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === 'NotAllowedError') {\n return { error: this.i18n.t('lt.auth.passkeyCreationAborted'), success: false };\n }\n // The authenticator already holds a credential of this account (it was in excludeCredentials).\n if (error.name === 'InvalidStateError' || error.message.includes('already registered')) {\n return { error: this.i18n.t('lt.auth.passkeyAlreadyRegistered'), success: false };\n }\n return { error: error.message || this.i18n.t('lt.auth.passkeyRegisterFailed'), success: false };\n }\n return { error: this.i18n.t('lt.auth.passkeyRegisterFailed'), success: false };\n }\n });\n }\n\n /** Passkey management of the signed-in user. */\n readonly passkey = {\n delete: async (id: string): Promise<LtAuthResponse> => (await this.getClient()).passkey.deletePasskey({ id }),\n list: async (): Promise<LtAuthResponse<LtPasskey[]>> => (await this.getClient()).passkey.listUserPasskeys(),\n update: async (id: string, name: string): Promise<LtAuthResponse> => (await this.getClient()).passkey.updatePasskey({ id, name }),\n };\n\n // ── two-factor (TOTP + backup codes) ───────────────────────────────────────────────────────────────────────────────\n /** Two-factor methods (passwords hashed). `verifyTotp` / `verifyBackupCode` set the user on success (fix vs. Nuxt). */\n readonly twoFactor = {\n disable: async <T extends { password: string }>(params: LtNoStrayCredential<T, 'password'>): Promise<LtAuthResponse> => (await this.getClient()).twoFactor.disable(params),\n enable: async <T extends { issuer?: string; password: string }>(params: LtNoStrayCredential<T, 'password'>): Promise<LtAuthResponse<LtTwoFactorEnableResult>> =>\n (await this.getClient()).twoFactor.enable(params),\n generateBackupCodes: async <T extends { password: string }>(params: LtNoStrayCredential<T, 'password'>): Promise<LtAuthResponse<{ backupCodes: string[] }>> =>\n (await this.getClient()).twoFactor.generateBackupCodes(params),\n verifyBackupCode: async (params: { code: string; trustDevice?: boolean }): Promise<LtAuthResponse> =>\n this.adoptVerifiedSession(await (await this.getClient()).twoFactor.verifyBackupCode(params)),\n verifyTotp: async (params: { code: string; trustDevice?: boolean }): Promise<LtAuthResponse> =>\n this.adoptVerifiedSession(await (await this.getClient()).twoFactor.verifyTotp(params)),\n };\n\n // ── password & email ───────────────────────────────────────────────────────────────────────────────────────────────\n /** `POST /iam/change-password` (both passwords hashed; `revokeOtherSessions` forwarded). */\n async changePassword<T extends { currentPassword: string; newPassword: string; revokeOtherSessions?: boolean }>(\n params: LtNoStrayCredential<T, 'currentPassword' | 'newPassword'>,\n ): Promise<LtAuthResponse> {\n return (await this.getClient()).changePassword(params);\n }\n\n /** `POST /iam/request-password-reset`; `redirectTo` defaults to `<siteUrl>/auth/reset-password` (absolute, never user-derived). */\n async requestPasswordReset<T extends { email: string; redirectTo?: string }>(params: LtNoStrayCredential<T, never>): Promise<LtAuthResponse> {\n const redirectTo = params.redirectTo || this.absoluteAppUrl('/auth/reset-password');\n return (await this.getClient()).requestPasswordReset({ ...params, redirectTo });\n }\n\n /** `POST /iam/reset-password` with `{ newPassword (hashed), token }`. */\n async resetPassword<T extends { newPassword: string; token: string }>(params: LtNoStrayCredential<T, 'newPassword'>): Promise<LtAuthResponse> {\n return (await this.getClient()).resetPassword(params);\n }\n\n /** `POST /iam/send-verification-email`; `callbackURL` defaults to `<siteUrl>/auth/login` (absolute). */\n async sendVerificationEmail<T extends { callbackURL?: string; email: string }>(params: LtNoStrayCredential<T, never>): Promise<LtAuthResponse> {\n const callbackURL = params.callbackURL || this.absoluteAppUrl('/auth/login');\n return (await this.getClient()).sendVerificationEmail({ ...params, callbackURL });\n }\n\n /** `GET /iam/verify-email?token=` (JSON answer without `callbackURL`). */\n async verifyEmail(params: { callbackURL?: string; token: string }): Promise<LtAuthResponse> {\n return (await this.getClient()).verifyEmail(params);\n }\n\n /**\n * Build an absolute app URL from `NG_PUBLIC_SITE_URL` (or the browser origin), for `redirectTo` / `callbackURL`.\n * Never derived from the request: on the server without a site URL it throws. Absolute input is returned unchanged.\n */\n absoluteAppUrl(path: string): string {\n return this.store.absoluteAppUrl(path);\n }\n\n /**\n * After a successful 2FA verification: see {@link LtAuthStore.adoptVerifiedSession} (a JWT in the answer → JWT mode,\n * else cookie mode; Better Auth's opaque session token is never stored as JWT, that would put the session into a\n * script-readable cookie).\n */\n private async adoptVerifiedSession(result: LtAuthResponse): Promise<LtAuthResponse> {\n if (!result || result.error || !result.data) {\n return result;\n }\n await this.store.adoptVerifiedSession(result);\n return result;\n }\n}\n\nfunction hasWebAuthn(): boolean {\n return typeof navigator !== 'undefined' && !!navigator.credentials;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lenne.tech-ng-base-auth.mjs","names":[],"sources":["../../../projects/ng-base/auth/src/lib/auth-client.ts","../tmp-esm2022/auth/lib/i18n/de.json","../tmp-esm2022/auth/lib/i18n/en.json","../../../projects/ng-base/auth/src/lib/i18n.ts","../../../projects/ng-base/auth/src/lib/auth-service.ts"],"sourcesContent":["import type { createAuthClient } from 'better-auth/client';\nimport { ltSha256, type LtApiUrlResolution, type LtFetch } from '@lenne.tech/ng-base';\n\nimport {\n isLtJwtShaped,\n type LtAuthCallOptions,\n type LtAuthResponse,\n type LtNoStrayCredential,\n type LtPasskey,\n type LtTwoFactorEnableResult,\n type LtUser,\n} from '@lenne.tech/ng-base/auth/provide';\n\n/** Configuration of {@link createLtAuthClient}. */\nexport interface LtAuthClientConfig {\n /** API origin (`''` for same-origin / proxy). */\n baseURL: string;\n /** Better Auth base path, e.g. `/iam` or `/api/iam` behind the dev proxy. */\n basePath: string;\n /** Register `adminClient()` (default `false`, see `LtAuthOptions.enableAdmin`). */\n enableAdmin?: boolean;\n /** Register `twoFactorClient()` (default `true`). */\n enableTwoFactor?: boolean;\n /** Fetch implementation (the auth-aware `LT_FETCH`). */\n fetch?: LtFetch;\n /** Called when a sign-in answers `twoFactorRedirect` (navigate to the 2FA page). */\n onTwoFactorRedirect?: () => void | Promise<void>;\n /** Additional Better Auth client plugins (e.g. `passkeyClient()` from `@better-auth/passkey/client`). */\n plugins?: unknown[];\n}\n\n/**\n * The Better Auth client surface ng-base exposes. Every method that sends a credential hashes it with SHA-256 first\n * (`{ ...params, field: sha256(field) }`, never a whitelist), so a plaintext password never leaves the browser.\n */\nexport interface LtAuthClient {\n /** The underlying Better Auth client (escape hatch; its methods do NOT hash). */\n readonly raw: ReturnType<typeof createAuthClient>;\n changePassword<T extends { currentPassword: string; newPassword: string; revokeOtherSessions?: boolean }>(\n params: LtNoStrayCredential<T, 'currentPassword' | 'newPassword'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse>;\n getSession(options?: LtAuthCallOptions): Promise<LtAuthResponse<{ session: Record<string, unknown>; user: LtUser } | null>>;\n passkey: {\n deletePasskey(params: { id: string }, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n listUserPasskeys(options?: LtAuthCallOptions): Promise<LtAuthResponse<LtPasskey[]>>;\n updatePasskey(params: { id: string; name: string }, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n };\n requestPasswordReset<T extends { email: string; redirectTo?: string }>(params: LtNoStrayCredential<T, never>, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n resetPassword<T extends { newPassword: string; token: string }>(params: LtNoStrayCredential<T, 'newPassword'>, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n sendVerificationEmail<T extends { callbackURL?: string; email: string }>(params: LtNoStrayCredential<T, never>, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n signIn: {\n email<T extends { callbackURL?: string; email: string; password: string; rememberMe?: boolean }>(\n params: LtNoStrayCredential<T, 'password'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse>;\n };\n signOut(options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n signUp: {\n email<T extends { callbackURL?: string; email: string; name: string; password: string }>(\n params: LtNoStrayCredential<T, 'password'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse>;\n };\n twoFactor: {\n disable<T extends { password: string }>(params: LtNoStrayCredential<T, 'password'>, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n enable<T extends { issuer?: string; password: string }>(\n params: LtNoStrayCredential<T, 'password'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse<LtTwoFactorEnableResult>>;\n generateBackupCodes<T extends { password: string }>(\n params: LtNoStrayCredential<T, 'password'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse<{ backupCodes: string[] }>>;\n verifyBackupCode(params: { code: string; disableSession?: boolean; trustDevice?: boolean }, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n verifyTotp(params: { code: string; trustDevice?: boolean }, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n };\n verifyEmail(params: { callbackURL?: string; token: string }, options?: LtAuthCallOptions): Promise<LtAuthResponse>;\n}\n\nconst pluginRegistry: unknown[] = [];\n\n/** Register extra Better Auth client plugins for every client created afterwards (prefer `provideLtAuth({ plugins })`). */\nexport function registerLtAuthPlugins(plugins: unknown[]): void {\n pluginRegistry.push(...plugins);\n}\n\n/** Plugins registered via {@link registerLtAuthPlugins}. */\nexport function getLtAuthPluginRegistry(): readonly unknown[] {\n return [...pluginRegistry];\n}\n\n/** Clear the plugin registry (tests). */\nexport function clearLtAuthPluginRegistry(): void {\n pluginRegistry.length = 0;\n}\n\n/**\n * `baseURL` / `basePath` of the Better Auth client for an API URL resolution: behind the dev proxy the origin is the\n * app itself (`''`) and the path gets the `/api` prefix (`/iam` → `/api/iam`), otherwise the API base URL with the\n * configured path.\n */\nexport function resolveLtAuthClientBase(resolution: Pick<LtApiUrlResolution, 'baseUrl' | 'proxy'>, basePath: string): { basePath: string; baseURL: string } {\n const trimmed = (basePath || '/iam').replace(/\\/+$/, '');\n const path = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;\n if (resolution.proxy) {\n return { basePath: path === '/api' || path.startsWith('/api/') ? path : `/api${path}`, baseURL: '' };\n }\n return { basePath: path, baseURL: resolution.baseUrl };\n}\n\n/**\n * Absolute Better Auth URL for `baseURL` + `basePath`.\n *\n * Better Auth rejects a relative `baseURL` (\"Invalid base URL\"), so an empty origin (same-origin deployment, dev proxy\n * `/api/iam`) is resolved against the browser origin. Without a usable origin (server without API URL) the path is\n * returned unchanged; such a client cannot reach the backend anyway.\n */\nexport function resolveLtAuthClientUrl(baseURL: string, basePath: string): string {\n const path = basePath ? (basePath.startsWith('/') ? basePath : `/${basePath}`) : '';\n if (baseURL) {\n return `${baseURL.replace(/\\/+$/, '')}${path}`;\n }\n const origin = typeof location !== 'undefined' ? location.origin : '';\n return origin && origin !== 'null' ? `${origin}${path}` : path;\n}\n\n/**\n * Load `better-auth/client` and its plugins on demand. Dynamic imports keep Better Auth (and better-fetch) out of the\n * initial bundle: esbuild puts them into a lazy chunk that is fetched with the first auth action.\n */\nfunction loadBetterAuth(): Promise<[typeof import('better-auth/client'), typeof import('better-auth/client/plugins')]> {\n return Promise.all([import('better-auth/client'), import('better-auth/client/plugins')]);\n}\n\n/**\n * Create the hashing Better Auth client wrapper.\n *\n * Uses the framework-agnostic `better-auth/client` (`createAuthClient`) with `twoFactorClient()` (and `adminClient()` when\n * enabled), the plugins of `config.plugins` and the registry, and the given fetch. Async because Better Auth is loaded\n * on first use (see `LtAuthService.getClient()`, which shares one client per app).\n */\nexport async function createLtAuthClient(config: LtAuthClientConfig): Promise<LtAuthClient> {\n const [{ createAuthClient }, { adminClient, twoFactorClient }] = await loadBetterAuth();\n const plugins: unknown[] = [];\n if (config.enableTwoFactor !== false) {\n plugins.push(twoFactorClient({ onTwoFactorRedirect: () => config.onTwoFactorRedirect?.() }));\n }\n if (config.enableAdmin) {\n plugins.push(adminClient());\n }\n plugins.push(...(config.plugins ?? []), ...pluginRegistry);\n const fetchImpl = config.fetch;\n const raw = createAuthClient({\n baseURL: resolveLtAuthClientUrl(config.baseURL, config.basePath),\n fetchOptions: {\n credentials: 'include',\n ...(fetchImpl ? { customFetchImpl: (input: RequestInfo | URL, init?: RequestInit) => fetchImpl(input, init) } : {}),\n },\n plugins: plugins as never[],\n });\n // Better Auth's client is a Proxy without own keys: every member is listed explicitly, never spread.\n const client = raw as unknown as Record<string, any>;\n const call = (path: string[], args: unknown[]): Promise<LtAuthResponse<any>> => {\n let target: any = client;\n for (const key of path) {\n target = target[key];\n }\n return target(...args);\n };\n const hashed = async <T extends Record<string, unknown>>(params: T, fields: string[], method: string): Promise<T> => {\n const result: Record<string, unknown> = { ...params };\n for (const field of fields) {\n const value = params[field];\n if (typeof value !== 'string' || !value) {\n throw new Error(`[@lenne.tech/ng-base] ${method}: \"${field}\" is required`);\n }\n result[field] = await ltSha256(value);\n }\n return result as T;\n };\n const withOptions = (options?: LtAuthCallOptions) => (options ? [{ fetchOptions: options }] : []);\n // Better Auth answers `two-factor/verify-totp` and `verify-backup-code` with `{ token, user }`; with cookies on, the\n // token is the opaque session token (the value of the httpOnly `iam.session_token`). It is dropped from the answer, so\n // no caller can store it where scripts read it. A JWT (`cookies: false`) stays: it is the transport of JWT mode.\n const withoutSessionToken = (response: LtAuthResponse<any>): LtAuthResponse<any> => {\n const data = response?.data;\n if (!data || typeof data !== 'object' || !('token' in data) || isLtJwtShaped(data.token)) {\n return response;\n }\n const { token: _sessionToken, ...rest } = data as Record<string, unknown>;\n return { ...response, data: rest };\n };\n\n return {\n raw,\n changePassword: async (params, options) =>\n call(['changePassword'], [await hashed(params as Record<string, unknown>, ['currentPassword', 'newPassword'], 'changePassword'), ...withOptions(options)]),\n getSession: (options) => call(['getSession'], options ? [{ fetchOptions: options }] : []),\n passkey: {\n deletePasskey: (params, options) => call(['$fetch'], ['/passkey/delete-passkey', { body: params, method: 'POST', ...options }]),\n listUserPasskeys: (options) => call(['$fetch'], ['/passkey/list-user-passkeys', { method: 'GET', ...options }]),\n updatePasskey: (params, options) => call(['$fetch'], ['/passkey/update-passkey', { body: params, method: 'POST', ...options }]),\n },\n requestPasswordReset: (params, options) => call(['requestPasswordReset'], [params, ...withOptions(options)]),\n resetPassword: async (params, options) => call(['resetPassword'], [await hashed(params as Record<string, unknown>, ['newPassword'], 'resetPassword'), ...withOptions(options)]),\n sendVerificationEmail: (params, options) => call(['sendVerificationEmail'], [params, ...withOptions(options)]),\n signIn: {\n email: async (params, options) => call(['signIn', 'email'], [await hashed(params as Record<string, unknown>, ['password'], 'signIn.email'), ...withOptions(options)]),\n },\n signOut: (options) => call(['signOut'], options ? [{ fetchOptions: options }] : []),\n signUp: {\n email: async (params, options) => call(['signUp', 'email'], [await hashed(params as Record<string, unknown>, ['password'], 'signUp.email'), ...withOptions(options)]),\n },\n twoFactor: {\n disable: async (params, options) =>\n call(['twoFactor', 'disable'], [await hashed(params as Record<string, unknown>, ['password'], 'twoFactor.disable'), ...withOptions(options)]),\n enable: async (params, options) =>\n call(['twoFactor', 'enable'], [await hashed(params as Record<string, unknown>, ['password'], 'twoFactor.enable'), ...withOptions(options)]),\n generateBackupCodes: async (params, options) =>\n call(['twoFactor', 'generateBackupCodes'], [await hashed(params as Record<string, unknown>, ['password'], 'twoFactor.generateBackupCodes'), ...withOptions(options)]),\n verifyBackupCode: async (params, options) => withoutSessionToken(await call(['twoFactor', 'verifyBackupCode'], [params, ...withOptions(options)])),\n verifyTotp: async (params, options) => withoutSessionToken(await call(['twoFactor', 'verifyTotp'], [params, ...withOptions(options)])),\n },\n verifyEmail: (params, options) => call(['verifyEmail'], [{ query: params }, ...withOptions(options)]),\n };\n}\n","","","import { inject } from '@angular/core';\nimport { LtI18nService } from '@lenne.tech/ng-base';\n\nimport de from './i18n/de.json';\nimport en from './i18n/en.json';\n\n/** The `lt.auth.*` texts (also shipped as `i18n/auth/{de,en}.json`). */\nexport const LT_AUTH_MESSAGES = { de, en } as const;\n\n/** `LtI18nService` with the `lt.auth.*` texts registered (injection context). */\nexport function injectLtAuthI18n(): LtI18nService {\n const i18n = inject(LtI18nService);\n i18n.registerLibraryMessages('auth', LT_AUTH_MESSAGES);\n return i18n;\n}\n","import { inject, Service, type Signal } from '@angular/core';\nimport { LtApiUrlService, ltArrayBufferToBase64Url, ltBase64UrlToUint8Array } from '@lenne.tech/ng-base';\nimport {\n LT_AUTH_OPTIONS,\n LtAuthStore,\n readLtAuthAnswer,\n type LtAuthCallOptions,\n type LtAuthFeatures,\n type LtAuthMode,\n type LtAuthResponse,\n type LtNoStrayCredential,\n type LtPasskey,\n type LtPasskeyAuthResult,\n type LtPasskeyRegisterResult,\n type LtTwoFactorEnableResult,\n type LtUser,\n type LtUserInput,\n} from '@lenne.tech/ng-base/auth/provide';\n\nimport { createLtAuthClient, resolveLtAuthClientBase, type LtAuthClient } from './auth-client';\nimport { injectLtAuthI18n } from './i18n';\n\n/** Passkey authentication options as nest-server sends them (base64url strings, plus the `challengeId` extension). */\ninterface LtPasskeyAuthenticationOptions {\n allowCredentials?: { id: string; transports?: string[]; type: string }[];\n challenge: string;\n challengeId?: string;\n rpId?: string;\n timeout?: number;\n userVerification?: UserVerificationRequirement;\n}\n\n/** Passkey registration options as nest-server sends them. */\ninterface LtPasskeyRegistrationOptions {\n attestation?: AttestationConveyancePreference;\n authenticatorSelection?: AuthenticatorSelectionCriteria;\n challenge: string;\n challengeId?: string;\n excludeCredentials?: { id: string; transports?: string[]; type: string }[];\n pubKeyCredParams: PublicKeyCredentialParameters[];\n rp: PublicKeyCredentialRpEntity;\n timeout?: number;\n user: { displayName: string; id: string; name: string };\n}\n\n/**\n * Authentication against nest-server's Better Auth integration (`/iam`), the Angular counterpart of `useLtAuth()`.\n *\n * A facade over {@link LtAuthStore} (`@lenne.tech/ng-base/auth/provide`): state, transport, session validation and\n * sign-out are the store's (same signals, same methods), this service adds the flows that need Better Auth or WebAuthn\n * — sign-in/up, 2FA, passkeys, password and email. Guards, the interceptor and `provideLtAuth()` use the store only, so\n * this service (and Better Auth, loaded on first use) stays out of the initial bundle until a page injects it.\n *\n * State is signal-based and backed by the client-readable `lt-auth-state` cookie, so SSR renders the signed-in shell\n * from the request cookie (UI gating only; the session itself is the httpOnly `iam.session_token`).\n *\n * Transport: session cookie first; when cookies do not work (401 while the state says signed in) the service switches\n * to JWT mode (`GET /iam/token`, Bearer header) and refreshes a stale JWT once per burst. All credentials are SHA-256\n * hashed before they leave the browser.\n */\n@Service()\nexport class LtAuthService {\n private readonly store = inject(LtAuthStore);\n private readonly api = inject(LtApiUrlService);\n private readonly options = inject(LT_AUTH_OPTIONS);\n private readonly i18n = injectLtAuthI18n();\n private clientRequest: Promise<LtAuthClient> | null = null;\n\n // ── state (the store's signals) ────────────────────────────────────────────────────────────────────────────────────\n /** Signed-in user (from the auth-state cookie, refreshed by `validateSession()` and every sign-in). */\n readonly user: Signal<LtUser | null> = this.store.user;\n /** Whether a user is signed in (UI gating; the backend decides). */\n readonly isAuthenticated: Signal<boolean> = this.store.isAuthenticated;\n /** Current transport. */\n readonly authMode: Signal<LtAuthMode> = this.store.authMode;\n /** Whether the JWT fallback is active. */\n readonly isJwtMode: Signal<boolean> = this.store.isJwtMode;\n /** Whether a sign-in/up/out or passkey flow is running. */\n readonly isLoading: Signal<boolean> = this.store.isLoading;\n /** Whether the user has the `admin` role (UX gate only). */\n readonly isAdmin: Signal<boolean> = this.store.isAdmin;\n /** Whether two-factor authentication is enabled for the user. */\n readonly is2FAEnabled: Signal<boolean> = this.store.is2FAEnabled;\n /** nest-server feature flags (`GET /iam/features`); fetched on the server and transferred to the browser. */\n readonly features: Signal<LtAuthFeatures> = this.store.features;\n /** Whether passkeys can be used: enabled in the options, backend feature on and WebAuthn available in this browser. */\n readonly passkeySupported: Signal<boolean> = this.store.passkeySupported;\n\n /**\n * The hashing Better Auth client (see {@link LtAuthClient}), created once per app on first use. Better Auth is loaded\n * lazily (dynamic import), so it is not part of the initial bundle; session state, guards, the interceptor and\n * {@link fetchWithAuth} never need it. The client sends every request through {@link fetchWithAuth} (the same\n * auth-aware fetch as `LT_FETCH`). During SSR it never carries the request's cookies, so do not use it on the\n * server for anything that needs the user's session. A failed load is retried on the next call.\n */\n getClient(): Promise<LtAuthClient> {\n if (!this.clientRequest) {\n this.clientRequest = createLtAuthClient({\n ...resolveLtAuthClientBase(this.api.resolve(), this.options.basePath),\n enableAdmin: this.options.enableAdmin,\n enableTwoFactor: this.options.enableTwoFactor,\n // No onTwoFactorRedirect: nest-server answers `requiresTwoFactor`, Better Auth's plugin only sees\n // `twoFactorRedirect`, so `signIn.email` recognises both itself and navigates once.\n fetch: (input, init) => this.store.fetchWithAuth(input, init),\n plugins: this.options.plugins,\n });\n this.clientRequest.catch(() => {\n this.clientRequest = null;\n });\n }\n return this.clientRequest;\n }\n\n /** The current JWT (JWT mode only), or `null`. */\n jwtToken(): string | null {\n return this.store.jwtToken();\n }\n\n // ── roles ──────────────────────────────────────────────────────────────────────────────────────────────────────────\n /** `user.role === role` or `user.roles` contains it (case-sensitive). */\n hasRole(role: string): boolean {\n return this.store.hasRole(role);\n }\n\n /** Whether the user has any of the roles. */\n hasAnyRole(...roles: string[]): boolean {\n return this.store.hasAnyRole(...roles);\n }\n\n // ── features ───────────────────────────────────────────────────────────────────────────────────────────────────────\n /** Fetch the feature flags (`GET /iam/features`, see {@link LtAuthStore.fetchFeatures}). */\n fetchFeatures(): Promise<LtAuthFeatures> {\n return this.store.fetchFeatures();\n }\n\n /** The feature flags, fetched at most once (transferred from SSR, or fetched on first call). */\n ensureFeatures(): Promise<LtAuthFeatures> {\n return this.store.ensureFeatures();\n }\n\n // ── sign-in / sign-up / sign-out ───────────────────────────────────────────────────────────────────────────────────\n /**\n * Email sign-in (password hashed). A second factor (`requiresTwoFactor` from nest-server or `twoFactorRedirect` from\n * Better Auth) navigates to `twoFactorRedirectPath` (keeping the current `redirect` query parameter) and sets no user. A JWT-shaped body token (nest-server\n * `cookies: false`) switches to JWT mode; otherwise the session cookie is used and a JWT is fetched in the background.\n */\n readonly signIn = {\n email: async <T extends { callbackURL?: string; email: string; password: string; rememberMe?: boolean }>(\n params: LtNoStrayCredential<T, 'password'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse> =>\n this.store.withLoading(async () => {\n const result = await (await this.getClient()).signIn.email(params, options);\n if (readLtAuthAnswer(result).twoFactor) {\n this.store.navigateKeepingRedirect(this.options.twoFactorRedirectPath);\n } else {\n this.store.applyAuthResult(result);\n }\n return result;\n }),\n /** Passkey sign-in (same as {@link authenticateWithPasskey}). */\n passkey: (): Promise<LtPasskeyAuthResult> => this.authenticateWithPasskey(),\n };\n\n /**\n * Email sign-up (password hashed), same token handling as sign-in. With email verification nest-server answers\n * `emailVerificationRequired: true` and revokes the session: no user is set, the caller leads to the verify page.\n */\n readonly signUp = {\n email: async <T extends { callbackURL?: string; email: string; name: string; password: string }>(\n params: LtNoStrayCredential<T, 'password'>,\n options?: LtAuthCallOptions,\n ): Promise<LtAuthResponse> =>\n this.store.withLoading(async () => {\n const result = await (await this.getClient()).signUp.email(params, options);\n this.store.applyAuthResult(result);\n return result;\n }),\n };\n\n /**\n * Sign out: sign-out hooks (offline sync flush), `POST /iam/sign-out`, local state cleared, after-hooks (sync wipe).\n * The local state is cleared even when the request fails (offline device). See {@link LtAuthStore.signOut}.\n */\n signOut(options?: LtAuthCallOptions): Promise<void> {\n return this.store.signOut(options);\n }\n\n // ── session ────────────────────────────────────────────────────────────────────────────────────────────────────────\n /** Store the user in state and cookie (browser always; SSR never writes). `image: null` becomes `undefined`. */\n setUser(user: LtUserInput | null, mode: LtAuthMode = 'cookie'): void {\n this.store.setUser(user, mode);\n }\n\n /** Clear local auth state and cookies (browser). Does not delete offline data (use `signOut()` for that). */\n clearUser(): void {\n this.store.clearUser();\n }\n\n /**\n * Validate the session (app start / hard reload). Server: answers from the cookie, no backend call. Browser:\n * `GET /iam/get-session` (≤ 10 s); a user is merged with the cached one (nest-server-only fields kept, authz keys\n * fail-closed); no user but a cached one → `true` (e.g. right after 2FA); else `false`.\n */\n validateSession(): Promise<boolean> {\n return this.store.validateSession();\n }\n\n /** The auth-aware fetch, also provided as `LT_FETCH` (see {@link LtAuthStore.fetchWithAuth}). */\n fetchWithAuth(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {\n return this.store.fetchWithAuth(input, init);\n }\n\n /** Token to retry a request with after a 401 (see {@link LtAuthStore.recoverFrom401}). */\n recoverFrom401(url: string, sentToken: string | null): Promise<string | null> {\n return this.store.recoverFrom401(url, sentToken);\n }\n\n /** Session-loss handling for a 401 from `url` (see `LtUnauthorizedHandler`). */\n handleUnauthorized(url?: string): Promise<boolean> {\n return this.store.handleUnauthorized(url);\n }\n\n /** `GET /iam/token` → store the JWT and switch to JWT mode. One request per burst. Browser only. */\n switchToJwtMode(): Promise<boolean> {\n return this.store.switchToJwtMode();\n }\n\n /** Re-mint the JWT (JWT mode only). */\n refreshJwtToken(): Promise<boolean> {\n return this.store.refreshJwtToken();\n }\n\n // ── passkeys (WebAuthn, nest-server challengeId) ───────────────────────────────────────────────────────────────────\n /** Passkey sign-in: options → `navigator.credentials.get` → `verify-authentication` (`challengeId`) → user set. */\n async authenticateWithPasskey(): Promise<LtPasskeyAuthResult> {\n if (!hasWebAuthn()) {\n return { error: this.i18n.t('lt.auth.passkeyUnsupported'), success: false };\n }\n return this.store.withLoading(async () => {\n try {\n const optionsResponse = await this.store.fetchWithAuth(this.store.authUrl('/passkey/generate-authenticate-options'), { method: 'GET' });\n if (!optionsResponse.ok) {\n return { error: this.i18n.t('lt.auth.passkeyError'), success: false };\n }\n const options = (await optionsResponse.json()) as LtPasskeyAuthenticationOptions;\n const credential = (await navigator.credentials.get({\n publicKey: {\n allowCredentials: (options.allowCredentials ?? []).map((descriptor) => ({\n ...descriptor,\n id: typeof descriptor.id === 'string' ? ltBase64UrlToUint8Array(descriptor.id).buffer : descriptor.id,\n })) as PublicKeyCredentialDescriptor[],\n challenge: ltBase64UrlToUint8Array(options.challenge).buffer,\n rpId: options.rpId,\n timeout: options.timeout,\n userVerification: options.userVerification,\n },\n })) as PublicKeyCredential | null;\n if (!credential) {\n return { error: this.i18n.t('lt.auth.noPasskeySelected'), success: false };\n }\n const assertion = credential.response as AuthenticatorAssertionResponse;\n const authResponse = await this.store.fetchWithAuth(this.store.authUrl('/passkey/verify-authentication'), {\n body: JSON.stringify({\n challengeId: options.challengeId,\n response: {\n clientExtensionResults: credential.getClientExtensionResults?.() ?? {},\n id: credential.id,\n rawId: ltArrayBufferToBase64Url(credential.rawId),\n response: {\n authenticatorData: ltArrayBufferToBase64Url(assertion.authenticatorData),\n clientDataJSON: ltArrayBufferToBase64Url(assertion.clientDataJSON),\n signature: ltArrayBufferToBase64Url(assertion.signature),\n userHandle: assertion.userHandle ? ltArrayBufferToBase64Url(assertion.userHandle) : null,\n },\n type: credential.type,\n },\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n });\n const result = ((await authResponse.json().catch(() => ({}))) ?? {}) as { message?: string; session?: { token: string }; user?: LtUserInput };\n if (!authResponse.ok) {\n return { error: result.message || this.i18n.t('lt.auth.passkeyFailed'), success: false };\n }\n // A JWT in the answer (nest-server with `cookies: false`) → JWT mode; otherwise the verification set the session\n // cookie. The opaque session token (`session.token`, or `token` from older servers) is never stored as JWT (it\n // would land in a script-readable cookie), and never returned: the caller gets success and user only. Without\n // user in the answer it is loaded from the session.\n const user = await this.store.adoptVerifiedSession(result);\n if (!user) {\n return { error: this.i18n.t('lt.auth.passkeyFailed'), success: false };\n }\n return { success: true, user };\n } catch (error) {\n if (error instanceof Error && error.name === 'NotAllowedError') {\n return { error: this.i18n.t('lt.auth.passkeyAborted'), success: false };\n }\n return { error: error instanceof Error && error.message ? error.message : this.i18n.t('lt.auth.passkeyFailed'), success: false };\n }\n });\n }\n\n /** Register a passkey for the signed-in user (`challengeId` body as nest-server expects). */\n async registerPasskey(name?: string): Promise<LtPasskeyRegisterResult> {\n if (!hasWebAuthn()) {\n return { error: this.i18n.t('lt.auth.passkeyUnsupported'), success: false };\n }\n return this.store.withLoading(async () => {\n try {\n const optionsResponse = await this.store.fetchWithAuth(this.store.authUrl('/passkey/generate-register-options'), { method: 'GET' });\n if (!optionsResponse.ok) {\n const body = (await optionsResponse.json().catch(() => ({}))) as { message?: string } | null;\n return { error: body?.message || this.i18n.t('lt.auth.registerOptionsError'), success: false };\n }\n const options = (await optionsResponse.json()) as LtPasskeyRegistrationOptions;\n const credential = (await navigator.credentials.create({\n publicKey: {\n attestation: options.attestation,\n authenticatorSelection: options.authenticatorSelection,\n challenge: ltBase64UrlToUint8Array(options.challenge).buffer,\n excludeCredentials: (options.excludeCredentials ?? []).map((descriptor) => ({\n ...descriptor,\n id: ltBase64UrlToUint8Array(descriptor.id).buffer,\n })) as PublicKeyCredentialDescriptor[],\n pubKeyCredParams: options.pubKeyCredParams,\n rp: options.rp,\n timeout: options.timeout,\n user: { ...options.user, id: ltBase64UrlToUint8Array(options.user.id).buffer },\n },\n })) as PublicKeyCredential | null;\n if (!credential) {\n return { error: this.i18n.t('lt.auth.passkeyCreationAborted'), success: false };\n }\n const attestation = credential.response as AuthenticatorAttestationResponse;\n const registerResponse = await this.store.fetchWithAuth(this.store.authUrl('/passkey/verify-registration'), {\n body: JSON.stringify({\n challengeId: options.challengeId,\n name,\n response: {\n clientExtensionResults: credential.getClientExtensionResults?.() ?? {},\n id: credential.id,\n rawId: ltArrayBufferToBase64Url(credential.rawId),\n response: {\n attestationObject: ltArrayBufferToBase64Url(attestation.attestationObject),\n clientDataJSON: ltArrayBufferToBase64Url(attestation.clientDataJSON),\n transports: attestation.getTransports?.() ?? [],\n },\n type: credential.type,\n },\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n });\n const result = (await registerResponse.json().catch(() => ({}))) as { message?: string } | null;\n if (!registerResponse.ok) {\n return { error: result?.message || this.i18n.t('lt.auth.passkeyRegisterFailed'), success: false };\n }\n return { passkey: result, success: true };\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === 'NotAllowedError') {\n return { error: this.i18n.t('lt.auth.passkeyCreationAborted'), success: false };\n }\n // The authenticator already holds a credential of this account (it was in excludeCredentials).\n if (error.name === 'InvalidStateError' || error.message.includes('already registered')) {\n return { error: this.i18n.t('lt.auth.passkeyAlreadyRegistered'), success: false };\n }\n return { error: error.message || this.i18n.t('lt.auth.passkeyRegisterFailed'), success: false };\n }\n return { error: this.i18n.t('lt.auth.passkeyRegisterFailed'), success: false };\n }\n });\n }\n\n /** Passkey management of the signed-in user. */\n readonly passkey = {\n delete: async (id: string): Promise<LtAuthResponse> => (await this.getClient()).passkey.deletePasskey({ id }),\n list: async (): Promise<LtAuthResponse<LtPasskey[]>> => (await this.getClient()).passkey.listUserPasskeys(),\n update: async (id: string, name: string): Promise<LtAuthResponse> => (await this.getClient()).passkey.updatePasskey({ id, name }),\n };\n\n // ── two-factor (TOTP + backup codes) ───────────────────────────────────────────────────────────────────────────────\n /**\n * Two-factor methods (passwords hashed). `verifyTotp` / `verifyBackupCode` set the user on success and return the answer\n * without Better Auth's opaque session token (a JWT of `cookies: false` stays).\n */\n readonly twoFactor = {\n disable: async <T extends { password: string }>(params: LtNoStrayCredential<T, 'password'>): Promise<LtAuthResponse> => (await this.getClient()).twoFactor.disable(params),\n enable: async <T extends { issuer?: string; password: string }>(params: LtNoStrayCredential<T, 'password'>): Promise<LtAuthResponse<LtTwoFactorEnableResult>> =>\n (await this.getClient()).twoFactor.enable(params),\n generateBackupCodes: async <T extends { password: string }>(params: LtNoStrayCredential<T, 'password'>): Promise<LtAuthResponse<{ backupCodes: string[] }>> =>\n (await this.getClient()).twoFactor.generateBackupCodes(params),\n verifyBackupCode: async (params: { code: string; trustDevice?: boolean }): Promise<LtAuthResponse> =>\n this.adoptVerifiedSession(await (await this.getClient()).twoFactor.verifyBackupCode(params)),\n verifyTotp: async (params: { code: string; trustDevice?: boolean }): Promise<LtAuthResponse> =>\n this.adoptVerifiedSession(await (await this.getClient()).twoFactor.verifyTotp(params)),\n };\n\n // ── password & email ───────────────────────────────────────────────────────────────────────────────────────────────\n /** `POST /iam/change-password` (both passwords hashed; `revokeOtherSessions` forwarded). */\n async changePassword<T extends { currentPassword: string; newPassword: string; revokeOtherSessions?: boolean }>(\n params: LtNoStrayCredential<T, 'currentPassword' | 'newPassword'>,\n ): Promise<LtAuthResponse> {\n return (await this.getClient()).changePassword(params);\n }\n\n /** `POST /iam/request-password-reset`; `redirectTo` defaults to `<siteUrl>/auth/reset-password` (absolute, never user-derived). */\n async requestPasswordReset<T extends { email: string; redirectTo?: string }>(params: LtNoStrayCredential<T, never>): Promise<LtAuthResponse> {\n const redirectTo = params.redirectTo || this.absoluteAppUrl('/auth/reset-password');\n return (await this.getClient()).requestPasswordReset({ ...params, redirectTo });\n }\n\n /** `POST /iam/reset-password` with `{ newPassword (hashed), token }`. */\n async resetPassword<T extends { newPassword: string; token: string }>(params: LtNoStrayCredential<T, 'newPassword'>): Promise<LtAuthResponse> {\n return (await this.getClient()).resetPassword(params);\n }\n\n /** `POST /iam/send-verification-email`; `callbackURL` defaults to `<siteUrl>/auth/login` (absolute). */\n async sendVerificationEmail<T extends { callbackURL?: string; email: string }>(params: LtNoStrayCredential<T, never>): Promise<LtAuthResponse> {\n const callbackURL = params.callbackURL || this.absoluteAppUrl('/auth/login');\n return (await this.getClient()).sendVerificationEmail({ ...params, callbackURL });\n }\n\n /** `GET /iam/verify-email?token=` (JSON answer without `callbackURL`). */\n async verifyEmail(params: { callbackURL?: string; token: string }): Promise<LtAuthResponse> {\n return (await this.getClient()).verifyEmail(params);\n }\n\n /**\n * Build an absolute app URL from `NG_PUBLIC_SITE_URL` (or the browser origin), for `redirectTo` / `callbackURL`.\n * Never derived from the request: on the server without a site URL it throws. Absolute input is returned unchanged.\n */\n absoluteAppUrl(path: string): string {\n return this.store.absoluteAppUrl(path);\n }\n\n /**\n * After a successful 2FA verification: see {@link LtAuthStore.adoptVerifiedSession} (a JWT in the answer → JWT mode,\n * else cookie mode; Better Auth's opaque session token is never stored as JWT, that would put the session into a\n * script-readable cookie).\n */\n private async adoptVerifiedSession(result: LtAuthResponse): Promise<LtAuthResponse> {\n if (!result || result.error || !result.data) {\n return result;\n }\n await this.store.adoptVerifiedSession(result);\n return result;\n }\n}\n\nfunction hasWebAuthn(): boolean {\n return typeof navigator !== 'undefined' && !!navigator.credentials;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as LtBugReportService } from "./lenne.tech-ng-base-bug-bug-report-service-
|
|
1
|
+
import { t as LtBugReportService } from "./lenne.tech-ng-base-bug-bug-report-service-D6slR3GD.mjs";
|
|
2
2
|
import { registerLtBugMessages } from "./lenne.tech-ng-base-bug-messages-BsP4fRQU.mjs";
|
|
3
3
|
import { emptyLtBugReportForm, formatLtBugFileSize, ltBugPrefillFromError, ltDataUrlSize } from "./lenne.tech-ng-base-bug-report-Tzlhe1Cm.mjs";
|
|
4
4
|
import * as i0 from "@angular/core";
|
|
@@ -100,7 +100,7 @@ var LtBugAttachments = class LtBugAttachments {
|
|
|
100
100
|
}
|
|
101
101
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
102
102
|
minVersion: "12.0.0",
|
|
103
|
-
version: "22.2.
|
|
103
|
+
version: "22.2.5",
|
|
104
104
|
ngImport: i0,
|
|
105
105
|
type: LtBugAttachments,
|
|
106
106
|
deps: [],
|
|
@@ -108,7 +108,7 @@ var LtBugAttachments = class LtBugAttachments {
|
|
|
108
108
|
});
|
|
109
109
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
110
110
|
minVersion: "17.0.0",
|
|
111
|
-
version: "22.2.
|
|
111
|
+
version: "22.2.5",
|
|
112
112
|
type: LtBugAttachments,
|
|
113
113
|
isStandalone: true,
|
|
114
114
|
selector: "lt-bug-attachments",
|
|
@@ -194,7 +194,7 @@ var LtBugAttachments = class LtBugAttachments {
|
|
|
194
194
|
};
|
|
195
195
|
i0.ɵɵngDeclareClassMetadata({
|
|
196
196
|
minVersion: "12.0.0",
|
|
197
|
-
version: "22.2.
|
|
197
|
+
version: "22.2.5",
|
|
198
198
|
ngImport: i0,
|
|
199
199
|
type: LtBugAttachments,
|
|
200
200
|
decorators: [{
|
|
@@ -345,7 +345,7 @@ var LtBugJourneyTimeline = class LtBugJourneyTimeline {
|
|
|
345
345
|
}
|
|
346
346
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
347
347
|
minVersion: "12.0.0",
|
|
348
|
-
version: "22.2.
|
|
348
|
+
version: "22.2.5",
|
|
349
349
|
ngImport: i0,
|
|
350
350
|
type: LtBugJourneyTimeline,
|
|
351
351
|
deps: [],
|
|
@@ -353,7 +353,7 @@ var LtBugJourneyTimeline = class LtBugJourneyTimeline {
|
|
|
353
353
|
});
|
|
354
354
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
355
355
|
minVersion: "17.0.0",
|
|
356
|
-
version: "22.2.
|
|
356
|
+
version: "22.2.5",
|
|
357
357
|
type: LtBugJourneyTimeline,
|
|
358
358
|
isStandalone: true,
|
|
359
359
|
selector: "lt-bug-journey-timeline",
|
|
@@ -430,7 +430,7 @@ var LtBugJourneyTimeline = class LtBugJourneyTimeline {
|
|
|
430
430
|
};
|
|
431
431
|
i0.ɵɵngDeclareClassMetadata({
|
|
432
432
|
minVersion: "12.0.0",
|
|
433
|
-
version: "22.2.
|
|
433
|
+
version: "22.2.5",
|
|
434
434
|
ngImport: i0,
|
|
435
435
|
type: LtBugJourneyTimeline,
|
|
436
436
|
decorators: [{
|
|
@@ -542,7 +542,7 @@ var LtBugReportDialog = class LtBugReportDialog {
|
|
|
542
542
|
}
|
|
543
543
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
544
544
|
minVersion: "12.0.0",
|
|
545
|
-
version: "22.2.
|
|
545
|
+
version: "22.2.5",
|
|
546
546
|
ngImport: i0,
|
|
547
547
|
type: LtBugReportDialog,
|
|
548
548
|
deps: [],
|
|
@@ -550,7 +550,7 @@ var LtBugReportDialog = class LtBugReportDialog {
|
|
|
550
550
|
});
|
|
551
551
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
552
552
|
minVersion: "17.0.0",
|
|
553
|
-
version: "22.2.
|
|
553
|
+
version: "22.2.5",
|
|
554
554
|
type: LtBugReportDialog,
|
|
555
555
|
isStandalone: true,
|
|
556
556
|
selector: "lt-bug-report-dialog",
|
|
@@ -756,7 +756,7 @@ var LtBugReportDialog = class LtBugReportDialog {
|
|
|
756
756
|
};
|
|
757
757
|
i0.ɵɵngDeclareClassMetadata({
|
|
758
758
|
minVersion: "12.0.0",
|
|
759
|
-
version: "22.2.
|
|
759
|
+
version: "22.2.5",
|
|
760
760
|
ngImport: i0,
|
|
761
761
|
type: LtBugReportDialog,
|
|
762
762
|
decorators: [{
|
|
@@ -822,4 +822,4 @@ function fieldError(field) {
|
|
|
822
822
|
}
|
|
823
823
|
export { openLtBugReportDialog };
|
|
824
824
|
|
|
825
|
-
//# sourceMappingURL=lenne.tech-ng-base-bug-bug-report-dialog-
|
|
825
|
+
//# sourceMappingURL=lenne.tech-ng-base-bug-bug-report-dialog-f6VNOWnW.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lenne.tech-ng-base-bug-bug-report-dialog-BRzVmKJr.mjs","names":["nextId"],"sources":["../../../projects/ng-base/bug/src/lib/ui/bug-attachments.ts","../../../projects/ng-base/bug/src/lib/ui/bug-attachments.html","../../../projects/ng-base/bug/src/lib/ui/bug-journey-timeline.ts","../../../projects/ng-base/bug/src/lib/ui/bug-journey-timeline.html","../../../projects/ng-base/bug/src/lib/ui/bug-report-dialog.ts","../../../projects/ng-base/bug/src/lib/ui/bug-report-dialog.html"],"sourcesContent":["import { Component, inject, input, output, signal } from '@angular/core';\nimport { LtI18nService } from '@lenne.tech/ng-base';\nimport { HlmBadgeImports, HlmButtonImports } from '@lenne.tech/ng-base/ui';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { lucideFileText, lucidePaperclip, lucideUpload, lucideX } from '@ng-icons/lucide';\n\nimport type { LtBugAttachment } from '../bug-types';\nimport { formatLtBugFileSize } from '../report';\n\nlet nextId = 0;\n\n/**\n * Attachments of a bug report (the counterpart of `AttachmentsList.vue`): drop zone with a file button, list with\n * thumbnail, name, size, screenshot badge and a labelled remove button. Files are read as data URLs; files above\n * `maxFileSize` MB or beyond `maxFiles` are rejected with a message (`role=\"alert\"`).\n */\n@Component({\n selector: 'lt-bug-attachments',\n imports: [HlmBadgeImports, HlmButtonImports, NgIcon],\n providers: [provideIcons({ lucideFileText, lucidePaperclip, lucideUpload, lucideX })],\n templateUrl: './bug-attachments.html',\n host: { class: 'flex flex-col gap-3' },\n})\nexport class LtBugAttachments {\n /** `accept` of the file input (default as `@lenne.tech/bug.lt`). */\n readonly accept = input('image/*,.pdf,.doc,.docx,.txt,.log');\n readonly attachments = input.required<readonly LtBugAttachment[]>();\n readonly disabled = input(false);\n /** Maximum number of attachments including the screenshot (default 10, the server limit). */\n readonly maxFiles = input(10);\n /** Maximum file size in MB (default 10). */\n readonly maxFileSize = input(10);\n\n /** Files were read and accepted. */\n readonly added = output<LtBugAttachment[]>();\n /** The user removed an attachment (its id). */\n readonly removed = output<string>();\n\n protected readonly i18n = inject(LtI18nService);\n protected readonly hintId = `lt-bug-attachments-${nextId++}-hint`;\n protected readonly dragging = signal(false);\n protected readonly rejections = signal<string[]>([]);\n protected readonly formatSize = formatLtBugFileSize;\n private dragDepth = 0;\n\n protected openFileDialog(fileInput: HTMLInputElement): void {\n if (!this.disabled()) {\n fileInput.click();\n }\n }\n\n protected onInputChange(fileInput: HTMLInputElement): void {\n const files = Array.from(fileInput.files ?? []);\n fileInput.value = '';\n void this.addFiles(files);\n }\n\n protected onDragEnter(event: DragEvent): void {\n event.preventDefault();\n if (!this.disabled()) {\n this.dragDepth++;\n this.dragging.set(true);\n }\n }\n\n protected onDragOver(event: DragEvent): void {\n event.preventDefault();\n if (event.dataTransfer) {\n event.dataTransfer.dropEffect = this.disabled() ? 'none' : 'copy';\n }\n }\n\n protected onDragLeave(event: DragEvent): void {\n event.preventDefault();\n this.dragDepth = Math.max(0, this.dragDepth - 1);\n if (this.dragDepth === 0) {\n this.dragging.set(false);\n }\n }\n\n protected onDrop(event: DragEvent): void {\n event.preventDefault();\n this.dragDepth = 0;\n this.dragging.set(false);\n if (!this.disabled()) {\n void this.addFiles(Array.from(event.dataTransfer?.files ?? []));\n }\n }\n\n /** Read, validate and emit files; returns the accepted attachments. */\n async addFiles(files: File[]): Promise<LtBugAttachment[]> {\n if (!files.length) {\n return [];\n }\n const messages: string[] = [];\n const accepted: LtBugAttachment[] = [];\n const maxBytes = this.maxFileSize() * 1024 * 1024;\n let count = this.attachments().length;\n for (const file of files) {\n if (file.size > maxBytes) {\n messages.push(this.i18n.t('lt.bug.fileTooLarge', { max: this.maxFileSize(), name: file.name }));\n continue;\n }\n if (count >= this.maxFiles()) {\n messages.push(this.i18n.t('lt.bug.tooManyFiles', { max: this.maxFiles(), name: file.name }));\n continue;\n }\n try {\n const data = await readAsDataUrl(file);\n accepted.push({\n data,\n id: `attachment-${Date.now()}-${nextId++}`,\n name: file.name,\n size: file.size,\n type: file.type || 'application/octet-stream',\n ...(file.type.startsWith('image/') ? { preview: data } : {}),\n });\n count++;\n } catch {\n messages.push(this.i18n.t('lt.bug.fileReadFailed', { name: file.name }));\n }\n }\n this.rejections.set(messages);\n if (accepted.length) {\n this.added.emit(accepted);\n }\n return accepted;\n }\n\n protected removeLabel(attachment: LtBugAttachment): string {\n return this.i18n.t('lt.bug.removeAttachment', { name: attachment.name });\n }\n}\n\nfunction readAsDataUrl(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => (typeof reader.result === 'string' ? resolve(reader.result) : reject(new Error('Unexpected FileReader result')));\n reader.onerror = () => reject(reader.error ?? new Error('FileReader error'));\n reader.onabort = () => reject(new Error('FileReader aborted'));\n reader.readAsDataURL(file);\n });\n}\n","<div\n class=\"border-border flex flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed p-4 text-center transition-colors data-[disabled=true]:opacity-50 data-[dragging=true]:border-primary data-[dragging=true]:bg-primary/5\"\n data-slot=\"dropzone\"\n [attr.data-dragging]=\"dragging() || null\"\n [attr.data-disabled]=\"disabled() || null\"\n (dragenter)=\"onDragEnter($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n <ng-icon name=\"lucideUpload\" class=\"text-muted-foreground text-2xl\" aria-hidden=\"true\" />\n <button hlmBtn type=\"button\" variant=\"outline\" size=\"sm\" [disabled]=\"disabled()\" [attr.aria-describedby]=\"hintId\" (click)=\"openFileDialog(fileInput)\">\n {{ i18n.t('lt.bug.selectFiles') }}\n </button>\n <p [id]=\"hintId\" class=\"text-muted-foreground text-xs\">{{ i18n.t('lt.bug.dropHint') }} · {{ i18n.t('lt.bug.maxFileSize', { max: maxFileSize() }) }}</p>\n <input\n #fileInput\n type=\"file\"\n multiple\n class=\"sr-only\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n data-slot=\"file-input\"\n [accept]=\"accept()\"\n [disabled]=\"disabled()\"\n (change)=\"onInputChange(fileInput)\"\n />\n</div>\n\n@if (rejections().length > 0) {\n <ul class=\"text-error flex flex-col gap-1 text-sm\" role=\"alert\" data-slot=\"rejections\">\n @for (message of rejections(); track $index) {\n <li>{{ message }}</li>\n }\n </ul>\n}\n\n@if (attachments().length > 0) {\n <div class=\"flex flex-col gap-2\">\n <p class=\"text-sm font-medium\" [id]=\"hintId + '-list'\">{{ i18n.t('lt.bug.attachmentsCount', { count: attachments().length }) }}</p>\n <ul class=\"flex flex-col gap-1\" data-slot=\"attachment-list\" [attr.aria-labelledby]=\"hintId + '-list'\">\n @for (attachment of attachments(); track attachment.id) {\n <li class=\"bg-muted/50 flex items-center gap-3 rounded-lg p-2\" data-slot=\"attachment\" [attr.data-screenshot]=\"attachment.isScreenshot || null\">\n @if (attachment.preview) {\n <img class=\"size-10 shrink-0 rounded object-cover\" [src]=\"attachment.preview\" [alt]=\"attachment.name\" />\n } @else {\n <ng-icon\n class=\"text-muted-foreground shrink-0 text-2xl\"\n aria-hidden=\"true\"\n [name]=\"attachment.type.startsWith('text/') || attachment.type.includes('pdf') ? 'lucideFileText' : 'lucidePaperclip'\"\n />\n }\n <div class=\"flex min-w-0 flex-1 flex-col\">\n <div class=\"flex min-w-0 items-center gap-2\">\n <span class=\"truncate text-sm font-medium\" [title]=\"attachment.name\">{{ attachment.name }}</span>\n @if (attachment.isScreenshot) {\n <span hlmBadge variant=\"secondary\">{{ i18n.t('lt.bug.screenshot') }}</span>\n }\n </div>\n <span class=\"text-muted-foreground text-xs\">{{ formatSize(attachment.size) }}</span>\n </div>\n <button\n hlmBtn\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n data-action=\"remove\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"removeLabel(attachment)\"\n (click)=\"removed.emit(attachment.id)\"\n >\n <ng-icon name=\"lucideX\" aria-hidden=\"true\" />\n </button>\n </li>\n }\n </ul>\n </div>\n}\n","import { Component, computed, inject, input, signal } from '@angular/core';\nimport { LtI18nService } from '@lenne.tech/ng-base';\nimport { HlmBadgeImports, HlmNativeSelectImports } from '@lenne.tech/ng-base/ui';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport {\n lucideAppWindow,\n lucideArrowUpDown,\n lucideCircleAlert,\n lucideCircleArrowRight,\n lucideClock,\n lucideFileText,\n lucideHand,\n lucideInfo,\n lucideKeyboard,\n lucideMousePointerClick,\n lucidePencil,\n lucideSend,\n lucideX,\n} from '@ng-icons/lucide';\n\nimport type { LtUserInteractionEvent } from '../bug-types';\n\nlet nextId = 0;\n\nconst ICONS: Record<string, string> = {\n click: 'lucideMousePointerClick',\n error: 'lucideCircleAlert',\n form_change: 'lucideFileText',\n form_submit: 'lucideSend',\n hover: 'lucideHand',\n input_change: 'lucidePencil',\n keyboard: 'lucideKeyboard',\n modal_close: 'lucideX',\n modal_open: 'lucideAppWindow',\n navigation: 'lucideCircleArrowRight',\n scroll: 'lucideArrowUpDown',\n};\n\nconst COLORS: Record<string, string> = {\n error: 'text-error',\n form_change: 'text-success',\n form_submit: 'text-success',\n input_change: 'text-warning',\n keyboard: 'text-info',\n modal_close: 'text-error',\n modal_open: 'text-info',\n navigation: 'text-info',\n};\n\n/**\n * User journey timeline (the counterpart of `UserJourneyTimeline.vue`): filter by event type, one row per event with\n * icon, type, time, target and details (text, value, URL, scroll position, key, error).\n */\n@Component({\n selector: 'lt-bug-journey-timeline',\n imports: [HlmBadgeImports, HlmNativeSelectImports, NgIcon],\n providers: [\n provideIcons({\n lucideAppWindow,\n lucideArrowUpDown,\n lucideCircleAlert,\n lucideCircleArrowRight,\n lucideClock,\n lucideFileText,\n lucideHand,\n lucideInfo,\n lucideKeyboard,\n lucideMousePointerClick,\n lucidePencil,\n lucideSend,\n lucideX,\n }),\n ],\n templateUrl: './bug-journey-timeline.html',\n host: { class: 'flex flex-col gap-3' },\n})\nexport class LtBugJourneyTimeline {\n readonly events = input.required<readonly LtUserInteractionEvent[]>();\n\n protected readonly i18n = inject(LtI18nService);\n protected readonly filterId = `lt-bug-journey-${nextId++}-filter`;\n protected readonly filter = signal('all');\n protected readonly types = computed(() => ['all', ...new Set(this.events().map((event) => event.type))]);\n protected readonly filtered = computed(() => {\n const filter = this.filter();\n return filter === 'all' ? this.events() : this.events().filter((event) => event.type === filter);\n });\n\n protected typeLabel(type: string): string {\n if (type === 'all') {\n return this.i18n.t('lt.bug.allEventTypes');\n }\n const key = `lt.bug.events.${type}`;\n const label = this.i18n.t(key);\n return label === key ? type.replace(/_/g, ' ').replace(/\\b\\w/g, (letter) => letter.toUpperCase()) : label;\n }\n\n protected icon(type: string): string {\n return ICONS[type] ?? 'lucideInfo';\n }\n\n protected color(type: string): string {\n return COLORS[type] ?? 'text-primary';\n }\n\n protected time(timestamp: string): string {\n const date = new Date(timestamp);\n return Number.isNaN(date.getTime()) ? '' : date.toLocaleTimeString(this.i18n.locale() === 'en' ? 'en-GB' : 'de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n }\n\n /** Details line (same order as `@lenne.tech/bug.lt`). */\n protected details(event: LtUserInteractionEvent): string {\n const metadata = event.metadata;\n if (!metadata) {\n return '';\n }\n const parts: string[] = [];\n if (metadata.text) {\n parts.push(`${this.i18n.t('lt.bug.metaText')}: ${metadata.text.length > 30 ? `${metadata.text.slice(0, 30)}…` : metadata.text}`);\n }\n if (metadata.value) {\n parts.push(`${this.i18n.t('lt.bug.metaValue')}: ${metadata.value}`);\n }\n if (metadata.url && metadata.url !== metadata.toUrl) {\n parts.push(`${this.i18n.t('lt.bug.metaUrl')}: ${metadata.url}`);\n }\n if (metadata.toUrl) {\n parts.push(`→ ${metadata.toUrl}`);\n }\n if (metadata.scrollPosition !== undefined) {\n parts.push(`${this.i18n.t('lt.bug.metaPosition')}: ${metadata.scrollPosition}px`);\n }\n if (metadata.key) {\n parts.push(`${this.i18n.t('lt.bug.metaKey')}: ${metadata.key}`);\n }\n if (metadata.errorMessage) {\n parts.push(`${this.i18n.t('lt.bug.metaError')}: ${metadata.errorMessage}`);\n }\n return parts.join(' | ');\n }\n}\n","<div class=\"flex flex-col gap-1.5\">\n <label class=\"text-sm font-medium\" [for]=\"filterId\">{{ i18n.t('lt.bug.journeyFilter') }}</label>\n <hlm-native-select size=\"sm\" [selectId]=\"filterId\" [value]=\"filter()\" (valueChange)=\"filter.set($event || 'all')\">\n @for (type of types(); track type) {\n <option hlmNativeSelectOption [value]=\"type\">{{ typeLabel(type) }}</option>\n }\n </hlm-native-select>\n</div>\n\n@if (filtered().length > 0) {\n <ol class=\"flex max-h-96 flex-col gap-2 overflow-y-auto\" data-slot=\"journey-list\" [attr.aria-label]=\"i18n.t('lt.bug.journeyEvents')\">\n @for (event of filtered(); track $index) {\n <li class=\"border-border flex items-start gap-3 rounded-lg border p-3\" data-slot=\"journey-event\" [attr.data-type]=\"event.type\">\n <ng-icon class=\"mt-0.5 shrink-0 text-lg\" aria-hidden=\"true\" [class]=\"color(event.type)\" [name]=\"icon(event.type)\" />\n <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div class=\"flex items-center justify-between gap-2\">\n <span hlmBadge variant=\"outline\">{{ typeLabel(event.type) }}</span>\n <time class=\"text-muted-foreground text-xs\" [attr.datetime]=\"event.timestamp\">{{ time(event.timestamp) }}</time>\n </div>\n <p class=\"truncate text-sm font-medium\" [title]=\"event.target\">{{ event.target }}</p>\n @if (details(event); as text) {\n <p class=\"text-muted-foreground truncate text-xs\" [title]=\"text\">{{ text }}</p>\n }\n </div>\n </li>\n }\n </ol>\n} @else {\n <div class=\"text-muted-foreground flex flex-col items-center gap-2 py-8 text-center\" data-slot=\"journey-empty\">\n <ng-icon name=\"lucideClock\" class=\"text-4xl opacity-50\" aria-hidden=\"true\" />\n <p class=\"text-sm\">{{ i18n.t('lt.bug.journeyEmpty') }}</p>\n </div>\n}\n","import { afterNextRender, Component, computed, ElementRef, inject, Injector, signal } from '@angular/core';\nimport { disabled, form, FormField, maxLength, required, submit, type FieldTree } from '@angular/forms/signals';\nimport { LT_I18N_OPTIONS, LtI18nService } from '@lenne.tech/ng-base';\nimport {\n HlmButtonImports,\n HlmCollapsibleImports,\n HlmDialogImports,\n HlmSpinnerImports,\n HlmSwitchImports,\n HlmTextareaImports,\n injectLtOverlayRef,\n LtFormField,\n LtNotice,\n LtOverlayService,\n LtSelect,\n LtTextField,\n type LtOverlayRef,\n type LtSelectOption,\n} from '@lenne.tech/ng-base/ui';\nimport { ltFocusFirstInvalid } from '@lenne.tech/ng-base/utils';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { lucideChevronDown, lucideX } from '@ng-icons/lucide';\n\nimport { LtBugReportService } from '../bug-report-service';\nimport type { LtBugAttachment, LtBugReportType, LtUserInteractionEvent } from '../bug-types';\nimport { registerLtBugMessages } from '../i18n/messages';\nimport { emptyLtBugReportForm, ltBugPrefillFromError, ltDataUrlSize, type LtBugReportFormValue, type LtBugReportIncludes } from '../report';\nimport { LtBugAttachments } from './bug-attachments';\nimport { LtBugJourneyTimeline } from './bug-journey-timeline';\n\nconst TYPES: readonly LtBugReportType[] = ['bug', 'feature', 'enhancement', 'other'];\n\n/**\n * Content of the bug report dialog (opened by `<lt-bug-report>` through {@link openLtBugReportDialog}): title, type,\n * description, expected behavior, steps, attachments with the screenshot, switches for the collected data and the user\n * journey preview. Signal Forms validation; a failed submit focuses the first invalid field. Loaded on first use.\n */\n@Component({\n selector: 'lt-bug-report-dialog',\n imports: [\n FormField,\n HlmButtonImports,\n HlmCollapsibleImports,\n HlmDialogImports,\n HlmSpinnerImports,\n HlmSwitchImports,\n HlmTextareaImports,\n LtBugAttachments,\n LtBugJourneyTimeline,\n LtFormField,\n LtNotice,\n LtSelect,\n LtTextField,\n NgIcon,\n ],\n providers: [provideIcons({ lucideChevronDown, lucideX })],\n templateUrl: './bug-report-dialog.html',\n host: { 'data-lt-bug-ui': '', class: 'grid min-w-0 gap-6' },\n})\nexport class LtBugReportDialog {\n protected readonly bug = inject(LtBugReportService);\n protected readonly i18n = registered(inject(LtI18nService), inject(LT_I18N_OPTIONS).messages);\n private readonly ref = injectLtOverlayRef<boolean>();\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly injector = inject(Injector);\n\n protected readonly model = signal<LtBugReportFormValue>(initialForm(this.bug.lastError()));\n protected readonly reportForm = form(this.model, (path) => {\n required(path.title, { message: this.i18n.t('lt.bug.titleRequired') });\n maxLength(path.title, 255);\n required(path.type, { message: this.i18n.t('lt.bug.typeRequired') });\n required(path.description, { message: this.i18n.t('lt.bug.descriptionRequired') });\n disabled(path, () => this.bug.isSubmitting());\n });\n\n protected readonly attachments = signal<LtBugAttachment[]>(initialAttachments(this.bug.screenshot()));\n protected readonly includes = signal<LtBugReportIncludes>({ browserInfo: true, consoleLogs: true, networkRequests: true, userJourney: true });\n protected readonly showAdditional = signal(false);\n protected readonly showJourney = signal(false);\n\n /** Snapshot of the collected data at opening time. */\n protected readonly journey: LtUserInteractionEvent[] = this.bug.getUserJourney();\n protected readonly consoleCount = this.bug.getConsoleLogs().length;\n protected readonly networkCount = this.bug.getNetworkRequests().length;\n\n protected readonly options = this.bug.options;\n protected readonly journeyEnabled = this.options.enableUserJourney && this.options.userJourney.enabled;\n protected readonly hasScreenshot = computed(() => this.attachments().some((attachment) => attachment.isScreenshot));\n protected readonly typeOptions = computed<LtSelectOption<LtBugReportType>[]>(() => TYPES.map((type) => ({ label: this.i18n.t(`lt.bug.types.${type}`), value: type })));\n protected readonly typeError = computed(() => fieldError(this.reportForm.type));\n protected readonly descriptionError = computed(() => fieldError(this.reportForm.description));\n\n protected setInclude(key: keyof LtBugReportIncludes, value: boolean): void {\n this.includes.update((current) => ({ ...current, [key]: value }));\n }\n\n protected addAttachments(added: LtBugAttachment[]): void {\n this.attachments.update((current) => [...current, ...added]);\n }\n\n protected removeAttachment(id: string): void {\n this.attachments.update((current) => current.filter((attachment) => attachment.id !== id));\n }\n\n protected cancel(): void {\n if (!this.bug.isSubmitting()) {\n this.ref.close(false);\n }\n }\n\n protected async onSubmit(event?: Event): Promise<void> {\n event?.preventDefault();\n if (this.bug.isSubmitting()) {\n return;\n }\n await submit(this.reportForm, {\n action: async () => {\n try {\n await this.bug.submit(await this.bug.buildReport(this.model(), this.attachments(), this.includes()));\n this.bug.reset();\n this.ref.close(true);\n } catch {\n // The error is shown in the dialog (and as a toast); the user can retry.\n }\n return undefined;\n },\n onInvalid: () => afterNextRender(() => ltFocusFirstInvalid(this.host.nativeElement, { offset: 0 }), { injector: this.injector }),\n });\n }\n}\n\n/**\n * Open the bug report dialog through `LtOverlayService` (focus trapped, Escape and backdrop close, focus returns to the\n * trigger). Resolves `true` after a sent report.\n */\nexport function openLtBugReportDialog(injector: Injector): LtOverlayRef<boolean> {\n return injector.get(LtOverlayService).open<boolean>(LtBugReportDialog, { contentClass: 'sm:max-w-2xl' });\n}\n\n/** Register the `lt.bug.*` texts (part of this lazy chunk) before the form schema reads its messages. */\nfunction registered(i18n: LtI18nService, appMessages: Parameters<typeof registerLtBugMessages>[1]): LtI18nService {\n registerLtBugMessages(i18n, appMessages);\n return i18n;\n}\n\nfunction initialForm(error: ReturnType<LtBugReportService['lastError']>): LtBugReportFormValue {\n return error ? { ...emptyLtBugReportForm(), ...ltBugPrefillFromError(error) } : emptyLtBugReportForm();\n}\n\nfunction initialAttachments(screenshot: string | null): LtBugAttachment[] {\n if (!screenshot) {\n return [];\n }\n return [\n { data: screenshot, id: `screenshot-${Date.now()}`, isScreenshot: true, name: 'screenshot.png', preview: screenshot, size: ltDataUrlSize(screenshot), type: 'image/png' },\n ];\n}\n\n/** First error message of a touched, invalid field. */\nfunction fieldError(field: FieldTree<unknown>): string | null {\n const state = field();\n return state.invalid() && state.touched() ? (state.errors().find((error) => !!error.message)?.message ?? null) : null;\n}\n","<div hlmDialogHeader>\n <h2 hlmDialogTitle>{{ i18n.t('lt.bug.dialogTitle') }}</h2>\n <p hlmDialogDescription>{{ i18n.t('lt.bug.dialogDescription') }}</p>\n</div>\n\n<form id=\"lt-bug-form\" class=\"flex min-w-0 flex-col gap-4\" novalidate data-slot=\"bug-report-form\" (submit)=\"onSubmit($event)\">\n @if (bug.error(); as message) {\n <lt-notice variant=\"error\" data-slot=\"bug-report-error\">\n <div class=\"flex items-start justify-between gap-3\">\n <span>{{ message }}</span>\n <button hlmBtn type=\"button\" variant=\"ghost\" size=\"icon-sm\" class=\"-my-1 shrink-0\" [attr.aria-label]=\"i18n.t('lt.bug.dismissError')\" (click)=\"bug.error.set(null)\">\n <ng-icon name=\"lucideX\" aria-hidden=\"true\" />\n </button>\n </div>\n </lt-notice>\n }\n\n <lt-text-field id=\"lt-bug-title\" [label]=\"i18n.t('lt.bug.title')\" [placeholder]=\"i18n.t('lt.bug.titlePlaceholder')\" [formField]=\"reportForm.title\" />\n\n <lt-form-field for=\"lt-bug-type\" [label]=\"i18n.t('lt.bug.type')\" [required]=\"true\" [error]=\"typeError()\">\n <lt-select id=\"lt-bug-type\" [options]=\"typeOptions()\" [placeholder]=\"i18n.t('lt.bug.typePlaceholder')\" [formField]=\"reportForm.type\" />\n </lt-form-field>\n\n <lt-form-field for=\"lt-bug-description\" [label]=\"i18n.t('lt.bug.description')\" [required]=\"true\" [error]=\"descriptionError()\">\n <textarea\n hlmTextarea\n id=\"lt-bug-description\"\n rows=\"4\"\n class=\"max-h-64\"\n [placeholder]=\"i18n.t('lt.bug.descriptionPlaceholder')\"\n [forceInvalid]=\"!!descriptionError()\"\n [formField]=\"reportForm.description\"\n ></textarea>\n </lt-form-field>\n\n <lt-form-field for=\"lt-bug-expected\" [label]=\"i18n.t('lt.bug.expectedBehavior')\">\n <textarea\n hlmTextarea\n id=\"lt-bug-expected\"\n rows=\"3\"\n class=\"max-h-48\"\n [placeholder]=\"i18n.t('lt.bug.expectedBehaviorPlaceholder')\"\n [formField]=\"reportForm.expectedBehavior\"\n ></textarea>\n </lt-form-field>\n\n <lt-form-field for=\"lt-bug-steps\" [label]=\"i18n.t('lt.bug.stepsToReproduce')\">\n <textarea\n hlmTextarea\n id=\"lt-bug-steps\"\n rows=\"3\"\n class=\"max-h-48\"\n [placeholder]=\"i18n.t('lt.bug.stepsToReproducePlaceholder')\"\n [formField]=\"reportForm.stepsToReproduce\"\n ></textarea>\n </lt-form-field>\n\n <section class=\"flex flex-col gap-2\" aria-labelledby=\"lt-bug-attachments-title\" data-slot=\"bug-report-attachments\">\n <h3 id=\"lt-bug-attachments-title\" class=\"text-sm font-medium\">\n {{ i18n.t('lt.bug.attachments') }}\n @if (hasScreenshot()) {\n <span class=\"text-muted-foreground ms-2 text-xs font-normal\">{{ i18n.t('lt.bug.screenshotCaptured') }}</span>\n }\n </h3>\n <lt-bug-attachments [attachments]=\"attachments()\" [disabled]=\"bug.isSubmitting()\" (added)=\"addAttachments($event)\" (removed)=\"removeAttachment($event)\" />\n </section>\n\n @if (options.enableBrowserInfo || options.enableConsoleLogs || options.enableNetworkRequests || journeyEnabled) {\n <div hlmCollapsible class=\"border-border rounded-lg border\" data-slot=\"bug-report-additional\" [expanded]=\"showAdditional()\" (expandedChange)=\"showAdditional.set($event)\">\n <button hlmCollapsibleTrigger type=\"button\" class=\"hover:bg-muted flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-start text-sm font-medium\">\n <span>{{ i18n.t('lt.bug.additionalInfo') }}</span>\n <ng-icon name=\"lucideChevronDown\" class=\"transition-transform duration-200\" aria-hidden=\"true\" [class.rotate-180]=\"showAdditional()\" />\n </button>\n <div hlmCollapsibleContent class=\"border-border flex flex-col gap-3 border-t p-3\">\n @if (options.enableBrowserInfo) {\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex min-w-0 flex-col\">\n <span id=\"lt-bug-include-browser-label\" class=\"text-sm font-medium\">{{ i18n.t('lt.bug.browserInfo') }}</span>\n <span id=\"lt-bug-include-browser-hint\" class=\"text-muted-foreground text-xs\">{{ i18n.t('lt.bug.browserInfoHint') }}</span>\n </div>\n <hlm-switch\n inputId=\"lt-bug-include-browser\"\n aria-labelledby=\"lt-bug-include-browser-label\"\n aria-describedby=\"lt-bug-include-browser-hint\"\n [checked]=\"includes().browserInfo\"\n [disabled]=\"bug.isSubmitting()\"\n (checkedChange)=\"setInclude('browserInfo', $event)\"\n />\n </div>\n }\n @if (options.enableConsoleLogs) {\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex min-w-0 flex-col\">\n <span id=\"lt-bug-include-console-label\" class=\"text-sm font-medium\">{{ i18n.t('lt.bug.consoleLogs') }}</span>\n <span id=\"lt-bug-include-console-hint\" class=\"text-muted-foreground text-xs\">{{ i18n.t('lt.bug.consoleLogsHint', { count: consoleCount }) }}</span>\n </div>\n <hlm-switch\n inputId=\"lt-bug-include-console\"\n aria-labelledby=\"lt-bug-include-console-label\"\n aria-describedby=\"lt-bug-include-console-hint\"\n [checked]=\"includes().consoleLogs\"\n [disabled]=\"bug.isSubmitting()\"\n (checkedChange)=\"setInclude('consoleLogs', $event)\"\n />\n </div>\n }\n @if (options.enableNetworkRequests) {\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex min-w-0 flex-col\">\n <span id=\"lt-bug-include-network-label\" class=\"text-sm font-medium\">{{ i18n.t('lt.bug.networkRequests') }}</span>\n <span id=\"lt-bug-include-network-hint\" class=\"text-muted-foreground text-xs\">{{ i18n.t('lt.bug.networkRequestsHint', { count: networkCount }) }}</span>\n </div>\n <hlm-switch\n inputId=\"lt-bug-include-network\"\n aria-labelledby=\"lt-bug-include-network-label\"\n aria-describedby=\"lt-bug-include-network-hint\"\n [checked]=\"includes().networkRequests\"\n [disabled]=\"bug.isSubmitting()\"\n (checkedChange)=\"setInclude('networkRequests', $event)\"\n />\n </div>\n }\n @if (journeyEnabled) {\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex min-w-0 flex-col\">\n <span id=\"lt-bug-include-journey-label\" class=\"text-sm font-medium\">{{ i18n.t('lt.bug.userJourney') }}</span>\n <span id=\"lt-bug-include-journey-hint\" class=\"text-muted-foreground text-xs\">{{ i18n.t('lt.bug.userJourneyHint', { count: journey.length }) }}</span>\n </div>\n <hlm-switch\n inputId=\"lt-bug-include-journey\"\n aria-labelledby=\"lt-bug-include-journey-label\"\n aria-describedby=\"lt-bug-include-journey-hint\"\n [checked]=\"includes().userJourney\"\n [disabled]=\"bug.isSubmitting()\"\n (checkedChange)=\"setInclude('userJourney', $event)\"\n />\n </div>\n }\n </div>\n </div>\n }\n\n @if (journeyEnabled && includes().userJourney && journey.length > 0) {\n <div hlmCollapsible class=\"border-border rounded-lg border\" data-slot=\"bug-report-journey\" [expanded]=\"showJourney()\" (expandedChange)=\"showJourney.set($event)\">\n <button hlmCollapsibleTrigger type=\"button\" class=\"hover:bg-muted flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-start text-sm font-medium\">\n <span>{{ i18n.t('lt.bug.journeyPreview', { count: journey.length }) }}</span>\n <ng-icon name=\"lucideChevronDown\" class=\"transition-transform duration-200\" aria-hidden=\"true\" [class.rotate-180]=\"showJourney()\" />\n </button>\n <div hlmCollapsibleContent class=\"border-border border-t p-3\">\n <lt-bug-journey-timeline [events]=\"journey\" />\n </div>\n </div>\n }\n</form>\n\n<div hlmDialogFooter>\n <button hlmBtn type=\"button\" variant=\"outline\" data-action=\"cancel\" [disabled]=\"bug.isSubmitting()\" (click)=\"cancel()\">{{ i18n.t('lt.bug.cancel') }}</button>\n <button hlmBtn type=\"submit\" form=\"lt-bug-form\" data-action=\"submit\" [attr.aria-busy]=\"bug.isSubmitting() || null\" [attr.aria-disabled]=\"bug.isSubmitting() || null\">\n @if (bug.isSubmitting()) {\n <hlm-spinner aria-hidden=\"true\" />\n {{ i18n.t('lt.bug.submitting') }}\n } @else {\n {{ i18n.t('lt.bug.submit') }}\n }\n </button>\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lenne.tech-ng-base-bug-bug-report-dialog-f6VNOWnW.mjs","names":["nextId"],"sources":["../../../projects/ng-base/bug/src/lib/ui/bug-attachments.ts","../../../projects/ng-base/bug/src/lib/ui/bug-attachments.html","../../../projects/ng-base/bug/src/lib/ui/bug-journey-timeline.ts","../../../projects/ng-base/bug/src/lib/ui/bug-journey-timeline.html","../../../projects/ng-base/bug/src/lib/ui/bug-report-dialog.ts","../../../projects/ng-base/bug/src/lib/ui/bug-report-dialog.html"],"sourcesContent":["import { Component, inject, input, output, signal } from '@angular/core';\nimport { LtI18nService } from '@lenne.tech/ng-base';\nimport { HlmBadgeImports, HlmButtonImports } from '@lenne.tech/ng-base/ui';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { lucideFileText, lucidePaperclip, lucideUpload, lucideX } from '@ng-icons/lucide';\n\nimport type { LtBugAttachment } from '../bug-types';\nimport { formatLtBugFileSize } from '../report';\n\nlet nextId = 0;\n\n/**\n * Attachments of a bug report (the counterpart of `AttachmentsList.vue`): drop zone with a file button, list with\n * thumbnail, name, size, screenshot badge and a labelled remove button. Files are read as data URLs; files above\n * `maxFileSize` MB or beyond `maxFiles` are rejected with a message (`role=\"alert\"`).\n */\n@Component({\n selector: 'lt-bug-attachments',\n imports: [HlmBadgeImports, HlmButtonImports, NgIcon],\n providers: [provideIcons({ lucideFileText, lucidePaperclip, lucideUpload, lucideX })],\n templateUrl: './bug-attachments.html',\n host: { class: 'flex flex-col gap-3' },\n})\nexport class LtBugAttachments {\n /** `accept` of the file input (default as `@lenne.tech/bug.lt`). */\n readonly accept = input('image/*,.pdf,.doc,.docx,.txt,.log');\n readonly attachments = input.required<readonly LtBugAttachment[]>();\n readonly disabled = input(false);\n /** Maximum number of attachments including the screenshot (default 10, the server limit). */\n readonly maxFiles = input(10);\n /** Maximum file size in MB (default 10). */\n readonly maxFileSize = input(10);\n\n /** Files were read and accepted. */\n readonly added = output<LtBugAttachment[]>();\n /** The user removed an attachment (its id). */\n readonly removed = output<string>();\n\n protected readonly i18n = inject(LtI18nService);\n protected readonly hintId = `lt-bug-attachments-${nextId++}-hint`;\n protected readonly dragging = signal(false);\n protected readonly rejections = signal<string[]>([]);\n protected readonly formatSize = formatLtBugFileSize;\n private dragDepth = 0;\n\n protected openFileDialog(fileInput: HTMLInputElement): void {\n if (!this.disabled()) {\n fileInput.click();\n }\n }\n\n protected onInputChange(fileInput: HTMLInputElement): void {\n const files = Array.from(fileInput.files ?? []);\n fileInput.value = '';\n void this.addFiles(files);\n }\n\n protected onDragEnter(event: DragEvent): void {\n event.preventDefault();\n if (!this.disabled()) {\n this.dragDepth++;\n this.dragging.set(true);\n }\n }\n\n protected onDragOver(event: DragEvent): void {\n event.preventDefault();\n if (event.dataTransfer) {\n event.dataTransfer.dropEffect = this.disabled() ? 'none' : 'copy';\n }\n }\n\n protected onDragLeave(event: DragEvent): void {\n event.preventDefault();\n this.dragDepth = Math.max(0, this.dragDepth - 1);\n if (this.dragDepth === 0) {\n this.dragging.set(false);\n }\n }\n\n protected onDrop(event: DragEvent): void {\n event.preventDefault();\n this.dragDepth = 0;\n this.dragging.set(false);\n if (!this.disabled()) {\n void this.addFiles(Array.from(event.dataTransfer?.files ?? []));\n }\n }\n\n /** Read, validate and emit files; returns the accepted attachments. */\n async addFiles(files: File[]): Promise<LtBugAttachment[]> {\n if (!files.length) {\n return [];\n }\n const messages: string[] = [];\n const accepted: LtBugAttachment[] = [];\n const maxBytes = this.maxFileSize() * 1024 * 1024;\n let count = this.attachments().length;\n for (const file of files) {\n if (file.size > maxBytes) {\n messages.push(this.i18n.t('lt.bug.fileTooLarge', { max: this.maxFileSize(), name: file.name }));\n continue;\n }\n if (count >= this.maxFiles()) {\n messages.push(this.i18n.t('lt.bug.tooManyFiles', { max: this.maxFiles(), name: file.name }));\n continue;\n }\n try {\n const data = await readAsDataUrl(file);\n accepted.push({\n data,\n id: `attachment-${Date.now()}-${nextId++}`,\n name: file.name,\n size: file.size,\n type: file.type || 'application/octet-stream',\n ...(file.type.startsWith('image/') ? { preview: data } : {}),\n });\n count++;\n } catch {\n messages.push(this.i18n.t('lt.bug.fileReadFailed', { name: file.name }));\n }\n }\n this.rejections.set(messages);\n if (accepted.length) {\n this.added.emit(accepted);\n }\n return accepted;\n }\n\n protected removeLabel(attachment: LtBugAttachment): string {\n return this.i18n.t('lt.bug.removeAttachment', { name: attachment.name });\n }\n}\n\nfunction readAsDataUrl(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => (typeof reader.result === 'string' ? resolve(reader.result) : reject(new Error('Unexpected FileReader result')));\n reader.onerror = () => reject(reader.error ?? new Error('FileReader error'));\n reader.onabort = () => reject(new Error('FileReader aborted'));\n reader.readAsDataURL(file);\n });\n}\n","<div\n class=\"border-border flex flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed p-4 text-center transition-colors data-[disabled=true]:opacity-50 data-[dragging=true]:border-primary data-[dragging=true]:bg-primary/5\"\n data-slot=\"dropzone\"\n [attr.data-dragging]=\"dragging() || null\"\n [attr.data-disabled]=\"disabled() || null\"\n (dragenter)=\"onDragEnter($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n <ng-icon name=\"lucideUpload\" class=\"text-muted-foreground text-2xl\" aria-hidden=\"true\" />\n <button hlmBtn type=\"button\" variant=\"outline\" size=\"sm\" [disabled]=\"disabled()\" [attr.aria-describedby]=\"hintId\" (click)=\"openFileDialog(fileInput)\">\n {{ i18n.t('lt.bug.selectFiles') }}\n </button>\n <p [id]=\"hintId\" class=\"text-muted-foreground text-xs\">{{ i18n.t('lt.bug.dropHint') }} · {{ i18n.t('lt.bug.maxFileSize', { max: maxFileSize() }) }}</p>\n <input\n #fileInput\n type=\"file\"\n multiple\n class=\"sr-only\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n data-slot=\"file-input\"\n [accept]=\"accept()\"\n [disabled]=\"disabled()\"\n (change)=\"onInputChange(fileInput)\"\n />\n</div>\n\n@if (rejections().length > 0) {\n <ul class=\"text-error flex flex-col gap-1 text-sm\" role=\"alert\" data-slot=\"rejections\">\n @for (message of rejections(); track $index) {\n <li>{{ message }}</li>\n }\n </ul>\n}\n\n@if (attachments().length > 0) {\n <div class=\"flex flex-col gap-2\">\n <p class=\"text-sm font-medium\" [id]=\"hintId + '-list'\">{{ i18n.t('lt.bug.attachmentsCount', { count: attachments().length }) }}</p>\n <ul class=\"flex flex-col gap-1\" data-slot=\"attachment-list\" [attr.aria-labelledby]=\"hintId + '-list'\">\n @for (attachment of attachments(); track attachment.id) {\n <li class=\"bg-muted/50 flex items-center gap-3 rounded-lg p-2\" data-slot=\"attachment\" [attr.data-screenshot]=\"attachment.isScreenshot || null\">\n @if (attachment.preview) {\n <img class=\"size-10 shrink-0 rounded object-cover\" [src]=\"attachment.preview\" [alt]=\"attachment.name\" />\n } @else {\n <ng-icon\n class=\"text-muted-foreground shrink-0 text-2xl\"\n aria-hidden=\"true\"\n [name]=\"attachment.type.startsWith('text/') || attachment.type.includes('pdf') ? 'lucideFileText' : 'lucidePaperclip'\"\n />\n }\n <div class=\"flex min-w-0 flex-1 flex-col\">\n <div class=\"flex min-w-0 items-center gap-2\">\n <span class=\"truncate text-sm font-medium\" [title]=\"attachment.name\">{{ attachment.name }}</span>\n @if (attachment.isScreenshot) {\n <span hlmBadge variant=\"secondary\">{{ i18n.t('lt.bug.screenshot') }}</span>\n }\n </div>\n <span class=\"text-muted-foreground text-xs\">{{ formatSize(attachment.size) }}</span>\n </div>\n <button\n hlmBtn\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n data-action=\"remove\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"removeLabel(attachment)\"\n (click)=\"removed.emit(attachment.id)\"\n >\n <ng-icon name=\"lucideX\" aria-hidden=\"true\" />\n </button>\n </li>\n }\n </ul>\n </div>\n}\n","import { Component, computed, inject, input, signal } from '@angular/core';\nimport { LtI18nService } from '@lenne.tech/ng-base';\nimport { HlmBadgeImports, HlmNativeSelectImports } from '@lenne.tech/ng-base/ui';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport {\n lucideAppWindow,\n lucideArrowUpDown,\n lucideCircleAlert,\n lucideCircleArrowRight,\n lucideClock,\n lucideFileText,\n lucideHand,\n lucideInfo,\n lucideKeyboard,\n lucideMousePointerClick,\n lucidePencil,\n lucideSend,\n lucideX,\n} from '@ng-icons/lucide';\n\nimport type { LtUserInteractionEvent } from '../bug-types';\n\nlet nextId = 0;\n\nconst ICONS: Record<string, string> = {\n click: 'lucideMousePointerClick',\n error: 'lucideCircleAlert',\n form_change: 'lucideFileText',\n form_submit: 'lucideSend',\n hover: 'lucideHand',\n input_change: 'lucidePencil',\n keyboard: 'lucideKeyboard',\n modal_close: 'lucideX',\n modal_open: 'lucideAppWindow',\n navigation: 'lucideCircleArrowRight',\n scroll: 'lucideArrowUpDown',\n};\n\nconst COLORS: Record<string, string> = {\n error: 'text-error',\n form_change: 'text-success',\n form_submit: 'text-success',\n input_change: 'text-warning',\n keyboard: 'text-info',\n modal_close: 'text-error',\n modal_open: 'text-info',\n navigation: 'text-info',\n};\n\n/**\n * User journey timeline (the counterpart of `UserJourneyTimeline.vue`): filter by event type, one row per event with\n * icon, type, time, target and details (text, value, URL, scroll position, key, error).\n */\n@Component({\n selector: 'lt-bug-journey-timeline',\n imports: [HlmBadgeImports, HlmNativeSelectImports, NgIcon],\n providers: [\n provideIcons({\n lucideAppWindow,\n lucideArrowUpDown,\n lucideCircleAlert,\n lucideCircleArrowRight,\n lucideClock,\n lucideFileText,\n lucideHand,\n lucideInfo,\n lucideKeyboard,\n lucideMousePointerClick,\n lucidePencil,\n lucideSend,\n lucideX,\n }),\n ],\n templateUrl: './bug-journey-timeline.html',\n host: { class: 'flex flex-col gap-3' },\n})\nexport class LtBugJourneyTimeline {\n readonly events = input.required<readonly LtUserInteractionEvent[]>();\n\n protected readonly i18n = inject(LtI18nService);\n protected readonly filterId = `lt-bug-journey-${nextId++}-filter`;\n protected readonly filter = signal('all');\n protected readonly types = computed(() => ['all', ...new Set(this.events().map((event) => event.type))]);\n protected readonly filtered = computed(() => {\n const filter = this.filter();\n return filter === 'all' ? this.events() : this.events().filter((event) => event.type === filter);\n });\n\n protected typeLabel(type: string): string {\n if (type === 'all') {\n return this.i18n.t('lt.bug.allEventTypes');\n }\n const key = `lt.bug.events.${type}`;\n const label = this.i18n.t(key);\n return label === key ? type.replace(/_/g, ' ').replace(/\\b\\w/g, (letter) => letter.toUpperCase()) : label;\n }\n\n protected icon(type: string): string {\n return ICONS[type] ?? 'lucideInfo';\n }\n\n protected color(type: string): string {\n return COLORS[type] ?? 'text-primary';\n }\n\n protected time(timestamp: string): string {\n const date = new Date(timestamp);\n return Number.isNaN(date.getTime()) ? '' : date.toLocaleTimeString(this.i18n.locale() === 'en' ? 'en-GB' : 'de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n }\n\n /** Details line (same order as `@lenne.tech/bug.lt`). */\n protected details(event: LtUserInteractionEvent): string {\n const metadata = event.metadata;\n if (!metadata) {\n return '';\n }\n const parts: string[] = [];\n if (metadata.text) {\n parts.push(`${this.i18n.t('lt.bug.metaText')}: ${metadata.text.length > 30 ? `${metadata.text.slice(0, 30)}…` : metadata.text}`);\n }\n if (metadata.value) {\n parts.push(`${this.i18n.t('lt.bug.metaValue')}: ${metadata.value}`);\n }\n if (metadata.url && metadata.url !== metadata.toUrl) {\n parts.push(`${this.i18n.t('lt.bug.metaUrl')}: ${metadata.url}`);\n }\n if (metadata.toUrl) {\n parts.push(`→ ${metadata.toUrl}`);\n }\n if (metadata.scrollPosition !== undefined) {\n parts.push(`${this.i18n.t('lt.bug.metaPosition')}: ${metadata.scrollPosition}px`);\n }\n if (metadata.key) {\n parts.push(`${this.i18n.t('lt.bug.metaKey')}: ${metadata.key}`);\n }\n if (metadata.errorMessage) {\n parts.push(`${this.i18n.t('lt.bug.metaError')}: ${metadata.errorMessage}`);\n }\n return parts.join(' | ');\n }\n}\n","<div class=\"flex flex-col gap-1.5\">\n <label class=\"text-sm font-medium\" [for]=\"filterId\">{{ i18n.t('lt.bug.journeyFilter') }}</label>\n <hlm-native-select size=\"sm\" [selectId]=\"filterId\" [value]=\"filter()\" (valueChange)=\"filter.set($event || 'all')\">\n @for (type of types(); track type) {\n <option hlmNativeSelectOption [value]=\"type\">{{ typeLabel(type) }}</option>\n }\n </hlm-native-select>\n</div>\n\n@if (filtered().length > 0) {\n <ol class=\"flex max-h-96 flex-col gap-2 overflow-y-auto\" data-slot=\"journey-list\" [attr.aria-label]=\"i18n.t('lt.bug.journeyEvents')\">\n @for (event of filtered(); track $index) {\n <li class=\"border-border flex items-start gap-3 rounded-lg border p-3\" data-slot=\"journey-event\" [attr.data-type]=\"event.type\">\n <ng-icon class=\"mt-0.5 shrink-0 text-lg\" aria-hidden=\"true\" [class]=\"color(event.type)\" [name]=\"icon(event.type)\" />\n <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div class=\"flex items-center justify-between gap-2\">\n <span hlmBadge variant=\"outline\">{{ typeLabel(event.type) }}</span>\n <time class=\"text-muted-foreground text-xs\" [attr.datetime]=\"event.timestamp\">{{ time(event.timestamp) }}</time>\n </div>\n <p class=\"truncate text-sm font-medium\" [title]=\"event.target\">{{ event.target }}</p>\n @if (details(event); as text) {\n <p class=\"text-muted-foreground truncate text-xs\" [title]=\"text\">{{ text }}</p>\n }\n </div>\n </li>\n }\n </ol>\n} @else {\n <div class=\"text-muted-foreground flex flex-col items-center gap-2 py-8 text-center\" data-slot=\"journey-empty\">\n <ng-icon name=\"lucideClock\" class=\"text-4xl opacity-50\" aria-hidden=\"true\" />\n <p class=\"text-sm\">{{ i18n.t('lt.bug.journeyEmpty') }}</p>\n </div>\n}\n","import { afterNextRender, Component, computed, ElementRef, inject, Injector, signal } from '@angular/core';\nimport { disabled, form, FormField, maxLength, required, submit, type FieldTree } from '@angular/forms/signals';\nimport { LT_I18N_OPTIONS, LtI18nService } from '@lenne.tech/ng-base';\nimport {\n HlmButtonImports,\n HlmCollapsibleImports,\n HlmDialogImports,\n HlmSpinnerImports,\n HlmSwitchImports,\n HlmTextareaImports,\n injectLtOverlayRef,\n LtFormField,\n LtNotice,\n LtOverlayService,\n LtSelect,\n LtTextField,\n type LtOverlayRef,\n type LtSelectOption,\n} from '@lenne.tech/ng-base/ui';\nimport { ltFocusFirstInvalid } from '@lenne.tech/ng-base/utils';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { lucideChevronDown, lucideX } from '@ng-icons/lucide';\n\nimport { LtBugReportService } from '../bug-report-service';\nimport type { LtBugAttachment, LtBugReportType, LtUserInteractionEvent } from '../bug-types';\nimport { registerLtBugMessages } from '../i18n/messages';\nimport { emptyLtBugReportForm, ltBugPrefillFromError, ltDataUrlSize, type LtBugReportFormValue, type LtBugReportIncludes } from '../report';\nimport { LtBugAttachments } from './bug-attachments';\nimport { LtBugJourneyTimeline } from './bug-journey-timeline';\n\nconst TYPES: readonly LtBugReportType[] = ['bug', 'feature', 'enhancement', 'other'];\n\n/**\n * Content of the bug report dialog (opened by `<lt-bug-report>` through {@link openLtBugReportDialog}): title, type,\n * description, expected behavior, steps, attachments with the screenshot, switches for the collected data and the user\n * journey preview. Signal Forms validation; a failed submit focuses the first invalid field. Loaded on first use.\n */\n@Component({\n selector: 'lt-bug-report-dialog',\n imports: [\n FormField,\n HlmButtonImports,\n HlmCollapsibleImports,\n HlmDialogImports,\n HlmSpinnerImports,\n HlmSwitchImports,\n HlmTextareaImports,\n LtBugAttachments,\n LtBugJourneyTimeline,\n LtFormField,\n LtNotice,\n LtSelect,\n LtTextField,\n NgIcon,\n ],\n providers: [provideIcons({ lucideChevronDown, lucideX })],\n templateUrl: './bug-report-dialog.html',\n host: { 'data-lt-bug-ui': '', class: 'grid min-w-0 gap-6' },\n})\nexport class LtBugReportDialog {\n protected readonly bug = inject(LtBugReportService);\n protected readonly i18n = registered(inject(LtI18nService), inject(LT_I18N_OPTIONS).messages);\n private readonly ref = injectLtOverlayRef<boolean>();\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly injector = inject(Injector);\n\n protected readonly model = signal<LtBugReportFormValue>(initialForm(this.bug.lastError()));\n protected readonly reportForm = form(this.model, (path) => {\n required(path.title, { message: this.i18n.t('lt.bug.titleRequired') });\n maxLength(path.title, 255);\n required(path.type, { message: this.i18n.t('lt.bug.typeRequired') });\n required(path.description, { message: this.i18n.t('lt.bug.descriptionRequired') });\n disabled(path, () => this.bug.isSubmitting());\n });\n\n protected readonly attachments = signal<LtBugAttachment[]>(initialAttachments(this.bug.screenshot()));\n protected readonly includes = signal<LtBugReportIncludes>({ browserInfo: true, consoleLogs: true, networkRequests: true, userJourney: true });\n protected readonly showAdditional = signal(false);\n protected readonly showJourney = signal(false);\n\n /** Snapshot of the collected data at opening time. */\n protected readonly journey: LtUserInteractionEvent[] = this.bug.getUserJourney();\n protected readonly consoleCount = this.bug.getConsoleLogs().length;\n protected readonly networkCount = this.bug.getNetworkRequests().length;\n\n protected readonly options = this.bug.options;\n protected readonly journeyEnabled = this.options.enableUserJourney && this.options.userJourney.enabled;\n protected readonly hasScreenshot = computed(() => this.attachments().some((attachment) => attachment.isScreenshot));\n protected readonly typeOptions = computed<LtSelectOption<LtBugReportType>[]>(() => TYPES.map((type) => ({ label: this.i18n.t(`lt.bug.types.${type}`), value: type })));\n protected readonly typeError = computed(() => fieldError(this.reportForm.type));\n protected readonly descriptionError = computed(() => fieldError(this.reportForm.description));\n\n protected setInclude(key: keyof LtBugReportIncludes, value: boolean): void {\n this.includes.update((current) => ({ ...current, [key]: value }));\n }\n\n protected addAttachments(added: LtBugAttachment[]): void {\n this.attachments.update((current) => [...current, ...added]);\n }\n\n protected removeAttachment(id: string): void {\n this.attachments.update((current) => current.filter((attachment) => attachment.id !== id));\n }\n\n protected cancel(): void {\n if (!this.bug.isSubmitting()) {\n this.ref.close(false);\n }\n }\n\n protected async onSubmit(event?: Event): Promise<void> {\n event?.preventDefault();\n if (this.bug.isSubmitting()) {\n return;\n }\n await submit(this.reportForm, {\n action: async () => {\n try {\n await this.bug.submit(await this.bug.buildReport(this.model(), this.attachments(), this.includes()));\n this.bug.reset();\n this.ref.close(true);\n } catch {\n // The error is shown in the dialog (and as a toast); the user can retry.\n }\n return undefined;\n },\n onInvalid: () => afterNextRender(() => ltFocusFirstInvalid(this.host.nativeElement, { offset: 0 }), { injector: this.injector }),\n });\n }\n}\n\n/**\n * Open the bug report dialog through `LtOverlayService` (focus trapped, Escape and backdrop close, focus returns to the\n * trigger). Resolves `true` after a sent report.\n */\nexport function openLtBugReportDialog(injector: Injector): LtOverlayRef<boolean> {\n return injector.get(LtOverlayService).open<boolean>(LtBugReportDialog, { contentClass: 'sm:max-w-2xl' });\n}\n\n/** Register the `lt.bug.*` texts (part of this lazy chunk) before the form schema reads its messages. */\nfunction registered(i18n: LtI18nService, appMessages: Parameters<typeof registerLtBugMessages>[1]): LtI18nService {\n registerLtBugMessages(i18n, appMessages);\n return i18n;\n}\n\nfunction initialForm(error: ReturnType<LtBugReportService['lastError']>): LtBugReportFormValue {\n return error ? { ...emptyLtBugReportForm(), ...ltBugPrefillFromError(error) } : emptyLtBugReportForm();\n}\n\nfunction initialAttachments(screenshot: string | null): LtBugAttachment[] {\n if (!screenshot) {\n return [];\n }\n return [\n { data: screenshot, id: `screenshot-${Date.now()}`, isScreenshot: true, name: 'screenshot.png', preview: screenshot, size: ltDataUrlSize(screenshot), type: 'image/png' },\n ];\n}\n\n/** First error message of a touched, invalid field. */\nfunction fieldError(field: FieldTree<unknown>): string | null {\n const state = field();\n return state.invalid() && state.touched() ? (state.errors().find((error) => !!error.message)?.message ?? null) : null;\n}\n","<div hlmDialogHeader>\n <h2 hlmDialogTitle>{{ i18n.t('lt.bug.dialogTitle') }}</h2>\n <p hlmDialogDescription>{{ i18n.t('lt.bug.dialogDescription') }}</p>\n</div>\n\n<form id=\"lt-bug-form\" class=\"flex min-w-0 flex-col gap-4\" novalidate data-slot=\"bug-report-form\" (submit)=\"onSubmit($event)\">\n @if (bug.error(); as message) {\n <lt-notice variant=\"error\" data-slot=\"bug-report-error\">\n <div class=\"flex items-start justify-between gap-3\">\n <span>{{ message }}</span>\n <button hlmBtn type=\"button\" variant=\"ghost\" size=\"icon-sm\" class=\"-my-1 shrink-0\" [attr.aria-label]=\"i18n.t('lt.bug.dismissError')\" (click)=\"bug.error.set(null)\">\n <ng-icon name=\"lucideX\" aria-hidden=\"true\" />\n </button>\n </div>\n </lt-notice>\n }\n\n <lt-text-field id=\"lt-bug-title\" [label]=\"i18n.t('lt.bug.title')\" [placeholder]=\"i18n.t('lt.bug.titlePlaceholder')\" [formField]=\"reportForm.title\" />\n\n <lt-form-field for=\"lt-bug-type\" [label]=\"i18n.t('lt.bug.type')\" [required]=\"true\" [error]=\"typeError()\">\n <lt-select id=\"lt-bug-type\" [options]=\"typeOptions()\" [placeholder]=\"i18n.t('lt.bug.typePlaceholder')\" [formField]=\"reportForm.type\" />\n </lt-form-field>\n\n <lt-form-field for=\"lt-bug-description\" [label]=\"i18n.t('lt.bug.description')\" [required]=\"true\" [error]=\"descriptionError()\">\n <textarea\n hlmTextarea\n id=\"lt-bug-description\"\n rows=\"4\"\n class=\"max-h-64\"\n [placeholder]=\"i18n.t('lt.bug.descriptionPlaceholder')\"\n [forceInvalid]=\"!!descriptionError()\"\n [formField]=\"reportForm.description\"\n ></textarea>\n </lt-form-field>\n\n <lt-form-field for=\"lt-bug-expected\" [label]=\"i18n.t('lt.bug.expectedBehavior')\">\n <textarea\n hlmTextarea\n id=\"lt-bug-expected\"\n rows=\"3\"\n class=\"max-h-48\"\n [placeholder]=\"i18n.t('lt.bug.expectedBehaviorPlaceholder')\"\n [formField]=\"reportForm.expectedBehavior\"\n ></textarea>\n </lt-form-field>\n\n <lt-form-field for=\"lt-bug-steps\" [label]=\"i18n.t('lt.bug.stepsToReproduce')\">\n <textarea\n hlmTextarea\n id=\"lt-bug-steps\"\n rows=\"3\"\n class=\"max-h-48\"\n [placeholder]=\"i18n.t('lt.bug.stepsToReproducePlaceholder')\"\n [formField]=\"reportForm.stepsToReproduce\"\n ></textarea>\n </lt-form-field>\n\n <section class=\"flex flex-col gap-2\" aria-labelledby=\"lt-bug-attachments-title\" data-slot=\"bug-report-attachments\">\n <h3 id=\"lt-bug-attachments-title\" class=\"text-sm font-medium\">\n {{ i18n.t('lt.bug.attachments') }}\n @if (hasScreenshot()) {\n <span class=\"text-muted-foreground ms-2 text-xs font-normal\">{{ i18n.t('lt.bug.screenshotCaptured') }}</span>\n }\n </h3>\n <lt-bug-attachments [attachments]=\"attachments()\" [disabled]=\"bug.isSubmitting()\" (added)=\"addAttachments($event)\" (removed)=\"removeAttachment($event)\" />\n </section>\n\n @if (options.enableBrowserInfo || options.enableConsoleLogs || options.enableNetworkRequests || journeyEnabled) {\n <div hlmCollapsible class=\"border-border rounded-lg border\" data-slot=\"bug-report-additional\" [expanded]=\"showAdditional()\" (expandedChange)=\"showAdditional.set($event)\">\n <button hlmCollapsibleTrigger type=\"button\" class=\"hover:bg-muted flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-start text-sm font-medium\">\n <span>{{ i18n.t('lt.bug.additionalInfo') }}</span>\n <ng-icon name=\"lucideChevronDown\" class=\"transition-transform duration-200\" aria-hidden=\"true\" [class.rotate-180]=\"showAdditional()\" />\n </button>\n <div hlmCollapsibleContent class=\"border-border flex flex-col gap-3 border-t p-3\">\n @if (options.enableBrowserInfo) {\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex min-w-0 flex-col\">\n <span id=\"lt-bug-include-browser-label\" class=\"text-sm font-medium\">{{ i18n.t('lt.bug.browserInfo') }}</span>\n <span id=\"lt-bug-include-browser-hint\" class=\"text-muted-foreground text-xs\">{{ i18n.t('lt.bug.browserInfoHint') }}</span>\n </div>\n <hlm-switch\n inputId=\"lt-bug-include-browser\"\n aria-labelledby=\"lt-bug-include-browser-label\"\n aria-describedby=\"lt-bug-include-browser-hint\"\n [checked]=\"includes().browserInfo\"\n [disabled]=\"bug.isSubmitting()\"\n (checkedChange)=\"setInclude('browserInfo', $event)\"\n />\n </div>\n }\n @if (options.enableConsoleLogs) {\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex min-w-0 flex-col\">\n <span id=\"lt-bug-include-console-label\" class=\"text-sm font-medium\">{{ i18n.t('lt.bug.consoleLogs') }}</span>\n <span id=\"lt-bug-include-console-hint\" class=\"text-muted-foreground text-xs\">{{ i18n.t('lt.bug.consoleLogsHint', { count: consoleCount }) }}</span>\n </div>\n <hlm-switch\n inputId=\"lt-bug-include-console\"\n aria-labelledby=\"lt-bug-include-console-label\"\n aria-describedby=\"lt-bug-include-console-hint\"\n [checked]=\"includes().consoleLogs\"\n [disabled]=\"bug.isSubmitting()\"\n (checkedChange)=\"setInclude('consoleLogs', $event)\"\n />\n </div>\n }\n @if (options.enableNetworkRequests) {\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex min-w-0 flex-col\">\n <span id=\"lt-bug-include-network-label\" class=\"text-sm font-medium\">{{ i18n.t('lt.bug.networkRequests') }}</span>\n <span id=\"lt-bug-include-network-hint\" class=\"text-muted-foreground text-xs\">{{ i18n.t('lt.bug.networkRequestsHint', { count: networkCount }) }}</span>\n </div>\n <hlm-switch\n inputId=\"lt-bug-include-network\"\n aria-labelledby=\"lt-bug-include-network-label\"\n aria-describedby=\"lt-bug-include-network-hint\"\n [checked]=\"includes().networkRequests\"\n [disabled]=\"bug.isSubmitting()\"\n (checkedChange)=\"setInclude('networkRequests', $event)\"\n />\n </div>\n }\n @if (journeyEnabled) {\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex min-w-0 flex-col\">\n <span id=\"lt-bug-include-journey-label\" class=\"text-sm font-medium\">{{ i18n.t('lt.bug.userJourney') }}</span>\n <span id=\"lt-bug-include-journey-hint\" class=\"text-muted-foreground text-xs\">{{ i18n.t('lt.bug.userJourneyHint', { count: journey.length }) }}</span>\n </div>\n <hlm-switch\n inputId=\"lt-bug-include-journey\"\n aria-labelledby=\"lt-bug-include-journey-label\"\n aria-describedby=\"lt-bug-include-journey-hint\"\n [checked]=\"includes().userJourney\"\n [disabled]=\"bug.isSubmitting()\"\n (checkedChange)=\"setInclude('userJourney', $event)\"\n />\n </div>\n }\n </div>\n </div>\n }\n\n @if (journeyEnabled && includes().userJourney && journey.length > 0) {\n <div hlmCollapsible class=\"border-border rounded-lg border\" data-slot=\"bug-report-journey\" [expanded]=\"showJourney()\" (expandedChange)=\"showJourney.set($event)\">\n <button hlmCollapsibleTrigger type=\"button\" class=\"hover:bg-muted flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-start text-sm font-medium\">\n <span>{{ i18n.t('lt.bug.journeyPreview', { count: journey.length }) }}</span>\n <ng-icon name=\"lucideChevronDown\" class=\"transition-transform duration-200\" aria-hidden=\"true\" [class.rotate-180]=\"showJourney()\" />\n </button>\n <div hlmCollapsibleContent class=\"border-border border-t p-3\">\n <lt-bug-journey-timeline [events]=\"journey\" />\n </div>\n </div>\n }\n</form>\n\n<div hlmDialogFooter>\n <button hlmBtn type=\"button\" variant=\"outline\" data-action=\"cancel\" [disabled]=\"bug.isSubmitting()\" (click)=\"cancel()\">{{ i18n.t('lt.bug.cancel') }}</button>\n <button hlmBtn type=\"submit\" form=\"lt-bug-form\" data-action=\"submit\" [attr.aria-busy]=\"bug.isSubmitting() || null\" [attr.aria-disabled]=\"bug.isSubmitting() || null\">\n @if (bug.isSubmitting()) {\n <hlm-spinner aria-hidden=\"true\" />\n {{ i18n.t('lt.bug.submitting') }}\n } @else {\n {{ i18n.t('lt.bug.submit') }}\n }\n </button>\n</div>\n"],"mappings":"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"}
|
|
@@ -325,7 +325,7 @@ var LtBugReportService = class LtBugReportService {
|
|
|
325
325
|
}
|
|
326
326
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
327
327
|
minVersion: "12.0.0",
|
|
328
|
-
version: "22.2.
|
|
328
|
+
version: "22.2.5",
|
|
329
329
|
ngImport: i0,
|
|
330
330
|
type: LtBugReportService,
|
|
331
331
|
deps: [],
|
|
@@ -333,14 +333,14 @@ var LtBugReportService = class LtBugReportService {
|
|
|
333
333
|
});
|
|
334
334
|
static ɵprov = i0.ɵɵngDeclareService({
|
|
335
335
|
minVersion: "22.0.0",
|
|
336
|
-
version: "22.2.
|
|
336
|
+
version: "22.2.5",
|
|
337
337
|
ngImport: i0,
|
|
338
338
|
type: LtBugReportService
|
|
339
339
|
});
|
|
340
340
|
};
|
|
341
341
|
i0.ɵɵngDeclareClassMetadata({
|
|
342
342
|
minVersion: "12.0.0",
|
|
343
|
-
version: "22.2.
|
|
343
|
+
version: "22.2.5",
|
|
344
344
|
ngImport: i0,
|
|
345
345
|
type: LtBugReportService,
|
|
346
346
|
decorators: [{ type: Service }],
|
|
@@ -348,4 +348,4 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
348
348
|
});
|
|
349
349
|
export { LT_BUG_DEFAULTS as a, resolveLtBugOptions as c, registerLtBugEagerMessages as i, LtBugSubmitError as n, LT_BUG_DEFAULT_ENDPOINT as o, LT_BUG_SCREENSHOT_LOADER as r, LT_BUG_OPTIONS as s, LtBugReportService as t };
|
|
350
350
|
|
|
351
|
-
//# sourceMappingURL=lenne.tech-ng-base-bug-bug-report-service-
|
|
351
|
+
//# sourceMappingURL=lenne.tech-ng-base-bug-bug-report-service-D6slR3GD.mjs.map
|