UNPKG

@zubridge/electron

Version:

A streamlined state management library for Electron applications using Zustand.

198 lines (197 loc) 6.46 kB
/** * Type guard to check if an object is an Electron WebContents */ export const isWebContents = (wrapperOrWebContents) => { return wrapperOrWebContents && typeof wrapperOrWebContents === 'object' && 'id' in wrapperOrWebContents; }; /** * Type guard to check if an object is a WebContentsWrapper */ export const isWrapper = (wrapperOrWebContents) => { return wrapperOrWebContents && typeof wrapperOrWebContents === 'object' && 'webContents' in wrapperOrWebContents; }; /** * Get the WebContents object from either a WebContentsWrapper or WebContents */ export const getWebContents = (wrapperOrWebContents) => { // Create a more readable description of the input for logging let description = 'Invalid input'; if (wrapperOrWebContents && typeof wrapperOrWebContents === 'object') { if ('id' in wrapperOrWebContents) { description = `WebContents ID: ${wrapperOrWebContents.id}`; } else if ('webContents' in wrapperOrWebContents) { description = `Wrapper with WebContents ID: ${wrapperOrWebContents.webContents?.id}`; } else { description = 'Unknown object type'; } } if (isWebContents(wrapperOrWebContents)) { return wrapperOrWebContents; } if (isWrapper(wrapperOrWebContents)) { const webContents = wrapperOrWebContents.webContents; return webContents; } return undefined; }; /** * Check if a WebContents is destroyed */ export const isDestroyed = (webContents) => { try { if (typeof webContents.isDestroyed === 'function') { const destroyed = webContents.isDestroyed(); return destroyed; } return false; } catch (error) { return true; } }; /** * Safely send a message to a WebContents */ export const safelySendToWindow = (webContents, channel, data) => { try { if (!webContents || isDestroyed(webContents)) { return false; } // Type check for WebContents API const hasWebContentsAPI = typeof webContents.send === 'function'; if (!hasWebContentsAPI) { return false; } // Check if isLoading is a function before calling it const isLoading = typeof webContents.isLoading === 'function' ? webContents.isLoading() : false; if (isLoading) { webContents.once('did-finish-load', () => { try { if (!webContents.isDestroyed()) { webContents.send(channel, data); } } catch (e) { } }); return true; } webContents.send(channel, data); return true; } catch (error) { return false; } }; /** * Set up cleanup when WebContents is destroyed */ export const setupDestroyListener = (webContents, cleanup) => { try { if (typeof webContents.once === 'function') { webContents.once('destroyed', () => { cleanup(); }); } } catch (e) { } }; /** * Creates a WebContents tracker that uses WeakMap for automatic garbage collection * but maintains a set of active IDs for tracking purposes */ export const createWebContentsTracker = () => { // WeakMap for the primary storage - won't prevent garbage collection const webContentsTracker = new WeakMap(); // Set to track active subscription IDs (not object references) const activeIds = new Set(); // Strong reference map of WebContents by ID - we need this to retrieve active WebContents // This will be maintained alongside the WeakMap const webContentsById = new Map(); return { track: (webContents) => { if (!webContents) { return false; } if (isDestroyed(webContents)) { return false; } const id = webContents.id; webContentsTracker.set(webContents, { id }); activeIds.add(id); webContentsById.set(id, webContents); // Set up the destroyed listener for cleanup setupDestroyListener(webContents, () => { activeIds.delete(id); webContentsById.delete(id); }); return true; }, untrack: (webContents) => { if (!webContents) { return; } const id = webContents.id; // Explicitly delete from all tracking structures webContentsTracker.delete(webContents); activeIds.delete(id); webContentsById.delete(id); }, untrackById: (id) => { activeIds.delete(id); const webContents = webContentsById.get(id); if (webContents) { webContentsTracker.delete(webContents); } webContentsById.delete(id); }, isTracked: (webContents) => { if (!webContents) { return false; } return webContents && webContentsTracker.has(webContents) && activeIds.has(webContents.id); }, hasId: (id) => { return activeIds.has(id); }, getActiveIds: () => { const ids = [...activeIds]; return ids; }, getActiveWebContents: () => { const result = []; // Filter out any destroyed WebContents that might still be in our map for (const [id, webContents] of webContentsById.entries()) { if (!isDestroyed(webContents)) { result.push(webContents); } else { activeIds.delete(id); webContentsById.delete(id); } } return result; }, cleanup: () => { activeIds.clear(); webContentsById.clear(); }, }; }; /** * Prepare WebContents objects from an array of wrappers or WebContents */ export const prepareWebContents = (wrappers) => { if (!wrappers || !Array.isArray(wrappers)) { return []; } const result = []; for (const wrapper of wrappers) { const webContents = getWebContents(wrapper); if (webContents && !isDestroyed(webContents)) { result.push(webContents); } } return result; };