feat: switch to electron-updater for auto-updates

Replaces the custom server-poll updater (which depended on a placeholder
serverUrl 'https://your-server.com/api' and never fired in practice) with
electron-updater. Now a banner appears automatically when a new release is
published — auto-download in background, in-place install via quitAndInstall,
delta updates via blockmap.

Changes:
- Add electron-updater dep, build.publish (provider: generic, placeholder URL)
- Rewrite UpdaterManager around autoUpdater events (available/progress/
  downloaded/error), forwarded to renderer via window.webContents.send
- Drop hardcoded APP_VERSION constant; main uses app.getVersion(), renderer
  fetches via new GET_APP_VERSION IPC channel
- IPC: drop DOWNLOAD_UPDATE (autoDownload handles it), add INSTALL_UPDATE
  + GET_APP_VERSION
- VersionInfo reshaped (currentVersion field, no downloadUrl/mandatory);
  add UpdateProgress and UpdateCheckResult types
- UpdateNotification: 3-phase UI (downloading with progress bar,
  ready with restart-and-install, hidden); App.tsx tracks phase state

TODO before first real release:
- Replace build.publish.url placeholder with the actual generic host
- Bump version, run package:win, upload latest.yml + .exe + .blockmap to host

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 00:13:19 +03:00
parent ecb5e7e49f
commit 571404ca94
10 changed files with 287 additions and 149 deletions

106
package-lock.json generated
View File

@@ -1,17 +1,18 @@
{
"name": "media-center",
"version": "1.0.0",
"version": "1.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "media-center",
"version": "1.0.0",
"version": "1.0.1",
"license": "MIT",
"dependencies": {
"axios": "^1.6.2",
"cheerio": "^1.0.0-rc.12",
"electron-store": "^8.1.0",
"electron-updater": "^6.8.3",
"nedb": "^1.8.0",
"uuid": "^9.0.1",
"xml2js": "^0.6.2"
@@ -2197,7 +2198,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/assert-plus": {
@@ -3135,7 +3135,6 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -3698,6 +3697,82 @@
"dev": true,
"license": "ISC"
},
"node_modules/electron-updater": {
"version": "6.8.3",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.3.tgz",
"integrity": "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==",
"license": "MIT",
"dependencies": {
"builder-util-runtime": "9.5.1",
"fs-extra": "^10.1.0",
"js-yaml": "^4.1.0",
"lazy-val": "^1.0.5",
"lodash.escaperegexp": "^4.1.2",
"lodash.isequal": "^4.5.0",
"semver": "~7.7.3",
"tiny-typed-emitter": "^2.1.0"
}
},
"node_modules/electron-updater/node_modules/builder-util-runtime": {
"version": "9.5.1",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz",
"integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/electron-updater/node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/electron-updater/node_modules/jsonfile": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/electron-updater/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/electron-updater/node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/electron/node_modules/@types/node": {
"version": "18.19.130",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
@@ -4334,7 +4409,6 @@
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC"
},
"node_modules/has-flag": {
@@ -4709,7 +4783,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -4796,7 +4869,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
"dev": true,
"license": "MIT"
},
"node_modules/lazystream": {
@@ -4903,6 +4975,12 @@
"license": "MIT",
"peer": true
},
"node_modules/lodash.escaperegexp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
"license": "MIT"
},
"node_modules/lodash.flatten": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
@@ -4911,6 +4989,13 @@
"license": "MIT",
"peer": true
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@@ -5118,7 +5203,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
@@ -6251,6 +6335,12 @@
"node": ">= 10.0.0"
}
},
"node_modules/tiny-typed-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
"license": "MIT"
},
"node_modules/tmp": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",

View File

