Skip to content

Commit 4e42741

Browse files
BrettClearyflavioislimaCommandMC
authored
[Tech] Enable sandboxing for ipcRenderer Processes (#1783)
* Refactored all ipcRenderer calls into preload script * preload script working, context isolation enabled * Cleaning up code, adding comments documenting design decisions * preload path in vite.config plugin, checks if legendary folder exists, uncomment react devtools, exe optional parameter on Tools * Merge branch 'main' of github.com:Heroic-Games-Launcher/HeroicGamesLauncher into beta * [Fix] Check if Legendary's `metadata` folder exists before trying to read it (#1785) * refactor ipcRenderer calls in Tools component * fixing yarn codecheck issues with window.api calls * fixed type issues window api install and rm wine * changed frontend error call to logError, removed superfluous comments * installParams type now used in library api, library will refresh installed games on sync regardless if online or not, additional check for is installed when rendering game cards * cleaning up after resolving conflicts with beta * removing multiple imports * fixing wine progress issue, rm isDefault * renaming handleProgressOf to handleProgressOfWineManager * wine manager download progress fixes * typing wine api methods Co-authored-by: Flavio F Lima <[email protected]> Co-authored-by: Mathis Dröge <[email protected]>
1 parent 3f6541c commit 4e42741

File tree

48 files changed

+863
-506
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+863
-506
lines changed

src/backend/api/helpers.ts

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { ipcRenderer } from 'electron'
2+
import { Runner, InstallPlatform } from '../../common/types'
3+
4+
export const notify = (notification: string[]) =>
5+
ipcRenderer.send('Notify', notification)
6+
export const openLoginPage = () => ipcRenderer.send('openLoginPage')
7+
export const openSidInfoPage = () => ipcRenderer.send('openSidInfoPage')
8+
export const openSupportPage = () => ipcRenderer.send('openSupportPage')
9+
export const quit = () => ipcRenderer.send('quit')
10+
export const showAboutWindow = () => ipcRenderer.send('showAboutWindow')
11+
export const openDiscordLink = () => ipcRenderer.send('openDiscordLink')
12+
export const createNewWindow = (url: string) =>
13+
ipcRenderer.send('createNewWindow', url)
14+
15+
export const readConfig = async (file: string) =>
16+
ipcRenderer.invoke('readConfig', file)
17+
export const getPlatform = async () => ipcRenderer.invoke('getPlatform')
18+
export const isLoggedIn = async () => ipcRenderer.invoke('isLoggedIn')
19+
export const writeConfig = async (data: [appName: string, x: unknown]) =>
20+
ipcRenderer.invoke('writeConfig', data)
21+
export const kill = async (appName: string, runner: Runner) =>
22+
ipcRenderer.invoke('kill', appName, runner)
23+
export const getUserInfo = async () => ipcRenderer.invoke('getUserInfo')
24+
export const syncSaves = async (
25+
args: [arg: string | undefined, path: string, appName: string, runner: string]
26+
) => ipcRenderer.invoke('syncSaves', args)
27+
export const getGameInfo = async (appName: string, runner: Runner) =>
28+
ipcRenderer.invoke('getGameInfo', appName, runner)
29+
export const getGameSettings = async (appName: string, runner: Runner) =>
30+
ipcRenderer.invoke('getGameSettings', appName, runner)
31+
export const getInstallInfo = async (
32+
appName: string,
33+
runner: Runner,
34+
installPlatform?: InstallPlatform | string
35+
) => ipcRenderer.invoke('getInstallInfo', appName, runner, installPlatform)
36+
interface runWineCommand {
37+
appName: string
38+
runner: string
39+
command: string
40+
}
41+
export const runWineCommandForGame = async (command: runWineCommand) =>
42+
ipcRenderer.invoke('runWineCommandForGame', command)
43+
export const requestSettings = async (appName: string) =>
44+
ipcRenderer.invoke('requestSettings', appName)

src/backend/api/index.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import * as Misc from './misc'
2+
import * as Helpers from './helpers'
3+
import * as Library from './library'
4+
import * as Menu from './menu'
5+
import * as Settings from './settings'
6+
import * as Wine from './wine'
7+
8+
export default {
9+
...Misc,
10+
...Helpers,
11+
...Library,
12+
...Menu,
13+
...Settings,
14+
...Wine
15+
}

src/backend/api/library.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { ipcRenderer } from 'electron'
2+
import { Runner, InstallParams, LaunchParams } from '../../common/types'
3+
4+
export const removeFolder = (args: [path: string, folderName: string]) =>
5+
ipcRenderer.send('removeFolder', args)
6+
7+
export const openDialog = async (args: Electron.OpenDialogOptions) =>
8+
ipcRenderer.invoke('openDialog', args)
9+
10+
export const install = async (args: InstallParams) =>
11+
ipcRenderer.invoke('install', args)
12+
export const openMessageBox = async (args: Electron.MessageBoxOptions) =>
13+
ipcRenderer.invoke('openMessageBox', args)
14+
export const uninstall = async (
15+
args: [appName: string, shouldRemovePrefix: boolean, runner: Runner]
16+
) => ipcRenderer.invoke('uninstall', args)
17+
export const repair = async (appName: string, runner: Runner) =>
18+
ipcRenderer.invoke('repair', appName, runner)
19+
export const launch = async (args: LaunchParams) =>
20+
ipcRenderer.invoke('launch', args)
21+
export const updateGame = async (appName: string, runner: Runner) =>
22+
ipcRenderer.invoke('updateGame', appName, runner)
23+
24+
interface ImportGameArgs {
25+
appName: string
26+
path: string
27+
runner: Runner
28+
}
29+
export const importGame = async (args: ImportGameArgs) =>
30+
ipcRenderer.invoke('importGame', args)
31+
32+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
33+
export const handleSetGameStatus = (callback: any) => {
34+
ipcRenderer.on('setGameStatus', callback)
35+
return () => {
36+
ipcRenderer.removeListener('setGameStatus', callback)
37+
}
38+
}
39+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
40+
export const handleLaunchGame = (callback: any) =>
41+
ipcRenderer.on('launchGame', callback)
42+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
43+
export const handleInstallGame = (callback: any) =>
44+
ipcRenderer.on('installGame', callback)
45+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
46+
export const handleRefreshLibrary = (callback: any) =>
47+
ipcRenderer.on('refreshLibrary', callback)

src/backend/api/menu.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ipcRenderer } from 'electron'
2+
import { Runner } from '../../common/types'
3+
4+
export const removeShortcut = (appName: string, runner: Runner) =>
5+
ipcRenderer.send('removeShortcut', appName, runner)
6+
export const addShortcut = (
7+
appName: string,
8+
runner: Runner,
9+
fromMenu: boolean
10+
) => ipcRenderer.send('addShortcut', appName, runner, fromMenu)
11+
export const moveInstall = async (
12+
args: [appName: string, path: string, runner: Runner]
13+
) => ipcRenderer.invoke('moveInstall', args)
14+
export const changeInstallPath = async (
15+
args: [appName: string, path: string, runner: Runner]
16+
) => ipcRenderer.invoke('changeInstallPath', args)
17+
export const disableEosOverlay = async (appName: string) =>
18+
ipcRenderer.invoke('disableEosOverlay', appName)
19+
export const enableEosOverlay = async (appName: string) =>
20+
ipcRenderer.invoke('enableEosOverlay', appName)
21+
export const installEosOverlay = async () =>
22+
ipcRenderer.invoke('installEosOverlay')
23+
export const removeFromSteam = async (appName: string, runner: Runner) =>
24+
ipcRenderer.invoke('removeFromSteam', appName, runner)
25+
export const addToSteam = async (
26+
appName: string,
27+
runner: Runner,
28+
bkgDataURL: string,
29+
bigPicDataURL: string
30+
) =>
31+
ipcRenderer.invoke('addToSteam', appName, runner, bkgDataURL, bigPicDataURL)
32+
export const shortcutsExists = async (appName: string, runner: Runner) =>
33+
ipcRenderer.invoke('shortcutsExists', appName, runner)
34+
export const isAddedToSteam = async (appName: string, runner: Runner) =>
35+
ipcRenderer.invoke('isAddedToSteam', appName, runner)
36+
export const isEosOverlayEnabled = async (appName?: string) =>
37+
ipcRenderer.invoke('isEosOverlayEnabled', appName)

