Skip to content

Commit f33d068

Browse files
committed
fix: memory leak in power IPC listeners
1 parent 6b6d2d1 commit f33d068

4 files changed

Lines changed: 15 additions & 13 deletions

File tree

PERFORMANCE_AUDIT.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,6 @@ This critical area for a background desktop app has been fully resolved.
7171
2. **Lazy Load `mathjs`**: Use `import()` to lazily load the `mathjs` engine similarly to how `openai` was handled.
7272

7373
### Low Priority
74-
3. **Optimize `electron-builder`**: Add `"compression": "maximum"` to `build` config in `package.json`.
75-
4. **Resolve ESLint Warnings**: Clear out the explicit `any` types across the codebase to ensure robust type safety during future expansions.
74+
3. **OpenAI Client Singleton**: The `openai` SDK is lazily imported correctly, but it re-instantiates the client on every `/ai` invocation. Implementing a singleton for the initialized client would prevent unnecessary object creation during rapid successive commands.
75+
4. **Optimize `electron-builder`**: Add `"compression": "maximum"` to `build` config in `package.json`.
76+
5. **Resolve ESLint Warnings**: Clear out the explicit `any` types across the codebase to ensure robust type safety during future expansions.

electron/preload.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@ contextBridge.exposeInMainWorld('electronAPI', {
2828
safeStorageEncrypt: (val: string) => ipcRenderer.invoke('safe-storage-encrypt', val),
2929
safeStorageDecrypt: (val: string) => ipcRenderer.invoke('safe-storage-decrypt', val),
3030
onPowerSuspend: (callback: () => void) => {
31-
ipcRenderer.on('power:suspend', () => callback())
31+
const handler = () => callback()
32+
ipcRenderer.on('power:suspend', handler)
33+
return () => ipcRenderer.removeListener('power:suspend', handler)
3234
},
3335
onPowerResume: (callback: () => void) => {
34-
ipcRenderer.on('power:resume', () => callback())
36+
const handler = () => callback()
37+
ipcRenderer.on('power:resume', handler)
38+
return () => ipcRenderer.removeListener('power:resume', handler)
3539
},
3640
})

src/hooks/useReminders.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,13 @@ export function useReminders() {
9595
checkAndSchedule()
9696
}
9797

98-
// ElectronAPI event handlers are persistent until removed, but since we re-bind on notes change,
99-
// we actually only want to update the `checkAndSchedule` closure.
100-
window.electronAPI.onPowerSuspend(onSuspend)
101-
window.electronAPI.onPowerResume(onResume)
98+
const unsubscribeSuspend = window.electronAPI.onPowerSuspend(onSuspend)
99+
const unsubscribeResume = window.electronAPI.onPowerResume(onResume)
102100

103101
return () => {
104102
if (timerRef.current) clearTimeout(timerRef.current)
105-
// Note: React's strict mode might double mount. Ideally we'd remove listeners here
106-
// but IPC `on` from preload doesn't currently return an un-listener.
107-
// It's acceptable for this hook since it's mounted once at App level.
103+
unsubscribeSuspend()
104+
unsubscribeResume()
108105
}
109106
}, [notes])
110107
}

src/types.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ export interface ElectronAPI {
1717
onTriggerTasks: (callback: () => void) => void
1818
safeStorageEncrypt: (val: string) => Promise<string>
1919
safeStorageDecrypt: (val: string) => Promise<string>
20-
onPowerSuspend: (callback: () => void) => void
21-
onPowerResume: (callback: () => void) => void
20+
onPowerSuspend: (callback: () => void) => () => void
21+
onPowerResume: (callback: () => void) => () => void
2222
}
2323

2424
declare global {

0 commit comments

Comments
 (0)