Compare commits

..

43 Commits

Author SHA1 Message Date
undergroundwires
6ada8d425c Improve script error dialogs #304
- Include the script's directory path #304.
- Exclude Windows-specific instructions on non-Windows OS.
- Standardize language across dialogs for consistency.

Other supporting changes:

- Add script diagnostics data collection from main process.
- Document script file storage and execution tamper protection in
  SECURITY.md.
- Remove redundant comment in `NodeReadbackFileWriter`.
- Centralize error display for uniformity and simplicity.
- Simpify `WindowVariablesValidator` to omit checks when not on the
  renderer process.
- Improve and centralize Electron environment detection.
- Use more emphatic language (don't worry) in error messages.
2024-01-17 23:59:05 +01:00
undergroundwires
f03fc24098 Add AD detection on desktop app #264, #304
This commit addresses issues #264 and #304, where users were not
receiving error messages when script execution failed due to
antivirus intervention, particularly with Microsoft Defender.
Now, desktop app users will see a detailed error message with
guidance on next steps if script saving or execution fails due
to antivirus removal.

Key changes:

- Implement a check to detect failure in file writing,
  including reading the written file back. This method effectively
  detects antivirus interventions, as the read operation triggers
  an antivirus scan, leading to file deletion by the antivirus.
- Introduce a specific error message for scenarios where an
  antivirus intervention is detected.
2024-01-16 22:26:28 +01:00
undergroundwires
756c736e21 Add Windows save instructions UI and fix URL #296
- Add Windows instruction dialog when saving scripts for Windows.
- Fix incorrect macOS download URL given for Linux instructions.
- Refactor UI rendering, eleminating the use of `v-html` and JavaScript
  variables to hold HTML code.
2024-01-15 22:38:39 +01:00
undergroundwires
e09db0f1bd Show save/execution error dialogs on desktop #264
This commit introduces system-native error dialogs on desktop
application for code save or execution failures, addressing user confusion
described in issue #264.

This commit adds informative feedback when script execution or saving
fails.

Changes:

- Implement support for system-native error dialogs.
- Refactor `CodeRunner` and `Dialog` interfaces and their
  implementations to improve error handling and provide better type
  safety.
- Introduce structured error handling, allowing UI to display detailed
  error messages.
- Replace error throwing with an error object interface for controlled
  handling. This ensures that errors are propagated to the renderer
  process without being limited by Electron's error object
  serialization limitations as detailed in electron/electron#24427.
- Add logging for dialog actions to aid in troubleshooting.
- Rename `fileName` to `defaultFilename` in `saveFile` functions
  to clarify its purpose.
- Centralize message assertion in `LoggerStub` for consistency.
- Introduce `expectTrue` in tests for clearer boolean assertions.
- Standardize `filename` usage across the codebase.
- Enhance existing test names and organization for clarity.
- Update related documentation.
2024-01-14 22:35:53 +01:00
undergroundwires
c546a33eff Show native save dialogs in desktop app #50, #264
This commit introduces native operating system file dialogs in the
desktop application replacing the existing web-based dialogs.

It lays the foundation for future enhancements such as:

- Providing error messages when saving or executing files, addressing
  #264.
- Creating system restore points, addressing #50.

Documentation updates:

- Update `desktop-vs-web-features.md` with added functionality.
- Update `README.md` with security feature highlights.
- Update home page documentation to emphasize security features.

Other supporting changes include:

- Integrate IPC communication channels for secure Electron dialog API
  interactions.
- Refactor `IpcRegistration` for more type-safety and simplicity.
- Introduce a Vue hook to encapsulate dialog functionality.
- Improve errors during IPC registration for easier troubleshooting.
- Move `ClientLoggerFactory` for consistency in hooks organization and
  remove `LoggerFactory` interface for simplicity.
- Add tests for the save file dialog in the browser context.
- Add `Blob` polyfill in tests to compensate for the missing
  `blob.text()` function in `jsdom` (see jsdom/jsdom#2555).

Improve environment detection logic:

- Treat test environment as browser environments to correctly activate
  features based on the environment. This resolves issues where the
  environment is misidentified as desktop, but Electron preloader APIs
  are missing.
- Rename `isDesktop` environment identification variable to
  `isRunningAsDesktopApplication` for better clarity and to avoid
  confusion with desktop environments in web/browser/test environments.
- Simplify `BrowserRuntimeEnvironment` to consistently detect
  non-desktop application environments.
- Improve environment detection for Electron main process
  (electron/electron#2288).
2024-01-13 18:04:23 +01:00
undergroundwires
da4be500da win: add missing extension apps, improve docs #279
This commit adds missing extension apps seen since Windows 11 22H2 and
improves documentation scripts and category of extension app removal.

Addition of new extension apps found since Windows 11 22H2:

- HEVC Video Extensions (`Microsoft.HEVCVideoExtension`)
- Raw Image Extension (`Microsoft.RawImageExtension`)

Documentation improvements:

- Fix links that are not correctly archived.
- Add cautionary notes for all extension app removal scripts.
- Add security implications associated with these extensions.
2024-01-10 21:59:55 +01:00
undergroundwires
b404a91ada Fix invisible script execution on Windows #264
This commit addresses an issue in the privacy.sexy desktop application
where scripts executed as administrator on Windows were running in the
background. This was observed in environments like Windows Pro VMs on
Azure, where operations typically run with administrative privileges.

Previously, the application used the `"$path"` shell command to execute
scripts. This mechanism failed to activate the logic for requesting
admin privileges if the app itself was running as an administrator.
To resolve this, the script execution process has been modified to
explicitly ask for administrator privileges using the `VerbAs` method.
This ensures that the script always runs in a new `cmd.exe` window,
enhancing visibility and user interaction.

Other supporting changes:

- Rename the generated script file from `run-{timestamp}-{extension}` er
  to `{timestamp}-privacy-script-{extension}` for clearer identification
  and better file sorting.
- Refactor `ScriptFileCreator` to parameterize file extension and
  script name.
- Rename `OsTimestampedFilenameGenerator` to
  `TimestampedFilenameGenerator` to better reflect its new and more
  scoped functionality after refactoring mentioned abvoe.
- Remove `setAppName()` due to ineffective behavior in Windows.
- Update `SECURITY.md` to highlight that the app doesn't require admin
  rights for standard operations.
- Add `.editorconfig` settings for PowerShell scripts.
- Add a integration test for script execution logic. Improve environment
  detection for more reliable test execution.
- Disable application logging during unit/integration tests to keep test
  outputs clean and focused.
2024-01-09 20:44:06 +01:00
undergroundwires
728584240c Fix touch, cursor and accessibility in slider
This commit improves the horizontal slider between the generated code
area and the script list. It enhances interaction, accessibility and
performance. It provides missing touch responsiveness, improves
accessibility by using better HTML semantics, introduces throttling and
refactors cursor handling during drag operations with added tests.

These changes provides smoother user experience, better support for
touch devices, reduce load during interactions and ensure the
component's behavior is intuitive and accessible across different
devices and interactions.

- Fix horizontal slider not responding to touch events.
- Improve slider handle to be a `<button>` for improved accessibility
  and native browser support, improving user interaction and keyboard
  support.
- Add throttling in the slider for performance optimization, reducing
  processing load during actions.
- Fix losing dragging state cursor on hover over page elements such as
  input boxes and buttons during dragging.
- Separate dragging logic into its own compositional hook for clearer
  separation of concerns.
- Refactor global cursor mutation process.
- Increase robustness in global cursor changes by preserving and
  restoring previous cursor style to prevent potential side-effects.
- Use Vue 3.2 feature for defining cursor CSS style in `<style>`
  section.
- Expand unit test coverage for horizontal slider, use MouseEvent and
  type cast it to PointerEvent as MouseEvent is not yet supported by
  `jsdom` (see jsdom/jsdom#2527).
2024-01-08 23:08:10 +01:00
undergroundwires
3b1a89ce86 Fix script execution for Linux VSCode development
This commit improves the VSCode configuration script for Linux-based
development environments.

It fixes a script execution failure in the deskto version during
development when using VSCode installed via Snap or Flatpak. It resolves
the following error encountered during script execution in development
mode (`npm run electron:dev`):

`symbol lookup error: /snap/core20/current/lib/x86_64-linux-gnu/libpthread.so.0:
 undefined symbol: __libc_pthread_init, version GLIBC_PRIVATE`

Changes:

- Add a setting in VSCode configuration script to workaround script
  execution errors in sandboxed VSCode installations on Linux (see
  see microsoft/vscode#179274).
- Migrate the configuration script to Python for cross-platform
  compatibility and simplicity.
- Refactor the script for better extensibility.
- Automate installation of recommended VSCode extensions.
- Recommend VSCode Pylint extension for Python linting.
- Standardize Python development settings in `.editorconfig`.
2024-01-07 14:02:40 +01:00
undergroundwires
c84a1bb74c Fix script deletion during execution on desktop
This commit fixes an issue seen on certain Windows environments (Windows
10 22H2 and 11 23H2 Pro Azure VMs) where scripts were being deleted
during execution due to temporary directory usage. To resolve this,
scripts are now stored in a persistent directory, enhancing reliability
for long-running scripts and improving auditability along with
troubleshooting.

Key changes:

- Move script execution logic to the `main` process from `preloader` to
  utilize Electron's `app.getPath`.
- Improve runtime environment detection for non-browser environments to
  allow its usage in Electron main process.
- Introduce a secure module to expose IPC channels from the main process
  to the renderer via the preloader process.

Supporting refactorings include:

- Simplify `CodeRunner` interface by removing the `tempScriptFolderName`
  parameter.
- Rename `NodeSystemOperations` to `NodeElectronSystemOperations` as it
  now wraps electron APIs too, and convert it to class for simplicity.
- Rename `TemporaryFileCodeRunner` to `ScriptFileCodeRunner` to reflect
  its new functinoality.
- Rename `SystemOperations` folder to `System` for simplicity.
- Rename `HostRuntimeEnvironment` to `BrowserRuntimeEnvironment` for
  clarity.
- Refactor main Electron process configuration to align with latest
  Electron documentation/recommendations.
- Refactor unit tests `BrowserRuntimeEnvironment` to simplify singleton
  workaround.
- Use alias imports like `electron/main` and `electron/common` for
  better clarity.
2024-01-06 18:47:58 +01:00
undergroundwires
bf7fb0732c Bump ESLint Typescript dependencies to latest
- Bump all ESLint dependencies related to TypeScript to their latest
  version. This was made possible by the resolution of compatibility
  issues with `@vue/eslint-config-airbnb-with-typescript`.
  See vuejs/eslint-config-airbnb#58 for details.
- Refactor code to comply with the latest linting configuration.
- Improve documentation in the ESLint configuration file to better
  describe the functionality and limitations of
  `@vue/typescript/recommended`.
  See vuejs/eslint-config-typescript#67 for details.
- Document functionality and limitation of `@vue/typescript/recommended`
  more clearly in ESLint configuration file.
2024-01-05 14:18:50 +01:00
undergroundwires
dc30825232 Fix macOS detection in desktop app and Chromium
This commit addresses an issue where macOS was incorrectly identified as
iPadOS in Chromium-based browsers. The root cause was related to touch
support detection being inaccurately triggered on Chromium browsers,
leading to misidentification.

The bug caused two issues:

1. Desktop version: Script execution on macOS did not work as the
   desktop app wrongly assumed that it was running on iPadOS.
2. Web and desktop version: The UI didn't default to macOS, presuming an
   iPadOS environment.

This bug was exclusive to Chromium browsers on macOS. Firefox and Safari
didn't exhibit this behavior, as they handle touch event browser API
as differently and initially expected.

Key changes:

- Improve touch support detection to accurately differentiate between
  macOS and iPadOS by removing an identification method used that is not
  reliable for Chromium-based browsers.
- Update user agent detection to correctly identify Electron-based
  applications as macOS even without needing the information from the
  preloader context.
2024-01-03 11:00:34 +01:00
undergroundwires
40f5eb8334 Fix handling special chars in script paths
This commit improves the handling of paths with spaces or special
characters during script execution in the desktop application.

Key improvements:

- Paths are now quoted for macOS/Linux, addressing issues with
  whitespace or single quotes.
- Windows paths are enclosed in double quotes to handle special
  characters.

Other supporting changes:

- Add more documentation for terminal execution commands.
- Refactor terminal script file execution into a dedicated file for
  improved separation of concerns.
- Refactor naming of `RuntimeEnvironment` to align with naming
  conventions (no interface with I prefix) and for clarity.
- Refactor `TemporaryFileCodeRunner` to simplify it by removing the `os`
  parameter and handling OS-specific logic within the filename generator
  instead.
- Refactor `fileName` to `filename` for consistency.
2024-01-02 16:16:31 +01:00
undergroundwires
fac72edd55 win: improve store app docs and add research #279
This commit improves documentation for removal of Windows store apps
along with adding related research.

1. Improve Store app removal documentation:
   The documentation for scripts that remove Store apps has been
   enhanced. It now includes information on the default preinstallation
   status of these apps across various Windows versions. This update
   covers Windows 10 (from version 19H2 to 23H2) and Windows 11 (from
   version 21H2 to 23H2), enabling users to identify potentially
   preinstalled apps that might affect privacy.

2. Add research documentation:
   A detailed research documentation on Windows Store apps has been
   introduced for Windows 10 (versions 1909 to 22H2) and Windows 11
   (versions 21H2 to 23H2). This includes lists of preinstalled Store
   apps, complete with package information. This research aids in
   understanding which default apps are present in different Windows
   versions and their status regarding removal. The documentation also
   includes the PowerShell script used for this research, serving as a
   resource for future updates and expansion.
2024-01-01 17:44:09 +01:00
undergroundwires
cdc32d1f12 Improve desktop script runs with timestamps & logs
Improve script execution in the desktop app by introducing timestamped
filenames and detailed logging. These changes aim to facilitate easier
debugging, auditing and overall better user experience.

Key changes:

- Add timestamps in filenames for temporary files to aid in
  troubleshooting and auditing.
- Add application logging throughout the script execution process to
  enhance troubleshooting capabilities.

Other supporting changes:

- Refactor `TemporaryFileCodeRunner` with subfunctions for improved
  readability, maintenance, reusability and extensibility.
- Refactor unit tests for `TemporaryFileCodeRunner` for improved
  granularity and simplicity.
- Create centralized definition of supported operating systems by
  privacy.sexy to ensure robust and consistent test case creation.
- Simplify the `runCode` method by removing the file extension
  parameter; now handled internally by `FileNameGenerator`.
2023-12-31 14:28:58 +01:00
undergroundwires
8f4b34f8f1 win: fix language dependent delete script #149
This commit addresses the language dependency of the `takeown /d y`
command in non-English Windows versions by using the `choice` utility.
This utility dynamically determines the equivalent of 'yes' in the
current system language, resolving issues encountered in the delete
script.

Other solution options such as enumerating language equivalents,
adjusting script culture settings, using side-effects of the `copy`
command, and parsing `takeown` help documentation proved either
impractical or unreliable.

The `choice` command has been successfully tested in both English and
German environments, ensuring reliable execution across various locales.
This change replaces the previous `takeown` usage in the script,
its reliability across diverse Windows locales.
2023-12-30 17:12:21 +01:00
undergroundwires
86fde6d7dc Fix button inconsistencies and macOS layout shifts
This commit fixes layout shifts experienced in macOS Safari when
hovering over top menu items. Instead of making text bold — which was
causing layout shifts — the hover effect now changes the text color.
This ensures a consistent UI across different browsers and platforms.

Additionally, this commit fixes the styling of the privacy button
located in the bottom right corner. Previously styled as an `<a>`
element, it is now correctly represented as a `<button>`.

Furthermore, the commit enhances HTML conformity and accessibility by
correctly using `<button>` and `<a>` tags instead of relying on click
interactions on `<span>` elements.

This commit introduces `FlatButton` Vue component and a new
`flat-button` mixin. These centralize button usage and link styles,
aligning the hover/touch reactions of buttons across the application,
thereby creating a more consistent user interface.
2023-12-29 17:26:40 +01:00
undergroundwires
2f06043559 Bump Node.js environment to 18.x
- Bump Node.js to version 18. This change is necessary as Node.js v16
  will reach end-of-life on 2023-09-11. It also ensure compatibility
  with dependencies requiring minimum of Node.js v18, such as `vite`,
  `@vitejs`plugin-legacy` and `icon-gen`.
- Bump `setup-node` action to v4.
- Recommend using the `nvm` tool for managing Node.js versions in the
  documentation.
- Update documentation to point to code reference for required Node.js
  version. This removes duplication of information, and keeps the code
  as single source of truth for required Node.js version.
- Refactor code to adopt the `node:` protocol for Node API imports as
  per Node.js 18 standards. This change addresses ambiguities and aligns
  with Node.js best practices (nodejs/node#38343). Currently, there is
  no ESLint rule to enforce this protocol, as noted in
  import-js/eslint-plugin-import#2717.
- Replace `cross-fetch` dependency with the native Node.js fetch API
  introduced in Node.js 18. Adjust type casting for async iterable read
  streams to align with the latest Node.js APIs, based on discussions in
  DefinitelyTyped/DefinitelyTyped#65542.
2023-12-28 11:57:38 +01:00
undergroundwires
fc9dd234e9 Improve documentation for contribution guidelines
- Improve `CONTRIBUTING.md` with clearer, more structured guidelines.
- Introduce a centralized 'Script Guidelines' document for consistent
  reference.
- Remove repetitive information across documents, providing links to the
  primary source.
- Simplify language across related documentation for better
  accessibility and readability.
2023-12-20 18:53:08 +01:00
undergroundwires
645c333787 Fix unresponsive circle icon in revert button
This commit fixes a UI bug where the circle icon of the revertbutton was
unresponsive to clicks. The solution involves replacing the
pseudo-element (`:before`) with an actual HTML element, enabling direct
event binding.

Additional improvements include:

- Removal of redundant `z-index` properties to simplify click event
  handling and reduce complexity.
- Programmatic toggle of `isChecked` on click, providing more controlled
  and explicit behavior and avoiding issues with native checkbox
  behavior, especially when overlaid on a pseudo-element.
2023-12-19 10:44:54 +01:00
undergroundwires
efa05f42bc Improve security by isolating code execution more
This commit enhances application security against potential attacks by
isolating dependencies that access the host system (like file
operations) from the renderer process. It narrows the exposed
functionality to script execution only, adding an extra security layer.

The changes allow secure and scalable API exposure, preparing for future
functionalities such as desktop notifications for script errors (#264),
improved script execution handling (#296), and creating restore points
(#50) in a secure and repeatable way.

Changes include:

- Inject `CodeRunner` into Vue components via dependency injection.
- Move `CodeRunner` to the application layer as an abstraction for
  better domain-driven design alignment.
- Refactor `SystemOperations` and related interfaces, removing the `I`
  prefix.
- Update architecture documentation for clarity.
- Update return types in `NodeSystemOperations` to match the Node APIs.
- Improve `WindowVariablesProvider` integration tests for better error
  context.
- Centralize type checks with common functions like `isArray` and
  `isNumber`.
- Change `CodeRunner` to use `os` parameter, ensuring correct window
  variable injection.
- Streamline API exposure to the renderer process:
  - Automatically bind function contexts to prevent loss of original
    context.
  - Implement a way to create facades (wrapper/proxy objects) for
    increased security.
2023-12-18 17:30:56 +01:00
undergroundwires
940febc3e8 Fix CSP for Vue, Ace, Vite, Safari compatibility
Relax Content Security Policy (CSP) to ensure essential functionality
of Vue, Ace and Vite legacy along with functioning developer experience
with macOS Safari.
2023-12-17 18:08:23 +01:00
undergroundwires-bot
3f62bb2d6e ⬆️ bump everywhere to 0.12.9 2023-12-16 03:56:16 +00:00
undergroundwires
e95b2ba217 win: add scripts to postpone auto-updates #272
This commit adds Windows update postponement techniques.

This provides users more control over the update process, aiming to
prevent automatic re-enabling of updates without user consent.

These scripts are tested and validated on Windows 10 (22H2 onwards) and
Windows 11 (22H3 onwards), introducing registry modifications for
sustained pause durations.
2023-12-16 04:10:02 +01:00
undergroundwires
20633972e9 Fix touch-enabled Chromium highlight on tree nodes
This commit resolves issues with the touch highlight behavior on tree
nodes in touch-enabled Chromium browsers (such as Google Chrome).

The fix addresses two issues:

1. Dual color transition issue during tapping actions on tree nodes.
2. Not highlighting full visible width of the node on keyboard focus.

Other changes include:

- Create `InteractableNode.vue` to centralize click styling and logic.
- Remove redundant click/hover/touch styling from `LeafTreeNode.vue` and
  `HierarchicalTreeNode.vue`.
2023-12-15 08:00:46 +01:00
undergroundwires
3457fe18cf Fix OS switching not working on tree view UI
This commit resolves a rendering bug in the tree view component.
Previously, updating the tree collection prior to node updates led to
rendering errors due to the presence of non-existent nodes in the new
collection.

Changes:

- Implement manual control over the rendering process in tree view. This
  includes clearing the rendering queue and currently rendered nodes
  before updates, aligning the rendering process with the updated
  collection.
- Add Cypress E2E tests to test switching between all operating systems
  and script views, ensuring no uncaught errors and preventing
  regression.
- Replace hardcoded operating system lists in the download URL list view
  with a unified `getSupportedOsList()` method from the application,
  reducing duplication and simplifying future updates.
- Rename `initial-nodes` to `nodes` in `TreeView.vue` to reflect their
  mutable nature.
- Centralize the function for getting operating system names into
  `OperatingSystemNames.ts`, improving reusability in E2E tests.
2023-12-14 09:51:42 +01:00
undergroundwires
fe3de498c8 win: improve disabling of Application Experience
This commit improves disabling of Application Experience component by
improving the categorization, documentation, existing scripts and adding
new scripts. It renames the scripts to be more user-friendly but still
technically accurate.

- Rename scripts to make them easier for non-technical users to
  understand.
- Improve existing documentation and add more documentation.
- Add new scripts for:
  - 'Disable "MareBackup" task'
  - 'Disable "SdbinstMergeDbTask" task'
  - 'Disable "PcaPatchDbTask" task'
- Improve `CompatTelRunner.exe` disabling to soft-delete the file.
2023-12-13 09:14:01 +01:00
undergroundwires
15134ea04b Fix tree view alignment and padding issues
This commit addresses issues with the tree view not fully utilizing the
available width (appearing squeezed on the left) on bigger screens, and
inconsistent padding during searches.

The changes centralize padding and script tree rendering logic to
enforce consistency and prevent regression.

Changes:

- Fix tree view width utilization.
- Refactor SCSS variables for better IDE support.
- Unify padding and tree background color logic for consistent padding
  and coloring around the tree component.
- Fix no padding around the tree in tree view.
- Centralize color SCSS variable for script background for consistent
  application theming.
2023-12-12 03:44:02 +01:00
undergroundwires
a9851272ae Fix touch state not being activated in iOS Safari
This commit resolves the issue with the `:active` pseudo-class not
activating in mobile Safari on iOS devices. It introduces a workaround
specifically for mobile Safari on iOS/iPadOS to enable the `:active`
pseudo-class. This ensures a consistent and responsive user interface
in response to touch states on mobile Safari.

Other supporting changes:

- Introduce new test utility functions such as `createWindowEventSpies`
  and `formatAssertionMessage` to improve code reusability and
  maintainability.
- Improve browser detection:
  - Add detection for iPadOS and Windows 10 Mobile.
  - Add touch support detection to correctly determine iPadOS vs macOS.
  - Fix misidentification of some Windows 10 Mobile platforms as Windows
    Phone.
  - Improve test coverage and refactor tests.
2023-12-11 05:24:27 +01:00
undergroundwires
916c9d62d9 Fix tooltip overflow on smaller screens
This commit addresses three main issues related to tooltips on devices
with smaller screens:

1. Fix tooltip overflow: On mobile devices, tooltips associated with
   script selection options (None, Standard, Strict, All) were
   overflowing due to inherited `white-space: nowrap` styling. This
   styling caused tooltips to render beyond screen limits. The fix
   involves applying `white-space: initial` to the tooltip overlay,
   preventing this style propagation and resolving the overflow issue.
2. Corrects tooltip arrow placement: Previously, when tooltips shifted
   from their default top position to the bottom on smaller screens,
   their arrows did not reposition accordingly. This issue was caused by
   using an incorrect reference for tooltip placement calculation. By
   updating the reference used to the one from `useFloating` function,
   the tooltip arrow now correctly aligns with the adjusted position.
3. Uniform margin handling: Enhances the margin settings around tooltips
   to maintain a consistent gap between the tooltip and the document
   edges, visible particularly on smaller screens.

Additionaly, the `DevToolkit` component has been improved for easier
testing. It is now non-interactable (except for its buttons) to prevent
it from getting in the way when testing on smaller screens. A new close
button has been added, allowing developers/testers to completely hide
the DevToolkit if desired.
2023-12-10 19:53:19 +01:00
undergroundwires
47b4823bc5 win: improve disabling update healing #272
This commit strengthens user control over the Windows Update Medic
Service (`WaaSMedicSvc`) and related components. These changes aim to
provide users with more control over Windows updates and telemetry data
shared with Microsoft, addressing privacy concerns.

Updates include:

- Soft deletion of various Windows Update Medic Service files and
  remediation files to prevent automatic re-enabling of Windows updates.
- Termination of `upfc.exe` to stop it from reactivating Windows Update
  Medic Service, thereby allowing users to maintain their desired update
  settings.
- Improving documentation with cautionary notes to guide users through
  poential impacts of these changes on system stability and update
  integrity.
- Including rationale behind the exclusion of `sedsvc`.
- Better documentation and output messages of `DisableService` function.
2023-12-09 19:30:33 +01:00
undergroundwires
c72f9f5016 win: discourage XboxIdentityProvider #64, #79 #181
Recommending the script that removes "Xbox Identity Provider" app
(`Microsoft.XboxIdentityProvider`) at the "Standard" level has led to
unforeseen consequences for Windows users using Xbox sign-in.

This commit introduces additional documentation and reduces the
recommendation level to mitigate these issues.

- Change recommendation level from "Standard" to "Strict".
- Improve documentation to outline the impact of uninstalling the "Xbox
  Identity Provider" app.
- Update script title to warn users about the breaking behavior.
2023-12-08 13:11:12 +01:00
undergroundwires
e747ee5cbc win: document and discourage admin shares #249
- Reduce recommendation level from "Standard" to "Strict" due to its
  potential breaking behavior.
- Add detailed documentation.
- Simplify script title for broader accessibility while maintaining
  technical accuracy.
- Note potential impact on remote system management in the script title.
- Adjust revert code align with recent Windows OS version.
2023-12-07 12:59:37 +01:00
undergroundwires
ba5b29a35d Improve security and privacy with strict meta tags
This commit introduces two meta tags to strengthen the application's
security posture and enhance user privacy, following best practices and
OWASP recommendations.

- Add Content-Security-Policy (CSP) to strictly to strictly control
  which resources the application is allowed, mitigating the risk of
  code injection attacks such as Cross-Site Scripting (XSS).
- Add `referrer` meta tag to prevent the users' browser from sending the
  page's address, or referrer, when navigating to another site, thereby
  enhancing user privacy.
2023-12-06 15:08:58 +01:00
undergroundwires
daa6230fc9 win: fix Win 11 Windows Security app removal #195
This commit fixes the issue of Windows Security app not being removed in
Windows 11. It addresses the problem by extending the app uninstallation
process to cover the new app package specific to Windows 11. It improves
the overall design of templated functions for store app removal to
implement the fix.

- Improve Windows Security removal script:
  - Add support for removing `Microsoft.SecHealthUI` in Windows 11.
  - Revise script documentation for clarity and correct typos.
- Redesign uninstallion of Store apps:
  - Change `UninstallSystemApp` to `UninstallNonRemovableStoreApp` for
    wider usage. This change is due to `Microsoft.SecHealthUI` being
    non-removable yet not a system app.
  - Refactor app data cleanup into two distinct functions
    (`ClearStoreAppDataBeforeUninstallation` and
    `ClearStoreAppDataAfterUninstallation`) for better clarity and
    maintainability. This also helps in testing by allowing easier
    reordering of operations.
  - Seperate between simple non-removable app uninstallation and
    uninstallation with cleanup in separate functions, highlighting that
    the latter is more invasive and should be used cautiously. This
    addresses permission issues encountered with `SecHealthUI` app
    removal during cleanup on Windows 11.
  - Separate uninstalling app and uninstalling app with cleanup to
    different functions, document that cleanup should no longer be
    prefered as it's invasive and too aggresive. Cleanup logic
    introduces permission issues/errors for `SecHealthUI` in Windows 11.
  - Extend app soft-deletion to include the default Windows app folder,
    this ensures that the cleanup covers any kind of Store apps (not
    only system apps).
2023-12-05 17:35:03 +01:00
undergroundwires
4765752ee3 Improve security and reliability of macOS updates
This commit introduces several improvements to the macOS update process,
primarily focusing on enhancing security and reliability:

- Add data integrity checks to ensure downloaded updates haven't been
  tampered with.
- Optimize update progress logging in `streamWithProgress` by limiting
  amount of logs during the download process.
- Improve resource management by ensuring proper closure of file
  read/write streams.
- Add retry logic with exponential back-off during file access to handle
  occassionally seen file system preparation delays on macOS.
- Improve decision-making based on user responses.
- Improve clarity and informativeness of log messages.
- Update error dialogs for better user guidance when updates fail to
  download, unexpected errors occur or the installer can't be opened.
- Add handling for unexpected errors during the update process.
- Move to asynchronous functions for more efficient operation.
- Move to scoped imports for better code clarity.
- Update `Readable` stream type to a more modern variant in Node.
- Refactor `ManualUpdater` for improved separation of concerns.
- Document the secure update process, and log directory locations.
- Rename files to more accurately reflect their purpose.
- Add `.DS_Store` in `.gitignore` to avoid unintended files in commits.
2023-12-04 18:28:43 +01:00
undergroundwires
25e23c89c3 win: fix revert and improve docs for SAM enum #255
- Rename script for simplicity.
- Add documentation.
- Fix default value not matching default OS state.
- Fix wrong registry path.
2023-12-03 17:07:49 +01:00
undergroundwires
08dbfead7c Centralize log file and refactor desktop logging
- Migrate to `electron-log` v5.X.X, centralizing log files to adhere to
  best-practices.
- Add critical event logging in the log file.
- Replace `ElectronLog` type with `LogFunctions` for better abstraction.
- Unify log handling in `desktop-runtime-error` by removing
  `renderer.log` due to `electron-log` v5 changes.
- Update and extend logger interfaces, removing 'I' prefix and adding
  common log levels to abstract `electron-log` completely.
- Move logger interfaces to the application layer as it's cross-cutting
  concern, meanwhile keeping the implementations in the infrastructure
  layer.
- Introduce `useLogger` hook for easier logging in Vue components.
- Simplify `WindowVariables` by removing nullable properties.
- Improve documentation to clearly differentiate between desktop and web
  versions, outlining specific features of each.
2023-12-02 11:50:25 +01:00
undergroundwires
8f5d7ed3cf win: improve documentation for "Get Help" app #280
- Update script name to mention breaking behavior.
- Add documentation to explain what the app does and how it impacts
  system functionality.
2023-12-01 14:49:24 +01:00
undergroundwires
807ae6a8f8 win: fix logic for terminating processes
This commit fixes and improves the process termination functionality in
related functions.

`KillProcessWhenItStarts` shared function:

- Fix registry key values configured by removing unnecessary single
  quotes.
- Rename to `TerminateExecutableOnLaunch` for clarity.
- Rename parameter `processName` to `executableNameWithExtension` for
  clarity.
- Add code comments.
- Document the function.
- Rename `%windir` to `%WINDIR%` for consistency in environment variable
  naming across scripts.
- Integrate `KillProcess` for robustness.
- Suppress errors in revert code to prevent false negatives.

`KillProcess` shared function to be able to support the termination:

- Rename to `TerminateRunningProcess` for clarity.
- Rename parameters for clarity and consistency:
  - `processName` to `executableNameWithExtension`.
  - `processStartPath` to `revertExecutablePath`.
  - `processStartArgs` to `revertExecutableArgs`.
- Make revert logic optional.
- Add code comments.
2023-11-30 08:15:24 +01:00
undergroundwires
5a7d7d88ff mac: improve clearing privacy permissions
- Improve the service permissions reset logic:
  - Implement more intuitive and user-friendly messages.
  - Ensure graceful handling when `tccutil` is unavailable.
  - Avoid treating unsupported service IDs as errors.
  - Introduce atemplated shared function.
- Rename 'Clear all privacy permissions for applications' to
  'Clear application privacy permissions' to enhance clarity.
- Add additional documentation.
- Introduce support for missing service permissions.
- Fix a bug where clearing "contacts" permissions inadvertently affected
  "full disk access" permissions.
- Move the option to clear all application permissions to top for
  improved accessibility.
- Standardize naming across scripts to maintain consistency and clarity.
2023-11-29 13:07:41 +01:00
undergroundwires
40ae8a8add win: improve docs and category of jump lists #146
- Add more documentation and improve existing documetation.
- Rename 'Clear most recently used (MRU) lists' to 'Clear recent
  activity logs' for simplicity.
- Move 'clearing recent activity logs' outside of 'Clear
  third-application data' to directy under 'Privacy cleanup' as these
  recent activities are not always necessarily from third-party
  applications.
- Fix dead link.

Co-authored-by: NerdyGamerB0i <85419060+NerdyGamerB0i@users.noreply.github.com>
2023-11-28 12:17:21 +01:00
undergroundwires-bot
6488e81901 ⬆️ bump everywhere to 0.12.8 2023-11-27 10:32:33 +00:00
342 changed files with 18709 additions and 5591 deletions

View File

@@ -1,7 +1,11 @@
root = true # Top-most EditorConfig file
[*]
end_of_line = lf
[*.{js,jsx,ts,tsx,vue,sh}]
indent_style = space
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 100
@@ -9,3 +13,14 @@ max_line_length = 100
[{Dockerfile}]
indent_style = space
indent_size = 4
[*.py]
indent_size = 4 # PEP 8 (the official Python style guide) recommends using 4 spaces per indentation level
indent_style = space
max_line_length = 100
[*.ps1]
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true

View File

@@ -9,14 +9,15 @@ module.exports = {
es2022: true, // add globals and sets parserOptions.ecmaVersion to 2022
},
extends: [
// Vue specific rules, eslint-plugin-vue
// Vue specific base rules, `eslint-plugin-vue`
'plugin:vue/vue3-recommended',
// Extends eslint-config-airbnb
// Extends `eslint-config-airbnb`
'@vue/eslint-config-airbnb-with-typescript',
// Extends @typescript-eslint/recommended
// Uses the recommended rules from the @typescript-eslint/eslint-plugin
// - Sets base parser and plugin options.
// - Includes `plugin:@typescript-eslint/recommended`. But incompatible with
// `strict-type-checked` and `stylistic-type-checked`, see https://github.com/vuejs/eslint-config-typescript/issues/67.
'@vue/typescript/recommended',
],
rules: {

View File

@@ -5,71 +5,56 @@ labels: enhancement
---
<!--
Thank you for suggesting an script to make privacy better. 🤗
Please fill in as much of the template below as you're able.
You could alternatively send a PR directly (see CONTRIBUTING.md).
Thank you for contributing to privacy.sexy! 🌟
For guidance, see our script guidelines: https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md.
Consider submitting a PR for faster implementation: https://github.com/undergroundwires/privacy.sexy/blob/master/CONTRIBUTING.md#extend-scripts.
-->
### OS
### Operating system
<!--
Which OS will the new script configure?
One of the supported OSes: "Windows", "macOS" or "Linux".
Specify the OS: Windows, macOS, or Linux.
-->
### Name
<!--
The name of the script.
It should start with an imperative noun such as "disable", "turn off" , "clear"...
E.g. "Disable webcam telemetry"
Suggest a name for the script.
Naming conventions: https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#name.
-->
### Script code
### Code
<!--
Code that will be executed when script is selected.
Try to keep it as simple and backwards-compatible as possible.
Allowed languages:
- Windows: PowerShell (ps1) or batchfile
- 💡 Prioritize the one that's simpler, batchfile if similar.
- macOS: bash (sh)
- Linux: bash (sh) or Python 3
- 💡 Prioritize the one that's simpler, bash if similar.
Provide or explain the code to execute when the script runs.
Code guidelines: https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#code.
-->
### Revert code
<!--
If applicable, add code that will revert the script code to its original (OS default) state.
It may require additional time, but it's much appreciated by the community.
Leave blank if the script is nonreversible (e.g. when clearing data without backup).
Include code to revert changes to the default state.
Leave blank for non-reversible scripts.
-->
### Suggested category
### Category
<!--
If applicable, suggest one more multiple suitable parent category of script.
A category is the item where the script will be presented under.
Most likely there already is a category for the script, so check the existing categories.
If you're unsure, leave blank and maintainer(s) will choose one.
Suggest a category for the script.
If unsure, leave blank for maintainers to decide.
-->
### Suggested recommendation level
### Recommendation level
<!--
If applicable, suggest recommending the script or not recommending at all.
A script should be only recommended if it'll be safe for your grandmother to run.
So you have three options here:
STANDARD: Non-breaking scripts that does not limit any functionality.
STRICT: Scripts that can break certain functionality but not intrusive to common daily OS usage.
NONE: Script is not recommended for newbies at all, only those who knows what's going on should select it.
If you're unsure, leave blank and maintainer(s) will choose one.
Suggest a recommendation level: STANDARD (non-breaking), STRICT (limits functionality), or NONE (for advanced users).
If unsure, leave blank for maintainers to decide.
-->
### Additional documentation/references
### Documentation/References
<!--
If applicable, refer to documentation that should show up on the script description.
Sources (URLs) should be as high quality as possible e.g. vendor documentation is favored over user forums.
<!--
Provide any relevant documentation or references.
Prefer high-quality sources such as vendor documentation.
Documentation guidelines: https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#documentation.
-->

View File

@@ -3,6 +3,6 @@ runs:
steps:
-
name: Setup node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 18.x

3
.gitignore vendored
View File

@@ -11,3 +11,6 @@ node_modules
# draw.io
*.bkp
*.dtmp
# macOS
.DS_Store

View File

@@ -16,7 +16,8 @@
// Scripting
"timonwong.shellcheck", // Lints bash files.
"ms-vscode.powershell", // Lints PowerShell files.
"ms-python.python", // Lints Python files.
"ms-python.python", // Python IntelliSense, debugging, and basic linting.
"ms-python.pylint", // Lints Python files
// Distribution
"ms-azuretools.vscode-docker" // Adds Docker support.
]

31
2 Normal file
View File

@@ -0,0 +1,31 @@
Show error on AV removal on desktop $264, $304
This solves $264 where users do not get error messages when running
script file fails due to antivirus intervention (it being blocking the
script file as soon as privacy.sexy generates it to run it). Now if the
desktop app users tries to save or run a script file and it afils due to
antivirus removal, they'll get a special error message with guiding next
steps.
- Add additional check to able to fail if the file writing fails. This
includes trying to reading the written file back as suggested in $304.
This successfully detects antivirus (Defender) intervation as read
file operation triggers the antivirus scan that deletes the file.
- Show directory and file path in error messages as suggested in $304.
- Show an error message with more detailed information if an antivirus
is detected.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Tue Jan 16 16:23:08 2024 +0100
#
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
# (use "git push" to publish your local commits)
#
# Changes to be committed:
# modified: ../../application/CodeRunner/CodeRunner.ts
# new file: NodeReliableFileWriter.ts
# new file: ReliableFileWriter.ts
#

View File

@@ -1,5 +1,54 @@
# Changelog
## 0.12.9 (2023-12-16)
* win: improve docs and category of jump lists #146 | [40ae8a8](https://github.com/undergroundwires/privacy.sexy/commit/40ae8a8addaeb834ee26eabd330fda5cbb495324)
* mac: improve clearing privacy permissions | [5a7d7d8](https://github.com/undergroundwires/privacy.sexy/commit/5a7d7d88ff2f3e8862b18c94d062f692ee4b690b)
* win: fix logic for terminating processes | [807ae6a](https://github.com/undergroundwires/privacy.sexy/commit/807ae6a8f8ca724d781169f3ecb40f43ccd3fe10)
* win: improve documentation for "Get Help" app #280 | [8f5d7ed](https://github.com/undergroundwires/privacy.sexy/commit/8f5d7ed3cfa57f66dded9b72374006c9b6df2ce9)
* Centralize log file and refactor desktop logging | [08dbfea](https://github.com/undergroundwires/privacy.sexy/commit/08dbfead7ca7b55fe85f7dded01f2d4b88906c72)
* win: fix revert and improve docs for SAM enum #255 | [25e23c8](https://github.com/undergroundwires/privacy.sexy/commit/25e23c89c3f86897d5661a24a774997c924d3b2d)
* Improve security and reliability of macOS updates | [4765752](https://github.com/undergroundwires/privacy.sexy/commit/4765752ee3a36301b3d97317c570432424de8460)
* win: fix Win 11 Windows Security app removal #195 | [daa6230](https://github.com/undergroundwires/privacy.sexy/commit/daa6230fc96f2cf7210bc8c165106c0d5544e5fb)
* Improve security and privacy with strict meta tags | [ba5b29a](https://github.com/undergroundwires/privacy.sexy/commit/ba5b29a35dd7665aeea430aec4aaa8ff5ca811de)
* win: document and discourage admin shares #249 | [e747ee5](https://github.com/undergroundwires/privacy.sexy/commit/e747ee5cbc7cf5f0fe28a87fe7d02457d777373e)
* win: discourage XboxIdentityProvider #64, #79 #181 | [c72f9f5](https://github.com/undergroundwires/privacy.sexy/commit/c72f9f501680c1d880a0b560d02451a9e31063b4)
* win: improve disabling update healing #272 | [47b4823](https://github.com/undergroundwires/privacy.sexy/commit/47b4823bc5e487188b12cbea67db2525260af497)
* Fix tooltip overflow on smaller screens | [916c9d6](https://github.com/undergroundwires/privacy.sexy/commit/916c9d62d9fce27c3cd3feaf90c66df584d4f04a)
* Fix touch state not being activated in iOS Safari | [a985127](https://github.com/undergroundwires/privacy.sexy/commit/a9851272ae14eb1b374767b0eed3eb68e6dd1560)
* Fix tree view alignment and padding issues | [15134ea](https://github.com/undergroundwires/privacy.sexy/commit/15134ea04bc46e8cb13977d75b788f5ff71c800e)
* win: improve disabling of Application Experience | [fe3de49](https://github.com/undergroundwires/privacy.sexy/commit/fe3de498c8a1394efd6517d436797a08f938bb57)
* Fix OS switching not working on tree view UI | [3457fe1](https://github.com/undergroundwires/privacy.sexy/commit/3457fe18cf8193883f45b50ecbc9638c91ace2fb)
* Fix touch-enabled Chromium highlight on tree nodes | [2063397](https://github.com/undergroundwires/privacy.sexy/commit/20633972e9b56bdc102357129e74df30a95cefa9)
* win: add scripts to postpone auto-updates #272 | [e95b2ba](https://github.com/undergroundwires/privacy.sexy/commit/e95b2ba2179e40c0033a51b0087871dbfdc32d78)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.8...0.12.9)
## 0.12.8 (2023-11-27)
* Remove duplicated `index.html` file | [aab0f7e](https://github.com/undergroundwires/privacy.sexy/commit/aab0f7ea4680f377c610066bd0e99011eed8b506)
* Refactor DI for simplicity and type safety | [7770a9b](https://github.com/undergroundwires/privacy.sexy/commit/7770a9b5211d7208cfb2bfa5f737d46dc90b7946)
* Refactor user selection state handling using hook | [58cd551](https://github.com/undergroundwires/privacy.sexy/commit/58cd551a304a03e42637e6858982f8c5dfd9f598)
* Refactor watch sources for reliability | [7ab16ec](https://github.com/undergroundwires/privacy.sexy/commit/7ab16ecccb31b2d54e5b634520a8246fbbc248c1)
* Refactor to enforce strictNullChecks | [949fac1](https://github.com/undergroundwires/privacy.sexy/commit/949fac1a7cbc962ed63058e6a896695cfb4d35c8)
* Fix icon tooltip alignment on instructions modal | [bd383ed](https://github.com/undergroundwires/privacy.sexy/commit/bd383ed273ca95c10ea1cce765c0aa6836ec508c)
* Fix mobile layout overflow caused by tooltips | [e541a35](https://github.com/undergroundwires/privacy.sexy/commit/e541a35e86c0eff83f84dd002b46de7c55ebbcac)
* win: improve disabling of scheduled tasks | [3864f04](https://github.com/undergroundwires/privacy.sexy/commit/3864f042180f62afe469fdfe36010b018f84f4b3)
* Fix card list UI layout shifts (jumps) on load | [bf3426f](https://github.com/undergroundwires/privacy.sexy/commit/bf3426f91b6b7dbcad58d58507222559a8d14242)
* Refactor to Vue 3 recommended ESLint rules | [4531645](https://github.com/undergroundwires/privacy.sexy/commit/4531645b4c0c5143f15240652368bb9b9ddb48a4)
* Fix code highlighting and optimize category select | [cb42f11](https://github.com/undergroundwires/privacy.sexy/commit/cb42f11b9785e74719338a0a80a50d81dfccb4b6)
* Fix layout jumps/shifts and overflow on modals | [e299d40](https://github.com/undergroundwires/privacy.sexy/commit/e299d40fa1d71d921d4dac37e469fe299c9da3af)
* win: fix and improve Store app categorization #190 | [094dbb0](https://github.com/undergroundwires/privacy.sexy/commit/094dbb01b83bce9925fafab778b922f64390c2be)
* win: fix persistent update disabling /w tasks #272 | [dee3279](https://github.com/undergroundwires/privacy.sexy/commit/dee3279f85c99a9c62201a093b1afa41ec2412ec)
* win: discourage IntelliCode disabling #267, #286 | [7f7a84e](https://github.com/undergroundwires/privacy.sexy/commit/7f7a84e3ba259fade22d4838563d16129a1585e6)
* Fix spacing in documentation for readability | [1442f62](https://github.com/undergroundwires/privacy.sexy/commit/1442f626335e30e3a8d74e4e13e561c41f073ef8)
* win: fix system app removal affecting updates #287 | [7c632f7](https://github.com/undergroundwires/privacy.sexy/commit/7c632f738853b32fd90952bb4ca1ac924f962eb0)
* Fix rendering of inline code blocks for docs | [9845a7c](https://github.com/undergroundwires/privacy.sexy/commit/9845a7cd68a9920c96da739b58238bb1fdb1251d)
* linux: fix Firefox settings not reverting #282 | [bcad357](https://github.com/undergroundwires/privacy.sexy/commit/bcad357017d9f29ce77e706ca943107dd9caefb6)
* Fix incorrect URL rendering in documentation texts | [d328f08](https://github.com/undergroundwires/privacy.sexy/commit/d328f0895244d998e885ad8df335b6444b9ac66b)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.7...0.12.8)
## 0.12.7 (2023-11-07)
* Add winget download instructions | [b2ffc90](https://github.com/undergroundwires/privacy.sexy/commit/b2ffc90da70367b9e65c82556e8f440f865ceb98)

View File

@@ -1,6 +1,6 @@
# Contributing
Love your input! Contributing to this project should be as easy and transparent as possible, whether it's:
Love your input ❤️! Contributing to this project should be as easy and transparent as possible, whether it's:
- reporting a bug,
- discussing the current state of the code,
@@ -16,7 +16,7 @@ Your pull requests are actively welcomed. We collaborate using [GitHub flow](htt
The steps:
1. Fork the repo and create your branch from master.
1. Fork the repository and create your branch from `master`.
2. If you've added code that requires testing, add tests. See [tests.md](./docs/tests.md).
3. If you've done a major change, update the documentation. See [docs/](./docs/).
4. Ensure the test suite passes. See [development.md | Testing](./docs/development.md#testing) for commands.
@@ -37,16 +37,44 @@ Automated pipelines will run to control your PR and they will publish your code
## Extend scripts
Here's quick information for you who want to add more scripts.
If you're interested in adding new scripts to privacy.sexy:
You have two alternatives:
1. Read [guidelines for a good script](./docs/script-guidelines.md)
2. Choose one of two ways to contribute:
1. [Create an issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose) requesting the addition of a new script. This allows other contributors to develop and add it for you. This will take longer time.
2. Submit a pull request with your script. This is the faster route to seeing your script included in the project. Add your scripts to the appropriate OS directory in the [collections](src/application/collections/) (for syntax guidance, see [collection-files.md](docs/collection-files.md)) folder, and follow the [pull request process](#pull-request-process).
1. [Create an issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose) and ask for someone else to add the script for you.
2. Or send a PR yourself. This would make it faster to get your code into the project. You need to add scripts to related OS in [collections](src/application/collections/) folder. Then you'd sent a pull request, see [pull request process](#pull-request-process).
- 💡 You should use existing shared functions for most of the operations, like `DisableService` for disabling services, to maintain code consistency and efficiency.
- 📖 If you're unsure about the syntax, check [collection-files.md](docs/collection-files.md).
- 📖 If you wish to use templates, use [templating.md](./docs/templating.md).
## Commit conventions
- Adhere to the 50/72 rule:
- Commit titles should not exceed 50 characters.
- Limit description lines to 72 characters, except for code blocks or inline codes.
- Avoid including delta (such as `git diff` information) or a list of changed files in the commit message. This information is redundant as it's already part of the commit.
- Focus on explaining the WHY and HOW of the changes, rather than WHAT changes are.
- Begin the commit message with a concise summary of what the commit accomplishes.
- Use imperative language in the commit title. For example, use "add" instead of "added".
- Commit prefixes:
- Prefix bug fixes with `fix:` or `Fix ...`.
- For commits affecting scripts of specific operating systems:
- Prefix the commit title with an OS-specific tag such as `win:` for Windows scripts, `mac:` for macOS scripts, and `linux:` for Linux scripts.
- Combine prefixes for commits affecting more than one operating system, e.g., `win, mac: ...`.
## Versioning
We base versioning on the release's content rather than strictly following semantic versioning.
There are two main types of releases:
1. **Patch Releases:** These focus on minor UI improvements, bug fixes, refactorings, dependency updates, and documentation updates. For scripts, they involve adjusting recommendation levels, enhancing functionality, and dividing scripts for more precise control. Patch releases may ship minor feature additions if they are essential for fixing a bug. For these updates, we increment the patch number in the `MAJOR.MINOR.PATCH`.
2. **Feature Releases:** These releases bring significant updates that change how users interact with privacy.sexy. They include major UI enhancements, the introduction of new scripts, and features. For these updates, we increment the minor number in the `MAJOR.MINOR.PATCH`.
Maintainers tag specific commits with a version number to trigger a release, and [bump-everywhere](https://github.com/undergroundwires/bump-everywhere) automates the release process including updating version numbers throughout the project.
## Refactoring
Opportunistic refactoring is welcome. If you're adding a feature or fixing a bug, feel free to also clean up and optimize the related code. Your contributions should leave the code in a better state than when you found it.
## License
By contributing, you agree that your [GNU General Public License v3.0](./LICENSE) will be the license for your contributions.
By contributing to this project, you agree that your contributions are licensed under the [GNU Affero General Public License](./LICENSE) as currently specified. Additionally, you expressly consent to the project maintainers having full authority to modify the licensing terms or relicense your contributions under different terms in the future.

View File

@@ -122,11 +122,11 @@
## Get started
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
- 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.7/privacy.sexy-Setup-0.12.7.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.7/privacy.sexy-0.12.7.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.7/privacy.sexy-0.12.7.AppImage). For more options, see [here](#additional-install-options).
- 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.9/privacy.sexy-Setup-0.12.9.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.9/privacy.sexy-0.12.9.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.9/privacy.sexy-0.12.9.AppImage). For more options, see [here](#additional-install-options).
Online version does not require to run any software on your computer. Offline version has more functions such as running the scripts directly.
For a detailed comparison of features between the desktop and web versions of privacy.sexy, see [Desktop vs. Web Features](./docs/desktop-vs-web-features.md).
💡 You should apply your configuration from time to time (more than once). It would strengthen your privacy and security control because privacy.sexy and its scripts get better and stronger in every new version.
💡 Regularly applying your configuration with privacy.sexy is recommended, especially after each new release and major operating system updates. Each version updates scripts to enhance stability, privacy, and security.
[![privacy.sexy application](img/screenshot.png?raw=true )](https://privacy.sexy)
@@ -137,6 +137,7 @@ Online version does not require to run any software on your computer. Offline ve
- **Transparent**. Have full visibility into what the tweaks do as you enable them.
- **Reversible**. Revert if something feels wrong.
- **Accessible**. No need to run any compiled software on your computer with web version.
- **Secure**: Security is a top priority at privacy.sexy with [comprehensive safeguards](./SECURITY.md#security-practices) in place.
- **Open**. What you see as code in this repository is what you get. The application itself, its infrastructure and deployments are open-source and automated thanks to [bump-everywhere](https://github.com/undergroundwires/bump-everywhere).
- **Tested**. A lot of tests. Automated and manual. Community-testing and verification. Stability improvements comes before new features.
- **Extensible**. Effortlessly [extend scripts](./CONTRIBUTING.md#extend-scripts) with a custom designed [templating language](./docs/templating.md).
@@ -179,4 +180,6 @@ Check [architecture.md](./docs/architecture.md) for an overview of design and ho
## Security
Security is a top priority at privacy.sexy. An extensive commitment to security verification ensures this priority. For any security concerns or vulnerabilities, please consult the [Security Policy](./SECURITY.md).
Security is a top priority at privacy.sexy.
An extensive commitment to security verification ensures this priority.
For any security concerns or vulnerabilities, please consult the [Security Policy](./SECURITY.md).

View File

@@ -1,6 +1,7 @@
# Security Policy
privacy.sexy takes security seriously. Commitment is made to address all security issues with urgency. Responsible reporting of any discovered vulnerabilities in the project is highly encouraged.
Security is a top priority at privacy.sexy.
Please report any discovered vulnerabilities responsibly.
## Reporting a Vulnerability
@@ -11,20 +12,56 @@ Efforts to responsibly disclose findings are greatly appreciated. To report a se
## Security Report Handling
Upon receipt of a security report, the following actions will be taken:
Upon receiving a security report, the process involves:
- The report will be confirmed, identifying the affected components.
- The impact and severity of the issue will be assessed.
- Work on a fix and plan a release to address the vulnerability will be initiated.
- The reporter will be kept updated about the progress.
- Confirming the report and identifying affected components.
- Assessing the impact and severity of the issue.
- Fixing the vulnerability and planning a release to address it.
- Keeping the reporter informed about progress.
## Testing
## Security Practices
Regular and extensive testing is conducted to ensure robust security in the project. Information about testing practices can be found in the [Testing Documentation](./docs/tests.md).
### Application Security
privacy.sexy adopts a defense in depth strategy to protect users on multiple layers:
- **Link Protection:**
privacy.sexy ensures each external link has special attributes for your privacy and security.
These attributes block the new site from accessing the privacy.sexy page, increasing your online safety and privacy.
- **Content Security Policies (CSP):**
privacy.sexy actively follows security guidelines from the Open Web Application Security Project (OWASP) at strictest level.
This approach protects against attacks like Cross Site Scripting (XSS) and data injection.
- **Host System Access Control:**
The desktop application segregates and isolates code sections based on their access levels through sandboxing.
This provides a critical defense mechanism, prevents attackers from introducing harmful code into the app, known as injection attacks.
- **Auditing and Transparency:**
The desktop application improves security and transparency by logging application activities and retaining files of executed scripts
This facilitates detailed auditability and effective troubleshooting, contributing to the integrity and reliability of the application.
- **Privilege Management:**
The desktop application operates without persistent administrative or `sudo` privileges, reinforcing its security posture. It requests
elevation of privileges for system modifications with explicit user consent and logs every action taken with high privileges. This
approach actively minimizes potential security risks by limiting privileged operations and aligning with the principle of least privilege.
- **Secure Script Execution/Storage:**
Before executing any script, the desktop application stores a copy to allow antivirus software to perform scans. This safeguards against
any unwanted modifications. Furthermore, the application incorporates integrity checks for tamper protection. If the script file differs from
the user's selected script, the application will not execute or save the script, ensuring the processing of authentic scripts.
### Update Security and Integrity
privacy.sexy benefits from automated update processes including security tests. Automated deployments from source code ensure immediate and secure updates, mirroring the latest source code. This aligns the deployed application with the expected source code, enhancing transparency and trust. For more details, see [CI/CD Documentation](./docs/ci-cd.md).
Every desktop update undergoes a thorough verification process. Updates are cryptographically signed to ensure authenticity and integrity, preventing tampered versions from reaching your device. Version checks are conducted to prevent downgrade attacks.
### Testing
privacy.sexy's testing approach includes a mix of automated and community-driven tests.
Details on testing practices are available in the [Testing Documentation](./docs/tests.md).
## Support
For additional assistance or any unanswered questions, [submit a GitHub issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose). Security concerns are a priority, and necessary support to address them is assured.
For help or any questions, [submit a GitHub issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose). Addressing security concerns is a priority, and we ensure the necessary support.
Support privacy.sexy's commitment to security by [making a donation ❤️](https://github.com/sponsors/undergroundwires). Your contributions aid in maintaining and enhancing the project's security features.
---

View File

@@ -27,13 +27,14 @@ Application uses highly decoupled models & services in different DDD layers:
**Domain layer**:
- Serves as the system's core and central truth.
- Facilitates communication between the application and presentation layers through the domain model.
- It should be independent of other layers and encapsulate the core business concepts.
**Infrastructure layer**:
- Manages technical implementations without dependencies on other layers or domain knowledge.
- Provides technical implementations.
- Depends on the application and domain layers in terms of interfaces and contracts but should not include business logic.
![DDD + vue.js](./../img/architecture/app-ddd.png)
![DDD + vue.js](./../img/architecture/app-ddd.drawio.png)
### Application state

View File

@@ -1,192 +1,164 @@
# Collection files
- privacy.sexy is a data-driven application where it reads the necessary OS-specific logic from yaml files in [`application/collections`](./../src/application/collections/)
- 💡 Best practices
- If you repeat yourself, try to utilize [YAML-defined functions](#function)
- Always try to add documentation and a way to revert a tweak in [scripts](#script)
- 📖 Types in code: [`collection.yaml.d.ts`](./../src/application/collections/collection.yaml.d.ts)
privacy.sexy is a data-driven application that reads YAML files.
This document details the structure and syntax of the YAML files located in [`application/collections`](./../src/application/collections/), which form the backbone of the application's data model.
Related documentation:
- 📖 [`collection.yaml.d.ts`](./../src/application/collections/collection.yaml.d.ts) outlines code types.
- 📖 [Script Guidelines](./script-guidelines.md) provide guidance on script creation including best-practices.
## Objects
### `Collection`
- A collection simply defines:
- different categories and their scripts in a tree structure
- OS specific details
- Also allows defining common [function](#function)s to be used throughout the collection if you'd like different scripts to share same code.
- Defines categories, scripts, and OS-specific details in a tree structure.
- Allows defining common [functions](#function) for code reuse.
#### `Collection` syntax
- `os:` *`string`* (**required**)
- Operating system that the [Collection](#collection) is written for.
- 📖 See [OperatingSystem.ts](./../src/domain/OperatingSystem.ts) enumeration for allowed values.
- `os:` *`string`* **(required)**
- Operating system for the collection.
- 📖 See [`OperatingSystem.ts`](./../src/domain/OperatingSystem.ts) for possible values.
- `actions: [` ***[`Category`](#category)*** `, ... ]` **(required)**
- Each [category](#category) is rendered as different cards in card presentation.
- Renders each parent category as cards in the user interface.
- ❗ A [Collection](#collection) must consist of at least one category.
- `functions: [` ***[`Function`](#function)*** `, ... ]`
- Functions are optionally defined to re-use the same code throughout different scripts.
- Optional for code reuse.
- `scripting:` ***[`ScriptingDefinition`](#scriptingdefinition)*** **(required)**
- Defines the scripting language that the code of other action uses.
- Sets the scripting language for all inline code used within the collection.
### `Category`
- Category has a parent that has tree-like structure where it can have subcategories or subscripts.
- It's a logical grouping of different scripts and other categories.
Represents a logical group of scripts and subcategories.
#### `Category` syntax
- `category:` *`string`* (**required**)
- Name of the category
- ❗ Must be unique throughout the [Collection](#collection)
- `children: [` ***[`Category`](#category)*** `|` [***`Script`***](#script) `, ... ]` (**required**)
- `category:` *`string`* **(required)**
- Name of the category.
- ❗ Must be unique throughout the [collection](#collection).
- `children: [` ***[`Category`](#category)*** `|` [***`Script`***](#script) `, ... ]` **(required)**
- ❗ Category must consist of at least one subcategory or script.
- Children can be combination of scripts and subcategories.
- `docs`: *`string`* | `[`*`string`*`, ... ]`
- Documentation pieces related to the category.
- Rendered as markdown.
- Markdown-formatted documentation related to the category.
### `Script`
- Script represents a single tweak.
- A script can be of two different types (just like [functions](#function)):
1. Inline script; a script with an inline code
- Must define `code` property and optionally `revertCode` but not `call`
2. Caller script; a script that calls other functions
- Must define `call` property but not `code` or `revertCode`
- 🙏 For any new script, please add `revertCode` and `docs` values if possible.
Represents an individual tweak.
Types (like [functions](#function)):
1. Inline script:
- Direct code.
- ❗ Requires `code` and optional `revertCode`.
2. Caller script:
- Calls other [functions](#function).
- ❗ Requires `call`, but not `code` or `revertCode`.
📖 For detailed guidelines, see [Script Guidelines](./script-guidelines.md).
#### `Script` syntax
- `name`: *`string`* (**required**)
- Name of the script
- ❗ Must be unique throughout the [Collection](#collection)
- E.g. `Disable targeted ads`
- `code`: *`string`* (may be **required**)
- Batch file commands that will be executed
- 💡 If defined, best practice to also define `revertCode`
- ❗ If not defined `call` must be defined, do not define if `call` is defined.
- `revertCode`: `string`
- 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`
- then `revertCode` should be doing `setx POWERSHELL_TELEMETRY_OPTOUT 0`
-Do not define if `call` is defined.
- `call`: ***[`FunctionCall`](#functioncall)*** | `[` ***[`FunctionCall`](#functioncall)*** `, ... ]` (may be **required**)
- A shared function or sequence of functions to call (called in order)
- ❗ If not defined `code` must be defined
- `name`: *`string`* **(required)**
- Script name.
- ❗ Must be unique throughout the [Collection](#collection).
- `code`: *`string`* **(conditionally required)**
- Code to execute when the user selects the script.
- 💡 If defined, it's best practice to also define `revertCode`.
- ❗ Cannot co-exist with `call`, define either `code` with optional `revertCode` or `call`.
- `revertCode`: *`string`*
- Reverts changes made by `code`.
- ❗ Cannot co-exist with `call`, define `revertCode` with `code` or `call`.
- `call`: ***[`FunctionCall`](#functioncall)*** | `[` ***[`FunctionCall`](#functioncall)*** `, ... ]` **(conditionally required)**
- A shared function or sequence of functions to call (called in order).
-Cannot co-exist with `code` or `revertCode`, define `code` with optional `revertCode` or `call`.
- `docs`: *`string`* | `[`*`string`*`, ... ]`
- Documentation pieces related to the script.
- Rendered as markdown.
- `recommend`: `"standard"` | `"strict"` | `undefined` (default)
- If not defined then the script will not be recommended
- If defined it can be either
- `standard`: Only non-breaking scripts without limiting OS functionality
- `strict`: Scripts that can break certain functionality in favor of privacy and security
- Markdown-formatted documentation related to the script.
- `recommend`: *`"standard"`* | *`"strict"`* | *`undefined`* (default: `undefined`)
- Sets recommendation level.
- Application will not recommend the script if `undefined`.
📖 For detailed guidelines, see [Script Guidelines](./script-guidelines.md).
### `FunctionCall`
- Describes a single call to a function by optionally providing values to its parameters.
- 👀 See [parameter substitution](./templating.md#parameter-substitution) for an example usage
Specifies a function call. It may require providing argument values to its parameters.
#### `FunctionCall` syntax
- `function`: *`string`* (**required**)
- `function`: *`string`* **(required)**
- Name of the function to call.
- ❗ Function with same name must defined in `functions` property of [Collection](#collection)
- `parameters`: `[ parameterName:` *`parameterValue`*`, ... ]`
- Defines key value dictionary for each parameter and its value
- E.g.
```yaml
parameters:
userDefinedParameterName: parameterValue
# ...
appName: Microsoft.WindowsFeedbackHub
```
- 💡 [Expressions (templating)](./templating.md#expressions) can be used as parameter value
- ❗ Function with same name must defined in `functions` property of [Collection](#collection).
- `parameters`: `[` *`parameterName: parameterValue`* `, ... ]`
- Key-value pairs representing function parameters and their corresponding argument values.
- 📖 See [parameter substitution](./templating.md#parameter-substitution) for an example usage.
- 💡 You can use [expressions (templating)](./templating.md#expressions) when providing argument values for parameters.
### `Function`
- Functions allow re-usable code throughout the defined scripts.
- Enables reusable code in scripts.
- Functions are templates compiled by privacy.sexy and uses special expression expressions.
- A function can be of two different types (just like [scripts](#script)):
- A function can be of two different types (like [scripts](#script)):
1. Inline function: a function with an inline code.
- Must define `code` property and optionally `revertCode` but not `call`.
- ❗ Requires `code` and optionally `revertCode`, but not `call`.
2. Caller function: a function that calls other functions.
- Must define `call` property but not `code` or `revertCode`.
- 👀 Read more on [Templating](./templating.md) for function expressions and [example usages](./templating.md#parameter-substitution).
- ❗ Requires `call`, but not `code` or `revertCode`.
- 📖 Read about function expressions in [Templating](./templating.md) with [example usages](./templating.md#parameter-substitution).
#### `Function` syntax
- `name`: *`string`* (**required**)
- `name`: *`string`* **(required)**
- Name of the function that scripts will use.
- Convention is to use camelCase, and be verbs.
- E.g. `uninstallStoreApp`
- ❗ Function names must be unique
- `parameters`: `[` ***[`FunctionParameter`](#functionparameter)*** `, ... ]`
- List of parameters that function code refers to.
- ❗ Must be defined to be able use in [`FunctionCall`](#functioncall) or [expressions (templating)](./templating.md#expressions)
`code`: *`string`* (**required** if `call` is undefined)
- Batch file commands that will be executed
- 💡 [Expressions (templating)](./templating.md#expressions) can be used in its value
- 💡 If defined, best practice to also define `revertCode`
- ❗ If not defined `call` must be defined
- ❗ Function names must be unique.
- ❗ Function names must follow camelCase and start with verbs (e.g., `uninstallStoreApp`).
- `parameters`: `[` ***[`FunctionParameter`](#functionparameter)*** `, ... ]` **(conditionally required)**
- Lists parameters used.
- ❗ Required to be able use in [`FunctionCall`](#functioncall) or [expressions (templating)](./templating.md#expressions).
- `code`: *`string`* **(conditionally required)**
- Code to execute when the user selects the script.
- 💡 You can use [expressions (templating)](./templating.md#expressions) in its value.
- 💡 If defined, it's best practice to also define `revertCode`.
- ❗ Cannot co-exist with `call`, define either `code` with optional `revertCode` or `call`.
- `revertCode`: *`string`*
- 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`
- then `revertCode` should be doing `setx POWERSHELL_TELEMETRY_OPTOUT 0`
- 💡 [Expressions (templating)](./templating.md#expressions) can be used in code
- `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 (templating)](./templating.md#expressions)
- ❗ If not defined `code` must be defined
- Reverts changes made by `code`.
- 💡 You can use [expressions (templating)](./templating.md#expressions) in its value.
- ❗ Cannot co-exist with `call`, define `revertCode` with `code` or `call`.
- `call`: ***[`FunctionCall`](#functioncall)*** | `[` ***[`FunctionCall`](#functioncall)*** `, ... ]` **(conditionally required)**
- A shared function or sequence of functions to call (called in order).
- 💡 You can use [expressions (templating)](./templating.md#expressions) in argument values provided for parameters.
- ❗ Cannot co-exist with `code` or `revertCode`, define `code` with optional `revertCode` or `call`.
### `FunctionParameter`
- Defines a parameter that function requires optionally or mandatory.
- Its arguments are provided by a [Script](#script) through a [FunctionCall](#functioncall).
- Defines a single parameter that may require an argument value optionally or mandatory.
- A [`FunctionCall`](#functioncall) provides argument values by a caller.
- A caller can be a [Script](#script) or [Function](#function).
#### `FunctionParameter` syntax
- `name`: *`string`* (**required**)
- Name of the parameters that the function has.
- Parameter names must be defined to be used in [expressions (templating)](./templating.md#expressions).
-Parameter names must be unique and include alphanumeric characters only.
- `name`: *`string`* **(required)**
- Name of the parameter that the function has.
- ❗ Required for [expressions (templating)](./templating.md#expressions).
-Must be unique and consists of alphanumeric characters.
- `optional`: *`boolean`* (default: `false`)
- Specifies whether the caller [Script](#script) must provide any value for the parameter.
- If set to `false` i.e. an argument value is not optional then it expects a non-empty value for the variable;
- Otherwise it throws.
- 💡 Set it to `true` if a parameter is used conditionally;
- Indicates the caller must provide and argument value for the parameter.
- 💡 If set to `false` i.e. an argument value is not optional then it expects a non-empty value for the variable.
- E.g., in a [`with` expression](./templating.md#with).
- 💡 Set it to `true` if you will use its argument value conditionally;
- Or else set it to `false` for verbosity or do not define it as default value is `false` anyway.
- 💡 Can be used in conjunction with [`with` expression](./templating.md#with).
### `ScriptingDefinition`
- Defines global properties for scripting that's used throughout its parent [Collection](#collection).
Sets global scripting properties for a [Collection](#collection).
#### `ScriptingDefinition` syntax
- `language:` *`string`* (**required**)
- 📖 See [ScriptingLanguage.ts](./../src/domain/ScriptingLanguage.ts) enumeration for allowed values.
- `startCode:` *`string`* (**required**)
- Code that'll be inserted on top of user created script.
- Global variables such as `$homepage`, `$version`, `$date` can be used using [parameter substitution](./templating.md#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!`
- `endCode:` *`string`* (**required**)
- Code that'll be inserted at the end of user created script.
- Global variables such as `$homepage`, `$version`, `$date` can be used using [parameter substitution](./templating.md#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!`
## Naming guidelines
- Prioritize consistency throughout all names.
- Use an instruction format like "do this, do that" for clear, direct guidance. This approach reduces potential confusion and offers easy-to-follow steps. It provides specific, unambiguous instructions.
- Ensure brand names adhere to their official casing.
- Choose clear and uncomplicated language.
- Favor the terms:
- "Disable" over "Turn off"
- "Configure" over "Set up"
- "Clear" over "Erase" or "Clean"
- "Minimize" over "Limit" or "Reduce" (when it enhances clarity)
- "Remove" over "Uninstall"
- Structure your phrases for clarity.
- For instance, "Disable XX telemetry" or "Clear XX data" are preferred over "Clear data from XX", "Disable telemetry in XX", or "Clear data of XX".
- Use sentence case rather than Title Case.
- `language:` *`string`* **(required)**
- 📖 See [`ScriptingLanguage.ts`](./../src/domain/ScriptingLanguage.ts) enumeration for allowed values.
- `startCode:` *`string`* **(required)**
- Prepends the given code to the generated script file.
- 💡 You can use global variables such as `$homepage`, `$version`, `$date` via [parameter substitution](./templating.md#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!`.
- `endCode:` *`string`* **(required)**
- Appends to the given code to the generated script file.
- 💡 You can use global variables such as `$homepage`, `$version`, `$date` via [parameter substitution](./templating.md#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!`.

View File

@@ -0,0 +1,95 @@
# Desktop vs. Web Features
This table highlights differences between the desktop and web versions of `privacy.sexy`.
| Feature | Desktop | Web |
| ------- | ------- | --- |
| [Usage without installation](#usage-without-installation) | 🔴 Not available | 🟢 Available |
| [Offline usage](#offline-usage) | 🟢 Available | 🟡 Partially available |
| [Auto-updates](#auto-updates) | 🟢 Available | 🟢 Available |
| [Logging](#logging) | 🟢 Available | 🔴 Not available |
| [Script execution](#script-execution) | 🟢 Available | 🔴 Not available |
| [Error handling](#error-handling) | 🟢 Advanced | 🟡 Limited |
| [Native dialogs](#native-dialogs) | 🟢 Available | 🔴 Not available |
| [Secure script execution/storage](#secure-script-executionstorage) | 🟢 Available | 🔴 Not available |
## Feature descriptions
### Usage without installation
You can use the web version directly in a browser without installation.
The desktop version requires download and installation.
> **Note for Linux users:** On Linux, privacy.sexy is available as an `AppImage`, a portable format that doesn't need traditional installation.
> This allows Linux users to use the desktop version without full installation, akin to the web version.
### Offline usage
The web version, once loaded, supports offline use.
Desktop version inherently allows offline usage.
### Auto-updates
Both the desktop and web versions of privacy.sexy provide timely access to the latest features and security improvements. The updates are automatically deployed from source code, reflecting the latest changes for enhanced security and reliability. For more details, see [CI/CD documentation](./ci-cd.md).
The desktop version ensures secure delivery through cryptographic signatures and version checks.
[Security is a top priority](./../SECURITY.md#update-security-and-integrity) at privacy.sexy.
> **Note for macOS users:** On macOS, the desktop version's auto-update process involves manual steps due to Apple's code signing costs.
> Users get notified about updates but might need to complete the installation manually.
> Consider [donating](https://github.com/sponsors/undergroundwires) to help improve this process ❤️.
### Logging
The desktop version supports logging of activities to aid in troubleshooting.
This feature is not available in the web version.
Log file locations vary by operating system:
- macOS: `$HOME/Library/Logs/privacy.sexy`
- Linux: `$HOME/.config/privacy.sexy/logs`
- Windows: `%APPDATA%\privacy.sexy\logs`
### Script execution
The desktop version of privacy.sexy enables direct script execution, providing a seamless and integrated experience.
This direct execution capability isn't available in the web version due to inherent browser restrictions.
**Script execution history:**
For enhanced auditability and easier troubleshooting, the desktop version keeps a record of executed scripts in designated directories.
These locations vary based on the operating system:
- macOS: `$HOME/Library/Application Support/privacy.sexy/runs`
- Linux: `$HOME/.config/privacy.sexy/runs`
- Windows: `%APPDATA%\privacy.sexy\runs`
### Error handling
The desktop version of privacy.sexy features advanced error handling capabilities.
It employs robust and reliable execution strategies, including self-healing mechanisms, and provides guidance and troubleshooting information to resolve issues effectively.
In contrast, the web version has more basic error handling due to browser limitations and the nature of web applications.
### Native dialogs
The desktop version uses native dialogs, offering more features and reliability compared to the browser's file system dialogs.
These native dialogs provide a more integrated and user-friendly experience, aligning with the operating system's standard interface and functionalities.
### Secure script execution/storage
**Integrity checks:**
The desktop version of privacy.sexy implements robust integrity checks for both script execution and storage.
Featuring tamper protection, the application actively verifies the integrity of script files before executing or saving them.
If the actual contents of a script file do not align with the expected contents, the application refuses to execute or save the script.
This proactive approach ensures only unaltered and verified scripts undergo processing, thereby enhancing both security and reliability.
Due to browser constraints, this feature is absent in the web version.
**Error handling:**
In scenarios where script execution or storage encounters failure, the desktop application initiates automated troubleshooting and self-healing processes.
It also guides users through potential issues with filesystem or third-party software, such as antivirus interventions.
Specifically, the application is capable of identifying when antivirus software blocks or removes a script, providing users with tailored error messages
and detailed resolution steps. This level of proactive error handling and user guidance enhances the application's security and reliability,
offering a feature not achievable in the web version due to browser limitations.

View File

@@ -13,8 +13,11 @@ See [ci-cd.md](./ci-cd.md) for more information.
### Prerequisites
- Install Node >16.x.
- Install Node.js:
- Refer to [action.yml](./../.github/actions/setup-node/action.yml) for the minimum required version compatible with the automated workflows.
- 💡 Recommended: Use [`nvm`](https://github.com/nvm-sh/nvm) CLI to install and switch between Node.js versions.
- Install dependencies using `npm install` (or [`npm run install-deps`](#utility-scripts) for more options).
- For Visual Studio Code users, running the configuration script is recommended to optimize the IDE settings, as detailed in [utility scripts](#utility-scripts).
### Testing
@@ -77,8 +80,8 @@ See [ci-cd.md](./ci-cd.md) for more information.
- [**`npm run install-deps [-- <options>]`**](../scripts/npm-install.js):
- Manages NPM dependency installation, it offers capabilities like doing a fresh install, retries on network errors, and other features.
- For example, you can run `npm run install-deps -- --fresh` to do clean installation of dependencies.
- [**`./scripts/configure-vscode.sh`**](../scripts/configure-vscode.sh):
- This script checks and sets the necessary configurations for VSCode in `settings.json` file.
- [**`python ./scripts/configure_vscode.py`**](../scripts/configure_vscode.py):
- Optimizes Visual Studio Code settings and installs essential extensions, enhancing the development environment.
#### Automation scripts
@@ -94,3 +97,4 @@ See [ci-cd.md](./ci-cd.md) for more information.
You should use EditorConfig to follow project style.
For Visual Studio Code, [`.vscode/extensions.json`](./../.vscode/extensions.json) includes list of recommended extensions.
You can use [VSCode configuration script](#utility-scripts) to automatically install those.

View File

@@ -79,19 +79,22 @@ To add a new dependency:
## Shared UI components
Shared UI components promote consistency and simplifies the creation of the front-end.
Shared UI components ensure consistency and streamline front-end development.
In order to maintain portability and easy maintainability, the preference is towards using homegrown components over third-party ones or comprehensive UI frameworks like Quasar.
We use homegrown components over third-party solutions or comprehensive UI frameworks like Quasar to maintain portability and easy maintenance.
Shared components include:
- [ModalDialog.vue](./../src/presentation/components/Shared/Modal/ModalDialog.vue) is utilized for rendering modal windows.
- [TooltipWrapper.vue](./../src/presentation/components/Shared/TooltipWrapper.vue) acts as a wrapper for rendering tooltips.
- [ModalDialog.vue](./../src/presentation/components/Shared/Modal/ModalDialog.vue): Renders modal windows.
- [TooltipWrapper.vue](./../src/presentation/components/Shared/TooltipWrapper.vue): Provides tooltip functionality for improved information accessibility.
- [FlatButton.vue](./../src/presentation/components/Shared/FlatButton.vue): Creates flat-style buttons for a unified and consistent user interface.
## Desktop builds
Desktop builds uses `electron-vite` to bundle the code, and `electron-builder` to build and publish the packages.
Host system access is strictly controlled. The [`preloader`](./../src/presentation/electron/preload/) isolates logic that interacts with the host system. These functionalities are then securely exposed to the renderer process (Vue application) using context-bridging. [`ApiContextBridge.ts`](./../src/presentation/electron/preload/ContextBridging/ApiContextBridge.ts) handles the configuration of the exposed APIs, ensuring a secure bridge between the Electron and Vue layers.
## Styles
### Style location

24
docs/research/README.md Normal file
View File

@@ -0,0 +1,24 @@
# Research Documentation
Welcome to the research section of privacy.sexy.
This area houses in-depth technical research and analyses, serving as a resource for developers, contributors, and technology enthusiasts.
**Structure:**
This folder organizes research into topic-specific subdirectories like `windows`, `linux`, etc.
Each contains materials relevant to its subject.
**Contents:**
These documents offer comprehensive insights into the respective topics, supporting development and contributions.
**Contributing:**
Contributions to our research documentation are welcome.
If your research aligns with privacy.sexy goals, please consider adding it here.
See [`CONTRIBUTING.md`](./../../CONTRIBUTING.md) on more information about how to contribute.
**Usage:**
This information is available for educational and research purposes.
We support knowledge sharing and aim to enhance understanding of privacy and security technologies.

View File

@@ -0,0 +1,84 @@
Name PublisherId Category NonRemovable
---- ----------- -------- ------------
1527c705-839a-4832-9118-54d4Bd6a0c89 cw5n1h2txyewy System True
c5e2524a-ea46-4f67-841f-6a9465d9d515 cw5n1h2txyewy System True
E2A4F912-2574-4A75-9BB0-0D023378592B cw5n1h2txyewy System True
F46D4000-FD22-4DB4-AC8E-4E1DDDE828FE cw5n1h2txyewy System True
InputApp cw5n1h2txyewy System True
Microsoft.AAD.BrokerPlugin cw5n1h2txyewy System True
Microsoft.AccountsControl cw5n1h2txyewy System True
Microsoft.AsyncTextService 8wekyb3d8bbwe System True
Microsoft.BingWeather 8wekyb3d8bbwe Provisioned False
Microsoft.BioEnrollment cw5n1h2txyewy System True
Microsoft.CredDialogHost cw5n1h2txyewy System True
Microsoft.DesktopAppInstaller 8wekyb3d8bbwe Provisioned False
Microsoft.ECApp 8wekyb3d8bbwe System True
Microsoft.GetHelp 8wekyb3d8bbwe Provisioned False
Microsoft.Getstarted 8wekyb3d8bbwe Provisioned False
Microsoft.HEIFImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.LockApp cw5n1h2txyewy System True
Microsoft.Messaging 8wekyb3d8bbwe Provisioned False
Microsoft.Microsoft3DViewer 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftEdge 8wekyb3d8bbwe System True
Microsoft.MicrosoftEdgeDevToolsClient 8wekyb3d8bbwe System True
Microsoft.MicrosoftOfficeHub 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftSolitaireCollection 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftStickyNotes 8wekyb3d8bbwe Provisioned False
Microsoft.MixedReality.Portal 8wekyb3d8bbwe Provisioned False
Microsoft.MSPaint 8wekyb3d8bbwe Provisioned False
Microsoft.Office.OneNote 8wekyb3d8bbwe Provisioned False
Microsoft.OneConnect 8wekyb3d8bbwe Provisioned False
Microsoft.People 8wekyb3d8bbwe Provisioned False
Microsoft.PPIProjection cw5n1h2txyewy System True
Microsoft.Print3D 8wekyb3d8bbwe Provisioned False
Microsoft.ScreenSketch 8wekyb3d8bbwe Provisioned False
Microsoft.SkypeApp kzf8qxf38zg5c Provisioned False
Microsoft.StorePurchaseApp 8wekyb3d8bbwe Provisioned False
Microsoft.VP9VideoExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.Wallet 8wekyb3d8bbwe Provisioned False
Microsoft.WebMediaExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.WebpImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.Win32WebViewHost cw5n1h2txyewy System True
Microsoft.Windows.Apprep.ChxApp cw5n1h2txyewy System True
Microsoft.Windows.AssignedAccessLockApp cw5n1h2txyewy System True
Microsoft.Windows.CallingShellApp cw5n1h2txyewy System True
Microsoft.Windows.CapturePicker cw5n1h2txyewy System True
Microsoft.Windows.CloudExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.ContentDeliveryManager cw5n1h2txyewy System True
Microsoft.Windows.Cortana cw5n1h2txyewy System True
Microsoft.Windows.NarratorQuickStart 8wekyb3d8bbwe System True
Microsoft.Windows.OOBENetworkCaptivePortal cw5n1h2txyewy System True
Microsoft.Windows.OOBENetworkConnectionFlow cw5n1h2txyewy System True
Microsoft.Windows.ParentalControls cw5n1h2txyewy System True
Microsoft.Windows.PeopleExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.Photos 8wekyb3d8bbwe Provisioned False
Microsoft.Windows.PinningConfirmationDialog cw5n1h2txyewy System True
Microsoft.Windows.SecHealthUI cw5n1h2txyewy System True
Microsoft.Windows.SecureAssessmentBrowser cw5n1h2txyewy System True
Microsoft.Windows.ShellExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.StartMenuExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.XGpuEjectDialog cw5n1h2txyewy System True
Microsoft.WindowsAlarms 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCalculator 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCamera 8wekyb3d8bbwe Provisioned False
microsoft.windowscommunicationsapps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsFeedbackHub 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsMaps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsSoundRecorder 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsStore 8wekyb3d8bbwe Provisioned False
Microsoft.Xbox.TCUI 8wekyb3d8bbwe Provisioned False
Microsoft.XboxApp 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGameCallableUI cw5n1h2txyewy System True
Microsoft.XboxGameOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGamingOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxIdentityProvider 8wekyb3d8bbwe Provisioned False
Microsoft.XboxSpeechToTextOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.YourPhone 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneMusic 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneVideo 8wekyb3d8bbwe Provisioned False
Windows.CBSPreview cw5n1h2txyewy System True
windows.immersivecontrolpanel cw5n1h2txyewy System True
Windows.PrintDialog cw5n1h2txyewy System True

View File

@@ -0,0 +1,85 @@
Name PublisherId Category NonRemovable
---- ----------- -------- ------------
1527c705-839a-4832-9118-54d4Bd6a0c89 cw5n1h2txyewy System True
c5e2524a-ea46-4f67-841f-6a9465d9d515 cw5n1h2txyewy System True
E2A4F912-2574-4A75-9BB0-0D023378592B cw5n1h2txyewy System True
F46D4000-FD22-4DB4-AC8E-4E1DDDE828FE cw5n1h2txyewy System True
Microsoft.549981C3F5F10 8wekyb3d8bbwe Provisioned False
Microsoft.AAD.BrokerPlugin cw5n1h2txyewy System True
Microsoft.AccountsControl cw5n1h2txyewy System True
Microsoft.AsyncTextService 8wekyb3d8bbwe System True
Microsoft.BingWeather 8wekyb3d8bbwe Provisioned False
Microsoft.BioEnrollment cw5n1h2txyewy System True
Microsoft.CredDialogHost cw5n1h2txyewy System True
Microsoft.DesktopAppInstaller 8wekyb3d8bbwe Provisioned False
Microsoft.ECApp 8wekyb3d8bbwe System True
Microsoft.GetHelp 8wekyb3d8bbwe Provisioned False
Microsoft.Getstarted 8wekyb3d8bbwe Provisioned False
Microsoft.HEIFImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.LockApp cw5n1h2txyewy System True
Microsoft.Microsoft3DViewer 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftEdge 8wekyb3d8bbwe System True
Microsoft.MicrosoftEdge.Stable 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftEdgeDevToolsClient 8wekyb3d8bbwe System True
Microsoft.MicrosoftOfficeHub 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftSolitaireCollection 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftStickyNotes 8wekyb3d8bbwe Provisioned False
Microsoft.MixedReality.Portal 8wekyb3d8bbwe Provisioned False
Microsoft.MSPaint 8wekyb3d8bbwe Provisioned False
Microsoft.Office.OneNote 8wekyb3d8bbwe Provisioned False
Microsoft.People 8wekyb3d8bbwe Provisioned False
Microsoft.ScreenSketch 8wekyb3d8bbwe Provisioned False
Microsoft.SkypeApp kzf8qxf38zg5c Provisioned False
Microsoft.StorePurchaseApp 8wekyb3d8bbwe Provisioned False
Microsoft.VCLibs.140.00 8wekyb3d8bbwe Provisioned False
Microsoft.VP9VideoExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.Wallet 8wekyb3d8bbwe Provisioned False
Microsoft.WebMediaExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.WebpImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.Win32WebViewHost cw5n1h2txyewy System True
Microsoft.Windows.Apprep.ChxApp cw5n1h2txyewy System True
Microsoft.Windows.AssignedAccessLockApp cw5n1h2txyewy System True
Microsoft.Windows.CallingShellApp cw5n1h2txyewy System True
Microsoft.Windows.CapturePicker cw5n1h2txyewy System True
Microsoft.Windows.CloudExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.ContentDeliveryManager cw5n1h2txyewy System True
Microsoft.Windows.NarratorQuickStart 8wekyb3d8bbwe System True
Microsoft.Windows.OOBENetworkCaptivePortal cw5n1h2txyewy System True
Microsoft.Windows.OOBENetworkConnectionFlow cw5n1h2txyewy System True
Microsoft.Windows.ParentalControls cw5n1h2txyewy System True
Microsoft.Windows.PeopleExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.Photos 8wekyb3d8bbwe Provisioned False
Microsoft.Windows.PinningConfirmationDialog cw5n1h2txyewy System True
Microsoft.Windows.Search cw5n1h2txyewy System True
Microsoft.Windows.SecHealthUI cw5n1h2txyewy System True
Microsoft.Windows.SecureAssessmentBrowser cw5n1h2txyewy System True
Microsoft.Windows.ShellExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.StartMenuExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.XGpuEjectDialog cw5n1h2txyewy System True
Microsoft.WindowsAlarms 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCalculator 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCamera 8wekyb3d8bbwe Provisioned False
microsoft.windowscommunicationsapps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsFeedbackHub 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsMaps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsSoundRecorder 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsStore 8wekyb3d8bbwe Provisioned False
Microsoft.Xbox.TCUI 8wekyb3d8bbwe Provisioned False
Microsoft.XboxApp 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGameCallableUI cw5n1h2txyewy System True
Microsoft.XboxGameOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGamingOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxIdentityProvider 8wekyb3d8bbwe Provisioned False
Microsoft.XboxSpeechToTextOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.YourPhone 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneMusic 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneVideo 8wekyb3d8bbwe Provisioned False
MicrosoftWindows.Client.CBS cw5n1h2txyewy System True
MicrosoftWindows.UndockedDevKit cw5n1h2txyewy System True
NcsiUwpApp 8wekyb3d8bbwe System True
Windows.CBSPreview cw5n1h2txyewy System True
windows.immersivecontrolpanel cw5n1h2txyewy System True
Windows.PrintDialog cw5n1h2txyewy System True

View File

@@ -0,0 +1,85 @@
Name PublisherId Category NonRemovable
---- ----------- -------- ------------
1527c705-839a-4832-9118-54d4Bd6a0c89 cw5n1h2txyewy System True
c5e2524a-ea46-4f67-841f-6a9465d9d515 cw5n1h2txyewy System True
E2A4F912-2574-4A75-9BB0-0D023378592B cw5n1h2txyewy System True
F46D4000-FD22-4DB4-AC8E-4E1DDDE828FE cw5n1h2txyewy System True
Microsoft.549981C3F5F10 8wekyb3d8bbwe Provisioned False
Microsoft.AAD.BrokerPlugin cw5n1h2txyewy System True
Microsoft.AccountsControl cw5n1h2txyewy System True
Microsoft.AsyncTextService 8wekyb3d8bbwe System True
Microsoft.BingWeather 8wekyb3d8bbwe Provisioned False
Microsoft.BioEnrollment cw5n1h2txyewy System True
Microsoft.CredDialogHost cw5n1h2txyewy System True
Microsoft.DesktopAppInstaller 8wekyb3d8bbwe Provisioned False
Microsoft.ECApp 8wekyb3d8bbwe System True
Microsoft.GetHelp 8wekyb3d8bbwe Provisioned False
Microsoft.Getstarted 8wekyb3d8bbwe Provisioned False
Microsoft.HEIFImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.LockApp cw5n1h2txyewy System True
Microsoft.Microsoft3DViewer 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftEdge 8wekyb3d8bbwe System True
Microsoft.MicrosoftEdge.Stable 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftEdgeDevToolsClient 8wekyb3d8bbwe System True
Microsoft.MicrosoftOfficeHub 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftSolitaireCollection 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftStickyNotes 8wekyb3d8bbwe Provisioned False
Microsoft.MixedReality.Portal 8wekyb3d8bbwe Provisioned False
Microsoft.MSPaint 8wekyb3d8bbwe Provisioned False
Microsoft.Office.OneNote 8wekyb3d8bbwe Provisioned False
Microsoft.People 8wekyb3d8bbwe Provisioned False
Microsoft.ScreenSketch 8wekyb3d8bbwe Provisioned False
Microsoft.SkypeApp kzf8qxf38zg5c Provisioned False
Microsoft.StorePurchaseApp 8wekyb3d8bbwe Provisioned False
Microsoft.VCLibs.140.00 8wekyb3d8bbwe Provisioned False
Microsoft.VP9VideoExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.Wallet 8wekyb3d8bbwe Provisioned False
Microsoft.WebMediaExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.WebpImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.Win32WebViewHost cw5n1h2txyewy System True
Microsoft.Windows.Apprep.ChxApp cw5n1h2txyewy System True
Microsoft.Windows.AssignedAccessLockApp cw5n1h2txyewy System True
Microsoft.Windows.CallingShellApp cw5n1h2txyewy System True
Microsoft.Windows.CapturePicker cw5n1h2txyewy System True
Microsoft.Windows.CloudExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.ContentDeliveryManager cw5n1h2txyewy System True
Microsoft.Windows.NarratorQuickStart 8wekyb3d8bbwe System True
Microsoft.Windows.OOBENetworkCaptivePortal cw5n1h2txyewy System True
Microsoft.Windows.OOBENetworkConnectionFlow cw5n1h2txyewy System True
Microsoft.Windows.ParentalControls cw5n1h2txyewy System True
Microsoft.Windows.PeopleExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.Photos 8wekyb3d8bbwe Provisioned False
Microsoft.Windows.PinningConfirmationDialog cw5n1h2txyewy System True
Microsoft.Windows.Search cw5n1h2txyewy System True
Microsoft.Windows.SecHealthUI cw5n1h2txyewy System True
Microsoft.Windows.SecureAssessmentBrowser cw5n1h2txyewy System True
Microsoft.Windows.ShellExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.StartMenuExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.XGpuEjectDialog cw5n1h2txyewy System True
Microsoft.WindowsAlarms 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCalculator 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCamera 8wekyb3d8bbwe Provisioned False
microsoft.windowscommunicationsapps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsFeedbackHub 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsMaps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsSoundRecorder 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsStore 8wekyb3d8bbwe Provisioned False
Microsoft.Xbox.TCUI 8wekyb3d8bbwe Provisioned False
Microsoft.XboxApp 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGameCallableUI cw5n1h2txyewy System True
Microsoft.XboxGameOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGamingOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxIdentityProvider 8wekyb3d8bbwe Provisioned False
Microsoft.XboxSpeechToTextOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.YourPhone 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneMusic 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneVideo 8wekyb3d8bbwe Provisioned False
MicrosoftWindows.Client.CBS cw5n1h2txyewy System True
MicrosoftWindows.UndockedDevKit cw5n1h2txyewy System True
NcsiUwpApp 8wekyb3d8bbwe System True
Windows.CBSPreview cw5n1h2txyewy System True
windows.immersivecontrolpanel cw5n1h2txyewy System True
Windows.PrintDialog cw5n1h2txyewy System True

View File

@@ -0,0 +1,85 @@
Name PublisherId Category NonRemovable
---- ----------- -------- ------------
1527c705-839a-4832-9118-54d4Bd6a0c89 cw5n1h2txyewy System True
c5e2524a-ea46-4f67-841f-6a9465d9d515 cw5n1h2txyewy System True
E2A4F912-2574-4A75-9BB0-0D023378592B cw5n1h2txyewy System True
F46D4000-FD22-4DB4-AC8E-4E1DDDE828FE cw5n1h2txyewy System True
Microsoft.549981C3F5F10 8wekyb3d8bbwe Provisioned False
Microsoft.AAD.BrokerPlugin cw5n1h2txyewy System True
Microsoft.AccountsControl cw5n1h2txyewy System True
Microsoft.AsyncTextService 8wekyb3d8bbwe System True
Microsoft.BingWeather 8wekyb3d8bbwe Provisioned False
Microsoft.BioEnrollment cw5n1h2txyewy System True
Microsoft.CredDialogHost cw5n1h2txyewy System True
Microsoft.DesktopAppInstaller 8wekyb3d8bbwe Provisioned False
Microsoft.ECApp 8wekyb3d8bbwe System True
Microsoft.GetHelp 8wekyb3d8bbwe Provisioned False
Microsoft.Getstarted 8wekyb3d8bbwe Provisioned False
Microsoft.HEIFImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.LockApp cw5n1h2txyewy System True
Microsoft.Microsoft3DViewer 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftEdge 8wekyb3d8bbwe System True
Microsoft.MicrosoftEdge.Stable 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftEdgeDevToolsClient 8wekyb3d8bbwe System True
Microsoft.MicrosoftOfficeHub 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftSolitaireCollection 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftStickyNotes 8wekyb3d8bbwe Provisioned False
Microsoft.MixedReality.Portal 8wekyb3d8bbwe Provisioned False
Microsoft.MSPaint 8wekyb3d8bbwe Provisioned False
Microsoft.Office.OneNote 8wekyb3d8bbwe Provisioned False
Microsoft.People 8wekyb3d8bbwe Provisioned False
Microsoft.ScreenSketch 8wekyb3d8bbwe Provisioned False
Microsoft.SkypeApp kzf8qxf38zg5c Provisioned False
Microsoft.StorePurchaseApp 8wekyb3d8bbwe Provisioned False
Microsoft.VCLibs.140.00 8wekyb3d8bbwe Provisioned False
Microsoft.VP9VideoExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.Wallet 8wekyb3d8bbwe Provisioned False
Microsoft.WebMediaExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.WebpImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.Win32WebViewHost cw5n1h2txyewy System True
Microsoft.Windows.Apprep.ChxApp cw5n1h2txyewy System True
Microsoft.Windows.AssignedAccessLockApp cw5n1h2txyewy System True
Microsoft.Windows.CallingShellApp cw5n1h2txyewy System True
Microsoft.Windows.CapturePicker cw5n1h2txyewy System True
Microsoft.Windows.CloudExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.ContentDeliveryManager cw5n1h2txyewy System True
Microsoft.Windows.NarratorQuickStart 8wekyb3d8bbwe System True
Microsoft.Windows.OOBENetworkCaptivePortal cw5n1h2txyewy System True
Microsoft.Windows.OOBENetworkConnectionFlow cw5n1h2txyewy System True
Microsoft.Windows.ParentalControls cw5n1h2txyewy System True
Microsoft.Windows.PeopleExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.Photos 8wekyb3d8bbwe Provisioned False
Microsoft.Windows.PinningConfirmationDialog cw5n1h2txyewy System True
Microsoft.Windows.Search cw5n1h2txyewy System True
Microsoft.Windows.SecHealthUI cw5n1h2txyewy System True
Microsoft.Windows.SecureAssessmentBrowser cw5n1h2txyewy System True
Microsoft.Windows.ShellExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.StartMenuExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.XGpuEjectDialog cw5n1h2txyewy System True
Microsoft.WindowsAlarms 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCalculator 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCamera 8wekyb3d8bbwe Provisioned False
microsoft.windowscommunicationsapps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsFeedbackHub 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsMaps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsSoundRecorder 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsStore 8wekyb3d8bbwe Provisioned False
Microsoft.Xbox.TCUI 8wekyb3d8bbwe Provisioned False
Microsoft.XboxApp 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGameCallableUI cw5n1h2txyewy System True
Microsoft.XboxGameOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGamingOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxIdentityProvider 8wekyb3d8bbwe Provisioned False
Microsoft.XboxSpeechToTextOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.YourPhone 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneMusic 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneVideo 8wekyb3d8bbwe Provisioned False
MicrosoftWindows.Client.CBS cw5n1h2txyewy System True
MicrosoftWindows.UndockedDevKit cw5n1h2txyewy System True
NcsiUwpApp 8wekyb3d8bbwe System True
Windows.CBSPreview cw5n1h2txyewy System True
windows.immersivecontrolpanel cw5n1h2txyewy System True
Windows.PrintDialog cw5n1h2txyewy System True

View File

@@ -0,0 +1,88 @@
Name PublisherId Category NonRemovable
---- ----------- -------- ------------
1527c705-839a-4832-9118-54d4Bd6a0c89 cw5n1h2txyewy System True
c5e2524a-ea46-4f67-841f-6a9465d9d515 cw5n1h2txyewy System True
E2A4F912-2574-4A75-9BB0-0D023378592B cw5n1h2txyewy System True
F46D4000-FD22-4DB4-AC8E-4E1DDDE828FE cw5n1h2txyewy System True
Microsoft.549981C3F5F10 8wekyb3d8bbwe Provisioned False
Microsoft.AAD.BrokerPlugin cw5n1h2txyewy System True
Microsoft.AccountsControl cw5n1h2txyewy System True
Microsoft.AsyncTextService 8wekyb3d8bbwe System True
Microsoft.BingNews 8wekyb3d8bbwe Provisioned False
Microsoft.BingWeather 8wekyb3d8bbwe Provisioned False
Microsoft.BioEnrollment cw5n1h2txyewy System True
Microsoft.CredDialogHost cw5n1h2txyewy System True
Microsoft.DesktopAppInstaller 8wekyb3d8bbwe Provisioned True
Microsoft.ECApp 8wekyb3d8bbwe System True
Microsoft.GamingApp 8wekyb3d8bbwe Provisioned False
Microsoft.GetHelp 8wekyb3d8bbwe Provisioned False
Microsoft.Getstarted 8wekyb3d8bbwe Provisioned False
Microsoft.HEIFImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.LockApp cw5n1h2txyewy System True
Microsoft.MicrosoftEdge 8wekyb3d8bbwe System True
Microsoft.MicrosoftEdge.Stable 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftEdgeDevToolsClient 8wekyb3d8bbwe System True
Microsoft.MicrosoftOfficeHub 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftSolitaireCollection 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftStickyNotes 8wekyb3d8bbwe Provisioned False
Microsoft.OneDriveSync 8wekyb3d8bbwe Installed False
Microsoft.Paint 8wekyb3d8bbwe Provisioned False
Microsoft.People 8wekyb3d8bbwe Provisioned False
Microsoft.PowerAutomateDesktop 8wekyb3d8bbwe Provisioned False
Microsoft.ScreenSketch 8wekyb3d8bbwe Provisioned False
Microsoft.SecHealthUI 8wekyb3d8bbwe Provisioned True
Microsoft.StorePurchaseApp 8wekyb3d8bbwe Provisioned False
Microsoft.Todos 8wekyb3d8bbwe Provisioned False
Microsoft.UI.Xaml.2.4 8wekyb3d8bbwe Provisioned False
Microsoft.VCLibs.140.00 8wekyb3d8bbwe Provisioned False
Microsoft.VP9VideoExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.WebMediaExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.WebpImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.Win32WebViewHost cw5n1h2txyewy System True
Microsoft.Windows.Apprep.ChxApp cw5n1h2txyewy System True
Microsoft.Windows.AssignedAccessLockApp cw5n1h2txyewy System True
Microsoft.Windows.CallingShellApp cw5n1h2txyewy System True
Microsoft.Windows.CapturePicker cw5n1h2txyewy System True
Microsoft.Windows.CloudExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.ContentDeliveryManager cw5n1h2txyewy System True
Microsoft.Windows.NarratorQuickStart 8wekyb3d8bbwe System True
Microsoft.Windows.OOBENetworkCaptivePortal cw5n1h2txyewy System True
Microsoft.Windows.OOBENetworkConnectionFlow cw5n1h2txyewy System True
Microsoft.Windows.ParentalControls cw5n1h2txyewy System True
Microsoft.Windows.PeopleExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.Photos 8wekyb3d8bbwe Provisioned False
Microsoft.Windows.PinningConfirmationDialog cw5n1h2txyewy System True
Microsoft.Windows.Search cw5n1h2txyewy System True
Microsoft.Windows.SecureAssessmentBrowser cw5n1h2txyewy System True
Microsoft.Windows.ShellExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.StartMenuExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.XGpuEjectDialog cw5n1h2txyewy System True
Microsoft.WindowsAlarms 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCalculator 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCamera 8wekyb3d8bbwe Provisioned False
microsoft.windowscommunicationsapps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsFeedbackHub 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsMaps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsNotepad 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsSoundRecorder 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsStore 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsTerminal 8wekyb3d8bbwe Provisioned False
Microsoft.Xbox.TCUI 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGameCallableUI cw5n1h2txyewy System True
Microsoft.XboxGameOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGamingOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxIdentityProvider 8wekyb3d8bbwe Provisioned False
Microsoft.XboxSpeechToTextOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.YourPhone 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneMusic 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneVideo 8wekyb3d8bbwe Provisioned False
MicrosoftWindows.Client.CBS cw5n1h2txyewy System True
MicrosoftWindows.Client.WebExperience cw5n1h2txyewy Provisioned False
MicrosoftWindows.UndockedDevKit cw5n1h2txyewy System True
NcsiUwpApp 8wekyb3d8bbwe System True
Windows.CBSPreview cw5n1h2txyewy System True
windows.immersivecontrolpanel cw5n1h2txyewy System True
Windows.PrintDialog cw5n1h2txyewy System True

View File

@@ -0,0 +1,91 @@
Name PublisherId Category NonRemovable
---- ----------- -------- ------------
1527c705-839a-4832-9118-54d4Bd6a0c89 cw5n1h2txyewy System True
c5e2524a-ea46-4f67-841f-6a9465d9d515 cw5n1h2txyewy System True
Clipchamp.Clipchamp yxz26nhyzhsrt Provisioned False
E2A4F912-2574-4A75-9BB0-0D023378592B cw5n1h2txyewy System True
F46D4000-FD22-4DB4-AC8E-4E1DDDE828FE cw5n1h2txyewy System True
Microsoft.549981C3F5F10 8wekyb3d8bbwe Provisioned False
Microsoft.AAD.BrokerPlugin cw5n1h2txyewy System True
Microsoft.AccountsControl cw5n1h2txyewy System True
Microsoft.AsyncTextService 8wekyb3d8bbwe System True
Microsoft.BingNews 8wekyb3d8bbwe Provisioned False
Microsoft.BingWeather 8wekyb3d8bbwe Provisioned False
Microsoft.BioEnrollment cw5n1h2txyewy System True
Microsoft.CredDialogHost cw5n1h2txyewy System True
Microsoft.DesktopAppInstaller 8wekyb3d8bbwe Provisioned True
Microsoft.ECApp 8wekyb3d8bbwe System True
Microsoft.GamingApp 8wekyb3d8bbwe Provisioned False
Microsoft.GetHelp 8wekyb3d8bbwe Provisioned False
Microsoft.Getstarted 8wekyb3d8bbwe Provisioned False
Microsoft.HEIFImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.HEVCVideoExtension 8wekyb3d8bbwe Provisioned False
Microsoft.LockApp cw5n1h2txyewy System True
Microsoft.MicrosoftEdge 8wekyb3d8bbwe System True
Microsoft.MicrosoftEdge.Stable 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftEdgeDevToolsClient 8wekyb3d8bbwe System True
Microsoft.MicrosoftOfficeHub 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftSolitaireCollection 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftStickyNotes 8wekyb3d8bbwe Provisioned False
Microsoft.Paint 8wekyb3d8bbwe Provisioned False
Microsoft.People 8wekyb3d8bbwe Provisioned False
Microsoft.PowerAutomateDesktop 8wekyb3d8bbwe Provisioned False
Microsoft.RawImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.ScreenSketch 8wekyb3d8bbwe Provisioned False
Microsoft.SecHealthUI 8wekyb3d8bbwe Provisioned True
Microsoft.StorePurchaseApp 8wekyb3d8bbwe Provisioned False
Microsoft.Todos 8wekyb3d8bbwe Provisioned False
Microsoft.VCLibs.140.00 8wekyb3d8bbwe Provisioned False
Microsoft.VP9VideoExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.WebMediaExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.WebpImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.Win32WebViewHost cw5n1h2txyewy System True
Microsoft.Windows.Apprep.ChxApp cw5n1h2txyewy System True
Microsoft.Windows.AssignedAccessLockApp cw5n1h2txyewy System True
Microsoft.Windows.CallingShellApp cw5n1h2txyewy System True
Microsoft.Windows.CapturePicker cw5n1h2txyewy System True
Microsoft.Windows.CloudExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.ContentDeliveryManager cw5n1h2txyewy System True
Microsoft.Windows.NarratorQuickStart 8wekyb3d8bbwe System True
Microsoft.Windows.OOBENetworkCaptivePortal cw5n1h2txyewy System True
Microsoft.Windows.OOBENetworkConnectionFlow cw5n1h2txyewy System True
Microsoft.Windows.ParentalControls cw5n1h2txyewy System True
Microsoft.Windows.PeopleExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.Photos 8wekyb3d8bbwe Provisioned False
Microsoft.Windows.PinningConfirmationDialog cw5n1h2txyewy System True
Microsoft.Windows.PrintQueueActionCenter cw5n1h2txyewy System True
Microsoft.Windows.SecureAssessmentBrowser cw5n1h2txyewy System True
Microsoft.Windows.ShellExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.StartMenuExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.XGpuEjectDialog cw5n1h2txyewy System True
Microsoft.WindowsAlarms 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCalculator 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCamera 8wekyb3d8bbwe Provisioned False
microsoft.windowscommunicationsapps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsFeedbackHub 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsMaps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsNotepad 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsSoundRecorder 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsStore 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsTerminal 8wekyb3d8bbwe Provisioned False
Microsoft.Xbox.TCUI 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGameCallableUI cw5n1h2txyewy System True
Microsoft.XboxGameOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGamingOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxIdentityProvider 8wekyb3d8bbwe Provisioned False
Microsoft.XboxSpeechToTextOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.YourPhone 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneMusic 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneVideo 8wekyb3d8bbwe Provisioned False
MicrosoftCorporationII.QuickAssist 8wekyb3d8bbwe Provisioned False
MicrosoftWindows.Client.CBS cw5n1h2txyewy System True
MicrosoftWindows.Client.Core cw5n1h2txyewy System True
MicrosoftWindows.Client.WebExperience cw5n1h2txyewy Provisioned False
MicrosoftWindows.UndockedDevKit cw5n1h2txyewy System True
NcsiUwpApp 8wekyb3d8bbwe System True
Windows.CBSPreview cw5n1h2txyewy System True
windows.immersivecontrolpanel cw5n1h2txyewy System True
Windows.PrintDialog cw5n1h2txyewy System True

View File

@@ -0,0 +1,91 @@
Name PublisherId Category NonRemovable
---- ----------- -------- ------------
1527c705-839a-4832-9118-54d4Bd6a0c89 cw5n1h2txyewy System True
c5e2524a-ea46-4f67-841f-6a9465d9d515 cw5n1h2txyewy System True
Clipchamp.Clipchamp yxz26nhyzhsrt Provisioned False
E2A4F912-2574-4A75-9BB0-0D023378592B cw5n1h2txyewy System True
F46D4000-FD22-4DB4-AC8E-4E1DDDE828FE cw5n1h2txyewy System True
Microsoft.549981C3F5F10 8wekyb3d8bbwe Provisioned False
Microsoft.AAD.BrokerPlugin cw5n1h2txyewy System True
Microsoft.AccountsControl cw5n1h2txyewy System True
Microsoft.AsyncTextService 8wekyb3d8bbwe System True
Microsoft.BingNews 8wekyb3d8bbwe Provisioned False
Microsoft.BingWeather 8wekyb3d8bbwe Provisioned False
Microsoft.BioEnrollment cw5n1h2txyewy System True
Microsoft.CredDialogHost cw5n1h2txyewy System True
Microsoft.DesktopAppInstaller 8wekyb3d8bbwe Provisioned True
Microsoft.ECApp 8wekyb3d8bbwe System True
Microsoft.GamingApp 8wekyb3d8bbwe Provisioned False
Microsoft.GetHelp 8wekyb3d8bbwe Provisioned False
Microsoft.Getstarted 8wekyb3d8bbwe Provisioned False
Microsoft.HEIFImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.HEVCVideoExtension 8wekyb3d8bbwe Provisioned False
Microsoft.LockApp cw5n1h2txyewy System True
Microsoft.MicrosoftEdge.Stable 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftEdgeDevToolsClient 8wekyb3d8bbwe System True
Microsoft.MicrosoftOfficeHub 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftSolitaireCollection 8wekyb3d8bbwe Provisioned False
Microsoft.MicrosoftStickyNotes 8wekyb3d8bbwe Provisioned False
Microsoft.Paint 8wekyb3d8bbwe Provisioned False
Microsoft.People 8wekyb3d8bbwe Provisioned False
Microsoft.PowerAutomateDesktop 8wekyb3d8bbwe Provisioned False
Microsoft.RawImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.ScreenSketch 8wekyb3d8bbwe Provisioned False
Microsoft.SecHealthUI 8wekyb3d8bbwe Provisioned True
Microsoft.StorePurchaseApp 8wekyb3d8bbwe Provisioned False
Microsoft.Todos 8wekyb3d8bbwe Provisioned False
Microsoft.VCLibs.140.00 8wekyb3d8bbwe Provisioned False
Microsoft.VP9VideoExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.WebMediaExtensions 8wekyb3d8bbwe Provisioned False
Microsoft.WebpImageExtension 8wekyb3d8bbwe Provisioned False
Microsoft.Win32WebViewHost cw5n1h2txyewy System True
Microsoft.Windows.Apprep.ChxApp cw5n1h2txyewy System True
Microsoft.Windows.AssignedAccessLockApp cw5n1h2txyewy System True
Microsoft.Windows.CallingShellApp cw5n1h2txyewy System True
Microsoft.Windows.CapturePicker cw5n1h2txyewy System True
Microsoft.Windows.CloudExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.ContentDeliveryManager cw5n1h2txyewy System True
Microsoft.Windows.NarratorQuickStart 8wekyb3d8bbwe System True
Microsoft.Windows.OOBENetworkCaptivePortal cw5n1h2txyewy System True
Microsoft.Windows.OOBENetworkConnectionFlow cw5n1h2txyewy System True
Microsoft.Windows.ParentalControls cw5n1h2txyewy System True
Microsoft.Windows.PeopleExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.Photos 8wekyb3d8bbwe Provisioned False
Microsoft.Windows.PinningConfirmationDialog cw5n1h2txyewy System True
Microsoft.Windows.PrintQueueActionCenter cw5n1h2txyewy System True
Microsoft.Windows.SecureAssessmentBrowser cw5n1h2txyewy System True
Microsoft.Windows.ShellExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.StartMenuExperienceHost cw5n1h2txyewy System True
Microsoft.Windows.XGpuEjectDialog cw5n1h2txyewy System True
Microsoft.WindowsAlarms 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCalculator 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsCamera 8wekyb3d8bbwe Provisioned False
microsoft.windowscommunicationsapps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsFeedbackHub 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsMaps 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsNotepad 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsSoundRecorder 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsStore 8wekyb3d8bbwe Provisioned False
Microsoft.WindowsTerminal 8wekyb3d8bbwe Provisioned False
Microsoft.Xbox.TCUI 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGameCallableUI cw5n1h2txyewy System True
Microsoft.XboxGameOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxGamingOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.XboxIdentityProvider 8wekyb3d8bbwe Provisioned False
Microsoft.XboxSpeechToTextOverlay 8wekyb3d8bbwe Provisioned False
Microsoft.YourPhone 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneMusic 8wekyb3d8bbwe Provisioned False
Microsoft.ZuneVideo 8wekyb3d8bbwe Provisioned False
MicrosoftCorporationII.QuickAssist 8wekyb3d8bbwe Provisioned False
MicrosoftWindows.Client.CBS cw5n1h2txyewy System True
MicrosoftWindows.Client.Core cw5n1h2txyewy System True
MicrosoftWindows.Client.FileExp cw5n1h2txyewy System True
MicrosoftWindows.Client.WebExperience cw5n1h2txyewy Provisioned False
MicrosoftWindows.UndockedDevKit cw5n1h2txyewy System True
NcsiUwpApp 8wekyb3d8bbwe System True
Windows.CBSPreview cw5n1h2txyewy System True
windows.immersivecontrolpanel cw5n1h2txyewy System True
Windows.PrintDialog cw5n1h2txyewy System True

View File

@@ -0,0 +1,46 @@
# Research on Windows
In this section, we maintain a structured approach to our research on Windows.
The use of `01` prefixed file names aids in organizing and retrieving search results effectively.
## Apps
The PowerShell script below serves as a method for gathering detailed information about Windows packages.
```ps1
$allPackages = @()
$provisionedPackages = Get-AppxProvisionedPackage -Online
foreach ($installedPackage in Get-AppxPackage -AllUsers) {
if ($installedPackage.IsFramework -eq $true) {
continue
}
$allPackages += [PSCustomObject]@{
Name = $installedPackage.Name
PublisherId = $installedPackage.PublisherId
Category = if ($installedPackage.SignatureKind -eq "System") {
'System'
} elseif ($provisionedPackages | Where-Object { $_.DisplayName -eq $installedPackage.Name }) {
'Provisioned'
} else {
'Installed'
}
NonRemovable = $installedPackage.NonRemovable
}
}
foreach ($provisionedPackage in $provisionedPackages) {
if ($allPackages | Where-Object { $_.Name -eq $provisionedPackage.DisplayName }) {
continue
}
$allPackages += [PSCustomObject]@{
Name = $provisionedPackage.DisplayName
PublisherId = $provisionedPackage.PackageName -split '_' | Select-Object -Last 1
Category = 'Provisioned'
NonRemovable = $false
}
}
$allPackages `
| Sort-Object Name `
| Select-Object Name, PublisherId, Category, NonRemovable `
| Format-Table `
| Out-File -FilePath "$([System.Environment]::GetFolderPath('Desktop'))\apps.txt"
```

56
docs/script-guidelines.md Normal file
View File

@@ -0,0 +1,56 @@
# privacy.sexy Script Guidelines
Create a script for privacy.sexy by submitting a PR or creating an issue (details in [Extend Scripts](./../CONTRIBUTING.md#extend-scripts)).
As scripts are central to privacy.sexy and reach a global audience, their design is critical.
Key attributes of a good script:
- ✅ Well-referenced [documentation](#documentation).
- ✅ Utilizes [shared functions](#shared-functions).
- ✅ Has a [simple name](#name).
## Name
- Choose a title that is easy to understand for all users, regardless of technical skill, yet remains technically accurate.
- Focus on privacy implications, avoiding complex or overly technical jargon.
- Maintain consistency in naming, avoiding linguistic variations.
- Use action-oriented language for clarity and directness. Use an instruction format like "do this, do that" for clear, direct guidance.
- Respect the official casing of brand names.
- Choose clear and uncomplicated language.
- It should start with an imperative noun.
- Start with action verbs like `Clear`, `Disable`, `Remove`, `Configure`, `Minimize`, `Maximize`. While exceptions exist, these prefixes help maintain naming consistency.
- Favor the terms:
- `Disable` over `Turn off`, `Stop`, `Prevent`
- `Configure` over `Set up`
- `Clear` over `Erase`, `Clean`
- `Minimize` over `Limit`, `Reduce`
- `Maximize` over `Extend`, `Delay`, `Postpone`, `Prolong`
- `Remove` over `Uninstall`
- Structure your phrases for clarity, examples:
- Prefer `Disable XX telemetry` over `Disable telemetry in XX`
- Prefer `Clear XX data` over `Clear data from XX`, or `Clear data of XX`.
- Use sentence case rather than Title Case.
## Documentation
- Use credible and reputable sources for references.
- Use archived links by using [archive.org](https://archive.org) or [archive.today](https://archive.today).
- Format archive.today links fully, for example: `https://archive.today/YYYYMMDDhhmmss/https://privacy.sexy`.
- Explain the default behavior if the script is not executed.
## Shared functions
Use existing shared functions when possible, like `DisableService` for disabling services,.
- 📖 Learn about templates in [templating.md](./templating.md).
- 📖 For syntax, see [collection-files.md](collection-files.md).
## Code
- Prefer [shared functions](#shared-functions); avoid custom code unless necessary.
- Keep code simple and compatible with older systems.
- Focus on reliability, ensuring the script is error-resistant, works on different locales and handles unexpected situations.
- Language selection:
- Windows: Use batch when simpler, otherwise PowerShell.
- macOS/Linux: Use bash when simpler, otherwise Python.
- Provide revert code to restore original/default settings when applicable.

View File

@@ -29,7 +29,9 @@ There are different types of tests executed:
- Evaluate individual components in isolation.
- Located in [`./tests/unit`](./../tests/unit).
- Achieve isolation using [stubs](./../tests/unit/shared/Stubs).
- Achieve isolation using stubs where you place:
- Common stubs in [`./shared/Stubs`](./../tests/unit/shared/Stubs),
- Component-specific stubs in same folder as test file.
- Include Vue component tests, enabled by `@vue/test-utils`.
#### Unit tests naming

View File

@@ -1,6 +1,6 @@
/* eslint-disable no-template-curly-in-string */
const { join } = require('path');
const { join } = require('node:path');
const { electronBundled, electronUnbundled } = require('./dist-dirs.json');
module.exports = {

View File

@@ -1,7 +1,7 @@
import { resolve } from 'path';
import { resolve } from 'node:path';
import { mergeConfig, UserConfig } from 'vite';
import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
import { getAliasesFromTsConfig, getClientEnvironmentVariables } from './vite-config-helper';
import { getAliases, getClientEnvironmentVariables } from './vite-config-helper';
import { createVueConfig } from './vite.config';
import distDirs from './dist-dirs.json' assert { type: 'json' };
@@ -54,7 +54,9 @@ function getSharedElectronConfig(options: {
},
rollupOptions: {
output: {
entryFileNames: '[name].cjs', // This is needed so `type="module"` works
// Mark: electron-esm-support
// This is needed so `type="module"` works
entryFileNames: '[name].cjs',
},
},
},
@@ -64,7 +66,7 @@ function getSharedElectronConfig(options: {
},
resolve: {
alias: {
...getAliasesFromTsConfig(),
...getAliases(),
},
},
};

View File

@@ -1 +0,0 @@
<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.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

1971
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "privacy.sexy",
"version": "0.12.7",
"version": "0.12.9",
"private": true,
"slogan": "Now you have the choice",
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆",
@@ -36,8 +36,7 @@
"@floating-ui/vue": "^1.0.2",
"@juggle/resize-observer": "^3.4.0",
"ace-builds": "^1.30.0",
"cross-fetch": "^4.0.0",
"electron-log": "^4.4.8",
"electron-log": "^5.0.1",
"electron-progressbar": "^2.1.0",
"electron-updater": "^6.1.4",
"file-saver": "^2.0.5",
@@ -46,15 +45,15 @@
},
"devDependencies": {
"@modyfi/vite-plugin-yaml": "^1.0.4",
"@rushstack/eslint-patch": "^1.5.1",
"@rushstack/eslint-patch": "^1.6.1",
"@types/ace": "^0.0.49",
"@types/file-saver": "^2.0.5",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/parser": "^6.17.0",
"@vitejs/plugin-legacy": "^4.1.1",
"@vitejs/plugin-vue": "^4.4.0",
"@vue/eslint-config-airbnb-with-typescript": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.3",
"@vue/eslint-config-airbnb-with-typescript": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/test-utils": "^2.4.1",
"autoprefixer": "^10.4.16",
"cypress": "^13.3.1",
@@ -63,9 +62,9 @@
"electron-devtools-installer": "^3.2.0",
"electron-icon-builder": "^2.0.1",
"electron-vite": "^1.0.28",
"eslint": "^8.51.0",
"eslint": "^8.56.0",
"eslint-plugin-cypress": "^2.15.1",
"eslint-plugin-vue": "^9.17.0",
"eslint-plugin-vue": "^9.19.2",
"eslint-plugin-vuejs-accessibility": "^2.2.0",
"icon-gen": "^4.0.0",
"jsdom": "^22.1.0",
@@ -87,11 +86,8 @@
"yaml-lint": "^1.7.0"
},
"//devDependencies": {
"terser": "Used by @vitejs/plugin-legacy for minification",
"@rushstack/eslint-patch": "Needed by @vue/eslint-config-typescript",
"@vue/eslint-config-typescript": "Cannot upgrade to 12.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58",
"@typescript-eslint/eslint-plugin": "Cannot upgrade to 6.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58",
"@typescript-eslint/parser": "Cannot upgrade to 6.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58"
"terser": "Used by `@vitejs/plugin-legacy` for minification",
"@rushstack/eslint-patch": "Needed by `@vue/eslint-config-typescript` and `@vue/eslint-config-airbnb-with-typescript`"
},
"homepage": "https://privacy.sexy",
"repository": {

View File

@@ -1,74 +0,0 @@
#!/usr/bin/env bash
# This script ensures that the '.vscode/settings.json' file exists and is configured correctly for ESLint validation on Vue and JavaScript files.
# See https://web.archive.org/web/20230801024405/https://eslint.vuejs.org/user-guide/#visual-studio-code
declare -r SETTINGS_FILE='.vscode/settings.json'
declare -ra CONFIG_KEYS=('vue' 'javascript' 'typescript')
declare -r TEMP_FILE="tmp.$$.json"
main() {
ensure_vscode_directory_exists
create_or_update_settings
}
ensure_vscode_directory_exists() {
local dir_name
dir_name=$(dirname "${SETTINGS_FILE}")
if [[ ! -d ${dir_name} ]]; then
mkdir -p "${dir_name}"
echo "🎉 Created directory: ${dir_name}"
fi
}
create_or_update_settings() {
if [[ ! -f ${SETTINGS_FILE} ]]; then
create_default_settings
else
add_or_update_eslint_validate
fi
}
create_default_settings() {
local default_validate
default_validate=$(printf '%s' "${CONFIG_KEYS[*]}" | jq -R -s -c -M 'split(" ")')
echo "{ \"eslint.validate\": ${default_validate} }" | jq '.' > "${SETTINGS_FILE}"
echo "🎉 Created default ${SETTINGS_FILE}"
}
add_or_update_eslint_validate() {
if ! jq -e '.["eslint.validate"]' "${SETTINGS_FILE}" >/dev/null; then
add_default_eslint_validate
else
update_eslint_validate
fi
}
add_default_eslint_validate() {
jq --argjson keys "$(printf '%s' "${CONFIG_KEYS[*]}" \
| jq -R -s -c 'split(" ")')" '. += {"eslint.validate": $keys}' "${SETTINGS_FILE}" > "${TEMP_FILE}"
replace_and_confirm
echo "🎉 Added default 'eslint.validate' to ${SETTINGS_FILE}"
}
update_eslint_validate() {
local existing_keys
existing_keys=$(jq '.["eslint.validate"]' "${SETTINGS_FILE}")
for key in "${CONFIG_KEYS[@]}"; do
if ! echo "${existing_keys}" | jq 'index("'"${key}"'")' >/dev/null; then
jq '.["eslint.validate"] += ["'"${key}"'"]' "${SETTINGS_FILE}" > "${TEMP_FILE}"
mv "${TEMP_FILE}" "${SETTINGS_FILE}"
echo "🎉 Updated 'eslint.validate' in ${SETTINGS_FILE} for ${key}"
else
echo "⏩️ No updated needed for ${key} ${SETTINGS_FILE}."
fi
done
}
replace_and_confirm() {
if mv "${TEMP_FILE}" "${SETTINGS_FILE}"; then
echo "🎉 Updated ${SETTINGS_FILE}"
fi
}
main

162
scripts/configure_vscode.py Executable file
View File

@@ -0,0 +1,162 @@
"""
This script configures project-level VSCode settings in '.vscode/settings.json' for
development and installs recommended extensions from '.vscode/extensions.json'.
"""
# pylint: disable=missing-function-docstring
import os
import json
import subprocess
import sys
import re
from typing import Any
from shutil import which
VSCODE_SETTINGS_JSON_FILE: str = '.vscode/settings.json'
VSCODE_EXTENSIONS_JSON_FILE: str = '.vscode/extensions.json'
def main() -> None:
ensure_vscode_directory_exists()
ensure_setting_file_exists()
add_or_update_settings()
install_recommended_extensions()
def ensure_vscode_directory_exists() -> None:
vscode_directory_path = os.path.dirname(VSCODE_SETTINGS_JSON_FILE)
try:
os.makedirs(vscode_directory_path, exist_ok=True)
print_success(f"Created or verified directory: {vscode_directory_path}")
except OSError as error:
print_error(f"Error handling directory {vscode_directory_path}: {error}")
def ensure_setting_file_exists() -> None:
try:
if os.path.isfile(VSCODE_SETTINGS_JSON_FILE):
print_success(f"VSCode settings file exists: {VSCODE_SETTINGS_JSON_FILE}")
return
with open(VSCODE_SETTINGS_JSON_FILE, 'w', encoding='utf-8') as file:
json.dump({}, file, indent=4)
print_success(f"Created empty {VSCODE_SETTINGS_JSON_FILE}")
except IOError as error:
print_error(f"Error creating file {VSCODE_SETTINGS_JSON_FILE}: {error}")
print(f"📄 Created empty {VSCODE_SETTINGS_JSON_FILE}")
def add_or_update_settings() -> None:
configure_setting_key('eslint.validate', ['vue', 'javascript', 'typescript'])
# Set ESLint validation for specific file types.
# Details: # pylint: disable-next=line-too-long
# - https://web.archive.org/web/20230801024405/https://eslint.vuejs.org/user-guide/#visual-studio-code
configure_setting_key('terminal.integrated.env.linux', {"GTK_PATH": ""})
# Unset GTK_PATH on Linux for Electron development in sandboxed environments
# like Snap or Flatpak VSCode installations, enabling script execution.
# Details: # pylint: disable-next=line-too-long
# - https://archive.ph/2024.01.06-003914/https://github.com/microsoft/vscode/issues/179274, https://web.archive.org/web/20240106003915/https://github.com/microsoft/vscode/issues/179274
def configure_setting_key(configuration_key: str, desired_value: Any) -> None:
try:
with open(VSCODE_SETTINGS_JSON_FILE, 'r+', encoding='utf-8') as file:
settings: dict = json.load(file)
if configuration_key in settings:
actual_value = settings[configuration_key]
if actual_value == desired_value:
print_skip(f"Already configured as desired: \"{configuration_key}\"")
return
settings[configuration_key] = desired_value
file.seek(0)
json.dump(settings, file, indent=4)
file.truncate()
print_success(f"Added or updated configuration: {configuration_key}")
except json.JSONDecodeError:
print_error(f"Failed to update JSON for key {configuration_key}.")
def install_recommended_extensions() -> None:
if not os.path.isfile(VSCODE_EXTENSIONS_JSON_FILE):
print_error(
f"The extensions.json file does not exist in the path: {VSCODE_EXTENSIONS_JSON_FILE}."
)
return
with open(VSCODE_EXTENSIONS_JSON_FILE, 'r', encoding='utf-8') as file:
json_content: str = remove_json_comments(file.read())
try:
data: dict = json.loads(json_content)
extensions: list[str] = data.get("recommendations", [])
if not extensions:
print_skip(f"No recommendations found in the {VSCODE_EXTENSIONS_JSON_FILE} file.")
return
vscode_cli_path = which('code') # More reliable than using `code`, especially on Windows.
if vscode_cli_path is None:
print_error('Visual Studio Code CLI (`code`) tool not found.')
return
install_vscode_extensions(vscode_cli_path, extensions)
except json.JSONDecodeError:
print_error(f"Invalid JSON in {VSCODE_EXTENSIONS_JSON_FILE}")
def remove_json_comments(json_like: str) -> str:
pattern: str = r'(?:"(?:\\.|[^"\\])*"|/\*[\s\S]*?\*/|//.*)|([^:]//.*$)'
return re.sub(
pattern,
lambda m: '' if m.group(1) else m.group(0), json_like, flags=re.MULTILINE,
)
def install_vscode_extensions(vscode_cli_path: str, extensions: list[str]) -> None:
successful_installations = 0
for ext in extensions:
try:
result = subprocess.run(
[vscode_cli_path, "--install-extension", ext],
check=True,
capture_output=True,
text=True,
)
if "already installed" in result.stdout:
print_skip(f"Created or verified directory: {ext}")
else:
print_success(f"Installed extension: {ext}")
successful_installations += 1
print_subprocess_output(result)
except subprocess.CalledProcessError as e:
print_subprocess_output(e)
print_error(f"Failed to install extension: {ext}")
except FileNotFoundError:
print_error(' '.join([
f"Visual Studio Code CLI tool not found: {vscode_cli_path}."
f"Could not install extension: {ext}",
]))
total_extensions = len(extensions)
print_installation_results(successful_installations, total_extensions)
def print_subprocess_output(result: subprocess.CompletedProcess[str]) -> None:
output = '\n'.join([text.strip() for text in [result.stdout, result.stderr] if text])
if not output:
return
formatted_output = '\t' + output.strip().replace('\n', '\n\t')
print(formatted_output)
def print_installation_results(successful_installations: int, total_extensions: int) -> None:
if successful_installations == total_extensions:
print_success(
f"Successfully installed or verified all {total_extensions} recommended extensions."
)
elif successful_installations > 0:
print_warning(
f"Partially successful: Installed or verified {successful_installations} "
f"out of {total_extensions} recommended extensions."
)
else:
print_error("Failed to install any of the recommended extensions.")
def print_error(message: str) -> None:
print(f"💀 Error: {message}", file=sys.stderr)
def print_success(message: str) -> None:
print(f"✅ Success: {message}")
def print_skip(message: str) -> None:
print(f"⏩ Skipped: {message}")
def print_warning(message: str) -> None:
print(f"⚠️ Warning: {message}", file=sys.stderr)
if __name__ == "__main__":
main()

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env bash
import { resolve, join } from 'path';
import { rm, mkdtemp, stat } from 'fs/promises';
import { spawn } from 'child_process';
import { URL, fileURLToPath } from 'url';
import { resolve, join } from 'node:path';
import { rm, mkdtemp, stat } from 'node:fs/promises';
import { spawn } from 'node:child_process';
import { URL, fileURLToPath } from 'node:url';
class Paths {
constructor(selfDirectory) {

View File

@@ -35,10 +35,10 @@ Note:
Example: npm run install-deps -- --fresh --non-deterministic
*/
import { exec } from 'child_process';
import { resolve } from 'path';
import { access, rm, unlink } from 'fs/promises';
import { constants } from 'fs';
import { exec } from 'node:child_process';
import { resolve } from 'node:path';
import { access, rm, unlink } from 'node:fs/promises';
import { constants } from 'node:fs';
const MAX_RETRIES = 5;
const RETRY_DELAY_IN_MS = 5 /* seconds */ * 1000;

View File

@@ -12,8 +12,8 @@
* --web Path for the web application
*/
import { resolve } from 'path';
import { readFile } from 'fs/promises';
import { resolve } from 'node:path';
import { readFile } from 'node:fs/promises';
const DIST_DIRS_JSON_FILE_PATH = resolve(process.cwd(), 'dist-dirs.json'); // cannot statically import because ESLint does not support it https://github.com/eslint/eslint/discussions/15305
const CLI_ARGUMENTS = process.argv.slice(2);

View File

@@ -13,9 +13,9 @@
* --web Verify artifacts for the web application.
*/
import { access, readdir } from 'fs/promises';
import { exec } from 'child_process';
import { resolve } from 'path';
import { access, readdir } from 'node:fs/promises';
import { exec } from 'node:child_process';
import { resolve } from 'node:path';
const PROCESS_ARGUMENTS = process.argv.slice(2);
const PRINT_DIST_DIR_SCRIPT_BASE_COMMAND = 'node scripts/print-dist-dir';

View File

@@ -14,3 +14,35 @@ export type ConstructorArguments<T> =
export type FunctionKeys<T> = {
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? K : never;
}[keyof T];
export function isString(value: unknown): value is string {
return typeof value === 'string';
}
export function isNumber(value: unknown): value is number {
return typeof value === 'number';
}
export function isBoolean(value: unknown): value is boolean {
return typeof value === 'boolean';
}
export function isFunction(value: unknown): value is (...args: unknown[]) => unknown {
return typeof value === 'function';
}
export function isArray(value: unknown): value is Array<unknown> {
return Array.isArray(value);
}
export function isPlainObject(
variable: unknown,
): variable is object & Record<string, unknown> {
return Boolean(variable) // the data type of null is an object
&& typeof variable === 'object'
&& !Array.isArray(variable);
}
export function isNullOrUndefined(value: unknown): value is (null | undefined) {
return typeof value === 'undefined' || value === null;
}

View File

@@ -0,0 +1,37 @@
export interface CodeRunner {
runCode(
code: string,
fileExtension: string,
): Promise<CodeRunOutcome>;
}
export type CodeRunOutcome = SuccessfulCodeRun | FailedCodeRun;
export type CodeRunErrorType =
| 'FileWriteError'
| 'FileReadbackVerificationError'
| 'FilePathGenerationError'
| 'UnsupportedOperatingSystem'
| 'FileExecutionError'
| 'DirectoryCreationError'
| 'UnexpectedError';
interface CodeRunStatus {
readonly success: boolean;
readonly error?: CodeRunError;
}
interface SuccessfulCodeRun extends CodeRunStatus {
readonly success: true;
readonly error?: undefined;
}
export interface FailedCodeRun extends CodeRunStatus {
readonly success: false;
readonly error: CodeRunError;
}
export interface CodeRunError {
readonly type: CodeRunErrorType;
readonly message: string;
}

View File

@@ -0,0 +1 @@
export const ScriptFilename = 'privacy-script' as const;

View File

@@ -1,3 +1,5 @@
import { isFunction } from '@/TypeHelpers';
/*
Provides a unified and resilient way to extend errors across platforms.
@@ -50,8 +52,3 @@ function ensureStackTrace(target: Error) {
}
captureStackTrace(target, target.constructor);
}
// eslint-disable-next-line @typescript-eslint/ban-types
function isFunction(func: unknown): func is Function {
return typeof func === 'function';
}

View File

@@ -1,3 +1,5 @@
import { isString } from '@/TypeHelpers';
// Because we cannot do "T extends enum" 😞 https://github.com/microsoft/TypeScript/issues/30611
export type EnumType = number | string;
export type EnumVariable<T extends EnumType, TEnumValue extends EnumType>
@@ -23,7 +25,7 @@ function parseEnumValue<T extends EnumType, TEnumValue extends EnumType>(
if (!value) {
throw new Error(`missing ${enumName}`);
}
if (typeof value !== 'string') {
if (!isString(value)) {
throw new Error(`unexpected type of ${enumName}: "${typeof value}"`);
}
const casedValue = getEnumNames(enumVariable)
@@ -40,7 +42,7 @@ export function getEnumNames
): string[] {
return Object
.values(enumVariable)
.filter((enumMember) => typeof enumMember === 'string') as string[];
.filter((enumMember): enumMember is string => isString(enumMember));
}
export function getEnumValues<T extends EnumType, TEnumValue extends EnumType>(

View File

@@ -0,0 +1,6 @@
export interface Logger {
info(...params: unknown[]): void;
warn(...params: unknown[]): void;
error(...params: unknown[]): void;
debug(...params: unknown[]): void;
}

View File

@@ -1,7 +1,7 @@
import { Timer, TimeoutType } from './Timer';
import { PlatformTimer } from './PlatformTimer';
export type CallbackType = (..._: unknown[]) => void;
export type CallbackType = (..._: readonly unknown[]) => void;
export function throttle(
callback: CallbackType,

View File

@@ -1,14 +1,14 @@
import { IApplicationContext } from '@/application/Context/IApplicationContext';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { IApplication } from '@/domain/IApplication';
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
import { CurrentEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironmentFactory';
import { IApplicationFactory } from '../IApplicationFactory';
import { ApplicationFactory } from '../ApplicationFactory';
import { ApplicationContext } from './ApplicationContext';
export async function buildContext(
factory: IApplicationFactory = ApplicationFactory.Current,
environment = RuntimeEnvironment.CurrentEnvironment,
environment = CurrentEnvironment,
): Promise<IApplicationContext> {
const app = await factory.getApp();
const os = getInitialOs(app, environment.os);

View File

@@ -1,4 +1,5 @@
import type { DocumentableData, DocumentationData } from '@/application/collections/';
import { isString, isArray } from '@/TypeHelpers';
export function parseDocs(documentable: DocumentableData): readonly string[] {
const { docs } = documentable;
@@ -14,11 +15,9 @@ function addDocs(
docs: DocumentationData,
container: DocumentationContainer,
): DocumentationContainer {
if (docs instanceof Array) {
if (docs.length > 0) {
container.addParts(docs);
}
} else if (typeof docs === 'string') {
if (isArray(docs)) {
docs.forEach((doc) => container.addPart(doc));
} else if (isString(docs)) {
container.addPart(docs);
} else {
throwInvalidType();
@@ -29,27 +28,21 @@ function addDocs(
class DocumentationContainer {
private readonly parts = new Array<string>();
public addPart(documentation: string) {
public addPart(documentation: unknown): void {
if (!documentation) {
throw Error('missing documentation');
}
if (typeof documentation !== 'string') {
if (!isString(documentation)) {
throwInvalidType();
}
this.parts.push(documentation);
}
public addParts(parts: readonly string[]) {
for (const part of parts) {
this.addPart(part);
}
}
public getAll(): ReadonlyArray<string> {
return this.parts;
}
}
function throwInvalidType() {
function throwInvalidType(): never {
throw new Error('docs field (documentation) must be an array of strings');
}

View File

@@ -1,3 +1,4 @@
import { isString } from '@/TypeHelpers';
import { INodeDataErrorContext, NodeDataError } from './NodeDataError';
import { NodeData } from './NodeData';
@@ -13,7 +14,7 @@ export class NodeValidator {
'missing name',
)
.assert(
() => typeof nameValue === 'string',
() => isString(nameValue),
`Name (${JSON.stringify(nameValue)}) is not a string but ${typeof nameValue}.`,
);
}

View File

@@ -1,4 +1,5 @@
import type { FunctionCallData, FunctionCallsData, FunctionCallParametersData } from '@/application/collections/';
import { isArray, isPlainObject } from '@/TypeHelpers';
import { FunctionCall } from './FunctionCall';
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
@@ -10,13 +11,13 @@ export function parseFunctionCalls(calls: FunctionCallsData): FunctionCall[] {
}
function getCallSequence(calls: FunctionCallsData): FunctionCallData[] {
if (typeof calls !== 'object') {
throw new Error('called function(s) must be an object');
if (!isPlainObject(calls) && !isArray(calls)) {
throw new Error('called function(s) must be an object or array');
}
if (calls instanceof Array) {
if (isArray(calls)) {
return calls as FunctionCallData[];
}
const singleCall = calls;
const singleCall = calls as FunctionCallData;
return [singleCall];
}

View File

@@ -6,6 +6,7 @@ import { CodeValidator } from '@/application/Parser/Script/Validation/CodeValida
import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmptyLines';
import { NoDuplicatedLines } from '@/application/Parser/Script/Validation/Rules/NoDuplicatedLines';
import { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
import { isArray, isNullOrUndefined, isPlainObject } from '@/TypeHelpers';
import { createFunctionWithInlineCode, createCallerFunction } from './SharedFunction';
import { SharedFunctionCollection } from './SharedFunctionCollection';
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
@@ -121,8 +122,11 @@ function ensureEitherCallOrCodeIsDefined(holders: readonly FunctionData[]) {
}
function ensureExpectedParametersType(functions: readonly FunctionData[]) {
const hasValidParameters = (
func: FunctionData,
) => isNullOrUndefined(func.parameters) || isArrayOfObjects(func.parameters);
const unexpectedFunctions = functions
.filter((func) => func.parameters && !isArrayOfObjects(func.parameters));
.filter((func) => !hasValidParameters(func));
if (unexpectedFunctions.length) {
const errorMessage = `parameters must be an array of objects in function(s) ${printNames(unexpectedFunctions)}`;
throw new Error(errorMessage);
@@ -130,8 +134,7 @@ function ensureExpectedParametersType(functions: readonly FunctionData[]) {
}
function isArrayOfObjects(value: unknown): boolean {
return Array.isArray(value)
&& value.every((item) => typeof item === 'object');
return isArray(value) && value.every((item) => isPlainObject(item));
}
function printNames(holders: readonly FunctionData[]) {

View File

@@ -0,0 +1,10 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
export interface ScriptDiagnosticsCollector {
collectDiagnosticInformation(): Promise<ScriptDiagnosticData>;
}
export interface ScriptDiagnosticData {
readonly scriptsDirectoryAbsolutePath?: string;
readonly currentOperatingSystem?: OperatingSystem;
}

View File

@@ -3241,6 +3241,8 @@ functions:
revertCode: '{{ with $revertCode }}{{ . }}{{ end }}'
-
name: RunIfCommandExists # Skips if command does not exist
# Marked: refactor-with-partials
# Same function as macOS
parameters:
- name: command
- name: code

View File

@@ -444,47 +444,285 @@ actions:
recommend: standard
code: sudo purge
-
category: Clear all privacy permissions for applications
category: Clear application privacy permissions
docs: |-
This category provides scripts to reset privacy permissions for a variety of applications on your device,
helping you to re-establish control over your personal data. Each script targets a specific permission type such
as camera, microphone, contacts, or accessibility services enabling you to revoke permissions that have previously
been granted to applications.
By resetting these permissions, you not only enhance your privacy but also improve your device's security. After
running these scripts, applications will require your explicit permission again to access these services or
information. This means the next time an app attempts to use a service like your camera or access your contacts,
you'll be prompted to grant or deny permission. It's a proactive step to ensure that your sensitive information
or system services are accessed only with your current and informed consent.
children:
# Main documentation: https://archive.ph/26Hlq (https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services)
-
name: Clear "camera" permissions
code: tccutil reset Camera
name: Clear **"All"** permissions
docs: |-
This script resets all permissions for applications.
It revokes all previously granted permissions, enhancing privacy and security by ensuring no application has unauthorized access to system services or user data.
call:
function: ResetServicePermissions
parameters:
serviceId: All
-
name: Clear "microphone" permissions
code: tccutil reset Microphone
name: Clear "Camera" permissions
docs: |-
This script resets permissions for camera access [1].
It ensures no application can access the system camera without explicit user permission, protecting against unauthorized surveillance and data breaches.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: Camera
-
name: Clear "accessibility" permissions
code: tccutil reset Accessibility
name: Clear "Microphone" permissions
docs: |-
This script resets permissions for microphone access [1].
It revokes all granted access to the microphone, protecting against eavesdropping and unauthorized audio recording by applications.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: Microphone
-
name: Clear "screen capture" permissions
code: tccutil reset ScreenCapture
name: Clear "Accessibility" permissions
docs: |-
This script resets permissions for accessibility features [1].
It revokes application access to accessibility services, preventing misuse and ensuring these features are used only with user consent.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: Accessibility
-
name: Clear "reminders" permissions
code: tccutil reset Reminders
name: Clear "Screen Capture" permissions
docs: |-
This script resets permissions for screen capture [1].
It ensures applications cannot capture screen content without user authorization, protecting sensitive information displayed on the screen.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: ScreenCapture
-
name: Clear "photos" permissions
code: tccutil reset Photos
name: Clear "Reminders" permissions
docs: |-
This script resets permissions for accessing reminders information managed by the Reminders app [1].
It ensures applications cannot access or modify reminders data without explicit user permission, maintaining the privacy of personal reminders.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: Reminders
-
name: Clear "calendar" permissions
code: tccutil reset Calendar
name: Clear "Photos" permissions
docs: |-
This script resets permissions for accessing the pictures managed by the Photos app [1].
It revokes all permissions granted to applications, safeguarding personal photos and media from unauthorized access.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: Photos
-
name: Clear "full disk access" permissions
code: tccutil reset SystemPolicyAllFiles
name: Clear "Calendar" permissions
docs: |-
This script resets permissions for accessing the calendar information managed by the Calendar app [1].
It ensures that applications cannot access calendar data without user consent, protecting personal and sensitive calendar information.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: Calendar
-
name: Clear "contacts" permissions
code: tccutil reset SystemPolicyAllFiles
name: Clear "Full Disk Access" permissions
docs: |-
This script resets permissions for full disk access.
Full disk access allows the application access to all protected files, including system administration files [1].
It revokes broad file access from applications, significantly reducing the risk of data exposure and enhancing overall system security.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: SystemPolicyAllFiles
-
name: Clear "desktop folder" permissions
code: tccutil reset SystemPolicyDesktopFolder
name: Clear "Contacts" permissions
docs: |-
This script resets permissions for accessing contacts.
The contact information managed by the Contacts app [1].
It ensures that applications cannot access the user's contact list without explicit permission, maintaining the confidentiality of personal contacts.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: AddressBook
-
name: Clear "documents folder" permissions
code: tccutil reset SystemPolicyDocumentsFolder
name: Clear "Desktop Folder" permissions
docs: |-
This script resets permissions for accessing the Desktop folder [1].
It revokes application access to files on the desktop, protecting personal and work-related documents from unauthorized access.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: SystemPolicyDesktopFolder
-
name: Clear "downloads" permissions
code: tccutil reset SystemPolicyDownloadsFolder
name: Clear "Documents Folder" permissions
docs: |-
This script resets permissions for accessing the Documents folder [1].
It prevents applications from accessing files in this folder without user consent, safeguarding important and private documents.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: SystemPolicyDocumentsFolder
-
name: Clear all app permissions
code: tccutil reset All
name: Clear "Downloads Folder" permissions
docs: |-
This script resets permissions for accessing the Downloads folder [1].
It ensures that applications cannot access downloaded files without user authorization, protecting downloaded content from misuse.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: SystemPolicyDownloadsFolder
-
name: Clear "Apple Events" permissions
docs: |-
This script resets permissions for Apple Events [1].
It revokes permissions for applications to send restricted Apple Events to other processes [1], enhancing privacy and security.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: AppleEvents
-
name: Clear "File Provider Presence" permissions
docs: |-
This script resets permissions for File Provider Presence [1].
It revokes the ability of File Provider applications to know when the user is accessing their managed files [1], enhancing user privacy.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: FileProviderPresence
-
name: Clear "Listen Events" permissions
docs: |-
This script resets "ListenEvent" permissions [1].
It revokes application access to listen to system events [1], preventing unauthorized monitoring of user interactions with the system.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: ListenEvent
-
name: Clear "Media Library" permissions
docs: |-
This script resets permissions for accessing the Media Library [1].
It ensures that applications cannot access Apple Music, music and video activity, and the media library [1] without user consent.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: MediaLibrary
-
name: Clear "Post Event" permissions
docs: |-
This script resets permissions for sending "PostEvent" [1].
It prevents applications from using CoreGraphics APIs to send system events [1], safeguarding against potential misuse.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: PostEvent
-
name: Clear "Speech Recognition" permissions
recommend: strict
docs: |-
This script resets permissions for using Speech Recognition [1].
It revokes application access to the speech recognition facility and sending speech data to Apple [1], protecting user privacy.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: SpeechRecognition
-
name: Clear "App Modification" permissions
docs: |-
This script resets permissions for modifying other apps [1].
It prevents applications from updating or deleting other apps [1], maintaining system integrity and user control.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: SystemPolicyAppBundles
-
name: Clear "Application Data" permissions
docs: |-
This script resets permissions for accessing application data [1].
It revokes application access to specific application data, enhancing privacy and data security.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: SystemPolicyAppData
-
name: Clear "Network Volumes" permissions
docs: |-
This script resets permissions for accessing files on network volumes [1].
It ensures applications cannot access network files without user authorization.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: SystemPolicyNetworkVolumes
-
name: Clear "Removable Volumes" permissions
docs: |-
This script resets permissions for accessing files on removable volumes [1].
It protects data on external drives from unauthorized application access.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: SystemPolicyRemovableVolumes
-
name: Clear "System Administration Files" permissions
docs: |-
This script resets permissions for accessing system administration files [1].
It enhances system security by restricting application access to critical system files.
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
call:
function: ResetServicePermissions
parameters:
serviceId: SystemPolicySysAdminFiles
-
category: Configure programs
children:
@@ -1268,3 +1506,55 @@ functions:
echo "[$profile_file] No need for any action, configuration does not exist"
fi
done
-
name: RunIfCommandExists # Skips if command does not exist
# Marked: refactor-with-partials
# Same function as Linux
parameters:
- name: command
- name: code
- name: revertCode
optional: true
code: |-
if ! command -v '{{ $command }}' &> /dev/null; then
echo 'Skipping because "{{ $command }}" is not found.'
else
{{ $code }}
fi
revertCode: |-
{{ with $revertCode }}
if ! command -v '{{ $command }}' &> /dev/null; then
>&2 echo 'Cannot revert because "{{ $command }}" is not found.'
else
{{ . }}
fi
{{ end }}
-
name: ResetServicePermissions
parameters:
- name: serviceId # Specifies the service ID for which to reset permissions
docs: |-
This function resets the specified service ID permissions.
The `serviceId` parameter allows you to define the specific service ID (e.g., Camera, Microphone,
Accessibility) for which you want to reset all user-granted permissions.
call:
function: RunIfCommandExists
parameters:
command: tccutil
code: |-
declare serviceId='{{ $serviceId }}'
declare reset_output reset_exit_code
{
reset_output=$(tccutil reset "$serviceId" 2>&1)
reset_exit_code=$?
}
if [ $reset_exit_code -eq 0 ]; then
echo "Successfully reset permissions for \"${serviceId}\"."
elif [ $reset_exit_code -eq 70 ]; then
echo "Skipping, service ID \"${serviceId}\" is not supported on your operating system version."
elif [ $reset_exit_code -ne 0 ]; then
>&2 echo "Failed to reset permissions for \"${serviceId}\". Exit code: $reset_exit_code."
if [ -n "$reset_output" ]; then
echo "Output from \`tccutil\`: $reset_output."
fi
fi

File diff suppressed because it is too large Load Diff

View File

@@ -4,10 +4,34 @@ export enum OperatingSystem {
Linux,
KaiOS,
ChromeOS,
BlackBerryOS,
BlackBerry,
BlackBerryTabletOS,
Android,
iOS,
iPadOS,
/**
* Legacy: Released in 1999, discontinued in 2013, succeeded by BlackBerry10.
*/
BlackBerryOS,
/**
* Legacy: Released in 2013, discontinued in 2015, succeeded by {@link OperatingSystem.Android}.
*/
BlackBerry10,
/**
* Legacy: Released in 2010, discontinued in 2017,
* succeeded by {@link OperatingSystem.Windows10Mobile}.
*/
WindowsPhone,
/**
* Legacy: Released in 2015, discontinued in 2017, succeeded by {@link OperatingSystem.Android}.
*/
Windows10Mobile,
/**
* Also known as "BlackBerry PlayBook OS"
* Legacy: Released in 2011, discontinued in 2014, succeeded by {@link OperatingSystem.Android}.
*/
BlackBerryTabletOS,
}

View File

@@ -1,46 +0,0 @@
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { getWindowInjectedSystemOperations } from './SystemOperations/WindowInjectedSystemOperations';
export class CodeRunner {
constructor(
private readonly system = getWindowInjectedSystemOperations(),
private readonly environment = RuntimeEnvironment.CurrentEnvironment,
) { }
public async runCode(code: string, folderName: string, fileExtension: string): Promise<void> {
const { os } = this.environment;
if (os === undefined) {
throw new Error('Unidentified operating system');
}
const dir = this.system.location.combinePaths(
this.system.operatingSystem.getTempDirectory(),
folderName,
);
await this.system.fileSystem.createDirectory(dir, true);
const filePath = this.system.location.combinePaths(dir, `run.${fileExtension}`);
await this.system.fileSystem.writeToFile(filePath, code);
await this.system.fileSystem.setFilePermissions(filePath, '755');
const command = getExecuteCommand(filePath, os);
this.system.command.execute(command);
}
}
function getExecuteCommand(
scriptPath: string,
currentOperatingSystem: OperatingSystem,
): string {
switch (currentOperatingSystem) {
case OperatingSystem.Linux:
return `x-terminal-emulator -e '${scriptPath}'`;
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(`unsupported os: ${OperatingSystem[currentOperatingSystem]}`);
}
}

View File

@@ -0,0 +1,116 @@
import { Logger } from '@/application/Common/Log/Logger';
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
import { CodeRunError, CodeRunErrorType } from '@/application/CodeRunner/CodeRunner';
import { SystemOperations } from '../../System/SystemOperations';
import { NodeElectronSystemOperations } from '../../System/NodeElectronSystemOperations';
import { ScriptDirectoryOutcome, ScriptDirectoryProvider } from './ScriptDirectoryProvider';
export const ExecutionSubdirectory = 'runs';
/**
* Provides a dedicated directory for script execution.
* Benefits of using a persistent directory:
* - Antivirus Exclusions: Easier antivirus configuration.
* - Auditability: Stores script execution history for troubleshooting.
* - Reliability: Avoids issues with directory clean-ups during execution,
* seen in Windows Pro Azure VMs when stored on Windows temporary directory.
*/
export class PersistentDirectoryProvider implements ScriptDirectoryProvider {
constructor(
private readonly system: SystemOperations = new NodeElectronSystemOperations(),
private readonly logger: Logger = ElectronLogger,
) { }
public async provideScriptDirectory(): Promise<ScriptDirectoryOutcome> {
const {
success: isPathConstructed,
error: pathConstructionError,
directoryPath,
} = this.constructScriptDirectoryPath();
if (!isPathConstructed) {
return {
success: false,
error: pathConstructionError,
};
}
const {
success: isDirectoryCreated,
error: directoryCreationError,
} = await this.createDirectory(directoryPath);
if (!isDirectoryCreated) {
return {
success: false,
error: directoryCreationError,
};
}
return {
success: true,
directoryAbsolutePath: directoryPath,
};
}
private async createDirectory(directoryPath: string): Promise<DirectoryPathCreationOutcome> {
try {
this.logger.info(`Attempting to create script directory at path: ${directoryPath}`);
await this.system.fileSystem.createDirectory(directoryPath, true);
this.logger.info(`Script directory successfully created at: ${directoryPath}`);
return {
success: true,
};
} catch (error) {
return {
success: false,
error: this.handleException(error, 'DirectoryCreationError'),
};
}
}
private constructScriptDirectoryPath(): DirectoryPathConstructionOutcome {
try {
const parentDirectory = this.system.operatingSystem.getUserDataDirectory();
const scriptDirectory = this.system.location.combinePaths(
parentDirectory,
ExecutionSubdirectory,
);
return {
success: true,
directoryPath: scriptDirectory,
};
} catch (error) {
return {
success: false,
error: this.handleException(error, 'DirectoryCreationError'),
};
}
}
private handleException(
exception: Error,
errorType: CodeRunErrorType,
): CodeRunError {
const errorMessage = 'Error during script directory creation';
this.logger.error(errorType, errorMessage, exception);
return {
type: errorType,
message: `${errorMessage}: ${exception.message}`,
};
}
}
type DirectoryPathConstructionOutcome = {
readonly success: false;
readonly error: CodeRunError;
readonly directoryPath?: undefined;
} | {
readonly success: true;
readonly directoryPath: string;
readonly error?: undefined;
};
type DirectoryPathCreationOutcome = {
readonly success: false;
readonly error: CodeRunError;
} | {
readonly success: true;
readonly error?: undefined;
};

View File

@@ -0,0 +1,23 @@
import { CodeRunError } from '@/application/CodeRunner/CodeRunner';
export interface ScriptDirectoryProvider {
provideScriptDirectory(): Promise<ScriptDirectoryOutcome>;
}
export type ScriptDirectoryOutcome = SuccessfulDirectoryCreation | FailedDirectoryCreation;
interface ScriptDirectoryCreationStatus {
readonly success: boolean;
readonly directoryAbsolutePath?: string;
readonly error?: CodeRunError;
}
interface SuccessfulDirectoryCreation extends ScriptDirectoryCreationStatus {
readonly success: true;
readonly directoryAbsolutePath: string;
}
interface FailedDirectoryCreation extends ScriptDirectoryCreationStatus {
readonly success: false;
readonly error: CodeRunError;
}

View File

@@ -0,0 +1,5 @@
import { ScriptFilenameParts } from '../ScriptFileCreator';
export interface FilenameGenerator {
generateFilename(scriptFilenameParts: ScriptFilenameParts): string;
}

View File

@@ -0,0 +1,31 @@
import { ScriptFilenameParts } from '../ScriptFileCreator';
import { FilenameGenerator } from './FilenameGenerator';
export class TimestampedFilenameGenerator implements FilenameGenerator {
public generateFilename(
scriptFilenameParts: ScriptFilenameParts,
date = new Date(),
): string {
validateScriptFilenameParts(scriptFilenameParts);
const baseFilename = `${createTimeStampForFile(date)}-${scriptFilenameParts.scriptName}`;
return scriptFilenameParts.scriptFileExtension ? `${baseFilename}.${scriptFilenameParts.scriptFileExtension}` : baseFilename;
}
}
/** Generates a timestamp for the filename in 'YYYY-MM-DD_HH-MM-SS' format. */
function createTimeStampForFile(date: Date): string {
return date
.toISOString()
.replace(/T/, '_')
.replace(/:/g, '-')
.replace(/\..+/, '');
}
function validateScriptFilenameParts(scriptFilenameParts: ScriptFilenameParts) {
if (!scriptFilenameParts.scriptName) {
throw new Error('Script name is required but not provided.');
}
if (scriptFilenameParts.scriptFileExtension?.startsWith('.')) {
throw new Error('File extension should not start with a dot.');
}
}

View File

@@ -0,0 +1,122 @@
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
import { Logger } from '@/application/Common/Log/Logger';
import { CodeRunError, CodeRunErrorType } from '@/application/CodeRunner/CodeRunner';
import { FileReadbackVerificationErrors, ReadbackFileWriter } from '@/infrastructure/ReadbackFileWriter/ReadbackFileWriter';
import { NodeReadbackFileWriter } from '@/infrastructure/ReadbackFileWriter/NodeReadbackFileWriter';
import { SystemOperations } from '../System/SystemOperations';
import { NodeElectronSystemOperations } from '../System/NodeElectronSystemOperations';
import { FilenameGenerator } from './Filename/FilenameGenerator';
import { ScriptFilenameParts, ScriptFileCreator, ScriptFileCreationOutcome } from './ScriptFileCreator';
import { TimestampedFilenameGenerator } from './Filename/TimestampedFilenameGenerator';
import { ScriptDirectoryProvider } from './Directory/ScriptDirectoryProvider';
import { PersistentDirectoryProvider } from './Directory/PersistentDirectoryProvider';
export class ScriptFileCreationOrchestrator implements ScriptFileCreator {
constructor(
private readonly system: SystemOperations = new NodeElectronSystemOperations(),
private readonly filenameGenerator: FilenameGenerator = new TimestampedFilenameGenerator(),
private readonly directoryProvider: ScriptDirectoryProvider = new PersistentDirectoryProvider(),
private readonly fileWriter: ReadbackFileWriter = new NodeReadbackFileWriter(),
private readonly logger: Logger = ElectronLogger,
) { }
public async createScriptFile(
contents: string,
scriptFilenameParts: ScriptFilenameParts,
): Promise<ScriptFileCreationOutcome> {
const {
success: isDirectoryCreated, error: directoryCreationError, directoryAbsolutePath,
} = await this.directoryProvider.provideScriptDirectory();
if (!isDirectoryCreated) {
return createFailure(directoryCreationError);
}
const {
success: isFilePathConstructed, error: filePathGenerationError, filePath,
} = this.constructFilePath(scriptFilenameParts, directoryAbsolutePath);
if (!isFilePathConstructed) {
return createFailure(filePathGenerationError);
}
const {
success: isFileCreated, error: fileCreationError,
} = await this.writeFile(filePath, contents);
if (!isFileCreated) {
return createFailure(fileCreationError);
}
return {
success: true,
scriptFileAbsolutePath: filePath,
};
}
private constructFilePath(
scriptFilenameParts: ScriptFilenameParts,
directoryPath: string,
): FilePathConstructionOutcome {
try {
const filename = this.filenameGenerator.generateFilename(scriptFilenameParts);
const filePath = this.system.location.combinePaths(directoryPath, filename);
return { success: true, filePath };
} catch (error) {
return {
success: false,
error: this.handleException(error, 'FilePathGenerationError'),
};
}
}
private async writeFile(
filePath: string,
contents: string,
): Promise<FileWriteOutcome> {
const {
success, error,
} = await this.fileWriter.writeAndVerifyFile(filePath, contents);
if (success) {
return { success: true };
}
return {
success: false,
error: {
message: error.message,
type: FileReadbackVerificationErrors.find((e) => e === error.type) ? 'FileReadbackVerificationError' : 'FileWriteError',
},
};
}
private handleException(
exception: Error,
errorType: CodeRunErrorType,
): CodeRunError {
const errorMessage = 'Error during script file operation';
this.logger.error(errorType, errorMessage, exception);
return {
type: errorType,
message: `${errorMessage}: ${exception.message}`,
};
}
}
function createFailure(error: CodeRunError): ScriptFileCreationOutcome {
return {
success: false,
error,
};
}
type FileWriteOutcome = {
readonly success: true;
readonly error?: undefined;
} | {
readonly success: false;
readonly error: CodeRunError;
};
type FilePathConstructionOutcome = {
readonly success: true;
readonly filePath: string;
readonly error?: undefined;
} | {
readonly success: false;
readonly filePath?: undefined;
readonly error: CodeRunError;
};

View File

@@ -0,0 +1,31 @@
import { CodeRunError } from '@/application/CodeRunner/CodeRunner';
export interface ScriptFileCreator {
createScriptFile(
contents: string,
scriptFilenameParts: ScriptFilenameParts,
): Promise<ScriptFileCreationOutcome>;
}
export interface ScriptFilenameParts {
readonly scriptName: string;
readonly scriptFileExtension: string | undefined;
}
export type ScriptFileCreationOutcome = SuccessfulScriptCreation | FailedScriptCreation;
interface ScriptFileCreationStatus {
readonly success: boolean;
readonly error?: CodeRunError;
readonly scriptFileAbsolutePath?: string;
}
interface SuccessfulScriptCreation extends ScriptFileCreationStatus {
readonly success: true;
readonly scriptFileAbsolutePath: string;
}
interface FailedScriptCreation extends ScriptFileCreationStatus {
readonly success: false;
readonly error: CodeRunError;
}

View File

@@ -0,0 +1,22 @@
import { CodeRunError } from '@/application/CodeRunner/CodeRunner';
export interface ScriptFileExecutor {
executeScriptFile(filePath: string): Promise<ScriptFileExecutionOutcome>;
}
export type ScriptFileExecutionOutcome = SuccessfulScriptFileExecution | FailedScriptFileExecution;
interface ScriptFileExecutionStatus {
readonly success: boolean;
readonly error?: CodeRunError;
}
interface SuccessfulScriptFileExecution extends ScriptFileExecutionStatus {
readonly success: true;
readonly error?: undefined;
}
export interface FailedScriptFileExecution extends ScriptFileExecutionStatus {
readonly success: false;
readonly error: CodeRunError;
}

View File

@@ -0,0 +1,214 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
import { CommandOps, SystemOperations } from '@/infrastructure/CodeRunner/System/SystemOperations';
import { Logger } from '@/application/Common/Log/Logger';
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
import { NodeElectronSystemOperations } from '@/infrastructure/CodeRunner/System/NodeElectronSystemOperations';
import { CurrentEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironmentFactory';
import { CodeRunErrorType } from '@/application/CodeRunner/CodeRunner';
import { isString } from '@/TypeHelpers';
import { FailedScriptFileExecution, ScriptFileExecutionOutcome, ScriptFileExecutor } from './ScriptFileExecutor';
export class VisibleTerminalScriptExecutor implements ScriptFileExecutor {
constructor(
private readonly system: SystemOperations = new NodeElectronSystemOperations(),
private readonly logger: Logger = ElectronLogger,
private readonly environment: RuntimeEnvironment = CurrentEnvironment,
) { }
public async executeScriptFile(filePath: string): Promise<ScriptFileExecutionOutcome> {
const { os } = this.environment;
if (os === undefined) {
return this.handleError('UnsupportedOperatingSystem', 'Operating system could not be identified from environment.');
}
const filePermissionsResult = await this.setFileExecutablePermissions(filePath);
if (!filePermissionsResult.success) {
return filePermissionsResult;
}
const scriptExecutionResult = await this.runFileWithRunner(filePath, os);
if (!scriptExecutionResult.success) {
return scriptExecutionResult;
}
return {
success: true,
};
}
private async setFileExecutablePermissions(
filePath: string,
): Promise<ScriptFileExecutionOutcome> {
/*
This is required on macOS and Linux otherwise the terminal emulators will refuse to
execute the script. It's not needed on Windows.
*/
try {
this.logger.info(`Setting execution permissions for file at ${filePath}`);
await this.system.fileSystem.setFilePermissions(filePath, '755');
this.logger.info(`Execution permissions set successfully for ${filePath}`);
return { success: true };
} catch (error) {
return this.handleError('FileExecutionError', error);
}
}
private async runFileWithRunner(
filePath: string,
os: OperatingSystem,
): Promise<ScriptFileExecutionOutcome> {
this.logger.info(`Executing script file: ${filePath} on ${OperatingSystem[os]}.`);
const runner = TerminalRunners[os];
if (!runner) {
return this.handleError('UnsupportedOperatingSystem', `Unsupported operating system: ${OperatingSystem[os]}`);
}
const context: TerminalExecutionContext = {
scriptFilePath: filePath,
commandOps: this.system.command,
logger: this.logger,
};
try {
await runner(context);
this.logger.info('Command script file successfully.');
return { success: true };
} catch (error) {
return this.handleError('FileExecutionError', error);
}
}
private handleError(
type: CodeRunErrorType,
error: Error | string,
): FailedScriptFileExecution {
const errorMessage = 'Error during script file execution';
this.logger.error([type, errorMessage, ...(error ? [error] : [])]);
return {
success: false,
error: {
type,
message: `${errorMessage}: ${isString(error) ? error : errorMessage}`,
},
};
}
}
interface TerminalExecutionContext {
readonly scriptFilePath: string;
readonly commandOps: CommandOps;
readonly logger: Logger;
}
type TerminalRunner = (context: TerminalExecutionContext) => Promise<void>;
export const LinuxTerminalEmulator = 'x-terminal-emulator';
const TerminalRunners: Partial<Record<OperatingSystem, TerminalRunner>> = {
[OperatingSystem.Windows]: async (context) => {
const command = [
'PowerShell',
'Start-Process',
'-Verb RunAs', // Run as administrator with GUI sudo prompt
`-FilePath ${cmdShellPathArgumentEscape(context.scriptFilePath)}`,
].join(' ');
/*
📝 Options:
`child_process.execFile()`
"path", `cmd.exe /c "path"`
❌ Script execution in the background without a visible terminal.
This occurs only when the user runs the application as administrator, as seen
in Windows Pro VMs on Azure.
`PowerShell Start -Verb RunAs "path"`
✅ Visible terminal window
✅ GUI sudo prompt (through `RunAs` option)
`PowerShell Start "path"`
`explorer.exe "path"`
`electron.shell.openPath`
`start cmd.exe /c "$path"`
✅ Visible terminal window
✅ GUI sudo prompt (through `RunAs` option)
👍 Among all options `start` command is the most explicit one, being the most resilient
against the potential changes in Windows or Electron framework (e.g. https://github.com/electron/electron/issues/36765).
`%COMSPEC%` environment variable should be checked before defaulting to `cmd.exe.
Related docs: https://web.archive.org/web/20240106002357/https://nodejs.org/api/child_process.html#spawning-bat-and-cmd-files-on-windows
*/
await runCommand(command, context);
},
[OperatingSystem.Linux]: async (context) => {
const command = `${LinuxTerminalEmulator} -e ${posixShellPathArgumentEscape(context.scriptFilePath)}`;
/*
🤔 Potential improvements:
Use user-friendly GUI sudo prompt (not terminal-based).
If `pkexec` exists, we could do `x-terminal-emulator -e pkexec 'path'`, which always
prompts with user-friendly GUI sudo prompt.
📝 Options:
`x-terminal-emulator -e 'path'`:
✅ Visible terminal window
❌ Terminal-based (not GUI) sudo prompt.
`x-terminal-emulator -e pkexec 'path'
✅ Visible terminal window
✅ Always prompts with user-friendly GUI sudo prompt.
🤔 Not using `pkexec` as it is not in all Linux distributions. It should have smarter
logic to handle if it does not exist.
`electron.shell.openPath`:
❌ Opens the script in the default text editor, verified on
Debian/Ubuntu-based distributions.
`child_process.execFile()`:
❌ Script execution in the background without a visible terminal.
*/
await runCommand(command, context);
},
[OperatingSystem.macOS]: async (context) => {
const command = `open -a Terminal.app ${posixShellPathArgumentEscape(context.scriptFilePath)}`;
// -a Specifies the application to use for opening the file
/* eslint-disable vue/max-len */
/*
🤔 Potential improvements:
Use user-friendly GUI sudo prompt for running the script.
📝 Options:
`open -a Terminal.app 'path'`
✅ Visible terminal window
❌ Terminal-based (not GUI) sudo prompt.
❌ Terminal app requires many privileges to execute the script, this prompts user
to grant privileges to the Terminal app.
`osascript -e 'do shell script "'/tmp/test.sh'" with administrator privileges'`
✅ Script as root
✅ GUI sudo prompt.
❌ Script execution in the background without a visible terminal.
`osascript -e 'do shell script "open -a 'Terminal.app' '/tmp/test.sh'" with administrator privileges'`
❌ Script as user, not root
✅ GUI sudo prompt.
✅ Visible terminal window
`osascript -e 'do shell script "/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal '/tmp/test.sh'" with administrator privileges'`
✅ Script as root
✅ GUI sudo prompt.
✅ Visible terminal window
Useful resources about `do shell script .. with administrator privileges`:
- Change "osascript wants to make changes" prompt: https://web.archive.org/web/20240109191128/https://apple.stackexchange.com/questions/283353/how-to-rename-osascript-in-the-administrator-privileges-dialog
- More about `do shell script`: https://web.archive.org/web/20100906222226/http://developer.apple.com/mac/library/technotes/tn2002/tn2065.html
*/
/* eslint-enable vue/max-len */
await runCommand(command, context);
},
} as const;
async function runCommand(command: string, context: TerminalExecutionContext): Promise<void> {
context.logger.info(`Executing command:\n${command}`);
await context.commandOps.exec(command);
context.logger.info('Executed command successfully.');
}
function posixShellPathArgumentEscape(pathArgument: string): string {
/*
- Wraps the path in single quotes, which is a standard practice in POSIX shells
(like bash and zsh) found on macOS/Linux to ensure that characters like spaces, '*', and
'?' are treated as literals, not as special characters.
- Escapes any single quotes within the path itself. This allows paths containing single
quotes to be correctly interpreted in POSIX-compliant systems, such as Linux and macOS.
*/
return `'${pathArgument.replaceAll('\'', '\'\\\'\'')}'`;
}
function cmdShellPathArgumentEscape(pathArgument: string): string {
// - Encloses the path in double quotes, which is necessary for Windows command line (cmd.exe)
// to correctly handle paths containing spaces.
// - Paths in Windows cannot include double quotes `"` themselves, so these are not escaped.
return `"${pathArgument}"`;
}

View File

@@ -0,0 +1,57 @@
import { Logger } from '@/application/Common/Log/Logger';
import { ScriptFilename } from '@/application/CodeRunner/ScriptFilename';
import {
CodeRunError, CodeRunOutcome, CodeRunner, FailedCodeRun,
} from '@/application/CodeRunner/CodeRunner';
import { ElectronLogger } from '../Log/ElectronLogger';
import { ScriptFileExecutor } from './Execution/ScriptFileExecutor';
import { ScriptFileCreator } from './Creation/ScriptFileCreator';
import { ScriptFileCreationOrchestrator } from './Creation/ScriptFileCreationOrchestrator';
import { VisibleTerminalScriptExecutor } from './Execution/VisibleTerminalScriptFileExecutor';
export class ScriptFileCodeRunner implements CodeRunner {
constructor(
private readonly scriptFileExecutor
: ScriptFileExecutor = new VisibleTerminalScriptExecutor(),
private readonly scriptFileCreator: ScriptFileCreator = new ScriptFileCreationOrchestrator(),
private readonly logger: Logger = ElectronLogger,
) { }
public async runCode(
code: string,
fileExtension: string,
): Promise<CodeRunOutcome> {
this.logger.info('Initiating script running process.');
const {
success: isFileCreated, scriptFileAbsolutePath, error: fileCreationError,
} = await this.scriptFileCreator.createScriptFile(code, {
scriptName: ScriptFilename,
scriptFileExtension: fileExtension,
});
if (!isFileCreated) {
return createFailure(fileCreationError);
}
const {
success: isFileSuccessfullyExecuted,
error: fileExecutionError,
} = await this.scriptFileExecutor.executeScriptFile(
scriptFileAbsolutePath,
);
if (!isFileSuccessfullyExecuted) {
return createFailure(fileExecutionError);
}
this.logger.info(`Successfully ran script at ${scriptFileAbsolutePath}`);
return {
success: true,
};
}
}
function createFailure(
error: CodeRunError,
): FailedCodeRun {
return {
success: false,
error,
};
}

View File

@@ -0,0 +1,61 @@
import { join } from 'node:path';
import { chmod, mkdir } from 'node:fs/promises';
import { exec } from 'node:child_process';
import { app } from 'electron/main';
import {
CommandOps, FileSystemOps, LocationOps, OperatingSystemOps, SystemOperations,
} from './SystemOperations';
export class NodeElectronSystemOperations implements SystemOperations {
public readonly operatingSystem: OperatingSystemOps = {
/*
This method returns the directory for storing app's configuration files.
It appends your app's name to the default appData directory.
Conventionally, you should store user data files in this directory.
However, avoid writing large files here as some environments might back up this directory
to cloud storage, potentially causing issues with file size.
Based on tests it returns:
- Windows: `%APPDATA%\privacy.sexy`
- Linux: `$HOME/.config/privacy.sexy/runs`
- macOS: `$HOME/Library/Application Support/privacy.sexy/runs`
For more details, refer to the Electron documentation: https://web.archive.org/web/20240104154857/https://www.electronjs.org/docs/latest/api/app#appgetpathname
*/
getUserDataDirectory: () => {
return app.getPath('userData');
},
};
public readonly location: LocationOps = {
combinePaths: (...pathSegments) => join(...pathSegments),
};
public readonly fileSystem: FileSystemOps = {
setFilePermissions: (
filePath: string,
mode: string | number,
) => chmod(filePath, mode),
createDirectory: async (
directoryPath: string,
isRecursive?: boolean,
) => {
await mkdir(directoryPath, { recursive: isRecursive });
// Ignoring the return value from `mkdir`, which is the first directory created
// when `recursive` is true, or empty return value.
// See https://github.com/nodejs/node/pull/31530
},
};
public readonly command: CommandOps = {
exec: (command) => new Promise((resolve, reject) => {
exec(command, (error) => {
if (error) {
reject(error);
}
resolve();
});
}),
};
}

View File

@@ -0,0 +1,23 @@
export interface SystemOperations {
readonly operatingSystem: OperatingSystemOps;
readonly location: LocationOps;
readonly fileSystem: FileSystemOps;
readonly command: CommandOps;
}
export interface OperatingSystemOps {
getUserDataDirectory(): string;
}
export interface LocationOps {
combinePaths(...pathSegments: string[]): string;
}
export interface CommandOps {
exec(command: string): Promise<void>;
}
export interface FileSystemOps {
setFilePermissions(filePath: string, mode: string | number): Promise<void>;
createDirectory(directoryPath: string, isRecursive?: boolean): Promise<void>;
}

View File

@@ -0,0 +1,30 @@
import { Dialog, FileType, SaveFileOutcome } from '@/presentation/common/Dialog';
import { FileSaverDialog } from './FileSaverDialog';
import { BrowserSaveFileDialog } from './BrowserSaveFileDialog';
export class BrowserDialog implements Dialog {
constructor(
private readonly window: WindowDialogAccessor = globalThis.window,
private readonly saveFileDialog: BrowserSaveFileDialog = new FileSaverDialog(),
) {
}
public showError(title: string, message: string): void {
this.window.alert(`${title}\n\n${message}`);
}
public saveFile(
fileContents: string,
defaultFilename: string,
type: FileType,
): Promise<SaveFileOutcome> {
return Promise.resolve(
this.saveFileDialog.saveFile(fileContents, defaultFilename, type),
);
}
}
export interface WindowDialogAccessor {
readonly alert: typeof window.alert;
}

View File

@@ -0,0 +1,9 @@
import { FileType, SaveFileOutcome } from '@/presentation/common/Dialog';
export interface BrowserSaveFileDialog {
saveFile(
fileContents: string,
defaultFilename: string,
fileType: FileType,
): SaveFileOutcome;
}

View File

@@ -0,0 +1,42 @@
import fileSaver from 'file-saver';
import { FileType, SaveFileOutcome } from '@/presentation/common/Dialog';
import { BrowserSaveFileDialog } from './BrowserSaveFileDialog';
export type SaveAsFunction = (data: Blob, filename?: string) => void;
export type WindowOpenFunction = (url: string, target: string, features: string) => void;
export class FileSaverDialog implements BrowserSaveFileDialog {
constructor(
private readonly fileSaverSaveAs: SaveAsFunction = fileSaver.saveAs,
private readonly windowOpen: WindowOpenFunction = window.open.bind(window),
) { }
public saveFile(
fileContents: string,
defaultFilename: string,
fileType: FileType,
): SaveFileOutcome {
const mimeType = MimeTypes[fileType];
this.saveBlob(fileContents, mimeType, defaultFilename);
return {
success: true, // Exceptions are handled internally
};
}
private saveBlob(file: BlobPart, mimeType: string, defaultFilename: string): void {
try {
const blob = new Blob([file], { type: mimeType });
this.fileSaverSaveAs(blob, defaultFilename);
} catch (e) {
this.windowOpen(`data:${mimeType},${encodeURIComponent(file.toString())}`, '_blank', '');
}
}
}
const MimeTypes: Record<FileType, string> = {
// Some browsers (including firefox + IE) require right mime type
// otherwise they ignore extension and save the file as text.
[FileType.BatchFile]: 'application/bat', // https://en.wikipedia.org/wiki/Batch_file
[FileType.ShellScript]: 'text/x-shellscript', // https://de.wikipedia.org/wiki/Shellskript#MIME-Typ
} as const;

View File

@@ -0,0 +1,29 @@
import { dialog } from 'electron/main';
import { Dialog, FileType, SaveFileOutcome } from '@/presentation/common/Dialog';
import { NodeElectronSaveFileDialog } from './NodeElectronSaveFileDialog';
import { ElectronSaveFileDialog } from './ElectronSaveFileDialog';
export class ElectronDialog implements Dialog {
constructor(
private readonly saveFileDialog: ElectronSaveFileDialog = new NodeElectronSaveFileDialog(),
private readonly electron: ElectronDialogAccessor = {
showErrorBox: dialog.showErrorBox.bind(dialog),
},
) { }
public saveFile(
fileContents: string,
defaultFilename: string,
type: FileType,
): Promise<SaveFileOutcome> {
return this.saveFileDialog.saveFile(fileContents, defaultFilename, type);
}
public showError(title: string, message: string): void {
this.electron.showErrorBox(title, message);
}
}
export interface ElectronDialogAccessor {
readonly showErrorBox: typeof dialog.showErrorBox;
}

View File

@@ -0,0 +1,9 @@
import { FileType, SaveFileOutcome } from '@/presentation/common/Dialog';
export interface ElectronSaveFileDialog {
saveFile(
fileContents: string,
defaultFilename: string,
type: FileType,
): Promise<SaveFileOutcome>;
}

View File

@@ -0,0 +1,176 @@
import { join } from 'node:path';
import { app, dialog } from 'electron/main';
import { Logger } from '@/application/Common/Log/Logger';
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
import {
FileType, SaveFileError, SaveFileErrorType, SaveFileOutcome,
} from '@/presentation/common/Dialog';
import { FileReadbackVerificationErrors, ReadbackFileWriter } from '@/infrastructure/ReadbackFileWriter/ReadbackFileWriter';
import { NodeReadbackFileWriter } from '@/infrastructure/ReadbackFileWriter/NodeReadbackFileWriter';
import { ElectronSaveFileDialog } from './ElectronSaveFileDialog';
export class NodeElectronSaveFileDialog implements ElectronSaveFileDialog {
constructor(
private readonly logger: Logger = ElectronLogger,
private readonly electron: ElectronFileDialogOperations = {
getUserDownloadsPath: () => app.getPath('downloads'),
showSaveDialog: dialog.showSaveDialog.bind(dialog),
},
private readonly node: NodeFileOperations = { join },
private readonly fileWriter: ReadbackFileWriter = new NodeReadbackFileWriter(),
) { }
public async saveFile(
fileContents: string,
defaultFilename: string,
type: FileType,
): Promise<SaveFileOutcome> {
const {
success: isPathConstructed,
filePath: defaultFilePath,
error: pathConstructionError,
} = this.constructDefaultFilePath(defaultFilename);
if (!isPathConstructed) {
return { success: false, error: pathConstructionError };
}
const fileDialog = await this.showSaveFileDialog(defaultFilename, defaultFilePath, type);
if (!fileDialog.success) {
return {
success: false,
error: fileDialog.error,
};
}
if (fileDialog.canceled) {
this.logger.info(`File save cancelled by user: ${defaultFilename}`);
return {
success: true,
};
}
const result = await this.writeFile(fileDialog.filePath, fileContents);
return result;
}
private async writeFile(
filePath: string,
fileContents: string,
): Promise<SaveFileOutcome> {
const {
success, error,
} = await this.fileWriter.writeAndVerifyFile(filePath, fileContents);
if (success) {
return { success: true };
}
return {
success: false,
error: {
message: error.message,
type: FileReadbackVerificationErrors.find((e) => e === error.type) ? 'FileReadbackVerificationError' : 'FileCreationError',
},
};
}
private async showSaveFileDialog(
defaultFilename: string,
defaultFilePath: string,
type: FileType,
): Promise<SaveDialogOutcome> {
try {
const dialogResult = await this.electron.showSaveDialog({
title: defaultFilename,
defaultPath: defaultFilePath,
filters: getDialogFileFilters(type),
properties: [
'createDirectory', // Enables directory creation on macOS.
'showOverwriteConfirmation', // Shows overwrite confirmation on Linux.
],
});
if (dialogResult.canceled) {
return { success: true, canceled: true };
}
if (!dialogResult.filePath) {
return {
success: false,
error: { type: 'DialogDisplayError', message: 'Unexpected Error: File path is undefined after save dialog completion.' },
};
}
return { success: true, filePath: dialogResult.filePath };
} catch (error) {
return {
success: false,
error: this.handleException(error, 'DialogDisplayError'),
};
}
}
private constructDefaultFilePath(defaultFilename: string): DefaultFilePathConstructionOutcome {
try {
const downloadsFolder = this.electron.getUserDownloadsPath();
const defaultFilePath = this.node.join(downloadsFolder, defaultFilename);
return {
success: true,
filePath: defaultFilePath,
};
} catch (err) {
return {
success: false,
error: this.handleException(err, 'DialogDisplayError'),
};
}
}
private handleException(
exception: Error,
errorType: SaveFileErrorType,
): SaveFileError {
const errorMessage = 'Error during saving script file.';
this.logger.error(errorType, errorMessage, exception);
return {
type: errorType,
message: `${errorMessage}: ${exception.message}`,
};
}
}
export interface ElectronFileDialogOperations {
getUserDownloadsPath(): string;
showSaveDialog(options: Electron.SaveDialogOptions): Promise<Electron.SaveDialogReturnValue>;
}
export interface NodeFileOperations {
readonly join: typeof join;
}
function getDialogFileFilters(fileType: FileType): Electron.FileFilter[] {
const filters = FileTypeSpecificFilters[fileType];
return [
...filters,
{
name: 'All Files',
extensions: ['*'],
},
];
}
const FileTypeSpecificFilters: Record<FileType, Electron.FileFilter[]> = {
[FileType.BatchFile]: [
{
name: 'Batch Files',
extensions: ['bat', 'cmd'],
},
],
[FileType.ShellScript]: [
{
name: 'Shell Scripts',
extensions: ['sh', 'bash', 'zsh'],
},
],
};
type SaveDialogOutcome =
| { readonly success: true; readonly filePath: string; readonly canceled?: false }
| { readonly success: true; readonly canceled: true }
| { readonly success: false; readonly error: SaveFileError; readonly canceled?: false };
type DefaultFilePathConstructionOutcome =
| { readonly success: true; readonly filePath: string; readonly error?: undefined; }
| { readonly success: false; readonly filePath?: undefined; readonly error: SaveFileError; };

View File

@@ -0,0 +1,36 @@
import { Logger } from '@/application/Common/Log/Logger';
import { Dialog, FileType } from '@/presentation/common/Dialog';
export function decorateWithLogging(
dialog: Dialog,
logger: Logger,
): Dialog {
return new LoggingDialogDecorator(dialog, logger);
}
class LoggingDialogDecorator implements Dialog {
constructor(
private readonly dialog: Dialog,
private readonly logger: Logger,
) { }
public async saveFile(
fileContents: string,
defaultFilename: string,
fileType: FileType,
) {
this.logger.info(`Opening save file dialog with default filename: ${defaultFilename}.`);
const dialogResult = await this.dialog.saveFile(fileContents, defaultFilename, fileType);
if (dialogResult.success) {
this.logger.info('File saving process completed successfully.');
} else {
this.logger.error('Error encountered while saving the file.', dialogResult.error);
}
return dialogResult;
}
public showError(title: string, message: string) {
this.logger.error(`Showing error dialog: ${title} - ${message}`);
this.dialog.showError(title, message);
}
}

View File

@@ -1,8 +1,9 @@
import { isNumber } from '@/TypeHelpers';
import { IEntity } from './IEntity';
export abstract class BaseEntity<TId> implements IEntity<TId> {
protected constructor(public id: TId) {
if (typeof id !== 'number' && !id) {
if (!isNumber(id) && !id) {
throw new Error('Id cannot be null or empty');
}
}

View File

@@ -1,3 +1,4 @@
import { isBoolean, isFunction } from '@/TypeHelpers';
import { IEnvironmentVariables } from './IEnvironmentVariables';
/* Validation is externalized to keep the environment objects simple */
@@ -15,7 +16,7 @@ export function validateEnvironmentVariables(environment: IEnvironmentVariables)
function getKeysMissingValues(keyValuePairs: Record<string, unknown>): string[] {
return Object.entries(keyValuePairs)
.reduce((acc, [key, value]) => {
if (!value && typeof value !== 'boolean') {
if (!value && !isBoolean(value)) {
acc.push(key);
}
return acc;
@@ -38,7 +39,7 @@ function capturePropertyValues(instance: object): Record<string, unknown> {
// Capture getter properties from the instance's prototype
for (const [key, descriptor] of Object.entries(descriptors)) {
if (typeof descriptor.get === 'function') {
if (isFunction(descriptor.get)) {
obj[key] = descriptor.get.call(instance);
}
}

View File

@@ -1,17 +1,32 @@
import { ILogger } from './ILogger';
import { Logger } from '@/application/Common/Log/Logger';
export class ConsoleLogger implements ILogger {
constructor(private readonly consoleProxy: Partial<Console> = console) {
export class ConsoleLogger implements Logger {
constructor(private readonly consoleProxy: ConsoleLogFunctions = globalThis.console) {
if (!consoleProxy) { // do not trust strictNullChecks for global objects
throw new Error('missing console');
}
}
public info(...params: unknown[]): void {
const logFunction = this.consoleProxy?.info;
if (!logFunction) {
throw new Error('missing "info" function');
}
logFunction.call(this.consoleProxy, ...params);
this.consoleProxy.info(...params);
}
public warn(...params: unknown[]): void {
this.consoleProxy.warn(...params);
}
public error(...params: unknown[]): void {
this.consoleProxy.error(...params);
}
public debug(...params: unknown[]): void {
this.consoleProxy.debug(...params);
}
}
interface ConsoleLogFunctions extends Partial<Console> {
readonly info: Console['info'];
readonly warn: Console['warn'];
readonly error: Console['error'];
readonly debug: Console['debug'];
}

View File

@@ -1,17 +1,9 @@
import { ElectronLog } from 'electron-log';
import { ILogger } from './ILogger';
import log from 'electron-log/main';
import { Logger } from '@/application/Common/Log/Logger';
import type { LogFunctions } from 'electron-log';
// Using plain-function rather than class so it can be used in Electron's context-bridging.
export function createElectronLogger(logger: Partial<ElectronLog>): ILogger {
if (!logger) {
throw new Error('missing logger');
}
return {
info: (...params) => {
if (!logger.info) {
throw new Error('missing "info" function');
}
logger.info(...params);
},
};
export function createElectronLogger(logger: LogFunctions = log): Logger {
return logger;
}
export const ElectronLogger = createElectronLogger();

View File

@@ -1,3 +0,0 @@
export interface ILogger {
info (...params: unknown[]): void;
}

View File

@@ -1,5 +0,0 @@
import { ILogger } from './ILogger';
export interface ILoggerFactory {
readonly logger: ILogger;
}

View File

@@ -1,5 +1,11 @@
import { ILogger } from './ILogger';
import { Logger } from '@/application/Common/Log/Logger';
export class NoopLogger implements ILogger {
export class NoopLogger implements Logger {
public info(): void { /* NOOP */ }
public warn(): void { /* NOOP */ }
public error(): void { /* NOOP */ }
public debug(): void { /* NOOP */ }
}

View File

@@ -1,11 +1,11 @@
import { Logger } from '@/application/Common/Log/Logger';
import { WindowVariables } from '../WindowVariables/WindowVariables';
import { ILogger } from './ILogger';
export class WindowInjectedLogger implements ILogger {
private readonly logger: ILogger;
export class WindowInjectedLogger implements Logger {
private readonly logger: Logger;
constructor(windowVariables: WindowVariables | undefined | null = window) {
if (!windowVariables) { // do not trust strict null checks for global objects
if (!windowVariables) { // do not trust strictNullChecks for global objects
throw new Error('missing window');
}
if (!windowVariables.log) {
@@ -17,4 +17,16 @@ export class WindowInjectedLogger implements ILogger {
public info(...params: unknown[]): void {
this.logger.info(...params);
}
public warn(...params: unknown[]): void {
this.logger.warn(...params);
}
public debug(...params: unknown[]): void {
this.logger.debug(...params);
}
public error(...params: unknown[]): void {
this.logger.error(...params);
}
}

View File

@@ -0,0 +1,115 @@
import { writeFile, access, readFile } from 'node:fs/promises';
import { constants } from 'node:fs';
import { Logger } from '@/application/Common/Log/Logger';
import { ElectronLogger } from '../Log/ElectronLogger';
import {
FailedFileWrite, ReadbackFileWriter, FileWriteErrorType,
FileWriteOutcome, SuccessfulFileWrite,
} from './ReadbackFileWriter';
const FILE_ENCODING: NodeJS.BufferEncoding = 'utf-8';
export class NodeReadbackFileWriter implements ReadbackFileWriter {
constructor(
private readonly logger: Logger = ElectronLogger,
private readonly fileSystem: FileReadWriteOperations = {
writeFile,
readFile: (path, encoding) => readFile(path, encoding),
access,
},
) { }
public async writeAndVerifyFile(
filePath: string,
fileContents: string,
): Promise<FileWriteOutcome> {
this.logger.info(`Starting file write and verification process for: ${filePath}`);
const fileWritePipelineActions: ReadonlyArray<() => Promise<FileWriteOutcome>> = [
() => this.createOrOverwriteFile(filePath, fileContents),
() => this.verifyFileExistsWithoutReading(filePath),
() => this.verifyFileContentsByReading(filePath, fileContents),
];
for (const action of fileWritePipelineActions) {
const actionOutcome = await action(); // eslint-disable-line no-await-in-loop
if (!actionOutcome.success) {
return actionOutcome;
}
}
return this.reportSuccess(`File successfully written and verified: ${filePath}`);
}
private async createOrOverwriteFile(
filePath: string,
fileContents: string,
): Promise<FileWriteOutcome> {
try {
this.logger.info(`Creating file at ${filePath}, size: ${fileContents.length} characters`);
await this.fileSystem.writeFile(filePath, fileContents, FILE_ENCODING);
return this.reportSuccess('Created file.');
} catch (error) {
return this.reportFailure('WriteOperationFailed', error);
}
}
private async verifyFileExistsWithoutReading(
filePath: string,
): Promise<FileWriteOutcome> {
try {
await this.fileSystem.access(filePath, constants.F_OK);
return this.reportSuccess('Verified file existence without reading.');
} catch (error) {
return this.reportFailure('FileExistenceVerificationFailed', error);
}
}
private async verifyFileContentsByReading(
filePath: string,
expectedFileContents: string,
): Promise<FileWriteOutcome> {
try {
const actualFileContents = await this.fileSystem.readFile(filePath, FILE_ENCODING);
if (actualFileContents !== expectedFileContents) {
return this.reportFailure(
'ContentVerificationFailed',
[
'The contents of the written file do not match the expected contents.',
'Written file contents do not match the expected file contents',
`File path: ${filePath}`,
`Expected total characters: ${actualFileContents.length}`,
`Actual total characters: ${expectedFileContents.length}`,
].join('\n'),
);
}
return this.reportSuccess('Verified file content by reading.');
} catch (error) {
return this.reportFailure('ReadVerificationFailed', error);
}
}
private reportFailure(
errorType: FileWriteErrorType,
error: Error | string,
): FailedFileWrite {
this.logger.error('Error saving file', errorType, error);
return {
success: false,
error: {
type: errorType,
message: typeof error === 'string' ? error : error.message,
},
};
}
private reportSuccess(successAction: string): SuccessfulFileWrite {
this.logger.info(`Successful file save: ${successAction}`);
return {
success: true,
};
}
}
export interface FileReadWriteOperations {
readonly writeFile: typeof writeFile;
readonly access: typeof access;
readFile: (filePath: string, encoding: NodeJS.BufferEncoding) => Promise<string>;
}

View File

@@ -0,0 +1,59 @@
/**
* It defines the contract for file writing operations with an added layer of
* verification. This approach is useful in environments where file write operations
* might be silently intercepted or manipulated by external factors, such as antivirus software.
*
* This additional verification provides a more reliable and transparent file writing
* process, enhancing the application's resilience against external disruptions and
* improving the overall user experience. It enables the application to notify users
* of potential issues, such as antivirus interventions, and offer guidance on how to
* resolve them.
*/
export interface ReadbackFileWriter {
writeAndVerifyFile(filePath: string, fileContents: string): Promise<FileWriteOutcome>;
}
export type FileWriteOutcome = SuccessfulFileWrite | FailedFileWrite;
export type FileWriteErrorType =
| UnionOfConstArray<typeof FileWriteOperationErrors>
| UnionOfConstArray<typeof FileReadbackVerificationErrors>;
export const FileWriteOperationErrors = [
'WriteOperationFailed',
] as const;
export const FileReadbackVerificationErrors = [
'FileExistenceVerificationFailed',
'ContentVerificationFailed',
/*
This error indicates a failure in verifying the contents of a written file.
This error often occurs when antivirus software falsely identifies a script as harmful and
either alters or removes it during the readback process. This verification step is crucial
for detecting and handling such antivirus interventions.
*/
'ReadVerificationFailed',
] as const;
interface FileWriteStatus {
readonly success: boolean;
readonly error?: FileWriteError;
}
export interface SuccessfulFileWrite extends FileWriteStatus {
readonly success: true;
readonly error?: undefined;
}
export interface FailedFileWrite extends FileWriteStatus {
readonly success: false;
readonly error: FileWriteError;
}
export interface FileWriteError {
readonly type: FileWriteErrorType;
readonly message: string;
}
type UnionOfConstArray<T extends ReadonlyArray<unknown>> = T[number];

View File

@@ -0,0 +1,16 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
export enum TouchSupportExpectation {
MustExist,
MustNotExist,
}
export interface BrowserCondition {
readonly operatingSystem: OperatingSystem;
readonly existingPartsInSameUserAgent: readonly string[];
readonly notExistingPartsInUserAgent?: readonly string[];
readonly touchSupport?: TouchSupportExpectation;
}

View File

@@ -0,0 +1,106 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
import { BrowserCondition, TouchSupportExpectation } from './BrowserCondition';
// They include "Android", "iPhone" in their user agents.
const WindowsMobileIdentifiers: readonly string[] = [
'Windows Phone',
'Windows Mobile',
] as const;
export const BrowserConditions: readonly BrowserCondition[] = [
{
operatingSystem: OperatingSystem.KaiOS,
existingPartsInSameUserAgent: ['KAIOS'],
},
{
operatingSystem: OperatingSystem.ChromeOS,
existingPartsInSameUserAgent: ['CrOS'],
},
{
operatingSystem: OperatingSystem.BlackBerryOS,
existingPartsInSameUserAgent: ['BlackBerry'],
},
{
operatingSystem: OperatingSystem.BlackBerryTabletOS,
existingPartsInSameUserAgent: ['RIM Tablet OS'],
},
{
operatingSystem: OperatingSystem.BlackBerry10,
existingPartsInSameUserAgent: ['BB10'],
},
{
operatingSystem: OperatingSystem.Android,
existingPartsInSameUserAgent: ['Android'],
notExistingPartsInUserAgent: [...WindowsMobileIdentifiers],
},
{
operatingSystem: OperatingSystem.Android,
existingPartsInSameUserAgent: ['Adr'],
notExistingPartsInUserAgent: [...WindowsMobileIdentifiers],
},
{
operatingSystem: OperatingSystem.iOS,
existingPartsInSameUserAgent: ['iPhone'],
notExistingPartsInUserAgent: [...WindowsMobileIdentifiers],
},
{
operatingSystem: OperatingSystem.iOS,
existingPartsInSameUserAgent: ['iPod'],
},
{
operatingSystem: OperatingSystem.iPadOS,
existingPartsInSameUserAgent: ['iPad'],
// On Safari, only for older iPads running ≤ iOS 12 reports `iPad`
// Other browsers report `iPad` both for older devices (≤ iOS 12) and newer (≥ iPadOS 13)
// We detect all as `iPadOS` for simplicity.
},
{
operatingSystem: OperatingSystem.iPadOS,
existingPartsInSameUserAgent: ['Macintosh'], // Reported by Safari on iPads running ≥ iPadOS 13
notExistingPartsInUserAgent: ['Electron'], // Electron supports only macOS, not iPadOS
touchSupport: TouchSupportExpectation.MustExist, // Safari same user agent as desktop macOS
},
{
operatingSystem: OperatingSystem.Linux,
existingPartsInSameUserAgent: ['Linux'],
notExistingPartsInUserAgent: ['Android', 'Adr'],
},
{
operatingSystem: OperatingSystem.Windows,
existingPartsInSameUserAgent: ['Windows'],
notExistingPartsInUserAgent: [...WindowsMobileIdentifiers],
},
...['Windows Phone OS', 'Windows Phone 8'].map((userAgentPart) => ({
operatingSystem: OperatingSystem.WindowsPhone,
existingPartsInSameUserAgent: [userAgentPart],
})),
...['Windows Mobile', 'Windows Phone 10'].map((userAgentPart) => ({
operatingSystem: OperatingSystem.Windows10Mobile,
existingPartsInSameUserAgent: [userAgentPart],
})),
{
operatingSystem: OperatingSystem.macOS,
existingPartsInSameUserAgent: ['Macintosh'],
notExistingPartsInUserAgent: ['like Mac OS X'], // Eliminate iOS and iPadOS for Safari
touchSupport: TouchSupportExpectation.MustNotExist, // Distinguish from iPadOS for Safari
},
...generateJsdomBrowserConditions(),
] as const;
function generateJsdomBrowserConditions(): readonly BrowserCondition[] {
// jsdom user agent format: `Mozilla/5.0 (${process.platform || "unknown OS"}) ...` (https://archive.ph/2023.02.14-193200/https://github.com/jsdom/jsdom#advanced-configuration)
const operatingSystemPlatformMap: Partial<Record<
OperatingSystem,
NodeJS.Platform> // Enforce right platform constants at compile time
> = {
[OperatingSystem.Linux]: 'linux',
[OperatingSystem.Windows]: 'win32',
[OperatingSystem.macOS]: 'darwin',
} as const;
return Object
.entries(operatingSystemPlatformMap)
.map(([operatingSystemKey, platformString]): BrowserCondition => ({
operatingSystem: Number(operatingSystemKey),
existingPartsInSameUserAgent: ['jsdom', platformString],
}));
}

View File

@@ -0,0 +1,10 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
export interface BrowserEnvironment {
readonly isTouchSupported: boolean;
readonly userAgent: string;
}
export interface BrowserOsDetector {
detect(environment: BrowserEnvironment): OperatingSystem | undefined;
}

View File

@@ -0,0 +1,92 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
import { assertInRange } from '@/application/Common/Enum';
import { BrowserEnvironment, BrowserOsDetector } from './BrowserOsDetector';
import { BrowserCondition, TouchSupportExpectation } from './BrowserCondition';
import { BrowserConditions } from './BrowserConditions';
export class ConditionBasedOsDetector implements BrowserOsDetector {
constructor(private readonly conditions: readonly BrowserCondition[] = BrowserConditions) {
validateConditions(conditions);
}
public detect(environment: BrowserEnvironment): OperatingSystem | undefined {
if (!environment.userAgent) {
return undefined;
}
for (const condition of this.conditions) {
if (satisfiesCondition(condition, environment)) {
return condition.operatingSystem;
}
}
return undefined;
}
}
function satisfiesCondition(
condition: BrowserCondition,
browserEnvironment: BrowserEnvironment,
): boolean {
const { userAgent } = browserEnvironment;
if (condition.touchSupport !== undefined) {
if (!satisfiesTouchExpectation(condition.touchSupport, browserEnvironment)) {
return false;
}
}
if (condition.existingPartsInSameUserAgent.some((part) => !userAgent.includes(part))) {
return false;
}
if (condition.notExistingPartsInUserAgent?.some((part) => userAgent.includes(part))) {
return false;
}
return true;
}
function satisfiesTouchExpectation(
expectation: TouchSupportExpectation,
browserEnvironment: BrowserEnvironment,
): boolean {
switch (expectation) {
case TouchSupportExpectation.MustExist:
if (!browserEnvironment.isTouchSupported) {
return false;
}
break;
case TouchSupportExpectation.MustNotExist:
if (browserEnvironment.isTouchSupported) {
return false;
}
break;
default:
throw new Error(`Unsupported touch support expectation: ${TouchSupportExpectation[expectation]}`);
}
return true;
}
function validateConditions(conditions: readonly BrowserCondition[]) {
if (!conditions.length) {
throw new Error('empty conditions');
}
for (const condition of conditions) {
validateCondition(condition);
}
}
function validateCondition(condition: BrowserCondition) {
if (!condition.existingPartsInSameUserAgent.length) {
throw new Error('Each condition must include at least one identifiable part of the user agent string.');
}
const duplicates = getDuplicates([
...condition.existingPartsInSameUserAgent,
...(condition.notExistingPartsInUserAgent ?? []),
]);
if (duplicates.length > 0) {
throw new Error(`Found duplicate entries in user agent parts: ${duplicates.join(', ')}. Each part should be unique.`);
}
if (condition.touchSupport !== undefined) {
assertInRange(condition.touchSupport, TouchSupportExpectation);
}
}
function getDuplicates(texts: readonly string[]): string[] {
return texts.filter((text, index) => texts.indexOf(text) !== index);
}

View File

@@ -0,0 +1,50 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
import { IEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/IEnvironmentVariables';
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
import { RuntimeEnvironment } from '../RuntimeEnvironment';
import { ConditionBasedOsDetector } from './BrowserOs/ConditionBasedOsDetector';
import { BrowserEnvironment, BrowserOsDetector } from './BrowserOs/BrowserOsDetector';
import { isTouchEnabledDevice } from './TouchSupportDetection';
export class BrowserRuntimeEnvironment implements RuntimeEnvironment {
public readonly isRunningAsDesktopApplication: boolean;
public readonly os: OperatingSystem | undefined;
public readonly isNonProduction: boolean;
public constructor(
window: Partial<Window>,
environmentVariables: IEnvironmentVariables = EnvironmentVariablesFactory.Current.instance,
browserOsDetector: BrowserOsDetector = new ConditionBasedOsDetector(),
touchDetector: TouchDetector = isTouchEnabledDevice,
) {
if (!window) { throw new Error('missing window'); } // do not trust strictNullChecks for global objects
this.isNonProduction = environmentVariables.isNonProduction;
this.isRunningAsDesktopApplication = isElectronRendererProcess(window);
this.os = determineOperatingSystem(window, touchDetector, browserOsDetector);
}
}
function isElectronRendererProcess(globalWindow: Partial<Window>): boolean {
return globalWindow.isRunningAsDesktopApplication === true; // Preloader injects this
// We could also do `globalWindow?.navigator?.userAgent?.includes('Electron') === true;`
}
function determineOperatingSystem(
globalWindow: Partial<Window>,
touchDetector: TouchDetector,
browserOsDetector: BrowserOsDetector,
): OperatingSystem | undefined {
const userAgent = globalWindow?.navigator?.userAgent;
if (!userAgent) {
return undefined;
}
const browserEnvironment: BrowserEnvironment = {
userAgent,
isTouchSupported: touchDetector(),
};
return browserOsDetector.detect(browserEnvironment);
}
type TouchDetector = () => boolean;

View File

@@ -0,0 +1,57 @@
export function isTouchEnabledDevice(
browserTouchAccessor: BrowserTouchSupportAccessor = GlobalTouchSupportAccessor,
): boolean {
return TouchSupportChecks.some(
(check) => check(browserTouchAccessor),
);
}
export interface BrowserTouchSupportAccessor {
navigatorMaxTouchPoints: () => number | undefined;
windowMatchMediaMatches: (query: string) => boolean;
documentOntouchend: () => undefined | unknown;
}
/*
Touch support checks are inconsistent across different browsers and OS.
`✅` and `❌` indicate correct and incorrect detections, respectively.
*/
const TouchSupportChecks: ReadonlyArray<(accessor: BrowserTouchSupportAccessor) => boolean> = [
/*
Mobile (iOS & Android): ✅ Chrome, ✅ Safari, ✅ Firefox
Touch-enabled Windows laptop: ❌ Chrome (reports no touch), ❌ Firefox (reports no touch)
Chromium has removed ontouch* events on desktop since Chrome 70+
Non-touch macOS: ✅ Firefox, ✅ Safari, ✅ Chromium
*/
(accessor) => accessor.documentOntouchend() !== undefined,
/*
Mobile (iOS & Android): ✅ Chrome, ✅ Safari, ✅ Firefox
Touch-enabled Windows laptop: ✅ Chrome, ❌ Firefox (reports no touch)
Non-touch macOS: ✅ Firefox, ✅ Safari, ✅ Chromium
*/
(accessor) => {
const maxTouchPoints = accessor.navigatorMaxTouchPoints();
return maxTouchPoints !== undefined && maxTouchPoints > 0;
},
/*
Mobile (iOS & Android): ✅ Chrome, ✅ Safari, ✅ Firefox
Touch-enabled Windows laptop: ✅ Chrome, ❌ Firefox (reports no touch)
Non-touch macOS: ✅ Firefox, ✅ Safari, ✅ Chromium
*/
(accessor) => accessor.windowMatchMediaMatches('(any-pointer: coarse)'),
/*
Do not check window.TouchEvent === undefined, as it incorrectly
reports touch support on Chromium macOS even though there is no
touch support.
Mobile (iOS & Android): ✅ Chrome, ✅ Safari, ✅ Firefox
Touch-enabled Windows laptop: ✅ Chrome, ❌ Firefox (reports no touch)
Non-touch macOS: ✅ Firefox, ✅ Safari, ❌ Chromium (reports touch)
*/
];
const GlobalTouchSupportAccessor: BrowserTouchSupportAccessor = {
navigatorMaxTouchPoints: () => navigator.maxTouchPoints,
windowMatchMediaMatches: (query: string) => window.matchMedia(query)?.matches,
documentOntouchend: () => document.ontouchend,
} as const;

View File

@@ -1,57 +0,0 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
import { DetectorBuilder } from './DetectorBuilder';
import { IBrowserOsDetector } from './IBrowserOsDetector';
export class BrowserOsDetector implements IBrowserOsDetector {
private readonly detectors = BrowserDetectors;
public detect(userAgent: string): OperatingSystem | undefined {
if (!userAgent) {
return undefined;
}
for (const detector of this.detectors) {
const os = detector.detect(userAgent);
if (os !== undefined) {
return os;
}
}
return undefined;
}
}
// Reference: https://github.com/keithws/browser-report/blob/master/index.js#L304
const BrowserDetectors = [
define(OperatingSystem.KaiOS, (b) => b
.mustInclude('KAIOS')),
define(OperatingSystem.ChromeOS, (b) => b
.mustInclude('CrOS')),
define(OperatingSystem.BlackBerryOS, (b) => b
.mustInclude('BlackBerry')),
define(OperatingSystem.BlackBerryTabletOS, (b) => b
.mustInclude('RIM Tablet OS')),
define(OperatingSystem.BlackBerry, (b) => b
.mustInclude('BB10')),
define(OperatingSystem.Android, (b) => b
.mustInclude('Android').mustNotInclude('Windows Phone')),
define(OperatingSystem.Android, (b) => b
.mustInclude('Adr').mustNotInclude('Windows Phone')),
define(OperatingSystem.iOS, (b) => b
.mustInclude('like Mac OS X')),
define(OperatingSystem.Linux, (b) => b
.mustInclude('Linux').mustNotInclude('Android').mustNotInclude('Adr')),
define(OperatingSystem.Windows, (b) => b
.mustInclude('Windows').mustNotInclude('Windows Phone')),
define(OperatingSystem.WindowsPhone, (b) => b
.mustInclude('Windows Phone')),
define(OperatingSystem.macOS, (b) => b
.mustInclude('OS X').mustNotInclude('Android').mustNotInclude('like Mac OS X')),
];
function define(
os: OperatingSystem,
applyRules: (builder: DetectorBuilder) => DetectorBuilder,
): IBrowserOsDetector {
const builder = new DetectorBuilder(os);
applyRules(builder);
return builder.build();
}

View File

@@ -1,54 +0,0 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
import { IBrowserOsDetector } from './IBrowserOsDetector';
export class DetectorBuilder {
private readonly existingPartsInUserAgent = new Array<string>();
private readonly notExistingPartsInUserAgent = new Array<string>();
constructor(private readonly os: OperatingSystem) { }
public mustInclude(str: string): DetectorBuilder {
return this.add(str, this.existingPartsInUserAgent);
}
public mustNotInclude(str: string): DetectorBuilder {
return this.add(str, this.notExistingPartsInUserAgent);
}
public build(): IBrowserOsDetector {
if (!this.existingPartsInUserAgent.length) {
throw new Error('Must include at least a part');
}
return {
detect: (agent) => this.detect(agent),
};
}
private detect(userAgent: string): OperatingSystem | undefined {
if (!userAgent) {
return undefined;
}
if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) {
return undefined;
}
if (this.notExistingPartsInUserAgent.some((part) => userAgent.includes(part))) {
return undefined;
}
return this.os;
}
private add(part: string, array: string[]): DetectorBuilder {
if (!part) {
throw new Error('part is empty or undefined');
}
if (this.existingPartsInUserAgent.includes(part)) {
throw new Error(`part ${part} is already included as existing part`);
}
if (this.notExistingPartsInUserAgent.includes(part)) {
throw new Error(`part ${part} is already included as not existing part`);
}
array.push(part);
return this;
}
}

View File

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

View File

@@ -0,0 +1,54 @@
import { ElectronEnvironmentDetector, ElectronProcessType } from './ElectronEnvironmentDetector';
export class ContextIsolatedElectronDetector implements ElectronEnvironmentDetector {
constructor(
private readonly nodeProcessAccessor: NodeProcessAccessor = () => globalThis?.process,
private readonly userAgentAccessor: UserAgentAccessor = () => globalThis?.navigator?.userAgent,
) { }
public isRunningInsideElectron(): boolean {
return isNodeProcessElectronBased(this.nodeProcessAccessor)
|| isUserAgentElectronBased(this.userAgentAccessor);
}
public determineElectronProcessType(): ElectronProcessType {
const isNodeAccessible = isNodeProcessElectronBased(this.nodeProcessAccessor);
const isBrowserAccessible = isUserAgentElectronBased(this.userAgentAccessor);
if (!isNodeAccessible && !isBrowserAccessible) {
throw new Error('Unable to determine the Electron process type. Neither Node.js nor browser-based Electron contexts were detected.');
}
if (isNodeAccessible && isBrowserAccessible) {
return 'preloader'; // Only preloader can access both Node.js and browser contexts in Electron with context isolation.
}
if (isNodeAccessible) {
return 'main';
}
return 'renderer';
}
}
export type NodeProcessAccessor = () => NodeJS.Process | undefined;
function isNodeProcessElectronBased(nodeProcessAccessor: NodeProcessAccessor): boolean {
const nodeProcess = nodeProcessAccessor();
if (!nodeProcess) {
return false;
}
if (nodeProcess.versions.electron) {
// Electron populates `nodeProcess.versions.electron` with its version, see https://web.archive.org/web/20240113162837/https://www.electronjs.org/docs/latest/api/process#processversionselectron-readonly.
return true;
}
return false;
}
export type UserAgentAccessor = () => string | undefined;
function isUserAgentElectronBased(
userAgentAccessor: UserAgentAccessor,
): boolean {
const userAgent = userAgentAccessor();
if (userAgent?.includes('Electron')) {
return true;
}
return false;
}

View File

@@ -0,0 +1,6 @@
export interface ElectronEnvironmentDetector {
isRunningInsideElectron(): boolean;
determineElectronProcessType(): ElectronProcessType;
}
export type ElectronProcessType = 'main' | 'preloader' | 'renderer';

View File

@@ -1,7 +0,0 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
export interface IRuntimeEnvironment {
readonly isDesktop: boolean;
readonly os: OperatingSystem | undefined;
readonly isNonProduction: boolean;
}

View File

@@ -1,6 +1,8 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
export function convertPlatformToOs(platform: NodeJS.Platform): OperatingSystem | undefined {
export function convertPlatformToOs(
platform: NodeJS.Platform,
): OperatingSystem | undefined {
switch (platform) {
case 'darwin':
return OperatingSystem.macOS;

View File

@@ -0,0 +1,28 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
import { RuntimeEnvironment } from '../RuntimeEnvironment';
import { convertPlatformToOs } from './NodeOsMapper';
export class NodeRuntimeEnvironment implements RuntimeEnvironment {
public readonly isRunningAsDesktopApplication: boolean;
public readonly os: OperatingSystem | undefined;
public readonly isNonProduction: boolean;
constructor(
nodeProcess: NodeJSProcessAccessor = globalThis.process,
convertToOs: PlatformToOperatingSystemConverter = convertPlatformToOs,
) {
if (!nodeProcess) { throw new Error('missing process'); } // do not trust strictNullChecks for global objects
this.isRunningAsDesktopApplication = true;
this.os = convertToOs(nodeProcess.platform);
this.isNonProduction = nodeProcess.env.NODE_ENV !== 'production'; // populated by Vite
}
}
export interface NodeJSProcessAccessor {
readonly platform: NodeJS.Platform;
readonly env: NodeJS.ProcessEnv;
}
export type PlatformToOperatingSystemConverter = typeof convertPlatformToOs;

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