'use strict'; /* * preload.js – stellt dem Renderer eine schmale, sichere API bereit. * contextIsolation ist aktiv; der Renderer hat KEINEN direkten Node-Zugriff. */ const { contextBridge, ipcRenderer } = require('electron'); // Debug-Hilfe: JS-Fehler im Renderer landen im Terminal des Hauptprozesses, // damit man sie auch ohne offene DevTools sieht window.addEventListener('error', (e) => { ipcRenderer.send('debug:rendererError', (e.error && e.error.stack) || e.message); }); window.addEventListener('unhandledrejection', (e) => { ipcRenderer.send('debug:rendererError', 'unhandledrejection: ' + ((e.reason && e.reason.stack) || e.reason)); }); contextBridge.exposeInMainWorld('api', { // Einstellungen getSettings: () => ipcRenderer.invoke('settings:get'), saveSettings: (patch) => ipcRenderer.invoke('settings:save', patch), getInstancesDir: () => ipcRenderer.invoke('settings:instancesDir'), detectJava: (javaPath) => ipcRenderer.invoke('java:detect', javaPath), scanJava: () => ipcRenderer.invoke('java:scan'), listJava: () => ipcRenderer.invoke('java:list'), // Minecraft-Versionen (Mojang) mcVersions: () => ipcRenderer.invoke('mc:versions'), loaderVersions: (loader, mcVersion) => ipcRenderer.invoke('loaders:versions', loader, mcVersion), optifineFind: (mcVersion) => ipcRenderer.invoke('optifine:find', mcVersion), optifineSetup: (id) => ipcRenderer.invoke('optifine:setup', id), optifineConfirm: (id) => ipcRenderer.invoke('optifine:confirm', id), // Mods (Modrinth) modSearch: (opts) => ipcRenderer.invoke('mods:search', opts), curseforgeTestKey: (apiKey) => ipcRenderer.invoke('curseforge:testKey', apiKey), // art: 'mod' | 'resourcepack' | 'shader' modList: (id, art) => ipcRenderer.invoke('mods:list', id, art), modInstall: (id, project, art) => ipcRenderer.invoke('mods:install', id, project, art), modRemove: (id, filename, art) => ipcRenderer.invoke('mods:remove', id, filename, art), modDependents: (id, filename, art) => ipcRenderer.invoke('mods:dependents', id, filename, art), modAddFile: (id, art) => ipcRenderer.invoke('mods:addFile', id, art), modSetEnabled: (id, filename, enabled, art) => ipcRenderer.invoke('mods:setEnabled', id, filename, enabled, art), systemMemory: () => ipcRenderer.invoke('system:memory'), checkMemory: (minMb, maxMb) => ipcRenderer.invoke('system:checkMemory', minMb, maxMb), crashLatest: (id, seit) => ipcRenderer.invoke('crash:latest', id, seit), onAutoBackup: (cb) => { const l = (_e, d) => cb(d); ipcRenderer.on('backup:auto', l); return () => ipcRenderer.removeListener('backup:auto', l); }, crashOpenFolder: (id) => ipcRenderer.invoke('crash:openFolder', id), serversList: (id) => ipcRenderer.invoke('servers:list', id), serversAdd: (id, eintrag) => ipcRenderer.invoke('servers:add', id, eintrag), serversRemove: (id, ip) => ipcRenderer.invoke('servers:remove', id, ip), serversRename: (id, ip, neuerName) => ipcRenderer.invoke('servers:rename', id, ip, neuerName), serversCopyTo: (zielIds, eintrag) => ipcRenderer.invoke('servers:copyTo', zielIds, eintrag), serversPing: (ip) => ipcRenderer.invoke('servers:ping', ip), logsList: (id) => ipcRenderer.invoke('logs:list', id), logsRead: (id, name) => ipcRenderer.invoke('logs:read', id, name), logsOpenFolder: (id) => ipcRenderer.invoke('logs:openFolder', id), modCheckUpdates: (id, art) => ipcRenderer.invoke('mods:checkUpdates', id, art), modApplyUpdates: (id, projectIds, art) => ipcRenderer.invoke('mods:applyUpdates', id, projectIds, art), onModUpdateProgress: (cb) => { const l = (_e, d) => cb(d); ipcRenderer.on('mods:updateProgress', l); return () => ipcRenderer.removeListener('mods:updateProgress', l); }, // Instanzen listInstances: () => ipcRenderer.invoke('instances:list'), getInstance: (id) => ipcRenderer.invoke('instances:get', id), createInstance: (data) => ipcRenderer.invoke('instances:create', data), installPerformancePack: (id) => ipcRenderer.invoke('instances:installPerformancePack', id), updateInstance: (id, patch) => ipcRenderer.invoke('instances:update', id, patch), deleteInstance: (id) => ipcRenderer.invoke('instances:delete', id), duplicateInstance: (id) => ipcRenderer.invoke('instances:duplicate', id), openInstanceFolder: (id) => ipcRenderer.invoke('instances:openFolder', id), openGameDir: (id) => ipcRenderer.invoke('instances:openGameDir', id), // Welten & Ressourcenpakete worlds: (id) => ipcRenderer.invoke('instance:worlds', id), resourcepacks: (id) => ipcRenderer.invoke('instance:resourcepacks', id), datapacks: (id) => ipcRenderer.invoke('instance:datapacks', id), deleteContent: (id, sub, name) => ipcRenderer.invoke('instance:deleteContent', id, sub, name), backupWorld: (id, worldName) => ipcRenderer.invoke('world:backup', id, worldName), openSubdir: (id, sub) => ipcRenderer.invoke('instance:openSubdir', id, sub), screenshots: (id) => ipcRenderer.invoke('instance:screenshots', id), screenshotData: (id, name) => ipcRenderer.invoke('instance:screenshotData', id, name), openScreenshot: (id, name) => ipcRenderer.invoke('instance:openScreenshot', id, name), launchInstance: (id, accountId, opts) => ipcRenderer.invoke('instances:launch', id, accountId, opts), exportInstance: (id) => ipcRenderer.invoke('instances:export', id), createGlobalBackup: () => ipcRenderer.invoke('backup:global'), listGlobalBackups: () => ipcRenderer.invoke('backup:list'), restoreGlobalBackup: (sourcePath) => ipcRenderer.invoke('backup:restore', sourcePath), onBackupProgress: (cb) => { const l = (_e, d) => cb(d); ipcRenderer.on('backup:progress', l); return () => ipcRenderer.removeListener('backup:progress', l); }, importInstance: () => ipcRenderer.invoke('instances:import'), createShortcut: (id, customName) => ipcRenderer.invoke('instances:shortcut', id, customName), serversShortcut: (id, ip, name, customName) => ipcRenderer.invoke('servers:shortcut', id, ip, name, customName), // Start über eine Desktop-Verknüpfung ("--launch=", optional "--server=") onLaunchRequest: (cb) => { const l = (_e, payload) => cb(payload); ipcRenderer.on('instance:launch-request', l); return () => ipcRenderer.removeListener('instance:launch-request', l); }, // Signalisiert dem Hauptprozess, dass onLaunchRequest jetzt registriert ist – // verhindert, dass ein Start-Wunsch aus einer Verknüpfung verloren geht, // weil er ankommt, bevor die Oberfläche fertig eingerichtet ist notifyReady: () => ipcRenderer.send('renderer:ready'), importModpack: () => ipcRenderer.invoke('modpack:import'), // Datei per Drag&Drop aufs Fenster gezogen (.jar/.mrpack/.zip) filesDrop: (filePath, targetId, targetArt) => ipcRenderer.invoke('files:drop', filePath, targetId, targetArt), modpackSearch: (opts) => ipcRenderer.invoke('modpack:search', opts), modpackInstallFromModrinth: (project) => ipcRenderer.invoke('modpack:installFromModrinth', project), modpackInstallFromCurseForge: (project) => ipcRenderer.invoke('modpack:installFromCurseForge', project), modpackCheck: (id) => ipcRenderer.invoke('modpack:check', id), modpackUpdate: (id, versionId) => ipcRenderer.invoke('modpack:update', id, versionId), onLaunchProgress: (cb) => { const listener = (_e, data) => cb(data); ipcRenderer.on('launch:progress', listener); return () => ipcRenderer.removeListener('launch:progress', listener); }, // eigenes Instanz-Bild pickImage: () => ipcRenderer.invoke('dialog:pickImage'), setInstanceImage: (id, sourcePath) => ipcRenderer.invoke('instances:setImage', id, sourcePath), clearInstanceImage: (id) => ipcRenderer.invoke('instances:clearImage', id), instanceIcon: (id) => ipcRenderer.invoke('instances:icon', id), fileDataUri: (p) => ipcRenderer.invoke('file:dataUri', p), // Microsoft-Login (Phase 3) + Mehrkonten authStatus: () => ipcRenderer.invoke('auth:status'), authAccounts: () => ipcRenderer.invoke('auth:accounts'), authSetActive: (id) => ipcRenderer.invoke('auth:setActive', id), skinProfile: () => ipcRenderer.invoke('skin:profile'), skinChoose: () => ipcRenderer.invoke('skin:choose'), skinApply: (pfad, variante) => ipcRenderer.invoke('skin:apply', pfad, variante), skinReset: () => ipcRenderer.invoke('skin:reset'), authRemove: (id) => ipcRenderer.invoke('auth:remove', id), authLogin: () => ipcRenderer.invoke('auth:login'), authLogout: () => ipcRenderer.invoke('auth:logout'), authHealth: () => ipcRenderer.invoke('auth:health'), authRecheck: () => ipcRenderer.invoke('auth:recheck'), onAuthHealth: (cb) => { const listener = (_e, data) => cb(data); ipcRenderer.on('auth:health', listener); return () => ipcRenderer.removeListener('auth:health', listener); }, // meldet sich, wenn ein Spiel startet oder endet -> Kontenliste auffrischen onAccountsChanged: (cb) => { const listener = () => cb(); ipcRenderer.on('accounts:changed', listener); return () => ipcRenderer.removeListener('accounts:changed', listener); }, onAuthRefreshed: (cb) => { const listener = () => cb(); ipcRenderer.on('auth:refreshed', listener); return () => ipcRenderer.removeListener('auth:refreshed', listener); }, onAuthCode: (cb) => { const listener = (_e, data) => cb(data); ipcRenderer.on('auth:code', listener); return () => ipcRenderer.removeListener('auth:code', listener); }, // Dialoge / App // MultiMC-Import mmcPickFolder: () => ipcRenderer.invoke('mmc:pickFolder'), mmcScan: (folder) => ipcRenderer.invoke('mmc:scan', folder), mmcImport: (entries, copyData) => ipcRenderer.invoke('mmc:import', entries, copyData), onMmcProgress: (cb) => { const l = (_e, d) => cb(d); ipcRenderer.on('mmc:progress', l); return () => ipcRenderer.removeListener('mmc:progress', l); }, cfPickFolder: () => ipcRenderer.invoke('cf:pickFolder'), cfScan: (folder) => ipcRenderer.invoke('cf:scan', folder), cfImport: (entries, copyData) => ipcRenderer.invoke('cf:import', entries, copyData), onCfProgress: (cb) => { const l = (_e, d) => cb(d); ipcRenderer.on('cf:progress', l); return () => ipcRenderer.removeListener('cf:progress', l); }, mcPickFolder: () => ipcRenderer.invoke('mc:pickFolder'), mcScan: (folder) => ipcRenderer.invoke('mc:scan', folder), mcImport: (entries, copyData) => ipcRenderer.invoke('mc:import', entries, copyData), onMcProgress: (cb) => { const l = (_e, d) => cb(d); ipcRenderer.on('mc:progress', l); return () => ipcRenderer.removeListener('mc:progress', l); }, technicPickFolder: () => ipcRenderer.invoke('technic:pickFolder'), technicScan: (folder) => ipcRenderer.invoke('technic:scan', folder), technicImport: (entries, copyData) => ipcRenderer.invoke('technic:import', entries, copyData), onTechnicProgress: (cb) => { const l = (_e, d) => cb(d); ipcRenderer.on('technic:progress', l); return () => ipcRenderer.removeListener('technic:progress', l); }, technicPlatformSearch: (query) => ipcRenderer.invoke('technicPlatform:search', query), technicPlatformInstall: (slug) => ipcRenderer.invoke('technicPlatform:install', slug), onTechnicPlatformProgress: (cb) => { const l = (_e, d) => cb(d); ipcRenderer.on('technicPlatform:progress', l); return () => ipcRenderer.removeListener('technicPlatform:progress', l); }, // Bedrock (Windows-Store-Ausgabe) bedrockDetect: () => ipcRenderer.invoke('bedrock:detect'), bedrockLaunch: () => ipcRenderer.invoke('bedrock:launch'), bedrockWorlds: () => ipcRenderer.invoke('bedrock:worlds'), bedrockList: (kind) => ipcRenderer.invoke('bedrock:list', kind), bedrockRemove: (kind, name) => ipcRenderer.invoke('bedrock:remove', kind, name), bedrockOpenFolder: (kind) => ipcRenderer.invoke('bedrock:openFolder', kind), bedrockInstallAddon: () => ipcRenderer.invoke('bedrock:installAddon'), bedrockOpenWorld: (dir) => ipcRenderer.invoke('bedrock:openWorld', dir), // echte Minecraft-Icons iconsList: () => ipcRenderer.invoke('icons:list'), iconsExtract: () => ipcRenderer.invoke('icons:extract'), // Updates updateCheck: () => ipcRenderer.invoke('update:check'), updateLastCheck: () => ipcRenderer.invoke('update:lastCheck'), updateInstall: (asset) => ipcRenderer.invoke('update:install', asset), onUpdateAvailable: (cb) => { const l = (_e, d) => cb(d); ipcRenderer.on('update:available', l); return () => ipcRenderer.removeListener('update:available', l); }, // Ergebnis des automatischen Checks beim Start – auch wenn kein Update // verfügbar ist (im Unterschied zu onUpdateAvailable, das nur bei // verfügbarem Update feuert) onUpdateChecked: (cb) => { const l = (_e, d) => cb(d); ipcRenderer.on('update:checked', l); return () => ipcRenderer.removeListener('update:checked', l); }, onUpdateProgress: (cb) => { const l = (_e, d) => cb(d); ipcRenderer.on('update:progress', l); return () => ipcRenderer.removeListener('update:progress', l); }, openExternal: (url) => ipcRenderer.invoke('app:openExternal', url), pickFolder: (title) => ipcRenderer.invoke('dialog:pickFolder', title), pickJava: () => ipcRenderer.invoke('dialog:pickJava'), appInfo: () => ipcRenderer.invoke('app:info'), appLicense: () => ipcRenderer.invoke('app:license'), // Einstellungen/Konsole als eigenes, frei verschiebbares Fenster popoutOpen: (modalId, params) => ipcRenderer.send('popout:open', modalId, params), popoutHide: () => ipcRenderer.send('popout:hide'), onPopoutReopen: (cb) => { const l = (_e, params) => cb(params); ipcRenderer.on('popout:reopen', l); return () => ipcRenderer.removeListener('popout:reopen', l); }, onSettingsChanged: (cb) => { const l = (_e, s) => cb(s); ipcRenderer.on('settings:changed', l); return () => ipcRenderer.removeListener('settings:changed', l); }, onInstancesChanged: (cb) => { const l = () => cb(); ipcRenderer.on('instances:changed', l); return () => ipcRenderer.removeListener('instances:changed', l); }, onSkinChanged: (cb) => { const l = () => cb(); ipcRenderer.on('skin:changed', l); return () => ipcRenderer.removeListener('skin:changed', l); }, updateGetPending: () => ipcRenderer.invoke('update:getPending'), // Spielaufzeichnung recordingListWindows: () => ipcRenderer.invoke('recording:listWindows'), recordingGetAudioSourceId: () => ipcRenderer.invoke('recording:getAudioSourceId'), recordingSave: (instanceName, buffer, serverLabel, fps) => ipcRenderer.invoke('recording:save', instanceName, buffer, serverLabel, fps), recordingOpenFolder: (instanceName) => ipcRenderer.invoke('recording:openFolder', instanceName), recordingList: (instanceName) => ipcRenderer.invoke('recording:list', instanceName), recordingThumbnail: (instanceName, dateiname) => ipcRenderer.invoke('recording:thumbnail', instanceName, dateiname), recordingDelete: (instanceName, dateiname) => ipcRenderer.invoke('recording:delete', instanceName, dateiname), recordingOpen: (instanceName, dateiname) => ipcRenderer.invoke('recording:open', instanceName, dateiname), // Freundesystem friendsEnabled: () => ipcRenderer.invoke('friends:enabled'), friendsList: () => ipcRenderer.invoke('friends:list'), friendsRequests: () => ipcRenderer.invoke('friends:requests'), friendsAdd: (targetUsername) => ipcRenderer.invoke('friends:add', targetUsername), friendsRespond: (otherUuid, accept) => ipcRenderer.invoke('friends:respond', otherUuid, accept), friendsRemove: (otherUuid) => ipcRenderer.invoke('friends:remove', otherUuid), friendsSentRequests: () => ipcRenderer.invoke('friends:sentRequests'), friendsCancel: (otherUuid) => ipcRenderer.invoke('friends:cancel', otherUuid), friendsSetNickname: (uuid, nickname) => ipcRenderer.invoke('friends:setNickname', uuid, nickname), friendsRequestCount: () => ipcRenderer.invoke('friends:requestCount'), clipSetActive: (active, instanceId) => ipcRenderer.invoke('clip:setActive', active, instanceId), clipSave: (data) => ipcRenderer.invoke('clip:save', data), onClipHotkey: (cb) => { const l = () => cb(); ipcRenderer.on('clip:hotkey', l); return () => ipcRenderer.removeListener('clip:hotkey', l); }, onUiToast: (cb) => { const l = (_e, t) => cb(t); ipcRenderer.on('ui:toast', l); return () => ipcRenderer.removeListener('ui:toast', l); }, friendsJoin: (server, version) => ipcRenderer.invoke('friends:join', server, version), friendsJoinNew: (server, version) => ipcRenderer.invoke('friends:joinNew', server, version), friendsMyServer: () => ipcRenderer.invoke('friends:myServer'), friendsInvite: (targetUuid) => ipcRenderer.invoke('friends:invite', targetUuid), friendsInvites: () => ipcRenderer.invoke('friends:invites'), friendsDismissInvite: (id) => ipcRenderer.invoke('friends:dismissInvite', id), onFriendsInvite: (cb) => { const l = (_e, einladung) => cb(einladung); ipcRenderer.on('friends:invite', l); return () => ipcRenderer.removeListener('friends:invite', l); }, onFriendsRequestCount: (cb) => { const l = (_e, zahl) => cb(zahl); ipcRenderer.on('friends:requestCount', l); return () => ipcRenderer.removeListener('friends:requestCount', l); }, recordingGetServer: (instanceId) => ipcRenderer.invoke('recording:getServer', instanceId), onRecordingServerDetected: (cb) => { const l = (_e, payload) => cb(payload); ipcRenderer.on('recording:serverDetected', l); return () => ipcRenderer.removeListener('recording:serverDetected', l); }, recordingGetPid: (instanceId) => ipcRenderer.invoke('recording:getPid', instanceId), recordingSetCaptureTarget: (sourceId) => ipcRenderer.invoke('recording:setCaptureTarget', sourceId), recordingGetWindowBounds: (instanceId, fensterTitel) => ipcRenderer.invoke('recording:getWindowBounds', instanceId, fensterTitel), recordingStartGameAudio: (pid) => ipcRenderer.invoke('recording:startGameAudio', pid), recordingStopGameAudio: () => ipcRenderer.invoke('recording:stopGameAudio'), onGameAudioChunk: (cb) => { const l = (_e, chunk) => cb(chunk); ipcRenderer.on('recording:gameAudioChunk', l); return () => ipcRenderer.removeListener('recording:gameAudioChunk', l); }, recordingSetGuard: (active) => ipcRenderer.send('recording:guard', active), onRecordCloseAttempt: (cb) => { const l = () => cb(); ipcRenderer.on('record:closeAttempt', l); return () => ipcRenderer.removeListener('record:closeAttempt', l); }, });