chore: replace update-electron-app with Gitea-backed updater
This commit is contained in:
parent
225c623ecb
commit
cc8ba75694
5 changed files with 251 additions and 38 deletions
|
|
@ -54,7 +54,6 @@
|
||||||
"discord-rpc": "^4.0.1",
|
"discord-rpc": "^4.0.1",
|
||||||
"electron-squirrel-startup": "^1.0.1",
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
"electron-store": "^10.1.0",
|
"electron-store": "^10.1.0",
|
||||||
"update-electron-app": "^3.1.2",
|
|
||||||
"utf-8-validate": "^6.0.5"
|
"utf-8-validate": "^6.0.5"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.33.0"
|
"packageManager": "pnpm@10.33.0"
|
||||||
|
|
|
||||||
34
pnpm-lock.yaml
generated
34
pnpm-lock.yaml
generated
|
|
@ -29,9 +29,6 @@ importers:
|
||||||
electron-store:
|
electron-store:
|
||||||
specifier: ^10.1.0
|
specifier: ^10.1.0
|
||||||
version: 10.1.0
|
version: 10.1.0
|
||||||
update-electron-app:
|
|
||||||
specifier: ^3.1.2
|
|
||||||
version: 3.1.2
|
|
||||||
utf-8-validate:
|
utf-8-validate:
|
||||||
specifier: ^6.0.5
|
specifier: ^6.0.5
|
||||||
version: 6.0.5
|
version: 6.0.5
|
||||||
|
|
@ -910,56 +907,67 @@ packages:
|
||||||
resolution: {integrity: sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==}
|
resolution: {integrity: sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.52.2':
|
'@rollup/rollup-linux-arm-musleabihf@4.52.2':
|
||||||
resolution: {integrity: sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==}
|
resolution: {integrity: sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.52.2':
|
'@rollup/rollup-linux-arm64-gnu@4.52.2':
|
||||||
resolution: {integrity: sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==}
|
resolution: {integrity: sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.52.2':
|
'@rollup/rollup-linux-arm64-musl@4.52.2':
|
||||||
resolution: {integrity: sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==}
|
resolution: {integrity: sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-loong64-gnu@4.52.2':
|
'@rollup/rollup-linux-loong64-gnu@4.52.2':
|
||||||
resolution: {integrity: sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==}
|
resolution: {integrity: sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-ppc64-gnu@4.52.2':
|
'@rollup/rollup-linux-ppc64-gnu@4.52.2':
|
||||||
resolution: {integrity: sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==}
|
resolution: {integrity: sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.52.2':
|
'@rollup/rollup-linux-riscv64-gnu@4.52.2':
|
||||||
resolution: {integrity: sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==}
|
resolution: {integrity: sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-musl@4.52.2':
|
'@rollup/rollup-linux-riscv64-musl@4.52.2':
|
||||||
resolution: {integrity: sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==}
|
resolution: {integrity: sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.52.2':
|
'@rollup/rollup-linux-s390x-gnu@4.52.2':
|
||||||
resolution: {integrity: sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==}
|
resolution: {integrity: sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.52.2':
|
'@rollup/rollup-linux-x64-gnu@4.52.2':
|
||||||
resolution: {integrity: sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==}
|
resolution: {integrity: sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.52.2':
|
'@rollup/rollup-linux-x64-musl@4.52.2':
|
||||||
resolution: {integrity: sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==}
|
resolution: {integrity: sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-openharmony-arm64@4.52.2':
|
'@rollup/rollup-openharmony-arm64@4.52.2':
|
||||||
resolution: {integrity: sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==}
|
resolution: {integrity: sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==}
|
||||||
|
|
@ -1977,9 +1985,6 @@ packages:
|
||||||
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
|
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
github-url-to-object@4.0.6:
|
|
||||||
resolution: {integrity: sha512-NaqbYHMUAlPcmWFdrAB7bcxrNIiiJWJe8s/2+iOc9vlcHlwHqSGrPk+Yi3nu6ebTwgsZEa7igz+NH2vEq3gYwQ==}
|
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
glob-parent@5.1.2:
|
||||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
@ -2267,9 +2272,6 @@ packages:
|
||||||
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
|
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
is-url@1.2.4:
|
|
||||||
resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==}
|
|
||||||
|
|
||||||
is-weakmap@2.0.2:
|
is-weakmap@2.0.2:
|
||||||
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
|
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -3302,9 +3304,6 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
browserslist: '>= 4.21.0'
|
browserslist: '>= 4.21.0'
|
||||||
|
|
||||||
update-electron-app@3.1.2:
|
|
||||||
resolution: {integrity: sha512-htLyPJv7mEoCpaSzCg0W3Hxz7ID0GC7BIhhpK32/ITG7McrWak4aOkLEOjJheKAI94AxtBVTjCk4EFIvyttw2w==}
|
|
||||||
|
|
||||||
uri-js@4.4.1:
|
uri-js@4.4.1:
|
||||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||||
|
|
||||||
|
|
@ -5835,10 +5834,6 @@ snapshots:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
get-intrinsic: 1.3.0
|
get-intrinsic: 1.3.0
|
||||||
|
|
||||||
github-url-to-object@4.0.6:
|
|
||||||
dependencies:
|
|
||||||
is-url: 1.2.4
|
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
glob-parent@5.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
|
|
@ -6146,8 +6141,6 @@ snapshots:
|
||||||
|
|
||||||
is-unicode-supported@0.1.0: {}
|
is-unicode-supported@0.1.0: {}
|
||||||
|
|
||||||
is-url@1.2.4: {}
|
|
||||||
|
|
||||||
is-weakmap@2.0.2: {}
|
is-weakmap@2.0.2: {}
|
||||||
|
|
||||||
is-weakref@1.1.1:
|
is-weakref@1.1.1:
|
||||||
|
|
@ -7218,11 +7211,6 @@ snapshots:
|
||||||
escalade: 3.2.0
|
escalade: 3.2.0
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
|
|
||||||
update-electron-app@3.1.2:
|
|
||||||
dependencies:
|
|
||||||
github-url-to-object: 4.0.6
|
|
||||||
ms: 2.1.3
|
|
||||||
|
|
||||||
uri-js@4.4.1:
|
uri-js@4.4.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
punycode: 2.3.1
|
punycode: 2.3.1
|
||||||
|
|
|
||||||
17
src/main.ts
17
src/main.ts
|
|
@ -1,8 +1,7 @@
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { updateElectronApp } from "update-electron-app";
|
|
||||||
|
|
||||||
import { BrowserWindow, Notification, app, shell } from "electron";
|
import { BrowserWindow, app, shell } from "electron";
|
||||||
import started from "electron-squirrel-startup";
|
import started from "electron-squirrel-startup";
|
||||||
|
|
||||||
import { aviaVersion } from "../package.json";
|
import { aviaVersion } from "../package.json";
|
||||||
|
|
@ -11,6 +10,7 @@ import { autoLaunch } from "./native/autoLaunch";
|
||||||
import { setBadgeCount } from "./native/badges";
|
import { setBadgeCount } from "./native/badges";
|
||||||
import { config } from "./native/config";
|
import { config } from "./native/config";
|
||||||
import { initDiscordRpc } from "./native/discordRpc";
|
import { initDiscordRpc } from "./native/discordRpc";
|
||||||
|
import { checkForUpdates } from "./native/updater";
|
||||||
import { initTray } from "./native/tray";
|
import { initTray } from "./native/tray";
|
||||||
import { BUILD_URL, createMainWindow, mainWindow } from "./native/window";
|
import { BUILD_URL, createMainWindow, mainWindow } from "./native/window";
|
||||||
|
|
||||||
|
|
@ -42,16 +42,6 @@ if (!config.hardwareAcceleration) {
|
||||||
|
|
||||||
const acquiredLock = app.requestSingleInstanceLock();
|
const acquiredLock = app.requestSingleInstanceLock();
|
||||||
|
|
||||||
const onNotifyUser = () => {
|
|
||||||
const notification = new Notification({
|
|
||||||
title: "Update Available",
|
|
||||||
body: "Restart the app to install the update.",
|
|
||||||
silent: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
notification.show();
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadInject = () => {
|
const loadInject = () => {
|
||||||
if (!mainWindow) return;
|
if (!mainWindow) return;
|
||||||
|
|
||||||
|
|
@ -87,8 +77,6 @@ const loadInject = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (acquiredLock) {
|
if (acquiredLock) {
|
||||||
updateElectronApp({ onNotifyUser });
|
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
applyAppName();
|
applyAppName();
|
||||||
createMainWindow();
|
createMainWindow();
|
||||||
|
|
@ -110,6 +98,7 @@ if (acquiredLock) {
|
||||||
|
|
||||||
initTray();
|
initTray();
|
||||||
initDiscordRpc();
|
initDiscordRpc();
|
||||||
|
checkForUpdates();
|
||||||
setBadgeCount(0);
|
setBadgeCount(0);
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
|
|
|
||||||
83
src/native/update-window.ts
Normal file
83
src/native/update-window.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
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+' installed';
|
||||||
|
document.getElementById('bar').style.width='100%';
|
||||||
|
var s=document.getElementById('status');
|
||||||
|
var n=2;
|
||||||
|
s.textContent='Restarting in '+n+'…';
|
||||||
|
var t=setInterval(function(){
|
||||||
|
n--;
|
||||||
|
if(n<=0){clearInterval(t);s.textContent='Restarting…';}
|
||||||
|
else s.textContent='Restarting in '+n+'…';
|
||||||
|
},1000);
|
||||||
|
});
|
||||||
|
ipcRenderer.on('upd-error',(_,msg)=>{
|
||||||
|
document.getElementById('title').textContent='Update Failed';
|
||||||
|
document.getElementById('status').textContent=msg;
|
||||||
|
document.getElementById('bar').style.background='#f38ba8';
|
||||||
|
});
|
||||||
|
</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, relaunch: boolean) {
|
||||||
|
win?.webContents.send("upd-ready", version);
|
||||||
|
setTimeout(() => {
|
||||||
|
if (relaunch) app.relaunch();
|
||||||
|
app.exit(0);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setUpdateError(msg: string) {
|
||||||
|
win?.webContents.send("upd-error", msg);
|
||||||
|
}
|
||||||
154
src/native/updater.ts
Normal file
154
src/native/updater.ts
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
import { Notification, app, ipcMain } from "electron";
|
||||||
|
import { exec, spawn } from "child_process";
|
||||||
|
import { createWriteStream, mkdirSync, writeFileSync } from "fs";
|
||||||
|
import { dirname, join } from "path";
|
||||||
|
import { tmpdir } from "os";
|
||||||
|
|
||||||
|
import { showUpdateWindow, setUpdateProgress, setUpdateReady, setUpdateError } from "./update-window";
|
||||||
|
|
||||||
|
ipcMain.handle("checkForUpdates", () => checkForUpdates());
|
||||||
|
|
||||||
|
const RELEASES_URL =
|
||||||
|
"https://git.mithraic.cloud/api/v1/repos/ad3laid3/sanctum/releases/latest";
|
||||||
|
|
||||||
|
interface Asset {
|
||||||
|
name: string;
|
||||||
|
browser_download_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Release {
|
||||||
|
tag_name: string;
|
||||||
|
html_url: string;
|
||||||
|
assets: Asset[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkForUpdates() {
|
||||||
|
try {
|
||||||
|
console.log("[updater] checking:", RELEASES_URL);
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const release = (await res.json()) as Release;
|
||||||
|
const latest = release.tag_name.replace(/^v/, "");
|
||||||
|
const current = app.getVersion();
|
||||||
|
|
||||||
|
console.log(`[updater] current=${current} latest=${latest}`);
|
||||||
|
|
||||||
|
if (!isNewer(latest, current)) {
|
||||||
|
notify("Already Up to Date", `You're on Sanctum ${current}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const asset = findAsset(release.assets);
|
||||||
|
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.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[updater] update available: ${current} → ${latest}, downloading ${asset.name}`);
|
||||||
|
showUpdateWindow();
|
||||||
|
await downloadAndInstall(asset.browser_download_url, latest);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[updater] update check failed:", err);
|
||||||
|
setUpdateError(String(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAsset(assets: Asset[]): Asset | undefined {
|
||||||
|
if (process.platform === "linux")
|
||||||
|
return assets.find((a) => a.name.includes("linux") && a.name.endsWith(".zip"));
|
||||||
|
if (process.platform === "win32")
|
||||||
|
return assets.find((a) => a.name.includes("win32") && a.name.endsWith(".zip"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadAndInstall(url: string, version: string) {
|
||||||
|
const tmpDir = join(tmpdir(), `sanctum-update-${version}`);
|
||||||
|
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 total = Number(res.headers.get("content-length")) || 0;
|
||||||
|
let downloaded = 0;
|
||||||
|
const writer = createWriteStream(zipPath);
|
||||||
|
const reader = res.body.getReader();
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
// Extract zip while the app is still running (no locked files yet)
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const cmd = `powershell -Command "Expand-Archive -Force -Path '${zipPath}' -DestinationPath '${extractDir}'"`;
|
||||||
|
exec(cmd, (err, _stdout, stderr) => {
|
||||||
|
if (err) { console.error("[updater] extract failed:", stderr); reject(err); }
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write a batch script that runs after we exit: waits, copies, relaunches
|
||||||
|
const batchPath = join(tmpDir, "apply-update.bat");
|
||||||
|
const bat = [
|
||||||
|
"@echo off",
|
||||||
|
"timeout /t 3 /nobreak >nul",
|
||||||
|
`for /d %%D in ("${extractDir}\\*") do set SUB=%%D`,
|
||||||
|
`xcopy /E /Y /I "%SUB%\\*" "${installDir}\\"`,
|
||||||
|
`start "" "${join(installDir, "sanctum.exe")}"`,
|
||||||
|
`del "%~f0"`,
|
||||||
|
].join("\r\n");
|
||||||
|
writeFileSync(batchPath, bat);
|
||||||
|
|
||||||
|
// Spawn batch truly detached so it outlives this process
|
||||||
|
const child = spawn("cmd.exe", ["/C", batchPath], {
|
||||||
|
detached: true,
|
||||||
|
stdio: "ignore",
|
||||||
|
windowsHide: false,
|
||||||
|
});
|
||||||
|
child.unref();
|
||||||
|
setUpdateReady(version, false); // batch script handles relaunch
|
||||||
|
} else {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const cmd = `unzip -o "${zipPath}" -d "${extractDir}" && SUBDIR=$(ls "${extractDir}" | head -1) && rm -f "${installDir}/sanctum" && cp -rT "${extractDir}/$SUBDIR" "${installDir}"`;
|
||||||
|
exec(cmd, { shell: "/bin/bash" }, (err, _stdout, stderr) => {
|
||||||
|
if (err) { console.error("[updater] extract failed:", stderr); reject(err); }
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setUpdateReady(version, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function notify(title: string, body: string) {
|
||||||
|
const n = new Notification({ title, body, silent: true });
|
||||||
|
n.show();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNewer(latest: string, current: string): boolean {
|
||||||
|
const parse = (v: string) => v.split(".").map(p => Number(p) || 0);
|
||||||
|
const [lA, lB, lC] = parse(latest);
|
||||||
|
const [cA, cB, cC] = parse(current);
|
||||||
|
if (lA !== cA) return lA > cA;
|
||||||
|
if (lB !== cB) return lB > cB;
|
||||||
|
return lC > cC;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue