refactor: AutoUpdater for Windows using async/await (#40289)

This commit is contained in:
Milan Burda 2023-11-07 17:55:22 -05:00 committed by GitHub
parent 1ba535296e
commit 0f68d845f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 48 additions and 73 deletions

View file

@ -34,7 +34,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
this.updateURL = updateURL; this.updateURL = updateURL;
} }
checkForUpdates () { async checkForUpdates () {
const url = this.updateURL; const url = this.updateURL;
if (!url) { if (!url) {
return this.emitError(new Error('Update URL is not set')); 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')); return this.emitError(new Error('Can not find Squirrel'));
} }
this.emit('checking-for-update'); this.emit('checking-for-update');
squirrelUpdate.checkForUpdate(url, (error, update) => { try {
if (error != null) { const update = await squirrelUpdate.checkForUpdate(url);
return this.emitError(error);
}
if (update == null) { if (update == null) {
return this.emit('update-not-available'); return this.emit('update-not-available');
} }
this.updateAvailable = true; this.updateAvailable = true;
this.emit('update-available'); this.emit('update-available');
squirrelUpdate.update(url, (error) => {
if (error != null) { await squirrelUpdate.update(url);
return this.emitError(error); const { releaseNotes, version } = update;
} // Date is not available on Windows, so fake it.
const { releaseNotes, version } = update; const date = new Date();
// Date is not available on Windows, so fake it. this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => {
const date = new Date(); this.quitAndInstall();
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 // Private: Emit both error object and message, this is to keep compatibility

View file

@ -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 // Spawn a command and invoke the callback when it completes with an error
// and the output from standard out. // and the output from standard out.
const spawnUpdate = function (args: string[], detached: boolean, callback: Function) { const spawnUpdate = async function (args: string[], options: { detached: boolean }): Promise<string> {
let error: Error, errorEmitted: boolean, stderr: string, stdout: string; return new Promise((resolve, reject) => {
try {
// Ensure we don't spawn multiple squirrel processes // Ensure we don't spawn multiple squirrel processes
// Process spawned, same args: Attach events to already running process // Process spawned, same args: Attach events to already running process
// Process spawned, different args: Return with error // Process spawned, different args: Return with error
// No process spawned: Spawn new process // No process spawned: Spawn new process
if (spawnedProcess && !isSameArgs(args)) { 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) { } else if (!spawnedProcess) {
spawnedProcess = spawn(updateExe, args, { spawnedProcess = spawn(updateExe, args, {
detached: detached, detached: options.detached,
windowsHide: true windowsHide: true
}); });
spawnedArgs = args || []; spawnedArgs = args || [];
} }
} catch (error1) {
error = error1 as Error;
// Shouldn't happen, but still guard it. let stdout = '';
process.nextTick(function () { let stderr = '';
return callback(error);
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.on('exit', function (code, signal) {
spawnedProcess.stderr.on('data', (data) => { stderr += data; }); spawnedProcess = undefined;
spawnedArgs = [];
errorEmitted = false; if (code !== 0) {
spawnedProcess.on('error', (error) => { // Process terminated with error.
errorEmitted = true; reject(new Error(`Command failed: ${signal ?? code}\n${stderr}`));
callback(error); } else {
}); // Success.
resolve(stdout);
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);
}); });
}; };
// Start an instance of the installed app. // Start an instance of the installed app.
export function processStart () { 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. // 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) { export async function checkForUpdate (updateURL: string): Promise<any> {
return spawnUpdate(['--checkForUpdate', updateURL], false, function (error: Error, stdout: string) { const stdout = await spawnUpdate(['--checkForUpdate', updateURL], { detached: false });
let ref, ref1, update; try {
if (error != null) { // Last line of output is the JSON details about the releases
return callback(error); const json = stdout.trim().split('\n').pop();
} return JSON.parse(json!)?.releasesToApply?.pop?.();
try { } catch {
// Last line of output is the JSON details about the releases throw new Error(`Invalid result:\n${stdout}`);
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);
});
} }
// Update the application to the latest remote version specified by URL. // Update the application to the latest remote version specified by URL.
export function update (updateURL: string, callback: (error: Error) => void) { export async function update (updateURL: string): Promise<void> {
return spawnUpdate(['--update', updateURL], false, callback); await spawnUpdate(['--update', updateURL], { detached: false });
} }
// Is the Update.exe installed with the current application? // Is the Update.exe installed with the current application?