-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathBaseUpdater.ts
161 lines (142 loc) · 5.35 KB
/
BaseUpdater.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import { AllPublishOptions } from "builder-util-runtime"
import { spawn, SpawnOptions, spawnSync, StdioOptions } from "child_process"
import { AppAdapter } from "./AppAdapter"
import { AppUpdater, DownloadExecutorTask } from "./AppUpdater"
export abstract class BaseUpdater extends AppUpdater {
protected quitAndInstallCalled = false
private quitHandlerAdded = false
protected constructor(options?: AllPublishOptions | null, app?: AppAdapter) {
super(options, app)
}
quitAndInstall(isSilent = false, isForceRunAfter = false): void {
this._logger.info(`Install on explicit quitAndInstall`)
// If NOT in silent mode use `autoRunAppAfterInstall` to determine whether to force run the app
const isInstalled = this.install(isSilent, isSilent ? isForceRunAfter : this.autoRunAppAfterInstall)
if (isInstalled) {
setImmediate(() => {
// this event is normally emitted when calling quitAndInstall, this emulates that
require("electron").autoUpdater.emit("before-quit-for-update")
this.app.quit()
})
} else {
this.quitAndInstallCalled = false
}
}
protected executeDownload(taskOptions: DownloadExecutorTask): Promise<Array<string>> {
return super.executeDownload({
...taskOptions,
done: event => {
this.dispatchUpdateDownloaded(event)
this.addQuitHandler()
return Promise.resolve()
},
})
}
// must be sync
protected abstract doInstall(options: InstallOptions): boolean
// must be sync (because quit even handler is not async)
install(isSilent = false, isForceRunAfter = false): boolean {
if (this.quitAndInstallCalled) {
this._logger.warn("install call ignored: quitAndInstallCalled is set to true")
return false
}
const downloadedUpdateHelper = this.downloadedUpdateHelper
const installerPath = downloadedUpdateHelper == null ? null : downloadedUpdateHelper.file
const downloadedFileInfo = downloadedUpdateHelper == null ? null : downloadedUpdateHelper.downloadedFileInfo
if (installerPath == null || downloadedFileInfo == null) {
this.dispatchError(new Error("No valid update available, can't quit and install"))
return false
}
// prevent calling several times
this.quitAndInstallCalled = true
try {
this._logger.info(`Install: isSilent: ${isSilent}, isForceRunAfter: ${isForceRunAfter}`)
return this.doInstall({
installerPath,
isSilent,
isForceRunAfter,
isAdminRightsRequired: downloadedFileInfo.isAdminRightsRequired,
})
} catch (e: any) {
this.dispatchError(e)
return false
}
}
protected addQuitHandler(): void {
if (this.quitHandlerAdded || !this.autoInstallOnAppQuit) {
return
}
this.quitHandlerAdded = true
this.app.onQuit(exitCode => {
if (this.quitAndInstallCalled) {
this._logger.info("Update installer has already been triggered. Quitting application.")
return
}
if (!this.autoInstallOnAppQuit) {
this._logger.info("Update will not be installed on quit because autoInstallOnAppQuit is set to false.")
return
}
if (exitCode !== 0) {
this._logger.info(`Update will be not installed on quit because application is quitting with exit code ${exitCode}`)
return
}
this._logger.info("Auto install update on quit")
this.install(true, false)
})
}
protected wrapSudo() {
const { name } = this.app
const installComment = `"${name} would like to update"`
const sudo = this.spawnSyncLog("which gksudo || which kdesudo || which pkexec || which beesu")
const command = [sudo]
if (/kdesudo/i.test(sudo)) {
command.push("--comment", installComment)
command.push("-c")
} else if (/gksudo/i.test(sudo)) {
command.push("--message", installComment)
} else if (/pkexec/i.test(sudo)) {
command.push("--disable-internal-agent")
}
return command.join(" ")
}
protected spawnSyncLog(cmd: string, args: string[] = [], env = {}): string {
this._logger.info(`Executing: ${cmd} with args: ${args}`)
const response = spawnSync(cmd, args, {
env: { ...process.env, ...env },
encoding: "utf-8",
shell: true,
})
return response.stdout.trim()
}
/**
* This handles both node 8 and node 10 way of emitting error when spawning a process
* - node 8: Throws the error
* - node 10: Emit the error(Need to listen with on)
*/
// https://github.com/electron-userland/electron-builder/issues/1129
// Node 8 sends errors: https://nodejs.org/dist/latest-v8.x/docs/api/errors.html#errors_common_system_errors
protected async spawnLog(cmd: string, args: string[] = [], env: any = undefined, stdio: StdioOptions = "ignore"): Promise<boolean> {
this._logger.info(`Executing: ${cmd} with args: ${args}`)
return new Promise<boolean>((resolve, reject) => {
try {
const params: SpawnOptions = { stdio, env, detached: true }
const p = spawn(cmd, args, params)
p.on("error", error => {
reject(error)
})
p.unref()
if (p.pid !== undefined) {
resolve(true)
}
} catch (error) {
reject(error)
}
})
}
}
export interface InstallOptions {
readonly installerPath: string
readonly isSilent: boolean
readonly isForceRunAfter: boolean
readonly isAdminRightsRequired: boolean
}