UNPKG

10.6 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.NsisUpdater = void 0;
4const builder_util_runtime_1 = require("builder-util-runtime");
5const child_process_1 = require("child_process");
6const path = require("path");
7const BaseUpdater_1 = require("./BaseUpdater");
8const FileWithEmbeddedBlockMapDifferentialDownloader_1 = require("./differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader");
9const GenericDifferentialDownloader_1 = require("./differentialDownloader/GenericDifferentialDownloader");
10const main_1 = require("./main");
11const util_1 = require("./util");
12const Provider_1 = require("./providers/Provider");
13const fs_extra_1 = require("fs-extra");
14const windowsExecutableCodeSignatureVerifier_1 = require("./windowsExecutableCodeSignatureVerifier");
15const url_1 = require("url");
16const zlib_1 = require("zlib");
17class NsisUpdater extends BaseUpdater_1.BaseUpdater {
18 constructor(options, app) {
19 super(options, app);
20 }
21 /*** @private */
22 doDownloadUpdate(downloadUpdateOptions) {
23 const provider = downloadUpdateOptions.updateInfoAndProvider.provider;
24 const fileInfo = Provider_1.findFile(provider.resolveFiles(downloadUpdateOptions.updateInfoAndProvider.info), "exe");
25 return this.executeDownload({
26 fileExtension: "exe",
27 downloadUpdateOptions,
28 fileInfo,
29 task: async (destinationFile, downloadOptions, packageFile, removeTempDirIfAny) => {
30 const packageInfo = fileInfo.packageInfo;
31 const isWebInstaller = packageInfo != null && packageFile != null;
32 if (isWebInstaller || (await this.differentialDownloadInstaller(fileInfo, downloadUpdateOptions, destinationFile, provider))) {
33 await this.httpExecutor.download(fileInfo.url, destinationFile, downloadOptions);
34 }
35 const signatureVerificationStatus = await this.verifySignature(destinationFile);
36 if (signatureVerificationStatus != null) {
37 await removeTempDirIfAny();
38 // noinspection ThrowInsideFinallyBlockJS
39 throw builder_util_runtime_1.newError(`New version ${downloadUpdateOptions.updateInfoAndProvider.info.version} is not signed by the application owner: ${signatureVerificationStatus}`, "ERR_UPDATER_INVALID_SIGNATURE");
40 }
41 if (isWebInstaller) {
42 if (await this.differentialDownloadWebPackage(downloadUpdateOptions, packageInfo, packageFile, provider)) {
43 try {
44 await this.httpExecutor.download(new url_1.URL(packageInfo.path), packageFile, {
45 headers: downloadUpdateOptions.requestHeaders,
46 cancellationToken: downloadUpdateOptions.cancellationToken,
47 sha512: packageInfo.sha512,
48 });
49 }
50 catch (e) {
51 try {
52 await fs_extra_1.unlink(packageFile);
53 }
54 catch (ignored) {
55 // ignore
56 }
57 throw e;
58 }
59 }
60 }
61 },
62 });
63 }
64 // $certificateInfo = (Get-AuthenticodeSignature 'xxx\yyy.exe'
65 // | where {$_.Status.Equals([System.Management.Automation.SignatureStatus]::Valid) -and $_.SignerCertificate.Subject.Contains("CN=siemens.com")})
66 // | Out-String ; if ($certificateInfo) { exit 0 } else { exit 1 }
67 async verifySignature(tempUpdateFile) {
68 let publisherName;
69 try {
70 publisherName = (await this.configOnDisk.value).publisherName;
71 if (publisherName == null) {
72 return null;
73 }
74 }
75 catch (e) {
76 if (e.code === "ENOENT") {
77 // no app-update.yml
78 return null;
79 }
80 throw e;
81 }
82 return await windowsExecutableCodeSignatureVerifier_1.verifySignature(Array.isArray(publisherName) ? publisherName : [publisherName], tempUpdateFile, this._logger);
83 }
84 doInstall(options) {
85 const args = ["--updated"];
86 if (options.isSilent) {
87 args.push("/S");
88 }
89 if (options.isForceRunAfter) {
90 args.push("--force-run");
91 }
92 const packagePath = this.downloadedUpdateHelper == null ? null : this.downloadedUpdateHelper.packageFile;
93 if (packagePath != null) {
94 // only = form is supported
95 args.push(`--package-file=${packagePath}`);
96 }
97 const callUsingElevation = () => {
98 _spawn(path.join(process.resourcesPath, "elevate.exe"), [options.installerPath].concat(args)).catch(e => this.dispatchError(e));
99 };
100 if (options.isAdminRightsRequired) {
101 this._logger.info("isAdminRightsRequired is set to true, run installer using elevate.exe");
102 callUsingElevation();
103 return true;
104 }
105 _spawn(options.installerPath, args).catch((e) => {
106 // https://github.com/electron-userland/electron-builder/issues/1129
107 // Node 8 sends errors: https://nodejs.org/dist/latest-v8.x/docs/api/errors.html#errors_common_system_errors
108 const errorCode = e.code;
109 this._logger.info(`Cannot run installer: error code: ${errorCode}, error message: "${e.message}", will be executed again using elevate if EACCES"`);
110 if (errorCode === "UNKNOWN" || errorCode === "EACCES") {
111 callUsingElevation();
112 }
113 else {
114 this.dispatchError(e);
115 }
116 });
117 return true;
118 }
119 async differentialDownloadInstaller(fileInfo, downloadUpdateOptions, installerPath, provider) {
120 try {
121 if (this._testOnlyOptions != null && !this._testOnlyOptions.isUseDifferentialDownload) {
122 return true;
123 }
124 const blockmapFileUrls = util_1.blockmapFiles(fileInfo.url, this.app.version, downloadUpdateOptions.updateInfoAndProvider.info.version);
125 this._logger.info(`Download block maps (old: "${blockmapFileUrls[0]}", new: ${blockmapFileUrls[1]})`);
126 const downloadBlockMap = async (url) => {
127 const data = await this.httpExecutor.downloadToBuffer(url, {
128 headers: downloadUpdateOptions.requestHeaders,
129 cancellationToken: downloadUpdateOptions.cancellationToken,
130 });
131 if (data == null || data.length === 0) {
132 throw new Error(`Blockmap "${url.href}" is empty`);
133 }
134 try {
135 return JSON.parse(zlib_1.gunzipSync(data).toString());
136 }
137 catch (e) {
138 throw new Error(`Cannot parse blockmap "${url.href}", error: ${e}`);
139 }
140 };
141 const downloadOptions = {
142 newUrl: fileInfo.url,
143 oldFile: path.join(this.downloadedUpdateHelper.cacheDir, builder_util_runtime_1.CURRENT_APP_INSTALLER_FILE_NAME),
144 logger: this._logger,
145 newFile: installerPath,
146 isUseMultipleRangeRequest: provider.isUseMultipleRangeRequest,
147 requestHeaders: downloadUpdateOptions.requestHeaders,
148 cancellationToken: downloadUpdateOptions.cancellationToken,
149 };
150 if (this.listenerCount(main_1.DOWNLOAD_PROGRESS) > 0) {
151 downloadOptions.onProgress = it => this.emit(main_1.DOWNLOAD_PROGRESS, it);
152 }
153 const blockMapDataList = await Promise.all(blockmapFileUrls.map(u => downloadBlockMap(u)));
154 await new GenericDifferentialDownloader_1.GenericDifferentialDownloader(fileInfo.info, this.httpExecutor, downloadOptions).download(blockMapDataList[0], blockMapDataList[1]);
155 return false;
156 }
157 catch (e) {
158 this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`);
159 if (this._testOnlyOptions != null) {
160 // test mode
161 throw e;
162 }
163 return true;
164 }
165 }
166 async differentialDownloadWebPackage(downloadUpdateOptions, packageInfo, packagePath, provider) {
167 if (packageInfo.blockMapSize == null) {
168 return true;
169 }
170 try {
171 const downloadOptions = {
172 newUrl: new url_1.URL(packageInfo.path),
173 oldFile: path.join(this.downloadedUpdateHelper.cacheDir, builder_util_runtime_1.CURRENT_APP_PACKAGE_FILE_NAME),
174 logger: this._logger,
175 newFile: packagePath,
176 requestHeaders: this.requestHeaders,
177 isUseMultipleRangeRequest: provider.isUseMultipleRangeRequest,
178 cancellationToken: downloadUpdateOptions.cancellationToken,
179 };
180 if (this.listenerCount(main_1.DOWNLOAD_PROGRESS) > 0) {
181 downloadOptions.onProgress = it => this.emit(main_1.DOWNLOAD_PROGRESS, it);
182 }
183 await new FileWithEmbeddedBlockMapDifferentialDownloader_1.FileWithEmbeddedBlockMapDifferentialDownloader(packageInfo, this.httpExecutor, downloadOptions).download();
184 }
185 catch (e) {
186 this._logger.error(`Cannot download differentially, fallback to full download: ${e.stack || e}`);
187 // during test (developer machine mac or linux) we must throw error
188 return process.platform === "win32";
189 }
190 return false;
191 }
192}
193exports.NsisUpdater = NsisUpdater;
194/**
195 * This handles both node 8 and node 10 way of emitting error when spawning a process
196 * - node 8: Throws the error
197 * - node 10: Emit the error(Need to listen with on)
198 */
199async function _spawn(exe, args) {
200 return new Promise((resolve, reject) => {
201 try {
202 const process = child_process_1.spawn(exe, args, {
203 detached: true,
204 stdio: "ignore",
205 });
206 process.on("error", error => {
207 reject(error);
208 });
209 process.unref();
210 if (process.pid !== undefined) {
211 resolve(true);
212 }
213 }
214 catch (error) {
215 reject(error);
216 }
217 });
218}
219//# sourceMappingURL=NsisUpdater.js.map
\No newline at end of file