diff --git a/package-lock.json b/package-lock.json index b26465e..ad9ef82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index becf94a..071d8cc 100644 --- a/package.json +++ b/package.json @@ -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/**/*", diff --git a/src/main/index.ts b/src/main/index.ts index 5bc6d5c..5b377ab 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -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 diff --git a/src/main/preload.ts b/src/main/preload.ts index 33e602e..494ba05 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -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; startProxy: () => Promise; stopProxy: () => Promise; - checkVersion: () => Promise; - downloadUpdate: (downloadUrl: string) => Promise; + checkVersion: () => Promise<{ updateAvailable: boolean; latestVersion: string }>; + installUpdate: () => Promise; + getAppVersion: () => Promise; getSettings: () => Promise; saveSettings: (settings: any) => Promise; onUpdateAvailable: (callback: (versionInfo: any) => void) => void; + onDownloadProgress: (callback: (progress: any) => void) => void; + onUpdateDownloaded: (callback: (info: any) => void) => void; + onUpdateError: (callback: (error: any) => void) => void; }; } } diff --git a/src/main/updater.ts b/src/main/updater.ts index 3be7a98..cb173b5 100644 --- a/src/main/updater.ts +++ b/src/main/updater.ts @@ -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 { - 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(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 { - 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); + autoUpdater.on('update-downloaded', (info) => { + this.send('update-downloaded', { latestVersion: info.version }); + }); - response.data.pipe(writer); - - return new Promise((resolve, reject) => { - writer.on('finish', () => resolve(filePath)); - writer.on('error', reject); - }); - } catch (error: any) { - console.error('Error downloading update:', error.message); - throw new Error(`Failed to download update: ${error.message}`); - } + autoUpdater.on('error', (err) => { + console.error('[updater] error:', err.message); + this.send('update-error', { message: err.message }); + }); } - async installUpdate(installerPath: string): Promise { + async checkForUpdates(): Promise { + 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(); } } diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 360cdd1..9c02f17 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -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(null); + const [phase, setPhase] = useState('idle'); + const [versionInfo, setVersionInfo] = useState(null); + const [progress, setProgress] = useState(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 ( - {updateInfo && updateInfo.updateAvailable && ( + {phase !== 'idle' && versionInfo && ( setUpdateInfo(null)} + phase={phase} + versionInfo={versionInfo} + progress={progress} + onClose={() => setPhase('idle')} /> )} diff --git a/src/renderer/components/UpdateNotification.tsx b/src/renderer/components/UpdateNotification.tsx index 272fa9d..4abda00 100644 --- a/src/renderer/components/UpdateNotification.tsx +++ b/src/renderer/components/UpdateNotification.tsx @@ -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 = ({ + 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}\nПриложение будет закрыто для установки.`); - // App will close automatically after opening installer - } catch (error) { - console.error('Error downloading update:', error); - alert('Ошибка при загрузке обновления'); - setIsDownloading(false); - } + const handleInstall = () => { + window.electronAPI.installUpdate(); }; return (
-

Доступно обновление!

+

{phase === 'ready' ? 'Обновление готово' : 'Доступно обновление'}

Версия {versionInfo.latestVersion}

-
-

Что нового:

-
{versionInfo.changelog}
-
+ {versionInfo.changelog && ( +
+

Что нового:

+
{versionInfo.changelog}
+
+ )} + {phase === 'downloading' && ( +
+
+
+
+

+ {progress + ? `${Math.round(progress.percent)}% — ${formatBytes(progress.transferred)} / ${formatBytes(progress.total)}` + : 'Подготовка загрузки…'} +

+
+ )}
- + {phase === 'ready' && ( + + )}
diff --git a/src/renderer/styles/UpdateNotification.css b/src/renderer/styles/UpdateNotification.css index 56e3561..3479d84 100644 --- a/src/renderer/styles/UpdateNotification.css +++ b/src/renderer/styles/UpdateNotification.css @@ -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; diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 94af09c..066e86e 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -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; diff --git a/src/shared/types.ts b/src/shared/types.ts index 2b1decb..616c2e0 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -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',