import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, catchError, map, of, shareReplay, switchMap, take, tap } from 'rxjs'; import { WidgetManifestEntry, WidgetManifestFile } from '../contracts/widget-manifest.contract'; import { ConfigService } from '../../core/config/config.service'; @Injectable({ providedIn: 'root' }) export class WidgetManifestService { private readonly fallbackManifestUrl = '/assets/mock/bootstrap/widget-manifest.json'; private readonly manifestByUrl = new Map>(); /** Last manifest resolved by getManifest(), for synchronous readers (e.g. ProjectValidator) that can't await an Observable. Mirrors ConfigService.getBootstrapSnapshot(). */ private manifestSnapshot: WidgetManifestFile | null = null; constructor( private readonly http: HttpClient, private readonly configService: ConfigService ) {} getManifest(): Observable { return this.resolveManifestUrl().pipe( switchMap((manifestUrl) => { const cached = this.manifestByUrl.get(manifestUrl); if (cached) { return cached; } const manifest$ = this.http.get(manifestUrl).pipe( tap(manifest => { this.manifestSnapshot = manifest; }), shareReplay({ bufferSize: 1, refCount: true }), catchError(() => of({ widgets: [] })) ); this.manifestByUrl.set(manifestUrl, manifest$); return manifest$; }) ); } getWidgets(): Observable { return this.getManifest().pipe(map((manifest) => manifest.widgets ?? [])); } getWidget(type: string): Observable { return this.getWidgets().pipe(map((widgets) => widgets.find((widget) => widget.type === type))); } /** Synchronous accessor for the last-resolved manifest, or null before it's loaded once. */ getManifestSnapshot(): WidgetManifestFile | null { return this.manifestSnapshot; } private resolveManifestUrl(): Observable { const snapshotUrl = this.configService.getBootstrapSnapshot()?.widgetRegistry?.manifestUrl; if (snapshotUrl) { return of(snapshotUrl); } return this.configService.loadBootstrap().pipe( take(1), map((bootstrap) => bootstrap.widgetRegistry?.manifestUrl || this.fallbackManifestUrl), catchError(() => of(this.fallbackManifestUrl)) ); } }