import * as path from 'path'; import * as fs from 'fs-extra'; import { autoUpdater, UpdateInfo } from 'electron-updater'; import { app, BrowserWindow } from 'electron'; import { markShouldQuit } from '../../app/window_state'; import { getPrintableError, LoggerType, MessagesType, showCannotUpdateDialog, showDownloadUpdateDialog, showUpdateDialog, } from './common'; import { gt as isVersionGreaterThan, parse as parseVersion } from 'semver'; let isUpdating = false; let downloadIgnored = false; let interval: NodeJS.Timeout | undefined; let stopped = false; export async function start( getMainWindow: () => BrowserWindow, messages: MessagesType, logger: LoggerType ) { if (interval) { logger.info('auto-update: Already running'); return; } logger.info('auto-update: starting checks...'); autoUpdater.logger = logger; autoUpdater.autoDownload = false; interval = global.setInterval(async () => { try { await checkForUpdates(getMainWindow, messages, logger); } catch (error) { logger.error('auto-update: error:', getPrintableError(error)); } }, 1000 * 60 * 10); // trigger and try to update every 10 minutes to let the file gets downloaded if we are updating stopped = false; await checkForUpdates(getMainWindow, messages, logger); } export function stop() { if (interval) { clearInterval(interval); interval = undefined; } stopped = true; } async function checkForUpdates( getMainWindow: () => BrowserWindow, messages: MessagesType, logger: LoggerType ) { logger.info('[updater] checkForUpdates'); if (stopped || isUpdating || downloadIgnored) { return; } const canUpdate = await canAutoUpdate(); logger.info('[updater] canUpdate', canUpdate); if (!canUpdate) { logger.info('checkForUpdates canAutoUpdate false'); return; } logger.info('[updater] checkForUpdates...'); isUpdating = true; try { const latestVersionFromFsFromRenderer = getMainWindow() ? ((getMainWindow() as any).getLatestDesktopRelease() as string | undefined) : undefined; logger.info('[updater] latestVersionFromFsFromRenderer', latestVersionFromFsFromRenderer); if (!latestVersionFromFsFromRenderer || !latestVersionFromFsFromRenderer?.length) { logger.info( '[updater] testVersionFromFsFromRenderer was not updated yet by renderer. Skipping update check' ); return; } const currentVersion = autoUpdater.currentVersion.toString(); const isMoreRecent = isVersionGreaterThan(latestVersionFromFsFromRenderer, currentVersion); logger.info('[updater] checkForUpdates isMoreRecent', isMoreRecent); if (!isMoreRecent) { logger.info( `Fileserver has no update so we are not looking for an update from github current:${currentVersion} fromFileServer:${latestVersionFromFsFromRenderer}` ); return; } // Get the update using electron-updater, this fetches from github const result = await autoUpdater.checkForUpdates(); logger.info('[updater] checkForUpdates got github response back '); if (!result.updateInfo) { logger.info('[updater] no update info received'); return; } try { const hasUpdate = isUpdateAvailable(result.updateInfo); logger.info('[updater] hasUpdate:', hasUpdate); if (!hasUpdate) { logger.info('[updater] no update available'); return; } logger.info('[updater] showing download dialog...'); const shouldDownload = await showDownloadUpdateDialog(getMainWindow(), messages); logger.info('[updater] shouldDownload:', shouldDownload); if (!shouldDownload) { downloadIgnored = true; return; } await autoUpdater.downloadUpdate(); } catch (error) { await showCannotUpdateDialog(getMainWindow(), messages); throw error; } // Update downloaded successfully, we should ask the user to update logger.info('[updater] showing update dialog...'); const shouldUpdate = await showUpdateDialog(getMainWindow(), messages); if (!shouldUpdate) { return; } logger.info('[updater] calling quitAndInstall...'); markShouldQuit(); autoUpdater.quitAndInstall(); } finally { isUpdating = false; } } function isUpdateAvailable(updateInfo: UpdateInfo): boolean { const latestVersion = parseVersion(updateInfo.version); if (!latestVersion) { return false; } // We need to convert this to string because typescript won't let us use types across submodules .... const currentVersion = autoUpdater.currentVersion.toString(); return isVersionGreaterThan(latestVersion, currentVersion); } /* Check if we have the required files to auto update. These files won't exist inside certain formats such as a linux deb file. */ async function canAutoUpdate(): Promise { const isPackaged = app.isPackaged; // On a production app, we need to use resources path to check for the file if (isPackaged && !process.resourcesPath) { return false; } // Taken from: https://github.com/electron-userland/electron-builder/blob/d4feb6d3c8b008f8b455c761d654c8088f90d8fa/packages/electron-updater/src/ElectronAppAdapter.ts#L25 const updateFile = isPackaged ? 'app-update.yml' : 'dev-app-update.yml'; const basePath = isPackaged && process.resourcesPath ? process.resourcesPath : app.getAppPath(); const appUpdateConfigPath = path.join(basePath, updateFile); return new Promise(resolve => { try { // tslint:disable-next-line: non-literal-fs-path const exists = fs.existsSync(appUpdateConfigPath); resolve(exists); } catch (e) { resolve(false); } }); }