Skip to content

Commit 770629b

Browse files
committed
add auto-update
1 parent d7c1ef6 commit 770629b

15 files changed

Lines changed: 350 additions & 13 deletions

File tree

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/server/public/app.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,60 @@ document.addEventListener('DOMContentLoaded', async () => {
8383
initializeExportSelection()
8484
initializeFormatDescription()
8585
initializePresetDescription()
86+
checkForUpdates()
8687
})
8788

89+
async function checkForUpdates() {
90+
if (!window.electronAPI?.checkForUpdates) return
91+
92+
const banner = document.getElementById('update-banner')
93+
const btn = document.getElementById('update-btn')
94+
if (!banner || !btn) return
95+
96+
try {
97+
const result = await window.electronAPI.checkForUpdates()
98+
if (!result.hasUpdate) return
99+
banner.style.display = 'flex'
100+
101+
if (!result.supported) {
102+
// Unsupported format (e.g. .deb/.rpm/.tar.gz): open the releases page instead of self-updating
103+
btn.onclick = () => {
104+
if (window.electronAPI?.openExternal) {
105+
window.electronAPI.openExternal(result.releaseUrl)
106+
} else {
107+
window.open(result.releaseUrl, '_blank')
108+
}
109+
}
110+
return
111+
}
112+
113+
// Supported format: download in-app, show progress, then restart to install
114+
window.electronAPI.onUpdateProgress?.((data) => {
115+
btn.textContent = `Downloading... ${data.percent}%`
116+
btn.disabled = true
117+
})
118+
119+
window.electronAPI.onUpdateDownloaded?.(() => {
120+
btn.textContent = window.i18n?.t('update.restart') ?? 'Restart to install'
121+
btn.disabled = false
122+
btn.onclick = () => window.electronAPI.installUpdate()
123+
})
124+
125+
window.electronAPI.onUpdateError?.(() => {
126+
btn.textContent = window.i18n?.t('update.button') ?? 'Download update'
127+
btn.disabled = false
128+
})
129+
130+
btn.onclick = async () => {
131+
btn.textContent = window.i18n?.t('update.downloading') ?? 'Starting download...'
132+
btn.disabled = true
133+
await window.electronAPI.downloadUpdate()
134+
}
135+
} catch (_) {
136+
// silently ignore
137+
}
138+
}
139+
88140
// Load presets from server
89141
async function loadPresets() {
90142
try {

dist/server/public/index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
</div>
3333
</nav>
3434

35+
<div id="update-banner" style="display:none" class="update-banner">
36+
<span data-i18n="update.available">A new version is available!</span>
37+
<button id="update-btn" class="update-btn" data-i18n="update.button">Download update</button>
38+
</div>
39+
3540
<div class="container">
3641
<header>
3742
<h1 data-i18n="header.title">LiaScript Exporter</h1>

dist/server/public/locales/de.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,5 +246,9 @@
246246
"status.autoRefresh": "Seite wird automatisch aktualisiert",
247247
"status.refreshNow": "Jetzt aktualisieren",
248248
"status.error": "Fehler",
249-
"status.errorMessage": "Fehler beim Laden des Status"
249+
"status.errorMessage": "Fehler beim Laden des Status",
250+
"update.available": "Eine neue Version ist verfügbar!",
251+
"update.button": "Update herunterladen",
252+
"update.downloading": "Download wird gestartet...",
253+
"update.restart": "Neu starten und installieren"
250254
}

dist/server/public/locales/en.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,5 +246,9 @@
246246
"status.autoRefresh": "Page will refresh automatically",
247247
"status.refreshNow": "Refresh Now",
248248
"status.error": "Error",
249-
"status.errorMessage": "Error loading status"
249+
"status.errorMessage": "Error loading status",
250+
"update.available": "A new version is available!",
251+
"update.button": "Download update",
252+
"update.downloading": "Starting download...",
253+
"update.restart": "Restart to install"
250254
}

dist/server/public/styles.css

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,4 +709,26 @@ footer {
709709
color: var(--text-muted);
710710
font-size: 0.875rem;
711711
margin-top: 1rem;
712-
}
712+
}
713+
.update-banner {
714+
display: flex;
715+
align-items: center;
716+
gap: 1rem;
717+
background: var(--primary);
718+
color: #fff;
719+
padding: 0.6rem 1.5rem;
720+
font-size: 0.9rem;
721+
}
722+
.update-btn {
723+
background: #fff;
724+
color: var(--primary);
725+
border: none;
726+
border-radius: 4px;
727+
padding: 0.3rem 0.8rem;
728+
font-size: 0.85rem;
729+
cursor: pointer;
730+
font-weight: 600;
731+
}
732+
.update-btn:hover {
733+
opacity: 0.85;
734+
}

electron/main.js

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
1+
const { app, BrowserWindow, ipcMain, dialog, shell, globalShortcut } = require('electron');
22
const path = require('path');
33
const fs = require('fs');
4+
const https = require('https');
5+
const { autoUpdater } = require('electron-updater');
6+
7+
// App version, normalized to strip the `--<electron-version>` suffix (e.g. 3.2.10--1.0.8 -> 3.2.10)
8+
const APP_VERSION = require('../package.json').version.replace(/--.*$/, '');
49

510
let mainWindow;
611
let serverInstance;
@@ -36,11 +41,97 @@ function createWindow() {
3641
mainWindow.on('closed', () => {
3742
mainWindow = null;
3843
});
44+
45+
globalShortcut.register('F12', () => {
46+
if (mainWindow) mainWindow.webContents.toggleDevTools();
47+
});
3948
}
4049

