/** * Host-agnostic client for reading marketplace metadata. */ export class PluginMarketplaceClient { constructor(options) { this.indexUrl = options.indexUrl; this.baseUrl = options.baseUrl; this.fetchFn = options.fetchFn; } /** * Fetches and validates marketplace index. */ async fetchIndex() { const response = await this.getFetchFn()(this.indexUrl); if (!response.ok) { throw new Error(`Failed to fetch plugin index: HTTP ${response.status}`); } return normalizePluginIndex(await response.json()); } /** * Fetches and validates single plugin metadata entry. * * @param pluginId - Identifier referenced by marketplace index. */ async fetchPlugin(pluginId) { const response = await this.getFetchFn()(this.buildMetadataUrl(pluginId)); if (!response.ok) { throw new Error(`Failed to fetch plugin ${pluginId}: HTTP ${response.status}`); } return normalizePluginMetadata(await response.json(), pluginId); } /** * Fetches full marketplace catalog. * * Entries that fail fetch or validation get skipped so host can still render * rest of catalog. */ async fetchPlugins() { const index = await this.fetchIndex(); const results = await Promise.allSettled(index.plugins.map((pluginId) => this.fetchPlugin(pluginId))); return results.flatMap((result) => (result.status === 'fulfilled' ? [result.value] : [])); } buildMetadataUrl(pluginId) { const trimmedBaseUrl = this.baseUrl.endsWith('/') ? this.baseUrl : `${this.baseUrl}/`; return `${trimmedBaseUrl}${pluginId}.json`; } getFetchFn() { if (this.fetchFn) { return this.fetchFn; } if (typeof fetch !== 'function') { throw new Error('No fetch implementation available for PluginMarketplaceClient'); } return fetch; } } function normalizePluginIndex(value) { const record = asRecord(value, 'Plugin index payload must be object'); return { version: asString(record.version), updatedAt: asString(record.updatedAt), plugins: Array.isArray(record.plugins) ? record.plugins.filter((entry) => typeof entry === 'string') : [], }; } function normalizePluginMetadata(value, fallbackId) { const record = asRecord(value, `Plugin metadata for ${fallbackId} must be object`); return { id: asString(record.id) || fallbackId, name: asString(record.name), version: asString(record.version), description: asString(record.description), author: asString(record.author), repository: asString(record.repository), thumbnail: asString(record.thumbnail), downloadUrl: asString(record.downloadUrl), homepage: asString(record.homepage), tags: Array.isArray(record.tags) ? record.tags.filter((entry) => typeof entry === 'string') : [], addedDate: asString(record.addedDate), }; } function asRecord(value, message) { if (!value || typeof value !== 'object' || Array.isArray(value)) { throw new Error(message); } return value; } function asString(value) { return typeof value === 'string' ? value : ''; } //# sourceMappingURL=PluginMarketplaceClient.js.map