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 fs = require("node:fs");4
const path = require("node:path");5
const { atomicWriteJson } = require("../storage/atomicFile");7
const SUPPORTED_LANGUAGES = Object.freeze(["cs", "de", "en"]);8
const CATALOG_CHANNELS = Object.freeze(["alpha", "beta", "stable", "all"]);10
const DEFAULT_CONFIG = Object.freeze({11
schemaVersion: 1,12
ui: {13
language: "cs",14
theme: "system"15
},16
updates: {17
channel: "alpha",18
autoCheck: true,19
lastLaunchedRelease: null,20
baseUrls: [21
"https://www.sm.ch-j.de/",22
"https://sm.ch-j.de/"23
]24
},25
plugins: {26
channel: "alpha"27
},28
security: { autoLockMinutes: 0, lockOnBlur: false, requireSystemAuthentication: false }29
});31
function clone(value) {32
return JSON.parse(JSON.stringify(value));33
}35
function normalizeReleaseMarker(value) {36
if (!value || typeof value !== "object" || Array.isArray(value)) return null;37
const id = String(value.id || "").trim();38
const version = String(value.version || "").trim();39
const publishedAt = String(value.publishedAt || "").trim();40
const sha512 = String(value.sha512 || "").trim().toLowerCase();41
if (!id || !version || Number.isNaN(Date.parse(publishedAt)) || !/^[a-f0-9]{128}$/.test(sha512)) return null;42
return { id, version, publishedAt, sha512 };43
}45
function normalizeConfig(input = {}) {46
const source = input && typeof input === "object" && !Array.isArray(input) ? input : {};47
const channel = CATALOG_CHANNELS.includes(source.updates?.channel)48
? source.updates.channel49
: DEFAULT_CONFIG.updates.channel;50
const pluginChannel = CATALOG_CHANNELS.includes(source.plugins?.channel)51
? source.plugins.channel52
: DEFAULT_CONFIG.plugins.channel;53
const theme = ["system", "dark", "light"].includes(source.ui?.theme)54
? source.ui.theme55
: DEFAULT_CONFIG.ui.theme;56
const language = SUPPORTED_LANGUAGES.includes(String(source.ui?.language || ""))57
? String(source.ui.language)58
: DEFAULT_CONFIG.ui.language;60
return {61
schemaVersion: 1,62
ui: { language, theme },63
updates: {64
channel,65
autoCheck: source.updates?.autoCheck !== false,66
lastLaunchedRelease: normalizeReleaseMarker(source.updates?.lastLaunchedRelease),67
baseUrls: clone(DEFAULT_CONFIG.updates.baseUrls)68
},69
plugins: { channel: pluginChannel },70
security: {71
autoLockMinutes: [0, 1, 5, 10, 15, 30, 60].includes(source.security?.autoLockMinutes) ? source.security.autoLockMinutes : 0,72
lockOnBlur: source.security?.lockOnBlur === true,73
requireSystemAuthentication: source.security?.requireSystemAuthentication === true74
}75
};76
}78
class ConfigStore {79
constructor(rootDir) {80
this.rootDir = rootDir;81
this.filePath = path.join(rootDir, "config", "config.json");82
this.value = clone(DEFAULT_CONFIG);83
}85
load() {86
try {87
const raw = fs.readFileSync(this.filePath, "utf8");88
this.value = normalizeConfig(JSON.parse(raw));89
} catch (error) {90
if (error?.code !== "ENOENT") {91
const backup = `${this.filePath}.invalid-${Date.now()}`;92
try { fs.renameSync(this.filePath, backup); } catch {}93
}94
this.value = clone(DEFAULT_CONFIG);95
this.save();96
}97
return this.get();98
}100
get() {101
return clone(this.value);102
}104
update(patch = {}) {105
const updatePatch = patch.updates && typeof patch.updates === "object" ? patch.updates : {};106
const next = {107
...this.value,108
ui: { ...this.value.ui, ...(patch.ui || {}) },109
updates: {110
...this.value.updates,111
channel: updatePatch.channel ?? this.value.updates.channel,112
autoCheck: updatePatch.autoCheck ?? this.value.updates.autoCheck113
},114
plugins: { ...this.value.plugins, ...(patch.plugins || {}) },115
security: { ...this.value.security, ...(patch.security || {}) }116
};117
this.value = normalizeConfig(next);118
this.save();119
return this.get();120
}122
recordLaunchedRelease(release) {123
const marker = normalizeReleaseMarker(release);124
if (!marker) throw new Error("Invalid launched update release metadata.");125
this.value = normalizeConfig({126
...this.value,127
updates: { ...this.value.updates, lastLaunchedRelease: marker }128
});129
this.save();130
return clone(marker);131
}133
save() {134
atomicWriteJson(this.filePath, this.value);135
}136
}138
module.exports = {139
ConfigStore,140
CATALOG_CHANNELS,141
DEFAULT_CONFIG,142
SUPPORTED_LANGUAGES,143
normalizeReleaseMarker,144
normalizeConfig145
};SHA-256: ef3ed13e02e6f67b19cdd3c7d417dcb17d779acad4ded7aec1b8d312349edc1e
Archive SHA-256: 5ac91caf4fa32a6fdb114f2430deed486fbe7489d5eea343d1f034169fafb5e0