Skip to content

Commit

Permalink
[Tech] Enable sandboxing for ipcRenderer Processes (#1783)
Browse files Browse the repository at this point in the history
* 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 <flavioislima@gmail.com>
Co-authored-by: Mathis Dröge <34034631+CommandMC@users.noreply.github.com>
  • Loading branch information
3 people committed Sep 20, 2022
1 parent 3f6541c commit 4e42741
Show file tree
Hide file tree
Showing 48 changed files with 863 additions and 506 deletions.
44 changes: 44 additions & 0 deletions src/backend/api/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ipcRenderer } from 'electron'
import { Runner, InstallPlatform } from '../../common/types'

export const notify = (notification: string[]) =>
ipcRenderer.send('Notify', notification)
export const openLoginPage = () => ipcRenderer.send('openLoginPage')
export const openSidInfoPage = () => ipcRenderer.send('openSidInfoPage')
export const openSupportPage = () => ipcRenderer.send('openSupportPage')
export const quit = () => ipcRenderer.send('quit')
export const showAboutWindow = () => ipcRenderer.send('showAboutWindow')
export const openDiscordLink = () => ipcRenderer.send('openDiscordLink')
export const createNewWindow = (url: string) =>
ipcRenderer.send('createNewWindow', url)

export const readConfig = async (file: string) =>
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 kill = async (appName: string, runner: Runner) =>
ipcRenderer.invoke('kill', appName, runner)
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 getGameInfo = async (appName: string, runner: Runner) =>
ipcRenderer.invoke('getGameInfo', appName, runner)
export const getGameSettings = async (appName: string, runner: Runner) =>
ipcRenderer.invoke('getGameSettings', appName, runner)
export const getInstallInfo = async (
appName: string,
runner: Runner,
installPlatform?: InstallPlatform | string
) => ipcRenderer.invoke('getInstallInfo', appName, runner, installPlatform)
interface runWineCommand {
appName: string
runner: string
command: string
}
export const runWineCommandForGame = async (command: runWineCommand) =>
ipcRenderer.invoke('runWineCommandForGame', command)
export const requestSettings = async (appName: string) =>
ipcRenderer.invoke('requestSettings', appName)
15 changes: 15 additions & 0 deletions src/backend/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as Misc from './misc'
import * as Helpers from './helpers'
import * as Library from './library'
import * as Menu from './menu'
import * as Settings from './settings'
import * as Wine from './wine'

export default {
...Misc,
...Helpers,
...Library,
...Menu,
...Settings,
...Wine
}
47 changes: 47 additions & 0 deletions src/backend/api/library.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ipcRenderer } from 'electron'
import { Runner, InstallParams, LaunchParams } from '../../common/types'

export const removeFolder = (args: [path: string, folderName: string]) =>
ipcRenderer.send('removeFolder', args)

export const openDialog = async (args: Electron.OpenDialogOptions) =>
ipcRenderer.invoke('openDialog', args)

export const install = async (args: InstallParams) =>
ipcRenderer.invoke('install', args)
export const openMessageBox = async (args: Electron.MessageBoxOptions) =>
ipcRenderer.invoke('openMessageBox', args)
export const uninstall = async (
args: [appName: string, shouldRemovePrefix: boolean, runner: Runner]
) => ipcRenderer.invoke('uninstall', args)
export const repair = async (appName: string, runner: Runner) =>
ipcRenderer.invoke('repair', appName, runner)
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) => {
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)
37 changes: 37 additions & 0 deletions src/backend/api/menu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ipcRenderer } from 'electron'
import { 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 disableEosOverlay = async (appName: string) =>
ipcRenderer.invoke('disableEosOverlay', appName)
export const enableEosOverlay = async (appName: string) =>
ipcRenderer.invoke('enableEosOverlay', 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,
bkgDataURL: string,
bigPicDataURL: string
) =>
ipcRenderer.invoke('addToSteam', appName, runner, bkgDataURL, bigPicDataURL)
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)
128 changes: 128 additions & 0 deletions src/backend/api/misc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { GOGCloudSavesLocation } from 'common/types/gog'
import { ipcRenderer } from 'electron'
import { Runner, Tools } from '../../common/types'

export const clearCache = () => ipcRenderer.send('clearCache')
export const resetHeroic = () => ipcRenderer.send('resetHeroic')

