Add type annotations to IPC handler parameters#179
Conversation
…dlers IPC handler callbacks in device.ts and sync.ts were receiving parameters typed as implicit `any`, providing no compile-time safety at the Electron IPC boundary. This adds explicit types so TypeScript can catch mismatches between what the renderer sends and what the main process expects. https://claude.ai/code/session_017mYtaignsX8oCKT17X1fJS
There was a problem hiding this comment.
The added type annotations are an improvement, but IPC remains a runtime trust boundary: typing alone does not prevent malformed or malicious payloads from the renderer. Using broad shapes like Device/Partial<Device> for IPC inputs is permissive and can unintentionally expand what the main process accepts. Adding runtime validation (and narrowing to explicit IPC input types) would materially improve robustness and security.
Summary of changes
Summary of changes
-
src/main/ipc/device.ts- Added
import type { Device } from '@/types/device'. - Annotated IPC handler parameters for
device:create,device:remove,device:update, anddevice:checkDeviceMount(e.g.,id: string,data: Device,data: Partial<Device>).
- Added
-
src/main/ipc/sync.ts- Annotated the
sync:starthandler parameters astagIds: string[],deviceId: string, andoptions: SyncOptions.
- Annotated the
| ipcMain.handle('device:create', (_, data: Device) => addDevice(data)); | ||
| ipcMain.handle('device:remove', (_, id: string) => removeDevice(id)); | ||
| ipcMain.handle('device:update', (_, id: string, data: Partial<Device>) => updateDevice(id, data)); | ||
| ipcMain.handle('device:checkDeviceMount', (_, deviceId: string) => checkDeviceMount(deviceId)); |
There was a problem hiding this comment.
These annotations help at compile time, but they don’t protect the IPC trust boundary at runtime. A compromised/buggy renderer can still send arbitrary payloads, and Device / especially Partial<Device> is quite permissive for an externally-provided input.
Consider validating and narrowing IPC payloads (and ideally using input types like DeviceCreateInput / DeviceUpdateInput that only include fields you actually accept) before forwarding into the service layer.
Suggestion
Add runtime validation (e.g., Zod) + tighten accepted input shapes. For example:
- Define schemas/types for IPC inputs (create/update) rather than using
Device/Partial<Device>. - Parse inside the handler before calling services.
import { z } from 'zod';
const DeviceCreateSchema = z.object({
// only the fields you actually accept from the renderer
name: z.string(),
// ...
});
type DeviceCreateInput = z.infer<typeof DeviceCreateSchema>;
ipcMain.handle('device:create', (_e, data: unknown) => {
const input: DeviceCreateInput = DeviceCreateSchema.parse(data);
return addDevice(input);
});Reply with "@CharlieHelps yes please" if you'd like me to add a commit with a minimal validation + input-type tightening pass for these handlers.
| ipcMain.handle('sync:start', (_, tagIds: string[], deviceId: string, options: SyncOptions) => | ||
| startSync(tagIds, deviceId, options) | ||
| ); |
There was a problem hiding this comment.
Same concern here: the new types improve readability and catch mistakes in main-process call sites, but they don’t validate what arrives over IPC. If startSync assumes invariants (non-empty tagIds, allowed option values, etc.), it’s safer to enforce them at the handler boundary and fail fast with a clear error.
Suggestion
Validate IPC inputs before calling startSync, even if it’s just minimal guards (or ideally a schema).
ipcMain.handle('sync:start', (_e, tagIds: unknown, deviceId: unknown, options: unknown) => {
if (!Array.isArray(tagIds) || !tagIds.every((t) => typeof t === 'string')) throw new Error('Invalid tagIds');
if (typeof deviceId !== 'string') throw new Error('Invalid deviceId');
// validate options similarly (or use a schema)
return startSync(tagIds, deviceId, options as SyncOptions);
});Reply with "@CharlieHelps yes please" if you'd like me to add a commit that introduces a small schema/guard layer for sync:start.
Summary
IPC handler callbacks in
src/main/ipc/device.tsandsrc/main/ipc/sync.tswere accepting parameters typed as implicitany. Since IPC is an important trust boundary between Electron's renderer and main processes, having untyped parameters means TypeScript can't catch bugs where the wrong data is passed or the shape of arguments changes.sync.ts, thesync:starthandler now declarestagIds: string[],deviceId: string, andoptions: SyncOptions— types that were already imported in the file but not applied to the handler callback.device.ts, the four handlers that forward arguments to service functions now declare their parameters using theDevicetype (already used throughout the service layer), making the handler signatures self-documenting and compiler-checked.This makes the IPC layer consistent: other handlers in the codebase (e.g.
settings.ts) already annotate their parameters, so these two files were the odd ones out.What's better
https://claude.ai/code/session_017mYtaignsX8oCKT17X1fJS