Skip to content

[Tech] Add types to all IPC functions #1901

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4aceabb
Add types to all IPC functions
CommandMC Oct 13, 2022
f9d27b3
Fixup: Missing !
CommandMC Oct 16, 2022
f733f5f
Fixup: mainWindow.show()
CommandMC Oct 16, 2022
936dcd5
Fix `openDialog` properly
CommandMC Oct 16, 2022
a1fa47a
Merge branch 'beta' into feat/returntypes-ipc
CommandMC Oct 16, 2022
2cb5305
Merge branch 'beta' into feat/returntypes-ipc
CommandMC Oct 20, 2022
1dd4b4f
Merge branch 'beta' into feat/returntypes-ipc
CommandMC Oct 25, 2022
05b96b1
Add types to new IPC calls
CommandMC Oct 26, 2022
7d7e32f
Merge branch 'beta' into feat/returntypes-ipc
CommandMC Oct 26, 2022
9340aab
Define most IPC-related types in new .d.ts file
CommandMC Oct 29, 2022
ec857d5
Merge branch 'beta' into feat/returntypes-ipc
CommandMC Oct 29, 2022
6b62feb
Merge branch 'beta' into feat/returntypes-ipc
CommandMC Oct 30, 2022
458df89
Remove unused typedCallback and WrapApiFunction types
CommandMC Nov 1, 2022
55e70c8
Add some documentation about how IPC calls work now
CommandMC Nov 1, 2022
4aa85fb
Merge branch 'beta' into feat/returntypes-ipc
CommandMC Nov 2, 2022
de572ac
Remove unused 'getGOGGameClientId' IPC function
CommandMC Nov 2, 2022
b598e70
Merge branch 'beta' into feat/returntypes-ipc
CommandMC Nov 4, 2022
4e27015
More precise types for the `gamepadAction` IPC call & related functions
CommandMC Nov 4, 2022
b669d41
Merge branch 'beta' into feat/returntypes-ipc
CommandMC Nov 6, 2022
bd6e29d
Use `path || foo` everywhere (instead of `path ? path : foo`)
CommandMC Nov 7, 2022
f0ea90f
Remove unnecessary ternary operator
CommandMC Nov 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/ipc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
To add type safety to Electron's IPC system, we've made the following changes to what you'd usually see in an Electron app:

- Since `contextIsolation` is turned on, you cannot simply use `ipcRenderer.send`/`ipcRenderer.invoke` in the frontend to make IPC calls to the backend. Instead, we define a helper function for each IPC channel in `src/backend/api/`, which will be injected into the frontend on load. It'll then be available as `window.api.yourFunction`. See [Context Isolation](https://www.electronjs.org/docs/latest/tutorial/context-isolation) for more details
- To make sure that the parameters passed to & value returned from the callback of an `ipcMain.on`/`ipcMain.handle` call are correct, every IPC channel has to be defined in `src/common/typedefs/ipcBridge.d.ts` (adding new channels either to `SyncIPCFunctions` or `AsyncIPCFunctions`). This will also result in your callback's parameters automatically getting type annotations, and your helper function in `src/backend/api/` getting type-checked as well
6 changes: 3 additions & 3 deletions src/backend/anticheat/ipc_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { ipcMain } from 'electron'
import { gameAnticheatInfo } from './utils'

// we use the game's `namespace` value here, it's the value that can be easily fetch by AreWeAnticheatYet
ipcMain.handle('getAnticheatInfo', async (_, appNamespace) => {
return gameAnticheatInfo(appNamespace)
})
ipcMain.handle('getAnticheatInfo', (e, appNamespace) =>
gameAnticheatInfo(appNamespace)
)
14 changes: 5 additions & 9 deletions src/backend/api/downloadmanager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,13 @@ export const install = async (args: InstallParams) => {
ipcRenderer.send('addToDMQueue', dmQueueElement)
}

export const getDMQueueInformation = async () => {
return ipcRenderer.invoke('getDMQueueInformation')
}
export const getDMQueueInformation = async () =>
ipcRenderer.invoke('getDMQueueInformation')

export const removeFromDMQueue = (appName: string) => {
return ipcRenderer.send('removeFromDMQueue', appName)
}
export const removeFromDMQueue = (appName: string) =>
ipcRenderer.send('removeFromDMQueue', appName)

export const clearDMFinished = () => {
return ipcRenderer.send('clearDMFinished')
}
export const clearDMFinished = () => ipcRenderer.send('clearDMFinished')

