diff --git a/src/backend/api/helpers.ts b/src/backend/api/helpers.ts new file mode 100644 index 0000000000..730155359e --- /dev/null +++ b/src/backend/api/helpers.ts @@ -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) diff --git a/src/backend/api/index.ts b/src/backend/api/index.ts new file mode 100644 index 0000000000..1eaca3270b --- /dev/null +++ b/src/backend/api/index.ts @@ -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 +} diff --git a/src/backend/api/library.ts b/src/backend/api/library.ts new file mode 100644 index 0000000000..04a89cc773 --- /dev/null +++ b/src/backend/api/library.ts @@ -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) diff --git a/src/backend/api/menu.ts b/src/backend/api/menu.ts new file mode 100644 index 0000000000..f40b8fa338 --- /dev/null +++ b/src/backend/api/menu.ts @@ -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) diff --git a/src/backend/api/misc.ts b/src/backend/api/misc.ts new file mode 100644 index 0000000000..b0361a67fe --- /dev/null +++ b/src/backend/api/misc.ts @@ -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> +// ) => 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> +) { + 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) diff --git a/src/backend/api/settings.ts b/src/backend/api/settings.ts new file mode 100644 index 0000000000..17c780789a --- /dev/null +++ b/src/backend/api/settings.ts @@ -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) diff --git a/src/backend/api/wine.ts b/src/backend/api/wine.ts new file mode 100644 index 0000000000..f2ff636470 --- /dev/null +++ b/src/backend/api/wine.ts @@ -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) + } +} diff --git a/src/backend/gog/library.ts b/src/backend/gog/library.ts index 77c72583e2..5e1fabc33e 100644 --- a/src/backend/gog/library.ts +++ b/src/backend/gog/library.ts @@ -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 } diff --git a/src/backend/main.ts b/src/backend/main.ts index 3ea917380f..7eb9034ed5 100644 --- a/src/backend/main.ts +++ b/src/backend/main.ts @@ -8,7 +8,9 @@ import { Runner, AppSettings, GameSettings, - InstallPlatform + InstallPlatform, + LaunchParams, + Tools } from 'common/types' import { GOGCloudSavesLocation } from 'common/types/gog' import * as path from 'path' @@ -21,7 +23,8 @@ import { ipcMain, powerSaveBlocker, protocol, - screen + screen, + clipboard } from 'electron' import './updater' import { autoUpdater } from 'electron-updater' @@ -72,7 +75,8 @@ import { getGame, getFirstExistingParentPath, getLatestReleases, - notify + notify, + quoteIfNecessary } from './utils' import { configStore, @@ -164,10 +168,13 @@ async function createWindow(): Promise { minHeight: 345, minWidth: 600, show: false, + webPreferences: { webviewTag: true, - contextIsolation: false, - nodeIntegration: true + contextIsolation: true, + nodeIntegration: true, + // sandbox: false, + preload: path.join(__dirname, 'preload.js') } }) @@ -484,10 +491,6 @@ ipcMain.on('frontendReady', () => { handleProtocol(mainWindow, [openUrlArgument, ...process.argv]) }) -ipcMain.on('frontendError', async (event, error) => { - logError(error, { prefix: LogPrefix.Frontend }) -}) - // Maybe this can help with white screens process.on('uncaughtException', async (err) => { logError(`${err.name}: ${err.message}`, { prefix: LogPrefix.Backend }) @@ -631,13 +634,6 @@ ipcMain.on('removeFolder', async (e, [path, folderName]) => { }) // Calls WineCFG or Winetricks. If is WineCFG, use the same binary as wine to launch it to dont update the prefix -interface Tools { - exe: string - tool: string - appName: string - runner: Runner -} - ipcMain.handle( 'callTool', async (event, { tool, exe, appName, runner }: Tools) => { @@ -653,7 +649,10 @@ ipcMain.handle( game.runWineCommand('winecfg') break case 'runExe': - game.runWineCommand(exe) + if (exe) { + exe = quoteIfNecessary(exe) + game.runWineCommand(exe) + } break } } @@ -894,22 +893,25 @@ if (existsSync(installed)) { }) } -ipcMain.handle('refreshLibrary', async (e, fullRefresh, library?: Runner) => { - switch (library) { - case 'legendary': - await LegendaryLibrary.get().getGames(fullRefresh) - break - case 'gog': - await GOGLibrary.get().sync() - break - default: - await Promise.allSettled([ - LegendaryLibrary.get().getGames(fullRefresh), - GOGLibrary.get().sync() - ]) - break +ipcMain.handle( + 'refreshLibrary', + async (e, fullRefresh?: boolean, library?: Runner) => { + switch (library) { + case 'legendary': + await LegendaryLibrary.get().getGames(fullRefresh) + break + case 'gog': + await GOGLibrary.get().sync() + break + default: + await Promise.allSettled([ + LegendaryLibrary.get().getGames(fullRefresh), + GOGLibrary.get().sync() + ]) + break + } } -}) +) ipcMain.on('logError', (e, err) => logError(err, { prefix: LogPrefix.Frontend }) @@ -923,12 +925,6 @@ type RecentGame = { title: string } -type LaunchParams = { - appName: string - launchArguments: string - runner: Runner -} - let powerDisplayId: number | null ipcMain.handle( @@ -1307,7 +1303,10 @@ ipcMain.handle('updateGame', async (e, appName, runner) => { const game = getGame(appName, runner) const { title } = game.getGameInfo() - notify({ title, body: i18next.t('notify.update.started', 'Update Started') }) + notify({ + title, + body: i18next.t('notify.update.started', 'Update Started') + }) return game .update() @@ -1563,6 +1562,15 @@ ipcMain.handle('getRealPath', (event, path) => { return resolvedPath }) + +ipcMain.handle('clipboardReadText', () => { + return clipboard.readText() +}) + +ipcMain.on('clipboardWriteText', (event, text) => { + return clipboard.writeText(text) +}) + /* Other Keys that should go into translation files: t('box.error.generic.title') @@ -1578,3 +1586,25 @@ import './shortcuts/ipc_handler' import './anticheat/ipc_handler' import './legendary/eos_overlay/ipc_handler' import './wine/runtimes/ipc_handler' + +// import Store from 'electron-store' +// interface StoreMap { +// [key: string]: Store +// } +// const stores: StoreMap = {} + +// ipcMain.on('storeNew', (event, storeName, options) => { +// stores[storeName] = new Store(options) +// }) + +// ipcMain.handle('storeHas', (event, storeName, key) => { +// return stores[storeName].has(key) +// }) + +// ipcMain.handle('storeGet', (event, storeName, key) => { +// return stores[storeName].get(key) +// }) + +// ipcMain.on('storeSet', (event, storeName, key, value) => { +// stores[storeName].set(key, value) +// }) diff --git a/src/backend/preload.ts b/src/backend/preload.ts new file mode 100644 index 0000000000..b0ccc26cd3 --- /dev/null +++ b/src/backend/preload.ts @@ -0,0 +1,4 @@ +import { contextBridge } from 'electron' +import api from './api' + +contextBridge.exposeInMainWorld('api', api) diff --git a/src/backend/wine/manager/ipc_handler.ts b/src/backend/wine/manager/ipc_handler.ts index df7fde076b..5087d9c296 100644 --- a/src/backend/wine/manager/ipc_handler.ts +++ b/src/backend/wine/manager/ipc_handler.ts @@ -1,4 +1,4 @@ -import { ipcMain } from 'electron' +import { ipcMain, BrowserWindow } from 'electron' import { ProgressInfo, State } from 'heroic-wine-downloader' import { WineVersionInfo } from 'common/types' import { @@ -23,8 +23,12 @@ ipcMain.handle('installWineVersion', async (e, release: WineVersionInfo) => { const abortController = new AbortController() abortControllers.set(release.version, abortController) + const window = BrowserWindow.getAllWindows()[0] const onProgress = (state: State, progress?: ProgressInfo) => { - e.sender.send('progressOf' + release.version, { state, progress }) + window.webContents.send('progressOfWineManager' + release.version, { + state, + progress + }) } const result = await installWineVersion( release, diff --git a/src/backend/wine/manager/utils.ts b/src/backend/wine/manager/utils.ts index c8c7792952..892b4c8c93 100644 --- a/src/backend/wine/manager/utils.ts +++ b/src/backend/wine/manager/utils.ts @@ -57,7 +57,7 @@ async function updateWineVersionInfos( return release?.version === old?.version }) - if (existsSync(old?.installDir)) { + if (old.installDir !== undefined && existsSync(old?.installDir)) { if (index !== -1) { releases[index].installDir = old.installDir releases[index].isInstalled = old.isInstalled @@ -175,7 +175,7 @@ async function installWineVersion( async function removeWineVersion(release: WineVersionInfo): Promise { // remove folder if exist - if (existsSync(release.installDir)) { + if (release.installDir !== undefined && existsSync(release.installDir)) { try { rmSync(release.installDir, { recursive: true }) } catch (error) { diff --git a/src/common/types.ts b/src/common/types.ts index d123969fef..c025d1e220 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -5,6 +5,31 @@ import { VersionInfo } from 'heroic-wine-downloader' export type Runner = 'legendary' | 'gog' +// here is a way to type the callback function in ipcMain.on or ipcMain.handle +// does not prevent callbacks with fewer parameters from being passed though +// the microsoft team is very opposed to enabling the above constraint https://github.com/microsoft/TypeScript/issues/17868 +// for ipcMain.handle('updateGame', async (e, appName, runner) => { for instance could be converted to: +// ipcMain.handle('updateGame', typedCallback>() => { +// this has the benefit of type checking for the arguments typed in the preload api +// but may be overly complex for a small benefit +export function typedCallback(arg: T) { + return arg +} + +export type WrapApiFunction< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TFunction extends (...args: any) => any +> = ( + e: Electron.IpcMainInvokeEvent, + ...args: [...Parameters] +) => ReturnType + +export type LaunchParams = { + appName: string + launchArguments: string + runner: Runner +} + interface About { description: string longDescription: string @@ -448,3 +473,10 @@ export type ElWebview = { export type WebviewType = HTMLWebViewElement & ElWebview export type InstallPlatform = LegendaryInstallPlatform | GogInstallPlatform + +export interface Tools { + exe?: string + tool: string + appName: string + runner: Runner +} diff --git a/src/common/types/proxy-types.ts b/src/common/types/proxy-types.ts new file mode 100644 index 0000000000..0a5abafc5e --- /dev/null +++ b/src/common/types/proxy-types.ts @@ -0,0 +1,9 @@ +type ApiType = typeof import('../../backend/api').default + +declare global { + interface Window { + api: ApiType + } +} + +export {} diff --git a/src/frontend/components/UI/Anticheat/index.tsx b/src/frontend/components/UI/Anticheat/index.tsx index 5872c8b802..842c794868 100644 --- a/src/frontend/components/UI/Anticheat/index.tsx +++ b/src/frontend/components/UI/Anticheat/index.tsx @@ -1,6 +1,6 @@ import React, { MouseEvent, useEffect, useState } from 'react' import { AntiCheatInfo, GameInfo } from 'common/types' -import { createNewWindow, ipcRenderer } from 'frontend/helpers' +import { createNewWindow } from 'frontend/helpers' import './index.css' import { useTranslation } from 'react-i18next' @@ -18,8 +18,8 @@ export default function Anticheat({ gameInfo }: Props) { useEffect(() => { if (gameInfo?.title) { - ipcRenderer - .invoke('getAnticheatInfo', gameInfo.namespace) + window.api + .getAnticheatInfo(gameInfo.namespace) .then((anticheatInfo: AntiCheatInfo | null) => { setAnticheatInfo(anticheatInfo) }) diff --git a/src/frontend/components/UI/ErrorComponent/index.tsx b/src/frontend/components/UI/ErrorComponent/index.tsx index c6bca8021a..e1dd74aec3 100644 --- a/src/frontend/components/UI/ErrorComponent/index.tsx +++ b/src/frontend/components/UI/ErrorComponent/index.tsx @@ -7,8 +7,6 @@ import { CleaningServicesOutlined, DeleteOutline } from '@mui/icons-material' import './index.css' import ContextProvider from 'frontend/state/ContextProvider' -import { ipcRenderer } from 'frontend/helpers' - export default function ErrorComponent({ message }: { message: string }) { const { t } = useTranslation() const { refreshLibrary } = useContext(ContextProvider) @@ -40,7 +38,7 @@ export default function ErrorComponent({ message }: { message: string }) { - -