Published source
Download source ZIP
CH-J Server Manager
Browse directories and files for a specific application release.
Source is provided under the CH-J Proprietary Software License 1.14. Its availability does not change the license terms or grant additional rights.
1
"use strict";3
const path = require("node:path");4
const { LICENSE_NAME, LICENSE_VERSION } = require("../legal/licenseAcceptance");6
const PRIVATE_KEY_DIALOG_COPY = Object.freeze({7
cs: { title: "Vybrat soukromý SSH klíč", keys: "Soukromé SSH klíče", all: "Všechny soubory" },8
de: { title: "Privaten SSH-Schlüssel auswählen", keys: "Private SSH-Schlüssel", all: "Alle Dateien" },9
en: { title: "Select an SSH private key", keys: "SSH private keys", all: "All files" }10
});12
function registerCoreIpc(options) {13
const {14
ipcMain, shell, dialog, clipboard, app, configStore, pluginRegistry, pluginService, pluginRuntime, profileService,15
sessionManager, vaultStore, biometricService, vaultLockController, updateService, updateInstallerLauncher, diagnosticsService, getMainWindow, logger16
} = options;18
function assertTrustedSender(event) {19
const mainWindow = getMainWindow();20
if (!mainWindow || mainWindow.isDestroyed() || event.sender.id !== mainWindow.webContents.id ||21
(mainWindow.webContents.mainFrame && event.senderFrame !== mainWindow.webContents.mainFrame)) {22
const error = new Error("Untrusted IPC sender.");23
error.code = "UNTRUSTED_IPC_SENDER";24
throw error;25
}26
}28
function handle(channel, handler) {29
ipcMain.handle(channel, async (event, payload) => {30
assertTrustedSender(event);31
try {32
return await handler(payload);33
} catch (error) {34
logger?.error("IPC operation failed.", { channel, message: error?.message || String(error), code: error?.code });35
throw error;36
}37
});38
}40
function send(channel, handler) {41
ipcMain.on(channel, (event, payload) => {42
try {43
assertTrustedSender(event);44
handler(payload);45
} catch (error) {46
logger?.warn("IPC event was rejected.", { channel, message: error?.message || String(error), code: error?.code });47
}48
});49
}51
function sessionResult(action) {52
return async (payload) => {53
try {54
return { ok: true, value: await action(payload || {}) };55
} catch (error) {56
return {57
ok: false,58
error: typeof error?.toJSON === "function"59
? error.toJSON()60
: { code: error?.code || "SSH_OPERATION_FAILED", message: error?.message || String(error) }61
};62
}63
};64
}66
handle("core:getInfo", async () => ({67
name: app.getName(),68
version: app.getVersion(),69
platform: process.platform,70
arch: process.arch,71
coreApi: "1.0.0",72
pluginApi: options.pluginApiVersion || require("../plugins/pluginRegistry").PLUGIN_API_VERSION,73
dataSchema: 1,74
license: `${LICENSE_NAME} ${LICENSE_VERSION}`,75
copyright: "Copyright 2026 Josef Chudy"76
}));77
handle("legal:open", async (payload = {}) => {78
const documents = {79
documentation: "README.md",80
safetyCzech: "provozni-bezpecnost-a-zalohovani.md",81
safetyEnglish: "operational-safety-and-backup-guidance.md",82
safetyGerman: "betriebssicherheit-und-datensicherung.md"83
};84
const documentKey = String(payload.document || "");85
if (Object.hasOwn(documents, documentKey)) {86
const docsRoot = path.join(app.isPackaged ? process.resourcesPath : app.getAppPath(), "docs");87
const error = await shell.openPath(path.join(docsRoot, documents[documentKey]));88
if (error) throw new Error(`The documentation could not be opened: ${error}`);89
return { ok: true };90
}91
const names = {92
application: "APPLICATION_LICENSE.txt",93
notice: "NOTICE.txt",94
thirdParty: "THIRD_PARTY_NOTICES.txt",95
thirdPartyBundle: "THIRD_PARTY_LICENSES.zip",96
electron: "ELECTRON_LICENSE.txt",97
chromium: "CHROMIUM_LICENSES.html"98
};99
const filename = Object.hasOwn(names, documentKey) ? names[documentKey] : null;100
if (!filename) throw new Error("Unknown legal document.");101
const root = app.isPackaged ? path.join(process.resourcesPath, "licenses") : app.getAppPath();102
const developmentFiles = {103
APPLICATION_LICENSE: "LICENSE",104
NOTICE: "NOTICE",105
THIRD_PARTY_NOTICES: "THIRD_PARTY_NOTICES.txt",106
THIRD_PARTY_LICENSES: "THIRD_PARTY_LICENSES.zip",107
ELECTRON_LICENSE: path.join("node_modules", "electron", "dist", "LICENSE"),108
CHROMIUM_LICENSES: path.join("node_modules", "electron", "dist", "LICENSES.chromium.html")109
};110
const stem = path.parse(filename).name;111
const filePath = app.isPackaged ? path.join(root, filename) : path.join(root, developmentFiles[stem]);112
const error = await shell.openPath(filePath);113
if (error) throw new Error(`The legal document could not be opened: ${error}`);114
return { ok: true };115
});116
handle("config:get", async () => configStore.get());117
handle("config:update", async (payload = {}) => {118
if (payload.security) {119
await vaultStore.verifyPassword(payload.masterPassword);120
if (payload.security.requireSystemAuthentication && !(await biometricService.getStatus()).available) {121
throw new Error("BIOMETRIC_UNAVAILABLE");122
}123
}124
const config = configStore.update(payload);125
pluginRuntime.notifyLanguageChanged(config.ui.language);126
return config;127
});128
handle("plugins:list", async () => pluginRegistry.listInstalled());129
handle("plugins:getState", async () => pluginService.getState());130
handle("plugins:checkCatalog", async () => pluginService.checkCatalog());131
handle("plugins:install", async (payload = {}) => { await biometricService?.requireSensitiveAction(); return pluginService.install(payload.releaseId); });132
handle("plugins:uninstall", async (payload = {}) => { await biometricService?.requireSensitiveAction(); return pluginService.uninstall(payload.pluginId); });133
handle("plugins:open", async (payload = {}) => pluginService.open(payload.pluginId));134
handle("vault:status", async () => vaultStore.status());135
handle("vault:create", async (payload = {}) => { const status = await vaultStore.create(payload.password); vaultLockController?.activity(); return status; });136
handle("vault:unlock", async (payload = {}) => { biometricService?.cancel(); const status = await vaultStore.unlock(payload.password); vaultLockController?.activity(); return status; });137
handle("biometrics:status", sessionResult(() => biometricService.getStatus()));138
handle("biometrics:enable", sessionResult(() => biometricService.enable()));139
handle("biometrics:disable", sessionResult(() => biometricService.disable()));140
handle("biometrics:unlock", sessionResult(async () => {141
const status = await biometricService.unlock(); vaultLockController?.activity(); return status;142
}));143
handle("biometrics:authenticate", sessionResult(() => biometricService.authenticateSensitiveAction()));144
handle("vault:lock", async () => {145
if (vaultLockController) return vaultLockController.lock();146
pluginRuntime.closeAll();147
await sessionManager.disconnectAll("vault-lock");148
return vaultStore.lock();149
});150
handle("vault:reset", async (payload = {}) => {151
if (payload.confirmation !== "SMAZAT") throw Object.assign(new Error("Vault reset confirmation is invalid."), { code: "VAULT_RESET_CONFIRMATION_REQUIRED" });152
if (vaultStore.status().unlocked) await biometricService?.requireSensitiveAction();153
await biometricService?.disable({ reset: true });154
pluginRuntime.closeAll();155
await sessionManager.disconnectAll("vault-reset");156
const status = vaultStore.reset(payload.confirmation);157
logger?.warn("Encrypted user vault was reset by the user.");158
return status;159
});160
handle("profiles:list", async () => profileService.list());161
handle("profiles:save", async (payload = {}) => profileService.save(payload));162
handle("profiles:delete", async (payload = {}) => { await biometricService?.requireSensitiveAction(); return profileService.delete(String(payload.id || "")); });163
handle("profiles:selectPrivateKey", async () => {164
const mainWindow = getMainWindow();165
const copy = PRIVATE_KEY_DIALOG_COPY[configStore.get().ui.language] || PRIVATE_KEY_DIALOG_COPY.cs;166
const result = await dialog.showOpenDialog(mainWindow, {167
title: copy.title,168
properties: ["openFile"],169
filters: [{ name: copy.keys, extensions: ["pem", "key", "ppk"] }, { name: copy.all, extensions: ["*"] }]170
});171
return { canceled: result.canceled, path: result.filePaths[0] || null };172
});173
handle("ssh:list", async () => sessionManager.list());174
const diagnosticsResult = (action) => async (payload = {}) => {175
try { return { ok: true, value: await action(payload) }; }176
catch (error) { return { ok: false, error: { code: error.code || "DIAGNOSTICS_ERROR", message: String(error.message || error).slice(0, 400) } }; }177
};178
handle("diagnostics:resolve", diagnosticsResult((payload) => diagnosticsService.resolve(payload)));179
handle("diagnostics:start", diagnosticsResult((payload) => diagnosticsService.start(payload)));180
handle("diagnostics:cancel", diagnosticsResult((payload) => diagnosticsService.cancel(payload.id, payload.tool)));181
handle("diagnostics:get", diagnosticsResult((payload) => diagnosticsService.get(payload.id)));182
handle("diagnostics:history", diagnosticsResult(() => diagnosticsService.history.list()));183
handle("diagnostics:removeHistory", diagnosticsResult((payload) => diagnosticsService.history.remove(payload.id)));184
handle("diagnostics:copy", diagnosticsResult((payload) => {185
clipboard.writeText(diagnosticsService.export(payload.id, payload.format || "txt"));186
return { copied: true };187
}));188
handle("diagnostics:export", diagnosticsResult(async (payload) => {189
const content = diagnosticsService.export(payload.id, payload.format);190
const result = await dialog.showSaveDialog(getMainWindow(), { defaultPath: `diagnostics.${payload.format}`, filters: [{ name: payload.format.toUpperCase(), extensions: [payload.format] }] });191
if (result.canceled || !result.filePath) return { canceled: true };192
await require("node:fs").promises.writeFile(result.filePath, content, { mode: 0o600 });193
return { canceled: false };194
}));195
handle("diagnostics:import", diagnosticsResult(async () => {196
const result = await dialog.showOpenDialog(getMainWindow(), { properties: ["openFile"], filters: [{ name: "Diagnostics JSON", extensions: ["json"] }] });197
if (result.canceled || !result.filePaths[0]) return { canceled: true };198
const fs = require("node:fs"); const stat = await fs.promises.lstat(result.filePaths[0]);199
if (!stat.isFile() || stat.size > 2 * 1024 * 1024) throw Object.assign(new Error("IMPORT_LIMIT"), { code: "IMPORT_LIMIT" });200
const id = diagnosticsService.history.import(await fs.promises.readFile(result.filePaths[0], "utf8"));201
return { id };202
}));203
handle("ssh:connect", sessionResult((payload) => sessionManager.connect(payload)));204
handle("ssh:trustHostKey", sessionResult((payload) => sessionManager.trustPending(payload)));205
handle("ssh:disconnect", sessionResult((payload) => sessionManager.disconnect(payload.sessionId, "user")));206
handle("ssh:resize", sessionResult((payload) => sessionManager.resize(payload.sessionId, payload.cols, payload.rows)));207
send("ssh:input", (payload = {}) => sessionManager.write(payload.sessionId, payload.data));208
handle("updates:getState", async () => updateService.getState());209
handle("updates:check", async () => updateService.check());210
handle("updates:select", async (payload = {}) => updateService.select(payload.id));211
handle("updates:download", async () => updateService.download());212
handle("updates:install", async () => {213
await biometricService?.requireSensitiveAction();214
// The renderer supplies neither a path nor a verification flag. The main215
// process re-hashes and re-verifies its internally tracked artifact here.216
const filePath = await updateService.prepareInstallerLaunch();217
const result = await updateInstallerLauncher.install(filePath);218
updateService.markInstallerLaunched();219
if (result.restartApplication) {220
setTimeout(() => {221
app.relaunch();222
app.quit();223
}, 250).unref?.();224
}225
return { ok: true, installed: result.installed === true, restarting: result.restartApplication === true };226
});227
handle("updates:reveal", async () => {228
const filePath = updateService.getVerifiedDownloadPath();229
if (!filePath) throw new Error("No verified update has been downloaded.");230
shell.showItemInFolder(filePath);231
return { ok: true };232
});233
}235
module.exports = { registerCoreIpc };SHA-256: 721d3f635c587e475ee10e11593f6c6973dd56d7ef8709c4ff2c886233bccbfc
Archive SHA-256: 5ac91caf4fa32a6fdb114f2430deed486fbe7489d5eea343d1f034169fafb5e0