Skip to content

Commit 3401630

Browse files
committed
fix(electron-auto-updater): Checking for updates from github was failing
Closes #1038
1 parent 43add64 commit 3401630

File tree

6 files changed

+73
-90
lines changed

6 files changed

+73
-90
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
# electron-auto-updater
2+
13
[Auto Update](https://github.com/electron-userland/electron-builder/wiki/Auto-Update).

packages/electron-auto-updater/src/GitHubProvider.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,27 @@ export class GitHubProvider implements Provider<VersionInfo> {
1111
async getLatestVersion(): Promise<UpdateInfo> {
1212
// do not use API to avoid limit
1313
const basePath = this.getBasePath()
14-
let version = (await request<Redirect>({hostname: "github.com", path: `${basePath}/latest`})).location
15-
const versionPosition = version.lastIndexOf("/") + 1
14+
let version = (await request<GithubReleaseInfo>(
15+
{hostname: "github.com", path: `${basePath}/latest`},
16+
null,
17+
null,
18+
"GET",
19+
{"Accept": "application/json"}
20+
)).tag_name
21+
1622
try {
17-
version = version.substring(version[versionPosition] === "v" ? versionPosition + 1 : versionPosition)
23+
version = (version.startsWith("v")) ? version.substring(1) : version
1824
}
1925
catch (e) {
20-
throw new Error(`Cannot parse extract version from location "${version}": ${e.stack || e.message}`)
26+
throw new Error(`Cannot parse determine latest version from github "${version}": ${e.stack || e.message}`)
2127
}
2228

2329
let result: UpdateInfo | null = null
2430
try {
25-
result = await request<UpdateInfo>({hostname: "github.com", path: `https://github.com${basePath}/download/v${version}/latest.yml`})
31+
result = await request<UpdateInfo>({
32+
hostname: "github.com",
33+
path: `https://github.com${basePath}/download/v${version}/latest.yml`
34+
})
2635
}
2736
catch (e) {
2837
if (e instanceof HttpError && e.response.statusCode === 404) {
@@ -51,6 +60,6 @@ export class GitHubProvider implements Provider<VersionInfo> {
5160
}
5261
}
5362

54-
interface Redirect {
55-
readonly location: string
63+
interface GithubReleaseInfo {
64+
readonly tag_name: string
5665
}

packages/electron-auto-updater/src/electronHttpExecutor.ts

Lines changed: 11 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import { net } from "electron"
33
import { createWriteStream, ensureDir } from "fs-extra-p"
44
import BluebirdPromise from "bluebird-lst-c"
55
import * as path from "path"
6-
import { HttpExecutor, DownloadOptions, HttpError, DigestTransform, checkSha2, calculateDownloadProgress } from "electron-builder-http/out/httpExecutor"
7-
import { Url } from "url"
6+
import { HttpExecutor, DownloadOptions, HttpError, DigestTransform, checkSha2, calculateDownloadProgress, maxRedirects } from "electron-builder-http/out/httpExecutor"
87
import { safeLoad } from "js-yaml"
98
import _debug from "debug"
109
import Debugger = debug.Debugger
@@ -14,32 +13,9 @@ function safeGetHeader(response: Electron.IncomingMessage, headerKey: string) {
1413
return response.headers[headerKey] ? response.headers[headerKey].pop() : null
1514
}
1615

17-
export class ElectronHttpExecutor implements HttpExecutor {
16+
export class ElectronHttpExecutor extends HttpExecutor<Electron.RequestOptions, Electron.ClientRequest> {
1817
private readonly debug: Debugger = _debug("electron-builder")
1918

20-
private readonly maxRedirects = 10
21-
22-
request<T>(url: Url, token: string | null = null, data: {[name: string]: any; } | null = null, method: string = "GET"): Promise<T> {
23-
const options: any = Object.assign({
24-
method: method,
25-
headers: {
26-
"User-Agent": "electron-builder"
27-
}
28-
}, url)
29-
30-
if (url.hostname!!.includes("github") && !url.path!.endsWith(".yml")) {
31-
options.headers.Accept = "application/vnd.github.v3+json"
32-
}
33-
34-
const encodedData = data == null ? undefined : new Buffer(JSON.stringify(data))
35-
if (encodedData != null) {
36-
options.method = "post"
37-
options.headers["Content-Type"] = "application/json"
38-
options.headers["Content-Length"] = encodedData.length
39-
}
40-
return this.doApiRequest<T>(options, token, it => it.end(encodedData))
41-
}
42-
4319
download(url: string, destination: string, options?: DownloadOptions | null): Promise<string> {
4420
return new BluebirdPromise( (resolve, reject) => {
4521
this.doDownload(url, destination, 0, options || {}, (error: Error) => {
@@ -86,11 +62,11 @@ export class ElectronHttpExecutor implements HttpExecutor {
8662

8763
const redirectUrl = safeGetHeader(response, "location")
8864
if (redirectUrl != null) {
89-
if (redirectCount < this.maxRedirects) {
65+
if (redirectCount < maxRedirects) {
9066
this.doDownload(redirectUrl, destination, redirectCount++, options, callback)
9167
}
9268
else {
93-
callback(new Error("Too many redirects (> " + this.maxRedirects + ")"))
69+
callback(new Error(`Too many redirects (> ${maxRedirects})`))
9470
}
9571
return
9672
}
@@ -162,14 +138,10 @@ Please double check that your authentication token is correct. Due to security r
162138
return
163139
}
164140

165-
if (options.path!.endsWith("/latest")) {
166-
resolve(<any>{location: redirectUrl})
167-
}
168-
else {
169-
this.doApiRequest(Object.assign({}, options, parseUrl(redirectUrl)), token, requestProcessor)
170-
.then(<any>resolve)
171-
.catch(reject)
172-
}
141+
this.doApiRequest(Object.assign({}, options, parseUrl(redirectUrl)), token, requestProcessor)
142+
.then(<any>resolve)
143+
.catch(reject)
144+
173145
return
174146
}
175147

@@ -182,7 +154,8 @@ Please double check that your authentication token is correct. Due to security r
182154
response.on("end", () => {
183155
try {
184156
const contentType = response.headers["content-type"]
185-
const isJson = contentType != null && contentType.includes("json")
157+
const isJson = contentType != null && contentType.find((i) => i.indexOf("json") !== -1)
158+
const isYaml = options.path!.includes(".yml")
186159
if (response.statusCode >= 400) {
187160
if (isJson) {
188161
reject(new HttpError(response, JSON.parse(data)))
@@ -192,7 +165,7 @@ Please double check that your authentication token is correct. Due to security r
192165
}
193166
}
194167
else {
195-
resolve(data.length === 0 ? null : (isJson || !options.path!.includes(".yml")) ? JSON.parse(data) : safeLoad(data))
168+
resolve(data.length === 0 ? null : (isJson ? JSON.parse(data) : isYaml ? safeLoad(data) : data))
196169
}
197170
}
198171
catch (e) {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# electron-builder-http
2+
3+
Part of [electron-builder](https://github.com/electron-userland/electron-builder).

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

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,47 @@ export interface DownloadOptions {
99
}
1010

1111
export class HttpExecutorHolder {
12-
private _httpExecutor: HttpExecutor
12+
private _httpExecutor: HttpExecutor<any, any>
1313

14-
get httpExecutor(): HttpExecutor {
14+
get httpExecutor(): HttpExecutor<any, any> {
1515
if (this._httpExecutor == null) {
1616
this._httpExecutor = new (require("electron-builder/out/util/nodeHttpExecutor").NodeHttpExecutor)()
1717
}
1818
return this._httpExecutor
1919
}
2020

21-
set httpExecutor(value: HttpExecutor) {
21+
set httpExecutor(value: HttpExecutor<any, any>) {
2222
this._httpExecutor = value
2323
}
2424
}
2525

26-
export interface HttpExecutor {
27-
request<T>(url: Url, token?: string | null, data?: {[name: string]: any; } | null, method?: string): Promise<T>
26+
export const maxRedirects = 10
2827

29-
download(url: string, destination: string, options?: DownloadOptions | null): Promise<string>
28+
export abstract class HttpExecutor<REQUEST_OPTS, REQUEST> {
29+
request<T>(url: Url, token?: string | null, data?: {[name: string]: any; } | null, method?: string, headers?: any): Promise<T> {
30+
const defaultHeaders = {"User-Agent": "electron-builder"}
31+
const options = Object.assign({
32+
method: method,
33+
headers: headers == null ? defaultHeaders : Object.assign(defaultHeaders, headers)
34+
}, url)
35+
36+
37+
if (url.hostname!!.includes("github") && !url.path!.endsWith(".yml") && !options.headers.Accept) {
38+
options.headers["Accept"] = "application/vnd.github.v3+json"
39+
}
40+
41+
const encodedData = data == null ? undefined : new Buffer(JSON.stringify(data))
42+
if (encodedData != null) {
43+
options.method = "post"
44+
options.headers["Content-Type"] = "application/json"
45+
options.headers["Content-Length"] = encodedData.length
46+
}
47+
return this.doApiRequest<T>(<any>options, token || null, it => (<any>it).end(encodedData), 0)
48+
}
49+
50+
protected abstract doApiRequest<T>(options: REQUEST_OPTS, token: string | null, requestProcessor: (request: REQUEST, reject: (error: Error) => void) => void, redirectCount: number): Promise<T>
51+
52+
abstract download(url: string, destination: string, options?: DownloadOptions | null): Promise<string>
3053
}
3154

3255
export class HttpError extends Error {
@@ -59,8 +82,8 @@ export function githubRequest<T>(path: string, token: string | null, data: {[nam
5982
return request<T>({hostname: "api.github.com", path: path}, token, data, method)
6083
}
6184

62-
export function request<T>(url: Url, token: string | null = null, data: {[name: string]: any; } | null = null, method: string = "GET"): Promise<T> {
63-
return executorHolder.httpExecutor.request(url, token, data, method)
85+
export function request<T>(url: Url, token: string | null = null, data: {[name: string]: any; } | null = null, method: string = "GET", headers?: any): Promise<T> {
86+
return executorHolder.httpExecutor.request(url, token, data, method, headers)
6487
}
6588

6689
export function checkSha2(sha2Header: string | null | undefined, sha2: string | null | undefined, callback: (error: Error | null) => void): boolean {

packages/electron-builder/src/util/nodeHttpExecutor.ts

Lines changed: 10 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,15 @@ import BluebirdPromise from "bluebird-lst-c"
66
import * as path from "path"
77
import { homedir } from "os"
88
import { parse as parseIni } from "ini"
9-
import { HttpExecutor, DownloadOptions, HttpError, DigestTransform, checkSha2, calculateDownloadProgress} from "electron-builder-http/out/httpExecutor"
10-
import { Url } from "url"
9+
import { HttpExecutor, DownloadOptions, HttpError, DigestTransform, checkSha2, calculateDownloadProgress, maxRedirects } from "electron-builder-http/out/httpExecutor"
1110
import { RequestOptions } from "https"
1211
import { safeLoad } from "js-yaml"
1312
import { parse as parseUrl } from "url"
1413
import { debug } from "./util"
1514

16-
export class NodeHttpExecutor implements HttpExecutor {
17-
private readonly maxRedirects = 10
18-
15+
export class NodeHttpExecutor extends HttpExecutor<RequestOptions, ClientRequest> {
1916
private httpsAgent: Promise<Agent> | null = null
2017

21-
request<T>(url: Url, token: string | null = null, data: {[name: string]: any; } | null = null, method: string = "GET"): Promise<T> {
22-
const options: any = Object.assign({
23-
method: method,
24-
headers: {
25-
"User-Agent": "electron-builder"
26-
}
27-
}, url)
28-
29-
if (url.hostname!!.includes("github") && !url.path!.endsWith(".yml")) {
30-
options.headers.Accept = "application/vnd.github.v3+json"
31-
}
32-
33-
const encodedData = data == null ? null : new Buffer(JSON.stringify(data))
34-
if (encodedData != null) {
35-
options.method = "post"
36-
options.headers["Content-Type"] = "application/json"
37-
options.headers["Content-Length"] = encodedData.length
38-
}
39-
return this.doApiRequest<T>(options, token, it => it.end(encodedData))
40-
}
41-
4218
download(url: string, destination: string, options?: DownloadOptions | null): Promise<string> {
4319
return <BluebirdPromise<string>>(this.httpsAgent || (this.httpsAgent = createAgent()))
4420
.then(it => new BluebirdPromise( (resolve, reject) => {
@@ -82,11 +58,11 @@ export class NodeHttpExecutor implements HttpExecutor {
8258

8359
const redirectUrl = response.headers.location
8460
if (redirectUrl != null) {
85-
if (redirectCount < this.maxRedirects) {
61+
if (redirectCount < maxRedirects) {
8662
this.doDownload(redirectUrl, destination, redirectCount++, options, agent, callback)
8763
}
8864
else {
89-
callback(new Error("Too many redirects (> " + this.maxRedirects + ")"))
65+
callback(new Error(`Too many redirects (> ${maxRedirects})`))
9066
}
9167
return
9268
}
@@ -159,14 +135,10 @@ Please double check that your authentication token is correct. Due to security r
159135
return
160136
}
161137

162-
if (options.path!.endsWith("/latest")) {
163-
resolve(<any>{location: redirectUrl})
164-
}
165-
else {
166-
this.doApiRequest(Object.assign({}, options, parseUrl(redirectUrl)), token, requestProcessor)
167-
.then(<any>resolve)
168-
.catch(reject)
169-
}
138+
this.doApiRequest(Object.assign({}, options, parseUrl(redirectUrl)), token, requestProcessor)
139+
.then(<any>resolve)
140+
.catch(reject)
141+
170142
return
171143
}
172144

@@ -180,6 +152,7 @@ Please double check that your authentication token is correct. Due to security r
180152
try {
181153
const contentType = response.headers["content-type"]
182154
const isJson = contentType != null && contentType.includes("json")
155+
const isYaml = options.path!.includes(".yml")
183156
if (response.statusCode >= 400) {
184157
if (isJson) {
185158
reject(new HttpError(response, JSON.parse(data)))
@@ -189,7 +162,7 @@ Please double check that your authentication token is correct. Due to security r
189162
}
190163
}
191164
else {
192-
resolve(data.length === 0 ? null : (isJson || !options.path!.includes(".yml")) ? JSON.parse(data) : safeLoad(data))
165+
resolve(data.length === 0 ? null : (isJson ? JSON.parse(data) : isYaml ? safeLoad(data) : data))
193166
}
194167
}
195168
catch (e) {

0 commit comments

Comments
 (0)