@@ -23,6 +23,7 @@
"axios": "^1.6.2",
"cheerio": "^1.0.0-rc.12",
"electron-store": "^8.1.0",
"electron-updater": "^6.8.3",
"nedb": "^1.8.0",
"uuid": "^9.0.1",
"xml2js": "^0.6.2"
@@ -53,6 +54,10 @@
"directories": {
"output": "release"
},
"publish": {
"provider": "generic",
"url": "https://your-update-server.example.com/media-center/"
},
"files": [
"dist/**/*",
"node_modules/**/*",

View File

@@ -51,7 +51,7 @@ async function initializeApp() {
proxyManager = new ProxyManager();
configManager = new ConfigManager(userDataPath);
databaseManager = new DatabaseManager(userDataPath);
updaterManager = new UpdaterManager(configManager);
updaterManager = new UpdaterManager(() => mainWindow);
tabManager = new TabManager();
// Load initial configuration
@@ -69,11 +69,7 @@ async function initializeApp() {
// Check for updates
setTimeout(() => {
updaterManager.checkForUpdates().then((versionInfo) => {
if (versionInfo.updateAvailable && mainWindow) {
mainWindow.webContents.send('update-available', versionInfo);
}
});
updaterManager.checkForUpdates();
}, 5000);
}
@@ -169,8 +165,12 @@ function setupIpcHandlers() {
return await updaterManager.checkForUpdates();
});
ipcMain.handle(IPC_CHANNELS.DOWNLOAD_UPDATE, async (_, downloadUrl: string) => {
return await updaterManager.downloadUpdate(downloadUrl);
ipcMain.handle(IPC_CHANNELS.INSTALL_UPDATE, () => {
updaterManager.installUpdate();
});
ipcMain.handle(IPC_CHANNELS.GET_APP_VERSION, () => {
return app.getVersion();
});
// Settings handlers

View File

@@ -17,7 +17,8 @@ const IPC_CHANNELS = {
START_PROXY: 'proxy:start',
STOP_PROXY: 'proxy:stop',
CHECK_VERSION: 'version:check',
DOWNLOAD_UPDATE: 'version:download',
INSTALL_UPDATE: 'version:install',
GET_APP_VERSION: 'app:version',
GET_SETTINGS: 'settings:get',
SAVE_SETTINGS: 'settings:save',
};
@@ -53,20 +54,29 @@ contextBridge.exposeInMainWorld('electronAPI', {
startProxy: () => ipcRenderer.invoke(IPC_CHANNELS.START_PROXY),
stopProxy: () => ipcRenderer.invoke(IPC_CHANNELS.STOP_PROXY),
// Version
// Version / Updates
checkVersion: () => ipcRenderer.invoke(IPC_CHANNELS.CHECK_VERSION),
downloadUpdate: (downloadUrl: string) =>
ipcRenderer.invoke(IPC_CHANNELS.DOWNLOAD_UPDATE, downloadUrl),
installUpdate: () => ipcRenderer.invoke(IPC_CHANNELS.INSTALL_UPDATE),
getAppVersion: () => ipcRenderer.invoke(IPC_CHANNELS.GET_APP_VERSION),
// Settings
getSettings: () => ipcRenderer.invoke(IPC_CHANNELS.GET_SETTINGS),
saveSettings: (settings: any) =>
ipcRenderer.invoke(IPC_CHANNELS.SAVE_SETTINGS, settings),
// Event listeners
// Update event listeners
onUpdateAvailable: (callback: (versionInfo: any) => void) => {
ipcRenderer.on('update-available', (_, versionInfo) => callback(versionInfo));
},
onDownloadProgress: (callback: (progress: any) => void) => {
ipcRenderer.on('update-download-progress', (_, progress) => callback(progress));
},
onUpdateDownloaded: (callback: (info: any) => void) => {
ipcRenderer.on('update-downloaded', (_, info) => callback(info));
},
onUpdateError: (callback: (error: any) => void) => {
ipcRenderer.on('update-error', (_, error) => callback(error));
},
});
// Type declaration for TypeScript
@@ -87,11 +97,15 @@ declare global {
getProxyStatus: () => Promise<any>;
startProxy: () => Promise<any>;
stopProxy: () => Promise<any>;
checkVersion: () => Promise<any>;
downloadUpdate: (downloadUrl: string) => Promise<string>;
checkVersion: () => Promise<{ updateAvailable: boolean; latestVersion: string }>;
installUpdate: () => Promise<void>;
getAppVersion: () => Promise<string>;
getSettings: () => Promise<any>;
saveSettings: (settings: any) => Promise<void>;
onUpdateAvailable: (callback: (versionInfo: any) => void) => void;
onDownloadProgress: (callback: (progress: any) => void) => void;
onUpdateDownloaded: (callback: (info: any) => void) => void;
onUpdateError: (callback: (error: any) => void) => void;
};
}
}

View File

@@ -1,99 +1,71 @@
import axios from 'axios';
import * as fs from 'fs';
import * as path from 'path';
import { app, shell } from 'electron';
import { VersionInfo } from '../shared/types';
import { APP_VERSION } from '../shared/constants';
import { ConfigManager } from './config';
import { app, BrowserWindow } from 'electron';
import { autoUpdater } from 'electron-updater';
import type { UpdateCheckResult } from '../shared/types';
export class UpdaterManager {
private configManager: ConfigManager;
private getWindow: () => BrowserWindow | null;
constructor(configManager: ConfigManager) {
this.configManager = configManager;
constructor(getWindow: () => BrowserWindow | null) {
this.getWindow = getWindow;
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = true;
this.wireEvents();
}
async checkForUpdates(): Promise<VersionInfo> {
try {
const settings = await this.configManager.getConfig();
const serverUrl = (settings as any).serverUrl || 'https://your-server.com/api';
const endpoint = `${serverUrl}/version/check`;
private send(channel: string, payload?: unknown) {
const win = this.getWindow();
if (win && !win.isDestroyed()) {
win.webContents.send(channel, payload);
}
}
const response = await axios.get<VersionInfo>(endpoint, {
params: {
currentVersion: APP_VERSION,
platform: process.platform,
},
timeout: 10000,
private wireEvents() {
autoUpdater.on('update-available', (info) => {
this.send('update-available', {
latestVersion: info.version,
currentVersion: app.getVersion(),
releaseDate: info.releaseDate ?? '',
changelog: typeof info.releaseNotes === 'string' ? info.releaseNotes : '',
});
});
return response.data;
} catch (error: any) {
console.error('Error checking for updates:', error.message);
// Return no update available if check fails
return {
latestVersion: APP_VERSION,
updateAvailable: false,
downloadUrl: '',
changelog: '',
releaseDate: '',
mandatory: false,
};
}
}
async downloadUpdate(downloadUrl: string): Promise<string> {
try {
const downloadsPath = app.getPath('downloads');
const fileName = path.basename(new URL(downloadUrl).pathname);
const filePath = path.join(downloadsPath, fileName);
const response = await axios.get(downloadUrl, {
responseType: 'stream',
timeout: 300000, // 5 minutes
autoUpdater.on('download-progress', (progress) => {
this.send('update-download-progress', {
percent: progress.percent,
bytesPerSecond: progress.bytesPerSecond,
transferred: progress.transferred,
total: progress.total,
});
});
const writer = fs.createWriteStream(filePath);
response.data.pipe(writer);
return new Promise((resolve, reject) => {
writer.on('finish', () => resolve(filePath));
writer.on('error', reject);
autoUpdater.on('update-downloaded', (info) => {
this.send('update-downloaded', { latestVersion: info.version });
});
autoUpdater.on('error', (err) => {
console.error('[updater] error:', err.message);
this.send('update-error', { message: err.message });
});
} catch (error: any) {
console.error('Error downloading update:', error.message);
throw new Error(`Failed to download update: ${error.message}`);
}
}
async installUpdate(installerPath: string): Promise<void> {
async checkForUpdates(): Promise<UpdateCheckResult> {
const currentVersion = app.getVersion();
if (!app.isPackaged) {
return { updateAvailable: false, latestVersion: currentVersion };
}
try {
// Open the installer
await shell.openPath(installerPath);
// Close the app after a short delay
setTimeout(() => {
app.quit();
}, 1000);
} catch (error: any) {
console.error('Error installing update:', error.message);
throw new Error(`Failed to install update: ${error.message}`);
const result = await autoUpdater.checkForUpdates();
const latestVersion = result?.updateInfo?.version ?? currentVersion;
return { updateAvailable: latestVersion !== currentVersion, latestVersion };
} catch (err: any) {
console.error('[updater] check failed:', err.message);
return { updateAvailable: false, latestVersion: currentVersion };
}
}
compareVersions(v1: string, v2: string): number {
const parts1 = v1.split('.').map(Number);
const parts2 = v2.split('.').map(Number);
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const part1 = parts1[i] || 0;
const part2 = parts2[i] || 0;
if (part1 > part2) return 1;
if (part1 < part2) return -1;
}
return 0;
installUpdate(): void {
autoUpdater.quitAndInstall();
}
}

View File

@@ -6,25 +6,38 @@ import Bookmarks from './pages/Bookmarks';
import ActiveTabs from './pages/ActiveTabs';
import Settings from './pages/Settings';
import UpdateNotification from './components/UpdateNotification';
import { VersionInfo } from '../shared/types';
import { VersionInfo, UpdateProgress } from '../shared/types';
type UpdatePhase = 'idle' | 'downloading' | 'ready';
function App() {
const [updateInfo, setUpdateInfo] = useState<VersionInfo | null>(null);
const [phase, setPhase] = useState<UpdatePhase>('idle');
const [versionInfo, setVersionInfo] = useState<VersionInfo | null>(null);
const [progress, setProgress] = useState<UpdateProgress | null>(null);
useEffect(() => {
// Listen for update notifications
window.electronAPI.onUpdateAvailable((versionInfo) => {
setUpdateInfo(versionInfo);
window.electronAPI.onUpdateAvailable((info) => {
setVersionInfo(info);
setPhase('downloading');
});
window.electronAPI.onDownloadProgress((p) => {
setProgress(p);
});
window.electronAPI.onUpdateDownloaded(() => {
setPhase('ready');
setProgress(null);
});
}, []);
return (
<BrowserRouter>
<Layout>
{updateInfo && updateInfo.updateAvailable && (
{phase !== 'idle' && versionInfo && (
<UpdateNotification
versionInfo={updateInfo}
onClose={() => setUpdateInfo(null)}
phase={phase}
versionInfo={versionInfo}
progress={progress}
onClose={() => setPhase('idle')}
/>
)}
<Routes>

View File

@@ -1,55 +1,66 @@
import React, { useState } from 'react';
import { VersionInfo } from '../../shared/types';
import React from 'react';
import { VersionInfo, UpdateProgress } from '../../shared/types';
import '../styles/UpdateNotification.css';
interface UpdateNotificationProps {
phase: 'downloading' | 'ready';
versionInfo: VersionInfo;
progress: UpdateProgress | null;
onClose: () => void;
}
function formatBytes(bytes: number): string {
if (bytes < 1024) return `${bytes} Б`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} КБ`;
return `${(bytes / (1024 * 1024)).toFixed(1)} МБ`;
}
const UpdateNotification: React.FC<UpdateNotificationProps> = ({
phase,
versionInfo,
progress,
onClose,
}) => {
const [isDownloading, setIsDownloading] = useState(false);
const handleUpdate = async () => {
setIsDownloading(true);
try {
const installerPath = await window.electronAPI.downloadUpdate(
versionInfo.downloadUrl
);
alert(`Обновление загружено: ${installerPath}\риложение будет закрыто для установки.`);
// App will close automatically after opening installer
} catch (error) {
console.error('Error downloading update:', error);
alert('Ошибка при загрузке обновления');
setIsDownloading(false);
}
const handleInstall = () => {
window.electronAPI.installUpdate();
};
return (
<div className="update-notification">
<div className="update-content">
<h3>Доступно обновление!</h3>
<h3>{phase === 'ready' ? 'Обновление готово' : 'Доступно обновление'}</h3>
<p className="version">
Версия <strong>{versionInfo.latestVersion}</strong>
</p>
{versionInfo.changelog && (
<div className="changelog">
<h4>Что нового:</h4>
<pre>{versionInfo.changelog}</pre>
</div>
)}
{phase === 'downloading' && (
<div className="update-progress">
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress?.percent ?? 0}%` }}
/>
</div>
<p className="progress-label">
{progress
? `${Math.round(progress.percent)}% — ${formatBytes(progress.transferred)} / ${formatBytes(progress.total)}`
: 'Подготовка загрузки…'}
</p>
</div>
)}
<div className="update-actions">
<button
className="button-primary"
onClick={handleUpdate}
disabled={isDownloading}
>
{isDownloading ? 'Загрузка...' : 'Обновить сейчас'}
{phase === 'ready' && (
<button className="button-primary" onClick={handleInstall}>
Перезапустить и установить
</button>
)}
<button className="button-secondary" onClick={onClose}>
Напомнить позже
{phase === 'ready' ? 'Позже' : 'Скрыть'}
</button>
</div>
</div>

View File

@@ -59,6 +59,30 @@
color: var(--text-secondary);
}
.update-progress {
margin-bottom: 1rem;
}
.progress-bar {
width: 100%;
height: 6px;
background-color: var(--bg-tertiary);
border-radius: 3px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background-color: var(--accent);
transition: width 0.2s ease-out;
}
.progress-label {
margin: 0.5rem 0 0 0;
font-size: 0.85rem;
color: var(--text-secondary);
}
.update-actions {
display: flex;
gap: 0.75rem;

View File

@@ -1,5 +1,3 @@
export const APP_VERSION = '1.0.1';
export const DEFAULT_CONFIG_SERVER_URL = 'https://your-server.com/api';
export const DEFAULT_PROXY_PORT = 10808;

View File

@@ -59,11 +59,21 @@ export interface ActiveTab {
// Version Check Types
export interface VersionInfo {
latestVersion: string;
updateAvailable: boolean;
downloadUrl: string;
changelog: string;
currentVersion: string;
releaseDate: string;
mandatory: boolean;
changelog: string;
}
export interface UpdateProgress {
percent: number;
bytesPerSecond: number;
transferred: number;
total: number;
}
export interface UpdateCheckResult {
updateAvailable: boolean;
latestVersion: string;
}
// Proxy Types
@@ -113,7 +123,8 @@ export enum IPC_CHANNELS {
// Version
CHECK_VERSION = 'version:check',
DOWNLOAD_UPDATE = 'version:download',
INSTALL_UPDATE = 'version:install',
GET_APP_VERSION = 'app:version',
// Settings
GET_SETTINGS = 'settings:get',