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 { compareVersions } = require("../../shared/version");5
class UpdateService {6
constructor(options) {7
this.appVersion = options.appVersion;8
this.buildId = options.buildId || null;9
this.platform = options.platform;10
this.arch = options.arch;11
this.configStore = options.configStore;12
this.providerFactory = options.providerFactory;13
this.logger = options.logger;14
this.lastCheck = null;15
this.releases = [];16
this.selectedReleaseId = null;17
this.lastVerifiedDownload = null;18
this.installerLaunchAuthorized = false;19
this.operation = null;20
}22
getState() {23
const release = this.lastCheck?.release || null;24
const releaseRelation = release ? this._relation(release) : null;25
return {26
status: this.operation || "idle",27
currentVersion: this.appVersion,28
currentBuildId: this.buildId,29
updateAvailable: Boolean(release && releaseRelation !== "sameVersionCurrentRelease"),30
release,31
releases: this.releases,32
selectedReleaseId: this.selectedReleaseId,33
releaseRelation,34
downloaded: this.lastVerifiedDownload ? {35
releaseId: this.lastVerifiedDownload.releaseId,36
filename: this.lastVerifiedDownload.filename,37
size: this.lastVerifiedDownload.size,38
sha512: this.lastVerifiedDownload.sha512,39
signatureVerified: this.lastVerifiedDownload.signatureVerified === true,40
primaryFingerprint: this.lastVerifiedDownload.primaryFingerprint,41
signingFingerprints: this.lastVerifiedDownload.signingFingerprints42
} : null43
};44
}46
async check() {47
if (this.operation) throw new Error("Another update operation is already running.");48
this.operation = "checking";49
try {50
const config = this.configStore.get();51
const provider = this.providerFactory(config.updates);52
const catalog = await provider.listReleases({53
currentVersion: this.appVersion,54
platform: this.platform,55
arch: this.arch,56
channel: config.updates.channel57
});58
this.releases = catalog.releases;59
const preferred = this.releases.find((release) => compareVersions(release.version, this.appVersion) > 0)60
|| this.releases[0]61
|| null;62
this.selectedReleaseId = preferred?.id || null;63
this.lastCheck = {64
updateAvailable: Boolean(preferred),65
currentVersion: this.appVersion,66
release: preferred,67
sourceBaseUrl: catalog.sourceBaseUrl68
};69
this.lastVerifiedDownload = null;70
this.installerLaunchAuthorized = false;71
this.logger?.info("Update check completed.", {72
channel: config.updates.channel,73
updateAvailable: this.lastCheck.updateAvailable,74
version: this.lastCheck.release?.version || null,75
sourceBaseUrl: this.lastCheck.sourceBaseUrl76
});77
return this.getState();78
} finally {79
this.operation = null;80
}81
}83
select(releaseId) {84
if (this.operation) throw new Error("Another update operation is already running.");85
const release = this.releases.find((item) => item.id === String(releaseId || ""));86
if (!release) throw new Error("Selected release is not available in the current catalog.");87
this.selectedReleaseId = release.id;88
this.lastCheck = {89
...(this.lastCheck || { currentVersion: this.appVersion }),90
updateAvailable: true,91
release92
};93
this.lastVerifiedDownload = null;94
this.installerLaunchAuthorized = false;95
return this.getState();96
}98
async download() {99
if (this.operation) throw new Error("Another update operation is already running.");100
if (!this.lastCheck?.release) throw new Error("No update release is selected.");101
this.operation = "downloading";102
this.lastVerifiedDownload = null;103
this.installerLaunchAuthorized = false;104
try {105
const provider = this.providerFactory(this.configStore.get().updates);106
this.lastVerifiedDownload = await provider.downloadAndVerify(this.lastCheck.release);107
if (this.lastVerifiedDownload.signatureVerified !== true) {108
this.lastVerifiedDownload = null;109
throw new Error("The update did not pass mandatory OpenPGP verification.");110
}111
this.lastVerifiedDownload.releaseId = this.lastCheck.release.id;112
this.installerLaunchAuthorized = false;113
this.logger?.info("Update downloaded and verified with SHA-512 and OpenPGP.", {114
filename: this.lastVerifiedDownload.filename,115
size: this.lastVerifiedDownload.size,116
reused: this.lastVerifiedDownload.reused,117
primaryFingerprint: this.lastVerifiedDownload.primaryFingerprint,118
signingFingerprints: this.lastVerifiedDownload.signingFingerprints119
});120
return this.getState();121
} finally {122
this.operation = null;123
}124
}126
getVerifiedDownloadPath() {127
return this.lastVerifiedDownload?.signatureVerified === true128
? this.lastVerifiedDownload.path129
: null;130
}132
async prepareInstallerLaunch() {133
if (this.operation) throw new Error("Another update operation is already running.");134
const release = this.lastCheck?.release;135
if (!release || !this.lastVerifiedDownload || this.lastVerifiedDownload.releaseId !== release.id) {136
throw new Error("No verified update release is ready to install.");137
}138
this.operation = "verifying-signature";139
this.installerLaunchAuthorized = false;140
try {141
const provider = this.providerFactory(this.configStore.get().updates);142
const filePath = await provider.reverifyForInstall(this.lastVerifiedDownload, release);143
this.installerLaunchAuthorized = true;144
return filePath;145
} catch (error) {146
this.lastVerifiedDownload = null;147
throw error;148
} finally {149
this.operation = null;150
}151
}153
cleanupDownloads() {154
const provider = this.providerFactory(this.configStore.get().updates);155
const result = provider.cleanupDownloads?.() || { cleaned: false };156
this.lastVerifiedDownload = null;157
this.installerLaunchAuthorized = false;158
return result;159
}161
markInstallerLaunched() {162
const release = this.lastCheck?.release;163
if (!release || !this.lastVerifiedDownload || this.lastVerifiedDownload.releaseId !== release.id164
|| this.lastVerifiedDownload.signatureVerified !== true || !this.installerLaunchAuthorized) {165
throw new Error("No verified update release is ready to install.");166
}167
this.configStore.recordLaunchedRelease({168
id: release.id,169
version: release.version,170
publishedAt: release.publishedAt,171
sha512: release.sha512172
});173
this.installerLaunchAuthorized = false;174
return this.getState();175
}177
_relation(release) {178
const comparison = compareVersions(release.version, this.appVersion);179
if (comparison > 0) return "newerVersion";180
if (comparison < 0) return "olderVersion";181
if (this.buildId && release.buildId === this.buildId) return "sameVersionCurrentRelease";183
const lastLaunched = this.configStore.get().updates.lastLaunchedRelease;184
if (lastLaunched && compareVersions(lastLaunched.version, release.version) === 0) {185
if (lastLaunched.id === release.id || lastLaunched.sha512 === release.sha512) {186
return "sameVersionCurrentRelease";187
}188
const releaseTime = Date.parse(release.publishedAt);189
const launchedTime = Date.parse(lastLaunched.publishedAt);190
if (Number.isFinite(releaseTime) && Number.isFinite(launchedTime)) {191
return releaseTime > launchedTime ? "sameVersionNewerRelease" : "sameVersionOlderRelease";192
}193
}195
const newestSameVersion = this.releases.find((item) => compareVersions(item.version, this.appVersion) === 0);196
return newestSameVersion?.id === release.id197
? "sameVersionNewerRelease"198
: "sameVersionOlderRelease";199
}200
}202
module.exports = { UpdateService };SHA-256: 10118684f925f713b6b6919552ba9c2de29ff1ffcd3a47c7962ce7b5604e7dfd
Archive SHA-256: 5ac91caf4fa32a6fdb114f2430deed486fbe7489d5eea343d1f034169fafb5e0