Foinse foilsithe
Íosluchtaigh foinse ZIP
CH-J Server Manager
Brabhsáil comhadlanna agus comhaid chun feidhmchlár áirithe a scaoileadh.
CH-J / Server Manager
377 Comhaid
core-20261019T111616Z-160a00bfcddc
Suimigh: · Tógtha:
1
"use strict";3
const path = require("node:path");4
const fs = require("node:fs");5
const { ProfileTransfer, serializeProfiles, MAX_BYTES } = require("../profiles/profileTransfer");6
const { LICENSE_NAME, LICENSE_VERSION } = require("../legal/licenseAcceptance");8
const PRIVATE_KEY_DIALOG_COPY = Object.freeze({9
cs: { title: "Vybrat soukromý SSH klíč", keys: "Soukromé SSH klíče", all: "Všechny soubory" },10
de: { title: "Privaten SSH-Schlüssel auswählen", keys: "Private SSH-Schlüssel", all: "Alle Dateien" },11
en: { title: "Select an SSH private key", keys: "SSH private keys", all: "All files" }12
});14
function registerCoreIpc(options) {15
const {16
ipcMain, shell, dialog, clipboard, app, configStore, pluginRegistry, pluginService, pluginRuntime, profileService,17
sessionManager, vaultStore, biometricService, vaultLockController, updateService, updateInstallerLauncher, diagnosticsService, getMainWindow, logger18
} = options;19
const profileTransfer = new ProfileTransfer(profileService);21
function assertTrustedSender(event) {22
const mainWindow = getMainWindow();23
if (!mainWindow || mainWindow.isDestroyed() || event.sender.id !== mainWindow.webContents.id ||24
(mainWindow.webContents.mainFrame && event.senderFrame !== mainWindow.webContents.mainFrame)) {25
const error = new Error("Untrusted IPC sender.");26
error.code = "UNTRUSTED_IPC_SENDER";27
throw error;28
}29
}31
function handle(channel, handler) {32
ipcMain.handle(channel, async (event, payload) => {33
assertTrustedSender(event);34
try {35
return await handler(payload);36
} catch (error) {37
logger?.error("IPC operation failed.", { channel, message: error?.message || String(error), code: error?.code });38
throw error;39
}40
});41
}43
function send(channel, handler) {44
ipcMain.on(channel, (event, payload) => {45
try {46
assertTrustedSender(event);47
handler(payload);48
} catch (error) {49
logger?.warn("IPC event was rejected.", { channel, message: error?.message || String(error), code: error?.code });50
}51
});52
}54
function sessionResult(action) {55
return async (payload) => {56
try {57
return { ok: true, value: await action(payload || {}) };58
} catch (error) {59
return {60
ok: false,61
error: typeof error?.toJSON === "function"62
? error.toJSON()63
: { code: error?.code || "SSH_OPERATION_FAILED", message: error?.message || String(error) }64
};65
}66
};67
}69
handle("core:getInfo", async () => ({70
name: app.getName(),71
version: app.getVersion(),72
platform: process.platform,73
arch: process.arch,74
coreApi: "1.0.0",75
pluginApi: options.pluginApiVersion || require("../plugins/pluginRegistry").PLUGIN_API_VERSION,76
dataSchema: 1,77
license: `${LICENSE_NAME} ${LICENSE_VERSION}`,78
copyright: "Copyright 2026 Josef Chudy"79
}));80
handle("legal:open", async (payload = {}) => {81
const documents = {82
documentation: "README.md",83
safetyCzech: "provozni-bezpecnost-a-zalohovani.md",84
safetyEnglish: "operational-safety-and-backup-guidance.md",85
safetyGerman: "betriebssicherheit-und-datensicherung.md"86
};87
const documentKey = String(payload.document || "");88
if (Object.hasOwn(documents, documentKey)) {89
const docsRoot = path.join(app.isPackaged ? process.resourcesPath : app.getAppPath(), "docs");90
const error = await shell.openPath(path.join(docsRoot, documents[documentKey]));91
if (error) throw new Error(`The documentation could not be opened: ${error}`);92
return { ok: true };93
}94
const names = {95
application: "APPLICATION_LICENSE.txt",96
notice: "NOTICE.txt",97
thirdParty: "THIRD_PARTY_NOTICES.txt",98
thirdPartyBundle: "THIRD_PARTY_LICENSES.zip",99
electron: "ELECTRON_LICENSE.txt",100
chromium: "CHROMIUM_LICENSES.html"101
};102
const filename = Object.hasOwn(names, documentKey) ? names[documentKey] : null;103
if (!filename) throw new Error("Unknown legal document.");104
const root = app.isPackaged ? path.join(process.resourcesPath, "licenses") : app.getAppPath();105
const developmentFiles = {106
APPLICATION_LICENSE: "LICENSE",107
NOTICE: "NOTICE",108
THIRD_PARTY_NOTICES: "THIRD_PARTY_NOTICES.txt",109
THIRD_PARTY_LICENSES: "THIRD_PARTY_LICENSES.zip",110
ELECTRON_LICENSE: path.join("node_modules", "electron", "dist", "LICENSE"),111
CHROMIUM_LICENSES: path.join("node_modules", "electron", "dist", "LICENSES.chromium.html")112
};113
const stem = path.parse(filename).name;114
const filePath = app.isPackaged ? path.join(root, filename) : path.join(root, developmentFiles[stem]);115
const error = await shell.openPath(filePath);116
if (error) throw new Error(`The legal document could not be opened: ${error}`);117
return { ok: true };118
});119
handle("config:get", async () => configStore.get());120
handle("config:update", async (payload = {}) => {121
if (payload.security) {122
await vaultStore.verifyPassword(payload.masterPassword);123
if (payload.security.requireSystemAuthentication && !(await biometricService.getStatus()).available) {124
throw new Error("BIOMETRIC_UNAVAILABLE");125
}126
}127
const config = configStore.update(payload);128
pluginRuntime.notifyLanguageChanged(config.ui.language);129
return config;130
});131
handle("plugins:list", async () => pluginRegistry.listInstalled());132
handle("plugins:getState", async () => pluginService.getState());133
handle("plugins:checkCatalog", async () => pluginService.checkCatalog());134
handle("plugins:install", async (payload = {}) => { await biometricService?.requireSensitiveAction(); return pluginService.install(payload.releaseId); });135
handle("plugins:uninstall", async (payload = {}) => { await biometricService?.requireSensitiveAction(); return pluginService.uninstall(payload.pluginId); });136
handle("plugins:open", async (payload = {}) => pluginService.open(payload.pluginId));137
handle("vault:status", async () => vaultStore.status());138
handle("vault:create", async (payload = {}) => { const status = await vaultStore.create(payload.password); vaultLockController?.activity(); return status; });139
handle("vault:unlock", async (payload = {}) => { biometricService?.cancel(); const status = await vaultStore.unlock(payload.password); biometricService?.disarmAutoUnlock?.(); vaultLockController?.activity(); return status; });140
handle("biometrics:status", sessionResult(() => biometricService.getStatus()));141
handle("biometrics:enable", sessionResult(() => biometricService.enable()));142
handle("biometrics:disable", sessionResult(() => biometricService.disable()));143
handle("biometrics:unlock", sessionResult(async () => {144
const status = await biometricService.unlock(); vaultLockController?.activity(); return status;145
}));146
handle("biometrics:autoUnlock", sessionResult(async () => {147
const window = getMainWindow?.();148
if (window && (window.isDestroyed() || !window.isVisible() || window.isMinimized())) return { attempted: false, reason: "WINDOW_NOT_READY" };149
const result = await biometricService.autoUnlock();150
if (result.status?.unlocked) vaultLockController?.activity();151
return result;152
}));153
handle("biometrics:authenticate", sessionResult(() => biometricService.authenticateSensitiveAction()));154
handle("vault:lock", async () => {155
if (vaultLockController) return vaultLockController.lock();156
pluginRuntime.closeAll();157
await sessionManager.disconnectAll("vault-lock");158
return vaultStore.lock();159
});160
handle("vault:reset", async (payload = {}) => {161
if (payload.confirmation !== "SMAZAT") throw Object.assign(new Error("Vault reset confirmation is invalid."), { code: "VAULT_RESET_CONFIRMATION_REQUIRED" });162
if (vaultStore.status().unlocked) await biometricService?.requireSensitiveAction();163
await biometricService?.disable({ reset: true });164
pluginRuntime.closeAll();165
await sessionManager.disconnectAll("vault-reset");166
const status = vaultStore.reset(payload.confirmation);167
logger?.warn("Encrypted user vault was reset by the user.");168
return status;169
});170
handle("profiles:list", async () => profileService.list());171
handle("profiles:export", async (payload = {}) => {172
const format = payload.format || "json";173
const profiles = profileService.list();174
const selected = payload.ids === undefined ? profiles : profiles.filter((profile) => Array.isArray(payload.ids) && payload.ids.includes(profile.id));175
if (!selected.length) throw new Error("Select at least one server to export.");176
const content = serializeProfiles(selected, format);177
const result = await dialog.showSaveDialog(getMainWindow(), {178
defaultPath: `chj-server-profiles.${format}`,179
filters: [{ name: format.toUpperCase(), extensions: [format] }]180
});181
if (result.canceled || !result.filePath) return { canceled: true };182
await fs.promises.writeFile(result.filePath, content, { encoding: "utf8", mode: 0o600 });183
return { canceled: false, count: selected.length };184
});185
handle("profiles:previewImport", async () => {186
profileService.list(); // Require an unlocked vault before opening the picker.187
profileTransfer.cancel();188
const result = await dialog.showOpenDialog(getMainWindow(), {189
properties: ["openFile"], filters: [{ name: "JSON / CSV", extensions: ["json", "csv"] }]190
});191
if (result.canceled || !result.filePaths[0]) return { canceled: true };192
const file = await fs.promises.open(result.filePaths[0], "r");193
try {194
const buffer = Buffer.alloc(MAX_BYTES + 1);195
let length = 0;196
while (length < buffer.length) {197
const read = await file.read(buffer, length, buffer.length - length, null);198
if (!read.bytesRead) break;199
length += read.bytesRead;200
}201
if (length > MAX_BYTES) throw new Error("Profile file exceeds the 5 MiB limit.");202
const content = new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, length));203
return { canceled: false, ...profileTransfer.preview(content) };204
} finally { await file.close(); }205
});206
handle("profiles:commitImport", async (payload = {}) => {207
await biometricService?.requireSensitiveAction();208
return profileTransfer.commit(payload.token, payload.policy);209
});210
handle("profiles:cancelImport", async () => { profileTransfer.cancel(); return { canceled: true }; });211
handle("profiles:save", async (payload = {}) => profileService.save(payload));212
handle("profiles:delete", async (payload = {}) => { await biometricService?.requireSensitiveAction(); return profileService.delete(String(payload.id || "")); });213
handle("profiles:selectPrivateKey", async () => {214
const mainWindow = getMainWindow();215
const copy = PRIVATE_KEY_DIALOG_COPY[configStore.get().ui.language] || PRIVATE_KEY_DIALOG_COPY.cs;216
const result = await dialog.showOpenDialog(mainWindow, {217
title: copy.title,218
properties: ["openFile"],219
filters: [{ name: copy.keys, extensions: ["pem", "key", "ppk"] }, { name: copy.all, extensions: ["*"] }]220
});221
return { canceled: result.canceled, path: result.filePaths[0] || null };222
});223
handle("ssh:list", async () => sessionManager.list());224
const diagnosticsResult = (action) => async (payload = {}) => {225
try { return { ok: true, value: await action(payload) }; }226
catch (error) { return { ok: false, error: { code: error.code || "DIAGNOSTICS_ERROR", message: String(error.message || error).slice(0, 400) } }; }227
};228
handle("diagnostics:resolve", diagnosticsResult((payload) => diagnosticsService.resolve(payload)));229
handle("diagnostics:start", diagnosticsResult((payload) => diagnosticsService.start(payload)));230
handle("diagnostics:cancel", diagnosticsResult((payload) => diagnosticsService.cancel(payload.id, payload.tool)));231
handle("diagnostics:get", diagnosticsResult((payload) => diagnosticsService.get(payload.id)));232
handle("diagnostics:history", diagnosticsResult(() => diagnosticsService.history.list()));233
handle("diagnostics:removeHistory", diagnosticsResult((payload) => diagnosticsService.history.remove(payload.id)));234
handle("diagnostics:copy", diagnosticsResult((payload) => {235
clipboard.writeText(diagnosticsService.export(payload.id, payload.format || "txt"));236
return { copied: true };237
}));238
handle("diagnostics:export", diagnosticsResult(async (payload) => {239
const content = diagnosticsService.export(payload.id, payload.format);240
const result = await dialog.showSaveDialog(getMainWindow(), { defaultPath: `diagnostics.${payload.format}`, filters: [{ name: payload.format.toUpperCase(), extensions: [payload.format] }] });241
if (result.canceled || !result.filePath) return { canceled: true };242
await require("node:fs").promises.writeFile(result.filePath, content, { mode: 0o600 });243
return { canceled: false };244
}));245
handle("diagnostics:import", diagnosticsResult(async () => {246
const result = await dialog.showOpenDialog(getMainWindow(), { properties: ["openFile"], filters: [{ name: "Diagnostics JSON", extensions: ["json"] }] });247
if (result.canceled || !result.filePaths[0]) return { canceled: true };248
const fs = require("node:fs"); const stat = await fs.promises.lstat(result.filePaths[0]);249
if (!stat.isFile() || stat.size > 2 * 1024 * 1024) throw Object.assign(new Error("IMPORT_LIMIT"), { code: "IMPORT_LIMIT" });250
const id = diagnosticsService.history.import(await fs.promises.readFile(result.filePaths[0], "utf8"));251
return { id };252
}));253
handle("ssh:connect", sessionResult((payload) => sessionManager.connect(payload)));254
handle("ssh:trustHostKey", sessionResult((payload) => sessionManager.trustPending(payload)));255
handle("ssh:disconnect", sessionResult((payload) => sessionManager.disconnect(payload.sessionId, "user")));256
handle("ssh:resize", sessionResult((payload) => sessionManager.resize(payload.sessionId, payload.cols, payload.rows)));257
send("ssh:input", (payload = {}) => sessionManager.write(payload.sessionId, payload.data));258
handle("updates:getState", async () => updateService.getState());259
handle("updates:check", async () => updateService.check());260
handle("updates:select", async (payload = {}) => updateService.select(payload.id));261
handle("updates:download", async () => updateService.download());262
handle("updates:install", async () => {263
await biometricService?.requireSensitiveAction();264
// The renderer supplies neither a path nor a verification flag. The main265
// process re-hashes and re-verifies its internally tracked artifact here.266
const filePath = await updateService.prepareInstallerLaunch();267
const result = await updateInstallerLauncher.install(filePath);268
updateService.markInstallerLaunched();269
if (result.restartApplication) {270
setTimeout(() => {271
app.relaunch();272
app.quit();273
}, 250).unref?.();274
}275
return { ok: true, installed: result.installed === true, restarting: result.restartApplication === true };276
});277
handle("updates:reveal", async () => {278
const filePath = updateService.getVerifiedDownloadPath();279
if (!filePath) throw new Error("No verified update has been downloaded.");280
shell.showItemInFolder(filePath);281
return { ok: true };282
});283
}285
module.exports = { registerCoreIpc };SHA-256
de8c503c6dd20a3874a8c2bf6df1bdce2ab03927c2b48268e0d70f7898de54a4CH-J Proprietary Software License 1.14 · Tá Foinse ar fáil faoin CH-J Proprietary Software License 1.14. Ní chuireann a infhaighteacht a athrú ar na téarmaí ceadúnais nó cearta breise a dheonú.
Cartlann SHA-256
a9605c0870a223b6a8542bb0705edd51d8b04f3069582653417a8397fabcc09a