feat: add update progress window with restart button
All checks were successful
Build & Release / build (push) Successful in 2m30s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MiTHRAL 2026-04-21 20:37:41 -04:00
parent 23823a9b59
commit fa250dda5f
2 changed files with 101 additions and 11 deletions

View file

@ -0,0 +1,79 @@
import { BrowserWindow, app, ipcMain } from "electron";
let win: BrowserWindow | null = null;
const HTML = `<!DOCTYPE html><html><head><meta charset="utf-8"><style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:system-ui,sans-serif;background:#1e1e2e;color:#cdd6f4;
display:flex;flex-direction:column;align-items:center;justify-content:center;
height:100vh;padding:28px 32px;gap:14px;user-select:none;-webkit-app-region:drag}
h2{font-size:15px;font-weight:600;letter-spacing:.3px}
#status{font-size:13px;color:#a6adc8}
.track{width:100%;height:6px;background:#313244;border-radius:3px}
#bar{height:6px;background:#89b4fa;border-radius:3px;width:0%;transition:width .4s ease}
#btn{display:none;margin-top:6px;padding:9px 24px;background:#89b4fa;color:#1e1e2e;
border:none;border-radius:6px;font-size:13px;font-weight:700;cursor:pointer;
-webkit-app-region:no-drag}
#btn:hover{background:#b4befe}
</style></head><body>
<h2 id="title">Updating Sanctum</h2>
<div id="status">Downloading</div>
<div class="track"><div id="bar"></div></div>
<button id="btn">Restart Now</button>
<script>
const {ipcRenderer}=require('electron');
ipcRenderer.on('upd-progress',(_,p)=>{
document.getElementById('bar').style.width=p+'%';
document.getElementById('status').textContent=p<100?'Downloading… '+p+'%':'Installing…';
});
ipcRenderer.on('upd-ready',(_,v)=>{
document.getElementById('title').textContent='Sanctum '+v+' Ready';
document.getElementById('status').textContent='Restart to apply the update.';
document.getElementById('bar').style.width='100%';
document.getElementById('btn').style.display='block';
});
ipcRenderer.on('upd-error',(_,msg)=>{
document.getElementById('title').textContent='Update Failed';
document.getElementById('status').textContent=msg;
document.getElementById('bar').style.background='#f38ba8';
});
document.getElementById('btn').onclick=()=>ipcRenderer.send('upd-restart');
</script></body></html>`;
export function showUpdateWindow() {
if (win) { win.focus(); return; }
win = new BrowserWindow({
width: 400,
height: 180,
resizable: false,
minimizable: false,
maximizable: false,
fullscreenable: false,
title: "Sanctum Update",
frame: false,
alwaysOnTop: true,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
});
win.loadURL("data:text/html;charset=utf-8," + encodeURIComponent(HTML));
win.on("closed", () => { win = null; });
}
export function setUpdateProgress(percent: number) {
win?.webContents.send("upd-progress", Math.round(percent));
}
export function setUpdateReady(version: string) {
win?.webContents.send("upd-ready", version);
}
export function setUpdateError(msg: string) {
win?.webContents.send("upd-error", msg);
}
ipcMain.on("upd-restart", () => {
app.relaunch();
app.exit(0);
});

View file

@ -3,8 +3,8 @@ import { exec } from "child_process";
import { createWriteStream, mkdirSync } from "fs";
import { dirname, join } from "path";
import { tmpdir } from "os";
import { pipeline } from "stream/promises";
import { Readable } from "stream";
import { showUpdateWindow, setUpdateProgress, setUpdateReady, setUpdateError } from "./update-window";
ipcMain.handle("checkForUpdates", () => checkForUpdates());
@ -25,12 +25,11 @@ interface Release {
export async function checkForUpdates() {
try {
console.log("[updater] checking:", RELEASES_URL);
notify("Checking for Updates", "Looking for a new version of Sanctum…");
const res = await fetch(RELEASES_URL);
if (!res.ok) {
console.error("[updater] releases API returned", res.status, res.statusText);
notify("Update Check Failed", `API returned ${res.status} — check console.`);
notify("Update Check Failed", `API returned ${res.status}`);
return;
}
@ -49,16 +48,16 @@ export async function checkForUpdates() {
if (!asset) {
const names = release.assets.map(a => a.name).join(", ");
console.error("[updater] no matching asset for platform:", process.platform, names);
notify("Update Failed", `No ${process.platform} asset found. Assets: ${names}`);
notify("Update Failed", `No ${process.platform} asset found.`);
return;
}
console.log(`[updater] update available: ${current}${latest}, downloading ${asset.name}`);
notify("Update Downloading", `Sanctum ${latest} is downloading in the background…`);
showUpdateWindow();
await downloadAndInstall(asset.browser_download_url, latest);
} catch (err) {
console.error("[updater] update check failed:", err);
notify("Update Check Failed", String(err));
setUpdateError(String(err));
}
}
@ -74,14 +73,27 @@ async function downloadAndInstall(url: string, version: string) {
mkdirSync(tmpDir, { recursive: true });
const zipPath = join(tmpDir, "update.zip");
const extractDir = join(tmpDir, "extracted");
const installDir = dirname(process.execPath);
console.log(`[updater] downloading from ${url}`);
const res = await fetch(url);
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
const installDir = dirname(process.execPath);
const total = Number(res.headers.get("content-length")) || 0;
let downloaded = 0;
const writer = createWriteStream(zipPath);
const reader = res.body.getReader();
await pipeline(Readable.fromWeb(res.body as Parameters<typeof Readable.fromWeb>[0]), createWriteStream(zipPath));
while (true) {
const { done, value } = await reader.read();
if (done) break;
writer.write(value);
downloaded += value.length;
if (total > 0) setUpdateProgress(Math.round((downloaded / total) * 90));
}
await new Promise<void>(resolve => writer.end(resolve));
setUpdateProgress(95);
console.log(`[updater] download complete, extracting to ${installDir}`);
await new Promise<void>((resolve, reject) => {
@ -96,8 +108,7 @@ async function downloadAndInstall(url: string, version: string) {
});
});
const n = notify("Update Ready", `Sanctum ${version} is ready — click to restart.`);
n.on("click", () => { app.relaunch(); app.exit(0); });
setUpdateReady(version);
}
function notify(title: string, body: string) {