-
-
Notifications
You must be signed in to change notification settings - Fork 188
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve security and reliability of macOS updates
This commit introduces several improvements to the macOS update process, primarily focusing on enhancing security and reliability: - Add data integrity checks to ensure downloaded updates haven't been tampered with. - Optimize update progress logging in `streamWithProgress` by limiting amount of logs during the download process. - Improve resource management by ensuring proper closure of file read/write streams. - Add retry logic with exponential back-off during file access to handle occassionally seen file system preparation delays on macOS. - Improve decision-making based on user responses. - Improve clarity and informativeness of log messages. - Update error dialogs for better user guidance when updates fail to download, unexpected errors occur or the installer can't be opened. - Add handling for unexpected errors during the update process. - Move to asynchronous functions for more efficient operation. - Move to scoped imports for better code clarity. - Update `Readable` stream type to a more modern variant in Node. - Refactor `ManualUpdater` for improved separation of concerns. - Document the secure update process, and log directory locations. - Rename files to more accurately reflect their purpose. - Add `.DS_Store` in `.gitignore` to avoid unintended files in commits.
- Loading branch information
1 parent
25e23c8
commit b13dfd9
Showing
14 changed files
with
637 additions
and
176 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,3 +11,6 @@ node_modules | |
# draw.io | ||
*.bkp | ||
*.dtmp | ||
|
||
# macOS | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file was deleted.
Oops, something went wrong.
122 changes: 122 additions & 0 deletions
122
src/presentation/electron/main/Update/ManualUpdater/Dialogs.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { dialog } from 'electron'; | ||
|
||
export enum ManualUpdateChoice { | ||
NoAction = 0, | ||
UpdateNow = 1, | ||
VisitReleasesPage = 2, | ||
} | ||
export async function promptForManualUpdate(): Promise<ManualUpdateChoice> { | ||
const visitPageResult = await dialog.showMessageBox({ | ||
type: 'info', | ||
buttons: [ | ||
'Not Now', | ||
'Download Update', | ||
'Visit Release Page', | ||
], | ||
message: [ | ||
'A new version is available.', | ||
'Would you like to download it now?', | ||
].join('\n\n'), | ||
detail: [ | ||
'Updates are highly recommended because they improve your privacy, security and safety.', | ||
'\n\n', | ||
'Auto-updates are not fully supported on macOS due to code signing costs.', | ||
'Consider donating ❤️.', | ||
].join(' '), | ||
defaultId: ManualUpdateChoice.UpdateNow, | ||
cancelId: ManualUpdateChoice.NoAction, | ||
}); | ||
return visitPageResult.response; | ||
} | ||
|
||
export enum IntegrityCheckChoice { | ||
Cancel = 0, | ||
RetryDownload = 1, | ||
ContinueAnyway = 2, | ||
} | ||
|
||
export async function promptIntegrityCheckFailure(): Promise<IntegrityCheckChoice> { | ||
const integrityResult = await dialog.showMessageBox({ | ||
type: 'error', | ||
buttons: [ | ||
'Cancel Update', | ||
'Retry Download', | ||
'Continue Anyway', | ||
], | ||
message: 'Integrity check failed', | ||
detail: | ||
'The integrity check for the installer has failed,' | ||
+ ' which means the file may be corrupted or has been tampered with.' | ||
+ ' It is recommended to retry the download or cancel the installation for your safety.' | ||
+ '\n\nContinuing the installation might put your system at risk.', | ||
defaultId: IntegrityCheckChoice.RetryDownload, | ||
cancelId: IntegrityCheckChoice.Cancel, | ||
noLink: true, | ||
}); | ||
return integrityResult.response; | ||
} | ||
|
||
export enum InstallerErrorChoice { | ||
Cancel = 0, | ||
RetryDownload = 1, | ||
RetryOpen = 2, | ||
} | ||
|
||
export async function promptInstallerOpenError(): Promise<InstallerErrorChoice> { | ||
const result = await dialog.showMessageBox({ | ||
type: 'error', | ||
buttons: [ | ||
'Cancel Update', | ||
'Retry Download', | ||
'Retry Installation', | ||
], | ||
message: 'Installation Error', | ||
detail: 'The installer could not be launched. Please try again.', | ||
defaultId: InstallerErrorChoice.RetryOpen, | ||
cancelId: InstallerErrorChoice.Cancel, | ||
noLink: true, | ||
}); | ||
return result.response; | ||
} | ||
|
||
export enum DownloadErrorChoice { | ||
Cancel = 0, | ||
RetryDownload = 1, | ||
} | ||
|
||
export async function promptDownloadError(): Promise<DownloadErrorChoice> { | ||
const result = await dialog.showMessageBox({ | ||
type: 'error', | ||
buttons: [ | ||
'Cancel Update', | ||
'Retry Download', | ||
], | ||
message: 'Download Error', | ||
detail: 'Unable to download the update. Check your internet connection or try again later.', | ||
defaultId: DownloadErrorChoice.RetryDownload, | ||
cancelId: DownloadErrorChoice.Cancel, | ||
noLink: true, | ||
}); | ||
return result.response; | ||
} | ||
|
||
export enum UnexpectedErrorChoice { | ||
Cancel = 0, | ||
RetryUpdate = 1, | ||
} | ||
|
||
export async function showUnexpectedError(): Promise<UnexpectedErrorChoice> { | ||
const result = await dialog.showMessageBox({ | ||
type: 'error', | ||
buttons: [ | ||
'Cancel', | ||
'Retry Update', | ||
], | ||
message: 'Unexpected Error', | ||
detail: 'An unexpected error occurred. Would you like to retry updating?', | ||
defaultId: UnexpectedErrorChoice.RetryUpdate, | ||
cancelId: UnexpectedErrorChoice.Cancel, | ||
noLink: true, | ||
}); | ||
return result.response; | ||
} |
Oops, something went wrong.