src/backend/api/misc.ts

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { GOGCloudSavesLocation } from 'common/types/gog'
2+
import { ipcRenderer } from 'electron'
3+
import { Runner, Tools } from '../../common/types'
4+
5+
export const clearCache = () => ipcRenderer.send('clearCache')
6+
export const resetHeroic = () => ipcRenderer.send('resetHeroic')
7+
8+
export const openWeblate = () => ipcRenderer.send('openWeblate')
9+
export const changeLanguage = (newLanguage: string) =>
10+
ipcRenderer.send('changeLanguage', newLanguage)
11+
12+
export const openExternalUrl = (url: string) =>
13+
ipcRenderer.send('openExternalUrl', url)
14+
export const getHeroicVersion = async () =>
15+
ipcRenderer.invoke('getHeroicVersion')
16+
export const getLatestReleases = async () =>
17+
ipcRenderer.invoke('getLatestReleases')
18+
19+
export const openPatreonPage = () => ipcRenderer.send('openPatreonPage')
20+
export const openKofiPage = () => ipcRenderer.send('openKofiPage')
21+
export const isFullscreen = async () => ipcRenderer.invoke('isFullscreen')
22+
23+
export const openWebviewPage = (url: string) =>
24+
ipcRenderer.send('openWebviewPage', url)
25+
26+
export const setZoomFactor = (zoom: string) =>
27+
ipcRenderer.send('setZoomFactor', zoom)
28+
export const frontendReady = () => ipcRenderer.send('frontendReady')
29+
export const lock = () => ipcRenderer.send('lock')
30+
export const unlock = () => ipcRenderer.send('unlock')
31+
export const login = async (sid: string) => ipcRenderer.invoke('login', sid)
32+
export const logoutLegendary = async () => ipcRenderer.invoke('logoutLegendary')
33+
export const authGOG = async (token: string) =>
34+
ipcRenderer.invoke('authGOG', token)
35+
export const logoutGOG = async () => ipcRenderer.invoke('logoutGOG')
36+
export const checkGameUpdates = async () =>
37+
ipcRenderer.invoke('checkGameUpdates')
38+
export const refreshLibrary = async (
39+
fullRefresh?: boolean,
40+
library?: Runner | 'all'
41+
) => ipcRenderer.invoke('refreshLibrary', fullRefresh, library)
42+
43+
export const gamepadAction = async (
44+
args: [action: string, metadata: { elementTag: string; x: number; y: number }]
45+
) => ipcRenderer.invoke('gamepadAction', args)
46+
47+
export const logError = (error: string) => ipcRenderer.send('logError', error)
48+
export const logInfo = (info: string) => ipcRenderer.send('logInfo', info)
49+
export const showConfigFileInFolder = (appName: string) =>
50+
ipcRenderer.send('showConfigFileInFolder', appName)
51+
export const openFolder = (installPath: string) =>
52+
ipcRenderer.send('openFolder', installPath)
53+
export const syncGOGSaves = async (
54+
gogSaves: GOGCloudSavesLocation[],
55+
appName: string,
56+
arg: string
57+
) => ipcRenderer.invoke('syncGOGSaves', gogSaves, appName, arg)
58+
export const getFonts = async (reload: boolean) =>
59+
ipcRenderer.invoke('getFonts', reload)
60+
export const checkDiskSpace = async (installPath: string) =>
61+
ipcRenderer.invoke('checkDiskSpace', installPath)
62+
export const getGOGLinuxInstallersLangs = async (appName: string) =>
63+
ipcRenderer.invoke('getGOGLinuxInstallersLangs', appName)
64+
export const getAlternativeWine = async () =>
65+
ipcRenderer.invoke('getAlternativeWine')
66+
export const getGOGGameClientId = async (appName: string) =>
67+
ipcRenderer.invoke('getGOGGameClientId', appName)
68+
export const getShellPath = async (saveLocation: string) =>
69+
ipcRenderer.invoke('getShellPath', saveLocation)
70+
export const getRealPath = async (actualPath: string) =>
71+
ipcRenderer.invoke('getRealPath', actualPath)
72+
export const callTool = async (toolArgs: Tools) =>
73+
ipcRenderer.invoke('callTool', toolArgs)
74+
export const getAnticheatInfo = async (namespace: string) =>
75+
ipcRenderer.invoke('getAnticheatInfo', namespace)
76+
77+
export const requestSettingsRemoveListeners = () =>
78+
ipcRenderer.removeAllListeners('requestSettings')
79+
80+
export const clipboardReadText = async () =>
81+
ipcRenderer.invoke('clipboardReadText')
82+
83+
export const clipboardWriteText = async (text: string) =>
84+
ipcRenderer.send('clipboardWriteText', text)
85+
86+
import Store from 'electron-store'
87+
// FUTURE WORK
88+
// here is how the store methods can be refactored
89+
// in order to set nodeIntegration: false
90+
// but converting sync methods to async propagates through frontend
91+
92+
// export const storeNew = async (
93+
// name: string,
94+
// options: Store.Options<Record<string, unknown>>
95+
// ) => ipcRenderer.send('storeNew', name, options)
96+
97+
// export const storeSet = async (name: string, key: string, value?: unknown) =>
98+
// ipcRenderer.send('storeSet', name, key, value)
99+
100+
// export const storeHas = async (name: string, key: string) =>
101+
// ipcRenderer.invoke('storeHas', name, key)
102+
103+
// export const storeGet = async (name: string, key: string) =>
104+
// ipcRenderer.invoke('storeGet', name, key)
105+
106+
interface StoreMap {
107+
[key: string]: Store
108+
}
109+
const stores: StoreMap = {}
110+
111+
export const storeNew = function (
112+
storeName: string,
113+
options: Store.Options<Record<string, unknown>>
114+
) {
115+
stores[storeName] = new Store(options)
116+
}
117+
118+
export const storeSet = (storeName: string, key: string, value?: unknown) =>
119+
stores[storeName].set(key, value)
120+
121+
export const storeHas = (storeName: string, key: string) =>
122+
stores[storeName].has(key)
123+
124+
export const storeGet = (
125+
storeName: string,
126+
key: string,
127+
defaultValue?: unknown
128+
) => stores[storeName].get(key, defaultValue)