export const handleDMQueueInformation = (
onChange: (e: Electron.IpcRendererEvent, elements: DMQueueElement[]) => void
Expand Down
79 changes: 52 additions & 27 deletions src/backend/api/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import {
InstallPlatform,
WineCommandArgs,
ConnectivityChangedCallback,
ConnectivityStatus
ConnectivityStatus,
AppSettings,
GameSettings,
RunWineCommandArgs
} from 'common/types'
import { GOGCloudSavesLocation } from 'common/types/gog'

export const notify = (notification: string[]) =>
ipcRenderer.send('Notify', notification)
export const notify = (args: { title: string; body: string }) =>
ipcRenderer.send('notify', args)
export const openLoginPage = () => ipcRenderer.send('openLoginPage')
export const openSidInfoPage = () => ipcRenderer.send('openSidInfoPage')
export const openSupportPage = () => ipcRenderer.send('openSupportPage')
Expand All @@ -22,24 +25,37 @@ export const openCustomThemesWiki = () =>
export const createNewWindow = (url: string) =>
ipcRenderer.send('createNewWindow', url)

export const readConfig = async (file: string) =>
export const readConfig = async (file: 'library' | 'user') =>
ipcRenderer.invoke('readConfig', file)

export const getPlatform = async () => ipcRenderer.invoke('getPlatform')

export const isLoggedIn = async () => ipcRenderer.invoke('isLoggedIn')
export const writeConfig = async (data: [appName: string, x: unknown]) =>
ipcRenderer.invoke('writeConfig', data)

export const writeConfig = async (data: {
appName: string
config: AppSettings | GameSettings
}) => ipcRenderer.invoke('writeConfig', data)

export const kill = async (appName: string, runner: Runner) =>
ipcRenderer.invoke('kill', appName, runner)
export const abort = async (id: string) => ipcRenderer.send('abort', id)

export const abort = (id: string) => ipcRenderer.send('abort', id)

export const getUserInfo = async () => ipcRenderer.invoke('getUserInfo')
export const syncSaves = async (
args: [arg: string | undefined, path: string, appName: string, runner: string]
) => ipcRenderer.invoke('syncSaves', args)

export const syncSaves = async (args: {
arg: string | undefined
path: string
appName: string
runner: Runner
}) => ipcRenderer.invoke('syncSaves', args)

export const getDefaultSavePath = async (
appName: string,
runner: Runner,
alreadyDefinedGogSaves: GOGCloudSavesLocation[] = []
): Promise<string | GOGCloudSavesLocation[]> =>
) =>
ipcRenderer.invoke(
'getDefaultSavePath',
appName,
Expand All @@ -48,36 +64,45 @@ export const getDefaultSavePath = async (
)
export const getGameInfo = async (appName: string, runner: Runner) =>
ipcRenderer.invoke('getGameInfo', appName, runner)
export const getGameSettings = async (appName: string, runner: Runner) =>

export const getGameSettings = async (
appName: string,
runner: Runner
): Promise<GameSettings | null> =>
ipcRenderer.invoke('getGameSettings', appName, runner)

export const getInstallInfo = async (
appName: string,
runner: Runner,
installPlatform?: InstallPlatform | string
installPlatform: InstallPlatform
) => ipcRenderer.invoke('getInstallInfo', appName, runner, installPlatform)

export const runWineCommand = async (args: WineCommandArgs) =>
ipcRenderer.invoke('runWineCommand', args)
export const runWineCommandForGame = async (args: {
appName: string
runner: Runner
commandParts: string[]
}) => ipcRenderer.invoke('runWineCommandForGame', args)
export const requestSettings = async (appName: string) =>
ipcRenderer.invoke('requestSettings', appName)

export const runWineCommandForGame = async (args: RunWineCommandArgs) =>
ipcRenderer.invoke('runWineCommandForGame', args)

export const requestAppSettings = async () =>
ipcRenderer.invoke('requestSettings', 'default') as Promise<AppSettings>

export const requestGameSettings = async (appName: string) =>
ipcRenderer.invoke('requestSettings', appName) as Promise<GameSettings>

export const onConnectivityChanged = async (
callback: ConnectivityChangedCallback
) => ipcRenderer.on('connectivity-changed', callback)
export const getConnectivityStatus = async (): Promise<ConnectivityStatus> =>
ipcRenderer.invoke('get-connectivity-status', [])

export const getConnectivityStatus = async () =>
ipcRenderer.invoke('get-connectivity-status')

export const connectivityChanged = async (newStatus: ConnectivityStatus) =>
ipcRenderer.send('connectivity-changed', newStatus)

export const getThemeCSS = async (theme: string): Promise<string> =>
export const isNative = async (args: { appName: string; runner: Runner }) =>
ipcRenderer.invoke('isNative', args)

export const getThemeCSS = async (theme: string) =>
ipcRenderer.invoke('getThemeCSS', theme)
export const getCustomThemes = async (): Promise<string[]> =>
ipcRenderer.invoke('getCustomThemes')

export const isNative = async (args: { appName: string; runner: Runner }) =>
ipcRenderer.invoke('isNative', args) as Promise<boolean>
export const getCustomThemes = async () => ipcRenderer.invoke('getCustomThemes')
63 changes: 37 additions & 26 deletions src/backend/api/library.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { ipcRenderer } from 'electron'
import { Runner, LaunchParams, SideloadGame, InstallParams } from 'common/types'
import {
Runner,
InstallParams,
LaunchParams,
SideloadGame,
ImportGameArgs,
GameStatus
} from 'common/types'

export const removeFolder = (args: [path: string, folderName: string]) =>
ipcRenderer.send('removeFolder', args)
Expand All @@ -9,48 +16,55 @@ export const openDialog = async (args: Electron.OpenDialogOptions) =>

export const install = async (args: InstallParams) =>
ipcRenderer.invoke('install', args)

export const uninstall = async (
args: [appName: string, shouldRemovePrefix: boolean, runner: Runner]
appName: string,
runner: Runner,
shouldRemovePrefix: boolean
) => {
const [appName, shouldRemovePrefix, runner] = args
if (runner === 'sideload') {
return ipcRenderer.invoke('removeApp', { appName, shouldRemovePrefix })
} else {
return ipcRenderer.invoke('uninstall', args)
return ipcRenderer.invoke('uninstall', appName, runner, shouldRemovePrefix)
}
}

export const repair = async (appName: string, runner: Runner) =>
ipcRenderer.invoke('repair', appName, runner)
export const launch = async (args: LaunchParams) => {

export const launch = async (args: LaunchParams) =>
ipcRenderer.invoke('launch', args)
}

export const updateGame = async (appName: string, runner: Runner) =>
ipcRenderer.invoke('updateGame', appName, runner)

interface ImportGameArgs {
appName: string
path: string
runner: Runner
}
export const importGame = async (args: ImportGameArgs) =>
ipcRenderer.invoke('importGame', args)

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const handleSetGameStatus = (callback: any) => {
export const handleSetGameStatus = (
callback: (event: Electron.IpcRendererEvent, status: GameStatus) => void
): (() => void) => {
ipcRenderer.on('setGameStatus', callback)
return () => {
ipcRenderer.removeListener('setGameStatus', callback)
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const handleLaunchGame = (callback: any) =>
ipcRenderer.on('launchGame', callback)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const handleInstallGame = (callback: any) =>
ipcRenderer.on('installGame', callback)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const handleRefreshLibrary = (callback: any) =>
ipcRenderer.on('refreshLibrary', callback)

export const handleLaunchGame = (
callback: (
event: Electron.IpcRendererEvent,
appName: string,
runner: Runner
) => Promise<{ status: 'done' | 'error' }>
) => ipcRenderer.on('launchGame', callback)

export const handleInstallGame = (
callback: (event: Electron.IpcRendererEvent, args: InstallParams) => void
) => ipcRenderer.on('installGame', callback)

export const handleRefreshLibrary = (
callback: (event: Electron.IpcRendererEvent, runner: Runner) => void
) => ipcRenderer.on('refreshLibrary', callback)

export const removeRecentGame = async (appName: string) =>
ipcRenderer.invoke('removeRecent', appName)
Expand All @@ -65,8 +79,5 @@ export const handleRecentGamesChanged = (callback: any) => {
export const addNewApp = (args: SideloadGame) =>
ipcRenderer.send('addNewApp', args)

export const removeApp = (appName: string) =>
ipcRenderer.send('removeApp', appName)

export const launchApp = async (appName: string) =>
export const launchApp = async (appName: string): Promise<boolean> =>
ipcRenderer.invoke('launchApp', appName)
33 changes: 22 additions & 11 deletions src/backend/api/menu.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
import { ipcRenderer } from 'electron'
import { Runner } from 'common/types'
import { MoveGameArgs, Runner } from 'common/types'

export const removeShortcut = (appName: string, runner: Runner) =>
ipcRenderer.send('removeShortcut', appName, runner)

export const addShortcut = (
appName: string,
runner: Runner,
fromMenu: boolean
) => ipcRenderer.send('addShortcut', appName, runner, fromMenu)
export const moveInstall = async (
args: [appName: string, path: string, runner: Runner]
) => ipcRenderer.invoke('moveInstall', args)
export const changeInstallPath = async (
args: [appName: string, path: string, runner: Runner]
) => ipcRenderer.invoke('changeInstallPath', args)

export const moveInstall = async (args: MoveGameArgs) =>
ipcRenderer.invoke('moveInstall', args)

export const changeInstallPath = async (args: MoveGameArgs) =>
ipcRenderer.invoke('changeInstallPath', args)

export const enableEosOverlay = async (
appName: string
): Promise<{ wasEnabled: boolean; installNow?: boolean }> =>
ipcRenderer.invoke('enableEosOverlay', appName)

export const disableEosOverlay = async (appName: string) =>
ipcRenderer.invoke('disableEosOverlay', appName)
export const enableEosOverlay = async (appName: string) =>
ipcRenderer.invoke('enableEosOverlay', appName)

export const isEosOverlayEnabled = async (appName?: string) =>
ipcRenderer.invoke('isEosOverlayEnabled', appName)

export const installEosOverlay = async () =>
ipcRenderer.invoke('installEosOverlay')

export const removeFromSteam = async (appName: string, runner: Runner) =>
ipcRenderer.invoke('removeFromSteam', appName, runner)

export const addToSteam = async (appName: string, runner: Runner) =>
ipcRenderer.invoke('addToSteam', appName, runner)

export const shortcutsExists = async (appName: string, runner: Runner) =>
ipcRenderer.invoke('shortcutsExists', appName, runner)

export const isAddedToSteam = async (appName: string, runner: Runner) =>
ipcRenderer.invoke('isAddedToSteam', appName, runner)
export const isEosOverlayEnabled = async (appName?: string) =>
ipcRenderer.invoke('isEosOverlayEnabled', appName)
19 changes: 11 additions & 8 deletions src/backend/api/misc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { GOGCloudSavesLocation } from 'common/types/gog'
import { ipcRenderer } from 'electron'
import { Runner, Tools, ButtonOptions, DialogType } from 'common/types'
import {
Runner,
Tools,
DialogType,
ButtonOptions,
GamepadActionArgs
} from 'common/types'

export const clearCache = () => ipcRenderer.send('clearCache')
export const resetHeroic = () => ipcRenderer.send('resetHeroic')
Expand Down Expand Up @@ -32,17 +38,16 @@ export const login = async (sid: string) => ipcRenderer.invoke('login', sid)
export const logoutLegendary = async () => ipcRenderer.invoke('logoutLegendary')
export const authGOG = async (token: string) =>
ipcRenderer.invoke('authGOG', token)
export const logoutGOG = async () => ipcRenderer.invoke('logoutGOG')
export const logoutGOG = () => ipcRenderer.send('logoutGOG')
export const checkGameUpdates = async () =>
ipcRenderer.invoke('checkGameUpdates')
export const refreshLibrary = async (
fullRefresh?: boolean,
library?: Runner | 'all'
) => ipcRenderer.invoke('refreshLibrary', fullRefresh, library)

export const gamepadAction = async (
args: [action: string, metadata: unknown]
) => ipcRenderer.invoke('gamepadAction', args)
export const gamepadAction = async (args: GamepadActionArgs) =>
ipcRenderer.invoke('gamepadAction', args)

export const logError = (error: string) => ipcRenderer.send('logError', error)
export const logInfo = (info: string) => ipcRenderer.send('logInfo', info)
Expand All @@ -63,8 +68,6 @@ export const getGOGLinuxInstallersLangs = async (appName: string) =>
ipcRenderer.invoke('getGOGLinuxInstallersLangs', appName)
export const getAlternativeWine = async () =>
ipcRenderer.invoke('getAlternativeWine')
export const getGOGGameClientId = async (appName: string) =>
ipcRenderer.invoke('getGOGGameClientId', appName)
export const getShellPath = async (saveLocation: string) =>
ipcRenderer.invoke('getShellPath', saveLocation)
export const callTool = async (toolArgs: Tools) =>
Expand All @@ -86,7 +89,7 @@ export const handleShowDialog = (
type: DialogType,
buttons?: Array<ButtonOptions>
) => void
) => {
): (() => void) => {
ipcRenderer.on('showDialog', onMessage)
return () => {
ipcRenderer.removeListener('showDialog', onMessage)
Expand Down
Loading