Compare commits

...

48 Commits

Author SHA1 Message Date
undergroundwires
b25b8cc805 fix vue warning for undefined property during render
currentOs is not recognized as reactive property as it's set to "undefined". JavaScript does not accept "undefined" as valid value to initialize. A property needs to be initialized with a non-undefined value to become reactive in a class-based component. Otherwise Vue warns: Property or method "currentOs" is not defined on the instance but referenced during render.
2021-04-19 18:21:06 +02:00
Marc05
8141a01ef7 fix typo and dead URL in Windows scripts (#70)
Co-authored-by: Marc05 <git@marc05.net>
2021-04-18 19:12:50 +02:00
undergroundwires
a2f10857e2 fix script revert activating recommendation level
Reverting any single of the scripts from standard recommendation pool
shows "Standard" selection as selected which is wrong. This commit fixes
it, refactors selection handling in a separate class and it also adds
missing tests. It removes UserSelection.totalSelected propertty in favor of using
UserSelection.selectedScripts.length to provide unified way of accessing
the information.
2021-04-17 14:34:29 +01:00
undergroundwires
aea04e5f7c document chromium warning for policy changes
Chromium shows "Your browser is managed" or "Your browser is managed by an organization" warnings when its behavior is manipulated using policies. This message confuses some users, so the commit marks this behavior to let users know why the box appears.
Read more:
- https://chromium.googlesource.com/chromium/src/+/refs/tags/92.0.4475.1/chrome/browser/ui/managed_ui.cc#67
- https://support.google.com/chrome/thread/3262871
2021-04-16 17:56:31 +02:00
undergroundwires
60c80611ea add module alias '@tests/'
Alias would remove unnecessary repetitions and less relative paths make changes easier when moving around files. This commit cleans also up some relative paths ('../../../') by using the alias and orders imports. It updates both path alias in tsconfig and module alias in Vue CLI's bundler (vuejs/vue-cli#2398).
2021-04-15 18:34:40 +02:00
undergroundwires
b1ed3ce55f document breaking behavior in script name #64
Removing Cloud Experience Hosting app breaks Microsoft cloud and
corporate sign in. It's now documented more cleary in the name of the
script.
2021-04-14 15:44:22 +01:00
undergroundwires
040ed2701c improve disabling ads and marketing #65
This commit documents the behavior better with more granularity of
choice and also adds options to revert the code.
2021-04-13 16:39:44 +01:00
undergroundwires
00d8e551db refactor extra code, duplicates, complexity
- refactor array equality check and add tests
- remove OperatingSystem.Unknown causing extra logic, return undefined instead
- refactor enum validation to share same logic
- refactor scripting language factories to share same logic
- refactor too many args in runCodeAsync
- refactor ScriptCode constructor to reduce complexity
- fix writing useless write to member object since another property write always override it
2021-04-11 14:37:02 +01:00
dependabot[bot]
3e9c99f5f8 Bump y18n from 3.2.1 to 3.2.2 (#66)
Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-06 20:29:15 +02:00
undergroundwires
02bdc4cf04 fix desktop initial window size being bigger than current display size on smaller Linux/Windows screens 2021-04-05 14:31:31 +01:00
undergroundwires
5c43965f0b in CI/CD, run other tests/check even if one of them fails 2021-03-28 14:26:20 +01:00
dependabot[bot]
b2376ecc30 Bump elliptic from 6.5.3 to 6.5.4 (#62)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-27 17:15:30 +01:00
undergroundwires-bot
aeaa6deeb4 ⬆️ bump everywhere to 0.10.1 2021-03-26 14:57:03 +00:00
undergroundwires
448e378dc4 increase performance by polyfilling ResizeObserver only if required 2021-03-25 13:24:19 +01:00
undergroundwires
ac2249f256 refactor features to use shared functions #41 2021-03-24 15:29:39 +01:00
undergroundwires
05932c5a36 fix safari cleanup scripts that are not working on modern versions 2021-03-23 19:06:20 +01:00
undergroundwires
6f46cdb4ed refactor all modals to use same dialog component 2021-03-20 16:13:25 +01:00
undergroundwires
5f527a00cf fix fs module hanging not allowing code to run
Run button on Windows stopped working as CodeRunner was hanging when
executing fs.promises.mkdir as described in electron/electron#20951
It started happening after electron update to v12 in 1f515e7.
This commit adds the workaround suggested in electron/electron#19554
that fixes the issue.
2021-03-14 17:26:56 +01:00
undergroundwires
1935db1019 fix throttle function not being able to run with argument(s) 2021-03-13 12:54:13 +01:00
undergroundwires
1f515e7be5 bump dependencies to latest
- fix npm vulnerabilities #62
- change ResizeObserver polyfill dependency que-etc/resize-observer-polyfill#80
- bump typescript to 4.2.x and add tslib for importing helpers
- update electron to v12.x and set contextIsolation to false (nklayman/vue-cli-plugin-electron-builder#1285, electron/electron#11608) to reach node APIs as it's now disabled by default (electron/electron#27949)
2021-03-11 14:50:35 +01:00
undergroundwires
1a5f92021f fix a test where "it" is not used inside "describe" 2021-03-08 17:21:11 +01:00
undergroundwires
f3c7413f52 restructure presentation layer
- Move most GUI related code to /presentation
- Move components to /components (separate from bootstrap and style)
- Move shared components helpers to /components/shared
- Rename Bootstrapping to bootstrapping to enforce same naming
  convention in /presentation
2021-03-07 19:37:54 +01:00
undergroundwires
646db90585 refactor script compilation to make it easy to add new expressions #41 #53 2021-03-05 15:52:49 +01:00
undergroundwires-bot
1f8a0cf9ab ⬆️ bump everywhere to 0.10.0 2021-03-02 16:08:31 +00:00
undergroundwires
bd41af466f update screenshot 2021-03-02 16:28:07 +01:00
undergroundwires
970221b996 remove "preview" disclaimer from macOS 2021-03-01 17:01:34 +01:00
undergroundwires
15004ff1f1 remove windows scripts for removing non-bloating system apps #55 2021-02-28 13:43:42 +01:00
undergroundwires
65226f3984 add better error messages to setting vscode settings 2021-02-27 14:40:14 +01:00
undergroundwires
b0a7d0b53b add more macos scripts for privacy cleanup 2021-02-26 14:27:12 +01:00
undergroundwires
ee43fd92a0 more scripts to disable speech recognition and Cortana 2021-02-25 16:37:57 +01:00
undergroundwires
cf39e6d254 move code area to right on bigger screens 2021-02-22 16:46:06 +01:00
undergroundwires
1260eea690 escape printed characters to prevent command injection #45 2021-02-21 12:34:33 +01:00
undergroundwires
45a3669443 refactor disabling application experience and document better 2021-02-20 12:13:08 +01:00
undergroundwires
c9b91f6d8f add script to automatically kill devicecensus process 2021-02-19 11:12:28 +01:00
undergroundwires
9a6b903b92 add option to run script directly in desktop app 2021-02-18 09:39:24 +01:00
undergroundwires
7661575573 allow functions to call other functions #53 2021-02-14 11:18:31 +01:00
undergroundwires-bot
f1abd7682f ⬆️ bump everywhere to 0.9.2 2021-02-13 11:08:41 +00:00
zy26
575636e6b7 correct the typo in application.md (#60) 2021-02-13 11:57:57 +01:00
undergroundwires
daa997b21b add GitHub issue templates 2021-02-10 21:16:56 +01:00
undergroundwires
5934b17283 refactor and add tests for NonCollapsingDirective 2021-02-09 08:53:29 +01:00
undergroundwires
d7de420d5c add test to ensure correct shared functions are being parsed 2021-02-08 07:10:41 +01:00
undergroundwires
df273f7f63 refactor state handling to make application available independent of the state 2021-02-07 12:32:05 +01:00
undergroundwires
67b2d1c11c refactor vscode configuration scripts using functions #41 2021-02-06 11:37:45 +01:00
undergroundwires
15353d0e25 make compiler throw if a function call includes an unexpected parameter 2021-02-05 13:27:40 +01:00
undergroundwires
f1e21babbf refactor event handling to consume base class for lifecycling 2021-02-04 19:58:09 +01:00
undergroundwires
34b8822ac8 fix wrong path for NvTelemtry file in NVIDIA script 2021-01-27 08:07:33 +01:00
undergroundwires
73e0520de7 do not compile with unused locals vuejs/vetur#1063 2021-01-26 06:00:19 +01:00
undergroundwires-bot
fbc3b109b9 ⬆️ bump everywhere to 0.9.1 2021-01-24 06:22:52 +00:00
238 changed files with 7992 additions and 3718 deletions

View File

@@ -0,0 +1,36 @@
---
name: Bug report (script bug or unexpected script behavior)
about: Create a bug report for generated scripts to help privacy.sexy improve
labels: bug
title: '[BUG]: '
---
<!--
Thank you for reporting an issue with generated script(s).
Please fill in as much of the template below as you're able.
As a small open source project with small community, it can sometimes take a long time for issues to be addressed so please be patient.
-->
### Describe the bug
<!-- A clear and concise description of what the bug is. -->
### OS
<!--
Which OS are you using? What version of OS you were using?
On Windows you can find it using "Start button" > "Settings" > "System" > "About".
On macOS you can find it using "Apple menu (top left corner)" > "About This Mac".
-->
### Screenshots
<!-- If applicable, add screenshots to help explain your problem. -->
### Scripts
<!-- Which scripts did you execute? If applicable, please paste the executed scripts or attach the generated privacy.sexy file . -->
### Additional information
<!-- Add any other context about the problem here. -->

View File

@@ -0,0 +1,52 @@
---
name: Bug report (unrelated to generated scripts)
about: Create a bug report that's not related to generated scripts to help privacy.sexy improve
labels: bug
title: '[BUG]: '
---
<!--
Thank you for reporting an issue.
Please fill in as much of the template below as you're able.
As a small open source project with small community, it can sometimes take a long time for issues to be addressed so please be patient.
-->
### Describe the bug
<!-- A clear and concise description of what the bug is. -->
### To Reproduce
<!--
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
-->
### Expected behavior
<!--
A clear and concise description of what you expected to happen.
-->
### Screenshots
<!--
If applicable, add screenshots to help explain your problem.
-->
### Distribution
<!--
If applicable, mention how you were using privacy.sexy when the bug was encountered:
- Web (on Desktop or mobile?)
- Or desktop (Windows, macOS or Linux?)
-->
### Additional context
<!--
Add any other context about the problem here.
-->

View File

@@ -0,0 +1,27 @@
---
name: Feature request
about: Suggest an idea for privacy.sexy
labels: enhancement
---
<!--
Thank you for suggesting an idea to make privacy better. 🤗
Please fill in as much of the template below as you're able.
-->
### Problem Description
<!-- Please add a clear and concise description of the problem you are seeking to solve with this feature request. Ex. I'm always frustrated when [...] -->
### Proposed solution
<!-- Describe the solution you'd like in a clear and concise manner. -->
### Alternatives considered
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
### Additional information
<!-- Add any other context or screenshots about the feature request here. -->

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: true

View File

@@ -13,6 +13,7 @@ jobs:
- npm run lint:md - npm run lint:md
- npm run lint:md:relative-urls - npm run lint:md:relative-urls
- npm run lint:md:consistency - npm run lint:md:consistency
fail-fast: false # So it continues with other commands if one fails
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2

View File

@@ -7,6 +7,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [macos, ubuntu, windows] os: [macos, ubuntu, windows]
fail-fast: false # So it still runs on other OSes if one of them fails
runs-on: ${{ matrix.os }}-latest runs-on: ${{ matrix.os }}-latest
steps: steps:
- -

View File

@@ -1,5 +1,64 @@
# Changelog # Changelog
## 0.10.1 (2021-03-25)
* refactor script compilation to make it easy to add new expressions #41 #53 | [646db90](https://github.com/undergroundwires/privacy.sexy/commit/646db9058541cebd0af437554de04fdc6bb63a6e)
* restructure presentation layer | [f3c7413](https://github.com/undergroundwires/privacy.sexy/commit/f3c7413f529be4a00dba7b0ab23904b48ea13a35)
* fix a test where "it" is not used inside "describe" | [1a5f920](https://github.com/undergroundwires/privacy.sexy/commit/1a5f92021f7423cd039f8f5326cd6f99b355c962)
* bump dependencies to latest | [1f515e7](https://github.com/undergroundwires/privacy.sexy/commit/1f515e7be525291c960ccb71db05312db6da53f5)
* fix throttle function not being able to run with argument(s) | [1935db1](https://github.com/undergroundwires/privacy.sexy/commit/1935db10192051401ab00ca2cd767955d0d3b866)
* fix fs module hanging not allowing code to run | [5f527a0](https://github.com/undergroundwires/privacy.sexy/commit/5f527a00cf225d3e74b3f6577d6e2456e919de24)
* refactor all modals to use same dialog component | [6f46cdb](https://github.com/undergroundwires/privacy.sexy/commit/6f46cdb4ed49a8941c6c0dde5c5e2a816c06daef)
* fix safari cleanup scripts that are not working on modern versions | [05932c5](https://github.com/undergroundwires/privacy.sexy/commit/05932c5a36446d551c5bc811165e3295fbe15e3f)
* refactor features to use shared functions #41 | [ac2249f](https://github.com/undergroundwires/privacy.sexy/commit/ac2249f25664827d8a6d2c7ebd659ccf126b0cde)
* increase performance by polyfilling ResizeObserver only if required | [448e378](https://github.com/undergroundwires/privacy.sexy/commit/448e378dc4501f9de69af63634c87d0e5060bf52)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.10.0...0.10.1)
## 0.10.0 (2021-03-02)
* allow functions to call other functions #53 | [7661575](https://github.com/undergroundwires/privacy.sexy/commit/7661575573c6d3e8f4bc28bfa7a124a764c72ef9)
* add option to run script directly in desktop app | [9a6b903](https://github.com/undergroundwires/privacy.sexy/commit/9a6b903b9297802845043fd41115756acd4a145c)
* add script to automatically kill devicecensus process | [c9b91f6](https://github.com/undergroundwires/privacy.sexy/commit/c9b91f6d8f9bd16308b6beda119e7154a985b6cf)
* refactor disabling application experience and document better | [45a3669](https://github.com/undergroundwires/privacy.sexy/commit/45a3669443d82855a52f60524d341c15f380f9e7)
* escape printed characters to prevent command injection #45 | [1260eea](https://github.com/undergroundwires/privacy.sexy/commit/1260eea690e4fa5420e58c9de9f88cc29cb242db)
* move code area to right on bigger screens | [cf39e6d](https://github.com/undergroundwires/privacy.sexy/commit/cf39e6d2541ea547f41d9553c380c54c24c58038)
* more scripts to disable speech recognition and Cortana | [ee43fd9](https://github.com/undergroundwires/privacy.sexy/commit/ee43fd92a019ebd26c13890f9146c5b5bb56afaf)
* add more macos scripts for privacy cleanup | [b0a7d0b](https://github.com/undergroundwires/privacy.sexy/commit/b0a7d0b53b3d8ac144a0241d70c037f460b0c0cc)
* add better error messages to setting vscode settings | [65226f3](https://github.com/undergroundwires/privacy.sexy/commit/65226f3984480d0bc7932fd8d76a328f08308850)
* remove windows scripts for removing non-bloating system apps #55 | [15004ff](https://github.com/undergroundwires/privacy.sexy/commit/15004ff1f1fb85a1d92e11ef695bcb2f37110610)
* remove "preview" disclaimer from macOS | [970221b](https://github.com/undergroundwires/privacy.sexy/commit/970221b996e25fe5b029cbaa78607c9bbc8c3c0e)
* update screenshot | [bd41af4](https://github.com/undergroundwires/privacy.sexy/commit/bd41af466fd135f7dc2f171633e4f60d8547c373)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.9.2...0.10.0)
## 0.9.2 (2021-02-13)
* do not compile with unused locals vuejs/vetur#1063 | [73e0520](https://github.com/undergroundwires/privacy.sexy/commit/73e0520de70cdbaf0ecdc6e9be5e85f003fcfb79)
* fix wrong path for NvTelemtry file in NVIDIA script | [34b8822](https://github.com/undergroundwires/privacy.sexy/commit/34b8822ac821acb47e483e21b57e380551bcf455)
* refactor event handling to consume base class for lifecycling | [f1e21ba](https://github.com/undergroundwires/privacy.sexy/commit/f1e21babbfaac21903594a37e30163bfe3338279)
* make compiler throw if a function call includes an unexpected parameter | [15353d0](https://github.com/undergroundwires/privacy.sexy/commit/15353d0e2513c89ee4ffd9d9c5e9e83ef69b96b6)
* refactor vscode configuration scripts using functions #41 | [67b2d1c](https://github.com/undergroundwires/privacy.sexy/commit/67b2d1c11cd5b131dff93a4437db79d96ed8b3dc)
* refactor state handling to make application available independent of the state | [df273f7](https://github.com/undergroundwires/privacy.sexy/commit/df273f7f635ab156ac51a8dfb3fec66c4979f1c4)
* add test to ensure correct shared functions are being parsed | [d7de420](https://github.com/undergroundwires/privacy.sexy/commit/d7de420d5c91bd9ce64880cd4a4391ad3a0a5401)
* refactor and add tests for NonCollapsingDirective | [5934b17](https://github.com/undergroundwires/privacy.sexy/commit/5934b1728328c3b2ece1597b74dd87477d162175)
* add GitHub issue templates | [daa997b](https://github.com/undergroundwires/privacy.sexy/commit/daa997b21b624d133c6f5e4cd6b70214588f9144)
* correct the typo in application.md (#60) | [575636e](https://github.com/undergroundwires/privacy.sexy/commit/575636e6b728a2bdd1a9bd72c57bbf2752f10887)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.9.1...0.9.2)
## 0.9.1 (2021-01-23)
* in CI/CD, allow publishing to github if release is more than 2 hours old electron-userland/electron-builder#2074 | [cf907d0](https://github.com/undergroundwires/privacy.sexy/commit/cf907d029a6d80682ba78ec887a9c4fab639db51)
* in CI/CD, publish packages for other OSes if single one fails | [4015e2c](https://github.com/undergroundwires/privacy.sexy/commit/4015e2ccd8492e0693365b70fbfe3bd0ac7a6ea2)
* specify desktop publish targets as defaults (may) change | [2316e3f](https://github.com/undergroundwires/privacy.sexy/commit/2316e3fb6867e5d765eafcf675b77f88bd2a0f52)
* fix selection state indicator on cards not showing up | [8b0e47d](https://github.com/undergroundwires/privacy.sexy/commit/8b0e47da38c49cfe2645d7d25970c448ecd200f8)
* transpile using babel for legacy browser support | [7930bef](https://github.com/undergroundwires/privacy.sexy/commit/7930bef48c4e9a4fe0823673958ed8377f5ee533)
* fix node APIs no longer working on desktop nklayman/vue-cli-plugin-electron-builder#610, nklayman/vue-cli-plugin-electron-builder#742 | [d7f9ef1](https://github.com/undergroundwires/privacy.sexy/commit/d7f9ef1cbebe911aa19f29be8c5fa9360550793e)
* improve explanation for selections | [229c13a](https://github.com/undergroundwires/privacy.sexy/commit/229c13a195dee92e4a31731b7b41c319273a16f1)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.9.0...0.9.1)
## 0.9.0 (2021-01-15) ## 0.9.0 (2021-01-15)
* refactor application.yaml to become an os definition #40 | [f7557bc](https://github.com/undergroundwires/privacy.sexy/commit/f7557bcc0faf44e8395b68c7eb14c5f715f07b92) * refactor application.yaml to become an os definition #40 | [f7557bc](https://github.com/undergroundwires/privacy.sexy/commit/f7557bcc0faf44e8395b68c7eb14c5f715f07b92)

View File

@@ -7,9 +7,9 @@
- Proposing new features - Proposing new features
- Becoming a maintainer - Becoming a maintainer
## Pull Request Process ## Pull request process
- [GitHub flow](https://guides.github.com/introduction/flow/index.html) is used - [GitHub flow](https://guides.github.com/introduction/flow/index.html) with [GitOps](./img/architecture/gitops.png) is used
- Your pull requests are actively welcomed. - Your pull requests are actively welcomed.
- The steps: - The steps:
1. Fork the repo and create your branch from master. 1. Fork the repo and create your branch from master.
@@ -23,17 +23,12 @@
- ❗ DON'T - ❗ DON'T
- Do not update the versions, current version is only [set by the maintainer](./img/architecture/gitops.png) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere) - Do not update the versions, current version is only [set by the maintainer](./img/architecture/gitops.png) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere)
## Guidelines
### Handle the state in presentation layer
- There are two types of components:
- **Stateless**, extends `Vue`
- **Stateful**, extends [`StatefulVue`](./src/presentation/StatefulVue.ts)
- The source of truth for the state lies in application layer ([`./src/application/`](src/application/)) and must be updated from the views if they're mutating the state
- They mutate or/and react to state changes in [ApplicationContext](src/application/Context/ApplicationContext.ts).
- You can react by getting the state and listening to it and update the view accordingly in [`mounted()`](https://vuejs.org/v2/api/#mounted) method.
## License ## License
By contributing, you agree that your contributions will be licensed under its GNU General Public License v3.0. By contributing, you agree that your contributions will be licensed under its [GNU General Public License v3.0](./LICENSE).
## Read more
- See [tests](./docs/tests.md) for testing
- See [extend script](./README.md#extend-scripts) for quick steps to extend scripts
- See [architecture overview](./README.md#architecture-overview) to deep dive into privacy.sexy codebase

View File

@@ -14,11 +14,13 @@
## Get started ## Get started
- Online version: [https://privacy.sexy](https://privacy.sexy) - Online version at [https://privacy.sexy](https://privacy.sexy)
- or download latest desktop version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.0/privacy.sexy-Setup-0.9.0.exe), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.0/privacy.sexy-0.9.0.AppImage), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.0/privacy.sexy-0.9.0.dmg) - 💡 No need to run any compiled software on your computer.
- 💡 Come back regularly to apply latest version for stronger privacy and security. - Alternatively download offline version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.10.1/privacy.sexy-Setup-0.10.1.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.10.1/privacy.sexy-0.10.1.dmg) or [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.10.1/privacy.sexy-0.10.1.AppImage).
- 💡 Single click to execute your script.
- ❗ Come back regularly to apply latest version for stronger privacy and security.
[![privacy.sexy application](img/screenshot.png)](https://privacy.sexy) [![privacy.sexy application](img/screenshot.png?raw=true)](https://privacy.sexy)
## Why ## Why
@@ -51,29 +53,17 @@
- Development: `npm run serve` to compile & hot-reload for development. - Development: `npm run serve` to compile & hot-reload for development.
- Production: `npm run build` to prepare files for distribution. - Production: `npm run build` to prepare files for distribution.
- Or run using Docker: - Or run using Docker:
1. Build: `docker build -t undergroundwires/privacy.sexy:0.9.0 .` 1. Build: `docker build -t undergroundwires/privacy.sexy:0.10.1 .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.9.0 undergroundwires/privacy.sexy:0.9.0` 2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.10.1 undergroundwires/privacy.sexy:0.10.1`
## Architecture ## Architecture overview
### Application ### Application
- Powered by **TypeScript**, **Vue.js** and **Electron** 💪 - Powered by **TypeScript**, **Vue.js** and **Electron** 💪
- and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts. - and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts.
- Application uses highly decoupled models & services in different DDD layers. - Application uses highly decoupled models & services in different DDD layers.
- **Domain layer** is where the application is modelled with validation logic. - 📖 Read more on • [Presentation](./docs/presentation.md) • [Application](./docs/application.md)
- **Presentation Layer**
- Consists of Vue.js components and other UI-related code.
- Desktop application is created using [Electron](https://www.electronjs.org/).
- Event driven as in components simply listens to events from the state and act accordingly.
- **Application Layer**
- Keeps the application state using [state pattern](https://en.wikipedia.org/wiki/State_pattern)
- [ApplicationContext](src/application/Context/ApplicationContext.ts)
- Holds the [CategoryCollectionState](src/application/Context/State/CategoryCollectionState.ts)] for each OS
- Same instance is shared throughout the application
- The scripts are defined and controlled in [yaml files](src/application/collections/) per OS
- Uses [data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming)
- 📖 See [extend scripts](#extend-scripts) to read about how to extend them.
![DDD + vue.js](img/architecture/app-ddd.png) ![DDD + vue.js](img/architecture/app-ddd.png)

44
docs/application.md Normal file
View File

@@ -0,0 +1,44 @@
# Application
- It's mainly responsible for
- creating and event based [application state](#application-state)
- [parsing](#parsing) and [compiling](#compiling) [application data](#application-data)
- Consumed by [presentation layer](./presentation.md)
## Structure
- [`/src/` **`application/`**](./../src/application/): Contains all application related code.
- [**`collections/`**](./../src/application/collections/): Holds [collection files](./collection-files.md)
- [**`Common/`**](./../src/application/Common/): Contains common functionality that is shared in application layer.
- `..`: other classes are categorized using folders-by-feature structure
## Application state
- [ApplicationContext.ts](./../src/application/Context/ApplicationContext.ts) holds the [CategoryCollectionState](./../src/application/Context/State/CategoryCollectionState.ts) for each OS
- Uses [state pattern](https://en.wikipedia.org/wiki/State_pattern)
- Same instance is shared throughout the application to ensure consistent state
- 📖 See [Application State | Presentation layer](./presentation.md#application-state) to read more about how the state should be managed by the presentation layer.
- 📖 See [ApplicationContext.ts](./../src/application/Context/ApplicationContext.ts) to start diving into the state code.
## Application data
- Compiled to [`Application`](./../src/domain/Application.ts) domain object.
- The scripts are defined and controlled in different data files per OS
- Enables [data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming) and easier contributions
- Application data is defined in collection files and
- 📖 See [Application data | Presentation layer](./presentation.md#application-data) to read how the application data is read by the presentation layer.
- 📖 See [collection files documentation](./collection-files.md) to read more about how the data files are structured/defined and see [collection yaml files](./../src/application/collections/) to directly check the code.
## Parsing
- Application data is parsed to domain object [`Application.ts`](./../src/domain/Application.ts)
- Steps
1. (Compile time) Load application data from [collection yaml files](./../src/application/collections/) using webpack loader
2. (Runtime) Parse and compile application and make it available to presentation layer by [`ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts)
### Compiling
- Parsing the application files includes compiling scripts using [collection file defined functions](./collection-files.md#function)
- To extend the syntax:
1. Add a new parser under [SyntaxParsers](./../src/application/Parser/Script/Compiler/Expressions/SyntaxParsers) where you can look at other parsers to understand more.
2. Register your in [CompositeExpressionParser](./../src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts)

View File

@@ -101,11 +101,15 @@
### `Function` ### `Function`
- Functions allow re-usable code throughout the defined scripts. - Functions allow re-usable code throughout the defined scripts.
- Functions are templates compiled by privacy.sexy and uses special expressions. - Functions are templates compiled by privacy.sexy and uses special [expressions](#expressions).
- Expressions are defined inside mustaches (double brackets, `{{` and `}}`) - Functions can call other functions by defining `call` property instead of `code`
- 👀 See [parameter substitution](#parameter-substitution) for an example usage - 👀 See [parameter substitution](#parameter-substitution) for an example usage
#### Parameter substitution #### Expressions
- Expressions are defined inside mustaches (double brackets, `{{` and `}}`)
##### Parameter substitution
A simple function example A simple function example
@@ -125,6 +129,22 @@ It would print "Hello world" if it's called in a [script](#script) as following:
argument: World argument: World
``` ```
A function can call other functions such as:
```yaml
-
function: CallerFunction
parameters: [ 'value' ]
call:
function: EchoArgument
parameters:
argument: {{ $value }}
-
function: EchoArgument
parameters: [ 'argument' ]
code: Hello {{ $argument }} !
```
#### `Function` syntax #### `Function` syntax
- `name`: *`string`* (**required**) - `name`: *`string`* (**required**)
@@ -135,15 +155,20 @@ It would print "Hello world" if it's called in a [script](#script) as following:
- `parameters`: `[` *`string`* `, ... ]` - `parameters`: `[` *`string`* `, ... ]`
- Name of the parameters that the function has. - Name of the parameters that the function has.
- Parameter values are provided by a [Script](#script) through a [FunctionCall](#FunctionCall) - Parameter values are provided by a [Script](#script) through a [FunctionCall](#FunctionCall)
- Parameter names must be defined to be used in expressions such as [parameter substitution](#parameter-substitution) - Parameter names must be defined to be used in [expressions](#expressions)
- ❗ Parameter names must be unique - ❗ Parameter names must be unique
`code`: *`string`* (**required**) `code`: *`string`* (**required** if `call` is undefined)
- Batch file commands that will be executed - Batch file commands that will be executed
- 💡 If defined, best practice to also define `revertCode` - 💡 If defined, best practice to also define `revertCode`
- ❗ If not defined `call` must be defined
- `revertCode`: *`string`* - `revertCode`: *`string`*
- Code that'll undo the change done by `code` property. - Code that'll undo the change done by `code` property.
- E.g. let's say `code` sets an environment variable as `setx POWERSHELL_TELEMETRY_OPTOUT 1` - E.g. let's say `code` sets an environment variable as `setx POWERSHELL_TELEMETRY_OPTOUT 1`
- then `revertCode` should be doing `setx POWERSHELL_TELEMETRY_OPTOUT 0` - then `revertCode` should be doing `setx POWERSHELL_TELEMETRY_OPTOUT 0`
- `call`: ***[`FunctionCall`](#FunctionCall)*** | `[` ***[`FunctionCall`](#FunctionCall)*** `, ... ]` (may be **required**)
- A shared function or sequence of functions to call (called in order)
- The parameter values that are sent can use [expressions](#expressions)
- ❗ If not defined `code` must be defined
### `ScriptingDefinition` ### `ScriptingDefinition`

51
docs/presentation.md Normal file
View File

@@ -0,0 +1,51 @@
# Presentation layer
- Consists of Vue.js components and other UI-related code.
- Desktop application is created using [Electron](https://www.electronjs.org/).
- Event driven as in components simply listens to events from the state and act accordingly.
## Structure
- [`/src/` **`presentation/`**](./../src/presentation/): Contains all presentation related code including Vue and Electron configurations
- [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue global objects including components and plugins.
- [**`components/`**](./../src/presentation/components/): Contains all Vue components and their helper classes.
- [**`Shared/`**](./../src/presentation/components/Shared): Contains Vue components and component helpers that are shared across other components.
- [**`styles/`**](./../src/presentation/styles/): Contains shared styles used throughout different components.
- [**`main.ts`**](./../src/presentation/main.ts): Application entry point that mounts and starts Vue application.
- [**`background.ts`**](./../src/presentation/background.ts): Main process of Electron, started as first thing when app starts.
- [**`/public/`**](./../public/): Contains static assets that will simply be copied and not go through webpack.
- [**`/vue.config.js`**](./../vue.config.js): Global Vue CLI configurations loaded by `@vue/cli-service`
- [**`/postcss.config.js`**](./../postcss.config.js): PostCSS configurations that are used by Vue CLI internally
- [**`/babel.config.js`**](./../babel.config.js): Babel configurations for polyfills used by `@vue/cli-plugin-babel`
## Application data
- Components and should use [ApplicationFactory](./../src/application/ApplicationFactory.ts) singleton to reach the application domain.
- [Application.ts](../src/domain/Application.ts) domain model is the stateless application representation including
- available scripts, collections as defined in [collection files](./collection-files.md)
- package information as defined in [`package.json`](./../package.json)
- 📖 See [Application data | Application layer](./presentation.md#application-data) where application data is parsed and compiled.
## Application state
- Stateful components mutate or/and react to state changes in [ApplicationContext](./../src/application/Context/ApplicationContext.ts).
- Stateless components that does not handle state extends `Vue`
- Stateful components that depends on the collection state such as user selection, search queries and more extends [`StatefulVue`](./../src/presentation/components/Shared/StatefulVue.ts)
- The single source of truth is a singleton of the state created and made available to presentation layer by [`StatefulVue`](./../src/presentation/components/Shared/StatefulVue.ts)
- `StatefulVue` includes abstract `handleCollectionState` that is fired once the component is loaded and also each time [collection](./collection-files.md) is changed.
- Do not forget to subscribe from events when component is destroyed or if needed [collection](./collection-files.md) is changed.
- 💡 `events` in base class [`StatefulVue`](./../src/presentation/components/Shared/StatefulVue.ts) makes lifecycling easier
- 📖 See [Application state | Application layer](./presentation.md#application-state) where the state is implemented using using state pattern.
## Modals
- [Dialog.vue](./../src/presentation/components/Shared/Dialog.vue) is a shared component that can be used to show modal windows
- Simply wrap the content inside of its slot and call `.show()` method on its reference.
- Example:
```html
<Dialog ref="testDialog">
<div>Hello world</div>
</Dialog>
<div @click="$refs.testDialog.show()">Show dialog</div>
```

33
docs/tests.md Normal file
View File

@@ -0,0 +1,33 @@
# Tests
- 💡 You can use path/module alias `@/tests` in import statements.
## Unit tests
- Unit tests are defined in [`./tests`](./../tests)
- They follow same folder structure as [`./src`](./../src)
### Naming
- Each test suite first describe the system under test
- E.g. tests for class `Application` is categorized under `Application`
- Tests for specific methods are categorized under method name (if applicable)
- E.g. test for `run()` is categorized under `run`
### Act, arrange, assert
- Tests use act, arrange and assert (AAA) pattern when applicable
- **Arrange**
- Should set up the test case
- Starts with comment line `// arrange`
- **Act**
- Should cover the main thing to be tested
- Starts with comment line `// act`
- **Assert**
- Should elicit some sort of response
- Starts with comment line `// assert`
### Stubs
- Stubs are defined in [`./tests/stubs`](./../tests/unit/stubs)
- They implement dummy behavior to be functional

View File

@@ -1 +1 @@
<mxfile host="www.draw.io" modified="2019-12-27T03:04:27.829Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" etag="O-1eaon4mqUmgvki0auB" version="12.4.3" pages="1"><diagram id="rhL8jzEM8kVVyiS98U7u" name="Page-1">3Zpdk6I4FIZ/jZdakPDlpa3tzlTNbnVVV83OzE1XhACZAUKF2Or8+k0wCBhau1f8GrpayUkC5HlzTg7IAE7T9V8M5fHfNMDJABjBegBnAwA8YIpPadhsDS50t4aIkWBrMmvDM/mNldFQ1iUJcNFqyClNOMnbRp9mGfZ5y4YYo6t2s5Am7bPmKMKa4dlHiW79lwQ8VsOyjdr+CZMors5sGqomRVVjZShiFNBVwwQfB3DKKOXbvXQ9xYlkV3HZ9pu/Ubu7MIYz/p4OD0/efJxMXmZfs6kd/vj2Txp+HlrVxfFNNWIcCACqSBmPaUQzlDzW1gdGl1mA5WENUarbfKE0F0ZTGH9izjdKTbTkVJhiniaqFq8J/ya7j2xV+t6oma3VkcvCpipknG0anWTxe7Ou7laWqn4FZ/QXntKEsnJ8cFxuomY7cjncN4kqU0GXzMcHMFYzE7EI8wPtwE534S+YplhcqejHcII4eW1fB1IzN9q1q8UVO0rfj2gNrqL1TreWarWI96AbPFG3suuEMbRpNMgpyXjROPKTNIgGKnZC5Zsqctp77n24NTDtvQmzPX09fXbjOGFGwatGj0bsMEbAfmf4uONpaF0zfKjjvqJkqc70xHAhxirOTTNtIrRlXsWE4+cclRhWIlNoSxqSJGkwDmzsBVYXfQ8soOPIHjTj1TyrrgwzjteHxdDhqQ6O23Yf5U2res2H1coeN9Z7xzgTbehc07XMlmu907PMtmeBm3AtcCHX+n8RftwO2iZsBe2j7YHrnj/IQ+9eUsSbmlCnpgwnRQ+gxepJnifE7yNUtwKvFrpDW/51ieGUmzpCw77ddiKdFMXtdhTf3Yw1wzjoCOPu2cK4fS/Oc9P3V/AeEiSoOd2Mpoj0mxotzCAIO7mbhgvH+Ayp0XjPqca6UwGrw6nsczmVpYH+nIUMCSJLny8Z7hV46PnY97uALzzbso0zAHf3clGnA7h3ySjmaMDxK1aZzRugzY+DDrHTDTpwxwvD6GeF8PbYujpbtwMtOBdaV0PLcE4Lwqk6+F3jhR0L8EXxenqoeMw44ffIdj8uXB3uWIPLY4ZRQLLoD8B79dBg6ll8Kn9e6TfsLpDvBbALLoCWZQf9wBXJSRuuYelw7ZHZgRecC6+er4ldEhy7R7pVxMA2R+Obg2xrkAuO+KEU7R18e6C15+xm9ZTjiqjOk2X1MbOc47DMrqej52Olp02o+YDDSGhE/NsDZ+pLymXBVWPoBjfaiNFfG9rtMdMXCg1SEaNc7pK0fJ+giUTCEHyTSUKiTNi4fIizs35BC5w8yYRfTlw4W1DOaSoaJLLiAfm/olKAVjokN9GkPNmkyLfvPcg0CFWFkKylZA/qemYx5/KFiYkkAeZ+kFkj4tMsJEJaNvLFGcFcrHxIfEm7iDlz4UK0GKIsGC6Y+JQmW6Ykc+i4L1+X+GfxIpuINMUb5VXG1+t9r60Jb1/yMQPQ16771x1+WPchCugC79QHnvfyjIqiR91NcGPC6yvx/QtvHhV+ka3Ep5B1Lf7FnlgYpFk+3Sp6Uhq4+zkqBCNdbQh1tStb/2rrucT9q20cVTukLMLZsMBU/rI/d8Stwjwn5fPTYkhzTlLyu0wKenR0WN0O7MTXpbc6pLc+LL0o1m/WbX8JrV9PhI//AQ==</diagram></mxfile> <mxfile host="Electron" modified="2021-01-31T12:32:01.751Z" agent="5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.1.8 Chrome/87.0.4280.88 Electron/11.1.1 Safari/537.36" etag="OTbSPW1ZOLwiPL6mt-j9" version="14.1.8" type="device"><diagram id="rhL8jzEM8kVVyiS98U7u" name="Page-1">3VtZd6JKF/01/dhZjA6PREjCXRbGFpM2L70QCYIoLsUI/PpvnwKcMD3cazrJl6wEqfHU3vvsKoz5Infm6e3KWU5ZPPGiL5IwSb/I+hdJUsUmflNBVhQoDaEo8FfBpCgS9wWDIPfKwqrZJph466OGSRxHSbA8LnTjxcJzk6MyZ7WKt8fNnuPoeNal43u1goHrRPXSx2CSTIvSlirsy++8wJ9WM4tCWTN3qsZlwXrqTOLtQZFsfJE7qzhOilfztONFhF2FS9Hv5pXaXWArb5H8Tofr+9ZNO9J+6A+Ljvr89N2aP5tflSq4JKtW7E0AQHkbr5Jp7McLJzL2pdereLOYeDSsgLt9m24cL1EoojD0kiQr2XQ2SYyiaTKPylovDZLv1P1KLe9GBzV6Wo7Mb7LqZpGssoNOdDs6rNt343dVv3WyimdeJ47iFV+f3OZfqKkDWGK6jjcr1/sJapUQnZXvJT9pJxXtCNGDCUp6br147iFSNFh5kZMEL8eSc0rl+rt2e3LxouT3T7iW3oXrHW9HrO1J/IC8yZfmjXfVVisnO2iwjINFsj4Y+Z4K0KD0TrnMzdI51ZP0/nlrSVRPBFNMv5fPbh3/QVHyu7rHgXcIV5L6m/bxeWSofCj7KMd9caJNOdP9yltj8Zg7XtSEcEzzdhok3mDpcFy2OCkcU/ocRNEBxhPVa02Uc+i3pLHcaFCPeJFUOjuzFVbBeqvES38KXlnbaB6nT5lN2/2eL1c7+/Rgv69OMxdHW268Z2qJR6n1m5klHmeW9B6pJb1Xav07h28fm7YoH5n2L9tLzebbm7zc+ixHxPcU1MWPDP/JPaSaV2vLZRS4l7DqI+OtWfezSt/nyGjwr3KEg/Li62ck/b6Lq8cuvnsYO7Rx6YyNN9/MxtXPkjwf6flK/pQHJLmWdHo8d4LLHo3G4mTyfBZ3UWjKbe8Njkbtk6Rq15NKUs4klfpWSaXUgDYXzysHiGzcZLPyLgr4c8v1XPcc4OOWqqjCGwDePDmLNs4A3vqbLtaoAe69eOXJ5hWgxT8H+tlrnAd60myPBeEyO0TrBNtmHdvmGWilt4K2WYN25S3jdZDE5eCfGl75zAb8V+Ft1a3CWCRB8hmxPfWFdwe3XQM3ma48ZxIs/P8DeN/dGsT6KX5Of165rO2OBU/2GufAFbyW0GpdBlwcTo7BFZQ6uOqVeAZe6a3grZ/X8DKY/OoZ6aNCLKniVfvDgazWQF4nTvKzI9qf42vo9H0ZFE9MQKze/XhHCP/K6euSGJ6+mXoORPHcu6lvh2H9mOUcviEiRLEfuJ8HULG+Nf1dQHdr2AOazamXG0eR5xKqH1efHw/N+lZUA289dZb0MpjzTywcQkUrh5QjLQr8BcoSeptoV9p1xl50T48UJHVZH8dJEs/RIKKKa8ed+ZyYowMXfaEJn0xbL4tPVtBBy6lunoOUqLwu49GnSUIfydAICenGnSyUq8CNF88BKF9duZhRusHe6uBC5VDHDZIuXn91FpOv4xV+U5FKh54budH88bDxwvUPaoKDUOtqWZ0pL/pk3aa/UpxyryhXap38fenl6a9vkp+ffvmP6f/qTOKxtxOB1Gr9GDjr9QXpl9TWVVP9PQlI5zUgvZkI6tv85xeB+EsRjBdb/AbFKX7wCtsyFdNbausLsS7LyhHlcuOqVT+QSO0zht9+K7Lr55HPT7bwS7Kf45XvLb6uvZg+TXDTAC83y4C/Z7v+Gi+TYB7k/ER2wZzfvUtSsS/XqK/0cUh9VfZfqH/Y3Hrj5vegr7fv2awhLwLh8cyf5n7J/CWIPS+Z8+Qe8V/RzamUteJWulm/gJzrlM58nfs7S3rKrpXxY7pxcyFw7r4Jrh6/dOWJPMlUmWXqizt3X1iobVmnnU/mbmDeTZZPd9/i+4GZWfYoMG+nkfM4iSe6ELBwKJnBteQ8Psj9eVtBm62paz4v103RCkzMfX/r+k/zaD1Gj/G8vXkamMV9R8wmj2l0P/gnmswfNmPp28wMldZIirKRlEbm7dNyfLttmwHL+uE/N0wwMLtFs6Qs9Om1unt9Z7bN0Mh6HfPlPkwXB33Vb7OH62/hLCjLEnfxsH6yi1i8+UM2zniUd9fTya3vPyFK2zawfkVkuulbubGxwijs2qbUBS7WQBC6oZtbgSBYgSJaobthoZl27SGVSyzQUqujCMweJqhPcfWZrW0se5Z17f6G5SMZ2KDtKLcGGq6mYOpMxb1gzY0MV8nKtOoKPA2fdajdKMOcVC4yiSVWpsiWbSbMZhjbxNgzxDFCHKZIYzEepylYmZAjnhx1PuJRmO6jD9aUz1LiqBsaQq8jpKjLMJZv6RjH9uWubaTd0M+tx3MxaRTTpmcPJawb8/Zlby4kWBvKZqqpu2jLtpbEUo4jxwBz2kOxWLshW4GWsUDJe3bfZ/kM8ZBmGOZkSq+j5bQ+FjJoiWL1wYO76el+jv4y4tiygaaijYR+GBu42po0sk1wxfEtr4hzoG27wIjpmkj4WzraZVgvjU9zow/TXQXrABY+8Qeeh4SJivi2TGcJHz830MYgvMCvoIBrzkcP2Fq8DeFPPDDU9WX0zxnVvcqpkHfDmQAN4DpUvYArdzv6/i02b/ttcyakPYo3NGl9W8xHvFPcWyg+QY5izQblGcadIXcFgc+n+xwP8CsWmhymLBBIu+grEB8CcEW8Ptqwck0zWgv0CDz1IepGGLt/Hs8O8BwoWyuc+Wifs9wKGdeARtio4C4j3Vvh0IfuVHALfg1gPEOsBtbaV0x9RPFluE8pZ4ABrn3oxkQe+FuKE30zWgt+gP1MfY1bU+8Tf9Ay8kDvS6RDi17nlAsjhWvtkdY7pHFIY4h1poInmluxOhrmdIUe8gXYiozj7UNrTCy4nAms0KqAssQKSQt96Jj0MiIfkICpwmwXGoWOwdPrsRJOrgz8ZCt/CLkubZYjZ0n3AmLJMJZq6W5i0Rj5CJgMEYNJmMiESY+0w/EeIUcpTkMs8sYAf8CNuAk06Arj5CbnGXy/ih/4Jx3JRR4b4hkdKtxrMi3t6S7laT7KMa+tCUUu+6RB4lXqwc8QP7h3eU739D7mIe0apPuUUUz6kDREeKo9e5T0qL3OuEYseFGXvIzy5/bVvCFNy/C3jPN2W3ohjYs1kkd0aT2hKUKLaW+gKD3ySfJpvQ//MEhjyG3kAvyD8XgE0qxI8XA/DYeUx7S2DJiCY7STXo0HOsB6uIZd1ZKXCTiBRkzu9z19BLwoj42kyEsT+qdYfLHUYIo4Ea+m0N6CeAXue1inRdjkM65dC+OAb8xtSow8PSM9ukmP+1c/ezVXqd4eCZQnaC+McoPvNSzTuIfhWuwPpF/eZkQ5grWT12jEPfj0aayUv87dpPBt4piwYrTPUR5R/iiv40T+oEHPtA/McuSpCK1k2DtoPInZ/4RYM+FAuSgSLvBZeBe45X7F84G4zfg+m1VtuYbLMu20n1/1IzzIO7G/kW/tvL+o2/Xf9ava0rqrGE777eYF5zx/4Iamfj0lbjAW4uvnZZ7Bi4wyV3gcx/WYAzGU9dcN2+7THqLynON7Vz8d5aSZ4UE8/dN4hAMcgAnXu0px4QwjHuC2679fB28LXWrJHhPe73DMPaaFXgn7Au+BdgbvPTZFjgAdySjXDB/RWbVu0jr5vQLfOq2vtFPUL+Kgmyst9/ZGcDrXM5wcLcxH5wHSqkjnLkv/Fv4Rd3WshDpW7BQr9V9j1flbWGEs7rczn2so9zOce6CxmVLstxo8XeBeWJwrNRWemvL9Jx8mHMP8aQo/ynCu86trNU9RT35J50BXIo/r2eRptNe78B/yf9pvyZ/orGls6ZxSxK3RtYxBK2OqYtCKmIr85vtOEdNTwwxadFLvtBf34fblCUroyniiyRV6hrrMnz8l+Uo9fuNJlurPoY0zz6GNP34Oxe3+v8qKTwHv/zVPNv4H</diagram></mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 89 KiB

2623
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "privacy.sexy", "name": "privacy.sexy",
"version": "0.9.0", "version": "0.10.1",
"private": true, "private": true,
"description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆", "description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆",
"author": "undergroundwires", "author": "undergroundwires",
@@ -21,47 +21,49 @@
}, },
"main": "background.js", "main": "background.js",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.32", "@fortawesome/fontawesome-svg-core": "^1.2.34",
"@fortawesome/free-brands-svg-icons": "^5.15.1", "@fortawesome/free-brands-svg-icons": "^5.15.2",
"@fortawesome/free-regular-svg-icons": "^5.15.1", "@fortawesome/free-regular-svg-icons": "^5.15.2",
"@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/free-solid-svg-icons": "^5.15.2",
"@fortawesome/vue-fontawesome": "^2.0.2", "@fortawesome/vue-fontawesome": "^2.0.2",
"@juggle/resize-observer": "^3.3.0",
"ace-builds": "^1.4.12", "ace-builds": "^1.4.12",
"core-js": "^3.6.5", "core-js": "^3.9.1",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"inversify": "^5.0.5", "inversify": "^5.0.5",
"liquor-tree": "^0.2.70", "liquor-tree": "^0.2.70",
"v-tooltip": "2.0.2", "v-tooltip": "2.1.2",
"vue": "^2.6.12", "vue": "^2.6.12",
"vue-class-component": "^7.2.6", "vue-class-component": "^7.2.6",
"vue-js-modal": "^2.0.0-rc.6", "vue-js-modal": "^2.0.0-rc.6",
"vue-property-decorator": "^9.1.2" "vue-property-decorator": "^9.1.2"
}, },
"devDependencies": { "devDependencies": {
"@types/ace": "0.0.44", "@types/ace": "0.0.45",
"@types/chai": "^4.2.14", "@types/chai": "^4.2.15",
"@types/file-saver": "^2.0.1", "@types/file-saver": "^2.0.1",
"@types/mocha": "^8.2.0", "@types/mocha": "^8.2.1",
"@vue/cli-plugin-babel": "^4.5.10", "@vue/cli-plugin-babel": "^4.5.11",
"@vue/cli-plugin-typescript": "^4.5.9", "@vue/cli-plugin-typescript": "^4.5.11",
"@vue/cli-plugin-unit-mocha": "^4.5.9", "@vue/cli-plugin-unit-mocha": "^4.5.11",
"@vue/cli-service": "^4.5.9", "@vue/cli-service": "^4.5.11",
"@vue/test-utils": "1.1.2", "@vue/test-utils": "1.1.3",
"chai": "^4.2.0", "chai": "^4.3.3",
"electron": "^11.1.0", "electron": "^12.0.1",
"electron-devtools-installer": "^3.1.1", "electron-devtools-installer": "^3.1.1",
"electron-log": "^4.3.1", "electron-log": "^4.3.2",
"electron-updater": "^4.3.5", "electron-updater": "^4.3.8",
"js-yaml-loader": "^1.2.2", "js-yaml-loader": "^1.2.2",
"markdownlint-cli": "^0.26.0", "markdownlint-cli": "^0.27.1",
"remark-cli": "^9.0.0", "remark-cli": "^9.0.0",
"remark-lint-no-dead-urls": "^1.1.0", "remark-lint-no-dead-urls": "^1.1.0",
"remark-preset-lint-consistent": "^4.0.0", "remark-preset-lint-consistent": "^4.0.0",
"remark-validate-links": "^10.0.2", "remark-validate-links": "^10.0.3",
"sass": "^1.30.0", "sass": "^1.32.8",
"sass-loader": "^10.1.0", "sass-loader": "^10.1.1",
"typescript": "^4.1.3", "tslib": "^2.1.0",
"vue-cli-plugin-electron-builder": "^2.0.0-rc.5", "typescript": "^4.2.3",
"vue-cli-plugin-electron-builder": "^2.0.0-rc.6",
"vue-template-compiler": "^2.6.12", "vue-template-compiler": "^2.6.12",
"yaml-lint": "^1.2.4" "yaml-lint": "^1.2.4"
}, },

View File

@@ -0,0 +1,21 @@
import { IApplication } from '@/domain/IApplication';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import { IApplicationFactory } from './IApplicationFactory';
import { parseApplication } from './Parser/ApplicationParser';
export type ApplicationGetter = () => IApplication;
const ApplicationGetter: ApplicationGetter = parseApplication;
export class ApplicationFactory implements IApplicationFactory {
public static readonly Current: IApplicationFactory = new ApplicationFactory(ApplicationGetter);
private readonly getter: AsyncLazy<IApplication>;
protected constructor(costlyGetter: ApplicationGetter) {
if (!costlyGetter) {
throw new Error('undefined getter');
}
this.getter = new AsyncLazy<IApplication>(() => Promise.resolve(costlyGetter()));
}
public getAppAsync(): Promise<IApplication> {
return this.getter.getValueAsync();
}
}

View File

@@ -0,0 +1,21 @@
// Compares to Array<T> objects for equality, ignoring order
export function scrambledEqual<T>(array1: readonly T[], array2: readonly T[]) {
if (!array1) { throw new Error('undefined first array'); }
if (!array2) { throw new Error('undefined second array'); }
const sortedArray1 = sort(array1);
const sortedArray2 = sort(array2);
return sequenceEqual(sortedArray1, sortedArray2);
function sort(array: readonly T[]) {
return array.slice().sort();
}
}
// Compares to Array<T> objects for equality in same order
export function sequenceEqual<T>(array1: readonly T[], array2: readonly T[]) {
if (!array1) { throw new Error('undefined first array'); }
if (!array2) { throw new Error('undefined second array'); }
if (array1.length !== array2.length) {
return false;
}
return array1.every((val, index) => val === array2[index]);
}

View File

@@ -1,6 +1,6 @@
// Because we cannot do "T extends enum" 😞 https://github.com/microsoft/TypeScript/issues/30611 // Because we cannot do "T extends enum" 😞 https://github.com/microsoft/TypeScript/issues/30611
type EnumType = number | string; export type EnumType = number | string;
type EnumVariable<T extends EnumType, TEnumValue extends EnumType> = { [key in T]: TEnumValue }; export type EnumVariable<T extends EnumType, TEnumValue extends EnumType> = { [key in T]: TEnumValue };
export interface IEnumParser<TEnum> { export interface IEnumParser<TEnum> {
parseEnum(value: string, propertyName: string): TEnum; parseEnum(value: string, propertyName: string): TEnum;
@@ -41,3 +41,14 @@ export function getEnumValues<T extends EnumType, TEnumValue extends EnumType>(
return getEnumNames(enumVariable) return getEnumNames(enumVariable)
.map((level) => enumVariable[level]) as TEnumValue[]; .map((level) => enumVariable[level]) as TEnumValue[];
} }
export function assertInRange<T extends EnumType, TEnumValue extends EnumType>(
value: TEnumValue,
enumVariable: EnumVariable<T, TEnumValue>) {
if (value === undefined) {
throw new Error('undefined enum value');
}
if (!(value in enumVariable)) {
throw new RangeError(`enum value "${value}" is out of range`);
}
}

View File

@@ -0,0 +1,5 @@
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
export interface IScriptingLanguageFactory<T> {
create(language: ScriptingLanguage): T;
}

View File

@@ -0,0 +1,31 @@
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IScriptingLanguageFactory } from './IScriptingLanguageFactory';
import { assertInRange } from '@/application/Common/Enum';
type Getter<T> = () => T;
export abstract class ScriptingLanguageFactory<T> implements IScriptingLanguageFactory<T> {
private readonly getters = new Map<ScriptingLanguage, Getter<T>>();
public create(language: ScriptingLanguage): T {
assertInRange(language, ScriptingLanguage);
if (!this.getters.has(language)) {
throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
}
const getter = this.getters.get(language);
const instance = getter();
return instance;
}
protected registerGetter(language: ScriptingLanguage, getter: Getter<T>) {
assertInRange(language, ScriptingLanguage);
if (!getter) {
throw new Error('undefined getter');
}
if (this.getters.has(language)) {
throw new Error(`${ScriptingLanguage[language]} is already registered`);
}
this.getters.set(language, getter);
}
}

View File

@@ -4,12 +4,13 @@ import { CategoryCollectionState } from './State/CategoryCollectionState';
import { IApplication } from '@/domain/IApplication'; import { IApplication } from '@/domain/IApplication';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { Signal } from '@/infrastructure/Events/Signal'; import { EventSource } from '@/infrastructure/Events/EventSource';
import { assertInRange } from '@/application/Common/Enum';
type StateMachine = Map<OperatingSystem, ICategoryCollectionState>; type StateMachine = Map<OperatingSystem, ICategoryCollectionState>;
export class ApplicationContext implements IApplicationContext { export class ApplicationContext implements IApplicationContext {
public readonly contextChanged = new Signal<IApplicationContextChangedEvent>(); public readonly contextChanged = new EventSource<IApplicationContextChangedEvent>();
public collection: ICategoryCollection; public collection: ICategoryCollection;
public currentOs: OperatingSystem; public currentOs: OperatingSystem;
@@ -22,7 +23,7 @@ export class ApplicationContext implements IApplicationContext {
public readonly app: IApplication, public readonly app: IApplication,
initialContext: OperatingSystem) { initialContext: OperatingSystem) {
validateApp(app); validateApp(app);
validateOs(initialContext); assertInRange(initialContext, OperatingSystem);
this.states = initializeStates(app); this.states = initializeStates(app);
this.changeContext(initialContext); this.changeContext(initialContext);
} }
@@ -50,18 +51,6 @@ function validateApp(app: IApplication) {
} }
} }
function validateOs(os: OperatingSystem) {
if (os === undefined) {
throw new Error('undefined os');
}
if (os === OperatingSystem.Unknown) {
throw new Error('unknown os');
}
if (!(os in OperatingSystem)) {
throw new Error(`os "${os}" is out of range`);
}
}
function initializeStates(app: IApplication): StateMachine { function initializeStates(app: IApplication): StateMachine {
const machine = new Map<OperatingSystem, ICategoryCollectionState>(); const machine = new Map<OperatingSystem, ICategoryCollectionState>();
for (const collection of app.collections) { for (const collection of app.collections) {

View File

@@ -4,15 +4,15 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
import { Environment } from '../Environment/Environment'; import { Environment } from '../Environment/Environment';
import { IApplication } from '@/domain/IApplication'; import { IApplication } from '@/domain/IApplication';
import { IEnvironment } from '../Environment/IEnvironment'; import { IEnvironment } from '../Environment/IEnvironment';
import { parseApplication } from '../Parser/ApplicationParser'; import { IApplicationFactory } from '../IApplicationFactory';
import { ApplicationFactory } from '../ApplicationFactory';
export type ApplicationParserType = () => IApplication; export async function buildContextAsync(
const ApplicationParser: ApplicationParserType = parseApplication; factory: IApplicationFactory = ApplicationFactory.Current,
environment = Environment.CurrentEnvironment): Promise<IApplicationContext> {
export function buildContext( if (!factory) { throw new Error('undefined factory'); }
parser = ApplicationParser, if (!environment) { throw new Error('undefined environment'); }
environment = Environment.CurrentEnvironment): IApplicationContext { const app = await factory.getAppAsync();
const app = parser();
const os = getInitialOs(app, environment); const os = getInitialOs(app, environment);
return new ApplicationContext(app, os); return new ApplicationContext(app, os);
} }

View File

@@ -1,12 +1,12 @@
import { ICategoryCollectionState } from './State/ICategoryCollectionState'; import { ICategoryCollectionState } from './State/ICategoryCollectionState';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { ISignal } from '@/infrastructure/Events/ISignal'; import { IEventSource } from '@/infrastructure/Events/IEventSource';
import { IApplication } from '@/domain/IApplication'; import { IApplication } from '@/domain/IApplication';
export interface IApplicationContext { export interface IApplicationContext {
readonly app: IApplication; readonly app: IApplication;
readonly state: ICategoryCollectionState; readonly state: ICategoryCollectionState;
readonly contextChanged: ISignal<IApplicationContextChangedEvent>; readonly contextChanged: IEventSource<IApplicationContextChangedEvent>;
changeContext(os: OperatingSystem): void; changeContext(os: OperatingSystem): void;
} }

View File

@@ -4,13 +4,13 @@ import { ICodeChangedEvent } from './Event/ICodeChangedEvent';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection'; import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection';
import { UserScriptGenerator } from './Generation/UserScriptGenerator'; import { UserScriptGenerator } from './Generation/UserScriptGenerator';
import { Signal } from '@/infrastructure/Events/Signal'; import { EventSource } from '@/infrastructure/Events/EventSource';
import { IApplicationCode } from './IApplicationCode'; import { IApplicationCode } from './IApplicationCode';
import { IUserScriptGenerator } from './Generation/IUserScriptGenerator'; import { IUserScriptGenerator } from './Generation/IUserScriptGenerator';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
export class ApplicationCode implements IApplicationCode { export class ApplicationCode implements IApplicationCode {
public readonly changed = new Signal<ICodeChangedEvent>(); public readonly changed = new EventSource<ICodeChangedEvent>();
public current: string; public current: string;
private scriptPositions = new Map<SelectedScript, CodePosition>(); private scriptPositions = new Map<SelectedScript, CodePosition>();

View File

@@ -1,15 +1,14 @@
import { ScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/ScriptingLanguageFactory';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ICodeBuilder } from './ICodeBuilder'; import { ICodeBuilder } from './ICodeBuilder';
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
import { BatchBuilder } from './Languages/BatchBuilder'; import { BatchBuilder } from './Languages/BatchBuilder';
import { ShellBuilder } from './Languages/ShellBuilder'; import { ShellBuilder } from './Languages/ShellBuilder';
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
export class CodeBuilderFactory implements ICodeBuilderFactory { export class CodeBuilderFactory extends ScriptingLanguageFactory<ICodeBuilder> implements ICodeBuilderFactory {
public create(language: ScriptingLanguage): ICodeBuilder { constructor() {
switch (language) { super();
case ScriptingLanguage.shellscript: return new ShellBuilder(); this.registerGetter(ScriptingLanguage.shellscript, () => new ShellBuilder());
case ScriptingLanguage.batchfile: return new BatchBuilder(); this.registerGetter(ScriptingLanguage.batchfile, () => new BatchBuilder());
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
}
} }
} }

View File

@@ -1,6 +1,5 @@
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ICodeBuilder } from './ICodeBuilder'; import { ICodeBuilder } from './ICodeBuilder';
import { IScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/IScriptingLanguageFactory';
export interface ICodeBuilderFactory { export interface ICodeBuilderFactory extends IScriptingLanguageFactory<ICodeBuilder> {
create(language: ScriptingLanguage): ICodeBuilder;
} }

View File

@@ -5,6 +5,12 @@ export class BatchBuilder extends CodeBuilder {
return '::'; return '::';
} }
protected writeStandardOut(text: string): string { protected writeStandardOut(text: string): string {
return `echo ${text}`; return `echo ${escapeForEcho(text)}`;
} }
} }
function escapeForEcho(text: string) {
return text
.replace(/&/g, '^&')
.replace(/%/g, '%%');
}

View File

@@ -5,6 +5,11 @@ export class ShellBuilder extends CodeBuilder {
return '#'; return '#';
} }
protected writeStandardOut(text: string): string { protected writeStandardOut(text: string): string {
return `echo '${text}'`; return `echo '${escapeForEcho(text)}'`;
} }
} }
function escapeForEcho(text: string) {
return text
.replace(/'/g, '\'\\\'\'');
}

View File

@@ -1,7 +1,7 @@
import { ICodeChangedEvent } from './Event/ICodeChangedEvent'; import { ICodeChangedEvent } from './Event/ICodeChangedEvent';
import { ISignal } from '@/infrastructure/Events/ISignal'; import { IEventSource } from '@/infrastructure/Events/IEventSource';
export interface IApplicationCode { export interface IApplicationCode {
readonly changed: ISignal<ICodeChangedEvent>; readonly changed: IEventSource<ICodeChangedEvent>;
readonly current: string; readonly current: string;
} }

View File

@@ -1,10 +1,10 @@
import { ISignal } from '@/infrastructure/Events/ISignal'; import { IEventSource } from '@/infrastructure/Events/IEventSource';
import { IFilterResult } from './IFilterResult'; import { IFilterResult } from './IFilterResult';
export interface IUserFilter { export interface IUserFilter {
readonly currentFilter: IFilterResult | undefined; readonly currentFilter: IFilterResult | undefined;
readonly filtered: ISignal<IFilterResult>; readonly filtered: IEventSource<IFilterResult>;
readonly filterRemoved: ISignal<void>; readonly filterRemoved: IEventSource<void>;
setFilter(filter: string): void; setFilter(filter: string): void;
removeFilter(): void; removeFilter(): void;
} }

View File

@@ -2,12 +2,12 @@ import { IScript } from '@/domain/IScript';
import { FilterResult } from './FilterResult'; import { FilterResult } from './FilterResult';
import { IFilterResult } from './IFilterResult'; import { IFilterResult } from './IFilterResult';
import { IUserFilter } from './IUserFilter'; import { IUserFilter } from './IUserFilter';
import { Signal } from '@/infrastructure/Events/Signal'; import { EventSource } from '@/infrastructure/Events/EventSource';
import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { ICategoryCollection } from '@/domain/ICategoryCollection';
export class UserFilter implements IUserFilter { export class UserFilter implements IUserFilter {
public readonly filtered = new Signal<IFilterResult>(); public readonly filtered = new EventSource<IFilterResult>();
public readonly filterRemoved = new Signal<void>(); public readonly filterRemoved = new EventSource<void>();
public currentFilter: IFilterResult | undefined; public currentFilter: IFilterResult | undefined;
constructor(private collection: ICategoryCollection) { constructor(private collection: ICategoryCollection) {

View File

@@ -1,12 +1,11 @@
import { SelectedScript } from './SelectedScript'; import { SelectedScript } from './SelectedScript';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory'; import { ICategory } from '@/domain/ICategory';
import { ISignal } from '@/infrastructure/Events/ISignal'; import { IEventSource } from '@/infrastructure/Events/IEventSource';
export interface IUserSelection { export interface IUserSelection {
readonly changed: ISignal<ReadonlyArray<SelectedScript>>; readonly changed: IEventSource<ReadonlyArray<SelectedScript>>;
readonly selectedScripts: ReadonlyArray<SelectedScript>; readonly selectedScripts: ReadonlyArray<SelectedScript>;
readonly totalSelected: number;
areAllSelected(category: ICategory): boolean; areAllSelected(category: ICategory): boolean;
isAnySelected(category: ICategory): boolean; isAnySelected(category: ICategory): boolean;
removeAllInCategory(categoryId: number): void; removeAllInCategory(categoryId: number): void;

View File

@@ -2,13 +2,13 @@ import { SelectedScript } from './SelectedScript';
import { IUserSelection } from './IUserSelection'; import { IUserSelection } from './IUserSelection';
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository'; import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { Signal } from '@/infrastructure/Events/Signal'; import { EventSource } from '@/infrastructure/Events/EventSource';
import { IRepository } from '@/infrastructure/Repository/IRepository'; import { IRepository } from '@/infrastructure/Repository/IRepository';
import { ICategory } from '@/domain/ICategory'; import { ICategory } from '@/domain/ICategory';
import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { ICategoryCollection } from '@/domain/ICategoryCollection';
export class UserSelection implements IUserSelection { export class UserSelection implements IUserSelection {
public readonly changed = new Signal<ReadonlyArray<SelectedScript>>(); public readonly changed = new EventSource<ReadonlyArray<SelectedScript>>();
private readonly scripts: IRepository<string, SelectedScript>; private readonly scripts: IRepository<string, SelectedScript>;
constructor( constructor(
@@ -101,10 +101,6 @@ export class UserSelection implements IUserSelection {
return this.scripts.getItems(); return this.scripts.getItems();
} }
public get totalSelected(): number {
return this.scripts.getItems().length;
}
public selectAll(): void { public selectAll(): void {
for (const script of this.collection.getAllScripts()) { for (const script of this.collection.getAllScripts()) {
if (!this.scripts.exists(script.id)) { if (!this.scripts.exists(script.id)) {

View File

@@ -4,17 +4,17 @@ import { IBrowserOsDetector } from './IBrowserOsDetector';
export class BrowserOsDetector implements IBrowserOsDetector { export class BrowserOsDetector implements IBrowserOsDetector {
private readonly detectors = BrowserDetectors; private readonly detectors = BrowserDetectors;
public detect(userAgent: string): OperatingSystem { public detect(userAgent: string): OperatingSystem | undefined {
if (!userAgent) { if (!userAgent) {
return OperatingSystem.Unknown; return undefined;
} }
for (const detector of this.detectors) { for (const detector of this.detectors) {
const os = detector.detect(userAgent); const os = detector.detect(userAgent);
if (os !== OperatingSystem.Unknown) { if (os !== undefined) {
return os; return os;
} }
} }
return OperatingSystem.Unknown; return undefined;
} }
} }

View File

@@ -29,10 +29,10 @@ export class DetectorBuilder {
throw new Error('User agent is null or undefined'); throw new Error('User agent is null or undefined');
} }
if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) { if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) {
return OperatingSystem.Unknown; return undefined;
} }
if (this.notExistingPartsInUserAgent.some((part) => userAgent.includes(part))) { if (this.notExistingPartsInUserAgent.some((part) => userAgent.includes(part))) {
return OperatingSystem.Unknown; return undefined;
} }
return this.os; return this.os;
} }

View File

@@ -1,5 +1,5 @@
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
export interface IBrowserOsDetector { export interface IBrowserOsDetector {
detect(userAgent: string): OperatingSystem; detect(userAgent: string): OperatingSystem | undefined;
} }

View File

@@ -44,7 +44,7 @@ function getProcessPlatform(variables: IEnvironmentVariables): string {
return variables.process.platform; return variables.process.platform;
} }
function getDesktopOsType(processPlatform: string): OperatingSystem { function getDesktopOsType(processPlatform: string): OperatingSystem | undefined {
// https://nodejs.org/api/process.html#process_process_platform // https://nodejs.org/api/process.html#process_process_platform
if (processPlatform === 'darwin') { if (processPlatform === 'darwin') {
return OperatingSystem.macOS; return OperatingSystem.macOS;
@@ -53,7 +53,7 @@ function getDesktopOsType(processPlatform: string): OperatingSystem {
} else if (processPlatform === 'linux') { } else if (processPlatform === 'linux') {
return OperatingSystem.Linux; return OperatingSystem.Linux;
} }
return OperatingSystem.Unknown; return undefined;
} }
function isDesktop(variables: IEnvironmentVariables): boolean { function isDesktop(variables: IEnvironmentVariables): boolean {

View File

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

View File

@@ -2,19 +2,20 @@ import { Category } from '@/domain/Category';
import { CollectionData } from 'js-yaml-loader!@/*'; import { CollectionData } from 'js-yaml-loader!@/*';
import { parseCategory } from './CategoryParser'; import { parseCategory } from './CategoryParser';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { parseScriptingDefinition } from './ScriptingDefinitionParser';
import { createEnumParser } from '../Common/Enum'; import { createEnumParser } from '../Common/Enum';
import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { CategoryCollection } from '@/domain/CategoryCollection'; import { CategoryCollection } from '@/domain/CategoryCollection';
import { IProjectInformation } from '@/domain/IProjectInformation'; import { IProjectInformation } from '@/domain/IProjectInformation';
import { CategoryCollectionParseContext } from './Script/CategoryCollectionParseContext'; import { CategoryCollectionParseContext } from './Script/CategoryCollectionParseContext';
import { ScriptingDefinitionParser } from './ScriptingDefinition/ScriptingDefinitionParser';
export function parseCategoryCollection( export function parseCategoryCollection(
content: CollectionData, content: CollectionData,
info: IProjectInformation, info: IProjectInformation,
osParser = createEnumParser(OperatingSystem)): ICategoryCollection { osParser = createEnumParser(OperatingSystem)): ICategoryCollection {
validate(content); validate(content);
const scripting = parseScriptingDefinition(content.scripting, info); const scripting = new ScriptingDefinitionParser()
.parse(content.scripting, info);
const context = new CategoryCollectionParseContext(content.functions, scripting); const context = new CategoryCollectionParseContext(content.functions, scripting);
const categories = new Array<Category>(); const categories = new Array<Category>();
for (const action of content.actions) { for (const action of content.actions) {

View File

@@ -1,6 +1,6 @@
import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ILanguageSyntax } from '@/domain/ScriptCode'; import { ILanguageSyntax } from '@/domain/ScriptCode';
import { FunctionData } from 'js-yaml-loader!*'; import { FunctionData } from 'js-yaml-loader!@/*';
import { IScriptCompiler } from './Compiler/IScriptCompiler'; import { IScriptCompiler } from './Compiler/IScriptCompiler';
import { ScriptCompiler } from './Compiler/ScriptCompiler'; import { ScriptCompiler } from './Compiler/ScriptCompiler';
import { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext'; import { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';

View File

@@ -0,0 +1,35 @@
import { ExpressionPosition } from './ExpressionPosition';
import { ExpressionArguments, IExpression } from './IExpression';
export type ExpressionEvaluator = (args?: ExpressionArguments) => string;
export class Expression implements IExpression {
constructor(
public readonly position: ExpressionPosition,
public readonly evaluator: ExpressionEvaluator,
public readonly parameters: readonly string[] = new Array<string>()) {
if (!position) {
throw new Error('undefined position');
}
if (!evaluator) {
throw new Error('undefined evaluator');
}
}
public evaluate(args?: ExpressionArguments): string {
args = filterUnusedArguments(this.parameters, args);
return this.evaluator(args);
}
}
function filterUnusedArguments(
parameters: readonly string[], args: ExpressionArguments): ExpressionArguments {
let result: ExpressionArguments = {};
for (const parameter of Object.keys(args)) {
if (parameters.includes(parameter)) {
result = {
...result,
[parameter]: args[parameter],
};
}
}
return result;
}

View File

@@ -0,0 +1,15 @@
export class ExpressionPosition {
constructor(
public readonly start: number,
public readonly end: number) {
if (start === end) {
throw new Error(`no length (start = end = ${start})`);
}
if (start > end) {
throw Error(`start (${start}) after end (${end})`);
}
if (start < 0) {
throw Error(`negative start position: ${start}`);
}
}
}

View File

@@ -0,0 +1,12 @@
import { ExpressionPosition } from './ExpressionPosition';
export interface IExpression {
readonly position: ExpressionPosition;
readonly parameters?: readonly string[];
evaluate(args?: ExpressionArguments): string;
}
export interface ExpressionArguments {
readonly [parameter: string]: string;
}

View File

@@ -0,0 +1,52 @@
import { IExpressionsCompiler, ParameterValueDictionary } from './IExpressionsCompiler';
import { IExpression } from './Expression/IExpression';
import { IExpressionParser } from './Parser/IExpressionParser';
import { CompositeExpressionParser } from './Parser/CompositeExpressionParser';
export class ExpressionsCompiler implements IExpressionsCompiler {
public constructor(private readonly extractor: IExpressionParser = new CompositeExpressionParser()) { }
public compileExpressions(code: string, parameters?: ParameterValueDictionary): string {
const expressions = this.extractor.findExpressions(code);
const requiredParameterNames = expressions.map((e) => e.parameters).filter((p) => p).flat();
const uniqueParameterNames = Array.from(new Set(requiredParameterNames));
ensureRequiredArgsProvided(uniqueParameterNames, parameters);
return compileExpressions(expressions, code, parameters);
}
}
function compileExpressions(expressions: IExpression[], code: string, parameters?: ParameterValueDictionary) {
let compiledCode = '';
expressions = expressions
.slice() // copy the array to not mutate the parameter
.sort((a, b) => b.position.start - a.position.start);
let index = 0;
while (index !== code.length) {
const nextExpression = expressions.pop();
if (nextExpression) {
compiledCode += code.substring(index, nextExpression.position.start);
const expressionCode = nextExpression.evaluate(parameters);
compiledCode += expressionCode;
index = nextExpression.position.end;
} else {
compiledCode += code.substring(index, code.length);
break;
}
}
return compiledCode;
}
function ensureRequiredArgsProvided(parameters: readonly string[], args: ParameterValueDictionary) {
parameters = parameters || [];
args = args || {};
if (!parameters.length) {
return;
}
const notProvidedParameters = parameters.filter((parameter) => !Boolean(args[parameter]));
if (notProvidedParameters.length) {
throw new Error(`parameter value(s) not provided for: ${printList(notProvidedParameters)}`);
}
}
function printList(list: readonly string[]): string {
return `"${list.join('", "')}"`;
}

View File

@@ -0,0 +1,5 @@
export interface ParameterValueDictionary { [parameterName: string]: string; }
export interface IExpressionsCompiler {
compileExpressions(code: string, parameters?: ParameterValueDictionary): string;
}

View File

@@ -0,0 +1,23 @@
import { IExpression } from '../Expression/IExpression';
import { IExpressionParser } from './IExpressionParser';
import { ParameterSubstitutionParser } from '../SyntaxParsers/ParameterSubstitutionParser';
const parsers = [
new ParameterSubstitutionParser(),
];
export class CompositeExpressionParser implements IExpressionParser {
public constructor(private readonly leafs: readonly IExpressionParser[] = parsers) {
if (leafs.some((leaf) => !leaf)) { throw new Error('undefined leaf'); }
}
public findExpressions(code: string): IExpression[] {
const expressions = new Array<IExpression>();
for (const parser of this.leafs) {
const newExpressions = parser.findExpressions(code);
if (newExpressions && newExpressions.length) {
expressions.push(...newExpressions);
}
}
return expressions;
}
}

View File

@@ -0,0 +1,5 @@
import { IExpression } from '../Expression/IExpression';
export interface IExpressionParser {
findExpressions(code: string): IExpression[];
}

View File

@@ -0,0 +1,35 @@
import { IExpressionParser } from './IExpressionParser';
import { ExpressionPosition } from '../Expression/ExpressionPosition';
import { IExpression } from '../Expression/IExpression';
import { Expression, ExpressionEvaluator } from '../Expression/Expression';
export abstract class RegexParser implements IExpressionParser {
protected abstract readonly regex: RegExp;
public findExpressions(code: string): IExpression[] {
return Array.from(this.findRegexExpressions(code));
}
protected abstract buildExpression(match: RegExpMatchArray): IPrimitiveExpression;
private* findRegexExpressions(code: string): Iterable<IExpression> {
const matches = Array.from(code.matchAll(this.regex));
for (const match of matches) {
const startPos = match.index;
const endPos = startPos + match[0].length;
let position: ExpressionPosition;
try {
position = new ExpressionPosition(startPos, endPos);
} catch (error) {
throw new Error(`[${this.constructor.name}] invalid script position: ${error.message}\nRegex ${this.regex}\nCode: ${code}`);
}
const primitiveExpression = this.buildExpression(match);
const expression = new Expression(position, primitiveExpression.evaluator, primitiveExpression.parameters);
yield expression;
}
}
}
export interface IPrimitiveExpression {
evaluator: ExpressionEvaluator;
parameters?: readonly string[];
}

View File

@@ -0,0 +1,12 @@
import { RegexParser, IPrimitiveExpression } from '../Parser/RegexParser';
export class ParameterSubstitutionParser extends RegexParser {
protected readonly regex = /{{\s*\$\s*([^}| ]+)\s*}}/g;
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
const parameterName = match[1];
return {
parameters: [ parameterName ],
evaluator: (args) => args[parameterName],
};
}
}

View File

@@ -0,0 +1,126 @@
import { FunctionData, InstructionHolder } from 'js-yaml-loader!@/*';
import { SharedFunction } from './SharedFunction';
import { SharedFunctionCollection } from './SharedFunctionCollection';
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
import { IFunctionCompiler } from './IFunctionCompiler';
import { IFunctionCallCompiler } from '../FunctionCall/IFunctionCallCompiler';
import { FunctionCallCompiler } from '../FunctionCall/FunctionCallCompiler';
export class FunctionCompiler implements IFunctionCompiler {
public static readonly instance: IFunctionCompiler = new FunctionCompiler();
protected constructor(
private readonly functionCallCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance) {
}
public compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection {
const collection = new SharedFunctionCollection();
if (!functions || !functions.length) {
return collection;
}
ensureValidFunctions(functions);
functions
.filter((func) => hasCode(func))
.forEach((func) => {
const shared = new SharedFunction(func.name, func.parameters, func.code, func.revertCode);
collection.addFunction(shared);
});
functions
.filter((func) => hasCall(func))
.forEach((func) => {
const code = this.functionCallCompiler.compileCall(func.call, collection);
const shared = new SharedFunction(func.name, func.parameters, code.code, code.revertCode);
collection.addFunction(shared);
});
return collection;
}
}
function hasCode(data: FunctionData): boolean {
return Boolean(data.code);
}
function hasCall(data: FunctionData): boolean {
return Boolean(data.call);
}
function ensureValidFunctions(functions: readonly FunctionData[]) {
ensureNoUndefinedItem(functions);
ensureNoDuplicatesInFunctionNames(functions);
ensureNoDuplicatesInParameterNames(functions);
ensureNoDuplicateCode(functions);
ensureEitherCallOrCodeIsDefined(functions);
ensureExpectedParameterNameTypes(functions);
}
function printList(list: readonly string[]): string {
return `"${list.join('","')}"`;
}
function ensureEitherCallOrCodeIsDefined(holders: readonly InstructionHolder[]) {
// Ensure functions do not define both call and code
const withBothCallAndCode = holders.filter((holder) => hasCode(holder) && hasCall(holder));
if (withBothCallAndCode.length) {
throw new Error(`both "code" and "call" are defined in ${printNames(withBothCallAndCode)}`);
}
// Ensure functions have either code or call
const hasEitherCodeOrCall = holders.filter((holder) => !hasCode(holder) && !hasCall(holder));
if (hasEitherCodeOrCall.length) {
throw new Error(`neither "code" or "call" is defined in ${printNames(hasEitherCodeOrCall)}`);
}
}
function ensureExpectedParameterNameTypes(functions: readonly FunctionData[]) {
const unexpectedFunctions = functions.filter((func) => func.parameters && !isArrayOfStrings(func.parameters));
if (unexpectedFunctions.length) {
throw new Error(`unexpected parameter name type in ${printNames(unexpectedFunctions)}`);
}
function isArrayOfStrings(value: any): boolean {
return Array.isArray(value) && value.every((item) => typeof item === 'string');
}
}
function printNames(holders: readonly InstructionHolder[]) {
return printList(holders.map((holder) => holder.name));
}
function ensureNoDuplicatesInFunctionNames(functions: readonly FunctionData[]) {
const duplicateFunctionNames = getDuplicates(functions
.map((func) => func.name.toLowerCase()));
if (duplicateFunctionNames.length) {
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
}
}
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
if (functions.some((func) => !func)) {
throw new Error(`some functions are undefined`);
}
}
function ensureNoDuplicatesInParameterNames(functions: readonly FunctionData[]) {
const functionsWithParameters = functions
.filter((func) => func.parameters && func.parameters.length > 0);
for (const func of functionsWithParameters) {
const duplicateParameterNames = getDuplicates(func.parameters);
if (duplicateParameterNames.length) {
throw new Error(`"${func.name}": duplicate parameter name: ${printList(duplicateParameterNames)}`);
}
}
}
function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
const duplicateCodes = getDuplicates(functions
.map((func) => func.code)
.filter((code) => code),
);
if (duplicateCodes.length > 0) {
throw new Error(`duplicate "code" in functions: ${printList(duplicateCodes)}`);
}
const duplicateRevertCodes = getDuplicates(functions
.filter((func) => func.revertCode)
.map((func) => func.revertCode));
if (duplicateRevertCodes.length > 0) {
throw new Error(`duplicate "revertCode" in functions: ${printList(duplicateRevertCodes)}`);
}
}
function getDuplicates(texts: readonly string[]): string[] {
return texts.filter((item, index) => texts.indexOf(item) !== index);
}

View File

@@ -0,0 +1,6 @@
import { FunctionData } from 'js-yaml-loader!@/*';
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
export interface IFunctionCompiler {
compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection;
}

View File

@@ -0,0 +1,6 @@
export interface ISharedFunction {
readonly name: string;
readonly parameters?: readonly string[];
readonly code: string;
readonly revertCode?: string;
}

View File

@@ -0,0 +1,5 @@
import { ISharedFunction } from './ISharedFunction';
export interface ISharedFunctionCollection {
getFunctionByName(name: string): ISharedFunction;
}

View File

@@ -0,0 +1,15 @@
import { ISharedFunction } from './ISharedFunction';
export class SharedFunction implements ISharedFunction {
public readonly parameters: readonly string[];
constructor(
public readonly name: string,
parameters: readonly string[],
public readonly code: string,
public readonly revertCode: string,
) {
if (!name) { throw new Error('undefined function name'); }
if (!code) { throw new Error(`undefined function ("${name}") code`); }
this.parameters = parameters || [];
}
}

View File

@@ -0,0 +1,23 @@
import { ISharedFunction } from './ISharedFunction';
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
export class SharedFunctionCollection implements ISharedFunctionCollection {
private readonly functionsByName = new Map<string, ISharedFunction>();
public addFunction(func: ISharedFunction): void {
if (!func) { throw new Error('undefined function'); }
if (this.functionsByName.has(func.name)) {
throw new Error(`function with name ${func.name} already exists`);
}
this.functionsByName.set(func.name, func);
}
public getFunctionByName(name: string): ISharedFunction {
if (!name) { throw Error('undefined function name'); }
const func = this.functionsByName.get(name);
if (!func) {
throw new Error(`called function is not defined "${name}"`);
}
return func;
}
}

View File

@@ -0,0 +1,89 @@
import { FunctionCallData, FunctionCallParametersData, FunctionData, ScriptFunctionCallData } from 'js-yaml-loader!@/*';
import { ICompiledCode } from './ICompiledCode';
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
import { IExpressionsCompiler } from '../Expressions/IExpressionsCompiler';
import { ExpressionsCompiler } from '../Expressions/ExpressionsCompiler';
export class FunctionCallCompiler implements IFunctionCallCompiler {
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
protected constructor(
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler()) { }
public compileCall(
call: ScriptFunctionCallData,
functions: ISharedFunctionCollection): ICompiledCode {
if (!functions) { throw new Error('undefined functions'); }
if (!call) { throw new Error('undefined call'); }
const compiledCodes = new Array<ICompiledCode>();
const calls = getCallSequence(call);
calls.forEach((currentCall, currentCallIndex) => {
ensureValidCall(currentCall);
const commonFunction = functions.getFunctionByName(currentCall.function);
ensureExpectedParameters(commonFunction, currentCall);
let functionCode = compileCode(commonFunction, currentCall.parameters, this.expressionsCompiler);
if (currentCallIndex !== calls.length - 1) {
functionCode = appendLine(functionCode);
}
compiledCodes.push(functionCode);
});
const compiledCode = merge(compiledCodes);
return compiledCode;
}
}
function ensureExpectedParameters(func: FunctionData, call: FunctionCallData) {
const actual = Object.keys(call.parameters || {});
const expected = func.parameters || [];
if (!actual.length && !expected.length) {
return;
}
const unexpectedParameters = actual.filter((callParam) => !expected.includes(callParam));
if (unexpectedParameters.length) {
throw new Error(
`function "${func.name}" has unexpected parameter(s) provided: "${unexpectedParameters.join('", "')}"`);
}
}
function merge(codes: readonly ICompiledCode[]): ICompiledCode {
return {
code: codes.map((code) => code.code).join(''),
revertCode: codes.map((code) => code.revertCode).join(''),
};
}
function compileCode(
func: FunctionData,
parameters: FunctionCallParametersData,
compiler: IExpressionsCompiler): ICompiledCode {
return {
code: compiler.compileExpressions(func.code, parameters),
revertCode: compiler.compileExpressions(func.revertCode, parameters),
};
}
function getCallSequence(call: ScriptFunctionCallData): FunctionCallData[] {
if (typeof call !== 'object') {
throw new Error('called function(s) must be an object');
}
if (call instanceof Array) {
return call as FunctionCallData[];
}
return [ call as FunctionCallData ];
}
function ensureValidCall(call: FunctionCallData) {
if (!call) {
throw new Error(`undefined function call`);
}
if (!call.function) {
throw new Error(`empty function name called`);
}
}
function appendLine(code: ICompiledCode): ICompiledCode {
const appendLineIfNotEmpty = (str: string) => str ? `${str}\n` : str;
return {
code: appendLineIfNotEmpty(code.code),
revertCode: appendLineIfNotEmpty(code.revertCode),
};
}

View File

@@ -0,0 +1,4 @@
export interface ICompiledCode {
readonly code: string;
readonly revertCode?: string;
}

View File

@@ -0,0 +1,9 @@
import { ScriptFunctionCallData } from 'js-yaml-loader!@/*';
import { ICompiledCode } from './ICompiledCode';
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
export interface IFunctionCallCompiler {
compileCall(
call: ScriptFunctionCallData,
functions: ISharedFunctionCollection): ICompiledCode;
}

View File

@@ -1,73 +0,0 @@
export interface IILCode {
compile(): string;
getUniqueParameterNames(): string[];
substituteParameter(parameterName: string, parameterValue: string): IILCode;
}
export function generateIlCode(rawText: string): IILCode {
const ilCode = generateIl(rawText);
return new ILCode(ilCode);
}
class ILCode implements IILCode {
private readonly ilCode: string;
constructor(ilCode: string) {
this.ilCode = ilCode;
}
public substituteParameter(parameterName: string, parameterValue: string): IILCode {
const newCode = substituteParameter(this.ilCode, parameterName, parameterValue);
return new ILCode(newCode);
}
public getUniqueParameterNames(): string[] {
return getUniqueParameterNames(this.ilCode);
}
public compile(): string {
ensureNoExpressionLeft(this.ilCode);
return this.ilCode;
}
}
// Trim each expression and put them inside "{{exp|}}" e.g. "{{ $hello }}" becomes "{{exp|$hello}}"
function generateIl(rawText: string): string {
return rawText.replace(/\{\{([\s]*[^;\s\{]+[\s]*)\}\}/g, (_, match) => {
return `\{\{exp|${match.trim()}\}\}`;
});
}
// finds all "{{exp|..}} left"
function ensureNoExpressionLeft(ilCode: string) {
const allSubstitutions = ilCode.matchAll(/\{\{exp\|(.*?)\}\}/g);
const allMatches = Array.from(allSubstitutions, (match) => match[1]);
const uniqueExpressions = getDistinctValues(allMatches);
if (uniqueExpressions.length > 0) {
throw new Error(`unknown expression: ${printList(uniqueExpressions)}`);
}
}
// Parses all distinct usages of {{exp|$parameterName}}
function getUniqueParameterNames(ilCode: string) {
const allSubstitutions = ilCode.matchAll(/\{\{exp\|\$([^;\s\{]+[\s]*)\}\}/g);
const allParameters = Array.from(allSubstitutions, (match) => match[1]);
const uniqueParameterNames = getDistinctValues(allParameters);
return uniqueParameterNames;
}
// substitutes {{exp|$parameterName}} to value of the parameter
function substituteParameter(ilCode: string, parameterName: string, parameterValue: string) {
const pattern = `{{exp|$${parameterName}}}`;
return ilCode.split(pattern).join(parameterValue); // as .replaceAll() is not yet supported by TS
}
function getDistinctValues(values: readonly string[]): string[] {
return values.filter((value, index, self) => {
return self.indexOf(value) === index;
});
}
function printList(list: readonly string[]): string {
return `"${list.join('","')}"`;
}

View File

@@ -1,171 +1,42 @@
import { generateIlCode, IILCode } from './ILCode';
import { IScriptCode } from '@/domain/IScriptCode'; import { IScriptCode } from '@/domain/IScriptCode';
import { ScriptCode } from '@/domain/ScriptCode'; import { ScriptCode } from '@/domain/ScriptCode';
import { ScriptData, FunctionData, FunctionCallData, ScriptFunctionCallData, FunctionCallParametersData } from 'js-yaml-loader!@/*'; import { FunctionData, ScriptData } from 'js-yaml-loader!@/*';
import { IScriptCompiler } from './IScriptCompiler'; import { IScriptCompiler } from './IScriptCompiler';
import { ILanguageSyntax } from '@/domain/ScriptCode'; import { ILanguageSyntax } from '@/domain/ScriptCode';
import { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
interface ICompiledCode { import { IFunctionCallCompiler } from './FunctionCall/IFunctionCallCompiler';
readonly code: string; import { FunctionCallCompiler } from './FunctionCall/FunctionCallCompiler';
readonly revertCode: string; import { IFunctionCompiler } from './Function/IFunctionCompiler';
} import { FunctionCompiler } from './Function/FunctionCompiler';
export class ScriptCompiler implements IScriptCompiler { export class ScriptCompiler implements IScriptCompiler {
private readonly functions: ISharedFunctionCollection;
constructor( constructor(
private readonly functions: readonly FunctionData[] | undefined, functions: readonly FunctionData[] | undefined,
private syntax: ILanguageSyntax) { private readonly syntax: ILanguageSyntax,
ensureValidFunctions(functions); functionCompiler: IFunctionCompiler = FunctionCompiler.instance,
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance,
) {
if (!syntax) { throw new Error('undefined syntax'); } if (!syntax) { throw new Error('undefined syntax'); }
this.functions = functionCompiler.compileFunctions(functions);
} }
public canCompile(script: ScriptData): boolean { public canCompile(script: ScriptData): boolean {
if (!script) { throw new Error('undefined script'); }
if (!script.call) { if (!script.call) {
return false; return false;
} }
return true; return true;
} }
public compile(script: ScriptData): IScriptCode { public compile(script: ScriptData): IScriptCode {
this.ensureCompilable(script.call); if (!script) { throw new Error('undefined script'); }
const compiledCodes = new Array<ICompiledCode>(); try {
const calls = getCallSequence(script.call); const compiledCode = this.callCompiler.compileCall(script.call, this.functions);
calls.forEach((currentCall, currentCallIndex) => { return new ScriptCode(
ensureValidCall(currentCall, script.name); compiledCode.code,
const commonFunction = this.getFunctionByName(currentCall.function); compiledCode.revertCode,
let functionCode = compileCode(commonFunction, currentCall.parameters); this.syntax);
if (currentCallIndex !== calls.length - 1) { } catch (error) {
functionCode = appendLine(functionCode); throw Error(`Script "${script.name}" ${error.message}`);
}
compiledCodes.push(functionCode);
});
const scriptCode = merge(compiledCodes);
return new ScriptCode(scriptCode.code, scriptCode.revertCode, script.name, this.syntax);
}
private getFunctionByName(name: string): FunctionData {
const func = this.functions.find((f) => f.name === name);
if (!func) {
throw new Error(`called function is not defined "${name}"`);
}
return func;
}
private ensureCompilable(call: ScriptFunctionCallData) {
if (!this.functions || this.functions.length === 0) {
throw new Error('cannot compile without shared functions');
}
if (typeof call !== 'object') {
throw new Error('called function(s) must be an object');
} }
} }
} }
function getDuplicates(texts: readonly string[]): string[] {
return texts.filter((item, index) => texts.indexOf(item) !== index);
}
function printList(list: readonly string[]): string {
return `"${list.join('","')}"`;
}
function ensureNoDuplicatesInFunctionNames(functions: readonly FunctionData[]) {
const duplicateFunctionNames = getDuplicates(functions
.map((func) => func.name.toLowerCase()));
if (duplicateFunctionNames.length) {
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
}
}
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
if (functions.some((func) => !func)) {
throw new Error(`some functions are undefined`);
}
}
function ensureNoDuplicatesInParameterNames(functions: readonly FunctionData[]) {
const functionsWithParameters = functions
.filter((func) => func.parameters && func.parameters.length > 0);
for (const func of functionsWithParameters) {
const duplicateParameterNames = getDuplicates(func.parameters);
if (duplicateParameterNames.length) {
throw new Error(`"${func.name}": duplicate parameter name: ${printList(duplicateParameterNames)}`);
}
}
}
function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
const duplicateCodes = getDuplicates(functions.map((func) => func.code));
if (duplicateCodes.length > 0) {
throw new Error(`duplicate "code" in functions: ${printList(duplicateCodes)}`);
}
const duplicateRevertCodes = getDuplicates(functions
.filter((func) => func.revertCode)
.map((func) => func.revertCode));
if (duplicateRevertCodes.length > 0) {
throw new Error(`duplicate "revertCode" in functions: ${printList(duplicateRevertCodes)}`);
}
}
function ensureValidFunctions(functions: readonly FunctionData[]) {
if (!functions || functions.length === 0) {
return;
}
ensureNoUndefinedItem(functions);
ensureNoDuplicatesInFunctionNames(functions);
ensureNoDuplicatesInParameterNames(functions);
ensureNoDuplicateCode(functions);
}
function appendLine(code: ICompiledCode): ICompiledCode {
const appendLineIfNotEmpty = (str: string) => str ? `${str}\n` : str;
return {
code: appendLineIfNotEmpty(code.code),
revertCode: appendLineIfNotEmpty(code.revertCode),
};
}
function merge(codes: readonly ICompiledCode[]): ICompiledCode {
return {
code: codes.map((code) => code.code).join(''),
revertCode: codes.map((code) => code.revertCode).join(''),
};
}
function compileCode(func: FunctionData, parameters: FunctionCallParametersData): ICompiledCode {
return {
code: compileExpressions(func.code, parameters),
revertCode: compileExpressions(func.revertCode, parameters),
};
}
function compileExpressions(code: string, parameters: FunctionCallParametersData): string {
let intermediateCode = generateIlCode(code);
intermediateCode = substituteParameters(intermediateCode, parameters);
return intermediateCode.compile();
}
function substituteParameters(intermediateCode: IILCode, parameters: FunctionCallParametersData): IILCode {
const parameterNames = intermediateCode.getUniqueParameterNames();
if (parameterNames.length && !parameters) {
throw new Error(`no parameters defined, expected: ${printList(parameterNames)}`);
}
for (const parameterName of parameterNames) {
const parameterValue = parameters[parameterName];
if (!parameterValue) {
throw Error(`parameter value is not provided for "${parameterName}" in function call`);
}
intermediateCode = intermediateCode.substituteParameter(parameterName, parameterValue);
}
return intermediateCode;
}
function ensureValidCall(call: FunctionCallData, scriptName: string) {
if (!call) {
throw new Error(`undefined function call in script "${scriptName}"`);
}
if (!call.function) {
throw new Error(`empty function name called in script "${scriptName}"`);
}
}
function getCallSequence(call: ScriptFunctionCallData): FunctionCallData[] {
if (call instanceof Array) {
return call as FunctionCallData[];
}
return [ call as FunctionCallData ];
}

View File

@@ -31,7 +31,7 @@ function parseCode(script: ScriptData, context: ICategoryCollectionParseContext)
if (context.compiler.canCompile(script)) { if (context.compiler.canCompile(script)) {
return context.compiler.compile(script); return context.compiler.compile(script);
} }
return new ScriptCode(script.code, script.revertCode, script.name, context.syntax); return new ScriptCode(script.code, script.revertCode, context.syntax);
} }
function ensureNotBothCallAndCode(script: ScriptData) { function ensureNotBothCallAndCode(script: ScriptData) {

View File

@@ -1,6 +1,5 @@
import { ILanguageSyntax } from '@/domain/ScriptCode'; import { ILanguageSyntax } from '@/domain/ScriptCode';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { IScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/IScriptingLanguageFactory';
export interface ISyntaxFactory { export interface ISyntaxFactory extends IScriptingLanguageFactory<ILanguageSyntax> {
create(language: ScriptingLanguage): ILanguageSyntax;
} }

View File

@@ -1,15 +1,14 @@
import { ILanguageSyntax } from '@/domain/ScriptCode'; import { ILanguageSyntax } from '@/domain/ScriptCode';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ISyntaxFactory } from './ISyntaxFactory'; import { ScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/ScriptingLanguageFactory';
import { BatchFileSyntax } from './BatchFileSyntax'; import { BatchFileSyntax } from './BatchFileSyntax';
import { ShellScriptSyntax } from './ShellScriptSyntax'; import { ShellScriptSyntax } from './ShellScriptSyntax';
import { ISyntaxFactory } from './ISyntaxFactory';
export class SyntaxFactory implements ISyntaxFactory { export class SyntaxFactory extends ScriptingLanguageFactory<ILanguageSyntax> implements ISyntaxFactory {
public create(language: ScriptingLanguage): ILanguageSyntax { constructor() {
switch (language) { super();
case ScriptingLanguage.batchfile: return new BatchFileSyntax(); this.registerGetter(ScriptingLanguage.batchfile, () => new BatchFileSyntax());
case ScriptingLanguage.shellscript: return new ShellScriptSyntax(); this.registerGetter(ScriptingLanguage.shellscript, () => new ShellScriptSyntax());
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
}
} }
} }

View File

@@ -0,0 +1,33 @@
import { IExpressionsCompiler, ParameterValueDictionary } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
import { ParameterSubstitutionParser } from '@/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser';
import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser';
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
import { IProjectInformation } from '@/domain/IProjectInformation';
import { ICodeSubstituter } from './ICodeSubstituter';
export class CodeSubstituter implements ICodeSubstituter {
constructor(
private readonly compiler: IExpressionsCompiler = createSubstituteCompiler(),
private readonly date = new Date(),
) {
}
public substitute(code: string, info: IProjectInformation): string {
if (!code) { throw new Error('undefined code'); }
if (!info) { throw new Error('undefined info'); }
const parameters: ParameterValueDictionary = {
homepage: info.homepage,
version: info.version,
date: this.date.toUTCString(),
};
const compiledCode = this.compiler.compileExpressions(code, parameters);
return compiledCode;
}
}
function createSubstituteCompiler(): IExpressionsCompiler {
const parsers = [ new ParameterSubstitutionParser() ];
const parser = new CompositeExpressionParser(parsers);
const expressionCompiler = new ExpressionsCompiler(parser);
return expressionCompiler;
}

View File

@@ -0,0 +1,5 @@
import { IProjectInformation } from '@/domain/IProjectInformation';
export interface ICodeSubstituter {
substitute(code: string, info: IProjectInformation): string;
}

View File

@@ -0,0 +1,31 @@
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ScriptingDefinitionData } from 'js-yaml-loader!@/*';
import { ScriptingDefinition } from '@/domain/ScriptingDefinition';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IProjectInformation } from '@/domain/IProjectInformation';
import { createEnumParser } from '../../Common/Enum';
import { ICodeSubstituter } from './ICodeSubstituter';
import { CodeSubstituter } from './CodeSubstituter';
export class ScriptingDefinitionParser {
constructor(
private readonly languageParser = createEnumParser(ScriptingLanguage),
private readonly codeSubstituter: ICodeSubstituter = new CodeSubstituter(),
) {
}
public parse(
definition: ScriptingDefinitionData,
info: IProjectInformation): IScriptingDefinition {
if (!info) { throw new Error('undefined info'); }
if (!definition) { throw new Error('undefined definition'); }
const language = this.languageParser.parseEnum(definition.language, 'language');
const startCode = this.codeSubstituter.substitute(definition.startCode, info);
const endCode = this.codeSubstituter.substitute(definition.endCode, info);
return new ScriptingDefinition(
language,
startCode,
endCode,
);
}
}

View File

@@ -1,36 +0,0 @@
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ScriptingDefinitionData } from 'js-yaml-loader!@/*';
import { ScriptingDefinition } from '@/domain/ScriptingDefinition';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IProjectInformation } from '@/domain/IProjectInformation';
import { createEnumParser } from '../Common/Enum';
import { generateIlCode } from './Script/Compiler/ILCode';
export function parseScriptingDefinition(
definition: ScriptingDefinitionData,
info: IProjectInformation,
date = new Date(),
languageParser = createEnumParser(ScriptingLanguage)): IScriptingDefinition {
if (!info) {
throw new Error('undefined info');
}
if (!definition) {
throw new Error('undefined definition');
}
const language = languageParser.parseEnum(definition.language, 'language');
const startCode = applySubstitutions(definition.startCode, info, date);
const endCode = applySubstitutions(definition.endCode, info, date);
return new ScriptingDefinition(
language,
startCode,
endCode,
);
}
function applySubstitutions(code: string, info: IProjectInformation, date: Date): string {
let ilCode = generateIlCode(code);
ilCode = ilCode.substituteParameter('homepage', info.homepage);
ilCode = ilCode.substituteParameter('version', info.version);
ilCode = ilCode.substituteParameter('date', date.toUTCString());
return ilCode.compile();
}

View File

@@ -1,4 +1,4 @@
declare module 'js-yaml-loader!*' { declare module 'js-yaml-loader!@/*' {
export interface CollectionData { export interface CollectionData {
readonly os: string; readonly os: string;
readonly scripting: ScriptingDefinitionData; readonly scripting: ScriptingDefinitionData;
@@ -18,30 +18,33 @@ declare module 'js-yaml-loader!*' {
readonly docs?: DocumentationUrlsData; readonly docs?: DocumentationUrlsData;
} }
export interface FunctionData { export interface InstructionHolder {
name: string; readonly name: string;
code: string;
revertCode?: string; readonly code?: string;
parameters?: readonly string[]; readonly revertCode?: string;
readonly call?: ScriptFunctionCallData;
}
export interface FunctionData extends InstructionHolder {
readonly parameters?: readonly string[];
} }
export interface FunctionCallParametersData { export interface FunctionCallParametersData {
[index: string]: string; readonly [index: string]: string;
} }
export interface FunctionCallData { export interface FunctionCallData {
function: string; readonly function: string;
parameters?: FunctionCallParametersData; readonly parameters?: FunctionCallParametersData;
} }
export type ScriptFunctionCallData = readonly FunctionCallData[] | FunctionCallData | undefined; export type ScriptFunctionCallData = readonly FunctionCallData[] | FunctionCallData | undefined;
export interface ScriptData extends DocumentableData { export interface ScriptData extends InstructionHolder, DocumentableData {
name: string; readonly name: string;
code?: string; readonly recommend?: string;
revertCode?: string;
call: ScriptFunctionCallData;
recommend?: string;
} }
export interface ScriptingDefinitionData { export interface ScriptingDefinitionData {

View File

@@ -40,17 +40,352 @@ actions:
sudo rm -rfv /var/spool/cups/tmp/* sudo rm -rfv /var/spool/cups/tmp/*
sudo rm -rfv /var/spool/cups/cache/job.cache* sudo rm -rfv /var/spool/cups/cache/job.cache*
- -
name: Clear the list of iOS devices connected name: Empty trash on all volumes
recommend: strict recommend: strict
code: |- code: |-
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect" # on all mounted volumes
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist Devices sudo rm -rfv /Volumes/*/.Trashes/* &>/dev/null
sudo defaults delete /Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect" # on main HDD
sudo defaults delete /Library/Preferences/com.apple.iPod.plist Devices sudo rm -rfv ~/.Trash/* &>/dev/null
sudo rm -rfv /var/db/lockdown/*
- -
name: Reset privacy database (remove all permissions) name: Clear system cache files
code: sudo tccutil reset All recommend: strict
code: |-
sudo rm -rfv /Library/Caches/* &>/dev/null
sudo rm -rfv /System/Library/Caches/* &>/dev/null
sudo rm -rfv ~/Library/Caches/* &>/dev/null
-
name: Clear system log files
recommend: strict
code: |-
sudo rm -rfv /private/var/log/asl/*.asl &>/dev/null
sudo rm -rfv /Library/Logs/DiagnosticReports/* &>/dev/null
sudo rm -rfv /Library/Logs/Adobe/* &>/dev/null
rm -rfv ~/Library/Containers/com.apple.mail/Data/Library/Logs/Mail/* &>/dev/null
rm -rfv ~/Library/Logs/CoreSimulator/* &>/dev/null
sudo rm -rfv /var/log/*
-
category: Clear browser history
children:
-
category: Clear Google Chrome history
children:
-
name: Clear Google Chrome browsing history
code: |-
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History &>/dev/null
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History-journal &>/dev/null
-
name: Google Chrome Cache Files
code: sudo rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/Application\ Cache/* &>/dev/null
-
category: Clear Safari history
children:
-
name: Clear Safari browsing history
docs:
- https://discussions.apple.com/thread/7586106?answerId=30314600022#30314600022
- https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
code: |-
rm -f ~/Library/Safari/History.db
rm -f ~/Library/Safari/History.db-lock
rm -f ~/Library/Safari/History.db-shm
rm -f ~/Library/Safari/History.db-wal
# For older versions of Safari
rm -f ~/Library/Safari/History.plist # URL, visit count, webpage title, last visited timestamp, redirected URL, autocomplete
rm -f ~/Library/Safari/HistoryIndex.sk # History index
-
name: Clear Safari downloads history
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
code: rm -f ~/Library/Safari/Downloads.plist
-
name: Clear Safari top sites
docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
code: rm -f ~/Library/Safari/TopSites.plist
-
name: Clear Safari last session (open tabs) history
docs:
- https://apple.stackexchange.com/a/374116
- https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-7127
code: rm -f ~/Library/Safari/LastSession.plist
-
category: Clear Safari caches
children:
-
name: Clear Safari cached blobs, URLs and timestamps
docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
code: rm -f ~/Library/Caches/com.apple.Safari/Cache.db
-
name: Clear Safari web page icons displayed on URL bar
docs:
- https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
- https://lifehacker.com/safaris-private-browsing-mode-saves-urls-in-an-easily-a-1691944343
code: rm -f ~/Library/Safari/WebpageIcons.db
-
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/
code: rm -rfv ~/Library/Caches/com.apple.Safari/Webpage\ Previews
-
name: Clear copy of the Safari history
docs: https://forensicsfromthesausagefactory.blogspot.com/2010/06/safari-history-spotlight-webhistory.html
code: rm -rfv ~/Library/Caches/Metadata/Safari/History
-
name: Clear search history embedded in Safari preferences
docs: https://krypted.com/tag/recentsearchstrings/
code: defaults write ~/Library/Preferences/com.apple.Safari RecentSearchStrings '( )'
-
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
code: |-
rm -f ~/Library/Cookies/Cookies.binarycookies
# Used before Safari 5.1
rm -f ~/Library/Cookies/Cookies.plist
-
name: Clear Safari zoom level preferences per site
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
code: rm -f ~/Library/Safari/PerSiteZoomPreferences.plist
-
name: Clear URLs that are allowed to display notifications in Safari
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
code: rm -f ~/Library/Safari/UserNotificationPreferences.plist
-
name: Clear Safari per-site preferences for Downloads, Geolocation, PopUps, and Autoplays
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
code: rm -f ~/Library/Safari/PerSitePreferences.db
-
category: Clear Firefox history
children:
-
name: Clear Firefox cache
code: |-
sudo rm -rf ~/Library/Caches/Mozilla/
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/netpredictions.sqlite
-
name: Delete Firefox form history
code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.sqlite
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.dat
-
name: Delete Firefox site preferences
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/content-prefs.sqlite
-
name: Delete Firefox session restore data (loads after the browser closes or crashes)
code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionCheckpoints.json
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore*.js*
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore.bak*
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/previous.js*
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/recovery.js*
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/recovery.bak*
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/previous.bak*
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/upgrade.js*-20*
-
name: Delete Firefox passwords
docs: http://kb.mozillazine.org/Password_Manager
code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.txt
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons2.txt
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons3.txt
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.sqlite
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/logins.json
-
name: Delete Firefox HTML5 cookies
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/webappsstore.sqlite
-
name: Delete Firefox crash reports
code: |-
rm -rfv ~/Library/Application\ Support/Firefox/Crash\ Reports/
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/minidumps/*.dmp
-
name: Delete Firefox backup files
code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.json
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.jsonlz4
-
name: Delete Firefox cookies
code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.txt
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite-shm
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite-wal
rm -rfv ~/Library/Application\ Support/Firefox/Profiles/*/storage/default/http*
-
category: Clear third party application data
children:
-
name: Clear Adobe cache
recommend: standard
code: sudo rm -rfv ~/Library/Application\ Support/Adobe/Common/Media\ Cache\ Files/* &>/dev/null
-
name: Clear Gradle cache
recommend: strict
code: |-
if [ -d "/Users/${HOST}/.gradle/caches" ]; then
rm -rfv ~/.gradle/caches/ &> /dev/null
fi
-
name: Clear Dropbox cache
recommend: standard
code: |-
if [ -d "/Users/${HOST}/Dropbox" ]; then
sudo rm -rfv ~/Dropbox/.dropbox.cache/* &>/dev/null
fi
-
name: Clear Google Drive file stream cache
recommend: standard
code: |-
killall "Google Drive File Stream"
rm -rfv ~/Library/Application\ Support/Google/DriveFS/[0-9a-zA-Z]*/content_cache &>/dev/null
-
name: Clear Composer cache
recommend: strict
code: |-
if type "composer" &> /dev/null; then
composer clearcache &> /dev/null
fi
-
name: Clear Homebrew cache
recommend: strict
code: |-
if type "brew" &>/dev/null; then
brew cleanup -s &>/dev/null
rm -rfv $(brew --cache) &>/dev/null
brew tap --repair &>/dev/null
fi
-
name: Clear any old versions of Ruby gems
recommend: strict
code: |-
if type "gem" &> /dev/null; then
gem cleanup &>/dev/null
fi
-
name: Clear Docker
recommend: strict
code: |-
if type "docker" &> /dev/null; then
docker system prune -af
fi
-
name: Clear Pyenv-VirtualEnv cache
recommend: strict
code: |-
if [ "$PYENV_VIRTUALENV_CACHE_PATH" ]; then
rm -rfv $PYENV_VIRTUALENV_CACHE_PATH &>/dev/null
fi
-
name: Clear NPM cache
recommend: strict
code: |-
if type "npm" &> /dev/null; then
npm cache clean --force
fi
-
name: Clear Yarn cache
recommend: strict
code: |-
if type "yarn" &> /dev/null; then
echo 'Cleanup Yarn Cache...'
yarn cache clean --force
fi
-
category: iOS Cleanup
children:
-
name: Clear iOS applications
recommend: strict
code: rm -rfv ~/Music/iTunes/iTunes\ Media/Mobile\ Applications/* &>/dev/null
-
name: Clear iOS photo caches
recommend: standard
code: rm -rf ~/Pictures/iPhoto\ Library/iPod\ Photo\ Cache/*
-
name: Remove iOS Device Backups
recommend: strict
code: rm -rfv ~/Library/Application\ Support/MobileSync/Backup/* &>/dev/null
-
name: Clear iOS Simulators
recommend: strict
code: |-
if type "xcrun" &>/dev/null; then
osascript -e 'tell application "com.apple.CoreSimulator.CoreSimulatorService" to quit'
osascript -e 'tell application "iOS Simulator" to quit'
osascript -e 'tell application "Simulator" to quit'
xcrun simctl shutdown all
xcrun simctl erase all
fi
-
name: Clear the list of iOS devices connected
recommend: strict
code: |-
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist Devices
sudo defaults delete /Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
sudo defaults delete /Library/Preferences/com.apple.iPod.plist Devices
sudo rm -rfv /var/db/lockdown/*
-
name: Clear XCode Derived Data and Archives
recommend: strict
code: |-
rm -rfv ~/Library/Developer/Xcode/DerivedData/* &>/dev/null
rm -rfv ~/Library/Developer/Xcode/Archives/* &>/dev/null
rm -rfv ~/Library/Developer/Xcode/iOS Device Logs/* &>/dev/null
-
name: Clear DNS cache
recommend: standard
code: |-
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
-
name: Purge inactive memory
recommend: standard
code: sudo purge
-
category: Reset privacy permissions for all applications
children:
-
name: Reset camera permissions
code: tccutil reset Camera
-
name: Reset microphone permissions
code: tccutil reset Microphone
-
name: Reset accessibility permissions
code: tccutil reset Accessibility
-
name: Reset screen capture permissions
code: tccutil reset ScreenCapture
-
name: Reset reminders permissions
code: tccutil reset Reminders
-
name: Reset photos permissions
code: tccutil reset Photos
-
name: Reset calendar permissions
code: tccutil reset Calendar
-
name: Reset full disk access permissions
code: tccutil reset SystemPolicyAllFiles
-
name: Reset contacts permissions
code: tccutil reset SystemPolicyAllFiles
-
name: Reset desktop folder permissions
code: tccutil reset SystemPolicyDesktopFolder
-
name: Reset documents folder permissions
code: tccutil reset SystemPolicyDocumentsFolder
-
name: Reset downloads permissions
code: tccutil reset SystemPolicyDownloadsFolder
-
name: Reset all app permissions
code: tccutil reset All
- -
category: Configure programs category: Configure programs
children: children:

View File

@@ -476,26 +476,62 @@ actions:
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\KernelCeipTask" /ENABLE schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\KernelCeipTask" /ENABLE
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip" /ENABLE schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip" /ENABLE
- -
name: Disable Webcam Telemetry (devicecensus.exe) category: Disable Webcam Telemetry (devicecensus.exe)
recommend: standard docs:
docs: https://www.ghacks.net/2019/09/23/what-is-devicecensus-exe-on-windows-10-and-why-does-it-need-internet-connectivity/ - https://www.ghacks.net/2019/09/23/what-is-devicecensus-exe-on-windows-10-and-why-does-it-need-internet-connectivity/
code: schtasks /change /TN "Microsoft\Windows\Device Information\Device" /DISABLE - https://answers.microsoft.com/en-us/windows/forum/windows_10-security/devicecensusexe-and-host-process-for-windows-task/520d42a2-45c1-402a-81de-e1116ecf2538
revertCode: schtasks /change /TN "Microsoft\Windows\Device Information\Device" /ENABLE children:
-
name: Disable devicecensus.exe (telemetry) task
recommend: standard
code: schtasks /change /TN "Microsoft\Windows\Device Information\Device" /disable
revertCode: schtasks /change /TN "Microsoft\Windows\Device Information\Device" /enable
-
name: Disable devicecensus.exe (telemetry) process
recommend: standard
call:
function: KillProcessWhenItStarts
parameters:
processName: DeviceCensus.exe
- -
name: Disable Application Experience (Compatibility Telemetry) category: Disable Compatibility Telemetry (Application Experience)
recommend: standard children:
code: |- -
schtasks /change /TN "Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" /DISABLE category: Disable Microsoft Compatibility Appraiser
schtasks /change /TN "Microsoft\Windows\Application Experience\ProgramDataUpdater" /DISABLE docs: https://www.ghacks.net/2016/10/26/turn-off-the-windows-customer-experience-program/
schtasks /change /TN "Microsoft\Windows\Application Experience\StartupAppTask" /DISABLE children:
schtasks /change /TN "Microsoft\Windows\Application Experience\AitAgent" /DISABLE -
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\CompatTelRunner.exe" /v "Debugger" /t REG_SZ /d "%windir%\System32\taskkill.exe" /f name: Disable Microsoft Compatibility Appraiser task
revertCode: |- recommend: standard
schtasks /change /TN "Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" /ENABLE code: schtasks /change /TN "Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" /disable
schtasks /change /TN "Microsoft\Windows\Application Experience\ProgramDataUpdater" /ENABLE revertCode: schtasks /change /TN "Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" /enable
schtasks /change /TN "Microsoft\Windows\Application Experience\StartupAppTask" /ENABLE -
schtasks /change /TN "Microsoft\Windows\Application Experience\AitAgent" /ENABLE name: Disable CompatTelRunner.exe (Microsoft Compatibility Appraiser) process
reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\CompatTelRunner.exe" /v "Debugger" /f recommend: standard
call:
function: KillProcessWhenItStarts
parameters:
processName: CompatTelRunner.exe
-
name: Disable sending information to Customer Experience Improvement Program
recommend: standard
docs:
- https://www.ghacks.net/2016/10/26/turn-off-the-windows-customer-experience-program/
- https://answers.microsoft.com/en-us/windows/forum/windows_10-performance/permanently-disabling-windows-compatibility/6bf71583-81b0-4a74-ae2e-8fd73305aad1
code: schtasks /change /TN "Microsoft\Windows\Application Experience\ProgramDataUpdater" /disable
revertCode: schtasks /change /TN "Microsoft\Windows\Application Experience\ProgramDataUpdater" /enable
-
name: Disable Application Impact Telemetry Agent task
recommend: standard
docs: https://www.shouldiblockit.com/aitagent.exe-6181.aspx
code: schtasks /change /TN "Microsoft\Windows\Application Experience\AitAgent" /disable
revertCode: schtasks /change /TN "Microsoft\Windows\Application Experience\AitAgent" /enable
-
name: Disable "Disable apps to improve performance" reminder
recommend: strict
docs: https://www.ghacks.net/2016/10/26/turn-off-the-windows-customer-experience-program/
code: schtasks /change /TN "Microsoft\Windows\Application Experience\StartupAppTask" /disable
revertCode: schtasks /change /TN "Microsoft\Windows\Application Experience\StartupAppTask" /enable
- -
name: Disable telemetry in data collection policy name: Disable telemetry in data collection policy
recommend: standard recommend: standard
@@ -1001,6 +1037,34 @@ actions:
recommend: standard recommend: standard
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\bluetoothSync" /v "Value" /d "Deny" /t REG_SZ /f code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\bluetoothSync" /v "Value" /d "Deny" /t REG_SZ /f
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\bluetoothSync" /v "Value" /d "Allow" /t REG_SZ /f revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\bluetoothSync" /v "Value" /d "Allow" /t REG_SZ /f
-
category: Disable app access to voice activation
docs: https://www.tenforums.com/tutorials/130122-allow-deny-apps-access-use-voice-activation-windows-10-a.html
children:
-
name: Disable apps and Cortana to activate with voice
recommend: standard
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.AppPrivacy::LetAppsActivateWithVoice
code: |-
reg add "HKCU\Software\Microsoft\Speech_OneCore\Settings\VoiceActivation\UserPreferenceForAllApps" /v "AgentActivationEnabled" /t REG_DWORD /d 0 /f
:: Using GPO (re-activation through GUI is not possible)
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsActivateWithVoice" /t REG_DWORD /d 2 /f
revertCode: |-
reg add "HKCU\Software\Microsoft\Speech_OneCore\Settings\VoiceActivation\UserPreferenceForAllApps" /v "AgentActivationEnabled" /t REG_DWORD /d 1 /f
:: Using GPO
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsActivateWithVoice" /f
-
name: Disable apps and Cortana to activate with voice when sytem is locked
recommend: standard
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.AppPrivacy::LetAppsActivateWithVoiceAboveLock
code: |-
reg add "HKCU\Software\Microsoft\Speech_OneCore\Settings\VoiceActivation\UserPreferenceForAllApps" /v "AgentActivationOnLockScreenEnabled" /t REG_DWORD /d 0 /f
:: Using GPO (re-activation through GUI is not possible)
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsActivateWithVoiceAboveLock" /t REG_DWORD /d 2 /f
revertCode: |-
reg add "HKCU\Software\Microsoft\Speech_OneCore\Settings\VoiceActivation\UserPreferenceForAllApps" /v "AgentActivationOnLockScreenEnabled" /t REG_DWORD /d 1 /f
:: Using GPO
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsActivateWithVoiceAboveLock" /f
- -
category: Disable location access category: Disable location access
children: children:
@@ -1081,11 +1145,61 @@ actions:
revertCode: |- revertCode: |-
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 1 /f reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 1 /f
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 1 /f reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 1 /f
-
category: Disable Cortana history
children:
-
name: Prevent Cortana from displaying history
recommend: standard
code: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "HistoryViewEnabled" /t REG_DWORD /d 0 /f
revertCode: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "HistoryViewEnabled" /f
-
name: Prevent Cortana from using device history
recommend: standard
code: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "DeviceHistoryEnabled" /t REG_DWORD /d 0 /f
revertCode: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "DeviceHistoryEnabled" /f
- -
name: Remove the Cortana taskbar icon name: Remove the Cortana taskbar icon
recommend: standard recommend: standard
code: reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v "ShowCortanaButton" /t REG_DWORD /d 0 /f code: reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v "ShowCortanaButton" /t REG_DWORD /d 0 /f
revertCode: reg delete HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v "ShowCortanaButton" /f revertCode: reg delete HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v "ShowCortanaButton" /f
-
name: Disable Cortana in ambient mode
recommend: standard
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaInAmbientMode" /t REG_DWORD /d 0 /f
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaInAmbientMode" /t REG_DWORD /d 1 /f
-
category: Disable Cortana voice listening
children:
-
name: Disable "Hey Cortana" voice activation
recommend: standard
code: |-
reg add "HKCU\Software\Microsoft\Speech_OneCore\Preferences" /v "VoiceActivationOn" /t REG_DWORD /d 0 /f
reg add "HKLM\Software\Microsoft\Speech_OneCore\Preferences" /v "VoiceActivationDefaultOn" /t REG_DWORD /d 0 /f
revertCode: |-
reg add "HKCU\Software\Microsoft\Speech_OneCore\Preferences" /v "VoiceActivationOn" /t REG_DWORD /d 1 /f
reg add "HKLM\Software\Microsoft\Speech_OneCore\Preferences" /v "VoiceActivationDefaultOn" /t REG_DWORD /d 1 /f
-
name: Disable Cortana listening to commands on Windows key + C
recommend: standard
code: reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Search" /v "VoiceShortcut" /t REG_DWORD /d 0 /f
revertCode: reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Search" /v "VoiceShortcut" /t REG_DWORD /d 1 /f
-
name: Disable using Cortana even when device is locked
recommend: standard
code: reg add "HKCU\Software\Microsoft\Speech_OneCore\Preferences" /v "VoiceActivationEnableAboveLockscreen" /t REG_DWORD /d 0 /f
revertCode: reg add "HKCU\Software\Microsoft\Speech_OneCore\Preferences" /v "VoiceActivationEnableAboveLockscreen" /t REG_DWORD /d 1 /f
-
name: Disable automatic update of Speech Data
recommend: standard
code: reg add "HKCU\Software\Microsoft\Speech_OneCore\Preferences" /v "ModelDownloadAllowed" /t REG_DWORD /d 0 /f
revertCode: reg delete "HKCU\Software\Microsoft\Speech_OneCore\Preferences" /v "ModelDownloadAllowed" /f
-
name: Disable Cortana voice support during Windows setup
recommend: standard
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v "DisableVoice" /t REG_DWORD /d 1 /f
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v "DisableVoice" /f
- -
category: Configure Windows search indexing category: Configure Windows search indexing
children: children:
@@ -1134,25 +1248,52 @@ actions:
- -
name: Disable ad customization with Advertising ID name: Disable ad customization with Advertising ID
recommend: standard recommend: standard
docs: https://docs.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#181-general
code: |- code: |-
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo" /v "Enabled" /t REG_DWORD /d 0 /f reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo" /v "Enabled" /t REG_DWORD /d "0" /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\AdvertisingInfo" /v "DisabledByGroupPolicy" /t REG_DWORD /d 1 /f reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\AdvertisingInfo" /v "DisabledByGroupPolicy" /t REG_DWORD /d "1" /f
revertCode: |-
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo" /v "Enabled" /t REG_DWORD /d "1" /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\AdvertisingInfo" /v "DisabledByGroupPolicy" /t REG_DWORD /d "0" /f
-
category: Disable cloud-based tips and ads
children:
-
name: Disable Windows Tips
recommend: standard
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.CloudContent::DisableSoftLanding
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\CloudContent" /v "DisableSoftLanding" /t REG_DWORD /d "1" /f
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\CloudContent" /v "DisableSoftLanding" /t REG_DWORD /d "0" /f
-
name: Disable Windows Spotlight (random wallpaper on lock screen)
recommend: standard
docs:
- https://docs.microsoft.com/en-us/windows/configuration/windows-spotlight
- https://docs.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#25-windows-spotlight
code: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /t "REG_DWORD" /d "1" /f
revertCode: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /t "REG_DWORD" /d "0" /f
-
name: Disable Microsoft consumer experiences
recommend: standard
docs:
- https://www.stigviewer.com/stig/windows_10/2018-04-06/finding/V-71771
- https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.CloudContent::DisableWindowsConsumerFeatures
- https://docs.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#1816-feedback--diagnostics
code: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsConsumerFeatures" /t "REG_DWORD" /d "1" /f
revertCode: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsConsumerFeatures" /t "REG_DWORD" /d "0" /f
- -
name: Disable targeted tips
recommend: standard
code: |-
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\CloudContent" /v "DisableSoftLanding" /t REG_DWORD /d 1 /f
reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /t "REG_DWORD" /d "1" /f
reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsConsumerFeatures" /t "REG_DWORD" /d "1" /f
-
name: Turn Off Suggested Content in Settings app name: Turn Off Suggested Content in Settings app
recommend: standard recommend: standard
docs: https://www.tenforums.com/tutorials/100541-turn-off-suggested-content-settings-app-windows-10-a.html docs: https://www.tenforums.com/tutorials/100541-turn-off-suggested-content-settings-app-windows-10-a.html
code: |- code: |-
reg add HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager /v "SubscribedContent-338393Enabled" /d "0" /t REG_DWORD /f reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338393Enabled" /d "0" /t REG_DWORD /f
reg add HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager /v "SubscribedContent-353694Enabled" /d "0" /t REG_DWORD /f reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-353694Enabled" /d "0" /t REG_DWORD /f
reg add HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager /v "SubscribedContent-353696Enabled" /d "0" /t REG_DWORD /f reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-353696Enabled" /d "0" /t REG_DWORD /f
- revertCode: |-
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338393Enabled" /d "1" /t REG_DWORD /f
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-353694Enabled" /d "1" /t REG_DWORD /f
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-353696Enabled" /d "1" /t REG_DWORD /f
-
category: Disable biometrics (breaks fingerprinting/facial login) category: Disable biometrics (breaks fingerprinting/facial login)
children: children:
- -
@@ -1467,7 +1608,7 @@ actions:
name: Delete NVIDIA residual telemetry files name: Delete NVIDIA residual telemetry files
recommend: standard recommend: standard
code: |- code: |-
del /s %systemdrive%\System32\DriverStore\FileRepository\NvTelemetry*.dll del /s %SystemRoot%\System32\DriverStore\FileRepository\NvTelemetry*.dll
rmdir /s /q "%ProgramFiles(x86)%\NVIDIA Corporation\NvTelemetry" 2>nul rmdir /s /q "%ProgramFiles(x86)%\NVIDIA Corporation\NvTelemetry" 2>nul
rmdir /s /q "%ProgramFiles%\NVIDIA Corporation\NvTelemetry" 2>nul rmdir /s /q "%ProgramFiles%\NVIDIA Corporation\NvTelemetry" 2>nul
- -
@@ -1508,45 +1649,72 @@ actions:
name: Disable Visual Studio Code telemetry name: Disable Visual Studio Code telemetry
docs: https://code.visualstudio.com/docs/getstarted/telemetry docs: https://code.visualstudio.com/docs/getstarted/telemetry
recommend: standard recommend: standard
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'telemetry.enableTelemetry' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json; $json.PSObject.Properties.Remove('telemetry.enableTelemetry'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: telemetry.enableTelemetry
powerShellValue: $false
- -
name: Disable Visual Studio Code crash reporting name: Disable Visual Studio Code crash reporting
docs: https://code.visualstudio.com/docs/getstarted/telemetry docs: https://code.visualstudio.com/docs/getstarted/telemetry
recommend: standard recommend: standard
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'telemetry.enableCrashReporter' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('telemetry.enableCrashReporter'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: telemetry.enableCrashReporter
powerShellValue: $false
- -
name: Do not run Microsoft online experiments 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/Microsoft/vscode/blob/1aee0c194cff72d179b9f8ef324e47f34555a07d/src/vs/workbench/contrib/experiments/node/experimentService.ts#L173
recommend: standard recommend: standard
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'workbench.enableExperiments' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('workbench.enableExperiments'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: workbench.enableExperiments
powerShellValue: $false
- -
name: Choose manual updates over automatic updates name: Choose manual updates over automatic updates
docs: https://github.com/Microsoft/vscode/blob/1aee0c194cff72d179b9f8ef324e47f34555a07d/src/vs/workbench/contrib/experiments/node/experimentService.ts#L173 call:
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'update.mode' -Value \"manual\" -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('update.mode'); $json | ConvertTo-Json | Set-Content $jsonfile;" parameters:
setting: update.mode
powerShellValue: >-
'manual'
- -
name: Show Release Notes from Microsoft online service after an update name: Show Release Notes from Microsoft online service after an update
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'update.showReleaseNotes' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('update.showReleaseNotes'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: update.showReleaseNotes
powerShellValue: $false
- -
name: Automatically check extensions from Microsoft online service name: Automatically check extensions from Microsoft online service
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'extensions.autoCheckUpdates' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('extensions.autoCheckUpdates'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: extensions.autoCheckUpdates
powerShellValue: $false
- -
name: Fetch recommendations from a Microsoft online service name: Fetch recommendations from Microsoft only on demand
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'extensions.showRecommendationsOnlyOnDemand' -Value $true -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('extensions.showRecommendationsOnlyOnDemand'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: extensions.showRecommendationsOnlyOnDemand
powerShellValue: $true
- -
name: Automatically fetch git commits from remote repository name: Automatically fetch git commits from remote repository
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'git.autofetch' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('git.autofetch'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: git.autofetch
powerShellValue: $false
- -
name: Fetch package information from NPM and Bower name: Fetch package information from NPM and Bower
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'npm.fetchOnlinePackageInfo' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('npm.fetchOnlinePackageInfo'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: npm.fetchOnlinePackageInfo
powerShellValue: $false
- -
category: Disable Microsoft Office telemetry category: Disable Microsoft Office telemetry
docs: https://docs.microsoft.com/en-us/deployoffice/compat/manage-the-privacy-of-data-monitored-by-telemetry-in-office docs: https://docs.microsoft.com/en-us/deployoffice/compat/manage-the-privacy-of-data-monitored-by-telemetry-in-office
@@ -1647,7 +1815,7 @@ actions:
category: Chromium Edge settings category: Chromium Edge settings
children: children:
- -
name: Disable Edge usage and crash-related data reporting # Obselete since Microsoft Edge version 89 name: Disable Edge usage and crash-related data reporting (shows "Your browser is managed") # Obselete since Microsoft Edge version 89
recommend: standard recommend: standard
docs: docs:
- https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::MetricsReportingEnabled - https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::MetricsReportingEnabled
@@ -1655,7 +1823,7 @@ actions:
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /t REG_DWORD /d 0 /f code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /t REG_DWORD /d 0 /f
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /f revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /f
- -
name: Disable sending site information # Obselete since Microsoft Edge version 89 name: Disable sending site information (shows "Your browser is managed") # Obselete since Microsoft Edge version 89
recommend: standard recommend: standard
docs: docs:
- https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::SendSiteInfoToImproveServices - https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::SendSiteInfoToImproveServices
@@ -1753,7 +1921,7 @@ actions:
category: Chrome cleanup category: Chrome cleanup
children: children:
- -
name: Do not share scanned software data to Google name: Do not share scanned software data to Google (shows "Your browser is managed")
recommend: standard recommend: standard
docs: docs:
- https://www.chromium.org/administrators/policy-list-3#ChromeCleanupReportingEnabled - https://www.chromium.org/administrators/policy-list-3#ChromeCleanupReportingEnabled
@@ -1761,7 +1929,7 @@ actions:
code: reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /v "ChromeCleanupReportingEnabled" /t REG_DWORD /d 0 /f code: reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /v "ChromeCleanupReportingEnabled" /t REG_DWORD /d 0 /f
revertCode: reg delete "HKLM\SOFTWARE\Policies\Google\Chrome" /v "ChromeCleanupReportingEnabled" /f revertCode: reg delete "HKLM\SOFTWARE\Policies\Google\Chrome" /v "ChromeCleanupReportingEnabled" /f
- -
name: Prevent Chrome from scanning the system for cleanup name: Prevent Chrome from scanning the system for cleanup (shows "Your browser is managed")
recommend: standard recommend: standard
docs: docs:
- https://www.chromium.org/administrators/policy-list-3#ChromeCleanupEnabled - https://www.chromium.org/administrators/policy-list-3#ChromeCleanupEnabled
@@ -1769,7 +1937,7 @@ actions:
code: reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /v "ChromeCleanupEnabled" /t REG_DWORD /d 0 /f code: reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /v "ChromeCleanupEnabled" /t REG_DWORD /d 0 /f
revertCode: reg delete "HKLM\SOFTWARE\Policies\Google\Chrome" /v "ChromeCleanupEnabled" /f revertCode: reg delete "HKLM\SOFTWARE\Policies\Google\Chrome" /v "ChromeCleanupEnabled" /f
- -
name: Disable Chrome metrics reporting name: Disable Chrome metrics reporting (shows "Your browser is managed")
recommend: standard recommend: standard
docs: https://www.stigviewer.com/stig/google_chrome_v23_windows/2013-01-11/finding/V-35780 docs: https://www.stigviewer.com/stig/google_chrome_v23_windows/2013-01-11/finding/V-35780
code: reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /v "MetricsReportingEnabled" /t REG_DWORD /d 0 /f code: reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /v "MetricsReportingEnabled" /t REG_DWORD /d 0 /f
@@ -1868,7 +2036,7 @@ actions:
reg add "HKCU\Software\Policies\Microsoft\WindowsMediaPlayer" /v "PreventRadioPresetsRetrieval" /t REG_DWORD /d 1 /f reg add "HKCU\Software\Policies\Microsoft\WindowsMediaPlayer" /v "PreventRadioPresetsRetrieval" /t REG_DWORD /d 1 /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\WMDRM" /v "DisableOnline" /t REG_DWORD /d 1 /f reg add "HKLM\SOFTWARE\Policies\Microsoft\WMDRM" /v "DisableOnline" /t REG_DWORD /d 1 /f
- -
name: Disable dows Media Player Network Sharing Service name: Disable Windows Media Player Network Sharing Service
recommend: standard recommend: standard
code: sc stop "WMPNetworkSvc" & sc config "WMPNetworkSvc" start=disabled code: sc stop "WMPNetworkSvc" & sc config "WMPNetworkSvc" start=disabled
- -
@@ -2692,8 +2860,19 @@ actions:
- -
name: Disable NetBios for all interfaces name: Disable NetBios for all interfaces
docs: https://10dsecurity.com/saying-goodbye-netbios/ docs: https://10dsecurity.com/saying-goodbye-netbios/
code: Powershell -Command "$key = 'HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces'; Get-ChildItem $key | foreach { Set-ItemProperty -Path \"$key\$($_.pschildname)\" -Name NetbiosOptions -Value 2 -Verbose}" call:
revertCode: Powershell -Command "$key = 'HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces'; Get-ChildItem $key | foreach { Set-ItemProperty -Path \"$key\$($_.pschildname)\" -Name NetbiosOptions -Value 0 -Verbose}" function: RunPowerShell
parameters:
code:
$key = 'HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces';
Get-ChildItem $key | foreach {
Set-ItemProperty -Path \"$key\$($_.pschildname)\" -Name NetbiosOptions -Value 2 -Verbose
}
revertCode:
$key = 'HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces';
Get-ChildItem $key | foreach {
Set-ItemProperty -Path \"$key\$($_.pschildname)\" -Name NetbiosOptions -Value 0 -Verbose
}
- -
category: Remove bloatware category: Remove bloatware
children: children:
@@ -2834,7 +3013,7 @@ actions:
packageName: Microsoft.Messaging packageName: Microsoft.Messaging
- -
name: Mixed Reality Portal app name: Mixed Reality Portal app
docs: https://www.microsoft.com/en-us/p/mixed-reality-portal docs: https://www.microsoft.com/en-us/p/mixed-reality-portal/9ng1h8b3zc7m
call: call:
function: UninstallStoreApp function: UninstallStoreApp
parameters: parameters:
@@ -2852,7 +3031,7 @@ actions:
packageName: Microsoft.MicrosoftOfficeHub packageName: Microsoft.MicrosoftOfficeHub
- -
name: OneNote app name: OneNote app
docs: https://www.microsoft.com/en-us/p/onenote-for-windows-10 docs: https://www.microsoft.com/en-us/p/onenote-for-windows-10/9wzdncrfhvjl
call: call:
function: UninstallStoreApp function: UninstallStoreApp
parameters: parameters:
@@ -3359,7 +3538,7 @@ actions:
parameters: parameters:
packageName: Microsoft.Windows.CapturePicker packageName: Microsoft.Windows.CapturePicker
- -
name: Cloud Experience Host app # Allows to connect to corporate domains or Microsoft cloud based services name: Cloud Experience Host app (breaks Microsoft cloud/corporate sign in) # Allows to connect to corporate domains or Microsoft cloud based services
recommend: strict recommend: strict
call: call:
function: UninstallSystemApp function: UninstallSystemApp
@@ -3459,12 +3638,13 @@ actions:
function: UninstallSystemApp function: UninstallSystemApp
parameters: parameters:
packageName: Microsoft.Windows.SecureAssessmentBrowser packageName: Microsoft.Windows.SecureAssessmentBrowser
- # -
name: Start app # # Not a bloatware, required for different setting windows such as WiFi and battery panes in action bar
call: # name: Start app
function: UninstallSystemApp # call:
parameters: # function: UninstallSystemApp
packageName: Microsoft.Windows.ShellExperienceHost # parameters:
# packageName: Microsoft.Windows.ShellExperienceHost
- -
category: Windows Feedback category: Windows Feedback
children: children:
@@ -3496,12 +3676,13 @@ actions:
function: UninstallSystemApp function: UninstallSystemApp
parameters: parameters:
packageName: Windows.ContactSupport packageName: Windows.ContactSupport
- # -
name: Settings app # # Not a bloatware, required for core OS functinoality
call: # name: Settings app
function: UninstallSystemApp # call:
parameters: # function: UninstallSystemApp
packageName: Windows.immersivecontrolpanel # parameters:
# packageName: Windows.immersivecontrolpanel
- -
name: Windows Print 3D app name: Windows Print 3D app
call: call:
@@ -3622,22 +3803,31 @@ actions:
children: children:
- -
name: Direct Play feature name: Direct Play feature
code: dism /Online /Disable-Feature /FeatureName:"DirectPlay" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"DirectPlay" /NoRestart function: DisableFeature
parameters:
featureName: DirectPlay
- -
name: Internet Explorer feature name: Internet Explorer feature
code: |- call:
dism /Online /Disable-Feature /FeatureName:"Internet-Explorer-Optional-x64" /NoRestart -
dism /Online /Disable-Feature /FeatureName:"Internet-Explorer-Optional-x84" /NoRestart function: DisableFeature
dism /Online /Disable-Feature /FeatureName:"Internet-Explorer-Optional-amd64" /NoRestart parameters:
revertCode: |- featureName: Internet-Explorer-Optional-x64
dism /Online /Enable-Feature /FeatureName:"Internet-Explorer-Optional-x64" /NoRestart -
dism /Online /Enable-Feature /FeatureName:"Internet-Explorer-Optional-x84" /NoRestart function: DisableFeature
dism /Online /Enable-Feature /FeatureName:"Internet-Explorer-Optional-amd64" /NoRestart parameters:
featureName: Internet-Explorer-Optional-x84
-
function: DisableFeature
parameters:
featureName: Internet-Explorer-Optional-amd64
- -
name: Legacy Components feature name: Legacy Components feature
code: dism /Online /Disable-Feature /FeatureName:"LegacyComponents" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"LegacyComponents" /NoRestart function: DisableFeature
parameters:
featureName: LegacyComponents
- -
category: Server features for developers & administrators category: Server features for developers & administrators
children: children:
@@ -3646,39 +3836,55 @@ actions:
children: children:
- -
name: Hyper-V feature name: Hyper-V feature
code: dism /Online /Disable-Feature /FeatureName:"Microsoft-Hyper-V-All" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"Microsoft-Hyper-V-All" /NoRestart function: DisableFeature
parameters:
featureName: Microsoft-Hyper-V-All
- -
name: Hyper-V GUI Management Tools feature name: Hyper-V GUI Management Tools feature
code: dism /Online /Disable-Feature /FeatureName:"Microsoft-Hyper-V-Management-Clients" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"Microsoft-Hyper-V-Management-Clients" /NoRestart function: DisableFeature
parameters:
featureName: Microsoft-Hyper-V-Management-Clients
- -
name: Hyper-V Management Tools feature name: Hyper-V Management Tools feature
code: dism /Online /Disable-Feature /FeatureName:"Microsoft-Hyper-V-Tools-All" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"Microsoft-Hyper-V-Tools-All" /NoRestart function: DisableFeature
parameters:
featureName: Microsoft-Hyper-V-Tools-All
- -
name: Hyper-V Module for Windows PowerShell feature name: Hyper-V Module for Windows PowerShell feature
code: dism /Online /Disable-Feature /FeatureName:"Microsoft-Hyper-V-Management-PowerShell" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"Microsoft-Hyper-V-Management-PowerShell" /NoRestart function: DisableFeature
parameters:
featureName: Microsoft-Hyper-V-Management-PowerShell
- -
name: Telnet Client feature name: Telnet Client feature
code: dism /Online /Disable-Feature /FeatureName:"TelnetClient" /NoRestart
revertCode: dism /Online /Enable-Feature /FeatureName:"TelnetClient" /NoRestart
docs: https://social.technet.microsoft.com/wiki/contents/articles/38433.windows-10-enabling-telnet-client.aspx docs: https://social.technet.microsoft.com/wiki/contents/articles/38433.windows-10-enabling-telnet-client.aspx
call:
function: DisableFeature
parameters:
featureName: TelnetClient
- -
name: Net.TCP Port Sharing feature name: Net.TCP Port Sharing feature
code: dism /Online /Disable-Feature /FeatureName:"WCF-TCP-PortSharing45" /NoRestart
revertCode: dism /Online /Enable-Feature /FeatureName:"WCF-TCP-PortSharing45" /NoRestart
docs: https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/net-tcp-port-sharing docs: https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/net-tcp-port-sharing
call:
function: DisableFeature
parameters:
featureName: WCF-TCP-PortSharing45
- -
name: SMB Direct feature name: SMB Direct feature
code: dism /Online /Disable-Feature /FeatureName:"SmbDirect" /NoRestart
revertCode: dism /Online /Enable-Feature /FeatureName:"SmbDirect" /NoRestart
docs: https://docs.microsoft.com/en-us/windows-server/storage/file-server/smb-direct docs: https://docs.microsoft.com/en-us/windows-server/storage/file-server/smb-direct
call:
function: DisableFeature
parameters:
featureName: SmbDirect
- -
name: TFTP Client feature name: TFTP Client feature
code: dism /Online /Disable-Feature /FeatureName:"TFTP" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"TFTP" /NoRestart function: DisableFeature
parameters:
featureName: TFTP
- -
category: Printing features category: Printing features
children: children:
@@ -3687,60 +3893,86 @@ actions:
children: children:
- -
name: Internet Printing Client name: Internet Printing Client
code: dism /Online /Disable-Feature /FeatureName:"Printing-Foundation-InternetPrinting-Client" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"Printing-Foundation-InternetPrinting-Client" /NoRestart function: DisableFeature
parameters:
featureName: Printing-Foundation-InternetPrinting-Client
- -
name: LPD Print Service name: LPD Print Service
code: dism /Online /Disable-Feature /FeatureName:"Printing-Foundation-LPDPrintService" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"Printing-Foundation-LPDPrintService" /NoRestart function: DisableFeature
parameters:
featureName: LPDPrintService
- -
name: LPR Port Monitor feature name: LPR Port Monitor feature
code: dism /Online /Disable-Feature /FeatureName:"Printing-Foundation-LPRPortMonitor" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"Printing-Foundation-LPRPortMonitor" /NoRestart function: DisableFeature
parameters:
featureName: Printing-Foundation-LPRPortMonitor
- -
name: Microsoft Print to PDF feature name: Microsoft Print to PDF feature
code: dism /Online /Disable-Feature /FeatureName:"Printing-PrintToPDFServices-Features" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"Printing-PrintToPDFServices-Features" /NoRestart function: DisableFeature
parameters:
featureName: Printing-PrintToPDFServices-Features
- -
name: Print and Document Services feature name: Print and Document Services feature
code: dism /Online /Disable-Feature /FeatureName:"Printing-Foundation-Features" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"Printing-Foundation-Features" /NoRestart function: DisableFeature
parameters:
featureName: Printing-Foundation-Features
- -
name: Work Folders Client feature name: Work Folders Client feature
code: dism /Online /Disable-Feature /FeatureName:"WorkFolders-Client" /NoRestart
revertCode: dism /Online /Enable-Feature /FeatureName:"WorkFolders-Client" /NoRestart
docs: https://docs.microsoft.com/en-us/windows-server/storage/work-folders/work-folders-overview docs: https://docs.microsoft.com/en-us/windows-server/storage/work-folders/work-folders-overview
call:
function: DisableFeature
parameters:
featureName: WorkFolders-Client
- -
category: XPS support category: XPS support
children: children:
- -
name: XPS Services feature name: XPS Services feature
code: dism /Online /Disable-Feature /FeatureName:"Printing-XPSServices-Features" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"Printing-XPSServices-Features" /NoRestart function: DisableFeature
parameters:
featureName: Printing-XPSServices-Features
- -
name: XPS Viewer feature name: XPS Viewer feature
code: dism /Online /Disable-Feature /FeatureName:"Xps-Foundation-Xps-Viewer" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"Xps-Foundation-Xps-Viewer" /NoRestart function: DisableFeature
parameters:
featureName: Xps-Foundation-Xps-Viewer
- -
name: Media Features feature name: Media Features feature
code: dism /Online /Disable-Feature /FeatureName:"MediaPlayback" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"MediaPlayback" /NoRestart function: DisableFeature
parameters:
featureName: MediaPlayback
- -
name: Scan Management feature name: Scan Management feature
code: dism /Online /Disable-Feature /FeatureName:"ScanManagementConsole" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"ScanManagementConsole" /NoRestart function: DisableFeature
parameters:
featureName: ScanManagementConsole
- -
name: Windows Fax and Scan feature name: Windows Fax and Scan feature
code: dism /Online /Disable-Feature /FeatureName:"FaxServicesClientPackage" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"FaxServicesClientPackage" /NoRestart function: DisableFeature
parameters:
featureName: FaxServicesClientPackage
- -
name: Windows Media Player feature name: Windows Media Player feature
code: dism /Online /Disable-Feature /FeatureName:"WindowsMediaPlayer" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"WindowsMediaPlayer" /NoRestart function: DisableFeature
parameters:
featureName: WindowsMediaPlayer
- -
name: Windows Search feature name: Windows Search feature
code: dism /Online /Disable-Feature /FeatureName:"SearchEngine-Client-Package" /NoRestart call:
revertCode: dism /Online /Enable-Feature /FeatureName:"SearchEngine-Client-Package" /NoRestart function: DisableFeature
parameters:
featureName: SearchEngine-Client-Package
- -
category: Uninstall capabilities & features on demand category: Uninstall capabilities & features on demand
docs: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/features-on-demand-non-language-fod#fods-that-are-not-preinstalled-but-may-need-to-be-preinstalled docs: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/features-on-demand-non-language-fod#fods-that-are-not-preinstalled-but-may-need-to-be-preinstalled
@@ -4138,67 +4370,86 @@ actions:
copy "%~dpnx0" "%AppData%\Microsoft\Windows\Start Menu\Programs\Startup\privacy-cleanup.bat" copy "%~dpnx0" "%AppData%\Microsoft\Windows\Start Menu\Programs\Startup\privacy-cleanup.bat"
revertCode: del /f /q %AppData%\Microsoft\Windows\Start Menu\Programs\Startup\privacy-cleanup.bat revertCode: del /f /q %AppData%\Microsoft\Windows\Start Menu\Programs\Startup\privacy-cleanup.bat
functions: functions:
-
name: KillProcessWhenItStarts
parameters: [ processName ]
# https://docs.microsoft.com/en-us/previous-versions/windows/desktop/xperf/image-file-execution-options
code: reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\'{{ $processName }}'" /v "Debugger" /t REG_SZ /d "%windir%\System32\taskkill.exe" /f
revertCode: reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\'{{ $processName }}'" /v "Debugger" /f
-
name: DisableFeature
parameters: [ featureName ]
code: dism /Online /Disable-Feature /FeatureName:"{{ $featureName }}" /NoRestart
revertCode: dism /Online /Enable-Feature /FeatureName:"{{ $featureName }}" /NoRestart
- -
name: UninstallStoreApp name: UninstallStoreApp
parameters: [ packageName ] parameters: [ packageName ]
code: PowerShell -Command "Get-AppxPackage '{{ $packageName }}' | Remove-AppxPackage" call:
revertCode: function: RunPowerShell
PowerShell -ExecutionPolicy Unrestricted -Command " parameters:
$package = Get-AppxPackage -AllUsers '{{ $packageName }}'; code: Get-AppxPackage '{{ $packageName }}' | Remove-AppxPackage
if (!$package) { revertCode:
Write-Error \"Cannot reinstall '{{ $packageName }}'\" -ErrorAction Stop $package = Get-AppxPackage -AllUsers '{{ $packageName }}';
} if (!$package) {
$manifest = $package.InstallLocation + '\AppxManifest.xml'; Write-Error \"Cannot reinstall '{{ $packageName }}'\" -ErrorAction Stop
Add-AppxPackage -DisableDevelopmentMode -Register \"$manifest\" " }
$manifest = $package.InstallLocation + '\AppxManifest.xml';
Add-AppxPackage -DisableDevelopmentMode -Register \"$manifest\"
- -
name: UninstallSystemApp name: UninstallSystemApp
parameters: [ packageName ] parameters: [ packageName ]
# It simply renames files # It simply renames files
# Because system apps are non removable (check: (Get-AppxPackage -AllUsers 'Windows.CBSPreview').NonRemovable) # Because system apps are non removable (check: (Get-AppxPackage -AllUsers 'Windows.CBSPreview').NonRemovable)
# Otherwise they throw 0x80070032 when trying to uninstall them # Otherwise they throw 0x80070032 when trying to uninstall them
code: call:
PowerShell -Command " function: RunPowerShell
$package = (Get-AppxPackage -AllUsers '{{ $packageName }}'); parameters:
if (!$package) { code:
Write-Host 'Not installed'; $package = (Get-AppxPackage -AllUsers '{{ $packageName }}');
exit 0; if (!$package) {
} Write-Host 'Not installed';
$directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\"); exit 0;
foreach($dir in $directories) { }
if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; } $directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\");
cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; } foreach($dir in $directories) {
cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; } if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; }
$files = Get-ChildItem -File -Path $dir -Recurse -Force; cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
foreach($file in $files) { cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
if($file.Name.EndsWith('.OLD')) { continue; } $files = Get-ChildItem -File -Path $dir -Recurse -Force;
$newName = $file.FullName + '.OLD'; foreach($file in $files) {
Write-Host \"Rename '$($file.FullName)' to '$newName'\"; if($file.Name.EndsWith('.OLD')) { continue; }
Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force; $newName = $file.FullName + '.OLD';
} Write-Host \"Rename '$($file.FullName)' to '$newName'\";
};" Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force;
revertCode: }
PowerShell -Command " }
$package = (Get-AppxPackage -AllUsers '{{ $packageName }}'); revertCode:
if (!$package) { $package = (Get-AppxPackage -AllUsers '{{ $packageName }}');
Write-Error 'App could not be found' -ErrorAction Stop; if (!$package) {
} Write-Error 'App could not be found' -ErrorAction Stop;
$directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\"); }
foreach($dir in $directories) { $directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\");
if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; } foreach($dir in $directories) {
cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; } if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; }
cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; } cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
$files = Get-ChildItem -File -Path \"$dir\*.OLD\" -Recurse -Force; cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
foreach($file in $files) { $files = Get-ChildItem -File -Path \"$dir\*.OLD\" -Recurse -Force;
$newName = $file.FullName.Substring(0, $file.FullName.Length - 4); foreach($file in $files) {
Write-Host \"Rename '$($file.FullName)' to '$newName'\"; $newName = $file.FullName.Substring(0, $file.FullName.Length - 4);
Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force; Write-Host \"Rename '$($file.FullName)' to '$newName'\";
} Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force;
};" }
}
- -
name: UninstallCapability name: UninstallCapability
parameters: [ capabilityName ] parameters: [ capabilityName ]
code: PowerShell -Command "Get-WindowsCapability -Online -Name '{{ $capabilityName }}*' | Remove-WindowsCapability -Online" call:
revertCode: PowerShell -Command "$capability = Get-WindowsCapability -Online -Name '{{ $capabilityName }}*'; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: RunPowerShell
parameters:
code: Get-WindowsCapability -Online -Name '{{ $capabilityName }}*' | Remove-WindowsCapability -Online
revertCode:
$capability = Get-WindowsCapability -Online -Name '{{ $capabilityName }}*';
Add-WindowsCapability -Name \"$capability.Name\" -Online
- -
name: RenameSystemFile name: RenameSystemFile
parameters: [ filePath ] parameters: [ filePath ]
@@ -4220,3 +4471,31 @@ functions:
) else ( ) else (
echo Could not find backup file "{{ $filePath }}.OLD" 1>&2 echo Could not find backup file "{{ $filePath }}.OLD" 1>&2
) )
-
name: SetVsCodeSetting
parameters: [ setting, powerShellValue ]
call:
function: RunPowerShell
parameters:
code:
$jsonfile = \"$env:APPDATA\Code\User\settings.json\";
if (!(Test-Path $jsonfile -PathType Leaf)) {
Write-Host \"No updates. Settings file was not at $jsonfile\";
exit 0;
}
$json = Get-Content $jsonfile | Out-String | ConvertFrom-Json;
$json | Add-Member -Type NoteProperty -Name '{{ $setting }}' -Value {{ $powerShellValue }} -Force;
$json | ConvertTo-Json | Set-Content $jsonfile;
revertCode:
$jsonfile = \"$env:APPDATA\Code\User\settings.json\";
if (!(Test-Path $jsonfile -PathType Leaf)) {
Write-Error \"Settings file could not be found at $jsonfile\" -ErrorAction Stop;
}
$json = Get-Content $jsonfile | ConvertFrom-Json;
$json.PSObject.Properties.Remove('{{ $setting }}');
$json | ConvertTo-Json | Set-Content $jsonfile;
-
name: RunPowerShell
parameters: [ code, revertCode ]
code: PowerShell -ExecutionPolicy Unrestricted -Command "{{ $code }}"
revertCode: PowerShell -ExecutionPolicy Unrestricted -Command "{{ $revertCode }}"

View File

@@ -1,4 +1,4 @@
import { getEnumNames, getEnumValues } from '@/application/Common/Enum'; import { getEnumNames, getEnumValues, assertInRange } from '@/application/Common/Enum';
import { IEntity } from '../infrastructure/Entity/IEntity'; import { IEntity } from '../infrastructure/Entity/IEntity';
import { ICategory } from './ICategory'; import { ICategory } from './ICategory';
import { IScript } from './IScript'; import { IScript } from './IScript';
@@ -21,7 +21,7 @@ export class CategoryCollection implements ICategoryCollection {
throw new Error('undefined scripting definition'); throw new Error('undefined scripting definition');
} }
this.queryable = makeQueryable(actions); this.queryable = makeQueryable(actions);
ensureValidOs(os); assertInRange(os, OperatingSystem);
ensureValid(this.queryable); ensureValid(this.queryable);
ensureNoDuplicates(this.queryable.allCategories); ensureNoDuplicates(this.queryable.allCategories);
ensureNoDuplicates(this.queryable.allScripts); ensureNoDuplicates(this.queryable.allScripts);
@@ -54,18 +54,6 @@ export class CategoryCollection implements ICategoryCollection {
} }
} }
function ensureValidOs(os: OperatingSystem): void {
if (os === undefined) {
throw new Error('undefined os');
}
if (os === OperatingSystem.Unknown) {
throw new Error('unknown os');
}
if (!(os in OperatingSystem)) {
throw new Error(`os "${os}" is out of range`);
}
}
function ensureNoDuplicates<TKey>(entities: ReadonlyArray<IEntity<TKey>>) { function ensureNoDuplicates<TKey>(entities: ReadonlyArray<IEntity<TKey>>) {
const totalOccurrencesById = new Map<TKey, number>(); const totalOccurrencesById = new Map<TKey, number>();
for (const entity of entities) { for (const entity of entities) {

View File

@@ -10,5 +10,4 @@ export enum OperatingSystem {
Android, Android,
iOS, iOS,
WindowsPhone, WindowsPhone,
Unknown,
} }

View File

@@ -1,5 +1,6 @@
import { IProjectInformation } from './IProjectInformation'; import { IProjectInformation } from './IProjectInformation';
import { OperatingSystem } from './OperatingSystem'; import { OperatingSystem } from './OperatingSystem';
import { assertInRange } from '@/application/Common/Enum';
export class ProjectInformation implements IProjectInformation { export class ProjectInformation implements IProjectInformation {
public readonly repositoryWebUrl: string; public readonly repositoryWebUrl: string;
@@ -42,6 +43,7 @@ function getWebUrl(gitUrl: string) {
} }
function getFileName(os: OperatingSystem, version: string): string { function getFileName(os: OperatingSystem, version: string): string {
assertInRange(os, OperatingSystem);
switch (os) { switch (os) {
case OperatingSystem.Linux: case OperatingSystem.Linux:
return `privacy.sexy-${version}.AppImage`; return `privacy.sexy-${version}.AppImage`;
@@ -50,6 +52,6 @@ function getFileName(os: OperatingSystem, version: string): string {
case OperatingSystem.Windows: case OperatingSystem.Windows:
return `privacy.sexy-Setup-${version}.exe`; return `privacy.sexy-Setup-${version}.exe`;
default: default:
throw new Error(`Unsupported os: ${OperatingSystem[os]}`); throw new RangeError(`Unsupported os: ${OperatingSystem[os]}`);
} }
} }

View File

@@ -4,18 +4,10 @@ export class ScriptCode implements IScriptCode {
constructor( constructor(
public readonly execute: string, public readonly execute: string,
public readonly revert: string, public readonly revert: string,
scriptName: string,
syntax: ILanguageSyntax) { syntax: ILanguageSyntax) {
if (!scriptName) { throw new Error('script name is undefined'); } if (!syntax) { throw new Error('undefined syntax'); }
if (!syntax) { throw new Error('syntax is undefined'); } validateCode(execute, syntax);
validateCode(scriptName, execute, syntax); validateRevertCode(revert, execute, syntax);
if (revert) {
scriptName = `${scriptName} (revert)`;
validateCode(scriptName, revert, syntax);
if (execute === revert) {
throw new Error(`${scriptName}: Code itself and its reverting code cannot be the same`);
}
}
} }
} }
@@ -24,21 +16,35 @@ export interface ILanguageSyntax {
readonly commonCodeParts: string[]; readonly commonCodeParts: string[];
} }
function validateCode(name: string, code: string, syntax: ILanguageSyntax): void { function validateRevertCode(revertCode: string, execute: string, syntax: ILanguageSyntax) {
if (!revertCode) {
return;
}
try {
validateCode(revertCode, syntax);
if (execute === revertCode) {
throw new Error(`Code itself and its reverting code cannot be the same`);
}
} catch (err) {
throw Error(`(revert): ${err.message}`);
}
}
function validateCode(code: string, syntax: ILanguageSyntax): void {
if (!code || code.length === 0) { if (!code || code.length === 0) {
throw new Error(`code of ${name} is empty or undefined`); throw new Error(`code is empty or undefined`);
} }
ensureNoEmptyLines(name, code); ensureNoEmptyLines(code);
ensureCodeHasUniqueLines(name, code, syntax); ensureCodeHasUniqueLines(code, syntax);
} }
function ensureNoEmptyLines(name: string, code: string): void { function ensureNoEmptyLines(code: string): void {
if (code.split('\n').some((line) => line.trim().length === 0)) { if (code.split('\n').some((line) => line.trim().length === 0)) {
throw Error(`script has empty lines "${name}"`); throw Error(`script has empty lines`);
} }
} }
function ensureCodeHasUniqueLines(name: string, code: string, syntax: ILanguageSyntax): void { function ensureCodeHasUniqueLines(code: string, syntax: ILanguageSyntax): void {
const lines = code.split('\n') const lines = code.split('\n')
.filter((line) => !shouldIgnoreLine(line, syntax)); .filter((line) => !shouldIgnoreLine(line, syntax));
if (lines.length === 0) { if (lines.length === 0) {
@@ -46,7 +52,7 @@ function ensureCodeHasUniqueLines(name: string, code: string, syntax: ILanguageS
} }
const duplicateLines = lines.filter((e, i, a) => a.indexOf(e) !== i); const duplicateLines = lines.filter((e, i, a) => a.indexOf(e) !== i);
if (duplicateLines.length !== 0) { if (duplicateLines.length !== 0) {
throw Error(`Duplicates detected in script "${name}":\n ${duplicateLines.join('\n')}`); throw Error(`Duplicates detected in script:\n${duplicateLines.map((line, index) => `(${index}) - ${line}`).join('\n')}`);
} }
} }

View File

@@ -1,4 +1,3 @@
export class Clipboard { export class Clipboard {
public static copyText(text: string): void { public static copyText(text: string): void {
const el = document.createElement('textarea'); const el = document.createElement('textarea');

View File

@@ -0,0 +1,73 @@
import { Environment } from '@/application/Environment/Environment';
import os from 'os';
import path from 'path';
import fs from 'fs';
import child_process from 'child_process';
import { OperatingSystem } from '@/domain/OperatingSystem';
export class CodeRunner {
constructor(
private readonly node = getNodeJs(),
private readonly environment = Environment.CurrentEnvironment) {
}
public async runCodeAsync(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}`);
await this.node.fs.promises.writeFile(filePath, code);
await this.node.fs.promises.chmod(filePath, '755');
const command = getExecuteCommand(filePath, this.environment);
this.node.child_process.exec(command);
}
}
function getExecuteCommand(scriptPath: string, environment: Environment): string {
switch (environment.os) {
case OperatingSystem.macOS:
return `open -a Terminal.app ${scriptPath}`;
// Another option with graphical sudo would be
// `osascript -e "do shell script \\"${scriptPath}\\" with administrator privileges"`
// However it runs in background
case OperatingSystem.Windows:
return scriptPath;
default:
throw Error('undefined os');
}
}
function getNodeJs(): INodeJs {
return { os, path, fs, child_process };
}
export interface INodeJs {
os: INodeOs;
path: INodePath;
fs: INodeFs;
child_process: INodeChildProcess;
}
export interface INodeOs {
tmpdir(): string;
}
export interface INodePath {
join(...paths: string[]): string;
}
export interface INodeChildProcess {
exec(command: string): void;
}
export interface INodeFs {
readonly promises: INodeFsPromises;
}
interface INodeFsPromisesMakeDirectoryOptions {
recursive?: boolean;
}
interface INodeFsPromises { // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/v13/fs.d.ts
chmod(path: string, mode: string | number): Promise<void>;
mkdir(path: string, options: INodeFsPromisesMakeDirectoryOptions): Promise<string>;
writeFile(path: string, data: string): Promise<void>;
}

View File

@@ -1,7 +1,6 @@
import { EventHandler, ISignal } from './ISignal'; import { EventHandler, IEventSource, IEventSubscription } from './IEventSource';
import { IEventSubscription } from './ISubscription';
export class Signal<T> implements ISignal<T> { export class EventSource<T> implements IEventSource<T> {
private handlers = new Map<number, EventHandler<T>>(); private handlers = new Map<number, EventHandler<T>>();
public on(handler: EventHandler<T>): IEventSubscription { public on(handler: EventHandler<T>): IEventSubscription {

View File

@@ -0,0 +1,12 @@
import { IEventSubscription } from './IEventSource';
export class EventSubscriptionCollection {
private readonly subscriptions = new Array<IEventSubscription>();
public register(...subscriptions: IEventSubscription[]) {
this.subscriptions.push(...subscriptions);
}
public unsubscribeAll() {
this.subscriptions.forEach((listener) => listener.unsubscribe());
this.subscriptions.splice(0, this.subscriptions.length);
}
}

View File

@@ -1,6 +1,11 @@
import { IEventSubscription } from './ISubscription'; export interface IEventSource<T> {
export interface ISignal<T> {
on(handler: EventHandler<T>): IEventSubscription; on(handler: EventHandler<T>): IEventSubscription;
} }
export interface IEventSubscription {
unsubscribe(): void;
}
export type EventHandler<T> = (data: T) => void; export type EventHandler<T> = (data: T) => void;

View File

@@ -1,3 +0,0 @@
export interface IEventSubscription {
unsubscribe(): void;
}

View File

@@ -1,7 +1,7 @@
import { Signal } from '../Events/Signal'; import { EventSource } from '../Events/EventSource';
export class AsyncLazy<T> { export class AsyncLazy<T> {
private valueCreated = new Signal(); private valueCreated = new EventSource();
private isValueCreated = false; private isValueCreated = false;
private isCreatingValue = false; private isCreatingValue = false;
private value: T | undefined; private value: T | undefined;
@@ -15,7 +15,7 @@ export class AsyncLazy<T> {
public async getValueAsync(): Promise<T> { public async getValueAsync(): Promise<T> {
// If value is already created, return the value directly // If value is already created, return the value directly
if (this.isValueCreated) { if (this.isValueCreated) {
return Promise.resolve(this.value as T); return Promise.resolve(this.value);
} }
// If value is being created, wait until the value is created and then return it. // If value is being created, wait until the value is created and then return it.
if (this.isCreatingValue) { if (this.isCreatingValue) {

View File

@@ -1,181 +0,0 @@
<template>
<div class="container">
<div class="part select">Select:</div>
<div class="part">
<div class="part">
<SelectableOption
label="None"
:enabled="this.currentSelection == SelectionState.None"
@click="selectAsync(SelectionState.None)"
v-tooltip=" 'Deselect all selected scripts.<br/>' +
'💡 Good start to dive deeper into tweaks and select only what you want.'"
/>
</div>
<div class="part"> | </div>
<div class="part">
<SelectableOption
label="Standard"
:enabled="this.currentSelection == SelectionState.Standard"
@click="selectAsync(SelectionState.Standard)"
v-tooltip=" '🛡️ Balanced for privacy and functionality.<br/>' +
'OS and applications will function normally.<br/>' +
'💡 Recommended for everyone'"
/>
</div>
<div class="part"> | </div>
<div class="part">
<SelectableOption
label="Strict"
:enabled="this.currentSelection == SelectionState.Strict"
@click="selectAsync(SelectionState.Strict)"
v-tooltip=" '🚫 Stronger privacy, disables risky functions that may leak your data.<br/>' +
'⚠️ Double check to remove sripts where you would trade functionality for privacy<br/>' +
'💡 Recommended for daily users that prefers more privacy over non-essential functions'"
/>
</div>
<div class="part"> | </div>
<div class="part">
<SelectableOption
label="All"
:enabled="this.currentSelection == SelectionState.All"
@click="selectAsync(SelectionState.All)"
v-tooltip=" '🔒 Strongest privacy, disabling any functionality that may leak your data.<br/>' +
'🛑 Not designed for daily users, it will break important functionalities.<br/>' +
'💡 Only recommended for extreme use-cases like crime labs where no leak is acceptable'"
/>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import SelectableOption from './SelectableOption.vue';
import { IScript } from '@/domain/IScript';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplication } from '@/domain/IApplication';
enum SelectionState {
Standard,
Strict,
All,
None,
Custom,
}
@Component({
components: {
SelectableOption,
},
})
export default class TheSelector extends StatefulVue {
public SelectionState = SelectionState;
public currentSelection = SelectionState.None;
public async selectAsync(type: SelectionState): Promise<void> {
if (this.currentSelection === type) {
return;
}
const context = await this.getCurrentContextAsync();
selectType(context.state, type);
}
protected initialize(app: IApplication): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
this.updateSelections(newState);
newState.selection.changed.on(() => this.updateSelections(newState));
if (oldState) {
oldState.selection.changed.on(() => this.updateSelections(oldState));
}
}
private updateSelections(state: ICategoryCollectionState) {
this.currentSelection = getCurrentSelectionState(state);
}
}
interface ITypeSelector {
isSelected: (state: ICategoryCollectionState) => boolean;
select: (state: ICategoryCollectionState) => void;
}
const selectors = new Map<SelectionState, ITypeSelector>([
[SelectionState.None, {
select: (state) =>
state.selection.deselectAll(),
isSelected: (state) =>
state.selection.totalSelected === 0,
}],
[SelectionState.Standard, {
select: (state) =>
state.selection.selectOnly(
state.collection.getScriptsByLevel(RecommendationLevel.Standard)),
isSelected: (state) =>
hasAllSelectedLevelOf(RecommendationLevel.Standard, state),
}],
[SelectionState.Strict, {
select: (state) =>
state.selection.selectOnly(state.collection.getScriptsByLevel(RecommendationLevel.Strict)),
isSelected: (state) =>
hasAllSelectedLevelOf(RecommendationLevel.Strict, state),
}],
[SelectionState.All, {
select: (state) =>
state.selection.selectAll(),
isSelected: (state) =>
state.selection.totalSelected === state.collection.totalScripts,
}],
]);
function selectType(state: ICategoryCollectionState, type: SelectionState) {
const selector = selectors.get(type);
selector.select(state);
}
function getCurrentSelectionState(state: ICategoryCollectionState): SelectionState {
for (const [type, selector] of Array.from(selectors.entries())) {
if (selector.isSelected(state)) {
return type;
}
}
return SelectionState.Custom;
}
function hasAllSelectedLevelOf(level: RecommendationLevel, state: ICategoryCollectionState) {
const scripts = state.collection.getScriptsByLevel(level);
const selectedScripts = state.selection.selectedScripts;
return areAllSelected(scripts, selectedScripts);
}
function areAllSelected(
expectedScripts: ReadonlyArray<IScript>,
selection: ReadonlyArray<SelectedScript>): boolean {
selection = selection.filter((selected) => !selected.revert);
if (expectedScripts.length < selection.length) {
return false;
}
const selectedScriptIds = selection.map((script) => script.id).sort();
const expectedScriptIds = expectedScripts.map((script) => script.id).sort();
return selectedScriptIds.every((id, index) => id === expectedScriptIds[index]);
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/fonts.scss";
.container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
.part {
display: flex;
margin-right:5px;
}
font-family: $normal-font;
}
</style>

View File

@@ -1,195 +0,0 @@
<template>
<div>
<div class="heading">
<TheSelector class="item"/>
<TheOsChanger class="item"/>
<TheGrouper
class="item"
v-on:groupingChanged="onGroupingChanged($event)"
v-if="!this.isSearching" />
</div>
<div class="scripts">
<div v-if="!isSearching">
<CardList v-if="currentGrouping === Grouping.Cards"/>
<div class="tree" v-if="currentGrouping === Grouping.None">
<ScriptsTree />
</div>
</div>
<div v-else> <!-- Searching -->
<div class="search">
<div class="search__query">
<div>Searching for "{{this.searchQuery | threeDotsTrim}}"</div>
<div class="search__query__close-button">
<font-awesome-icon
:icon="['fas', 'times']"
v-on:click="clearSearchQueryAsync()"/>
</div>
</div>
<div v-if="!searchHasMatches" class="search-no-matches">
<div>Sorry, no matches for "{{this.searchQuery | threeDotsTrim}}" 😞</div>
<div>Feel free to extend the scripts <a :href="repositoryUrl" target="_blank" class="child github" >here</a> </div>
</div>
</div>
<div v-if="searchHasMatches" class="tree tree--searching">
<ScriptsTree />
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import TheGrouper from '@/presentation/Scripts/Grouping/TheGrouper.vue';
import TheOsChanger from '@/presentation/Scripts/TheOsChanger.vue';
import TheSelector from '@/presentation/Scripts/Selector/TheSelector.vue';
import ScriptsTree from '@/presentation/Scripts/ScriptsTree/ScriptsTree.vue';
import CardList from '@/presentation/Scripts/Cards/CardList.vue';
import { Component } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import { Grouping } from './Grouping/Grouping';
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplication } from '@/domain/IApplication';
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
/** Shows content of single category or many categories */
@Component({
components: {
TheGrouper,
TheSelector,
ScriptsTree,
CardList,
TheOsChanger,
},
filters: {
threeDotsTrim(query: string) {
const threshold = 30;
if (query.length <= threshold - 3) {
return query;
}
return `${query.substr(0, threshold)}...`;
},
},
})
export default class TheScripts extends StatefulVue {
public repositoryUrl = '';
public Grouping = Grouping; // Make it accessible from view
public currentGrouping = Grouping.Cards;
public searchQuery = '';
public isSearching = false;
public searchHasMatches = false;
private listeners = new Array<IEventSubscription>();
public destroyed() {
this.unsubscribeAll();
}
public async clearSearchQueryAsync() {
const context = await this.getCurrentContextAsync();
const filter = context.state.filter;
filter.removeFilter();
}
public onGroupingChanged(group: Grouping) {
this.currentGrouping = group;
}
protected initialize(app: IApplication): void {
this.repositoryUrl = app.info.repositoryWebUrl;
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
this.unsubscribeAll();
this.subscribe(newState);
}
private subscribe(state: ICategoryCollectionState) {
this.listeners.push(state.filter.filterRemoved.on(() => {
this.isSearching = false;
}));
state.filter.filtered.on((result: IFilterResult) => {
this.searchQuery = result.query;
this.isSearching = true;
this.searchHasMatches = result.hasAnyMatches();
});
}
private unsubscribeAll() {
this.listeners.forEach((listener) => listener.unsubscribe());
this.listeners.splice(0, this.listeners.length);
}
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
@import "@/presentation/styles/fonts.scss";
$inner-margin: 4px;
.scripts {
margin-top: $inner-margin;
.tree {
padding-left: 3%;
padding-top: 15px;
padding-bottom: 15px;
&--searching {
padding-top: 0px;
}
}
}
.search {
display: flex;
flex-direction: column;
background-color: $slate;
&__query {
display: flex;
justify-content: center;
flex-direction: row;
align-items: center;
margin-top: 1em;
color: $gray;
&__close-button {
cursor: pointer;
font-size: 1.25em;
margin-left: 0.25rem;
&:hover {
opacity: 0.9;
}
}
}
&-no-matches {
display:flex;
flex-direction: column;
word-break:break-word;
text-transform: uppercase;
color: $light-gray;
font-size: 1.5em;
padding:10px;
text-align:center;
> div {
padding-bottom:13px;
}
a {
color: $gray;
}
}
}
.heading {
margin-top: $inner-margin;
display: flex;
flex-wrap: wrap;
.item {
flex: 1;
white-space: nowrap;
display: flex;
justify-content: center;
margin: 0 5px 0 5px;
&:first-child {
justify-content: flex-start;
}
&:last-child {
justify-content: flex-end;
}
}
}
</style>

View File

@@ -1,6 +1,10 @@
'use strict'; 'use strict';
import { app, protocol, BrowserWindow, shell } from 'electron'; // This is main process of Electron, started as first thing when app starts.
// This script is running through entire life of the application.
// It doesn't have any windows which you can see on screen, opens the main window from here.
import { app, protocol, BrowserWindow, shell, screen } from 'electron';
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'; import { createProtocol } from 'vue-cli-plugin-electron-builder/lib';
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'; import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer';
import path from 'path'; import path from 'path';
@@ -30,10 +34,12 @@ if (!process.env.IS_TEST) {
function createWindow() { function createWindow() {
// Create the browser window. // Create the browser window.
const size = getWindowSize(1350, 955);
win = new BrowserWindow({ win = new BrowserWindow({
width: 1350, width: size.width,
height: 955, height: size.height,
webPreferences: { webPreferences: {
contextIsolation: false, // To reach node https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1285
// Use pluginOptions.nodeIntegration, leave this alone // Use pluginOptions.nodeIntegration, leave this alone
// See https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration // See https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration
nodeIntegration: (process.env nodeIntegration: (process.env
@@ -110,14 +116,14 @@ if (isDevelopment) {
function loadApplication(window: BrowserWindow) { function loadApplication(window: BrowserWindow) {
if (process.env.WEBPACK_DEV_SERVER_URL) { if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode // Load the url of the dev server if in development mode
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string); loadUrlWithNodeWorkaround(win, process.env.WEBPACK_DEV_SERVER_URL as string);
if (!process.env.IS_TEST) { if (!process.env.IS_TEST) {
win.webContents.openDevTools(); win.webContents.openDevTools();
} }
} else { } else {
createProtocol('app'); createProtocol('app');
// Load the index.html when not in development // Load the index.html when not in development
win.loadURL('app://./index.html'); loadUrlWithNodeWorkaround(win, 'app://./index.html');
// tslint:disable-next-line:max-line-length // tslint:disable-next-line:max-line-length
autoUpdater.checkForUpdatesAndNotify(); // https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#check-for-updates-in-background-js-ts autoUpdater.checkForUpdatesAndNotify(); // https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#check-for-updates-in-background-js-ts
} }
@@ -131,3 +137,19 @@ function configureExternalsUrlsOpenBrowser(window: BrowserWindow) {
} }
}); });
} }
// Workaround for https://github.com/electron/electron/issues/19554 otherwise fs does not work
function loadUrlWithNodeWorkaround(window: BrowserWindow, url: string) {
setTimeout(() => {
window.loadURL(url);
}, 10);
}
function getWindowSize(idealWidth: number, idealHeight: number) {
let { width, height } = screen.getPrimaryDisplay().workAreaSize;
// To ensure not creating a screen bigger than current screen size
// Not using "enableLargerThanScreen" as it's macOS only (see https://www.electronjs.org/docs/api/browser-window)
width = Math.min(width, idealWidth);
height = Math.min(height, idealHeight);
return { width, height };
}

View File

@@ -7,8 +7,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { faFolderOpen, faFolder, faSmile } from '@fortawesome/free-regular-svg-icons'; import { faFolderOpen, faFolder, faSmile } from '@fortawesome/free-regular-svg-icons';
/** SOLID ICONS (PREFIX: fas (default)) */ /** SOLID ICONS (PREFIX: fas (default)) */
import { faTimes, faFileDownload, faCopy, faSearch, faInfoCircle, faUserSecret, faDesktop, import { faTimes, faFileDownload, faCopy, faSearch, faInfoCircle, faUserSecret, faDesktop,
faTag, faGlobe, faSave, faBatteryFull, faBatteryHalf } from '@fortawesome/free-solid-svg-icons'; faTag, faGlobe, faSave, faBatteryFull, faBatteryHalf, faPlay, faArrowsAltH } from '@fortawesome/free-solid-svg-icons';
export class IconBootstrapper implements IVueBootstrapper { export class IconBootstrapper implements IVueBootstrapper {
public bootstrap(vue: VueConstructor): void { public bootstrap(vue: VueConstructor): void {
@@ -24,9 +23,12 @@ export class IconBootstrapper implements IVueBootstrapper {
faTimes, faTimes,
faFileDownload, faSave, faFileDownload, faSave,
faCopy, faCopy,
faPlay,
faSearch, faSearch,
faBatteryFull, faBatteryHalf, faBatteryFull, faBatteryHalf,
faInfoCircle); faInfoCircle,
faArrowsAltH,
);
vue.component('font-awesome-icon', FontAwesomeIcon); vue.component('font-awesome-icon', FontAwesomeIcon);
} }
} }

View File

@@ -1,31 +1,28 @@
<template> <template>
<div id="app"> <div id="app">
<div class="wrapper"> <div class="wrapper">
<TheHeader class="row" /> <TheHeader class="row" />
<TheSearchBar class="row" /> <TheSearchBar class="row" />
<TheScripts class="row"/> <TheScriptArea class="row" />
<TheCodeArea class="row" theme="xcode" /> <TheCodeButtons class="row code-buttons" />
<TheCodeButtons class="row code-buttons" /> <TheFooter />
<TheFooter />
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
import TheHeader from '@/presentation/TheHeader.vue'; import TheHeader from '@/presentation/components/TheHeader.vue';
import TheFooter from '@/presentation/TheFooter/TheFooter.vue'; import TheFooter from '@/presentation/components/TheFooter/TheFooter.vue';
import TheCodeArea from '@/presentation/TheCodeArea.vue'; import TheCodeButtons from '@/presentation/components/Code/CodeButtons/TheCodeButtons.vue';
import TheCodeButtons from '@/presentation/CodeButtons/TheCodeButtons.vue'; import TheScriptArea from '@/presentation/components/Scripts/TheScriptArea.vue';
import TheSearchBar from '@/presentation/TheSearchBar.vue'; import TheSearchBar from '@/presentation/components/TheSearchBar.vue';
import TheScripts from '@/presentation/Scripts/TheScripts.vue';
@Component({ @Component({
components: { components: {
TheHeader, TheHeader,
TheCodeArea,
TheCodeButtons, TheCodeButtons,
TheScripts, TheScriptArea,
TheSearchBar, TheSearchBar,
TheFooter, TheFooter,
}, },
@@ -38,6 +35,7 @@ export default class App extends Vue {
<style lang="scss"> <style lang="scss">
@import "@/presentation/styles/colors.scss"; @import "@/presentation/styles/colors.scss";
@import "@/presentation/styles/fonts.scss"; @import "@/presentation/styles/fonts.scss";
@import "@/presentation/styles/media.scss";
* { * {
box-sizing: border-box; box-sizing: border-box;
@@ -49,12 +47,10 @@ body {
color: $slate; color: $slate;
} }
#app { #app {
margin-right: auto; margin-right: auto;
margin-left: auto; margin-left: auto;
max-width: 1500px; max-width: 1600px;
.wrapper { .wrapper {
margin: 0% 2% 0% 2%; margin: 0% 2% 0% 2%;
background-color: white; background-color: white;
@@ -62,18 +58,15 @@ body {
padding: 2%; padding: 2%;
display:flex; display:flex;
flex-direction: column; flex-direction: column;
.row { .row {
margin-bottom: 10px; margin-bottom: 10px;
} }
.code-buttons { .code-buttons {
padding-bottom: 10px; padding-bottom: 10px;
} }
} }
} }
@import "@/presentation/styles/tooltip.scss"; @import "@/presentation/styles/tooltip.scss";
@import "@/presentation/styles/tree.scss"; @import "@/presentation/styles/tree.scss";
</style> </style>

View File

@@ -1,8 +1,8 @@
<template> <template>
<div class="instructions"> <div class="instructions">
<!-- <p> <p>
Since you're using online version of {{ this.appName }}, you will need to do additional steps after downloading the file to execute your script on macOS: Since you're using online version of {{ this.appName }}, you will need to do additional steps after downloading the file to execute your script on macOS:
</p> --> </p>
<p> <p>
<ol> <ol>
<li> <li>
@@ -73,36 +73,33 @@
</li> </li>
</ol> </ol>
</p> </p>
<!-- <p> <p>
Or download the <a :href="this.macOsDownloadUrl">offline version</a> to run your scripts directly to skip these steps. Or download the <a :href="this.macOsDownloadUrl">offline version</a> to run your scripts directly to skip these steps.
</p> --> </p>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import Code from './Code.vue'; import Code from './Code.vue';
import { IApplication } from '@/domain/IApplication';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { ApplicationFactory } from '@/application/ApplicationFactory';
@Component({ @Component({
components: { components: {
Code, Code,
}, },
}) })
export default class MacOsInstructions extends StatefulVue { export default class MacOsInstructions extends Vue {
@Prop() public fileName: string; @Prop() public fileName: string;
public appName = ''; public appName = '';
public macOsDownloadUrl = ''; public macOsDownloadUrl = '';
protected initialize(app: IApplication): void { public async created() {
const app = await ApplicationFactory.Current.getAppAsync();
this.appName = app.info.name; this.appName = app.info.name;
this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS); this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS);
} }
protected handleCollectionState(): void {
return;
}
} }
</script> </script>

View File

@@ -1,5 +1,11 @@
<template> <template>
<div class="container" v-if="hasCode"> <div class="container" v-if="hasCode">
<IconButton
v-if="this.canRun"
text="Run"
v-on:click="executeCodeAsync"
icon-prefix="fas" icon-name="play">
</IconButton>
<IconButton <IconButton
:text="this.isDesktopVersion ? 'Save' : 'Download'" :text="this.isDesktopVersion ? 'Save' : 'Download'"
v-on:click="saveCodeAsync" v-on:click="saveCodeAsync"
@@ -11,52 +17,43 @@
v-on:click="copyCodeAsync" v-on:click="copyCodeAsync"
icon-prefix="fas" icon-name="copy"> icon-prefix="fas" icon-name="copy">
</IconButton> </IconButton>
<modal :name="macOsModalName" height="auto" :scrollable="true" :adaptive="true" <Dialog v-if="this.isMacOsCollection" ref="instructionsDialog">
v-if="this.isMacOsCollection"> <MacOsInstructions :fileName="this.fileName" />
<div class="modal"> </Dialog>
<div class="modal__content">
<MacOsInstructions :fileName="this.fileName" />
</div>
<div class="modal__close-button">
<font-awesome-icon :icon="['fas', 'times']" @click="$modal.hide(macOsModalName)"/>
</div>
</div>
</modal>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component } from 'vue-property-decorator'; import { Component } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue'; import { StatefulVue } from '@/presentation/components/Shared/StatefulVue';
import { SaveFileDialog, FileType } from '@/infrastructure/SaveFileDialog'; import { SaveFileDialog, FileType } from '@/infrastructure/SaveFileDialog';
import { Clipboard } from '@/infrastructure/Clipboard'; import { Clipboard } from '@/infrastructure/Clipboard';
import Dialog from '@/presentation/components/Shared/Dialog.vue';
import IconButton from './IconButton.vue'; import IconButton from './IconButton.vue';
import MacOsInstructions from './MacOsInstructions.vue'; import MacOsInstructions from './MacOsInstructions.vue';
import { Environment } from '@/application/Environment/Environment'; import { Environment } from '@/application/Environment/Environment';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IApplication } from '@/domain/IApplication';
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode'; import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { CodeRunner } from '@/infrastructure/CodeRunner';
import { IApplicationContext } from '@/application/Context/IApplicationContext';
@Component({ @Component({
components: { components: {
IconButton, IconButton,
MacOsInstructions, MacOsInstructions,
Dialog,
}, },
}) })
export default class TheCodeButtons extends StatefulVue { export default class TheCodeButtons extends StatefulVue {
public readonly macOsModalName = 'macos-instructions'; public readonly isDesktopVersion = Environment.CurrentEnvironment.isDesktop;
public canRun = false;
public hasCode = false; public hasCode = false;
public isDesktopVersion = Environment.CurrentEnvironment.isDesktop;
public isMacOsCollection = false; public isMacOsCollection = false;
public fileName = ''; public fileName = '';
private codeListener: IEventSubscription;
public async copyCodeAsync() { public async copyCodeAsync() {
const code = await this.getCurrentCodeAsync(); const code = await this.getCurrentCodeAsync();
Clipboard.copyText(code.current); Clipboard.copyText(code.current);
@@ -65,19 +62,16 @@ export default class TheCodeButtons extends StatefulVue {
const context = await this.getCurrentContextAsync(); const context = await this.getCurrentContextAsync();
saveCode(this.fileName, context.state); saveCode(this.fileName, context.state);
if (this.isMacOsCollection) { if (this.isMacOsCollection) {
this.$modal.show(this.macOsModalName); (this.$refs.instructionsDialog as any).show();
} }
} }
public destroyed() { public async executeCodeAsync() {
if (this.codeListener) { const context = await this.getCurrentContextAsync();
this.codeListener.unsubscribe(); await executeCodeAsync(context);
}
} }
protected initialize(app: IApplication): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState): void { protected handleCollectionState(newState: ICategoryCollectionState): void {
this.canRun = this.isDesktopVersion && newState.collection.os === Environment.CurrentEnvironment.os;
this.isMacOsCollection = newState.collection.os === OperatingSystem.macOS; this.isMacOsCollection = newState.collection.os === OperatingSystem.macOS;
this.fileName = buildFileName(newState.collection.scripting); this.fileName = buildFileName(newState.collection.scripting);
this.react(newState.code); this.react(newState.code);
@@ -90,12 +84,10 @@ export default class TheCodeButtons extends StatefulVue {
} }
private async react(code: IApplicationCode) { private async react(code: IApplicationCode) {
this.hasCode = code.current && code.current.length > 0; this.hasCode = code.current && code.current.length > 0;
if (this.codeListener) { this.events.unsubscribeAll();
this.codeListener.unsubscribe(); this.events.register(code.changed.on((newCode) => {
}
this.codeListener = code.changed.on((newCode) => {
this.hasCode = newCode && newCode.code.length > 0; this.hasCode = newCode && newCode.code.length > 0;
}); }));
} }
} }
@@ -122,12 +114,19 @@ function buildFileName(scripting: IScriptingDefinition) {
} }
return fileName; return fileName;
} }
async function executeCodeAsync(context: IApplicationContext) {
const runner = new CodeRunner();
await runner.runCodeAsync(
/*code*/ context.state.code.current,
/*appName*/ context.app.info.name,
/*fileExtension*/ context.state.collection.scripting.fileExtension,
);
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
@import "@/presentation/styles/fonts.scss";
.container { .container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -136,26 +135,4 @@ function buildFileName(scripting: IScriptingDefinition) {
.container > * + * { .container > * + * {
margin-left: 30px; margin-left: 30px;
} }
.modal {
font-family: $normal-font;
margin-bottom: 10px;
display: flex;
flex-direction: row;
&__content {
width: 100%;
margin: 5%;
}
&__close-button {
width: auto;
font-size: 1.5em;
margin-right:0.25em;
align-self: flex-start;
cursor: pointer;
&:hover {
opacity: 0.9;
}
}
}
</style> </style>

Some files were not shown because too many files have changed in this diff Show More