Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: AutoUpdater for Windows using async/await #40289

Merged
merged 4 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 13 additions & 16 deletions lib/browser/api/auto-updater/auto-updater-win.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class AutoUpdater extends EventEmitter {
this.updateURL = updateURL;
}

checkForUpdates () {
async checkForUpdates () {
const url = this.updateURL;
if (!url) {
return this.emitError(new Error('Update URL is not set'));
Expand All @@ -43,27 +43,24 @@ class AutoUpdater extends EventEmitter {
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
Expand Down
92 changes: 35 additions & 57 deletions lib/browser/api/auto-updater/squirrel-update-win.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[], detached: boolean): Promise<string> {
miniak marked this conversation as resolved.
Show resolved Hide resolved
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`);
miniak marked this conversation as resolved.
Show resolved Hide resolved
} else if (!spawnedProcess) {
spawnedProcess = spawn(updateExe, args, {
detached: detached,
detached,
windowsHide: true
});
spawnedArgs = args || [];
}
} catch (error1) {
error = error1 as Error;

// Shouldn't happen, but still guard it.
process.nextTick(function () {
return callback(error);
});
return;
}
stdout = '';
stderr = '';

spawnedProcess.stdout.on('data', (data) => { stdout += data; });
spawnedProcess.stderr.on('data', (data) => { stderr += data; });
let stdout = '';
let stderr = '';

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;
}
spawnedProcess.stdout.on('data', (data) => { stdout += data; });
spawnedProcess.stderr.on('data', (data) => { stderr += data; });

// Process terminated with error.
if (code !== 0) {
return callback(`Command failed: ${signal ?? code}\n${stderr}`);
}
spawnedProcess.on('error', (error) => {
reject(error);
});

// Success.
callback(null, stdout);
spawnedProcess.on('exit', function (code, signal) {
spawnedProcess = undefined;
spawnedArgs = [];

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], 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;
miniak marked this conversation as resolved.
Show resolved Hide resolved
} catch {
return callback(new Error(`Invalid result:\n${stdout}`));
}
return callback(null, update);
});
export async function checkForUpdate (updateURL: string): Promise<any> {
const stdout = await spawnUpdate(['--checkForUpdate', updateURL], false);
try {
// Last line of output is the JSON details about the releases
const json = stdout.trim().split('\n').pop();
JSON.parse(json!)?.releasesToApply?.pop?.();
miniak marked this conversation as resolved.
Show resolved Hide resolved
} 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<void> {
await spawnUpdate(['--update', updateURL], false);
}

// Is the Update.exe installed with the current application?
Expand Down