refactor(gui): centralize API origin handling
This commit is contained in:
parent
b50149af5d
commit
dd9e8da619
5 changed files with 58 additions and 26 deletions
|
|
@ -165,7 +165,7 @@ export class DashboardComponent {
|
|||
protected readonly providers = this.discovery.providers;
|
||||
protected readonly providerCount = computed(() => this.providers().length);
|
||||
protected readonly connected = this.websocket.connected;
|
||||
protected readonly apiBase = computed(() => this.apiConfig.baseUrl || window.location.origin);
|
||||
protected readonly apiBase = computed(() => this.apiConfig.effectiveBaseUrl);
|
||||
|
||||
protected readonly featuredProviders = computed<ProviderInfo[]>(() =>
|
||||
this.providers().filter((provider) => provider.element?.tag).slice(0, 6),
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export class ProviderHostComponent implements OnInit, OnChanges {
|
|||
|
||||
// Create and append the custom element
|
||||
const el = this.renderer.createElement(this.tag);
|
||||
const url = this.apiUrl || this.apiConfig.baseUrl;
|
||||
const url = this.apiUrl || this.apiConfig.effectiveBaseUrl;
|
||||
if (url) {
|
||||
this.renderer.setAttribute(el, 'api-url', url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@ export class ApiConfigService {
|
|||
return this._baseUrl;
|
||||
}
|
||||
|
||||
/** The effective API base URL, falling back to the current origin. */
|
||||
get effectiveBaseUrl(): string {
|
||||
return this._baseUrl || window.location.origin;
|
||||
}
|
||||
|
||||
/** Override the base URL. Strips trailing slash if present. */
|
||||
set baseUrl(url: string) {
|
||||
this._baseUrl = url.replace(/\/+$/, '');
|
||||
|
|
@ -24,6 +29,6 @@ export class ApiConfigService {
|
|||
/** Build a full URL for the given path. */
|
||||
url(path: string): string {
|
||||
const cleanPath = path.startsWith('/') ? path : `/${path}`;
|
||||
return `${this._baseUrl}${cleanPath}`;
|
||||
return `${this.effectiveBaseUrl}${cleanPath}`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,42 +33,69 @@ export class ProviderDiscoveryService {
|
|||
readonly providers = this._providers.asReadonly();
|
||||
|
||||
private discovered = false;
|
||||
private discoveryPromise: Promise<void> | null = null;
|
||||
private discoveryGeneration = 0;
|
||||
|
||||
constructor(private apiConfig: ApiConfigService) {}
|
||||
|
||||
/** Fetch providers from the API and load custom element scripts. */
|
||||
async discover(): Promise<void> {
|
||||
if (this.discovered) {
|
||||
async discover(force = false): Promise<void> {
|
||||
if (!force && this.discovered) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force && this.discoveryPromise) {
|
||||
return this.discoveryPromise;
|
||||
}
|
||||
|
||||
const generation = ++this.discoveryGeneration;
|
||||
const promise = this.doDiscover(generation);
|
||||
this.discoveryPromise = promise;
|
||||
|
||||
try {
|
||||
const res = await fetch(this.apiConfig.url('/api/v1/providers'));
|
||||
if (!res.ok) {
|
||||
console.warn('ProviderDiscoveryService: failed to fetch providers:', res.statusText);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const providers: ProviderInfo[] = data.providers ?? [];
|
||||
this._providers.set(providers);
|
||||
this.discovered = true;
|
||||
|
||||
// Load custom elements for Renderable providers
|
||||
for (const p of providers) {
|
||||
if (p.element?.tag && p.element?.source) {
|
||||
await this.loadElement(p.element.tag, p.element.source);
|
||||
}
|
||||
}
|
||||
await promise;
|
||||
} catch (err) {
|
||||
console.warn('ProviderDiscoveryService: discovery failed:', err);
|
||||
} finally {
|
||||
if (this.discoveryPromise === promise) {
|
||||
this.discoveryPromise = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Refresh the provider list (force re-discovery). */
|
||||
async refresh(): Promise<void> {
|
||||
this.discovered = false;
|
||||
await this.discover();
|
||||
await this.discover(true);
|
||||
}
|
||||
|
||||
private async doDiscover(generation: number): Promise<void> {
|
||||
const res = await fetch(this.apiConfig.url('/api/v1/providers'));
|
||||
if (!res.ok) {
|
||||
console.warn('ProviderDiscoveryService: failed to fetch providers:', res.statusText);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const providers: ProviderInfo[] = data.providers ?? [];
|
||||
|
||||
if (generation !== this.discoveryGeneration) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._providers.set(providers);
|
||||
this.discovered = true;
|
||||
|
||||
// Load custom elements for renderable providers.
|
||||
for (const provider of providers) {
|
||||
if (generation !== this.discoveryGeneration) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (provider.element?.tag && provider.element?.source) {
|
||||
await this.loadElement(provider.element.tag, provider.element.source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Dynamically load a custom element script if not already registered. */
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ export class WebSocketService implements OnDestroy {
|
|||
}
|
||||
|
||||
this.shouldReconnect = true;
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const base = this.apiConfig.baseUrl || window.location.origin;
|
||||
const cleanPath = path.startsWith('/') ? path : `/${path}`;
|
||||
const base = this.apiConfig.effectiveBaseUrl;
|
||||
const wsBase = base.replace(/^http/, 'ws');
|
||||
const url = `${wsBase.length > 0 ? wsBase : `${protocol}//${window.location.host}`}${path}`;
|
||||
const url = `${wsBase}${cleanPath}`;
|
||||
|
||||
this.ws = new WebSocket(url);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue