Files
SubMiner/update-service.js
T

173 lines
6.3 KiB
JavaScript

'use strict';
var __importDefault =
(this && this.__importDefault) ||
function (mod) {
return mod && mod.__esModule ? mod : { default: mod };
};
Object.defineProperty(exports, '__esModule', { value: true });
exports.createUpdateService = createUpdateService;
exports.createFileUpdateStateStore = createFileUpdateStateStore;
const node_fs_1 = __importDefault(require('node:fs'));
const node_path_1 = __importDefault(require('node:path'));
const release_assets_1 = require('./release-assets');
function getBestLatestVersion(currentVersion, appUpdate, release) {
const releaseVersion = (0, release_assets_1.parseReleaseVersion)(release);
const candidates = [appUpdate.version, releaseVersion].filter(
(value) => typeof value === 'string' && value.length > 0,
);
const latest = candidates.reduce(
(best, candidate) =>
(0, release_assets_1.compareSemverLike)(candidate, best) > 0 ? candidate : best,
currentVersion,
);
return {
available:
appUpdate.available || (0, release_assets_1.compareSemverLike)(latest, currentVersion) > 0,
version: latest,
};
}
function shouldSkipAutomaticCheck(config, state, now) {
if (!config.enabled) return true;
if (!state.lastAutomaticCheckAt) return false;
const intervalMs = Math.max(1, config.checkIntervalHours) * 60 * 60 * 1000;
return now - state.lastAutomaticCheckAt < intervalMs;
}
function summarizeError(error) {
const raw = error instanceof Error ? error.message : String(error);
const firstLine = raw
.split('\n')
.map((line) => line.trim())
.find((line) => line.length > 0);
return firstLine ?? 'unknown error';
}
function createUpdateService(deps) {
const inFlightBySource = new Map();
async function runCheck(request) {
const now = deps.now();
const config = deps.getConfig();
const channel = config.channel;
const state = await deps.readState();
const isAutomatic = request.source === 'automatic';
if (isAutomatic && !request.force && shouldSkipAutomaticCheck(config, state, now)) {
return { status: 'skipped' };
}
try {
const appUpdate = await deps.checkAppUpdate(channel).catch((error) => {
if (isAutomatic) {
deps.log(`App update metadata check failed: ${summarizeError(error)}`);
}
return {
available: false,
version: deps.getCurrentVersion(),
};
});
const shouldFetchReleaseMetadata =
deps.shouldFetchReleaseMetadata?.({ request, channel, appUpdate }) ?? true;
const release = shouldFetchReleaseMetadata
? await deps.fetchLatestStableRelease(channel).catch((error) => {
deps.log(`GitHub release update check failed: ${summarizeError(error)}`);
return null;
})
: null;
const currentVersion = deps.getCurrentVersion();
const latest = getBestLatestVersion(currentVersion, appUpdate, release);
if (isAutomatic) {
const nextState = {
...state,
lastAutomaticCheckAt: now,
};
if (latest.available && state.lastNotifiedVersion !== latest.version) {
await deps.notifyUpdateAvailable(latest.version);
nextState.lastNotifiedVersion = latest.version;
}
await deps.writeState(nextState);
}
if (!latest.available) {
if (!isAutomatic) {
await deps.showNoUpdateDialog(currentVersion);
}
return { status: 'up-to-date', version: currentVersion };
}
if (isAutomatic) {
return { status: 'update-available', version: latest.version };
}
const choice = await deps.showUpdateAvailableDialog(latest.version);
if (choice === 'close') {
return { status: 'update-available', version: latest.version };
}
const canInstallAppUpdate = appUpdate.available && appUpdate.canUpdate !== false;
let appUpdateApplied = false;
if (canInstallAppUpdate) {
await deps.downloadAppUpdate();
appUpdateApplied = true;
}
const launcherResult = await deps.updateLauncher(request.launcherPath, channel, release);
if (launcherResult.status === 'protected' && launcherResult.command) {
deps.log(`Launcher update requires manual command: ${launcherResult.command}`);
}
if (!appUpdateApplied) {
await deps.showManualUpdateRequiredDialog(latest.version);
return { status: 'update-available', version: latest.version };
}
const restartChoice = await deps.showRestartDialog();
if (restartChoice === 'restart') {
await deps.quitAndInstall();
}
return { status: 'updated', version: latest.version };
} catch (error) {
const message = summarizeError(error);
if (isAutomatic) {
deps.log(`Automatic update check failed: ${message}`);
} else {
await deps.showUpdateFailedDialog(message);
}
return { status: 'failed', error: message };
}
}
return {
checkForUpdates(request) {
const inFlight = inFlightBySource.get(request.source);
if (inFlight) return inFlight;
const nextInFlight = runCheck(request).finally(() => {
inFlightBySource.delete(request.source);
});
inFlightBySource.set(request.source, nextInFlight);
return nextInFlight;
},
startAutomaticChecks(options = {}) {
const setTimeoutFn = deps.setTimeout ?? setTimeout;
const setIntervalFn = deps.setInterval ?? setInterval;
const startupDelayMs = options.startupDelayMs ?? 15_000;
const pollIntervalMs = options.pollIntervalMs ?? 60 * 60 * 1000;
setTimeoutFn(() => {
void this.checkForUpdates({ source: 'automatic' });
}, startupDelayMs);
setIntervalFn(() => {
void this.checkForUpdates({ source: 'automatic' });
}, pollIntervalMs);
},
};
}
function createFileUpdateStateStore(statePath) {
return {
async readState() {
try {
return JSON.parse(await node_fs_1.default.promises.readFile(statePath, 'utf8'));
} catch {
return {};
}
},
async writeState(state) {
await node_fs_1.default.promises.mkdir(node_path_1.default.dirname(statePath), {
recursive: true,
});
await node_fs_1.default.promises.writeFile(
statePath,
`${JSON.stringify(state, null, 2)}\n`,
'utf8',
);
},
};
}
//# sourceMappingURL=update-service.js.map