import { Injectable, signal, computed } from '@angular/core'; import { applyThemeToCss, BootstrapPayload, PasswordPolicyInfo, Theme, } from './bootstrap.types'; export { applyThemeToCss, clearThemeCss, THEME_CSS_PROPERTIES, Theme, ThemeTokens, ThemeCssTarget, BootstrapPayload, PasswordPolicyInfo, } from './bootstrap.types'; const DEFAULT_POLICY: PasswordPolicyInfo = { minLength: 12, requireMixed: true, description: 'At least 12 characters with upper, lower, digit and symbol', }; @Injectable({ providedIn: 'root' }) export class BootstrapService { readonly payload = signal(null); readonly initialized = signal(false); readonly passwordPolicy = computed( () => this.payload()?.passwordPolicy ?? DEFAULT_POLICY, ); private loadPromise: Promise | null = null; async load(): Promise { if (!this.loadPromise) { this.loadPromise = this._load(); } return this.loadPromise; } private async _load(): Promise { try { const data = await this.fetchAndApply('/api/v1/bootstrap'); return data; } catch (e) { console.error('Bootstrap load failed', e); return null; } } ready(): Promise { if (!this.loadPromise) { this.loadPromise = this._load(); } return this.loadPromise; } async refresh(): Promise { const data = await this.fetchAndApply('/api/v1/bootstrap'); return data; } markInitialized(): void { this.initialized.set(true); } applyTheme(theme: Theme | null | undefined): void { applyThemeToCss(theme); } private async fetchAndApply(url: string): Promise { const res = await fetch(url, { credentials: 'include' }); if (!res.ok) { throw new Error(`Bootstrap request failed: ${res.status}`); } const data = (await res.json()) as BootstrapPayload; this.payload.set(data); this.initialized.set(data.initialized); this.applyTheme(data.theme); return data; } }