| 
									
										
										
										
											2023-01-03 11:55:46 -08:00
										 |  |  | // Copyright 2019 Signal Messenger, LLC
 | 
					
						
							| 
									
										
										
										
											2020-10-30 15:34:04 -05:00
										 |  |  | // SPDX-License-Identifier: AGPL-3.0-only
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-16 12:31:05 -07:00
										 |  |  | /* eslint-disable no-console */ | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  | import { createWriteStream } from 'fs'; | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  | import { pathExists } from 'fs-extra'; | 
					
						
							| 
									
										
										
										
											2024-11-13 17:20:36 -08:00
										 |  |  | import { mkdir, readdir, stat, writeFile } from 'fs/promises'; | 
					
						
							| 
									
										
										
										
											2021-08-19 18:56:29 -04:00
										 |  |  | import { throttle } from 'lodash'; | 
					
						
							| 
									
										
										
										
											2024-11-13 17:20:36 -08:00
										 |  |  | import { release as osRelease, tmpdir } from 'os'; | 
					
						
							|  |  |  | import { extname, join, normalize } from 'path'; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-13 17:20:36 -08:00
										 |  |  | import config from 'config'; | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import type { ParserConfiguration } from 'dashdash'; | 
					
						
							|  |  |  | import { createParser } from 'dashdash'; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | import { FAILSAFE_SCHEMA, safeLoad } from 'js-yaml'; | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  | import { gt, gte, lt } from 'semver'; | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import got from 'got'; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | import { v4 as getGuid } from 'uuid'; | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import type { BrowserWindow } from 'electron'; | 
					
						
							|  |  |  | import { app, ipcMain } from 'electron'; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  | import { missingCaseError } from '../util/missingCaseError'; | 
					
						
							| 
									
										
										
										
											2022-10-24 13:46:36 -07:00
										 |  |  | import { getTempPath, getUpdateCachePath } from '../../app/attachments'; | 
					
						
							| 
									
										
										
										
											2024-02-26 16:18:50 -08:00
										 |  |  | import { markShouldNotQuit, markShouldQuit } from '../../app/window_state'; | 
					
						
							| 
									
										
										
										
											2021-08-19 18:56:29 -04:00
										 |  |  | import { DialogType } from '../types/Dialogs'; | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  | import * as Errors from '../types/errors'; | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  | import { strictAssert } from '../util/assert'; | 
					
						
							| 
									
										
										
										
											2024-02-14 10:27:31 -08:00
										 |  |  | import { drop } from '../util/drop'; | 
					
						
							| 
									
										
										
										
											2024-11-13 17:20:36 -08:00
										 |  |  | import * as durations from '../util/durations'; | 
					
						
							|  |  |  | import { isAlpha, isBeta, isStaging } from '../util/version'; | 
					
						
							| 
									
										
										
										
											2019-05-23 18:27:42 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | import * as packageJson from '../../package.json'; | 
					
						
							| 
									
										
										
										
											2024-11-13 17:20:36 -08:00
										 |  |  | import type { SettingsChannel } from '../main/settingsChannel'; | 
					
						
							|  |  |  | import { isPathInside } from '../util/isPathInside'; | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  | import { | 
					
						
							| 
									
										
										
										
											2024-11-13 17:20:36 -08:00
										 |  |  |   getSignatureFileName, | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |   hexToBinary, | 
					
						
							|  |  |  |   verifySignature, | 
					
						
							|  |  |  | } from './signature'; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import type { LoggerType } from '../types/Logging'; | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  | import type { PrepareDownloadResultType as DifferentialDownloadDataType } from './differential'; | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   download as downloadDifferentialData, | 
					
						
							|  |  |  |   getBlockMapFileName, | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |   isValidPreparedData as isValidDifferentialData, | 
					
						
							| 
									
										
										
										
											2024-11-13 17:20:36 -08:00
										 |  |  |   prepareDownload as prepareDifferentialDownload, | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  | } from './differential'; | 
					
						
							| 
									
										
										
										
											2024-11-13 17:20:36 -08:00
										 |  |  | import { getGotOptions } from './got'; | 
					
						
							|  |  |  | import { checkIntegrity, gracefulRename, gracefulRmRecursive } from './util'; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-14 10:27:31 -08:00
										 |  |  | const POLL_INTERVAL = 30 * durations.MINUTE; | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 21:52:54 +01:00
										 |  |  | type JSONVendorSchema = { | 
					
						
							|  |  |  |   minOSVersion?: string; | 
					
						
							| 
									
										
										
										
											2023-12-13 18:20:58 +01:00
										 |  |  |   requireManualUpdate?: 'true' | 'false'; | 
					
						
							|  |  |  |   requireUserConfirmation?: 'true' | 'false'; | 
					
						
							| 
									
										
										
										
											2023-12-12 21:52:54 +01:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-19 18:56:29 -04:00
										 |  |  | type JSONUpdateSchema = { | 
					
						
							|  |  |  |   version: string; | 
					
						
							|  |  |  |   files: Array<{ | 
					
						
							|  |  |  |     url: string; | 
					
						
							|  |  |  |     sha512: string; | 
					
						
							|  |  |  |     size: string; | 
					
						
							|  |  |  |     blockMapSize?: string; | 
					
						
							|  |  |  |   }>; | 
					
						
							|  |  |  |   path: string; | 
					
						
							|  |  |  |   sha512: string; | 
					
						
							|  |  |  |   releaseDate: string; | 
					
						
							| 
									
										
										
										
											2023-12-12 21:52:54 +01:00
										 |  |  |   vendor?: JSONVendorSchema; | 
					
						
							| 
									
										
										
										
											2021-08-19 18:56:29 -04:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export type UpdateInformationType = { | 
					
						
							|  |  |  |   fileName: string; | 
					
						
							|  |  |  |   size: number; | 
					
						
							|  |  |  |   version: string; | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |   sha512: string; | 
					
						
							|  |  |  |   differentialData: DifferentialDownloadDataType | undefined; | 
					
						
							| 
									
										
										
										
											2023-12-12 21:52:54 +01:00
										 |  |  |   vendor?: JSONVendorSchema; | 
					
						
							| 
									
										
										
										
											2021-08-19 18:56:29 -04:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  | enum DownloadMode { | 
					
						
							|  |  |  |   DifferentialOnly = 'DifferentialOnly', | 
					
						
							|  |  |  |   FullOnly = 'FullOnly', | 
					
						
							|  |  |  |   Automatic = 'Automatic', | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |   ForceUpdate = 'ForceUpdate', | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  | type DownloadUpdateResultType = Readonly<{ | 
					
						
							|  |  |  |   updateFilePath: string; | 
					
						
							|  |  |  |   signature: Buffer; | 
					
						
							|  |  |  | }>; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 03:15:36 +01:00
										 |  |  | export type UpdaterOptionsType = Readonly<{ | 
					
						
							|  |  |  |   settingsChannel: SettingsChannel; | 
					
						
							|  |  |  |   logger: LoggerType; | 
					
						
							|  |  |  |   getMainWindow: () => BrowserWindow | undefined; | 
					
						
							|  |  |  |   canRunSilently: () => boolean; | 
					
						
							|  |  |  | }>; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  | enum CheckType { | 
					
						
							|  |  |  |   Normal = 'Normal', | 
					
						
							|  |  |  |   AllowSameVersion = 'AllowSameVersion', | 
					
						
							|  |  |  |   ForceDownload = 'ForceDownload', | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  | export abstract class Updater { | 
					
						
							|  |  |  |   protected fileName: string | undefined; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |   protected version: string | undefined; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |   protected cachedDifferentialData: DifferentialDownloadDataType | undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 03:15:36 +01:00
										 |  |  |   protected readonly logger: LoggerType; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private readonly settingsChannel: SettingsChannel; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   protected readonly getMainWindow: () => BrowserWindow | undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-12 14:04:10 -08:00
										 |  |  |   private throttledSendDownloadingUpdate: (( | 
					
						
							|  |  |  |     downloadedSize: number, | 
					
						
							|  |  |  |     downloadSize: number | 
					
						
							|  |  |  |   ) => void) & { | 
					
						
							| 
									
										
										
										
											2024-02-23 10:54:29 -08:00
										 |  |  |     cancel: () => void; | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |   private activeDownload: Promise<boolean> | undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-28 12:05:44 -07:00
										 |  |  |   private markedCannotUpdate = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-26 16:18:50 -08:00
										 |  |  |   private restarting = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 03:15:36 +01:00
										 |  |  |   private readonly canRunSilently: () => boolean; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor({ | 
					
						
							|  |  |  |     settingsChannel, | 
					
						
							|  |  |  |     logger, | 
					
						
							|  |  |  |     getMainWindow, | 
					
						
							|  |  |  |     canRunSilently, | 
					
						
							|  |  |  |   }: UpdaterOptionsType) { | 
					
						
							|  |  |  |     this.settingsChannel = settingsChannel; | 
					
						
							|  |  |  |     this.logger = logger; | 
					
						
							|  |  |  |     this.getMainWindow = getMainWindow; | 
					
						
							|  |  |  |     this.canRunSilently = canRunSilently; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-12 14:04:10 -08:00
										 |  |  |     this.throttledSendDownloadingUpdate = throttle( | 
					
						
							|  |  |  |       (downloadedSize: number, downloadSize: number) => { | 
					
						
							|  |  |  |         const mainWindow = this.getMainWindow(); | 
					
						
							|  |  |  |         mainWindow?.webContents.send( | 
					
						
							|  |  |  |           'show-update-dialog', | 
					
						
							|  |  |  |           DialogType.Downloading, | 
					
						
							|  |  |  |           { downloadedSize, downloadSize } | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       50 | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2024-11-12 15:56:20 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     ipcMain.handle('updater/force-update', () => this.force()); | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |   //
 | 
					
						
							|  |  |  |   // Public APIs
 | 
					
						
							|  |  |  |   //
 | 
					
						
							| 
									
										
										
										
											2021-08-19 18:56:29 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |   public async force(): Promise<void> { | 
					
						
							| 
									
										
										
										
											2024-11-12 14:04:10 -08:00
										 |  |  |     this.markedCannotUpdate = false; | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |     return this.checkForUpdatesMaybeInstall(CheckType.ForceDownload); | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-26 16:18:50 -08:00
										 |  |  |   // If the updater was about to restart the app but the user cancelled it, show dialog
 | 
					
						
							|  |  |  |   // to let them retry the restart
 | 
					
						
							|  |  |  |   public onRestartCancelled(): void { | 
					
						
							|  |  |  |     if (!this.restarting) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.logger.info( | 
					
						
							| 
									
										
										
										
											2024-02-26 22:03:07 -08:00
										 |  |  |       'updater/onRestartCancelled: restart was cancelled. forcing update to reset updater state' | 
					
						
							| 
									
										
										
										
											2024-02-26 16:18:50 -08:00
										 |  |  |     ); | 
					
						
							|  |  |  |     this.restarting = false; | 
					
						
							|  |  |  |     markShouldNotQuit(); | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |     drop(this.checkForUpdatesMaybeInstall(CheckType.AllowSameVersion)); | 
					
						
							| 
									
										
										
										
											2024-02-26 16:18:50 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |   public async start(): Promise<void> { | 
					
						
							|  |  |  |     this.logger.info('updater/start: starting checks...'); | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-14 10:27:31 -08:00
										 |  |  |     this.schedulePoll(); | 
					
						
							| 
									
										
										
										
											2019-08-02 14:11:10 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     await this.deletePreviousInstallers(); | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |     await this.checkForUpdatesMaybeInstall(CheckType.Normal); | 
					
						
							| 
									
										
										
										
											2019-08-02 14:11:10 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |   //
 | 
					
						
							|  |  |  |   // Abstract methods
 | 
					
						
							|  |  |  |   //
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   protected abstract deletePreviousInstallers(): Promise<void>; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 03:15:36 +01:00
										 |  |  |   protected abstract installUpdate( | 
					
						
							|  |  |  |     updateFilePath: string, | 
					
						
							|  |  |  |     isSilent: boolean | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |   ): Promise<() => Promise<void>>; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |   //
 | 
					
						
							|  |  |  |   // Protected methods
 | 
					
						
							|  |  |  |   //
 | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |   protected setUpdateListener( | 
					
						
							| 
									
										
										
										
											2024-02-26 16:18:50 -08:00
										 |  |  |     performUpdateCallback: () => Promise<void> | void | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |   ): void { | 
					
						
							|  |  |  |     ipcMain.removeHandler('start-update'); | 
					
						
							|  |  |  |     ipcMain.handleOnce('start-update', performUpdateCallback); | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-28 12:05:44 -07:00
										 |  |  |   protected markCannotUpdate( | 
					
						
							|  |  |  |     error: Error, | 
					
						
							|  |  |  |     dialogType = DialogType.Cannot_Update | 
					
						
							|  |  |  |   ): void { | 
					
						
							|  |  |  |     if (this.markedCannotUpdate) { | 
					
						
							|  |  |  |       this.logger.warn( | 
					
						
							|  |  |  |         'updater/markCannotUpdate: already marked', | 
					
						
							|  |  |  |         Errors.toLogFormat(error) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.markedCannotUpdate = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.logger.error( | 
					
						
							|  |  |  |       'updater/markCannotUpdate: marking due to error: ' + | 
					
						
							|  |  |  |         `${Errors.toLogFormat(error)}, ` + | 
					
						
							|  |  |  |         `dialogType: ${dialogType}` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const mainWindow = this.getMainWindow(); | 
					
						
							|  |  |  |     mainWindow?.webContents.send('show-update-dialog', dialogType); | 
					
						
							| 
									
										
										
										
											2022-04-27 16:02:41 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     this.setUpdateListener(async () => { | 
					
						
							|  |  |  |       this.logger.info('updater/markCannotUpdate: retrying after user action'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       this.markedCannotUpdate = false; | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |       await this.checkForUpdatesMaybeInstall(CheckType.Normal); | 
					
						
							| 
									
										
										
										
											2022-04-27 16:02:41 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-03-28 12:05:44 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-26 16:18:50 -08:00
										 |  |  |   protected markRestarting(): void { | 
					
						
							|  |  |  |     this.restarting = true; | 
					
						
							|  |  |  |     markShouldQuit(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |   //
 | 
					
						
							|  |  |  |   // Private methods
 | 
					
						
							|  |  |  |   //
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-14 10:27:31 -08:00
										 |  |  |   private schedulePoll(): void { | 
					
						
							|  |  |  |     const now = Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const earliestPollTime = now - (now % POLL_INTERVAL) + POLL_INTERVAL; | 
					
						
							|  |  |  |     const selectedPollTime = Math.round( | 
					
						
							|  |  |  |       earliestPollTime + Math.random() * POLL_INTERVAL | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const timeoutMs = selectedPollTime - now; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.logger.info(`updater/start: polling in ${timeoutMs}ms`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setTimeout(() => { | 
					
						
							|  |  |  |       drop(this.safePoll()); | 
					
						
							|  |  |  |     }, timeoutMs); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async safePoll(): Promise<void> { | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       this.logger.info('updater/start: polling now'); | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |       await this.checkForUpdatesMaybeInstall(CheckType.Normal); | 
					
						
							| 
									
										
										
										
											2024-02-14 10:27:31 -08:00
										 |  |  |     } catch (error) { | 
					
						
							|  |  |  |       this.logger.error(`updater/start: ${Errors.toLogFormat(error)}`); | 
					
						
							|  |  |  |     } finally { | 
					
						
							|  |  |  |       this.schedulePoll(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |   private async downloadAndInstall( | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |     updateInfo: UpdateInformationType, | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |     mode: DownloadMode | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |   ): Promise<boolean> { | 
					
						
							|  |  |  |     if (this.activeDownload) { | 
					
						
							|  |  |  |       return this.activeDownload; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       this.activeDownload = this.doDownloadAndInstall(updateInfo, mode); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return await this.activeDownload; | 
					
						
							|  |  |  |     } finally { | 
					
						
							|  |  |  |       this.activeDownload = undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async doDownloadAndInstall( | 
					
						
							|  |  |  |     updateInfo: UpdateInformationType, | 
					
						
							|  |  |  |     mode: DownloadMode | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |   ): Promise<boolean> { | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     const { logger } = this; | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const { fileName: newFileName, version: newVersion } = updateInfo; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     try { | 
					
						
							|  |  |  |       const oldVersion = this.version; | 
					
						
							|  |  |  |       this.version = newVersion; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |       let downloadResult: DownloadUpdateResultType | undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |       try { | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |         downloadResult = await this.downloadUpdate(updateInfo, mode); | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |       } catch (error) { | 
					
						
							|  |  |  |         // Restore state in case of download error
 | 
					
						
							|  |  |  |         this.version = oldVersion; | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |         throw error; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-08-02 14:11:10 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |       if (!downloadResult) { | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |         logger.warn('downloadAndInstall: no update was downloaded'); | 
					
						
							| 
									
										
										
										
											2022-02-25 11:02:09 -08:00
										 |  |  |         strictAssert( | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |           mode !== DownloadMode.ForceUpdate && | 
					
						
							|  |  |  |             mode !== DownloadMode.Automatic && | 
					
						
							|  |  |  |             mode !== DownloadMode.FullOnly, | 
					
						
							|  |  |  |           'Automatic/full/force update mode downloads are ' + | 
					
						
							|  |  |  |             'guaranteed to happen or error' | 
					
						
							| 
									
										
										
										
											2022-02-25 11:02:09 -08:00
										 |  |  |         ); | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |         return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |       const { updateFilePath, signature } = downloadResult; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |       const publicKey = hexToBinary(config.get('updatesPublicKey')); | 
					
						
							|  |  |  |       const verified = await verifySignature( | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |         updateFilePath, | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |         this.version, | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |         signature, | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |         publicKey | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       if (!verified) { | 
					
						
							|  |  |  |         // Note: We don't delete the cache here, because we don't want to continually
 | 
					
						
							|  |  |  |         //   re-download the broken release. We will download it only once per launch.
 | 
					
						
							|  |  |  |         throw new Error( | 
					
						
							|  |  |  |           'Downloaded update did not pass signature verification ' + | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |             `(version: '${this.version}'; fileName: '${newFileName}')` | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |       const isSilent = | 
					
						
							| 
									
										
										
										
											2023-12-13 18:20:58 +01:00
										 |  |  |         updateInfo.vendor?.requireUserConfirmation !== 'true' && | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |         this.canRunSilently(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const handler = await this.installUpdate(updateFilePath, isSilent); | 
					
						
							|  |  |  |       if (isSilent || mode === DownloadMode.ForceUpdate) { | 
					
						
							|  |  |  |         await handler(); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         this.setUpdateListener(handler); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |       const mainWindow = this.getMainWindow(); | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |       if (mode === DownloadMode.ForceUpdate) { | 
					
						
							|  |  |  |         logger.info('downloadAndInstall: force update, no dialog...'); | 
					
						
							|  |  |  |       } else if (mainWindow) { | 
					
						
							| 
									
										
										
										
											2023-01-23 08:56:39 -08:00
										 |  |  |         logger.info('downloadAndInstall: showing update dialog...'); | 
					
						
							| 
									
										
										
										
											2023-03-15 09:57:27 -07:00
										 |  |  |         mainWindow.webContents.send( | 
					
						
							|  |  |  |           'show-update-dialog', | 
					
						
							|  |  |  |           mode === DownloadMode.Automatic | 
					
						
							|  |  |  |             ? DialogType.AutoUpdate | 
					
						
							|  |  |  |             : DialogType.DownloadedUpdate, | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             version: this.version, | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |       } else { | 
					
						
							|  |  |  |         logger.warn( | 
					
						
							|  |  |  |           'downloadAndInstall: no mainWindow, cannot show update dialog' | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       return true; | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     } catch (error) { | 
					
						
							|  |  |  |       logger.error(`downloadAndInstall: ${Errors.toLogFormat(error)}`); | 
					
						
							| 
									
										
										
										
											2022-03-28 12:05:44 -07:00
										 |  |  |       this.markCannotUpdate(error); | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |       throw error; | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |   private async checkForUpdatesMaybeInstall( | 
					
						
							|  |  |  |     checkType: CheckType | 
					
						
							|  |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     const { logger } = this; | 
					
						
							| 
									
										
										
										
											2021-08-19 18:56:29 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     logger.info('checkForUpdatesMaybeInstall: checking for update...'); | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |     const updateInfo = await this.checkForUpdates(checkType); | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |     if (!updateInfo) { | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |     const { version: newVersion } = updateInfo; | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |     if (checkType === CheckType.ForceDownload) { | 
					
						
							|  |  |  |       await this.downloadAndInstall(updateInfo, DownloadMode.ForceUpdate); | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |     if (checkType === CheckType.Normal) { | 
					
						
							|  |  |  |       // Verify that the downloaded version is greater than downloaded
 | 
					
						
							|  |  |  |       if (this.version && !gt(newVersion, this.version)) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } else if (checkType === CheckType.AllowSameVersion) { | 
					
						
							|  |  |  |       // Verify that the downloaded version is greater or the same as downloaded
 | 
					
						
							|  |  |  |       if (this.version && !gte(newVersion, this.version)) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       throw missingCaseError(checkType); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |     const autoDownloadUpdates = await this.getAutoDownloadUpdateSetting(); | 
					
						
							|  |  |  |     if (autoDownloadUpdates) { | 
					
						
							|  |  |  |       await this.downloadAndInstall(updateInfo, DownloadMode.Automatic); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let mode = DownloadMode.FullOnly; | 
					
						
							|  |  |  |     if (updateInfo.differentialData) { | 
					
						
							|  |  |  |       mode = DownloadMode.DifferentialOnly; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await this.offerUpdate(updateInfo, mode, 0); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async offerUpdate( | 
					
						
							|  |  |  |     updateInfo: UpdateInformationType, | 
					
						
							|  |  |  |     mode: DownloadMode, | 
					
						
							|  |  |  |     attempt: number | 
					
						
							|  |  |  |   ): Promise<void> { | 
					
						
							|  |  |  |     const { logger } = this; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.setUpdateListener(async () => { | 
					
						
							|  |  |  |       logger.info('offerUpdate: have not downloaded update, going to download'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const didDownload = await this.downloadAndInstall(updateInfo, mode); | 
					
						
							|  |  |  |       if (!didDownload && mode === DownloadMode.DifferentialOnly) { | 
					
						
							|  |  |  |         this.logger.warn( | 
					
						
							|  |  |  |           'offerUpdate: Failed to download differential update, offering full' | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2024-02-23 10:54:29 -08:00
										 |  |  |         this.throttledSendDownloadingUpdate.cancel(); | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |         return this.offerUpdate(updateInfo, DownloadMode.FullOnly, attempt + 1); | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       strictAssert(didDownload, 'FullOnly must always download update'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const mainWindow = this.getMainWindow(); | 
					
						
							|  |  |  |     if (!mainWindow) { | 
					
						
							|  |  |  |       logger.warn('offerUpdate: no mainWindow, cannot show update dialog'); | 
					
						
							|  |  |  |       return; | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     let downloadSize: number; | 
					
						
							|  |  |  |     if (mode === DownloadMode.DifferentialOnly) { | 
					
						
							|  |  |  |       strictAssert( | 
					
						
							|  |  |  |         updateInfo.differentialData, | 
					
						
							|  |  |  |         'Must have differential data in DifferentialOnly mode' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       downloadSize = updateInfo.differentialData.downloadSize; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       downloadSize = updateInfo.size; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     logger.info(`offerUpdate: offering ${mode} update`); | 
					
						
							|  |  |  |     mainWindow.webContents.send( | 
					
						
							|  |  |  |       'show-update-dialog', | 
					
						
							|  |  |  |       attempt === 0 ? DialogType.DownloadReady : DialogType.FullDownloadReady, | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         downloadSize, | 
					
						
							|  |  |  |         downloadMode: mode, | 
					
						
							|  |  |  |         version: updateInfo.version, | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async checkForUpdates( | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |     checkType: CheckType | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |   ): Promise<UpdateInformationType | undefined> { | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     const yaml = await getUpdateYaml(); | 
					
						
							|  |  |  |     const parsedYaml = parseYaml(yaml); | 
					
						
							| 
									
										
										
										
											2022-03-29 15:46:18 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-18 15:31:10 -08:00
										 |  |  |     const { vendor } = parsedYaml; | 
					
						
							|  |  |  |     if (vendor) { | 
					
						
							| 
									
										
										
										
											2023-12-13 18:20:58 +01:00
										 |  |  |       if (vendor.requireManualUpdate === 'true') { | 
					
						
							| 
									
										
										
										
											2023-01-18 15:31:10 -08:00
										 |  |  |         this.logger.warn('checkForUpdates: manual update required'); | 
					
						
							|  |  |  |         this.markCannotUpdate( | 
					
						
							|  |  |  |           new Error('yaml file has requireManualUpdate flag'), | 
					
						
							|  |  |  |           DialogType.Cannot_Update_Require_Manual | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (vendor.minOSVersion && lt(osRelease(), vendor.minOSVersion)) { | 
					
						
							|  |  |  |         this.logger.warn( | 
					
						
							|  |  |  |           `checkForUpdates: OS version ${osRelease()} is less than the ` + | 
					
						
							|  |  |  |             `minimum supported version ${vendor.minOSVersion}` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         this.markCannotUpdate( | 
					
						
							|  |  |  |           new Error('yaml file has unsatisfied minOSVersion value'), | 
					
						
							|  |  |  |           DialogType.UnsupportedOS | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2022-03-29 15:46:18 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     const version = getVersion(parsedYaml); | 
					
						
							| 
									
										
										
										
											2021-08-19 18:56:29 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     if (!version) { | 
					
						
							|  |  |  |       this.logger.warn( | 
					
						
							|  |  |  |         'checkForUpdates: no version extracted from downloaded yaml' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |       return; | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |     if (checkType === CheckType.Normal && !isVersionNewer(version)) { | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |       this.logger.info( | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |         `checkForUpdates: ${version} is not newer than ${packageJson.version}; ` + | 
					
						
							|  |  |  |           'no new update available' | 
					
						
							| 
									
										
										
										
											2021-12-03 23:49:15 +01:00
										 |  |  |       ); | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |       return; | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.logger.info( | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |       `checkForUpdates: found newer version ${version} ` + | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |         `checkType=${checkType}` | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const fileName = getUpdateFileName( | 
					
						
							|  |  |  |       parsedYaml, | 
					
						
							|  |  |  |       process.platform, | 
					
						
							|  |  |  |       await this.getArch() | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const sha512 = getSHA512(parsedYaml, fileName); | 
					
						
							|  |  |  |     strictAssert(sha512 !== undefined, 'Missing required hash'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const latestInstaller = await this.getLatestCachedInstaller( | 
					
						
							|  |  |  |       extname(fileName) | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |     let differentialData: DifferentialDownloadDataType | undefined; | 
					
						
							|  |  |  |     if (latestInstaller) { | 
					
						
							|  |  |  |       this.logger.info( | 
					
						
							|  |  |  |         `checkForUpdates: Found local installer ${latestInstaller}` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |       const diffOptions = { | 
					
						
							|  |  |  |         oldFile: latestInstaller, | 
					
						
							|  |  |  |         newUrl: `${getUpdatesBase()}/${fileName}`, | 
					
						
							|  |  |  |         sha512, | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |       if ( | 
					
						
							|  |  |  |         this.cachedDifferentialData && | 
					
						
							|  |  |  |         isValidDifferentialData(this.cachedDifferentialData, diffOptions) | 
					
						
							|  |  |  |       ) { | 
					
						
							|  |  |  |         this.logger.info('checkForUpdates: using cached differential data'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         differentialData = this.cachedDifferentialData; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |           differentialData = await prepareDifferentialDownload(diffOptions); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           this.cachedDifferentialData = differentialData; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           this.logger.info( | 
					
						
							|  |  |  |             'checkForUpdates: differential download size', | 
					
						
							|  |  |  |             differentialData.downloadSize | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         } catch (error) { | 
					
						
							|  |  |  |           this.logger.error( | 
					
						
							|  |  |  |             'checkForUpdates: Failed to prepare differential update', | 
					
						
							|  |  |  |             Errors.toLogFormat(error) | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |           this.cachedDifferentialData = undefined; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       fileName, | 
					
						
							|  |  |  |       size: getSize(parsedYaml, fileName), | 
					
						
							|  |  |  |       version, | 
					
						
							|  |  |  |       sha512, | 
					
						
							|  |  |  |       differentialData, | 
					
						
							| 
									
										
										
										
											2023-12-12 21:52:54 +01:00
										 |  |  |       vendor, | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async getLatestCachedInstaller( | 
					
						
							|  |  |  |     extension: string | 
					
						
							|  |  |  |   ): Promise<string | undefined> { | 
					
						
							|  |  |  |     const cacheDir = await createUpdateCacheDirIfNeeded(); | 
					
						
							|  |  |  |     const oldFiles = (await readdir(cacheDir)).map(fileName => { | 
					
						
							|  |  |  |       return join(cacheDir, fileName); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return oldFiles.find(fileName => extname(fileName) === extension); | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async downloadUpdate( | 
					
						
							| 
									
										
										
										
											2024-11-12 14:04:10 -08:00
										 |  |  |     { fileName, sha512, differentialData, size }: UpdateInformationType, | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |     mode: DownloadMode | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |   ): Promise<DownloadUpdateResultType | undefined> { | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     const baseUrl = getUpdatesBase(); | 
					
						
							|  |  |  |     const updateFileUrl = `${baseUrl}/${fileName}`; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-11 11:57:55 -08:00
										 |  |  |     // Show progress on DifferentialOnly, FullOnly, and ForceUpdate
 | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |     const updateOnProgress = mode !== DownloadMode.Automatic; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     const signatureFileName = getSignatureFileName(fileName); | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |     const blockMapFileName = getBlockMapFileName(fileName); | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     const signatureUrl = `${baseUrl}/${signatureFileName}`; | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |     const blockMapUrl = `${baseUrl}/${blockMapFileName}`; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |     let cacheDir = await createUpdateCacheDirIfNeeded(); | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |     const targetUpdatePath = join(cacheDir, fileName); | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const tempDir = await createTempDir(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const tempUpdatePath = join(tempDir, fileName); | 
					
						
							|  |  |  |     const tempBlockMapPath = join(tempDir, blockMapFileName); | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-28 12:05:44 -07:00
										 |  |  |     // If true - we will attempt to install from a temporary directory.
 | 
					
						
							|  |  |  |     let tempPathFailover = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     try { | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |       validatePath(cacheDir, targetUpdatePath); | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       validatePath(tempDir, tempUpdatePath); | 
					
						
							|  |  |  |       validatePath(tempDir, tempBlockMapPath); | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       this.logger.info(`downloadUpdate: Downloading signature ${signatureUrl}`); | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |       const signature = Buffer.from( | 
					
						
							| 
									
										
										
										
											2024-03-20 11:05:10 -07:00
										 |  |  |         await got(signatureUrl, await getGotOptions()).text(), | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |         'hex' | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |       if (differentialData) { | 
					
						
							|  |  |  |         this.logger.info(`downloadUpdate: Saving blockmap ${blockMapUrl}`); | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |         await writeFile(tempBlockMapPath, differentialData.newBlockMap); | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |       } else { | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |           this.logger.info( | 
					
						
							|  |  |  |             `downloadUpdate: Downloading blockmap ${blockMapUrl}` | 
					
						
							|  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2024-03-20 11:05:10 -07:00
										 |  |  |           const blockMap = await got( | 
					
						
							|  |  |  |             blockMapUrl, | 
					
						
							|  |  |  |             await getGotOptions() | 
					
						
							|  |  |  |           ).buffer(); | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |           await writeFile(tempBlockMapPath, blockMap); | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |         } catch (error) { | 
					
						
							|  |  |  |           this.logger.warn( | 
					
						
							|  |  |  |             'downloadUpdate: Failed to download blockmap, continuing', | 
					
						
							|  |  |  |             Errors.toLogFormat(error) | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |       let gotUpdate = false; | 
					
						
							|  |  |  |       if (!gotUpdate && (await pathExists(targetUpdatePath))) { | 
					
						
							|  |  |  |         const checkResult = await checkIntegrity(targetUpdatePath, sha512); | 
					
						
							|  |  |  |         if (checkResult.ok) { | 
					
						
							|  |  |  |           this.logger.info( | 
					
						
							|  |  |  |             `downloadUpdate: Not downloading update ${updateFileUrl}, ` + | 
					
						
							|  |  |  |               'local file has the same hash' | 
					
						
							|  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |           // Move file into downloads directory
 | 
					
						
							|  |  |  |           try { | 
					
						
							| 
									
										
										
										
											2022-03-28 12:05:44 -07:00
										 |  |  |             await gracefulRename(this.logger, targetUpdatePath, tempUpdatePath); | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |             gotUpdate = true; | 
					
						
							|  |  |  |           } catch (error) { | 
					
						
							|  |  |  |             this.logger.error( | 
					
						
							|  |  |  |               'downloadUpdate: failed to move already downloaded file', | 
					
						
							|  |  |  |               Errors.toLogFormat(error) | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |         } else { | 
					
						
							|  |  |  |           this.logger.error( | 
					
						
							|  |  |  |             'downloadUpdate: integrity check failure', | 
					
						
							|  |  |  |             checkResult.error | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |       const isDifferentialEnabled = | 
					
						
							|  |  |  |         differentialData && mode !== DownloadMode.FullOnly; | 
					
						
							|  |  |  |       if (!gotUpdate && isDifferentialEnabled) { | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |         this.logger.info( | 
					
						
							|  |  |  |           `downloadUpdate: Downloading differential update ${updateFileUrl}` | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |         try { | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |           await downloadDifferentialData(tempUpdatePath, differentialData, { | 
					
						
							| 
									
										
										
										
											2022-03-02 11:48:07 -08:00
										 |  |  |             statusCallback: updateOnProgress | 
					
						
							|  |  |  |               ? this.throttledSendDownloadingUpdate | 
					
						
							|  |  |  |               : undefined, | 
					
						
							|  |  |  |             logger: this.logger, | 
					
						
							|  |  |  |           }); | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |           gotUpdate = true; | 
					
						
							|  |  |  |         } catch (error) { | 
					
						
							|  |  |  |           this.logger.error( | 
					
						
							|  |  |  |             'downloadUpdate: Failed to apply differential update', | 
					
						
							|  |  |  |             Errors.toLogFormat(error) | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-08-19 18:56:29 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |       const isFullEnabled = mode !== DownloadMode.DifferentialOnly; | 
					
						
							|  |  |  |       if (!gotUpdate && isFullEnabled) { | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |         this.logger.info( | 
					
						
							|  |  |  |           `downloadUpdate: Downloading full update ${updateFileUrl}` | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // We could have failed to update differentially due to low free disk
 | 
					
						
							|  |  |  |         // space. Remove all cached updates since we are doing a full download
 | 
					
						
							|  |  |  |         // anyway.
 | 
					
						
							| 
									
										
										
										
											2024-11-13 17:20:36 -08:00
										 |  |  |         await gracefulRmRecursive(this.logger, cacheDir); | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |         cacheDir = await createUpdateCacheDirIfNeeded(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |         await this.downloadAndReport( | 
					
						
							|  |  |  |           updateFileUrl, | 
					
						
							| 
									
										
										
										
											2024-11-12 14:04:10 -08:00
										 |  |  |           size, | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |           tempUpdatePath, | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |           updateOnProgress | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         gotUpdate = true; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       if (!gotUpdate) { | 
					
						
							|  |  |  |         return undefined; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-04 11:59:47 -08:00
										 |  |  |       this.logger.info( | 
					
						
							|  |  |  |         'downloadUpdate: Downloaded update, moving into cache dir' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |       // Backup old files
 | 
					
						
							| 
									
										
										
										
											2022-03-04 11:59:47 -08:00
										 |  |  |       const restoreDir = await getTempDir(); | 
					
						
							| 
									
										
										
										
											2022-03-28 12:05:44 -07:00
										 |  |  |       await gracefulRename(this.logger, cacheDir, restoreDir); | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |       // Move the files into the final position
 | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |       try { | 
					
						
							| 
									
										
										
										
											2022-03-28 12:05:44 -07:00
										 |  |  |         await gracefulRename(this.logger, tempDir, cacheDir); | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |       } catch (error) { | 
					
						
							| 
									
										
										
										
											2022-03-28 12:05:44 -07:00
										 |  |  |         try { | 
					
						
							|  |  |  |           // Attempt to restore old files
 | 
					
						
							|  |  |  |           await gracefulRename(this.logger, restoreDir, cacheDir); | 
					
						
							|  |  |  |         } catch (restoreError) { | 
					
						
							|  |  |  |           this.logger.warn( | 
					
						
							|  |  |  |             'downloadUpdate: Failed to restore from backup folder, ignoring', | 
					
						
							|  |  |  |             Errors.toLogFormat(restoreError) | 
					
						
							|  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2024-08-14 14:10:34 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |           // If not possible - at least clean up
 | 
					
						
							|  |  |  |           try { | 
					
						
							|  |  |  |             await deleteTempDir(this.logger, restoreDir); | 
					
						
							|  |  |  |           } catch (cleanupError) { | 
					
						
							|  |  |  |             this.logger.warn( | 
					
						
							|  |  |  |               'downloadUpdate: Failed to remove backup folder after ' + | 
					
						
							|  |  |  |                 'failed restore, ignoring', | 
					
						
							|  |  |  |               Errors.toLogFormat(cleanupError) | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2022-03-28 12:05:44 -07:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-28 12:05:44 -07:00
										 |  |  |         this.logger.warn( | 
					
						
							|  |  |  |           'downloadUpdate: running update from a temporary folder due to error', | 
					
						
							|  |  |  |           Errors.toLogFormat(error) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         tempPathFailover = true; | 
					
						
							|  |  |  |         return { updateFilePath: tempUpdatePath, signature }; | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-04 11:59:47 -08:00
										 |  |  |       try { | 
					
						
							| 
									
										
										
										
											2023-03-02 09:57:36 -08:00
										 |  |  |         await deleteTempDir(this.logger, restoreDir); | 
					
						
							| 
									
										
										
										
											2022-03-04 11:59:47 -08:00
										 |  |  |       } catch (error) { | 
					
						
							|  |  |  |         this.logger.warn( | 
					
						
							|  |  |  |           'downloadUpdate: Failed to remove backup folder, ignoring', | 
					
						
							|  |  |  |           Errors.toLogFormat(error) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |       return { updateFilePath: targetUpdatePath, signature }; | 
					
						
							|  |  |  |     } finally { | 
					
						
							| 
									
										
										
										
											2022-03-28 12:05:44 -07:00
										 |  |  |       if (!tempPathFailover) { | 
					
						
							| 
									
										
										
										
											2023-03-02 09:57:36 -08:00
										 |  |  |         await deleteTempDir(this.logger, tempDir); | 
					
						
							| 
									
										
										
										
											2022-03-28 12:05:44 -07:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |   private async downloadAndReport( | 
					
						
							|  |  |  |     updateFileUrl: string, | 
					
						
							| 
									
										
										
										
											2024-11-12 14:04:10 -08:00
										 |  |  |     downloadSize: number, | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |     targetUpdatePath: string, | 
					
						
							|  |  |  |     updateOnProgress = false | 
					
						
							|  |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2024-03-20 11:05:10 -07:00
										 |  |  |     const downloadStream = got.stream(updateFileUrl, await getGotOptions()); | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |     const writeStream = createWriteStream(targetUpdatePath); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await new Promise<void>((resolve, reject) => { | 
					
						
							| 
									
										
										
										
											2022-02-25 10:44:03 -08:00
										 |  |  |       if (updateOnProgress) { | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |         let downloadedSize = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         downloadStream.on('data', data => { | 
					
						
							|  |  |  |           downloadedSize += data.length; | 
					
						
							| 
									
										
										
										
											2024-11-12 14:04:10 -08:00
										 |  |  |           this.throttledSendDownloadingUpdate(downloadedSize, downloadSize); | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       downloadStream.on('error', error => { | 
					
						
							|  |  |  |         reject(error); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       downloadStream.on('end', () => { | 
					
						
							|  |  |  |         resolve(); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       writeStream.on('error', error => { | 
					
						
							|  |  |  |         reject(error); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       downloadStream.pipe(writeStream); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  |   private async getAutoDownloadUpdateSetting(): Promise<boolean> { | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       return await this.settingsChannel.getSettingFromMainWindow( | 
					
						
							|  |  |  |         'autoDownloadUpdate' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       this.logger.warn( | 
					
						
							|  |  |  |         'getAutoDownloadUpdateSetting: Failed to fetch, returning false', | 
					
						
							|  |  |  |         Errors.toLogFormat(error) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 23:49:15 +01:00
										 |  |  |   private async getArch(): Promise<typeof process.arch> { | 
					
						
							| 
									
										
										
										
											2024-10-31 09:48:25 -07:00
										 |  |  |     if (process.arch === 'arm64') { | 
					
						
							|  |  |  |       return process.arch; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (process.platform !== 'darwin' && process.platform !== 'win32') { | 
					
						
							| 
									
										
										
										
											2021-12-03 23:49:15 +01:00
										 |  |  |       return process.arch; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 09:04:42 -08:00
										 |  |  |     if (app.runningUnderARM64Translation) { | 
					
						
							|  |  |  |       this.logger.info('updater: running under arm64 translation'); | 
					
						
							|  |  |  |       return 'arm64'; | 
					
						
							| 
									
										
										
										
											2021-12-03 23:49:15 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 09:04:42 -08:00
										 |  |  |     this.logger.info('updater: not running under arm64 translation'); | 
					
						
							| 
									
										
										
										
											2021-12-03 23:49:15 +01:00
										 |  |  |     return process.arch; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-11-10 01:56:56 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function validatePath(basePath: string, targetPath: string): void { | 
					
						
							|  |  |  |   const normalized = normalize(targetPath); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!isPathInside(normalized, basePath)) { | 
					
						
							|  |  |  |     throw new Error( | 
					
						
							|  |  |  |       `validatePath: Path ${normalized} is not under base path ${basePath}` | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Helper functions
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function getUpdateCheckUrl(): string { | 
					
						
							|  |  |  |   return `${getUpdatesBase()}/${getUpdatesFileName()}`; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function getUpdatesBase(): string { | 
					
						
							| 
									
										
										
										
											2021-08-27 13:21:42 -07:00
										 |  |  |   return config.get('updatesUrl'); | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function getUpdatesFileName(): string { | 
					
						
							| 
									
										
										
										
											2021-08-06 14:21:01 -07:00
										 |  |  |   const prefix = getChannel(); | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 23:49:15 +01:00
										 |  |  |   if (process.platform === 'darwin') { | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  |     return `${prefix}-mac.yml`; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-09-16 12:31:05 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return `${prefix}.yml`; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-06 14:21:01 -07:00
										 |  |  | function getChannel(): string { | 
					
						
							|  |  |  |   const { version } = packageJson; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-14 15:08:38 -07:00
										 |  |  |   if (isStaging(version)) { | 
					
						
							|  |  |  |     return 'staging'; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-08-06 14:21:01 -07:00
										 |  |  |   if (isAlpha(version)) { | 
					
						
							|  |  |  |     return 'alpha'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (isBeta(version)) { | 
					
						
							|  |  |  |     return 'beta'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return 'latest'; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function isVersionNewer(newVersion: string): boolean { | 
					
						
							|  |  |  |   const { version } = packageJson; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return gt(newVersion, version); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-19 18:56:29 -04:00
										 |  |  | export function getVersion(info: JSONUpdateSchema): string | null { | 
					
						
							| 
									
										
										
										
											2020-09-16 12:31:05 -07:00
										 |  |  |   return info && info.version; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-16 12:31:05 -07:00
										 |  |  | const validFile = /^[A-Za-z0-9.-]+$/; | 
					
						
							|  |  |  | export function isUpdateFileNameValid(name: string): boolean { | 
					
						
							| 
									
										
										
										
											2019-08-02 14:11:10 -07:00
										 |  |  |   return validFile.test(name); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 23:49:15 +01:00
										 |  |  | export function getUpdateFileName( | 
					
						
							|  |  |  |   info: JSONUpdateSchema, | 
					
						
							|  |  |  |   platform: typeof process.platform, | 
					
						
							|  |  |  |   arch: typeof process.arch | 
					
						
							|  |  |  | ): string { | 
					
						
							| 
									
										
										
										
											2019-08-02 14:11:10 -07:00
										 |  |  |   if (!info || !info.path) { | 
					
						
							|  |  |  |     throw new Error('getUpdateFileName: No path present in YAML file'); | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 23:49:15 +01:00
										 |  |  |   let path: string | undefined; | 
					
						
							| 
									
										
										
										
											2024-10-29 15:29:30 -07:00
										 |  |  |   let fileFilter: (({ url }: { url: string }) => boolean) | undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 23:49:15 +01:00
										 |  |  |   if (platform === 'darwin') { | 
					
						
							| 
									
										
										
										
											2024-10-29 15:29:30 -07:00
										 |  |  |     fileFilter = ({ url }) => url.includes(arch) && url.endsWith('.zip'); | 
					
						
							|  |  |  |   } else if (platform === 'win32') { | 
					
						
							|  |  |  |     fileFilter = ({ url }) => url.includes(arch) && url.endsWith('.exe'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (fileFilter) { | 
					
						
							| 
									
										
										
										
											2021-12-03 23:49:15 +01:00
										 |  |  |     const { files } = info; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-29 15:29:30 -07:00
										 |  |  |     const candidates = files.filter(fileFilter); | 
					
						
							| 
									
										
										
										
											2021-12-03 23:49:15 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (candidates.length === 1) { | 
					
						
							|  |  |  |       path = candidates[0].url; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   path = path ?? info.path; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-02 14:11:10 -07:00
										 |  |  |   if (!isUpdateFileNameValid(path)) { | 
					
						
							|  |  |  |     throw new Error( | 
					
						
							|  |  |  |       `getUpdateFileName: Path '${path}' contains invalid characters` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return path; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  | function getSHA512( | 
					
						
							|  |  |  |   info: JSONUpdateSchema, | 
					
						
							|  |  |  |   fileName: string | 
					
						
							|  |  |  | ): string | undefined { | 
					
						
							|  |  |  |   if (!info || !info.files) { | 
					
						
							|  |  |  |     throw new Error('getSHA512: No files present in YAML file'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const foundFile = info.files.find(file => file.url === fileName); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return foundFile?.sha512; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-19 18:56:29 -04:00
										 |  |  | function getSize(info: JSONUpdateSchema, fileName: string): number { | 
					
						
							|  |  |  |   if (!info || !info.files) { | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  |     throw new Error('getSize: No files present in YAML file'); | 
					
						
							| 
									
										
										
										
											2021-08-19 18:56:29 -04:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const foundFile = info.files.find(file => file.url === fileName); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return Number(foundFile?.size) || 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function parseYaml(yaml: string): JSONUpdateSchema { | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  |   return safeLoad(yaml, { schema: FAILSAFE_SCHEMA, json: true }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function getUpdateYaml(): Promise<string> { | 
					
						
							|  |  |  |   const targetUrl = getUpdateCheckUrl(); | 
					
						
							| 
									
										
										
										
											2024-03-20 11:05:10 -07:00
										 |  |  |   const body = await got(targetUrl, await getGotOptions()).text(); | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   if (!body) { | 
					
						
							|  |  |  |     throw new Error('Got unexpected response back from update check'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-06 09:25:22 -07:00
										 |  |  |   return body; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getBaseTempDir() { | 
					
						
							|  |  |  |   // We only use tmpdir() when this code is run outside of an Electron app (as in: tests)
 | 
					
						
							| 
									
										
										
										
											2019-05-23 18:27:42 -07:00
										 |  |  |   return app ? getTempPath(app.getPath('userData')) : tmpdir(); | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-16 12:31:05 -07:00
										 |  |  | export async function createTempDir(): Promise<string> { | 
					
						
							| 
									
										
										
										
											2022-03-04 11:59:47 -08:00
										 |  |  |   const targetDir = await getTempDir(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 13:05:51 +01:00
										 |  |  |   await mkdir(targetDir, { recursive: true }); | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return targetDir; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-04 11:59:47 -08:00
										 |  |  | export async function getTempDir(): Promise<string> { | 
					
						
							|  |  |  |   const baseTempDir = getBaseTempDir(); | 
					
						
							|  |  |  |   const uniqueName = getGuid(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Create parent folder if not already present
 | 
					
						
							|  |  |  |   if (!(await pathExists(baseTempDir))) { | 
					
						
							| 
									
										
										
										
											2021-11-29 13:05:51 +01:00
										 |  |  |     await mkdir(baseTempDir, { recursive: true }); | 
					
						
							| 
									
										
										
										
											2022-03-04 11:59:47 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return join(baseTempDir, uniqueName); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  | function getUpdateCacheDir() { | 
					
						
							|  |  |  |   // We only use tmpdir() when this code is run outside of an Electron app (as in: tests)
 | 
					
						
							|  |  |  |   return app ? getUpdateCachePath(app.getPath('userData')) : tmpdir(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export async function createUpdateCacheDirIfNeeded(): Promise<string> { | 
					
						
							|  |  |  |   const targetDir = getUpdateCacheDir(); | 
					
						
							| 
									
										
										
										
											2021-11-29 13:05:51 +01:00
										 |  |  |   await mkdir(targetDir, { recursive: true }); | 
					
						
							| 
									
										
										
										
											2022-02-24 13:01:41 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return targetDir; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-02 09:57:36 -08:00
										 |  |  | export async function deleteTempDir( | 
					
						
							|  |  |  |   logger: LoggerType, | 
					
						
							|  |  |  |   targetDir: string | 
					
						
							|  |  |  | ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2022-03-03 14:34:51 -08:00
										 |  |  |   if (await pathExists(targetDir)) { | 
					
						
							|  |  |  |     const pathInfo = await stat(targetDir); | 
					
						
							|  |  |  |     if (!pathInfo.isDirectory()) { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							|  |  |  |         `deleteTempDir: Cannot delete path '${targetDir}' because it is not a directory` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const baseTempDir = getBaseTempDir(); | 
					
						
							| 
									
										
										
										
											2020-08-27 13:08:37 -05:00
										 |  |  |   if (!isPathInside(targetDir, baseTempDir)) { | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  |     throw new Error( | 
					
						
							|  |  |  |       `deleteTempDir: Cannot delete path '${targetDir}' since it is not within base temp dir` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-13 17:20:36 -08:00
										 |  |  |   await gracefulRmRecursive(logger, targetDir); | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-16 12:31:05 -07:00
										 |  |  | export function getCliOptions<T>(options: ParserConfiguration['options']): T { | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  |   const parser = createParser({ options }); | 
					
						
							|  |  |  |   const cliOptions = parser.parse(process.argv); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (cliOptions.help) { | 
					
						
							|  |  |  |     const help = parser.help().trimRight(); | 
					
						
							|  |  |  |     console.log(help); | 
					
						
							|  |  |  |     process.exit(0); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-11 16:43:05 -06:00
										 |  |  |   return cliOptions as unknown as T; | 
					
						
							| 
									
										
										
										
											2019-03-28 10:09:26 -07:00
										 |  |  | } |