electron-pronto-interconnect
Version:
Instant React State Hooks for Electron
164 lines • 7.19 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.expose = expose;
exports.updateAndNotify = updateAndNotify;
exports.register = register;
const electron_1 = require("electron");
// A simple logger to control verbosity. Errors are always logged.
const logger = {
info: (verbose, message, ...args) => {
if (verbose) {
console.log(message, ...args);
}
},
warn: (verbose, message, ...args) => {
if (verbose) {
console.warn(message, ...args);
}
},
error: (message, ...args) => {
console.error(message, ...args);
},
};
const exposedState = new Map();
const registeredFunctions = new Map();
// Track which windows we are already watching for destruction
const monitoredWindows = new Set();
// Global cleanup handler
const handleWindowDestroyed = (webContents) => {
// Remove from our monitor list
monitoredWindows.delete(webContents);
// Loop through ALL exposed variables and remove this window from their listeners
exposedState.forEach((variableEntry) => {
if (variableEntry.listeners.has(webContents)) {
variableEntry.listeners.delete(webContents);
}
});
};
/**
* Exposes a variable from the main process to be used in renderer processes.
* @param variableName The unique name for this variable.
* @param initialValue The initial value of the variable.
* @param mainProcessSetter A callback function that updates the variable in the main process's scope.
* @param options Optional settings for the exposed variable, such as verbosity.
*/
function expose(variableName, initialValue, mainProcessSetter, options // Optional verbosity flag
) {
const verbose = options?.verbose ?? false;
if (exposedState.has(variableName)) {
logger.warn(true, `[IPC State] Variable "${variableName}" is already exposed. Overwriting.`);
}
const variableEntry = {
value: initialValue,
mainProcessSetter,
listeners: new Set(),
verbose, // Store the verbosity flag
};
exposedState.set(variableName, variableEntry);
// Handler for renderer to get the current/initial value
electron_1.ipcMain.handle(`ipc-get-${variableName}`, (event) => {
const entry = exposedState.get(variableName);
if (entry) {
logger.info(entry.verbose, `[IPC State] GET request for "${variableName}".`);
const senderWebContents = event.sender;
entry.listeners.add(senderWebContents);
// Central Cleanup Manager:
// Only attach the 'destroyed' listener if we aren't already monitoring this window.
// This prevents MaxListenersExceededWarning and ensures only one listener per window.
if (!monitoredWindows.has(senderWebContents)) {
monitoredWindows.add(senderWebContents);
senderWebContents.once('destroyed', () => {
handleWindowDestroyed(senderWebContents);
});
}
return entry.value;
}
logger.warn(true, `[IPC State] GET request for unexposed variable "${variableName}".`);
return undefined;
});
// Handler for renderer to set the value
electron_1.ipcMain.on(`ipc-set-${variableName}`, (_event, newValue) => {
const entry = exposedState.get(variableName);
if (entry) {
try {
entry.mainProcessSetter(newValue); // Update variable in main.ts scope
entry.value = newValue; // Update the synchronized value in our manager
// Broadcast the change to all listening renderers
entry.listeners.forEach(listenerWebContents => {
if (!listenerWebContents.isDestroyed()) {
listenerWebContents.send(`ipc-update-${variableName}`, entry.value);
}
});
}
catch (error) {
logger.error(`[IPC State] Error in mainProcessSetter for "${variableName}":`, error);
}
}
else {
logger.warn(true, `[IPC State] SET request for unexposed variable "${variableName}".`);
}
});
logger.info(verbose, `[IPC State] Variable "${variableName}" exposed.`);
}
/**
* Allows the main process to programmatically update an exposed variable
* and notify all listening renderers.
* @param variableName The name of the variable to update.
* @param newValue The new value for the variable.
*/
function updateAndNotify(variableName, newValue) {
const entry = exposedState.get(variableName);
if (entry) {
try {
entry.mainProcessSetter(newValue); // Update variable in main.ts scope
entry.value = newValue; // Update the synchronized value
entry.listeners.forEach(listenerWebContents => {
if (!listenerWebContents.isDestroyed()) {
listenerWebContents.send(`ipc-update-${variableName}`, entry.value);
}
});
}
catch (error) {
logger.error(`[IPC State] Error in mainProcessSetter during updateAndNotify for "${variableName}":`, error);
}
}
else {
logger.warn(true, `[IPC State] updateAndNotify called for unexposed variable "${variableName}". You might need to expose it first.`);
}
}
/**
* Registers a function from the main process that can be invoked from renderer processes.
* @param functionName The unique name for this function.
* @param handler The async or sync function to execute when called from a renderer.
* @param options Optional settings for the registered function, such as verbosity.
*/
function register(functionName,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handler, options // Optional verbosity flag
) {
const channel = `ipc-errand-${functionName}`; // Channel name updated for clarity
const verbose = options?.verbose ?? false; // Default verbosity to false if not provided
if (registeredFunctions.has(functionName)) {
logger.warn(true, `[IPC Errand] Function "${functionName}" is already registered. Overwriting handler.`);
// To truly overwrite, we must remove the old handler first.
electron_1.ipcMain.removeHandler(channel);
}
registeredFunctions.set(functionName, {
handler,
verbose, // Store the verbosity flag
});
electron_1.ipcMain.handle(channel, async (_event, ...args) => {
logger.info(verbose, `[IPC Errand] Received errand for "${functionName}" with args:`, args);
try {
// Await the handler in case it's async. If it's sync, it will resolve immediately.
return await handler(...args);
}
catch (error) {
logger.error(`[IPC Errand] Error executing function "${functionName}":`, error);
// It's important to re-throw or return an error object so the renderer's promise rejects.
throw error;
}
});
logger.info(verbose, `[IPC Errand] Function "${functionName}" registered on channel "${channel}".`);
}
//# sourceMappingURL=manager.js.map