From 0f68d845f9b2dc741381eb1c571a5f0369f230f6 Mon Sep 17 00:00:00 2001 From: Milan Burda Date: Tue, 7 Nov 2023 17:55:22 -0500 Subject: [PATCH] refactor: AutoUpdater for Windows using async/await (#40289) --- .../api/auto-updater/auto-updater-win.ts | 29 +++--- .../api/auto-updater/squirrel-update-win.ts | 92 +++++++------------ 2 files changed, 48 insertions(+), 73 deletions(-) diff --git a/lib/browser/api/auto-updater/auto-updater-win.ts b/lib/browser/api/auto-updater/auto-updater-win.ts index 0505fc8ac2ae..4b2bac16c0c0 100644 --- a/lib/browser/api/auto-updater/auto-updater-win.ts +++ b/lib/browser/api/auto-updater/auto-updater-win.ts @@ -34,7 +34,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater { this.updateURL = updateURL; } - checkForUpdates () { + async checkForUpdates () { const url = this.updateURL; if (!url) { return this.emitError(new Error('Update URL is not set')); @@ -43,27 +43,24 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater { return this.emitError(new Error('Can not find Squirrel')); } this.emit('checking-for-update'); - squirrelUpdate.checkForUpdate(url, (error, update) => { - if (error != null) { - return this.emitError(error); - } + try { + const update = await squirrelUpdate.checkForUpdate(url); if (update == null) { return this.emit('update-not-available'); } this.updateAvailable = true; this.emit('update-available'); - squirrelUpdate.update(url, (error) => { - if (error != null) { - return this.emitError(error); - } - const { releaseNotes, version } = update; - // Date is not available on Windows, so fake it. - const date = new Date(); - this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => { - this.quitAndInstall(); - }); + + await squirrelUpdate.update(url); + const { releaseNotes, version } = update; + // Date is not available on Windows, so fake it. + const date = new Date(); + this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => { + this.quitAndInstall(); }); - }); + } catch (error) { + this.emitError(error as Error); + } } // Private: Emit both error object and message, this is to keep compatibility diff --git a/lib/browser/api/auto-updater/squirrel-update-win.ts b/lib/browser/api/auto-updater/squirrel-update-win.ts index 3f5735cfecd2..d5e54d3344a1 100644 --- a/lib/browser/api/auto-updater/squirrel-update-win.ts +++ b/lib/browser/api/auto-updater/squirrel-update-win.ts @@ -15,89 +15,67 @@ const isSameArgs = (args: string[]) => args.length === spawnedArgs.length && arg // Spawn a command and invoke the callback when it completes with an error // and the output from standard out. -const spawnUpdate = function (args: string[], detached: boolean, callback: Function) { - let error: Error, errorEmitted: boolean, stderr: string, stdout: string; - - try { +const spawnUpdate = async function (args: string[], options: { detached: boolean }): Promise { + return new Promise((resolve, reject) => { // Ensure we don't spawn multiple squirrel processes // Process spawned, same args: Attach events to already running process // Process spawned, different args: Return with error // No process spawned: Spawn new process if (spawnedProcess && !isSameArgs(args)) { - return callback(`AutoUpdater process with arguments ${args} is already running`); + throw new Error(`AutoUpdater process with arguments ${args} is already running`); } else if (!spawnedProcess) { spawnedProcess = spawn(updateExe, args, { - detached: detached, + detached: options.detached, windowsHide: true }); spawnedArgs = args || []; } - } catch (error1) { - error = error1 as Error; - // Shouldn't happen, but still guard it. - process.nextTick(function () { - return callback(error); + let stdout = ''; + let stderr = ''; + + spawnedProcess.stdout.on('data', (data) => { stdout += data; }); + spawnedProcess.stderr.on('data', (data) => { stderr += data; }); + + spawnedProcess.on('error', (error) => { + reject(error); }); - return; - } - stdout = ''; - stderr = ''; - spawnedProcess.stdout.on('data', (data) => { stdout += data; }); - spawnedProcess.stderr.on('data', (data) => { stderr += data; }); + spawnedProcess.on('exit', function (code, signal) { + spawnedProcess = undefined; + spawnedArgs = []; - errorEmitted = false; - spawnedProcess.on('error', (error) => { - errorEmitted = true; - callback(error); - }); - - return spawnedProcess.on('exit', function (code, signal) { - spawnedProcess = undefined; - spawnedArgs = []; - - // We may have already emitted an error. - if (errorEmitted) { - return; - } - - // Process terminated with error. - if (code !== 0) { - return callback(`Command failed: ${signal ?? code}\n${stderr}`); - } - - // Success. - callback(null, stdout); + if (code !== 0) { + // Process terminated with error. + reject(new Error(`Command failed: ${signal ?? code}\n${stderr}`)); + } else { + // Success. + resolve(stdout); + } + }); }); }; // Start an instance of the installed app. export function processStart () { - return spawnUpdate(['--processStartAndWait', exeName], true, function () {}); + spawnUpdate(['--processStartAndWait', exeName], { detached: true }); } // Download the releases specified by the URL and write new results to stdout. -export function checkForUpdate (updateURL: string, callback: (error: Error | null, update?: any) => void) { - return spawnUpdate(['--checkForUpdate', updateURL], false, function (error: Error, stdout: string) { - let ref, ref1, update; - if (error != null) { - return callback(error); - } - try { - // Last line of output is the JSON details about the releases - const json = stdout.trim().split('\n').pop(); - update = (ref = JSON.parse(json!)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : undefined : undefined : undefined; - } catch { - return callback(new Error(`Invalid result:\n${stdout}`)); - } - return callback(null, update); - }); +export async function checkForUpdate (updateURL: string): Promise { + const stdout = await spawnUpdate(['--checkForUpdate', updateURL], { detached: false }); + try { + // Last line of output is the JSON details about the releases + const json = stdout.trim().split('\n').pop(); + return JSON.parse(json!)?.releasesToApply?.pop?.(); + } catch { + throw new Error(`Invalid result:\n${stdout}`); + } } // Update the application to the latest remote version specified by URL. -export function update (updateURL: string, callback: (error: Error) => void) { - return spawnUpdate(['--update', updateURL], false, callback); +export async function update (updateURL: string): Promise { + await spawnUpdate(['--update', updateURL], { detached: false }); } // Is the Update.exe installed with the current application?