@lenne.tech/ng-base 22.0.5 → 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.
@@ -1,4 +1,4 @@
1
- import { LT_AUTH_OPTIONS, LtAuthStore, readLtAuthAnswer } from "@lenne.tech/ng-base/auth/provide";
1
+ import { LT_AUTH_OPTIONS, LtAuthStore, isLtJwtShaped, readLtAuthAnswer } from "@lenne.tech/ng-base/auth/provide";
2
2
  import { LtApiUrlService, LtI18nService, ltArrayBufferToBase64Url, ltBase64UrlToUint8Array, ltSha256 } from "@lenne.tech/ng-base";
3
3
  import * as i0 from "@angular/core";
4
4
  import { Service, inject } from "@angular/core";
@@ -65,6 +65,15 @@ async function createLtAuthClient(config) {
65
65
  return result;
66
66
  };
67
67
  const withOptions = (options) => options ? [{ fetchOptions: options }] : [];
68
+ const withoutSessionToken = (response) => {
69
+ const data = response?.data;
70
+ if (!data || typeof data !== "object" || !("token" in data) || isLtJwtShaped(data.token)) return response;
71
+ const { token: _sessionToken, ...rest } = data;
72
+ return {
73
+ ...response,
74
+ data: rest
75
+ };
76
+ };
68
77
  return {
69
78
  raw,
70
79
  changePassword: async (params, options) => call(["changePassword"], [await hashed(params, ["currentPassword", "newPassword"], "changePassword"), ...withOptions(options)]),
@@ -95,15 +104,14 @@ async function createLtAuthClient(config) {
95
104
  disable: async (params, options) => call(["twoFactor", "disable"], [await hashed(params, ["password"], "twoFactor.disable"), ...withOptions(options)]),
96
105
  enable: async (params, options) => call(["twoFactor", "enable"], [await hashed(params, ["password"], "twoFactor.enable"), ...withOptions(options)]),
97
106
  generateBackupCodes: async (params, options) => call(["twoFactor", "generateBackupCodes"], [await hashed(params, ["password"], "twoFactor.generateBackupCodes"), ...withOptions(options)]),
98
- verifyBackupCode: (params, options) => call(["twoFactor", "verifyBackupCode"], [params, ...withOptions(options)]),
99
- verifyTotp: (params, options) => call(["twoFactor", "verifyTotp"], [params, ...withOptions(options)])
107
+ verifyBackupCode: async (params, options) => withoutSessionToken(await call(["twoFactor", "verifyBackupCode"], [params, ...withOptions(options)])),
108
+ verifyTotp: async (params, options) => withoutSessionToken(await call(["twoFactor", "verifyTotp"], [params, ...withOptions(options)]))
100
109
  },
101
110
  verifyEmail: (params, options) => call(["verifyEmail"], [{ query: params }, ...withOptions(options)])
102
111
  };
103
112
  }