4150
app.whenReady().then(() => {
4251
createWindow();
4352

53+
// Register IPC handler for opening external URLs
54+
ipcMain.handle('shell:openExternal', async (event, url) => {
55+
await shell.openExternal(url);
56+
});
57+
58+
// Auto-updater setup (AppImage, NSIS, DMG only — others fall back to releases page)
59+
autoUpdater.autoDownload = false;
60+
autoUpdater.autoInstallOnAppQuit = false;
61+
autoUpdater.channel = 'latest';
62+
autoUpdater.allowPrerelease = false;
63+
64+
const updatableFormats = ['appimage', 'nsis', 'dmg'];
65+
66+
function getInstallerType() {
67+
if (process.platform === 'linux') return process.env.APPIMAGE ? 'appimage' : 'other';
68+
if (process.platform === 'win32') return fs.existsSync(path.join(process.resourcesPath, '..', 'Uninstall LiaScript-Exporter.exe')) ? 'nsis' : 'other';
69+
if (process.platform === 'darwin') return 'dmg';
70+
return 'other';
71+
}
72+
73+
autoUpdater.on('update-available', (info) => {
74+
if (mainWindow) mainWindow.webContents.send('update:available', { version: info.version });
75+
});
76+
77+
autoUpdater.on('download-progress', (progress) => {
78+
if (mainWindow) mainWindow.webContents.send('update:progress', { percent: Math.round(progress.percent) });
79+
});
80+
81+
autoUpdater.on('update-downloaded', () => {
82+
if (mainWindow) mainWindow.webContents.send('update:downloaded');
83+
});
84+
85+
autoUpdater.on('error', () => {
86+
if (mainWindow) mainWindow.webContents.send('update:error');
87+
});
88+
89+
ipcMain.handle('app:checkForUpdates', async () => {
90+
if (!updatableFormats.includes(getInstallerType())) {
91+
// Fallback: check GitHub API and return release URL for unsupported formats
92+
return new Promise((resolve) => {
93+
https.get({
94+
hostname: 'api.github.com',
95+
path: '/repos/LiaScript/LiaScript-Exporter/releases/latest',
96+
headers: { 'User-Agent': 'LiaScript-Exporter' }
97+
}, (res) => {
98+
let data = '';
99+
res.on('data', (chunk) => { data += chunk; });
100+
res.on('end', () => {
101+
try {
102+
const release = JSON.parse(data);
103+
const latestVersion = release.tag_name.replace(/^v/, '');
104+
resolve({
105+
supported: false,
106+
hasUpdate: latestVersion !== APP_VERSION,
107+
releaseUrl: release.html_url
108+
});
109+
} catch {
110+
resolve({ supported: false, hasUpdate: false });
111+
}
112+
});
113+
}).on('error', () => resolve({ supported: false, hasUpdate: false }));
114+
});
115+
}
116+
try {
117+
const result = await autoUpdater.checkForUpdates();
118+
const latestVersion = result?.updateInfo?.version;
119+
const hasUpdate = !!latestVersion && latestVersion !== APP_VERSION;
120+
return { supported: true, hasUpdate, version: latestVersion };
121+
} catch (e) {
122+
console.error('[update-check] autoUpdater error:', e.message);
123+
return { supported: false, hasUpdate: false };
124+
}
125+
});
126+
127+
ipcMain.handle('app:downloadUpdate', async () => {
128+
await autoUpdater.downloadUpdate();
129+
});
130+
131+
ipcMain.handle('app:installUpdate', () => {
132+
autoUpdater.quitAndInstall();
133+
});
134+
44135
// Register IPC handler for file dialog
45136
ipcMain.handle('dialog:openFile', async () => {
46137
const result = await dialog.showOpenDialog(mainWindow, {

electron/preload.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ contextBridge.exposeInMainWorld('electronAPI', {
2222

2323
// File dialog
2424
openFileDialog: () => ipcRenderer.invoke('dialog:openFile'),
25+
26+
// Update check
27+
checkForUpdates: () => ipcRenderer.invoke('app:checkForUpdates'),
28+
downloadUpdate: () => ipcRenderer.invoke('app:downloadUpdate'),
29+
installUpdate: () => ipcRenderer.invoke('app:installUpdate'),
30+
openExternal: (url) => ipcRenderer.invoke('shell:openExternal', url),
31+
onUpdateAvailable: (cb) => ipcRenderer.on('update:available', (_e, data) => cb(data)),
32+
onUpdateProgress: (cb) => ipcRenderer.on('update:progress', (_e, data) => cb(data)),
33+
onUpdateDownloaded: (cb) => ipcRenderer.on('update:downloaded', () => cb()),
34+
onUpdateError: (cb) => ipcRenderer.on('update:error', () => cb()),
2535
});
2636

2737
// Log that the preload script has loaded

package-lock.json

Lines changed: 65 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"@liascript/simple-scorm-packager": "^0.3.0",
7373
"@turbodocx/html-to-docx": "^1.20.1",
7474
"archiver": "^7.0.1",
75+
"electron-updater": "^6.8.9",
7576
"epub-gen": "^0.1.0",
7677
"fastify": "^4.26.0",
7778
"fs-extra": "^11.3.3",
@@ -115,4 +116,4 @@
115116
"optimize": true
116117
}
117118
}
118-
}
119+
}

0 commit comments

Comments
 (0)