CH-J Server Managerserver management over SSH
Menu
Published source

CH-J Server Manager

Browse directories and files for a specific application release.

Download source ZIP
CH-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.

5,9 KB · 153 linesDownload file
1"use strict";
3const { compareVersions, parseVersion } = require("./version");
5const SHA512_PATTERN = /^[a-f0-9]{128}$/i;
6const BUILD_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{7,127}$/;
7const CHANNEL_TO_SERVER = Object.freeze({
8 alpha: "alpha",
9 beta: "beta",
10 stable: "stable"
11});
12const SUPPORTED_ARCHITECTURES = Object.freeze({
13 win: Object.freeze(["x64", "x86", "arm64", "universal"]),
14 mac: Object.freeze(["arm64"]),
15 ubuntu: Object.freeze(["x64", "arm64", "universal"])
16});
18function requireString(value, field) {
19 const normalized = String(value || "").trim();
20 if (!normalized) throw new Error(`Missing ${field}.`);
21 return normalized;
24function normalizeArchitecture(value) {
25 const arch = String(value || "").trim().toLowerCase();
26 if (arch === "ia32" || arch === "x86") return "x86";
27 if (arch === "x64" || arch === "amd64") return "x64";
28 if (arch === "arm64" || arch === "aarch64") return "arm64";
29 return arch;
32function normalizePlatform(value) {
33 const platform = String(value || "").trim().toLowerCase();
34 if (platform === "win32" || platform === "windows") return "win";
35 if (platform === "darwin" || platform === "macos") return "mac";
36 if (platform === "linux") return "ubuntu";
37 return platform;
40function serverChannel(channel) {
41 const normalized = String(channel || "").trim().toLowerCase();
42 const mapped = CHANNEL_TO_SERVER[normalized];
43 if (!mapped) throw new Error(`Unsupported update channel: ${normalized || "<empty>"}`);
44 return mapped;
47function assertSupportedUpdateTarget(platform, arch) {
48 const normalizedPlatform = normalizePlatform(platform);
49 const normalizedArch = normalizeArchitecture(arch);
50 const allowed = SUPPORTED_ARCHITECTURES[normalizedPlatform];
51 if (!allowed) throw new Error(`Unsupported update platform: ${normalizedPlatform || "<empty>"}`);
52 if (!allowed.includes(normalizedArch)) {
53 throw new Error(`Unsupported update architecture for ${normalizedPlatform}: ${normalizedArch || "<empty>"}`);
54 }
55 return { platform: normalizedPlatform, arch: normalizedArch };
58function validateReleaseResponse(payload, expected) {
59 if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
60 throw new Error("Update response must be an object.");
61 }
62 if (!payload.release) {
63 return {
64 updateAvailable: false,
65 currentVersion: expected.currentVersion,
66 release: null
67 };
68 }
70 const source = payload.release;
71 const version = requireString(source.version, "release.version");
72 parseVersion(version);
73 const platform = normalizePlatform(requireString(source.platform, "release.platform"));
74 const arch = normalizeArchitecture(requireString(source.arch || "universal", "release.arch"));
75 const channel = requireString(source.channel || "stable", "release.channel").toLowerCase();
76 const expectedPlatform = normalizePlatform(expected.platform);
77 const expectedArch = normalizeArchitecture(expected.arch);
78 const expectedChannel = serverChannel(expected.channel);
80 assertSupportedUpdateTarget(expectedPlatform, expectedArch);
81 assertSupportedUpdateTarget(platform, arch);
83 if (source.type && source.type !== "app") throw new Error("Release is not an application update.");
84 if (platform !== expectedPlatform) throw new Error(`Unexpected release platform: ${platform}`);
85 if (arch !== "universal" && arch !== expectedArch) throw new Error(`Unexpected release architecture: ${arch}`);
86 if (channel !== expectedChannel) throw new Error(`Unexpected release channel: ${channel}`);
88 const sha512 = requireString(source.sha512, "release.sha512").toLowerCase();
89 if (!SHA512_PATTERN.test(sha512)) throw new Error("Invalid release SHA-512.");
90 const size = Number(source.size);
91 if (!Number.isSafeInteger(size) || size <= 0) throw new Error("Invalid release size.");
92 const buildId = source.build_id == null || source.build_id === ""
93 ? null
94 : requireString(source.build_id, "release.build_id");
95 if (buildId && !BUILD_ID_PATTERN.test(buildId)) throw new Error("Invalid release build ID.");
97 const updateAvailable = compareVersions(version, expected.currentVersion) > 0;
98 return {
99 updateAvailable,
100 currentVersion: expected.currentVersion,
101 release: {
102 id: requireString(source.id || `${platform}-${arch}-${version}`, "release.id"),
103 buildId,
104 name: requireString(source.name || "CH-J Server Manager", "release.name"),
105 version,
106 platform,
107 arch,
108 channel,
109 mandatory: Boolean(source.mandatory),
110 notes: String(source.notes || ""),
111 sha512,
112 size,
113 filename: requireString(source.filename, "release.filename"),
114 downloadUrl: requireString(source.download_url || source.download_path, "release.download_url"),
115 signatureUrl: requireString(source.signature_url || source.signature_path, "release.signature_url"),
116 publishedAt: source.published_at ? String(source.published_at) : null
117 }
118 };
121function validateReleaseListResponse(payload, expected) {
122 if (!payload || typeof payload !== "object" || Array.isArray(payload) || !Array.isArray(payload.releases)) {
123 throw new Error("Update catalog response must contain a releases array.");
124 }
125 const ids = new Set();
126 const releases = payload.releases.map((source) => {
127 const release = validateReleaseResponse({ release: source }, expected).release;
128 if (!release?.publishedAt || Number.isNaN(Date.parse(release.publishedAt))) {
129 throw new Error("Invalid release publication date.");
130 }
131 if (ids.has(release.id)) throw new Error(`Duplicate release id: ${release.id}`);
132 ids.add(release.id);
133 return release;
134 });
135 releases.sort((a, b) => compareVersions(b.version, a.version) || Date.parse(b.publishedAt) - Date.parse(a.publishedAt));
136 return {
137 currentVersion: expected.currentVersion,
138 releases
139 };
142module.exports = {
143 BUILD_ID_PATTERN,
144 CHANNEL_TO_SERVER,
145 SHA512_PATTERN,
146 SUPPORTED_ARCHITECTURES,
147 assertSupportedUpdateTarget,
148 normalizeArchitecture,
149 normalizePlatform,
150 serverChannel,
151 validateReleaseListResponse,
152 validateReleaseResponse
153};

SHA-256: 3f65aed2fd8c462ae733642a0db0d0d2e07e77b22b9e0f01ee574a347c8d88a4

Archive SHA-256: 5ac91caf4fa32a6fdb114f2430deed486fbe7489d5eea343d1f034169fafb5e0