Add self-update feature via GitHub Releases

Checks for updates on startup (silent, badge on button if available)
and on manual click. Downloads new portable .exe to temp dir, creates
a batch script to swap the running executable after quit, then relaunches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Zac
2026-02-09 22:23:48 -05:00
parent cccb20bd31
commit 7309d78344
6 changed files with 555 additions and 1 deletions
+140
View File
@@ -21,10 +21,23 @@ const cookieKey = document.getElementById('cookieKey');
const startCookieProxyBtn = document.getElementById('startCookieProxyBtn');
const stopCookieProxyBtn = document.getElementById('stopCookieProxyBtn');
// Update elements
const checkUpdateBtn = document.getElementById('checkUpdateBtn');
const updateModalOverlay = document.getElementById('updateModalOverlay');
const updateModalCloseBtn = document.getElementById('updateModalCloseBtn');
const updateModalMessage = document.getElementById('updateModalMessage');
const updateModalNotes = document.getElementById('updateModalNotes');
const updateProgressContainer = document.getElementById('updateProgressContainer');
const updateProgressFill = document.getElementById('updateProgressFill');
const updateProgressText = document.getElementById('updateProgressText');
const updateInstallBtn = document.getElementById('updateInstallBtn');
const updateLaterBtn = document.getElementById('updateLaterBtn');
// Track selected device
let selectedDevice = null;
let activeCookieProxyConnections = new Map(); // Track cookie-based proxy connections
let allDevices = []; // Store all devices for search functionality
let pendingUpdateInfo = null; // Store update info for install action
// Event listeners
disconnectBtn.addEventListener('click', handleDisconnect);
@@ -39,6 +52,12 @@ cookieKey.addEventListener('input', updateCookieProxyButtonStates);
// Device search event listener
deviceSearch.addEventListener('input', handleDeviceSearch);
// Update event listeners
checkUpdateBtn.addEventListener('click', handleCheckForUpdates);
updateInstallBtn.addEventListener('click', handleInstallUpdate);
updateLaterBtn.addEventListener('click', closeUpdateModal);
updateModalCloseBtn.addEventListener('click', closeUpdateModal);
// Handle disconnect
function handleDisconnect() {
sessionData.isConnected = false;
@@ -432,6 +451,118 @@ function escapeHtml(text) {
return div.innerHTML;
}
// --- Self-Update Functions ---
async function handleCheckForUpdates() {
checkUpdateBtn.disabled = true;
try {
const result = await window.electronAPI.checkForUpdates();
if (!result.success) {
showStatus(connectionStatus, result.message || 'Failed to check for updates', 'error');
return;
}
if (result.message === 'No releases available yet') {
showStatus(connectionStatus, 'No releases available yet', 'info');
return;
}
if (result.updateAvailable) {
showUpdateModal(result);
} else {
showStatus(connectionStatus, `You're on the latest version (v${result.currentVersion})`, 'success');
}
} catch (error) {
console.error('Check for updates error:', error);
showStatus(connectionStatus, 'Could not check for updates. Check your internet connection.', 'error');
} finally {
checkUpdateBtn.disabled = false;
}
}
function showUpdateModal(updateInfo) {
pendingUpdateInfo = updateInfo;
updateModalMessage.textContent = `A new version is available: v${updateInfo.latestVersion} (current: v${updateInfo.currentVersion})`;
updateModalNotes.textContent = updateInfo.releaseNotes || '';
// Reset progress state
updateProgressContainer.style.display = 'none';
updateProgressFill.style.width = '0%';
updateProgressText.textContent = '0%';
// Reset button states
updateInstallBtn.disabled = !updateInfo.downloadUrl;
updateLaterBtn.disabled = false;
updateInstallBtn.textContent = 'Install Update';
if (!updateInfo.downloadUrl) {
updateModalMessage.textContent += '\n(No downloadable asset found for this release)';
}
updateModalOverlay.style.display = 'flex';
}
async function handleInstallUpdate() {
if (!pendingUpdateInfo || !pendingUpdateInfo.downloadUrl) return;
// Disable controls during download
updateInstallBtn.disabled = true;
updateInstallBtn.textContent = 'Downloading...';
updateLaterBtn.disabled = true;
updateModalCloseBtn.style.display = 'none';
updateProgressContainer.style.display = 'flex';
try {
const result = await window.electronAPI.downloadAndInstallUpdate({
downloadUrl: pendingUpdateInfo.downloadUrl
});
if (result.success) {
updateInstallBtn.textContent = 'Restarting...';
updateProgressFill.style.width = '100%';
updateProgressText.textContent = '100%';
} else {
showStatus(connectionStatus, `Update failed: ${result.message}`, 'error');
closeUpdateModal();
}
} catch (error) {
console.error('Install update error:', error);
showStatus(connectionStatus, 'Update failed. Please try again.', 'error');
closeUpdateModal();
}
}
function closeUpdateModal() {
updateModalOverlay.style.display = 'none';
pendingUpdateInfo = null;
updateProgressContainer.style.display = 'none';
updateProgressFill.style.width = '0%';
updateProgressText.textContent = '0%';
updateInstallBtn.textContent = 'Install Update';
updateInstallBtn.disabled = false;
updateLaterBtn.disabled = false;
updateModalCloseBtn.style.display = '';
}
async function checkForUpdatesOnStartup() {
try {
const result = await window.electronAPI.checkForUpdates();
if (result.success && result.updateAvailable) {
checkUpdateBtn.classList.add('update-available');
checkUpdateBtn.title = `Update available: v${result.latestVersion}`;
showStatus(connectionStatus, `Update available: v${result.latestVersion}`, 'info');
// Store for quick modal open
pendingUpdateInfo = result;
}
} catch (error) {
// Silent fail on startup check
console.log('Startup update check failed:', error.message);
}
}
// Handle cookie received from Chrome extension via local HTTP bridge
async function handleExtensionCookie(data) {
const { deploymentUrl, cookies, cookieValue } = data;
@@ -473,4 +604,13 @@ document.addEventListener('DOMContentLoaded', async () => {
// Listen for cookies pushed from Chrome extension
window.electronAPI.onExtensionCookie(handleExtensionCookie);
// Listen for update download progress
window.electronAPI.onUpdateDownloadProgress((data) => {
updateProgressFill.style.width = `${data.percent}%`;
updateProgressText.textContent = `${data.percent}%`;
});
// Auto-check for updates after 2 seconds
setTimeout(checkForUpdatesOnStartup, 2000);
});