Files
HIPCTF2/frontend/src/app/core/services/bootstrap.service.ts
T

84 lines
2.1 KiB
TypeScript

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<BootstrapPayload | null>(null);
readonly initialized = signal<boolean>(false);
readonly passwordPolicy = computed<PasswordPolicyInfo>(
() => this.payload()?.passwordPolicy ?? DEFAULT_POLICY,
);
private loadPromise: Promise<BootstrapPayload | null> | null = null;
async load(): Promise<BootstrapPayload | null> {
if (!this.loadPromise) {
this.loadPromise = this._load();
}
return this.loadPromise;
}
private async _load(): Promise<BootstrapPayload | null> {
try {
const data = await this.fetchAndApply('/api/v1/bootstrap');
return data;
} catch (e) {
console.error('Bootstrap load failed', e);
return null;
}
}
ready(): Promise<BootstrapPayload | null> {
if (!this.loadPromise) {
this.loadPromise = this._load();
}
return this.loadPromise;
}
async refresh(): Promise<BootstrapPayload> {
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<BootstrapPayload> {
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;
}
}