Files
Recordly/electron/extensions/extensionIpc.ts

192 lines
5.3 KiB
TypeScript

/**
* Extension IPC Handlers — Main Process
*
* Registers IPC handlers for extension management (discover, install,
* uninstall, enable/disable) and exposes them to the renderer via preload.
*/
import { BrowserWindow, dialog, ipcMain, shell } from "electron";
import {
discoverExtensions,
getExtension,
getExtensionsDirectory,
getRegisteredExtensions,
installExtensionFromPath,
setExtensionStatus,
uninstallExtension,
} from "./extensionLoader";
import {
downloadAndInstallExtension,
fetchPendingReviews,
getMarketplaceExtension,
searchMarketplace,
submitExtensionForReview,
updateReviewStatus,
} from "./extensionMarketplace";
import { getErrorMessage } from "./errorUtils";
import type { ExtensionInfo, MarketplaceReviewStatus } from "./extensionTypes";
/**
* Serialize extension info for IPC transfer (strip non-serializable fields).
*/
function serializeExtensionInfo(info: ExtensionInfo) {
return {
manifest: info.manifest,
status: info.status,
path: info.path,
error: info.error,
builtin: info.builtin ?? false,
};
}
/**
* Register all extension-related IPC handlers.
* Call this once during app initialization (in main.ts).
*/
export function registerExtensionIpcHandlers(): void {
// Discover all extensions (builtin + user-installed)
ipcMain.handle("extensions:discover", async () => {
const extensions = await discoverExtensions();
return extensions.map(serializeExtensionInfo);
});
// List currently registered extensions
ipcMain.handle("extensions:list", () => {
return getRegisteredExtensions().map(serializeExtensionInfo);
});
// Get a specific extension by ID
ipcMain.handle("extensions:get", (_event, id: string) => {
const ext = getExtension(id);
return ext ? serializeExtensionInfo(ext) : null;
});
// Enable an extension
ipcMain.handle("extensions:enable", async (_event, id: string) => {
return setExtensionStatus(id, "active");
});
// Disable an extension
ipcMain.handle("extensions:disable", async (_event, id: string) => {
return setExtensionStatus(id, "disabled");
});
// Install an extension from a folder picker
ipcMain.handle("extensions:install-from-folder", async (event) => {
const window = BrowserWindow.fromWebContents(event.sender);
const result = await dialog.showOpenDialog(window!, {
title: "Select Extension Folder",
properties: ["openDirectory"],
message: "Select a folder containing a recordly-extension.json manifest",
});
if (result.canceled || result.filePaths.length === 0) {
return { success: false, reason: "cancelled" };
}
const info = await installExtensionFromPath(result.filePaths[0]);
if (!info) {
return {
success: false,
reason: "Invalid extension: missing or invalid recordly-extension.json",
};
}
return { success: true, extension: serializeExtensionInfo(info) };
});
// Uninstall an extension
ipcMain.handle("extensions:uninstall", async (_event, id: string) => {
const success = await uninstallExtension(id);
return { success };
});
// Get extensions directory path
ipcMain.handle("extensions:get-directory", () => {
return getExtensionsDirectory();
});
// Open extensions directory in file manager
ipcMain.handle("extensions:open-directory", async () => {
const dir = getExtensionsDirectory();
await shell.openPath(dir);
return { success: true };
});
// ── Marketplace ─────────────────────────────────────────────────────
// Search/browse marketplace
ipcMain.handle(
"extensions:marketplace-search",
async (
_event,
params: {
query?: string;
tags?: string[];
sort?: "popular" | "recent" | "rating";
page?: number;
pageSize?: number;
},
) => {
try {
return await searchMarketplace(params);
} catch (error: unknown) {
return {
extensions: [],
total: 0,
page: 1,
pageSize: 20,
error: getErrorMessage(error),
};
}
},
);
// Get a specific marketplace extension
ipcMain.handle("extensions:marketplace-get", async (_event, id: string) => {
return getMarketplaceExtension(id);
});
// Download and install a marketplace extension
ipcMain.handle(
"extensions:marketplace-install",
async (_event, extensionId: string, downloadUrl: string) => {
return downloadAndInstallExtension(extensionId, downloadUrl);
},
);
// Submit an extension for marketplace review
ipcMain.handle("extensions:marketplace-submit", async (_event, extensionId: string) => {
return submitExtensionForReview(extensionId);
});
// ── Admin Review System ─────────────────────────────────────────────
// Fetch pending reviews (admin only)
ipcMain.handle(
"extensions:reviews-list",
async (
_event,
params: {
status?: MarketplaceReviewStatus;
page?: number;
pageSize?: number;
},
) => {
try {
return await fetchPendingReviews(params);
} catch (error: unknown) {
return { reviews: [], total: 0, error: getErrorMessage(error) };
}
},
);
// Update review status (admin only)
ipcMain.handle(
"extensions:review-update",
async (_event, reviewId: string, status: MarketplaceReviewStatus, notes?: string) => {
return updateReviewStatus(reviewId, status, notes);
},
);
}