export const openWeblate = () => ipcRenderer.send('openWeblate')
export const changeLanguage = (newLanguage: string) =>
ipcRenderer.send('changeLanguage', newLanguage)

export const openExternalUrl = (url: string) =>
ipcRenderer.send('openExternalUrl', url)
export const getHeroicVersion = async () =>
ipcRenderer.invoke('getHeroicVersion')
export const getLatestReleases = async () =>
ipcRenderer.invoke('getLatestReleases')

export const openPatreonPage = () => ipcRenderer.send('openPatreonPage')
export const openKofiPage = () => ipcRenderer.send('openKofiPage')
export const isFullscreen = async () => ipcRenderer.invoke('isFullscreen')

export const openWebviewPage = (url: string) =>
ipcRenderer.send('openWebviewPage', url)

export const setZoomFactor = (zoom: string) =>
ipcRenderer.send('setZoomFactor', zoom)
export const frontendReady = () => ipcRenderer.send('frontendReady')
export const lock = () => ipcRenderer.send('lock')
export const unlock = () => ipcRenderer.send('unlock')
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 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: { elementTag: string; x: number; y: number }]
) => ipcRenderer.invoke('gamepadAction', args)

export const logError = (error: string) => ipcRenderer.send('logError', error)
export const logInfo = (info: string) => ipcRenderer.send('logInfo', info)
export const showConfigFileInFolder = (appName: string) =>
ipcRenderer.send('showConfigFileInFolder', appName)
export const openFolder = (installPath: string) =>
ipcRenderer.send('openFolder', installPath)
export const syncGOGSaves = async (
gogSaves: GOGCloudSavesLocation[],
appName: string,
arg: string
) => ipcRenderer.invoke('syncGOGSaves', gogSaves, appName, arg)
export const getFonts = async (reload: boolean) =>
ipcRenderer.invoke('getFonts', reload)
export const checkDiskSpace = async (installPath: string) =>
ipcRenderer.invoke('checkDiskSpace', installPath)
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 getRealPath = async (actualPath: string) =>
ipcRenderer.invoke('getRealPath', actualPath)
export const callTool = async (toolArgs: Tools) =>
ipcRenderer.invoke('callTool', toolArgs)
export const getAnticheatInfo = async (namespace: string) =>
ipcRenderer.invoke('getAnticheatInfo', namespace)

export const requestSettingsRemoveListeners = () =>
ipcRenderer.removeAllListeners('requestSettings')

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

export const clipboardWriteText = async (text: string) =>
ipcRenderer.send('clipboardWriteText', text)

import Store from 'electron-store'
// FUTURE WORK
// here is how the store methods can be refactored
// in order to set nodeIntegration: false
// but converting sync methods to async propagates through frontend

// export const storeNew = async (
// name: string,
// options: Store.Options<Record<string, unknown>>
// ) => ipcRenderer.send('storeNew', name, options)

// export const storeSet = async (name: string, key: string, value?: unknown) =>
// ipcRenderer.send('storeSet', name, key, value)

// export const storeHas = async (name: string, key: string) =>
// ipcRenderer.invoke('storeHas', name, key)

// export const storeGet = async (name: string, key: string) =>
// ipcRenderer.invoke('storeGet', name, key)

interface StoreMap {
[key: string]: Store
}
const stores: StoreMap = {}

export const storeNew = function (
storeName: string,
options: Store.Options<Record<string, unknown>>
) {
stores[storeName] = new Store(options)
}

export const storeSet = (storeName: string, key: string, value?: unknown) =>
stores[storeName].set(key, value)

export const storeHas = (storeName: string, key: string) =>
stores[storeName].has(key)

export const storeGet = (
storeName: string,
key: string,
defaultValue?: unknown
) => stores[storeName].get(key, defaultValue)
33 changes: 33 additions & 0 deletions src/backend/api/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ipcRenderer } from 'electron'