src/backend/api/settings.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { ipcRenderer } from 'electron'
2+
3+
export const getLegendaryVersion = async () =>
4+
ipcRenderer.invoke('getLegendaryVersion')
5+
export const getGogdlVersion = async () => ipcRenderer.invoke('getGogdlVersion')
6+
export const getEosOverlayStatus = async () =>
7+
ipcRenderer.invoke('getEosOverlayStatus')
8+
export const getLatestEosOverlayVersion = async () =>
9+
ipcRenderer.invoke('getLatestEosOverlayVersion')
10+
export const removeEosOverlay = async () =>
11+
ipcRenderer.invoke('removeEosOverlay')
12+
export const cancelEosOverlayInstallOrUpdate = async () =>
13+
ipcRenderer.invoke('cancelEosOverlayInstallOrUpdate')
14+
export const updateEosOverlayInfo = async () =>
15+
ipcRenderer.invoke('updateEosOverlayInfo')
16+
17+
export const changeTrayColor = () => ipcRenderer.send('changeTrayColor')
18+
export const getMaxCpus = async () => ipcRenderer.invoke('getMaxCpus')
19+
export const showUpdateSetting = async () =>
20+
ipcRenderer.invoke('showUpdateSetting')
21+
export const egsSync = async (args: string) =>
22+
ipcRenderer.invoke('egsSync', args)
23+
export const showErrorBox = async (args: [title: string, message: string]) =>
24+
ipcRenderer.invoke('showErrorBox', args)
25+
26+
export const showLogFileInFolder = (args: {
27+
appName: string
28+
defaultLast?: boolean
29+
}) => ipcRenderer.send('showLogFileInFolder', args)
30+
export const getLogContent = async (args: {
31+
appName: string
32+
defaultLast?: boolean
33+
}) => ipcRenderer.invoke('getLogContent', args)

