Files
plugin-manager/dist/PluginMarketplaceManager.js
Max Litruv Boonzaayer dcc17946ef feat: Implement Plugin Marketplace Manager and Client
- Added PluginMarketplaceManager to coordinate marketplace reads and manage installed plugins.
- Introduced PluginMarketplaceClient for fetching marketplace metadata.
- Implemented storage management for installed plugins using MemoryStorage.
- Added methods for installing, uninstalling, and enabling/disabling plugins.
- Created type definitions for PluginMarketplaceManager options and installed plugin records.
- Added support for fetching full marketplace catalog and individual plugin metadata.
- Included error handling for fetch operations and validation of plugin data.
- Updated index.browser.ts to export new modules and types.
2026-04-19 01:03:58 +10:00

169 lines
7.1 KiB
JavaScript

import { MemoryStorage } from './interfaces.js';
const DEFAULT_INSTALLED_PLUGINS_STORAGE_KEY = 'plugin-manager:installed-plugins';
/**
* Coordinates marketplace reads with host-specific install and enable state.
*/
export class PluginMarketplaceManager {
constructor(options) {
this.client = options.client;
this.storage = options.storage ?? new MemoryStorage();
this.host = options.host;
this.registry = options.registry;
this.installedPluginsStorageKey =
options.installedPluginsStorageKey ?? DEFAULT_INSTALLED_PLUGINS_STORAGE_KEY;
}
/**
* Fetches full marketplace catalog.
*/
async fetchMarketplacePlugins() {
return this.client.fetchPlugins();
}
/**
* Lists installed plugins merged with persisted enabled state.
*/
async listInstalledPlugins() {
const storedPlugins = this.readStoredInstalledPlugins();
if (!this.host?.listInstalledPlugins) {
return storedPlugins;
}
const hostPlugins = await this.host.listInstalledPlugins();
const mergedPlugins = mergeInstalledPlugins(hostPlugins, storedPlugins);
this.writeStoredInstalledPlugins(mergedPlugins);
return mergedPlugins;
}
/**
* Lists installed plugins currently enabled.
*/
async listEnabledPlugins() {
return (await this.listInstalledPlugins()).filter((plugin) => plugin.enabled);
}
/**
* Returns whether plugin is recorded as installed.
*/
async isPluginInstalled(pluginId) {
return (await this.listInstalledPlugins()).some((plugin) => plugin.id === pluginId);
}
/**
* Installs marketplace plugin via host adapter and persists state.
*/
async installPlugin(plugin) {
await this.host?.installPlugin?.(plugin);
const installedPlugin = {
...plugin,
installedDate: new Date().toISOString(),
enabled: true,
};
const updatedPlugins = upsertInstalledPlugin(this.readStoredInstalledPlugins(), installedPlugin);
this.writeStoredInstalledPlugins(updatedPlugins);
return updatedPlugins.find((entry) => entry.id === plugin.id) ?? installedPlugin;
}
/**
* Removes plugin from host and clears persisted install state.
*/
async uninstallPlugin(pluginId) {
if (this.registry) {
await this.registry.unregisterPlugin(pluginId);
}
await this.host?.uninstallPlugin?.(pluginId);
this.writeStoredInstalledPlugins(this.readStoredInstalledPlugins().filter((plugin) => plugin.id !== pluginId));
}
/**
* Enables or disables installed plugin.
*
* Disabling triggers `registry.unregisterPlugin` when registry exists.
* Enabling only updates persisted state; host controls actual load timing.
*/
async setPluginEnabled(pluginId, enabled) {
if (!enabled && this.registry) {
await this.registry.unregisterPlugin(pluginId);
}
const installedPlugins = await this.listInstalledPlugins();
const targetPlugin = installedPlugins.find((plugin) => plugin.id === pluginId);
if (!targetPlugin) {
return null;
}
const updatedPlugin = { ...targetPlugin, enabled };
this.writeStoredInstalledPlugins(upsertInstalledPlugin(installedPlugins, updatedPlugin));
return updatedPlugin;
}
readStoredInstalledPlugins() {
const rawValue = this.storage.getItem(this.installedPluginsStorageKey);
if (!rawValue) {
return [];
}
try {
const parsedValue = JSON.parse(rawValue);
if (!Array.isArray(parsedValue)) {
return [];
}
return parsedValue
.map((entry) => normalizeInstalledPlugin(entry))
.filter((entry) => entry !== null);
}
catch {
return [];
}
}
writeStoredInstalledPlugins(plugins) {
this.storage.setItem(this.installedPluginsStorageKey, JSON.stringify(plugins));
}
}
function mergeInstalledPlugins(hostPlugins, storedPlugins) {
const storedPluginsById = new Map(storedPlugins.map((plugin) => [plugin.id, plugin]));
const discoveredAt = new Date().toISOString();
return hostPlugins.map((plugin) => {
const storedPlugin = storedPluginsById.get(plugin.id);
return {
id: plugin.id,
name: pickString(plugin.name, storedPlugin?.name),
version: pickString(plugin.version, storedPlugin?.version),
description: pickString(plugin.description, storedPlugin?.description),
author: pickString(plugin.author, storedPlugin?.author),
repository: pickString(plugin.repository, storedPlugin?.repository),
thumbnail: pickString(plugin.thumbnail, storedPlugin?.thumbnail),
downloadUrl: pickString(plugin.downloadUrl, storedPlugin?.downloadUrl),
homepage: pickString(plugin.homepage, storedPlugin?.homepage),
tags: Array.isArray(plugin.tags)
? plugin.tags.filter((entry) => typeof entry === 'string')
: storedPlugin?.tags ?? [],
addedDate: pickString(plugin.addedDate, storedPlugin?.addedDate),
installedDate: pickString(plugin.installedDate, storedPlugin?.installedDate) || discoveredAt,
enabled: typeof plugin.enabled === 'boolean' ? plugin.enabled : storedPlugin?.enabled ?? true,
};
});
}
function upsertInstalledPlugin(plugins, nextPlugin) {
const filteredPlugins = plugins.filter((plugin) => plugin.id !== nextPlugin.id);
return [...filteredPlugins, nextPlugin];
}
function normalizeInstalledPlugin(value) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return null;
}
const record = value;
const id = typeof record.id === 'string' ? record.id : '';
if (!id) {
return null;
}
return {
id,
name: typeof record.name === 'string' ? record.name : '',
version: typeof record.version === 'string' ? record.version : '',
description: typeof record.description === 'string' ? record.description : '',
author: typeof record.author === 'string' ? record.author : '',
repository: typeof record.repository === 'string' ? record.repository : '',
thumbnail: typeof record.thumbnail === 'string' ? record.thumbnail : '',
downloadUrl: typeof record.downloadUrl === 'string' ? record.downloadUrl : '',
homepage: typeof record.homepage === 'string' ? record.homepage : '',
tags: Array.isArray(record.tags)
? record.tags.filter((entry) => typeof entry === 'string')
: [],
addedDate: typeof record.addedDate === 'string' ? record.addedDate : '',
installedDate: typeof record.installedDate === 'string' ? record.installedDate : '',
enabled: typeof record.enabled === 'boolean' ? record.enabled : true,
};
}
function pickString(value, fallback = '') {
return typeof value === 'string' && value.length > 0 ? value : fallback;
}
//# sourceMappingURL=PluginMarketplaceManager.js.map