// See the Electron documentation for details on how to use preload scripts: // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts import { contextBridge, ipcRenderer } from "electron"; import type { GameShop, DownloadProgress, UserPreferences, AppUpdaterEvent, StartGameDownloadPayload, GameRunning, FriendRequestAction, UpdateProfileRequest, CatalogueSearchPayload, SeedingStatus, GameAchievement, Theme, FriendRequestSync, ShortcutLocation, ShopAssets, AchievementCustomNotificationPosition, AchievementNotificationInfo, } from "@types"; import type { AuthPage, CatalogueCategory } from "@shared"; import type { AxiosProgressEvent } from "axios"; contextBridge.exposeInMainWorld("electron", { /* Torrenting */ startGameDownload: (payload: StartGameDownloadPayload) => ipcRenderer.invoke("startGameDownload", payload), cancelGameDownload: (shop: GameShop, objectId: string) => ipcRenderer.invoke("cancelGameDownload", shop, objectId), pauseGameDownload: (shop: GameShop, objectId: string) => ipcRenderer.invoke("pauseGameDownload", shop, objectId), resumeGameDownload: (shop: GameShop, objectId: string) => ipcRenderer.invoke("resumeGameDownload", shop, objectId), pauseGameSeed: (shop: GameShop, objectId: string) => ipcRenderer.invoke("pauseGameSeed", shop, objectId), resumeGameSeed: (shop: GameShop, objectId: string) => ipcRenderer.invoke("resumeGameSeed", shop, objectId), onDownloadProgress: (cb: (value: DownloadProgress | null) => void) => { const listener = ( _event: Electron.IpcRendererEvent, value: DownloadProgress | null ) => cb(value); ipcRenderer.on("on-download-progress", listener); return () => ipcRenderer.removeListener("on-download-progress", listener); }, onHardDelete: (cb: () => void) => { const listener = (_event: Electron.IpcRendererEvent) => cb(); ipcRenderer.on("on-hard-delete", listener); return () => ipcRenderer.removeListener("on-hard-delete", listener); }, onSeedingStatus: (cb: (value: SeedingStatus[]) => void) => { const listener = ( _event: Electron.IpcRendererEvent, value: SeedingStatus[] ) => cb(value); ipcRenderer.on("on-seeding-status", listener); return () => ipcRenderer.removeListener("on-seeding-status", listener); }, checkDebridAvailability: (magnets: string[]) => ipcRenderer.invoke("checkDebridAvailability", magnets), /* Catalogue */ searchGames: (payload: CatalogueSearchPayload, take: number, skip: number) => ipcRenderer.invoke("searchGames", payload, take, skip), getCatalogue: (category: CatalogueCategory) => ipcRenderer.invoke("getCatalogue", category), saveGameShopAssets: (objectId: string, shop: GameShop, assets: ShopAssets) => ipcRenderer.invoke("saveGameShopAssets", objectId, shop, assets), getGameShopDetails: (objectId: string, shop: GameShop, language: string) => ipcRenderer.invoke("getGameShopDetails", objectId, shop, language), getRandomGame: () => ipcRenderer.invoke("getRandomGame"), getHowLongToBeat: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getHowLongToBeat", objectId, shop), getGameStats: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getGameStats", objectId, shop), getTrendingGames: () => ipcRenderer.invoke("getTrendingGames"), onUpdateAchievements: ( objectId: string, shop: GameShop, cb: (achievements: GameAchievement[]) => void ) => { const listener = ( _event: Electron.IpcRendererEvent, achievements: GameAchievement[] ) => cb(achievements); ipcRenderer.on(`on-update-achievements-${objectId}-${shop}`, listener); return () => ipcRenderer.removeListener( `on-update-achievements-${objectId}-${shop}`, listener ); }, /* User preferences */ getUserPreferences: () => ipcRenderer.invoke("getUserPreferences"), updateUserPreferences: (preferences: UserPreferences) => ipcRenderer.invoke("updateUserPreferences", preferences), autoLaunch: (autoLaunchProps: { enabled: boolean; minimized: boolean }) => ipcRenderer.invoke("autoLaunch", autoLaunchProps), authenticateRealDebrid: (apiToken: string) => ipcRenderer.invoke("authenticateRealDebrid", apiToken), authenticateTorBox: (apiToken: string) => ipcRenderer.invoke("authenticateTorBox", apiToken), /* Download sources */ putDownloadSource: (objectIds: string[]) => ipcRenderer.invoke("putDownloadSource", objectIds), createDownloadSources: (urls: string[]) => ipcRenderer.invoke("createDownloadSources", urls), removeDownloadSource: (url: string, removeAll?: boolean) => ipcRenderer.invoke("removeDownloadSource", url, removeAll), getDownloadSources: () => ipcRenderer.invoke("getDownloadSources"), /* Library */ toggleAutomaticCloudSync: ( shop: GameShop, objectId: string, automaticCloudSync: boolean ) => ipcRenderer.invoke( "toggleAutomaticCloudSync", shop, objectId, automaticCloudSync ), addGameToLibrary: (shop: GameShop, objectId: string, title: string) => ipcRenderer.invoke("addGameToLibrary", shop, objectId, title), addCustomGameToLibrary: ( title: string, executablePath: string, iconUrl?: string, logoImageUrl?: string, libraryHeroImageUrl?: string ) => ipcRenderer.invoke( "addCustomGameToLibrary", title, executablePath, iconUrl, logoImageUrl, libraryHeroImageUrl ), copyCustomGameAsset: ( sourcePath: string, assetType: "icon" | "logo" | "hero" ) => ipcRenderer.invoke("copyCustomGameAsset", sourcePath, assetType), saveTempFile: (fileName: string, fileData: Uint8Array) => ipcRenderer.invoke("saveTempFile", fileName, fileData), deleteTempFile: (filePath: string) => ipcRenderer.invoke("deleteTempFile", filePath), cleanupUnusedAssets: () => ipcRenderer.invoke("cleanupUnusedAssets"), updateCustomGame: ( shop: GameShop, objectId: string, title: string, iconUrl?: string, logoImageUrl?: string, libraryHeroImageUrl?: string ) => ipcRenderer.invoke( "updateCustomGame", shop, objectId, title, iconUrl, logoImageUrl, libraryHeroImageUrl ), updateGameCustomAssets: ( shop: GameShop, objectId: string, title: string, customIconUrl?: string | null, customLogoImageUrl?: string | null, customHeroImageUrl?: string | null ) => ipcRenderer.invoke( "updateGameCustomAssets", shop, objectId, title, customIconUrl, customLogoImageUrl, customHeroImageUrl ), createGameShortcut: ( shop: GameShop, objectId: string, location: ShortcutLocation ) => ipcRenderer.invoke("createGameShortcut", shop, objectId, location), updateExecutablePath: ( shop: GameShop, objectId: string, executablePath: string | null ) => ipcRenderer.invoke("updateExecutablePath", shop, objectId, executablePath), addGameToFavorites: (shop: GameShop, objectId: string) => ipcRenderer.invoke("addGameToFavorites", shop, objectId), removeGameFromFavorites: (shop: GameShop, objectId: string) => ipcRenderer.invoke("removeGameFromFavorites", shop, objectId), toggleGamePin: (shop: GameShop, objectId: string, pinned: boolean) => ipcRenderer.invoke("toggleGamePin", shop, objectId, pinned), updateLaunchOptions: ( shop: GameShop, objectId: string, launchOptions: string | null ) => ipcRenderer.invoke("updateLaunchOptions", shop, objectId, launchOptions), selectGameWinePrefix: ( shop: GameShop, objectId: string, winePrefixPath: string | null ) => ipcRenderer.invoke("selectGameWinePrefix", shop, objectId, winePrefixPath), verifyExecutablePathInUse: (executablePath: string) => ipcRenderer.invoke("verifyExecutablePathInUse", executablePath), getLibrary: () => ipcRenderer.invoke("getLibrary"), openGameInstaller: (shop: GameShop, objectId: string) => ipcRenderer.invoke("openGameInstaller", shop, objectId), openGameInstallerPath: (shop: GameShop, objectId: string) => ipcRenderer.invoke("openGameInstallerPath", shop, objectId), openGameExecutablePath: (shop: GameShop, objectId: string) => ipcRenderer.invoke("openGameExecutablePath", shop, objectId), openGame: ( shop: GameShop, objectId: string, executablePath: string, launchOptions?: string | null ) => ipcRenderer.invoke( "openGame", shop, objectId, executablePath, launchOptions ), closeGame: (shop: GameShop, objectId: string) => ipcRenderer.invoke("closeGame", shop, objectId), removeGameFromLibrary: (shop: GameShop, objectId: string) => ipcRenderer.invoke("removeGameFromLibrary", shop, objectId), removeGame: (shop: GameShop, objectId: string) => ipcRenderer.invoke("removeGame", shop, objectId), deleteGameFolder: (shop: GameShop, objectId: string) => ipcRenderer.invoke("deleteGameFolder", shop, objectId), getGameByObjectId: (shop: GameShop, objectId: string) => ipcRenderer.invoke("getGameByObjectId", shop, objectId), resetGameAchievements: (shop: GameShop, objectId: string) => ipcRenderer.invoke("resetGameAchievements", shop, objectId), changeGamePlayTime: (shop: GameShop, objectId: string, playtime: number) => ipcRenderer.invoke("changeGamePlayTime", shop, objectId, playtime), extractGameDownload: (shop: GameShop, objectId: string) => ipcRenderer.invoke("extractGameDownload", shop, objectId), getDefaultWinePrefixSelectionPath: () => ipcRenderer.invoke("getDefaultWinePrefixSelectionPath"), createSteamShortcut: (shop: GameShop, objectId: string) => ipcRenderer.invoke("createSteamShortcut", shop, objectId), onGamesRunning: ( cb: ( gamesRunning: Pick[] ) => void ) => { const listener = (_event: Electron.IpcRendererEvent, gamesRunning) => cb(gamesRunning); ipcRenderer.on("on-games-running", listener); return () => ipcRenderer.removeListener("on-games-running", listener); }, onLibraryBatchComplete: (cb: () => void) => { const listener = (_event: Electron.IpcRendererEvent) => cb(); ipcRenderer.on("on-library-batch-complete", listener); return () => ipcRenderer.removeListener("on-library-batch-complete", listener); }, onExtractionComplete: (cb: (shop: GameShop, objectId: string) => void) => { const listener = ( _event: Electron.IpcRendererEvent, shop: GameShop, objectId: string ) => cb(shop, objectId); ipcRenderer.on("on-extraction-complete", listener); return () => ipcRenderer.removeListener("on-extraction-complete", listener); }, /* Hardware */ getDiskFreeSpace: (path: string) => ipcRenderer.invoke("getDiskFreeSpace", path), checkFolderWritePermission: (path: string) => ipcRenderer.invoke("checkFolderWritePermission", path), /* Cloud save */ uploadSaveGame: ( objectId: string, shop: GameShop, downloadOptionTitle: string | null ) => ipcRenderer.invoke("uploadSaveGame", objectId, shop, downloadOptionTitle), toggleArtifactFreeze: (gameArtifactId: string, freeze: boolean) => ipcRenderer.invoke("toggleArtifactFreeze", gameArtifactId, freeze), renameGameArtifact: (gameArtifactId: string, label: string) => ipcRenderer.invoke("renameGameArtifact", gameArtifactId, label), downloadGameArtifact: ( objectId: string, shop: GameShop, gameArtifactId: string ) => ipcRenderer.invoke("downloadGameArtifact", objectId, shop, gameArtifactId), getGameArtifacts: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getGameArtifacts", objectId, shop), getGameBackupPreview: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getGameBackupPreview", objectId, shop), deleteGameArtifact: (gameArtifactId: string) => ipcRenderer.invoke("deleteGameArtifact", gameArtifactId), selectGameBackupPath: ( shop: GameShop, objectId: string, backupPath: string | null ) => ipcRenderer.invoke("selectGameBackupPath", shop, objectId, backupPath), onUploadComplete: (objectId: string, shop: GameShop, cb: () => void) => { const listener = (_event: Electron.IpcRendererEvent) => cb(); ipcRenderer.on(`on-upload-complete-${objectId}-${shop}`, listener); return () => ipcRenderer.removeListener( `on-upload-complete-${objectId}-${shop}`, listener ); }, onBackupDownloadProgress: ( objectId: string, shop: GameShop, cb: (progress: AxiosProgressEvent) => void ) => { const listener = ( _event: Electron.IpcRendererEvent, progress: AxiosProgressEvent ) => cb(progress); ipcRenderer.on(`on-backup-download-progress-${objectId}-${shop}`, listener); return () => ipcRenderer.removeListener( `on-backup-download-progress-${objectId}-${shop}`, listener ); }, onBackupDownloadComplete: ( objectId: string, shop: GameShop, cb: () => void ) => { const listener = (_event: Electron.IpcRendererEvent) => cb(); ipcRenderer.on(`on-backup-download-complete-${objectId}-${shop}`, listener); return () => ipcRenderer.removeListener( `on-backup-download-complete-${objectId}-${shop}`, listener ); }, /* Misc */ ping: () => ipcRenderer.invoke("ping"), getVersion: () => ipcRenderer.invoke("getVersion"), getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"), isStaging: () => ipcRenderer.invoke("isStaging"), isPortableVersion: () => ipcRenderer.invoke("isPortableVersion"), openExternal: (src: string) => ipcRenderer.invoke("openExternal", src), openCheckout: () => ipcRenderer.invoke("openCheckout"), showOpenDialog: (options: Electron.OpenDialogOptions) => ipcRenderer.invoke("showOpenDialog", options), showItemInFolder: (path: string) => ipcRenderer.invoke("showItemInFolder", path), getFeatures: () => ipcRenderer.invoke("getFeatures"), getBadges: () => ipcRenderer.invoke("getBadges"), canInstallCommonRedist: () => ipcRenderer.invoke("canInstallCommonRedist"), installCommonRedist: () => ipcRenderer.invoke("installCommonRedist"), platform: process.platform, /* Auto update */ onAutoUpdaterEvent: (cb: (value: AppUpdaterEvent) => void) => { const listener = ( _event: Electron.IpcRendererEvent, value: AppUpdaterEvent ) => cb(value); ipcRenderer.on("autoUpdaterEvent", listener); return () => { ipcRenderer.removeListener("autoUpdaterEvent", listener); }; }, onCommonRedistProgress: ( cb: (value: { log: string; complete: boolean }) => void ) => { const listener = ( _event: Electron.IpcRendererEvent, value: { log: string; complete: boolean } ) => cb(value); ipcRenderer.on("common-redist-progress", listener); return () => ipcRenderer.removeListener("common-redist-progress", listener); }, checkForUpdates: () => ipcRenderer.invoke("checkForUpdates"), restartAndInstallUpdate: () => ipcRenderer.invoke("restartAndInstallUpdate"), /* Profile */ getMe: () => ipcRenderer.invoke("getMe"), undoFriendship: (userId: string) => ipcRenderer.invoke("undoFriendship", userId), updateProfile: (updateProfile: UpdateProfileRequest) => ipcRenderer.invoke("updateProfile", updateProfile), processProfileImage: (imagePath: string) => ipcRenderer.invoke("processProfileImage", imagePath), getFriendRequests: () => ipcRenderer.invoke("getFriendRequests"), syncFriendRequests: () => ipcRenderer.invoke("syncFriendRequests"), onSyncFriendRequests: (cb: (friendRequests: FriendRequestSync) => void) => { const listener = ( _event: Electron.IpcRendererEvent, friendRequests: FriendRequestSync ) => cb(friendRequests); ipcRenderer.on("on-sync-friend-requests", listener); return () => ipcRenderer.removeListener("on-sync-friend-requests", listener); }, updateFriendRequest: (userId: string, action: FriendRequestAction) => ipcRenderer.invoke("updateFriendRequest", userId, action), sendFriendRequest: (userId: string) => ipcRenderer.invoke("sendFriendRequest", userId), /* User */ getUser: (userId: string) => ipcRenderer.invoke("getUser", userId), getUserLibrary: ( userId: string, take?: number, skip?: number, sortBy?: string ) => ipcRenderer.invoke("getUserLibrary", userId, take, skip, sortBy), blockUser: (userId: string) => ipcRenderer.invoke("blockUser", userId), unblockUser: (userId: string) => ipcRenderer.invoke("unblockUser", userId), getUserFriends: (userId: string, take: number, skip: number) => ipcRenderer.invoke("getUserFriends", userId, take, skip), getBlockedUsers: (take: number, skip: number) => ipcRenderer.invoke("getBlockedUsers", take, skip), getUserStats: (userId: string) => ipcRenderer.invoke("getUserStats", userId), reportUser: (userId: string, reason: string, description: string) => ipcRenderer.invoke("reportUser", userId, reason, description), getComparedUnlockedAchievements: ( objectId: string, shop: GameShop, userId: string ) => ipcRenderer.invoke( "getComparedUnlockedAchievements", objectId, shop, userId ), getUnlockedAchievements: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getUnlockedAchievements", objectId, shop), /* Auth */ getAuth: () => ipcRenderer.invoke("getAuth"), signOut: () => ipcRenderer.invoke("signOut"), openAuthWindow: (page: AuthPage) => ipcRenderer.invoke("openAuthWindow", page), getSessionHash: () => ipcRenderer.invoke("getSessionHash"), onSignIn: (cb: () => void) => { const listener = (_event: Electron.IpcRendererEvent) => cb(); ipcRenderer.on("on-signin", listener); return () => ipcRenderer.removeListener("on-signin", listener); }, onAccountUpdated: (cb: () => void) => { const listener = (_event: Electron.IpcRendererEvent) => cb(); ipcRenderer.on("on-account-updated", listener); return () => ipcRenderer.removeListener("on-account-updated", listener); }, onSignOut: (cb: () => void) => { const listener = (_event: Electron.IpcRendererEvent) => cb(); ipcRenderer.on("on-signout", listener); return () => ipcRenderer.removeListener("on-signout", listener); }, /* Notifications */ publishNewRepacksNotification: (newRepacksCount: number) => ipcRenderer.invoke("publishNewRepacksNotification", newRepacksCount), onAchievementUnlocked: ( cb: ( position?: AchievementCustomNotificationPosition, achievements?: AchievementNotificationInfo[] ) => void ) => { const listener = ( _event: Electron.IpcRendererEvent, position?: AchievementCustomNotificationPosition, achievements?: AchievementNotificationInfo[] ) => cb(position, achievements); ipcRenderer.on("on-achievement-unlocked", listener); return () => ipcRenderer.removeListener("on-achievement-unlocked", listener); }, onCombinedAchievementsUnlocked: ( cb: ( gameCount: number, achievementsCount: number, position: AchievementCustomNotificationPosition ) => void ) => { const listener = ( _event: Electron.IpcRendererEvent, gameCount: number, achievementCount: number, position: AchievementCustomNotificationPosition ) => cb(gameCount, achievementCount, position); ipcRenderer.on("on-combined-achievements-unlocked", listener); return () => ipcRenderer.removeListener("on-combined-achievements-unlocked", listener); }, updateAchievementCustomNotificationWindow: () => ipcRenderer.invoke("updateAchievementCustomNotificationWindow"), showAchievementTestNotification: () => ipcRenderer.invoke("showAchievementTestNotification"), /* Themes */ addCustomTheme: (theme: Theme) => ipcRenderer.invoke("addCustomTheme", theme), getAllCustomThemes: () => ipcRenderer.invoke("getAllCustomThemes"), deleteAllCustomThemes: () => ipcRenderer.invoke("deleteAllCustomThemes"), deleteCustomTheme: (themeId: string) => ipcRenderer.invoke("deleteCustomTheme", themeId), updateCustomTheme: (themeId: string, code: string) => ipcRenderer.invoke("updateCustomTheme", themeId, code), getCustomThemeById: (themeId: string) => ipcRenderer.invoke("getCustomThemeById", themeId), getActiveCustomTheme: () => ipcRenderer.invoke("getActiveCustomTheme"), toggleCustomTheme: (themeId: string, isActive: boolean) => ipcRenderer.invoke("toggleCustomTheme", themeId, isActive), /* Editor */ openEditorWindow: (themeId: string) => ipcRenderer.invoke("openEditorWindow", themeId), onCustomThemeUpdated: (cb: () => void) => { const listener = (_event: Electron.IpcRendererEvent) => cb(); ipcRenderer.on("on-custom-theme-updated", listener); return () => ipcRenderer.removeListener("on-custom-theme-updated", listener); }, closeEditorWindow: (themeId?: string) => ipcRenderer.invoke("closeEditorWindow", themeId), });