Provide file url to autoUpdater on macos

This commit is contained in:
Fedor Indutny 2023-12-04 20:11:17 +01:00 committed by GitHub
parent a81833d3ed
commit 3ea7fe6226
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,14 +1,8 @@
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { createReadStream, statSync } from 'fs';
import type { IncomingMessage, Server, ServerResponse } from 'http';
import { createServer } from 'http';
import type { AddressInfo } from 'net';
import { v4 as getGuid } from 'uuid';
import { pathToFileURL } from 'url';
import { autoUpdater } from 'electron';
import got from 'got';
import { Updater } from './common';
import { explodePromise } from '../util/explodePromise';
@ -54,187 +48,23 @@ export class MacOSUpdater extends Updater {
const { logger } = this;
const { promise, resolve, reject } = explodePromise<void>();
const token = getGuid();
const updateFileUrl = generateFileUrl();
const server = createServer();
let serverUrl: string;
autoUpdater.on('error', (...args) => {
logger.error('autoUpdater: error', ...args.map(Errors.toLogFormat));
server.on('error', (error: Error) => {
logger.error(`handToAutoUpdate: ${Errors.toLogFormat(error)}`);
this.shutdown(server);
const [error] = args;
reject(error);
});
server.on(
'request',
(request: IncomingMessage, response: ServerResponse) => {
const { url } = request;
if (url === '/') {
const absoluteUrl = `${serverUrl}${updateFileUrl}`;
writeJSONResponse(absoluteUrl, response);
return;
}
if (url === '/token') {
writeTokenResponse(token, response);
return;
}
if (!url || !url.startsWith(updateFileUrl)) {
this.logger.error(
`write404: Squirrel requested unexpected url '${url}'`
);
response.writeHead(404);
response.end();
return;
}
this.pipeUpdateToSquirrel(filePath, server, response, reject);
}
);
server.listen(0, '127.0.0.1', async () => {
try {
serverUrl = getServerUrl(server);
autoUpdater.on('error', (...args) => {
logger.error('autoUpdater: error', ...args.map(Errors.toLogFormat));
const [error] = args;
reject(error);
});
autoUpdater.on('update-downloaded', () => {
logger.info('autoUpdater: update-downloaded event fired');
this.shutdown(server);
resolve();
});
const response = await got.get(`${serverUrl}/token`);
if (JSON.parse(response.body).token !== token) {
throw new Error(
'autoUpdater: did not receive token back from updates server'
);
}
autoUpdater.setFeedURL({
url: serverUrl,
headers: { 'Cache-Control': 'no-cache' },
});
autoUpdater.checkForUpdates();
} catch (error) {
reject(error);
}
autoUpdater.on('update-downloaded', () => {
logger.info('autoUpdater: update-downloaded event fired');
resolve();
});
autoUpdater.setFeedURL({
url: pathToFileURL(filePath).href,
serverType: 'json',
});
autoUpdater.checkForUpdates();
return promise;
}
private pipeUpdateToSquirrel(
filePath: string,
server: Server,
response: ServerResponse,
reject: (error: Error) => void
): void {
const { logger } = this;
const updateFileSize = getFileSize(filePath);
const readStream = createReadStream(filePath);
response.on('error', (error: Error) => {
logger.error(
`pipeUpdateToSquirrel: update file download request had an error ${Errors.toLogFormat(
error
)}`
);
this.shutdown(server);
reject(error);
});
readStream.on('error', (error: Error) => {
logger.error(
`pipeUpdateToSquirrel: read stream error response: ${Errors.toLogFormat(
error
)}`
);
this.shutdown(server, response);
reject(error);
});
response.writeHead(200, {
'Content-Type': 'application/zip',
'Content-Length': updateFileSize,
});
readStream.pipe(response);
}
private shutdown(server: Server, response?: ServerResponse): void {
const { logger } = this;
try {
if (server) {
server.close();
}
} catch (error) {
logger.error(
`shutdown: Error closing server ${Errors.toLogFormat(error)}`
);
}
try {
if (response) {
response.end();
}
} catch (endError) {
logger.error(
`shutdown: couldn't end response ${Errors.toLogFormat(endError)}`
);
}
}
}
// Helpers
function writeJSONResponse(url: string, response: ServerResponse) {
const data = Buffer.from(
JSON.stringify({
url,
})
);
response.writeHead(200, {
'Content-Type': 'application/json',
'Content-Length': data.byteLength,
});
response.end(data);
}
function writeTokenResponse(token: string, response: ServerResponse) {
const data = Buffer.from(
JSON.stringify({
token,
})
);
response.writeHead(200, {
'Content-Type': 'application/json',
'Content-Length': data.byteLength,
});
response.end(data);
}
function getServerUrl(server: Server) {
const address = server.address() as AddressInfo;
return `http://127.0.0.1:${address.port}`;
}
function generateFileUrl(): string {
return `/${getGuid()}.zip`;
}
function getFileSize(targetPath: string): number {
const { size } = statSync(targetPath);
return size;
}