diff --git a/src/native/update-window.ts b/src/native/update-window.ts
new file mode 100644
index 0000000..bc4be42
--- /dev/null
+++ b/src/native/update-window.ts
@@ -0,0 +1,79 @@
+import { BrowserWindow, app, ipcMain } from "electron";
+
+let win: BrowserWindow | null = null;
+
+const HTML = `
+Updating Sanctum
+Downloading…
+
+
+`;
+
+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);
+});
diff --git a/src/native/updater.ts b/src/native/updater.ts
index 055e455..a817318 100644
--- a/src/native/updater.ts
+++ b/src/native/updater.ts
@@ -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[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(resolve => writer.end(resolve));
+
+ setUpdateProgress(95);
console.log(`[updater] download complete, extracting to ${installDir}`);
await new Promise((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) {