Skip to content

Commit a75bac8

Browse files
committed
feat(electon-updater): autoUpdater download-progress event is not called on macOS
Close #1167
1 parent 63f019f commit a75bac8

File tree

5 files changed

+113
-37
lines changed

5 files changed

+113
-37
lines changed

packages/electron-builder-http/src/httpExecutor.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,10 @@ export abstract class HttpExecutor<REQUEST> {
188188
}
189189
}
190190

191-
class DigestTransform extends Transform {
191+
export class DigestTransform extends Transform {
192192
private readonly digester: Hash
193193

194-
constructor(private readonly expected: string, algorithm: string, private readonly encoding: "hex" | "base64" | "latin1") {
194+
constructor(private readonly expected: string, private readonly algorithm: string, private readonly encoding: "hex" | "base64" | "latin1") {
195195
super()
196196

197197
this.digester = createHash(algorithm)
@@ -204,7 +204,7 @@ class DigestTransform extends Transform {
204204

205205
_flush(callback: Function): void {
206206
const hash = this.digester.digest(this.encoding)
207-
callback(hash === this.expected ? null : new Error(`SHA2 checksum mismatch, expected ${this.expected}, got ${hash}`))
207+
callback(hash === this.expected ? null : new Error(`${this.algorithm} checksum mismatch, expected ${this.expected}, got ${hash}`))
208208
}
209209
}
210210

@@ -227,7 +227,7 @@ function checkSha2(sha2Header: string | null | undefined, sha2: string | null |
227227
return true
228228
}
229229

230-
function safeGetHeader(response: any, headerKey: string) {
230+
export function safeGetHeader(response: any, headerKey: string) {
231231
const value = response.headers[headerKey]
232232
if (value == null) {
233233
return null

packages/electron-updater/src/AppUpdater.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ export abstract class AppUpdater extends EventEmitter {
7171

7272
private currentVersion: string
7373

74+
protected httpExecutor: ElectronHttpExecutor
75+
7476
constructor(options: PublishConfiguration | null | undefined, app?: any) {
7577
super()
7678

@@ -86,7 +88,8 @@ export abstract class AppUpdater extends EventEmitter {
8688
}
8789
else {
8890
this.app = require("electron").app
89-
executorHolder.httpExecutor = new ElectronHttpExecutor((authInfo, callback) => this.emit("login", authInfo, callback))
91+
this.httpExecutor = new ElectronHttpExecutor((authInfo, callback) => this.emit("login", authInfo, callback))
92+
executorHolder.httpExecutor = this.httpExecutor
9093
this.untilAppReady = new BluebirdPromise(resolve => {
9194
if (this.app.isReady()) {
9295
if (this.logger != null) {

packages/electron-updater/src/GitHubProvider.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ export class GitHubProvider extends BaseGitHubProvider<UpdateInfo> {
6161
let result: UpdateInfo
6262
const channelFile = getChannelFilename(getDefaultChannelName())
6363
const requestOptions = Object.assign({path: this.getBaseDownloadPath(version, channelFile), headers: this.requestHeaders || undefined}, this.baseUrl)
64+
let rawData: string
6465
try {
65-
result = safeLoad(await request<string>(requestOptions, cancellationToken))
66+
rawData = await request<string>(requestOptions, cancellationToken)
6667
}
6768
catch (e) {
6869
if (!this.updater.allowPrerelease) {
@@ -73,6 +74,13 @@ export class GitHubProvider extends BaseGitHubProvider<UpdateInfo> {
7374
throw e
7475
}
7576

77+
try {
78+
result = safeLoad(rawData)
79+
}
80+
catch (e) {
81+
throw new Error(`Cannot parse update info from ${channelFile} in the latest release artifacts (${formatUrl(<any>requestOptions)}): ${e.stack || e.message}, rawData: ${rawData}`)
82+
}
83+
7684
Provider.validateUpdateInfo(result)
7785
if (isUseOldMacProvider()) {
7886
(<any>result).releaseJsonUrl = `${githubUrl(this.options)}/${requestOptions.path}`
Lines changed: 95 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import BluebirdPromise from "bluebird-lst"
2+
import { configureRequestOptions, DigestTransform, safeGetHeader } from "electron-builder-http"
23
import { CancellationToken } from "electron-builder-http/out/CancellationToken"
4+
import { ProgressCallbackTransform } from "electron-builder-http/out/ProgressCallbackTransform"
35
import { PublishConfiguration, VersionInfo } from "electron-builder-http/out/publishOptions"
4-
import { createServer, IncomingMessage, Server, ServerResponse } from "http"
6+
import { createServer, IncomingMessage, ServerResponse } from "http"
7+
import { parse as parseUrl } from "url"
58
import { AppUpdater } from "./AppUpdater"
6-
import { FileInfo } from "./main"
9+
import { DOWNLOAD_PROGRESS, FileInfo } from "./main"
710
import AutoUpdater = Electron.AutoUpdater
811

912
export class MacUpdater extends AppUpdater {
1013
private readonly nativeUpdater: AutoUpdater = require("electron").autoUpdater
1114

12-
private server: Server | null
13-
1415
constructor(options?: PublishConfiguration) {
1516
super(options)
1617

@@ -28,43 +29,107 @@ export class MacUpdater extends AppUpdater {
2829
})
2930
}
3031

31-
protected onUpdateAvailable(versionInfo: VersionInfo, fileInfo: FileInfo) {
32-
super.onUpdateAvailable(versionInfo, fileInfo)
32+
protected doDownloadUpdate(versionInfo: VersionInfo, fileInfo: FileInfo, cancellationToken: CancellationToken) {
33+
const server = createServer()
34+
server.on("close", () => {
35+
if (this.logger != null) {
36+
this.logger.info(`Proxy server for native Squirrel.Mac is closed (was started to download ${fileInfo.url})`)
37+
}
38+
})
3339

34-
let server = this.server
35-
if (server != null) {
36-
return
40+
function getServerUrl() {
41+
const address = server.address()
42+
return `http://${address.address}:${address.port}`
3743
}
3844

39-
server = createServer()
40-
this.server = server
41-
server.on("request", (request: IncomingMessage, response: ServerResponse) => {
42-
if (request.url!.endsWith("/update.json")) {
43-
response.writeHead(200, {"Content-Type": "application/json"})
44-
response.end(`{ "url": "${this.getServerUrl()}/app.zip" }`)
45+
return new BluebirdPromise((resolve, reject) => {
46+
server.on("request", (request: IncomingMessage, response: ServerResponse) => {
47+
const requestUrl = request.url!
48+
if (requestUrl === "/") {
49+
response.writeHead(200, {"Content-Type": "application/json"})
50+
response.end(`{ "url": "${getServerUrl()}/app.zip" }`)
51+
}
52+
else if (requestUrl === "/app.zip") {
53+
let errorOccurred = false
54+
response.on("finish", () => {
55+
if (!errorOccurred) {
56+
resolve()
57+
}
58+
})
59+
this.proxyUpdateFile(response, fileInfo, error => {
60+
errorOccurred = true
61+
try {
62+
response.writeHead(500)
63+
response.end()
64+
}
65+
finally {
66+
reject(new Error(`Cannot download "${fileInfo.url}": ${error}`))
67+
}
68+
})
69+
}
70+
else {
71+
response.writeHead(404)
72+
response.end()
73+
}
74+
})
75+
server.listen(0, "127.0.0.1", 16, () => {
76+
this.nativeUpdater.setFeedURL(`${getServerUrl()}`, Object.assign({"Cache-Control": "no-cache"}, this.computeRequestHeaders(fileInfo)))
77+
this.nativeUpdater.checkForUpdates()
78+
})
79+
})
80+
}
81+
82+
private proxyUpdateFile(nativeResponse: ServerResponse, fileInfo: FileInfo, errorHandler: (error: Error) => void) {
83+
nativeResponse.writeHead(200, {"Content-Type": "application/zip"})
84+
85+
const parsedUrl = parseUrl(fileInfo.url)
86+
const downloadRequest = this.httpExecutor.doRequest(configureRequestOptions({
87+
protocol: parsedUrl.protocol,
88+
hostname: parsedUrl.hostname,
89+
path: parsedUrl.path,
90+
port: parsedUrl.port ? parseInt(parsedUrl.port, 10) : undefined,
91+
headers: this.computeRequestHeaders(fileInfo) || undefined,
92+
}), downloadResponse => {
93+
if (downloadResponse.statusCode! >= 400) {
94+
try {
95+
nativeResponse.writeHead(404)
96+
nativeResponse.end()
97+
}
98+
finally {
99+
this.emit("error", new Error(`Cannot download "${fileInfo.url}", status ${downloadResponse.statusCode}: ${downloadResponse.statusMessage}`))
100+
}
101+
return
45102
}
46-
else {
47-
response.writeHead(404)
48-
response.end()
49103

104+
const streams: Array<any> = []
105+
if (this.listenerCount(DOWNLOAD_PROGRESS) > 0) {
106+
const contentLength = safeGetHeader(downloadResponse, "content-length")
107+
if (contentLength != null) {
108+
streams.push(new ProgressCallbackTransform(parseInt(contentLength, 10), new CancellationToken(), it => this.emit(DOWNLOAD_PROGRESS, it)))
109+
}
110+
}
111+
112+
// for mac only sha512 is produced (sha256 is published for windows only to preserve backward compatibility)
113+
const sha512 = fileInfo.sha512
114+
if (sha512 != null) {
115+
// "hex" to easy migrate to new base64 encoded hash (we already produces latest-mac.yml with hex encoded hash)
116+
streams.push(new DigestTransform(sha512, "sha512", sha512.length === 128 && !sha512.includes("+") && !sha512.includes("Z") && !sha512.includes("=") ? "hex" : "base64"))
117+
}
118+
119+
streams.push(nativeResponse)
120+
121+
let lastStream = downloadResponse
122+
for (const stream of streams) {
123+
stream.on("error", errorHandler)
124+
lastStream = lastStream.pipe(stream)
50125
}
51126
})
52-
server.listen(0, "127.0.0.1", 16, () => {
53-
this.nativeUpdater.setFeedURL(`${this.getServerUrl()}`, Object.assign({"Cache-Control": "no-cache"}, this.computeRequestHeaders(fileInfo)))
54-
})
55-
}
56127

57-
protected doDownloadUpdate(versionInfo: VersionInfo, fileInfo: FileInfo, cancellationToken: CancellationToken) {
58-
this.nativeUpdater.checkForUpdates()
59-
return BluebirdPromise.resolve()
128+
downloadRequest.on("error", errorHandler)
129+
downloadRequest.end()
60130
}
61131

62132
quitAndInstall(): void {
63133
this.nativeUpdater.quitAndInstall()
64134
}
65-
66-
private getServerUrl() {
67-
const address = this.server!.address()
68-
return `http://${address.address}:${address.port}`
69-
}
70135
}

packages/electron-updater/src/electronHttpExecutor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export class ElectronHttpExecutor extends HttpExecutor<Electron.ClientRequest> {
6565
}
6666

6767

68-
protected doRequest(options: any, callback: (response: any) => void): any {
68+
public doRequest(options: any, callback: (response: any) => void): any {
6969
const request = (<any>net).request(Object.assign({session: (<any>session).fromPartition(NET_SESSION_NAME)}, options), callback)
7070
this.addProxyLoginHandler(request)
7171
return request

0 commit comments

Comments
 (0)