From 5161cff61786a5968e5bd573a364a826ec503c56 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Tue, 24 Mar 2026 10:31:14 -0400 Subject: [PATCH] refactor: preload consistency Signed-off-by: Adam Setch --- src/preload/index.ts | 13 +++++++------ src/preload/utils.test.ts | 3 +++ src/preload/utils.ts | 10 ++++++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/preload/index.ts b/src/preload/index.ts index 695d4ff8b..cb2e2d7ac 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -9,8 +9,8 @@ import { invokeMainEvent, onRendererEvent, sendMainEvent } from './utils'; /** * The Gitify Bridge API exposed to the renderer via `contextBridge`. * - * All renderer↔main IPC communication must go through this object. - * It is available on `window.gitify` inside the renderer process. + * Provides a safe, sandboxed interface for IPC communication between renderer and main. + * Accessible as `window.gitify` in the renderer. */ export const api = { /** @@ -118,7 +118,6 @@ export const api = { */ twemojiDirectory: () => invokeMainEvent(EVENTS.TWEMOJI_DIRECTORY), - /** Platform detection helpers. */ /** Platform detection helpers. */ platform: { /** Returns `true` when running on Linux. */ @@ -161,7 +160,7 @@ export const api = { }, }, - /** Electron web frame zoom controls. */ + /** Electron `webFrame` zoom controls. */ zoom: { /** * Return the current Electron zoom level. @@ -231,8 +230,10 @@ export const api = { }, }; -// Use `contextBridge` APIs to expose Electron APIs to renderer -// Context isolation is always enabled in this app +/** + * Use `contextBridge` APIs to expose Electron APIs to renderer. + * Context isolation is always enabled in this app + */ try { contextBridge.exposeInMainWorld('gitify', api); } catch (error) { diff --git a/src/preload/utils.test.ts b/src/preload/utils.test.ts index 11a90970f..5a24810b4 100644 --- a/src/preload/utils.test.ts +++ b/src/preload/utils.test.ts @@ -31,6 +31,7 @@ import { ipcRenderer } from 'electron'; describe('preload/utils', () => { it('sendMainEvent forwards to ipcRenderer.send', () => { sendMainEvent(EVENTS.WINDOW_SHOW); + expect(ipcRenderer.send).toHaveBeenCalledWith( EVENTS.WINDOW_SHOW, undefined, @@ -39,6 +40,7 @@ describe('preload/utils', () => { it('invokeMainEvent forwards and resolves', async () => { const result = await invokeMainEvent(EVENTS.VERSION, 'data'); + expect(ipcRenderer.invoke).toHaveBeenCalledWith(EVENTS.VERSION, 'data'); expect(result).toBe('response'); }); @@ -57,6 +59,7 @@ describe('preload/utils', () => { __emit: (channel: string, ...a: unknown[]) => void; } ).__emit(EVENTS.UPDATE_ICON_TITLE, 'payload'); + expect(ipcRenderer.on).toHaveBeenCalledWith( EVENTS.UPDATE_ICON_TITLE, handlerMock, diff --git a/src/preload/utils.ts b/src/preload/utils.ts index 223e9bb1b..b8edc5033 100644 --- a/src/preload/utils.ts +++ b/src/preload/utils.ts @@ -19,11 +19,17 @@ export function sendMainEvent(event: EventType, data?: EventData): void { * @param data - Optional string payload to include with the event. * @returns A promise that resolves to the string response from the main process. */ -export function invokeMainEvent( +export async function invokeMainEvent( event: EventType, data?: string, ): Promise { - return ipcRenderer.invoke(event, data); + try { + return await ipcRenderer.invoke(event, data); + } catch (err) { + // biome-ignore lint/suspicious/noConsole: preload environment is strictly sandboxed + console.error(`[IPC] invoke failed: ${event}`, err); + throw err; + } } /**