Published source
Download source ZIP
CH-J Server Manager
Browse directories and files for a specific application release.
CH-J / Server Manager
339 files
core-20261014T184258Z-3fbed0b93fdf
Added: Date not recorded · Built:
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); 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:authenticate", sessionResult(() => biometricService.authenticateSensitiveAction()));147
handle("vault:lock", async () => {148
if (vaultLockController) return vaultLockController.lock();149
pluginRuntime.closeAll();150
await sessionManager.disconnectAll("vault-lock");151
return vaultStore.lock();152
});153
handle("vault:reset", async (payload = {}) => {154
if (payload.confirmation !== "SMAZAT") throw Object.assign(new Error("Vault reset confirmation is invalid."), { code: "VAULT_RESET_CONFIRMATION_REQUIRED" });155
if (vaultStore.status().unlocked) await biometricService?.requireSensitiveAction();156
await biometricService?.disable({ reset: true });157
pluginRuntime.closeAll();158
await sessionManager.disconnectAll("vault-reset");159
const status = vaultStore.reset(payload.confirmation);160
logger?.warn("Encrypted user vault was reset by the user.");161
return status;162
});163
handle("profiles:list", async () => profileService.list());164
handle("profiles:export", async (payload = {}) => {165
const format = payload.format || "json";166
const profiles = profileService.list();167
const selected = payload.ids === undefined ? profiles : profiles.filter((profile) => Array.isArray(payload.ids) && payload.ids.includes(profile.id));168
if (!selected.length) throw new Error("Select at least one server to export.");169
const content = serializeProfiles(selected, format);170
const result = await dialog.showSaveDialog(getMainWindow(), {171
defaultPath: `chj-server-profiles.${format}`,172
filters: [{ name: format.toUpperCase(), extensions: [format] }]173
});174
if (result.canceled || !result.filePath) return { canceled: true };175
await fs.promises.writeFile(result.filePath, content, { encoding: "utf8", mode: 0o600 });176
return { canceled: false, count: selected.length };177
});178
handle("profiles:previewImport", async () => {179
profileService.list(); // Require an unlocked vault before opening the picker.180
profileTransfer.cancel();181
const result = await dialog.showOpenDialog(getMainWindow(), {182
properties: ["openFile"], filters: [{ name: "JSON / CSV", extensions: ["json", "csv"] }]183
});184
if (result.canceled || !result.filePaths[0]) return { canceled: true };185
const file = await fs.promises.open(result.filePaths[0], "r");186
try {187
const buffer = Buffer.alloc(MAX_BYTES + 1);188
let length = 0;189
while (length < buffer.length) {190
const read = await file.read(buffer, length, buffer.length - length, null);191
if (!read.bytesRead) break;192
length += read.bytesRead;193
}194
if (length > MAX_BYTES) throw new Error("Profile file exceeds the 5 MiB limit.");195
const content = new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, length));196
return { canceled: false, ...profileTransfer.preview(content) };197
} finally { await file.close(); }198
});199
handle("profiles:commitImport", async (payload = {}) => {200
await biometricService?.requireSensitiveAction();201
return profileTransfer.commit(payload.token, payload.policy);202
});203
handle("profiles:cancelImport", async () => { profileTransfer.cancel(); return { canceled: true }; });204
handle("profiles:save", async (payload = {}) => profileService.save(payload));205
handle("profiles:delete", async (payload = {}) => { await biometricService?.requireSensitiveAction(); return profileService.delete(String(payload.id || "")); });206
handle("profiles:selectPrivateKey", async () => {207
const mainWindow = getMainWindow();208
const copy = PRIVATE_KEY_DIALOG_COPY[configStore.get().ui.language] || PRIVATE_KEY_DIALOG_COPY.cs;209
const result = await dialog.showOpenDialog(mainWindow, {210
title: copy.title,211
properties: ["openFile"],212
filters: [{ name: copy.keys, extensions: ["pem", "key", "ppk"] }, { name: copy.all, extensions: ["*"] }]213
});214
return { canceled: result.canceled, path: result.filePaths[0] || null };215
});216
handle("ssh:list", async () => sessionManager.list());217
const diagnosticsResult = (action) => async (payload = {}) => {218
try { return { ok: true, value: await action(payload) }; }219
catch (error) { return { ok: false, error: { code: error.code || "DIAGNOSTICS_ERROR", message: String(error.message || error).slice(0, 400) } }; }220
};221
handle("diagnostics:resolve", diagnosticsResult((payload) => diagnosticsService.resolve(payload)));222
handle("diagnostics:start", diagnosticsResult((payload) => diagnosticsService.start(payload)));223
handle("diagnostics:cancel", diagnosticsResult((payload) => diagnosticsService.cancel(payload.id, payload.tool)));224
handle("diagnostics:get", diagnosticsResult((payload) => diagnosticsService.get(payload.id)));225
handle("diagnostics:history", diagnosticsResult(() => diagnosticsService.history.list()));226
handle("diagnostics:removeHistory", diagnosticsResult((payload) => diagnosticsService.history.remove(payload.id)));227
handle("diagnostics:copy", diagnosticsResult((payload) => {228
clipboard.writeText(diagnosticsService.export(payload.id, payload.format || "txt"));229
return { copied: true };230
}));231
handle("diagnostics:export", diagnosticsResult(async (payload) => {232
const content = diagnosticsService.export(payload.id, payload.format);233
const result = await dialog.showSaveDialog(getMainWindow(), { defaultPath: `diagnostics.${payload.format}`, filters: [{ name: payload.format.toUpperCase(), extensions: [payload.format] }] });234
if (result.canceled || !result.filePath) return { canceled: true };235
await require("node:fs").promises.writeFile(result.filePath, content, { mode: 0o600 });236
return { canceled: false };237
}));238
handle("diagnostics:import", diagnosticsResult(async () => {239
const result = await dialog.showOpenDialog(getMainWindow(), { properties: ["openFile"], filters: [{ name: "Diagnostics JSON", extensions: ["json"] }] });240
if (result.canceled || !result.filePaths[0]) return { canceled: true };241
const fs = require("node:fs"); const stat = await fs.promises.lstat(result.filePaths[0]);242
if (!stat.isFile() || stat.size > 2 * 1024 * 1024) throw Object.assign(new Error("IMPORT_LIMIT"), { code: "IMPORT_LIMIT" });243
const id = diagnosticsService.history.import(await fs.promises.readFile(result.filePaths[0], "utf8"));244
return { id };245
}));246
handle("ssh:connect", sessionResult((payload) => sessionManager.connect(payload)));247
handle("ssh:trustHostKey", sessionResult((payload) => sessionManager.trustPending(payload)));248
handle("ssh:disconnect", sessionResult((payload) => sessionManager.disconnect(payload.sessionId, "user")));249
handle("ssh:resize", sessionResult((payload) => sessionManager.resize(payload.sessionId, payload.cols, payload.rows)));250
send("ssh:input", (payload = {}) => sessionManager.write(payload.sessionId, payload.data));251
handle("updates:getState", async () => updateService.getState());252
handle("updates:check", async () => updateService.check());253
handle("updates:select", async (payload = {}) => updateService.select(payload.id));254
handle("updates:download", async () => updateService.download());255
handle("updates:install", async () => {256
await biometricService?.requireSensitiveAction();257
// The renderer supplies neither a path nor a verification flag. The main258
// process re-hashes and re-verifies its internally tracked artifact here.259
const filePath = await updateService.prepareInstallerLaunch();260
const result = await updateInstallerLauncher.install(filePath);261
updateService.markInstallerLaunched();262
if (result.restartApplication) {263
setTimeout(() => {264
app.relaunch();265
app.quit();266
}, 250).unref?.();267
}268
return { ok: true, installed: result.installed === true, restarting: result.restartApplication === true };269
});270
handle("updates:reveal", async () => {271
const filePath = updateService.getVerifiedDownloadPath();272
if (!filePath) throw new Error("No verified update has been downloaded.");273
shell.showItemInFolder(filePath);274
return { ok: true };275
});276
}278
module.exports = { registerCoreIpc };SHA-256
d6d0ef10312fdde72e527e867815622b5e9b46f3b79d1316a7aa380e23ee6238CH-J Proprietary Software License 1.14 · Source is provided under the CH-J Proprietary Software License 1.14. Its availability does not change the license terms or grant additional rights.
Archive SHA-256
3df08cf5e899262a6ce92d7ec2caf5c42b683f1f2b57055662b845ff7dde67a8