104
113
  const LT_AUTH_MESSAGES = {
105
114
  de: { lt: { "auth": {
106
- "loggingOut": "Abmelden...",
107
115
  "noPasskeySelected": "Kein Passkey ausgewählt",
108
116
  "passkeyAborted": "Passkey-Authentifizierung wurde abgebrochen",
109
117
  "passkeyAlreadyRegistered": "Du hast bereits einen Passkey für diese Website registriert. Lösche ihn zuerst oder verwende einen anderen Authenticator.",
@@ -112,11 +120,9 @@ const LT_AUTH_MESSAGES = {
112
120
  "passkeyFailed": "Passkey-Anmeldung fehlgeschlagen",
113
121
  "passkeyRegisterFailed": "Passkey-Registrierung fehlgeschlagen",
114
122
  "passkeyUnsupported": "Passkeys werden in diesem Browser nicht unterstützt",
115
- "registerOptionsError": "Konnte Registrierungsoptionen nicht laden",
116
- "sessionExpired": "Sitzung abgelaufen"
123
+ "registerOptionsError": "Konnte Registrierungsoptionen nicht laden"
117
124
  } } },
118
125
  en: { lt: { "auth": {
119
- "loggingOut": "Logging out...",
120
126
  "noPasskeySelected": "No passkey selected",
121
127
  "passkeyAborted": "Passkey authentication was cancelled",
122
128
  "passkeyAlreadyRegistered": "You already registered a passkey for this website. Delete it first or use another authenticator.",
@@ -125,8 +131,7 @@ const LT_AUTH_MESSAGES = {
125
131
  "passkeyFailed": "Passkey login failed",
126
132
  "passkeyRegisterFailed": "Passkey registration failed",
127
133
  "passkeyUnsupported": "Passkeys are not supported in this browser",
128
- "registerOptionsError": "Could not load registration options",
129
- "sessionExpired": "Session expired"
134
+ "registerOptionsError": "Could not load registration options"
130
135
  } } }
131
136
  };
132
137
  function injectLtAuthI18n() {
@@ -278,7 +283,6 @@ var LtAuthService = class LtAuthService {
278
283
  success: false
279
284
  };
280
285
  return {
281
- session: result.session,
282
286
  success: true,
283
287
  user
284
288
  };
@@ -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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"}
@@ -47,7 +47,7 @@ var LtFileService = class LtFileService {
47
47
  }
48
48
  }
49
49
  getFileUrl(id) {
50
- return `${this.baseUrl()}/id/${encodeURIComponent(id)}`;
50
+ return `${this.linkBaseUrl()}/id/${encodeURIComponent(id)}`;
51
51
  }
52
52
  getDownloadUrl(id, _filename) {
53
53
  return this.getFileUrl(id);
@@ -65,6 +65,10 @@ var LtFileService = class LtFileService {
65
65
  const basePath = this.options.filesBasePath || "/files";
66
66
  return this.api.buildUrl(basePath).replace(/\/+$/, "");
67
67
  }
68
+ linkBaseUrl() {
69
+ const basePath = this.options.filesBasePath || "/files";
70
+ return this.api.buildBrowserUrl(basePath).replace(/\/+$/, "");
71
+ }
68
72
  static ɵfac = i0.ɵɵngDeclareFactory({
69
73
  minVersion: "12.0.0",
70
74
  version: "22.2.5",
@@ -1 +1 @@
1
- {"version":3,"file":"lenne.tech-ng-base-files.mjs","names":[],"sources":["../../../projects/ng-base/files/src/lib/files-options.ts","../../../projects/ng-base/files/src/lib/file-service.ts","../../../projects/ng-base/files/src/lib/tus-client.ts","../../../projects/ng-base/files/src/lib/tus-upload.ts"],"sourcesContent":["import { inject, InjectionToken, makeEnvironmentProviders, type EnvironmentProviders } from '@angular/core';\nimport { LT_RUNTIME_CONFIG, mergeLtFeatureOptions } from '@lenne.tech/ng-base';\n\n/** TUS options of `provideLtFiles({ tus })`; runtime overrides `NG_PUBLIC_LT_TUS_*`. */\nexport interface LtTusOptions {\n /** Chunk size in bytes (default 5 MB). */\n defaultChunkSize?: number;\n /**\n * TUS endpoint (default **`/tus`**, nest-server's default; nuxt-extensions defaulted to `/files/upload`). Relative values\n * are paths on the API while `resolveAgainstApi` is on; absolute URLs are used as they are.\n */\n defaultEndpoint?: string;\n /** Resolve a relative endpoint against the API base URL (default `true`). Turn off only behind an own same-origin proxy. */\n resolveAgainstApi?: boolean;\n}\n\n/** Options of `provideLtFiles()`. */\nexport interface LtFilesOptions {\n /** Base path of nest-server's file controller for info/download URLs (default `/files`). */\n filesBasePath?: string;\n /** TUS upload options. */\n tus?: LtTusOptions;\n}\n\n/** Resolved files options. */\nexport interface LtResolvedFilesOptions {\n filesBasePath: string;\n tus: Required<LtTusOptions>;\n}\n\n/** Defaults of {@link LtFilesOptions}. */\nexport const LT_FILES_DEFAULTS: LtResolvedFilesOptions = {\n filesBasePath: '/files',\n tus: { defaultChunkSize: 5 * 1024 * 1024, defaultEndpoint: '/tus', resolveAgainstApi: true },\n};\n\nconst LT_FILES_INPUT_OPTIONS = new InjectionToken<LtFilesOptions>('LT_FILES_INPUT_OPTIONS', { factory: () => ({}) });\n\n/** Resolved files options (defaults ← `provideLtFiles(options)` ← runtime `NG_PUBLIC_LT_TUS_*`). */\nexport const LT_FILES_OPTIONS = new InjectionToken<LtResolvedFilesOptions>('LT_FILES_OPTIONS', {\n factory: () => {\n const input = inject(LT_FILES_INPUT_OPTIONS);\n const runtime = inject(LT_RUNTIME_CONFIG, { optional: true });\n return {\n filesBasePath: input.filesBasePath ?? LT_FILES_DEFAULTS.filesBasePath,\n tus: mergeLtFeatureOptions(LT_FILES_DEFAULTS.tus, input.tus, runtime?.lt.tus),\n };\n },\n});\n\n/** Files providers (TUS upload defaults, file info/download base path). Optional when the defaults fit. */\nexport function provideLtFiles(options: LtFilesOptions = {}): EnvironmentProviders {\n return makeEnvironmentProviders([{ provide: LT_FILES_INPUT_OPTIONS, useValue: options }]);\n}\n","import { inject, isDevMode, Service } from '@angular/core';\nimport { LT_FETCH, LtApiUrlService } from '@lenne.tech/ng-base';\n\nimport { LT_FILES_OPTIONS } from './files-options';\nimport type { LtFileInfo, LtFileState } from './upload-types';\n\n/**\n * nest-server file helpers (counterpart of `useLtFile()`). URLs are resolved against the API base URL (fix: the Nuxt\n * version built them relative to the app origin), under `filesBasePath` of `provideLtFiles()` (default `/files`).\n */\n@Service()\nexport class LtFileService implements LtFileState {\n private readonly api = inject(LtApiUrlService);\n private readonly fetch = inject(LT_FETCH);\n private readonly options = inject(LT_FILES_OPTIONS);\n\n /** `GET <api>/files/info/:id` with credentials; `null` for empty ids or on error, a non-ObjectId string is returned as is. */\n async getFileInfo(id: string | undefined): Promise<LtFileInfo | null | string> {\n if (!id) {\n return null;\n }\n if (!isLtMongoId(id)) {\n return id;\n }\n try {\n const response = await this.fetch(`${this.baseUrl()}/info/${encodeURIComponent(id)}`, {\n credentials: 'include',\n headers: { accept: 'application/json' },\n method: 'GET',\n });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n const info = (await response.json()) as LtFileInfo | null;\n return info && typeof info === 'object' ? info : null;\n } catch (error) {\n if (isDevMode()) {\n console.error('[@lenne.tech/ng-base] Error fetching file info:', error);\n }\n return null;\n }\n }\n\n /**\n * `<api>/files/id/:id` — nest-server's `GET files/id/:id` (CoreFileController), which streams the file (or redirects\n * to a presigned S3 URL) after the rights check (`file.downloadRoles`, default admin).\n */\n getFileUrl(id: string): string {\n return `${this.baseUrl()}/id/${encodeURIComponent(id)}`;\n }\n\n /**\n * Download URL. nest-server sets `Content-Disposition` from the stored filename on `GET files/id/:id`, so this is the\n * same URL as {@link getFileUrl}; `filename` is accepted for API compatibility with nuxt-extensions and ignored (the\n * server has no rename parameter — nuxt-extensions' `/files/download/:id` does not exist in nest-server 12.5).\n */\n getDownloadUrl(id: string, _filename?: string): string {\n return this.getFileUrl(id);\n }\n\n /** 1024-based `B`/`KB`/`MB`/`GB`/`TB` with two decimals. */\n formatFileSize(bytes: number): string {\n return formatLtFileSize(bytes);\n }\n\n /** `Ns`, `Nm Ns`, `Nh Nm`. */\n formatDuration(seconds: number): string {\n return formatLtDuration(seconds);\n }\n\n /** `/^[a-f\\d]{24}$/i`. */\n isValidMongoID(id: string): boolean {\n return isLtMongoId(id);\n }\n\n /** `<api><filesBasePath>` without a trailing slash; an absolute `filesBasePath` is used as is. */\n private baseUrl(): string {\n const basePath = this.options.filesBasePath || '/files';\n return this.api.buildUrl(basePath).replace(/\\/+$/, '');\n }\n}\n\n/** 1024-based file size with two decimals (`0 B` for non-positive input). */\nexport function formatLtFileSize(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '0 B';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB'];\n const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);\n return `${parseFloat((bytes / 1024 ** exponent).toFixed(2))} ${units[exponent]}`;\n}\n\n/** Human readable duration (`45s`, `2m 5s`, `1h 3m`). */\nexport function formatLtDuration(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 60) {\n return `${Math.max(0, Math.round(Number.isFinite(seconds) ? seconds : 0))}s`;\n }\n if (seconds < 3600) {\n return `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;\n }\n return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;\n}\n\n/** Whether `id` is a MongoDB ObjectId (24 hex characters). */\nexport function isLtMongoId(id: string): boolean {\n return typeof id === 'string' && /^[a-f\\d]{24}$/i.test(id);\n}\n","import { InjectionToken } from '@angular/core';\n\n/** The request object tus-js-client hands to `onBeforeRequest` / `onAfterResponse`. */\nexport interface LtTusHttpRequest {\n getUnderlyingObject(): unknown;\n setHeader(header: string, value: string): void;\n}\n\n/** The response object tus-js-client hands to `onAfterResponse` (and inside `DetailedError.originalResponse`). */\nexport interface LtTusHttpResponse {\n getHeader(header: string): null | string | undefined;\n getStatus(): number;\n}\n\n/** A previous upload of the same file found in tus-js-client's URL storage. */\nexport interface LtTusPreviousUpload {\n creationTime?: string;\n}\n\n/** The tus-js-client upload options `injectLtTusUpload()` sets. */\nexport interface LtTusClientUploadOptions {\n chunkSize?: number;\n endpoint?: string;\n headers?: Record<string, string>;\n metadata?: Record<string, string>;\n onAfterResponse?: (req: LtTusHttpRequest, res: LtTusHttpResponse) => void;\n onBeforeRequest?: (req: LtTusHttpRequest) => void;\n onError?: (error: Error) => void;\n onProgress?: (bytesUploaded: number, bytesTotal: number) => void;\n onShouldRetry?: (error: Error & { originalResponse?: LtTusHttpResponse | null }, retryAttempt: number, options: unknown) => boolean;\n onSuccess?: () => void;\n removeFingerprintOnSuccess?: boolean;\n retryDelays?: number[];\n}\n\n/** The part of a tus-js-client `Upload` instance `injectLtTusUpload()` uses. */\nexport interface LtTusClientUpload {\n url: null | string;\n abort(shouldTerminate?: boolean): Promise<void>;\n findPreviousUploads(): Promise<LtTusPreviousUpload[]>;\n resumeFromPreviousUpload(previousUpload: LtTusPreviousUpload): void;\n start(): void;\n}\n\n/** The part of the `tus-js-client` module `injectLtTusUpload()` uses (structural, so the peer stays optional). */\nexport interface LtTusClient {\n Upload: new (file: File, options: LtTusClientUploadOptions) => LtTusClientUpload;\n}\n\n/** Error text when the optional peer `tus-js-client` is missing. */\nexport const LT_TUS_CLIENT_MISSING_MESSAGE =\n '[@lenne.tech/ng-base] tus-js-client is not installed. Install it to use uploads: pnpm add tus-js-client (or npm install tus-js-client).';\n\nlet tusClient: Promise<LtTusClient> | null = null;\n\n/** Load `tus-js-client` lazily (once); rejects with an install hint when the optional peer is missing. */\nexport function loadLtTusClient(): Promise<LtTusClient> {\n tusClient ??= import('tus-js-client').then(\n (module) => {\n const candidate = module as unknown as Partial<LtTusClient> & { default?: Partial<LtTusClient> };\n const client = candidate.Upload ? candidate : candidate.default;\n if (!client?.Upload) {\n throw new Error(LT_TUS_CLIENT_MISSING_MESSAGE);\n }\n return client as LtTusClient;\n },\n () => {\n throw new Error(LT_TUS_CLIENT_MISSING_MESSAGE);\n },\n );\n // A failed load is not cached, so installing the package and reloading the chunk can recover.\n tusClient.catch(() => (tusClient = null));\n return tusClient;\n}\n\n/**\n * Loader of the `tus-js-client` module used by `injectLtTusUpload()` (default: lazy `import('tus-js-client')`).\n * Replace it in tests with a fake client, or to supply a preconfigured build.\n */\nexport const LT_TUS_CLIENT_LOADER = new InjectionToken<() => Promise<LtTusClient>>('LT_TUS_CLIENT_LOADER', {\n factory: () => loadLtTusClient,\n});\n","import { assertInInjectionContext, computed, DestroyRef, inject, signal } from '@angular/core';\nimport { isLtAbsoluteUrl, LtApiUrlService } from '@lenne.tech/ng-base';\nimport { LtAuthStateService } from '@lenne.tech/ng-base/auth/provide';\n\nimport { LT_FILES_OPTIONS } from './files-options';\nimport { LT_TUS_CLIENT_LOADER, type LtTusClientUpload, type LtTusHttpRequest, type LtTusPreviousUpload } from './tus-client';\nimport type { LtTusUploadState, LtUploadItem, LtUploadOptions, LtUploadProgress } from './upload-types';\n\n/** A TUS upload queue with signal state, created by {@link injectLtTusUpload}. */\nexport type LtTusUpload = LtTusUploadState;\n\n/**\n * Response header in which nest-server (11.42.13+, `TUS_FILE_ID_HEADER`) names the stored file. Sent on the response that\n * completes the upload and listed in `Access-Control-Expose-Headers`.\n */\nexport const LT_TUS_FILE_ID_HEADER = 'X-File-Id';\n\n/** Default retry delays of a TUS upload in milliseconds. */\nexport const LT_TUS_RETRY_DELAYS: readonly number[] = [0, 1000, 3000, 5000, 10000];\n\nconst DEFAULT_PARALLEL_UPLOADS = 3;\n\ninterface ResolvedUploadOptions extends LtUploadOptions {\n autoStart: boolean;\n chunkSize: number;\n endpoint: string;\n parallelUploads: number;\n retryDelays: number[];\n}\n\n/** Exponential moving average (0.7 old / 0.3 new) of the upload speed in bytes per second. */\nclass LtSpeedTracker {\n private lastBytes = 0;\n private lastTime = 0;\n private smoothed = 0;\n private baseline = true;\n\n /** Start a new measurement from `bytes` (after a (re)start; the time of a pause does not count). */\n reset(bytes: number): void {\n this.lastBytes = bytes;\n this.lastTime = Date.now();\n this.smoothed = 0;\n this.baseline = true;\n }\n\n /** Record `bytes` uploaded so far and return the smoothed speed. */\n sample(bytes: number): number {\n const now = Date.now();\n if (this.baseline && bytes < this.lastBytes) {\n this.lastBytes = bytes; // a resumed upload reports its offset first\n }\n const seconds = (now - this.lastTime) / 1000;\n if (seconds > 0 && bytes >= this.lastBytes) {\n const current = (bytes - this.lastBytes) / seconds;\n this.smoothed = this.smoothed === 0 ? current : this.smoothed * 0.7 + current * 0.3;\n this.lastBytes = bytes;\n this.lastTime = now;\n this.baseline = false;\n }\n return Math.round(this.smoothed);\n }\n}\n\n/**\n * Create a TUS upload queue (counterpart of `useLtTusUpload()`), bound to the current injection context: running\n * uploads are aborted when the component/service is destroyed.\n *\n * `tus-js-client` (optional peer) is loaded lazily; without it `addFiles` rejects with an install hint.\n * Defaults: `autoStart` true, chunk size and endpoint from `provideLtFiles()`, 3 parallel uploads, retries\n * `[0, 1000, 3000, 5000, 10000]` (none on 4xx except 429), `withCredentials` for the session cookie, Bearer token\n * in JWT mode, the nest-server file id from the `X-File-Id` response header, resume via fingerprints.\n *\n * Options are read whenever an upload is created or started, so an object with getters (e.g. over component inputs)\n * stays current. `undefined` values keep the default. Uploads beyond `parallelUploads` wait in a queue and start as\n * soon as a slot is free; `startUpload`/`resumeUpload`/`retryUpload` of a single item start it right away.\n *\n * @example\n * protected readonly upload = injectLtTusUpload({ onSuccess: (item) => this.attach(item.fileId) });\n */\nexport function injectLtTusUpload(options: LtUploadOptions = {}): LtTusUpload {\n assertInInjectionContext(injectLtTusUpload);\n const filesOptions = inject(LT_FILES_OPTIONS);\n const api = inject(LtApiUrlService);\n const authState = inject(LtAuthStateService);\n const loadClient = inject(LT_TUS_CLIENT_LOADER);\n\n const items = signal<LtUploadItem[]>([]);\n const tusUploads = new Map<string, LtTusClientUpload>();\n const speedTrackers = new Map<string, LtSpeedTracker>();\n /** Start generation per upload: a pause/cancel invalidates a start whose resume lookup is still running. */\n const generations = new Map<string, number>();\n const queue: string[] = [];\n let sequence = 0;\n let destroyed = false;\n\n const uploads = items.asReadonly();\n const isUploading = computed(() => items().some((item) => item.status === 'uploading'));\n const totalProgress = computed<LtUploadProgress>(() => {\n const list = items();\n if (list.length === 0) {\n return idleProgress(0);\n }\n const bytesUploaded = list.reduce((sum, item) => sum + item.progress.bytesUploaded, 0);\n const bytesTotal = list.reduce((sum, item) => sum + item.progress.bytesTotal, 0);\n const speed = list.reduce((sum, item) => sum + item.progress.speed, 0);\n return {\n bytesTotal,\n bytesUploaded,\n percentage: bytesTotal > 0 ? Math.round((bytesUploaded / bytesTotal) * 100) : 0,\n remainingTime: speed > 0 ? Math.ceil((bytesTotal - bytesUploaded) / speed) : 0,\n speed,\n };\n });\n\n function config(): ResolvedUploadOptions {\n const defined = Object.fromEntries(Object.entries(options).filter(([, value]) => value !== undefined)) as LtUploadOptions;\n return {\n ...defined,\n autoStart: defined.autoStart ?? true,\n chunkSize: defined.chunkSize || filesOptions.tus.defaultChunkSize,\n endpoint: defined.endpoint || filesOptions.tus.defaultEndpoint,\n parallelUploads: Math.max(1, defined.parallelUploads || DEFAULT_PARALLEL_UPLOADS),\n retryDelays: defined.retryDelays ?? [...LT_TUS_RETRY_DELAYS],\n };\n }\n\n /** A relative endpoint is a path on the API (like auth and AI requests) unless `tus.resolveAgainstApi` is off. */\n function resolveEndpoint(endpoint: string): string {\n if (!endpoint || filesOptions.tus.resolveAgainstApi === false || isLtAbsoluteUrl(endpoint)) {\n return endpoint;\n }\n return api.buildUrl(endpoint.startsWith('/') ? endpoint : `/${endpoint}`);\n }\n\n function get(id: string): LtUploadItem | undefined {\n return items().find((item) => item.id === id);\n }\n\n function update(id: string, changes: Partial<LtUploadItem>): LtUploadItem | undefined {\n let updated: LtUploadItem | undefined;\n items.update((list) =>\n list.map((item) => {\n if (item.id !== id) {\n return item;\n }\n updated = { ...item, ...changes };\n return updated;\n }),\n );\n return updated;\n }\n\n function invalidate(id: string): void {\n generations.set(id, (generations.get(id) ?? 0) + 1);\n }\n\n function dequeue(id: string): void {\n const index = queue.indexOf(id);\n if (index >= 0) {\n queue.splice(index, 1);\n }\n }\n\n function uploadingCount(): number {\n return items().filter((item) => item.status === 'uploading').length;\n }\n\n /** Start queued uploads while slots are free. */\n function pump(): void {\n if (destroyed) {\n return;\n }\n const limit = config().parallelUploads;\n while (queue.length > 0 && uploadingCount() < limit) {\n const id = queue.shift() as string;\n const item = get(id);\n if (item && (item.status === 'idle' || item.status === 'paused')) {\n begin(id);\n }\n }\n }\n\n function enqueue(ids: string[]): void {\n for (const id of ids) {\n if (!queue.includes(id)) {\n queue.push(id);\n }\n }\n pump();\n }\n\n function begin(id: string): void {\n const item = get(id);\n const tusUpload = tusUploads.get(id);\n if (destroyed || !item || !tusUpload || item.status === 'uploading' || item.status === 'completed') {\n return;\n }\n dequeue(id);\n invalidate(id);\n const generation = generations.get(id);\n speedTrackers.get(id)?.reset(item.progress.bytesUploaded);\n update(id, { error: undefined, startedAt: new Date(), status: 'uploading' });\n\n const stillWanted = (): boolean => !destroyed && generations.get(id) === generation && get(id)?.status === 'uploading';\n // An upload that already has its URL continues there; a new one looks for an interrupted upload of the same file.\n const lookup: Promise<LtTusPreviousUpload[]> = tusUpload.url ? Promise.resolve([]) : tusUpload.findPreviousUploads().catch(() => []);\n void lookup.then((previousUploads) => {\n if (!stillWanted()) {\n return;\n }\n const previous = latest(previousUploads);\n if (previous) {\n tusUpload.resumeFromPreviousUpload(previous);\n }\n tusUpload.start();\n });\n }\n\n function settle(id: string): void {\n invalidate(id);\n pump();\n }\n\n function createTusUpload(client: Awaited<ReturnType<typeof loadClient>>, item: LtUploadItem, resolved: ResolvedUploadOptions): LtTusClientUpload {\n // Read in `onAfterResponse`, not from `onSuccess`'s payload: tus-js-client passes `{ lastResponse }` to `onSuccess`\n // only from 4.2.0, and the peer range starts at 4.0.0.\n let fileId: string | undefined;\n const callerSetsAuthorization = Object.keys(resolved.headers ?? {}).some((name) => name.toLowerCase() === 'authorization');\n\n const upload: LtTusClientUpload = new client.Upload(item.file, {\n chunkSize: resolved.chunkSize,\n endpoint: resolveEndpoint(resolved.endpoint),\n headers: resolved.headers,\n metadata: {\n filename: item.file.name,\n filetype: item.file.type,\n ...resolved.metadata,\n ...item.metadata,\n },\n onAfterResponse: (_req, res) => {\n // Only the completing response carries it; XHR answers `null` for a header that was not sent or not exposed.\n fileId = res.getHeader(LT_TUS_FILE_ID_HEADER) || fileId;\n },\n onBeforeRequest: (req: LtTusHttpRequest) => {\n const xhr = req.getUnderlyingObject() as { withCredentials?: boolean } | null;\n if (xhr && typeof xhr === 'object' && 'withCredentials' in xhr) {\n xhr.withCredentials = true;\n }\n if (!callerSetsAuthorization && authState.getAuthMode() === 'jwt') {\n const token = authState.getJwtToken();\n if (token) {\n req.setHeader('Authorization', `Bearer ${token}`);\n }\n }\n },\n onError: (error) => {\n const failed = update(item.id, { error: error.message, progress: { ...(get(item.id)?.progress ?? item.progress), remainingTime: 0, speed: 0 }, status: 'error' });\n settle(item.id);\n if (failed) {\n config().onError?.(failed, error);\n }\n },\n onProgress: (bytesUploaded, bytesTotal) => {\n const current = get(item.id);\n if (!current || current.status !== 'uploading') {\n return;\n }\n const speed = speedTrackers.get(item.id)?.sample(bytesUploaded) ?? 0;\n const progress: LtUploadProgress = {\n bytesTotal,\n bytesUploaded,\n percentage: bytesTotal > 0 ? Math.min(100, Math.round((bytesUploaded / bytesTotal) * 100)) : 0,\n remainingTime: speed > 0 ? Math.ceil((bytesTotal - bytesUploaded) / speed) : 0,\n speed,\n };\n const updated = update(item.id, { progress });\n if (updated) {\n config().onProgress?.(updated);\n }\n },\n onShouldRetry: (error) => {\n const status = error.originalResponse?.getStatus?.();\n // No retry on 4xx (except 429 Too Many Requests): the request itself is wrong or not allowed.\n return !(status && status >= 400 && status < 500 && status !== 429);\n },\n onSuccess: () => {\n const current = get(item.id);\n if (!current) {\n return;\n }\n const bytesTotal = current.progress.bytesTotal || item.file.size;\n const completed = update(item.id, {\n completedAt: new Date(),\n error: undefined,\n fileId,\n progress: { bytesTotal, bytesUploaded: bytesTotal, percentage: 100, remainingTime: 0, speed: 0 },\n status: 'completed',\n url: upload.url ?? undefined,\n });\n settle(item.id);\n if (completed) {\n config().onSuccess?.(completed);\n }\n },\n // Keep the fingerprint only for interrupted uploads: a completed one must not be \"resumed\" by the next upload of\n // the same file, which would finish without a request and therefore without an `X-File-Id`.\n removeFingerprintOnSuccess: true,\n retryDelays: resolved.retryDelays,\n });\n return upload;\n }\n\n async function addFiles(files: File | File[]): Promise<string[]> {\n const list = (Array.isArray(files) ? files : [files]).filter(Boolean);\n if (list.length === 0) {\n return [];\n }\n const client = await loadClient();\n if (destroyed) {\n return [];\n }\n const resolved = config();\n const ids: string[] = [];\n for (const file of list) {\n const id = `upload_${Date.now().toString(36)}_${++sequence}_${Math.random().toString(36).slice(2, 8)}`;\n const item: LtUploadItem = { file, id, metadata: resolved.metadata, progress: idleProgress(file.size), status: 'idle' };\n tusUploads.set(id, createTusUpload(client, item, resolved));\n speedTrackers.set(id, new LtSpeedTracker());\n items.update((current) => [...current, item]);\n ids.push(id);\n }\n if (resolved.autoStart) {\n enqueue(ids);\n }\n return ids;\n }\n\n function startUpload(id: string): void {\n begin(id);\n }\n\n function startAll(): void {\n enqueue(\n items()\n .filter((item) => item.status === 'idle' || item.status === 'paused')\n .map((item) => item.id),\n );\n }\n\n function pauseUpload(id: string): void {\n const item = get(id);\n const tusUpload = tusUploads.get(id);\n if (!item || !tusUpload) {\n return;\n }\n if (item.status === 'uploading') {\n invalidate(id);\n void tusUpload.abort().catch(() => undefined);\n update(id, { progress: { ...item.progress, remainingTime: 0, speed: 0 }, status: 'paused' });\n pump();\n } else if (item.status === 'idle') {\n // A queued upload is taken out of the queue.\n dequeue(id);\n update(id, { status: 'paused' });\n }\n }\n\n function pauseAll(): void {\n items()\n .filter((item) => item.status === 'uploading')\n .forEach((item) => pauseUpload(item.id));\n }\n\n function resumeUpload(id: string): void {\n if (get(id)?.status === 'paused') {\n begin(id);\n }\n }\n\n function resumeAll(): void {\n enqueue(\n items()\n .filter((item) => item.status === 'paused')\n .map((item) => item.id),\n );\n }\n\n function cancelUpload(id: string): void {\n const tusUpload = tusUploads.get(id);\n invalidate(id);\n dequeue(id);\n if (tusUpload) {\n void tusUpload.abort().catch(() => undefined);\n }\n tusUploads.delete(id);\n speedTrackers.delete(id);\n generations.delete(id);\n items.update((list) => list.filter((item) => item.id !== id));\n pump();\n }\n\n function cancelAll(): void {\n items().forEach((item) => cancelUpload(item.id));\n }\n\n function removeUpload(id: string): void {\n cancelUpload(id);\n }\n\n function clearCompleted(): void {\n items()\n .filter((item) => item.status === 'completed')\n .forEach((item) => removeUpload(item.id));\n }\n\n function retryUpload(id: string): void {\n if (get(id)?.status === 'error') {\n update(id, { error: undefined, status: 'idle' });\n begin(id);\n }\n }\n\n inject(DestroyRef).onDestroy(() => {\n destroyed = true;\n queue.length = 0;\n for (const tusUpload of tusUploads.values()) {\n void tusUpload.abort().catch(() => undefined);\n }\n });\n\n return {\n addFiles,\n cancelAll,\n cancelUpload,\n clearCompleted,\n getUpload: get,\n isUploading,\n pauseAll,\n pauseUpload,\n removeUpload,\n resumeAll,\n resumeUpload,\n retryUpload,\n startAll,\n startUpload,\n totalProgress,\n uploads,\n };\n}\n\nfunction idleProgress(bytesTotal: number): LtUploadProgress {\n return { bytesTotal, bytesUploaded: 0, percentage: 0, remainingTime: 0, speed: 0 };\n}\n\nfunction latest(previousUploads: LtTusPreviousUpload[]): LtTusPreviousUpload | undefined {\n return [...previousUploads].sort((a, b) => Date.parse(b.creationTime ?? '') - Date.parse(a.creationTime ?? '') || 0)[0];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lenne.tech-ng-base-files.mjs","names":[],"sources":["../../../projects/ng-base/files/src/lib/files-options.ts","../../../projects/ng-base/files/src/lib/file-service.ts","../../../projects/ng-base/files/src/lib/tus-client.ts","../../../projects/ng-base/files/src/lib/tus-upload.ts"],"sourcesContent":["import { inject, InjectionToken, makeEnvironmentProviders, type EnvironmentProviders } from '@angular/core';\nimport { LT_RUNTIME_CONFIG, mergeLtFeatureOptions } from '@lenne.tech/ng-base';\n\n/** TUS options of `provideLtFiles({ tus })`; runtime overrides `NG_PUBLIC_LT_TUS_*`. */\nexport interface LtTusOptions {\n /** Chunk size in bytes (default 5 MB). */\n defaultChunkSize?: number;\n /**\n * TUS endpoint (default **`/tus`**, nest-server's default; nuxt-extensions defaulted to `/files/upload`). Relative values\n * are paths on the API while `resolveAgainstApi` is on; absolute URLs are used as they are.\n */\n defaultEndpoint?: string;\n /** Resolve a relative endpoint against the API base URL (default `true`). Turn off only behind an own same-origin proxy. */\n resolveAgainstApi?: boolean;\n}\n\n/** Options of `provideLtFiles()`. */\nexport interface LtFilesOptions {\n /** Base path of nest-server's file controller for info/download URLs (default `/files`). */\n filesBasePath?: string;\n /** TUS upload options. */\n tus?: LtTusOptions;\n}\n\n/** Resolved files options. */\nexport interface LtResolvedFilesOptions {\n filesBasePath: string;\n tus: Required<LtTusOptions>;\n}\n\n/** Defaults of {@link LtFilesOptions}. */\nexport const LT_FILES_DEFAULTS: LtResolvedFilesOptions = {\n filesBasePath: '/files',\n tus: { defaultChunkSize: 5 * 1024 * 1024, defaultEndpoint: '/tus', resolveAgainstApi: true },\n};\n\nconst LT_FILES_INPUT_OPTIONS = new InjectionToken<LtFilesOptions>('LT_FILES_INPUT_OPTIONS', { factory: () => ({}) });\n\n/** Resolved files options (defaults ← `provideLtFiles(options)` ← runtime `NG_PUBLIC_LT_TUS_*`). */\nexport const LT_FILES_OPTIONS = new InjectionToken<LtResolvedFilesOptions>('LT_FILES_OPTIONS', {\n factory: () => {\n const input = inject(LT_FILES_INPUT_OPTIONS);\n const runtime = inject(LT_RUNTIME_CONFIG, { optional: true });\n return {\n filesBasePath: input.filesBasePath ?? LT_FILES_DEFAULTS.filesBasePath,\n tus: mergeLtFeatureOptions(LT_FILES_DEFAULTS.tus, input.tus, runtime?.lt.tus),\n };\n },\n});\n\n/** Files providers (TUS upload defaults, file info/download base path). Optional when the defaults fit. */\nexport function provideLtFiles(options: LtFilesOptions = {}): EnvironmentProviders {\n return makeEnvironmentProviders([{ provide: LT_FILES_INPUT_OPTIONS, useValue: options }]);\n}\n","import { inject, isDevMode, Service } from '@angular/core';\nimport { LT_FETCH, LtApiUrlService } from '@lenne.tech/ng-base';\n\nimport { LT_FILES_OPTIONS } from './files-options';\nimport type { LtFileInfo, LtFileState } from './upload-types';\n\n/**\n * nest-server file helpers (counterpart of `useLtFile()`). URLs are resolved against the API base URL (fix: the Nuxt\n * version built them relative to the app origin), under `filesBasePath` of `provideLtFiles()` (default `/files`).\n */\n@Service()\nexport class LtFileService implements LtFileState {\n private readonly api = inject(LtApiUrlService);\n private readonly fetch = inject(LT_FETCH);\n private readonly options = inject(LT_FILES_OPTIONS);\n\n /** `GET <api>/files/info/:id` with credentials; `null` for empty ids or on error, a non-ObjectId string is returned as is. */\n async getFileInfo(id: string | undefined): Promise<LtFileInfo | null | string> {\n if (!id) {\n return null;\n }\n if (!isLtMongoId(id)) {\n return id;\n }\n try {\n const response = await this.fetch(`${this.baseUrl()}/info/${encodeURIComponent(id)}`, {\n credentials: 'include',\n headers: { accept: 'application/json' },\n method: 'GET',\n });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n const info = (await response.json()) as LtFileInfo | null;\n return info && typeof info === 'object' ? info : null;\n } catch (error) {\n if (isDevMode()) {\n console.error('[@lenne.tech/ng-base] Error fetching file info:', error);\n }\n return null;\n }\n }\n\n /**\n * `<api>/files/id/:id` — nest-server's `GET files/id/:id` (CoreFileController), which streams the file (or redirects\n * to a presigned S3 URL) after the rights check (`file.downloadRoles`, default admin). The URL the browser uses, also\n * during SSR (`NG_PUBLIC_API_URL` or `/api`): it ends up in `href`/`src`, so the server-only `NG_API_URL` never does.\n */\n getFileUrl(id: string): string {\n return `${this.linkBaseUrl()}/id/${encodeURIComponent(id)}`;\n }\n\n /**\n * Download URL. nest-server sets `Content-Disposition` from the stored filename on `GET files/id/:id`, so this is the\n * same URL as {@link getFileUrl}; `filename` is accepted for API compatibility with nuxt-extensions and ignored (the\n * server has no rename parameter — nuxt-extensions' `/files/download/:id` does not exist in nest-server 12.5).\n */\n getDownloadUrl(id: string, _filename?: string): string {\n return this.getFileUrl(id);\n }\n\n /** 1024-based `B`/`KB`/`MB`/`GB`/`TB` with two decimals. */\n formatFileSize(bytes: number): string {\n return formatLtFileSize(bytes);\n }\n\n /** `Ns`, `Nm Ns`, `Nh Nm`. */\n formatDuration(seconds: number): string {\n return formatLtDuration(seconds);\n }\n\n /** `/^[a-f\\d]{24}$/i`. */\n isValidMongoID(id: string): boolean {\n return isLtMongoId(id);\n }\n\n /** `<api><filesBasePath>` without a trailing slash; an absolute `filesBasePath` is used as is. */\n /** Base for requests this service sends itself (`NG_API_URL` during SSR). */\n private baseUrl(): string {\n const basePath = this.options.filesBasePath || '/files';\n return this.api.buildUrl(basePath).replace(/\\/+$/, '');\n }\n\n /** Base for links the browser follows (see {@link getFileUrl}). */\n private linkBaseUrl(): string {\n const basePath = this.options.filesBasePath || '/files';\n return this.api.buildBrowserUrl(basePath).replace(/\\/+$/, '');\n }\n}\n\n/** 1024-based file size with two decimals (`0 B` for non-positive input). */\nexport function formatLtFileSize(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes <= 0) {\n return '0 B';\n }\n const units = ['B', 'KB', 'MB', 'GB', 'TB'];\n const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);\n return `${parseFloat((bytes / 1024 ** exponent).toFixed(2))} ${units[exponent]}`;\n}\n\n/** Human readable duration (`45s`, `2m 5s`, `1h 3m`). */\nexport function formatLtDuration(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 60) {\n return `${Math.max(0, Math.round(Number.isFinite(seconds) ? seconds : 0))}s`;\n }\n if (seconds < 3600) {\n return `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;\n }\n return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;\n}\n\n/** Whether `id` is a MongoDB ObjectId (24 hex characters). */\nexport function isLtMongoId(id: string): boolean {\n return typeof id === 'string' && /^[a-f\\d]{24}$/i.test(id);\n}\n","import { InjectionToken } from '@angular/core';\n\n/** The request object tus-js-client hands to `onBeforeRequest` / `onAfterResponse`. */\nexport interface LtTusHttpRequest {\n getUnderlyingObject(): unknown;\n setHeader(header: string, value: string): void;\n}\n\n/** The response object tus-js-client hands to `onAfterResponse` (and inside `DetailedError.originalResponse`). */\nexport interface LtTusHttpResponse {\n getHeader(header: string): null | string | undefined;\n getStatus(): number;\n}\n\n/** A previous upload of the same file found in tus-js-client's URL storage. */\nexport interface LtTusPreviousUpload {\n creationTime?: string;\n}\n\n/** The tus-js-client upload options `injectLtTusUpload()` sets. */\nexport interface LtTusClientUploadOptions {\n chunkSize?: number;\n endpoint?: string;\n headers?: Record<string, string>;\n metadata?: Record<string, string>;\n onAfterResponse?: (req: LtTusHttpRequest, res: LtTusHttpResponse) => void;\n onBeforeRequest?: (req: LtTusHttpRequest) => void;\n onError?: (error: Error) => void;\n onProgress?: (bytesUploaded: number, bytesTotal: number) => void;\n onShouldRetry?: (error: Error & { originalResponse?: LtTusHttpResponse | null }, retryAttempt: number, options: unknown) => boolean;\n onSuccess?: () => void;\n removeFingerprintOnSuccess?: boolean;\n retryDelays?: number[];\n}\n\n/** The part of a tus-js-client `Upload` instance `injectLtTusUpload()` uses. */\nexport interface LtTusClientUpload {\n url: null | string;\n abort(shouldTerminate?: boolean): Promise<void>;\n findPreviousUploads(): Promise<LtTusPreviousUpload[]>;\n resumeFromPreviousUpload(previousUpload: LtTusPreviousUpload): void;\n start(): void;\n}\n\n/** The part of the `tus-js-client` module `injectLtTusUpload()` uses (structural, so the peer stays optional). */\nexport interface LtTusClient {\n Upload: new (file: File, options: LtTusClientUploadOptions) => LtTusClientUpload;\n}\n\n/** Error text when the optional peer `tus-js-client` is missing. */\nexport const LT_TUS_CLIENT_MISSING_MESSAGE =\n '[@lenne.tech/ng-base] tus-js-client is not installed. Install it to use uploads: pnpm add tus-js-client (or npm install tus-js-client).';\n\nlet tusClient: Promise<LtTusClient> | null = null;\n\n/** Load `tus-js-client` lazily (once); rejects with an install hint when the optional peer is missing. */\nexport function loadLtTusClient(): Promise<LtTusClient> {\n tusClient ??= import('tus-js-client').then(\n (module) => {\n const candidate = module as unknown as Partial<LtTusClient> & { default?: Partial<LtTusClient> };\n const client = candidate.Upload ? candidate : candidate.default;\n if (!client?.Upload) {\n throw new Error(LT_TUS_CLIENT_MISSING_MESSAGE);\n }\n return client as LtTusClient;\n },\n () => {\n throw new Error(LT_TUS_CLIENT_MISSING_MESSAGE);\n },\n );\n // A failed load is not cached, so installing the package and reloading the chunk can recover.\n tusClient.catch(() => (tusClient = null));\n return tusClient;\n}\n\n/**\n * Loader of the `tus-js-client` module used by `injectLtTusUpload()` (default: lazy `import('tus-js-client')`).\n * Replace it in tests with a fake client, or to supply a preconfigured build.\n */\nexport const LT_TUS_CLIENT_LOADER = new InjectionToken<() => Promise<LtTusClient>>('LT_TUS_CLIENT_LOADER', {\n factory: () => loadLtTusClient,\n});\n","import { assertInInjectionContext, computed, DestroyRef, inject, signal } from '@angular/core';\nimport { isLtAbsoluteUrl, LtApiUrlService } from '@lenne.tech/ng-base';\nimport { LtAuthStateService } from '@lenne.tech/ng-base/auth/provide';\n\nimport { LT_FILES_OPTIONS } from './files-options';\nimport { LT_TUS_CLIENT_LOADER, type LtTusClientUpload, type LtTusHttpRequest, type LtTusPreviousUpload } from './tus-client';\nimport type { LtTusUploadState, LtUploadItem, LtUploadOptions, LtUploadProgress } from './upload-types';\n\n/** A TUS upload queue with signal state, created by {@link injectLtTusUpload}. */\nexport type LtTusUpload = LtTusUploadState;\n\n/**\n * Response header in which nest-server (11.42.13+, `TUS_FILE_ID_HEADER`) names the stored file. Sent on the response that\n * completes the upload and listed in `Access-Control-Expose-Headers`.\n */\nexport const LT_TUS_FILE_ID_HEADER = 'X-File-Id';\n\n/** Default retry delays of a TUS upload in milliseconds. */\nexport const LT_TUS_RETRY_DELAYS: readonly number[] = [0, 1000, 3000, 5000, 10000];\n\nconst DEFAULT_PARALLEL_UPLOADS = 3;\n\ninterface ResolvedUploadOptions extends LtUploadOptions {\n autoStart: boolean;\n chunkSize: number;\n endpoint: string;\n parallelUploads: number;\n retryDelays: number[];\n}\n\n/** Exponential moving average (0.7 old / 0.3 new) of the upload speed in bytes per second. */\nclass LtSpeedTracker {\n private lastBytes = 0;\n private lastTime = 0;\n private smoothed = 0;\n private baseline = true;\n\n /** Start a new measurement from `bytes` (after a (re)start; the time of a pause does not count). */\n reset(bytes: number): void {\n this.lastBytes = bytes;\n this.lastTime = Date.now();\n this.smoothed = 0;\n this.baseline = true;\n }\n\n /** Record `bytes` uploaded so far and return the smoothed speed. */\n sample(bytes: number): number {\n const now = Date.now();\n if (this.baseline && bytes < this.lastBytes) {\n this.lastBytes = bytes; // a resumed upload reports its offset first\n }\n const seconds = (now - this.lastTime) / 1000;\n if (seconds > 0 && bytes >= this.lastBytes) {\n const current = (bytes - this.lastBytes) / seconds;\n this.smoothed = this.smoothed === 0 ? current : this.smoothed * 0.7 + current * 0.3;\n this.lastBytes = bytes;\n this.lastTime = now;\n this.baseline = false;\n }\n return Math.round(this.smoothed);\n }\n}\n\n/**\n * Create a TUS upload queue (counterpart of `useLtTusUpload()`), bound to the current injection context: running\n * uploads are aborted when the component/service is destroyed.\n *\n * `tus-js-client` (optional peer) is loaded lazily; without it `addFiles` rejects with an install hint.\n * Defaults: `autoStart` true, chunk size and endpoint from `provideLtFiles()`, 3 parallel uploads, retries\n * `[0, 1000, 3000, 5000, 10000]` (none on 4xx except 429), `withCredentials` for the session cookie, Bearer token\n * in JWT mode, the nest-server file id from the `X-File-Id` response header, resume via fingerprints.\n *\n * Options are read whenever an upload is created or started, so an object with getters (e.g. over component inputs)\n * stays current. `undefined` values keep the default. Uploads beyond `parallelUploads` wait in a queue and start as\n * soon as a slot is free; `startUpload`/`resumeUpload`/`retryUpload` of a single item start it right away.\n *\n * @example\n * protected readonly upload = injectLtTusUpload({ onSuccess: (item) => this.attach(item.fileId) });\n */\nexport function injectLtTusUpload(options: LtUploadOptions = {}): LtTusUpload {\n assertInInjectionContext(injectLtTusUpload);\n const filesOptions = inject(LT_FILES_OPTIONS);\n const api = inject(LtApiUrlService);\n const authState = inject(LtAuthStateService);\n const loadClient = inject(LT_TUS_CLIENT_LOADER);\n\n const items = signal<LtUploadItem[]>([]);\n const tusUploads = new Map<string, LtTusClientUpload>();\n const speedTrackers = new Map<string, LtSpeedTracker>();\n /** Start generation per upload: a pause/cancel invalidates a start whose resume lookup is still running. */\n const generations = new Map<string, number>();\n const queue: string[] = [];\n let sequence = 0;\n let destroyed = false;\n\n const uploads = items.asReadonly();\n const isUploading = computed(() => items().some((item) => item.status === 'uploading'));\n const totalProgress = computed<LtUploadProgress>(() => {\n const list = items();\n if (list.length === 0) {\n return idleProgress(0);\n }\n const bytesUploaded = list.reduce((sum, item) => sum + item.progress.bytesUploaded, 0);\n const bytesTotal = list.reduce((sum, item) => sum + item.progress.bytesTotal, 0);\n const speed = list.reduce((sum, item) => sum + item.progress.speed, 0);\n return {\n bytesTotal,\n bytesUploaded,\n percentage: bytesTotal > 0 ? Math.round((bytesUploaded / bytesTotal) * 100) : 0,\n remainingTime: speed > 0 ? Math.ceil((bytesTotal - bytesUploaded) / speed) : 0,\n speed,\n };\n });\n\n function config(): ResolvedUploadOptions {\n const defined = Object.fromEntries(Object.entries(options).filter(([, value]) => value !== undefined)) as LtUploadOptions;\n return {\n ...defined,\n autoStart: defined.autoStart ?? true,\n chunkSize: defined.chunkSize || filesOptions.tus.defaultChunkSize,\n endpoint: defined.endpoint || filesOptions.tus.defaultEndpoint,\n parallelUploads: Math.max(1, defined.parallelUploads || DEFAULT_PARALLEL_UPLOADS),\n retryDelays: defined.retryDelays ?? [...LT_TUS_RETRY_DELAYS],\n };\n }\n\n /** A relative endpoint is a path on the API (like auth and AI requests) unless `tus.resolveAgainstApi` is off. */\n function resolveEndpoint(endpoint: string): string {\n if (!endpoint || filesOptions.tus.resolveAgainstApi === false || isLtAbsoluteUrl(endpoint)) {\n return endpoint;\n }\n return api.buildUrl(endpoint.startsWith('/') ? endpoint : `/${endpoint}`);\n }\n\n function get(id: string): LtUploadItem | undefined {\n return items().find((item) => item.id === id);\n }\n\n function update(id: string, changes: Partial<LtUploadItem>): LtUploadItem | undefined {\n let updated: LtUploadItem | undefined;\n items.update((list) =>\n list.map((item) => {\n if (item.id !== id) {\n return item;\n }\n updated = { ...item, ...changes };\n return updated;\n }),\n );\n return updated;\n }\n\n function invalidate(id: string): void {\n generations.set(id, (generations.get(id) ?? 0) + 1);\n }\n\n function dequeue(id: string): void {\n const index = queue.indexOf(id);\n if (index >= 0) {\n queue.splice(index, 1);\n }\n }\n\n function uploadingCount(): number {\n return items().filter((item) => item.status === 'uploading').length;\n }\n\n /** Start queued uploads while slots are free. */\n function pump(): void {\n if (destroyed) {\n return;\n }\n const limit = config().parallelUploads;\n while (queue.length > 0 && uploadingCount() < limit) {\n const id = queue.shift() as string;\n const item = get(id);\n if (item && (item.status === 'idle' || item.status === 'paused')) {\n begin(id);\n }\n }\n }\n\n function enqueue(ids: string[]): void {\n for (const id of ids) {\n if (!queue.includes(id)) {\n queue.push(id);\n }\n }\n pump();\n }\n\n function begin(id: string): void {\n const item = get(id);\n const tusUpload = tusUploads.get(id);\n if (destroyed || !item || !tusUpload || item.status === 'uploading' || item.status === 'completed') {\n return;\n }\n dequeue(id);\n invalidate(id);\n const generation = generations.get(id);\n speedTrackers.get(id)?.reset(item.progress.bytesUploaded);\n update(id, { error: undefined, startedAt: new Date(), status: 'uploading' });\n\n const stillWanted = (): boolean => !destroyed && generations.get(id) === generation && get(id)?.status === 'uploading';\n // An upload that already has its URL continues there; a new one looks for an interrupted upload of the same file.\n const lookup: Promise<LtTusPreviousUpload[]> = tusUpload.url ? Promise.resolve([]) : tusUpload.findPreviousUploads().catch(() => []);\n void lookup.then((previousUploads) => {\n if (!stillWanted()) {\n return;\n }\n const previous = latest(previousUploads);\n if (previous) {\n tusUpload.resumeFromPreviousUpload(previous);\n }\n tusUpload.start();\n });\n }\n\n function settle(id: string): void {\n invalidate(id);\n pump();\n }\n\n function createTusUpload(client: Awaited<ReturnType<typeof loadClient>>, item: LtUploadItem, resolved: ResolvedUploadOptions): LtTusClientUpload {\n // Read in `onAfterResponse`, not from `onSuccess`'s payload: tus-js-client passes `{ lastResponse }` to `onSuccess`\n // only from 4.2.0, and the peer range starts at 4.0.0.\n let fileId: string | undefined;\n const callerSetsAuthorization = Object.keys(resolved.headers ?? {}).some((name) => name.toLowerCase() === 'authorization');\n\n const upload: LtTusClientUpload = new client.Upload(item.file, {\n chunkSize: resolved.chunkSize,\n endpoint: resolveEndpoint(resolved.endpoint),\n headers: resolved.headers,\n metadata: {\n filename: item.file.name,\n filetype: item.file.type,\n ...resolved.metadata,\n ...item.metadata,\n },\n onAfterResponse: (_req, res) => {\n // Only the completing response carries it; XHR answers `null` for a header that was not sent or not exposed.\n fileId = res.getHeader(LT_TUS_FILE_ID_HEADER) || fileId;\n },\n onBeforeRequest: (req: LtTusHttpRequest) => {\n const xhr = req.getUnderlyingObject() as { withCredentials?: boolean } | null;\n if (xhr && typeof xhr === 'object' && 'withCredentials' in xhr) {\n xhr.withCredentials = true;\n }\n if (!callerSetsAuthorization && authState.getAuthMode() === 'jwt') {\n const token = authState.getJwtToken();\n if (token) {\n req.setHeader('Authorization', `Bearer ${token}`);\n }\n }\n },\n onError: (error) => {\n const failed = update(item.id, { error: error.message, progress: { ...(get(item.id)?.progress ?? item.progress), remainingTime: 0, speed: 0 }, status: 'error' });\n settle(item.id);\n if (failed) {\n config().onError?.(failed, error);\n }\n },\n onProgress: (bytesUploaded, bytesTotal) => {\n const current = get(item.id);\n if (!current || current.status !== 'uploading') {\n return;\n }\n const speed = speedTrackers.get(item.id)?.sample(bytesUploaded) ?? 0;\n const progress: LtUploadProgress = {\n bytesTotal,\n bytesUploaded,\n percentage: bytesTotal > 0 ? Math.min(100, Math.round((bytesUploaded / bytesTotal) * 100)) : 0,\n remainingTime: speed > 0 ? Math.ceil((bytesTotal - bytesUploaded) / speed) : 0,\n speed,\n };\n const updated = update(item.id, { progress });\n if (updated) {\n config().onProgress?.(updated);\n }\n },\n onShouldRetry: (error) => {\n const status = error.originalResponse?.getStatus?.();\n // No retry on 4xx (except 429 Too Many Requests): the request itself is wrong or not allowed.\n return !(status && status >= 400 && status < 500 && status !== 429);\n },\n onSuccess: () => {\n const current = get(item.id);\n if (!current) {\n return;\n }\n const bytesTotal = current.progress.bytesTotal || item.file.size;\n const completed = update(item.id, {\n completedAt: new Date(),\n error: undefined,\n fileId,\n progress: { bytesTotal, bytesUploaded: bytesTotal, percentage: 100, remainingTime: 0, speed: 0 },\n status: 'completed',\n url: upload.url ?? undefined,\n });\n settle(item.id);\n if (completed) {\n config().onSuccess?.(completed);\n }\n },\n // Keep the fingerprint only for interrupted uploads: a completed one must not be \"resumed\" by the next upload of\n // the same file, which would finish without a request and therefore without an `X-File-Id`.\n removeFingerprintOnSuccess: true,\n retryDelays: resolved.retryDelays,\n });\n return upload;\n }\n\n async function addFiles(files: File | File[]): Promise<string[]> {\n const list = (Array.isArray(files) ? files : [files]).filter(Boolean);\n if (list.length === 0) {\n return [];\n }\n const client = await loadClient();\n if (destroyed) {\n return [];\n }\n const resolved = config();\n const ids: string[] = [];\n for (const file of list) {\n const id = `upload_${Date.now().toString(36)}_${++sequence}_${Math.random().toString(36).slice(2, 8)}`;\n const item: LtUploadItem = { file, id, metadata: resolved.metadata, progress: idleProgress(file.size), status: 'idle' };\n tusUploads.set(id, createTusUpload(client, item, resolved));\n speedTrackers.set(id, new LtSpeedTracker());\n items.update((current) => [...current, item]);\n ids.push(id);\n }\n if (resolved.autoStart) {\n enqueue(ids);\n }\n return ids;\n }\n\n function startUpload(id: string): void {\n begin(id);\n }\n\n function startAll(): void {\n enqueue(\n items()\n .filter((item) => item.status === 'idle' || item.status === 'paused')\n .map((item) => item.id),\n );\n }\n\n function pauseUpload(id: string): void {\n const item = get(id);\n const tusUpload = tusUploads.get(id);\n if (!item || !tusUpload) {\n return;\n }\n if (item.status === 'uploading') {\n invalidate(id);\n void tusUpload.abort().catch(() => undefined);\n update(id, { progress: { ...item.progress, remainingTime: 0, speed: 0 }, status: 'paused' });\n pump();\n } else if (item.status === 'idle') {\n // A queued upload is taken out of the queue.\n dequeue(id);\n update(id, { status: 'paused' });\n }\n }\n\n function pauseAll(): void {\n items()\n .filter((item) => item.status === 'uploading')\n .forEach((item) => pauseUpload(item.id));\n }\n\n function resumeUpload(id: string): void {\n if (get(id)?.status === 'paused') {\n begin(id);\n }\n }\n\n function resumeAll(): void {\n enqueue(\n items()\n .filter((item) => item.status === 'paused')\n .map((item) => item.id),\n );\n }\n\n function cancelUpload(id: string): void {\n const tusUpload = tusUploads.get(id);\n invalidate(id);\n dequeue(id);\n if (tusUpload) {\n void tusUpload.abort().catch(() => undefined);\n }\n tusUploads.delete(id);\n speedTrackers.delete(id);\n generations.delete(id);\n items.update((list) => list.filter((item) => item.id !== id));\n pump();\n }\n\n function cancelAll(): void {\n items().forEach((item) => cancelUpload(item.id));\n }\n\n function removeUpload(id: string): void {\n cancelUpload(id);\n }\n\n function clearCompleted(): void {\n items()\n .filter((item) => item.status === 'completed')\n .forEach((item) => removeUpload(item.id));\n }\n\n function retryUpload(id: string): void {\n if (get(id)?.status === 'error') {\n update(id, { error: undefined, status: 'idle' });\n begin(id);\n }\n }\n\n inject(DestroyRef).onDestroy(() => {\n destroyed = true;\n queue.length = 0;\n for (const tusUpload of tusUploads.values()) {\n void tusUpload.abort().catch(() => undefined);\n }\n });\n\n return {\n addFiles,\n cancelAll,\n cancelUpload,\n clearCompleted,\n getUpload: get,\n isUploading,\n pauseAll,\n pauseUpload,\n removeUpload,\n resumeAll,\n resumeUpload,\n retryUpload,\n startAll,\n startUpload,\n totalProgress,\n uploads,\n };\n}\n\nfunction idleProgress(bytesTotal: number): LtUploadProgress {\n return { bytesTotal, bytesUploaded: 0, percentage: 0, remainingTime: 0, speed: 0 };\n}\n\nfunction latest(previousUploads: LtTusPreviousUpload[]): LtTusPreviousUpload | undefined {\n return [...previousUploads].sort((a, b) => Date.parse(b.creationTime ?? '') - Date.parse(a.creationTime ?? '') || 0)[0];\n}\n"],"mappings":"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"}
@@ -692,10 +692,6 @@ const HlmBreadcrumbImports = [
692
692
  const LT_UI_MESSAGES = {
693
693
  de: { lt: { "ui": {
694
694
  "close": "Schließen",
695
- "colorMode": "Farbschema",
696
- "colorModeDark": "Dunkel",
697
- "colorModeLight": "Hell",
698
- "colorModeSystem": "System",
699
695
  "hidePassword": "Passwort verbergen",
700
696
  "showPassword": "Passwort anzeigen",
701
697
  "breadcrumb": "Brotkrumennavigation",
@@ -705,7 +701,6 @@ const LT_UI_MESSAGES = {
705
701
  "noData": "Keine Einträge",
706
702
  "selectPlaceholder": "Bitte auswählen",
707
703
  "searchPlaceholder": "Suchen...",
708
- "datePlaceholder": "Datum wählen",
709
704
  "time": "Uhrzeit",
710
705
  "confirm": "Bestätigen",
711
706
  "cancel": "Abbrechen",
@@ -726,10 +721,6 @@ const LT_UI_MESSAGES = {
726
721
  } } },
727
722
  en: { lt: { "ui": {
728
723
  "close": "Close",
729
- "colorMode": "Color mode",
730
- "colorModeDark": "Dark",
731
- "colorModeLight": "Light",
732
- "colorModeSystem": "System",
733
724
  "hidePassword": "Hide password",
734
725
  "showPassword": "Show password",
735
726
  "breadcrumb": "Breadcrumb",
@@ -739,7 +730,6 @@ const LT_UI_MESSAGES = {
739
730
  "noData": "No entries",
740
731
  "selectPlaceholder": "Please select",
741
732
  "searchPlaceholder": "Search...",
742
- "datePlaceholder": "Pick a date",
743
733
  "time": "Time",
744
734
  "confirm": "Confirm",
745
735
  "cancel": "Cancel",