New option for control over update downloads

This commit is contained in:
Josh Perez
2021-08-19 18:56:29 -04:00
committed by GitHub
parent 80c1ad6ee3
commit e9308bbafb
49 changed files with 1230 additions and 803 deletions

View File

@@ -9,6 +9,7 @@ import {
} from 'fs';
import { join, normalize } from 'path';
import { tmpdir } from 'os';
import { throttle } from 'lodash';
import { createParser, ParserConfiguration } from 'dashdash';
import ProxyAgent from 'proxy-agent';
@@ -20,10 +21,10 @@ import { v4 as getGuid } from 'uuid';
import pify from 'pify';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import { app, BrowserWindow, dialog, ipcMain } from 'electron';
import { app, BrowserWindow, ipcMain } from 'electron';
import { getTempPath } from '../../app/attachments';
import { Dialogs } from '../types/Dialogs';
import { DialogType } from '../types/Dialogs';
import { getUserAgent } from '../util/getUserAgent';
import { isAlpha, isBeta } from '../util/version';
@@ -31,7 +32,6 @@ import * as packageJson from '../../package.json';
import { getSignatureFileName } from './signature';
import { isPathInside } from '../util/isPathInside';
import { LocaleType } from '../types/I18N';
import { LoggerType } from '../types/Logging';
const writeFile = pify(writeFileCallback);
@@ -39,24 +39,40 @@ const mkdirpPromise = pify(mkdirp);
const rimrafPromise = pify(rimraf);
const { platform } = process;
export const ACK_RENDER_TIMEOUT = 10000;
export const GOT_CONNECT_TIMEOUT = 2 * 60 * 1000;
export const GOT_LOOKUP_TIMEOUT = 2 * 60 * 1000;
export const GOT_SOCKET_TIMEOUT = 2 * 60 * 1000;
type JSONUpdateSchema = {
version: string;
files: Array<{
url: string;
sha512: string;
size: string;
blockMapSize?: string;
}>;
path: string;
sha512: string;
releaseDate: string;
};
export type UpdaterInterface = {
force(): Promise<void>;
};
export type UpdateInformationType = {
fileName: string;
size: number;
version: string;
};
export async function checkForUpdates(
logger: LoggerType,
forceUpdate = false
): Promise<{
fileName: string;
version: string;
} | null> {
): Promise<UpdateInformationType | null> {
const yaml = await getUpdateYaml();
const version = getVersion(yaml);
const parsedYaml = parseYaml(yaml);
const version = getVersion(parsedYaml);
if (!version) {
logger.warn('checkForUpdates: no version extracted from downloaded yaml');
@@ -70,8 +86,11 @@ export async function checkForUpdates(
`forceUpdate=${forceUpdate}`
);
const fileName = getUpdateFileName(parsedYaml);
return {
fileName: getUpdateFileName(yaml),
fileName,
size: getSize(parsedYaml, fileName),
version,
};
}
@@ -95,7 +114,8 @@ export function validatePath(basePath: string, targetPath: string): void {
export async function downloadUpdate(
fileName: string,
logger: LoggerType
logger: LoggerType,
mainWindow?: BrowserWindow
): Promise<string> {
const baseUrl = getUpdatesBase();
const updateFileUrl = `${baseUrl}/${fileName}`;
@@ -121,6 +141,23 @@ export async function downloadUpdate(
const writeStream = createWriteStream(targetUpdatePath);
await new Promise<void>((resolve, reject) => {
if (mainWindow) {
let downloadedSize = 0;
const throttledSend = throttle(() => {
mainWindow.webContents.send(
'show-update-dialog',
DialogType.Downloading,
{ downloadedSize }
);
}, 500);
downloadStream.on('data', data => {
downloadedSize += data.length;
throttledSend();
});
}
downloadStream.on('error', error => {
reject(error);
});
@@ -144,106 +181,6 @@ export async function downloadUpdate(
}
}
let showingUpdateDialog = false;
async function showFallbackUpdateDialog(
mainWindow: BrowserWindow,
locale: LocaleType
): Promise<boolean> {
if (showingUpdateDialog) {
return false;
}
const RESTART_BUTTON = 0;
const LATER_BUTTON = 1;
const options = {
type: 'info',
buttons: [
locale.messages.autoUpdateRestartButtonLabel.message,
locale.messages.autoUpdateLaterButtonLabel.message,
],
title: locale.messages.autoUpdateNewVersionTitle.message,
message: locale.messages.autoUpdateNewVersionMessage.message,
detail: locale.messages.autoUpdateNewVersionInstructions.message,
defaultId: LATER_BUTTON,
cancelId: LATER_BUTTON,
};
showingUpdateDialog = true;
const { response } = await dialog.showMessageBox(mainWindow, options);
showingUpdateDialog = false;
return response === RESTART_BUTTON;
}
export function showUpdateDialog(
mainWindow: BrowserWindow,
locale: LocaleType,
performUpdateCallback: () => void
): void {
let ack = false;
ipcMain.once('show-update-dialog-ack', () => {
ack = true;
});
mainWindow.webContents.send('show-update-dialog', Dialogs.Update);
setTimeout(async () => {
if (!ack) {
const shouldUpdate = await showFallbackUpdateDialog(mainWindow, locale);
if (shouldUpdate) {
performUpdateCallback();
}
}
}, ACK_RENDER_TIMEOUT);
}
let showingCannotUpdateDialog = false;
async function showFallbackCannotUpdateDialog(
mainWindow: BrowserWindow,
locale: LocaleType
): Promise<void> {
if (showingCannotUpdateDialog) {
return;
}
const options = {
type: 'error',
buttons: [locale.messages.ok.message],
title: locale.messages.cannotUpdate.message,
message: locale.i18n('cannotUpdateDetail', ['https://signal.org/download']),
};
showingCannotUpdateDialog = true;
await dialog.showMessageBox(mainWindow, options);
showingCannotUpdateDialog = false;
}
export function showCannotUpdateDialog(
mainWindow: BrowserWindow,
locale: LocaleType
): void {
let ack = false;
ipcMain.once('show-update-dialog-ack', () => {
ack = true;
});
mainWindow.webContents.send('show-update-dialog', Dialogs.Cannot_Update);
setTimeout(async () => {
if (!ack) {
await showFallbackCannotUpdateDialog(mainWindow, locale);
}
}, ACK_RENDER_TIMEOUT);
}
// Helper functions
export function getUpdateCheckUrl(): string {
@@ -288,9 +225,7 @@ function isVersionNewer(newVersion: string): boolean {
return gt(newVersion, version);
}
export function getVersion(yaml: string): string | null {
const info = parseYaml(yaml);
export function getVersion(info: JSONUpdateSchema): string | null {
return info && info.version;
}
@@ -299,11 +234,7 @@ export function isUpdateFileNameValid(name: string): boolean {
return validFile.test(name);
}
// Reliant on third party parser that returns any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getUpdateFileName(yaml: string): any {
const info = parseYaml(yaml);
export function getUpdateFileName(info: JSONUpdateSchema): string {
if (!info || !info.path) {
throw new Error('getUpdateFileName: No path present in YAML file');
}
@@ -318,9 +249,17 @@ export function getUpdateFileName(yaml: string): any {
return path;
}
// Reliant on third party parser that returns any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function parseYaml(yaml: string): any {
function getSize(info: JSONUpdateSchema, fileName: string): number {
if (!info || !info.files) {
throw new Error('getUpdateFileName: No files present in YAML file');
}
const foundFile = info.files.find(file => file.url === fileName);
return Number(foundFile?.size) || 0;
}
export function parseYaml(yaml: string): JSONUpdateSchema {
return safeLoad(yaml, { schema: FAILSAFE_SCHEMA, json: true });
}
@@ -413,3 +352,21 @@ export function getCliOptions<T>(options: ParserConfiguration['options']): T {
export function setUpdateListener(performUpdateCallback: () => void): void {
ipcMain.once('start-update', performUpdateCallback);
}
export function getAutoDownloadUpdateSetting(
mainWindow: BrowserWindow
): Promise<boolean> {
return new Promise((resolve, reject) => {
ipcMain.once(
'settings:get-success:autoDownloadUpdate',
(_, error, value: boolean) => {
if (error) {
reject(error);
} else {
resolve(value);
}
}
);
mainWindow.webContents.send('settings:get:autoDownloadUpdate');
});
}

View File

@@ -7,7 +7,6 @@ import { BrowserWindow } from 'electron';
import { UpdaterInterface } from './common';
import { start as startMacOS } from './macos';
import { start as startWindows } from './windows';
import { LocaleType } from '../types/I18N';
import { LoggerType } from '../types/Logging';
let initialized = false;
@@ -16,7 +15,6 @@ let updater: UpdaterInterface | undefined;
export async function start(
getMainWindow: () => BrowserWindow,
locale?: LocaleType,
logger?: LoggerType
): Promise<void> {
const { platform } = process;
@@ -26,9 +24,6 @@ export async function start(
}
initialized = true;
if (!locale) {
throw new Error('updater/start: Must provide locale!');
}
if (!logger) {
throw new Error('updater/start: Must provide logger!');
}
@@ -42,9 +37,9 @@ export async function start(
}
if (platform === 'win32') {
updater = await startWindows(getMainWindow, locale, logger);
updater = await startWindows(getMainWindow, logger);
} else if (platform === 'darwin') {
updater = await startMacOS(getMainWindow, locale, logger);
updater = await startMacOS(getMainWindow, logger);
} else {
throw new Error('updater/start: Unsupported platform');
}

View File

@@ -7,27 +7,25 @@ import { AddressInfo } from 'net';
import { dirname } from 'path';
import { v4 as getGuid } from 'uuid';
import { app, autoUpdater, BrowserWindow, dialog, ipcMain } from 'electron';
import { app, autoUpdater, BrowserWindow } from 'electron';
import { get as getFromConfig } from 'config';
import { gt } from 'semver';
import got from 'got';
import {
ACK_RENDER_TIMEOUT,
checkForUpdates,
deleteTempDir,
downloadUpdate,
getAutoDownloadUpdateSetting,
getPrintableError,
setUpdateListener,
showCannotUpdateDialog,
showUpdateDialog,
UpdaterInterface,
UpdateInformationType,
} from './common';
import { LocaleType } from '../types/I18N';
import { LoggerType } from '../types/Logging';
import { hexToBinary, verifySignature } from './signature';
import { markShouldQuit } from '../../app/window_state';
import { Dialogs } from '../types/Dialogs';
import { DialogType } from '../types/Dialogs';
const SECOND = 1000;
const MINUTE = SECOND * 60;
@@ -35,7 +33,6 @@ const INTERVAL = MINUTE * 30;
export async function start(
getMainWindow: () => BrowserWindow,
locale: LocaleType,
logger: LoggerType
): Promise<UpdaterInterface> {
logger.info('macos/start: starting checks...');
@@ -45,19 +42,17 @@ export async function start(
setInterval(async () => {
try {
await checkDownloadAndInstall(getMainWindow, locale, logger);
await checkForUpdatesMaybeInstall(getMainWindow, logger);
} catch (error) {
logger.error('macos/start: error:', getPrintableError(error));
logger.error(`macos/start: ${getPrintableError(error)}`);
}
}, INTERVAL);
setUpdateListener(createUpdater(logger));
await checkDownloadAndInstall(getMainWindow, locale, logger);
await checkForUpdatesMaybeInstall(getMainWindow, logger);
return {
async force(): Promise<void> {
return checkDownloadAndInstall(getMainWindow, locale, logger, true);
return checkForUpdatesMaybeInstall(getMainWindow, logger, true);
},
};
}
@@ -67,39 +62,69 @@ let version: string;
let updateFilePath: string;
let loggerForQuitHandler: LoggerType;
async function checkDownloadAndInstall(
async function checkForUpdatesMaybeInstall(
getMainWindow: () => BrowserWindow,
locale: LocaleType,
logger: LoggerType,
force = false
) {
logger.info('checkDownloadAndInstall: checking for update...');
try {
const result = await checkForUpdates(logger, force);
if (!result) {
logger.info('checkForUpdatesMaybeInstall: checking for update...');
const result = await checkForUpdates(logger, force);
if (!result) {
return;
}
const { fileName: newFileName, version: newVersion } = result;
setUpdateListener(createUpdater(getMainWindow, result, logger));
if (fileName !== newFileName || !version || gt(newVersion, version)) {
const autoDownloadUpdates = await getAutoDownloadUpdateSetting(
getMainWindow()
);
if (!autoDownloadUpdates) {
getMainWindow().webContents.send(
'show-update-dialog',
DialogType.DownloadReady,
{
downloadSize: result.size,
version: result.version,
}
);
return;
}
await downloadAndInstall(newFileName, newVersion, getMainWindow, logger);
}
}
const { fileName: newFileName, version: newVersion } = result;
if (fileName !== newFileName || !version || gt(newVersion, version)) {
const oldFileName = fileName;
const oldVersion = version;
async function downloadAndInstall(
newFileName: string,
newVersion: string,
getMainWindow: () => BrowserWindow,
logger: LoggerType,
updateOnProgress?: boolean
) {
try {
const oldFileName = fileName;
const oldVersion = version;
deleteCache(updateFilePath, logger);
fileName = newFileName;
version = newVersion;
try {
updateFilePath = await downloadUpdate(fileName, logger);
} catch (error) {
// Restore state in case of download error
fileName = oldFileName;
version = oldVersion;
throw error;
}
deleteCache(updateFilePath, logger);
fileName = newFileName;
version = newVersion;
try {
updateFilePath = await downloadUpdate(
fileName,
logger,
updateOnProgress ? getMainWindow() : undefined
);
} catch (error) {
// Restore state in case of download error
fileName = oldFileName;
version = oldVersion;
throw error;
}
if (!updateFilePath) {
logger.info('checkDownloadAndInstall: no update file path. Skipping!');
logger.info('downloadAndInstall: no update file path. Skipping!');
return;
}
@@ -109,7 +134,7 @@ async function checkDownloadAndInstall(
// Note: We don't delete the cache here, because we don't want to continually
// re-download the broken release. We will download it only once per launch.
throw new Error(
`checkDownloadAndInstall: Downloaded update did not pass signature verification (version: '${version}'; fileName: '${fileName}')`
`downloadAndInstall: Downloaded update did not pass signature verification (version: '${version}'; fileName: '${fileName}')`
);
}
@@ -119,13 +144,19 @@ async function checkDownloadAndInstall(
const readOnly = 'Cannot update while running on a read-only volume';
const message: string = error.message || '';
if (message.includes(readOnly)) {
logger.info('checkDownloadAndInstall: showing read-only dialog...');
showReadOnlyDialog(getMainWindow(), locale);
logger.info('downloadAndInstall: showing read-only dialog...');
getMainWindow().webContents.send(
'show-update-dialog',
DialogType.MacOS_Read_Only
);
} else {
logger.info(
'checkDownloadAndInstall: showing general update failure dialog...'
'downloadAndInstall: showing general update failure dialog...'
);
getMainWindow().webContents.send(
'show-update-dialog',
DialogType.Cannot_Update
);
showCannotUpdateDialog(getMainWindow(), locale);
}
throw error;
@@ -133,12 +164,13 @@ async function checkDownloadAndInstall(
// At this point, closing the app will cause the update to be installed automatically
// because Squirrel has cached the update file and will do the right thing.
logger.info('downloadAndInstall: showing update dialog...');
logger.info('checkDownloadAndInstall: showing update dialog...');
showUpdateDialog(getMainWindow(), locale, createUpdater(logger));
getMainWindow().webContents.send('show-update-dialog', DialogType.Update, {
version,
});
} catch (error) {
logger.error('checkDownloadAndInstall: error', getPrintableError(error));
logger.error(`downloadAndInstall: ${getPrintableError(error)}`);
}
}
@@ -152,10 +184,7 @@ function deleteCache(filePath: string | null, logger: LoggerType) {
if (filePath) {
const tempDir = dirname(filePath);
deleteTempDir(tempDir).catch(error => {
logger.error(
'quitHandler: error deleting temporary directory:',
getPrintableError(error)
);
logger.error(`quitHandler: ${getPrintableError(error)}`);
});
}
}
@@ -171,10 +200,7 @@ async function handToAutoUpdate(
let serverUrl: string;
server.on('error', (error: Error) => {
logger.error(
'handToAutoUpdate: server had error',
getPrintableError(error)
);
logger.error(`handToAutoUpdate: ${getPrintableError(error)}`);
shutdown(server, logger);
reject(error);
});
@@ -254,8 +280,9 @@ function pipeUpdateToSquirrel(
response.on('error', (error: Error) => {
logger.error(
'pipeUpdateToSquirrel: update file download request had an error',
getPrintableError(error)
`pipeUpdateToSquirrel: update file download request had an error ${getPrintableError(
error
)}`
);
shutdown(server, logger);
reject(error);
@@ -263,8 +290,9 @@ function pipeUpdateToSquirrel(
readStream.on('error', (error: Error) => {
logger.error(
'pipeUpdateToSquirrel: read stream error response:',
getPrintableError(error)
`pipeUpdateToSquirrel: read stream error response: ${getPrintableError(
error
)}`
);
shutdown(server, logger, response);
reject(error);
@@ -339,7 +367,7 @@ function shutdown(
server.close();
}
} catch (error) {
logger.error('shutdown: Error closing server', getPrintableError(error));
logger.error(`shutdown: Error closing server ${getPrintableError(error)}`);
}
try {
@@ -348,62 +376,32 @@ function shutdown(
}
} catch (endError) {
logger.error(
"shutdown: couldn't end response",
getPrintableError(endError)
`shutdown: couldn't end response ${getPrintableError(endError)}`
);
}
}
export function showReadOnlyDialog(
mainWindow: BrowserWindow,
locale: LocaleType
): void {
let ack = false;
ipcMain.once('show-update-dialog-ack', () => {
ack = true;
});
mainWindow.webContents.send('show-update-dialog', Dialogs.MacOS_Read_Only);
setTimeout(async () => {
if (!ack) {
await showFallbackReadOnlyDialog(mainWindow, locale);
}
}, ACK_RENDER_TIMEOUT);
}
let showingReadOnlyDialog = false;
async function showFallbackReadOnlyDialog(
mainWindow: BrowserWindow,
locale: LocaleType
function createUpdater(
getMainWindow: () => BrowserWindow,
info: Pick<UpdateInformationType, 'fileName' | 'version'>,
logger: LoggerType
) {
if (showingReadOnlyDialog) {
return;
}
const options = {
type: 'warning',
buttons: [locale.messages.ok.message],
title: locale.messages.cannotUpdate.message,
message: locale.i18n('readOnlyVolume', {
app: 'Signal.app',
folder: '/Applications',
}),
};
showingReadOnlyDialog = true;
await dialog.showMessageBox(mainWindow, options);
showingReadOnlyDialog = false;
}
function createUpdater(logger: LoggerType) {
return () => {
logger.info('performUpdate: calling quitAndInstall...');
markShouldQuit();
autoUpdater.quitAndInstall();
return async () => {
if (updateFilePath) {
logger.info('performUpdate: calling quitAndInstall...');
markShouldQuit();
autoUpdater.quitAndInstall();
} else {
logger.info(
'performUpdate: have not downloaded update, going to download'
);
await downloadAndInstall(
info.fileName,
info.version,
getMainWindow,
logger,
true
);
}
};
}

View File

@@ -14,16 +14,16 @@ import {
checkForUpdates,
deleteTempDir,
downloadUpdate,
getAutoDownloadUpdateSetting,
getPrintableError,
setUpdateListener,
showCannotUpdateDialog,
showUpdateDialog,
UpdaterInterface,
UpdateInformationType,
} from './common';
import { LocaleType } from '../types/I18N';
import { LoggerType } from '../types/Logging';
import { hexToBinary, verifySignature } from './signature';
import { markShouldQuit } from '../../app/window_state';
import { DialogType } from '../types/Dialogs';
const readdir = pify(readdirCallback);
const unlink = pify(unlinkCallback);
@@ -40,7 +40,6 @@ let loggerForQuitHandler: LoggerType;
export async function start(
getMainWindow: () => BrowserWindow,
locale: LocaleType,
logger: LoggerType
): Promise<UpdaterInterface> {
logger.info('windows/start: starting checks...');
@@ -48,56 +47,84 @@ export async function start(
loggerForQuitHandler = logger;
app.once('quit', quitHandler);
setUpdateListener(createUpdater(getMainWindow, locale, logger));
setInterval(async () => {
try {
await checkDownloadAndInstall(getMainWindow, locale, logger);
await checkForUpdatesMaybeInstall(getMainWindow, logger);
} catch (error) {
logger.error('windows/start: error:', getPrintableError(error));
logger.error(`windows/start: ${getPrintableError(error)}`);
}
}, INTERVAL);
await deletePreviousInstallers(logger);
await checkDownloadAndInstall(getMainWindow, locale, logger);
await checkForUpdatesMaybeInstall(getMainWindow, logger);
return {
async force(): Promise<void> {
return checkDownloadAndInstall(getMainWindow, locale, logger, true);
return checkForUpdatesMaybeInstall(getMainWindow, logger, true);
},
};
}
async function checkDownloadAndInstall(
async function checkForUpdatesMaybeInstall(
getMainWindow: () => BrowserWindow,
locale: LocaleType,
logger: LoggerType,
force = false
) {
try {
logger.info('checkDownloadAndInstall: checking for update...');
const result = await checkForUpdates(logger, force);
if (!result) {
logger.info('checkForUpdatesMaybeInstall: checking for update...');
const result = await checkForUpdates(logger, force);
if (!result) {
return;
}
const { fileName: newFileName, version: newVersion } = result;
setUpdateListener(createUpdater(getMainWindow, result, logger));
if (fileName !== newFileName || !version || gt(newVersion, version)) {
const autoDownloadUpdates = await getAutoDownloadUpdateSetting(
getMainWindow()
);
if (!autoDownloadUpdates) {
getMainWindow().webContents.send(
'show-update-dialog',
DialogType.DownloadReady,
{
downloadSize: result.size,
version: result.version,
}
);
return;
}
await downloadAndInstall(newFileName, newVersion, getMainWindow, logger);
}
}
const { fileName: newFileName, version: newVersion } = result;
if (fileName !== newFileName || !version || gt(newVersion, version)) {
const oldFileName = fileName;
const oldVersion = version;
async function downloadAndInstall(
newFileName: string,
newVersion: string,
getMainWindow: () => BrowserWindow,
logger: LoggerType,
updateOnProgress?: boolean
) {
try {
const oldFileName = fileName;
const oldVersion = version;
deleteCache(updateFilePath, logger);
fileName = newFileName;
version = newVersion;
deleteCache(updateFilePath, logger);
fileName = newFileName;
version = newVersion;
try {
updateFilePath = await downloadUpdate(fileName, logger);
} catch (error) {
// Restore state in case of download error
fileName = oldFileName;
version = oldVersion;
throw error;
}
try {
updateFilePath = await downloadUpdate(
fileName,
logger,
updateOnProgress ? getMainWindow() : undefined
);
} catch (error) {
// Restore state in case of download error
fileName = oldFileName;
version = oldVersion;
throw error;
}
const publicKey = hexToBinary(getFromConfig('updatesPublicKey'));
@@ -110,14 +137,12 @@ async function checkDownloadAndInstall(
);
}
logger.info('checkDownloadAndInstall: showing dialog...');
showUpdateDialog(
getMainWindow(),
locale,
createUpdater(getMainWindow, locale, logger)
);
logger.info('downloadAndInstall: showing dialog...');
getMainWindow().webContents.send('show-update-dialog', DialogType.Update, {
version,
});
} catch (error) {
logger.error('checkDownloadAndInstall: error', getPrintableError(error));
logger.error(`downloadAndInstall: ${getPrintableError(error)}`);
}
}
@@ -125,10 +150,7 @@ function quitHandler() {
if (updateFilePath && !installing) {
verifyAndInstall(updateFilePath, version, loggerForQuitHandler).catch(
error => {
loggerForQuitHandler.error(
'quitHandler: error installing:',
getPrintableError(error)
);
loggerForQuitHandler.error(`quitHandler: ${getPrintableError(error)}`);
}
);
}
@@ -208,10 +230,7 @@ function deleteCache(filePath: string | null, logger: LoggerType) {
if (filePath) {
const tempDir = dirname(filePath);
deleteTempDir(tempDir).catch(error => {
logger.error(
'deleteCache: error deleting temporary directory',
getPrintableError(error)
);
logger.error(`deleteCache: ${getPrintableError(error)}`);
});
}
}
@@ -237,23 +256,37 @@ async function spawn(
function createUpdater(
getMainWindow: () => BrowserWindow,
locale: LocaleType,
info: Pick<UpdateInformationType, 'fileName' | 'version'>,
logger: LoggerType
) {
return async () => {
try {
await verifyAndInstall(updateFilePath, version, logger);
installing = true;
} catch (error) {
if (updateFilePath) {
try {
await verifyAndInstall(updateFilePath, version, logger);
installing = true;
} catch (error) {
logger.info('createUpdater: showing general update failure dialog...');
getMainWindow().webContents.send(
'show-update-dialog',
DialogType.Cannot_Update
);
throw error;
}
markShouldQuit();
app.quit();
} else {
logger.info(
'checkDownloadAndInstall: showing general update failure dialog...'
'performUpdate: have not downloaded update, going to download'
);
await downloadAndInstall(
info.fileName,
info.version,
getMainWindow,
logger,
true
);
showCannotUpdateDialog(getMainWindow(), locale);
throw error;
}
markShouldQuit();
app.quit();
};
}