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 { app, BrowserWindow, clipboard, dialog, ipcMain, protocol, shell } = require("electron");5
const { createMainWindow, hardenSession } = require("./bootstrap/createMainWindow");6
const { ConfigStore } = require("./config/configStore");7
const { registerCoreIpc } = require("./ipc/registerCoreIpc");8
const { Logger } = require("./logging/logger");9
const { LogService } = require("./logging/logService");10
const { RemoteFileService } = require("./files/remoteFileService");11
const { LocalHashService } = require("./hashing/localHashService");12
const { DiagnosticsService } = require("./diagnostics/diagnosticsService");13
const { PLUGIN_API_VERSION, PluginRegistry } = require("./plugins/pluginRegistry");14
const { PluginCatalogProvider } = require("./plugins/pluginCatalogProvider");15
const { PluginInstaller } = require("./plugins/pluginInstaller");16
const { PluginRuntime } = require("./plugins/pluginRuntime");17
const { PluginService } = require("./plugins/pluginService");18
const { KeyGeneratorService } = require("./keys/keyGeneratorService");19
const { ProfileService } = require("./profiles/profileService");20
const { createTestHttpsFetch } = require("./security/testHttpsFetch");21
const { VaultStore } = require("./security/vaultStore");22
const { BiometricService } = require("./security/biometrics/biometricService");23
const { PlatformAdapter } = require("./security/biometrics/platformAdapter");24
const { VaultLockController } = require("./security/vaultLockController");25
const { SessionManager } = require("./sessions/sessionManager");26
const { HashUrlProvider } = require("./updates/hashUrlProvider");27
const { OpenPgpVerifier } = require("./updates/openPgpVerifier");28
const { UpdateInstallerLauncher } = require("./updates/updateInstallerLauncher");29
const { UpdateService } = require("./updates/updateService");30
const buildInfo = require("../shared/buildInfo.json");31
const { LicenseAcceptance } = require("./legal/licenseAcceptance");32
const { showLicenseWindow } = require("./legal/showLicenseWindow");34
app.setName("CH-J Server Manager");35
const buildSmokeUserData = process.env.CHJ_BUILD_SMOKE_USER_DATA_DIR;36
if (buildSmokeUserData && path.isAbsolute(buildSmokeUserData)) app.setPath("userData", buildSmokeUserData);37
if (process.platform === "linux") {38
app.setDesktopName("ch-j-server-manager.desktop");39
app.commandLine.appendSwitch("class", "ch-j-server-manager");40
}41
if (process.platform === "win32") app.setAppUserModelId("de.ch-j.servermanager");42
protocol.registerSchemesAsPrivileged([{ scheme: "chj-plugin", privileges: { standard: true, secure: true, supportFetchAPI: true } }]);44
let mainWindow = null;45
let logger = null;46
let updateService = null;47
let sessionManager = null;48
let vaultStore = null;49
let diagnosticsService = null;50
let biometricService = null;51
let vaultLockController = null;52
let licenseWindow = null;53
let coreReady = false;55
function watchWindow(window) {56
window.webContents.on("before-input-event", () => vaultLockController?.activity());57
window.webContents.on("before-mouse-event", () => vaultLockController?.activity());58
window.on("blur", () => {59
const checkFocus = () => {60
if (BrowserWindow.getFocusedWindow()) return;61
if (biometricService?.active) { setTimeout(checkFocus, 250).unref(); return; }62
void vaultLockController?.lostFocus().catch(error => logger?.warn("Vault focus lock failed.", { message: error.message }));63
};64
setTimeout(checkFocus, 250).unref();65
});66
window.on("closed", () => { if (window === mainWindow) diagnosticsService?.stopAll(); });67
}69
const hasSingleInstanceLock = buildSmokeUserData ? true : app.requestSingleInstanceLock();70
if (!hasSingleInstanceLock) app.quit();72
function focusMainWindow() {73
if (licenseWindow && !licenseWindow.isDestroyed()) { licenseWindow.show(); licenseWindow.focus(); return; }74
if (!mainWindow || mainWindow.isDestroyed()) return;75
if (mainWindow.isMinimized()) mainWindow.restore();76
mainWindow.show();77
mainWindow.focus();78
}80
async function bootstrap() {81
const storageRoot = app.getPath("userData");82
const appIcon = path.join(__dirname, "..", "..", "build", "icon.png");83
logger = new Logger(storageRoot);84
const logService = new LogService(logger.logPath);85
const configStore = new ConfigStore(storageRoot);86
configStore.load();87
hardenSession(logger);88
const acceptance = new LicenseAcceptance({89
storageRoot,90
licensePath: app.isPackaged ? path.join(process.resourcesPath, "licenses", "APPLICATION_LICENSE.txt") : path.join(app.getAppPath(), "LICENSE"),91
docsRoot: path.join(app.isPackaged ? process.resourcesPath : app.getAppPath(), "docs"),92
appVersion: app.getVersion(), buildId: buildInfo.buildId93
});94
if (!acceptance.isAccepted()) {95
const accepted = await showLicenseWindow({ BrowserWindow, ipcMain, acceptance, language: configStore.get().ui.language,96
onWindow: window => { licenseWindow = window; } });97
if (!accepted) { app.quit(); return; }98
}99
diagnosticsService = new DiagnosticsService({ storageRoot });100
const testUpdateFetch = createTestHttpsFetch({101
allowedBaseUrls: configStore.get().updates.baseUrls,102
// Jen starý host používá dočasnou alfa výjimku. Kanonický www host103
// musí vždy projít standardním ověřením veřejného certifikátu.104
insecureBaseUrls: ["https://sm.ch-j.de/"]105
});106
const pluginRegistry = new PluginRegistry(storageRoot, logger, { bundledRoot: path.join(__dirname, "firstPartyPlugins") });107
vaultStore = new VaultStore(storageRoot);108
const profileService = new ProfileService(vaultStore);109
sessionManager = new SessionManager({ profileService, logger });110
const keyGeneratorService = new KeyGeneratorService({111
selectSavePath: async (suggestedName) => {112
const result = await dialog.showSaveDialog(mainWindow, { title: "Uložit pár SSH klíčů", defaultPath: suggestedName });113
return { canceled: result.canceled, path: result.filePath || null };114
}115
});116
const remoteFileService = new RemoteFileService({117
sessionManager,118
selectUploadPath: async () => {119
const result = await dialog.showOpenDialog(mainWindow, { title: "Vybrat soubory nebo složky k nahrání", properties: ["openFile", "openDirectory", "multiSelections"] });120
return { canceled: result.canceled, paths: result.filePaths };121
},122
selectDownloadPath: async (suggestedName) => {123
const result = await dialog.showSaveDialog(mainWindow, { title: "Uložit stažený soubor", defaultPath: suggestedName });124
return { canceled: result.canceled, path: result.filePath || null };125
},126
selectDownloadDirectory: async () => {127
const result = await dialog.showOpenDialog(mainWindow, { title: "Vybrat složku pro stažení", properties: ["openDirectory", "createDirectory"] });128
return { canceled: result.canceled, path: result.filePaths[0] || null };129
},130
selectArchivePath: async (suggestedName, format) => {131
const extensions = format === "tar.gz" ? ["tar.gz", "tgz"] : [format];132
const result = await dialog.showSaveDialog(mainWindow, { title: "Uložit vzdálené položky jako archiv", defaultPath: suggestedName, filters: [{ name: format.toUpperCase(), extensions }] });133
return { canceled: result.canceled, path: result.filePath || null };134
},135
logger136
});137
const hashText = (key) => require("./firstPartyPlugins/chj.hash-checksum/0.0.1/ui/i18n").createI18n(configStore.get().ui.language).t(key);138
const localHashService = new LocalHashService({139
selectFilesDialog: async ({ multiple }) => {140
const result = await dialog.showOpenDialog(mainWindow, { title: hashText(multiple ? "Select local files to hash" : "Select a local file to hash"), properties: multiple ? ["openFile", "multiSelections"] : ["openFile"] });141
return { canceled: result.canceled, paths: result.filePaths };142
},143
selectDirectoryDialog: async () => {144
const result = await dialog.showOpenDialog(mainWindow, { title: hashText("Select a local directory"), properties: ["openDirectory"] });145
return { canceled: result.canceled, path: result.filePaths[0] || null };146
},147
selectManifestDialog: async () => {148
const result = await dialog.showOpenDialog(mainWindow, { title: hashText("Select a checksum manifest"), properties: ["openFile"], filters: [{ name: hashText("Checksum manifests"), extensions: ["sha224", "sha256", "sha384", "sha512", "sha3", "blake3", "md5", "sha1", "sfv", "checksums", "txt"] }] });149
return { canceled: result.canceled, path: result.filePaths[0] || null };150
},151
selectSaveDialog: async ({ suggestedName }) => {152
const result = await dialog.showSaveDialog(mainWindow, { title: hashText("Save checksum manifest"), defaultPath: suggestedName });153
return { canceled: result.canceled, path: result.filePath || null };154
},155
writeClipboard: (text) => clipboard.writeText(text),156
logger157
});158
const pluginInstaller = new PluginInstaller({ storageRoot, appVersion: app.getVersion(), pluginApiVersion: PLUGIN_API_VERSION, logger });159
const pluginRuntime = new PluginRuntime({160
BrowserWindow,161
registry: pluginRegistry,162
sessionManager,163
keyGeneratorService,164
logService,165
remoteFileService,166
localHashService,167
getLanguage: () => configStore.get().ui.language,168
getMainWindow: () => mainWindow,169
isVaultUnlocked: () => vaultStore.status().unlocked,170
preload: path.join(__dirname, "..", "preload", "pluginPreload.js"),171
icon: appIcon,172
onWindowState: (windows) => {173
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send("core:pluginWindowState", windows);174
},175
logger176
});177
protocol.handle("chj-plugin", (request) => pluginRuntime.handleRequest(request));178
pluginRuntime.registerIpc(ipcMain);179
const pluginService = new PluginService({180
appVersion: app.getVersion(),181
pluginApiVersion: PLUGIN_API_VERSION,182
configStore,183
registry: pluginRegistry,184
installer: pluginInstaller,185
runtime: pluginRuntime,186
logger,187
providerFactory: (updateConfig) => new PluginCatalogProvider({188
baseUrls: updateConfig.baseUrls,189
downloadRoot: path.join(storageRoot, "plugins", ".downloads"),190
fetchImpl: testUpdateFetch,191
logger192
})193
});194
const signingPublicKeyPath = app.isPackaged195
? path.join(process.resourcesPath, "signing", "ch-j-signing-public.asc")196
: path.join(app.getAppPath(), "ch-j-signing-public.asc");197
const signatureVerifier = new OpenPgpVerifier({198
publicKeyPath: signingPublicKeyPath,199
logger200
});201
updateService = new UpdateService({202
appVersion: app.getVersion(),203
buildId: buildInfo.buildId,204
platform: process.platform,205
arch: process.arch,206
configStore,207
logger,208
providerFactory: (updateConfig) => new HashUrlProvider({209
baseUrls: updateConfig.baseUrls,210
downloadRoot: path.join(storageRoot, "updates", "downloads"),211
fetchImpl: testUpdateFetch,212
signatureVerifier,213
logger214
})215
});216
const updateInstallerLauncher = new UpdateInstallerLauncher({217
platform: process.platform,218
shell,219
logger220
});222
for (const [cache, service] of [["Core update", updateService], ["plugin package", pluginService]]) {223
try {224
const result = service.cleanupDownloads();225
if (result.cleaned) logger.info(`${cache} download cache cleaned on startup.`, { root: result.root });226
} catch (error) {227
logger.warn(`${cache} download cache cleanup failed.`, { message: error?.message || String(error) });228
}229
}231
mainWindow = createMainWindow({ logger, icon: appIcon });232
biometricService = new BiometricService({ storageRoot, vaultStore, configStore, adapter: new PlatformAdapter({233
resourcesPath: process.resourcesPath, appPath: app.getAppPath(), isPackaged: app.isPackaged, getMainWindow: () => mainWindow234
}) });235
vaultLockController = new VaultLockController({ vaultStore, configStore, biometricService, sessionManager, pluginRuntime, diagnosticsService });236
app.on("browser-window-created", (_event, window) => watchWindow(window));237
watchWindow(mainWindow);238
vaultLockController.on("locked", payload => {239
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send("vault:locked", payload);240
});241
registerCoreIpc({242
ipcMain,243
clipboard,244
shell,245
dialog,246
app,247
configStore,248
pluginRegistry,249
pluginApiVersion: PLUGIN_API_VERSION,250
pluginService,251
pluginRuntime,252
profileService,253
sessionManager,254
vaultStore,255
biometricService,256
vaultLockController,257
updateService,258
updateInstallerLauncher,259
diagnosticsService,260
getMainWindow: () => mainWindow,261
logger262
});264
const sendToRenderer = (channel, payload) => {265
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send(channel, payload);266
};267
sessionManager.on("data", (payload) => sendToRenderer("ssh:data", payload));268
sessionManager.on("state", (payload) => sendToRenderer("ssh:state", payload));269
sessionManager.on("sessionError", (payload) => sendToRenderer("ssh:error", payload));270
diagnosticsService.on("progress", (payload) => sendToRenderer("diagnostics:progress", payload));272
mainWindow.webContents.once("did-finish-load", () => {273
const config = configStore.get();274
if (!config.updates.autoCheck) return;275
updateService.check()276
.then((state) => {277
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send("core:updateState", state);278
})279
.catch((error) => logger.warn("Automatic update check failed.", { message: error?.message || String(error) }));280
});282
logger.info("Core initialized.", {283
version: app.getVersion(),284
buildId: buildInfo.buildId,285
platform: process.platform,286
arch: process.arch,287
storageRoot288
});289
coreReady = true;290
if (buildSmokeUserData) {291
console.log("CHJ_BUILD_SMOKE_READY");292
setTimeout(() => app.quit(), 50);293
}294
}296
app.on("second-instance", focusMainWindow);297
app.whenReady().then(bootstrap).catch((error) => {298
logger?.error("Core bootstrap failed.", { message: error?.message || String(error), stack: error?.stack });299
dialog.showErrorBox("CH-J Server Manager", error?.message || String(error));300
app.quit();301
});303
app.on("activate", () => {304
if (!coreReady) { focusMainWindow(); return; }305
if (BrowserWindow.getAllWindows().length === 0) {306
mainWindow = createMainWindow({ logger, icon: path.join(__dirname, "..", "..", "build", "icon.png") });307
} else {308
focusMainWindow();309
}310
});312
app.on("window-all-closed", () => {313
if (coreReady && process.platform !== "darwin") app.quit();314
});316
app.on("before-quit", () => {317
vaultLockController?.dispose();318
diagnosticsService?.stopAll();319
void sessionManager?.disconnectAll("app-quit");320
vaultStore?.lock();321
});323
process.on("uncaughtException", (error) => {324
logger?.error("Uncaught exception.", { message: error?.message || String(error), stack: error?.stack });325
});326
process.on("unhandledRejection", (error) => {327
logger?.error("Unhandled rejection.", { message: error?.message || String(error), stack: error?.stack });328
});SHA-256: 370de12d8e1ddfb2453402c32fc8bbea8c5226529f7d382ded0a5bbd7a3ea56c
Archive SHA-256: 5ac91caf4fa32a6fdb114f2430deed486fbe7489d5eea343d1f034169fafb5e0