export const getLegendaryVersion = async () =>
ipcRenderer.invoke('getLegendaryVersion')
export const getGogdlVersion = async () => ipcRenderer.invoke('getGogdlVersion')
export const getEosOverlayStatus = async () =>
ipcRenderer.invoke('getEosOverlayStatus')
export const getLatestEosOverlayVersion = async () =>
ipcRenderer.invoke('getLatestEosOverlayVersion')
export const removeEosOverlay = async () =>
ipcRenderer.invoke('removeEosOverlay')
export const cancelEosOverlayInstallOrUpdate = async () =>
ipcRenderer.invoke('cancelEosOverlayInstallOrUpdate')
export const updateEosOverlayInfo = async () =>
ipcRenderer.invoke('updateEosOverlayInfo')

export const changeTrayColor = () => ipcRenderer.send('changeTrayColor')
export const getMaxCpus = async () => ipcRenderer.invoke('getMaxCpus')
export const showUpdateSetting = async () =>
ipcRenderer.invoke('showUpdateSetting')
export const egsSync = async (args: string) =>
ipcRenderer.invoke('egsSync', args)
export const showErrorBox = async (args: [title: string, message: string]) =>
ipcRenderer.invoke('showErrorBox', args)

export const showLogFileInFolder = (args: {
appName: string
defaultLast?: boolean
}) => ipcRenderer.send('showLogFileInFolder', args)
export const getLogContent = async (args: {
appName: string
defaultLast?: boolean
}) => ipcRenderer.invoke('getLogContent', args)
51 changes: 51 additions & 0 deletions src/backend/api/wine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ipcRenderer } from 'electron'
import { RuntimeName, WineVersionInfo } from '../../common/types'
import { ProgressInfo, State } from 'heroic-wine-downloader'

export const toggleDXVK = (
args: [wineArgs: { winePrefix: string; winePath: string }, action: string]
) => ipcRenderer.send('toggleDXVK', args)
export const toggleVKD3D = (
args: [wineArgs: { winePrefix: string; winePath: string }, action: string]
) => ipcRenderer.send('toggleVKD3D', args)
export const isFlatpak = async () => ipcRenderer.invoke('isFlatpak')
export const isRuntimeInstalled = async (runtime_name: RuntimeName) =>
ipcRenderer.invoke('isRuntimeInstalled', runtime_name)
export const downloadRuntime = async (runtime_name: RuntimeName) =>
ipcRenderer.invoke('downloadRuntime', runtime_name)

export const showItemInFolder = (installDir: string) =>
ipcRenderer.send('showItemInFolder', installDir)
export const abortWineInstallation = (version: string) =>
ipcRenderer.send('abortWineInstallation', version)
export const installWineVersion = async (release: WineVersionInfo) =>
ipcRenderer.invoke('installWineVersion', release)
export const removeWineVersion = async (release: WineVersionInfo) =>
ipcRenderer.invoke('removeWineVersion', release)
export const refreshWineVersionInfo = async (fetch?: boolean) =>
ipcRenderer.invoke('refreshWineVersionInfo', fetch)

export const handleProgressOfWinetricks = (
onProgress: (e: Electron.IpcRendererEvent, messages: string[]) => void
) => {
ipcRenderer.on('progressOfWinetricks', onProgress)
return () => {
ipcRenderer.removeListener('progressOfWinetricks', onProgress)
}
}

export const handleProgressOfWineManager = (
version: string,
callback: (
e: Electron.IpcRendererEvent,
progress: {
state: State
progress: ProgressInfo
}
) => void
) => {
ipcRenderer.on('progressOfWineManager' + version, callback)
return () => {
ipcRenderer.removeListener('progressOfWineManager' + version, callback)
}
}
16 changes: 8 additions & 8 deletions src/backend/gog/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,16 @@ export class GOGLibrary {
return
}
this.refreshInstalled()
for (const game of libraryStore.get('games', []) as GameInfo[]) {
const copyObject = { ...game }
if (this.installedGames.has(game.app_name)) {
copyObject.install = this.installedGames.get(game.app_name)!
copyObject.is_installed = true
}
this.library.set(game.app_name, copyObject)
}

if (!isOnline()) {
for (const game of libraryStore.get('games', []) as GameInfo[]) {
const copyObject = { ...game }
if (this.installedGames.has(game.app_name)) {
copyObject.install = this.installedGames.get(game.app_name)!
copyObject.is_installed = true
}
this.library.set(game.app_name, copyObject)
}
return
}

Expand Down

0 comments on commit 4e42741

Please sign in to comment.