Skip to content

Commit

Permalink
fix(client-electron): prevent memory leak in preload
Browse files Browse the repository at this point in the history
  • Loading branch information
marcincichocki authored Oct 19, 2021
1 parent f43f616 commit 182db5a
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 30 deletions.
22 changes: 9 additions & 13 deletions src/electron/renderer/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
WorkerStatus,
} from '@/electron/common';
import { format, formatDistanceToNow } from 'date-fns';
import type { IpcRendererEvent } from 'electron';
import { useContext, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { ScreenshotDisplayOutput } from 'screenshot-desktop';
Expand Down Expand Up @@ -50,17 +49,13 @@ export function transformTimestamp(timestamp: number) {

export function useIpcEvent<T>(
channels: IpcOnChannels[],
callback: (event: IpcRendererEvent, value: T) => void
callback: (value: T) => void
) {
useEffect(() => {
channels.forEach((c) => {
api.on(c, callback);
});
const unsubscriptions = channels.map((c) => api.on(c, callback));

return () => {
channels.forEach((c) => {
api.removeListener(c, callback);
});
unsubscriptions.forEach((u) => u());
};
}, []);
}
Expand All @@ -70,7 +65,7 @@ export function useIpcEventDialog<T>(channel: IpcOnChannels) {
const [data, setData] = useState<T>(null);
const close = () => setIsOpen(false);

useIpcEvent([channel], (e, data: T) => {
useIpcEvent([channel], (data: T) => {
setData(data);
setIsOpen(true);
});
Expand All @@ -81,7 +76,7 @@ export function useIpcEventDialog<T>(channel: IpcOnChannels) {
export function useIpcState() {
const [state, setState] = useState<State>(api.getState());

function handleEvent(e: IpcRendererEvent, { payload }: Action<State>) {
function handleEvent({ payload }: Action<State>) {
setState(payload);
}

Expand Down Expand Up @@ -116,16 +111,17 @@ export function dispatchAsyncRequest<TRes, TReq = any>(
return new Promise<TRes>((resolve) => {
const uuid = uuidv4();
const req: Request<TReq> = { ...action, uuid };
let removeListener: () => void = null;

function onAsyncResponse(e: IpcRendererEvent, res: Response<TRes>) {
function onAsyncResponse(res: Response<TRes>) {
if (res.uuid !== uuid) return;

api.removeListener('renderer:async-response', onAsyncResponse);
removeListener();

resolve(res.data);
}

api.on('renderer:async-response', onAsyncResponse);
removeListener = api.on('renderer:async-response', onAsyncResponse);
api.send('main:async-request', req);
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/electron/renderer/components/StatusBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function useSettingsChangeListener(delay = 2000) {
const [show, setShow] = useState(false);
let id: any = null;

useIpcEvent([ActionTypes.UPDATE_SETTINGS], (e, { meta }: any) => {
useIpcEvent([ActionTypes.UPDATE_SETTINGS], ({ meta }: any) => {
if (meta?.notify === false) {
return;
}
Expand All @@ -90,7 +90,7 @@ function useSettingsChangeListener(delay = 2000) {
function useDownloadProgress() {
const [progress, setProgress] = useState(0);

useIpcEvent(['renderer:download-progress'], (e, info: ProgressInfo) => {
useIpcEvent(['renderer:download-progress'], (info: ProgressInfo) => {
setProgress(info.percent);
});

Expand Down
27 changes: 12 additions & 15 deletions src/electron/renderer/preload/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,20 @@ function validateChannel(input: string, whitelist: readonly string[]) {
}

/** Wrapper around {@link ipcRenderer.on}. Accepts only curated list of channels. */
export function on(
channel: IpcOnChannels,
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
) {
export function on(channel: IpcOnChannels, listener: (...args: any[]) => void) {
validateChannel(channel, onChannels);

ipcRenderer.on(channel, listener);
// Intentionally remove event from parameters.
const safeListener = (e: any, ...args: any[]) => listener(...args);

ipcRenderer.on(channel, safeListener);

return () => {
// Function passed to preload will be wrapped in a proxy and its
// reference will be lost. Creating local version of the listener will
// allow to free memory later on.
ipcRenderer.removeListener(channel, safeListener);
};
}

/** Wrapper around {@link ipcRenderer.send}. Accepts only curated list of channels. */
Expand All @@ -69,16 +76,6 @@ export function invoke<T = any>(channel: IpcInvokeChannels, ...args: any[]) {
return ipcRenderer.invoke(channel, ...args) as Promise<T>;
}

/** Wrapper around {@link ipcRenderer.removeListener}. Accepts only curated list of channels. */
export function removeListener(
channel: IpcOnChannels,
listener: (...args: any[]) => void
) {
validateChannel(channel, onChannels);

ipcRenderer.removeListener(channel, listener);
}

export function getState(): State {
return ipcRenderer.sendSync('main:get-state');
}
Expand Down

0 comments on commit 182db5a

Please sign in to comment.