Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb65b32c6c | ||
|
|
8f497974a8 | ||
|
|
b71ad797a3 | ||
|
|
ec34ac1124 | ||
|
|
840adf9429 | ||
|
|
5eff3a0488 | ||
|
|
5abf8ff216 | ||
|
|
e7218850ba | ||
|
|
adc2089887 |
7
.github/workflows/checks.build.yaml
vendored
7
.github/workflows/checks.build.yaml
vendored
@@ -81,10 +81,15 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Install Docker on macOS
|
||||
if: matrix.os == 'macos' # macOS runner is missing Docker
|
||||
if: matrix.os == 'macos' # macOS runner is missing Docker (see actions/runner-images#17)
|
||||
run: |-
|
||||
# Install Docker
|
||||
brew install docker
|
||||
# Install `buildx` (`docker build` without `buildx` is deprecated)
|
||||
brew install docker-buildx
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
ln -sfn $(which docker-buildx) ~/.docker/cli-plugins/docker-buildx
|
||||
# docker buildx install # Allow running `docker build` instead of `docker buildx build`
|
||||
# Docker on macOS misses daemon due to licensing, so install colima as runtime
|
||||
brew install colima
|
||||
# Start the daemon
|
||||
|
||||
@@ -35,8 +35,8 @@ Key attributes of a good script:
|
||||
## Documentation
|
||||
|
||||
- Use credible and reputable sources for references.
|
||||
- Use archived links by using [archive.org](https://archive.org) or [archive.today](https://archive.today).
|
||||
- Format archive.today links fully, for example: `https://archive.today/YYYYMMDDhhmmss/https://privacy.sexy`.
|
||||
- Use archived links by using [archive.org](https://archive.org) or [archive.ph](https://archive.ph).
|
||||
- Format archive.today links fully, for example: `https://archive.ph/YYYYMMDDhhmmss/https://privacy.sexy`.
|
||||
- Explain the default behavior if the script is not executed.
|
||||
|
||||
## Shared functions
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
/* eslint-disable no-template-curly-in-string */
|
||||
|
||||
const { join } = require('node:path');
|
||||
const { readdirSync } = require('fs');
|
||||
const { electronBundled, electronUnbundled } = require('./dist-dirs.json');
|
||||
|
||||
/**
|
||||
* @type {import('electron-builder').Configuration}
|
||||
* @see https://www.electron.build/configuration/configuration
|
||||
*/
|
||||
module.exports = {
|
||||
// Common options
|
||||
publish: {
|
||||
@@ -14,7 +19,9 @@ module.exports = {
|
||||
output: electronBundled,
|
||||
},
|
||||
extraMetadata: {
|
||||
main: join(electronUnbundled, 'main/index.cjs'), // do not `path.resolve`, it expects a relative path
|
||||
main: findMainEntryFile(
|
||||
join(electronUnbundled, 'main'), // do not `path.resolve`, it expects a relative path
|
||||
),
|
||||
},
|
||||
|
||||
// Windows
|
||||
@@ -41,3 +48,15 @@ module.exports = {
|
||||
artifactName: '${name}-${version}.${ext}',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds by accommodating different JS file extensions and module formats.
|
||||
*/
|
||||
function findMainEntryFile(parentDirectory) {
|
||||
const files = readdirSync(parentDirectory);
|
||||
const entryFile = files.find((file) => /^index\.(cjs|mjs|js)$/.test(file));
|
||||
if (!entryFile) {
|
||||
throw new Error(`Main entry file not found in ${parentDirectory}.`);
|
||||
}
|
||||
return join(parentDirectory, entryFile);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ const ELECTRON_DIST_SUBDIRECTORIES = {
|
||||
renderer: resolveElectronDistSubdirectory('renderer'),
|
||||
};
|
||||
|
||||
process.env.ELECTRON_ENTRY = resolve(ELECTRON_DIST_SUBDIRECTORIES.main, 'index.cjs');
|
||||
process.env.ELECTRON_ENTRY = resolve(ELECTRON_DIST_SUBDIRECTORIES.main, 'index.mjs');
|
||||
|
||||
export default defineConfig({
|
||||
main: getSharedElectronConfig({
|
||||
@@ -54,13 +54,23 @@ function getSharedElectronConfig(options: {
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// Mark: electron-esm-support
|
||||
// This is needed so `type="module"` works
|
||||
entryFileNames: '[name].cjs',
|
||||
format: 'es',
|
||||
|
||||
// Ensure all generated files use '.mjs' for module consistency.
|
||||
// Otherwise, preloader process get `.mjs` extension but main process get `.js` extension, see https://github.com/alex8088/electron-vite/issues/397.
|
||||
entryFileNames: '[name].mjs',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
plugins: [externalizeDepsPlugin({
|
||||
exclude: [
|
||||
// Keep 'electron-log' in bundling process.
|
||||
// This is a workaround for inability of Electron's ESM loader to resolve subpath imports.
|
||||
// Do not externalize `electron-log` so subpath imports such as `electron-log/main` works.
|
||||
// See https://github.com/electron/electron/issues/41241, https://github.com/alex8088/electron-vite/issues/401
|
||||
'electron-log',
|
||||
],
|
||||
})],
|
||||
define: {
|
||||
...getClientEnvironmentVariables(),
|
||||
},
|
||||
|
||||
898
package-lock.json
generated
898
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,7 @@
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"@types/markdown-it": "^13.0.7",
|
||||
"ace-builds": "^1.30.0",
|
||||
"electron-log": "^5.0.1",
|
||||
"electron-log": "^5.1.2",
|
||||
"electron-progressbar": "^2.1.0",
|
||||
"electron-updater": "^6.1.4",
|
||||
"file-saver": "^2.0.5",
|
||||
@@ -58,8 +58,8 @@
|
||||
"@vue/test-utils": "^2.4.1",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"cypress": "^13.3.1",
|
||||
"electron": "^27.0.0",
|
||||
"electron-builder": "^24.6.4",
|
||||
"electron": "^29.1.4",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-icon-builder": "^2.0.1",
|
||||
"electron-vite": "^2.1.0",
|
||||
@@ -82,7 +82,7 @@
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.1.6",
|
||||
"vitest": "^0.34.6",
|
||||
"vitest": "^1.3.1",
|
||||
"vue-tsc": "^1.8.19",
|
||||
"yaml-lint": "^1.7.0"
|
||||
},
|
||||
|
||||
@@ -44,8 +44,8 @@ function getBuildVerificationConfigs() {
|
||||
'--electron-unbundled': {
|
||||
printDistDirScriptArgument: '--electron-unbundled',
|
||||
filePatterns: [
|
||||
/main[/\\]index\.cjs/,
|
||||
/preload[/\\]index\.cjs/,
|
||||
/main[/\\]index\.(cjs|mjs|js)/,
|
||||
/preload[/\\]index\.(cjs|mjs|js)/,
|
||||
/renderer[/\\]index\.htm(l)?/,
|
||||
],
|
||||
},
|
||||
|
||||
@@ -95,7 +95,7 @@ function getLines(code: string): string[] {
|
||||
|
||||
/*
|
||||
Merges inline here-strings to a single lined string with Windows line terminator (\r\n)
|
||||
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules#here-strings
|
||||
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.4#here-strings
|
||||
*/
|
||||
function mergeHereStrings(code: string) {
|
||||
const regex = /@(['"])\s*(?:\r\n|\r|\n)((.|\n|\r)+?)(\r\n|\r|\n)\1@/g;
|
||||
|
||||
@@ -74,7 +74,7 @@ actions:
|
||||
- [tcsh source code](https://web.archive.org/web/20221029212024/https://github.com/tcsh-org/tcsh).
|
||||
|
||||
[1]: https://web.archive.org/web/20221029134950/https://linux.die.net/man/1/tcsh "tcsh(1) - Linux man page | linux.die.net"
|
||||
[2]: https://web.archive.org/web/20221029135041/https://books.google.com/books?id=LyDP5b2xzaMC&pg=PA56 "Sams Teach Yourself FreeBSD in 24 Hours - Michael Urban, Brian Tiemann - Google Books | books.google.com"
|
||||
[2]: https://web.archive.org/web/20221029135007/https://books.google.com/books?id=LyDP5b2xzaMC&pg=PA56#v=onepage&q&f=false "Sams Teach Yourself FreeBSD in 24 Hours - Michael Urban, Brian Tiemann - Google Books | books.google.com"
|
||||
call:
|
||||
function: DeleteFileFromUserAndRootHome
|
||||
parameters:
|
||||
@@ -1733,7 +1733,7 @@ actions:
|
||||
See also:
|
||||
- [Source code for the Ubuntu Report tool | github.com](https://web.archive.org/web/20221029221854/https://github.com/ubuntu/ubuntu-report/)
|
||||
- [Statistics gathered and visualized | ubuntu.com/desktop/statistics](https://web.archive.org/web/20221029221910/https://ubuntu.com/desktop/statistics)
|
||||
- [ubuntu-devel mailing list thread where ubuntu-report was first proposed, | lists.ubuntu.com ](https://web.archive.org/web/20221029221924/https://lists.ubuntu.com/archives/ubuntu-devel/2018-February/040139.html)
|
||||
- [ubuntu-devel mailing list thread where ubuntu-report was first proposed, | lists.ubuntu.com ](https://web.archive.org/web/20221029162523/https://lists.ubuntu.com/archives/ubuntu-devel/2018-February/040139.html)
|
||||
|
||||
[1]: https://web.archive.org/web/20221029162505/https://github.com/ubuntu/ubuntu-report/blob/30e902ebc17e4e10d83392d7cd3dc05fc9e35cc4/README.md "ubuntu-report/README.md at master · ubuntu/ubuntu-report | github.com"
|
||||
[2]: https://web.archive.org/web/20221029162538/https://github.com/ubuntu/ubuntu-report/blob/8e6030ff9bbeacacf41a9b58ea638a5c9a6f864d/README.md "More diagnostics data from desktop | lists.ubuntu.com"
|
||||
@@ -1974,10 +1974,10 @@ actions:
|
||||
|
||||
Read more about Zeitgeist:
|
||||
|
||||
- [Official website | zeitgeist.freedesktop.org](https://web.archive.org/web/20221029222739/https://zeitgeist.freedesktop.org/)
|
||||
- [Official website | zeitgeist.freedesktop.org](https://web.archive.org/web/20221029150843/https://zeitgeist.freedesktop.org/)
|
||||
- [Wikipedia article | en.wikipedia.org](https://web.archive.org/web/20221029222921/https://en.wikipedia.org/wiki/Zeitgeist_%28free_software%29)
|
||||
- [Launchpad project page | launchpad.net](https://web.archive.org/web/20221029223026/https://launchpad.net/zeitgeist/)
|
||||
- [ArchWiki article | wiki.archlinux.org](https://web.archive.org/web/20221029223033/https://wiki.archlinux.org/title/Zeitgeist)
|
||||
- [ArchWiki article | wiki.archlinux.org](https://web.archive.org/web/20221029164539/https://wiki.archlinux.org/title/Zeitgeist)
|
||||
|
||||
[1]: https://web.archive.org/web/20221029163704/https://packages.debian.org/en/sid/libdevel/libzeitgeist-2.0-dev "libzeitgeist-2.0-dev | Debian Packages | packages.debian.org"
|
||||
[2]: https://web.archive.org/web/20221029163817/https://gitlab.gnome.org/crvi/gnome-activity-journal "crvi / GNOME Activity Journal · GitLab | gitlab.gnome.org"
|
||||
@@ -2116,7 +2116,7 @@ actions:
|
||||
[3]: https://web.archive.org/web/20221029170026/https://packages.ubuntu.com/bionic/all/network-manager-config-connectivity-ubuntu/filelist "Ubuntu - File list of package network-manager-config-connectivity-ubuntu/bionic/all | packages.ubuntu.com"
|
||||
[4]: https://web.archive.org/web/20221029170108/https://github.com/pop-os/connectivity/blob/master/debian/20-connectivity-pop.conf "connectivity/20-connectivity-pop.conf at master · pop-os/connectivity | github.com"
|
||||
[5]: https://web.archive.org/web/20221029170202/https://cgit.freedesktop.org/NetworkManager/NetworkManager/tree/contrib/fedora/rpm/20-connectivity-fedora.conf "20-connectivity-fedora.conf\rpm\fedora\contrib - NetworkManager/NetworkManager - Network connection manager and user applications | reedesktop.org"
|
||||
[6]: https://web.archive.org/web/20221029170207/https://fedora.pkgs.org/35/fedora-updates-testing-x86_64/NetworkManager-config-connectivity-fedora-1.32.12-1.fc35.noarch.rpm.html "NetworkManager-config-connectivity-fedora | fedora.pkgs.org"
|
||||
[6]: https://archive.ph/2023.12.06-185917/https://pkgs.org/download/NetworkManager-config-connectivity-fedora "Networkmanager-config-connectivity-fedora Download (RPM) | pkgs.org"
|
||||
call:
|
||||
function: RunIfCommandExists
|
||||
parameters:
|
||||
@@ -2202,7 +2202,7 @@ actions:
|
||||
- Diagnostic information about your system and usage is sent to Microsoft servers [3].
|
||||
- Your usage data and data about feature performance [3].
|
||||
|
||||
[1]: https://web.archive.org/web/20221029170818/https://en.wikipedia.org/wiki/Visual_Studio_Code "Visual Studio Code - Wikipedia | en.wikipedia.org"
|
||||
[1]: https://web.archive.org/web/20221029142001/https://en.wikipedia.org/wiki/Visual_Studio_Code "Visual Studio Code - Wikipedia | en.wikipedia.org"
|
||||
[2]: https://web.archive.org/web/20221029170840/https://code.visualstudio.com/updates/v1_26#_offline-mode "Visual Studio Code July 2018 | code.visualstudio.com"
|
||||
[3]: https://web.archive.org/web/20221029171138/https://code.visualstudio.com/docs/getstarted/telemetry "Visual Studio Code Telemetry | code.visualstudio.com"
|
||||
children:
|
||||
@@ -2697,7 +2697,7 @@ actions:
|
||||
[2]: https://web.archive.org/web/20231003094154/https://bugzilla.mozilla.org/show_bug.cgi?id=1746646 "1746646 - (tcp-mochitests) [meta] Make mochitests work with TCP enabled (cookieBehavior = 5) | bugzilla.mozilla.org"
|
||||
[3]: https://web.archive.org/web/20230918172155/https://developer.mozilla.org/en-US/docs/Web/Privacy/State_Partitioning#disable_dynamic_state_partitioning "State Partitioning - Privacy on the web | MDN"
|
||||
[4]: https://web.archive.org/web/20231003094207/https://bugzilla.mozilla.org/show_bug.cgi?id=1649876#c5 "1649876 - Migrate FPI users to dFPI | bugzilla.mozilla.org"
|
||||
[5]: https://blog.mozilla.org/en/products/firefox/firefox-rolls-out-total-cookie-protection-by-default-to-all-users-worldwide/ "Firefox Rolls Out Total Cookie Protection By Default"
|
||||
[5]: https://web.archive.org/web/20231207105610/https://blog.mozilla.org/en/products/firefox/firefox-rolls-out-total-cookie-protection-by-default-to-all-users-worldwide/ "Firefox Rolls Out Total Cookie Protection By Default"
|
||||
[6]: https://web.archive.org/web/20231003094350/https://bugzilla.mozilla.org/show_bug.cgi?id=1631676#c25 "1631676 - Disable dfpi when privacy.firstparty.isolate=true | bugzilla.mozilla.org"
|
||||
call:
|
||||
function: AddFirefoxPrefs
|
||||
@@ -2887,7 +2887,7 @@ actions:
|
||||
setting [4].
|
||||
|
||||
[1]: https://web.archive.org/web/20221015102124/https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/internals/preferences.html "Preferences and Defines — Firefox Source Docs documentation | firefox-source-docs.mozilla.org"
|
||||
[2]: https://web.archive.org/web/20221015102305/https://searchfox.org/mozilla-central/source/modules/libpref/Preferences.cpp#3213
|
||||
[2]: https://web.archive.org/web/20221015102338/https://searchfox.org/mozilla-central/source/modules/libpref/Preferences.cpp#3213
|
||||
[3]: https://web.archive.org/web/20221015102419/https://bugzilla.mozilla.org/show_bug.cgi?id=1422689#c1
|
||||
[4]: https://web.archive.org/web/20221015102604/https://stigviewer.com/stig/mozilla_firefox/2020-12-10/finding/V-223170
|
||||
call:
|
||||
@@ -3173,7 +3173,7 @@ actions:
|
||||
portal is in place and blocking traffic, this feature prevents all other connection attempts,
|
||||
possibly revealing your usage habits.
|
||||
|
||||
See also: [Captive portal | Wikipedia](https://web.archive.org/web/20221029223534/https://en.wikipedia.org/wiki/Captive_portal).
|
||||
See also: [Captive portal | Wikipedia](https://web.archive.org/web/20221029163002/https://en.wikipedia.org/wiki/Captive_portal).
|
||||
|
||||
This script sets `network.captive-portal-service.enabled` to 'false', thereby disabling automatic
|
||||
connections [1].
|
||||
@@ -3207,7 +3207,7 @@ actions:
|
||||
There have been concerns about the potential for Google Safe Browsing to be used for censorship
|
||||
in the future, although this has not occurred as of yet [3].
|
||||
|
||||
[1]: https://web.archive.org/web/20221025192643/https://wiki.mozilla.org/Security/Safe_Browsing "Security/Safe Browsing - MozillaWiki | wiki.mozilla.org"
|
||||
[1]: https://web.archive.org/web/20221026164502/https://wiki.mozilla.org/Security/Safe_Browsing "Security/Safe Browsing - MozillaWiki | wiki.mozilla.org"
|
||||
[2]: https://web.archive.org/web/20221025193000/https://support.mozilla.org/en-US/kb/how-does-phishing-and-malware-protection-work#w_what-information-is-sent-to-mozilla-or-its-partners-when-phishing-and-malware-protection-is-enabled
|
||||
[3]: https://web.archive.org/web/20221025192516/https://www.usnews.com/opinion/articles/2016-06-22/google-is-the-worlds-biggest-censor-and-its-power-must-be-regulated "Google Is the World's Biggest Censor and Its Power Must Be Regulated | usnews.com"
|
||||
children:
|
||||
@@ -3226,7 +3226,7 @@ actions:
|
||||
|
||||
If this blocking is removed, the user should be knowledgeable about the potential risks and will take precautions.
|
||||
|
||||
[1]: https://web.archive.org/web/20221025192643/https://wiki.mozilla.org/Security/Safe_Browsing#Prefs "Security/Safe Browsing - MozillaWiki | wiki.mozilla.org"
|
||||
[1]: https://web.archive.org/web/20221026164502/https://wiki.mozilla.org/Security/Safe_Browsing#Prefs "Security/Safe Browsing - MozillaWiki | wiki.mozilla.org"
|
||||
[2]: https://web.archive.org/web/20230811024650/https://blog.mozilla.org/addons/2020/08/24/introducing-a-scalable-add-ons-blocklist/ "Introducing a scalable add-ons blocklist | Mozilla Add-ons Community Blog"
|
||||
call:
|
||||
function: AddFirefoxPrefs
|
||||
@@ -3286,7 +3286,7 @@ actions:
|
||||
|
||||
It is active by default [2].
|
||||
|
||||
[1]: https://web.archive.org/web/20221025192643/https://wiki.mozilla.org/Security/Safe_Browsing#Prefs "Security/Safe Browsing - MozillaWiki | wiki.mozilla.org"
|
||||
[1]: https://web.archive.org/web/20221026164502/https://wiki.mozilla.org/Security/Safe_Browsing#Prefs "Security/Safe Browsing - MozillaWiki | wiki.mozilla.org"
|
||||
[2]: https://web.archive.org/web/20221029173442/https://github.com/mozilla/policy-templates/blob/master/README.md#preferences "policy-templates/README.md at master · mozilla/policy-templates · GitHub | github.com"
|
||||
call:
|
||||
function: AddFirefoxPrefs
|
||||
|
||||
@@ -108,7 +108,7 @@ actions:
|
||||
name: Clear user activity audit logs (login, logout, authentication, etc.)
|
||||
docs:
|
||||
- https://papers.put.as/papers/macosx/2012/Mac_Log_Analysis_Sarah_Edwards_DFIRSummit2012.pdf
|
||||
- http://macadmins.psu.edu/wp-content/uploads/sites/24696/2016/06/psumac2016-19-osxlogs_macadmins_2016.pdf
|
||||
- https://web.archive.org/web/20240314054514/https://bpb-us-e1.wpmucdn.com/sites.psu.edu/dist/4/24696/files/2016/06/psumac2016-19-osxlogs_macadmins_2016.pdf
|
||||
code: |-
|
||||
sudo rm -rfv /var/audit/*
|
||||
sudo rm -rfv /private/var/audit/*
|
||||
@@ -171,7 +171,7 @@ actions:
|
||||
-
|
||||
name: Clear Safari last session (open tabs) history
|
||||
docs:
|
||||
- https://apple.stackexchange.com/a/374116
|
||||
- https://web.archive.org/web/20240314061752/https://apple.stackexchange.com/questions/374099/where-does-safari-store-the-open-tabs/374116#374116
|
||||
- https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-7127
|
||||
code: rm -f ~/Library/Safari/LastSession.plist
|
||||
-
|
||||
@@ -191,7 +191,7 @@ actions:
|
||||
name: Clear Safari webpage previews (thumbnails)
|
||||
docs:
|
||||
- https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
||||
- https://www.reddit.com/r/apple/comments/18lp92/your_apple_computer_keeps_a_screen_shot_of_nearly/
|
||||
- https://archive.ph/2024.03.14-100910/https://www.reddit.com/r/apple/comments/18lp92/your_apple_computer_keeps_a_screen_shot_of_nearly/?rdt=59921
|
||||
code: rm -rfv ~/Library/Caches/com.apple.Safari/Webpage\ Previews
|
||||
-
|
||||
name: Clear Safari history copy
|
||||
@@ -204,8 +204,8 @@ actions:
|
||||
-
|
||||
name: Clear Safari cookies
|
||||
docs:
|
||||
- https://www.toolbox.com/tech/operating-systems/blogs/understanding-the-safari-cookiesbinarycookies-file-format-010712/
|
||||
- https://link.springer.com/content/pdf/10.1007/0-387-36891-4_13.pdf
|
||||
- https://web.archive.org/web/20240314132018/https://community.spiceworks.com/t/understanding-the-safari-cookies-binarycookies-file-format/928827
|
||||
- https://web.archive.org/web/20240314060318/https://link.springer.com/content/pdf/10.1007/0-387-36891-4_13.pdf
|
||||
code: |-
|
||||
rm -f ~/Library/Cookies/Cookies.binarycookies
|
||||
# Used before Safari 5.1
|
||||
@@ -520,7 +520,7 @@ actions:
|
||||
you'll be prompted to grant or deny permission. It's a proactive step to ensure that your sensitive information
|
||||
or system services are accessed only with your current and informed consent.
|
||||
children:
|
||||
# Main documentation: https://archive.ph/26Hlq (https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services)
|
||||
# Main documentation: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services
|
||||
-
|
||||
name: Clear **"All"** permissions
|
||||
docs: |-
|
||||
@@ -536,7 +536,7 @@ actions:
|
||||
This script resets permissions for camera access [1].
|
||||
It ensures no application can access the system camera without explicit user permission, protecting against unauthorized surveillance and data breaches.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -547,7 +547,7 @@ actions:
|
||||
This script resets permissions for microphone access [1].
|
||||
It revokes all granted access to the microphone, protecting against eavesdropping and unauthorized audio recording by applications.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -558,7 +558,7 @@ actions:
|
||||
This script resets permissions for accessibility features [1].
|
||||
It revokes application access to accessibility services, preventing misuse and ensuring these features are used only with user consent.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -569,7 +569,7 @@ actions:
|
||||
This script resets permissions for screen capture [1].
|
||||
It ensures applications cannot capture screen content without user authorization, protecting sensitive information displayed on the screen.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -580,7 +580,7 @@ actions:
|
||||
This script resets permissions for accessing reminders information managed by the Reminders app [1].
|
||||
It ensures applications cannot access or modify reminders data without explicit user permission, maintaining the privacy of personal reminders.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -591,7 +591,7 @@ actions:
|
||||
This script resets permissions for accessing the pictures managed by the Photos app [1].
|
||||
It revokes all permissions granted to applications, safeguarding personal photos and media from unauthorized access.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -602,7 +602,7 @@ actions:
|
||||
This script resets permissions for accessing the calendar information managed by the Calendar app [1].
|
||||
It ensures that applications cannot access calendar data without user consent, protecting personal and sensitive calendar information.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -614,7 +614,7 @@ actions:
|
||||
Full disk access allows the application access to all protected files, including system administration files [1].
|
||||
It revokes broad file access from applications, significantly reducing the risk of data exposure and enhancing overall system security.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -626,7 +626,7 @@ actions:
|
||||
The contact information managed by the Contacts app [1].
|
||||
It ensures that applications cannot access the user's contact list without explicit permission, maintaining the confidentiality of personal contacts.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -637,7 +637,7 @@ actions:
|
||||
This script resets permissions for accessing the Desktop folder [1].
|
||||
It revokes application access to files on the desktop, protecting personal and work-related documents from unauthorized access.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -648,7 +648,7 @@ actions:
|
||||
This script resets permissions for accessing the Documents folder [1].
|
||||
It prevents applications from accessing files in this folder without user consent, safeguarding important and private documents.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -659,7 +659,7 @@ actions:
|
||||
This script resets permissions for accessing the Downloads folder [1].
|
||||
It ensures that applications cannot access downloaded files without user authorization, protecting downloaded content from misuse.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -670,7 +670,7 @@ actions:
|
||||
This script resets permissions for Apple Events [1].
|
||||
It revokes permissions for applications to send restricted Apple Events to other processes [1], enhancing privacy and security.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -681,7 +681,7 @@ actions:
|
||||
This script resets permissions for File Provider Presence [1].
|
||||
It revokes the ability of File Provider applications to know when the user is accessing their managed files [1], enhancing user privacy.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -692,7 +692,7 @@ actions:
|
||||
This script resets "ListenEvent" permissions [1].
|
||||
It revokes application access to listen to system events [1], preventing unauthorized monitoring of user interactions with the system.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -703,7 +703,7 @@ actions:
|
||||
This script resets permissions for accessing the Media Library [1].
|
||||
It ensures that applications cannot access Apple Music, music and video activity, and the media library [1] without user consent.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -714,7 +714,7 @@ actions:
|
||||
This script resets permissions for sending "PostEvent" [1].
|
||||
It prevents applications from using CoreGraphics APIs to send system events [1], safeguarding against potential misuse.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -726,7 +726,7 @@ actions:
|
||||
This script resets permissions for using Speech Recognition [1].
|
||||
It revokes application access to the speech recognition facility and sending speech data to Apple [1], protecting user privacy.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -737,7 +737,7 @@ actions:
|
||||
This script resets permissions for modifying other apps [1].
|
||||
It prevents applications from updating or deleting other apps [1], maintaining system integrity and user control.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -748,7 +748,7 @@ actions:
|
||||
This script resets permissions for accessing application data [1].
|
||||
It revokes application access to specific application data, enhancing privacy and data security.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -759,7 +759,7 @@ actions:
|
||||
This script resets permissions for accessing files on network volumes [1].
|
||||
It ensures applications cannot access network files without user authorization.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -770,7 +770,7 @@ actions:
|
||||
This script resets permissions for accessing files on removable volumes [1].
|
||||
It protects data on external drives from unauthorized application access.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -781,7 +781,7 @@ actions:
|
||||
This script resets permissions for accessing system administration files [1].
|
||||
It enhances system security by restricting application access to critical system files.
|
||||
|
||||
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||
call:
|
||||
function: ResetServicePermissions
|
||||
parameters:
|
||||
@@ -877,7 +877,7 @@ actions:
|
||||
There is also `WelcomeScreenPromo.PromoOff` setting that's pre-configured to `1` (`no` as
|
||||
default). It's undocumented but still kept disabled by this script.
|
||||
|
||||
[1]: https://web.archive.org/save/https://forum.parallels.com/threads/unable-to-process-the-upgrade-request.345603/ "Unable to process the upgrade request | Parallels Forums | forum.parallels.com"
|
||||
[1]: https://web.archive.org/web/20240314062932/https://forum.parallels.com/threads/unable-to-process-the-upgrade-request.345603/ "Unable to process the upgrade request | Parallels Forums | forum.parallels.com"
|
||||
[2]: https://web.archive.org/web/20221012151800/https://kb.parallels.com/114422 "How do I turn off notifications in Parallels Desktop and Parallels Access? | Knowledge Base | parallels.com"
|
||||
code: |-
|
||||
defaults write 'com.parallels.Parallels Desktop' 'ProductPromo.ForcePromoOff' -bool yes
|
||||
@@ -988,16 +988,16 @@ actions:
|
||||
recommend: strict
|
||||
docs:
|
||||
- https://github.com/privacysexy-forks/starter/blob/master/system/siri.sh
|
||||
- https://machippie.github.io/system/
|
||||
- https://web.archive.org/web/20201002133713/https://machippie.github.io/system/
|
||||
code: defaults write com.apple.assistant.backedup 'Use device speaker for TTS' -int 3
|
||||
revertCode: defaults write com.apple.assistant.backedup 'Use device speaker for TTS' -int 2
|
||||
-
|
||||
name: Disable Siri services (Siri and assistantd)
|
||||
recommend: strict
|
||||
docs:
|
||||
- https://apple.stackexchange.com/questions/57514/what-is-assistantd
|
||||
- https://www.jamf.com/jamf-nation/discussions/22757/kill-siri#responseChild137563
|
||||
- https://apple.stackexchange.com/a/370426
|
||||
- https://web.archive.org/web/20240314060540/https://apple.stackexchange.com/questions/57514/what-is-assistantd
|
||||
- https://archive.ph/2024.03.14-055010/https://community.jamf.com/t5/jamf-pro/kill-siri/td-p/171543
|
||||
- https://web.archive.org/web/20240314060501/https://apple.stackexchange.com/questions/258816/how-to-completely-disable-siri-on-sierra/370426#370426
|
||||
# To see status: • `launchctl print-disabled system` • `launchctl print-disabled user/$UID` • `launchctl print-disabled gui/$UID`
|
||||
code: |-
|
||||
launchctl disable "user/$UID/com.apple.assistantd"
|
||||
@@ -1021,10 +1021,20 @@ actions:
|
||||
fi
|
||||
-
|
||||
name: Disable "Do you want to enable Siri?" pop-up
|
||||
docs:
|
||||
- https://discussions.apple.com/thread/7694127?answerId=30752577022#30752577022
|
||||
- https://windowsreport.com/mac/siri-keeps-popping-up/
|
||||
- https://www.jamf.com/jamf-nation/discussions/21783/disable-siri-setup-assistant-in-macos-sierra#responseChild131588
|
||||
docs: |-
|
||||
This script stops the "Enable Siri" pop-up [1] from appearing the first time a user logs into macOS [2].
|
||||
|
||||
Introduced in macOS version 10.12 [2], this pop-up asks, "Do you want to enable Siri?" [1]
|
||||
which could lead to Siri being enabled unintentionally.
|
||||
|
||||
This script configures the `com.apple.SetupAssistant!DidSeeSiriSetup` setting to suppress this pop-up [1] [2] [3] [4].
|
||||
This command tells the system that the Siri setup is complete, preventing the pop-up in future sessions and
|
||||
enhancing privacy by avoiding unintended Siri activation.
|
||||
|
||||
[1]: https://archive.ph/2024.03.14-053325/https://discussions.apple.com/thread/7694127?answerId=30752577022&sortBy=best%2330752577022 "macOS keeps nagging me about enabling Siri - Apple Community | discussions.apple.com"
|
||||
[2]: https://web.archive.org/web/20240314052600/https://derflounder.wordpress.com/2016/09/20/supressing-siri-pop-up-windows-on-macos-sierra/ "Suppressing Siri pop-up windows on macOS Sierra | Der Flounder"
|
||||
[3]: https://web.archive.org/web/20240314052901/https://windowsreport.com/mac/siri-keeps-popping-up/ "Siri keeps popping up on Mac? Here's how to easily fix that • MacTips | windowsreport.com"
|
||||
[4]: https://web.archive.org/web/20240314052247/https://community.jamf.com/t5/jamf-pro/disable-siri-setup-assistant-in-macos-sierra/m-p/205836/highlight/true#M194536 "Solved: Re: Disable Siri setup assistant in macOS Sierra - Jamf Nation Community - 205834 | community.jamf.com"
|
||||
code: defaults write com.apple.SetupAssistant 'DidSeeSiriSetup' -bool True
|
||||
revertCode: defaults delete com.apple.SetupAssistant 'DidSeeSiriSetup'
|
||||
-
|
||||
@@ -1084,7 +1094,7 @@ actions:
|
||||
by default.
|
||||
|
||||
[1]: https://web.archive.org/web/20230731152633/https://www.apple.com/legal/privacy/data/en/apple-advertising/ "Legal - Apple Advertising & Privacy - Apple"
|
||||
[2]: https://web.archive.org/web/20220805052411/https://support.apple.com/en-sg/guide/mac-help/mh32356/mac: "Change Privacy preferences on Mac - Apple Support (SG)"
|
||||
[2]: https://web.archive.org/web/20220805052411/https://support.apple.com/en-sg/guide/mac-help/mh32356/mac "Change Privacy preferences on Mac - Apple Support (SG)"
|
||||
[3]: https://web.archive.org/web/20230731155827/https://developer.apple.com/documentation/devicemanagement/restrictions "Restrictions | Apple Developer Documentation"
|
||||
[4]: https://web.archive.org/web/20230731155653/https://paper.bobylive.com/Security/CIS/CIS_Apple_macOS_11_0_Big_Sur_Benchmark_v2_0_0.pdf "CIS Apple macOS 11.0 Big Sur Benchmark"
|
||||
[5]: https://web.archive.org/web/20230731155131/https://developer.apple.com/documentation/adsupport/asidentifiermanager/1614151-advertisingidentifier "advertisingIdentifier | Apple Developer Documentation"
|
||||
@@ -1280,7 +1290,7 @@ actions:
|
||||
# OS tracks downloaded files with help of quarantine-aware applications
|
||||
# (such as Safari, Chrome) adding quarantine extended attributes to files.
|
||||
# then OS warns and asks if you really want to open it
|
||||
docs: https://support.apple.com/en-gb/HT202491
|
||||
docs: https://web.archive.org/web/20210319081714/https://support.apple.com/en-gb/HT202491
|
||||
children:
|
||||
-
|
||||
category: Clean File Quarantine from downloaded files
|
||||
@@ -1391,7 +1401,7 @@ actions:
|
||||
name: Disable Gatekeeper's automatic reactivation
|
||||
docs:
|
||||
- https://osxdaily.com/2015/11/05/stop-gatekeeper-auto-rearm-mac-os-x/
|
||||
- https://www.cnet.com/tech/computing/how-to-disable-gatekeeper-permanently-on-os-x/
|
||||
- https://web.archive.org/web/20230327050142/https://www.cnet.com/tech/computing/how-to-disable-gatekeeper-permanently-on-os-x/
|
||||
code: sudo defaults write /Library/Preferences/com.apple.security GKAutoRearm -bool true
|
||||
revertCode: sudo defaults write /Library/Preferences/com.apple.security GKAutoRearm -bool false
|
||||
-
|
||||
@@ -1450,13 +1460,19 @@ actions:
|
||||
revertCode: sudo defaults write /Library/Preferences/com.apple.security.libraryvalidation.plist 'DisableLibraryValidation' -bool false
|
||||
-
|
||||
category: Disable automatic updates
|
||||
docs:
|
||||
- https://developer.apple.com/documentation/devicemanagement/deviceinformationresponse/queryresponses/osupdatesettings
|
||||
- https://macadminsdoc.readthedocs.io/en/master/Profiles-and-Settings/OS-X-Updates.html
|
||||
docs: |-
|
||||
This category contains scripts to disable automatic operating system updates.
|
||||
|
||||
Disabling automatic updates gives users full control over when and which updates are applied to their system.
|
||||
It improves privacy by preventing unwanted data collection, new vulnerabilities and unapproved changes to system settings.
|
||||
|
||||
> **Caution**:
|
||||
> Disabling automatic updates can leave your system vulnerable to unpatched exploits.
|
||||
> Manually check and and apply updates to stay protected.
|
||||
children:
|
||||
-
|
||||
name: Disable automatic checks for updates
|
||||
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||
docs: https://archive.ph/2024.03.21-180353/https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||
code: |-
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool false
|
||||
@@ -1465,7 +1481,7 @@ actions:
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool true
|
||||
-
|
||||
name: Disable automatic downloads for updates
|
||||
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||
docs: https://archive.ph/2024.03.21-180353/https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||
code: |-
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool false
|
||||
@@ -1474,12 +1490,41 @@ actions:
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool true
|
||||
-
|
||||
name: Disable automatic installation of macOS updates
|
||||
docs:
|
||||
# References for AutoUpdateRestartRequired
|
||||
- https://kb.vmware.com/s/article/2960635
|
||||
- https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
||||
# References for AutomaticallyInstallMacOSUpdates
|
||||
- https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||
docs: |-
|
||||
This script stops macOS from automatically installing updates.
|
||||
|
||||
This script improves privacy by reducing unwanted data collection and ensuring updates don't change
|
||||
settings or data without your approval.
|
||||
|
||||
The Center for Internet Security (CIS) advises against automatic updates in scenarios where changes require
|
||||
thorough testing and approval processes to avoid operational disruptions [1] [2] [3] [4].
|
||||
|
||||
This script configures following to stop macOS from installing updates automatically:
|
||||
|
||||
1. `/Library/Preferences/com.apple.commerce!AutoUpdateRestartRequired`:
|
||||
This preference stops the system from automatically installing macOS updates [1] [2] [3] [4] [5] [6] [7] [8].
|
||||
By doing this, updates will only be installed when you decide, giving you a chance to check them first [1] [2] [3] [4] [5] [6] [7] [8].
|
||||
This setting applies to OS X Yosemite through macOS High Sierra [7] [9].
|
||||
|
||||
2. `/Library/Preferences/com.apple.commerce!AutomaticallyInstallMacOSUpdates`:
|
||||
Changing this setting stops macOS from installing updates automatically [3] [5] [9] [10], giving you control over when to update.
|
||||
If restricts the *Install macOS Updates* option and prevents the user from changing the option [10].
|
||||
While this setting enhances privacy, it's generally not advised by NIST due to potential security risks [9].
|
||||
This setting applies to macOS Mojave and newer versions [9].
|
||||
|
||||
> **Caution**: Disabling automatic updates requires you to manually check and apply updates to stay protected against security threats [1] [2] [3] [4].
|
||||
|
||||
[1]: https://web.archive.org/web/20240321165149/https://www.tenable.com/audits/items/CIS_Apple_macOS_10.12_v1.1.0_Level_1.audit:e02dfdd6bec9556a3ce537f60b91b549 "CIS Apple macOS 10.12 L1 v1.1.0 | 1.5 Enable OS X update installs | Tenable®"
|
||||
[2]: https://web.archive.org/web/20240321165851/https://paper.bobylive.com/Security/CIS/CIS_Apple_macOS_10_13_Benchmark_v1_1_0---PDF.pdf "CIS Apple macOS 10.13 Benchmark v1.1.0 | paper.bobylive.com"
|
||||
[3]: https://web.archive.org/web/20240321170400/https://www.tenable.com/audits/items/CIS_Apple_macOS_13.0_Ventura_v1.0.0_L1.audit:fe03c59a39c7c949507ff20d07f89993 "1.4 Ensure Install of macOS Updates Is Enabled | Tenable® | www.tenable.com"
|
||||
[4]: https://web.archive.org/web/20240321170036/https://paper.bobylive.com/Security/CIS/CIS_Apple_macOS_10_14_Benchmark_v1_4_0_PDF.pdf "CIS Apple macOS 10.14 Benchmark v1.4.0 | paper.bobylive.com"
|
||||
[5]: https://web.archive.org/web/20240321164917/https://www.ncsc.gov.uk/files/macos_provisioning_script.sh_.txt "macOS provisioning script | UK National Cyber Security Centre | www.ncsc.gov.uk"
|
||||
[6]: https://web.archive.org/web/20240321165118/https://macadminsdoc.readthedocs.io/en/master/Profiles-and-Settings/OS-X-Updates.html "macOS Updates — MacAdmins Community Documentation documentation | macadminsdoc.readthedocs.io"
|
||||
[7]: https://web.archive.org/web/20240321165304/https://derflounder.wordpress.com/2014/12/29/managing-automatic-app-store-and-os-x-update-installation-on-yosemite/ "Managing automatic App Store and OS X update installation on Yosemite | Der Flounder | derflounder.wordpress.com"
|
||||
[8]: https://web.archive.org/web/20240321170034/https://krypted.com/mac-os-x/app-store-preferences-set-server-5-4-macos-high-sierra/ "App Store Preferences To Set In On Server 5.4 for macOS High Sierra – krypted | krypted.com"
|
||||
[9]: https://web.archive.org/web/20240321170251/https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/ "Enabling automatic macOS software updates for OS X Yosemite through macOS Mojave | Der Flounder | derflounder.wordpress.com"
|
||||
[10]: https://archive.ph/2024.03.21-180353/https://developer.apple.com/documentation/devicemanagement/softwareupdate "SoftwareUpdate | Apple Developer Documentation | developer.apple.com"
|
||||
[11]: https://web.archive.org/web/20240321165931/https://csrc.nist.gov/CSRC/media/Projects/national-vulnerability-database/documents/CCE/CCE-macos_monterey.xls "CCE-91129-7 | CCE-macos_monterey.xls | Sheet 1 - NIST Computer Security Resource Center | csrc.nist.gov"
|
||||
code: |-
|
||||
# For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14)
|
||||
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool false
|
||||
@@ -1492,9 +1537,44 @@ actions:
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool true
|
||||
-
|
||||
name: Disable automatic app updates from the App Store
|
||||
docs:
|
||||
- https://kb.vmware.com/s/article/2960635
|
||||
- https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
||||
docs: |-
|
||||
This script disables automatic app updates [1] [2] [3] [4] from the App Store [5] [6] [7] [8] [9] [10] [11] [12] [13].
|
||||
It prevents automatic installation of application updates as soon as they become available from Apple [2] [3] [6] [9] [11] [12] [13].
|
||||
Thus, applications are updated only when you choose to do so [5].
|
||||
|
||||
Disabling automatic updates prevents unexpected app behavior or settings changes.
|
||||
It helps you to maintain your current app configurations and privacy settings.
|
||||
It also protects against potential zero-day vulnerabilities in your apps.
|
||||
This gives you the ability to choose which updates to install and when, enabling you to review the details of updates before deciding to proceed.
|
||||
|
||||
The script modifies the following settings:
|
||||
|
||||
1. `/Library/Preferences/com.apple.commerce!AutoUpdate`:
|
||||
Disables automated app updates [1] [2] [3] [6] [9] [10] [13] from the App Store [7] [8].
|
||||
This setting applies to OS X Yosemite and newer versions [1].
|
||||
2. `/Library/Preferences/com.apple.SoftwareUpdate!AutomaticallyInstallAppUpdates`:
|
||||
Stops the automatic installation of app updates [1] [4] from App Store [9] [10] [11] [12] [13].
|
||||
It deselects the *Install app updates from the App Store* option and prevents the user from changing the option [10].
|
||||
While this setting enhances privacy, it's generally not advised by NIST due to potential security risks [4].
|
||||
This setting applies to macOS Mojave and newer versions [1].
|
||||
|
||||
> **Caution**:
|
||||
> Disabling app updates means you should manually check for and install important security patches for every application
|
||||
> to protect against vulnerabilities [2] [3] [5] [6] [9] [11] [12] [13].
|
||||
|
||||
[1]: https://web.archive.org/web/20240321170251/https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/ "Enabling automatic macOS software updates for OS X Yosemite through macOS Mojave | Der Flounder | derflounder.wordpress.com"
|
||||
[2]: https://web.archive.org/web/20240321190032/https://www.irs.gov/pub/irs-utl/safeguards-scsem-macosx-v6-1-093021.xlsx "SCSEM OSX 10.14 | Internal Revenue Service Office of Safeguards | www.irs.gov"
|
||||
[3]: https://web.archive.org/web/20240321170036/https://paper.bobylive.com/Security/CIS/CIS_Apple_macOS_10_14_Benchmark_v1_4_0_PDF.pdf "CIS Apple macOS 10.14 Benchmark v1.4.0 | paper.bobylive.com"
|
||||
[5]: https://web.archive.org/web/20240321190244/https://github-wiki-see.page/m/edamametechnologies/threatmodels/wiki/threatmodel-macOS-EN "threatmodel macOS EN - edamametechnologies/threatmodels GitHub Wiki | github-wiki-see.page"
|
||||
[6]: https://web.archive.org/web/20240321190315/https://www.tenable.com/audits/items/CIS_Apple_macOS_14.0_Sonoma_v1.0.0_L1.audit:66d3b86318384ba7947a3409e0c6e902 "1.5 Ensure Install Application Updates from the App Store Is E... | Tenable® | www.tenable.com"
|
||||
[7]: https://web.archive.org/web/20240321165304/https://derflounder.wordpress.com/2014/12/29/managing-automatic-app-store-and-os-x-update-installation-on-yosemite/ "Managing automatic App Store and OS X update installation on Yosemite | Der Flounder | derflounder.wordpress.com"
|
||||
[8]: https://web.archive.org/web/20240321190410/https://krypted.com/mac-security/app-store-preferences-set-server-5-2-macos-sierra/ "App Store Preferences To Set In On Server 5.2 for macOS Sierra – krypted | krypted.com"
|
||||
[4]: https://web.archive.org/web/20240321165931/https://csrc.nist.gov/CSRC/media/Projects/national-vulnerability-database/documents/CCE/CCE-macos_monterey.xls "CCE-91129-7 | CCE-macos_monterey.xls | Sheet 1 - NIST Computer Security Resource Center | csrc.nist.gov"
|
||||
[9]: https://web.archive.org/web/20240321190114/https://www.irs.gov/pub/irs-utl/safeguards-scsem-macosx.xlsx "SCSEM OSX 13.0 | Internal Revenue Service Office of Safeguards | www.irs.gov"
|
||||
[10]: https://archive.ph/2024.03.21-180353/https://developer.apple.com/documentation/devicemanagement/softwareupdate "SoftwareUpdate | Apple Developer Documentation | developer.apple.com"
|
||||
[11]: https://web.archive.org/web/20240321190122/https://paper.bobylive.com/Security/CIS/CIS_Apple_macOS_12_0_Monterey_Benchmark_v1_0_0.pdf "CIS Apple macOS 12.0 Monterey | CIS Benchmarks | paper.bobylive.com"
|
||||
[12]: https://web.archive.org/web/20240321190537/https://www.tenable.com/audits/items/CIS_Apple_macOS_11_v2.0.0_L1.audit:55e8759872dce781b8dbc5a3f42e23b9 "1.4 Ensure Installation of App Update Is Enabled | Tenable® | www.tenable.com"
|
||||
[13]: https://web.archive.org/web/20240321164917/https://www.ncsc.gov.uk/files/macos_provisioning_script.sh_.txt "macOS provisioning script | UK National Cyber Security Centre | www.ncsc.gov.uk"
|
||||
code: |-
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool false
|
||||
@@ -1507,7 +1587,7 @@ actions:
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool true
|
||||
-
|
||||
name: Disable macOS beta release installation
|
||||
docs: https://support.apple.com/en-gb/HT203018
|
||||
docs: https://web.archive.org/web/20170106103856/https://support.apple.com/en-gb/HT203018
|
||||
code: |-
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool false
|
||||
@@ -1516,7 +1596,7 @@ actions:
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool true
|
||||
-
|
||||
name: Disable automatic installation for configuration data (e.g. XProtect, Gatekeeper, MRT)
|
||||
docs: https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
||||
docs: https://web.archive.org/web/20240321170251/https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
||||
code: |-
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool false
|
||||
@@ -1525,12 +1605,47 @@ actions:
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool true
|
||||
-
|
||||
name: Disable automatic installation for system data files and security updates
|
||||
docs:
|
||||
# References for CriticalUpdateInstall
|
||||
- https://derflounder.wordpress.com/2014/12/24/managing-os-xs-automatic-security-updates/
|
||||
- https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||
# References for softwareupdate --background-critical
|
||||
- https://managingosx.wordpress.com/2013/04/30/undocumented-options/
|
||||
docs: |-
|
||||
This script stops automatic installations of critical updates [1],
|
||||
including security [1] [2] [3] [4] [5] [6] [7] and system data file [1] [8] updates.
|
||||
|
||||
It improves privacy by providing:
|
||||
|
||||
- **Control Over Update Timing**:
|
||||
Users can review updates before installation to ensure they meet privacy standards and do not introduce
|
||||
unwanted telemetry or changes.
|
||||
- **Reduced External Communications**:
|
||||
Reduces how often it connects to update servers, potentially protection user information.
|
||||
|
||||
The script configures the `/Library/Preferences/com.apple.SoftwareUpdate!CriticalUpdateInstall` setting [1] [4] [5] [7] [8].
|
||||
This action prevents automatic downloads and installations of updates [1].
|
||||
It also prevents users from changing the Install system data files and security updates option manually [1].
|
||||
This script is compatible with OS X Yosemite and later versions [6] [8].
|
||||
|
||||
The revert script triggers `softwareupdate --background-critical` to install any pending critical updates directly [2] [9].
|
||||
|
||||
> **Caution:**
|
||||
> Only disable automatic updates if you're committed to manually installing them quickly to maintain your computer's security [4] [5] [8].
|
||||
> It's important to install updates soon to protect your computer. [4] [5] [8].
|
||||
>
|
||||
> This script disables:
|
||||
>
|
||||
> - Definition updates for **XProtect** and **Gatekeeper** that keep your computer safe from new threats [5].
|
||||
> - **Rapid Security Response** [10] [11].
|
||||
> **Rapid Security Responses** are software releases providing important security improvements between standard updates [12].
|
||||
|
||||
[1]: https://archive.ph/2024.03.21-180353/https://developer.apple.com/documentation/devicemanagement/softwareupdate "SoftwareUpdate | Apple Developer Documentation | developer.apple.com"
|
||||
[2]: https://web.archive.org/web/20240321201417/https://derflounder.wordpress.com/2014/12/24/managing-os-xs-automatic-security-updates/ "Managing OS X’s automatic security updates | Der Flounder | derflounder.wordpress.com"
|
||||
[3]: https://web.archive.org/web/20240321165118/https://macadminsdoc.readthedocs.io/en/master/Profiles-and-Settings/OS-X-Updates.html "macOS Updates — MacAdmins Community Documentation documentation | macadminsdoc.readthedocs.io"
|
||||
[4]: https://web.archive.org/web/20240321165931/https://csrc.nist.gov/CSRC/media/Projects/national-vulnerability-database/documents/CCE/CCE-macos_monterey.xls "CCE-91129-7 | CCE-macos_monterey.xls | Sheet 1 - NIST Computer Security Resource Center | csrc.nist.gov"
|
||||
[5]: https://web.archive.org/web/20240321201450/https://paper.bobylive.com/Security/CIS/CIS_Apple_OSX_10_9_Benchmark_v1_3_0.pdf "CIS Apple OSX 10.9 Benchmark | paper.bobylive.com"
|
||||
[6]: https://web.archive.org/web/20240321201643/https://derflounder.wordpress.com/2014/12/27/managing-automatic-installation-of-configdata-and-security-software-updates-on-yosemite/ "Managing automatic installation of ConfigData and security software updates on Yosemite | Der Flounder | derflounder.wordpress.com"
|
||||
[7]: https://web.archive.org/web/20240321201652/https://ss64.com/mac/syntax-defaults.html "System preference settings for macOS - macOS - SS64.com | ss64.com"
|
||||
[8]: https://web.archive.org/web/20240321201436/https://www.tenable.com/audits/items/CIS_OSX_10.10_v1.2.0_L1.audit:97f36c2eaa06045e85a1beff1a76a088 "1.4 Enable system data files and security update installs - 'C... | Tenable® | www.tenable.com"
|
||||
[9]: https://web.archive.org/web/20240321201406/https://managingosx.wordpress.com/2013/04/30/undocumented-options/ "Undocumented options – Managing OS X | managingosx.wordpress.com"
|
||||
[10]: https://web.archive.org/web/20240321201558/https://www.intuneirl.com/rapid-security-response/ "Managing Rapid Security Response on Apple Devices | www.intuneirl.com"
|
||||
[11]: https://web.archive.org/web/20240321201614/https://onsitegroup.co.za/rapid-security-response/ "Rapid security response - Onsite | onsitegroup.co.za"
|
||||
[12]: https://web.archive.org/web/20240321201623/https://support.apple.com/en-us/102657 "About Rapid Security Responses for iOS, iPadOS, and macOS - Apple Support | support.apple.com"
|
||||
code: |-
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool false
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,10 @@
|
||||
export type SchedulerCallbackType = (...args: unknown[]) => void;
|
||||
export type SchedulerType = (callback: SchedulerCallbackType, ms: number) => void;
|
||||
|
||||
export function sleep(time: number, scheduler: SchedulerType = setTimeout) {
|
||||
export function sleep(
|
||||
time: number,
|
||||
scheduler: SchedulerType = setTimeout,
|
||||
): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
scheduler(() => resolve(undefined), time);
|
||||
});
|
||||
|
||||
@@ -128,3 +128,8 @@
|
||||
$calculated-width-in-em: calc(#{$estimated-width-per-character-in-em} * #{$value-in-ch});
|
||||
#{$property}: $calculated-width-in-em;
|
||||
}
|
||||
|
||||
@mixin base-font-style {
|
||||
font-family: $font-family-main;
|
||||
font-size: $font-size-absolute-normal;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,7 @@ $base-spacing: 1em;
|
||||
|
||||
body {
|
||||
background: $color-background;
|
||||
font-family: $font-family-main;
|
||||
font-size: $font-size-absolute-normal;
|
||||
|
||||
@include base-font-style;
|
||||
@include apply-uniform-spacing($base-spacing: $base-spacing)
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,9 @@ $color-tooltip-background: $color-primary-darkest;
|
||||
color: $color-on-primary;
|
||||
border-radius: 16px;
|
||||
padding: 12px 10px;
|
||||
font-size: $font-size-absolute-normal;
|
||||
|
||||
// Explicitly set font styling for tooltips to prevent inconsistent appearances due to style inheritance from trigger elements.
|
||||
@include base-font-style;
|
||||
|
||||
/*
|
||||
This margin creates a visual buffer between the tooltip and the edges of the document.
|
||||
|
||||
@@ -13,4 +13,4 @@ export const RENDERER_URL = process.env.ELECTRON_RENDERER_URL;
|
||||
|
||||
export const RENDERER_HTML_PATH = join('file://', __dirname, '../renderer/index.html');
|
||||
|
||||
export const PRELOADER_SCRIPT_PATH = join(__dirname, '../preload/index.cjs');
|
||||
export const PRELOADER_SCRIPT_PATH = join(__dirname, '../preload/index.mjs');
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { app, dialog } from 'electron/main';
|
||||
import { autoUpdater, type UpdateInfo } from 'electron-updater';
|
||||
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
||||
import { UpdateProgressBar } from './UpdateProgressBar';
|
||||
import { getAutoUpdater } from './ElectronAutoUpdaterFactory';
|
||||
import type { AppUpdater, UpdateInfo } from 'electron-updater';
|
||||
import type { ProgressInfo } from 'electron-builder';
|
||||
|
||||
export async function handleAutoUpdate() {
|
||||
const autoUpdater = getAutoUpdater();
|
||||
if (await askDownloadAndInstall() === DownloadDialogResult.NotNow) {
|
||||
return;
|
||||
}
|
||||
startHandlingUpdateProgress();
|
||||
startHandlingUpdateProgress(autoUpdater);
|
||||
await autoUpdater.downloadUpdate();
|
||||
}
|
||||
|
||||
function startHandlingUpdateProgress() {
|
||||
function startHandlingUpdateProgress(autoUpdater: AppUpdater) {
|
||||
const progressBar = new UpdateProgressBar();
|
||||
progressBar.showIndeterminateState();
|
||||
autoUpdater.on('error', (e) => {
|
||||
@@ -29,11 +31,11 @@ function startHandlingUpdateProgress() {
|
||||
autoUpdater.on('update-downloaded', async (info: UpdateInfo) => {
|
||||
ElectronLogger.info('@update-downloaded@\n', info);
|
||||
progressBar.close();
|
||||
await handleUpdateDownloaded();
|
||||
await handleUpdateDownloaded(autoUpdater);
|
||||
});
|
||||
}
|
||||
|
||||
async function handleUpdateDownloaded() {
|
||||
async function handleUpdateDownloaded(autoUpdater: AppUpdater) {
|
||||
if (await askRestartAndInstall() === InstallDialogResult.NotNow) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import electronUpdater, { type AppUpdater } from 'electron-updater';
|
||||
|
||||
export function getAutoUpdater(): AppUpdater {
|
||||
// Using destructuring to access autoUpdater due to the CommonJS module of 'electron-updater'.
|
||||
// It is a workaround for ESM compatibility issues, see https://github.com/electron-userland/electron-builder/issues/7976.
|
||||
const { autoUpdater } = electronUpdater;
|
||||
return autoUpdater;
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
import { autoUpdater, type UpdateInfo } from 'electron-updater';
|
||||
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
||||
import { requiresManualUpdate, startManualUpdateProcess } from './ManualUpdater/ManualUpdateCoordinator';
|
||||
import { handleAutoUpdate } from './AutomaticUpdateCoordinator';
|
||||
import { getAutoUpdater } from './ElectronAutoUpdaterFactory';
|
||||
import type { UpdateInfo } from 'electron-updater';
|
||||
|
||||
interface Updater {
|
||||
checkForUpdates(): Promise<void>;
|
||||
}
|
||||
|
||||
export function setupAutoUpdater(): Updater {
|
||||
const autoUpdater = getAutoUpdater();
|
||||
|
||||
autoUpdater.logger = ElectronLogger;
|
||||
|
||||
// Auto-downloads are disabled to allow separate handling of 'check' and 'download' actions,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { splitTextIntoLines, indentText } from '../utils/text';
|
||||
import { indentText, splitTextIntoLines } from '@tests/shared/Text';
|
||||
import { log, die } from '../utils/log';
|
||||
import { readAppLogFile } from './app-logs';
|
||||
import { STDERR_IGNORE_PATTERNS } from './error-ignore-patterns';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { filterEmpty } from '@tests/shared/Text';
|
||||
import { runCommand } from '../../utils/run-command';
|
||||
import { log, LogLevel } from '../../utils/log';
|
||||
import { SupportedPlatform, CURRENT_PLATFORM } from '../../utils/platform';
|
||||
import { filterEmpty } from '../../utils/text';
|
||||
|
||||
export async function captureWindowTitles(processId: number) {
|
||||
if (!processId) { throw new Error('Missing process ID.'); }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { indentText } from '@tests/shared/Text';
|
||||
import { logCurrentArgs, CommandLineFlag, hasCommandLineFlag } from './cli-args';
|
||||
import { log, die } from './utils/log';
|
||||
import { ensureNpmProjectDir, npmInstall, npmBuild } from './utils/npm';
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
APP_EXECUTION_DURATION_IN_SECONDS,
|
||||
SCREENSHOT_PATH,
|
||||
} from './config';
|
||||
import { indentText } from './utils/text';
|
||||
import type { ExtractionResult } from './app/extractors/common/extraction-result';
|
||||
|
||||
export async function main(): Promise<void> {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { exec, type ExecOptions, type ExecException } from 'node:child_process';
|
||||
import { indentText } from './text';
|
||||
import { exec } from 'child_process';
|
||||
import { indentText } from '@tests/shared/Text';
|
||||
import type { ExecOptions, ExecException } from 'child_process';
|
||||
|
||||
const TIMEOUT_IN_SECONDS = 180;
|
||||
const MAX_OUTPUT_BUFFER_SIZE = 1024 * 1024; // 1 MB
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
describe, it, beforeAll, afterAll,
|
||||
} from 'vitest';
|
||||
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
||||
import { main } from './check-desktop-runtime-errors/main';
|
||||
import { COMMAND_LINE_FLAGS, CommandLineFlag } from './check-desktop-runtime-errors/cli-args';
|
||||
|
||||
@@ -14,7 +15,10 @@ describe('desktop runtime error checks', () => {
|
||||
() => main(),
|
||||
);
|
||||
// assert
|
||||
expect(exitCode).to.equal(0);
|
||||
expect(exitCode).to.equal(0, formatAssertionMessage([
|
||||
`Test failed with exit code ${exitCode}; expected 0.`,
|
||||
'Examine preceding logs to identify errors.',
|
||||
]));
|
||||
}, {
|
||||
timeout: 60 /* minutes */ * 60000,
|
||||
});
|
||||
|
||||
@@ -1,64 +1,65 @@
|
||||
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
|
||||
import { getUrlStatus, type IRequestOptions } from './Requestor';
|
||||
import { groupUrlsByDomain } from './UrlPerDomainGrouper';
|
||||
import type { IUrlStatus } from './IUrlStatus';
|
||||
import { getUrlStatus, type RequestOptions } from './Requestor';
|
||||
import { groupUrlsByDomain } from './UrlDomainProcessing';
|
||||
import type { FollowOptions } from './FetchFollow';
|
||||
import type { UrlStatus } from './UrlStatus';
|
||||
|
||||
export async function getUrlStatusesInParallel(
|
||||
urls: string[],
|
||||
options?: IBatchRequestOptions,
|
||||
): Promise<IUrlStatus[]> {
|
||||
// urls = [ 'https://privacy.sexy' ]; // Here to comment out when testing
|
||||
options?: BatchRequestOptions,
|
||||
): Promise<UrlStatus[]> {
|
||||
// urls = ['https://privacy.sexy']; // Comment out this line to use a hardcoded URL for testing.
|
||||
const uniqueUrls = Array.from(new Set(urls));
|
||||
const defaultedOptions = { ...DefaultOptions, ...options };
|
||||
console.log('Options: ', defaultedOptions);
|
||||
const results = await request(uniqueUrls, defaultedOptions);
|
||||
const defaultedDomainOptions: Required<DomainOptions> = {
|
||||
...DefaultDomainOptions,
|
||||
...options?.domainOptions,
|
||||
};
|
||||
console.log('Batch request options applied:', defaultedDomainOptions);
|
||||
const results = await request(uniqueUrls, defaultedDomainOptions, options);
|
||||
return results;
|
||||
}
|
||||
|
||||
export interface IBatchRequestOptions {
|
||||
domainOptions?: IDomainOptions;
|
||||
requestOptions?: IRequestOptions;
|
||||
export interface BatchRequestOptions {
|
||||
readonly domainOptions?: Partial<DomainOptions>;
|
||||
readonly requestOptions?: Partial<RequestOptions>;
|
||||
readonly followOptions?: Partial<FollowOptions>;
|
||||
}
|
||||
|
||||
interface IDomainOptions {
|
||||
sameDomainParallelize?: boolean;
|
||||
sameDomainDelayInMs?: number;
|
||||
interface DomainOptions {
|
||||
readonly sameDomainParallelize?: boolean;
|
||||
readonly sameDomainDelayInMs?: number;
|
||||
}
|
||||
|
||||
const DefaultOptions: Required<IBatchRequestOptions> = {
|
||||
domainOptions: {
|
||||
sameDomainParallelize: false,
|
||||
sameDomainDelayInMs: 3 /* sec */ * 1000,
|
||||
},
|
||||
requestOptions: {
|
||||
retryExponentialBaseInMs: 5 /* sec */ * 1000,
|
||||
requestTimeoutInMs: 60 /* sec */ * 1000,
|
||||
additionalHeaders: {},
|
||||
},
|
||||
const DefaultDomainOptions: Required<DomainOptions> = {
|
||||
sameDomainParallelize: false,
|
||||
sameDomainDelayInMs: 3 /* sec */ * 1000,
|
||||
};
|
||||
|
||||
function request(
|
||||
urls: string[],
|
||||
options: Required<IBatchRequestOptions>,
|
||||
): Promise<IUrlStatus[]> {
|
||||
if (!options.domainOptions.sameDomainParallelize) {
|
||||
domainOptions: Required<DomainOptions>,
|
||||
options?: BatchRequestOptions,
|
||||
): Promise<UrlStatus[]> {
|
||||
if (!domainOptions.sameDomainParallelize) {
|
||||
return runOnEachDomainWithDelay(
|
||||
urls,
|
||||
(url) => getUrlStatus(url, options.requestOptions),
|
||||
options.domainOptions.sameDomainDelayInMs,
|
||||
(url) => getUrlStatus(url, options?.requestOptions, options?.followOptions),
|
||||
domainOptions.sameDomainDelayInMs,
|
||||
);
|
||||
}
|
||||
return Promise.all(urls.map((url) => getUrlStatus(url, options.requestOptions)));
|
||||
return Promise.all(
|
||||
urls.map((url) => getUrlStatus(url, options?.requestOptions, options?.followOptions)),
|
||||
);
|
||||
}
|
||||
|
||||
async function runOnEachDomainWithDelay(
|
||||
urls: string[],
|
||||
action: (url: string) => Promise<IUrlStatus>,
|
||||
action: (url: string) => Promise<UrlStatus>,
|
||||
delayInMs: number | undefined,
|
||||
): Promise<IUrlStatus[]> {
|
||||
): Promise<UrlStatus[]> {
|
||||
const grouped = groupUrlsByDomain(urls);
|
||||
const tasks = grouped.map(async (group) => {
|
||||
const results = new Array<IUrlStatus>();
|
||||
const results = new Array<UrlStatus>();
|
||||
/* eslint-disable no-await-in-loop */
|
||||
for (const url of group) {
|
||||
const status = await action(url);
|
||||
|
||||
@@ -1,27 +1,33 @@
|
||||
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
|
||||
import type { IUrlStatus } from './IUrlStatus';
|
||||
import { indentText } from '@tests/shared/Text';
|
||||
import { type UrlStatus, formatUrlStatus } from './UrlStatus';
|
||||
|
||||
const DefaultBaseRetryIntervalInMs = 5 /* sec */ * 1000;
|
||||
|
||||
export async function retryWithExponentialBackOff(
|
||||
action: () => Promise<IUrlStatus>,
|
||||
action: () => Promise<UrlStatus>,
|
||||
baseRetryIntervalInMs: number = DefaultBaseRetryIntervalInMs,
|
||||
currentRetry = 1,
|
||||
): Promise<IUrlStatus> {
|
||||
): Promise<UrlStatus> {
|
||||
const maxTries = 3;
|
||||
const status = await action();
|
||||
if (shouldRetry(status)) {
|
||||
if (currentRetry <= maxTries) {
|
||||
const exponentialBackOffInMs = getRetryTimeoutInMs(currentRetry, baseRetryIntervalInMs);
|
||||
console.log(`Retrying (${currentRetry}) in ${exponentialBackOffInMs / 1000} seconds`, status);
|
||||
console.log([
|
||||
`Attempt ${currentRetry}: Retrying in ${exponentialBackOffInMs / 1000} seconds.`,
|
||||
'Details:',
|
||||
indentText(formatUrlStatus(status)),
|
||||
].join('\n'));
|
||||
await sleep(exponentialBackOffInMs);
|
||||
return retryWithExponentialBackOff(action, baseRetryIntervalInMs, currentRetry + 1);
|
||||
}
|
||||
console.warn('💀 All retry attempts failed. Final failure to retrieve URL:', indentText(formatUrlStatus(status)));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
function shouldRetry(status: IUrlStatus) {
|
||||
function shouldRetry(status: UrlStatus): boolean {
|
||||
if (status.error) {
|
||||
return true;
|
||||
}
|
||||
@@ -32,14 +38,14 @@ function shouldRetry(status: IUrlStatus) {
|
||||
|| status.code === 429; // Too Many Requests
|
||||
}
|
||||
|
||||
function isTransientError(statusCode: number) {
|
||||
function isTransientError(statusCode: number): boolean {
|
||||
return statusCode >= 500 && statusCode <= 599;
|
||||
}
|
||||
|
||||
function getRetryTimeoutInMs(
|
||||
currentRetry: number,
|
||||
baseRetryIntervalInMs: number = DefaultBaseRetryIntervalInMs,
|
||||
) {
|
||||
): number {
|
||||
const retryRandomFactor = 0.5; // Retry intervals are between 50% and 150%
|
||||
// of the exponentially increasing base amount
|
||||
const minRandom = 1 - retryRandomFactor;
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { indentText } from '@tests/shared/Text';
|
||||
import { fetchWithTimeout } from './FetchWithTimeout';
|
||||
import { getDomainFromUrl } from './UrlDomainProcessing';
|
||||
|
||||
export function fetchFollow(
|
||||
url: string,
|
||||
timeoutInMs: number,
|
||||
fetchOptions: RequestInit,
|
||||
followOptions: IFollowOptions | undefined,
|
||||
fetchOptions?: Partial<RequestInit>,
|
||||
followOptions?: Partial<FollowOptions>,
|
||||
): Promise<Response> {
|
||||
const defaultedFollowOptions = {
|
||||
const defaultedFollowOptions: Required<FollowOptions> = {
|
||||
...DefaultFollowOptions,
|
||||
...followOptions,
|
||||
};
|
||||
if (followRedirects(defaultedFollowOptions)) {
|
||||
console.log(indentText(`Follow options: ${JSON.stringify(defaultedFollowOptions)}`));
|
||||
if (!followRedirects(defaultedFollowOptions)) {
|
||||
return fetchWithTimeout(url, timeoutInMs, fetchOptions);
|
||||
}
|
||||
fetchOptions = { ...fetchOptions, redirect: 'manual' /* handled manually */ };
|
||||
fetchOptions = { ...fetchOptions, redirect: 'manual' /* handled manually */, mode: 'cors' };
|
||||
const cookies = new CookieStorage(defaultedFollowOptions.enableCookies);
|
||||
return followRecursivelyWithCookies(
|
||||
url,
|
||||
@@ -24,13 +27,13 @@ export function fetchFollow(
|
||||
);
|
||||
}
|
||||
|
||||
export interface IFollowOptions {
|
||||
followRedirects?: boolean;
|
||||
maximumRedirectFollowDepth?: number;
|
||||
enableCookies?: boolean;
|
||||
export interface FollowOptions {
|
||||
readonly followRedirects?: boolean;
|
||||
readonly maximumRedirectFollowDepth?: number;
|
||||
readonly enableCookies?: boolean;
|
||||
}
|
||||
|
||||
export const DefaultFollowOptions: Required<IFollowOptions> = {
|
||||
const DefaultFollowOptions: Required<FollowOptions> = {
|
||||
followRedirects: true,
|
||||
maximumRedirectFollowDepth: 20,
|
||||
enableCookies: true,
|
||||
@@ -64,6 +67,10 @@ async function followRecursivelyWithCookies(
|
||||
if (cookieHeader) {
|
||||
cookies.addHeader(cookieHeader);
|
||||
}
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
Host: getDomainFromUrl(nextUrl),
|
||||
};
|
||||
return followRecursivelyWithCookies(nextUrl, timeoutInMs, options, newFollowDepth, cookies);
|
||||
}
|
||||
|
||||
@@ -77,7 +84,7 @@ class CookieStorage {
|
||||
constructor(private readonly enabled: boolean) {
|
||||
}
|
||||
|
||||
public hasAny() {
|
||||
public hasAny(): boolean {
|
||||
return this.enabled && this.cookies.length > 0;
|
||||
}
|
||||
|
||||
@@ -88,17 +95,17 @@ class CookieStorage {
|
||||
this.cookies.push(header);
|
||||
}
|
||||
|
||||
public getHeader() {
|
||||
public getHeader(): string {
|
||||
return this.cookies.join(' ; ');
|
||||
}
|
||||
}
|
||||
|
||||
function followRedirects(options: IFollowOptions) {
|
||||
if (!options.followRedirects) {
|
||||
function followRedirects(options: FollowOptions): boolean {
|
||||
if (options.followRedirects !== true) {
|
||||
return false;
|
||||
}
|
||||
if (options.maximumRedirectFollowDepth === 0) {
|
||||
return false;
|
||||
if (options.maximumRedirectFollowDepth === undefined || options.maximumRedirectFollowDepth <= 0) {
|
||||
throw new Error('Invalid followRedirects configuration: maximumRedirectFollowDepth must be a positive integer');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ export async function fetchWithTimeout(
|
||||
url: string,
|
||||
timeoutInMs: number,
|
||||
init?: RequestInit,
|
||||
): Promise<Response> {
|
||||
const controller = new AbortController();
|
||||
): ReturnType<typeof fetch> {
|
||||
const options: RequestInit = {
|
||||
...(init ?? {}),
|
||||
signal: controller.signal,
|
||||
signal: AbortSignal.timeout(timeoutInMs),
|
||||
};
|
||||
const promise = fetch(url, options);
|
||||
const timeout = setTimeout(() => controller.abort(), timeoutInMs);
|
||||
return promise.finally(() => clearTimeout(timeout));
|
||||
return fetch(
|
||||
url,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export interface IUrlStatus {
|
||||
url: string;
|
||||
error?: string;
|
||||
code?: number;
|
||||
}
|
||||
@@ -13,7 +13,10 @@ A CLI and SDK for checking the availability of external URLs.
|
||||
- 😇 **Rate Limiting**: Queues requests by domain to be polite.
|
||||
- 🔁 **Retries**: Implements retry pattern with exponential back-off.
|
||||
- ⌚ **Timeouts**: Configurable timeout for each request.
|
||||
- 🎭️ **User-Agent Rotation**: Change user agents for each request.
|
||||
- 🎭️ **Impersonation**: Impersonate different browsers for each request.
|
||||
- **🌐 User-Agent Rotation**: Change user agents.
|
||||
- **🔑 TLS Handshakes**: Perform TLS and HTTP handshakes that are identical to that of a real browser.
|
||||
- 🫙 **Cookie jar**: Preserve cookies during redirects to mimic real browser.
|
||||
|
||||
## CLI
|
||||
|
||||
@@ -54,6 +57,7 @@ const statuses = await getUrlStatusesInParallel([ 'https://privacy.sexy', /* ...
|
||||
- **`sameDomainDelayInMs`** (*number*), default: `3000` (3 seconds)
|
||||
- Sets the delay between requests to the same domain.
|
||||
- `requestOptions` (*object*): See [request options](#request-options).
|
||||
- `followOptions` (*object*): See [follow options](#follow-options).
|
||||
|
||||
### `getUrlStatus`
|
||||
|
||||
@@ -72,7 +76,6 @@ console.log(`Status code: ${status.code}`);
|
||||
- The longer the base time, the greater the intervals between retries.
|
||||
- **`additionalHeaders`** (*object*), default: `false`
|
||||
- Additional HTTP headers to send along with the default headers. Overrides default headers if specified.
|
||||
- **`followOptions`** (*object*): See [follow options](#follow-options).
|
||||
- **`requestTimeoutInMs`** (*number*), default: `60000` (60 seconds)
|
||||
- Time limit to abort the request if no response is received within the specified time frame.
|
||||
|
||||
@@ -83,19 +86,7 @@ Follows `3XX` redirects while preserving cookies.
|
||||
Same fetch API except third parameter that specifies [follow options](#follow-options), `redirect: 'follow' | 'manual' | 'error'` is discarded in favor of the third parameter.
|
||||
|
||||
```js
|
||||
const status = await fetchFollow('https://privacy.sexy', {
|
||||
// First argument is same options as fetch API, except `redirect` options
|
||||
// that's discarded in favor of next argument follow options
|
||||
headers: {
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0'
|
||||
},
|
||||
}, {
|
||||
// Second argument sets the redirect behavior
|
||||
followRedirects: true,
|
||||
maximumRedirectFollowDepth: 20,
|
||||
enableCookies: true,
|
||||
}
|
||||
);
|
||||
const status = await fetchFollow('https://privacy.sexy', 1000 /* timeout in milliseconds */);
|
||||
console.log(`Status code: ${status.code}`);
|
||||
```
|
||||
|
||||
@@ -109,3 +100,10 @@ console.log(`Status code: ${status.code}`);
|
||||
- **`enableCookies`** (*boolean*), default: `true`
|
||||
- Enables cookie storage to facilitate seamless navigation through login or other authentication challenges.
|
||||
- 💡 Helps to over-come sign-in challenges with callbacks.
|
||||
- **`forceHttpGetForUrlPatterns`** (*array*), default: `[]`
|
||||
- Specifies URL patterns that should always use an HTTP GET request instead of the default HTTP HEAD.
|
||||
- This is useful for websites that do not respond to HEAD requests, such as those behind certain CDN or web application firewalls.
|
||||
- Provide patterns as regular expressions (`RegExp`), allowing them to match any part of a URL.
|
||||
- Examples:
|
||||
- To match any URL starting with "https://example.com/api": `/^https:\/\/example\.com\/api/`
|
||||
- To match any domain ending with "cloudflare.com": `/^https:\/\/.*\.cloudflare\.com\//`
|
||||
|
||||
@@ -1,70 +1,123 @@
|
||||
import { indentText } from '@tests/shared/Text';
|
||||
import { retryWithExponentialBackOff } from './ExponentialBackOffRetryHandler';
|
||||
import { fetchFollow, type IFollowOptions, DefaultFollowOptions } from './FetchFollow';
|
||||
import { fetchFollow, type FollowOptions } from './FetchFollow';
|
||||
import { getRandomUserAgent } from './UserAgents';
|
||||
import type { IUrlStatus } from './IUrlStatus';
|
||||
import { getDomainFromUrl } from './UrlDomainProcessing';
|
||||
import { randomizeTlsFingerprint, getTlsContextInfo } from './TlsFingerprintRandomizer';
|
||||
import type { UrlStatus } from './UrlStatus';
|
||||
|
||||
export function getUrlStatus(
|
||||
url: string,
|
||||
options: IRequestOptions = DefaultOptions,
|
||||
): Promise<IUrlStatus> {
|
||||
const defaultedOptions = { ...DefaultOptions, ...options };
|
||||
const fetchOptions = getFetchOptions(url, defaultedOptions);
|
||||
return retryWithExponentialBackOff(async () => {
|
||||
console.log('Requesting', url);
|
||||
let result: IUrlStatus;
|
||||
try {
|
||||
const response = await fetchFollow(
|
||||
url,
|
||||
defaultedOptions.requestTimeoutInMs,
|
||||
fetchOptions,
|
||||
defaultedOptions.followOptions,
|
||||
);
|
||||
result = { url, code: response.status };
|
||||
} catch (err) {
|
||||
result = { url, error: JSON.stringify(err, null, '\t') };
|
||||
}
|
||||
return result;
|
||||
}, defaultedOptions.retryExponentialBaseInMs);
|
||||
requestOptions?: Partial<RequestOptions>,
|
||||
followOptions?: Partial<FollowOptions>,
|
||||
): Promise<UrlStatus> {
|
||||
const defaultedOptions = getDefaultedRequestOptions(requestOptions);
|
||||
if (defaultedOptions.randomizeTlsFingerprint) {
|
||||
randomizeTlsFingerprint();
|
||||
}
|
||||
return fetchUrlStatusWithRetry(url, defaultedOptions, followOptions);
|
||||
}
|
||||
|
||||
export interface IRequestOptions {
|
||||
export interface RequestOptions {
|
||||
readonly retryExponentialBaseInMs?: number;
|
||||
readonly additionalHeaders?: Record<string, string>;
|
||||
readonly additionalHeadersUrlIgnore?: string[];
|
||||
readonly followOptions?: IFollowOptions;
|
||||
readonly requestTimeoutInMs: number;
|
||||
readonly randomizeTlsFingerprint: boolean;
|
||||
readonly forceHttpGetForUrlPatterns: RegExp[];
|
||||
}
|
||||
|
||||
const DefaultOptions: Required<IRequestOptions> = {
|
||||
retryExponentialBaseInMs: 5000,
|
||||
const DefaultOptions: Required<RequestOptions> = {
|
||||
retryExponentialBaseInMs: 5 /* sec */ * 1000,
|
||||
additionalHeaders: {},
|
||||
additionalHeadersUrlIgnore: [],
|
||||
requestTimeoutInMs: 60 /* seconds */ * 1000,
|
||||
followOptions: DefaultFollowOptions,
|
||||
randomizeTlsFingerprint: true,
|
||||
forceHttpGetForUrlPatterns: [],
|
||||
};
|
||||
|
||||
function getFetchOptions(url: string, options: Required<IRequestOptions>): RequestInit {
|
||||
function fetchUrlStatusWithRetry(
|
||||
url: string,
|
||||
requestOptions: Required<RequestOptions>,
|
||||
followOptions?: Partial<FollowOptions>,
|
||||
): Promise<UrlStatus> {
|
||||
const fetchOptions = getFetchOptions(url, requestOptions);
|
||||
return retryWithExponentialBackOff(async () => {
|
||||
console.log(`🚀 Initiating request for URL: ${url}`);
|
||||
console.log(indentText([
|
||||
`HTTP method: ${fetchOptions.method}`,
|
||||
`Request options: ${JSON.stringify(requestOptions)}`,
|
||||
].join('\n')));
|
||||
let result: UrlStatus;
|
||||
try {
|
||||
const response = await fetchFollow(
|
||||
url,
|
||||
requestOptions.requestTimeoutInMs,
|
||||
fetchOptions,
|
||||
followOptions,
|
||||
);
|
||||
result = { url, code: response.status };
|
||||
} catch (err) {
|
||||
result = {
|
||||
url,
|
||||
error: [
|
||||
'Error:', indentText(JSON.stringify(err, null, '\t') || err.toString()),
|
||||
'Fetch options:', indentText(JSON.stringify(fetchOptions, null, '\t')),
|
||||
'Request options:', indentText(JSON.stringify(requestOptions, null, '\t')),
|
||||
'TLS:', indentText(getTlsContextInfo()),
|
||||
].join('\n'),
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}, requestOptions.retryExponentialBaseInMs);
|
||||
}
|
||||
|
||||
function getFetchOptions(url: string, options: Required<RequestOptions>): RequestInit {
|
||||
const additionalHeaders = options.additionalHeadersUrlIgnore
|
||||
.some((ignorePattern) => url.startsWith(ignorePattern))
|
||||
? {}
|
||||
: options.additionalHeaders;
|
||||
return {
|
||||
method: 'HEAD',
|
||||
method: getHttpMethod(url, options),
|
||||
headers: {
|
||||
...getDefaultHeaders(),
|
||||
...getDefaultHeaders(url),
|
||||
...additionalHeaders,
|
||||
},
|
||||
redirect: 'manual', // Redirects are handled manually, automatic redirects do not work with Host header
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultHeaders(): Record<string, string> {
|
||||
function getHttpMethod(url: string, options: Required<RequestOptions>): 'HEAD' | 'GET' {
|
||||
if (options.forceHttpGetForUrlPatterns.some((pattern) => url.match(pattern))) {
|
||||
return 'GET';
|
||||
}
|
||||
// By default fetch only headers without the full response body for better speed
|
||||
return 'HEAD';
|
||||
}
|
||||
|
||||
function getDefaultHeaders(url: string): Record<string, string> {
|
||||
return {
|
||||
'user-agent': getRandomUserAgent(),
|
||||
'upgrade-insecure-requests': '1',
|
||||
connection: 'keep-alive',
|
||||
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
|
||||
'accept-encoding': 'gzip, deflate, br',
|
||||
'cache-control': 'max-age=0',
|
||||
'accept-language': 'en-US,en;q=0.9',
|
||||
// Needed for websites that filter out non-browser user agents.
|
||||
'User-Agent': getRandomUserAgent(),
|
||||
|
||||
// Required for some websites, especially those behind proxies, to correctly handle the request.
|
||||
Host: getDomainFromUrl(url),
|
||||
|
||||
// The following mimic a real browser request to improve compatibility with most web servers.
|
||||
'Upgrade-Insecure-Requests': '1',
|
||||
Connection: 'keep-alive',
|
||||
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Cache-Control': 'max-age=0',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultedRequestOptions(
|
||||
options?: Partial<RequestOptions>,
|
||||
): Required<RequestOptions> {
|
||||
return {
|
||||
...DefaultOptions,
|
||||
...options,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Modifies the TLS fingerprint of Node.js HTTP client to circumvent TLS fingerprinting blocks.
|
||||
* TLS fingerprinting is a technique used to identify clients based on the unencrypted data sent
|
||||
* during the TLS handshake, used for blocking or identifying non-browser clients like debugging
|
||||
* proxies or automated scripts.
|
||||
*
|
||||
* However, Node.js's HTTP client does not fully support all methods required for impersonating a
|
||||
* browser's TLS fingerprint, as reported in https://github.com/nodejs/undici/issues/1983.
|
||||
* While this implementation can alter the TLS fingerprint by randomizing the cipher suite order,
|
||||
* it may not perfectly mimic specific browser fingerprints due to limitations in the TLS
|
||||
* implementation of Node.js.
|
||||
*
|
||||
* For more detailed information, visit:
|
||||
* - https://archive.today/2024.03.13-102042/https://httptoolkit.com/blog/tls-fingerprinting-node-js/
|
||||
* - https://check.ja3.zone/ (To check your tool's or browser's fingerprint)
|
||||
* - https://github.com/lwthiker/curl-impersonate (A solution for curl)
|
||||
* - https://github.com/depicts/got-tls (Cipher manipulation support for Node.js)
|
||||
*/
|
||||
|
||||
import { constants } from 'crypto';
|
||||
import tls from 'tls';
|
||||
import { indentText } from '@tests/shared/Text';
|
||||
|
||||
export function randomizeTlsFingerprint() {
|
||||
tls.DEFAULT_CIPHERS = getShuffledCiphers().join(':');
|
||||
console.log(indentText(
|
||||
`TLS context:\n${indentText([
|
||||
'Original ciphers:', indentText(constants.defaultCipherList),
|
||||
'Current ciphers:', indentText(getTlsContextInfo()),
|
||||
].join('\n'))}`,
|
||||
));
|
||||
}
|
||||
|
||||
export function getTlsContextInfo(): string {
|
||||
return [
|
||||
`Ciphers: ${tls.DEFAULT_CIPHERS}`,
|
||||
`Minimum TLS protocol version: ${tls.DEFAULT_MIN_VERSION}`,
|
||||
`Node fingerprint: ${constants.defaultCoreCipherList === tls.DEFAULT_CIPHERS ? 'Visible' : 'Masked'}`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffles the order of TLS ciphers, excluding the top 3 most important ciphers to maintain
|
||||
* security preferences. This approach modifies the default cipher list of Node.js to create a
|
||||
* unique TLS fingerprint, thus helping to circumvent detection mechanisms based on static
|
||||
* fingerprinting. It leverages randomness in the cipher order as a simple method to generate a
|
||||
* new, unique TLS fingerprint which is not easily identifiable. The technique is based on altering
|
||||
* parameters used in the TLS handshake process, particularly the cipher suite order, to avoid
|
||||
* matching known fingerprints that could identify the client as a Node.js application.
|
||||
*
|
||||
* For more details, refer to:
|
||||
* - https://archive.today/2024.03.13-102234/https://getsetfetch.org/blog/tls-fingerprint.html
|
||||
*/
|
||||
export function getShuffledCiphers(): readonly string[] {
|
||||
const nodeOrderedCipherList = constants.defaultCoreCipherList.split(':');
|
||||
const totalTopCiphersToKeep = 3;
|
||||
// Keep the most important ciphers in the same order
|
||||
const fixedCiphers = nodeOrderedCipherList.slice(0, totalTopCiphersToKeep);
|
||||
// Shuffle the rest
|
||||
const shuffledCiphers = nodeOrderedCipherList.slice(totalTopCiphersToKeep)
|
||||
.map((cipher) => ({ cipher, sort: Math.random() }))
|
||||
.sort((a, b) => a.sort - b.sort)
|
||||
.map(({ cipher }) => cipher);
|
||||
const ciphers = [
|
||||
...fixedCiphers,
|
||||
...shuffledCiphers,
|
||||
];
|
||||
return ciphers;
|
||||
}
|
||||
@@ -2,18 +2,18 @@ export function groupUrlsByDomain(urls: string[]): string[][] {
|
||||
const domains = new Set<string>();
|
||||
const urlsWithDomain = urls.map((url) => ({
|
||||
url,
|
||||
domain: extractDomain(url),
|
||||
domain: getDomainFromUrl(url),
|
||||
}));
|
||||
for (const url of urlsWithDomain) {
|
||||
domains.add(url.domain);
|
||||
}
|
||||
return Array.from(domains).map((domain) => {
|
||||
return urlsWithDomain
|
||||
.filter((url) => url.domain === domain)
|
||||
.filter((url) => url.domain.toLowerCase() === domain.toLowerCase())
|
||||
.map((url) => url.url);
|
||||
});
|
||||
}
|
||||
|
||||
function extractDomain(url: string): string {
|
||||
return url.split('://')[1].split('/')[0].toLowerCase();
|
||||
export function getDomainFromUrl(url: string): string {
|
||||
return new URL(url).host;
|
||||
}
|
||||
19
tests/checks/external-urls/StatusChecker/UrlStatus.ts
Normal file
19
tests/checks/external-urls/StatusChecker/UrlStatus.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { indentText } from '@tests/shared/Text';
|
||||
|
||||
export interface UrlStatus {
|
||||
readonly url: string;
|
||||
readonly error?: string;
|
||||
readonly code?: number;
|
||||
}
|
||||
|
||||
export function formatUrlStatus(status: UrlStatus): string {
|
||||
return [
|
||||
`URL: ${status.url}`,
|
||||
...status.code !== undefined ? [
|
||||
`Response code: ${status.code}`,
|
||||
] : [],
|
||||
...status.error ? [
|
||||
`Error:\n${indentText(status.error)}`,
|
||||
] : [],
|
||||
].join('\n');
|
||||
}
|
||||
@@ -3,73 +3,28 @@ export function getRandomUserAgent(): string {
|
||||
}
|
||||
|
||||
const UserAgents = [
|
||||
// Chrome
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537',
|
||||
|
||||
// Firefox
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Safari/605.1.15',
|
||||
|
||||
// Safari
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/604.1',
|
||||
|
||||
// Internet Explorer
|
||||
'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko',
|
||||
|
||||
// Edge
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3 Edge/15.0',
|
||||
|
||||
// Opera
|
||||
'Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14',
|
||||
|
||||
// iOS Devices
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/18.2b11866 Mobile/16B91 Safari/605.1.15',
|
||||
'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
|
||||
|
||||
// Android Devices
|
||||
'Mozilla/5.0 (Linux; Android 7.0; SM-G930V Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.125 Mobile Safari/537.3',
|
||||
|
||||
// Other Devices/Browsers
|
||||
'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.3',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15',
|
||||
'Mozilla/5.0 (Windows Phone 10.0; Android 6.0.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Mobile Safari/537.3 Edge/15.0',
|
||||
'Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko',
|
||||
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:54.0) Gecko/20100101 Firefox/54.0',
|
||||
'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0',
|
||||
'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.3',
|
||||
'Mozilla/5.0 (Linux; Android 7.0; SM-G930F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.3',
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.3',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.2 Safari/605.1.15',
|
||||
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1',
|
||||
'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.3 OPR/53.0.2907.99',
|
||||
'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2)',
|
||||
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:46.0) Gecko/20120121 Firefox/46.0',
|
||||
'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; Tablet PC 2.0)',
|
||||
'Mozilla/5.0 (Windows NT 5.1; rv:36.0) Gecko/20100101 Firefox/36.0',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0',
|
||||
'Mozilla/5.0 (X11; Linux i686; rv:30.0) Gecko/20100101 Firefox/30.0',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:28.0) Gecko/20100101 Firefox/28.0',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.3',
|
||||
'Mozilla/5.0 (Windows NT 6.1; rv:27.3) Gecko/20130101 Firefox/27.3',
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.3',
|
||||
'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.3',
|
||||
'Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0) Gecko/20161202 Firefox/21.0.1',
|
||||
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:20.0) Gecko/20100101 Firefox/20.0',
|
||||
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20130401 Firefox/31.0',
|
||||
'Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0',
|
||||
'Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0',
|
||||
'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.3',
|
||||
'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.3',
|
||||
'Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.3',
|
||||
'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.3',
|
||||
'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.3',
|
||||
'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.3',
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.517 Safari/537.3',
|
||||
'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.3',
|
||||
'Mozilla/5.0 (X11; CrOS x86_64 4319.74.0) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.3',
|
||||
'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.3',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.3',
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.3',
|
||||
'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.3',
|
||||
// Safari 17.1 - macOS and iPad
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15',
|
||||
// Safari - iOS 17 - iPhone
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
|
||||
// Safari - iOS 17 - iPad mini
|
||||
'Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
|
||||
// Edge - macOS
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.51',
|
||||
// Edge - Windows
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.58',
|
||||
// Edge - Android
|
||||
'Mozilla/5.0 (Linux; Android 10; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.43 Mobile Safari/537.36 EdgA/119.0.2151.92',
|
||||
// Chrome - macOS
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
|
||||
// Chrome - Windows
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
|
||||
// Chrome - Android (Phone)
|
||||
'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36',
|
||||
// Firefox - macOS
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/114.0',
|
||||
// Firefox - Windows
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0,',
|
||||
// Firefox - Android (Phone)
|
||||
'Mozilla/5.0 (Android 14; Mobile; rv:109.0) Gecko/120.0 Firefox/120.0',
|
||||
];
|
||||
|
||||
@@ -1,50 +1,84 @@
|
||||
import { test, expect } from 'vitest';
|
||||
import { parseApplication } from '@/application/Parser/ApplicationParser';
|
||||
import type { IApplication } from '@/domain/IApplication';
|
||||
import { getUrlStatusesInParallel, type IBatchRequestOptions } from './StatusChecker/BatchStatusChecker';
|
||||
import type { IUrlStatus } from './StatusChecker/IUrlStatus';
|
||||
import { indentText } from '@tests/shared/Text';
|
||||
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
||||
import { type UrlStatus, formatUrlStatus } from './StatusChecker/UrlStatus';
|
||||
import { getUrlStatusesInParallel, type BatchRequestOptions } from './StatusChecker/BatchStatusChecker';
|
||||
|
||||
// arrange
|
||||
const app = parseApplication();
|
||||
const urls = collectUniqueUrls(app);
|
||||
const requestOptions: IBatchRequestOptions = {
|
||||
const urls = collectUniqueUrls({
|
||||
application: app,
|
||||
excludePatterns: [
|
||||
/^https:\/\/archive\.ph/, // Drops HEAD/GET requests via fetch/curl, responding to Postman/Chromium.
|
||||
],
|
||||
});
|
||||
const requestOptions: BatchRequestOptions = {
|
||||
domainOptions: {
|
||||
sameDomainParallelize: false, // be nice to our external servers
|
||||
sameDomainParallelize: false, // be nice to our third-party servers
|
||||
sameDomainDelayInMs: 5 /* sec */ * 1000,
|
||||
},
|
||||
requestOptions: {
|
||||
retryExponentialBaseInMs: 3 /* sec */ * 1000,
|
||||
requestTimeoutInMs: 60 /* sec */ * 1000,
|
||||
additionalHeaders: { referer: app.projectDetails.homepage },
|
||||
randomizeTlsFingerprint: true,
|
||||
},
|
||||
followOptions: {
|
||||
followRedirects: true,
|
||||
enableCookies: true,
|
||||
},
|
||||
};
|
||||
const testTimeoutInMs = urls.length * 60 /* seconds */ * 1000;
|
||||
|
||||
test(`all URLs (${urls.length}) should be alive`, async () => {
|
||||
// act
|
||||
const results = await getUrlStatusesInParallel(urls, requestOptions);
|
||||
const deadUrls = results.filter((r) => r.code !== 200);
|
||||
expect(deadUrls).to.have.lengthOf(0, printUrls(deadUrls));
|
||||
// assert
|
||||
const deadUrls = results.filter((r) => r.code === undefined || !isOkStatusCode(r.code));
|
||||
expect(deadUrls).to.have.lengthOf(0, formatAssertionMessage([formatUrlStatusReport(deadUrls)]));
|
||||
}, testTimeoutInMs);
|
||||
|
||||
function collectUniqueUrls(application: IApplication): string[] {
|
||||
function isOkStatusCode(statusCode: number): boolean {
|
||||
return statusCode >= 200 && statusCode < 300;
|
||||
}
|
||||
|
||||
function collectUniqueUrls(
|
||||
options: {
|
||||
readonly application: IApplication,
|
||||
readonly excludePatterns?: readonly RegExp[],
|
||||
},
|
||||
): string[] {
|
||||
return [ // Get all nodes
|
||||
...application.collections.flatMap((c) => c.getAllCategories()),
|
||||
...application.collections.flatMap((c) => c.getAllScripts()),
|
||||
...options.application.collections.flatMap((c) => c.getAllCategories()),
|
||||
...options.application.collections.flatMap((c) => c.getAllScripts()),
|
||||
]
|
||||
// Get all docs
|
||||
.flatMap((documentable) => documentable.docs)
|
||||
// Parse all URLs
|
||||
.flatMap((docString) => docString.match(/(https?:\/\/[^\s`"<>()]+)/g) || [])
|
||||
.flatMap((docString) => extractUrls(docString))
|
||||
// Remove duplicates
|
||||
.filter((url, index, array) => array.indexOf(url) === index);
|
||||
.filter((url, index, array) => array.indexOf(url) === index)
|
||||
// Exclude certain URLs based on patterns
|
||||
.filter((url) => !shouldExcludeUrl(url, options.excludePatterns ?? []));
|
||||
}
|
||||
|
||||
function printUrls(statuses: IUrlStatus[]): string {
|
||||
/* eslint-disable prefer-template */
|
||||
return '\n'
|
||||
+ statuses.map((status) => `- ${status.url}\n`
|
||||
+ (status.code ? `\tResponse code: ${status.code}` : '')
|
||||
+ (status.error ? `\tError: ${status.error}` : ''))
|
||||
.join('\n')
|
||||
+ '\n';
|
||||
/* eslint-enable prefer-template */
|
||||
function shouldExcludeUrl(url: string, patterns: readonly RegExp[]): boolean {
|
||||
return patterns.some((pattern) => pattern.test(url));
|
||||
}
|
||||
|
||||
function formatUrlStatusReport(deadUrlStatuses: readonly UrlStatus[]): string {
|
||||
return `\n${deadUrlStatuses.map((status) => indentText(formatUrlStatus(status))).join('\n---\n')}\n`;
|
||||
}
|
||||
|
||||
function extractUrls(textWithInlineCode: string): string[] {
|
||||
/*
|
||||
Matches URLs:
|
||||
- Excludes inline code blocks as they may contain URLs not intended for user interaction
|
||||
and not guaranteed to support expected HTTP methods, leading to false-negatives.
|
||||
- Supports URLs containing parentheses, avoiding matches within code that might not represent
|
||||
actual links.
|
||||
*/
|
||||
const nonCodeBlockUrlRegex = /(?<!`)(https?:\/\/[^\s`"<>()]+(?:\([^\s`"<>()]*\))?[^\s`"<>()]*)/g;
|
||||
return textWithInlineCode.match(nonCodeBlockUrlRegex) || [];
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest';
|
||||
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
|
||||
import { FunctionCallArgumentStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentStub';
|
||||
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import type { IFunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgument';
|
||||
|
||||
describe('FunctionCallArgumentCollection', () => {
|
||||
describe('addArgument', () => {
|
||||
@@ -20,21 +21,25 @@ describe('FunctionCallArgumentCollection', () => {
|
||||
});
|
||||
});
|
||||
describe('getAllParameterNames', () => {
|
||||
it('returns as expected', () => {
|
||||
describe('returns as expected', () => {
|
||||
// arrange
|
||||
const testCases = [{
|
||||
name: 'no args',
|
||||
const testCases: ReadonlyArray<{
|
||||
readonly description: string;
|
||||
readonly args: readonly IFunctionCallArgument[];
|
||||
readonly expectedParameterNames: string[];
|
||||
}> = [{
|
||||
description: 'no args',
|
||||
args: [],
|
||||
expected: [],
|
||||
expectedParameterNames: [],
|
||||
}, {
|
||||
name: 'with some args',
|
||||
description: 'with some args',
|
||||
args: [
|
||||
new FunctionCallArgumentStub().withParameterName('a-param-name'),
|
||||
new FunctionCallArgumentStub().withParameterName('b-param-name')],
|
||||
expected: ['a-param-name', 'b-param-name'],
|
||||
expectedParameterNames: ['a-param-name', 'b-param-name'],
|
||||
}];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
it(testCase.description, () => {
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
for (const arg of testCase.args) {
|
||||
@@ -42,7 +47,7 @@ describe('FunctionCallArgumentCollection', () => {
|
||||
}
|
||||
const actual = sut.getAllParameterNames();
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
expect(actual).to.deep.equal(testCase.expectedParameterNames);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('SharedFunction', () => {
|
||||
// assert
|
||||
expect(sut.name).equal(expected);
|
||||
});
|
||||
it('throws when absent', () => {
|
||||
describe('throws when absent', () => {
|
||||
itEachAbsentStringValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing function name';
|
||||
|
||||
@@ -4,6 +4,7 @@ import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
import { provideDependencies, type VueDependencyInjectionApi } from '@/presentation/bootstrapping/DependencyProvider';
|
||||
import { ApplicationContextStub } from '@tests/unit/shared/Stubs/ApplicationContextStub';
|
||||
import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
|
||||
import type { IApplicationContext } from '@/application/Context/IApplicationContext';
|
||||
|
||||
describe('DependencyProvider', () => {
|
||||
describe('provideDependencies', () => {
|
||||
@@ -74,25 +75,25 @@ function createSingletonTests() {
|
||||
const registeredObject = api.inject(injectionKey);
|
||||
expect(registeredObject).to.be.instanceOf(Object);
|
||||
});
|
||||
it('should return the same instance for singleton dependency', () => {
|
||||
describe('should return the same instance for singleton dependency', () => {
|
||||
// arrange
|
||||
const singletonContext = new ApplicationContextStub();
|
||||
const api = new VueDependencyInjectionApiStub();
|
||||
new ProvideDependenciesBuilder()
|
||||
.withContext(singletonContext)
|
||||
.withApi(api)
|
||||
.provideDependencies();
|
||||
// act
|
||||
const getRegisteredInstance = () => api.inject(injectionKey);
|
||||
// assert
|
||||
itIsSingleton({
|
||||
getter: () => {
|
||||
// arrange
|
||||
const api = new VueDependencyInjectionApiStub();
|
||||
// act
|
||||
new ProvideDependenciesBuilder()
|
||||
.withApi(api)
|
||||
.provideDependencies();
|
||||
// expect
|
||||
const registeredObject = api.inject(injectionKey);
|
||||
return registeredObject;
|
||||
},
|
||||
getter: getRegisteredInstance,
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
class ProvideDependenciesBuilder {
|
||||
private context = new ApplicationContextStub();
|
||||
private context: IApplicationContext = new ApplicationContextStub();
|
||||
|
||||
private api: VueDependencyInjectionApi = new VueDependencyInjectionApiStub();
|
||||
|
||||
@@ -101,6 +102,11 @@ class ProvideDependenciesBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public withContext(context: IApplicationContext): this {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
public provideDependencies() {
|
||||
return provideDependencies(this.context, this.api);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { TreeInputNodeData } from '@/presentation/components/Scripts/View/T
|
||||
|
||||
describe('TreeNodeInitializerAndUpdater', () => {
|
||||
describe('updateRootNodes', () => {
|
||||
it('should throw an error if no data is provided', () => {
|
||||
describe('should throw an error if no data is provided', () => {
|
||||
itEachAbsentCollectionValue<TreeInputNodeData>((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing data';
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ScriptDiagnosticsCollectorStub } from '../../../shared/Stubs/ScriptDiag
|
||||
|
||||
describe('IpcRegistration', () => {
|
||||
describe('registerAllIpcChannels', () => {
|
||||
it('registers all defined IPC channels', () => {
|
||||
describe('registers all defined IPC channels', () => {
|
||||
Object.entries(IpcChannelDefinitions).forEach(([key, expectedChannel]) => {
|
||||
it(key, () => {
|
||||
// arrange
|
||||
|
||||
@@ -55,7 +55,7 @@ function getPathAliasesFromTsConfig(): ViteAliasDefinitions {
|
||||
}
|
||||
|
||||
function getElectronProcessSpecificModuleAliases(): ViteAliasDefinitions {
|
||||
// Workaround for scoped Electron module imports due to https://github.com/alex8088/electron-vite/issues/372
|
||||
// Workaround for Vite not being able to build tests with scoped Electron module imports.
|
||||
const electronProcessScopedModuleAliases = [
|
||||
'electron/main',
|
||||
'electron/renderer',
|
||||
|
||||
Reference in New Issue
Block a user