Improving Installer and Updater Process

This commit is contained in:
2026-05-31 18:52:51 +02:00
parent 8e6b79ca68
commit ca74a23d19
5 changed files with 74 additions and 6 deletions
+4 -1
View File
@@ -84,7 +84,10 @@
"uninstallerIcon": "static/icons/icon.ico",
"installerHeaderIcon": "static/icons/icon.ico",
"shortcutName": "Scoreko",
"useZip": false
"useZip": false,
"deleteAppDataOnUninstall": true,
"showInstDetails": "show",
"showUninstDetails": "show"
},
"compression": "normal"
},
+16
View File
@@ -41,6 +41,22 @@ export async function askToInstallUpdate(update: ReleaseUpdate, parentWindow: Br
return result.response === 0;
}
export async function showDownloadFailedDialog(
update: ReleaseUpdate,
error: unknown,
parentWindow: BrowserWindow | null,
): Promise<void> {
const errorMessage = error instanceof Error ? error.message : String(error);
await showMessageBox(parentWindow, {
type: "error",
title: "Error de descarga",
message: `No se pudo descargar la actualización para Scoreko ${update.version}.`,
detail: `Detalles del error: ${errorMessage}\n\nPor favor, comprueba tu conexión a internet e inténtalo de nuevo.`,
buttons: ["Aceptar"],
defaultId: 0,
});
}
function showMessageBox(
parentWindow: BrowserWindow | null,
options: MessageBoxOptions,
+7
View File
@@ -23,6 +23,13 @@ export async function downloadInstaller(update: ReleaseUpdate, config: UpdateDow
const targetPath = getSafeChildPath(downloadDirectory, safeFileName);
const stagingPath = getSafeChildPath(downloadDirectory, `${safeFileName}.${process.pid}.${Date.now()}.download`);
if (fs.existsSync(targetPath)) {
const stats = fs.statSync(targetPath);
if (typeof update.installer.size === "number" && stats.size === update.installer.size) {
return targetPath;
}
}
fs.mkdirSync(downloadDirectory, { recursive: true });
fs.rmSync(stagingPath, { force: true });
+12 -5
View File
@@ -1,7 +1,7 @@
import { app, BrowserWindow, shell } from "electron";
import { AppRuntimeConfig } from "../config/runtime-config";
import { askToDownloadUpdate, askToInstallUpdate } from "./update-dialogs";
import { askToDownloadUpdate, askToInstallUpdate, showDownloadFailedDialog } from "./update-dialogs";
import { loadUpdateSettings, UpdateSettings } from "./update-config";
import { downloadInstaller } from "./update-download";
import { buildReleaseUpdate, GiteaRelease, parseGiteaRelease } from "./update-schema";
@@ -76,10 +76,17 @@ async function checkForUpdates({
return;
}
const installerPath = await downloadInstaller(update, {
tempDirectory: app.getPath("temp"),
allowInsecureHttp: protocolPolicy.allowInsecureHttp,
});
let installerPath: string;
try {
installerPath = await downloadInstaller(update, {
tempDirectory: app.getPath("temp"),
allowInsecureHttp: protocolPolicy.allowInsecureHttp,
});
} catch (error) {
log("Update installer download failed.", error);
await showDownloadFailedDialog(update, error, getParentWindow());
return;
}
const shouldInstall = await askToInstallUpdate(update, getParentWindow());
if (!shouldInstall) {
await shell.showItemInFolder(installerPath);
+35
View File
@@ -56,3 +56,38 @@ test("downloadInstaller rejects insecure production download URLs", async () =>
/unsupported protocol/,
);
});
test("downloadInstaller reuses existing file if size matches and does not download again", async () => {
const tempDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "scoreko-update-download-"));
const downloadDirectory = path.join(tempDirectory, "scoreko-updates");
fs.mkdirSync(downloadDirectory, { recursive: true });
const installerPath = path.join(downloadDirectory, "Scoreko_setup_0.2.0.exe");
fs.writeFileSync(installerPath, "cached-installer-bytes");
const cachedSize = fs.statSync(installerPath).size;
const previousFetch = globalThis.fetch;
globalThis.fetch = async () => {
throw new Error("Should not fetch when using cached file!");
};
try {
const resultPath = await downloadInstaller(
{
version: "0.2.0",
title: "Scoreko 0.2.0",
installer: {
name: "Scoreko/setup:0.2.0.exe",
downloadUrl: "https://updates.local/Scoreko-setup-0.2.0.exe",
size: cachedSize,
},
},
{ tempDirectory, allowInsecureHttp: false },
);
assert.equal(resultPath, installerPath);
assert.equal(fs.readFileSync(resultPath, "utf8"), "cached-installer-bytes");
} finally {
globalThis.fetch = previousFetch;
}
});