AppUpdater.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.NoOpLogger = exports.AppUpdater = void 0;
  6. function _bluebirdLst() {
  7. const data = require("bluebird-lst");
  8. _bluebirdLst = function () {
  9. return data;
  10. };
  11. return data;
  12. }
  13. function _builderUtilRuntime() {
  14. const data = require("builder-util-runtime");
  15. _builderUtilRuntime = function () {
  16. return data;
  17. };
  18. return data;
  19. }
  20. function _crypto() {
  21. const data = require("crypto");
  22. _crypto = function () {
  23. return data;
  24. };
  25. return data;
  26. }
  27. function _electron() {
  28. const data = require("electron");
  29. _electron = function () {
  30. return data;
  31. };
  32. return data;
  33. }
  34. function _electronIsDev() {
  35. const data = _interopRequireDefault(require("electron-is-dev"));
  36. _electronIsDev = function () {
  37. return data;
  38. };
  39. return data;
  40. }
  41. function _events() {
  42. const data = require("events");
  43. _events = function () {
  44. return data;
  45. };
  46. return data;
  47. }
  48. function _fsExtraP() {
  49. const data = require("fs-extra-p");
  50. _fsExtraP = function () {
  51. return data;
  52. };
  53. return data;
  54. }
  55. function _jsYaml() {
  56. const data = require("js-yaml");
  57. _jsYaml = function () {
  58. return data;
  59. };
  60. return data;
  61. }
  62. function _lazyVal() {
  63. const data = require("lazy-val");
  64. _lazyVal = function () {
  65. return data;
  66. };
  67. return data;
  68. }
  69. var path = _interopRequireWildcard(require("path"));
  70. function _semver() {
  71. const data = require("semver");
  72. _semver = function () {
  73. return data;
  74. };
  75. return data;
  76. }
  77. require("source-map-support/register");
  78. function _electronHttpExecutor() {
  79. const data = require("./electronHttpExecutor");
  80. _electronHttpExecutor = function () {
  81. return data;
  82. };
  83. return data;
  84. }
  85. function _GenericProvider() {
  86. const data = require("./GenericProvider");
  87. _GenericProvider = function () {
  88. return data;
  89. };
  90. return data;
  91. }
  92. function _main() {
  93. const data = require("./main");
  94. _main = function () {
  95. return data;
  96. };
  97. return data;
  98. }
  99. function _providerFactory() {
  100. const data = require("./providerFactory");
  101. _providerFactory = function () {
  102. return data;
  103. };
  104. return data;
  105. }
  106. function _DownloadedUpdateHelper() {
  107. const data = require("./DownloadedUpdateHelper");
  108. _DownloadedUpdateHelper = function () {
  109. return data;
  110. };
  111. return data;
  112. }
  113. function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
  114. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  115. class AppUpdater extends _events().EventEmitter {
  116. constructor(options, app) {
  117. super();
  118. /**
  119. * Whether to automatically download an update when it is found.
  120. */
  121. this.autoDownload = true;
  122. /**
  123. * Whether to automatically install a downloaded update on app quit (if `quitAndInstall` was not called before).
  124. *
  125. * Applicable only on Windows and Linux.
  126. */
  127. this.autoInstallOnAppQuit = true;
  128. /**
  129. * *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`.
  130. *
  131. * If `true`, downgrade will be allowed (`allowDowngrade` will be set to `true`).
  132. */
  133. this.allowPrerelease = false;
  134. /**
  135. * *GitHub provider only.* Get all release notes (from current version to latest), not just the latest.
  136. * @default false
  137. */
  138. this.fullChangelog = false;
  139. /**
  140. * Whether to allow version downgrade (when a user from the beta channel wants to go back to the stable channel).
  141. * @default false
  142. */
  143. this.allowDowngrade = false;
  144. this._channel = null;
  145. /**
  146. * The request headers.
  147. */
  148. this.requestHeaders = null;
  149. this._logger = console;
  150. /**
  151. * For type safety you can use signals, e.g. `autoUpdater.signals.updateDownloaded(() => {})` instead of `autoUpdater.on('update-available', () => {})`
  152. */
  153. this.signals = new (_main().UpdaterSignal)(this);
  154. this._appUpdateConfigPath = null;
  155. this.updateAvailable = false;
  156. this.clientPromise = null;
  157. this.stagingUserIdPromise = new (_lazyVal().Lazy)(() => this.getOrCreateStagingUserId()); // public, allow to read old config for anyone
  158. this.configOnDisk = new (_lazyVal().Lazy)(() => this.loadUpdateConfig());
  159. this.checkForUpdatesPromise = null;
  160. this.updateInfo = null;
  161. this.on("error", error => {
  162. this._logger.error(`Error: ${error.stack || error.message}`);
  163. });
  164. if (app != null || global.__test_app != null) {
  165. this.app = app || global.__test_app;
  166. this.untilAppReady = Promise.resolve();
  167. this.httpExecutor = null;
  168. } else {
  169. this.app = require("electron").app;
  170. this.httpExecutor = new (_electronHttpExecutor().ElectronHttpExecutor)((authInfo, callback) => this.emit("login", authInfo, callback));
  171. this.untilAppReady = new Promise(resolve => {
  172. if (this.app.isReady()) {
  173. resolve();
  174. } else {
  175. this.app.on("ready", resolve);
  176. }
  177. });
  178. }
  179. this.downloadedUpdateHelper = new (_DownloadedUpdateHelper().DownloadedUpdateHelper)(this.app.getPath("userData"));
  180. const currentVersionString = this.app.getVersion();
  181. const currentVersion = (0, _semver().valid)(currentVersionString);
  182. if (currentVersion == null) {
  183. throw (0, _builderUtilRuntime().newError)(`App version is not a valid semver version: "${currentVersionString}`, "ERR_UPDATER_INVALID_VERSION");
  184. }
  185. this.currentVersion = currentVersion;
  186. this.allowPrerelease = hasPrereleaseComponents(this.currentVersion);
  187. if (options != null) {
  188. this.setFeedURL(options);
  189. }
  190. }
  191. /**
  192. * Get the update channel. Not applicable for GitHub. Doesn't return `channel` from the update configuration, only if was previously set.
  193. */
  194. get channel() {
  195. return this._channel;
  196. }
  197. /**
  198. * Set the update channel. Not applicable for GitHub. Overrides `channel` in the update configuration.
  199. *
  200. * `allowDowngrade` will be automatically set to `true`. If this behavior is not suitable for you, simple set `allowDowngrade` explicitly after.
  201. */
  202. set channel(value) {
  203. if (this._channel != null) {
  204. if (typeof value !== "string") {
  205. throw (0, _builderUtilRuntime().newError)(`Channel must be a string, but got: ${value}`, "ERR_UPDATER_INVALID_CHANNEL");
  206. } else if (value.length === 0) {
  207. throw (0, _builderUtilRuntime().newError)(`Channel must be not an empty string`, "ERR_UPDATER_INVALID_CHANNEL");
  208. }
  209. }
  210. this._channel = value;
  211. this.allowDowngrade = true;
  212. }
  213. /**
  214. * The logger. You can pass [electron-log](https://github.com/megahertz/electron-log), [winston](https://github.com/winstonjs/winston) or another logger with the following interface: `{ info(), warn(), error() }`.
  215. * Set it to `null` if you would like to disable a logging feature.
  216. */
  217. get logger() {
  218. return this._logger;
  219. }
  220. set logger(value) {
  221. this._logger = value == null ? new NoOpLogger() : value;
  222. } // noinspection JSUnusedGlobalSymbols
  223. /**
  224. * test only
  225. * @private
  226. */
  227. set updateConfigPath(value) {
  228. this.clientPromise = null;
  229. this._appUpdateConfigPath = value;
  230. this.configOnDisk = new (_lazyVal().Lazy)(() => this.loadUpdateConfig());
  231. }
  232. get provider() {
  233. return this.clientPromise;
  234. } //noinspection JSMethodCanBeStatic,JSUnusedGlobalSymbols
  235. getFeedURL() {
  236. return "Deprecated. Do not use it.";
  237. }
  238. /**
  239. * Configure update provider. If value is `string`, [GenericServerOptions](/configuration/publish.md#genericserveroptions) will be set with value as `url`.
  240. * @param options If you want to override configuration in the `app-update.yml`.
  241. */
  242. setFeedURL(options) {
  243. // https://github.com/electron-userland/electron-builder/issues/1105
  244. let provider;
  245. if (typeof options === "string") {
  246. provider = new (_GenericProvider().GenericProvider)({
  247. provider: "generic",
  248. url: options
  249. }, this);
  250. } else {
  251. provider = (0, _providerFactory().createClient)(options, this);
  252. }
  253. this.clientPromise = Promise.resolve(provider);
  254. }
  255. /**
  256. * Asks the server whether there is an update.
  257. */
  258. checkForUpdates() {
  259. let checkForUpdatesPromise = this.checkForUpdatesPromise;
  260. if (checkForUpdatesPromise != null) {
  261. return checkForUpdatesPromise;
  262. }
  263. checkForUpdatesPromise = this._checkForUpdates();
  264. this.checkForUpdatesPromise = checkForUpdatesPromise;
  265. const nullizePromise = () => this.checkForUpdatesPromise = null;
  266. checkForUpdatesPromise.then(nullizePromise).catch(nullizePromise);
  267. return checkForUpdatesPromise;
  268. }
  269. checkForUpdatesAndNotify() {
  270. if (_electronIsDev().default) {
  271. return Promise.resolve(null);
  272. }
  273. const checkForUpdatesPromise = this.checkForUpdates();
  274. checkForUpdatesPromise.then(it => {
  275. const downloadPromise = it.downloadPromise;
  276. if (downloadPromise == null) {
  277. this._logger.warn("checkForUpdatesAndNotify called, but downloadPromise is null");
  278. return;
  279. }
  280. downloadPromise.then(() => {
  281. new (_electron().Notification)({
  282. title: "A new update is ready to install",
  283. body: `${this.app.getName()} version ${it.updateInfo.version} is downloaded and will be automatically installed on exit`
  284. }).show();
  285. });
  286. });
  287. return checkForUpdatesPromise;
  288. }
  289. isStagingMatch(updateInfo) {
  290. var _this = this;
  291. return (0, _bluebirdLst().coroutine)(function* () {
  292. const rawStagingPercentage = updateInfo.stagingPercentage;
  293. let stagingPercentage = rawStagingPercentage;
  294. if (stagingPercentage == null) {
  295. return true;
  296. }
  297. stagingPercentage = parseInt(stagingPercentage, 10);
  298. if (isNaN(stagingPercentage)) {
  299. _this._logger.warn(`Staging percentage is NaN: ${rawStagingPercentage}`);
  300. return true;
  301. } // convert from user 0-100 to internal 0-1
  302. stagingPercentage = stagingPercentage / 100;
  303. const stagingUserId = yield _this.stagingUserIdPromise.value;
  304. const val = _builderUtilRuntime().UUID.parse(stagingUserId).readUInt32BE(12);
  305. const percentage = val / 0xFFFFFFFF;
  306. _this._logger.info(`Staging percentage: ${stagingPercentage}, percentage: ${percentage}, user id: ${stagingUserId}`);
  307. return percentage < stagingPercentage;
  308. })();
  309. }
  310. _checkForUpdates() {
  311. var _this2 = this;
  312. return (0, _bluebirdLst().coroutine)(function* () {
  313. try {
  314. yield _this2.untilAppReady;
  315. _this2._logger.info("Checking for update");
  316. _this2.emit("checking-for-update");
  317. return yield _this2.doCheckForUpdates();
  318. } catch (e) {
  319. _this2.emit("error", e, `Cannot check for updates: ${(e.stack || e).toString()}`);
  320. throw e;
  321. }
  322. })();
  323. }
  324. computeFinalHeaders(headers) {
  325. if (this.requestHeaders != null) {
  326. Object.assign(headers, this.requestHeaders);
  327. }
  328. return headers;
  329. }
  330. getUpdateInfo() {
  331. var _this3 = this;
  332. return (0, _bluebirdLst().coroutine)(function* () {
  333. yield _this3.untilAppReady;
  334. if (_this3.clientPromise == null) {
  335. _this3.clientPromise = _this3.configOnDisk.value.then(it => (0, _providerFactory().createClient)(it, _this3));
  336. }
  337. const client = yield _this3.clientPromise;
  338. const stagingUserId = yield _this3.stagingUserIdPromise.value;
  339. client.setRequestHeaders(_this3.computeFinalHeaders({
  340. "X-User-Staging-Id": stagingUserId
  341. }));
  342. return yield client.getLatestVersion();
  343. })();
  344. }
  345. doCheckForUpdates() {
  346. var _this4 = this;
  347. return (0, _bluebirdLst().coroutine)(function* () {
  348. const updateInfo = yield _this4.getUpdateInfo();
  349. const latestVersion = (0, _semver().valid)(updateInfo.version);
  350. if (latestVersion == null) {
  351. throw (0, _builderUtilRuntime().newError)(`Latest version (from update server) is not valid semver version: "${latestVersion}`, "ERR_UPDATER_INVALID_VERSION");
  352. }
  353. const isStagingMatch = yield _this4.isStagingMatch(updateInfo);
  354. if (!isStagingMatch || (_this4.allowDowngrade && !hasPrereleaseComponents(latestVersion) ? (0, _semver().eq)(latestVersion, _this4.currentVersion) : !(0, _semver().gt)(latestVersion, _this4.currentVersion))) {
  355. _this4.updateAvailable = false;
  356. _this4._logger.info(`Update for version ${_this4.currentVersion} is not available (latest version: ${updateInfo.version}, downgrade is ${_this4.allowDowngrade ? "allowed" : "disallowed"}).`);
  357. _this4.emit("update-not-available", updateInfo);
  358. return {
  359. versionInfo: updateInfo,
  360. updateInfo
  361. };
  362. }
  363. _this4.updateAvailable = true;
  364. _this4.updateInfo = updateInfo;
  365. _this4.onUpdateAvailable(updateInfo);
  366. const cancellationToken = new (_builderUtilRuntime().CancellationToken)(); //noinspection ES6MissingAwait
  367. return {
  368. versionInfo: updateInfo,
  369. updateInfo,
  370. cancellationToken,
  371. downloadPromise: _this4.autoDownload ? _this4.downloadUpdate(cancellationToken) : null
  372. };
  373. })();
  374. }
  375. onUpdateAvailable(updateInfo) {
  376. this._logger.info(`Found version ${updateInfo.version} (url: ${(0, _builderUtilRuntime().asArray)(updateInfo.files).map(it => it.url).join(", ")})`);
  377. this.emit("update-available", updateInfo);
  378. }
  379. /**
  380. * Start downloading update manually. You can use this method if `autoDownload` option is set to `false`.
  381. * @returns {Promise<string>} Path to downloaded file.
  382. */
  383. downloadUpdate(cancellationToken = new (_builderUtilRuntime().CancellationToken)()) {
  384. var _this5 = this;
  385. return (0, _bluebirdLst().coroutine)(function* () {
  386. const updateInfo = _this5.updateInfo;
  387. if (updateInfo == null) {
  388. const error = new Error("Please check update first");
  389. _this5.dispatchError(error);
  390. throw error;
  391. }
  392. _this5._logger.info(`Downloading update from ${(0, _builderUtilRuntime().asArray)(updateInfo.files).map(it => it.url).join(", ")}`);
  393. try {
  394. return yield _this5.doDownloadUpdate(updateInfo, cancellationToken);
  395. } catch (e) {
  396. _this5.dispatchError(e);
  397. throw e;
  398. }
  399. })();
  400. }
  401. dispatchError(e) {
  402. this.emit("error", e, (e.stack || e).toString());
  403. }
  404. loadUpdateConfig() {
  405. var _this6 = this;
  406. return (0, _bluebirdLst().coroutine)(function* () {
  407. if (_this6._appUpdateConfigPath == null) {
  408. _this6._appUpdateConfigPath = _electronIsDev().default ? path.join(_this6.app.getAppPath(), "dev-app-update.yml") : path.join(process.resourcesPath, "app-update.yml");
  409. }
  410. return (0, _jsYaml().safeLoad)((yield (0, _fsExtraP().readFile)(_this6._appUpdateConfigPath, "utf-8")));
  411. })();
  412. }
  413. /*** @private */
  414. computeRequestHeaders() {
  415. var _this7 = this;
  416. return (0, _bluebirdLst().coroutine)(function* () {
  417. const fileExtraDownloadHeaders = (yield _this7.provider).fileExtraDownloadHeaders;
  418. if (fileExtraDownloadHeaders != null) {
  419. const requestHeaders = _this7.requestHeaders;
  420. return requestHeaders == null ? fileExtraDownloadHeaders : Object.assign({}, fileExtraDownloadHeaders, requestHeaders);
  421. }
  422. return _this7.computeFinalHeaders({
  423. Accept: "*/*"
  424. });
  425. })();
  426. }
  427. getOrCreateStagingUserId() {
  428. var _this8 = this;
  429. return (0, _bluebirdLst().coroutine)(function* () {
  430. const file = path.join(_this8.app.getPath("userData"), ".updaterId");
  431. try {
  432. const id = yield (0, _fsExtraP().readFile)(file, "utf-8");
  433. if (_builderUtilRuntime().UUID.check(id)) {
  434. return id;
  435. } else {
  436. _this8._logger.warn(`Staging user id file exists, but content was invalid: ${id}`);
  437. }
  438. } catch (e) {
  439. if (e.code !== "ENOENT") {
  440. _this8._logger.warn(`Couldn't read staging user ID, creating a blank one: ${e}`);
  441. }
  442. }
  443. const id = _builderUtilRuntime().UUID.v5((0, _crypto().randomBytes)(4096), _builderUtilRuntime().UUID.OID);
  444. _this8._logger.info(`Generated new staging user ID: ${id}`);
  445. try {
  446. yield (0, _fsExtraP().outputFile)(file, id);
  447. } catch (e) {
  448. _this8._logger.warn(`Couldn't write out staging user ID: ${e}`);
  449. }
  450. return id;
  451. })();
  452. }
  453. }
  454. exports.AppUpdater = AppUpdater;
  455. function hasPrereleaseComponents(version) {
  456. const versionPrereleaseComponent = (0, _semver().prerelease)(version);
  457. return versionPrereleaseComponent != null && versionPrereleaseComponent.length > 0;
  458. }
  459. /** @private */
  460. class NoOpLogger {
  461. info(message) {// ignore
  462. }
  463. warn(message) {// ignore
  464. }
  465. error(message) {// ignore
  466. }
  467. } exports.NoOpLogger = NoOpLogger;
  468. //# sourceMappingURL=AppUpdater.js.map