328 lines
17 KiB
JavaScript
328 lines
17 KiB
JavaScript
'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),
|
||
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=<id>", optional "--server=<ip>")
|
||
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'),
|
||
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'),
|
||
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);
|
||
},
|
||
});
|