Skip to content

Commit

Permalink
feat(electron-updater): Make it possible to "auto-downgrade" the appl…
Browse files Browse the repository at this point in the history
…ication on channel change

Close electron-userland#1149
  • Loading branch information
develar committed Apr 16, 2017
1 parent 4351b56 commit ea29397
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 27 deletions.
45 changes: 32 additions & 13 deletions packages/electron-updater/src/AppUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { EventEmitter } from "events"
import { readFile } from "fs-extra-p"
import { safeLoad } from "js-yaml"
import * as path from "path"
import { gt as isVersionGreaterThan, valid as parseVersion } from "semver"
import { eq as isVersionsEqual, gt as isVersionGreaterThan, prerelease as getVersionPreleaseComponents, valid as parseVersion } from "semver"
import "source-map-support/register"
import { FileInfo, Provider, UpdateCheckResult, UpdaterSignal } from "./api"
import { BintrayProvider } from "./BintrayProvider"
Expand All @@ -25,10 +25,23 @@ export interface Logger {

export abstract class AppUpdater extends EventEmitter {
/**
* Automatically download an update when it is found.
* Whether to automatically download an update when it is found.
*/
autoDownload = true

/**
* *GitHub provider only.* Whether to allow update to pre-release versions. Defaults to `true` if application version contains prerelease components (e.g. `0.12.1-alpha.1`, here `alpha` is a prerelease component), otherwise `false`.
*
* If `true`, downgrade will be allowed (`allowDowngrade` will be set to `true`).
*/
allowPrerelease = false

/**
* Whether to allow version downgrade (when a user from the beta channel wants to go back to the stable channel).
* Defaults to `true` if application version contains prerelease components (e.g. `0.12.1-alpha.1`, here `alpha` is a prerelease component), otherwise `false`.
*/
allowDowngrade = false

/**
* The request headers.
*/
Expand Down Expand Up @@ -64,7 +77,9 @@ export abstract class AppUpdater extends EventEmitter {
protected versionInfo: VersionInfo | null
private fileInfo: FileInfo | null

constructor(options: PublishConfiguration | null | undefined) {
private currentVersion: string

constructor(options: PublishConfiguration | null | undefined, app?: any) {
super()

this.on("error", (error: Error) => {
Expand All @@ -73,8 +88,8 @@ export abstract class AppUpdater extends EventEmitter {
}
})

if ((<any>global).__test_app != null) {
this.app = (<any>global).__test_app
if (app != null || (<any>global).__test_app != null) {
this.app = app || (<any>global).__test_app
this.untilAppReady = BluebirdPromise.resolve()
}
else {
Expand All @@ -96,6 +111,16 @@ export abstract class AppUpdater extends EventEmitter {
})
}

const currentVersionString = this.app.getVersion()
this.currentVersion = parseVersion(currentVersionString)
if (this.currentVersion == null) {
throw new Error(`App version is not valid semver version: "${currentVersionString}`)
}

const versionPrereleaseComponent = getVersionPreleaseComponents(this.currentVersion)
this.allowDowngrade = versionPrereleaseComponent != null && versionPrereleaseComponent.length > 0
this.allowPrerelease = this.allowDowngrade

if (options != null) {
this.setFeedURL(options)
}
Expand Down Expand Up @@ -171,16 +196,10 @@ export abstract class AppUpdater extends EventEmitter {
throw new Error(`Latest version (from update server) is not valid semver version: "${latestVersion}`)
}

const currentVersionString = this.app.getVersion()
const currentVersion = parseVersion(currentVersionString)
if (currentVersion == null) {
throw new Error(`App version is not valid semver version: "${currentVersion}`)
}

if (!isVersionGreaterThan(latestVersion, currentVersion)) {
if (this.allowDowngrade ? isVersionsEqual(latestVersion, this.currentVersion) : !isVersionGreaterThan(latestVersion, this.currentVersion)) {
this.updateAvailable = false
if (this.logger != null) {
this.logger.info(`Update for version ${currentVersionString} is not available (latest version: ${versionInfo.version})`)
this.logger.info(`Update for version ${this.currentVersion} is not available (latest version: ${versionInfo.version}, downgrade is ${this.allowDowngrade ? "allowed" : "disallowed"}.`)
}
this.emit("update-not-available", versionInfo)
return {
Expand Down
4 changes: 2 additions & 2 deletions packages/electron-updater/src/NsisUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export class NsisUpdater extends AppUpdater {
private quitAndInstallCalled = false
private quitHandlerAdded = false

constructor(options?: PublishConfiguration) {
super(options)
constructor(options?: PublishConfiguration, app?: any) {
super(options, app)
}

/**
Expand Down
10 changes: 10 additions & 0 deletions test/out/__snapshots__/nsisUpdaterTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ Array [
]
`;

exports[`downgrade (allowed) 1`] = `
Object {
"name": "TestApp Setup 1.1.0.exe",
"sha2": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b",
"url": "https://dl.bintray.com/actperepo/generic/TestApp Setup 1.1.0.exe",
}
`;

exports[`downgrade (disallowed) 1`] = `undefined`;

exports[`file url 1`] = `
Object {
"name": "TestApp Setup 1.1.0.exe",
Expand Down
63 changes: 51 additions & 12 deletions test/src/nsisUpdaterTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,8 @@ test("check updates - no versions at all", async () => {
await assertThat(updater.checkForUpdates()).throws()
})

// test("cannot find suitable file for version", async () => {
// const updater = new NsisUpdater(<BintrayOptions>{
// provider: "bintray",
// owner: "actperepo",
// package: "incorrect-file-version",
// })
//
// await assertThat(updater.checkForUpdates()).throws()
// })

test("file url", async () => {
const updater = new NsisUpdater()
async function testUpdateFromBintray(app: any) {
const updater = new NsisUpdater(null, app)
updater.updateConfigPath = await writeUpdateConfig(<BintrayOptions>{
provider: "bintray",
owner: "actperepo",
Expand All @@ -71,9 +61,58 @@ test("file url", async () => {
expect(updateCheckResult.fileInfo).toMatchSnapshot()
await assertThat(path.join(await updateCheckResult.downloadPromise)).isFile()

expect(actualEvents).toEqual(expectedEvents)
}
test("file url", () => testUpdateFromBintray(null))

test("downgrade (disallowed)", async () => {
const updater = new NsisUpdater(null, {
getVersion: function () {
return "2.0.0"
},

getAppPath: function () {
},

on: function () {
// ignored
},
}
)
updater.updateConfigPath = await writeUpdateConfig(<BintrayOptions>{
provider: "bintray",
owner: "actperepo",
package: "TestApp",
})

const actualEvents: Array<string> = []
const expectedEvents = ["checking-for-update", "update-not-available"]
for (const eventName of expectedEvents) {
updater.addListener(eventName, () => {
actualEvents.push(eventName)
})
}

const updateCheckResult = await updater.checkForUpdates()
expect(updateCheckResult.fileInfo).toMatchSnapshot()
expect(updateCheckResult.downloadPromise).toBeUndefined()

expect(actualEvents).toEqual(expectedEvents)
})

test("downgrade (allowed)", () => testUpdateFromBintray({
getVersion: function () {
return "2.0.0-beta.1"
},

getAppPath: function () {
},

on: function () {
// ignored
},
}))

test("file url generic", async () => {
const updater = new NsisUpdater()
updater.updateConfigPath = await writeUpdateConfig(<GenericServerOptions>{
Expand Down

0 comments on commit ea29397

Please sign in to comment.