src/backend/api/wine.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { ipcRenderer } from 'electron'
2+
import { RuntimeName, WineVersionInfo } from '../../common/types'
3+
import { ProgressInfo, State } from 'heroic-wine-downloader'
4+
5+
export const toggleDXVK = (
6+
args: [wineArgs: { winePrefix: string; winePath: string }, action: string]
7+
) => ipcRenderer.send('toggleDXVK', args)
8+
export const toggleVKD3D = (
9+
args: [wineArgs: { winePrefix: string; winePath: string }, action: string]
10+
) => ipcRenderer.send('toggleVKD3D', args)
11+
export const isFlatpak = async () => ipcRenderer.invoke('isFlatpak')
12+
export const isRuntimeInstalled = async (runtime_name: RuntimeName) =>
13+
ipcRenderer.invoke('isRuntimeInstalled', runtime_name)
14+
export const downloadRuntime = async (runtime_name: RuntimeName) =>
15+
ipcRenderer.invoke('downloadRuntime', runtime_name)
16+
17+
export const showItemInFolder = (installDir: string) =>
18+
ipcRenderer.send('showItemInFolder', installDir)
19+
export const abortWineInstallation = (version: string) =>
20+
ipcRenderer.send('abortWineInstallation', version)
21+
export const installWineVersion = async (release: WineVersionInfo) =>
22+
ipcRenderer.invoke('installWineVersion', release)
23+
export const removeWineVersion = async (release: WineVersionInfo) =>
24+
ipcRenderer.invoke('removeWineVersion', release)
25+
export const refreshWineVersionInfo = async (fetch?: boolean) =>
26+
ipcRenderer.invoke('refreshWineVersionInfo', fetch)
27+
28+
export const handleProgressOfWinetricks = (
29+
onProgress: (e: Electron.IpcRendererEvent, messages: string[]) => void
30+
) => {
31+
ipcRenderer.on('progressOfWinetricks', onProgress)
32+
return () => {
33+
ipcRenderer.removeListener('progressOfWinetricks', onProgress)
34+
}
35+
}
36+
37+
export const handleProgressOfWineManager = (
38+
version: string,
39+
callback: (
40+
e: Electron.IpcRendererEvent,
41+
progress: {
42+
state: State
43+
progress: ProgressInfo
44+
}
45+
) => void
46+
) => {
47+
ipcRenderer.on('progressOfWineManager' + version, callback)
48+
return () => {
49+
ipcRenderer.removeListener('progressOfWineManager' + version, callback)
50+
}
51+
}

src/backend/gog/library.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -138,16 +138,16 @@ export class GOGLibrary {
138138
return
139139
}
140140
this.refreshInstalled()
141+
for (const game of libraryStore.get('games', []) as GameInfo[]) {
142+
const copyObject = { ...game }
143+
if (this.installedGames.has(game.app_name)) {
144+
copyObject.install = this.installedGames.get(game.app_name)!
145+
copyObject.is_installed = true
146+
}
147+
this.library.set(game.app_name, copyObject)
148+
}
141149

142150
if (!isOnline()) {
143-
for (const game of libraryStore.get('games', []) as GameInfo[]) {
144-
const copyObject = { ...game }
145-
if (this.installedGames.has(game.app_name)) {
146-
copyObject.install = this.installedGames.get(game.app_name)!
147-
copyObject.is_installed = true
148-
}
149-
this.library.set(game.app_name, copyObject)
150-
}
151151
return
152152
}
153153

0 commit comments

Comments
 (0)