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) {