Compare commits

...

7 Commits

Author SHA1 Message Date
undergroundwires
0db8cc4206 Fix website not loading on Safari
It's caused by lookahead regex used in dash comment regex for inlining
PowerShell. This commit changes dash comment inlining.

- Change regex to one without lookahead.
- Add more test cases for inlining dash comment in tricky situations.
- Refactor makeInlineComment to be it's own function to easily test
  other regex options.
- Document all regex alternatives.
- Remove redundant null check (`||`) with adding safe navigation
  operator  (`?`) to allow variable before check to be null instead of
  throwing exception.
2021-11-04 18:42:44 +01:00
undergroundwires
97ddc027cb Fix dead URLs and use forks as GitHub references
Change all GitHub URLs with forks so they survive if their maintainer
decides to remove them.

Fix dead URLs in:
  - "Windows Push Notification Service" (#101)
  - "Limit CPU usage during scans to minimum"
  - "Disable NVIDIA telemetry"
2021-11-03 20:08:56 +01:00
undergroundwires
82c43ba2e3 Refactor to remove "Async" function name suffix
Remove convention where Async suffix is added to functions that returns
a Promise. It was a habit from C#, but is not widely used in JavaScript
/ TypeScript world, also bloats the code. The code is more consistent
with third party dependencies/frameworks without the suffix.
2021-11-01 19:02:22 +01:00
undergroundwires
799fb091b8 Fix failing URL status checking integration tests
Implement following redirects over `fetch` supporting cookies.
`node-fetch` does not support sending cookies during redirect. However,
this is needed to not end-up in a redirect loop for a sign-in callback.

Fix integration tests failing due to redirects and 403 errors:
  - Many redirects from `answers.microsoft.com` was throwing: throwing
    `FetchError: maximum redirect reached` error. It was caused by not
    having cookies when following redirects therefore having an infinite
    sign-in callback for the webpage.
  - Fixes integration tests failing due to additional referer header being
    sent by the application. It adds support for making exceptions to
    additional header sending through a list of regexes.

Add in-depth documentation for URL status checking.
2021-10-30 16:19:10 +01:00
undergroundwires
5ead1a087d Fix, document, unrecommend Windows browser cleanup
The main goal is to highlight and exclude scripts that clears user data
(such as Chrome bookmarks) from standard recommendation, thus allowing
more granular and intentional user selection. Because scripts that are
recommended as "standard" should be non-breaking.

Standard: Recommend only clearing data that would not be noticable by
user. E.g. caches and logs.
Strict	: Recommend clearing data that may be noticable by user, but
does not affect stored consciously data by user. E.g. cookies.
Do not recommend if data is stored consciously by user. E.g. favorites
/ bookmarks.

[General]
  - Change wording from "Clear xx traces" to "Clean xx history" to make
  it more clear and unify the naming with macOS scripts.
  - More documentation both in code and both as more references.

[Chrome]
  - Unrecommend deleting Chrome user profile.
  - Document what each chrome clean-up script is doing in more detail.

[Internet Explorer]
  - Document IE scripts better.
  - For Cookie cleanup, add solutions for later Windows version.
  - Unrecommend some from standard.
  - Remove undocumented `Local Settings\Traces` folder.
  - Take ownership before deleting Temporary Internet Files. Fixes
    permission error.
  - Remove `INetCookies\PrivacIE` script because it's undocumented and
    we already have cleanup for its parent folder (`INetCookies`).
  - Remove "%USERPROFILE%\Local Settings\Traces" due to lack of
    documentation.

[Safari]
  - Remove cleanup for undocumented traces folders `Safari\Traces`.
  - Document with subcategories and references.
  - Fix clearing all data not pointing to `localappdata`.
  - Unrecomend clearing all data.

[Opera]
  - Rename to "Clear all.." to show intent.
  - Unrecommend as it removes everything.
2021-10-28 17:43:04 +01:00
undergroundwires
64631a4552 Update dependencies
- Bump dependencies to latest.
- Remove unused inversify dependency.
- Lock sass-loader to a version that's compatible to 10. Because later
  versions (>=11) require Webpack v5 while Vue CLI v4 uses Webpack v4.
- Changes slashes as division to `math.div` as it's depreciated by SASS
  https://sass-lang.com/documentation/breaking-changes/slash
2021-10-23 20:25:03 +01:00
undergroundwires-bot
f47cb04860 ⬆️ bump everywhere to 0.11.0 2021-10-21 14:57:54 +00:00
45 changed files with 11489 additions and 3027 deletions

View File

@@ -1,5 +1,38 @@
# Changelog
## 0.11.0 (2021-10-21)
* Change "grouping" to "view" | [c0c475f](https://github.com/undergroundwires/privacy.sexy/commit/c0c475ff564b23a4dabcc03ac2909207a8eb61ce)
* Tighten parameter substitution tolerance | [dcccb61](https://github.com/undergroundwires/privacy.sexy/commit/dcccb617813625c224a28242c5b965bb4cd6f189)
* Add optionality for parameters | [6a89c62](https://github.com/undergroundwires/privacy.sexy/commit/6a89c6224bdef5eb96980471f3b3935b9351b197)
* Do not collapse cards on links and code area #88 | [e73c0ad](https://github.com/undergroundwires/privacy.sexy/commit/e73c0ad1bf922b1dd3360fc5aafc3434951fa63c)
* Add scripts to disable, hide and opt-out from Siri | [c92dc1e](https://github.com/undergroundwires/privacy.sexy/commit/c92dc1e25387c65a3a41ca64d2a23cf8131b4c86)
* Improve macOS scripts for cleaning OS logs | [6c3c2e6](https://github.com/undergroundwires/privacy.sexy/commit/6c3c2e6709ec84f8e0411f19c024bab2c7e5753b)
* Add "with" expression for templating #53 | [862914b](https://github.com/undergroundwires/privacy.sexy/commit/862914b06ea9ef74c4b58a9a4164a10a38273638)
* Add support for pipes in templates #53 | [4d7ff7e](https://github.com/undergroundwires/privacy.sexy/commit/4d7ff7edc5a96cc0d99d3c1ca4fdf9bbdace3fd2)
* Bump node environment to 15.x | [2f0321f](https://github.com/undergroundwires/privacy.sexy/commit/2f0321f315ac0da8c713dd50e37032f1de194942)
* Add new UX for optionally downloading updates | [ddf417a](https://github.com/undergroundwires/privacy.sexy/commit/ddf417a16a79551b43576befab0541ea08487969)
* Add pipes to write pretty PowerShell #53 | [5217b0b](https://github.com/undergroundwires/privacy.sexy/commit/5217b0b7587ccfe509ba8adc3a7748b9bae14d7a)
* Improve alignment, padding/margin issues on UI | [c8cb7a5](https://github.com/undergroundwires/privacy.sexy/commit/c8cb7a5c28420557319606da82f56b011e88f470)
* Support disabling per-user services in Windows #16 | [4b23907](https://github.com/undergroundwires/privacy.sexy/commit/4b2390736ac1f9de2d5176b7b07da0e827112f9a)
* Add script to remove Meet Now icon in Windows | [f39ee76](https://github.com/undergroundwires/privacy.sexy/commit/f39ee76c0cda95f54502b19d5c49390fd0f12b5e)
* Add support for more depth in function calls | [20b7d28](https://github.com/undergroundwires/privacy.sexy/commit/20b7d283b02dd751dfbde18ef1fe334c6bf76e2b)
* Increase default screen width on desktop app | [9942df1](https://github.com/undergroundwires/privacy.sexy/commit/9942df16c8334ff041fb92f432a3a29e351c88df)
* Improve disabling of SmartScreen #74 | [0696ed8](https://github.com/undergroundwires/privacy.sexy/commit/0696ed8396e298a358bec17adb91c9145dd90418)
* Remove integration tests from deployments #90 | [37ad26a](https://github.com/undergroundwires/privacy.sexy/commit/37ad26a082851c02497c36e7fce40555b9480e11)
* Use a consistent color system | [b08a6b5](https://github.com/undergroundwires/privacy.sexy/commit/b08a6b5cecf4a53023053695292146edbd24b960)
* Add semi-automatic update support for macOS | [410bcd8](https://github.com/undergroundwires/privacy.sexy/commit/410bcd82445097c29c9fcf0eabf7af9ebcb93c1e)
* Add more ways to disable and clean Defender #74 | [2492f2d](https://github.com/undergroundwires/privacy.sexy/commit/2492f2d8141b3abdf590ccad59680b1f50ecb59e)
* Add privacy over security scripts for macOS #83 | [236a0f6](https://github.com/undergroundwires/privacy.sexy/commit/236a0f6c8241294fc397194cd1b20bdeccbbb50b)
* Change PowerShell double quotes escape | [9aa8166](https://github.com/undergroundwires/privacy.sexy/commit/9aa816689146ee6cd86d8262112677c38651c6bd)
* Change theme colors | [a8031d1](https://github.com/undergroundwires/privacy.sexy/commit/a8031d18d520dd3b0567f7b8cfe2dcd694b65073)
* Improve security hardening for macOS | [e6152fa](https://github.com/undergroundwires/privacy.sexy/commit/e6152fa76f5e7d23b0f79d5dd98713daaecbff90)
* Support disabling of protected services #74 | [ab8bce7](https://github.com/undergroundwires/privacy.sexy/commit/ab8bce768650a10677f0a13b3a9fae93c83802ff)
* Fix minor issues with Defender scripts | [739287a](https://github.com/undergroundwires/privacy.sexy/commit/739287ac71b3f8b04348fc101f1fa06f2d7d86a2)
* Update screenshot | [504fa05](https://github.com/undergroundwires/privacy.sexy/commit/504fa056d7d8b17fc20afd398f9a557495fca7e8)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.10.3...0.11.0)
## 0.10.3 (2021-08-27)
* unrecommend VSS and document its breaking behavior | [7714898](https://github.com/undergroundwires/privacy.sexy/commit/77148980e08859f89c15c6604e55b56ce4f74358)

View File

@@ -16,7 +16,7 @@
- Online version at [https://privacy.sexy](https://privacy.sexy)
- 💡 No need to run any compiled software on your computer.
- Alternatively download offline version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.10.3/privacy.sexy-Setup-0.10.3.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.10.3/privacy.sexy-0.10.3.dmg) or [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.10.3/privacy.sexy-0.10.3.AppImage).
- Alternatively download offline version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.0/privacy.sexy-Setup-0.11.0.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.0/privacy.sexy-0.11.0.dmg) or [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.0/privacy.sexy-0.11.0.AppImage).
- 💡 Single click to execute your script.
- ❗ Come back regularly to apply latest version for stronger privacy and security.
@@ -57,8 +57,8 @@
- Development: `npm run serve` to compile & hot-reload for development.
- Production: `npm run build` to prepare files for distribution.
- Or run using Docker:
1. Build: `docker build -t undergroundwires/privacy.sexy:0.10.3 .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.10.3 undergroundwires/privacy.sexy:0.10.3`
1. Build: `docker build -t undergroundwires/privacy.sexy:0.11.0 .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.11.0 undergroundwires/privacy.sexy:0.11.0`
## Architecture overview

13509
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "privacy.sexy",
"version": "0.10.3",
"version": "0.11.0",
"private": true,
"description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆",
"author": "undergroundwires",
@@ -22,52 +22,53 @@
},
"main": "background.js",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-brands-svg-icons": "^5.15.3",
"@fortawesome/free-regular-svg-icons": "^5.15.3",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/vue-fontawesome": "^2.0.2",
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^2.0.6",
"@juggle/resize-observer": "^3.3.1",
"ace-builds": "^1.4.12",
"core-js": "^3.12.1",
"ace-builds": "^1.4.13",
"core-js": "^3.18.3",
"cross-fetch": "^3.1.4",
"electron-progressbar": "^2.0.1",
"file-saver": "^2.0.5",
"inversify": "^5.1.1",
"install": "^0.13.0",
"liquor-tree": "^0.2.70",
"npm": "^8.1.1",
"v-tooltip": "2.1.3",
"vue": "^2.6.12",
"vue": "^2.6.14",
"vue-class-component": "^7.2.6",
"vue-js-modal": "^2.0.0-rc.6",
"vue-js-modal": "^2.0.1",
"vue-property-decorator": "^9.1.2"
},
"devDependencies": {
"@types/ace": "0.0.45",
"@types/chai": "^4.2.18",
"@types/file-saver": "^2.0.2",
"@types/mocha": "^8.2.2",
"@vue/cli-plugin-babel": "^4.5.13",
"@vue/cli-plugin-typescript": "^4.5.13",
"@vue/cli-plugin-unit-mocha": "^4.5.13",
"@vue/cli-service": "^4.5.13",
"@vue/test-utils": "1.2.0",
"@types/ace": "0.0.47",
"@types/chai": "^4.2.22",
"@types/file-saver": "^2.0.3",
"@types/mocha": "^9.0.0",
"@vue/cli-plugin-babel": "^4.5.14",
"@vue/cli-plugin-typescript": "^4.5.14",
"@vue/cli-plugin-unit-mocha": "^4.5.14",
"@vue/cli-service": "^4.5.14",
"@vue/test-utils": "1.2.2",
"chai": "^4.3.4",
"electron": "^12.0.7",
"electron": "^15.3.0",
"electron-devtools-installer": "^3.2.0",
"electron-log": "^4.3.5",
"electron-updater": "^4.3.8",
"electron-log": "^4.4.1",
"electron-updater": "^4.3.9",
"js-yaml-loader": "^1.2.2",
"markdownlint-cli": "^0.27.1",
"remark-cli": "^9.0.0",
"markdownlint-cli": "^0.29.0",
"remark-cli": "^10.0.0",
"remark-lint-no-dead-urls": "^1.1.0",
"remark-preset-lint-consistent": "^4.0.0",
"remark-validate-links": "^10.0.4",
"sass": "^1.32.12",
"sass-loader": "^10.0.1",
"tslib": "^2.2.0",
"typescript": "^4.2.4",
"vue-cli-plugin-electron-builder": "^2.0.0-rc.6",
"vue-template-compiler": "^2.6.12",
"remark-preset-lint-consistent": "^5.1.0",
"remark-validate-links": "^11.0.1",
"sass": "^1.43.3",
"sass-loader": "10.2.0",
"tslib": "^2.3.1",
"typescript": "^4.4.4",
"vue-cli-plugin-electron-builder": "^2.1.1",
"vue-template-compiler": "^2.6.14",
"yaml-lint": "^1.2.4"
},
"homepage": "https://privacy.sexy",

View File

@@ -15,7 +15,7 @@ export class ApplicationFactory implements IApplicationFactory {
}
this.getter = new AsyncLazy<IApplication>(() => Promise.resolve(costlyGetter()));
}
public getAppAsync(): Promise<IApplication> {
return this.getter.getValueAsync();
public getApp(): Promise<IApplication> {
return this.getter.getValue();
}
}

View File

@@ -7,12 +7,12 @@ import { IEnvironment } from '../Environment/IEnvironment';
import { IApplicationFactory } from '../IApplicationFactory';
import { ApplicationFactory } from '../ApplicationFactory';
export async function buildContextAsync(
export async function buildContext(
factory: IApplicationFactory = ApplicationFactory.Current,
environment = Environment.CurrentEnvironment): Promise<IApplicationContext> {
if (!factory) { throw new Error('undefined factory'); }
if (!environment) { throw new Error('undefined environment'); }
const app = await factory.getAppAsync();
const app = await factory.getApp();
const os = getInitialOs(app, environment);
return new ApplicationContext(app, os);
}

View File

@@ -1,5 +1,5 @@
import { IApplication } from '@/domain/IApplication';
export interface IApplicationFactory {
getAppAsync(): Promise<IApplication>;
getApp(): Promise<IApplication>;
}

View File

@@ -6,7 +6,7 @@ export class InlinePowerShell implements IPipe {
if (!code || !hasLines(code)) {
return code;
}
code = replaceComments(code);
code = inlineComments(code);
code = mergeLinesWithBacktick(code);
code = mergeHereStrings(code);
const lines = getLines(code)
@@ -25,18 +25,71 @@ function hasLines(text: string) {
Line comments using "#" are replaced with inline comment syntax <# comment.. #>
Otherwise single # comments out rest of the code
*/
function replaceComments(code: string) {
return code.replaceAll(/#(?<!<#)(?![<>])(.*)$/gm, (_$, match1 ) => {
const value = match1?.trim();
function inlineComments(code: string): string {
const makeInlineComment = (comment: string) => {
const value = comment?.trim();
if (!value) {
return '<##>';
}
return `<# ${value} #>`;
};
return code.replaceAll(/<#.*?#>|#(.*)/g, (match, captureComment) => {
if (captureComment === undefined) {
return match;
}
return makeInlineComment(captureComment);
});
/*
Other alternatives considered:
--------------------------
/#(?<!<#)(?![<>])(.*)$/gm
-------------------------
✅ Simple, yet matches and captures only what's necessary
❌ Fails to match some cases
❌ `Write-Host "hi" # Comment ending line inline comment but not one #>`
❌ `Write-Host "hi" <#Comment starting like inline comment start but not one`
❌ `Write-Host "hi" #>Comment starting like inline comment end but not one`
❌ Uses lookbehind
Safari does not yet support lookbehind and syntax, leading application to not
load and throw "Invalid regular expression: invalid group specifier name"
https://caniuse.com/js-regexp-lookbehind
⏩ Usage
return code.replaceAll(/#(?<!<#)(?![<>])(.*)$/gm, (match, captureComment) => {
return makeInlineComment(captureComment)
});
----------------
/<#.*?#>|#(.*)/g
----------------
✅ Simple yet affective
❌ Matches all comments, but only captures dash comments
❌ Fails to match some cases
❌ `Write-Host "hi" # Comment ending line inline comment but not one #>`
❌ `Write-Host "hi" <#Comment starting like inline comment start but not one`
⏩ Usage
return code.replaceAll(/<#.*?#>|#(.*)/g, (match, captureComment) => {
if (captureComment === undefined) {
return match;
}
return makeInlineComment(captureComment);
});
------------------------------------
/(^(?:<#.*?#>|[^#])*)(?:(#)(.*))?/gm
------------------------------------
✅ Covers all cases
❌ Matches every line, three capture groups are used to build result
⏩ Usage
return code.replaceAll(/(^(?:<#.*?#>|[^#])*)(?:(#)(.*))?/gm,
(match, captureLeft, captureDash, captureComment) => {
if (!captureDash) {
return match;
}
return captureLeft + makeInlineComment(captureComment);
});
*/
}
function getLines(code: string) {
return (code.split(/\r\n|\r|\n/) || []);
function getLines(code: string): string [] {
return (code?.split(/\r\n|\r|\n/) || []);
}
/*
@@ -59,7 +112,7 @@ interface IInlinedHereString {
readonly escapedQuotes: string;
readonly separator: string;
}
// We handle @' and @" differently so single quotes are interpreted literally and doubles are expandable
// We handle @' and @" differently so single quotes are interpreted literally and doubles are expandable
function getHereStringHandler(quotes: string): IInlinedHereString {
const expandableNewLine = '`r`n';
switch (quotes) {

View File

@@ -72,7 +72,7 @@ actions:
name: Clear shared-cache strings data
docs:
- https://eclecticlight.co/2017/09/23/sierras-unified-log-evolves-more-persistent-and-a-valuable-log-log/
- https://github.com/libyal/dtformats/blob/main/documentation/Apple%20Unified%20Logging%20and%20Activity%20Tracing%20formats.asciidoc
- https://github.com/privacysexy-forks/dtformats/blob/main/documentation/Apple%20Unified%20Logging%20and%20Activity%20Tracing%20formats.asciidoc
code: |-
sudo rm -rfv /private/var/db/uuidtext/
sudo rm -rfv /var/db/uuidtext/
@@ -458,7 +458,7 @@ actions:
-
name: Disable Firefox telemetry
recommend: standard
docs: https://github.com/mozilla/policy-templates/blob/master/README.md
docs: https://github.com/privacysexy-forks/policy-templates/blob/master/README.md
code: |-
# Enable Firefox policies so the telemetry can be configured.
sudo defaults write /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled -bool TRUE
@@ -503,7 +503,7 @@ actions:
-
name: Disable PowerShell Core telemetry
recommend: standard
docs: https://github.com/PowerShell/PowerShell/blob/v7.1.0/README.md#telemetry
docs: https://github.com/privacysexy-forks/PowerShell/blob/v7.1.5/README.md#telemetry
call:
-
function: PersistUserEnvironmentConfiguration
@@ -576,7 +576,7 @@ actions:
name: Disable Siri voice feedback
recommend: strict
docs:
- https://github.com/joeyhoer/starter/blob/master/system/siri.sh
- https://github.com/privacysexy-forks/starter/blob/master/system/siri.sh
- 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

View File

@@ -147,32 +147,109 @@ actions:
category: Clear browser history
children:
-
name: Clear Internet Explorer traces
recommend: standard
code: |-
del /f /q "%localappdata%\Microsoft\Windows\INetCache\IE\*"
reg delete "HKCU\SOFTWARE\Microsoft\Internet Explorer\TypedURLs" /va /f
reg delete "HKCU\SOFTWARE\Microsoft\Internet Explorer\TypedURLsTime" /va /f
rd /s /q "%localappdata%\Microsoft\Internet Explorer"
rd /s /q "%APPDATA%\Microsoft\Windows\Cookies"
rd /s /q "%USERPROFILE%\Cookies"
rd /s /q "%USERPROFILE%\Local Settings\Traces"
rd /s /q "%localappdata%\Temporary Internet Files"
rd /s /q "%localappdata%\Microsoft\Windows\Temporary Internet Files"
rd /s /q "%localappdata%\Microsoft\Windows\INetCookies\PrivacIE"
rd /s /q "%localappdata%\Microsoft\Feeds Cache"
rd /s /q "%localappdata%\Microsoft\InternetExplorer\DOMStore"
category: Clear Internet Explorer history
children:
-
name: Clear Internet Explorer caches
recommend: standard
docs:
# INetCache
- https://support.microsoft.com/en-us/help/260897/how-to-delete-the-contents-of-the-temporary-internet-files-folder
- https://docs.microsoft.com/en-us/troubleshoot/browsers/apps-access-admin-web-cache
# WebCache
- https://docs.microsoft.com/en-us/troubleshoot/browsers/apps-access-admin-web-cache
code: |-
del /f /q "%localappdata%\Microsoft\Windows\INetCache\IE\*"
rd /s /q "%localappdata%\Microsoft\Windows\WebCache"
-
name: Clear Internet Explorer recent URLs
recommend: strict
docs:
- https://web.archive.org/web/20160304232740/http://crucialsecurityblog.harris.com/2011/03/14/typedurls-part-1/
- https://web.archive.org/web/20160321221849/http://crucialsecurityblog.harris.com/2011/03/23/typedurls-part-2/
- https://web.archive.org/web/20150601014235/http://randomthoughtsofforensics.blogspot.com/2012/07/trouble-with-typedurlstime.html
- http://sketchymoose.blogspot.com/2014/02/typedurls-registry-key.html
code: |-
reg delete "HKCU\SOFTWARE\Microsoft\Internet Explorer\TypedURLs" /va /f
reg delete "HKCU\SOFTWARE\Microsoft\Internet Explorer\TypedURLsTime" /va /f
-
name: Clear Temporary Internet Files (browser cache)
recommend: standard
docs:
- https://en.wikipedia.org/wiki/Temporary_Internet_Files
- https://www.windows-commandline.com/delete-temporary-internet-files/ # %localappdata%\Temporary Internet Files
- https://www.thewindowsclub.com/temporary-internet-files-folder-location # %localappdata%\Microsoft\Windows\Temporary Internet Files and INetCache
code: |-
:: Windows XP
rd /s /q %userprofile%\Local Settings\Temporary Internet Files
:: Windows 7
rd /s /q "%localappdata%\Microsoft\Windows\Temporary Internet Files"
takeown /f "%localappdata%\Temporary Internet Files" /r /d y
icacls "%localappdata%\Temporary Internet Files" /grant administrators:F /t
rd /s /q "%localappdata%\Temporary Internet Files"
:: Windows 8 and above
rd /s /q "%localappdata%\Microsoft\Windows\INetCache"
-
name: Clear Internet Explorer Feeds Cache
recommend: standard
docs: https://kb.digital-detective.net/display/BF/Location+of+Internet+Explorer+11+Data
code: rd /s /q "%localappdata%\Microsoft\Feeds Cache"
-
name: Clear Internet Explorer cookies
recommend: strict
docs:
- https://docs.microsoft.com/en-us/windows/win32/wininet/managing-cookies
- https://docs.microsoft.com/en-us/internet-explorer/kb-support/ie-edge-faqs
- https://www.thewindowsclub.com/cookies-folder-location-windows
code: |-
:: Windows 7 browsers
rd /s /q "%APPDATA%\Microsoft\Windows\Cookies"
:: Windows 8 and higher
rd /s /q "%localappdata%\Microsoft\Windows\INetCookies"
-
name: Clear Internet Explorer DOMStore
recommend: standard
docs: https://web.archive.org/web/20100416135352/http://msdn.microsoft.com/en-us/library/cc197062(VS.85).aspx
code: rd /s /q "%localappdata%\Microsoft\InternetExplorer\DOMStore"
-
name: Clear all Internet Explorer user data
docs:
- https://kb.digital-detective.net/display/BF/Location+of+Internet+Explorer+Data
- https://kb.digital-detective.net/display/BF/Location+of+Internet+Explorer+11+Data
- https://www.forensafe.com/blogs/internetexplorer.html
# Includes Internet Explorer cache, tab recovery data, persistance storage (DOMStore, indexed DB etc.)
# Folders: CacheStorage\, Tracking Protection\, Tiles\, TabRoaming\, IECompatData\
# DOMStore\, Recovery\ (that includes browser history), DomainSuggestions\,
# VersionManager\, UrlBlockManager\, Indexed DB\, imagestore\, IEFlipAheadCache\
# EUPP\, EmieUserList\, EmieSiteList\, EmieBrowserModeList\
# Files: brndlog.txt, brndlog.bak, ie4uinit-ClearIconCache.log, ie4uinit-UserConfig.log,
# MSIMGSIZ.DAT
code: rd /s /q "%localappdata%\Microsoft\Internet Explorer"
-
name: Clear Google Chrome traces
recommend: standard
code: |-
del /f /q "%localappdata%\Google\Software Reporter Tool\*.log"
rd /s /q "%USERPROFILE%\Local Settings\Application Data\Google\Chrome\User Data"
rd /s /q "%localappdata%\Google\Chrome\User Data"
rd /s /q "%localappdata%\Google\CrashReports\""
rd /s /q "%localappdata%\Google\Chrome\User Data\Crashpad\reports\""
category: Clear Google Chrome history
children:
-
name: Clear Google Chrome crash reports
recommend: standard
docs: https://www.chromium.org/developers/crash-reports
code: |-
rd /s /q "%localappdata%\Google\Chrome\User Data\Crashpad\reports\"
rd /s /q "%localappdata%\Google\CrashReports\"
-
name: Clear Software Reporter Tool logs
recommend: standard
docs: https://support.google.com/chrome/forum/AAAAP1KN0B0T8qnffV5gwM/
code: del /f /q "%localappdata%\Google\Software Reporter Tool\*.log"
-
name: Clear all Chrome user data
docs: https://chromium.googlesource.com/chromium/src/+/HEAD/docs/user_data_dir.md
code: |-
:: Windows XP
rd /s /q "%USERPROFILE%\Local Settings\Application Data\Google\Chrome\User Data"
:: Windows Vista and later
rd /s /q "%localappdata%\Google\Chrome\User Data"
-
category: Clear Firefox traces
category: Clear Firefox history
children:
-
name: Clear browsing history and caches
@@ -201,26 +278,57 @@ actions:
-
name: Clear all Firefox user profiles, settings, and data
code: |-
rd /s /q "%LOCALAPPDATA%\Mozilla\Firefox\Profiles"
rd /s /q "%localappdata%\Mozilla\Firefox\Profiles"
rd /s /q "%APPDATA%\Mozilla\Firefox\Profiles"
-
name: Clear Opera traces
recommend: standard
name: Clear all Opera data (user profiles, settings, and data)
code: |-
rd /s /q "%USERPROFILE%\AppData\Local\Opera\Opera"
rd /s /q "%APPDATA%\Opera\Opera"
:: Windows XP
rd /s /q "%USERPROFILE%\Local Settings\Application Data\Opera\Opera"
:: Windows Vista and later
rd /s /q "%localappdata%\Opera\Opera"
rd /s /q "%APPDATA%\Opera\Opera"
-
name: Clear Safari traces
recommend: standard
code: |-
rd /s /q "%USERPROFILE%\AppData\Local\Apple Computer\Safari\Traces"
rd /s /q "%APPDATA%\Apple Computer\Safari"
del /q /s /f "%USERPROFILE%\AppData\Local\Apple Computer\Safari\Cache.db"
del /q /s /f "%USERPROFILE%\AppData\Local\Apple Computer\Safari\WebpageIcons.db"
rd /s /q "%USERPROFILE%\Local Settings\Application Data\Apple Computer\Safari\Traces"
del /q /s /f "%USERPROFILE%\Local Settings\Application Data\Apple Computer\Safari\Cache.db"
del /q /s /f "%USERPROFILE%\Local Settings\Application Data\Safari\WebpageIcons.db"
category: Clear Safari history
children:
-
name: Clear Webpage Icons
recommend: standard
docs: https://www.sans.org/blog/safari-browser-forensics/
code: |-
:: Windows XP
del /q /s /f "%USERPROFILE%\Local Settings\Application Data\Safari\WebpageIcons.db"
:: Windows Vista and later
del /q /s /f "%localappdata%\Apple Computer\Safari\WebpageIcons.db"
-
name: Clear Safari cache
recommend: standard
docs: https://forensicswiki.xyz/wiki/index.php?title=Apple_Safari
code: |-
:: Windows XP
del /q /s /f "%USERPROFILE%\Local Settings\Application Data\Apple Computer\Safari\Cache.db"
:: Windows Vista and later
del /q /s /f "%localappdata%\Apple Computer\Safari\Cache.db"
-
name: Clear Safari cookies
recommend: strict
docs: https://kb.digital-detective.net/display/BF/Location+of+Safari+Data
code: |-
:: Windows XP
del /q /s /f "%USERPROFILE%\Local Settings\Application Data\Apple Computer\Safari\Cookies.db"
:: Windows Vista and later
del /q /s /f "%localappdata%\Apple Computer\Safari\Cookies.db"
-
name: Clear all Safari data (user profiles, settings, and data)
docs:
- https://kb.digital-detective.net/display/BF/Location+of+Safari+Data
- https://forensicswiki.xyz/wiki/index.php?title=Apple_Safari
- https://zerosecurity.org/2013/04/safari-forensic-tutorial
code: |-
:: Windows XP
rd /s /q "%USERPROFILE%\Local Settings\Application Data\Apple Computer\Safari"
:: Windows Vista and later
rd /s /q "%AppData%\Apple Computer\Safari"
-
category: Clear Windows logs & caches
children:
@@ -1565,8 +1673,8 @@ actions:
-
category: Disable NVIDIA telemetry
docs:
- https://github.com/CHEF-KOCH/nVidia-modded-Inf
- https://github.com/NateShoffner/Disable-Nvidia-Telemetry
- https://github.com/privacysexy-forks/nVidia-modded-Inf
- https://github.com/privacysexy-forks/Disable-Nvidia-Telemetry
- https://forum.palemoon.org/viewtopic.php?f=4&t=15686&sid=3d7982d3b9e89c713547f1a581ea44a2&start=20
children:
-
@@ -1638,7 +1746,7 @@ actions:
powerShellValue: $false
-
name: Do not run Microsoft online experiments
docs: https://github.com/Microsoft/vscode/blob/1aee0c194cff72d179b9f8ef324e47f34555a07d/src/vs/workbench/contrib/experiments/node/experimentService.ts#L173
docs: https://github.com/privacysexy-forks/vscode/blob/1aee0c194cff72d179b9f8ef324e47f34555a07d/src/vs/workbench/contrib/experiments/node/experimentService.ts#L173
recommend: standard
call:
function: SetVsCodeSetting
@@ -1941,7 +2049,7 @@ actions:
-
name: Disable Firefox metrics reporting
recommend: standard
docs: https://github.com/mozilla/policy-templates#disabletelemetry
docs: https://github.com/privacysexy-forks/policy-templates#disabletelemetry
code: reg add HKLM\SOFTWARE\Policies\Mozilla\Firefox /v DisableTelemetry /t REG_DWORD /d 1 /f
revertCode: reg add HKLM\SOFTWARE\Policies\Mozilla\Firefox /v DisableTelemetry /t REG_DWORD /d 0 /f
-
@@ -3070,7 +3178,7 @@ actions:
-
name: Limit CPU usage during scans to minimum
docs:
- https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsDefender::Scan_AvgCPULoadFact
- https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsDefender::Scan_AvgCPULoadFactor
- https://docs.microsoft.com/en-us/powershell/module/defender/set-mppreference
call:
-
@@ -4210,7 +4318,7 @@ actions:
-
name: Windows Push Notification Service
recommend: standard
docs: https://en.wikipedia.org/wiki/Windows_Push_Notification_Service#Privacy_Issue
docs: https://en.wikipedia.org/w/index.php?title=Windows_Push_Notification_Service&oldid=1012335551#Privacy_Issue
code: sc stop "WpnService" & sc config "WpnService" start=disabled
revertCode: sc config "WpnService" start=auto & sc start "WpnService"
-
@@ -4790,7 +4898,7 @@ actions:
category: Uninstall system apps
docs:
- https://docs.microsoft.com/en-us/windows/application-management/apps-in-windows-10#system-apps
- https://github.com/Sycnex/Windows10Debloater/blob/02963b6844cf7d13ed3fa64d75128f4e312689ca/Windows10Debloater.ps1#L43
- https://github.com/privacysexy-forks/Windows10Debloater/blob/d4ede6d3225e7def087b389c7e8cf6be0d5e2cd7/Windows10Debloater.ps1#L43-L47
children:
-
name: File Picker app

View File

@@ -10,7 +10,7 @@ export class CodeRunner {
private readonly node = getNodeJs(),
private readonly environment = Environment.CurrentEnvironment) {
}
public async runCodeAsync(code: string, folderName: string, fileExtension: string): Promise<void> {
public async runCode(code: string, folderName: string, fileExtension: string): Promise<void> {
const dir = this.node.path.join(this.node.os.tmpdir(), folderName);
await this.node.fs.promises.mkdir(dir, {recursive: true});
const filePath = this.node.path.join(dir, `run.${fileExtension}`);

View File

@@ -12,7 +12,7 @@ export class AsyncLazy<T> {
this.valueFactory = valueFactory;
}
public async getValueAsync(): Promise<T> {
public async getValue(): Promise<T> {
// If value is already created, return the value directly
if (this.isValueCreated) {
return Promise.resolve(this.value);

View File

@@ -1,5 +1,5 @@
export type SchedulerType = (callback: (...args: any[]) => void, ms: number) => void;
export function sleepAsync(time: number, scheduler: SchedulerType = setTimeout) {
export function sleep(time: number, scheduler: SchedulerType = setTimeout) {
return new Promise((resolve) => scheduler(() => resolve(undefined), time));
}

View File

@@ -96,7 +96,7 @@ export default class MacOsInstructions extends Vue {
public macOsDownloadUrl = '';
public async created() {
const app = await ApplicationFactory.Current.getAppAsync();
const app = await ApplicationFactory.Current.getApp();
this.appName = app.info.name;
this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS);
}

View File

@@ -3,18 +3,18 @@
<IconButton
v-if="this.canRun"
text="Run"
v-on:click="executeCodeAsync"
v-on:click="executeCode"
icon-prefix="fas" icon-name="play">
</IconButton>
<IconButton
:text="this.isDesktopVersion ? 'Save' : 'Download'"
v-on:click="saveCodeAsync"
v-on:click="saveCode"
icon-prefix="fas"
:icon-name="this.isDesktopVersion ? 'save' : 'file-download'">
</IconButton>
<IconButton
text="Copy"
v-on:click="copyCodeAsync"
v-on:click="copyCode"
icon-prefix="fas" icon-name="copy">
</IconButton>
<Dialog v-if="this.isMacOsCollection" ref="instructionsDialog">
@@ -54,20 +54,20 @@ export default class TheCodeButtons extends StatefulVue {
public isMacOsCollection = false;
public fileName = '';
public async copyCodeAsync() {
const code = await this.getCurrentCodeAsync();
public async copyCode() {
const code = await this.getCurrentCode();
Clipboard.copyText(code.current);
}
public async saveCodeAsync() {
const context = await this.getCurrentContextAsync();
public async saveCode() {
const context = await this.getCurrentContext();
saveCode(this.fileName, context.state);
if (this.isMacOsCollection) {
(this.$refs.instructionsDialog as any).show();
}
}
public async executeCodeAsync() {
const context = await this.getCurrentContextAsync();
await executeCodeAsync(context);
public async executeCode() {
const context = await this.getCurrentContext();
await executeCode(context);
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
@@ -77,8 +77,8 @@ export default class TheCodeButtons extends StatefulVue {
this.react(newState.code);
}
private async getCurrentCodeAsync(): Promise<IApplicationCode> {
const context = await this.getCurrentContextAsync();
private async getCurrentCode(): Promise<IApplicationCode> {
const context = await this.getCurrentContext();
const code = context.state.code;
return code;
}
@@ -115,9 +115,9 @@ function buildFileName(scripting: IScriptingDefinition) {
return fileName;
}
async function executeCodeAsync(context: IApplicationContext) {
async function executeCode(context: IApplicationContext) {
const runner = new CodeRunner();
await runner.runCodeAsync(
await runner.runCode(
/*code*/ context.state.code.current,
/*appName*/ context.app.info.name,
/*fileExtension*/ context.state.collection.scripting.fileExtension,

View File

@@ -51,13 +51,13 @@ export default class TheCodeArea extends StatefulVue {
const appCode = newState.code;
this.editor.setValue(appCode.current || getDefaultCode(newState.collection.scripting.language), 1);
this.events.unsubscribeAll();
this.events.register(appCode.changed.on((code) => this.updateCodeAsync(code)));
this.events.register(appCode.changed.on((code) => this.updateCode(code)));
}
private async updateCodeAsync(event: ICodeChangedEvent) {
private async updateCode(event: ICodeChangedEvent) {
this.removeCurrentHighlighting();
if (event.isEmpty()) {
const context = await this.getCurrentContextAsync();
const context = await this.getCurrentContext();
const defaultCode = getDefaultCode(context.state.collection.scripting.language);
this.editor.setValue(defaultCode, 1);
return;

View File

@@ -3,7 +3,7 @@
<MenuOptionListItem
v-for="os in this.allOses" :key="os.name"
:enabled="currentOs !== os.os"
@click="changeOsAsync(os.os)"
@click="changeOs(os.os)"
:label="os.name"
/>
</MenuOptionList>
@@ -29,12 +29,12 @@ export default class TheOsChanger extends StatefulVue {
public currentOs?: OperatingSystem = null;
public async created() {
const app = await ApplicationFactory.Current.getAppAsync();
const app = await ApplicationFactory.Current.getApp();
this.allOses = app.getSupportedOsList()
.map((os) => ({ os, name: renderOsName(os) }));
}
public async changeOsAsync(newOs: OperatingSystem) {
const context = await this.getCurrentContextAsync();
public async changeOs(newOs: OperatingSystem) {
const context = await this.getCurrentContext();
context.changeContext(newOs);
}

View File

@@ -50,10 +50,10 @@ export default class CardListItem extends StatefulVue {
public areAllChildrenSelected = false;
public async mounted() {
const context = await this.getCurrentContextAsync();
const context = await this.getCurrentContext();
this.events.register(context.state.selection.changed.on(
() => this.updateSelectionIndicatorsAsync(this.categoryId)));
await this.updateStateAsync(this.categoryId);
() => this.updateSelectionIndicators(this.categoryId)));
await this.updateState(this.categoryId);
}
@Emit('selected')
public onSelected(isExpanded: boolean) {
@@ -64,7 +64,7 @@ export default class CardListItem extends StatefulVue {
this.isExpanded = value === this.categoryId;
}
@Watch('isExpanded')
public async onExpansionChangedAsync(newValue: number, oldValue: number) {
public async onExpansionChanged(newValue: number, oldValue: number) {
if (!oldValue && newValue) {
await new Promise((r) => setTimeout(r, 400));
const focusElement = this.$refs.cardElement as HTMLElement;
@@ -72,19 +72,19 @@ export default class CardListItem extends StatefulVue {
}
}
@Watch('categoryId')
public async updateStateAsync(value: |number) {
const context = await this.getCurrentContextAsync();
public async updateState(value: |number) {
const context = await this.getCurrentContext();
const category = !value ? undefined : context.state.collection.findCategory(value);
this.cardTitle = category ? category.name : undefined;
await this.updateSelectionIndicatorsAsync(value);
await this.updateSelectionIndicators(value);
}
protected handleCollectionState(): void {
return;
}
private async updateSelectionIndicatorsAsync(categoryId: number) {
const context = await this.getCurrentContextAsync();
private async updateSelectionIndicators(categoryId: number) {
const context = await this.getCurrentContext();
const selection = context.state.selection;
const category = context.state.collection.findCategory(categoryId);
this.isAnyChildSelected = category ? selection.isAnySelected(category) : false;

View File

@@ -6,7 +6,7 @@
:selectedNodeIds="selectedNodeIds"
:filterPredicate="filterPredicate"
:filterText="filterText"
v-on:nodeSelected="toggleNodeSelectionAsync($event)"
v-on:nodeSelected="toggleNodeSelection($event)"
>
</SelectableTree>
</span>
@@ -42,8 +42,8 @@ export default class ScriptsTree extends StatefulVue {
private filtered?: IFilterResult;
public async toggleNodeSelectionAsync(event: INodeSelectedEvent) {
const context = await this.getCurrentContextAsync();
public async toggleNodeSelection(event: INodeSelectedEvent) {
const context = await this.getCurrentContext();
switch (event.node.type) {
case NodeType.Category:
toggleCategoryNodeSelection(event, context.state);
@@ -56,8 +56,8 @@ export default class ScriptsTree extends StatefulVue {
}
}
@Watch('categoryId', { immediate: true })
public async setNodesAsync(categoryId?: number) {
const context = await this.getCurrentContextAsync();
public async setNodes(categoryId?: number) {
const context = await this.getCurrentContext();
if (categoryId) {
this.nodes = parseSingleCategory(categoryId, context.state.collection);
} else {

View File

@@ -2,7 +2,7 @@
<div class="checkbox-switch" >
<input type="checkbox" class="input-checkbox"
v-model="isReverted"
@change="onRevertToggledAsync()"
@change="onRevertToggled()"
v-on:click.stop>
<div class="checkbox-animate">
<span class="checkbox-off">revert</span>
@@ -28,12 +28,12 @@ export default class RevertToggle extends StatefulVue {
private handler: IReverter;
@Watch('node', {immediate: true}) public async onNodeChangedAsync(node: INode) {
const context = await this.getCurrentContextAsync();
@Watch('node', {immediate: true}) public async onNodeChanged(node: INode) {
const context = await this.getCurrentContext();
this.handler = getReverter(node, context.state.collection);
}
public async onRevertToggledAsync() {
const context = await this.getCurrentContextAsync();
public async onRevertToggled() {
const context = await this.getCurrentContext();
this.handler.selectWithRevertState(this.isReverted, context.state.selection);
}
@@ -51,6 +51,7 @@ export default class RevertToggle extends StatefulVue {
<style scoped lang="scss">
@use 'sass:math';
@import "@/presentation/styles/colors.scss";
$color-unchecked-bullet : $color-primary-darker;
@@ -73,7 +74,7 @@ $size-height : 30px;
-webkit-border-radius: $size-height;
border-radius: $size-height;
line-height: $size-height;
font-size: $size-height / 2;
font-size: math.div($size-height, 2);
display: inline-block;
input.input-checkbox {
@@ -122,7 +123,7 @@ $size-height : 30px;
background-color: $color-checked-bg;
}
+ .checkbox-animate:before {
left: ($size-width - $size-width/3.5);
left: ($size-width - math.div($size-width, 3.5));
background-color: $color-checked-bullet;
}
+ .checkbox-animate .checkbox-off {
@@ -143,7 +144,7 @@ $size-height : 30px;
}
.checkbox-off {
margin-left: $size-width / 3;
margin-left: math.div($size-width, 3);
opacity: 1;
color: $color-unchecked-text;
}
@@ -151,7 +152,7 @@ $size-height : 30px;
.checkbox-on {
display: none;
float: right;
margin-right: $size-width / 3;
margin-right: math.div($size-width, 3);
opacity: 0;
color: $color-checked-text;
}

View File

@@ -23,7 +23,7 @@ import Node from './Node/Node.vue';
import { INode } from './Node/INode';
import { convertExistingToNode, toNewLiquorTreeNode } from './LiquorTree/NodeWrapper/NodeTranslator';
import { INodeSelectedEvent } from './INodeSelectedEvent';
import { sleepAsync } from '@/infrastructure/Threading/AsyncSleep';
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
import { getNewState } from './LiquorTree/NodeWrapper/NodeStateUpdater';
import { LiquorTreeOptions } from './LiquorTree/LiquorTreeOptions';
import { FilterPredicate, NodePredicateFilter } from './LiquorTree/NodeWrapper/NodePredicateFilter';
@@ -56,7 +56,7 @@ export default class SelectableTree extends Vue { // Keep it stateless to make i
}
@Watch('initialNodes', { immediate: true })
public async updateNodesAsync(nodes: readonly INode[]) {
public async updateNodes(nodes: readonly INode[]) {
if (!nodes) {
throw new Error('undefined initial nodes');
}
@@ -66,12 +66,12 @@ export default class SelectableTree extends Vue { // Keep it stateless to make i
(node) => node.state = updateState(node.state, node, this.selectedNodeIds));
}
this.initialLiquourTreeNodes = initialNodes;
const api = await this.getLiquorTreeApiAsync();
const api = await this.getLiquorTreeApi();
api.setModel(this.initialLiquourTreeNodes); // as liquor tree is not reactive to data after initialization
}
@Watch('filterText', { immediate: true })
public async updateFilterTextAsync(filterText: |string) {
const api = await this.getLiquorTreeApiAsync();
public async updateFilterText(filterText: |string) {
const api = await this.getLiquorTreeApi();
if (!filterText) {
api.clearFilter();
} else {
@@ -80,22 +80,22 @@ export default class SelectableTree extends Vue { // Keep it stateless to make i
}
@Watch('selectedNodeIds')
public async setSelectedStatusAsync(selectedNodeIds: ReadonlyArray<string>) {
public async setSelectedStatus(selectedNodeIds: ReadonlyArray<string>) {
if (!selectedNodeIds) {
throw new Error('SelectedrecurseDown nodes are undefined');
}
const tree = await this.getLiquorTreeApiAsync();
const tree = await this.getLiquorTreeApi();
tree.recurseDown(
(node) => node.states = updateState(node.states, node, selectedNodeIds),
);
}
private async getLiquorTreeApiAsync(): Promise<ILiquorTree> {
private async getLiquorTreeApi(): Promise<ILiquorTree> {
const accessor = (): ILiquorTree => {
const uiElement = this.$refs.treeElement;
return uiElement ? (uiElement as any).tree : undefined;
};
const treeElement = await tryUntilDefinedAsync(accessor, 5, 20); // Wait for it to render
const treeElement = await tryUntilDefined(accessor, 5, 20); // Wait for it to render
if (!treeElement) {
throw Error('Referenced tree element cannot be found. Perhaps it\'s not yet rendered?');
}
@@ -119,7 +119,7 @@ function recurseDown(
}
}
}
async function tryUntilDefinedAsync<T>(
async function tryUntilDefined<T>(
accessor: () => T | undefined,
delayInMs: number, maxTries: number): Promise<T | undefined> {
let triesLeft = maxTries;
@@ -130,7 +130,7 @@ async function tryUntilDefinedAsync<T>(
return value;
}
triesLeft--;
await sleepAsync(delayInMs);
await sleep(delayInMs);
}
return value;
}

View File

@@ -13,7 +13,7 @@
<div class="search__query__close-button">
<font-awesome-icon
:icon="['fas', 'times']"
v-on:click="clearSearchQueryAsync()"/>
v-on:click="clearSearchQuery()"/>
</div>
</div>
<div v-if="!searchHasMatches" class="search-no-matches">
@@ -65,11 +65,11 @@ export default class TheScriptsView extends StatefulVue {
public ViewType = ViewType; // Make it accessible from the view
public async created() {
const app = await ApplicationFactory.Current.getAppAsync();
const app = await ApplicationFactory.Current.getApp();
this.repositoryUrl = app.info.repositoryWebUrl;
}
public async clearSearchQueryAsync() {
const context = await this.getCurrentContextAsync();
public async clearSearchQuery() {
const context = await this.getCurrentContext();
const filter = context.state.filter;
filter.removeFilter();
}

View File

@@ -1,7 +1,7 @@
import { Component, Vue } from 'vue-property-decorator';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import { IApplicationContext } from '@/application/Context/IApplicationContext';
import { buildContextAsync } from '@/application/Context/ApplicationContextFactory';
import { buildContext } from '@/application/Context/ApplicationContextFactory';
import { IApplicationContextChangedEvent } from '@/application/Context/IApplicationContext';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { EventSubscriptionCollection } from '@/infrastructure/Events/EventSubscriptionCollection';
@@ -9,14 +9,14 @@ import { EventSubscriptionCollection } from '@/infrastructure/Events/EventSubscr
// @ts-ignore because https://github.com/vuejs/vue-class-component/issues/91
@Component
export abstract class StatefulVue extends Vue {
private static readonly instance = new AsyncLazy<IApplicationContext>(() => buildContextAsync());
private static readonly instance = new AsyncLazy<IApplicationContext>(() => buildContext());
protected readonly events = new EventSubscriptionCollection();
private readonly ownEvents = new EventSubscriptionCollection();
public async mounted() {
const context = await this.getCurrentContextAsync();
const context = await this.getCurrentContext();
this.ownEvents.register(context.contextChanged.on((event) => this.handleStateChangedEvent(event)));
this.handleCollectionState(context.state, undefined);
}
@@ -27,8 +27,8 @@ export abstract class StatefulVue extends Vue {
protected abstract handleCollectionState(
newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined): void;
protected getCurrentContextAsync(): Promise<IApplicationContext> {
return StatefulVue.instance.getValueAsync();
protected getCurrentContext(): Promise<IApplicationContext> {
return StatefulVue.instance.getValue();
}
private handleStateChangedEvent(event: IApplicationContextChangedEvent) {

View File

@@ -24,20 +24,20 @@ export default class DownloadUrlListItem extends Vue {
public hasCurrentOsDesktopVersion: boolean = false;
public async mounted() {
await this.onOperatingSystemChangedAsync(this.operatingSystem);
await this.onOperatingSystemChanged(this.operatingSystem);
}
@Watch('operatingSystem')
public async onOperatingSystemChangedAsync(os: OperatingSystem) {
public async onOperatingSystemChanged(os: OperatingSystem) {
const currentOs = Environment.CurrentEnvironment.os;
this.isCurrentOs = os === currentOs;
this.downloadUrl = await this.getDownloadUrlAsync(os);
this.downloadUrl = await this.getDownloadUrl(os);
this.operatingSystemName = getOperatingSystemName(os);
this.hasCurrentOsDesktopVersion = hasDesktopVersion(currentOs);
}
private async getDownloadUrlAsync(os: OperatingSystem): Promise<string> {
const context = await ApplicationFactory.Current.getAppAsync();
private async getDownloadUrl(os: OperatingSystem): Promise<string> {
const context = await ApplicationFactory.Current.getApp();
return context.info.getDownloadUrl(os);
}
}

View File

@@ -43,7 +43,7 @@ export default class PrivacyPolicy extends Vue {
public isDesktop = Environment.CurrentEnvironment.isDesktop;
public async created() {
const app = await ApplicationFactory.Current.getAppAsync();
const app = await ApplicationFactory.Current.getApp();
this.initialize(app);
}

View File

@@ -65,7 +65,7 @@ export default class TheFooter extends Vue {
public homepageUrl: string = '';
public async created() {
const app = await ApplicationFactory.Current.getAppAsync();
const app = await ApplicationFactory.Current.getApp();
this.initialize(app);
}

View File

@@ -15,7 +15,7 @@ export default class TheHeader extends Vue {
public subtitle = '';
public async created() {
const app = await ApplicationFactory.Current.getAppAsync();
const app = await ApplicationFactory.Current.getApp();
this.title = app.info.name;
}
}

View File

@@ -26,8 +26,8 @@ export default class TheSearchBar extends StatefulVue {
public searchQuery = '';
@Watch('searchQuery')
public async updateFilterAsync(newFilter: |string) {
const context = await this.getCurrentContextAsync();
public async updateFilter(newFilter: |string) {
const context = await this.getCurrentContext();
const filter = context.state.filter;
if (!newFilter) {
filter.removeFilter();

View File

@@ -4,8 +4,8 @@ import { ProgressInfo } from 'electron-builder';
import { UpdateProgressBar } from './UpdateProgressBar';
import log from 'electron-log';
export async function handleAutoUpdateAsync() {
if (await askDownloadAndInstallAsync() === DownloadDialogResult.NotNow) {
export async function handleAutoUpdate() {
if (await askDownloadAndInstall() === DownloadDialogResult.NotNow) {
return;
}
startHandlingUpdateProgress();
@@ -29,12 +29,12 @@ function startHandlingUpdateProgress() {
autoUpdater.on('update-downloaded', async (info: UpdateInfo) => {
log.info('@update-downloaded@\n', info);
progressBar.close();
await handleUpdateDownloadedAsync();
await handleUpdateDownloaded();
});
}
async function handleUpdateDownloadedAsync() {
if (await askRestartAndInstallAsync() === InstallDialogResult.NotNow) {
async function handleUpdateDownloaded() {
if (await askRestartAndInstall() === InstallDialogResult.NotNow) {
return;
}
setTimeout(() => autoUpdater.quitAndInstall(), 1);
@@ -44,7 +44,7 @@ enum DownloadDialogResult {
Install = 0,
NotNow = 1,
}
async function askDownloadAndInstallAsync(): Promise<DownloadDialogResult> {
async function askDownloadAndInstall(): Promise<DownloadDialogResult> {
const updateDialogResult = await dialog.showMessageBox({
type: 'question',
buttons: ['Install', 'Not now' ],
@@ -61,7 +61,7 @@ enum InstallDialogResult {
InstallAndRestart = 0,
NotNow = 1,
}
async function askRestartAndInstallAsync(): Promise<InstallDialogResult> {
async function askRestartAndInstall(): Promise<InstallDialogResult> {
const installDialogResult = await dialog.showMessageBox({
type: 'question',
buttons: ['Install and restart', 'Later'],

View File

@@ -12,8 +12,8 @@ export function requiresManualUpdate(): boolean {
return process.platform === 'darwin';
}
export async function handleManualUpdateAsync(info: UpdateInfo) {
const result = await askForVisitingWebsiteForManualUpdateAsync();
export async function handleManualUpdate(info: UpdateInfo) {
const result = await askForVisitingWebsiteForManualUpdate();
if (result === ManualDownloadDialogResult.NoAction) {
return;
}
@@ -26,7 +26,7 @@ export async function handleManualUpdateAsync(info: UpdateInfo) {
if (result === ManualDownloadDialogResult.VisitReleasesPage) {
await shell.openExternal(project.releaseUrl);
} else if (result === ManualDownloadDialogResult.UpdateNow) {
await downloadAsync(info, project);
await download(info, project);
}
}
@@ -35,7 +35,7 @@ enum ManualDownloadDialogResult {
UpdateNow = 1,
VisitReleasesPage = 2,
}
async function askForVisitingWebsiteForManualUpdateAsync(): Promise<ManualDownloadDialogResult> {
async function askForVisitingWebsiteForManualUpdate(): Promise<ManualDownloadDialogResult> {
const visitPageResult = await dialog.showMessageBox({
type: 'info',
buttons: [
@@ -54,7 +54,7 @@ async function askForVisitingWebsiteForManualUpdateAsync(): Promise<ManualDownlo
return visitPageResult.response;
}
async function downloadAsync(info: UpdateInfo, project: ProjectInformation) {
async function download(info: UpdateInfo, project: ProjectInformation) {
log.info('Downloading update manually');
const progressBar = new UpdateProgressBar();
progressBar.showIndeterminateState();
@@ -69,7 +69,7 @@ async function downloadAsync(info: UpdateInfo, project: ProjectInformation) {
await fs.promises.mkdir(parentFolder, { recursive: true });
}
const dmgFileUrl = project.getDownloadUrl(OperatingSystem.macOS);
await downloadFileWithProgressAsync(dmgFileUrl, filePath,
await downloadFileWithProgress(dmgFileUrl, filePath,
(percentage) => { progressBar.showPercentage(percentage); });
await shell.openPath(filePath);
progressBar.close();
@@ -81,7 +81,7 @@ async function downloadAsync(info: UpdateInfo, project: ProjectInformation) {
type ProgressCallback = (progress: number) => void;
async function downloadFileWithProgressAsync(
async function downloadFileWithProgress(
url: string, filePath: string, progressHandler: ProgressCallback) {
// We don't download through autoUpdater as it cannot download DMG but requires distributing ZIP
log.info(`Fetching ${url}`);
@@ -100,10 +100,10 @@ async function downloadFileWithProgressAsync(
if (!reader) {
throw new Error('No response body');
}
await streamWithProgressAsync(contentLength, reader, writer, progressHandler);
await streamWithProgress(contentLength, reader, writer, progressHandler);
}
async function streamWithProgressAsync(
async function streamWithProgress(
totalLength: number,
readStream: NodeJS.ReadableStream,
writeStream: fs.WriteStream,

View File

@@ -1,10 +1,10 @@
import { autoUpdater, UpdateInfo } from 'electron-updater';
import log from 'electron-log';
import { handleManualUpdateAsync, requiresManualUpdate } from './ManualUpdater';
import { handleAutoUpdateAsync } from './AutoUpdater';
import { handleManualUpdate, requiresManualUpdate } from './ManualUpdater';
import { handleAutoUpdate } from './AutoUpdater';
interface IUpdater {
checkForUpdatesAsync(): Promise<void>;
checkForUpdates(): Promise<void>;
}
export function setupAutoUpdater(): IUpdater {
@@ -21,20 +21,20 @@ export function setupAutoUpdater(): IUpdater {
return;
}
isAlreadyHandled = true;
await handleAvailableUpdateAsync(info);
await handleAvailableUpdate(info);
});
return {
checkForUpdatesAsync: async () => {
checkForUpdates: async () => {
// autoUpdater.emit('update-available'); // For testing
await autoUpdater.checkForUpdates();
},
};
}
async function handleAvailableUpdateAsync(info: UpdateInfo) {
async function handleAvailableUpdate(info: UpdateInfo) {
if (requiresManualUpdate()) {
await handleManualUpdateAsync(info);
await handleManualUpdate(info);
return;
}
await handleAutoUpdateAsync();
await handleAutoUpdate();
}

View File

@@ -124,7 +124,7 @@ function loadApplication(window: BrowserWindow) {
// Load the index.html when not in development
loadUrlWithNodeWorkaround(win, 'app://./index.html');
const updater = setupAutoUpdater();
updater.checkForUpdatesAsync();
updater.checkForUpdates();
}
}

View File

@@ -3,7 +3,7 @@ import { expect } from 'chai';
import { parseApplication } from '@/application/Parser/ApplicationParser';
import { IApplication } from '@/domain/IApplication';
import { IUrlStatus } from './StatusChecker/IUrlStatus';
import { getUrlStatusesInParallelAsync, IBatchRequestOptions } from './StatusChecker/BatchStatusChecker';
import { getUrlStatusesInParallel, IBatchRequestOptions } from './StatusChecker/BatchStatusChecker';
describe('collections', () => {
// arrange
@@ -17,14 +17,17 @@ describe('collections', () => {
requestOptions: {
retryExponentialBaseInMs: 3 /* sec */ * 1000,
additionalHeaders: { referer: app.info.homepage },
additionalHeadersUrlIgnore: [
'http://batcmd.com/', // Otherwise it responds with 403
],
},
};
const testTimeoutInMs = urls.length * 60000 /* 1 minute */;
it('have no dead urls', async () => {
// act
const results = await getUrlStatusesInParallelAsync(urls, options);
const results = await getUrlStatusesInParallel(urls, options);
// assert
const deadUrls = results.filter((r) => r.statusCode !== 200);
const deadUrls = results.filter((r) => r.code !== 200);
expect(deadUrls).to.have.lengthOf(0, printUrls(deadUrls));
}).timeout(testTimeoutInMs);
});
@@ -41,7 +44,7 @@ function printUrls(statuses: IUrlStatus[]): string {
return '\n' +
statuses.map((status) =>
`- ${status.url}\n` +
(status.statusCode ? `\tResponse code: ${status.statusCode}` : '') +
(status.code ? `\tResponse code: ${status.code}` : '') +
(status.error ? `\tException: ${JSON.stringify(status.error, null, '\t')}` : ''))
.join(`\n`)
+ '\n';

View File

@@ -1,15 +1,16 @@
import { sleepAsync } from '@/infrastructure/Threading/AsyncSleep';
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
import { IUrlStatus } from './IUrlStatus';
import { getUrlStatusAsync, IRequestOptions } from './Requestor';
import { getUrlStatus, IRequestOptions } from './Requestor';
import { groupUrlsByDomain } from './UrlPerDomainGrouper';
export async function getUrlStatusesInParallelAsync(
export async function getUrlStatusesInParallel(
urls: string[],
options?: IBatchRequestOptions): Promise<IUrlStatus[]> {
// urls = [ 'https://privacy.sexy' ]; // Here to comment out when testing
const uniqueUrls = Array.from(new Set(urls));
options = { ...DefaultOptions, ...options };
console.log('Options: ', options); // tslint:disable-line: no-console
const results = await requestAsync(uniqueUrls, options);
const results = await request(uniqueUrls, options);
return results;
}
@@ -34,19 +35,19 @@ const DefaultOptions: IBatchRequestOptions = {
},
};
function requestAsync(urls: string[], options: IBatchRequestOptions): Promise<IUrlStatus[]> {
function request(urls: string[], options: IBatchRequestOptions): Promise<IUrlStatus[]> {
if (!options.domainOptions.sameDomainParallelize) {
return runOnEachDomainWithDelayAsync(
return runOnEachDomainWithDelay(
urls,
(url) => getUrlStatusAsync(url, options.requestOptions),
(url) => getUrlStatus(url, options.requestOptions),
options.domainOptions.sameDomainDelayInMs);
} else {
return Promise.all(
urls.map((url) => getUrlStatusAsync(url, options.requestOptions)));
urls.map((url) => getUrlStatus(url, options.requestOptions)));
}
}
async function runOnEachDomainWithDelayAsync(
async function runOnEachDomainWithDelay(
urls: string[],
action: (url: string) => Promise<IUrlStatus>,
delayInMs: number): Promise<IUrlStatus[]> {
@@ -57,7 +58,7 @@ async function runOnEachDomainWithDelayAsync(
const status = await action(url);
results.push(status);
if (results.length !== group.length) {
await sleepAsync(delayInMs);
await sleep(delayInMs);
}
}
return results;

View File

@@ -1,9 +1,9 @@
import { sleepAsync } from '@/infrastructure/Threading/AsyncSleep';
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
import { IUrlStatus } from './IUrlStatus';
const DefaultBaseRetryIntervalInMs = 5 /* sec */ * 1000;
export async function retryWithExponentialBackOffAsync(
export async function retryWithExponentialBackOff(
action: () => Promise<IUrlStatus>,
baseRetryIntervalInMs: number = DefaultBaseRetryIntervalInMs,
currentRetry = 1): Promise<IUrlStatus> {
@@ -14,8 +14,8 @@ export async function retryWithExponentialBackOffAsync(
const exponentialBackOffInMs = getRetryTimeoutInMs(currentRetry, baseRetryIntervalInMs);
// tslint:disable-next-line: no-console
console.log(`Retrying (${currentRetry}) in ${exponentialBackOffInMs / 1000} seconds`, status);
await sleepAsync(exponentialBackOffInMs);
return retryWithExponentialBackOffAsync(action, baseRetryIntervalInMs, currentRetry + 1);
await sleep(exponentialBackOffInMs);
return retryWithExponentialBackOff(action, baseRetryIntervalInMs, currentRetry + 1);
}
}
return status;
@@ -25,8 +25,8 @@ function shouldRetry(status: IUrlStatus) {
if (status.error) {
return true;
}
return isTransientError(status.statusCode)
|| status.statusCode === 429; // Too Many Requests
return isTransientError(status.code)
|| status.code === 429; // Too Many Requests
}
function isTransientError(statusCode: number) {

View File

@@ -0,0 +1,66 @@
import fetch from 'cross-fetch';
export function fetchFollow(
url: string, fetchOptions: RequestInit, followOptions: IFollowOptions): Promise<Response> {
followOptions = { ...DefaultOptions, ...followOptions };
if (!followOptions.followRedirects
|| followOptions.maximumRedirectFollowDepth === 0) {
return fetch(url, fetchOptions);
}
fetchOptions = { ...fetchOptions, redirect: 'manual' /* handled manually */ };
const cookies = new CookieStorage(followOptions.enableCookies);
return followRecursivelyWithCookies(
url, fetchOptions, followOptions.maximumRedirectFollowDepth, cookies);
}
export interface IFollowOptions {
followRedirects?: boolean;
maximumRedirectFollowDepth?: number;
enableCookies?: boolean;
}
const DefaultOptions: IFollowOptions = {
followRedirects: true,
maximumRedirectFollowDepth: 20,
enableCookies: true,
};
async function followRecursivelyWithCookies(
url: string, options: RequestInit, followDepth: number, cookies: CookieStorage): Promise<Response> {
if (cookies.hasAny()) {
options = { ...options, headers: { ...options.headers, cookie: cookies.getHeader() } };
}
const response = await fetch(url, options);
if (!isRedirect(response.status)) {
return response;
}
if (--followDepth < 0) {
throw new Error(`[max-redirect] maximum redirect reached at: ${url}`);
}
const cookieHeader = response.headers.get('set-cookie');
cookies.addHeader(cookieHeader);
const nextUrl = response.headers.get('location');
return followRecursivelyWithCookies(nextUrl, options, followDepth, cookies);
}
function isRedirect(code: number): boolean {
return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
}
class CookieStorage {
public cookies = new Array<string>();
constructor(private readonly enabled: boolean) {
}
public hasAny() {
return this.enabled && this.cookies.length > 0;
}
public addHeader(header: string) {
if (!this.enabled || !header) {
return;
}
this.cookies.push(header);
}
public getHeader() {
return this.cookies.join(' ; ');
}
}

View File

@@ -1,5 +1,5 @@
export interface IUrlStatus {
url: string;
error?: any;
statusCode?: number;
code?: number;
}

View File

@@ -0,0 +1,108 @@
# status-checker
CLI and SDK to check whether an external URL is alive.
🧐 Why?
- 🏃🏻 Batch checking status of URLs in parallel.
- 🤖 Zero-touch start, pre-configured for reliable results, still configurable.
- 🤞 Reliable, mimics a real web browser by following redirect, and cookie storage.
🍭 Sweets such as
- 😇 Queueing requests by domain to be nice to them
- 🔁 Retry pattern with exponential back-off
## CLI
Coming soon 🚧
## Programmatic usage
Programmatic usage is supported both on Node.js and browser.
### `getUrlStatusesInParallel`
```js
// Simple example
const statuses = await getUrlStatusesInParallel([ 'https://privacy.sexy', /* ... */ ]);
if(statuses.all((r) => r.code === 200)) {
console.log('All URLs are alive!');
} else {
console.log('Dead URLs:', statuses.filter((r) => r.code !== 200).map((r) => r.url));
}
// Fastest configuration
const statuses = await getUrlStatusesInParallel([ 'https://privacy.sexy', /* ... */ ], {
domainOptions: {
sameDomainParallelize: false,
}
});
```
#### Batch request options
- `domainOptions`:
- **`sameDomainParallelize`**, (*boolean*), default: `false`
- Determines whether the requests to URLs under same domain will be parallelize.
- Setting `false` parallelizes all requests.
- Setting `true` sends requests in queue for each unique domain, still parallelizing for different domains.
- Requests to different domains are always parallelized regardless of this option.
- 💡 This helps to avoid `429 Too Many Requests` and be nice to websites
- **`sameDomainDelayInMs`** (*boolean*), default: `3000` (3 seconds)
- Sets delay between requests to same host (domain) if same domain parallelization is disabled.
- `requestOptions` (*object*): See [request options](#request-options).
### `getUrlStatus`
Checks whether single URL is dead or alive.
```js
// Simple example
const status = await getUrlStatus('https://privacy.sexy');
console.log(`Status code: ${status.code}`);
```
#### Request options
- **`retryExponentialBaseInMs`** (*boolean*), default: `5000` (5 seconds)
- The based time that's multiplied by exponential value for exponential backoff and retry calculations
- The longer it is, the longer the delay between retries are.
- **`additionalHeaders`** (*boolean*), default: `false`
- Additional headers that will be sent alongside default headers mimicking browser.
- If default header are specified, additional headers override defaults.
- **`followOptions`** (*object*): See [follow options](#follow-options).
### `fetchFollow`
Gets response from single URL by following `3XX` redirect targets by sending necessary 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,
}
);
console.log(`Status code: ${status.code}`);
```
#### Follow options
- **`followRedirects`** (*boolean*), default: `true`
- Determines whether redirects with `3XX` response code will be followed.
- **`maximumRedirectFollowDepth`** (*boolean*), default: `20`
- Determines maximum consequent redirects that will be followed.
- 💡 Helps to solve maximum redirect reached errors.
- **`enableCookies`** (*boolean*), default: `true`
- Saves cookies requested to store by webpages and sends them when redirected.
- 💡 Helps to over-come sign-in challenges with callbacks.

View File

@@ -1,37 +1,44 @@
import { retryWithExponentialBackOffAsync } from './ExponentialBackOffRetryHandler';
import { retryWithExponentialBackOff } from './ExponentialBackOffRetryHandler';
import { IUrlStatus } from './IUrlStatus';
import fetch from 'cross-fetch';
import { fetchFollow, IFollowOptions } from './FetchFollow';
export async function getUrlStatus(
url: string,
options: IRequestOptions = DefaultOptions): Promise<IUrlStatus> {
options = { ...DefaultOptions, ...options };
const fetchOptions = getFetchOptions(url, options);
return retryWithExponentialBackOff(async () => {
console.log('Requesting', url); // tslint:disable-line: no-console
let result: IUrlStatus;
try {
const response = await fetchFollow(url, fetchOptions, options.followOptions);
result = { url, code: response.status };
} catch (err) {
result = { url, error: err };
}
return result;
}, options.retryExponentialBaseInMs);
}
export interface IRequestOptions {
retryExponentialBaseInMs?: number;
additionalHeaders?: Record<string, string>;
}
export async function getUrlStatusAsync(
url: string,
options: IRequestOptions = DefaultOptions): Promise<IUrlStatus> {
options = { ...DefaultOptions, ...options };
const fetchOptions = getFetchOptions(options);
return retryWithExponentialBackOffAsync(async () => {
console.log('Requesting', url); // tslint:disable-line: no-console
try {
const response = await fetch(url, fetchOptions);
return { url, statusCode: response.status};
} catch (err) {
return { url, error: err};
}
}, options.retryExponentialBaseInMs);
additionalHeadersUrlIgnore?: string[];
followOptions?: IFollowOptions;
}
const DefaultOptions: IRequestOptions = {
retryExponentialBaseInMs: 5000,
additionalHeaders: {},
additionalHeadersUrlIgnore: [],
};
function getFetchOptions(options: IRequestOptions) {
function getFetchOptions(url: string, options: IRequestOptions): RequestInit {
const additionalHeaders = options.additionalHeadersUrlIgnore.some(
(ignorePattern) => url.match(ignorePattern)) ? {} : options.additionalHeaders;
return {
method: 'GET',
headers: { ...DefaultHeaders, ...options.additionalHeaders },
headers: { ...DefaultHeaders, ...additionalHeaders },
};
}

View File

@@ -15,7 +15,7 @@ describe('ApplicationFactory', () => {
expect(act).to.throw(expectedError);
});
});
describe('getAppAsync', () => {
describe('getApp', () => {
it('returns result from the getter', async () => {
// arrange
const expected = new ApplicationStub();
@@ -23,10 +23,10 @@ describe('ApplicationFactory', () => {
const sut = new SystemUnderTest(getter);
// act
const actual = await Promise.all( [
sut.getAppAsync(),
sut.getAppAsync(),
sut.getAppAsync(),
sut.getAppAsync(),
sut.getApp(),
sut.getApp(),
sut.getApp(),
sut.getApp(),
]);
// assert
expect(actual.every((value) => value === expected));
@@ -42,10 +42,10 @@ describe('ApplicationFactory', () => {
const sut = new SystemUnderTest(getter);
// act
await Promise.all( [
sut.getAppAsync(),
sut.getAppAsync(),
sut.getAppAsync(),
sut.getAppAsync(),
sut.getApp(),
sut.getApp(),
sut.getApp(),
sut.getApp(),
]);
// assert
expect(totalExecution).to.equal(1);

View File

@@ -2,7 +2,7 @@ import 'mocha';
import { expect } from 'chai';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { buildContextAsync } from '@/application/Context/ApplicationContextFactory';
import { buildContext } from '@/application/Context/ApplicationContextFactory';
import { IApplicationFactory } from '@/application/IApplicationFactory';
import { IApplication } from '@/domain/IApplication';
import { EnvironmentStub } from '@tests/unit/stubs/EnvironmentStub';
@@ -10,7 +10,7 @@ import { ApplicationStub } from '@tests/unit/stubs/ApplicationStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
describe('ApplicationContextFactory', () => {
describe('buildContextAsync', () => {
describe('buildContext', () => {
describe('factory', () => {
it('sets application from factory', async () => {
// arrange
@@ -18,7 +18,7 @@ describe('ApplicationContextFactory', () => {
new CategoryCollectionStub().withOs(OperatingSystem.macOS));
const factoryMock = mockFactoryWithApp(expected);
// act
const context = await buildContextAsync(factoryMock);
const context = await buildContext(factoryMock);
// assert
expect(expected).to.equal(context.app);
});
@@ -32,7 +32,7 @@ describe('ApplicationContextFactory', () => {
const collection = new CategoryCollectionStub().withOs(expected);
const factoryMock = mockFactoryWithCollection(collection);
// act
const context = await buildContextAsync(factoryMock, environment);
const context = await buildContext(factoryMock, environment);
// assert
const actual = context.state.os;
expect(expected).to.equal(actual);
@@ -45,7 +45,7 @@ describe('ApplicationContextFactory', () => {
const collection = new CategoryCollectionStub().withOs(expected);
const factoryMock = mockFactoryWithCollection(collection);
// act
const context = await buildContextAsync(factoryMock, environment);
const context = await buildContext(factoryMock, environment);
// assert
const actual = context.state.os;
expect(expected).to.equal(actual);
@@ -62,7 +62,7 @@ describe('ApplicationContextFactory', () => {
const app = new ApplicationStub().withCollections(...allCollections);
const factoryMock = mockFactoryWithApp(app);
// act
const context = await buildContextAsync(factoryMock, environment);
const context = await buildContext(factoryMock, environment);
// assert
const actual = context.state.os;
expect(expectedOs).to.equal(actual, `Expected: ${OperatingSystem[expectedOs]}, actual: ${OperatingSystem[actual]}`);
@@ -78,6 +78,6 @@ function mockFactoryWithCollection(result: ICategoryCollection): IApplicationFac
function mockFactoryWithApp(app: IApplication): IApplicationFactory {
return {
getAppAsync: () => Promise.resolve(app),
getApp: () => Promise.resolve(app),
};
}

View File

@@ -283,7 +283,7 @@ function getCommentCases(): IPipeTestCase[] {
),
},
{
name: 'can convert comment with inline comment parts',
name: 'can convert comment with inline comment parts inside',
input: getWindowsLines(
'$text+= #Comment with < inside',
'$text+= #Comment ending with >',
@@ -295,14 +295,28 @@ function getCommentCases(): IPipeTestCase[] {
'$text+= <# Comment with <# inline comment #> #>',
),
},
{
name: 'can convert comment with inline comment parts around', // Pretty uncommon
input: getWindowsLines(
'Write-Host "hi" # Comment ending line inline comment but not one #>',
'Write-Host "hi" #>Comment starting like inline comment end but not one',
// Following line does not compile as valid PowerShell referring to missing #> for inline comment
'Write-Host "hi" <#Comment starting like inline comment start but not one',
),
expectedOutput: getSingleLinedOutput(
'Write-Host "hi" <# Comment ending line inline comment but not one #> #>',
'Write-Host "hi" <# >Comment starting like inline comment end but not one #>',
'Write-Host "hi" <<# Comment starting like inline comment start but not one #>',
),
},
{
name: 'converts empty hash comment',
input: getWindowsLines(
'Write-Host "Lorem ipsus" #',
'Write-Host "Comment without text" #',
'Write-Host "Non-empty line"',
),
expectedOutput: getSingleLinedOutput(
'Write-Host "Lorem ipsus" <##>',
'Write-Host "Comment without text" <##>',
'Write-Host "Non-empty line"',
),
},
@@ -318,7 +332,7 @@ function getCommentCases(): IPipeTestCase[] {
),
},
{
name: 'trims whitespaces around from match',
name: 'trims whitespaces around comment',
input: getWindowsLines(
'# Comment with whitespaces around ',
'#\tComment with tabs around\t\t',

View File

@@ -5,7 +5,7 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
import { CodeRunner } from '@/infrastructure/CodeRunner';
describe('CodeRunner', () => {
describe('runCodeAsync', () => {
describe('runCode', () => {
it('creates temporary directory recursively', async () => {
// arrange
const expectedDir = 'expected-dir';
@@ -17,7 +17,7 @@ describe('CodeRunner', () => {
// act
await context
.withFolderName(folderName)
.runCodeAsync();
.runCode();
// assert
expect(context.mocks.fs.mkdirHistory.length).to.equal(1);
@@ -42,7 +42,7 @@ describe('CodeRunner', () => {
.withCode(expectedCode)
.withFolderName(folderName)
.withExtension(extension)
.runCodeAsync();
.runCode();
// assert
expect(context.mocks.fs.writeFileHistory.length).to.equal(1);
@@ -66,7 +66,7 @@ describe('CodeRunner', () => {
await context
.withFolderName(folderName)
.withExtension(extension)
.runCodeAsync();
.runCode();
// assert
expect(context.mocks.fs.chmodCallHistory.length).to.equal(1);
@@ -93,7 +93,7 @@ describe('CodeRunner', () => {
// act
await context
.withOs(data.os)
.runCodeAsync();
.runCode();
// assert
expect(context.mocks.child_process.executionHistory.length).to.equal(1);
@@ -109,7 +109,7 @@ describe('CodeRunner', () => {
context.mocks.path.setupJoinSequence('non-important-folder-name1', 'non-important-folder-name2');
// act
await context.runCodeAsync();
await context.runCode();
// assert
const actualOrder = context.mocks.commandHistory.filter((command) => expectedOrder.includes(command));
@@ -126,9 +126,9 @@ class TestContext {
private fileExtension: string = 'fileExtension';
private env = mockEnvironment(OperatingSystem.Windows);
public async runCodeAsync(): Promise<void> {
public async runCode(): Promise<void> {
const runner = new CodeRunner(this.mocks, this.env);
await runner.runCodeAsync(this.code, this.folderName, this.fileExtension);
await runner.runCode(this.code, this.folderName, this.fileExtension);
}
public withOs(os: OperatingSystem) {
this.env = mockEnvironment(os);

View File

@@ -1,7 +1,7 @@
import 'mocha';
import { expect } from 'chai';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import { sleepAsync } from '@/infrastructure/Threading/AsyncSleep';
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
describe('AsyncLazy', () => {
it('returns value from lambda', async () => {
@@ -10,7 +10,7 @@ describe('AsyncLazy', () => {
const lambda = () => Promise.resolve(expected);
const sut = new AsyncLazy(lambda);
// act
const actual = await sut.getValueAsync();
const actual = await sut.getValue();
// assert
expect(actual).to.equal(expected);
});
@@ -26,7 +26,7 @@ describe('AsyncLazy', () => {
});
const results = new Array<number>();
for (let i = 0; i < 5; i++) {
results.push(await sut.getValueAsync());
results.push(await sut.getValue());
}
// assert
expect(totalExecuted).to.equal(1);
@@ -35,16 +35,16 @@ describe('AsyncLazy', () => {
it('when running long-running task in parallel', async () => {
// act
const sut = new AsyncLazy(async () => {
await sleepAsync(100);
await sleep(100);
totalExecuted++;
return Promise.resolve(totalExecuted);
});
const results = await Promise.all([
sut.getValueAsync(),
sut.getValueAsync(),
sut.getValueAsync(),
sut.getValueAsync(),
sut.getValueAsync()]);
sut.getValue(),
sut.getValue(),
sut.getValue(),
sut.getValue(),
sut.getValue()]);
// assert
expect(totalExecuted).to.equal(1);
expect(results).to.deep.equal([1, 1, 1, 1, 1]);

View File

@@ -1,33 +1,35 @@
import 'mocha';
import { expect } from 'chai';
import { sleepAsync, SchedulerType } from '@/infrastructure/Threading/AsyncSleep';
import { sleep, SchedulerType } from '@/infrastructure/Threading/AsyncSleep';
describe('AsyncSleep', () => {
it('fulfills after delay', async () => {
// arrange
const delayInMs = 10;
const scheduler = new SchedulerMock();
// act
const sleep = sleepAsync(delayInMs, scheduler.mock);
const promiseState = watchPromiseState(sleep);
scheduler.tickNext(delayInMs);
await flushPromiseResolutionQueue();
// assert
const actual = promiseState.isFulfilled();
expect(actual).to.equal(true);
});
it('pending before delay', async () => {
// arrange
const delayInMs = 10;
const scheduler = new SchedulerMock();
// act
const sleep = sleepAsync(delayInMs, scheduler.mock);
const promiseState = watchPromiseState(sleep);
scheduler.tickNext(delayInMs / 5);
await flushPromiseResolutionQueue();
// assert
const actual = promiseState.isPending();
expect(actual).to.equal(true);
describe('sleep', () => {
it('fulfills after delay', async () => {
// arrange
const delayInMs = 10;
const scheduler = new SchedulerMock();
// act
const promise = sleep(delayInMs, scheduler.mock);
const promiseState = watchPromiseState(promise);
scheduler.tickNext(delayInMs);
await flushPromiseResolutionQueue();
// assert
const actual = promiseState.isFulfilled();
expect(actual).to.equal(true);
});
it('pending before delay', async () => {
// arrange
const delayInMs = 10;
const scheduler = new SchedulerMock();
// act
const promise = sleep(delayInMs, scheduler.mock);
const promiseState = watchPromiseState(promise);
scheduler.tickNext(delayInMs / 5);
await flushPromiseResolutionQueue();
// assert
const actual = promiseState.isPending();
expect(actual).to.equal(true);
});
});
});