diff --git a/.github/actions/install-certs-and-profiles/action.yml b/.github/actions/install-certs-and-profiles/action.yml index a436002469..466c511d2a 100644 --- a/.github/actions/install-certs-and-profiles/action.yml +++ b/.github/actions/install-certs-and-profiles/action.yml @@ -34,15 +34,6 @@ inputs: NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64: required: true type: string - NETP_START_VPN_PROVISION_PROFILE_BASE64: - required: true - type: string - NETP_STOP_VPN_PROVISION_PROFILE_BASE64: - required: true - type: string - NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64: - required: true - type: string access-token: description: "Asana access token" required: true @@ -70,9 +61,6 @@ runs: NETP_AGENT_REVIEW_PP_PATH=$RUNNER_TEMP/netp_agent_review_pp.provisionprofile NETP_NOTIFICATIONS_RELEASE_PP_PATH=$RUNNER_TEMP/netp_notifications_release_pp.provisionprofile NETP_NOTIFICATIONS_REVIEW_PP_PATH=$RUNNER_TEMP/netp_notifications_review_pp.provisionprofile - NETP_START_VPN_PP_PATH=$RUNNER_TEMP/netp_start_vpn_pp.provisionprofile - NETP_STOP_VPN_PP_PATH=$RUNNER_TEMP/netp_stop_vpn_pp.provisionprofile - NETP_ENABLE_ON_DEMAND_PP_PATH=$RUNNER_TEMP/netp_enable_on_demand_pp.provisionprofile # import certificate from secrets echo -n "${{ inputs.BUILD_CERTIFICATE_BASE64 }}" | base64 --decode -o $CERTIFICATE_PATH @@ -84,9 +72,6 @@ runs: echo -n "${{ inputs.NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64 }}" | base64 --decode -o $NETP_AGENT_REVIEW_PP_PATH echo -n "${{ inputs.NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64 }}" | base64 --decode -o $NETP_NOTIFICATIONS_RELEASE_PP_PATH echo -n "${{ inputs.NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64 }}" | base64 --decode -o $NETP_NOTIFICATIONS_REVIEW_PP_PATH - echo -n "${{ inputs.NETP_START_VPN_PROVISION_PROFILE_BASE64 }}" | base64 --decode -o $NETP_START_VPN_PP_PATH - echo -n "${{ inputs.NETP_STOP_VPN_PROVISION_PROFILE_BASE64 }}" | base64 --decode -o $NETP_STOP_VPN_PP_PATH - echo -n "${{ inputs.NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64 }}" | base64 --decode -o $NETP_ENABLE_ON_DEMAND_PP_PATH # create temporary keychain security create-keychain -p "${{ inputs.KEYCHAIN_PASSWORD }}" $KEYCHAIN_PATH @@ -107,8 +92,5 @@ runs: $NETP_AGENT_REVIEW_PP_PATH \ $NETP_NOTIFICATIONS_RELEASE_PP_PATH \ $NETP_NOTIFICATIONS_REVIEW_PP_PATH \ - $NETP_START_VPN_PP_PATH \ - $NETP_STOP_VPN_PP_PATH \ - $NETP_ENABLE_ON_DEMAND_PP_PATH \ ~/Library/MobileDevice/Provisioning\ Profiles shell: bash diff --git a/.github/workflows/build_notarized.yml b/.github/workflows/build_notarized.yml index bfea0fd9dc..5ddbc2388d 100644 --- a/.github/workflows/build_notarized.yml +++ b/.github/workflows/build_notarized.yml @@ -49,24 +49,18 @@ on: required: true RELEASE_PROVISION_PROFILE_BASE64: required: true - NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64: + NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64_V2: required: true - NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64: + NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64_V2: required: true - NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: + NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64_V2: required: true - NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: + NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64_V2: required: true NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64: required: true NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64: required: true - NETP_START_VPN_PROVISION_PROFILE_BASE64: - required: true - NETP_STOP_VPN_PROVISION_PROFILE_BASE64: - required: true - NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64: - required: true SSH_PRIVATE_KEY_FIND_IN_PAGE: required: true APPLE_API_KEY_BASE64: @@ -121,15 +115,12 @@ jobs: KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.REVIEW_PROVISION_PROFILE_BASE64 }} RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.RELEASE_PROVISION_PROFILE_BASE64 }} - NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64 }} - NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64 }} - NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64 }} - NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64 }} + NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64_V2 }} NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64 }} NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64 }} - NETP_START_VPN_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_START_VPN_PROVISION_PROFILE_BASE64 }} - NETP_STOP_VPN_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_STOP_VPN_PROVISION_PROFILE_BASE64 }} - NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64 }} - name: Install xcbeautify if: runner.debug != '1' diff --git a/.github/workflows/create_variants.yml b/.github/workflows/create_variants.yml index 0b65c2d89a..758627690d 100644 --- a/.github/workflows/create_variants.yml +++ b/.github/workflows/create_variants.yml @@ -70,15 +70,12 @@ jobs: KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.REVIEW_PROVISION_PROFILE_BASE64 }} RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.RELEASE_PROVISION_PROFILE_BASE64 }} - NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64 }} - NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64 }} - NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64 }} - NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64 }} + NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64_V2 }} NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64 }} NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64 }} - NETP_START_VPN_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_START_VPN_PROVISION_PROFILE_BASE64 }} - NETP_STOP_VPN_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_STOP_VPN_PROVISION_PROFILE_BASE64 }} - NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64 }} - name: Set up variant working-directory: ${{ github.workspace }}/dmg diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index e710bebb36..75e1cc8cd1 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -236,15 +236,12 @@ jobs: KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.REVIEW_PROVISION_PROFILE_BASE64 }} RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.RELEASE_PROVISION_PROFILE_BASE64 }} - NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64 }} - NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64 }} - NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64 }} - NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64 }} + NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64_V2 }} NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64 }} NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64 }} - NETP_START_VPN_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_START_VPN_PROVISION_PROFILE_BASE64 }} - NETP_STOP_VPN_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_STOP_VPN_PROVISION_PROFILE_BASE64 }} - NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64 }} - name: Set cache key hash run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2033f2ce9f..41b5fffa3c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,15 +23,12 @@ jobs: KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.REVIEW_PROVISION_PROFILE_BASE64 }} RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.RELEASE_PROVISION_PROFILE_BASE64 }} - NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64 }} - NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64 }} - NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64 }} - NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64 }} + NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64_V2 }} + NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64_V2: ${{ secrets.NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64_V2 }} NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64 }} NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64 }} - NETP_START_VPN_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_START_VPN_PROVISION_PROFILE_BASE64 }} - NETP_STOP_VPN_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_STOP_VPN_PROVISION_PROFILE_BASE64 }} - NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64 }} SSH_PRIVATE_KEY_FIND_IN_PAGE: ${{ secrets.SSH_PRIVATE_KEY_FIND_IN_PAGE }} APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} diff --git a/.sparkle_tools_release_date b/.sparkle_tools_release_date new file mode 100644 index 0000000000..084fcd51ee --- /dev/null +++ b/.sparkle_tools_release_date @@ -0,0 +1 @@ +2023-10-16 diff --git a/Configuration/App/AppTargetsBase.xcconfig b/Configuration/App/AppTargetsBase.xcconfig index 4b9ed6a1cc..68e7f7a026 100644 --- a/Configuration/App/AppTargetsBase.xcconfig +++ b/Configuration/App/AppTargetsBase.xcconfig @@ -33,7 +33,7 @@ CURRENT_PROJECT_VERSION = $(MARKETING_VERSION) ENABLE_HARDENED_RUNTIME = YES INFOPLIST_FILE = DuckDuckGo/Info.plist -INFOPLIST_KEY_NSPrincipalClass = NSApplication +INFOPLIST_KEY_NSPrincipalClass = Application LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/../Frameworks diff --git a/Configuration/App/DuckDuckGo.xcconfig b/Configuration/App/DuckDuckGo.xcconfig index 2c85f56e17..08c9dcb67e 100644 --- a/Configuration/App/DuckDuckGo.xcconfig +++ b/Configuration/App/DuckDuckGo.xcconfig @@ -44,20 +44,6 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTE SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Debug][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION DEBUG $(FEATURE_FLAGS) SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Review][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION REVIEW $(FEATURE_FLAGS) -AGENT_BUNDLE_ID[sdk=*] = $(DEVELOPMENT_TEAM).com.duckduckgo.macos.browser.network-protection.system-extension.agent -AGENT_BUNDLE_ID[config=Debug][sdk=*] = $(DEVELOPMENT_TEAM).com.duckduckgo.macos.browser.network-protection.system-extension.agent.debug -AGENT_BUNDLE_ID[config=CI][sdk=*] = $(DEVELOPMENT_TEAM).com.duckduckgo.macos.browser.network-protection.system-extension.agent.debug -AGENT_BUNDLE_ID[config=Review][sdk=*] = $(DEVELOPMENT_TEAM).com.duckduckgo.macos.browser.network-protection.system-extension.agent.review - -// We could be tempted to use SYSEX_BUNDLE_ID = $(SYSEX_BUNDLE_ID_BASE).systemextension -// but Xcode is currently not expanding it well. You can check this in the project build -// settings. -SYSEX_BUNDLE_ID[sdk=*] = $(SYSEX_BUNDLE_ID_BASE).systemextension -SYSEX_BUNDLE_ID[config=CI][sdk=*] = $(SYSEX_BUNDLE_ID_BASE).systemextension -SYSEX_BUNDLE_ID[config=Review][sdk=*] = $(SYSEX_BUNDLE_ID_BASE).systemextension -SYSEX_BUNDLE_ID[config=Debug][sdk=*] = $(SYSEX_BUNDLE_ID_BASE).systemextension -SYSEX_BUNDLE_ID[config=Release][sdk=*] = $(SYSEX_BUNDLE_ID_BASE).systemextension - // Install Developer ID build to /Applications/DEBUG/ DEPLOYMENT_LOCATION[config=Debug] = YES; DSTROOT[config=Debug] = ${BUILT_PRODUCTS_DIR} diff --git a/Configuration/App/NetworkProtection/DuckDuckGoAgent.xcconfig b/Configuration/App/NetworkProtection/DuckDuckGoVPN.xcconfig similarity index 77% rename from Configuration/App/NetworkProtection/DuckDuckGoAgent.xcconfig rename to Configuration/App/NetworkProtection/DuckDuckGoVPN.xcconfig index c653903f5d..f464da1499 100644 --- a/Configuration/App/NetworkProtection/DuckDuckGoAgent.xcconfig +++ b/Configuration/App/NetworkProtection/DuckDuckGoVPN.xcconfig @@ -24,7 +24,7 @@ PRODUCT_BUNDLE_IDENTIFIER[config=Debug][sdk=*] = $(BUNDLE_IDENTIFIER_PREFIX) PRODUCT_BUNDLE_IDENTIFIER[config=CI][sdk=*] = $(BUNDLE_IDENTIFIER_PREFIX) PRODUCT_BUNDLE_IDENTIFIER[config=Review][sdk=*] = $(BUNDLE_IDENTIFIER_PREFIX) -INFOPLIST_FILE = DuckDuckGoAgent/Info.plist +INFOPLIST_FILE = DuckDuckGoVPN/Info.plist GENERATE_INFOPLIST_FILE = YES INFOPLIST_KEY_LSUIElement = YES INFOPLIST_KEY_NSPrincipalClass = Application @@ -33,10 +33,10 @@ INFOPLIST_KEY_NSPrincipalClass = Application //CODE_SIGN_STYLE[config=Debug][sdk=*] = Manual //CODE_SIGN_STYLE[config=Release][sdk=*] = Manual -CODE_SIGN_ENTITLEMENTS[config=Review][sdk=macosx*] = DuckDuckGoAgent/DuckDuckGoAgent.entitlements -CODE_SIGN_ENTITLEMENTS[config=CI][sdk=macosx*] = DuckDuckGoAgent/DuckDuckGoAgent.entitlements -CODE_SIGN_ENTITLEMENTS[config=Debug][sdk=macosx*] = DuckDuckGoAgent/DuckDuckGoAgent.entitlements -CODE_SIGN_ENTITLEMENTS[config=Release][sdk=macosx*] = DuckDuckGoAgent/DuckDuckGoAgent.entitlements +CODE_SIGN_ENTITLEMENTS[config=Review][sdk=macosx*] = DuckDuckGoVPN/DuckDuckGoVPN.entitlements +CODE_SIGN_ENTITLEMENTS[config=CI][sdk=macosx*] = DuckDuckGoVPN/DuckDuckGoVPN.entitlements +CODE_SIGN_ENTITLEMENTS[config=Debug][sdk=macosx*] = DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements +CODE_SIGN_ENTITLEMENTS[config=Release][sdk=macosx*] = DuckDuckGoVPN/DuckDuckGoVPN.entitlements CODE_SIGN_IDENTITY[sdk=macosx*] = Developer ID Application CODE_SIGN_IDENTITY[config=Debug][sdk=macosx*] = Apple Development @@ -45,13 +45,13 @@ CODE_SIGN_IDENTITY[config=CI][sdk=macosx*] = PRODUCT_NAME = $(AGENT_PRODUCT_NAME) PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*] = -PROVISIONING_PROFILE_SPECIFIER[config=Review][sdk=macosx*] = macOS Network Protection Agent App Product Review -PROVISIONING_PROFILE_SPECIFIER[config=Release][sdk=macosx*] = macOS Network Protection Agent App (Distribution) +PROVISIONING_PROFILE_SPECIFIER[config=Review][sdk=macosx*] = macOS NetP VPN App - Review (XPC) +PROVISIONING_PROFILE_SPECIFIER[config=Release][sdk=macosx*] = macOS NetP VPN App - Release (XPC) -FEATURE_FLAGS[arch=*][sdk=*] = NETP_SYSTEM_EXTENSION -FEATURE_FLAGS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION -FEATURE_FLAGS[config=Debug][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION -FEATURE_FLAGS[config=Review][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION +FEATURE_FLAGS[arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_PROTECTION +FEATURE_FLAGS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_PROTECTION +FEATURE_FLAGS[config=Debug][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_PROTECTION +FEATURE_FLAGS[config=Review][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_PROTECTION SWIFT_OBJC_BRIDGING_HEADER = SKIP_INSTALL = YES diff --git a/Configuration/App/NetworkProtection/DuckDuckGoAgentAppStore.xcconfig b/Configuration/App/NetworkProtection/DuckDuckGoVPNAppStore.xcconfig similarity index 92% rename from Configuration/App/NetworkProtection/DuckDuckGoAgentAppStore.xcconfig rename to Configuration/App/NetworkProtection/DuckDuckGoVPNAppStore.xcconfig index 93dab9befe..6bbaffcaf7 100644 --- a/Configuration/App/NetworkProtection/DuckDuckGoAgentAppStore.xcconfig +++ b/Configuration/App/NetworkProtection/DuckDuckGoVPNAppStore.xcconfig @@ -24,7 +24,7 @@ PRODUCT_BUNDLE_IDENTIFIER[config=Debug][sdk=*] = $(BUNDLE_IDENTIFIER_PREFIX).deb PRODUCT_BUNDLE_IDENTIFIER[config=CI][sdk=*] = $(BUNDLE_IDENTIFIER_PREFIX).debug PRODUCT_BUNDLE_IDENTIFIER[config=Review][sdk=*] = $(BUNDLE_IDENTIFIER_PREFIX).review -INFOPLIST_FILE = DuckDuckGoAgent/Info-AppStore.plist +INFOPLIST_FILE = DuckDuckGoVPN/Info-AppStore.plist GENERATE_INFOPLIST_FILE = YES INFOPLIST_KEY_LSUIElement = YES INFOPLIST_KEY_NSPrincipalClass = Application @@ -36,8 +36,8 @@ INFOPLIST_KEY_NSPrincipalClass = Application // Left empty intentionally as we currently only support debug and release builds CODE_SIGN_ENTITLEMENTS[config=Review][sdk=macosx*] = CODE_SIGN_ENTITLEMENTS[config=CI][sdk=macosx*] = -CODE_SIGN_ENTITLEMENTS[config=Debug][sdk=macosx*] = DuckDuckGoAgent/DuckDuckGoAgentAppStore.entitlements -CODE_SIGN_ENTITLEMENTS[config=Release][sdk=macosx*] = DuckDuckGoAgent/DuckDuckGoAgentAppStore.entitlements +CODE_SIGN_ENTITLEMENTS[config=Debug][sdk=macosx*] = DuckDuckGoVPN/DuckDuckGoVPNAppStore.entitlements +CODE_SIGN_ENTITLEMENTS[config=Release][sdk=macosx*] = DuckDuckGoVPN/DuckDuckGoVPNAppStore.entitlements CODE_SIGN_IDENTITY[sdk=macosx*] = 3rd Party Mac Developer Application CODE_SIGN_IDENTITY[config=Review][sdk=macosx*] = Developer ID Application diff --git a/Configuration/App/NetworkProtection/NetworkProtectionEnableOnDemand.xcconfig b/Configuration/App/NetworkProtection/NetworkProtectionEnableOnDemand.xcconfig deleted file mode 100644 index 5339b078d6..0000000000 --- a/Configuration/App/NetworkProtection/NetworkProtectionEnableOnDemand.xcconfig +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright © 2023 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include "NetworkProtectionVPNHelpersBase.xcconfig" - -PRODUCT_BUNDLE_IDENTIFIER=com.duckduckgo.macos.browser.network-protection.enable-on-demand - -PROVISIONING_PROFILE_SPECIFIER[config=Debug][sdk=macosx*] = -PROVISIONING_PROFILE_SPECIFIER[config=CI][sdk=macosx*] = -PROVISIONING_PROFILE_SPECIFIER[config=Release][sdk=macosx*] = MacOS Browser NetP - Enable On-Demand -PROVISIONING_PROFILE_SPECIFIER[config=Review][sdk=macosx*] = MacOS Browser NetP - Enable On-Demand diff --git a/Configuration/App/NetworkProtection/NetworkProtectionVPNHelpersBase.xcconfig b/Configuration/App/NetworkProtection/NetworkProtectionVPNHelpersBase.xcconfig deleted file mode 100644 index 01016dc681..0000000000 --- a/Configuration/App/NetworkProtection/NetworkProtectionVPNHelpersBase.xcconfig +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright © 2023 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include "../../Common.xcconfig" - -ALWAYS_SEARCH_USER_PATHS=NO -ASSETCATALOG_COMPILER_APPICON_NAME=AppIcon - -CODE_SIGN_ENTITLEMENTS=DuckDuckGo/NetworkProtectionVPNController.entitlements -CODE_SIGN_IDENTITY[sdk=macosx*] = Developer ID Application -CODE_SIGN_IDENTITY[config=CI][sdk=macosx*] = -CODE_SIGN_IDENTITY[config=Debug][sdk=macosx*] = Apple Development - -CODE_SIGN_STYLE[sdk=*] = Manual -CODE_SIGN_STYLE[config=Debug][sdk=*] = Automatic - -COMBINE_HIDPI_IMAGES=YES -COPY_PHASE_STRIP=NO - -DEBUG_INFORMATION_FORMAT[config=CI]=dwarf-with-dsym -DEBUG_INFORMATION_FORMAT[config=Debug]=dwarf -DEBUG_INFORMATION_FORMAT[config=Release]=dwarf-with-dsym -DEBUG_INFORMATION_FORMAT[config=Review]=dwarf-with-dsym - -ENABLE_HARDENED_RUNTIME=YES -ENABLE_NS_ASSERTIONS[config=CI]=NO -ENABLE_NS_ASSERTIONS[config=Release]=NO -ENABLE_NS_ASSERTIONS[config=Review]=NO -ENABLE_PREVIEWS=YES -ENABLE_STRICT_OBJC_MSGSEND=YES -ENABLE_TESTABILITY[config=Debug]=YES - -GCC_C_LANGUAGE_STANDARD=gnu11 -GCC_DYNAMIC_NO_PIC[config=Debug]=NO -GCC_NO_COMMON_BLOCKS=YES -GCC_OPTIMIZATION_LEVEL[config=Debug]=0 -GCC_PREPROCESSOR_DEFINITIONS[config=Debug]=DEBUG=1 $(inherited) -GCC_WARN_64_TO_32_BIT_CONVERSION=YES -GCC_WARN_ABOUT_RETURN_TYPE=YES_ERROR -GCC_WARN_UNDECLARED_SELECTOR=YES -GCC_WARN_UNINITIALIZED_AUTOS=YES_AGGRESSIVE -GCC_WARN_UNUSED_FUNCTION=YES -GCC_WARN_UNUSED_VARIABLE=YES -GENERATE_INFOPLIST_FILE=YES - -INFOPLIST_KEY_LSUIElement=YES -INFOPLIST_KEY_NSHumanReadableCopyright=Copyright © 2023 DuckDuckGo. All rights reserved. - -LD_RUNPATH_SEARCH_PATHS[config=CI]=$(inherited) @executable_path/../Frameworks -LD_RUNPATH_SEARCH_PATHS[config=Debug]=$(inherited) @executable_path/../Frameworks -LD_RUNPATH_SEARCH_PATHS[config=Release]=$(inherited) @executable_path/../Frameworks -LD_RUNPATH_SEARCH_PATHS[config=Review]=$(inherited) @executable_path/../Frameworks - -MTL_ENABLE_DEBUG_INFO[config=CI]=NO -MTL_ENABLE_DEBUG_INFO[config=Debug]=INCLUDE_SOURCE -MTL_ENABLE_DEBUG_INFO[config=Release]=NO -MTL_ENABLE_DEBUG_INFO[config=Review]=NO -MTL_FAST_MATH=YES - -ONLY_ACTIVE_ARCH[config=Debug]=YES -PRODUCT_NAME=$(TARGET_NAME) -SDKROOT=macosx - -SKIP_INSTALL=YES - -SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Debug]=DEBUG -SWIFT_COMPILATION_MODE[config=CI]=wholemodule -SWIFT_COMPILATION_MODE[config=Release]=wholemodule -SWIFT_COMPILATION_MODE[config=Review]=wholemodule -SWIFT_EMIT_LOC_STRINGS=YES diff --git a/Configuration/AppStore.xcconfig b/Configuration/AppStore.xcconfig index cf8d7df84a..804cd0af5f 100644 --- a/Configuration/AppStore.xcconfig +++ b/Configuration/AppStore.xcconfig @@ -46,4 +46,4 @@ AGENT_BUNDLE_ID[config=Debug][sdk=*] = HKE973VLUW.com.duckduckgo.macos.browser.n AGENT_BUNDLE_ID[config=CI][sdk=*] = HKE973VLUW.com.duckduckgo.macos.browser.network-protection.agent.debug AGENT_BUNDLE_ID[config=Review][sdk=*] = HKE973VLUW.com.duckduckgo.macos.browser.network-protection.agent.review -AGENT_PRODUCT_NAME = DuckDuckGo Agent App Store +AGENT_PRODUCT_NAME = DuckDuckGo VPN App Store diff --git a/Configuration/AppStoreBuildNumber.xcconfig b/Configuration/AppStoreBuildNumber.xcconfig index b17354dc70..9300bf9e4e 100644 --- a/Configuration/AppStoreBuildNumber.xcconfig +++ b/Configuration/AppStoreBuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 67 +CURRENT_PROJECT_VERSION = 72 diff --git a/Configuration/Extensions/NetworkProtection/NetworkProtectionSystemExtension.xcconfig b/Configuration/Extensions/NetworkProtection/NetworkProtectionSystemExtension.xcconfig index daa556afee..85a6dccf05 100644 --- a/Configuration/Extensions/NetworkProtection/NetworkProtectionSystemExtension.xcconfig +++ b/Configuration/Extensions/NetworkProtection/NetworkProtectionSystemExtension.xcconfig @@ -36,21 +36,21 @@ FEATURE_FLAGS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_EXTENSIO FEATURE_FLAGS[config=Debug][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_EXTENSION NETWORK_PROTECTION FEATURE_FLAGS[config=Review][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_EXTENSION NETWORK_PROTECTION -PRODUCT_BUNDLE_IDENTIFIER[sdk=*] = $(SYSEX_BUNDLE_ID_BASE) -PRODUCT_BUNDLE_IDENTIFIER[config=CI][sdk=*] = $(SYSEX_BUNDLE_ID_BASE) -PRODUCT_BUNDLE_IDENTIFIER[config=Debug][sdk=*] = $(SYSEX_BUNDLE_ID_BASE) -PRODUCT_BUNDLE_IDENTIFIER[config=Release][sdk=*] = $(SYSEX_BUNDLE_ID_BASE) -PRODUCT_BUNDLE_IDENTIFIER[config=Review][sdk=*] = $(SYSEX_BUNDLE_ID_BASE) - -PRODUCT_NAME[sdk=*] = $(SYSEX_BUNDLE_ID_BASE) -PRODUCT_NAME[config=CI][sdk=*] = $(SYSEX_BUNDLE_ID_BASE) -PRODUCT_NAME[config=Debug][sdk=*] = $(SYSEX_BUNDLE_ID_BASE) -PRODUCT_NAME[config=Release][sdk=*] = $(SYSEX_BUNDLE_ID_BASE) -PRODUCT_NAME[config=Review][sdk=*] = $(SYSEX_BUNDLE_ID_BASE) +PRODUCT_BUNDLE_IDENTIFIER[sdk=*] = $(SYSEX_BUNDLE_ID) +PRODUCT_BUNDLE_IDENTIFIER[config=CI][sdk=*] = $(SYSEX_BUNDLE_ID) +PRODUCT_BUNDLE_IDENTIFIER[config=Debug][sdk=*] = $(SYSEX_BUNDLE_ID) +PRODUCT_BUNDLE_IDENTIFIER[config=Release][sdk=*] = $(SYSEX_BUNDLE_ID) +PRODUCT_BUNDLE_IDENTIFIER[config=Review][sdk=*] = $(SYSEX_BUNDLE_ID) + +PRODUCT_NAME[sdk=*] = $(SYSEX_BUNDLE_ID) +PRODUCT_NAME[config=CI][sdk=*] = $(SYSEX_BUNDLE_ID) +PRODUCT_NAME[config=Debug][sdk=*] = $(SYSEX_BUNDLE_ID) +PRODUCT_NAME[config=Release][sdk=*] = $(SYSEX_BUNDLE_ID) +PRODUCT_NAME[config=Review][sdk=*] = $(SYSEX_BUNDLE_ID) PROVISIONING_PROFILE_SPECIFIER[config=CI][sdk=macosx*] = -PROVISIONING_PROFILE_SPECIFIER[config=Release][sdk=macosx*] = macOS NetP System Extension - Release -PROVISIONING_PROFILE_SPECIFIER[config=Review][sdk=macosx*] = macOS NetP System Extension - Review +PROVISIONING_PROFILE_SPECIFIER[config=Release][sdk=macosx*] = macOS NetP VPN SysEx - Release (XPC) +PROVISIONING_PROFILE_SPECIFIER[config=Review][sdk=macosx*] = macOS NetP VPN SysEx - Review (XPC) SDKROOT = macosx SKIP_INSTALL = YES diff --git a/Configuration/NetworkProtectionDeveloperID.xcconfig b/Configuration/NetworkProtectionDeveloperID.xcconfig index a7bf7d5007..f236ca2992 100644 --- a/Configuration/NetworkProtectionDeveloperID.xcconfig +++ b/Configuration/NetworkProtectionDeveloperID.xcconfig @@ -20,13 +20,19 @@ MAIN_BUNDLE_IDENTIFIER[config=Debug][sdk=*] = $(MAIN_BUNDLE_IDENTIFIER_PREFIX).d MAIN_BUNDLE_IDENTIFIER[config=CI][sdk=*] = $(MAIN_BUNDLE_IDENTIFIER_PREFIX).debug MAIN_BUNDLE_IDENTIFIER[config=Review][sdk=*] = $(MAIN_BUNDLE_IDENTIFIER_PREFIX).review -SYSEX_BUNDLE_ID_BASE[sdk=*] = com.duckduckgo.macos.browser.network-protection-extension -SYSEX_BUNDLE_ID_BASE[config=CI][sdk=*] = com.duckduckgo.macos.browser.debug.network-protection-extension -SYSEX_BUNDLE_ID_BASE[config=Review][sdk=*] = com.duckduckgo.macos.browser.review.network-protection-extension -SYSEX_BUNDLE_ID_BASE[config=Debug][sdk=*] = com.duckduckgo.macos.browser.debug.network-protection-extension -SYSEX_BUNDLE_ID_BASE[config=Release][sdk=*] = com.duckduckgo.macos.browser.network-protection-extension +SYSEX_BUNDLE_ID_BASE[sdk=*] = $(AGENT_BUNDLE_ID_BASE).network-extension +SYSEX_BUNDLE_ID_BASE[config=Debug][sdk=*] = $(AGENT_BUNDLE_ID_BASE).network-extension +SYSEX_BUNDLE_ID_BASE[config=CI][sdk=*] = $(AGENT_BUNDLE_ID_BASE).network-extension +SYSEX_BUNDLE_ID_BASE[config=Review][sdk=*] = $(AGENT_BUNDLE_ID_BASE).network-extension +SYSEX_BUNDLE_ID_BASE[config=Release][sdk=*] = $(AGENT_BUNDLE_ID_BASE).network-extension -DISTRIBUTED_NOTIFICATIONS_PREFIX_BASE = com.duckduckgo.macos.browser.network-protection.distributed-notification +SYSEX_BUNDLE_ID[sdk=*] = $(SYSEX_BUNDLE_ID_BASE) +SYSEX_BUNDLE_ID[config=Debug][sdk=*] = $(SYSEX_BUNDLE_ID_BASE).debug +SYSEX_BUNDLE_ID[config=CI][sdk=*] = $(SYSEX_BUNDLE_ID_BASE).debug +SYSEX_BUNDLE_ID[config=Review][sdk=*] = $(SYSEX_BUNDLE_ID_BASE).review +SYSEX_BUNDLE_ID[config=Release][sdk=*] = $(SYSEX_BUNDLE_ID_BASE) + +DISTRIBUTED_NOTIFICATIONS_PREFIX_BASE = $(SYSEX_BUNDLE_ID_BASE) DISTRIBUTED_NOTIFICATIONS_PREFIX[config=CI][sdk=*] = $(DISTRIBUTED_NOTIFICATIONS_PREFIX_BASE).ci DISTRIBUTED_NOTIFICATIONS_PREFIX[config=Review][sdk=*] = $(DISTRIBUTED_NOTIFICATIONS_PREFIX_BASE).review DISTRIBUTED_NOTIFICATIONS_PREFIX[config=Debug][sdk=*] = $(DISTRIBUTED_NOTIFICATIONS_PREFIX_BASE).debug @@ -49,10 +55,12 @@ NOTIFICATIONS_AGENT_BUNDLE_ID[config=Debug][sdk=*] = $(DEVELOPMENT_TEAM).com.duc NOTIFICATIONS_AGENT_BUNDLE_ID[config=CI][sdk=*] = $(DEVELOPMENT_TEAM).com.duckduckgo.macos.browser.network-protection.notifications.debug NOTIFICATIONS_AGENT_BUNDLE_ID[config=Review][sdk=*] = $(DEVELOPMENT_TEAM).com.duckduckgo.macos.browser.network-protection.notifications.review -AGENT_BUNDLE_ID[sdk=*] = $(DEVELOPMENT_TEAM).com.duckduckgo.macos.browser.network-protection.system-extension.agent -AGENT_BUNDLE_ID[config=Debug][sdk=*] = $(DEVELOPMENT_TEAM).com.duckduckgo.macos.browser.network-protection.system-extension.agent.debug -AGENT_BUNDLE_ID[config=CI][sdk=*] = $(DEVELOPMENT_TEAM).com.duckduckgo.macos.browser.network-protection.system-extension.agent.debug -AGENT_BUNDLE_ID[config=Review][sdk=*] = $(DEVELOPMENT_TEAM).com.duckduckgo.macos.browser.network-protection.system-extension.agent.review +AGENT_BUNDLE_ID_BASE[sdk=*] = com.duckduckgo.macos.vpn + +AGENT_BUNDLE_ID[sdk=*] = $(AGENT_BUNDLE_ID_BASE) +AGENT_BUNDLE_ID[config=Debug][sdk=*] = $(AGENT_BUNDLE_ID_BASE).debug +AGENT_BUNDLE_ID[config=CI][sdk=*] = $(AGENT_BUNDLE_ID_BASE).debug +AGENT_BUNDLE_ID[config=Review][sdk=*] = $(AGENT_BUNDLE_ID_BASE).review -AGENT_PRODUCT_NAME = DuckDuckGo Agent +AGENT_PRODUCT_NAME = DuckDuckGo VPN NOTIFICATIONS_AGENT_PRODUCT_NAME = DuckDuckGo Notifications diff --git a/Configuration/App/NetworkProtection/NetworkProtectionStartVPN.xcconfig b/Configuration/Tests/DBPUnitTests.xcconfig similarity index 53% rename from Configuration/App/NetworkProtection/NetworkProtectionStartVPN.xcconfig rename to Configuration/Tests/DBPUnitTests.xcconfig index 4df2675e3f..1852b3c454 100644 --- a/Configuration/App/NetworkProtection/NetworkProtectionStartVPN.xcconfig +++ b/Configuration/Tests/DBPUnitTests.xcconfig @@ -1,4 +1,4 @@ -// Copyright © 2023 DuckDuckGo. All rights reserved. +// Copyright © 2022 DuckDuckGo. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,11 +13,13 @@ // limitations under the License. // -#include "NetworkProtectionVPNHelpersBase.xcconfig" +#include "TestsTargetsBase.xcconfig" -PRODUCT_BUNDLE_IDENTIFIER=com.duckduckgo.macos.browser.network-protection.start-vpn +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES -PROVISIONING_PROFILE_SPECIFIER[config=Debug][sdk=macosx*] = -PROVISIONING_PROFILE_SPECIFIER[config=CI][sdk=macosx*] = -PROVISIONING_PROFILE_SPECIFIER[config=Release][sdk=macosx*] = MacOS Browser NetP - Start VPN -PROVISIONING_PROFILE_SPECIFIER[config=Review][sdk=macosx*] = MacOS Browser NetP - Start VPN +FEATURE_FLAGS = FEEDBACK NETWORK_PROTECTION + +INFOPLIST_FILE = DuckDuckGoDBPTests/Info.plist +PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.macos.browser.DuckDuckGoDBPTests + +TEST_HOST=$(BUILT_PRODUCTS_DIR)/DuckDuckGoDBP.app/Contents/MacOS/DuckDuckGoDBP diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 473bdc7f84..514fafb819 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 1.60.0 +MARKETING_VERSION = 1.61.2 diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 05a885cb15..ee05998dc0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -183,7 +183,6 @@ 31929FD82A4C4CFF0084EA89 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D33F1125C82EB3002B91A6 /* ConfigurationManager.swift */; }; 31929FDA2A4C4CFF0084EA89 /* YoutubePlayerUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F28C4C28C8EEC500119F70 /* YoutubePlayerUserScript.swift */; }; 31929FDB2A4C4CFF0084EA89 /* PixelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */; }; - 31929FDC2A4C4CFF0084EA89 /* ErrorWithParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A94B329C16294000C7D4C /* ErrorWithParameters.swift */; }; 31929FDD2A4C4CFF0084EA89 /* FaviconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5FA696275F90C400DCE9C9 /* FaviconImageCache.swift */; }; 31929FDE2A4C4CFF0084EA89 /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1430DFF424D0580F00B8978C /* TabBarViewController.swift */; }; 31929FDF2A4C4CFF0084EA89 /* BookmarkOutlineViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929126670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift */; }; @@ -253,11 +252,9 @@ 3192A0232A4C4CFF0084EA89 /* URLEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D700625545EF800C3411E /* URLEventHandler.swift */; }; 3192A0242A4C4CFF0084EA89 /* WKWebViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA92127625ADA07900600CD4 /* WKWebViewExtension.swift */; }; 3192A0262A4C4CFF0084EA89 /* CleanThisHistoryMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAAB9113288EB1D600A057A9 /* CleanThisHistoryMenuItem.swift */; }; - 3192A0272A4C4CFF0084EA89 /* TimedPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E498261474120067D1B9 /* TimedPixel.swift */; }; 3192A0282A4C4CFF0084EA89 /* DownloadListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C0B23526E732000031CB7F /* DownloadListItem.swift */; }; 3192A0292A4C4CFF0084EA89 /* DownloadsPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87D26D5DA0E0062C350 /* DownloadsPopover.swift */; }; 3192A02A2A4C4CFF0084EA89 /* SpacerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929626670D2A00AD2C21 /* SpacerNode.swift */; }; - 3192A02C2A4C4CFF0084EA89 /* SystemExtensionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60622A0B29FA00BCD287 /* SystemExtensionManager.swift */; }; 3192A02D2A4C4CFF0084EA89 /* SyncManagementDialogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3775913529AB9A1C00E26367 /* SyncManagementDialogViewController.swift */; }; 3192A02E2A4C4CFF0084EA89 /* BookmarkExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C0BB6629AEFF8100AE8E3C /* BookmarkExtension.swift */; }; 3192A02F2A4C4CFF0084EA89 /* PasswordManagementCreditCardModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE6547B271FCD4D008D1D63 /* PasswordManagementCreditCardModel.swift */; }; @@ -423,7 +420,6 @@ 3192A0D52A4C4CFF0084EA89 /* PasteboardFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929226670D2A00AD2C21 /* PasteboardFolder.swift */; }; 3192A0D72A4C4CFF0084EA89 /* CookieManagedNotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3171D6B72889849F0068632A /* CookieManagedNotificationView.swift */; }; 3192A0D82A4C4CFF0084EA89 /* PermissionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106BAA26A7BF1D0013B453 /* PermissionType.swift */; }; - 3192A0D92A4C4CFF0084EA89 /* NetworkProtectionTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8F52402A18326600BE7131 /* NetworkProtectionTunnelController.swift */; }; 3192A0DA2A4C4CFF0084EA89 /* RecentlyClosedWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC6881A28626C1900D54247 /* RecentlyClosedWindow.swift */; }; 3192A0DB2A4C4CFF0084EA89 /* ActionSpeech.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85707F29276A35FE00DC0649 /* ActionSpeech.swift */; }; 3192A0DC2A4C4CFF0084EA89 /* PrivacySecurityPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0511A6262CAA5A00F6079C /* PrivacySecurityPreferences.swift */; }; @@ -773,7 +769,6 @@ 3192A23A2A4C4CFF0084EA89 /* PrivacyDashboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6FA893C269C423100588ECD /* PrivacyDashboard.storyboard */; }; 3192A23B2A4C4CFF0084EA89 /* shield.json in Resources */ = {isa = PBXBuildFile; fileRef = AA34396A2754D4E200B241FA /* shield.json */; }; 3192A23C2A4C4CFF0084EA89 /* TabBarViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA7412B124D0B3AC00D22FE0 /* TabBarViewItem.xib */; }; - 3192A23D2A4C4CFF0084EA89 /* MainMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85480F8925CDC360009424E3 /* MainMenu.storyboard */; }; 3192A23E2A4C4CFF0084EA89 /* httpsMobileV2FalsePositives.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B67742A255DBEB800025BD8 /* httpsMobileV2FalsePositives.json */; }; 3192A23F2A4C4CFF0084EA89 /* BookmarksBar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4BD18F04283F151F00058124 /* BookmarksBar.storyboard */; }; 3192A2402A4C4CFF0084EA89 /* trackers-1.json in Resources */ = {isa = PBXBuildFile; fileRef = AA3439732754D55100B241FA /* trackers-1.json */; }; @@ -808,11 +803,8 @@ 3192A25D2A4C4CFF0084EA89 /* trackers-2.json in Resources */ = {isa = PBXBuildFile; fileRef = AA3439742754D55100B241FA /* trackers-2.json */; }; 3192A25E2A4C4CFF0084EA89 /* ProximaNova-Reg-webfont.woff2 in Resources */ = {isa = PBXBuildFile; fileRef = EAA29AE8278D2E43007070CF /* ProximaNova-Reg-webfont.woff2 */; }; 3192A25F2A4C4CFF0084EA89 /* clickToLoad.js in Resources */ = {isa = PBXBuildFile; fileRef = EAFAD6C92728BD1200F9DF00 /* clickToLoad.js */; }; - 3192A2612A4C4CFF0084EA89 /* DuckDuckGo Agent.app in Embed Login Items */ = {isa = PBXBuildFile; fileRef = 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo Agent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 3192A2612A4C4CFF0084EA89 /* DuckDuckGo VPN.app in Embed Login Items */ = {isa = PBXBuildFile; fileRef = 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo VPN.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 3192A2622A4C4CFF0084EA89 /* DuckDuckGo Notifications.app in Embed Login Items */ = {isa = PBXBuildFile; fileRef = 4B4BEC202A11B4E2001D9AC5 /* DuckDuckGo Notifications.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 3192A2642A4C4CFF0084EA89 /* enableOnDemand.app in Embed NetP Controller Apps */ = {isa = PBXBuildFile; fileRef = 7B736E5F2A4A22B700F9922A /* enableOnDemand.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 3192A2652A4C4CFF0084EA89 /* stopVPN.app in Embed NetP Controller Apps */ = {isa = PBXBuildFile; fileRef = 4B5F14E12A1476BD0060320F /* stopVPN.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 3192A2662A4C4CFF0084EA89 /* startVPN.app in Embed NetP Controller Apps */ = {isa = PBXBuildFile; fileRef = 4B5F14CB2A14702C0060320F /* startVPN.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 3192EC882A4DCF21001E97A5 /* DBPHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3192EC872A4DCF21001E97A5 /* DBPHomeViewController.swift */; }; 3198FCAC2A5D8A3C002EF5F8 /* AppLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD7A6E2A1D3E1F002A24E7 /* AppLauncher.swift */; }; 31A031A6288191230090F792 /* CookieConsentAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A031A5288191230090F792 /* CookieConsentAnimationView.swift */; }; @@ -946,7 +938,6 @@ 3706FAF7293F65D500E42796 /* FireproofDomainsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0511B4262CAA5A00F6079C /* FireproofDomainsViewController.swift */; }; 3706FAF8293F65D500E42796 /* URLEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D700625545EF800C3411E /* URLEventHandler.swift */; }; 3706FAFA293F65D500E42796 /* CleanThisHistoryMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAAB9113288EB1D600A057A9 /* CleanThisHistoryMenuItem.swift */; }; - 3706FAFB293F65D500E42796 /* TimedPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E498261474120067D1B9 /* TimedPixel.swift */; }; 3706FAFC293F65D500E42796 /* DownloadListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C0B23526E732000031CB7F /* DownloadListItem.swift */; }; 3706FAFD293F65D500E42796 /* DownloadsPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87D26D5DA0E0062C350 /* DownloadsPopover.swift */; }; 3706FAFE293F65D500E42796 /* SpacerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929626670D2A00AD2C21 /* SpacerNode.swift */; }; @@ -1363,7 +1354,6 @@ 3706FCD1293F65D500E42796 /* PrivacyDashboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6FA893C269C423100588ECD /* PrivacyDashboard.storyboard */; }; 3706FCD2293F65D500E42796 /* shield.json in Resources */ = {isa = PBXBuildFile; fileRef = AA34396A2754D4E200B241FA /* shield.json */; }; 3706FCD4293F65D500E42796 /* TabBarViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA7412B124D0B3AC00D22FE0 /* TabBarViewItem.xib */; }; - 3706FCD5293F65D500E42796 /* MainMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85480F8925CDC360009424E3 /* MainMenu.storyboard */; }; 3706FCD6293F65D500E42796 /* httpsMobileV2FalsePositives.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B67742A255DBEB800025BD8 /* httpsMobileV2FalsePositives.json */; }; 3706FCD8293F65D500E42796 /* BookmarksBar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4BD18F04283F151F00058124 /* BookmarksBar.storyboard */; }; 3706FCD9293F65D500E42796 /* trackers-1.json in Resources */ = {isa = PBXBuildFile; fileRef = AA3439732754D55100B241FA /* trackers-1.json */; }; @@ -1491,7 +1481,6 @@ 3706FE38293F661700E42796 /* SuggestionContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA63745324C9BF9A00AB2AC4 /* SuggestionContainerTests.swift */; }; 3706FE39293F661700E42796 /* TabTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC9C01424CAFBCE00AD1325 /* TabTests.swift */; }; 3706FE3A293F661700E42796 /* MockVariantManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B504A2726CA2900758A2B /* MockVariantManager.swift */; }; - 3706FE3B293F661700E42796 /* ContentBlockerRulesManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */; }; 3706FE3C293F661700E42796 /* FireproofDomainsStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BBF1712744CE36004F850E /* FireproofDomainsStoreMock.swift */; }; 3706FE3D293F661700E42796 /* DataEncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA1A6D8258C0CB300F6F690 /* DataEncryptionTests.swift */; }; 3706FE3E293F661700E42796 /* ClickToLoadModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1E52B42798CF98002EC53C /* ClickToLoadModelTests.swift */; }; @@ -1610,10 +1599,7 @@ 3707C727294B5D2900682A9F /* WKWebView+SessionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63D466825BEB6C200874977 /* WKWebView+SessionState.swift */; }; 3707C728294B5D2900682A9F /* WKWebViewConfigurationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68458CC25C7EB9000DC17B6 /* WKWebViewConfigurationExtensions.swift */; }; 3707C72A294B5D2900682A9F /* URLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8EDF2324923E980071C2E8 /* URLExtension.swift */; }; - 3707C72B294B5D3D00682A9F /* ContentBlockingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */; }; - 3707C72C294B5D3D00682A9F /* ContentBlockingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */; }; 3707C72D294B5D4100682A9F /* EmptyAttributionRulesProver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AE39F029373AF200C37AA4 /* EmptyAttributionRulesProver.swift */; }; - 3707C72E294B5D4400682A9F /* ContentBlockerRulesManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */; }; 3707C72F294B5D4F00682A9F /* WebViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3783F92229432E1800BCA897 /* WebViewTests.swift */; }; 3707C730294B5D5C00682A9F /* DuckPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3714B1E828EDBAAB0056C57A /* DuckPlayerTests.swift */; }; 370A34B12AB24E3700C77F7C /* SyncDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370A34B02AB24E3700C77F7C /* SyncDebugMenu.swift */; }; @@ -1775,7 +1761,6 @@ 4B2537772A11BFE100610219 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2537762A11BFE100610219 /* PixelKit */; }; 4B2537782A11C00F00610219 /* NetworkProtectionExtensionMachService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60782A0B29FA00BCD287 /* NetworkProtectionExtensionMachService.swift */; }; 4B25377A2A11C01700610219 /* UserText+NetworkProtectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D607C2A0B29FA00BCD287 /* UserText+NetworkProtectionExtensions.swift */; }; - 4B25377C2A11C07600610219 /* NetworkProtectionPixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60832A0B29FA00BCD287 /* NetworkProtectionPixelEvent.swift */; }; 4B29759728281F0900187C4E /* FirefoxEncryptionKeyReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B29759628281F0900187C4E /* FirefoxEncryptionKeyReader.swift */; }; 4B2975992828285900187C4E /* FirefoxKeyReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2975982828285900187C4E /* FirefoxKeyReaderTests.swift */; }; 4B2AAAF529E70DEA0026AFC0 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2AAAF429E70DEA0026AFC0 /* Lottie */; }; @@ -1786,14 +1771,9 @@ 4B2D06302A11C15900DE1F49 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2D062F2A11C15900DE1F49 /* Common */; }; 4B2D06322A11C1D300DE1F49 /* NSApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5C8F622591021700748EB7 /* NSApplicationExtension.swift */; }; 4B2D06332A11C1E300DE1F49 /* OptionalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B637273C26CCF0C200C8CB02 /* OptionalExtension.swift */; }; - 4B2D06342A11CC4600DE1F49 /* SystemExtensionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60622A0B29FA00BCD287 /* SystemExtensionManager.swift */; }; - 4B2D064F2A11D0D000DE1F49 /* NetworkProtectionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2D064E2A11D0D000DE1F49 /* NetworkProtectionUI */; }; - 4B2D06572A11D19B00DE1F49 /* DuckDuckGoAgentAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D06512A11D19B00DE1F49 /* DuckDuckGoAgentAppDelegate.swift */; }; - 4B2D06582A11D19B00DE1F49 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B2D06522A11D19B00DE1F49 /* Assets.xcassets */; }; 4B2D065B2A11D1FF00DE1F49 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4BEC322A11B509001D9AC5 /* Logging.swift */; }; 4B2D065E2A11D2D700DE1F49 /* DuckDuckGo Notifications.app in Embed Login Items */ = {isa = PBXBuildFile; fileRef = 4B4BEC202A11B4E2001D9AC5 /* DuckDuckGo Notifications.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 4B2D065F2A11D2D700DE1F49 /* DuckDuckGo Agent.app in Embed Login Items */ = {isa = PBXBuildFile; fileRef = 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo Agent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 4B2D067A2A1333EF00DE1F49 /* DuckDuckGoAgentAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D06512A11D19B00DE1F49 /* DuckDuckGoAgentAppDelegate.swift */; }; + 4B2D065F2A11D2D700DE1F49 /* DuckDuckGo VPN.app in Embed Login Items */ = {isa = PBXBuildFile; fileRef = 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo VPN.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4B2D067C2A13340900DE1F49 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4BEC322A11B509001D9AC5 /* Logging.swift */; }; 4B2D067F2A1334D700DE1F49 /* NetworkProtectionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2D067E2A1334D700DE1F49 /* NetworkProtectionUI */; }; 4B2E7D6326FF9D6500D2DB17 /* PrintingUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E7D6226FF9D6500D2DB17 /* PrintingUserScript.swift */; }; @@ -1819,7 +1799,6 @@ 4B4BEC482A11B61F001D9AC5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B4BEC342A11B509001D9AC5 /* Assets.xcassets */; }; 4B4D603F2A0B290200BCD287 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B4D603E2A0B290200BCD287 /* NetworkExtension.framework */; }; 4B4D60892A0B2A1C00BCD287 /* NetworkProtectionUNNotificationsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60762A0B29FA00BCD287 /* NetworkProtectionUNNotificationsPresenter.swift */; }; - 4B4D60932A0B2A3700BCD287 /* NetworkProtectionPixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60832A0B29FA00BCD287 /* NetworkProtectionPixelEvent.swift */; }; 4B4D60982A0B2A5C00BCD287 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4B4D60972A0B2A5C00BCD287 /* PixelKit */; }; 4B4D609F2A0B2C7300BCD287 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85799C1725DEBB3F0007EC87 /* Logging.swift */; }; 4B4D60A02A0B2D5B00BCD287 /* NetworkProtectionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* NetworkProtectionBundle.swift */; }; @@ -1862,10 +1841,6 @@ 4B59024C26B38BB800489384 /* ChromiumLoginReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B59024B26B38BB800489384 /* ChromiumLoginReaderTests.swift */; }; 4B59CC8C290083240058F2F6 /* ConnectBitwardenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B59CC8B290083240058F2F6 /* ConnectBitwardenViewModelTests.swift */; }; 4B5A4F4C27F3A5AA008FBD88 /* NSNotificationName+DataImport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5A4F4B27F3A5AA008FBD88 /* NSNotificationName+DataImport.swift */; }; - 4B5F14DC2A1470AA0060320F /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5F14C52A145D6A0060320F /* Main.swift */; }; - 4B5F14F22A1476EF0060320F /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5F14C52A145D6A0060320F /* Main.swift */; }; - 4B5F14F42A14824F0060320F /* startVPN.app in Embed NetP Controller Apps */ = {isa = PBXBuildFile; fileRef = 4B5F14CB2A14702C0060320F /* startVPN.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 4B5F14F52A1482530060320F /* stopVPN.app in Embed NetP Controller Apps */ = {isa = PBXBuildFile; fileRef = 4B5F14E12A1476BD0060320F /* stopVPN.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4B5FF67826B602B100D42879 /* FirefoxDataImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FF67726B602B100D42879 /* FirefoxDataImporter.swift */; }; 4B65143E263924B5005B46EB /* EmailUrlExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B65143D263924B5005B46EB /* EmailUrlExtensions.swift */; }; 4B677432255DBEB800025BD8 /* httpsMobileV2BloomSpec.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B677427255DBEB800025BD8 /* httpsMobileV2BloomSpec.json */; }; @@ -1904,8 +1879,6 @@ 4B78A86B26BB3ADD0071BB16 /* BrowserImportSummaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B78A86A26BB3ADD0071BB16 /* BrowserImportSummaryViewController.swift */; }; 4B7A57CF279A4EF300B1C70E /* ChromePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A57CE279A4EF300B1C70E /* ChromePreferences.swift */; }; 4B7A60A1273E0BE400BBDFEB /* WKWebsiteDataStoreExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A60A0273E0BE400BBDFEB /* WKWebsiteDataStoreExtension.swift */; }; - 4B7A94B429C16294000C7D4C /* ErrorWithParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A94B329C16294000C7D4C /* ErrorWithParameters.swift */; }; - 4B7A94B529C16294000C7D4C /* ErrorWithParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A94B329C16294000C7D4C /* ErrorWithParameters.swift */; }; 4B85A48028821CC500FC4C39 /* NSPasteboardItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B85A47F28821CC500FC4C39 /* NSPasteboardItemExtension.swift */; }; 4B8A4DFF27C83B29005F40E8 /* SaveIdentityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8A4DFE27C83B29005F40E8 /* SaveIdentityViewController.swift */; }; 4B8A4E0127C8447E005F40E8 /* SaveIdentityPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8A4E0027C8447E005F40E8 /* SaveIdentityPopover.swift */; }; @@ -1917,8 +1890,6 @@ 4B8AD0B127A86D9200AE44D6 /* WKWebsiteDataStoreExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8AD0B027A86D9200AE44D6 /* WKWebsiteDataStoreExtensionTests.swift */; }; 4B8D9062276D1D880078DB17 /* LocaleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8D9061276D1D880078DB17 /* LocaleExtension.swift */; }; 4B8F52352A169D2D00BE7131 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 4B8F52342A169D2D00BE7131 /* Common */; }; - 4B8F52412A18326600BE7131 /* NetworkProtectionTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8F52402A18326600BE7131 /* NetworkProtectionTunnelController.swift */; }; - 4B8F52422A18326600BE7131 /* NetworkProtectionTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8F52402A18326600BE7131 /* NetworkProtectionTunnelController.swift */; }; 4B92928B26670D1700AD2C21 /* BookmarksOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928526670D1600AD2C21 /* BookmarksOutlineView.swift */; }; 4B92928C26670D1700AD2C21 /* OutlineSeparatorViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928626670D1600AD2C21 /* OutlineSeparatorViewCell.swift */; }; 4B92928D26670D1700AD2C21 /* BookmarkOutlineViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928726670D1600AD2C21 /* BookmarkOutlineViewCell.swift */; }; @@ -2028,7 +1999,6 @@ 4B9579872AC7AE700062CA31 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D33F1125C82EB3002B91A6 /* ConfigurationManager.swift */; }; 4B9579882AC7AE700062CA31 /* YoutubePlayerUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F28C4C28C8EEC500119F70 /* YoutubePlayerUserScript.swift */; }; 4B9579892AC7AE700062CA31 /* PixelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */; }; - 4B95798A2AC7AE700062CA31 /* ErrorWithParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A94B329C16294000C7D4C /* ErrorWithParameters.swift */; }; 4B95798B2AC7AE700062CA31 /* FaviconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5FA696275F90C400DCE9C9 /* FaviconImageCache.swift */; }; 4B95798C2AC7AE700062CA31 /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1430DFF424D0580F00B8978C /* TabBarViewController.swift */; }; 4B95798D2AC7AE700062CA31 /* BookmarkOutlineViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929126670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift */; }; @@ -2106,13 +2076,11 @@ 4B9579D52AC7AE700062CA31 /* SupportedOsChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8057C72A83CAEE00F4FED6 /* SupportedOsChecker.swift */; }; 4B9579D62AC7AE700062CA31 /* WKWebViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA92127625ADA07900600CD4 /* WKWebViewExtension.swift */; }; 4B9579D72AC7AE700062CA31 /* CleanThisHistoryMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAAB9113288EB1D600A057A9 /* CleanThisHistoryMenuItem.swift */; }; - 4B9579D82AC7AE700062CA31 /* TimedPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E498261474120067D1B9 /* TimedPixel.swift */; }; 4B9579D92AC7AE700062CA31 /* DownloadListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C0B23526E732000031CB7F /* DownloadListItem.swift */; }; 4B9579DA2AC7AE700062CA31 /* WaitlistRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00A2A983B24000927DB /* WaitlistRequest.swift */; }; 4B9579DB2AC7AE700062CA31 /* DownloadsPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87D26D5DA0E0062C350 /* DownloadsPopover.swift */; }; 4B9579DC2AC7AE700062CA31 /* BookmarksBarMenuFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85774AFE2A713D3B00DE0561 /* BookmarksBarMenuFactory.swift */; }; 4B9579DD2AC7AE700062CA31 /* SpacerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929626670D2A00AD2C21 /* SpacerNode.swift */; }; - 4B9579DE2AC7AE700062CA31 /* SystemExtensionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60622A0B29FA00BCD287 /* SystemExtensionManager.swift */; }; 4B9579DF2AC7AE700062CA31 /* SyncManagementDialogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3775913529AB9A1C00E26367 /* SyncManagementDialogViewController.swift */; }; 4B9579E02AC7AE700062CA31 /* BookmarkExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C0BB6629AEFF8100AE8E3C /* BookmarkExtension.swift */; }; 4B9579E12AC7AE700062CA31 /* PasswordManagementCreditCardModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE6547B271FCD4D008D1D63 /* PasswordManagementCreditCardModel.swift */; }; @@ -2148,7 +2116,7 @@ 4B9579FF2AC7AE700062CA31 /* FireViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB7320826DD0CD9002FACF9 /* FireViewController.swift */; }; 4B957A002AC7AE700062CA31 /* OutlineSeparatorViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928626670D1600AD2C21 /* OutlineSeparatorViewCell.swift */; }; 4B957A012AC7AE700062CA31 /* SafariDataImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB99CFD26FE191E001E4761 /* SafariDataImporter.swift */; }; - 4B957A022AC7AE700062CA31 /* WaitlistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00C2A983B24000927DB /* WaitlistViewModel.swift */; }; + 4B957A022AC7AE700062CA31 /* NetworkProtectionWaitlistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00C2A983B24000927DB /* NetworkProtectionWaitlistViewModel.swift */; }; 4B957A032AC7AE700062CA31 /* LocalBookmarkStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987799F829999973005D8EB6 /* LocalBookmarkStore.swift */; }; 4B957A042AC7AE700062CA31 /* BWEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D02633528D8A9A9005CBB41 /* BWEncryption.m */; }; 4B957A052AC7AE700062CA31 /* StatisticsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50342726A11F00758A2B /* StatisticsLoader.swift */; }; @@ -2208,7 +2176,7 @@ 4B957A3B2AC7AE700062CA31 /* CookieNotificationAnimationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3184AC6E288F2A1100C35E4B /* CookieNotificationAnimationModel.swift */; }; 4B957A3C2AC7AE700062CA31 /* JoinedWaitlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0152A983B24000927DB /* JoinedWaitlistView.swift */; }; 4B957A3D2AC7AE700062CA31 /* SharingMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B63ED0E426BB8FB900A9DAD1 /* SharingMenu.swift */; }; - 4B957A3E2AC7AE700062CA31 /* EnableNetworkProtectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0132A983B24000927DB /* EnableNetworkProtectionView.swift */; }; + 4B957A3E2AC7AE700062CA31 /* EnableWaitlistFeatureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0132A983B24000927DB /* EnableWaitlistFeatureView.swift */; }; 4B957A3F2AC7AE700062CA31 /* GrammarFeaturesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4FF40B2624751A004E2377 /* GrammarFeaturesManager.swift */; }; 4B957A402AC7AE700062CA31 /* WaitlistModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0192A983B24000927DB /* WaitlistModalViewController.swift */; }; 4B957A412AC7AE700062CA31 /* WKMenuItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA06E7291401D700225DE2 /* WKMenuItemIdentifier.swift */; }; @@ -2247,7 +2215,7 @@ 4B957A622AC7AE700062CA31 /* AddressBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4F025D6BF10007F5990 /* AddressBarButton.swift */; }; 4B957A632AC7AE700062CA31 /* HistoryEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7527D263B05C600B973F8 /* HistoryEntry.swift */; }; 4B957A642AC7AE700062CA31 /* FaviconStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5FA69C275F945C00DCE9C9 /* FaviconStore.swift */; }; - 4B957A652AC7AE700062CA31 /* NetworkProtectionTermsAndConditionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0142A983B24000927DB /* NetworkProtectionTermsAndConditionsView.swift */; }; + 4B957A652AC7AE700062CA31 /* WaitlistTermsAndConditionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0142A983B24000927DB /* WaitlistTermsAndConditionsView.swift */; }; 4B957A662AC7AE700062CA31 /* SuggestionListCharacteristics.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB8203B26B2DE0D00788AC3 /* SuggestionListCharacteristics.swift */; }; 4B957A672AC7AE700062CA31 /* TimeIntervalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAADFD05264AA282001555EA /* TimeIntervalExtension.swift */; }; 4B957A682AC7AE700062CA31 /* NetworkProtectionFeatureDisabler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6785432AA8DE1F008A5004 /* NetworkProtectionFeatureDisabler.swift */; }; @@ -2297,7 +2265,6 @@ 4B957A942AC7AE700062CA31 /* PasteboardFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929226670D2A00AD2C21 /* PasteboardFolder.swift */; }; 4B957A952AC7AE700062CA31 /* CookieManagedNotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3171D6B72889849F0068632A /* CookieManagedNotificationView.swift */; }; 4B957A962AC7AE700062CA31 /* PermissionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106BAA26A7BF1D0013B453 /* PermissionType.swift */; }; - 4B957A972AC7AE700062CA31 /* NetworkProtectionTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8F52402A18326600BE7131 /* NetworkProtectionTunnelController.swift */; }; 4B957A982AC7AE700062CA31 /* RecentlyClosedWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC6881A28626C1900D54247 /* RecentlyClosedWindow.swift */; }; 4B957A992AC7AE700062CA31 /* ActionSpeech.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85707F29276A35FE00DC0649 /* ActionSpeech.swift */; }; 4B957A9A2AC7AE700062CA31 /* PrivacySecurityPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0511A6262CAA5A00F6079C /* PrivacySecurityPreferences.swift */; }; @@ -2526,7 +2493,7 @@ 4B957B792AC7AE700062CA31 /* ContentBlockingTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D574B12947224C008ED1B6 /* ContentBlockingTabExtension.swift */; }; 4B957B7A2AC7AE700062CA31 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B7184B27677C6500B4277F /* OnboardingViewController.swift */; }; 4B957B7B2AC7AE700062CA31 /* DeviceAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B379C1D27BDB7FF008A968E /* DeviceAuthenticator.swift */; }; - 4B957B7C2AC7AE700062CA31 /* NetworkProtectionWaitlistMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistMenu.swift */; }; + 4B957B7C2AC7AE700062CA31 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift */; }; 4B957B7D2AC7AE700062CA31 /* TabBarCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1456D6E024EFCBC300775049 /* TabBarCollectionView.swift */; }; 4B957B7E2AC7AE700062CA31 /* NetworkProtection+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60692A0B29FA00BCD287 /* NetworkProtection+ConvenienceInitializers.swift */; }; 4B957B7F2AC7AE700062CA31 /* NavigationActionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66B9C5B29A5EBAD0010E8F3 /* NavigationActionExtension.swift */; }; @@ -2670,7 +2637,6 @@ 4B957C0C2AC7AE700062CA31 /* PrivacyDashboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6FA893C269C423100588ECD /* PrivacyDashboard.storyboard */; }; 4B957C0D2AC7AE700062CA31 /* shield.json in Resources */ = {isa = PBXBuildFile; fileRef = AA34396A2754D4E200B241FA /* shield.json */; }; 4B957C0E2AC7AE700062CA31 /* TabBarViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA7412B124D0B3AC00D22FE0 /* TabBarViewItem.xib */; }; - 4B957C0F2AC7AE700062CA31 /* MainMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85480F8925CDC360009424E3 /* MainMenu.storyboard */; }; 4B957C102AC7AE700062CA31 /* httpsMobileV2FalsePositives.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B67742A255DBEB800025BD8 /* httpsMobileV2FalsePositives.json */; }; 4B957C112AC7AE700062CA31 /* BookmarksBar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4BD18F04283F151F00058124 /* BookmarksBar.storyboard */; }; 4B957C122AC7AE700062CA31 /* trackers-1.json in Resources */ = {isa = PBXBuildFile; fileRef = AA3439732754D55100B241FA /* trackers-1.json */; }; @@ -2705,11 +2671,8 @@ 4B957C2F2AC7AE700062CA31 /* trackers-2.json in Resources */ = {isa = PBXBuildFile; fileRef = AA3439742754D55100B241FA /* trackers-2.json */; }; 4B957C302AC7AE700062CA31 /* ProximaNova-Reg-webfont.woff2 in Resources */ = {isa = PBXBuildFile; fileRef = EAA29AE8278D2E43007070CF /* ProximaNova-Reg-webfont.woff2 */; }; 4B957C312AC7AE700062CA31 /* clickToLoad.js in Resources */ = {isa = PBXBuildFile; fileRef = EAFAD6C92728BD1200F9DF00 /* clickToLoad.js */; }; - 4B957C342AC7AE700062CA31 /* DuckDuckGo Agent.app in Embed Login Items */ = {isa = PBXBuildFile; fileRef = 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo Agent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 4B957C342AC7AE700062CA31 /* DuckDuckGo VPN.app in Embed Login Items */ = {isa = PBXBuildFile; fileRef = 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo VPN.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4B957C352AC7AE700062CA31 /* DuckDuckGo Notifications.app in Embed Login Items */ = {isa = PBXBuildFile; fileRef = 4B4BEC202A11B4E2001D9AC5 /* DuckDuckGo Notifications.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 4B957C372AC7AE700062CA31 /* enableOnDemand.app in Embed NetP Controller Apps */ = {isa = PBXBuildFile; fileRef = 7B736E5F2A4A22B700F9922A /* enableOnDemand.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 4B957C382AC7AE700062CA31 /* stopVPN.app in Embed NetP Controller Apps */ = {isa = PBXBuildFile; fileRef = 4B5F14E12A1476BD0060320F /* stopVPN.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 4B957C392AC7AE700062CA31 /* startVPN.app in Embed NetP Controller Apps */ = {isa = PBXBuildFile; fileRef = 4B5F14CB2A14702C0060320F /* startVPN.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4B9754EC2984300100D7B834 /* EmailManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3B848F297A0E1000A384BD /* EmailManagerExtension.swift */; }; 4B980E212817604000282EE1 /* NSNotificationName+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B980E202817604000282EE1 /* NSNotificationName+Debug.swift */; }; 4B98D27A28D95F1A003C2B6F /* ChromiumFaviconsReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B98D27928D95F1A003C2B6F /* ChromiumFaviconsReaderTests.swift */; }; @@ -2723,21 +2686,21 @@ 4B9DB0232A983B24000927DB /* WaitlistRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00A2A983B24000927DB /* WaitlistRequest.swift */; }; 4B9DB0242A983B24000927DB /* WaitlistRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00A2A983B24000927DB /* WaitlistRequest.swift */; }; 4B9DB0252A983B24000927DB /* WaitlistRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00A2A983B24000927DB /* WaitlistRequest.swift */; }; - 4B9DB0262A983B24000927DB /* WaitlistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00C2A983B24000927DB /* WaitlistViewModel.swift */; }; - 4B9DB0272A983B24000927DB /* WaitlistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00C2A983B24000927DB /* WaitlistViewModel.swift */; }; - 4B9DB0282A983B24000927DB /* WaitlistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00C2A983B24000927DB /* WaitlistViewModel.swift */; }; + 4B9DB0262A983B24000927DB /* NetworkProtectionWaitlistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00C2A983B24000927DB /* NetworkProtectionWaitlistViewModel.swift */; }; + 4B9DB0272A983B24000927DB /* NetworkProtectionWaitlistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00C2A983B24000927DB /* NetworkProtectionWaitlistViewModel.swift */; }; + 4B9DB0282A983B24000927DB /* NetworkProtectionWaitlistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00C2A983B24000927DB /* NetworkProtectionWaitlistViewModel.swift */; }; 4B9DB0292A983B24000927DB /* WaitlistStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00E2A983B24000927DB /* WaitlistStorage.swift */; }; 4B9DB02A2A983B24000927DB /* WaitlistStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00E2A983B24000927DB /* WaitlistStorage.swift */; }; 4B9DB02B2A983B24000927DB /* WaitlistStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00E2A983B24000927DB /* WaitlistStorage.swift */; }; 4B9DB02C2A983B24000927DB /* WaitlistKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00F2A983B24000927DB /* WaitlistKeychainStorage.swift */; }; 4B9DB02D2A983B24000927DB /* WaitlistKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00F2A983B24000927DB /* WaitlistKeychainStorage.swift */; }; 4B9DB02E2A983B24000927DB /* WaitlistKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB00F2A983B24000927DB /* WaitlistKeychainStorage.swift */; }; - 4B9DB0322A983B24000927DB /* EnableNetworkProtectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0132A983B24000927DB /* EnableNetworkProtectionView.swift */; }; - 4B9DB0332A983B24000927DB /* EnableNetworkProtectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0132A983B24000927DB /* EnableNetworkProtectionView.swift */; }; - 4B9DB0342A983B24000927DB /* EnableNetworkProtectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0132A983B24000927DB /* EnableNetworkProtectionView.swift */; }; - 4B9DB0352A983B24000927DB /* NetworkProtectionTermsAndConditionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0142A983B24000927DB /* NetworkProtectionTermsAndConditionsView.swift */; }; - 4B9DB0362A983B24000927DB /* NetworkProtectionTermsAndConditionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0142A983B24000927DB /* NetworkProtectionTermsAndConditionsView.swift */; }; - 4B9DB0372A983B24000927DB /* NetworkProtectionTermsAndConditionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0142A983B24000927DB /* NetworkProtectionTermsAndConditionsView.swift */; }; + 4B9DB0322A983B24000927DB /* EnableWaitlistFeatureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0132A983B24000927DB /* EnableWaitlistFeatureView.swift */; }; + 4B9DB0332A983B24000927DB /* EnableWaitlistFeatureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0132A983B24000927DB /* EnableWaitlistFeatureView.swift */; }; + 4B9DB0342A983B24000927DB /* EnableWaitlistFeatureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0132A983B24000927DB /* EnableWaitlistFeatureView.swift */; }; + 4B9DB0352A983B24000927DB /* WaitlistTermsAndConditionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0142A983B24000927DB /* WaitlistTermsAndConditionsView.swift */; }; + 4B9DB0362A983B24000927DB /* WaitlistTermsAndConditionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0142A983B24000927DB /* WaitlistTermsAndConditionsView.swift */; }; + 4B9DB0372A983B24000927DB /* WaitlistTermsAndConditionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0142A983B24000927DB /* WaitlistTermsAndConditionsView.swift */; }; 4B9DB0382A983B24000927DB /* JoinedWaitlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0152A983B24000927DB /* JoinedWaitlistView.swift */; }; 4B9DB0392A983B24000927DB /* JoinedWaitlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0152A983B24000927DB /* JoinedWaitlistView.swift */; }; 4B9DB03A2A983B24000927DB /* JoinedWaitlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9DB0152A983B24000927DB /* JoinedWaitlistView.swift */; }; @@ -2850,6 +2813,21 @@ 4BE65481271FCD4D008D1D63 /* PasswordManagementNoteModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE6547D271FCD4D008D1D63 /* PasswordManagementNoteModel.swift */; }; 4BE65485271FCD7B008D1D63 /* LoginFaviconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE65484271FCD7B008D1D63 /* LoginFaviconView.swift */; }; 4BF01C00272AE74C00884A61 /* CountryList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE65482271FCD53008D1D63 /* CountryList.swift */; }; + 4BF0E5052AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5042AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift */; }; + 4BF0E5062AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5042AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift */; }; + 4BF0E5072AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5042AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift */; }; + 4BF0E5082AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5042AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift */; }; + 4BF0E5092AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5042AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift */; }; + 4BF0E50A2AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5042AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift */; }; + 4BF0E50B2AD2552200FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5042AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift */; }; + 4BF0E50C2AD2552300FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5042AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift */; }; + 4BF0E50E2AD2555D00FFEC9E /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + 4BF0E5122AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5112AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift */; }; + 4BF0E5132AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5112AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift */; }; + 4BF0E5142AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5112AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift */; }; + 4BF0E5152AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5112AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift */; }; + 4BF0E5162AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5112AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift */; }; + 4BF0E5172AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0E5112AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift */; }; 4BF4951826C08395000547B8 /* ThirdPartyBrowserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF4951726C08395000547B8 /* ThirdPartyBrowserTests.swift */; }; 4BF4EA5027C71F26004E57C4 /* PasswordManagementListSectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF4EA4F27C71F26004E57C4 /* PasswordManagementListSectionTests.swift */; }; 4BF6961D28BE911100D402D4 /* RecentlyVisitedSiteModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF6961C28BE911100D402D4 /* RecentlyVisitedSiteModelTests.swift */; }; @@ -2891,40 +2869,87 @@ 7B1E819E27C8874900FF0E60 /* ContentOverlayPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1E819B27C8874900FF0E60 /* ContentOverlayPopover.swift */; }; 7B1E819F27C8874900FF0E60 /* ContentOverlay.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7B1E819C27C8874900FF0E60 /* ContentOverlay.storyboard */; }; 7B1E81A027C8874900FF0E60 /* ContentOverlayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1E819D27C8874900FF0E60 /* ContentOverlayViewController.swift */; }; + 7B20D5C62ADFEC6E0053C42A /* PixelKitTestingUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = 7B20D5C52ADFEC6E0053C42A /* PixelKitTestingUtilities */; }; 7B2DDCF82A93A8BB0039D884 /* NetworkProtectionAppEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2DDCF72A93A8BB0039D884 /* NetworkProtectionAppEvents.swift */; }; 7B2DDCFA2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */; }; 7B2DDCFB2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */; }; - 7B2DDD022A93BAA60039D884 /* NetworkProtectionBouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2DDD012A93BAA60039D884 /* NetworkProtectionBouncer.swift */; }; - 7B2DDD032A93BBEC0039D884 /* NetworkProtectionBouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2DDD012A93BAA60039D884 /* NetworkProtectionBouncer.swift */; }; - 7B2DDD052A93BEE20039D884 /* FeatureProtectedTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2DDD042A93BEE20039D884 /* FeatureProtectedTunnelController.swift */; }; - 7B2DDD072A93C17D0039D884 /* UserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2DDD062A93C17D0039D884 /* UserText.swift */; }; - 7B2DDD092A93C2440039D884 /* AppLauncher+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2DDD082A93C2440039D884 /* AppLauncher+DefaultInitializer.swift */; }; 7B2E52252A5FEC09000C6D39 /* NetworkProtectionAgentNotificationsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2E52242A5FEC09000C6D39 /* NetworkProtectionAgentNotificationsPresenter.swift */; }; + 7B31FD882AD124620086AA24 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + 7B31FD8A2AD124680086AA24 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + 7B31FD8C2AD125620086AA24 /* NetworkProtectionIPC in Frameworks */ = {isa = PBXBuildFile; productRef = 7B31FD8B2AD125620086AA24 /* NetworkProtectionIPC */; }; + 7B31FD8E2AD125760086AA24 /* NetworkProtectionIPC in Frameworks */ = {isa = PBXBuildFile; productRef = 7B31FD8D2AD125760086AA24 /* NetworkProtectionIPC */; }; + 7B31FD902AD1257B0086AA24 /* NetworkProtectionIPC in Frameworks */ = {isa = PBXBuildFile; productRef = 7B31FD8F2AD1257B0086AA24 /* NetworkProtectionIPC */; }; + 7B3618C22ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3618C12ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift */; }; + 7B3618C42ADE77D2000D6154 /* NetworkProtectionNavBarPopoverManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3618C12ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift */; }; + 7B3618C52ADE77D3000D6154 /* NetworkProtectionNavBarPopoverManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3618C12ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift */; }; 7B430EA12A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B430EA02A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift */; }; 7B430EA22A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B430EA02A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift */; }; 7B4CE8E726F02135009134B1 /* TabBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4CE8E626F02134009134B1 /* TabBarTests.swift */; }; - 7B736E582A4A22B700F9922A /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5F14C52A145D6A0060320F /* Main.swift */; }; - 7B736E6A2A4A22FC00F9922A /* enableOnDemand.app in Embed NetP Controller Apps */ = {isa = PBXBuildFile; fileRef = 7B736E5F2A4A22B700F9922A /* enableOnDemand.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 7B838C382A1DD8DD00E05A13 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B2D06522A11D19B00DE1F49 /* Assets.xcassets */; }; + 7B5F9A752AE2BE4E002AEBC0 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7B5F9A742AE2BE4E002AEBC0 /* PixelKit */; }; 7B934C412A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; + 7B96D0DC2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B96D0DB2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift */; }; 7BA4727D26F01BC400EAA165 /* CoreDataTestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292C42667104B00AD2C21 /* CoreDataTestUtilities.swift */; }; + 7BA59C9B2AE18B49009A97B1 /* SystemExtensionManager in Frameworks */ = {isa = PBXBuildFile; productRef = 7BA59C9A2AE18B49009A97B1 /* SystemExtensionManager */; }; + 7BA7CC392AD11E2D0042E5CE /* DuckDuckGoVPNAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC0E2AD11DC80042E5CE /* DuckDuckGoVPNAppDelegate.swift */; }; + 7BA7CC3A2AD11E2D0042E5CE /* DuckDuckGoVPNAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC0E2AD11DC80042E5CE /* DuckDuckGoVPNAppDelegate.swift */; }; + 7BA7CC3B2AD11E330042E5CE /* Bundle+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC0F2AD11DC80042E5CE /* Bundle+Configuration.swift */; }; + 7BA7CC3C2AD11E330042E5CE /* Bundle+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC0F2AD11DC80042E5CE /* Bundle+Configuration.swift */; }; + 7BA7CC3D2AD11E380042E5CE /* TunnelControllerIPCService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC112AD11DC80042E5CE /* TunnelControllerIPCService.swift */; }; + 7BA7CC3E2AD11E380042E5CE /* TunnelControllerIPCService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC112AD11DC80042E5CE /* TunnelControllerIPCService.swift */; }; + 7BA7CC3F2AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC132AD11DC80042E5CE /* AppLauncher+DefaultInitializer.swift */; }; + 7BA7CC402AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC132AD11DC80042E5CE /* AppLauncher+DefaultInitializer.swift */; }; + 7BA7CC412AD11E420042E5CE /* NetworkProtectionBouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC152AD11DC80042E5CE /* NetworkProtectionBouncer.swift */; }; + 7BA7CC422AD11E420042E5CE /* NetworkProtectionBouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC152AD11DC80042E5CE /* NetworkProtectionBouncer.swift */; }; + 7BA7CC432AD11E480042E5CE /* UserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC172AD11DC80042E5CE /* UserText.swift */; }; + 7BA7CC442AD11E490042E5CE /* UserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC172AD11DC80042E5CE /* UserText.swift */; }; + 7BA7CC472AD11E5C0042E5CE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7BA7CC122AD11DC80042E5CE /* Assets.xcassets */; }; + 7BA7CC482AD11E5C0042E5CE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7BA7CC122AD11DC80042E5CE /* Assets.xcassets */; }; + 7BA7CC4A2AD11EA00042E5CE /* NetworkProtectionTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8F52402A18326600BE7131 /* NetworkProtectionTunnelController.swift */; }; + 7BA7CC4B2AD11EC60042E5CE /* NetworkProtectionControllerErrorStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D606A2A0B29FA00BCD287 /* NetworkProtectionControllerErrorStore.swift */; }; + 7BA7CC4C2AD11EC70042E5CE /* NetworkProtectionControllerErrorStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D606A2A0B29FA00BCD287 /* NetworkProtectionControllerErrorStore.swift */; }; + 7BA7CC4E2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC4D2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift */; }; + 7BA7CC4F2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC4D2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift */; }; + 7BA7CC502AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC4D2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift */; }; + 7BA7CC532AD11FCE0042E5CE /* NetworkProtectionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* NetworkProtectionBundle.swift */; }; + 7BA7CC542AD11FCE0042E5CE /* NetworkProtectionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* NetworkProtectionBundle.swift */; }; + 7BA7CC552AD11FFB0042E5CE /* NetworkProtectionOptionKeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */; }; + 7BA7CC562AD11FFB0042E5CE /* NetworkProtectionOptionKeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */; }; + 7BA7CC582AD1203A0042E5CE /* UserText+NetworkProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60D22A0C84F700BCD287 /* UserText+NetworkProtection.swift */; }; + 7BA7CC592AD1203B0042E5CE /* UserText+NetworkProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60D22A0C84F700BCD287 /* UserText+NetworkProtection.swift */; }; + 7BA7CC5A2AD120640042E5CE /* NetworkProtection+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60692A0B29FA00BCD287 /* NetworkProtection+ConvenienceInitializers.swift */; }; + 7BA7CC5B2AD120640042E5CE /* NetworkProtection+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60692A0B29FA00BCD287 /* NetworkProtection+ConvenienceInitializers.swift */; }; + 7BA7CC5C2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60722A0B29FA00BCD287 /* EventMapping+NetworkProtectionError.swift */; }; + 7BA7CC5D2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60722A0B29FA00BCD287 /* EventMapping+NetworkProtectionError.swift */; }; + 7BA7CC5F2AD1210C0042E5CE /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = 7BA7CC5E2AD1210C0042E5CE /* Networking */; }; + 7BA7CC612AD1211C0042E5CE /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = 7BA7CC602AD1211C0042E5CE /* Networking */; }; 7BAF9E4B2A8A3CC9002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; 7BAF9E4C2A8A3CCA002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; 7BAF9E4D2A8A3CCB002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; 7BB108592A43375D000AB95F /* PFMoveApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BB108582A43375D000AB95F /* PFMoveApplication.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 7BBD44282AD730A400D0A064 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7BBD44272AD730A400D0A064 /* PixelKit */; }; 7BBD45B12A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBD45B02A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift */; }; 7BBD45B22A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBD45B02A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift */; }; + 7BD1688E2AD4A4C400D24876 /* NetworkExtensionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD1688D2AD4A4C400D24876 /* NetworkExtensionController.swift */; }; 7BD3AF5D2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */; }; 7BE146072A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */; }; 7BE146082A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */; }; - 7BFE95522A9DF1CE0081ABE9 /* NetworkProtectionWaitlistMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistMenu.swift */; }; + 7BEC182F2AD5D8DC00D30536 /* SystemExtensionManager in Frameworks */ = {isa = PBXBuildFile; productRef = 7BEC182E2AD5D8DC00D30536 /* SystemExtensionManager */; }; + 7BEEA5122AD1235B00A9E72B /* NetworkProtectionIPC in Frameworks */ = {isa = PBXBuildFile; productRef = 7BEEA5112AD1235B00A9E72B /* NetworkProtectionIPC */; }; + 7BEEA5142AD1236300A9E72B /* NetworkProtectionIPC in Frameworks */ = {isa = PBXBuildFile; productRef = 7BEEA5132AD1236300A9E72B /* NetworkProtectionIPC */; }; + 7BEEA5162AD1236E00A9E72B /* NetworkProtectionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 7BEEA5152AD1236E00A9E72B /* NetworkProtectionUI */; }; + 7BF1A9D82AE054D300FCA683 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7BF1A9D72AE054D300FCA683 /* Info.plist */; }; + 7BF1A9DC2AE0551C00FCA683 /* DBPUnitTests.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; }; + 7BF7705F2AD6C999001C9182 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7BF7705E2AD6C999001C9182 /* PixelKit */; }; + 7BFCB74E2ADE7E1A00DA3EA7 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7BFCB74D2ADE7E1A00DA3EA7 /* PixelKit */; }; + 7BFCB7502ADE7E2300DA3EA7 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7BFCB74F2ADE7E2300DA3EA7 /* PixelKit */; }; + 7BFE95522A9DF1CE0081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift */; }; 7BFE95542A9DF2930081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95532A9DF2930081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift */; }; 7BFE95552A9DF2990081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95532A9DF2930081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift */; }; 7BFE95562A9DF29B0081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95532A9DF2930081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift */; }; 7BFE95582A9DF2A80081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95532A9DF2930081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift */; }; 7BFE95592A9DF2AF0081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95532A9DF2930081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift */; }; - 7BFE955A2A9DF4550081ABE9 /* NetworkProtectionWaitlistMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistMenu.swift */; }; - 7BFE955B2A9DF5210081ABE9 /* NetworkProtectionWaitlistMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistMenu.swift */; }; + 7BFE955A2A9DF4550081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift */; }; + 7BFE955B2A9DF5210081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift */; }; 85012B0229133F9F003D0DCC /* NavigationBarPopovers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85012B0129133F9F003D0DCC /* NavigationBarPopovers.swift */; }; 850E8DFB2A6FEC5E00691187 /* BookmarksBarAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850E8DFA2A6FEC5E00691187 /* BookmarksBarAppearance.swift */; }; 8511E18425F82B34002F516B /* 01_Fire_really_small.json in Resources */ = {isa = PBXBuildFile; fileRef = 8511E18325F82B34002F516B /* 01_Fire_really_small.json */; }; @@ -2935,7 +2960,6 @@ 85378DA2274E7F25007C5CBF /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85378DA1274E7F25007C5CBF /* EmailManagerRequestDelegate.swift */; }; 85393C872A6FF1B600F11EB3 /* BookmarksBarAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850E8DFA2A6FEC5E00691187 /* BookmarksBarAppearance.swift */; }; 8546DE6225C03056000CA5E1 /* UserAgentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8546DE6125C03056000CA5E1 /* UserAgentTests.swift */; }; - 85480F8A25CDC360009424E3 /* MainMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85480F8925CDC360009424E3 /* MainMenu.storyboard */; }; 85480FCF25D1AA22009424E3 /* ConfigurationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85480FCE25D1AA22009424E3 /* ConfigurationStore.swift */; }; 854DAAAE2A72B613001E2E24 /* BookmarksBarPromptAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 859F30662A72B38500C20372 /* BookmarksBarPromptAssets.xcassets */; }; 85589E7F27BBB8630038AD11 /* AddEditFavoriteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E7927BBB8620038AD11 /* AddEditFavoriteViewController.swift */; }; @@ -3294,7 +3318,6 @@ B610F2BB27A145C500FCEBE9 /* RulesCompilationMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2BA27A145C500FCEBE9 /* RulesCompilationMonitor.swift */; }; B610F2E427A8F37A00FCEBE9 /* CBRCompileTimeReporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2E327A8F37A00FCEBE9 /* CBRCompileTimeReporterTests.swift */; }; B610F2EB27AA8E4500FCEBE9 /* ContentBlockingUpdatingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2E527AA388100FCEBE9 /* ContentBlockingUpdatingTests.swift */; }; - B610F2EC27AA8F9400FCEBE9 /* ContentBlockerRulesManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */; }; B61E2CD5294346C000773D8A /* Tab+Navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61E2CD4294346C000773D8A /* Tab+Navigation.swift */; }; B61EF3EC266F91E700B4D78F /* WKWebView+Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61EF3EB266F91E700B4D78F /* WKWebView+Download.swift */; }; B626A7552991413000053070 /* SerpHeadersNavigationResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BF5D922947199A006742B1 /* SerpHeadersNavigationResponder.swift */; }; @@ -3306,12 +3329,18 @@ B626A7652992506A00053070 /* SerpHeadersNavigationResponderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B626A7632992506A00053070 /* SerpHeadersNavigationResponderTests.swift */; }; B626A76D29928B1600053070 /* TestsClosureNavigationResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B626A76C29928B1600053070 /* TestsClosureNavigationResponder.swift */; }; B626A76E29928B1600053070 /* TestsClosureNavigationResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B626A76C29928B1600053070 /* TestsClosureNavigationResponder.swift */; }; - B626A77229928C6A00053070 /* MockPrivacyConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B626A76F29928C4100053070 /* MockPrivacyConfiguration.swift */; }; - B626A77329928C6B00053070 /* MockPrivacyConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B626A76F29928C4100053070 /* MockPrivacyConfiguration.swift */; }; B62A233C29C322BC00D22475 /* NavigationProtectionIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62A233B29C322BC00D22475 /* NavigationProtectionIntegrationTests.swift */; }; B62A233D29C322BC00D22475 /* NavigationProtectionIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62A233B29C322BC00D22475 /* NavigationProtectionIntegrationTests.swift */; }; B62A234029C41D4400D22475 /* HistoryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62A233F29C41D4400D22475 /* HistoryIntegrationTests.swift */; }; B62A234129C41D4400D22475 /* HistoryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62A233F29C41D4400D22475 /* HistoryIntegrationTests.swift */; }; + B62B48392ADE46FC000DECE5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B48382ADE46FC000DECE5 /* Application.swift */; }; + B62B483A2ADE46FC000DECE5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B48382ADE46FC000DECE5 /* Application.swift */; }; + B62B483B2ADE46FC000DECE5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B48382ADE46FC000DECE5 /* Application.swift */; }; + B62B483C2ADE46FC000DECE5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B48382ADE46FC000DECE5 /* Application.swift */; }; + B62B483E2ADE48DE000DECE5 /* MenuBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B483D2ADE48DE000DECE5 /* MenuBuilder.swift */; }; + B62B483F2ADE48DE000DECE5 /* MenuBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B483D2ADE48DE000DECE5 /* MenuBuilder.swift */; }; + B62B48402ADE48DE000DECE5 /* MenuBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B483D2ADE48DE000DECE5 /* MenuBuilder.swift */; }; + B62B48412ADE48DE000DECE5 /* MenuBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B483D2ADE48DE000DECE5 /* MenuBuilder.swift */; }; B62EB47C25BAD3BB005745C6 /* WKWebViewPrivateMethodsAvailabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62EB47B25BAD3BB005745C6 /* WKWebViewPrivateMethodsAvailabilityTests.swift */; }; B630793526731BC400DCEE41 /* URLSuggestedFilenameTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8553FF51257523760029327F /* URLSuggestedFilenameTests.swift */; }; B630793A26731F2600DCEE41 /* FileDownloadManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B630793926731F2600DCEE41 /* FileDownloadManagerTests.swift */; }; @@ -3420,6 +3449,22 @@ B68C2FB227706E6A00BF2C7D /* ProcessExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C2FB127706E6A00BF2C7D /* ProcessExtension.swift */; }; B68C92C1274E3EF4002AC6B0 /* PopUpWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C0274E3EF4002AC6B0 /* PopUpWindow.swift */; }; B68C92C42750EF76002AC6B0 /* PixelDataRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */; }; + B68D21C32ACBC916002DA3C2 /* ContentBlockingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */; }; + B68D21C42ACBC917002DA3C2 /* ContentBlockingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */; }; + B68D21C82ACBC96D002DA3C2 /* MockPrivacyConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B626A76F29928C4100053070 /* MockPrivacyConfiguration.swift */; }; + B68D21C92ACBC96E002DA3C2 /* MockPrivacyConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B626A76F29928C4100053070 /* MockPrivacyConfiguration.swift */; }; + B68D21CA2ACBC971002DA3C2 /* MockPrivacyConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B626A76F29928C4100053070 /* MockPrivacyConfiguration.swift */; }; + B68D21CB2ACBC9A3002DA3C2 /* ContentBlockingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */; }; + B68D21CC2ACBC9A3002DA3C2 /* ContentBlockingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */; }; + B68D21CD2ACBC9A7002DA3C2 /* MockPrivacyConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B626A76F29928C4100053070 /* MockPrivacyConfiguration.swift */; }; + B68D21CF2ACBC9FC002DA3C2 /* ContentBlockerRulesManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */; }; + B68D21D02ACBC9FD002DA3C2 /* ContentBlockerRulesManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */; }; + B68D21D12ACBCA00002DA3C2 /* ContentBlockerRulesManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */; }; + B68D21D22ACBCA01002DA3C2 /* ContentBlockerRulesManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */; }; + B690152C2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B690152B2ACBF4DA00AD0BAB /* MenuPreview.swift */; }; + B690152D2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B690152B2ACBF4DA00AD0BAB /* MenuPreview.swift */; }; + B690152E2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B690152B2ACBF4DA00AD0BAB /* MenuPreview.swift */; }; + B690152F2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B690152B2ACBF4DA00AD0BAB /* MenuPreview.swift */; }; B693954A26F04BEB0015B914 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693953C26F04BE70015B914 /* NibLoadable.swift */; }; B693954B26F04BEB0015B914 /* MouseOverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693953D26F04BE70015B914 /* MouseOverView.swift */; }; B693954C26F04BEB0015B914 /* FocusRingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693953E26F04BE70015B914 /* FocusRingView.swift */; }; @@ -3470,17 +3515,13 @@ B6A9E47726146A570067D1B9 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47626146A570067D1B9 /* PixelEvent.swift */; }; B6A9E47F26146A800067D1B9 /* PixelArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E47E26146A800067D1B9 /* PixelArguments.swift */; }; B6A9E48426146AAB0067D1B9 /* PixelParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */; }; - B6A9E499261474120067D1B9 /* TimedPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A9E498261474120067D1B9 /* TimedPixel.swift */; }; B6AA64732994B43300D99CD6 /* FutureExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */; }; B6AA64742994B43300D99CD6 /* FutureExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */; }; - B6AA64752995164700D99CD6 /* MockPrivacyConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6CA482F298D04690067ECCE /* MockPrivacyConfiguration.swift */; }; - B6AA64762995164900D99CD6 /* MockPrivacyConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6CA482F298D04690067ECCE /* MockPrivacyConfiguration.swift */; }; B6AAAC2D260330580029438D /* PublishedAfter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AAAC2C260330580029438D /* PublishedAfter.swift */; }; B6AAAC3E26048F690029438D /* RandomAccessCollectionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AAAC3D26048F690029438D /* RandomAccessCollectionExtension.swift */; }; B6AE39F129373AF200C37AA4 /* EmptyAttributionRulesProver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AE39F029373AF200C37AA4 /* EmptyAttributionRulesProver.swift */; }; B6AE39F329374AEC00C37AA4 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = B6AE39F229374AEC00C37AA4 /* OHHTTPStubs */; }; B6AE39F529374AEC00C37AA4 /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B6AE39F429374AEC00C37AA4 /* OHHTTPStubsSwift */; }; - B6AE39F629374B8E00C37AA4 /* ContentBlockerRulesManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */; }; B6AE39F729374B9900C37AA4 /* DuckPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3714B1E828EDBAAB0056C57A /* DuckPlayerTests.swift */; }; B6AE74342609AFCE005B9B1A /* ProgressEstimationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AE74332609AFCE005B9B1A /* ProgressEstimationTests.swift */; }; B6AFE6BC29A5D3F8002FF962 /* PrivacyDashboardTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BF5D842946FFDA006742B1 /* PrivacyDashboardTabExtension.swift */; }; @@ -3500,8 +3541,6 @@ B6BBF1702744CDE1004F850E /* CoreDataStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BBF16F2744CDE1004F850E /* CoreDataStoreTests.swift */; }; B6BBF1722744CE36004F850E /* FireproofDomainsStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BBF1712744CE36004F850E /* FireproofDomainsStoreMock.swift */; }; B6BBF17427475B15004F850E /* PopupBlockedPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BBF17327475B15004F850E /* PopupBlockedPopover.swift */; }; - B6BDD9F529409DDD00F68088 /* ContentBlockingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */; }; - B6BDD9F62940B5B500F68088 /* ContentBlockingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */; }; B6BDDA012942389000F68088 /* TabExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDDA002942389000F68088 /* TabExtensions.swift */; }; B6BE9FAA293F7955006363C6 /* ModalSheetCancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BE9FA9293F7955006363C6 /* ModalSheetCancellable.swift */; }; B6BF5D852946FFDA006742B1 /* PrivacyDashboardTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BF5D842946FFDA006742B1 /* PrivacyDashboardTabExtension.swift */; }; @@ -3634,13 +3673,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 31929F7E2A4C4CFF0084EA89 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = AA585D76248FD31100E9A3E2 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B2537592A11BE7300610219; - remoteInfo = NetworkProtectionSystemExtension; - }; 37079A92294236F20031BB3C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = AA585D76248FD31100E9A3E2 /* Project object */; @@ -3662,26 +3694,26 @@ remoteGlobalIDString = AA585D7D248FD31100E9A3E2; remoteInfo = "DuckDuckGo Privacy Browser"; }; - 4B2537632A11BE7600610219 /* PBXContainerItemProxy */ = { + 7B4CE8DF26F02108009134B1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = AA585D76248FD31100E9A3E2 /* Project object */; proxyType = 1; - remoteGlobalIDString = 4B2537592A11BE7300610219; - remoteInfo = NetworkProtectionSystemExtension; + remoteGlobalIDString = AA585D7D248FD31100E9A3E2; + remoteInfo = "DuckDuckGo Privacy Browser"; }; - 4B9579282AC7AE700062CA31 /* PBXContainerItemProxy */ = { + 7B96D0D32ADFDA7F007E02C8 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = AA585D76248FD31100E9A3E2 /* Project object */; proxyType = 1; - remoteGlobalIDString = 4B2537592A11BE7300610219; - remoteInfo = NetworkProtectionSystemExtension; + remoteGlobalIDString = 31929F7B2A4C4CFF0084EA89; + remoteInfo = "DuckDuckGo DBP"; }; - 7B4CE8DF26F02108009134B1 /* PBXContainerItemProxy */ = { + 7BEC18302AD5DA3300D30536 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = AA585D76248FD31100E9A3E2 /* Project object */; proxyType = 1; - remoteGlobalIDString = AA585D7D248FD31100E9A3E2; - remoteInfo = "DuckDuckGo Privacy Browser"; + remoteGlobalIDString = 4B2537592A11BE7300610219; + remoteInfo = NetworkProtectionSystemExtension; }; AA585D91248FD31400E9A3E2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -3713,75 +3745,36 @@ dstPath = Contents/Library/LoginItems; dstSubfolderSpec = 1; files = ( - 3192A2612A4C4CFF0084EA89 /* DuckDuckGo Agent.app in Embed Login Items */, + 3192A2612A4C4CFF0084EA89 /* DuckDuckGo VPN.app in Embed Login Items */, 3192A2622A4C4CFF0084EA89 /* DuckDuckGo Notifications.app in Embed Login Items */, ); name = "Embed Login Items"; runOnlyForDeploymentPostprocessing = 0; }; - 3192A2632A4C4CFF0084EA89 /* Embed NetP Controller Apps */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 7; - files = ( - 3192A2642A4C4CFF0084EA89 /* enableOnDemand.app in Embed NetP Controller Apps */, - 3192A2652A4C4CFF0084EA89 /* stopVPN.app in Embed NetP Controller Apps */, - 3192A2662A4C4CFF0084EA89 /* startVPN.app in Embed NetP Controller Apps */, - ); - name = "Embed NetP Controller Apps"; - runOnlyForDeploymentPostprocessing = 0; - }; 4B2D065D2A11D2AE00DE1F49 /* Embed Login Items */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = Contents/Library/LoginItems; dstSubfolderSpec = 1; files = ( - 4B2D065F2A11D2D700DE1F49 /* DuckDuckGo Agent.app in Embed Login Items */, + 4B2D065F2A11D2D700DE1F49 /* DuckDuckGo VPN.app in Embed Login Items */, 4B2D065E2A11D2D700DE1F49 /* DuckDuckGo Notifications.app in Embed Login Items */, ); name = "Embed Login Items"; runOnlyForDeploymentPostprocessing = 0; }; - 4B5F14F32A14823D0060320F /* Embed NetP Controller Apps */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 7; - files = ( - 7B736E6A2A4A22FC00F9922A /* enableOnDemand.app in Embed NetP Controller Apps */, - 4B5F14F52A1482530060320F /* stopVPN.app in Embed NetP Controller Apps */, - 4B5F14F42A14824F0060320F /* startVPN.app in Embed NetP Controller Apps */, - ); - name = "Embed NetP Controller Apps"; - runOnlyForDeploymentPostprocessing = 0; - }; 4B957C332AC7AE700062CA31 /* Embed Login Items */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = Contents/Library/LoginItems; dstSubfolderSpec = 1; files = ( - 4B957C342AC7AE700062CA31 /* DuckDuckGo Agent.app in Embed Login Items */, + 4B957C342AC7AE700062CA31 /* DuckDuckGo VPN.app in Embed Login Items */, 4B957C352AC7AE700062CA31 /* DuckDuckGo Notifications.app in Embed Login Items */, ); name = "Embed Login Items"; runOnlyForDeploymentPostprocessing = 0; }; - 4B957C362AC7AE700062CA31 /* Embed NetP Controller Apps */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 7; - files = ( - 4B957C372AC7AE700062CA31 /* enableOnDemand.app in Embed NetP Controller Apps */, - 4B957C382AC7AE700062CA31 /* stopVPN.app in Embed NetP Controller Apps */, - 4B957C392AC7AE700062CA31 /* startVPN.app in Embed NetP Controller Apps */, - ); - name = "Embed NetP Controller Apps"; - runOnlyForDeploymentPostprocessing = 0; - }; B6EC37E629B5DA2A001ACE79 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -4009,9 +4002,6 @@ 4B11060925903EAC0039B979 /* CoreDataEncryptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataEncryptionTests.swift; sourceTree = ""; }; 4B117F7C276C0CB5002F3D8C /* LocalStatisticsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStatisticsStoreTests.swift; sourceTree = ""; }; 4B139AFC26B60BD800894F82 /* NSImageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImageExtensions.swift; sourceTree = ""; }; - 4B18E3232A1D32B1005D0AAA /* NetworkProtectionStartVPN.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = NetworkProtectionStartVPN.xcconfig; path = Configuration/App/NetworkProtection/NetworkProtectionStartVPN.xcconfig; sourceTree = SOURCE_ROOT; }; - 4B18E3252A1D3581005D0AAA /* NetworkProtectionStopVPN.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = NetworkProtectionStopVPN.xcconfig; path = Configuration/App/NetworkProtection/NetworkProtectionStopVPN.xcconfig; sourceTree = SOURCE_ROOT; }; - 4B18E3272A1D3896005D0AAA /* NetworkProtectionVPNHelpersBase.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetworkProtectionVPNHelpersBase.xcconfig; sourceTree = ""; }; 4B1AD89D25FC27E200261379 /* Integration Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Integration Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 4B1AD8A125FC27E200261379 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4B1AD91625FC46FB00261379 /* CoreDataEncryptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataEncryptionTests.swift; sourceTree = ""; }; @@ -4019,24 +4009,16 @@ 4B1E6EEC27AB5E5100F51793 /* PasswordManagementListSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordManagementListSection.swift; sourceTree = ""; }; 4B1E6EEF27AB5E5D00F51793 /* NSPopUpButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSPopUpButtonView.swift; sourceTree = ""; }; 4B1E6EF027AB5E5D00F51793 /* PasswordManagementItemList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordManagementItemList.swift; sourceTree = ""; }; - 4B25375A2A11BE7300610219 /* com.duckduckgo.macos.browser.debug.network-protection-extension.systemextension */ = {isa = PBXFileReference; explicitFileType = "wrapper.system-extension"; includeInIndex = 0; path = "com.duckduckgo.macos.browser.debug.network-protection-extension.systemextension"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4B25375A2A11BE7300610219 /* com.duckduckgo.macos.vpn.network-extension.debug.systemextension */ = {isa = PBXFileReference; explicitFileType = "wrapper.system-extension"; includeInIndex = 0; path = "com.duckduckgo.macos.vpn.network-extension.debug.systemextension"; sourceTree = BUILT_PRODUCTS_DIR; }; 4B25376C2A11BF6D00610219 /* NetworkProtectionSystemExtension_Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = NetworkProtectionSystemExtension_Release.entitlements; sourceTree = ""; }; 4B25376D2A11BF6D00610219 /* NetworkProtectionSystemExtension_Debug.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = NetworkProtectionSystemExtension_Debug.entitlements; sourceTree = ""; }; 4B25376E2A11BF8B00610219 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4B25376F2A11BF8B00610219 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 4B29759628281F0900187C4E /* FirefoxEncryptionKeyReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirefoxEncryptionKeyReader.swift; sourceTree = ""; }; 4B2975982828285900187C4E /* FirefoxKeyReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirefoxKeyReaderTests.swift; sourceTree = ""; }; - 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo Agent.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DuckDuckGo Agent.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 4B2D06442A11CFBE00DE1F49 /* DuckDuckGoAgent.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DuckDuckGoAgent.entitlements; sourceTree = ""; }; - 4B2D064B2A11D04000DE1F49 /* DuckDuckGoAgent.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DuckDuckGoAgent.xcconfig; sourceTree = ""; }; - 4B2D064D2A11D04000DE1F49 /* DuckDuckGoAgentAppStore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DuckDuckGoAgentAppStore.xcconfig; sourceTree = ""; }; - 4B2D06502A11D19B00DE1F49 /* Info-AppStore.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-AppStore.plist"; sourceTree = ""; }; - 4B2D06512A11D19B00DE1F49 /* DuckDuckGoAgentAppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuckDuckGoAgentAppDelegate.swift; sourceTree = ""; }; - 4B2D06522A11D19B00DE1F49 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 4B2D06532A11D19B00DE1F49 /* DuckDuckGoAgentAppStore.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = DuckDuckGoAgentAppStore.entitlements; sourceTree = ""; }; - 4B2D06542A11D19B00DE1F49 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo VPN.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DuckDuckGo VPN.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4B2D06642A132F3A00DE1F49 /* NetworkProtectionAppExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetworkProtectionAppExtension.entitlements; sourceTree = ""; }; - 4B2D06692A13318400DE1F49 /* DuckDuckGo Agent App Store.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DuckDuckGo Agent App Store.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4B2D06692A13318400DE1F49 /* DuckDuckGo VPN App Store.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DuckDuckGo VPN App Store.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4B2E7D6226FF9D6500D2DB17 /* PrintingUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrintingUserScript.swift; sourceTree = ""; }; 4B379C1427BD91E3008A968E /* QuartzIdleStateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuartzIdleStateProvider.swift; sourceTree = ""; }; 4B379C1D27BDB7FF008A968E /* DeviceAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAuthenticator.swift; sourceTree = ""; }; @@ -4061,7 +4043,6 @@ 4B4D60512A0B293C00BCD287 /* ExtensionBase.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ExtensionBase.xcconfig; sourceTree = ""; }; 4B4D605E2A0B29FA00BCD287 /* NetworkProtectionBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionBundle.swift; sourceTree = ""; }; 4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionOptionKeyExtension.swift; sourceTree = ""; }; - 4B4D60622A0B29FA00BCD287 /* SystemExtensionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemExtensionManager.swift; sourceTree = ""; }; 4B4D60652A0B29FA00BCD287 /* NetworkProtectionNavBarButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionNavBarButtonModel.swift; sourceTree = ""; }; 4B4D60692A0B29FA00BCD287 /* NetworkProtection+ConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtection+ConvenienceInitializers.swift"; sourceTree = ""; }; 4B4D606A2A0B29FA00BCD287 /* NetworkProtectionControllerErrorStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionControllerErrorStore.swift; sourceTree = ""; }; @@ -4072,7 +4053,6 @@ 4B4D60762A0B29FA00BCD287 /* NetworkProtectionUNNotificationsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionUNNotificationsPresenter.swift; sourceTree = ""; }; 4B4D60782A0B29FA00BCD287 /* NetworkProtectionExtensionMachService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionExtensionMachService.swift; sourceTree = ""; }; 4B4D607C2A0B29FA00BCD287 /* UserText+NetworkProtectionExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserText+NetworkProtectionExtensions.swift"; sourceTree = ""; }; - 4B4D60832A0B29FA00BCD287 /* NetworkProtectionPixelEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionPixelEvent.swift; sourceTree = ""; }; 4B4D609C2A0B2C2300BCD287 /* DuckDuckGo_NetP_Release.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DuckDuckGo_NetP_Release.entitlements; sourceTree = ""; }; 4B4D609E2A0B2C2300BCD287 /* DuckDuckGo_NetP_Debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DuckDuckGo_NetP_Debug.entitlements; sourceTree = ""; }; 4B4D60D22A0C84F700BCD287 /* UserText+NetworkProtection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserText+NetworkProtection.swift"; sourceTree = ""; }; @@ -4088,9 +4068,6 @@ 4B59CC8B290083240058F2F6 /* ConnectBitwardenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectBitwardenViewModelTests.swift; sourceTree = ""; }; 4B5A4F4B27F3A5AA008FBD88 /* NSNotificationName+DataImport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNotificationName+DataImport.swift"; sourceTree = ""; }; 4B5F14C42A145D6A0060320F /* NetworkProtectionVPNController.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetworkProtectionVPNController.entitlements; sourceTree = ""; }; - 4B5F14C52A145D6A0060320F /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Main.swift; sourceTree = ""; }; - 4B5F14CB2A14702C0060320F /* startVPN.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = startVPN.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 4B5F14E12A1476BD0060320F /* stopVPN.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = stopVPN.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4B5F14F82A148B230060320F /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4B5F15032A1570F10060320F /* DuckDuckGoDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DuckDuckGoDebug.entitlements; sourceTree = ""; }; 4B5FF67726B602B100D42879 /* FirefoxDataImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirefoxDataImporter.swift; sourceTree = ""; }; @@ -4123,7 +4100,6 @@ 4B78A86A26BB3ADD0071BB16 /* BrowserImportSummaryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserImportSummaryViewController.swift; sourceTree = ""; }; 4B7A57CE279A4EF300B1C70E /* ChromePreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromePreferences.swift; sourceTree = ""; }; 4B7A60A0273E0BE400BBDFEB /* WKWebsiteDataStoreExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKWebsiteDataStoreExtension.swift; sourceTree = ""; }; - 4B7A94B329C16294000C7D4C /* ErrorWithParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorWithParameters.swift; sourceTree = ""; }; 4B85A47F28821CC500FC4C39 /* NSPasteboardItemExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSPasteboardItemExtension.swift; sourceTree = ""; }; 4B8A4DFE27C83B29005F40E8 /* SaveIdentityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveIdentityViewController.swift; sourceTree = ""; }; 4B8A4E0027C8447E005F40E8 /* SaveIdentityPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveIdentityPopover.swift; sourceTree = ""; }; @@ -4182,11 +4158,11 @@ 4B9DB0072A983B23000927DB /* Waitlist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Waitlist.swift; sourceTree = ""; }; 4B9DB0092A983B23000927DB /* ProductWaitlistRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductWaitlistRequest.swift; sourceTree = ""; }; 4B9DB00A2A983B24000927DB /* WaitlistRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WaitlistRequest.swift; sourceTree = ""; }; - 4B9DB00C2A983B24000927DB /* WaitlistViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WaitlistViewModel.swift; sourceTree = ""; }; + 4B9DB00C2A983B24000927DB /* NetworkProtectionWaitlistViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionWaitlistViewModel.swift; sourceTree = ""; }; 4B9DB00E2A983B24000927DB /* WaitlistStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WaitlistStorage.swift; sourceTree = ""; }; 4B9DB00F2A983B24000927DB /* WaitlistKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WaitlistKeychainStorage.swift; sourceTree = ""; }; - 4B9DB0132A983B24000927DB /* EnableNetworkProtectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnableNetworkProtectionView.swift; sourceTree = ""; }; - 4B9DB0142A983B24000927DB /* NetworkProtectionTermsAndConditionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionTermsAndConditionsView.swift; sourceTree = ""; }; + 4B9DB0132A983B24000927DB /* EnableWaitlistFeatureView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnableWaitlistFeatureView.swift; sourceTree = ""; }; + 4B9DB0142A983B24000927DB /* WaitlistTermsAndConditionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WaitlistTermsAndConditionsView.swift; sourceTree = ""; }; 4B9DB0152A983B24000927DB /* JoinedWaitlistView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoinedWaitlistView.swift; sourceTree = ""; }; 4B9DB0162A983B24000927DB /* InvitedToWaitlistView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvitedToWaitlistView.swift; sourceTree = ""; }; 4B9DB0172A983B24000927DB /* JoinWaitlistView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoinWaitlistView.swift; sourceTree = ""; }; @@ -4273,6 +4249,8 @@ 4BE6547D271FCD4D008D1D63 /* PasswordManagementNoteModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordManagementNoteModel.swift; sourceTree = ""; }; 4BE65482271FCD53008D1D63 /* CountryList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountryList.swift; sourceTree = ""; }; 4BE65484271FCD7B008D1D63 /* LoginFaviconView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginFaviconView.swift; sourceTree = ""; }; + 4BF0E5042AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionPixelEvent.swift; sourceTree = ""; }; + 4BF0E5112AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckDuckGoUserAgent.swift; sourceTree = ""; }; 4BF4951726C08395000547B8 /* ThirdPartyBrowserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartyBrowserTests.swift; sourceTree = ""; }; 4BF4EA4F27C71F26004E57C4 /* PasswordManagementListSectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordManagementListSectionTests.swift; sourceTree = ""; }; 4BF6961C28BE911100D402D4 /* RecentlyVisitedSiteModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentlyVisitedSiteModelTests.swift; sourceTree = ""; }; @@ -4300,29 +4278,45 @@ 7B1E819D27C8874900FF0E60 /* ContentOverlayViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentOverlayViewController.swift; sourceTree = ""; }; 7B25FE322AD12C990012AFAB /* NetworkProtectionMac */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = NetworkProtectionMac; sourceTree = ""; }; 7B2DDCF72A93A8BB0039D884 /* NetworkProtectionAppEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionAppEvents.swift; sourceTree = ""; }; - 7B2DDD012A93BAA60039D884 /* NetworkProtectionBouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionBouncer.swift; sourceTree = ""; }; - 7B2DDD042A93BEE20039D884 /* FeatureProtectedTunnelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureProtectedTunnelController.swift; sourceTree = ""; }; - 7B2DDD062A93C17D0039D884 /* UserText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserText.swift; sourceTree = ""; }; - 7B2DDD082A93C2440039D884 /* AppLauncher+DefaultInitializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppLauncher+DefaultInitializer.swift"; sourceTree = ""; }; 7B2E52242A5FEC09000C6D39 /* NetworkProtectionAgentNotificationsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionAgentNotificationsPresenter.swift; sourceTree = ""; }; + 7B3618C12ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionNavBarPopoverManager.swift; sourceTree = ""; }; 7B430EA02A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionSimulateFailureMenu.swift; sourceTree = ""; }; 7B4CE8DA26F02108009134B1 /* UI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 7B4CE8DE26F02108009134B1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7B4CE8E626F02134009134B1 /* TabBarTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarTests.swift; sourceTree = ""; }; 7B5291882A1697680022E406 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7B5291892A169BC90022E406 /* NetworkProtectionDeveloperID.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetworkProtectionDeveloperID.xcconfig; sourceTree = ""; }; - 7B736E5F2A4A22B700F9922A /* enableOnDemand.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = enableOnDemand.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7B76E6852AD5D77600186A84 /* XPCHelper */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = XPCHelper; sourceTree = ""; }; 7B934C3D2A866CFF00FC8F9C /* NetworkProtectionOnboardingMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionOnboardingMenu.swift; sourceTree = ""; }; 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+NetworkProtectionShared.swift"; sourceTree = ""; }; - 7B9459632A4A5BAF0012535A /* NetworkProtectionEnableOnDemand.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetworkProtectionEnableOnDemand.xcconfig; sourceTree = ""; }; + 7B96D0CF2ADFDA7E007E02C8 /* DuckDuckGoDBPTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DuckDuckGoDBPTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 7B96D0DB2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionPixelTests.swift; sourceTree = ""; }; + 7BA7CC0B2AD11D1E0042E5CE /* DuckDuckGoVPNAppStore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DuckDuckGoVPNAppStore.xcconfig; sourceTree = ""; }; + 7BA7CC0C2AD11D1E0042E5CE /* DuckDuckGoVPN.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DuckDuckGoVPN.xcconfig; sourceTree = ""; }; + 7BA7CC0E2AD11DC80042E5CE /* DuckDuckGoVPNAppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuckDuckGoVPNAppDelegate.swift; sourceTree = ""; }; + 7BA7CC0F2AD11DC80042E5CE /* Bundle+Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Configuration.swift"; sourceTree = ""; }; + 7BA7CC102AD11DC80042E5CE /* Info-AppStore.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-AppStore.plist"; sourceTree = ""; }; + 7BA7CC112AD11DC80042E5CE /* TunnelControllerIPCService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelControllerIPCService.swift; sourceTree = ""; }; + 7BA7CC122AD11DC80042E5CE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 7BA7CC132AD11DC80042E5CE /* AppLauncher+DefaultInitializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppLauncher+DefaultInitializer.swift"; sourceTree = ""; }; + 7BA7CC142AD11DC80042E5CE /* DuckDuckGoVPNAppStore.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = DuckDuckGoVPNAppStore.entitlements; sourceTree = ""; }; + 7BA7CC152AD11DC80042E5CE /* NetworkProtectionBouncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionBouncer.swift; sourceTree = ""; }; + 7BA7CC162AD11DC80042E5CE /* DuckDuckGoVPN.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = DuckDuckGoVPN.entitlements; sourceTree = ""; }; + 7BA7CC172AD11DC80042E5CE /* UserText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserText.swift; sourceTree = ""; }; + 7BA7CC182AD11DC80042E5CE /* DuckDuckGoVPNDebug.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = DuckDuckGoVPNDebug.entitlements; sourceTree = ""; }; + 7BA7CC1A2AD11DC80042E5CE /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7BA7CC4D2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionIPCTunnelController.swift; sourceTree = ""; }; 7BB108572A43375D000AB95F /* PFMoveApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFMoveApplication.h; sourceTree = ""; }; 7BB108582A43375D000AB95F /* PFMoveApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFMoveApplication.m; sourceTree = ""; }; 7BBD45B02A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugUtilities.swift; sourceTree = ""; }; - 7BD168912AD5B20600D24876 /* XPC */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = XPC; sourceTree = ""; }; + 7BD1688D2AD4A4C400D24876 /* NetworkExtensionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkExtensionController.swift; sourceTree = ""; }; 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeychainType+ClientDefault.swift"; sourceTree = ""; }; 7BD8679A2A9E9E000063B9F7 /* NetworkProtectionFeatureVisibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibility.swift; sourceTree = ""; }; 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugMenu.swift; sourceTree = ""; }; - 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionWaitlistMenu.swift; sourceTree = ""; }; + 7BEC182D2AD5D89C00D30536 /* SystemExtensionManager */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SystemExtensionManager; sourceTree = ""; }; + 7BF1A9D72AE054D300FCA683 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DBPUnitTests.xcconfig; sourceTree = ""; }; + 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift; sourceTree = ""; }; 7BFE95532A9DF2930081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+NetworkProtectionWaitlist.swift"; sourceTree = ""; }; 85012B0129133F9F003D0DCC /* NavigationBarPopovers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarPopovers.swift; sourceTree = ""; }; 850E8DFA2A6FEC5E00691187 /* BookmarksBarAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksBarAppearance.swift; sourceTree = ""; }; @@ -4333,7 +4327,6 @@ 85378D9F274E6F42007C5CBF /* NSNotificationName+EmailManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNotificationName+EmailManager.swift"; sourceTree = ""; }; 85378DA1274E7F25007C5CBF /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; 8546DE6125C03056000CA5E1 /* UserAgentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentTests.swift; sourceTree = ""; }; - 85480F8925CDC360009424E3 /* MainMenu.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = MainMenu.storyboard; sourceTree = ""; }; 85480FCE25D1AA22009424E3 /* ConfigurationStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationStore.swift; sourceTree = ""; }; 8553FF51257523760029327F /* URLSuggestedFilenameTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSuggestedFilenameTests.swift; sourceTree = ""; }; 85589E7927BBB8620038AD11 /* AddEditFavoriteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddEditFavoriteViewController.swift; sourceTree = ""; }; @@ -4658,6 +4651,8 @@ B626A76F29928C4100053070 /* MockPrivacyConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockPrivacyConfiguration.swift; sourceTree = ""; }; B62A233B29C322BC00D22475 /* NavigationProtectionIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProtectionIntegrationTests.swift; sourceTree = ""; }; B62A233F29C41D4400D22475 /* HistoryIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryIntegrationTests.swift; sourceTree = ""; }; + B62B48382ADE46FC000DECE5 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; + B62B483D2ADE48DE000DECE5 /* MenuBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBuilder.swift; sourceTree = ""; }; B62EB47B25BAD3BB005745C6 /* WKWebViewPrivateMethodsAvailabilityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKWebViewPrivateMethodsAvailabilityTests.swift; sourceTree = ""; }; B630793926731F2600DCEE41 /* FileDownloadManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileDownloadManagerTests.swift; sourceTree = ""; }; B630794126731F5400DCEE41 /* WKDownloadMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKDownloadMock.swift; sourceTree = ""; }; @@ -4739,6 +4734,7 @@ B68C2FB127706E6A00BF2C7D /* ProcessExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessExtension.swift; sourceTree = ""; }; B68C92C0274E3EF4002AC6B0 /* PopUpWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpWindow.swift; sourceTree = ""; }; B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelDataRecord.swift; sourceTree = ""; }; + B690152B2ACBF4DA00AD0BAB /* MenuPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuPreview.swift; sourceTree = ""; }; B693953C26F04BE70015B914 /* NibLoadable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; B693953D26F04BE70015B914 /* MouseOverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MouseOverView.swift; sourceTree = ""; }; B693953E26F04BE70015B914 /* FocusRingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FocusRingView.swift; sourceTree = ""; }; @@ -4789,7 +4785,6 @@ B6A9E47626146A570067D1B9 /* PixelEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelEvent.swift; sourceTree = ""; }; B6A9E47E26146A800067D1B9 /* PixelArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelArguments.swift; sourceTree = ""; }; B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelParameters.swift; sourceTree = ""; }; - B6A9E498261474120067D1B9 /* TimedPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimedPixel.swift; sourceTree = ""; }; B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FutureExtensionTests.swift; sourceTree = ""; }; B6AAAC2C260330580029438D /* PublishedAfter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedAfter.swift; sourceTree = ""; }; B6AAAC3D26048F690029438D /* RandomAccessCollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomAccessCollectionExtension.swift; sourceTree = ""; }; @@ -4831,7 +4826,6 @@ B6C2C9F52760B659005B7F0A /* Permissions.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Permissions.xcdatamodel; sourceTree = ""; }; B6C416A6294A4AE500C4F2E7 /* DuckPlayerTabExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerTabExtension.swift; sourceTree = ""; }; B6CA4823298CDC2E0067ECCE /* AdClickAttributionTabExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdClickAttributionTabExtensionTests.swift; sourceTree = ""; }; - B6CA482F298D04690067ECCE /* MockPrivacyConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPrivacyConfiguration.swift; sourceTree = ""; }; B6D574B12947224C008ED1B6 /* ContentBlockingTabExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentBlockingTabExtension.swift; sourceTree = ""; }; B6D574B329472253008ED1B6 /* FBProtectionTabExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FBProtectionTabExtension.swift; sourceTree = ""; }; B6DA06E02913AEDB00225DE2 /* TestNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestNavigationDelegate.swift; sourceTree = ""; }; @@ -4899,6 +4893,7 @@ 3192A2082A4C4CFF0084EA89 /* Bookmarks in Frameworks */, 3192A2092A4C4CFF0084EA89 /* ContentBlocking in Frameworks */, 3192A20A2A4C4CFF0084EA89 /* SwiftUIExtensions in Frameworks */, + 7B31FD8E2AD125760086AA24 /* NetworkProtectionIPC in Frameworks */, 3192A20B2A4C4CFF0084EA89 /* UserScript in Frameworks */, 3192A20C2A4C4CFF0084EA89 /* TrackerRadarKit in Frameworks */, 3192A20D2A4C4CFF0084EA89 /* Configuration in Frameworks */, @@ -4922,6 +4917,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7B5F9A752AE2BE4E002AEBC0 /* PixelKit in Frameworks */, B6F7128229F6820A00594A45 /* QuickLookUI.framework in Frameworks */, 984FD3BF299ACF35007334DD /* Bookmarks in Frameworks */, 37A5E2F0298AA1B20047046B /* Persistence in Frameworks */, @@ -4983,8 +4979,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7BEEA5122AD1235B00A9E72B /* NetworkProtectionIPC in Frameworks */, + 7BA7CC5F2AD1210C0042E5CE /* Networking in Frameworks */, + 7BEEA5162AD1236E00A9E72B /* NetworkProtectionUI in Frameworks */, + 7BFCB74E2ADE7E1A00DA3EA7 /* PixelKit in Frameworks */, EE7295ED2A545C0A008C0991 /* NetworkProtection in Frameworks */, - 4B2D064F2A11D0D000DE1F49 /* NetworkProtectionUI in Frameworks */, + 7B31FD882AD124620086AA24 /* (null) in Frameworks */, + 7BEC182F2AD5D8DC00D30536 /* SystemExtensionManager in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4992,8 +4993,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7BFCB7502ADE7E2300DA3EA7 /* PixelKit in Frameworks */, + 7BA7CC612AD1211C0042E5CE /* Networking in Frameworks */, + 7BEEA5142AD1236300A9E72B /* NetworkProtectionIPC in Frameworks */, EE7295EF2A545C12008C0991 /* NetworkProtection in Frameworks */, 4B2D067F2A1334D700DE1F49 /* NetworkProtectionUI in Frameworks */, + 7B31FD8A2AD124680086AA24 /* (null) in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5017,20 +5022,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 4B5F14C82A14702C0060320F /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4B5F14DE2A1476BC0060320F /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 4B957BD42AC7AE700062CA31 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -5044,6 +5035,8 @@ 4B957BDB2AC7AE700062CA31 /* ContentBlocking in Frameworks */, 4B957BDC2AC7AE700062CA31 /* SwiftUIExtensions in Frameworks */, 4B957BDD2AC7AE700062CA31 /* UserScript in Frameworks */, + 7BBD44282AD730A400D0A064 /* PixelKit in Frameworks */, + 7B31FD902AD1257B0086AA24 /* NetworkProtectionIPC in Frameworks */, 4B957BDE2AC7AE700062CA31 /* Configuration in Frameworks */, 4B957BDF2AC7AE700062CA31 /* Purchase in Frameworks */, 4B957BE02AC7AE700062CA31 /* Lottie in Frameworks */, @@ -5068,10 +5061,11 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 7B736E592A4A22B700F9922A /* Frameworks */ = { + 7B96D0CC2ADFDA7E007E02C8 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7B20D5C62ADFEC6E0053C42A /* PixelKitTestingUtilities in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5089,13 +5083,17 @@ 378F44E429B4BDE900899924 /* SwiftUIExtensions in Frameworks */, 1E950E432912A10D0051A99B /* UserScript in Frameworks */, CBC83E3629B63D380008E19C /* Configuration in Frameworks */, + 7B31FD8C2AD125620086AA24 /* NetworkProtectionIPC in Frameworks */, 1E3ED4FD2AC1E0290075F60F /* Purchase in Frameworks */, 4B2AAAF529E70DEA0026AFC0 /* Lottie in Frameworks */, AA06B6B72672AF8100F541C5 /* Sparkle in Frameworks */, + 7BF7705F2AD6C999001C9182 /* PixelKit in Frameworks */, B6B77BE8297973D4001E68A1 /* Navigation in Frameworks */, 3739326729AE4B42009346AE /* DDGSync in Frameworks */, + 7BA59C9B2AE18B49009A97B1 /* SystemExtensionManager in Frameworks */, 371D00E129D8509400EC8598 /* OpenSSL in Frameworks */, 1E950E412912A10D0051A99B /* PrivacyDashboard in Frameworks */, + 4BF0E50E2AD2555D00FFEC9E /* (null) in Frameworks */, 37DF000529F9C056002B7D3E /* SyncDataProviders in Frameworks */, 37BA812D29B3CD690053F1A3 /* SyncUI in Frameworks */, 4B4D60B12A0C83B900BCD287 /* NetworkProtectionUI in Frameworks */, @@ -5462,6 +5460,7 @@ children = ( 373A26962964CF0B0043FC57 /* TestsTargetsBase.xcconfig */, 378B588D295CF447002C0CC0 /* UnitTests.xcconfig */, + 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */, 378B58C8295CF9A7002C0CC0 /* IntegrationTests.xcconfig */, 37E75733296F4F0500E1C162 /* UnitTestsAppStore.xcconfig */, 37E75734296F4F0500E1C162 /* IntegrationTestsAppStore.xcconfig */, @@ -5491,7 +5490,8 @@ 378F44E229B4B7B600899924 /* SwiftUIExtensions */, 37BA812B29B3CB8A0053F1A3 /* SyncUI */, 1E862A882A9FC01200F84D4B /* Subscription */, - 7BD168912AD5B20600D24876 /* XPC */, + 7BEC182D2AD5D89C00D30536 /* SystemExtensionManager */, + 7B76E6852AD5D77600186A84 /* XPCHelper */, ); path = LocalPackages; sourceTree = ""; @@ -5647,13 +5647,9 @@ 4B18E3222A1D31E4005D0AAA /* NetworkProtection */ = { isa = PBXGroup; children = ( - 4B2D064B2A11D04000DE1F49 /* DuckDuckGoAgent.xcconfig */, - 4B2D064D2A11D04000DE1F49 /* DuckDuckGoAgentAppStore.xcconfig */, 4B4BEC182A11B3EA001D9AC5 /* DuckDuckGoNotifications.xcconfig */, - 7B9459632A4A5BAF0012535A /* NetworkProtectionEnableOnDemand.xcconfig */, - 4B18E3272A1D3896005D0AAA /* NetworkProtectionVPNHelpersBase.xcconfig */, - 4B18E3232A1D32B1005D0AAA /* NetworkProtectionStartVPN.xcconfig */, - 4B18E3252A1D3581005D0AAA /* NetworkProtectionStopVPN.xcconfig */, + 7BA7CC0C2AD11D1E0042E5CE /* DuckDuckGoVPN.xcconfig */, + 7BA7CC0B2AD11D1E0042E5CE /* DuckDuckGoVPNAppStore.xcconfig */, ); path = NetworkProtection; sourceTree = ""; @@ -5697,23 +5693,6 @@ path = NetworkProtectionSystemExtension; sourceTree = ""; }; - 4B2D063A2A11CFBD00DE1F49 /* DuckDuckGoAgent */ = { - isa = PBXGroup; - children = ( - 4B2D06512A11D19B00DE1F49 /* DuckDuckGoAgentAppDelegate.swift */, - 7B2DDD082A93C2440039D884 /* AppLauncher+DefaultInitializer.swift */, - 7B2DDD042A93BEE20039D884 /* FeatureProtectedTunnelController.swift */, - 7B2DDD012A93BAA60039D884 /* NetworkProtectionBouncer.swift */, - 7B2DDD062A93C17D0039D884 /* UserText.swift */, - 4B2D06522A11D19B00DE1F49 /* Assets.xcassets */, - 4B2D06442A11CFBE00DE1F49 /* DuckDuckGoAgent.entitlements */, - 4B2D06532A11D19B00DE1F49 /* DuckDuckGoAgentAppStore.entitlements */, - 4B2D06502A11D19B00DE1F49 /* Info-AppStore.plist */, - 4B2D06542A11D19B00DE1F49 /* Info.plist */, - ); - path = DuckDuckGoAgent; - sourceTree = ""; - }; 4B379C1C27BDB7EA008A968E /* DeviceAuthentication */ = { isa = PBXGroup; children = ( @@ -5805,7 +5784,7 @@ isa = PBXGroup; children = ( 4BCF15D52ABB83D70083F6DF /* NetworkProtectionRemoteMessaging */, - 4B4D60622A0B29FA00BCD287 /* SystemExtensionManager.swift */, + 7BA7CC4D2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift */, ); path = DeveloperIDTarget; sourceTree = ""; @@ -5819,9 +5798,10 @@ 7B430EA02A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift */, 7BBD45B02A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift */, 7B934C3D2A866CFF00FC8F9C /* NetworkProtectionOnboardingMenu.swift */, - 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistMenu.swift */, + 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift */, B602E81C2A1E25B0006D261F /* NEOnDemandRuleExtension.swift */, 4B4D60652A0B29FA00BCD287 /* NetworkProtectionNavBarButtonModel.swift */, + 7B3618C12ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift */, 4B8F52402A18326600BE7131 /* NetworkProtectionTunnelController.swift */, 4B4D60692A0B29FA00BCD287 /* NetworkProtection+ConvenienceInitializers.swift */, 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */, @@ -5882,7 +5862,6 @@ 4B4D607D2A0B29FA00BCD287 /* NetworkExtensionTargets */ = { isa = PBXGroup; children = ( - 4B4D60832A0B29FA00BCD287 /* NetworkProtectionPixelEvent.swift */, EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */, ); path = NetworkExtensionTargets; @@ -5909,14 +5888,6 @@ path = Bitwarden; sourceTree = ""; }; - 4B5F14C32A145D6A0060320F /* NetworkProtectionVPNController */ = { - isa = PBXGroup; - children = ( - 4B5F14C52A145D6A0060320F /* Main.swift */, - ); - path = NetworkProtectionVPNController; - sourceTree = ""; - }; 4B5F14F72A148B230060320F /* NetworkProtectionAppExtension */ = { isa = PBXGroup; children = ( @@ -5928,6 +5899,7 @@ 4B6160D125B14E5E007DE5B2 /* ContentBlocker */ = { isa = PBXGroup; children = ( + B68D21CE2ACBC9E7002DA3C2 /* Mocks */, EAA29AEB278D2E51007070CF /* fonts */, 026ADE1326C3010C002518EE /* macos-config.json */, 9833913027AAA4B500DAF119 /* trackerData.json */, @@ -6103,9 +6075,7 @@ EA1E52B42798CF98002EC53C /* ClickToLoadModelTests.swift */, EA8AE769279FBDB20078943E /* ClickToLoadTDSTests.swift */, B610F2E527AA388100FCEBE9 /* ContentBlockingUpdatingTests.swift */, - B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */, B6AE39F029373AF200C37AA4 /* EmptyAttributionRulesProver.swift */, - B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */, CBDD5DE229A67F2700832877 /* MockConfigurationStore.swift */, ); path = ContentBlocker; @@ -6161,7 +6131,7 @@ 4B9DB00B2A983B24000927DB /* Models */ = { isa = PBXGroup; children = ( - 4B9DB00C2A983B24000927DB /* WaitlistViewModel.swift */, + 4B9DB00C2A983B24000927DB /* NetworkProtectionWaitlistViewModel.swift */, ); path = Models; sourceTree = ""; @@ -6190,8 +6160,8 @@ 4B9DB0122A983B24000927DB /* WaitlistSteps */ = { isa = PBXGroup; children = ( - 4B9DB0132A983B24000927DB /* EnableNetworkProtectionView.swift */, - 4B9DB0142A983B24000927DB /* NetworkProtectionTermsAndConditionsView.swift */, + 4B9DB0132A983B24000927DB /* EnableWaitlistFeatureView.swift */, + 4B9DB0142A983B24000927DB /* WaitlistTermsAndConditionsView.swift */, 4B9DB0152A983B24000927DB /* JoinedWaitlistView.swift */, 4B9DB0162A983B24000927DB /* InvitedToWaitlistView.swift */, 4B9DB0172A983B24000927DB /* JoinWaitlistView.swift */, @@ -6279,7 +6249,6 @@ B6AAAC2C260330580029438D /* PublishedAfter.swift */, 4BB88B4F25B7BA2B006F6B06 /* TabInstrumentation.swift */, 85C6A29525CC1FFD00EEB5F1 /* UserDefaultsWrapper.swift */, - 4B7A94B329C16294000C7D4C /* ErrorWithParameters.swift */, 4B9579202AC687170062CA31 /* HardwareModel.swift */, ); path = Utilities; @@ -6492,6 +6461,35 @@ path = UITests; sourceTree = ""; }; + 7B96D0D02ADFDA7F007E02C8 /* DuckDuckGoDBPTests */ = { + isa = PBXGroup; + children = ( + 7BF1A9D72AE054D300FCA683 /* Info.plist */, + 7B96D0DB2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift */, + ); + path = DuckDuckGoDBPTests; + sourceTree = ""; + }; + 7BA7CC0D2AD11DC80042E5CE /* DuckDuckGoVPN */ = { + isa = PBXGroup; + children = ( + 7BA7CC132AD11DC80042E5CE /* AppLauncher+DefaultInitializer.swift */, + 7BA7CC0F2AD11DC80042E5CE /* Bundle+Configuration.swift */, + 7BA7CC0E2AD11DC80042E5CE /* DuckDuckGoVPNAppDelegate.swift */, + 7BD1688D2AD4A4C400D24876 /* NetworkExtensionController.swift */, + 7BA7CC152AD11DC80042E5CE /* NetworkProtectionBouncer.swift */, + 7BA7CC112AD11DC80042E5CE /* TunnelControllerIPCService.swift */, + 7BA7CC172AD11DC80042E5CE /* UserText.swift */, + 7BA7CC122AD11DC80042E5CE /* Assets.xcassets */, + 7BA7CC162AD11DC80042E5CE /* DuckDuckGoVPN.entitlements */, + 7BA7CC182AD11DC80042E5CE /* DuckDuckGoVPNDebug.entitlements */, + 7BA7CC142AD11DC80042E5CE /* DuckDuckGoVPNAppStore.entitlements */, + 7BA7CC1A2AD11DC80042E5CE /* Info.plist */, + 7BA7CC102AD11DC80042E5CE /* Info-AppStore.plist */, + ); + path = DuckDuckGoVPN; + sourceTree = ""; + }; 7BB108552A43375D000AB95F /* LocalThirdParty */ = { isa = PBXGroup; children = ( @@ -6607,6 +6605,7 @@ 4B1E6EEF27AB5E5D00F51793 /* NSPopUpButtonView.swift */, 31C3CE0128EDC1E70002C24A /* CustomRoundedCornersShape.swift */, 378F44EA29B4C73E00899924 /* View+RoundedCorners.swift */, + B690152B2ACBF4DA00AD0BAB /* MenuPreview.swift */, ); path = SwiftUI; sourceTree = ""; @@ -6829,13 +6828,11 @@ B6F56566299A414300A04298 /* WKWebViewMockingExtension.swift */, B603972B29BEDF2100902A34 /* ExpectedNavigationExtension.swift */, B603970F29B9D67E00902A34 /* PublishersExtensions.swift */, - B626A76F29928C4100053070 /* MockPrivacyConfiguration.swift */, B630E7FD29C887ED00363609 /* NSErrorAdditionalInfo.swift */, B693956626F352940015B914 /* TestsBridging.h */, B6DA06E02913AEDB00225DE2 /* TestNavigationDelegate.swift */, B60C6F8029B1B4AD007BFAA8 /* TestRunHelper.swift */, B60C6F7D29B1B41D007BFAA8 /* TestRunHelperInitializer.m */, - B6CA482F298D04690067ECCE /* MockPrivacyConfiguration.swift */, ); path = Common; sourceTree = ""; @@ -6897,6 +6894,7 @@ isa = PBXGroup; children = ( 14505A07256084EF00272CC6 /* UserAgent.swift */, + 4BF0E5112AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift */, ); path = Model; sourceTree = ""; @@ -6935,6 +6933,7 @@ AA4D700525545EDE00C3411E /* AppDelegate */ = { isa = PBXGroup; children = ( + B62B48382ADE46FC000DECE5 /* Application.swift */, AA585D81248FD31100E9A3E2 /* AppDelegate.swift */, AA4D700625545EF800C3411E /* URLEventHandler.swift */, AA4FF40B2624751A004E2377 /* GrammarFeaturesManager.swift */, @@ -6967,10 +6966,10 @@ 4B1AD89E25FC27E200261379 /* IntegrationTests */, 7B4CE8DB26F02108009134B1 /* UITests */, B6EC37E929B5DA2A001ACE79 /* tests-server */, + 7B96D0D02ADFDA7F007E02C8 /* DuckDuckGoDBPTests */, 4B5F14F72A148B230060320F /* NetworkProtectionAppExtension */, 4B25375C2A11BE7500610219 /* NetworkProtectionSystemExtension */, - 4B5F14C32A145D6A0060320F /* NetworkProtectionVPNController */, - 4B2D063A2A11CFBD00DE1F49 /* DuckDuckGoAgent */, + 7BA7CC0D2AD11DC80042E5CE /* DuckDuckGoVPN */, AA585D7F248FD31100E9A3E2 /* Products */, 85AE2FF024A33A2D002D507F /* Frameworks */, ); @@ -6989,14 +6988,12 @@ B6EC37E829B5DA2A001ACE79 /* tests-server */, 4B4D603D2A0B290200BCD287 /* NetworkProtectionAppExtension.appex */, 4B4BEC202A11B4E2001D9AC5 /* DuckDuckGo Notifications.app */, - 4B25375A2A11BE7300610219 /* com.duckduckgo.macos.browser.debug.network-protection-extension.systemextension */, - 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo Agent.app */, - 4B2D06692A13318400DE1F49 /* DuckDuckGo Agent App Store.app */, - 4B5F14CB2A14702C0060320F /* startVPN.app */, - 4B5F14E12A1476BD0060320F /* stopVPN.app */, - 7B736E5F2A4A22B700F9922A /* enableOnDemand.app */, + 4B25375A2A11BE7300610219 /* com.duckduckgo.macos.vpn.network-extension.debug.systemextension */, + 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo VPN.app */, + 4B2D06692A13318400DE1F49 /* DuckDuckGo VPN App Store.app */, 3192A26E2A4C4CFF0084EA89 /* DuckDuckGoDBP.app */, 4B957C412AC7AE700062CA31 /* DuckDuckGo Privacy Pro.app */, + 7B96D0CF2ADFDA7E007E02C8 /* DuckDuckGoDBPTests.xctest */, ); name = Products; sourceTree = ""; @@ -7549,11 +7546,11 @@ isa = PBXGroup; children = ( AA97BF4525135DD30014931A /* ApplicationDockMenu.swift */, - 85480F8925CDC360009424E3 /* MainMenu.storyboard */, + AAAB9113288EB1D600A057A9 /* CleanThisHistoryMenuItem.swift */, + AA7E919628746BCC00AB6B62 /* HistoryMenu.swift */, AA4BBA3A25C58FA200C4FB0F /* MainMenu.swift */, AA6EF9B425081B4C004754E6 /* MainMenuActions.swift */, - AA7E919628746BCC00AB6B62 /* HistoryMenu.swift */, - AAAB9113288EB1D600A057A9 /* CleanThisHistoryMenuItem.swift */, + B62B483D2ADE48DE000DECE5 /* MenuBuilder.swift */, AAAB9115288EB46B00A057A9 /* VisitMenuItem.swift */, ); path = Menus; @@ -8249,6 +8246,16 @@ path = StateRestoration; sourceTree = ""; }; + B68D21CE2ACBC9E7002DA3C2 /* Mocks */ = { + isa = PBXGroup; + children = ( + B6BDD9F429409DDD00F68088 /* ContentBlockingMock.swift */, + B626A76F29928C4100053070 /* MockPrivacyConfiguration.swift */, + B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */, + ); + path = Mocks; + sourceTree = ""; + }; B69B50332726A10700758A2B /* ATB */ = { isa = PBXGroup; children = ( @@ -8305,7 +8312,6 @@ 857E5AF32A79044900FC0FB4 /* Experiment */, B69B50332726A10700758A2B /* ATB */, B6A9E45226142B070067D1B9 /* Pixel.swift */, - B6A9E498261474120067D1B9 /* TimedPixel.swift */, 4B67853E2AA7C726008A5004 /* DailyPixel.swift */, B6A9E47626146A570067D1B9 /* PixelEvent.swift */, B6A9E47E26146A800067D1B9 /* PixelArguments.swift */, @@ -8489,6 +8495,7 @@ isa = PBXGroup; children = ( EEAD7A6E2A1D3E1F002A24E7 /* AppLauncher.swift */, + 4BF0E5042AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift */, B6F92BA42A691A44002ABA6B /* NetworkProtectionUserDefaultsConstants.swift */, 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */, 7BFE95532A9DF2930081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift */, @@ -8518,15 +8525,11 @@ 3192A2052A4C4CFF0084EA89 /* Frameworks */, 3192A2192A4C4CFF0084EA89 /* Resources */, 3192A2602A4C4CFF0084EA89 /* Embed Login Items */, - 3192A2632A4C4CFF0084EA89 /* Embed NetP Controller Apps */, - 3192A2672A4C4CFF0084EA89 /* Replace VPN Controllers with Symlinks */, - 3192A2682A4C4CFF0084EA89 /* Embed System Network Extension */, ); buildRules = ( ); dependencies = ( 31929F7C2A4C4CFF0084EA89 /* PBXTargetDependency */, - 31929F7D2A4C4CFF0084EA89 /* PBXTargetDependency */, ); name = "DuckDuckGo DBP"; packageProductDependencies = ( @@ -8550,6 +8553,7 @@ 31929F952A4C4CFF0084EA89 /* NetworkProtectionUI */, 31A93F502A5D8AF0008BB88D /* DataBrokerProtection */, 9DB6E7252AA0DC6600A17F3C /* LoginItems */, + 7B31FD8D2AD125760086AA24 /* NetworkProtectionIPC */, ); productName = DuckDuckGo; productReference = 3192A26E2A4C4CFF0084EA89 /* DuckDuckGoDBP.app */; @@ -8590,6 +8594,7 @@ B6EC37FE29B8D915001ACE79 /* Configuration */, 37DF000629F9C061002B7D3E /* SyncDataProviders */, 9DC70B192AA1FA5B005A844B /* LoginItems */, + 7B5F9A742AE2BE4E002AEBC0 /* PixelKit */, ); productName = DuckDuckGo; productReference = 3706FD05293F65D500E42796 /* DuckDuckGo App Store.app */; @@ -8679,34 +8684,40 @@ EE7295E82A545BC4008C0991 /* NetworkProtection */, ); productName = NetworkProtectionSystemExtension; - productReference = 4B25375A2A11BE7300610219 /* com.duckduckgo.macos.browser.debug.network-protection-extension.systemextension */; + productReference = 4B25375A2A11BE7300610219 /* com.duckduckgo.macos.vpn.network-extension.debug.systemextension */; productType = "com.apple.product-type.system-extension"; }; - 4B2D06382A11CFBA00DE1F49 /* DuckDuckGoAgent */ = { + 4B2D06382A11CFBA00DE1F49 /* DuckDuckGoVPN */ = { isa = PBXNativeTarget; - buildConfigurationList = 4B2D06452A11CFBE00DE1F49 /* Build configuration list for PBXNativeTarget "DuckDuckGoAgent" */; + buildConfigurationList = 4B2D06452A11CFBE00DE1F49 /* Build configuration list for PBXNativeTarget "DuckDuckGoVPN" */; buildPhases = ( 4B2D06352A11CFBA00DE1F49 /* Sources */, 4B2D06362A11CFBA00DE1F49 /* Frameworks */, 4B2D06372A11CFBA00DE1F49 /* Resources */, 4B2D065C2A11D23600DE1F49 /* Copy Assets */, + 7B31FD922AD126C40086AA24 /* Embed System Network Extension */, ); buildRules = ( ); dependencies = ( + 7BEC18312AD5DA3300D30536 /* PBXTargetDependency */, ); - name = DuckDuckGoAgent; + name = DuckDuckGoVPN; packageProductDependencies = ( - 4B2D064E2A11D0D000DE1F49 /* NetworkProtectionUI */, EE7295EC2A545C0A008C0991 /* NetworkProtection */, + 7BA7CC5E2AD1210C0042E5CE /* Networking */, + 7BEEA5112AD1235B00A9E72B /* NetworkProtectionIPC */, + 7BEEA5152AD1236E00A9E72B /* NetworkProtectionUI */, + 7BEC182E2AD5D8DC00D30536 /* SystemExtensionManager */, + 7BFCB74D2ADE7E1A00DA3EA7 /* PixelKit */, ); productName = DuckDuckGoAgent; - productReference = 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo Agent.app */; + productReference = 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo VPN.app */; productType = "com.apple.product-type.application"; }; - 4B2D06682A13318400DE1F49 /* DuckDuckGoAgentAppStore */ = { + 4B2D06682A13318400DE1F49 /* DuckDuckGoVPNAppStore */ = { isa = PBXNativeTarget; - buildConfigurationList = 4B2D06752A13318600DE1F49 /* Build configuration list for PBXNativeTarget "DuckDuckGoAgentAppStore" */; + buildConfigurationList = 4B2D06752A13318600DE1F49 /* Build configuration list for PBXNativeTarget "DuckDuckGoVPNAppStore" */; buildPhases = ( 4B2D06652A13318400DE1F49 /* Sources */, 4B2D06662A13318400DE1F49 /* Frameworks */, @@ -8717,13 +8728,16 @@ ); dependencies = ( ); - name = DuckDuckGoAgentAppStore; + name = DuckDuckGoVPNAppStore; packageProductDependencies = ( 4B2D067E2A1334D700DE1F49 /* NetworkProtectionUI */, EE7295EE2A545C12008C0991 /* NetworkProtection */, + 7BA7CC602AD1211C0042E5CE /* Networking */, + 7BEEA5132AD1236300A9E72B /* NetworkProtectionIPC */, + 7BFCB74F2ADE7E2300DA3EA7 /* PixelKit */, ); productName = DuckDuckGoAgentAppStore; - productReference = 4B2D06692A13318400DE1F49 /* DuckDuckGo Agent App Store.app */; + productReference = 4B2D06692A13318400DE1F49 /* DuckDuckGo VPN App Store.app */; productType = "com.apple.product-type.application"; }; 4B4BEC1F2A11B4E2001D9AC5 /* DuckDuckGoNotifications */ = { @@ -8770,38 +8784,6 @@ productReference = 4B4D603D2A0B290200BCD287 /* NetworkProtectionAppExtension.appex */; productType = "com.apple.product-type.app-extension"; }; - 4B5F14CA2A14702C0060320F /* startVPN */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4B5F14D72A14702E0060320F /* Build configuration list for PBXNativeTarget "startVPN" */; - buildPhases = ( - 4B5F14C72A14702C0060320F /* Sources */, - 4B5F14C82A14702C0060320F /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = startVPN; - productName = startVPN; - productReference = 4B5F14CB2A14702C0060320F /* startVPN.app */; - productType = "com.apple.product-type.application"; - }; - 4B5F14E02A1476BC0060320F /* stopVPN */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4B5F14ED2A1476C20060320F /* Build configuration list for PBXNativeTarget "stopVPN" */; - buildPhases = ( - 4B5F14DD2A1476BC0060320F /* Sources */, - 4B5F14DE2A1476BC0060320F /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = stopVPN; - productName = stopVPN; - productReference = 4B5F14E12A1476BD0060320F /* stopVPN.app */; - productType = "com.apple.product-type.application"; - }; 4B9579252AC7AE700062CA31 /* DuckDuckGo Privacy Pro */ = { isa = PBXNativeTarget; buildConfigurationList = 4B957C3C2AC7AE700062CA31 /* Build configuration list for PBXNativeTarget "DuckDuckGo Privacy Pro" */; @@ -8814,15 +8796,12 @@ 4B957BEC2AC7AE700062CA31 /* Resources */, 4B957C322AC7AE700062CA31 /* Make /Applications symlink, remove app on Clean build */, 4B957C332AC7AE700062CA31 /* Embed Login Items */, - 4B957C362AC7AE700062CA31 /* Embed NetP Controller Apps */, - 4B957C3A2AC7AE700062CA31 /* Replace VPN Controllers with Symlinks */, - 4B957C3B2AC7AE700062CA31 /* Embed System Network Extension */, + 7B31FD942AD126FA0086AA24 /* Embed System Network Extension */, ); buildRules = ( ); dependencies = ( 4B9579262AC7AE700062CA31 /* PBXTargetDependency */, - 4B9579272AC7AE700062CA31 /* PBXTargetDependency */, ); name = "DuckDuckGo Privacy Pro"; packageProductDependencies = ( @@ -8848,6 +8827,7 @@ 4B9579402AC7AE700062CA31 /* Subscription */, 4B9579412AC7AE700062CA31 /* Account */, 4B9579422AC7AE700062CA31 /* Purchase */, + 7B31FD8F2AD1257B0086AA24 /* NetworkProtectionIPC */, ); productName = DuckDuckGo; productReference = 4B957C412AC7AE700062CA31 /* DuckDuckGo Privacy Pro.app */; @@ -8871,21 +8851,27 @@ productReference = 7B4CE8DA26F02108009134B1 /* UI Tests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; - 7B736E562A4A22B700F9922A /* enableOnDemand */ = { + 7B96D0CE2ADFDA7E007E02C8 /* DuckDuckGoDBPTests */ = { isa = PBXNativeTarget; - buildConfigurationList = 7B736E5A2A4A22B700F9922A /* Build configuration list for PBXNativeTarget "enableOnDemand" */; + buildConfigurationList = 7B96D0D52ADFDA7F007E02C8 /* Build configuration list for PBXNativeTarget "DuckDuckGoDBPTests" */; buildPhases = ( - 7B736E572A4A22B700F9922A /* Sources */, - 7B736E592A4A22B700F9922A /* Frameworks */, + 7B96D0CB2ADFDA7E007E02C8 /* Sources */, + 7B96D0CC2ADFDA7E007E02C8 /* Frameworks */, + 7B96D0CD2ADFDA7E007E02C8 /* Resources */, ); buildRules = ( ); dependencies = ( + 7B20D5C82ADFEC730053C42A /* PBXTargetDependency */, + 7B96D0D42ADFDA7F007E02C8 /* PBXTargetDependency */, ); - name = enableOnDemand; - productName = stopVPN; - productReference = 7B736E5F2A4A22B700F9922A /* enableOnDemand.app */; - productType = "com.apple.product-type.application"; + name = DuckDuckGoDBPTests; + packageProductDependencies = ( + 7B20D5C52ADFEC6E0053C42A /* PixelKitTestingUtilities */, + ); + productName = DuckDuckGoDBPTests; + productReference = 7B96D0CF2ADFDA7E007E02C8 /* DuckDuckGoDBPTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; }; AA585D7D248FD31100E9A3E2 /* DuckDuckGo Privacy Browser */ = { isa = PBXNativeTarget; @@ -8899,15 +8885,11 @@ AA585D7C248FD31100E9A3E2 /* Resources */, B6F2C8722A7A4C7D000498CF /* Make /Applications symlink, remove app on Clean build */, 4B2D065D2A11D2AE00DE1F49 /* Embed Login Items */, - 4B5F14F32A14823D0060320F /* Embed NetP Controller Apps */, - 4B5F14F62A14825A0060320F /* Replace VPN Controllers with Symlinks */, - 7B6469992A165AE00095095A /* Embed System Network Extension */, ); buildRules = ( ); dependencies = ( 4B5F14FC2A15291D0060320F /* PBXTargetDependency */, - 4B2537642A11BE7600610219 /* PBXTargetDependency */, ); name = "DuckDuckGo Privacy Browser"; packageProductDependencies = ( @@ -8932,6 +8914,9 @@ 9DB6E7232AA0DC5800A17F3C /* LoginItems */, 1EC88CA02AC1DD63003A4471 /* Account */, 1E3ED4FC2AC1E0290075F60F /* Purchase */, + 7B31FD8B2AD125620086AA24 /* NetworkProtectionIPC */, + 7BF7705E2AD6C999001C9182 /* PixelKit */, + 7BA59C9A2AE18B49009A97B1 /* SystemExtensionManager */, ); productName = DuckDuckGo; productReference = AA585D7E248FD31100E9A3E2 /* DuckDuckGo.app */; @@ -8985,7 +8970,7 @@ AA585D76248FD31100E9A3E2 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1430; + LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1400; ORGANIZATIONNAME = DuckDuckGo; TargetAttributes = { @@ -9016,16 +9001,14 @@ 4B4D603C2A0B290200BCD287 = { CreatedOnToolsVersion = 14.3; }; - 4B5F14CA2A14702C0060320F = { - CreatedOnToolsVersion = 14.3; - }; - 4B5F14E02A1476BC0060320F = { - CreatedOnToolsVersion = 14.3; - }; 7B4CE8D926F02108009134B1 = { CreatedOnToolsVersion = 12.5.1; TestTargetID = AA585D7D248FD31100E9A3E2; }; + 7B96D0CE2ADFDA7E007E02C8 = { + CreatedOnToolsVersion = 15.0; + TestTargetID = 31929F7B2A4C4CFF0084EA89; + }; AA585D7D248FD31100E9A3E2 = { CreatedOnToolsVersion = 11.5; }; @@ -9070,12 +9053,10 @@ 4B4D603C2A0B290200BCD287 /* NetworkProtectionAppExtension */, 4B2537592A11BE7300610219 /* NetworkProtectionSystemExtension */, 4B4BEC1F2A11B4E2001D9AC5 /* DuckDuckGoNotifications */, - 4B2D06382A11CFBA00DE1F49 /* DuckDuckGoAgent */, - 4B2D06682A13318400DE1F49 /* DuckDuckGoAgentAppStore */, - 4B5F14CA2A14702C0060320F /* startVPN */, - 4B5F14E02A1476BC0060320F /* stopVPN */, - 7B736E562A4A22B700F9922A /* enableOnDemand */, + 4B2D06382A11CFBA00DE1F49 /* DuckDuckGoVPN */, + 4B2D06682A13318400DE1F49 /* DuckDuckGoVPNAppStore */, 31929F7B2A4C4CFF0084EA89 /* DuckDuckGo DBP */, + 7B96D0CE2ADFDA7E007E02C8 /* DuckDuckGoDBPTests */, 4B9579252AC7AE700062CA31 /* DuckDuckGo Privacy Pro */, ); }; @@ -9120,7 +9101,6 @@ 3192A23A2A4C4CFF0084EA89 /* PrivacyDashboard.storyboard in Resources */, 3192A23B2A4C4CFF0084EA89 /* shield.json in Resources */, 3192A23C2A4C4CFF0084EA89 /* TabBarViewItem.xib in Resources */, - 3192A23D2A4C4CFF0084EA89 /* MainMenu.storyboard in Resources */, 3192A23E2A4C4CFF0084EA89 /* httpsMobileV2FalsePositives.json in Resources */, 3192A23F2A4C4CFF0084EA89 /* BookmarksBar.storyboard in Resources */, 3192A2402A4C4CFF0084EA89 /* trackers-1.json in Resources */, @@ -9196,7 +9176,6 @@ 3706FCD1293F65D500E42796 /* PrivacyDashboard.storyboard in Resources */, 3706FCD2293F65D500E42796 /* shield.json in Resources */, 3706FCD4293F65D500E42796 /* TabBarViewItem.xib in Resources */, - 3706FCD5293F65D500E42796 /* MainMenu.storyboard in Resources */, 3706FCD6293F65D500E42796 /* httpsMobileV2FalsePositives.json in Resources */, 3706FCD8293F65D500E42796 /* BookmarksBar.storyboard in Resources */, 3706FCD9293F65D500E42796 /* trackers-1.json in Resources */, @@ -9267,7 +9246,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4B2D06582A11D19B00DE1F49 /* Assets.xcassets in Resources */, + 7BA7CC482AD11E5C0042E5CE /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -9275,7 +9254,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7B838C382A1DD8DD00E05A13 /* Assets.xcassets in Resources */, + 7BA7CC472AD11E5C0042E5CE /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -9325,7 +9304,6 @@ 4B957C0C2AC7AE700062CA31 /* PrivacyDashboard.storyboard in Resources */, 4B957C0D2AC7AE700062CA31 /* shield.json in Resources */, 4B957C0E2AC7AE700062CA31 /* TabBarViewItem.xib in Resources */, - 4B957C0F2AC7AE700062CA31 /* MainMenu.storyboard in Resources */, 4B957C102AC7AE700062CA31 /* httpsMobileV2FalsePositives.json in Resources */, 4B957C112AC7AE700062CA31 /* BookmarksBar.storyboard in Resources */, 4B957C122AC7AE700062CA31 /* trackers-1.json in Resources */, @@ -9370,6 +9348,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7B96D0CD2ADFDA7E007E02C8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7BF1A9D82AE054D300FCA683 /* Info.plist in Resources */, + 7BF1A9DC2AE0551C00FCA683 /* DBPUnitTests.xcconfig in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; AA585D7C248FD31100E9A3E2 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -9408,7 +9395,6 @@ B6FA893D269C423100588ECD /* PrivacyDashboard.storyboard in Resources */, AA34396C2754D4E300B241FA /* shield.json in Resources */, AA7412B324D0B3AC00D22FE0 /* TabBarViewItem.xib in Resources */, - 85480F8A25CDC360009424E3 /* MainMenu.storyboard in Resources */, 4B677435255DBEB800025BD8 /* httpsMobileV2FalsePositives.json in Resources */, 4BD18F05283F151F00058124 /* BookmarksBar.storyboard in Resources */, AA3439792754D55100B241FA /* trackers-1.json in Resources */, @@ -9522,44 +9508,6 @@ shellPath = /bin/zsh; shellScript = "./lint.sh\n"; }; - 3192A2672A4C4CFF0084EA89 /* Replace VPN Controllers with Symlinks */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Replace VPN Controllers with Symlinks"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"Replace and Sign\" # for easier build log search\n\n# startVPN\n\npushd \"${CONFIGURATION_BUILD_DIR}/${WRAPPER_NAME}/Contents/Resources/startVPN.app/Contents/MacOS\"\nrm ./startVPN\nln -s \"../../../../MacOS/${PRODUCT_NAME}\" ./startVPN\npopd\n\n# stopVPN\n\npushd \"${CONFIGURATION_BUILD_DIR}/${WRAPPER_NAME}/Contents/Resources/stopVPN.app/Contents/MacOS\"\nrm ./stopVPN\nln -s \"../../../../MacOS/${PRODUCT_NAME}\" ./stopVPN\ncd ../../.. \npopd\n\n# enableOnDemand\n\npushd \"${CONFIGURATION_BUILD_DIR}/${WRAPPER_NAME}/Contents/Resources/enableOnDemand.app/Contents/MacOS\"\nrm ./enableOnDemand\nln -s \"../../../../MacOS/${PRODUCT_NAME}\" ./enableOnDemand\ncd ../../.. \npopd\n"; - }; - 3192A2682A4C4CFF0084EA89 /* Embed System Network Extension */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Embed System Network Extension"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [[ -z \"${SYSEX_BUNDLE_ID}\" ]]; then\n echo \"Required build settings are not defined, please check xcconfig files\"\n exit 1\nfi\n\necho \"ditto ${BUILT_PRODUCTS_DIR}/${SYSEX_BUNDLE_ID} $BUILT_PRODUCTS_DIR/${CONTENTS_FOLDER_PATH}/Library/SystemExtensions/${SYSEX_BUNDLE_ID}\"\n\nditto \"${BUILT_PRODUCTS_DIR}/${SYSEX_BUNDLE_ID}\" \"$BUILT_PRODUCTS_DIR/${CONTENTS_FOLDER_PATH}/Library/SystemExtensions/${SYSEX_BUNDLE_ID}\" || exit 1\n"; - }; 3705272528992C8A000C06A2 /* Check Embedded Config URLs */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -9577,7 +9525,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [ \"$CONFIGURATION\" == \"Release\" ]; then\n \"${SRCROOT}/scripts/update_embedded.sh\" -c\nfi\n"; + shellScript = "if [ \"$ENABLE_PREVIEWS\" = \"YES\" ]; then exit 0; fi\nif [ \"$CONFIGURATION\" == \"Release\" ]; then\n \"${SRCROOT}/scripts/update_embedded.sh\" -c\nfi\n"; }; 3706FA79293F65D500E42796 /* Check Embedded Config URLs */ = { isa = PBXShellScriptBuildPhase; @@ -9596,7 +9544,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [ \"$CONFIGURATION\" == \"Release\" ]; then\n \"${SRCROOT}/scripts/update_embedded.sh\" -c\nfi\n"; + shellScript = "if [ \"$ENABLE_PREVIEWS\" = \"YES\" ]; then exit 0; fi\nif [ \"$CONFIGURATION\" == \"Release\" ]; then\n \"${SRCROOT}/scripts/update_embedded.sh\" -c\nfi\n"; }; 3706FCA5293F65D500E42796 /* Swift Lint */ = { isa = PBXShellScriptBuildPhase; @@ -9615,7 +9563,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/zsh; - shellScript = "./lint.sh\n"; + shellScript = "if [ \"$ENABLE_PREVIEWS\" = \"YES\" ]; then exit 0; fi\n./lint.sh\n"; }; 378E2798296F6D1D00FCADA2 /* Validate PRODUCT_NAME */ = { isa = PBXShellScriptBuildPhase; @@ -9672,25 +9620,6 @@ shellPath = /bin/sh; shellScript = "# We had issues where the Swift Package resources were not being added to the Agent Apps,\n# so we're manually coping them here.\n# It seems to be a known issue: https://forums.swift.org/t/swift-packages-resource-bundle-not-present-in-xcarchive-when-framework-using-said-package-is-archived/50084/2\ncp -RL \"${BUILT_PRODUCTS_DIR}\"/NetworkProtectionMac_NetworkProtectionUI.bundle \"${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/\"\n"; }; - 4B5F14F62A14825A0060320F /* Replace VPN Controllers with Symlinks */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Replace VPN Controllers with Symlinks"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"Replace and Sign\" # for easier build log search\n\n# startVPN\n\npushd \"${CONFIGURATION_BUILD_DIR}/${WRAPPER_NAME}/Contents/Resources/startVPN.app/Contents/MacOS\"\nrm ./startVPN\nln -s \"../../../../MacOS/${PRODUCT_NAME}\" ./startVPN\npopd\n\n# stopVPN\n\npushd \"${CONFIGURATION_BUILD_DIR}/${WRAPPER_NAME}/Contents/Resources/stopVPN.app/Contents/MacOS\"\nrm ./stopVPN\nln -s \"../../../../MacOS/${PRODUCT_NAME}\" ./stopVPN\ncd ../../.. \npopd\n\n# enableOnDemand\n\npushd \"${CONFIGURATION_BUILD_DIR}/${WRAPPER_NAME}/Contents/Resources/enableOnDemand.app/Contents/MacOS\"\nrm ./enableOnDemand\nln -s \"../../../../MacOS/${PRODUCT_NAME}\" ./enableOnDemand\ncd ../../.. \npopd\n"; - }; 4B9579432AC7AE700062CA31 /* Assert Xcode version */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -9767,28 +9696,8 @@ shellPath = /bin/sh; shellScript = "if [[ \"${CONFIGURATION}\" != \"Debug\" ]]; then\n # run only for Debug builds\n exit 0\nfi\n\n# if Xcode created real Applications directory inside BUILT_PRODUCTS_DIR \nif [[ ! -L ${BUILT_PRODUCTS_DIR}/Applications ]]; then\n # we only get here on clean build, remove an existing app in /Applications/DEBUG on clean build\n echo \"rm -rf /${INSTALL_PATH}/${PRODUCT_NAME}.app\"\n rm -rf \"/${INSTALL_PATH}/${PRODUCT_NAME}.app\"\n\n # create /Applications/DEBUG dir\n echo \"mkdir -p /${INSTALL_PATH}\"\n mkdir -p \"/${INSTALL_PATH}\"\n\n # move the app bundle to /Applications/DEBUG\n echo \"mv ${DSTROOT}/${INSTALL_PATH}/${PRODUCT_NAME}.app /${INSTALL_PATH}/${PRODUCT_NAME}.app\"\n mv \"${DSTROOT}/${INSTALL_PATH}/${PRODUCT_NAME}.app\" \"/${INSTALL_PATH}/${PRODUCT_NAME}.app\"\n\n # rm ${BUILT_PRODUCTS_DIR}/Applications directory created by Xcode\n echo \"rm -rf ${BUILT_PRODUCTS_DIR}/Applications\" \n rm -rf \"${BUILT_PRODUCTS_DIR}/Applications\"\n # create ${BUILT_PRODUCTS_DIR}/Applications symlink to /Applications\n echo \"ln -s /Applications ${BUILT_PRODUCTS_DIR}/Applications\"\n ln -s /Applications \"${BUILT_PRODUCTS_DIR}/Applications\"\nfi\n"; }; - 4B957C3A2AC7AE700062CA31 /* Replace VPN Controllers with Symlinks */ = { + 7B31FD922AD126C40086AA24 /* Embed System Network Extension */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Replace VPN Controllers with Symlinks"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"Replace and Sign\" # for easier build log search\n\n# startVPN\n\npushd \"${CONFIGURATION_BUILD_DIR}/${WRAPPER_NAME}/Contents/Resources/startVPN.app/Contents/MacOS\"\nrm ./startVPN\nln -s \"../../../../MacOS/${PRODUCT_NAME}\" ./startVPN\npopd\n\n# stopVPN\n\npushd \"${CONFIGURATION_BUILD_DIR}/${WRAPPER_NAME}/Contents/Resources/stopVPN.app/Contents/MacOS\"\nrm ./stopVPN\nln -s \"../../../../MacOS/${PRODUCT_NAME}\" ./stopVPN\ncd ../../.. \npopd\n\n# enableOnDemand\n\npushd \"${CONFIGURATION_BUILD_DIR}/${WRAPPER_NAME}/Contents/Resources/enableOnDemand.app/Contents/MacOS\"\nrm ./enableOnDemand\nln -s \"../../../../MacOS/${PRODUCT_NAME}\" ./enableOnDemand\ncd ../../.. \npopd\n"; - }; - 4B957C3B2AC7AE700062CA31 /* Embed System Network Extension */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -9803,9 +9712,9 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [[ -z \"${SYSEX_BUNDLE_ID}\" ]]; then\n echo \"Required build settings are not defined, please check xcconfig files\"\n exit 1\nfi\n\n\necho \"ditto ${BUILT_PRODUCTS_DIR}/${SYSEX_BUNDLE_ID} $BUILT_PRODUCTS_DIR/${CONTENTS_FOLDER_PATH}/Library/SystemExtensions/${SYSEX_BUNDLE_ID}\"\n\nditto \"${BUILT_PRODUCTS_DIR}/${SYSEX_BUNDLE_ID}\" \"$BUILT_PRODUCTS_DIR/${CONTENTS_FOLDER_PATH}/Library/SystemExtensions/${SYSEX_BUNDLE_ID}\" || exit 1\n"; + shellScript = "if [[ -z \"${SYSEX_BUNDLE_ID}\" ]]; then\n echo \"Required build settings are not defined, please check xcconfig files\"\n exit 1\nfi\n\n\necho \"ditto ${BUILT_PRODUCTS_DIR}/${SYSEX_BUNDLE_ID}.systemextension $BUILT_PRODUCTS_DIR/${CONTENTS_FOLDER_PATH}/Library/SystemExtensions/${SYSEX_BUNDLE_ID}.systemextension\"\n\nditto \"${BUILT_PRODUCTS_DIR}/${SYSEX_BUNDLE_ID}.systemextension\" \"$BUILT_PRODUCTS_DIR/${CONTENTS_FOLDER_PATH}/Library/SystemExtensions/${SYSEX_BUNDLE_ID}.systemextension\" || exit 1\n"; }; - 7B6469992A165AE00095095A /* Embed System Network Extension */ = { + 7B31FD942AD126FA0086AA24 /* Embed System Network Extension */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; @@ -9822,7 +9731,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [[ -z \"${SYSEX_BUNDLE_ID}\" ]]; then\n echo \"Required build settings are not defined, please check xcconfig files\"\n exit 1\nfi\n\n\necho \"ditto ${BUILT_PRODUCTS_DIR}/${SYSEX_BUNDLE_ID} $BUILT_PRODUCTS_DIR/${CONTENTS_FOLDER_PATH}/Library/SystemExtensions/${SYSEX_BUNDLE_ID}\"\n\nditto \"${BUILT_PRODUCTS_DIR}/${SYSEX_BUNDLE_ID}\" \"$BUILT_PRODUCTS_DIR/${CONTENTS_FOLDER_PATH}/Library/SystemExtensions/${SYSEX_BUNDLE_ID}\" || exit 1\n"; + shellScript = "if [[ -z \"${SYSEX_BUNDLE_ID}\" ]]; then\n echo \"Required build settings are not defined, please check xcconfig files\"\n exit 1\nfi\n\n\necho \"ditto ${BUILT_PRODUCTS_DIR}/${SYSEX_BUNDLE_ID}.systemextension $BUILT_PRODUCTS_DIR/${CONTENTS_FOLDER_PATH}/Library/SystemExtensions/${SYSEX_BUNDLE_ID}.systemextension\"\n\nditto \"${BUILT_PRODUCTS_DIR}/${SYSEX_BUNDLE_ID}.systemextension\" \"$BUILT_PRODUCTS_DIR/${CONTENTS_FOLDER_PATH}/Library/SystemExtensions/${SYSEX_BUNDLE_ID}.systemextension\" || exit 1\n"; }; AA8EDF2824925E940071C2E8 /* Swift Lint */ = { isa = PBXShellScriptBuildPhase; @@ -9841,7 +9750,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/zsh; - shellScript = "./lint.sh\n"; + shellScript = "if [ \"$ENABLE_PREVIEWS\" = \"YES\" ]; then exit 0; fi\n./lint.sh\n"; }; B6BD8F0A2A260E5900B6A41F /* embed libswift_Concurrency.dylib */ = { isa = PBXShellScriptBuildPhase; @@ -9898,7 +9807,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/scripts/assert_xcode_version.sh\"\n"; + shellScript = "if [ \"$ENABLE_PREVIEWS\" = \"YES\" ]; then exit 0; fi\n\"${SRCROOT}/scripts/assert_xcode_version.sh\"\n"; }; CBCCF59F2996681700C02DFE /* Assert Xcode version */ = { isa = PBXShellScriptBuildPhase; @@ -9917,7 +9826,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/scripts/assert_xcode_version.sh\"\n"; + shellScript = "if [ \"$ENABLE_PREVIEWS\" = \"YES\" ]; then exit 0; fi\n\"${SRCROOT}/scripts/assert_xcode_version.sh\"\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -9996,7 +9905,6 @@ 31929FD82A4C4CFF0084EA89 /* ConfigurationManager.swift in Sources */, 31929FDA2A4C4CFF0084EA89 /* YoutubePlayerUserScript.swift in Sources */, 31929FDB2A4C4CFF0084EA89 /* PixelParameters.swift in Sources */, - 31929FDC2A4C4CFF0084EA89 /* ErrorWithParameters.swift in Sources */, 31929FDD2A4C4CFF0084EA89 /* FaviconImageCache.swift in Sources */, 31929FDE2A4C4CFF0084EA89 /* TabBarViewController.swift in Sources */, 31929FDF2A4C4CFF0084EA89 /* BookmarkOutlineViewDataSource.swift in Sources */, @@ -10071,13 +9979,11 @@ 3192A0232A4C4CFF0084EA89 /* URLEventHandler.swift in Sources */, 3192A0242A4C4CFF0084EA89 /* WKWebViewExtension.swift in Sources */, 3192A0262A4C4CFF0084EA89 /* CleanThisHistoryMenuItem.swift in Sources */, - 3192A0272A4C4CFF0084EA89 /* TimedPixel.swift in Sources */, 3192A0282A4C4CFF0084EA89 /* DownloadListItem.swift in Sources */, 3192A0292A4C4CFF0084EA89 /* DownloadsPopover.swift in Sources */, 3192A02A2A4C4CFF0084EA89 /* SpacerNode.swift in Sources */, 4B9DB0252A983B24000927DB /* WaitlistRequest.swift in Sources */, 316C7A862A7E9C2F00AA3BAE /* NetworkProtectionUserDefaultsConstants.swift in Sources */, - 3192A02C2A4C4CFF0084EA89 /* SystemExtensionManager.swift in Sources */, 3192A02D2A4C4CFF0084EA89 /* SyncManagementDialogViewController.swift in Sources */, 3192A02E2A4C4CFF0084EA89 /* BookmarkExtension.swift in Sources */, 4B6785412AA7C726008A5004 /* DailyPixel.swift in Sources */, @@ -10114,6 +10020,7 @@ 3192A04E2A4C4CFF0084EA89 /* SafariDataImporter.swift in Sources */, 3192A04F2A4C4CFF0084EA89 /* LocalBookmarkStore.swift in Sources */, 3192A0502A4C4CFF0084EA89 /* BWEncryption.m in Sources */, + B68D21D12ACBCA00002DA3C2 /* ContentBlockerRulesManagerMock.swift in Sources */, 318115E42A8A4F1C00129796 /* SyncCredentialsAdapter.swift in Sources */, 3192A0512A4C4CFF0084EA89 /* StatisticsLoader.swift in Sources */, 3192A0522A4C4CFF0084EA89 /* WebsiteBreakageReporter.swift in Sources */, @@ -10134,11 +10041,12 @@ 3192A0602A4C4CFF0084EA89 /* DeviceAuthenticationService.swift in Sources */, 3192A0612A4C4CFF0084EA89 /* AppConfigurationURLProvider.swift in Sources */, 3192A0622A4C4CFF0084EA89 /* AutofillPreferences.swift in Sources */, - 4B9DB0282A983B24000927DB /* WaitlistViewModel.swift in Sources */, + 4B9DB0282A983B24000927DB /* NetworkProtectionWaitlistViewModel.swift in Sources */, 3192A0632A4C4CFF0084EA89 /* WebsiteBreakage.swift in Sources */, 3192A0642A4C4CFF0084EA89 /* PasswordManagerCoordinator.swift in Sources */, 3192A0652A4C4CFF0084EA89 /* PasswordManagementIdentityModel.swift in Sources */, 3192A0662A4C4CFF0084EA89 /* UserDefaultsWrapper.swift in Sources */, + 4BF0E5092AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, 3192A0672A4C4CFF0084EA89 /* PasswordManagementPopover.swift in Sources */, 4BCF15DE2ABB970D0083F6DF /* NetworkProtectionRemoteMessage.swift in Sources */, 3192A0682A4C4CFF0084EA89 /* BWCommunicator.swift in Sources */, @@ -10213,14 +10121,14 @@ 3192A0A72A4C4CFF0084EA89 /* NavigationBarBadgeAnimationView.swift in Sources */, 3192A0A82A4C4CFF0084EA89 /* AddressBarButton.swift in Sources */, 3192A0A92A4C4CFF0084EA89 /* HistoryEntry.swift in Sources */, - 4B9DB0342A983B24000927DB /* EnableNetworkProtectionView.swift in Sources */, + 4B9DB0342A983B24000927DB /* EnableWaitlistFeatureView.swift in Sources */, 3192A0AA2A4C4CFF0084EA89 /* FaviconStore.swift in Sources */, 3192A0AB2A4C4CFF0084EA89 /* SuggestionListCharacteristics.swift in Sources */, 3192A0AC2A4C4CFF0084EA89 /* TimeIntervalExtension.swift in Sources */, 3192A0AD2A4C4CFF0084EA89 /* BookmarkListViewController.swift in Sources */, 3192A0AE2A4C4CFF0084EA89 /* SecureVaultLoginImporter.swift in Sources */, 3192A0AF2A4C4CFF0084EA89 /* WKProcessPoolExtension.swift in Sources */, - 7BFE955B2A9DF5210081ABE9 /* NetworkProtectionWaitlistMenu.swift in Sources */, + 7BFE955B2A9DF5210081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */, 3192A0B02A4C4CFF0084EA89 /* AddBookmarkModalViewController.swift in Sources */, 3192A0B12A4C4CFF0084EA89 /* DuckPlayerTabExtension.swift in Sources */, 3192A0B22A4C4CFF0084EA89 /* RecentlyClosedCoordinator.swift in Sources */, @@ -10238,6 +10146,7 @@ 3192A0BF2A4C4CFF0084EA89 /* NSAlert+ActiveDownloadsTermination.swift in Sources */, 3192A0C02A4C4CFF0084EA89 /* IndexPathExtension.swift in Sources */, 3192A0C12A4C4CFF0084EA89 /* PasswordManagementNoteItemView.swift in Sources */, + B68D21CC2ACBC9A3002DA3C2 /* ContentBlockingMock.swift in Sources */, 3192A0C22A4C4CFF0084EA89 /* NSApplicationExtension.swift in Sources */, 3192A0C32A4C4CFF0084EA89 /* NSWindowExtension.swift in Sources */, 3192A0C42A4C4CFF0084EA89 /* BookmarkPopover.swift in Sources */, @@ -10251,7 +10160,7 @@ 3192A0CB2A4C4CFF0084EA89 /* ContinueSetUpView.swift in Sources */, 3192A0CC2A4C4CFF0084EA89 /* PasswordManagementListSection.swift in Sources */, 3192A0CD2A4C4CFF0084EA89 /* FaviconReferenceCache.swift in Sources */, - 4B9DB0372A983B24000927DB /* NetworkProtectionTermsAndConditionsView.swift in Sources */, + 4B9DB0372A983B24000927DB /* WaitlistTermsAndConditionsView.swift in Sources */, 3192A0CE2A4C4CFF0084EA89 /* BookmarkTreeController.swift in Sources */, 3192A0CF2A4C4CFF0084EA89 /* FirefoxEncryptionKeyReader.swift in Sources */, 3192A0D02A4C4CFF0084EA89 /* EventMapping+NetworkProtectionError.swift in Sources */, @@ -10263,7 +10172,6 @@ 3192A0D52A4C4CFF0084EA89 /* PasteboardFolder.swift in Sources */, 3192A0D72A4C4CFF0084EA89 /* CookieManagedNotificationView.swift in Sources */, 3192A0D82A4C4CFF0084EA89 /* PermissionType.swift in Sources */, - 3192A0D92A4C4CFF0084EA89 /* NetworkProtectionTunnelController.swift in Sources */, 3192A0DA2A4C4CFF0084EA89 /* RecentlyClosedWindow.swift in Sources */, 3192A0DB2A4C4CFF0084EA89 /* ActionSpeech.swift in Sources */, 3192A0DC2A4C4CFF0084EA89 /* PrivacySecurityPreferences.swift in Sources */, @@ -10301,6 +10209,7 @@ 3192A0FB2A4C4CFF0084EA89 /* WebKitDownloadTask.swift in Sources */, 3192A0FC2A4C4CFF0084EA89 /* ChromiumLoginReader.swift in Sources */, 3192A0FD2A4C4CFF0084EA89 /* NSAlert+PasswordManager.swift in Sources */, + B690152E2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */, 3192A0FE2A4C4CFF0084EA89 /* UserContentUpdating.swift in Sources */, 3192A0FF2A4C4CFF0084EA89 /* ChromePreferences.swift in Sources */, 3192A1002A4C4CFF0084EA89 /* FirePopoverViewController.swift in Sources */, @@ -10377,6 +10286,7 @@ 3192A1402A4C4CFF0084EA89 /* UpdateController.swift in Sources */, 3192A1412A4C4CFF0084EA89 /* FindInPageModel.swift in Sources */, 3192A1422A4C4CFF0084EA89 /* PseudoFolder.swift in Sources */, + 7BA7CC4F2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift in Sources */, 3192A1432A4C4CFF0084EA89 /* Visit.swift in Sources */, 3192A1442A4C4CFF0084EA89 /* PixelDataStore.swift in Sources */, 3192A1452A4C4CFF0084EA89 /* Pixel.swift in Sources */, @@ -10414,6 +10324,7 @@ 3192A1642A4C4CFF0084EA89 /* OnboardingFlow.swift in Sources */, 3192A1652A4C4CFF0084EA89 /* PasswordManagementLoginModel.swift in Sources */, BB5CB0A12A7AD59D00B312D1 /* NetworkProtectionDebugUtilities.swift in Sources */, + 4BF0E5162AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, 3192A1662A4C4CFF0084EA89 /* TabViewModel.swift in Sources */, B6676BE32AA986A700525A21 /* AddressBarTextEditor.swift in Sources */, 3192A1672A4C4CFF0084EA89 /* TabDragAndDropManager.swift in Sources */, @@ -10492,6 +10403,7 @@ BB79F4F52A8FDDE200EDD78C /* SetExtension.swift in Sources */, 3192A1A82A4C4CFF0084EA89 /* TabPreviewWindowController.swift in Sources */, 3192A1A92A4C4CFF0084EA89 /* NSSizeExtension.swift in Sources */, + B68D21CD2ACBC9A7002DA3C2 /* MockPrivacyConfiguration.swift in Sources */, 3192A1AA2A4C4CFF0084EA89 /* Fire.swift in Sources */, 3192A1AB2A4C4CFF0084EA89 /* SyncBookmarksAdapter.swift in Sources */, 3192A1AC2A4C4CFF0084EA89 /* RandomAccessCollectionExtension.swift in Sources */, @@ -10516,6 +10428,7 @@ 3192A1BC2A4C4CFF0084EA89 /* HomePageViewController.swift in Sources */, 3192A1BD2A4C4CFF0084EA89 /* BraveDataImporter.swift in Sources */, 3192A1BE2A4C4CFF0084EA89 /* OperatingSystemVersionExtension.swift in Sources */, + B62B483B2ADE46FC000DECE5 /* Application.swift in Sources */, 3192A1BF2A4C4CFF0084EA89 /* ToggleableScrollView.swift in Sources */, 3192A1C02A4C4CFF0084EA89 /* NetworkProtectionOptionKeyExtension.swift in Sources */, 3192A1C12A4C4CFF0084EA89 /* UserScripts.swift in Sources */, @@ -10555,6 +10468,7 @@ 3192A1E22A4C4CFF0084EA89 /* TabBarViewItem.swift in Sources */, 3192A1E32A4C4CFF0084EA89 /* NSWindow+Toast.swift in Sources */, 3192A1E42A4C4CFF0084EA89 /* AutoconsentUserScript.swift in Sources */, + B62B48402ADE48DE000DECE5 /* MenuBuilder.swift in Sources */, 3192A1E52A4C4CFF0084EA89 /* BookmarksExporter.swift in Sources */, 3192A1E62A4C4CFF0084EA89 /* FirefoxDataImporter.swift in Sources */, 3192A1E72A4C4CFF0084EA89 /* PreferencesGeneralView.swift in Sources */, @@ -10564,6 +10478,7 @@ 3192A1EB2A4C4CFF0084EA89 /* URLExtension.swift in Sources */, 3192A1EC2A4C4CFF0084EA89 /* Tab+UIDelegate.swift in Sources */, 3192A1ED2A4C4CFF0084EA89 /* CookieConsentAnimationView.swift in Sources */, + 7B3618C42ADE77D2000D6154 /* NetworkProtectionNavBarPopoverManager.swift in Sources */, 3192A1EE2A4C4CFF0084EA89 /* NSStoryboardExtension.swift in Sources */, 3192A1EF2A4C4CFF0084EA89 /* PreferencesViewController.swift in Sources */, 377D801E2AB48189002AF251 /* FavoritesDisplayModeSyncHandler.swift in Sources */, @@ -10657,7 +10572,7 @@ 3706FAB7293F65D500E42796 /* PixelParameters.swift in Sources */, 3706FAB8293F65D500E42796 /* FaviconImageCache.swift in Sources */, 3706FAB9293F65D500E42796 /* TabBarViewController.swift in Sources */, - 4B9DB0332A983B24000927DB /* EnableNetworkProtectionView.swift in Sources */, + 4B9DB0332A983B24000927DB /* EnableWaitlistFeatureView.swift in Sources */, 3706FABA293F65D500E42796 /* BookmarkOutlineViewDataSource.swift in Sources */, 3706FABB293F65D500E42796 /* PasswordManagementBitwardenItemView.swift in Sources */, 3706FABC293F65D500E42796 /* CookieConsentPopover.swift in Sources */, @@ -10694,7 +10609,6 @@ 3707C722294B5D2900682A9F /* WKWebViewExtension.swift in Sources */, 3706FAD8293F65D500E42796 /* RequestFilePermissionViewController.swift in Sources */, 3706FAD9293F65D500E42796 /* FirefoxFaviconsReader.swift in Sources */, - 4B7A94B529C16294000C7D4C /* ErrorWithParameters.swift in Sources */, 3706FADA293F65D500E42796 /* CopyHandler.swift in Sources */, 3706FADB293F65D500E42796 /* ContentBlockingRulesUpdateObserver.swift in Sources */, 3706FADC293F65D500E42796 /* FirefoxLoginReader.swift in Sources */, @@ -10729,12 +10643,12 @@ 3706FAF6293F65D500E42796 /* LoginFaviconView.swift in Sources */, 3706FEC0293F6EFF00E42796 /* BWRequest.swift in Sources */, 3706FAF7293F65D500E42796 /* FireproofDomainsViewController.swift in Sources */, + 4BF0E5062AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, 3706FAF8293F65D500E42796 /* URLEventHandler.swift in Sources */, 37197EA72942443D00394917 /* AuthenticationAlert.swift in Sources */, 3706FEC3293F6F0600E42796 /* BWCommunicator.swift in Sources */, 3706FAFA293F65D500E42796 /* CleanThisHistoryMenuItem.swift in Sources */, 1DA6D0FE2A1FF9A100540406 /* HTTPCookie.swift in Sources */, - 3706FAFB293F65D500E42796 /* TimedPixel.swift in Sources */, 3706FAFC293F65D500E42796 /* DownloadListItem.swift in Sources */, 3706FAFD293F65D500E42796 /* DownloadsPopover.swift in Sources */, 3706FAFE293F65D500E42796 /* SpacerNode.swift in Sources */, @@ -10757,7 +10671,6 @@ 3706FB0B293F65D500E42796 /* DefaultBrowserPromptView.swift in Sources */, 3706FB0D293F65D500E42796 /* BrowserImportSummaryViewController.swift in Sources */, 3706FB0E293F65D500E42796 /* FaviconManager.swift in Sources */, - 4B8F52422A18326600BE7131 /* NetworkProtectionTunnelController.swift in Sources */, 3706FB0F293F65D500E42796 /* ChromiumFaviconsReader.swift in Sources */, 4B0BD7B82A9FE6E600EF609D /* NetworkProtectionOnboardingMenu.swift in Sources */, 3706FB10293F65D500E42796 /* SuggestionTableRowView.swift in Sources */, @@ -10806,7 +10719,7 @@ 3706FB35293F65D500E42796 /* FlatButton.swift in Sources */, 3706FB36293F65D500E42796 /* PinnedTabView.swift in Sources */, 3706FB37293F65D500E42796 /* DataEncryption.swift in Sources */, - 4B9DB0362A983B24000927DB /* NetworkProtectionTermsAndConditionsView.swift in Sources */, + 4B9DB0362A983B24000927DB /* WaitlistTermsAndConditionsView.swift in Sources */, 37197EA82942443D00394917 /* BrowserTabViewController.swift in Sources */, 3706FB39293F65D500E42796 /* PrivacyDashboardPopover.swift in Sources */, 3706FB3B293F65D500E42796 /* RootView.swift in Sources */, @@ -10925,6 +10838,7 @@ 3706FB95293F65D500E42796 /* PermissionType.swift in Sources */, 3706FB96293F65D500E42796 /* RecentlyClosedWindow.swift in Sources */, 4B9DB0242A983B24000927DB /* WaitlistRequest.swift in Sources */, + B690152D2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */, 1D36F4252A3B85C50052B527 /* TabCleanupPreparer.swift in Sources */, 3706FB97293F65D500E42796 /* ActionSpeech.swift in Sources */, 3706FB99293F65D500E42796 /* PrivacySecurityPreferences.swift in Sources */, @@ -10957,6 +10871,7 @@ 3706FBAF293F65D500E42796 /* FireproofDomains.xcdatamodeld in Sources */, 3706FBB0293F65D500E42796 /* CookieConsentUserPermissionViewController.swift in Sources */, B626A7552991413000053070 /* SerpHeadersNavigationResponder.swift in Sources */, + B68D21C42ACBC917002DA3C2 /* ContentBlockingMock.swift in Sources */, 3706FBB1293F65D500E42796 /* HomePageView.swift in Sources */, 3706FBB2293F65D500E42796 /* WebKitDownloadTask.swift in Sources */, 3706FBB3293F65D500E42796 /* ChromiumLoginReader.swift in Sources */, @@ -11002,6 +10917,7 @@ 7B430EA22A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */, 3706FBD5293F65D500E42796 /* TabCollection+NSSecureCoding.swift in Sources */, 3706FBD6293F65D500E42796 /* Instruments.swift in Sources */, + B62B483F2ADE48DE000DECE5 /* MenuBuilder.swift in Sources */, 569277C229DDCBB500B633EF /* HomePageContinueSetUpModel.swift in Sources */, 3706FBD7293F65D500E42796 /* ContentBlockerRulesLists.swift in Sources */, 3706FBD8293F65D500E42796 /* NSViewControllerExtension.swift in Sources */, @@ -11028,11 +10944,13 @@ 3706FBE7293F65D500E42796 /* PasswordManagementItemListModel.swift in Sources */, 3706FBE8293F65D500E42796 /* SuggestionTableCellView.swift in Sources */, 3706FBE9293F65D500E42796 /* FireViewModel.swift in Sources */, + B68D21D02ACBC9FD002DA3C2 /* ContentBlockerRulesManagerMock.swift in Sources */, 3706FEC6293F6F0600E42796 /* BWKeyStorage.swift in Sources */, 4B6785482AA8DE69008A5004 /* NetworkProtectionFeatureDisabler.swift in Sources */, 3706FBEC293F65D500E42796 /* EditableTextView.swift in Sources */, 3706FBED293F65D500E42796 /* TabCollection.swift in Sources */, B6C0BB6B29AF1C7000AE8E3C /* BrowserTabView.swift in Sources */, + B62B483A2ADE46FC000DECE5 /* Application.swift in Sources */, 3706FBEE293F65D500E42796 /* MainView.swift in Sources */, 3706FBEF293F65D500E42796 /* EmailUrlExtensions.swift in Sources */, 3706FBF0293F65D500E42796 /* PasswordManagementItemModel.swift in Sources */, @@ -11052,6 +10970,7 @@ 987799FA29999973005D8EB6 /* LocalBookmarkStore.swift in Sources */, B602E7D02A93A5FF00F12201 /* WKBackForwardListExtension.swift in Sources */, 3706FBFE293F65D500E42796 /* History.xcdatamodeld in Sources */, + B68D21C92ACBC96E002DA3C2 /* MockPrivacyConfiguration.swift in Sources */, 3706FBFF293F65D500E42796 /* PermissionStore.swift in Sources */, 3706FC00293F65D500E42796 /* PrivacyIconViewModel.swift in Sources */, 3706FC01293F65D500E42796 /* ChromiumBookmarksReader.swift in Sources */, @@ -11105,7 +11024,7 @@ 3706FC29293F65D500E42796 /* QuartzIdleStateProvider.swift in Sources */, 3706FC2A293F65D500E42796 /* DuckPlayerPreferences.swift in Sources */, 3706FC2B293F65D500E42796 /* DownloadViewModel.swift in Sources */, - 4B9DB0272A983B24000927DB /* WaitlistViewModel.swift in Sources */, + 4B9DB0272A983B24000927DB /* NetworkProtectionWaitlistViewModel.swift in Sources */, 4B9DB03F2A983B24000927DB /* JoinWaitlistView.swift in Sources */, 987799F22999993C005D8EB6 /* LegacyBookmarkStore.swift in Sources */, 3706FC2C293F65D500E42796 /* BookmarkHTMLReader.swift in Sources */, @@ -11192,8 +11111,9 @@ 3706FC73293F65D500E42796 /* AddressBarButtonsViewController.swift in Sources */, 3706FC75293F65D500E42796 /* ChromeDataImporter.swift in Sources */, 3706FC76293F65D500E42796 /* PixelDataRecord.swift in Sources */, - 7BFE955A2A9DF4550081ABE9 /* NetworkProtectionWaitlistMenu.swift in Sources */, + 7BFE955A2A9DF4550081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */, 3706FC77293F65D500E42796 /* PageObserverUserScript.swift in Sources */, + 4BF0E5132AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, 3706FC78293F65D500E42796 /* SecureVaultErrorReporter.swift in Sources */, 3706FC79293F65D500E42796 /* NSImageExtensions.swift in Sources */, 3706FEBD293F6EFF00E42796 /* BWCommand.swift in Sources */, @@ -11368,7 +11288,6 @@ 3706FE38293F661700E42796 /* SuggestionContainerTests.swift in Sources */, 3706FE39293F661700E42796 /* TabTests.swift in Sources */, 3706FE3A293F661700E42796 /* MockVariantManager.swift in Sources */, - 3706FE3B293F661700E42796 /* ContentBlockerRulesManagerMock.swift in Sources */, 3706FE3C293F661700E42796 /* FireproofDomainsStoreMock.swift in Sources */, 3706FE3D293F661700E42796 /* DataEncryptionTests.swift in Sources */, 3706FE3E293F661700E42796 /* ClickToLoadModelTests.swift in Sources */, @@ -11411,7 +11330,6 @@ 3706FE5B293F661700E42796 /* FirefoxLoginReaderTests.swift in Sources */, 1D1C36E429FAE8DA001FA40C /* FaviconManagerTests.swift in Sources */, 376E2D29294286B8001CD31B /* PixelEventTests.swift in Sources */, - 3707C72B294B5D3D00682A9F /* ContentBlockingMock.swift in Sources */, 37716D8029707E5D00A9FC6D /* FireproofingReferenceTests.swift in Sources */, B6AA64742994B43300D99CD6 /* FutureExtensionTests.swift in Sources */, 3706FE5C293F661700E42796 /* DuckPlayerPreferencesTests.swift in Sources */, @@ -11466,7 +11384,6 @@ B60C6F8529B1BAD3007BFAA8 /* FileManagerTempDirReplacement.swift in Sources */, 567DA94629E95C3F008AC5EE /* YoutubeOverlayUserScriptTests.swift in Sources */, 3706FE82293F661700E42796 /* MockStatisticsStore.swift in Sources */, - B6AA64752995164700D99CD6 /* MockPrivacyConfiguration.swift in Sources */, 3706FE83293F661700E42796 /* AutofillPreferencesModelTests.swift in Sources */, 3706FE84293F661700E42796 /* TabCollectionViewModelTests+PinnedTabs.swift in Sources */, B603975229C1FFAD00902A34 /* ExpectedNavigationExtension.swift in Sources */, @@ -11488,8 +11405,6 @@ B6EC37DF29B5D05A001ACE79 /* DownloadsIntegrationTests.swift in Sources */, B6EC37FD29B83E99001ACE79 /* TestsURLExtension.swift in Sources */, B603973529BEF86200902A34 /* HTTPSUpgradeIntegrationTests.swift in Sources */, - 3707C72E294B5D4400682A9F /* ContentBlockerRulesManagerMock.swift in Sources */, - 3707C72C294B5D3D00682A9F /* ContentBlockingMock.swift in Sources */, B644B44029D57299003FA9AB /* SuggestionLoadingMock.swift in Sources */, 1D8B7D6B2A38BF060045C6F6 /* FireproofDomainsStoreMock.swift in Sources */, B630E80229C887ED00363609 /* NSErrorAdditionalInfo.swift in Sources */, @@ -11497,7 +11412,6 @@ B60C6F8929B1CAB7007BFAA8 /* TestRunHelperInitializer.m in Sources */, B60C6F8B29B1CAC0007BFAA8 /* FileManagerTempDirReplacement.swift in Sources */, 3706FEA4293F662100E42796 /* CoreDataTestUtilities.swift in Sources */, - B626A77229928C6A00053070 /* MockPrivacyConfiguration.swift in Sources */, B62A234129C41D4400D22475 /* HistoryIntegrationTests.swift in Sources */, B603973929BF0EBE00902A34 /* PrivacyDashboardIntegrationTests.swift in Sources */, B644B43E29D5682B003FA9AB /* SearchNonexistentDomainTests.swift in Sources */, @@ -11515,7 +11429,6 @@ buildActionMask = 2147483647; files = ( B603971129B9D67E00902A34 /* PublishersExtensions.swift in Sources */, - B6AE39F629374B8E00C37AA4 /* ContentBlockerRulesManagerMock.swift in Sources */, B662D3DF275616FF0035D4D6 /* EncryptionKeyStoreMock.swift in Sources */, B6AE39F729374B9900C37AA4 /* DuckPlayerTests.swift in Sources */, B62A233C29C322BC00D22475 /* NavigationProtectionIntegrationTests.swift in Sources */, @@ -11532,11 +11445,9 @@ B60C6F8829B1CAB6007BFAA8 /* TestRunHelperInitializer.m in Sources */, B60C6F8A29B1CABF007BFAA8 /* FileManagerTempDirReplacement.swift in Sources */, 4B1AD92125FC474E00261379 /* CoreDataEncryptionTesting.xcdatamodeld in Sources */, - B626A77329928C6B00053070 /* MockPrivacyConfiguration.swift in Sources */, B62A234029C41D4400D22475 /* HistoryIntegrationTests.swift in Sources */, B603973829BF0EBE00902A34 /* PrivacyDashboardIntegrationTests.swift in Sources */, B644B43D29D56829003FA9AB /* SearchNonexistentDomainTests.swift in Sources */, - B6BDD9F62940B5B500F68088 /* ContentBlockingMock.swift in Sources */, B603973C29BF1D7D00902A34 /* AutoconsentIntegrationTests.swift in Sources */, B60C6F8629B1CAB0007BFAA8 /* TestRunHelper.swift in Sources */, B603972C29BEDF2100902A34 /* ExpectedNavigationExtension.swift in Sources */, @@ -11556,11 +11467,11 @@ B6F92BA82A691A44002ABA6B /* NetworkProtectionUserDefaultsConstants.swift in Sources */, B602E8232A1E260E006D261F /* Bundle+NetworkProtectionExtensions.swift in Sources */, EEAD7A7B2A1D3E20002A24E7 /* AppLauncher.swift in Sources */, - 4B25377C2A11C07600610219 /* NetworkProtectionPixelEvent.swift in Sources */, 4B2D062A2A11C0C900DE1F49 /* NetworkProtectionOptionKeyExtension.swift in Sources */, B602E8192A1E2570006D261F /* URL+NetworkProtection.swift in Sources */, 4B2D06322A11C1D300DE1F49 /* NSApplicationExtension.swift in Sources */, 4B2D06332A11C1E300DE1F49 /* OptionalExtension.swift in Sources */, + 4BF0E50B2AD2552200FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, 4B2537782A11C00F00610219 /* NetworkProtectionExtensionMachService.swift in Sources */, B65DA5F32A77D3C700CBEE8D /* UserDefaultsWrapper.swift in Sources */, 4B2537722A11BF8B00610219 /* main.swift in Sources */, @@ -11573,20 +11484,31 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7B2DDD052A93BEE20039D884 /* FeatureProtectedTunnelController.swift in Sources */, B6F92BA22A691580002ABA6B /* UserDefaultsWrapper.swift in Sources */, 4B2D065B2A11D1FF00DE1F49 /* Logging.swift in Sources */, + 7BA7CC5B2AD120640042E5CE /* NetworkProtection+ConvenienceInitializers.swift in Sources */, + 7BA7CC3A2AD11E2D0042E5CE /* DuckDuckGoVPNAppDelegate.swift in Sources */, 7BAF9E4C2A8A3CCA002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */, - 4B2D06572A11D19B00DE1F49 /* DuckDuckGoAgentAppDelegate.swift in Sources */, + 7BA7CC592AD1203B0042E5CE /* UserText+NetworkProtection.swift in Sources */, + 7BA7CC562AD11FFB0042E5CE /* NetworkProtectionOptionKeyExtension.swift in Sources */, 7B2DDCFA2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */, - 7B2DDD072A93C17D0039D884 /* UserText.swift in Sources */, + 7BA7CC4C2AD11EC70042E5CE /* NetworkProtectionControllerErrorStore.swift in Sources */, B6F92BAC2A6937B3002ABA6B /* OptionalExtension.swift in Sources */, + 7BA7CC532AD11FCE0042E5CE /* NetworkProtectionBundle.swift in Sources */, + 7BA7CC3C2AD11E330042E5CE /* Bundle+Configuration.swift in Sources */, 7BFE95562A9DF29B0081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */, - 7B2DDD092A93C2440039D884 /* AppLauncher+DefaultInitializer.swift in Sources */, + 7BA7CC5D2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */, + 7BA7CC4A2AD11EA00042E5CE /* NetworkProtectionTunnelController.swift in Sources */, + 7BD1688E2AD4A4C400D24876 /* NetworkExtensionController.swift in Sources */, + 7BA7CC3E2AD11E380042E5CE /* TunnelControllerIPCService.swift in Sources */, B6F92BAA2A691A44002ABA6B /* NetworkProtectionUserDefaultsConstants.swift in Sources */, + 7BA7CC402AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */, EEC589DB2A4F1CE700BCD60C /* AppLauncher.swift in Sources */, B65DA5EF2A77CC3A00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */, - 7B2DDD022A93BAA60039D884 /* NetworkProtectionBouncer.swift in Sources */, + 4BF0E5072AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, + 7BA7CC442AD11E490042E5CE /* UserText.swift in Sources */, + 4BF0E5142AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, + 7BA7CC422AD11E420042E5CE /* NetworkProtectionBouncer.swift in Sources */, B65DA5F12A77D2BC00CBEE8D /* BundleExtension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -11598,14 +11520,26 @@ 7B2DDCFB2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */, B6F92BA32A691583002ABA6B /* UserDefaultsWrapper.swift in Sources */, 4B2D067C2A13340900DE1F49 /* Logging.swift in Sources */, - 4B2D067A2A1333EF00DE1F49 /* DuckDuckGoAgentAppDelegate.swift in Sources */, B6F92BAD2A6937B5002ABA6B /* OptionalExtension.swift in Sources */, B6F92BAB2A691A44002ABA6B /* NetworkProtectionUserDefaultsConstants.swift in Sources */, + 7BA7CC5A2AD120640042E5CE /* NetworkProtection+ConvenienceInitializers.swift in Sources */, + 7BA7CC3B2AD11E330042E5CE /* Bundle+Configuration.swift in Sources */, EEC589DC2A4F1CE800BCD60C /* AppLauncher.swift in Sources */, + 7BA7CC3F2AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */, + 7BA7CC412AD11E420042E5CE /* NetworkProtectionBouncer.swift in Sources */, + 4BF0E5082AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, + 7BA7CC582AD1203A0042E5CE /* UserText+NetworkProtection.swift in Sources */, + 7BA7CC4B2AD11EC60042E5CE /* NetworkProtectionControllerErrorStore.swift in Sources */, + 4BF0E5152AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, 7BFE95592A9DF2AF0081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */, - 7B2DDD032A93BBEC0039D884 /* NetworkProtectionBouncer.swift in Sources */, + 7BA7CC5C2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */, B65DA5F02A77CC3C00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */, 7BAF9E4D2A8A3CCB002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */, + 7BA7CC392AD11E2D0042E5CE /* DuckDuckGoVPNAppDelegate.swift in Sources */, + 7BA7CC552AD11FFB0042E5CE /* NetworkProtectionOptionKeyExtension.swift in Sources */, + 7BA7CC3D2AD11E380042E5CE /* TunnelControllerIPCService.swift in Sources */, + 7BA7CC432AD11E480042E5CE /* UserText.swift in Sources */, + 7BA7CC542AD11FCE0042E5CE /* NetworkProtectionBundle.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -11637,10 +11571,10 @@ B65DA5F52A77D3FA00CBEE8D /* BundleExtension.swift in Sources */, 4B4D60892A0B2A1C00BCD287 /* NetworkProtectionUNNotificationsPresenter.swift in Sources */, 4B4D60A02A0B2D5B00BCD287 /* NetworkProtectionBundle.swift in Sources */, - 4B4D60932A0B2A3700BCD287 /* NetworkProtectionPixelEvent.swift in Sources */, 4B4D60AD2A0C807300BCD287 /* NSApplicationExtension.swift in Sources */, 4B4D60A52A0B2EC000BCD287 /* UserText+NetworkProtectionExtensions.swift in Sources */, EEF12E6E2A2111880023E6BF /* MacPacketTunnelProvider.swift in Sources */, + 4BF0E50C2AD2552300FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, 4B4D60AC2A0C804B00BCD287 /* OptionalExtension.swift in Sources */, B6F92BA72A691A44002ABA6B /* NetworkProtectionUserDefaultsConstants.swift in Sources */, B65DA5F22A77D3C600CBEE8D /* UserDefaultsWrapper.swift in Sources */, @@ -11648,22 +11582,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 4B5F14C72A14702C0060320F /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B5F14DC2A1470AA0060320F /* Main.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4B5F14DD2A1476BC0060320F /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B5F14F22A1476EF0060320F /* Main.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 4B9579452AC7AE700062CA31 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -11737,7 +11655,6 @@ 4B9579872AC7AE700062CA31 /* ConfigurationManager.swift in Sources */, 4B9579882AC7AE700062CA31 /* YoutubePlayerUserScript.swift in Sources */, 4B9579892AC7AE700062CA31 /* PixelParameters.swift in Sources */, - 4B95798A2AC7AE700062CA31 /* ErrorWithParameters.swift in Sources */, 4B95798B2AC7AE700062CA31 /* FaviconImageCache.swift in Sources */, 4B95798C2AC7AE700062CA31 /* TabBarViewController.swift in Sources */, 4B95798D2AC7AE700062CA31 /* BookmarkOutlineViewDataSource.swift in Sources */, @@ -11759,6 +11676,7 @@ 4B95799D2AC7AE700062CA31 /* AppTrackerDataSetProvider.swift in Sources */, 4B95799E2AC7AE700062CA31 /* EncryptionKeyGeneration.swift in Sources */, 4B95799F2AC7AE700062CA31 /* TabLazyLoader.swift in Sources */, + B690152F2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */, 4B9579A02AC7AE700062CA31 /* InvitedToWaitlistView.swift in Sources */, 4B9579A12AC7AE700062CA31 /* FileImportViewController.swift in Sources */, 4B9579A22AC7AE700062CA31 /* SaveCredentialsViewController.swift in Sources */, @@ -11815,13 +11733,12 @@ 4B9579D52AC7AE700062CA31 /* SupportedOsChecker.swift in Sources */, 4B9579D62AC7AE700062CA31 /* WKWebViewExtension.swift in Sources */, 4B9579D72AC7AE700062CA31 /* CleanThisHistoryMenuItem.swift in Sources */, - 4B9579D82AC7AE700062CA31 /* TimedPixel.swift in Sources */, 4B9579D92AC7AE700062CA31 /* DownloadListItem.swift in Sources */, 4B9579DA2AC7AE700062CA31 /* WaitlistRequest.swift in Sources */, 4B9579DB2AC7AE700062CA31 /* DownloadsPopover.swift in Sources */, 4B9579DC2AC7AE700062CA31 /* BookmarksBarMenuFactory.swift in Sources */, 4B9579DD2AC7AE700062CA31 /* SpacerNode.swift in Sources */, - 4B9579DE2AC7AE700062CA31 /* SystemExtensionManager.swift in Sources */, + B62B483C2ADE46FC000DECE5 /* Application.swift in Sources */, 4B9579DF2AC7AE700062CA31 /* SyncManagementDialogViewController.swift in Sources */, 4B9579E02AC7AE700062CA31 /* BookmarkExtension.swift in Sources */, 4B9579E12AC7AE700062CA31 /* PasswordManagementCreditCardModel.swift in Sources */, @@ -11845,6 +11762,7 @@ 4B9579F32AC7AE700062CA31 /* BrowserImportSummaryViewController.swift in Sources */, 4B9579F42AC7AE700062CA31 /* FaviconManager.swift in Sources */, 4B9579F52AC7AE700062CA31 /* PFMoveApplication.m in Sources */, + B68D21D22ACBCA01002DA3C2 /* ContentBlockerRulesManagerMock.swift in Sources */, 4B9579F62AC7AE700062CA31 /* ChromiumFaviconsReader.swift in Sources */, 4B9579F72AC7AE700062CA31 /* SuggestionTableRowView.swift in Sources */, 4B9579F82AC7AE700062CA31 /* DownloadsPreferences.swift in Sources */, @@ -11857,7 +11775,7 @@ 4B9579FF2AC7AE700062CA31 /* FireViewController.swift in Sources */, 4B957A002AC7AE700062CA31 /* OutlineSeparatorViewCell.swift in Sources */, 4B957A012AC7AE700062CA31 /* SafariDataImporter.swift in Sources */, - 4B957A022AC7AE700062CA31 /* WaitlistViewModel.swift in Sources */, + 4B957A022AC7AE700062CA31 /* NetworkProtectionWaitlistViewModel.swift in Sources */, 4B957A032AC7AE700062CA31 /* LocalBookmarkStore.swift in Sources */, 4B957A042AC7AE700062CA31 /* BWEncryption.m in Sources */, 4B957A052AC7AE700062CA31 /* StatisticsLoader.swift in Sources */, @@ -11918,7 +11836,7 @@ 4B957A3B2AC7AE700062CA31 /* CookieNotificationAnimationModel.swift in Sources */, 4B957A3C2AC7AE700062CA31 /* JoinedWaitlistView.swift in Sources */, 4B957A3D2AC7AE700062CA31 /* SharingMenu.swift in Sources */, - 4B957A3E2AC7AE700062CA31 /* EnableNetworkProtectionView.swift in Sources */, + 4B957A3E2AC7AE700062CA31 /* EnableWaitlistFeatureView.swift in Sources */, 4B957A3F2AC7AE700062CA31 /* GrammarFeaturesManager.swift in Sources */, 4B957A402AC7AE700062CA31 /* WaitlistModalViewController.swift in Sources */, 4B957A412AC7AE700062CA31 /* WKMenuItemIdentifier.swift in Sources */, @@ -11926,6 +11844,7 @@ 4B957A432AC7AE700062CA31 /* NSScreenExtension.swift in Sources */, 4B957A442AC7AE700062CA31 /* NSBezierPathExtension.swift in Sources */, 4B957A452AC7AE700062CA31 /* NetworkProtectionBundle.swift in Sources */, + B68D21CA2ACBC971002DA3C2 /* MockPrivacyConfiguration.swift in Sources */, 4B957A462AC7AE700062CA31 /* WebsiteDataStore.swift in Sources */, 4B957A472AC7AE700062CA31 /* NetworkProtectionFeatureVisibility.swift in Sources */, 3778183D2AD6F86D00533759 /* FavoritesDisplayModeSyncHandler.swift in Sources */, @@ -11955,12 +11874,13 @@ 4B957A5E2AC7AE700062CA31 /* DownloadListCoordinator.swift in Sources */, 4B957A5F2AC7AE700062CA31 /* AdClickAttributionTabExtension.swift in Sources */, 1E2AE4CC2ACB224A00684E0A /* NetworkProtectionRemoteMessagingRequest.swift in Sources */, + 7B3618C52ADE77D3000D6154 /* NetworkProtectionNavBarPopoverManager.swift in Sources */, 4B957A602AC7AE700062CA31 /* NSNotificationName+Debug.swift in Sources */, 4B957A612AC7AE700062CA31 /* NavigationBarBadgeAnimationView.swift in Sources */, 4B957A622AC7AE700062CA31 /* AddressBarButton.swift in Sources */, 4B957A632AC7AE700062CA31 /* HistoryEntry.swift in Sources */, 4B957A642AC7AE700062CA31 /* FaviconStore.swift in Sources */, - 4B957A652AC7AE700062CA31 /* NetworkProtectionTermsAndConditionsView.swift in Sources */, + 4B957A652AC7AE700062CA31 /* WaitlistTermsAndConditionsView.swift in Sources */, 4B957A662AC7AE700062CA31 /* SuggestionListCharacteristics.swift in Sources */, 4B957A672AC7AE700062CA31 /* TimeIntervalExtension.swift in Sources */, 4B957A682AC7AE700062CA31 /* NetworkProtectionFeatureDisabler.swift in Sources */, @@ -11979,11 +11899,14 @@ 4B957A752AC7AE700062CA31 /* ASN1Parser.swift in Sources */, 4B957A762AC7AE700062CA31 /* FileDownloadManager.swift in Sources */, 4B957A772AC7AE700062CA31 /* BookmarkImport.swift in Sources */, + 4BF0E5172AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, 4B957A782AC7AE700062CA31 /* KeySetDictionary.swift in Sources */, + B68D21CB2ACBC9A3002DA3C2 /* ContentBlockingMock.swift in Sources */, 4B957A792AC7AE700062CA31 /* HistoryTabExtension.swift in Sources */, 4B957A7A2AC7AE700062CA31 /* FireCoordinator.swift in Sources */, 4B957A7B2AC7AE700062CA31 /* GeolocationProvider.swift in Sources */, 4B957A7C2AC7AE700062CA31 /* NSAlert+ActiveDownloadsTermination.swift in Sources */, + B62B48412ADE48DE000DECE5 /* MenuBuilder.swift in Sources */, 4B957A7D2AC7AE700062CA31 /* IndexPathExtension.swift in Sources */, 4B957A7E2AC7AE700062CA31 /* PasswordManagementNoteItemView.swift in Sources */, 4B957A7F2AC7AE700062CA31 /* NSApplicationExtension.swift in Sources */, @@ -12010,7 +11933,6 @@ 4B957A942AC7AE700062CA31 /* PasteboardFolder.swift in Sources */, 4B957A952AC7AE700062CA31 /* CookieManagedNotificationView.swift in Sources */, 4B957A962AC7AE700062CA31 /* PermissionType.swift in Sources */, - 4B957A972AC7AE700062CA31 /* NetworkProtectionTunnelController.swift in Sources */, 4B957A982AC7AE700062CA31 /* RecentlyClosedWindow.swift in Sources */, 4B957A992AC7AE700062CA31 /* ActionSpeech.swift in Sources */, 4B957A9A2AC7AE700062CA31 /* PrivacySecurityPreferences.swift in Sources */, @@ -12239,9 +12161,10 @@ 4B957B792AC7AE700062CA31 /* ContentBlockingTabExtension.swift in Sources */, 4B957B7A2AC7AE700062CA31 /* OnboardingViewController.swift in Sources */, 4B957B7B2AC7AE700062CA31 /* DeviceAuthenticator.swift in Sources */, - 4B957B7C2AC7AE700062CA31 /* NetworkProtectionWaitlistMenu.swift in Sources */, + 4B957B7C2AC7AE700062CA31 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */, 4B957B7D2AC7AE700062CA31 /* TabBarCollectionView.swift in Sources */, 4B957B7E2AC7AE700062CA31 /* NetworkProtection+ConvenienceInitializers.swift in Sources */, + 7BA7CC502AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift in Sources */, 4B957B7F2AC7AE700062CA31 /* NavigationActionExtension.swift in Sources */, 4B957B802AC7AE700062CA31 /* NSAlertExtension.swift in Sources */, 4B957B812AC7AE700062CA31 /* ThirdPartyBrowser.swift in Sources */, @@ -12303,6 +12226,7 @@ 4B957BB72AC7AE700062CA31 /* PinnedTabsView.swift in Sources */, 4B957BB82AC7AE700062CA31 /* FireproofInfoViewController.swift in Sources */, 4B957BB92AC7AE700062CA31 /* SyncErrorHandler.swift in Sources */, + 4BF0E50A2AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, 4B957BBA2AC7AE700062CA31 /* URLExtension.swift in Sources */, 4B957BBB2AC7AE700062CA31 /* Tab+UIDelegate.swift in Sources */, 1E2AE4C92ACB217800684E0A /* NetworkProtectionRemoteMessagingStorage.swift in Sources */, @@ -12343,11 +12267,11 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 7B736E572A4A22B700F9922A /* Sources */ = { + 7B96D0CB2ADFDA7E007E02C8 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7B736E582A4A22B700F9922A /* Main.swift in Sources */, + 7B96D0DC2ADFDB8E007E02C8 /* DataBrokerProtectionPixelTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -12405,6 +12329,7 @@ 37AFCE8527DA2D3900471A10 /* PreferencesSidebar.swift in Sources */, B6C00ED5292FB21E009C73A6 /* HoveredLinkTabExtension.swift in Sources */, AA5C8F5E2590EEE800748EB7 /* NSPointExtension.swift in Sources */, + 4BF0E5122AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, AA6EF9AD25066F42004754E6 /* WindowsManager.swift in Sources */, 1D43EB3A292B63B00065E5D6 /* BWRequest.swift in Sources */, B68458CD25C7EB9000DC17B6 /* WKWebViewConfigurationExtensions.swift in Sources */, @@ -12423,9 +12348,9 @@ 85D33F1225C82EB3002B91A6 /* ConfigurationManager.swift in Sources */, 31F28C4F28C8EEC500119F70 /* YoutubePlayerUserScript.swift in Sources */, B6A9E48426146AAB0067D1B9 /* PixelParameters.swift in Sources */, - 4B7A94B429C16294000C7D4C /* ErrorWithParameters.swift in Sources */, AA5FA697275F90C400DCE9C9 /* FaviconImageCache.swift in Sources */, 1430DFF524D0580F00B8978C /* TabBarViewController.swift in Sources */, + B62B483E2ADE48DE000DECE5 /* MenuBuilder.swift in Sources */, 4B92929B26670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift in Sources */, 56D145EB29E6C99B00E3488A /* DataImportStatusProviding.swift in Sources */, 31D5375C291D944100407A95 /* PasswordManagementBitwardenItemView.swift in Sources */, @@ -12501,13 +12426,11 @@ 1D8057C82A83CAEE00F4FED6 /* SupportedOsChecker.swift in Sources */, AA92127725ADA07900600CD4 /* WKWebViewExtension.swift in Sources */, AAAB9114288EB1D600A057A9 /* CleanThisHistoryMenuItem.swift in Sources */, - B6A9E499261474120067D1B9 /* TimedPixel.swift in Sources */, B6C0B23626E732000031CB7F /* DownloadListItem.swift in Sources */, 4B9DB0232A983B24000927DB /* WaitlistRequest.swift in Sources */, B6B1E87E26D5DA0E0062C350 /* DownloadsPopover.swift in Sources */, 85774AFF2A713D3B00DE0561 /* BookmarksBarMenuFactory.swift in Sources */, 4B9292A026670D2A00AD2C21 /* SpacerNode.swift in Sources */, - 4B2D06342A11CC4600DE1F49 /* SystemExtensionManager.swift in Sources */, 3775913629AB9A1C00E26367 /* SyncManagementDialogViewController.swift in Sources */, B6C0BB6729AEFF8100AE8E3C /* BookmarkExtension.swift in Sources */, 4BE6547F271FCD4D008D1D63 /* PasswordManagementCreditCardModel.swift in Sources */, @@ -12544,7 +12467,7 @@ AAB7320926DD0CD9002FACF9 /* FireViewController.swift in Sources */, 4B92928C26670D1700AD2C21 /* OutlineSeparatorViewCell.swift in Sources */, 4BB99D0426FE191E001E4761 /* SafariDataImporter.swift in Sources */, - 4B9DB0262A983B24000927DB /* WaitlistViewModel.swift in Sources */, + 4B9DB0262A983B24000927DB /* NetworkProtectionWaitlistViewModel.swift in Sources */, 987799F929999973005D8EB6 /* LocalBookmarkStore.swift in Sources */, 1D02633628D8A9A9005CBB41 /* BWEncryption.m in Sources */, B69B503A2726A12500758A2B /* StatisticsLoader.swift in Sources */, @@ -12604,7 +12527,7 @@ 3184AC6F288F2A1100C35E4B /* CookieNotificationAnimationModel.swift in Sources */, 4B9DB0382A983B24000927DB /* JoinedWaitlistView.swift in Sources */, B63ED0E526BB8FB900A9DAD1 /* SharingMenu.swift in Sources */, - 4B9DB0322A983B24000927DB /* EnableNetworkProtectionView.swift in Sources */, + 4B9DB0322A983B24000927DB /* EnableWaitlistFeatureView.swift in Sources */, AA4FF40C2624751A004E2377 /* GrammarFeaturesManager.swift in Sources */, 4B9DB0442A983B24000927DB /* WaitlistModalViewController.swift in Sources */, B6DA06E8291401D700225DE2 /* WKMenuItemIdentifier.swift in Sources */, @@ -12636,13 +12559,15 @@ B63BDF7E27FDAA640072D75B /* PrivacyDashboardWebView.swift in Sources */, 37CD54CF27F2FDD100F1F7B9 /* AppearancePreferences.swift in Sources */, B6B1E87B26D381710062C350 /* DownloadListCoordinator.swift in Sources */, + B68D21C82ACBC96D002DA3C2 /* MockPrivacyConfiguration.swift in Sources */, B647EFBB2922584B00BA628D /* AdClickAttributionTabExtension.swift in Sources */, 4B980E212817604000282EE1 /* NSNotificationName+Debug.swift in Sources */, + B690152C2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */, 31F7F2A6288AD2CA001C0D64 /* NavigationBarBadgeAnimationView.swift in Sources */, AAC5E4F125D6BF10007F5990 /* AddressBarButton.swift in Sources */, AAE7527E263B05C600B973F8 /* HistoryEntry.swift in Sources */, AA5FA69D275F945C00DCE9C9 /* FaviconStore.swift in Sources */, - 4B9DB0352A983B24000927DB /* NetworkProtectionTermsAndConditionsView.swift in Sources */, + 4B9DB0352A983B24000927DB /* WaitlistTermsAndConditionsView.swift in Sources */, AAB8203C26B2DE0D00788AC3 /* SuggestionListCharacteristics.swift in Sources */, AAADFD06264AA282001555EA /* TimeIntervalExtension.swift in Sources */, 4B6785472AA8DE68008A5004 /* NetworkProtectionFeatureDisabler.swift in Sources */, @@ -12694,7 +12619,6 @@ 4B92929C26670D2A00AD2C21 /* PasteboardFolder.swift in Sources */, 3171D6B82889849F0068632A /* CookieManagedNotificationView.swift in Sources */, B6106BAB26A7BF1D0013B453 /* PermissionType.swift in Sources */, - 4B8F52412A18326600BE7131 /* NetworkProtectionTunnelController.swift in Sources */, AAC6881B28626C1900D54247 /* RecentlyClosedWindow.swift in Sources */, 85707F2A276A35FE00DC0649 /* ActionSpeech.swift in Sources */, 4B0511BD262CAA5A00F6079C /* PrivacySecurityPreferences.swift in Sources */, @@ -12703,6 +12627,7 @@ 7B430EA12A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */, 1E7E2E942902AC0E00C01B54 /* PrivacyDashboardPermissionHandler.swift in Sources */, AA9FF95F24A1FB690039E328 /* TabCollectionViewModel.swift in Sources */, + 4BF0E5052AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, AAC5E4D125D6A709007F5990 /* BookmarkManager.swift in Sources */, 37CD54CD27F2FDD100F1F7B9 /* AboutModel.swift in Sources */, 4BE65476271FCD41008D1D63 /* PasswordManagementCreditCardItemView.swift in Sources */, @@ -12730,6 +12655,7 @@ 85589E8227BBB8630038AD11 /* HomePageView.swift in Sources */, B6BF5D932947199A006742B1 /* SerpHeadersNavigationResponder.swift in Sources */, 569277C129DDCBB500B633EF /* HomePageContinueSetUpModel.swift in Sources */, + B68D21CF2ACBC9FC002DA3C2 /* ContentBlockerRulesManagerMock.swift in Sources */, B6A924D92664C72E001A28CA /* WebKitDownloadTask.swift in Sources */, 4B59023E26B35F3600489384 /* ChromiumLoginReader.swift in Sources */, 85D885B326A5A9DE0077C374 /* NSAlert+PasswordManager.swift in Sources */, @@ -12806,6 +12732,7 @@ 85CC1D7D26A05F250062F04E /* PasswordManagementItemModel.swift in Sources */, AAD86E52267A0DFF005C11BE /* UpdateController.swift in Sources */, 85A0118225AF60E700FA6A0C /* FindInPageModel.swift in Sources */, + 7BA7CC4E2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift in Sources */, 4B9292A226670D2A00AD2C21 /* PseudoFolder.swift in Sources */, AA7E919A2875B39300AB6B62 /* Visit.swift in Sources */, 4BCF15D92ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift in Sources */, @@ -12926,7 +12853,7 @@ B687B7CA2947A029001DEA6F /* ContentBlockingTabExtension.swift in Sources */, 85B7184C27677C6500B4277F /* OnboardingViewController.swift in Sources */, 4B379C1E27BDB7FF008A968E /* DeviceAuthenticator.swift in Sources */, - 7BFE95522A9DF1CE0081ABE9 /* NetworkProtectionWaitlistMenu.swift in Sources */, + 7BFE95522A9DF1CE0081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */, 1456D6E124EFCBC300775049 /* TabBarCollectionView.swift in Sources */, 4B4D60BF2A0C848A00BCD287 /* NetworkProtection+ConvenienceInitializers.swift in Sources */, B655124829A79465009BFE1C /* NavigationActionExtension.swift in Sources */, @@ -12960,6 +12887,7 @@ 853014D625E671A000FB8205 /* PageObserverUserScript.swift in Sources */, B642738227B65BAC0005DFD1 /* SecureVaultErrorReporter.swift in Sources */, 4B139AFD26B60BD800894F82 /* NSImageExtensions.swift in Sources */, + B62B48392ADE46FC000DECE5 /* Application.swift in Sources */, 4B9DB02C2A983B24000927DB /* WaitlistKeychainStorage.swift in Sources */, 85625996269C953C00EE44BC /* PasswordManagementViewController.swift in Sources */, 4BB99D0226FE191E001E4761 /* ImportedBookmarks.swift in Sources */, @@ -12981,6 +12909,7 @@ AA7412B224D0B3AC00D22FE0 /* TabBarViewItem.swift in Sources */, 856C98D52570116900A22F1F /* NSWindow+Toast.swift in Sources */, B31055C427A1BA1D001AC618 /* AutoconsentUserScript.swift in Sources */, + 7B3618C22ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift in Sources */, 859E7D6B27453BF3009C2B69 /* BookmarksExporter.swift in Sources */, 7B2DDCF82A93A8BB0039D884 /* NetworkProtectionAppEvents.swift in Sources */, 377D801C2AB47FBB002AF251 /* FavoritesDisplayModeSyncHandler.swift in Sources */, @@ -13014,6 +12943,7 @@ 987799F62999996B005D8EB6 /* BookmarkDatabase.swift in Sources */, 4BE53374286E39F10019DBFD /* ChromiumKeychainPrompt.swift in Sources */, B6553692268440D700085A79 /* WKProcessPool+GeolocationProvider.swift in Sources */, + B68D21C32ACBC916002DA3C2 /* ContentBlockingMock.swift in Sources */, AA5C1DD1285A154E0089850C /* RecentlyClosedMenu.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -13087,7 +13017,6 @@ B63ED0DE26AFD9A300A9DAD1 /* AVCaptureDeviceMock.swift in Sources */, 98A95D88299A2DF900B9B81A /* BookmarkMigrationTests.swift in Sources */, B63ED0E026AFE32F00A9DAD1 /* GeolocationProviderMock.swift in Sources */, - B6BDD9F529409DDD00F68088 /* ContentBlockingMock.swift in Sources */, 378205FB283C277800D1D4AA /* MainMenuTests.swift in Sources */, 4B43469528655D1400177407 /* FirefoxDataImporterTests.swift in Sources */, 4B723E0926B0003E00E14D75 /* CSVLoginExporterTests.swift in Sources */, @@ -13147,7 +13076,6 @@ AAC9C01524CAFBCE00AD1325 /* TabTests.swift in Sources */, B69B504C2726CA2900758A2B /* MockVariantManager.swift in Sources */, 310E79BF294A19A8007C49E8 /* FireproofingReferenceTests.swift in Sources */, - B610F2EC27AA8F9400FCEBE9 /* ContentBlockerRulesManagerMock.swift in Sources */, B6BBF1722744CE36004F850E /* FireproofDomainsStoreMock.swift in Sources */, 4BA1A6D9258C0CB300F6F690 /* DataEncryptionTests.swift in Sources */, B6AA64732994B43300D99CD6 /* FutureExtensionTests.swift in Sources */, @@ -13155,7 +13083,6 @@ EA1E52B52798CF98002EC53C /* ClickToLoadModelTests.swift in Sources */, B603975029C1FF5F00902A34 /* TestsURLExtension.swift in Sources */, B6A5A27E25B9403E00AA7ADA /* FileStoreMock.swift in Sources */, - B6AA64762995164900D99CD6 /* MockPrivacyConfiguration.swift in Sources */, 56534DED29DF252C00121467 /* CapturingDefaultBrowserProvider.swift in Sources */, 1D3B1AC429378953006F4388 /* BWResponseTests.swift in Sources */, B693955F26F1C17F0015B914 /* DownloadListCoordinatorTests.swift in Sources */, @@ -13263,11 +13190,6 @@ isa = PBXTargetDependency; productRef = 4B5F14FB2A15291D0060320F /* InputFilesChecker */; }; - 31929F7D2A4C4CFF0084EA89 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B2537592A11BE7300610219 /* NetworkProtectionSystemExtension */; - targetProxy = 31929F7E2A4C4CFF0084EA89 /* PBXContainerItemProxy */; - }; 37079A93294236F20031BB3C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3706FA6A293F65D500E42796 /* DuckDuckGo Privacy Browser App Store */; @@ -13283,11 +13205,6 @@ target = AA585D7D248FD31100E9A3E2 /* DuckDuckGo Privacy Browser */; targetProxy = 4B1AD8A225FC27E200261379 /* PBXContainerItemProxy */; }; - 4B2537642A11BE7600610219 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B2537592A11BE7300610219 /* NetworkProtectionSystemExtension */; - targetProxy = 4B2537632A11BE7600610219 /* PBXContainerItemProxy */; - }; 4B4BEC4A2A11B627001D9AC5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = 4B4BEC492A11B627001D9AC5 /* NetworkProtection */; @@ -13308,16 +13225,25 @@ isa = PBXTargetDependency; productRef = 4B5F14FB2A15291D0060320F /* InputFilesChecker */; }; - 4B9579272AC7AE700062CA31 /* PBXTargetDependency */ = { + 7B20D5C82ADFEC730053C42A /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 4B2537592A11BE7300610219 /* NetworkProtectionSystemExtension */; - targetProxy = 4B9579282AC7AE700062CA31 /* PBXContainerItemProxy */; + productRef = 7B20D5C72ADFEC730053C42A /* PixelKitTestingUtilities */; }; 7B4CE8E026F02108009134B1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = AA585D7D248FD31100E9A3E2 /* DuckDuckGo Privacy Browser */; targetProxy = 7B4CE8DF26F02108009134B1 /* PBXContainerItemProxy */; }; + 7B96D0D42ADFDA7F007E02C8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31929F7B2A4C4CFF0084EA89 /* DuckDuckGo DBP */; + targetProxy = 7B96D0D32ADFDA7F007E02C8 /* PBXContainerItemProxy */; + }; + 7BEC18312AD5DA3300D30536 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4B2537592A11BE7300610219 /* NetworkProtectionSystemExtension */; + targetProxy = 7BEC18302AD5DA3300D30536 /* PBXContainerItemProxy */; + }; AA585D92248FD31400E9A3E2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = AA585D7D248FD31100E9A3E2 /* DuckDuckGo Privacy Browser */; @@ -13563,56 +13489,56 @@ }; 4B2D06462A11CFBE00DE1F49 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4B2D064B2A11D04000DE1F49 /* DuckDuckGoAgent.xcconfig */; + baseConfigurationReference = 7BA7CC0C2AD11D1E0042E5CE /* DuckDuckGoVPN.xcconfig */; buildSettings = { }; name = Debug; }; 4B2D06472A11CFBE00DE1F49 /* CI */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4B2D064B2A11D04000DE1F49 /* DuckDuckGoAgent.xcconfig */; + baseConfigurationReference = 7BA7CC0C2AD11D1E0042E5CE /* DuckDuckGoVPN.xcconfig */; buildSettings = { }; name = CI; }; 4B2D06482A11CFBE00DE1F49 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4B2D064B2A11D04000DE1F49 /* DuckDuckGoAgent.xcconfig */; + baseConfigurationReference = 7BA7CC0C2AD11D1E0042E5CE /* DuckDuckGoVPN.xcconfig */; buildSettings = { }; name = Release; }; 4B2D06492A11CFBE00DE1F49 /* Review */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4B2D064B2A11D04000DE1F49 /* DuckDuckGoAgent.xcconfig */; + baseConfigurationReference = 7BA7CC0C2AD11D1E0042E5CE /* DuckDuckGoVPN.xcconfig */; buildSettings = { }; name = Review; }; 4B2D06762A13318600DE1F49 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4B2D064D2A11D04000DE1F49 /* DuckDuckGoAgentAppStore.xcconfig */; + baseConfigurationReference = 7BA7CC0B2AD11D1E0042E5CE /* DuckDuckGoVPNAppStore.xcconfig */; buildSettings = { }; name = Debug; }; 4B2D06772A13318600DE1F49 /* CI */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4B2D064D2A11D04000DE1F49 /* DuckDuckGoAgentAppStore.xcconfig */; + baseConfigurationReference = 7BA7CC0B2AD11D1E0042E5CE /* DuckDuckGoVPNAppStore.xcconfig */; buildSettings = { }; name = CI; }; 4B2D06782A13318600DE1F49 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4B2D064D2A11D04000DE1F49 /* DuckDuckGoAgentAppStore.xcconfig */; + baseConfigurationReference = 7BA7CC0B2AD11D1E0042E5CE /* DuckDuckGoVPNAppStore.xcconfig */; buildSettings = { }; name = Release; }; 4B2D06792A13318600DE1F49 /* Review */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4B2D064D2A11D04000DE1F49 /* DuckDuckGoAgentAppStore.xcconfig */; + baseConfigurationReference = 7BA7CC0B2AD11D1E0042E5CE /* DuckDuckGoVPNAppStore.xcconfig */; buildSettings = { }; name = Review; @@ -13673,62 +13599,6 @@ }; name = Review; }; - 4B5F14D82A14702E0060320F /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 4B18E3232A1D32B1005D0AAA /* NetworkProtectionStartVPN.xcconfig */; - buildSettings = { - }; - name = Debug; - }; - 4B5F14D92A14702E0060320F /* CI */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 4B18E3232A1D32B1005D0AAA /* NetworkProtectionStartVPN.xcconfig */; - buildSettings = { - }; - name = CI; - }; - 4B5F14DA2A14702E0060320F /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 4B18E3232A1D32B1005D0AAA /* NetworkProtectionStartVPN.xcconfig */; - buildSettings = { - }; - name = Release; - }; - 4B5F14DB2A14702E0060320F /* Review */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 4B18E3232A1D32B1005D0AAA /* NetworkProtectionStartVPN.xcconfig */; - buildSettings = { - }; - name = Review; - }; - 4B5F14EE2A1476C20060320F /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 4B18E3252A1D3581005D0AAA /* NetworkProtectionStopVPN.xcconfig */; - buildSettings = { - }; - name = Debug; - }; - 4B5F14EF2A1476C20060320F /* CI */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 4B18E3252A1D3581005D0AAA /* NetworkProtectionStopVPN.xcconfig */; - buildSettings = { - }; - name = CI; - }; - 4B5F14F02A1476C20060320F /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 4B18E3252A1D3581005D0AAA /* NetworkProtectionStopVPN.xcconfig */; - buildSettings = { - }; - name = Release; - }; - 4B5F14F12A1476C20060320F /* Review */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 4B18E3252A1D3581005D0AAA /* NetworkProtectionStopVPN.xcconfig */; - buildSettings = { - }; - name = Review; - }; 4B957C3D2AC7AE700062CA31 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4B957C432AC7AF190062CA31 /* DuckDuckGoPrivacyPro.xcconfig */; @@ -13778,30 +13648,30 @@ }; name = Release; }; - 7B736E5B2A4A22B700F9922A /* Debug */ = { + 7B96D0D62ADFDA7F007E02C8 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7B9459632A4A5BAF0012535A /* NetworkProtectionEnableOnDemand.xcconfig */; + baseConfigurationReference = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; buildSettings = { }; name = Debug; }; - 7B736E5C2A4A22B700F9922A /* CI */ = { + 7B96D0D72ADFDA7F007E02C8 /* CI */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7B9459632A4A5BAF0012535A /* NetworkProtectionEnableOnDemand.xcconfig */; + baseConfigurationReference = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; buildSettings = { }; name = CI; }; - 7B736E5D2A4A22B700F9922A /* Release */ = { + 7B96D0D82ADFDA7F007E02C8 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7B9459632A4A5BAF0012535A /* NetworkProtectionEnableOnDemand.xcconfig */; + baseConfigurationReference = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; buildSettings = { }; name = Release; }; - 7B736E5E2A4A22B700F9922A /* Review */ = { + 7B96D0D92ADFDA7F007E02C8 /* Review */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7B9459632A4A5BAF0012535A /* NetworkProtectionEnableOnDemand.xcconfig */; + baseConfigurationReference = 7BF1A9DB2AE0551C00FCA683 /* DBPUnitTests.xcconfig */; buildSettings = { }; name = Review; @@ -13980,7 +13850,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 4B2D06452A11CFBE00DE1F49 /* Build configuration list for PBXNativeTarget "DuckDuckGoAgent" */ = { + 4B2D06452A11CFBE00DE1F49 /* Build configuration list for PBXNativeTarget "DuckDuckGoVPN" */ = { isa = XCConfigurationList; buildConfigurations = ( 4B2D06462A11CFBE00DE1F49 /* Debug */, @@ -13991,7 +13861,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 4B2D06752A13318600DE1F49 /* Build configuration list for PBXNativeTarget "DuckDuckGoAgentAppStore" */ = { + 4B2D06752A13318600DE1F49 /* Build configuration list for PBXNativeTarget "DuckDuckGoVPNAppStore" */ = { isa = XCConfigurationList; buildConfigurations = ( 4B2D06762A13318600DE1F49 /* Debug */, @@ -14024,28 +13894,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 4B5F14D72A14702E0060320F /* Build configuration list for PBXNativeTarget "startVPN" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4B5F14D82A14702E0060320F /* Debug */, - 4B5F14D92A14702E0060320F /* CI */, - 4B5F14DA2A14702E0060320F /* Release */, - 4B5F14DB2A14702E0060320F /* Review */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4B5F14ED2A1476C20060320F /* Build configuration list for PBXNativeTarget "stopVPN" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4B5F14EE2A1476C20060320F /* Debug */, - 4B5F14EF2A1476C20060320F /* CI */, - 4B5F14F02A1476C20060320F /* Release */, - 4B5F14F12A1476C20060320F /* Review */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 4B957C3C2AC7AE700062CA31 /* Build configuration list for PBXNativeTarget "DuckDuckGo Privacy Pro" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -14068,13 +13916,13 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 7B736E5A2A4A22B700F9922A /* Build configuration list for PBXNativeTarget "enableOnDemand" */ = { + 7B96D0D52ADFDA7F007E02C8 /* Build configuration list for PBXNativeTarget "DuckDuckGoDBPTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 7B736E5B2A4A22B700F9922A /* Debug */, - 7B736E5C2A4A22B700F9922A /* CI */, - 7B736E5D2A4A22B700F9922A /* Release */, - 7B736E5E2A4A22B700F9922A /* Review */, + 7B96D0D62ADFDA7F007E02C8 /* Debug */, + 7B96D0D72ADFDA7F007E02C8 /* CI */, + 7B96D0D82ADFDA7F007E02C8 /* Release */, + 7B96D0D92ADFDA7F007E02C8 /* Review */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -14515,10 +14363,6 @@ package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Common; }; - 4B2D064E2A11D0D000DE1F49 /* NetworkProtectionUI */ = { - isa = XCSwiftPackageProductDependency; - productName = NetworkProtectionUI; - }; 4B2D067E2A1334D700DE1F49 /* NetworkProtectionUI */ = { isa = XCSwiftPackageProductDependency; productName = NetworkProtectionUI; @@ -14660,6 +14504,76 @@ isa = XCSwiftPackageProductDependency; productName = Purchase; }; + 7B20D5C52ADFEC6E0053C42A /* PixelKitTestingUtilities */ = { + isa = XCSwiftPackageProductDependency; + productName = PixelKitTestingUtilities; + }; + 7B20D5C72ADFEC730053C42A /* PixelKitTestingUtilities */ = { + isa = XCSwiftPackageProductDependency; + productName = PixelKitTestingUtilities; + }; + 7B31FD8B2AD125620086AA24 /* NetworkProtectionIPC */ = { + isa = XCSwiftPackageProductDependency; + productName = NetworkProtectionIPC; + }; + 7B31FD8D2AD125760086AA24 /* NetworkProtectionIPC */ = { + isa = XCSwiftPackageProductDependency; + productName = NetworkProtectionIPC; + }; + 7B31FD8F2AD1257B0086AA24 /* NetworkProtectionIPC */ = { + isa = XCSwiftPackageProductDependency; + productName = NetworkProtectionIPC; + }; + 7B5F9A742AE2BE4E002AEBC0 /* PixelKit */ = { + isa = XCSwiftPackageProductDependency; + productName = PixelKit; + }; + 7BA59C9A2AE18B49009A97B1 /* SystemExtensionManager */ = { + isa = XCSwiftPackageProductDependency; + productName = SystemExtensionManager; + }; + 7BA7CC5E2AD1210C0042E5CE /* Networking */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Networking; + }; + 7BA7CC602AD1211C0042E5CE /* Networking */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Networking; + }; + 7BBD44272AD730A400D0A064 /* PixelKit */ = { + isa = XCSwiftPackageProductDependency; + productName = PixelKit; + }; + 7BEC182E2AD5D8DC00D30536 /* SystemExtensionManager */ = { + isa = XCSwiftPackageProductDependency; + productName = SystemExtensionManager; + }; + 7BEEA5112AD1235B00A9E72B /* NetworkProtectionIPC */ = { + isa = XCSwiftPackageProductDependency; + productName = NetworkProtectionIPC; + }; + 7BEEA5132AD1236300A9E72B /* NetworkProtectionIPC */ = { + isa = XCSwiftPackageProductDependency; + productName = NetworkProtectionIPC; + }; + 7BEEA5152AD1236E00A9E72B /* NetworkProtectionUI */ = { + isa = XCSwiftPackageProductDependency; + productName = NetworkProtectionUI; + }; + 7BF7705E2AD6C999001C9182 /* PixelKit */ = { + isa = XCSwiftPackageProductDependency; + productName = PixelKit; + }; + 7BFCB74D2ADE7E1A00DA3EA7 /* PixelKit */ = { + isa = XCSwiftPackageProductDependency; + productName = PixelKit; + }; + 7BFCB74F2ADE7E2300DA3EA7 /* PixelKit */ = { + isa = XCSwiftPackageProductDependency; + productName = PixelKit; + }; 9807F644278CA16F00E1547B /* BrowserServicesKit */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b50af55827..425c987815 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,7 +15,7 @@ "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "branch" : "dominik/sync-ffs-favorites", - "revision" : "c9e4e96a29cfedd8356ddd4aa9801a90f186145c" + "revision" : "e2480a22fb70cbf8348bbec5202a04c8afc6a755" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "aa279a3b006a0b1e009707311283c7fcaed24fb7", - "version" : "4.39.0" + "revision" : "254b23cf292140498650421bb31fd05740f4579b", + "version" : "4.40.0" } }, { @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", "state" : { - "revision" : "6dd7d696d4e666cedb2f1890a46fe53615226646", - "version" : "8.4.2" + "revision" : "c8e895c8fd50dc76e8d8dc827a636ad77b7f46ff", + "version" : "9.0.0" } }, { @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "51e2b46f413bf3ef18afefad631ca70f2c25ef70", - "version" : "1.4.0" + "revision" : "b4ac92a444e79d5651930482623b9f6dc9265667", + "version" : "2.0.0" } }, { diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo DBP.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo DBP.xcscheme index b869953f7d..421a9228db 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo DBP.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo DBP.xcscheme @@ -28,6 +28,19 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" shouldAutocreateTestPlan = "YES"> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGoAgent.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGoVPN.xcscheme similarity index 89% rename from DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGoAgent.xcscheme rename to DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGoVPN.xcscheme index adc90361ec..83d1615164 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGoAgent.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGoVPN.xcscheme @@ -15,8 +15,8 @@ @@ -44,8 +44,8 @@ @@ -61,8 +61,8 @@ diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGoVPNAppStore.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGoVPNAppStore.xcscheme new file mode 100644 index 0000000000..3367c080ae --- /dev/null +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGoVPNAppStore.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DuckDuckGo/AppDelegate/AppConfigurationURLProvider.swift b/DuckDuckGo/AppDelegate/AppConfigurationURLProvider.swift index 3a867a0494..333d16cb3c 100644 --- a/DuckDuckGo/AppDelegate/AppConfigurationURLProvider.swift +++ b/DuckDuckGo/AppDelegate/AppConfigurationURLProvider.swift @@ -22,6 +22,8 @@ import Configuration struct AppConfigurationURLProvider: ConfigurationURLProviding { func url(for configuration: Configuration) -> URL { + // URLs for privacyConfiguration and trackerDataSet shall match the ones in update_embedded.sh. + // Danger checks that the URLs match on every PR. If the code changes, the regex that Danger uses may need an update. switch configuration { case .bloomFilterBinary: return URL(string: "https://staticcdn.duckduckgo.com/https/https-mobile-v2-bloom.bin")! case .bloomFilterSpec: return URL(string: "https://staticcdn.duckduckgo.com/https/https-mobile-v2-bloom-spec.json")! diff --git a/DuckDuckGo/AppDelegate/AppDelegate.swift b/DuckDuckGo/AppDelegate/AppDelegate.swift index 7979c1f334..74ee5e66c6 100644 --- a/DuckDuckGo/AppDelegate/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate/AppDelegate.swift @@ -55,13 +55,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel #else private let keyStore = EncryptionKeyStore() #endif - private var fileStore: FileStore! + + private let fileStore: FileStore private(set) var stateRestorationManager: AppStateRestorationManager! private var grammarFeaturesManager = GrammarFeaturesManager() private let crashReporter = CrashReporter() - private(set) var internalUserDecider: InternalUserDecider? - private(set) var featureFlagger: FeatureFlagger! + let internalUserDecider: InternalUserDecider + let featureFlagger: FeatureFlagger private var appIconChanger: AppIconChanger! private(set) var syncDataProviders: SyncDataProviders! @@ -78,11 +79,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel #endif // swiftlint:disable:next function_body_length - func applicationWillFinishLaunching(_ notification: Notification) { - APIRequest.Headers.setUserAgent(UserAgent.duckDuckGoUserAgent()) - Configuration.setURLProvider(AppConfigurationURLProvider()) + override init() { + do { + let encryptionKey = NSApplication.runType.requiresEnvironment ? try keyStore.readKey() : nil + fileStore = EncryptedFileStore(encryptionKey: encryptionKey) + } catch { + os_log("App Encryption Key could not be read: %s", "\(error)") + fileStore = EncryptedFileStore() + } + + let internalUserDeciderStore = InternalUserDeciderStore(fileStore: fileStore) + internalUserDecider = DefaultInternalUserDecider(store: internalUserDeciderStore) - if !NSApp.isRunningUnitTests { + if NSApplication.runType.requiresEnvironment { #if DEBUG Pixel.setUp(dryRun: true) #else @@ -124,34 +133,24 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel } } - do { - let encryptionKey = NSApp.isRunningUnitTests ? nil : try keyStore.readKey() - fileStore = EncryptedFileStore(encryptionKey: encryptionKey) - } catch { - os_log("App Encryption Key could not be read: %s", "\(error)") - fileStore = EncryptedFileStore() - } - stateRestorationManager = AppStateRestorationManager(fileStore: fileStore) - - let internalUserDeciderStore = InternalUserDeciderStore(fileStore: fileStore) - let internalUserDecider = DefaultInternalUserDecider(store: internalUserDeciderStore) - self.internalUserDecider = internalUserDecider - #if DEBUG - func mock(_ className: String) -> T { - ((NSClassFromString(className) as? NSObject.Type)!.init() as? T)! - } - AppPrivacyFeatures.shared = NSApp.isRunningUnitTests - // runtime mock-replacement for Unit Tests, to be redone when we‘ll be doing Dependency Injection - ? AppPrivacyFeatures(contentBlocking: mock("ContentBlockingMock"), httpsUpgradeStore: mock("HTTPSUpgradeStoreMock")) - : AppPrivacyFeatures(contentBlocking: AppContentBlocking(internalUserDecider: internalUserDecider), database: Database.shared) + AppPrivacyFeatures.shared = NSApplication.runType.requiresEnvironment + // runtime mock-replacement for Unit Tests, to be redone when we‘ll be doing Dependency Injection + ? AppPrivacyFeatures(contentBlocking: AppContentBlocking(internalUserDecider: internalUserDecider), database: Database.shared) + : AppPrivacyFeatures(contentBlocking: ContentBlockingMock(), httpsUpgradeStore: HTTPSUpgradeStoreMock()) #else AppPrivacyFeatures.shared = AppPrivacyFeatures(contentBlocking: AppContentBlocking(internalUserDecider: internalUserDecider), database: Database.shared) #endif featureFlagger = DefaultFeatureFlagger(internalUserDecider: internalUserDecider, privacyConfig: AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager.privacyConfig) - NSApp.mainMenuTyped.setup(with: featureFlagger) + } + + func applicationWillFinishLaunching(_ notification: Notification) { + APIRequest.Headers.setUserAgent(UserAgent.duckDuckGoUserAgent()) + Configuration.setURLProvider(AppConfigurationURLProvider()) + + stateRestorationManager = AppStateRestorationManager(fileStore: fileStore) #if SPARKLE updateController = UpdateController(internalUserDecider: internalUserDecider) @@ -162,7 +161,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel } func applicationDidFinishLaunching(_ notification: Notification) { - guard !NSApp.isRunningUnitTests else { return } + guard NSApp.runType.requiresEnvironment else { return } defer { didFinishLaunching = true } @@ -188,7 +187,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel // MARK: perform first time launch logic here } - let statisticsLoader = (NSApp.isRunningUnitTests ? nil : StatisticsLoader.shared) + let statisticsLoader = NSApp.runType.requiresEnvironment ? StatisticsLoader.shared : nil statisticsLoader?.load() startupSync() @@ -221,7 +220,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel #endif #if DBP - DataBrokerProtectionManager.shared.runOperationsAndStartSchedulerIfPossible() + DispatchQueue.global(qos: .background).async { + DataBrokerProtectionManager.shared.runOperationsAndStartSchedulerIfPossible() + } #endif } @@ -272,7 +273,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel } func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { - if WindowControllersManager.shared.mainWindowControllers.isEmpty { + if WindowControllersManager.shared.mainWindowControllers.isEmpty, + case .normal = sender.runType { WindowsManager.openNewWindow() return true } @@ -280,10 +282,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel } func applicationDockMenu(_ sender: NSApplication) -> NSMenu? { - guard let internalUserDecider else { - return nil - } - return ApplicationDockMenu(internalUserDecider: internalUserDecider) } diff --git a/DuckDuckGo/AppDelegate/Application.swift b/DuckDuckGo/AppDelegate/Application.swift new file mode 100644 index 0000000000..2099e4aef8 --- /dev/null +++ b/DuckDuckGo/AppDelegate/Application.swift @@ -0,0 +1,50 @@ +// +// Application.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppKit +import Foundation + +@objc(Application) +final class Application: NSApplication { + + private let copyHandler = CopyHandler() + private var _delegate: AppDelegate! + + override init() { + super.init() + + _delegate = AppDelegate() + self.delegate = _delegate + + let mainMenu = MainMenu(featureFlagger: _delegate.featureFlagger, + bookmarkManager: _delegate.bookmarksManager, + faviconManager: FaviconManager.shared, + copyHandler: copyHandler) + self.mainMenu = mainMenu + + // Makes sure Spotlight search is part of Help menu + self.helpMenu = mainMenu.helpMenu + self.windowsMenu = mainMenu.windowsMenu + self.servicesMenu = mainMenu.servicesMenu + } + + required init?(coder: NSCoder) { + fatalError("\(Self.self): Bad initializer") + } + +} diff --git a/DuckDuckGoAgent/Assets.xcassets/Contents.json b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/Contents.json similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Contents.json rename to DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/Contents.json diff --git a/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-JoinWaitlistHeader.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-JoinWaitlistHeader.imageset/Contents.json new file mode 100644 index 0000000000..3e77343ab8 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-JoinWaitlistHeader.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "DBP-JoinWaitlistHeader.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "DBP-JoinWaitlistHeader-Dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-JoinWaitlistHeader.imageset/DBP-JoinWaitlistHeader-Dark.pdf b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-JoinWaitlistHeader.imageset/DBP-JoinWaitlistHeader-Dark.pdf new file mode 100644 index 0000000000..388eb32cca Binary files /dev/null and b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-JoinWaitlistHeader.imageset/DBP-JoinWaitlistHeader-Dark.pdf differ diff --git a/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-JoinWaitlistHeader.imageset/DBP-JoinWaitlistHeader.pdf b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-JoinWaitlistHeader.imageset/DBP-JoinWaitlistHeader.pdf new file mode 100644 index 0000000000..bd9ba674a7 Binary files /dev/null and b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-JoinWaitlistHeader.imageset/DBP-JoinWaitlistHeader.pdf differ diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift b/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift index bd7f489e34..bbcad8d6bb 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift @@ -332,7 +332,7 @@ final class LocalBookmarkManager: BookmarkManager { func requestSync() { Task { @MainActor in - guard let syncService = (NSApp.delegate as? AppDelegate)?.syncService else { + guard let syncService = NSApp.delegateTyped.syncService else { return } os_log(.debug, log: OSLog.sync, "Requesting sync if enabled") diff --git a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift index a3a755df5a..d9600852ec 100644 --- a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift +++ b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift @@ -24,6 +24,7 @@ import DDGSync import Bookmarks import Cocoa import Persistence +import PixelKit // swiftlint:disable:next type_body_length final class LocalBookmarkStore: BookmarkStore { @@ -179,7 +180,7 @@ final class LocalBookmarkStore: BookmarkStore { } private func commonOnSaveErrorHandler(_ error: Error, source: String = #function) { - guard !NSApp.isRunningUnitTests else { return } + guard NSApp.runType.requiresEnvironment else { return } assertionFailure("LocalBookmarkStore: Saving of context failed") @@ -188,19 +189,19 @@ final class LocalBookmarkStore: BookmarkStore { let processedErrors = CoreDataErrorsParser.parse(error: innerError as NSError) var params = processedErrors.errorPixelParameters - params[Pixel.Parameters.errorSource] = source + params[PixelKit.Parameters.errorSource] = source Pixel.fire(.debug(event: .bookmarksSaveFailed, error: error), withAdditionalParameters: params) } else { Pixel.fire(.debug(event: .bookmarksSaveFailed, error: localError), - withAdditionalParameters: [Pixel.Parameters.errorSource: source]) + withAdditionalParameters: [PixelKit.Parameters.errorSource: source]) } } else { let error = error as NSError let processedErrors = CoreDataErrorsParser.parse(error: error) var params = processedErrors.errorPixelParameters - params[Pixel.Parameters.errorSource] = source + params[PixelKit.Parameters.errorSource] = source Pixel.fire(.debug(event: .bookmarksSaveFailed, error: error), withAdditionalParameters: params) } @@ -289,7 +290,7 @@ final class LocalBookmarkStore: BookmarkStore { private func reportOrphanedBookmarksIfNeeded() { Task { @MainActor in - guard let syncService = (NSApp.delegate as? AppDelegate)?.syncService, syncService.authState == .inactive else { + guard let syncService = NSApp.delegateTyped.syncService, syncService.authState == .inactive else { return } Pixel.fire(.debug(event: .orphanedBookmarksPresent)) @@ -733,7 +734,7 @@ final class LocalBookmarkStore: BookmarkStore { let error = error as NSError let processedErrors = CoreDataErrorsParser.parse(error: error) - if !NSApp.isRunningUnitTests { + if NSApp.runType.requiresEnvironment { Pixel.fire(.debug(event: .bookmarksSaveFailedOnImport, error: error), withAdditionalParameters: processedErrors.errorPixelParameters) assertionFailure("LocalBookmarkStore: Saving of context failed, error: \(error.localizedDescription)") diff --git a/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuFactory.swift b/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuFactory.swift index b006c76570..05339f29b9 100644 --- a/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuFactory.swift +++ b/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuFactory.swift @@ -20,16 +20,10 @@ import Foundation struct BookmarksBarMenuFactory { - static func replace(_ menuItem: NSMenuItem?, _ prefs: AppearancePreferences = .shared) -> NSMenuItem? { - assert(menuItem != nil) - guard let menuItem, let menu = menuItem.menu else { - return nil - } - + static func replace(_ menuItem: NSMenuItem, _ prefs: AppearancePreferences = .shared) -> NSMenuItem? { + guard let menu = menuItem.menu else { return nil } let index = menu.index(of: menuItem) - guard index >= 0 else { - return nil - } + guard index >= 0 else { return nil } let item = makeMenuItem(prefs) menu.replaceItem(at: index, with: item) diff --git a/DuckDuckGo/Common/Database/CoreDataStore.swift b/DuckDuckGo/Common/Database/CoreDataStore.swift index 83160398dd..180a166507 100644 --- a/DuckDuckGo/Common/Database/CoreDataStore.swift +++ b/DuckDuckGo/Common/Database/CoreDataStore.swift @@ -60,7 +60,7 @@ internal class CoreDataStore { private var readContext: NSManagedObjectContext? { if case .none = _readContext { #if DEBUG - if NSApp.isRunningUnitTests { + guard NSApp.runType.requiresEnvironment else { _readContext = .some(.none) return .none } diff --git a/DuckDuckGo/Common/Database/Database.swift b/DuckDuckGo/Common/Database/Database.swift index 0bcb66cd3c..7af2ce4cf2 100644 --- a/DuckDuckGo/Common/Database/Database.swift +++ b/DuckDuckGo/Common/Database/Database.swift @@ -58,7 +58,7 @@ final class Database { model: .init(byMerging: [mainModel, httpsUpgradeModel])!), nil) } #if DEBUG - assert(!NSApp.isRunningUnitTests, "Use CoreData.---Container() methods for testing purposes") + assert(![.unitTests, .xcPreviews].contains(NSApp.runType), "Use CoreData.---Container() methods for testing purposes") #endif let keyStore: EncryptionKeyStoring diff --git a/DuckDuckGo/Common/Extensions/BundleExtension.swift b/DuckDuckGo/Common/Extensions/BundleExtension.swift index ecb2bcd243..8a5dd88545 100644 --- a/DuckDuckGo/Common/Extensions/BundleExtension.swift +++ b/DuckDuckGo/Common/Extensions/BundleExtension.swift @@ -36,11 +36,6 @@ extension Bundle { static let appGroup = "NETP_APP_GROUP" } - var displayName: String? { - object(forInfoDictionaryKey: Keys.displayName) as? String ?? - object(forInfoDictionaryKey: Keys.name) as? String - } - var versionNumber: String? { object(forInfoDictionaryKey: Keys.versionNumber) as? String } diff --git a/DuckDuckGo/Common/Extensions/EmailManagerExtension.swift b/DuckDuckGo/Common/Extensions/EmailManagerExtension.swift index 928746220e..9818033dab 100644 --- a/DuckDuckGo/Common/Extensions/EmailManagerExtension.swift +++ b/DuckDuckGo/Common/Extensions/EmailManagerExtension.swift @@ -18,6 +18,7 @@ import Foundation import BrowserServicesKit +import PixelKit extension EmailManager { @@ -35,10 +36,10 @@ extension EmailManager { var pixelParameters: [String: String] = [:] if let cohort = self.cohort { - pixelParameters[Pixel.Parameters.emailCohort] = cohort + pixelParameters[PixelKit.Parameters.emailCohort] = cohort } - pixelParameters[Pixel.Parameters.emailLastUsed] = self.lastUseDate + pixelParameters[PixelKit.Parameters.emailLastUsed] = self.lastUseDate return pixelParameters } diff --git a/DuckDuckGo/Common/Extensions/NSApplicationExtension.swift b/DuckDuckGo/Common/Extensions/NSApplicationExtension.swift index 226302d287..d613999103 100644 --- a/DuckDuckGo/Common/Extensions/NSApplicationExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSApplicationExtension.swift @@ -25,38 +25,52 @@ extension NSApplication { ProcessInfo.processInfo.environment["APP_SANDBOX_CONTAINER_ID"] != nil } - @objc enum RunType: Int, CustomStringConvertible { + enum RunType { case normal case unitTests case integrationTests case uiTests + case xcPreviews - var description: String { + /// Defines if app run type requires loading full environment, i.e. databases, saved state, keychain etc. + var requiresEnvironment: Bool { switch self { - case .normal: return "normal" - case .unitTests: return "unitTests" - case .integrationTests: return "integrationTests" - case .uiTests: return "uiTests" + case .normal, .integrationTests, .uiTests: + return true + case .unitTests, .xcPreviews: + return false } } } - @objc dynamic class var runType: RunType { .normal } + static let runType: RunType = { +#if DEBUG + if let testBundlePath = ProcessInfo().environment["XCTestBundlePath"] { + if testBundlePath.contains("Unit") { + return .unitTests + } else if testBundlePath.contains("Integration") { + return .integrationTests + } else { + return .uiTests + } + } else if ProcessInfo().environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { + return .xcPreviews + } else { + return .normal + } +#else + return .normal +#endif + }() var runType: RunType { Self.runType } - var isRunningUnitTests: Bool { - if case .unitTests = Self.runType { return true } - return false - } - - var isRunningIntegrationTests: Bool { - if case .integrationTests = Self.runType { return true } - return false - } - #if !NETWORK_EXTENSION var mainMenuTyped: MainMenu { return mainMenu as! MainMenu // swiftlint:disable:this force_cast } + + var delegateTyped: AppDelegate { + return delegate as! AppDelegate // swiftlint:disable:this force_cast + } #endif var isCommandPressed: Bool { diff --git a/DuckDuckGo/Common/Extensions/NSMenuExtension.swift b/DuckDuckGo/Common/Extensions/NSMenuExtension.swift index 010ac88143..d05cfbc33b 100644 --- a/DuckDuckGo/Common/Extensions/NSMenuExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSMenuExtension.swift @@ -16,16 +16,13 @@ // limitations under the License. // -import Cocoa +import AppKit extension NSMenu { - convenience init(items: [NSMenuItem]) { - self.init() - - items.forEach { item in - addItem(item) - } + convenience init(title: String = "", items: [NSMenuItem]) { + self.init(title: title) + self.items = items } func indexOfItem(withIdentifier id: String) -> Int? { diff --git a/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift b/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift index 7e5f66192e..1363b9dc1c 100644 --- a/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift @@ -16,20 +16,31 @@ // limitations under the License. // -import Cocoa +import AppKit extension NSMenuItem { static var empty: NSMenuItem { - let item = NSMenuItem(title: UserText.bookmarksBarFolderEmpty, action: nil, target: nil, keyEquivalent: "") + let item = NSMenuItem(title: UserText.bookmarksBarFolderEmpty) item.isEnabled = false return item } - convenience init(title string: String, action selector: Selector?, target: AnyObject?, keyEquivalent charCode: String = "", representedObject: Any? = nil) { - self.init(title: string, action: selector, keyEquivalent: charCode) + convenience init(title string: String, action selector: Selector? = nil, target: AnyObject? = nil, keyEquivalent: [KeyEquivalentElement] = [], representedObject: Any? = nil, items: [NSMenuItem]? = nil) { + self.init(title: string, action: selector, keyEquivalent: keyEquivalent.charCode) + if !keyEquivalent.modifierMask.isEmpty { + self.keyEquivalentModifierMask = keyEquivalent.modifierMask + } self.target = target self.representedObject = representedObject + + if let items { + self.submenu = NSMenu(title: title, items: items) + } + } + + convenience init(title string: String, action selector: Selector? = nil, target: AnyObject? = nil, keyEquivalent: [KeyEquivalentElement] = [], representedObject: Any? = nil, @MenuBuilder items: () -> [NSMenuItem]) { + self.init(title: string, action: selector, target: target, keyEquivalent: keyEquivalent, representedObject: representedObject, items: items()) } convenience init(action selector: Selector?) { @@ -71,4 +82,104 @@ extension NSMenuItem { parent?.submenu?.removeItem(self) } + @discardableResult + func alternate() -> NSMenuItem { + self.isAlternate = true + return self + } + + @discardableResult + func hidden() -> NSMenuItem { + self.isHidden = true + if !keyEquivalent.isEmpty { + self.allowsKeyEquivalentWhenHidden = true + } + return self + } + + @discardableResult + func submenu(_ submenu: NSMenu) -> NSMenuItem { + self.submenu = submenu + return self + } + + @discardableResult + func withImage(_ image: NSImage?) -> NSMenuItem { + self.image = image + return self + } + + @discardableResult + func targetting(_ target: AnyObject) -> NSMenuItem { + self.target = target + return self + } + + @discardableResult + func withSubmenu(_ submenu: NSMenu) -> NSMenuItem { + self.submenu = submenu + return self + } + + @discardableResult + func withModifierMask(_ mask: NSEvent.ModifierFlags) -> NSMenuItem { + self.keyEquivalentModifierMask = mask + return self + } + +} + +enum KeyEquivalentElement: ExpressibleByStringLiteral { + public typealias StringLiteralType = String + + case charCode(String) + case command + case shift + case option + case control + + static let backspace = KeyEquivalentElement.charCode("\u{8}") + static let tab = KeyEquivalentElement.charCode("\t") + static let left = KeyEquivalentElement.charCode("\u{2190}") + static let right = KeyEquivalentElement.charCode("\u{2192}") + + init(stringLiteral value: String) { + self = .charCode(value) + } +} + +extension [KeyEquivalentElement]: ExpressibleByStringLiteral, ExpressibleByUnicodeScalarLiteral, ExpressibleByExtendedGraphemeClusterLiteral { + public typealias StringLiteralType = String + + public init(stringLiteral value: String) { + self = [.charCode(value)] + } + + var charCode: String { + for item in self { + if case .charCode(let value) = item { + return value + } + } + return "" + } + + var modifierMask: NSEvent.ModifierFlags { + var result: NSEvent.ModifierFlags = [] + for item in self { + switch item { + case .charCode: continue + case .command: + result.insert(.command) + case .shift: + result.insert(.shift) + case .option: + result.insert(.option) + case .control: + result.insert(.control) + } + } + return result + } + } diff --git a/DuckDuckGo/Common/Extensions/URLExtension.swift b/DuckDuckGo/Common/Extensions/URLExtension.swift index da4a435127..0b3193e87e 100644 --- a/DuckDuckGo/Common/Extensions/URLExtension.swift +++ b/DuckDuckGo/Common/Extensions/URLExtension.swift @@ -136,18 +136,6 @@ extension URL { return NavigationalScheme.validSchemes.contains(scheme) } - // MARK: Pixel - - static let pixelBase = ProcessInfo.processInfo.environment["PIXEL_BASE_URL", default: "https://improving.duckduckgo.com"] - - static func pixelUrl(forPixelNamed pixelName: String) -> URL { - let urlString = "\(Self.pixelBase)/t/\(pixelName)" - let url = URL(string: urlString)! - // url = url.addParameter(name: \"atb\", value: statisticsStore.atbWithVariant ?? \"\")") - // https://app.asana.com/0/1177771139624306/1199951074455863/f - return url - } - // MARK: ATB static var devMode: String { diff --git a/DuckDuckGo/Common/FileSystem/EncryptionKeys/EncryptionKeyStore.swift b/DuckDuckGo/Common/FileSystem/EncryptionKeys/EncryptionKeyStore.swift index d59dd38ae8..f2812f1f64 100644 --- a/DuckDuckGo/Common/FileSystem/EncryptionKeys/EncryptionKeyStore.swift +++ b/DuckDuckGo/Common/FileSystem/EncryptionKeys/EncryptionKeyStore.swift @@ -18,8 +18,9 @@ import Foundation import CryptoKit +import PixelKit -enum EncryptionKeyStoreError: Error, ErrorWithParameters { +enum EncryptionKeyStoreError: Error, ErrorWithPixelParameters { case storageFailed(OSStatus) case readFailed(OSStatus) case deletionFailed(OSStatus) @@ -29,15 +30,15 @@ enum EncryptionKeyStoreError: Error, ErrorWithParameters { var errorParameters: [String: String] { switch self { case .storageFailed(let status): - return [Pixel.Parameters.keychainErrorCode: "\(status)"] + return [PixelKit.Parameters.keychainErrorCode: "\(status)"] case .readFailed(let status): - return [Pixel.Parameters.keychainErrorCode: "\(status)"] + return [PixelKit.Parameters.keychainErrorCode: "\(status)"] case .deletionFailed(let status): - return [Pixel.Parameters.keychainErrorCode: "\(status)"] + return [PixelKit.Parameters.keychainErrorCode: "\(status)"] case .cannotTransformDataToString(let status): - return [Pixel.Parameters.keychainErrorCode: "\(status)"] + return [PixelKit.Parameters.keychainErrorCode: "\(status)"] case .cannotTransfrotmStringToBase64Data(let status): - return [Pixel.Parameters.keychainErrorCode: "\(status)"] + return [PixelKit.Parameters.keychainErrorCode: "\(status)"] } } } diff --git a/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift b/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift index 9886421058..517541bcd5 100644 --- a/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift +++ b/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift @@ -52,6 +52,7 @@ extension UserText { } } + static let networkProtectionUnknownActivationError = NSLocalizedString("network.protection.system.extension.unknown.activation.error", value: "There as an unexpected error. Please try again.", comment: "Message shown to users when they try to enable NetP and there is an unexpected activation error.") static let networkProtectionPleaseReboot = NSLocalizedString("network.protection.system.extension.please.reboot", value: "Please reboot to activate Network Protection", comment: "Message shown to users when they try to enable NetP and they need to reboot the computer to complete the installation") } @@ -152,3 +153,52 @@ extension UserText { static let networkProtectionTermsOfServiceSection8List = NSLocalizedString("network-protection.terms-of-service.section.7.list", value: "You may be asked during the beta period to provide feedback about your experience. Doing so is optional and your feedback may be used to improve the service.\n\nIf you have enabled notifications for the DuckDuckGo app, we may use notifications to ask about your experience. You can disable notifications if you do not want to receive them.", comment: "Terms of Service list for Network Protection") } + +#if DBP +// MARK: - Data Broker Protection Waitlist +extension UserText { + static let dataBrokerProtectionPrivacyPolicyTitle = NSLocalizedString("data-broker-protection.privacy-policy.title", value: "Privacy Policy", comment: "Privacy Policy title for Personal Information Removal") + + static let dataBrokerProtectionWaitlistNotificationTitle = NSLocalizedString("data-broker-protection.waitlist.notification.title", value: "Personal Information Removal beta is ready!", comment: "Title for Personal Information Removal waitlist notification") + static let dataBrokerProtectionWaitlistNotificationText = NSLocalizedString("data-broker-protection.waitlist.notification.text", value: "Open your invite", comment: "Title for Personal Information Removal waitlist notification") + + static let dataBrokerProtectionWaitlistJoinTitle = NSLocalizedString("data-broker-protection.waitlist.join.title", value: "Personal Information Removal Beta", comment: "Title for Personal Information Removal join waitlist screen") + static let dataBrokerProtectionWaitlistJoinSubtitle1 = NSLocalizedString("data-broker-protection.waitlist.join.subtitle.1", value: "Automatically scan and remove your data from 75+ sites that sell personal information with DuckDuckGo’s Personal Information Removal.", comment: "First subtitle for Personal Information Removal join waitlist screen") + static let dataBrokerProtectionWaitlistJoinSubtitle2 = NSLocalizedString("data-broker-protection.waitlist.join.subtitle.2", value: "Join the waitlist, and we’ll notify you when it’s your turn.", comment: "Second subtitle for Personal Information Removal join waitlist screen") + + static let dataBrokerProtectionWaitlistJoinedTitle = NSLocalizedString("data-broker-protection.waitlist.joined.title", value: "You’re on the list!", comment: "Title for Personal Information Removal joined waitlist screen") + static let dataBrokerProtectionWaitlistJoinedWithNotificationsSubtitle1 = NSLocalizedString("data-broker-protection.waitlist.joined.with-notifications.subtitle.1", value: "New invites are sent every few days, on a first come, first served basis.", comment: "Subtitle 1 for Personal Information Removal joined waitlist screen when notifications are enabled") + static let dataBrokerProtectionWaitlistJoinedWithNotificationsSubtitle2 = NSLocalizedString("data-broker-protection.waitlist.joined.with-notifications.subtitle.2", value: "We’ll notify you when your invite is ready.", comment: "Subtitle 2 for Personal Information Removal joined waitlist screen when notifications are enabled") + static let dataBrokerProtectionWaitlistEnableNotifications = NSLocalizedString("data-broker-protection.waitlist.enable-notifications", value: "Want to get a notification when your Personal Information Removal invite is ready?", comment: "Enable notifications prompt for Personal Information Removal joined waitlist screen") + + static let dataBrokerProtectionWaitlistInvitedTitle = NSLocalizedString("data-broker-protection.waitlist.invited.title", value: "You’re invited to try\nPersonal Information Removal beta!", comment: "Title for Personal Information Removal invited screen") + static let dataBrokerProtectionWaitlistInvitedSubtitle = NSLocalizedString("data-broker-protection.waitlist.invited.subtitle", value: "Automatically find and remove your personal information – such as your name and address – from 75+ sites that store and sell it, reducing the risk of identity theft and spam.", comment: "Subtitle for Personal Information Removal invited screen") + + static let dataBrokerProtectionWaitlistInvitedSection1Title = NSLocalizedString("data-broker-protection.waitlist.invited.section-1.title", value: "Continuous Scan and Removal", comment: "Title for section 1 of the Personal Information Removal invited screen") + static let dataBrokerProtectionWaitlistInvitedSection1Subtitle = NSLocalizedString("data-broker-protection.waitlist.invited.section-1.subtitle", value: "Automatically scans for your info, requests its removal, and re-scans regularly to ensure it doesn’t reappear.", comment: "Subtitle for section 1 of the Personal Information Removal invited screen") + + static let dataBrokerProtectionWaitlistInvitedSection2Title = NSLocalizedString("data-broker-protection.waitlist.invited.section-2.title", value: "Private by Design", comment: "Title for section 2 of the Personal Information Removal invited screen") + static let dataBrokerProtectionWaitlistInvitedSection2Subtitle = NSLocalizedString("data-broker-protection.waitlist.invited.section-2.subtitle", value: "The removal process is initiated on your device, and the info you provide during setup is stored on your device only.", comment: "Subtitle for section 2 of the Personal Information Removal invited screen") + + static let dataBrokerProtectionWaitlistInvitedSection3Title = NSLocalizedString("data-broker-protection.waitlist.invited.section-3.title", value: "Real-Time Progress Updates", comment: "Title for section 3 of the Personal Information Removal invited screen") + static let dataBrokerProtectionWaitlistInvitedSection3Subtitle = NSLocalizedString("data-broker-protection.waitlist.invited.section-3.subtitle", value: "See what information has been removed, and monitor progress of ongoing removals from your dashboard.", comment: "Subtitle for section 3 of the Personal Information Removal invited screen") + + static let dataBrokerProtectionWaitlistEnableTitle = NSLocalizedString("data-broker-protection.waitlist.enable.title", value: "Let’s get started", comment: "Title for Personal Information Removal enable screen") + static let dataBrokerProtectionWaitlistEnableSubtitle = NSLocalizedString("data-broker-protection.waitlist.enable.subtitle", value: "We’ll need your name, address and the year you were born in order to find your personal information on data broker sites\n\nThis info is stored securely on your device, and is never sent to DuckDuckGo.", comment: "Subtitle for Personal Information Removal enable screen") + + static let dataBrokerProtectionWaitlistAvailabilityDisclaimer = NSLocalizedString("data-broker-protection.waitlist.availability-disclaimer", value: "Personal Information Removal is free to use during the beta.", comment: "Availability disclaimer for Personal Information Removal join waitlist screen") + + static let dataBrokerProtectionWaitlistButtonClose = NSLocalizedString("data-broker-protection.waitlist.button.close", value: "Close", comment: "Close button for Personal Information Removal join waitlist screen") + static let dataBrokerProtectionWaitlistButtonDone = NSLocalizedString("data-broker-protection.waitlist.button.done", value: "Done", comment: "Close button for Personal Information Removal joined waitlist screen") + static let dataBrokerProtectionWaitlistButtonDismiss = NSLocalizedString("data-broker-protection.waitlist.button.dismiss", value: "Dismiss", comment: "Dismiss button for Personal Information Removal join waitlist screen") + static let dataBrokerProtectionWaitlistButtonCancel = NSLocalizedString("data-broker-protection.waitlist.button.cancel", value: "Cancel", comment: "Cancel button for Personal Information Removal join waitlist screen") + static let dataBrokerProtectionWaitlistButtonNoThanks = NSLocalizedString("data-broker-protection.waitlist.button.no-thanks", value: "No Thanks", comment: "No Thanks button for Personal Information Removal joined waitlist screen") + static let dataBrokerProtectionWaitlistButtonGetStarted = NSLocalizedString("data-broker-protection.waitlist.button.get-started", value: "Get Started", comment: "Get Started button for Personal Information Removal joined waitlist screen") + static let dataBrokerProtectionWaitlistButtonGotIt = NSLocalizedString("data-broker-protection.waitlist.button.got-it", value: "Get started", comment: "Get started button for Personal Information Removal joined waitlist screen") + + static let dataBrokerProtectionWaitlistButtonEnableNotifications = NSLocalizedString("data-broker-protection.waitlist.button.enable-notifications", value: "Enable Notifications", comment: "Enable Notifications button for Personal Information Removal joined waitlist screen") + static let dataBrokerProtectionWaitlistButtonJoinWaitlist = NSLocalizedString("data-broker-protection.waitlist.button.join-waitlist", value: "Join the Waitlist", comment: "Join Waitlist button for Personal Information Removal join waitlist screen") + static let dataBrokerProtectionWaitlistButtonAgreeAndContinue = NSLocalizedString("data-broker-protection.waitlist.button.agree-and-continue", value: "Agree and Continue", comment: "Agree and Continue button for Personal Information Removal join waitlist screen") +} + +#endif diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index ba06cb120f..9a97da292b 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -23,6 +23,7 @@ struct UserText { static let duckDuckGo = NSLocalizedString("about.app_name", value: "DuckDuckGo", comment: "Application name to be displayed in the About dialog") static let duckDuckGoForMacAppStore = NSLocalizedString("about.app_name_app_store", value: "DuckDuckGo for Mac App Store", comment: "Application name to be displayed in the About dialog in App Store app") + // MARK: - Dialogs static let ok = NSLocalizedString("ok", value: "OK", comment: "OK button") static let cancel = NSLocalizedString("cancel", value: "Cancel", comment: "Cancel button") static let notNow = NSLocalizedString("notnow", value: "Not Now", comment: "Not Now button") @@ -49,6 +50,118 @@ struct UserText { return String(format: localized, value) } + // MARK: - Main Menu -> DuckDuckGo + static let mainMenuAppPreferences = NSLocalizedString("Preferences…", comment: "Main Menu DuckDuckGo item") + static let mainMenuAppServices = NSLocalizedString("Services", comment: "Main Menu DuckDuckGo item") + static let mainMenuAppCheckforUpdates = NSLocalizedString("Check for Updates…", comment: "Main Menu DuckDuckGo item") + static let mainMenuAppHideDuckDuckGo = NSLocalizedString("Hide DuckDuckGo", comment: "Main Menu DuckDuckGo item") + static let mainMenuAppHideOthers = NSLocalizedString("Hide Others", comment: "Main Menu DuckDuckGo item") + static let mainMenuAppShowAll = NSLocalizedString("Show All", comment: "Main Menu DuckDuckGo item") + static let mainMenuAppQuitDuckDuckGo = NSLocalizedString("Quit DuckDuckGo", comment: "Main Menu DuckDuckGo item") + + // MARK: - Main Menu -> -File + static let mainMenuFile = NSLocalizedString("File", comment: "Main Menu File") + static let mainMenuFileNewTab = NSLocalizedString("New Tab", comment: "Main Menu File item") + static let mainMenuFileOpenLocation = NSLocalizedString("Open Location…", comment: "Main Menu File item") + static let mainMenuFileCloseWindow = NSLocalizedString("Close Window", comment: "Main Menu File item") + static let mainMenuFileCloseAllWindows = NSLocalizedString("Close All Windows", comment: "Main Menu File item") + static let mainMenuFileSaveAs = NSLocalizedString("Save As…", comment: "Main Menu File item") + static let mainMenuFileImportBookmarksandPasswords = NSLocalizedString("Import Bookmarks and Passwords…", comment: "Main Menu File item") + static let mainMenuFileExport = NSLocalizedString("Export", comment: "Main Menu File item") + static let mainMenuFileExportPasswords = NSLocalizedString("Passwords…", comment: "Main Menu File-Export item") + static let mainMenuFileExportBookmarks = NSLocalizedString("Bookmarks…", comment: "Main Menu File-Export item") + + // MARK: - Main Menu -> Edit + static let mainMenuEdit = NSLocalizedString("Edit", comment: "Main Menu Edit") + static let mainMenuEditUndo = NSLocalizedString("Undo", comment: "Main Menu Edit item") + static let mainMenuEditRedo = NSLocalizedString("Redo", comment: "Main Menu Edit item") + static let mainMenuEditCut = NSLocalizedString("Cut", comment: "Main Menu Edit item") + static let mainMenuEditCopy = NSLocalizedString("Copy", comment: "Main Menu Edit item") + static let mainMenuEditPaste = NSLocalizedString("Paste", comment: "Main Menu Edit item") + static let mainMenuEditPasteAndMatchStyle = NSLocalizedString("Paste and Match Style", comment: "Main Menu Edit item") + static let mainMenuEditDelete = NSLocalizedString("Delete", comment: "Main Menu Edit item") + static let mainMenuEditSelectAll = NSLocalizedString("Select All", comment: "Main Menu Edit item") + + static let mainMenuEditFind = NSLocalizedString("Find", comment: "Main Menu Edit item") + + // MARK: Main Menu -> Edit -> Find + static let mainMenuEditFindFindNext = NSLocalizedString("Find Next", comment: "Main Menu Edit-Find item") + static let mainMenuEditFindFindPrevious = NSLocalizedString("Find Previous", comment: "Main Menu Edit-Find item") + static let mainMenuEditFindHideFind = NSLocalizedString("Hide Find", comment: "Main Menu Edit-Find item") + + static let mainMenuEditSpellingandGrammar = NSLocalizedString("Spelling and Grammar", comment: "Main Menu Edit item") + + // MARK: Main Menu -> Edit -> Spellingand + static let mainMenuEditSpellingandShowSpellingandGrammar = NSLocalizedString("Show Spelling and Grammar", comment: "Main Menu Edit-Spellingand item") + static let mainMenuEditSpellingandCheckDocumentNow = NSLocalizedString("Check Document Now", comment: "Main Menu Edit-Spellingand item") + static let mainMenuEditSpellingandCheckSpellingWhileTyping = NSLocalizedString("Check Spelling While Typing", comment: "Main Menu Edit-Spellingand item") + static let mainMenuEditSpellingandCheckGrammarWithSpelling = NSLocalizedString("Check Grammar With Spelling", comment: "Main Menu Edit-Spellingand item") + static let mainMenuEditSpellingandCorrectSpellingAutomatically = NSLocalizedString("Correct Spelling Automatically", comment: "Main Menu Edit-Spellingand item") + + static let mainMenuEditSubstitutions = NSLocalizedString("Substitutions", comment: "Main Menu Edit item") + + // MARK: Main Menu -> Edit -> Substitutions + static let mainMenuEditSubstitutionsShowSubstitutions = NSLocalizedString("Show Substitutions", comment: "Main Menu Edit-Substitutions item") + static let mainMenuEditSubstitutionsSmartCopyPaste = NSLocalizedString("Smart Copy/Paste", comment: "Main Menu Edit-Substitutions item") + static let mainMenuEditSubstitutionsSmartQuotes = NSLocalizedString("Smart Quotes", comment: "Main Menu Edit-Substitutions item") + static let mainMenuEditSubstitutionsSmartDashes = NSLocalizedString("Smart Dashes", comment: "Main Menu Edit-Substitutions item") + static let mainMenuEditSubstitutionsSmartLinks = NSLocalizedString("Smart Links", comment: "Main Menu Edit-Substitutions item") + static let mainMenuEditSubstitutionsDataDetectors = NSLocalizedString("Data Detectors", comment: "Main Menu Edit-Substitutions item") + static let mainMenuEditSubstitutionsTextReplacement = NSLocalizedString("Text Replacement", comment: "Main Menu Edit-Substitutions item") + + static let mainMenuEditTransformations = NSLocalizedString("Transformations", comment: "Main Menu Edit item") + + // MARK: Main Menu -> Edit -> Transformations + static let mainMenuEditTransformationsMakeUpperCase = NSLocalizedString("Make Upper Case", comment: "Main Menu Edit-Transformations item") + static let mainMenuEditTransformationsMakeLowerCase = NSLocalizedString("Make Lower Case", comment: "Main Menu Edit-Transformations item") + static let mainMenuEditTransformationsCapitalize = NSLocalizedString("Capitalize", comment: "Main Menu Edit-Transformations item") + + static let mainMenuEditSpeech = NSLocalizedString("Speech", comment: "Main Menu Edit item") + + // MARK: Main Menu -> Edit -> Speech + static let mainMenuEditSpeechStartSpeaking = NSLocalizedString("Start Speaking", comment: "Main Menu Edit-Speech item") + static let mainMenuEditSpeechStopSpeaking = NSLocalizedString("Stop Speaking", comment: "Main Menu Edit-Speech item") + + // MARK: - Main Menu -> View + static let mainMenuView = NSLocalizedString("View", comment: "Main Menu View") + static let mainMenuViewStop = NSLocalizedString("Stop", comment: "Main Menu View item") + static let mainMenuViewReloadPage = NSLocalizedString("Reload Page", comment: "Main Menu View item") + static let mainMenuViewHome = NSLocalizedString("Home", comment: "Main Menu View item") + static let mainMenuViewShowHomeShortcut = NSLocalizedString("Show Home Shortcut", comment: "Main Menu View item") + static let mainMenuViewShowAutofillShortcut = NSLocalizedString("Show Autofill Shortcut", comment: "Main Menu View item") + static let mainMenuViewShowBookmarksShortcut = NSLocalizedString("Show Bookmarks Shortcut", comment: "Main Menu View item") + static let mainMenuViewShowDownloadsShortcut = NSLocalizedString("Show Downloads Shortcut", comment: "Main Menu View item") + static let mainMenuViewEnterFullScreen = NSLocalizedString("Enter Full Screen", comment: "Main Menu View item") + static let mainMenuViewActualSize = NSLocalizedString("Actual Size", comment: "Main Menu View item") + static let mainMenuViewZoomIn = NSLocalizedString("Zoom In", comment: "Main Menu View item") + static let mainMenuViewZoomOut = NSLocalizedString("Zoom Out", comment: "Main Menu View item") + + static let mainMenuDeveloper = NSLocalizedString("Developer", comment: "Main Menu ") + + // MARK: Main Menu -> View -> Developer + static let mainMenuViewDeveloperJavaScriptConsole = NSLocalizedString("JavaScript Console", comment: "Main Menu View-Developer item") + static let mainMenuViewDeveloperShowPageSource = NSLocalizedString("Show Page Source", comment: "Main Menu View-Developer item") + static let mainMenuViewDeveloperShowResources = NSLocalizedString("Show Resources", comment: "Main Menu View-Developer item") + + // MARK: - Main Menu -> History + static let mainMenuHistory = NSLocalizedString("History", comment: "Main Menu ") + static let mainMenuHistoryRecentlyClosed = NSLocalizedString("Recently Closed", comment: "Main Menu History item") + static let mainMenuHistoryClearAllHistory = NSLocalizedString("Clear All History…", comment: "Main Menu History item") + static let mainMenuHistoryManageBookmarks = NSLocalizedString("Manage Bookmarks", comment: "Main Menu History item") + static let mainMenuHistoryFavoriteThisPage = NSLocalizedString("Favorite This Page…", comment: "Main Menu History item") + + // MARK: - Main Menu -> Window + static let mainMenuWindow = NSLocalizedString("Window", comment: "Main Menu ") + static let mainMenuWindowMinimize = NSLocalizedString("Minimize", comment: "Main Menu Window item") + static let mainMenuWindowMergeAllWindows = NSLocalizedString("Merge All Windows", comment: "Main Menu Window item") + static let mainMenuWindowShowPreviousTab = NSLocalizedString("Show Previous Tab", comment: "Main Menu Window item") + static let mainMenuWindowShowNextTab = NSLocalizedString("Show Next Tab", comment: "Main Menu Window item") + static let mainMenuWindowBringAllToFront = NSLocalizedString("Bring All to Front", comment: "Main Menu Window item") + + // MARK: - Main Menu -> Help + static let mainMenuHelp = NSLocalizedString("Help", comment: "Main Menu Help") + static let mainMenuHelpDuckDuckGoHelp = NSLocalizedString("DuckDuckGo Help", comment: "Main Menu Help item") + static let duplicateTab = NSLocalizedString("duplicate.tab", value: "Duplicate Tab", comment: "Menu item. Duplicate as a verb") static let pinTab = NSLocalizedString("pin.tab", value: "Pin Tab", comment: "Menu item. Pin as a verb") static let unpinTab = NSLocalizedString("unpin.tab", value: "Unpin Tab", comment: "Menu item. Unpin as a verb") @@ -118,7 +231,7 @@ struct UserText { static let fireproofSites = NSLocalizedString("fireproof.sites", value: "Fireproof Sites", comment: "Fireproof sites list title") static let fireproofCheckboxTitle = NSLocalizedString("fireproof.checkbox.title", value: "Ask to Fireproof websites when signing in", comment: "Fireproof settings checkbox title") static let fireproofExplanation = NSLocalizedString("fireproof.explanation", value: "When you Fireproof a site, cookies won't be erased and you'll stay signed in, even after using the Fire Button.", comment: "Fireproofing mechanism explanation") - static let manageFireproofSites = NSLocalizedString("fireproof.manage-sites", value: "Manage Fireproof Sites...", comment: "Fireproof settings button caption") + static let manageFireproofSites = NSLocalizedString("fireproof.manage-sites", value: "Manage Fireproof Sites…", comment: "Fireproof settings button caption") static let fireDialogFireproofSites = NSLocalizedString("fire.dialog.fireproof.sites", value: "Fireproof sites won't be cleared", comment: "Category of domains in fire button dialog") static let fireDialogClearSites = NSLocalizedString("fire.dialog.clear.sites", value: "Selected sites will be cleared", comment: "Category of domains in fire button dialog") @@ -207,7 +320,7 @@ struct UserText { static let downloadsLocation = NSLocalizedString("downloads.location", value: "Location", comment: "Downloads directory location") static let downloadsAlwaysAsk = NSLocalizedString("downloads.always-ask", value: "Always ask where to save files", comment: "Downloads preferences checkbox") - static let downloadsChangeDirectory = NSLocalizedString("downloads.change", value: "Change...", comment: "Change downloads directory button") + static let downloadsChangeDirectory = NSLocalizedString("downloads.change", value: "Change…", comment: "Change downloads directory button") static let passwordManagement = NSLocalizedString("passsword.management", value: "Autofill", comment: "Used as title for password management user interface") static let passwordManagementAllItems = NSLocalizedString("passsword.management.all-items", value: "All Items", comment: "Used as title for the Autofill All Items option") @@ -218,7 +331,6 @@ struct UserText { static let passwordManagementLock = NSLocalizedString("passsword.management.lock", value: "Lock", comment: "Lock Logins Vault menu") static let passwordManagementUnlock = NSLocalizedString("passsword.management.unlock", value: "Unlock", comment: "Unlock Logins Vault menu") -// static let importBrowserData = NSLocalizedString("import.browser.data", value: "Import Bookmarks and Passwords…", comment: "Opens Import Browser Data dialog") static let importBookmarks = NSLocalizedString("import.browser.data", value: "Import Bookmarks…", comment: "Opens Import Browser Data dialog") static let importPasswords = NSLocalizedString("import.browser.data", value: "Import Passwords…", comment: "Opens Import Browser Data dialog") static let exportLogins = NSLocalizedString("export.logins.data", value: "Export Passwords…", comment: "Opens Export Logins Data dialog") @@ -312,17 +424,17 @@ struct UserText { static let downloads = NSLocalizedString("preferences.downloads", value: "Downloads", comment: "Show downloads browser preferences") static let isDefaultBrowser = NSLocalizedString("preferences.default-browser.active", value: "DuckDuckGo is your default browser", comment: "Indicate that the browser is the default") static let isNotDefaultBrowser = NSLocalizedString("preferences.default-browser.inactive", value: "DuckDuckGo is not your default browser.", comment: "Indicate that the browser is not the default") - static let makeDefaultBrowser = NSLocalizedString("preferences.default-browser.button.make-default", value: "Make DuckDuckGo Default...", comment: "") + static let makeDefaultBrowser = NSLocalizedString("preferences.default-browser.button.make-default", value: "Make DuckDuckGo Default…", comment: "") static let onStartup = NSLocalizedString("preferences.on-startup", value: "On Startup", comment: "Name of the preferences section related to app startup") - static let reopenAllWindowsFromLastSession = NSLocalizedString("preferences.reopen-windows", value: "Reopen all windows from last session", comment: "Option to control session restoration") + static let reopenAllWindowsFromLastSession = NSLocalizedString("preferences.reopen-windows", value: "Reopen All Windows from Last Session", comment: "Option to control session restoration") static let showHomePage = NSLocalizedString("preferences.show-home", value: "Open a new window", comment: "Option to control session startup") static let homePage = NSLocalizedString("preferences-homepage", value: "Homepage", comment: "Title for Homepage section in settings") static let homePageDescription = NSLocalizedString("preferences-homepage", value: "When navigating home or opening new windows.", comment: "Homepage behavior description") static let newTab = NSLocalizedString("preferences-homepage-newTab", value: "New Tab page", comment: "Option to open a new tab") static let specificPage = NSLocalizedString("preferences-homepage-customPage", value: "Specific page", comment: "Option to control Specific Home Page") - static let setPage = NSLocalizedString("preferences-homepage-set-page", value: "Set Page...", comment: "Option to control the Specific Page") - + static let setPage = NSLocalizedString("preferences-homepage-set-page", value: "Set Page…", comment: "Option to control the Specific Page") + static let setHomePage = NSLocalizedString("preferences-homepage-set-homePage", value: "Set Homepage", comment: "Set Homepage dialog title") static let addressLabel = NSLocalizedString("preferences-homepage-address", value: "Address:", comment: "Homepage address field label") @@ -562,7 +674,7 @@ struct UserText { static let moreOrLessExpand = NSLocalizedString("more.or.less.expand", value: "Show More", comment: "For expanding views to show more.") static let defaultBrowserPromptMessage = NSLocalizedString("default.browser.prompt.message", value: "Make DuckDuckGo your default browser", comment: "") - static let defaultBrowserPromptButton = NSLocalizedString("default.browser.prompt.button", value: "Set Default...", comment: "") + static let defaultBrowserPromptButton = NSLocalizedString("default.browser.prompt.button", value: "Set Default…", comment: "") static let homePageProtectionSummaryInfo = NSLocalizedString("home.page.protection.summary.info", value: "No recent activity", comment: "") static func homePageProtectionSummaryMessage(numberOfTrackersBlocked: Int) -> String { @@ -666,7 +778,7 @@ struct UserText { static let installBitwardenInfo = NSLocalizedString("bitwarden.install.info", value: "To begin setup, first install Bitwarden from the App Store.", comment: "Setup of the integration with Bitwarden app") static let afterBitwardenInstallationInfo = NSLocalizedString("after.bitwarden.installation.info", value: "After installing, return to DuckDuckGo to complete the setup.", comment: "Setup of the integration with Bitwarden app") static let bitwardenAppFound = NSLocalizedString("bitwarden.app.found", value: "Bitwarden app found!", comment: "Setup of the integration with Bitwarden app") - static let lookingForBitwarden = NSLocalizedString("looking.for.bitwarden", value: "Bitwarden not installed...", comment: "Setup of the integration with Bitwarden app") + static let lookingForBitwarden = NSLocalizedString("looking.for.bitwarden", value: "Bitwarden not installed…", comment: "Setup of the integration with Bitwarden app") static let allowIntegration = NSLocalizedString("allow.integration", value: "Allow Integration with DuckDuckGo", comment: "Setup of the integration with Bitwarden app") static let openBitwardenAndLogInOrUnlock = NSLocalizedString("open.bitwarden.and.log.in.or.unlock", value: "Open Bitwarden and Log in or Unlock your vault.", comment: "Setup of the integration with Bitwarden app") static let selectBitwardenPreferences = NSLocalizedString("select.bitwarden.preferences", value: "Select Bitwarden → Preferences from the Mac menu bar.", comment: "Setup of the integration with Bitwarden app (up to and including macOS 12)") diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index a0c27fae68..e80751be0e 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -136,18 +136,7 @@ public struct UserDefaultsWrapper { // Network Protection - case networkProtectionShouldEnforceRoutes = "netp.enforce-routes" - case networkProtectionShouldIncludeAllNetworks = "netp.include-all-networks" - case networkProtectionExcludedRoutes = "netp.excluded-routes" - case networkProtectionShouldExcludeLocalRoutes = "netp.exclude-local-routes" - case networkProtectionConnectionTesterEnabled = "netp.connection-tester-enabled" - - case networkProtectionConnectOnLogIn = "netp.connect-on-login" - - case networkProtectionRegistrationKeyValidity = "com.duckduckgo.network-protection.NetworkProtectionTunnelController.registrationKeyValidityKey" - - case netpMenuAgentLaunchTime = "netp.agent.launch-time" case networkProtectionTermsAndConditionsAccepted = "network-protection.waitlist-terms-and-conditions.accepted" @@ -171,12 +160,21 @@ public struct UserDefaultsWrapper { case syncEnvironment = "sync.environment" case favoritesDisplayMode = "sync.favorites-display-mode" + case syncBookmarksPaused = "sync.bookmarks-paused" + case syncCredentialsPaused = "sync.credentials-paused" } enum RemovedKeys: String, CaseIterable { case passwordManagerDoNotPromptDomains = "com.duckduckgo.passwordmanager.do-not-prompt-domains" case incrementalFeatureFlagTestHasSentPixel = "network-protection.incremental-feature-flag-test.has-sent-pixel" case homePageShowNetworkProtectionBetaEndedNotice = "home.page.network-protection.show-beta-ended-notice" + + // NetP removed keys + case networkProtectionShouldEnforceRoutes = "netp.enforce-routes" + case networkProtectionShouldIncludeAllNetworks = "netp.include-all-networks" + case networkProtectionConnectionTesterEnabled = "netp.connection-tester-enabled" + case networkProtectionShouldExcludeLocalNetworks = "netp.exclude-local-routes" + case networkProtectionRegistrationKeyValidity = "com.duckduckgo.network-protection.NetworkProtectionTunnelController.registrationKeyValidityKey" } private let key: Key @@ -194,7 +192,7 @@ public struct UserDefaultsWrapper { if case .normal = NSApplication.runType { return .standard } else { - return UserDefaults(suiteName: Bundle.main.bundleIdentifier! + "." + NSApplication.runType.description)! + return UserDefaults(suiteName: "\(Bundle.main.bundleIdentifier!).\(NSApplication.runType)")! } #else return .standard diff --git a/DuckDuckGo/Common/View/SwiftUI/MenuPreview.swift b/DuckDuckGo/Common/View/SwiftUI/MenuPreview.swift new file mode 100644 index 0000000000..a662b7b362 --- /dev/null +++ b/DuckDuckGo/Common/View/SwiftUI/MenuPreview.swift @@ -0,0 +1,90 @@ +// +// MenuPreview.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import SwiftUI + +extension CoordinateSpace { + static let windowCoordinateSpaceName = "windowCoordinateSpace" + static let window = CoordinateSpace.named(windowCoordinateSpaceName) +} + +#if DEBUG + +struct MenuPreview: View { + let menu: NSMenu + + var body: some View { + guard #available(macOS 13.0, *) else { fatalError() } + + return HStack(spacing: 0) { + ForEach(menu.items.indices, id: \.self) { idx in + MenuPreviewButton(menuItem: menu.items[idx], bold: idx == 0) + } + Spacer() + } + .padding(.horizontal, 10) + .coordinateSpace(name: CoordinateSpace.windowCoordinateSpaceName) + } +} + +@available(macOS 13.0, *) +struct MenuPreviewButton: View { + + let menuItem: NSMenuItem + let bold: Bool + + @State var frame: CGRect = .zero + @State var isMenuShown = false + + var body: some View { + Button(menuItem.title) { + isMenuShown = true + menuItem.submenu?.popUp(positioning: nil, + at: NSPoint(x: frame.minX, y: frame.maxY + 4), + in: NSApp.keyWindow!.contentView) + isMenuShown = false + } + .buttonStyle(MainMenuItemButtonStyle(isBold: bold, isMenuShown: isMenuShown)) + .background( + GeometryReader { proxy in + Color.clear.onChange(of: proxy.size) { _ in + frame = proxy.frame(in: .window) + } + } + ) + } + +} + +@available(macOS 13.0, *) +struct MainMenuItemButtonStyle: ButtonStyle { + + let isBold: Bool + let isMenuShown: Bool + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .fontWeight(isBold ? .bold : .regular) + .padding(.vertical, 6) + .padding(.horizontal, 6) + .background(configuration.isPressed || isMenuShown ? Color.gray : Color.clear) + } +} + +#endif diff --git a/DuckDuckGo/Configuration/ConfigurationStore.swift b/DuckDuckGo/Configuration/ConfigurationStore.swift index cb1890d020..15891111a3 100644 --- a/DuckDuckGo/Configuration/ConfigurationStore.swift +++ b/DuckDuckGo/Configuration/ConfigurationStore.swift @@ -94,7 +94,7 @@ final class ConfigurationStore: ConfigurationStoring { do { return try Data(contentsOf: file) } catch { - guard !NSApp.isRunningUnitTests else { return nil } + guard NSApp.runType.requiresEnvironment else { return nil } let nserror = error as NSError diff --git a/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift b/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift index ec2682e330..353a4dd723 100644 --- a/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift +++ b/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift @@ -22,8 +22,8 @@ import BrowserServicesKit final class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"584fd7bc1f376d621c085cc62d7b810f\"" - public static let embeddedDataSHA = "5e702665b8619ce06fc79aeef3571c726204a73f295f020845984ce244a5adba" + public static let embeddedDataETag = "\"e440951fcecc1ba944530887010bb09a\"" + public static let embeddedDataSHA = "a4a9f9511aa6c0b572892cea1872e8a00ce9785e649cd24e938820ee94867468" } var embeddedDataEtag: String { diff --git a/DuckDuckGo/ContentBlocker/AppTrackerDataSetProvider.swift b/DuckDuckGo/ContentBlocker/AppTrackerDataSetProvider.swift index b9d314db05..2c87b5b096 100644 --- a/DuckDuckGo/ContentBlocker/AppTrackerDataSetProvider.swift +++ b/DuckDuckGo/ContentBlocker/AppTrackerDataSetProvider.swift @@ -22,8 +22,8 @@ import BrowserServicesKit final class AppTrackerDataSetProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"a1bb76901a395eb251cd6f30f54037f1\"" - public static let embeddedDataSHA = "53cad742076d1382fba8db0e508307668eb0609738d526cb16cbc623b50410fc" + public static let embeddedDataETag = "\"3b73923ebfd4702c33aea682db0bac11\"" + public static let embeddedDataSHA = "16e1e52530db19badfefc9a07d76620f9c608c12dc144f67020da8a9a4ddb0fa" } var embeddedDataEtag: String { diff --git a/DuckDuckGo/ContentBlocker/ContentBlocking.swift b/DuckDuckGo/ContentBlocker/ContentBlocking.swift index d29026d189..637834c2b3 100644 --- a/DuckDuckGo/ContentBlocker/ContentBlocking.swift +++ b/DuckDuckGo/ContentBlocker/ContentBlocking.swift @@ -97,7 +97,7 @@ final class AppContentBlocking { } private static let debugEvents = EventMapping { event, error, parameters, onComplete in - guard !NSApp.isRunningUnitTests else { return } + guard NSApp.runType.requiresEnvironment else { return } let domainEvent: Pixel.Event.Debug switch event { diff --git a/UnitTests/ContentBlocker/ContentBlockerRulesManagerMock.swift b/DuckDuckGo/ContentBlocker/Mocks/ContentBlockerRulesManagerMock.swift similarity index 94% rename from UnitTests/ContentBlocker/ContentBlockerRulesManagerMock.swift rename to DuckDuckGo/ContentBlocker/Mocks/ContentBlockerRulesManagerMock.swift index 2bc032b231..7df870692b 100644 --- a/UnitTests/ContentBlocker/ContentBlockerRulesManagerMock.swift +++ b/DuckDuckGo/ContentBlocker/Mocks/ContentBlockerRulesManagerMock.swift @@ -16,11 +16,11 @@ // limitations under the License. // -@testable import DuckDuckGo_Privacy_Browser import BrowserServicesKit import Combine -@objc(ContentBlockerRulesManagerMock) +#if DEBUG + final class ContentBlockerRulesManagerMock: NSObject, ContentBlockerRulesManagerProtocol { func scheduleCompilation() -> BrowserServicesKit.ContentBlockerRulesManager.CompletionToken { fatalError() @@ -39,3 +39,5 @@ final class ContentBlockerRulesManagerMock: NSObject, ContentBlockerRulesManager var currentRules: [ContentBlockerRulesManager.Rules] = [] } + +#endif diff --git a/UnitTests/ContentBlocker/ContentBlockingMock.swift b/DuckDuckGo/ContentBlocker/Mocks/ContentBlockingMock.swift similarity index 98% rename from UnitTests/ContentBlocker/ContentBlockingMock.swift rename to DuckDuckGo/ContentBlocker/Mocks/ContentBlockingMock.swift index 02000a3207..524491ceba 100644 --- a/UnitTests/ContentBlocker/ContentBlockingMock.swift +++ b/DuckDuckGo/ContentBlocker/Mocks/ContentBlockingMock.swift @@ -20,9 +20,9 @@ import BrowserServicesKit import Combine import Common import Foundation -@testable import DuckDuckGo_Privacy_Browser -@objc(ContentBlockingMock) +#if DEBUG + final class ContentBlockingMock: NSObject, ContentBlockingProtocol, AdClickAttributionDependencies { struct EDP: EmbeddedDataProvider { @@ -61,7 +61,6 @@ final class ContentBlockingMock: NSObject, ContentBlockingProtocol, AdClickAttri } } -@objc(HTTPSUpgradeStoreMock) final class HTTPSUpgradeStoreMock: NSObject, HTTPSUpgradeStore { var bloomFilter: BloomFilterWrapper? @@ -138,3 +137,5 @@ final class MockAttributionRulesProvider: AdClickAttributionRulesProviding { } } + +#endif diff --git a/UnitTests/Common/MockPrivacyConfiguration.swift b/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift similarity index 93% rename from UnitTests/Common/MockPrivacyConfiguration.swift rename to DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift index e0faba662c..df918a336e 100644 --- a/UnitTests/Common/MockPrivacyConfiguration.swift +++ b/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift @@ -16,12 +16,12 @@ // limitations under the License. // -import XCTest import BrowserServicesKit import Combine -@testable import DuckDuckGo_Privacy_Browser -class MockPrivacyConfiguration: PrivacyConfiguration { +#if DEBUG + +final class MockPrivacyConfiguration: PrivacyConfiguration { var isSubfeatureKeyEnabled: ((any PrivacySubfeature, AppVersionProvider) -> Bool)? func isSubfeatureEnabled(_ subfeature: any PrivacySubfeature, versionProvider: AppVersionProvider, randomizer: (Range) -> Double) -> Bool { @@ -51,8 +51,7 @@ class MockPrivacyConfiguration: PrivacyConfiguration { func userDisabledProtection(forDomain: String) {} } -@objc(MockPrivacyConfigurationManager) -class MockPrivacyConfigurationManager: NSObject, PrivacyConfigurationManaging { +final class MockPrivacyConfigurationManager: NSObject, PrivacyConfigurationManaging { var embeddedConfigData: BrowserServicesKit.PrivacyConfigurationManager.ConfigurationData { fatalError("not implemented") } @@ -72,3 +71,5 @@ class MockPrivacyConfigurationManager: NSObject, PrivacyConfigurationManaging { var updatesPublisher: AnyPublisher = Just(()).eraseToAnyPublisher() var privacyConfig: PrivacyConfiguration = MockPrivacyConfiguration() } + +#endif diff --git a/DuckDuckGo/ContentBlocker/macos-config.json b/DuckDuckGo/ContentBlocker/macos-config.json index c0d430504a..d35ec7621d 100644 --- a/DuckDuckGo/ContentBlocker/macos-config.json +++ b/DuckDuckGo/ContentBlocker/macos-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1697058000288, + "version": 1697802863205, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -100,7 +100,6 @@ ], "keywords": [ "=amp", - "amp=", "&", "amp&", "/amp", @@ -115,7 +114,7 @@ ] }, "state": "enabled", - "hash": "87198c8b0bc8c044f0773599845fa371" + "hash": "cf1e1726876b6f6510eae65e2bc37047" }, "autoconsent": { "exceptions": [ @@ -1204,6 +1203,10 @@ "domain": "optout.networkadvertising.org", "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1377" }, + { + "domain": "ads.google.com", + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1401" + }, { "domain": "earth.google.com", "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1099" @@ -1221,7 +1224,7 @@ "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1085" } ], - "hash": "f43e0ec67d1177139fe5cfc603cbd8a3" + "hash": "f94b331bc97d678adbd90ed74feb40b2" }, "cookie": { "settings": { @@ -1466,7 +1469,7 @@ } ], "settings": { - "useStrictHideStyleTag": false, + "useStrictHideStyleTag": true, "rules": [ { "selector": "[id*='gpt-']", @@ -1853,13 +1856,16 @@ 300, 500, 1000, + 1500, 2000, - 3000 + 3000, + 5000 ], "unhideTimeouts": [ 1250, 2250, - 3000 + 3250, + 5250 ], "mediaAndFormSelectors": "video,canvas,embed,object,audio,map,form,input,textarea,select,option", "adLabelStrings": [ @@ -1918,6 +1924,15 @@ } ] }, + { + "domain": "avito.ru", + "rules": [ + { + "selector": "[class*='advert-mimicry-block']", + "type": "hide" + } + ] + }, { "domain": "bbc.com", "rules": [ @@ -2922,6 +2937,23 @@ } ] }, + { + "domain": "spanishdict.com", + "rules": [ + { + "selector": "[id*='adSide']", + "type": "hide-empty" + }, + { + "selector": "[id*='adTop']", + "type": "hide-empty" + }, + { + "selector": "[id*='adMiddle']", + "type": "hide-empty" + } + ] + }, { "domain": "spankbang.com", "rules": [ @@ -3121,6 +3153,15 @@ } ] }, + { + "domain": "whitakerfuneralhome.com", + "rules": [ + { + "selector": ".top-banner", + "type": "override" + } + ] + }, { "domain": "winnipegfreepress.com", "rules": [ @@ -3264,27 +3305,6 @@ } ] }, - { - "domain": "privacy-test-pages.glitch.me", - "rules": [ - { - "selector": ".hide-test", - "type": "hide" - }, - { - "selector": ".hide-empty-test", - "type": "hide-empty" - }, - { - "selector": ".closest-empty-test", - "type": "closest-empty" - }, - { - "selector": "[class^='ad-container']", - "type": "override" - } - ] - }, { "domain": "privacy-test-pages.site", "rules": [ @@ -3309,7 +3329,7 @@ ] }, "state": "enabled", - "hash": "06c34e66bdfa75be52d4306f6f843d0a" + "hash": "5c92278b92b084940cc99a794908039f" }, "exceptionHandler": { "exceptions": [ @@ -3744,6 +3764,10 @@ "domain": "costco.com", "reason": "https://github.com/duckduckgo/privacy-configuration/issues/644" }, + { + "domain": "crunchyroll.com", + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1422" + }, { "domain": "eventbrite.com", "reason": "https://github.com/duckduckgo/privacy-configuration/issues/700" @@ -3783,11 +3807,10 @@ "globalprivacycontrol.org", "washingtonpost.com", "nytimes.com", - "privacy-test-pages.glitch.me", "privacy-test-pages.site" ] }, - "hash": "9962dbae2a6a668fbdae3b20f02bcbad" + "hash": "5c43f4f38bad7596fa34ac88305f444a" }, "harmfulApis": { "settings": { @@ -3914,11 +3937,15 @@ "exceptions": [ { "domain": "act.alz.org", - "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1158" + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1392" }, { "domain": "amica.com", - "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1336" + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1392" + }, + { + "domain": "jp.square-enix.com", + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1392" }, { "domain": "earth.google.com", @@ -3937,7 +3964,7 @@ "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1085" } ], - "hash": "22b055005a4ea3a5f5d387000c4b838b" + "hash": "2b7a26bbc23127a7ce24cc992066ad14" }, "incontextSignup": { "exceptions": [], @@ -4017,6 +4044,12 @@ }, { "percent": 25 + }, + { + "percent": 50 + }, + { + "percent": 75 } ] } @@ -4024,7 +4057,7 @@ }, "exceptions": [], "minSupportedVersion": "1.57.1", - "hash": "1b2ebf40368d83ebb6ec4f02e6a3e15c" + "hash": "8d9f72bb3b8264979cbef85febb5215e" }, "newTabContinueSetUp": { "exceptions": [], @@ -4060,6 +4093,19 @@ "state": "disabled", "hash": "81f357de4bfc7abeddf1a3a3fb1fc7a9" }, + "privacyDashboard": { + "exceptions": [], + "features": { + "highlightedProtectionsToggle": { + "state": "disabled", + "rollout": { + "steps": [] + } + } + }, + "state": "disabled", + "hash": "dede7e70939822f5ecb9eb5fae577fa3" + }, "referrer": { "exceptions": [ { @@ -5323,7 +5369,7 @@ { "rule": "geoip-js.com/js/apis/geoip2/v2.1/geoip2.js", "domains": [ - "yourrewardcard.com" + "" ], "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1288" } @@ -6194,6 +6240,13 @@ "" ], "reason": "https://github.com/duckduckgo/privacy-configuration/issues/529" + }, + { + "rule": "cmp.osano.com", + "domains": [ + "" + ], + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1398" } ] }, @@ -6667,6 +6720,17 @@ } ] }, + "succeedscene.com": { + "rules": [ + { + "rule": "succeedscene.com", + "domains": [ + "" + ], + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1396" + } + ] + }, "tealiumiq.com": { "rules": [ { @@ -6711,56 +6775,32 @@ } ] }, - "tiqcdn.com": { + "tiktok.com": { "rules": [ { - "rule": "tags.tiqcdn.com/utag/bofa/main/prod/utag.sync.js", - "domains": [ - "bankofamerica.com" - ], - "reason": "https://github.com/duckduckgo/privacy-configuration/issues/798" - }, - { - "rule": "tags.tiqcdn.com/utag/lgi/vm-uk/prod/utag.77.js", - "domains": [ - "virginmedia.com" - ], - "reason": "Chat button appears faded and cannot be interacted with." - }, - { - "rule": "tags.tiqcdn.com/utag/rccl/gdp/prod/utag.js", - "domains": [ - "royalcaribbean.com" - ], - "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1255" - }, - { - "rule": "tags.tiqcdn.com/utag/lbg/", + "rule": "www.tiktok.com/embed", "domains": [ "" ], - "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1334" - }, - { - "rule": "tags.tiqcdn.com/utag/pch/", - "domains": [ - "pch.com" - ], - "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1344" - }, + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1412" + } + ] + }, + "tiqcdn.com": { + "rules": [ { - "rule": "tags.tiqcdn.com/utag/tiqapp/utag.v.js", + "rule": "tags.tiqcdn.com/utag/.*/utag.js", "domains": [ - "oracle.com" + "" ], - "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1353" + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/379" }, { - "rule": "tags.tiqcdn.com/utag/politico/main/prod/utag.js", + "rule": "tags.tiqcdn.com/utag/.*/utag..*.js", "domains": [ - "politico.com" + "" ], - "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1382" + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/379" } ] }, @@ -7019,7 +7059,7 @@ "zopim.com": { "rules": [ { - "rule": "widget-mediator.zopim.com", + "rule": "zopim.com", "domains": [ "" ], @@ -7120,7 +7160,7 @@ "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1085" } ], - "hash": "7ad830c2383f44acd8f10407d570362d" + "hash": "cbcff4a76b22444e895247ccf73a0950" }, "trackingCookies1p": { "settings": { diff --git a/DuckDuckGo/ContentBlocker/trackerData.json b/DuckDuckGo/ContentBlocker/trackerData.json index 3de737a946..18ddb15be4 100644 --- a/DuckDuckGo/ContentBlocker/trackerData.json +++ b/DuckDuckGo/ContentBlocker/trackerData.json @@ -1,6 +1,6 @@ { "_builtWith": { - "tracker-radar": "6eb8582c70d4e62ccb4b23113fc8bfdeb9b945b8ba986a60e71ab693af87e755-4013b4e91930c643394cb31c6c745356f133b04f", + "tracker-radar": "29f80529aa526f0a0f8daadb1c8717d6dccdbb1a45f98477de48e8a8d213d386-4013b4e91930c643394cb31c6c745356f133b04f", "tracker-surrogates": "d61691a2fdf9f4dc062a8d248fd1e78c20b5b892" }, "readme": "https://github.com/duckduckgo/tracker-blocklists", @@ -482,7 +482,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -493,7 +493,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -539,7 +539,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2417,7 +2417,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2428,7 +2428,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2472,7 +2472,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2483,7 +2483,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2506,7 +2506,7 @@ "default": "block", "rules": [ { - "rule": "amazon-adsystem\\.com\\/aax2\\/amzn_ads\\.js", + "rule": "amazon-adsystem\\.com/aax2/amzn_ads\\.js", "surrogate": "amzn_ads.js" }, { @@ -2610,7 +2610,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2749,7 +2749,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2760,7 +2760,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3079,7 +3079,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3206,7 +3206,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3330,7 +3330,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3714,7 +3714,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3725,7 +3725,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3736,7 +3736,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3747,7 +3747,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3813,7 +3813,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3865,7 +3865,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3876,7 +3876,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -4013,7 +4013,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -4364,7 +4364,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -4399,7 +4399,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -4850,7 +4850,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -4861,7 +4861,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5063,7 +5063,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5105,7 +5105,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5128,7 +5128,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5163,7 +5163,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5192,7 +5192,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5480,7 +5480,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5505,11 +5505,11 @@ "default": "block", "rules": [ { - "rule": "chartbeat\\.com\\/chartbeat\\.js", + "rule": "chartbeat\\.com/chartbeat\\.js", "surrogate": "chartbeat.js" }, { - "rule": "chartbeat\\.com\\/js\\/chartbeat\\.js", + "rule": "chartbeat\\.com/js/chartbeat\\.js", "surrogate": "chartbeat.js" } ] @@ -5597,7 +5597,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5608,7 +5608,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5619,7 +5619,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5630,7 +5630,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5667,7 +5667,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5722,7 +5722,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6258,7 +6258,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6417,7 +6417,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6733,7 +6733,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6744,7 +6744,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6868,7 +6868,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7064,7 +7064,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7100,7 +7100,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7111,7 +7111,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7122,7 +7122,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7705,7 +7705,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7716,7 +7716,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7727,7 +7727,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7803,7 +7803,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7926,7 +7926,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7937,7 +7937,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8015,7 +8015,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8026,7 +8026,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8141,7 +8141,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8214,7 +8214,7 @@ "surrogate": "gpt.js" }, { - "rule": "doubleclick\\.net\\/tag\\/js\\/gpt\\.js", + "rule": "doubleclick\\.net/tag/js/gpt\\.js", "surrogate": "gpt.js" }, { @@ -8284,7 +8284,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8295,7 +8295,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8821,7 +8821,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8832,7 +8832,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8873,7 +8873,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9289,7 +9289,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9899,7 +9899,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9910,7 +9910,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9921,7 +9921,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9961,7 +9961,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10003,7 +10003,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10077,7 +10077,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10145,7 +10145,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10156,7 +10156,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10204,7 +10204,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10330,7 +10330,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10490,7 +10490,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10501,7 +10501,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10512,7 +10512,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10539,7 +10539,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10589,7 +10589,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10636,7 +10636,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11115,7 +11115,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11155,7 +11155,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11220,7 +11220,7 @@ "default": "block", "rules": [ { - "rule": "google-analytics\\.com\\/ga\\.js", + "rule": "google-analytics\\.com/ga\\.js", "surrogate": "ga.js" }, { @@ -11235,19 +11235,19 @@ } }, { - "rule": "google-analytics\\.com\\/inpage_linkid\\.js", + "rule": "google-analytics\\.com/inpage_linkid\\.js", "surrogate": "inpage_linkid.js" }, { - "rule": "google-analytics\\.com\\/plugins\\/ga\\/inpage_linkid\\.js", + "rule": "google-analytics\\.com/plugins/ga/inpage_linkid\\.js", "surrogate": "inpage_linkid.js" }, { - "rule": "google-analytics\\.com\\/cx\\/api\\.js", + "rule": "google-analytics\\.com/cx/api\\.js", "surrogate": "api.js" }, { - "rule": "google-analytics\\.com\\/gtm\\/js", + "rule": "google-analytics\\.com/gtm/js", "surrogate": "gtm.js" } ] @@ -11616,11 +11616,11 @@ "default": "block", "rules": [ { - "rule": "googlesyndication\\.com\\/adsbygoogle\\.js", + "rule": "googlesyndication\\.com/adsbygoogle\\.js", "surrogate": "adsbygoogle.js" }, { - "rule": "googlesyndication\\.com\\/pagead\\/js\\/adsbygoogle\\.js", + "rule": "googlesyndication\\.com/pagead/js/adsbygoogle\\.js", "surrogate": "adsbygoogle.js" } ] @@ -11815,7 +11815,7 @@ "default": "block", "rules": [ { - "rule": "googletagservices\\.com\\/gpt\\.js", + "rule": "googletagservices\\.com/gpt\\.js", "surrogate": "gpt.js" }, { @@ -11848,7 +11848,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11935,7 +11935,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11946,7 +11946,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12178,7 +12178,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12218,7 +12218,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12229,7 +12229,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12240,7 +12240,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12251,7 +12251,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12577,7 +12577,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12588,7 +12588,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12599,7 +12599,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -14522,7 +14522,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -15207,7 +15207,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -15282,7 +15282,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -15333,7 +15333,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16155,7 +16155,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16166,7 +16166,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16194,7 +16194,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16413,7 +16413,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16721,7 +16721,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16732,7 +16732,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16917,7 +16917,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16966,7 +16966,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -17240,7 +17240,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -17251,7 +17251,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -17814,7 +17814,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -18126,7 +18126,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -18265,7 +18265,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -18615,7 +18615,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -18845,7 +18845,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20023,7 +20023,7 @@ "default": "block", "rules": [ { - "rule": "outbrain\\.com\\/outbrain\\.js", + "rule": "outbrain\\.com/outbrain\\.js", "surrogate": "outbrain.js" } ] @@ -20034,7 +20034,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20255,7 +20255,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20266,7 +20266,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20277,7 +20277,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20385,7 +20385,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20862,7 +20862,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20891,7 +20891,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20902,7 +20902,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20913,7 +20913,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20977,7 +20977,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21057,7 +21057,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21105,7 +21105,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21116,7 +21116,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21161,7 +21161,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21172,7 +21172,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21207,7 +21207,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21218,7 +21218,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21351,7 +21351,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21442,7 +21442,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21913,7 +21913,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21924,7 +21924,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21935,7 +21935,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22005,7 +22005,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22016,7 +22016,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22169,7 +22169,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22180,7 +22180,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22222,7 +22222,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22233,7 +22233,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22244,7 +22244,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22514,7 +22514,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22525,7 +22525,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22586,7 +22586,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22644,7 +22644,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22655,7 +22655,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22755,7 +22755,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22778,7 +22778,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22789,7 +22789,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23130,7 +23130,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23234,7 +23234,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23631,7 +23631,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23706,7 +23706,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23717,7 +23717,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23761,7 +23761,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23772,7 +23772,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23783,7 +23783,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23794,7 +23794,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23874,7 +23874,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23896,7 +23896,7 @@ "default": "block", "rules": [ { - "rule": "scorecardresearch\\.com\\/beacon\\.js", + "rule": "scorecardresearch\\.com/beacon\\.js", "surrogate": "beacon.js" } ] @@ -23907,7 +23907,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23958,7 +23958,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24094,7 +24094,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24223,7 +24223,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24234,7 +24234,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24481,7 +24481,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24492,7 +24492,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24503,7 +24503,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24784,7 +24784,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24819,7 +24819,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24860,7 +24860,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24976,7 +24976,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24987,7 +24987,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25016,7 +25016,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25112,7 +25112,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25169,7 +25169,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25180,7 +25180,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25301,7 +25301,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25458,7 +25458,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25484,7 +25484,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25495,7 +25495,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25558,7 +25558,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25569,7 +25569,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25580,7 +25580,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25811,7 +25811,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25838,7 +25838,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25849,7 +25849,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25860,7 +25860,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25890,7 +25890,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25901,7 +25901,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25929,7 +25929,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25940,7 +25940,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26028,7 +26028,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26076,7 +26076,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26087,7 +26087,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26098,7 +26098,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26109,7 +26109,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26120,7 +26120,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26131,7 +26131,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26203,7 +26203,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26214,7 +26214,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26268,7 +26268,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26470,7 +26470,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26778,7 +26778,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26789,7 +26789,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26800,7 +26800,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -27051,7 +27051,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -27062,7 +27062,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -27427,7 +27427,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28284,7 +28284,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28414,7 +28414,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28425,7 +28425,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28558,7 +28558,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28641,7 +28641,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28652,7 +28652,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -29012,7 +29012,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -29308,7 +29308,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -29319,7 +29319,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -29445,7 +29445,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -29456,7 +29456,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -31190,7 +31190,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -31494,7 +31494,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32654,7 +32654,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32665,7 +32665,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32676,7 +32676,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32687,7 +32687,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32698,7 +32698,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32709,7 +32709,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32720,7 +32720,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32731,7 +32731,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "adamantsnail.com": { + "domain": "adamantsnail.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32742,7 +32753,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32753,7 +32764,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32764,7 +32775,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32775,7 +32786,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32786,7 +32797,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32797,7 +32808,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32808,7 +32819,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32819,7 +32830,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32830,7 +32841,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32841,7 +32852,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32852,7 +32863,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32863,7 +32874,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32874,7 +32885,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32885,7 +32896,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32896,7 +32907,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32907,7 +32918,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32918,7 +32929,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32929,7 +32940,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32940,7 +32951,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32951,7 +32962,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32962,7 +32973,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32973,7 +32984,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32984,7 +32995,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32995,7 +33006,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33006,7 +33017,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33017,7 +33028,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33028,7 +33039,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33039,7 +33050,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33050,7 +33061,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33061,7 +33072,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33072,7 +33083,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33083,7 +33094,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33094,7 +33105,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33105,7 +33116,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33116,7 +33127,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33127,7 +33138,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33138,7 +33149,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33149,7 +33160,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33160,7 +33171,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33171,7 +33182,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33182,7 +33193,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33193,7 +33204,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33204,7 +33215,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33215,7 +33226,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33226,7 +33237,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33237,7 +33248,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33248,7 +33259,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33259,7 +33270,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33270,7 +33281,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33281,7 +33292,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33292,7 +33303,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33303,7 +33314,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33314,7 +33325,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33325,7 +33336,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33336,7 +33347,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33347,7 +33358,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33358,7 +33369,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "chalkoil.com": { + "domain": "chalkoil.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33369,7 +33391,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33380,7 +33402,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33391,7 +33413,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33402,7 +33424,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33413,7 +33435,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33424,7 +33446,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33435,7 +33457,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33446,7 +33468,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33457,7 +33479,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33468,7 +33490,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33479,7 +33501,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33490,7 +33512,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33501,7 +33523,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33512,7 +33534,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33523,7 +33545,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33534,7 +33556,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33545,7 +33567,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33556,7 +33578,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33567,7 +33589,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33578,7 +33600,29 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "cozyhillside.com": { + "domain": "cozyhillside.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0144, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "crimsonmeadow.com": { + "domain": "crimsonmeadow.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33589,7 +33633,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33600,7 +33644,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "crystalboulevard.com": { + "domain": "crystalboulevard.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33611,7 +33666,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33622,7 +33677,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33633,7 +33688,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33644,7 +33699,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33655,7 +33710,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33666,7 +33721,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33677,7 +33732,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33688,7 +33743,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33699,7 +33754,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33710,7 +33765,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33721,7 +33776,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33732,7 +33787,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "delicatecascade.com": { + "domain": "delicatecascade.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33743,7 +33809,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33754,7 +33820,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33765,7 +33831,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33776,7 +33842,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33787,7 +33853,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33798,7 +33864,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33809,7 +33875,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33820,7 +33886,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33831,7 +33897,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33842,7 +33908,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33853,7 +33919,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33864,7 +33930,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33875,7 +33941,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33886,7 +33952,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "ethereallagoon.com": { + "domain": "ethereallagoon.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33897,7 +33974,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33908,7 +33985,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33919,7 +33996,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33930,7 +34007,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33941,7 +34018,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33952,7 +34029,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33963,7 +34040,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33974,7 +34051,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33985,7 +34062,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33996,7 +34073,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34007,7 +34084,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34018,7 +34095,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34029,7 +34106,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34040,7 +34117,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34051,7 +34128,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34062,7 +34139,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34073,7 +34150,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34084,7 +34161,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34095,7 +34172,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34106,7 +34183,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34117,7 +34194,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34128,7 +34205,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34139,7 +34216,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "forgetfulsnail.com": { + "domain": "forgetfulsnail.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34150,7 +34238,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34161,7 +34249,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34172,7 +34260,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34183,7 +34271,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34194,7 +34282,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34205,7 +34293,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34216,7 +34304,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34227,7 +34315,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34238,7 +34326,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34249,7 +34337,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34260,7 +34348,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34271,7 +34359,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34282,7 +34370,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34293,7 +34381,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34304,7 +34392,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34315,7 +34403,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34326,7 +34414,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34337,7 +34425,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34348,7 +34436,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34359,7 +34447,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34370,7 +34458,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34381,7 +34469,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34392,7 +34480,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34403,7 +34491,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34414,7 +34502,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34425,7 +34513,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34436,7 +34524,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34447,7 +34535,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34458,7 +34546,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "headydegree.com": { + "domain": "headydegree.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34469,7 +34568,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34480,7 +34579,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34491,7 +34590,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34502,7 +34601,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34513,7 +34612,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34524,7 +34623,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34535,7 +34634,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34546,7 +34645,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34557,7 +34656,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34568,7 +34667,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34579,7 +34678,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34590,7 +34689,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34601,7 +34700,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34612,7 +34711,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34623,7 +34722,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34634,7 +34733,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "jubilantcanyon.com": { + "domain": "jubilantcanyon.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34645,7 +34755,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34656,7 +34766,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34667,7 +34777,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34678,7 +34788,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34689,7 +34799,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34700,7 +34810,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34711,7 +34821,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34722,7 +34832,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34733,7 +34843,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34744,7 +34854,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34755,7 +34865,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34766,7 +34876,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34777,7 +34887,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34788,7 +34898,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34799,7 +34909,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34810,7 +34920,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34821,7 +34931,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34832,7 +34942,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34843,7 +34953,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34854,7 +34964,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34865,7 +34975,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34876,7 +34986,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34887,7 +34997,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34898,7 +35008,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34909,7 +35019,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34920,7 +35030,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34931,7 +35041,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34942,7 +35052,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34953,7 +35063,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34964,7 +35074,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34975,7 +35085,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34986,7 +35096,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34997,7 +35107,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35008,7 +35118,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35019,7 +35129,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35030,7 +35140,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35041,7 +35151,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35052,7 +35162,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35063,7 +35173,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35074,7 +35184,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35085,7 +35195,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35096,7 +35206,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35107,7 +35217,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35118,7 +35228,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35129,7 +35239,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35140,7 +35250,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35151,7 +35261,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35162,7 +35272,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35173,7 +35283,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35184,7 +35294,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35195,7 +35305,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35206,7 +35316,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35217,7 +35327,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35228,7 +35338,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35239,7 +35349,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35250,7 +35360,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35261,7 +35371,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35272,7 +35382,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35283,7 +35393,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35294,7 +35404,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35305,7 +35415,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35316,7 +35426,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35327,7 +35437,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35338,7 +35448,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35349,7 +35459,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35360,7 +35470,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35371,7 +35481,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35382,7 +35492,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35393,7 +35503,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35404,7 +35514,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35415,7 +35525,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35426,7 +35536,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35437,7 +35547,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35448,7 +35558,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35459,7 +35569,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35470,7 +35580,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35481,7 +35591,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35492,7 +35602,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35503,7 +35613,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35514,7 +35624,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35525,7 +35635,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35536,7 +35646,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35547,7 +35657,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35558,7 +35668,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35569,7 +35679,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35580,7 +35690,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35591,7 +35701,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35602,7 +35712,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35613,7 +35723,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35624,7 +35734,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35635,7 +35745,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35646,7 +35756,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35657,7 +35767,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35668,7 +35778,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35679,7 +35789,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35690,7 +35800,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35701,7 +35811,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35712,7 +35822,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35723,7 +35833,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35734,7 +35844,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35745,7 +35855,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35756,7 +35866,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35767,7 +35877,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35778,7 +35888,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35789,7 +35899,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35800,7 +35910,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35811,7 +35921,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35822,7 +35932,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35833,7 +35943,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35844,7 +35954,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35855,7 +35965,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35866,7 +35976,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35877,7 +35987,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35888,7 +35998,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35899,7 +36009,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35910,7 +36020,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35921,7 +36031,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35932,7 +36042,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35943,7 +36053,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35954,7 +36064,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35965,7 +36075,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35976,7 +36086,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35987,7 +36097,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35998,7 +36108,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "smilingswim.com": { + "domain": "smilingswim.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36009,7 +36130,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36020,7 +36141,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36031,7 +36152,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36042,7 +36163,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36053,7 +36174,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36064,7 +36185,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36075,7 +36196,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36086,7 +36207,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36097,7 +36218,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36108,7 +36229,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36119,7 +36240,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36130,7 +36251,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36141,7 +36262,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36152,7 +36273,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36163,7 +36284,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36174,7 +36295,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36185,7 +36306,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36196,7 +36317,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36207,7 +36328,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36218,7 +36339,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36229,7 +36350,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36240,7 +36361,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36251,7 +36372,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36262,7 +36383,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36273,7 +36394,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36284,7 +36405,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36295,7 +36416,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36306,7 +36427,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36317,7 +36438,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "succeedscene.com": { + "domain": "succeedscene.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36328,7 +36460,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36339,7 +36471,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36350,7 +36482,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36361,7 +36493,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36372,7 +36504,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36383,7 +36515,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36394,7 +36526,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36405,7 +36537,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36416,7 +36548,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36427,7 +36559,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36438,7 +36570,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36449,7 +36581,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36460,7 +36592,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36471,7 +36603,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36482,7 +36614,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36493,7 +36625,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36504,7 +36636,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36515,7 +36647,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36526,7 +36658,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36537,7 +36669,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36548,7 +36680,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36559,7 +36691,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36570,7 +36702,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36581,7 +36713,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36592,7 +36724,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "tranquilcanyon.com": { + "domain": "tranquilcanyon.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36603,7 +36746,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36614,7 +36757,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36625,7 +36768,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36636,7 +36779,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36647,7 +36790,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36658,7 +36801,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36669,7 +36812,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36680,7 +36823,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36691,7 +36834,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36702,7 +36845,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36713,7 +36856,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36724,7 +36867,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36735,7 +36878,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36746,7 +36889,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36757,7 +36900,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36768,7 +36911,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36779,7 +36922,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36790,7 +36933,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36801,7 +36944,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36812,7 +36955,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36823,7 +36966,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36834,7 +36977,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36845,7 +36988,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36856,7 +36999,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36867,7 +37010,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36878,7 +37021,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36889,7 +37032,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36900,7 +37043,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36911,7 +37054,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36922,7 +37065,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0138, + "prevalence": 0.0144, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -46961,6 +47104,7 @@ "actoramusement.com", "actuallysnake.com", "actuallything.com", + "adamantsnail.com", "adorableanger.com", "adventurousamount.com", "agreeablearch.com", @@ -47045,6 +47189,7 @@ "cautiouscherries.com", "cautiouscredit.com", "ceciliavenus.com", + "chalkoil.com", "chargecracker.com", "charmingplate.com", "cherriescare.com", @@ -47074,11 +47219,14 @@ "consciousdirt.com", "courageousbaby.com", "coverapparatus.com", + "cozyhillside.com", "crabbychin.com", "cratecamera.com", + "crimsonmeadow.com", "critictruck.com", "crookedcreature.com", "crowdedmass.com", + "crystalboulevard.com", "cubchannel.com", "cumbersomecarpenter.com", "currentcollar.com", @@ -47099,6 +47247,7 @@ "decisivedrawer.com", "decisiveducks.com", "deerbeginner.com", + "delicatecascade.com", "detailedkitten.com", "detectdiscovery.com", "devilishdinner.com", @@ -47123,6 +47272,7 @@ "entertainskin.com", "enviousshape.com", "equablekettle.com", + "ethereallagoon.com", "evanescentedge.com", "eventexistence.com", "exampleshake.com", @@ -47156,6 +47306,7 @@ "floweryflavor.com", "flutteringfireman.com", "followborder.com", + "forgetfulsnail.com", "fortunatemark.com", "frailfruit.com", "franticroof.com", @@ -47202,6 +47353,7 @@ "haplessland.com", "harborcaption.com", "hatefulrequest.com", + "headydegree.com", "heartbreakingmind.com", "hearthorn.com", "heavyplayground.com", @@ -47222,6 +47374,7 @@ "inquisitiveice.com", "internalsink.com", "j93557g.com", + "jubilantcanyon.com", "kaputquill.com", "knitstamp.com", "knottyswing.com", @@ -47429,6 +47582,7 @@ "slopesoap.com", "smashquartz.com", "smashsurprise.com", + "smilingswim.com", "smoggysnakes.com", "smoggysongs.com", "soggysponge.com", @@ -47479,6 +47633,7 @@ "stupendoussleet.com", "stupendoussnow.com", "stupidscene.com", + "succeedscene.com", "sugarfriction.com", "suggestionbridge.com", "sulkycook.com", @@ -47515,6 +47670,7 @@ "tidymitten.com", "tiredthroat.com", "tiresomethunder.com", + "tranquilcanyon.com", "tremendousearthquake.com", "tremendousplastic.com", "tritebadge.com", @@ -47558,7 +47714,7 @@ "zipperxray.com", "zlp6s.pw" ], - "prevalence": 0.0138, + "prevalence": 0.0144, "displayName": "Admiral" } }, @@ -48288,6 +48444,7 @@ "actoramusement.com": "Leven Labs, Inc. DBA Admiral", "actuallysnake.com": "Leven Labs, Inc. DBA Admiral", "actuallything.com": "Leven Labs, Inc. DBA Admiral", + "adamantsnail.com": "Leven Labs, Inc. DBA Admiral", "adorableanger.com": "Leven Labs, Inc. DBA Admiral", "adventurousamount.com": "Leven Labs, Inc. DBA Admiral", "agreeablearch.com": "Leven Labs, Inc. DBA Admiral", @@ -48372,6 +48529,7 @@ "cautiouscherries.com": "Leven Labs, Inc. DBA Admiral", "cautiouscredit.com": "Leven Labs, Inc. DBA Admiral", "ceciliavenus.com": "Leven Labs, Inc. DBA Admiral", + "chalkoil.com": "Leven Labs, Inc. DBA Admiral", "chargecracker.com": "Leven Labs, Inc. DBA Admiral", "charmingplate.com": "Leven Labs, Inc. DBA Admiral", "cherriescare.com": "Leven Labs, Inc. DBA Admiral", @@ -48401,11 +48559,14 @@ "consciousdirt.com": "Leven Labs, Inc. DBA Admiral", "courageousbaby.com": "Leven Labs, Inc. DBA Admiral", "coverapparatus.com": "Leven Labs, Inc. DBA Admiral", + "cozyhillside.com": "Leven Labs, Inc. DBA Admiral", "crabbychin.com": "Leven Labs, Inc. DBA Admiral", "cratecamera.com": "Leven Labs, Inc. DBA Admiral", + "crimsonmeadow.com": "Leven Labs, Inc. DBA Admiral", "critictruck.com": "Leven Labs, Inc. DBA Admiral", "crookedcreature.com": "Leven Labs, Inc. DBA Admiral", "crowdedmass.com": "Leven Labs, Inc. DBA Admiral", + "crystalboulevard.com": "Leven Labs, Inc. DBA Admiral", "cubchannel.com": "Leven Labs, Inc. DBA Admiral", "cumbersomecarpenter.com": "Leven Labs, Inc. DBA Admiral", "currentcollar.com": "Leven Labs, Inc. DBA Admiral", @@ -48426,6 +48587,7 @@ "decisivedrawer.com": "Leven Labs, Inc. DBA Admiral", "decisiveducks.com": "Leven Labs, Inc. DBA Admiral", "deerbeginner.com": "Leven Labs, Inc. DBA Admiral", + "delicatecascade.com": "Leven Labs, Inc. DBA Admiral", "detailedkitten.com": "Leven Labs, Inc. DBA Admiral", "detectdiscovery.com": "Leven Labs, Inc. DBA Admiral", "devilishdinner.com": "Leven Labs, Inc. DBA Admiral", @@ -48450,6 +48612,7 @@ "entertainskin.com": "Leven Labs, Inc. DBA Admiral", "enviousshape.com": "Leven Labs, Inc. DBA Admiral", "equablekettle.com": "Leven Labs, Inc. DBA Admiral", + "ethereallagoon.com": "Leven Labs, Inc. DBA Admiral", "evanescentedge.com": "Leven Labs, Inc. DBA Admiral", "eventexistence.com": "Leven Labs, Inc. DBA Admiral", "exampleshake.com": "Leven Labs, Inc. DBA Admiral", @@ -48483,6 +48646,7 @@ "floweryflavor.com": "Leven Labs, Inc. DBA Admiral", "flutteringfireman.com": "Leven Labs, Inc. DBA Admiral", "followborder.com": "Leven Labs, Inc. DBA Admiral", + "forgetfulsnail.com": "Leven Labs, Inc. DBA Admiral", "fortunatemark.com": "Leven Labs, Inc. DBA Admiral", "frailfruit.com": "Leven Labs, Inc. DBA Admiral", "franticroof.com": "Leven Labs, Inc. DBA Admiral", @@ -48529,6 +48693,7 @@ "haplessland.com": "Leven Labs, Inc. DBA Admiral", "harborcaption.com": "Leven Labs, Inc. DBA Admiral", "hatefulrequest.com": "Leven Labs, Inc. DBA Admiral", + "headydegree.com": "Leven Labs, Inc. DBA Admiral", "heartbreakingmind.com": "Leven Labs, Inc. DBA Admiral", "hearthorn.com": "Leven Labs, Inc. DBA Admiral", "heavyplayground.com": "Leven Labs, Inc. DBA Admiral", @@ -48549,6 +48714,7 @@ "inquisitiveice.com": "Leven Labs, Inc. DBA Admiral", "internalsink.com": "Leven Labs, Inc. DBA Admiral", "j93557g.com": "Leven Labs, Inc. DBA Admiral", + "jubilantcanyon.com": "Leven Labs, Inc. DBA Admiral", "kaputquill.com": "Leven Labs, Inc. DBA Admiral", "knitstamp.com": "Leven Labs, Inc. DBA Admiral", "knottyswing.com": "Leven Labs, Inc. DBA Admiral", @@ -48756,6 +48922,7 @@ "slopesoap.com": "Leven Labs, Inc. DBA Admiral", "smashquartz.com": "Leven Labs, Inc. DBA Admiral", "smashsurprise.com": "Leven Labs, Inc. DBA Admiral", + "smilingswim.com": "Leven Labs, Inc. DBA Admiral", "smoggysnakes.com": "Leven Labs, Inc. DBA Admiral", "smoggysongs.com": "Leven Labs, Inc. DBA Admiral", "soggysponge.com": "Leven Labs, Inc. DBA Admiral", @@ -48806,6 +48973,7 @@ "stupendoussleet.com": "Leven Labs, Inc. DBA Admiral", "stupendoussnow.com": "Leven Labs, Inc. DBA Admiral", "stupidscene.com": "Leven Labs, Inc. DBA Admiral", + "succeedscene.com": "Leven Labs, Inc. DBA Admiral", "sugarfriction.com": "Leven Labs, Inc. DBA Admiral", "suggestionbridge.com": "Leven Labs, Inc. DBA Admiral", "sulkycook.com": "Leven Labs, Inc. DBA Admiral", @@ -48842,6 +49010,7 @@ "tidymitten.com": "Leven Labs, Inc. DBA Admiral", "tiredthroat.com": "Leven Labs, Inc. DBA Admiral", "tiresomethunder.com": "Leven Labs, Inc. DBA Admiral", + "tranquilcanyon.com": "Leven Labs, Inc. DBA Admiral", "tremendousearthquake.com": "Leven Labs, Inc. DBA Admiral", "tremendousplastic.com": "Leven Labs, Inc. DBA Admiral", "tritebadge.com": "Leven Labs, Inc. DBA Admiral", diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index 9e8c7d23c5..51d76c289e 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -51,9 +51,11 @@ final class DBPHomeViewController: NSViewController { return DataBrokerProtectionViewController(scheduler: dataBrokerProtectionManager.scheduler, dataManager: dataBrokerProtectionManager.dataManager, - notificationCenter: NotificationCenter.default, privacyConfig: privacyConfigurationManager, - prefs: prefs) + prefs: prefs, + openURLHandler: { url in + WindowControllersManager.shared.show(url: url, newTab: true) + }) }() init(dataBrokerProtectionManager: DataBrokerProtectionManager) { @@ -128,40 +130,26 @@ extension DBPHomeViewController: DataBrokerProtectionInviteDialogsViewModelDeleg public class DataBrokerProtectionPixelsHandler: EventMapping { - // swiftlint:disable:next cyclomatic_complexity public init() { super.init { event, _, _, _ in switch event { case .error(let error, _): - Pixel.fire(.debug(event: .dataBrokerProtectionError, error: error), withAdditionalParameters: event.params) - case .parentChildMatches: - Pixel.fire(.parentChildMatches, withAdditionalParameters: event.params) - case .optOutStart: - Pixel.fire(.optOutStart, withAdditionalParameters: event.params) - case .optOutEmailGenerate: - Pixel.fire(.optOutEmailGenerate, withAdditionalParameters: event.params) - case .optOutCaptchaParse: - Pixel.fire(.optOutCaptchaParse, withAdditionalParameters: event.params) - case .optOutCaptchaSend: - Pixel.fire(.optOutCaptchaSend, withAdditionalParameters: event.params) - case .optOutCaptchaSolve: - Pixel.fire(.optOutCaptchaSolve, withAdditionalParameters: event.params) - case .optOutSubmit: - Pixel.fire(.optOutSubmit, withAdditionalParameters: event.params) - case .optOutEmailReceive: - Pixel.fire(.optOutEmailReceive, withAdditionalParameters: event.params) - case .optOutEmailConfirm: - Pixel.fire(.optOutEmailConfirm, withAdditionalParameters: event.params) - case .optOutValidate: - Pixel.fire(.optOutValidate, withAdditionalParameters: event.params) - case .optOutFinish: - Pixel.fire(.optOutFinish, withAdditionalParameters: event.params) - case .optOutSubmitSuccess: - Pixel.fire(.optOutSubmitSuccess, withAdditionalParameters: event.params) - case .optOutSuccess: - Pixel.fire(.optOutSuccess, withAdditionalParameters: event.params) - case .optOutFailure: - Pixel.fire(.optOutFailure, withAdditionalParameters: event.params) + Pixel.fire(.debug(event: .pixelKitEvent(event), error: error)) + case .parentChildMatches, + .optOutStart, + .optOutEmailGenerate, + .optOutCaptchaParse, + .optOutCaptchaSend, + .optOutCaptchaSolve, + .optOutSubmit, + .optOutEmailReceive, + .optOutEmailConfirm, + .optOutValidate, + .optOutFinish, + .optOutSubmitSuccess, + .optOutSuccess, + .optOutFailure: + Pixel.fire(.pixelKitEvent(event)) } } } diff --git a/DuckDuckGo/DataImport/DataImport.swift b/DuckDuckGo/DataImport/DataImport.swift index e31a4a5af2..53f024d052 100644 --- a/DuckDuckGo/DataImport/DataImport.swift +++ b/DuckDuckGo/DataImport/DataImport.swift @@ -18,6 +18,7 @@ import AppKit import SecureStorage +import PixelKit enum DataImport { @@ -267,7 +268,7 @@ enum DataImportAction { case generic } -protocol DataImportError: Error, CustomNSError, ErrorWithParameters { +protocol DataImportError: Error, CustomNSError, ErrorWithPixelParameters { associatedtype OperationType: RawRepresentable where OperationType.RawValue == Int var source: DataImport.Source { get } diff --git a/DuckDuckGo/DataImport/View/DataImportViewController.swift b/DuckDuckGo/DataImport/View/DataImportViewController.swift index d2fda1b1a2..ff7e1fbb29 100644 --- a/DuckDuckGo/DataImport/View/DataImportViewController.swift +++ b/DuckDuckGo/DataImport/View/DataImportViewController.swift @@ -483,7 +483,7 @@ final class DataImportViewController: NSViewController { } private func requestSync() { - guard let syncService = (NSApp.delegate as? AppDelegate)?.syncService else { + guard let syncService = NSApp.delegateTyped.syncService else { return } os_log(.debug, log: OSLog.sync, "Requesting sync if enabled") diff --git a/DuckDuckGo/Favicons/Model/FaviconManager.swift b/DuckDuckGo/Favicons/Model/FaviconManager.swift index 000907a621..3341db705a 100644 --- a/DuckDuckGo/Favicons/Model/FaviconManager.swift +++ b/DuckDuckGo/Favicons/Model/FaviconManager.swift @@ -25,6 +25,7 @@ import Common protocol FaviconManagement: AnyObject { var areFaviconsLoaded: Bool { get } + var faviconsLoadedPublisher: Published.Publisher { get } func loadFavicons() @@ -85,6 +86,7 @@ final class FaviconManager: FaviconManagement { private let faviconURLSession = URLSession(configuration: .ephemeral) @Published private(set) var faviconsLoaded = false + var faviconsLoadedPublisher: Published.Publisher { $faviconsLoaded } nonisolated func loadFavicons() { imageCache.loadFavicons { _ in diff --git a/DuckDuckGo/FileDownload/Services/DownloadListStore.swift b/DuckDuckGo/FileDownload/Services/DownloadListStore.swift index 2ad560b75d..a60415329f 100644 --- a/DuckDuckGo/FileDownload/Services/DownloadListStore.swift +++ b/DuckDuckGo/FileDownload/Services/DownloadListStore.swift @@ -55,7 +55,7 @@ final class DownloadListStore: DownloadListStoring { private var context: NSManagedObjectContext? { if case .none = _context { #if DEBUG - if NSApp.isRunningUnitTests { + if case .unitTests = NSApp.runType { _context = .some(.none) return .none } diff --git a/DuckDuckGo/Fire/Model/Fire.swift b/DuckDuckGo/Fire/Model/Fire.swift index 6f9b17b45e..d0b9e18be3 100644 --- a/DuckDuckGo/Fire/Model/Fire.swift +++ b/DuckDuckGo/Fire/Model/Fire.swift @@ -109,17 +109,15 @@ final class Fire { self.recentlyClosedCoordinator = recentlyClosedCoordinator self.pinnedTabsManager = pinnedTabsManager ?? WindowControllersManager.shared.pinnedTabsManager self.bookmarkManager = bookmarkManager - self.syncService = syncService ?? (NSApp.delegate as? AppDelegate)?.syncService - self.syncDataProviders = syncDataProviders ?? (NSApp.delegate as? AppDelegate)?.syncDataProviders + self.syncService = syncService ?? NSApp.delegateTyped.syncService + self.syncDataProviders = syncDataProviders ?? NSApp.delegateTyped.syncDataProviders self.secureVaultFactory = secureVaultFactory self.tld = tld self.autoconsentManagement = autoconsentManagement ?? AutoconsentManagement.shared if let stateRestorationManager = stateRestorationManager { self.stateRestorationManager = stateRestorationManager - } else if let appDelegate = NSApp.delegate as? AppDelegate { - self.stateRestorationManager = appDelegate.stateRestorationManager } else { - self.stateRestorationManager = nil + self.stateRestorationManager = NSApp.delegateTyped.stateRestorationManager } } @@ -306,7 +304,7 @@ final class Fire { DispatchQueue.main.async { [weak self] in guard let self else { return } if self.windowControllerManager.mainWindowControllers.count == 0 { - (NSApp.delegate as? AppDelegate)?.newWindow(self) + NSApp.delegateTyped.newWindow(self) } } } diff --git a/DuckDuckGo/Fire/View/FirePopoverViewController.swift b/DuckDuckGo/Fire/View/FirePopoverViewController.swift index 5f70977be6..41abbdf6e8 100644 --- a/DuckDuckGo/Fire/View/FirePopoverViewController.swift +++ b/DuckDuckGo/Fire/View/FirePopoverViewController.swift @@ -127,7 +127,7 @@ final class FirePopoverViewController: NSViewController { } @IBAction func openNewBurnerWindowAction(_ sender: Any) { - (NSApp.delegate as? AppDelegate)?.newBurnerWindow(self) + NSApp.delegateTyped.newBurnerWindow(self) } @IBAction func openDetailsButtonAction(_ sender: Any) { diff --git a/DuckDuckGo/Fire/View/FireViewController.swift b/DuckDuckGo/Fire/View/FireViewController.swift index fcd88e569d..730b483f26 100644 --- a/DuckDuckGo/Fire/View/FireViewController.swift +++ b/DuckDuckGo/Fire/View/FireViewController.swift @@ -63,21 +63,16 @@ final class FireViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() -#if DEBUG - let isRunningTests = NSApp.isRunningUnitTests -#else - let isRunningTests = false -#endif - - fireAnimationViewLoadingTask = isRunningTests ? nil : Task.detached(priority: .userInitiated) { - await self.setupFireAnimationView() + if case .normal = NSApp.runType { + fireAnimationViewLoadingTask = Task.detached(priority: .userInitiated) { + await self.setupFireAnimationView() + } } } override func viewWillAppear() { super.viewWillAppear() -// self.view.superview?.isHidden = true subscribeToFireAnimationEvents() progressIndicator.startAnimation(self) } diff --git a/DuckDuckGo/HomePage/View/HomePageViewController.swift b/DuckDuckGo/HomePage/View/HomePageViewController.swift index 9c0de27cce..4db924d2b3 100644 --- a/DuckDuckGo/HomePage/View/HomePageViewController.swift +++ b/DuckDuckGo/HomePage/View/HomePageViewController.swift @@ -129,7 +129,7 @@ final class HomePageViewController: NSViewController { } func refreshModels() { - guard !NSApp.isRunningUnitTests else { return } + guard NSApp.runType.requiresEnvironment else { return } refreshFavoritesModel() refreshRecentlyVisitedModel() diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index 6a5b9f5cae..25cc319f62 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -84,8 +84,6 @@ Allows you to share your geolocation NSLocationWhenInUseUsageDescription Allows you to share your location - NSMainStoryboardFile - MainMenu NSMicrophoneUsageDescription Allows you to share recordings NSAppDataUsageDescription diff --git a/DuckDuckGo/LoginItems/LoginItemsManager.swift b/DuckDuckGo/LoginItems/LoginItemsManager.swift index 461931b4f0..c6d741ce9a 100644 --- a/DuckDuckGo/LoginItems/LoginItemsManager.swift +++ b/DuckDuckGo/LoginItems/LoginItemsManager.swift @@ -23,30 +23,14 @@ import LoginItems /// Class to manage the login items for Network Protection and DBP /// final class LoginItemsManager { - - /// Save agent last launch time to distinguish between system launch at Log In and Main App launch - /// Used for the Connect On Log In feature to prevent connection when started by the Main App - /// Ideally we should remove this to make this class completely generic - @UserDefaultsWrapper(key: .netpMenuAgentLaunchTime, defaults: .shared) - private var netpMenuAgentLaunchTime: Date? - // MARK: - Main Interactions func enableLoginItems(_ items: Set, log: OSLog) { - -#if NETWORK_PROTECTION - if items.contains(.vpnMenu) { - netpMenuAgentLaunchTime = Date() - } -#endif - updateLoginItems(items, whatAreWeDoing: "enable", using: LoginItem.enable) - ensureLoginItemsAreRunning(items, log: log) } func restartLoginItems(_ items: Set, log: OSLog) { updateLoginItems(items, whatAreWeDoing: "restart", using: LoginItem.restart) - ensureLoginItemsAreRunning(items, log: log, condition: .ifLoginItemsAreEnabled) } func disableLoginItems(_ items: Set) { @@ -85,36 +69,4 @@ final class LoginItemsManager { self == .none } } - - /// Ensures that the login items are running. If an item that's supposed to be running is not, this method launches it manually. - /// - func ensureLoginItemsAreRunning(_ items: Set, log: OSLog, condition: LoginItemCheckCondition = .none, after interval: TimeInterval = .seconds(5)) { - - Task { - try await Task.sleep(interval: interval) - - os_log(.info, log: log, "Checking whether login agents are enabled and running") - - for item in items { - guard !item.isRunning && (condition.shouldIgnoreItemStatus || item.status.isEnabled) else { - os_log(.info, log: log, "Login item with ID '%{public}s': ok", item.debugDescription) - continue - } - - os_log(.error, log: log, "%{public}s is not running, launching manually", item.debugDescription) - - do { -#if NETWORK_PROTECTION - if item == .vpnMenu { - netpMenuAgentLaunchTime = Date() - } -#endif - try await item.launch() - os_log(.info, log: log, "Launched login item with ID '%{public}s'", item.debugDescription) - } catch { - os_log(.error, log: log, "Login item with ID '%{public}s' could not be launched. Error: %{public}s", item.debugDescription, "\(error)") - } - } - } - } } diff --git a/DuckDuckGo/Main/Main.swift b/DuckDuckGo/Main/Main.swift index 39cd889519..862af2fa26 100644 --- a/DuckDuckGo/Main/Main.swift +++ b/DuckDuckGo/Main/Main.swift @@ -38,57 +38,6 @@ final class AppMain { } static func main() { -#if NETWORK_PROTECTION - - // If the app is sandboxed, attempt to use the symlink approach for determining launch command: - if let launchPath = (CommandLine.arguments.first as NSString?)?.lastPathComponent { - switch launchPath { - case AppLaunchCommand.startVPN.rawValue: - swizzleMainBundle() - - Task { - await NetworkProtectionTunnelController().start(enableLoginItems: false) - exit(0) - } - - dispatchMain() - - case AppLaunchCommand.stopVPN.rawValue: - swizzleMainBundle() - - Task { - await NetworkProtectionTunnelController().stop() - exit(0) - } - - dispatchMain() - default: break - } - } - - // If the app is not sandboxed, read the process arguments to determine launch command: - if ProcessInfo.processInfo.arguments.contains(AppLaunchCommand.startVPN.rawValue) { - swizzleMainBundle() - - Task { - await NetworkProtectionTunnelController().start(enableLoginItems: false) - exit(0) - } - - dispatchMain() - } else if ProcessInfo.processInfo.arguments.contains(AppLaunchCommand.stopVPN.rawValue) { - swizzleMainBundle() - - Task { - await NetworkProtectionTunnelController().stop() - exit(0) - } - - dispatchMain() - } - -#endif - #if !APPSTORE && !DEBUG && !DBP PFMoveToApplicationsFolderIfNecessary() #endif diff --git a/DuckDuckGo/Main/View/MainViewController.swift b/DuckDuckGo/Main/View/MainViewController.swift index 31973d43a8..858c612fce 100644 --- a/DuckDuckGo/Main/View/MainViewController.swift +++ b/DuckDuckGo/Main/View/MainViewController.swift @@ -376,12 +376,7 @@ final class MainViewController: NSViewController { os_log("MainViewController: No tab view model selected", type: .error) return } - guard let backMenuItem = NSApplication.shared.mainMenuTyped.backMenuItem else { - assertionFailure("MainViewController: Failed to get reference to back menu item") - return - } - - backMenuItem.isEnabled = selectedTabViewModel.canGoBack + NSApp.mainMenuTyped.backMenuItem.isEnabled = selectedTabViewModel.canGoBack } private func updateForwardMenuItem() { @@ -390,12 +385,7 @@ final class MainViewController: NSViewController { os_log("MainViewController: No tab view model selected", type: .error) return } - guard let forwardMenuItem = NSApplication.shared.mainMenuTyped.forwardMenuItem else { - assertionFailure("MainViewController: Failed to get reference to Forward menu item") - return - } - - forwardMenuItem.isEnabled = selectedTabViewModel.canGoForward + NSApp.mainMenuTyped.forwardMenuItem.isEnabled = selectedTabViewModel.canGoForward } private func updateReloadMenuItem() { @@ -404,12 +394,7 @@ final class MainViewController: NSViewController { os_log("MainViewController: No tab view model selected", type: .error) return } - guard let reloadMenuItem = NSApplication.shared.mainMenuTyped.reloadMenuItem else { - assertionFailure("MainViewController: Failed to get reference to Reload menu item") - return - } - - reloadMenuItem.isEnabled = selectedTabViewModel.canReload + NSApp.mainMenuTyped.reloadMenuItem.isEnabled = selectedTabViewModel.canReload } private func updateStopMenuItem() { @@ -418,12 +403,7 @@ final class MainViewController: NSViewController { os_log("MainViewController: No tab view model selected", type: .error) return } - guard let stopMenuItem = NSApplication.shared.mainMenuTyped.stopMenuItem else { - assertionFailure("MainViewController: Failed to get reference to Stop menu item") - return - } - - stopMenuItem.isEnabled = selectedTabViewModel.isLoading + NSApp.mainMenuTyped.stopMenuItem.isEnabled = selectedTabViewModel.isLoading } #if NETWORK_PROTECTION diff --git a/DuckDuckGo/Menus/HistoryMenu.swift b/DuckDuckGo/Menus/HistoryMenu.swift index 140c0451f7..8fee4d75cd 100644 --- a/DuckDuckGo/Menus/HistoryMenu.swift +++ b/DuckDuckGo/Menus/HistoryMenu.swift @@ -23,24 +23,45 @@ import Common @MainActor final class HistoryMenu: NSMenu { - @IBOutlet weak var recentlyClosedMenuItem: NSMenuItem? - @IBOutlet weak var reopenLastClosedMenuItem: NSMenuItem? { - didSet { - reopenMenuItemKeyEquivalentManager.reopenLastClosedMenuItem = reopenLastClosedMenuItem - } - } - @IBOutlet weak var reopenAllWindowsFromLastSessionMenuItem: NSMenuItem? { - didSet { - reopenMenuItemKeyEquivalentManager.lastSessionMenuItem = reopenAllWindowsFromLastSessionMenuItem - } - } - @IBOutlet weak var clearAllHistoryMenuItem: NSMenuItem? + let backMenuItem = NSMenuItem(title: UserText.navigateBack, action: #selector(MainViewController.back), keyEquivalent: "[") + let forwardMenuItem = NSMenuItem(title: UserText.navigateForward, action: #selector(MainViewController.forward), keyEquivalent: "]") + + private let recentlyClosedMenuItem = NSMenuItem(title: UserText.mainMenuHistoryRecentlyClosed) + private let reopenLastClosedMenuItem = NSMenuItem(title: UserText.reopenLastClosedTab, action: #selector(AppDelegate.reopenLastClosedTab)) + private let reopenAllWindowsFromLastSessionMenuItem = NSMenuItem(title: UserText.reopenAllWindowsFromLastSession, + action: #selector(AppDelegate.reopenAllWindowsFromLastSession)) + private let clearAllHistoryMenuItem = NSMenuItem(title: UserText.mainMenuHistoryClearAllHistory, + action: #selector(MainViewController.clearAllHistory), + keyEquivalent: [.command, .shift, .backspace]) private let clearAllHistorySeparator = NSMenuItem.separator() - private let historyCoordinator: HistoryCoordinating = HistoryCoordinator.shared - private var recentlyClosedMenu: RecentlyClosedMenu? + private let historyCoordinator: HistoryCoordinating private let reopenMenuItemKeyEquivalentManager = ReopenMenuItemKeyEquivalentManager() + init(historyCoordinator: HistoryCoordinating = HistoryCoordinator.shared) { + self.historyCoordinator = historyCoordinator + + super.init(title: UserText.mainMenuHistory) + + self.buildItems { + backMenuItem + forwardMenuItem + NSMenuItem.separator() + + reopenLastClosedMenuItem + recentlyClosedMenuItem + reopenAllWindowsFromLastSessionMenuItem + NSMenuItem.separator() + } + + reopenMenuItemKeyEquivalentManager.reopenLastClosedMenuItem = reopenLastClosedMenuItem + reopenMenuItemKeyEquivalentManager.lastSessionMenuItem = reopenAllWindowsFromLastSessionMenuItem + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func update() { super.update() @@ -66,17 +87,17 @@ final class HistoryMenu: NSMenu { private func updateReopenLastClosedMenuItem() { switch RecentlyClosedCoordinator.shared.cache.last { case is RecentlyClosedWindow: - reopenLastClosedMenuItem?.title = UserText.reopenLastClosedWindow + reopenLastClosedMenuItem.title = UserText.reopenLastClosedWindow default: - reopenLastClosedMenuItem?.title = UserText.reopenLastClosedTab + reopenLastClosedMenuItem.title = UserText.reopenLastClosedTab } } private func updateRecentlyClosedMenu() { - recentlyClosedMenu = RecentlyClosedMenu(recentlyClosedCoordinator: RecentlyClosedCoordinator.shared) - recentlyClosedMenuItem?.submenu = recentlyClosedMenu - recentlyClosedMenuItem?.isEnabled = !(recentlyClosedMenu?.items ?? [] ).isEmpty + let recentlyClosedMenu = RecentlyClosedMenu(recentlyClosedCoordinator: RecentlyClosedCoordinator.shared) + recentlyClosedMenuItem.submenu = recentlyClosedMenu + recentlyClosedMenuItem.isEnabled = !recentlyClosedMenu.items.isEmpty } // MARK: - Recently Visited @@ -87,7 +108,7 @@ final class HistoryMenu: NSMenu { return item } - var recentlyVisitedMenuItems = [NSMenuItem]() + private var recentlyVisitedMenuItems = [NSMenuItem]() private func addRecentlyVisited() { recentlyVisitedMenuItems = [recentlyVisitedHeaderMenuItem] @@ -108,7 +129,7 @@ final class HistoryMenu: NSMenu { let visits: [Visit] } - var historyGroupingsMenuItems = [NSMenuItem]() + private var historyGroupingsMenuItems = [NSMenuItem]() private func addHistoryGroupings() { let groupings = historyCoordinator.getVisitGroupings() @@ -208,17 +229,15 @@ final class HistoryMenu: NSMenu { action: #selector(AppDelegate.clearThisHistory(_:)), keyEquivalent: "") headerItem.setDateString(dateString) - return [headerItem, - NSMenuItem.separator()] + return [ + headerItem, + .separator() + ] } // MARK: - Clear All History private func addClearAllHistoryOnTheBottom() { - guard let clearAllHistoryMenuItem else { - return - } - if clearAllHistorySeparator.menu != nil { removeItem(clearAllHistorySeparator) } @@ -285,7 +304,7 @@ extension HistoryMenu { private extension NSApplication { var canRestoreLastSessionState: Bool { - (delegate as? AppDelegate)?.stateRestorationManager?.canRestoreLastSessionState ?? false + delegateTyped.stateRestorationManager?.canRestoreLastSessionState ?? false } } diff --git a/DuckDuckGo/Menus/MainMenu.storyboard b/DuckDuckGo/Menus/MainMenu.storyboard deleted file mode 100644 index e98dc123e0..0000000000 --- a/DuckDuckGo/Menus/MainMenu.storyboard +++ /dev/null @@ -1,1344 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -CA - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -GQ - - - - - - - - - - -CQ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 5cc7405f9e..f2aead764d 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -21,6 +21,7 @@ import Cocoa import Common import Combine import OSLog // swiftlint:disable:this enforce_os_log_wrapper +import SwiftUI import WebKit #if NETWORK_PROTECTION @@ -31,114 +32,337 @@ import NetworkProtection import Subscription #endif -@MainActor -final class MainMenu: NSMenu { +// swiftlint:disable:next type_body_length +@MainActor final class MainMenu: NSMenu { enum Constants { static let maxTitleLength = 55 } // MARK: - DuckDuckGo - @IBOutlet weak var checkForUpdatesMenuItem: NSMenuItem? - @IBOutlet weak var checkForUpdatesSeparatorItem: NSMenuItem? - @IBOutlet weak var preferencesMenuItem: NSMenuItem! + let servicesMenu = NSMenu(title: UserText.mainMenuAppServices) + let preferencesMenuItem = NSMenuItem(title: UserText.mainMenuAppPreferences, action: #selector(AppDelegate.openPreferences), keyEquivalent: ",") // MARK: - File - @IBOutlet weak var newWindowMenuItem: NSMenuItem! - @IBOutlet weak var newBurnerWindowMenuItem: NSMenuItem! - @IBOutlet weak var newTabMenuItem: NSMenuItem! - @IBOutlet weak var openLocationMenuItem: NSMenuItem! - @IBOutlet weak var closeWindowMenuItem: NSMenuItem! - @IBOutlet weak var closeAllWindowsMenuItem: NSMenuItem! - @IBOutlet weak var closeTabMenuItem: NSMenuItem! - @IBOutlet weak var printSeparatorItem: NSMenuItem? - @IBOutlet weak var printMenuItem: NSMenuItem? - @IBOutlet weak var shareMenuItem: NSMenuItem! - @IBOutlet weak var importBrowserDataMenuItem: NSMenuItem! - - // MARK: - Edit - @IBOutlet weak var checkSpellingWhileTypingMenuItem: NSMenuItem? - @IBOutlet weak var checkGrammarWithSpellingMenuItem: NSMenuItem? + let newWindowMenuItem = NSMenuItem(title: UserText.newWindowMenuItem, action: #selector(AppDelegate.newWindow), keyEquivalent: "n") + let newTabMenuItem = NSMenuItem(title: UserText.mainMenuFileNewTab, action: #selector(AppDelegate.newTab), keyEquivalent: "t") + let openLocationMenuItem = NSMenuItem(title: UserText.mainMenuFileOpenLocation, action: #selector(AppDelegate.openLocation), keyEquivalent: "l") + let closeWindowMenuItem = NSMenuItem(title: UserText.mainMenuFileCloseWindow, action: #selector(NSWindow.performClose), keyEquivalent: "W") + let closeAllWindowsMenuItem = NSMenuItem(title: UserText.mainMenuFileCloseAllWindows, action: #selector(AppDelegate.closeAllWindows), keyEquivalent: [.option, .command, "W"]) + let closeTabMenuItem = NSMenuItem(title: UserText.closeTab, action: #selector(MainViewController.closeTab), keyEquivalent: "w") + let importBrowserDataMenuItem = NSMenuItem(title: UserText.mainMenuFileImportBookmarksandPasswords, action: #selector(AppDelegate.openImportBrowserDataWindow)) + + let sharingMenu = SharingMenu(title: UserText.shareMenuItem) // MARK: - View - @IBOutlet weak var backMenuItem: NSMenuItem? - @IBOutlet weak var forwardMenuItem: NSMenuItem? - @IBOutlet weak var reloadMenuItem: NSMenuItem? - @IBOutlet weak var stopMenuItem: NSMenuItem? - @IBOutlet weak var homeMenuItem: NSMenuItem? - @IBOutlet weak var toggleFullscreenMenuItem: NSMenuItem? - @IBOutlet weak var zoomInMenuItem: NSMenuItem? - @IBOutlet weak var zoomOutMenuItem: NSMenuItem? - @IBOutlet weak var actualSizeMenuItem: NSMenuItem? + let stopMenuItem = NSMenuItem(title: UserText.mainMenuViewStop, action: #selector(MainViewController.stopLoadingPage), keyEquivalent: ".") + let reloadMenuItem = NSMenuItem(title: UserText.mainMenuViewReloadPage, action: #selector(MainViewController.reloadPage), keyEquivalent: "r") + + let toggleFullscreenMenuItem = NSMenuItem(title: UserText.mainMenuViewEnterFullScreen, action: #selector(NSWindow.toggleFullScreen), keyEquivalent: [.control, .command, "f"]) + let actualSizeMenuItem = NSMenuItem(title: UserText.mainMenuViewActualSize, action: #selector(MainViewController.actualSize), keyEquivalent: "0") + let zoomInMenuItem = NSMenuItem(title: UserText.mainMenuViewZoomIn, action: #selector(MainViewController.zoomIn), keyEquivalent: "+") + let zoomOutMenuItem = NSMenuItem(title: UserText.mainMenuViewZoomOut, action: #selector(MainViewController.zoomOut), keyEquivalent: "-") + + // MARK: - History + let historyMenu = HistoryMenu() + + var backMenuItem: NSMenuItem { historyMenu.backMenuItem } + var forwardMenuItem: NSMenuItem { historyMenu.forwardMenuItem } // MARK: - Bookmarks - @IBOutlet weak var manageBookmarksMenuItem: NSMenuItem! - @IBOutlet weak var bookmarksMenuToggleBookmarksBarMenuItem: NSMenuItem? - @IBOutlet weak var importBookmarksMenuItem: NSMenuItem! - @IBOutlet weak var exportBookmarksMenuItem: NSMenuItem! - @IBOutlet weak var bookmarksMenuItem: NSMenuItem? - @IBOutlet weak var bookmarkThisPageMenuItem: NSMenuItem? - @IBOutlet weak var favoritesMenuItem: NSMenuItem? - @IBOutlet weak var favoriteThisPageMenuItem: NSMenuItem? - - @IBOutlet weak var toggleBookmarksBarMenuItem: NSMenuItem? - @IBOutlet weak var toggleAutofillShortcutMenuItem: NSMenuItem? - @IBOutlet weak var toggleBookmarksShortcutMenuItem: NSMenuItem? - @IBOutlet weak var toggleDownloadsShortcutMenuItem: NSMenuItem? - @IBOutlet weak var toggleNetworkProtectionShortcutMenuItem: NSMenuItem? - @IBOutlet weak var toggleHomeButtonMenuItem: NSMenuItem? + let manageBookmarksMenuItem = NSMenuItem(title: UserText.mainMenuHistoryManageBookmarks, action: #selector(MainViewController.showManageBookmarks)) + var bookmarksMenuToggleBookmarksBarMenuItem = NSMenuItem(title: "BookmarksBarMenuPlaceholder", action: #selector(MainViewController.toggleBookmarksBarFromMenu), keyEquivalent: "B") + let importBookmarksMenuItem = NSMenuItem(title: UserText.importBookmarks, action: #selector(AppDelegate.openImportBrowserDataWindow)) + let bookmarksMenu = NSMenu(title: UserText.bookmarks) + let favoritesMenu = NSMenu(title: UserText.favorites) + + private var toggleBookmarksBarMenuItem = NSMenuItem(title: "BookmarksBarMenuPlaceholder", action: #selector(MainViewController.toggleBookmarksBarFromMenu), keyEquivalent: "B") + let toggleHomeButtonMenuItem = NSMenuItem(title: UserText.mainMenuViewShowHomeShortcut, action: #selector(MainViewController.toggleHomeButton), keyEquivalent: "Y") + let toggleAutofillShortcutMenuItem = NSMenuItem(title: UserText.mainMenuViewShowAutofillShortcut, action: #selector(MainViewController.toggleAutofillShortcut), keyEquivalent: "A") + let toggleBookmarksShortcutMenuItem = NSMenuItem(title: UserText.mainMenuViewShowBookmarksShortcut, action: #selector(MainViewController.toggleBookmarksShortcut), keyEquivalent: "K") + let toggleDownloadsShortcutMenuItem = NSMenuItem(title: UserText.mainMenuViewShowDownloadsShortcut, action: #selector(MainViewController.toggleDownloadsShortcut), keyEquivalent: "J") + +#if NETWORK_PROTECTION + let toggleNetworkProtectionShortcutMenuItem = NSMenuItem(title: UserText.showNetworkProtectionShortcut, action: #selector(MainViewController.toggleNetworkProtectionShortcut), keyEquivalent: "N") +#endif + + // MARK: - Window + let windowsMenu = NSMenu(title: UserText.mainMenuWindow) // MARK: - Debug - @IBOutlet weak var debugMenuItem: NSMenuItem? + private var loggingMenu: NSMenu? - @IBOutlet weak var debugNetworkProtectionWaitlistTokenItem: NSMenuItem? - @IBOutlet weak var debugNetworkProtectionWaitlistTimestampItem: NSMenuItem? - @IBOutlet weak var debugNetworkProtectionWaitlistInviteCodeItem: NSMenuItem? - @IBOutlet weak var debugNetworkProtectionWaitlistTermsAndConditionsAcceptedItem: NSMenuItem? - @IBOutlet weak var debugNetworkProtectionWaitlistEnterInviteCodeItem: NSMenuItem? + // MARK: - Help - private func setupDebugMenuItem(with featureFlagger: FeatureFlagger) { - guard let debugMenuItem else { - assertionFailure("debugMenuItem missing") - return - } + let helpMenu = NSMenu(title: UserText.mainMenuHelp) { + NSMenuItem(title: UserText.mainMenuHelpDuckDuckGoHelp, action: #selector(NSApplication.showHelp), keyEquivalent: "?") + .hidden() -#if !DEBUG && !REVIEW - guard featureFlagger.isFeatureOn(.debugMenu) else { - removeItem(debugMenuItem) - self.debugMenuItem = nil - return - } +#if FEEDBACK + NSMenuItem.separator() + NSMenuItem(title: UserText.sendFeedback, action: #selector(AppDelegate.openFeedback)) #endif + } -#if SUBSCRIPTION - debugMenuItem.submenu!.addItem(SubscriptionDebugMenu(currentViewController: { - WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController - })) + // swiftlint:disable:next function_body_length + init(featureFlagger: FeatureFlagger, bookmarkManager: BookmarkManager, faviconManager: FaviconManagement, copyHandler: CopyHandler) { + + super.init(title: UserText.duckDuckGo) + + buildItems { + // MARK: DuckDuckGo + NSMenuItem(title: UserText.duckDuckGo) { + NSMenuItem(title: UserText.aboutDuckDuckGo, action: #selector(AppDelegate.openAbout)) + NSMenuItem.separator() + + preferencesMenuItem + + NSMenuItem.separator() + + NSMenuItem(title: UserText.mainMenuAppServices) + .submenu(servicesMenu) + NSMenuItem.separator() + +#if SPARKLE + NSMenuItem(title: UserText.mainMenuAppCheckforUpdates, action: #selector(AppDelegate.checkForUpdates)) + NSMenuItem.separator() #endif - if debugMenuItem.submenu?.items.contains(loggingMenuItem) == false { - debugMenuItem.submenu!.addItem(loggingMenuItem) - } - } + NSMenuItem(title: UserText.mainMenuAppHideDuckDuckGo, action: #selector(NSApplication.hide), keyEquivalent: "h") + NSMenuItem(title: UserText.mainMenuAppHideOthers, action: #selector(NSApplication.hideOtherApplications), keyEquivalent: [.option, .command, "h"]) + NSMenuItem(title: UserText.mainMenuAppShowAll, action: #selector(NSApplication.unhideAllApplications)) + NSMenuItem.separator() - // MARK: - Help - @IBOutlet weak var helpMenuItem: NSMenuItem? - @IBOutlet weak var helpSeparatorMenuItem: NSMenuItem? - @IBOutlet weak var sendFeedbackMenuItem: NSMenuItem? + NSMenuItem(title: UserText.mainMenuAppQuitDuckDuckGo, action: #selector(NSApplication.terminate), keyEquivalent: "q") + } + + // MARK: File + NSMenuItem(title: UserText.mainMenuFile) { + newWindowMenuItem + NSMenuItem(title: UserText.newBurnerWindowMenuItem, action: #selector(AppDelegate.newBurnerWindow), keyEquivalent: "N") + newTabMenuItem + openLocationMenuItem + NSMenuItem.separator() + + closeWindowMenuItem + closeAllWindowsMenuItem + closeTabMenuItem + NSMenuItem(title: UserText.mainMenuFileSaveAs, action: #selector(MainViewController.saveAs), keyEquivalent: "s") + NSMenuItem.separator() + + importBrowserDataMenuItem + NSMenuItem(title: UserText.mainMenuFileExport) { + NSMenuItem(title: UserText.mainMenuFileExportPasswords, action: #selector(AppDelegate.openExportLogins)) + NSMenuItem(title: UserText.mainMenuFileExportBookmarks, action: #selector(AppDelegate.openExportBookmarks)) + } + NSMenuItem.separator() + + NSMenuItem(title: UserText.shareMenuItem) + .submenu(sharingMenu) + NSMenuItem.separator() - // MARK: - Setup + NSMenuItem(title: UserText.printMenuItem, action: #selector(MainViewController.printWebView), keyEquivalent: "p") + } + + // MARK: Edit + NSMenuItem(title: UserText.mainMenuEdit) { + NSMenuItem(title: UserText.mainMenuEditUndo, action: Selector(("undo:")), keyEquivalent: "z") + NSMenuItem(title: UserText.mainMenuEditRedo, action: Selector(("redo:")), keyEquivalent: "Z") + NSMenuItem.separator() + + NSMenuItem(title: UserText.mainMenuEditCut, action: #selector(NSText.cut), keyEquivalent: "x") + NSMenuItem(title: UserText.mainMenuEditCopy, action: #selector(CopyHandler.copy(_:)), target: copyHandler, keyEquivalent: "c") + NSMenuItem(title: UserText.mainMenuEditPaste, action: #selector(NSText.paste), keyEquivalent: "v") + NSMenuItem(title: UserText.mainMenuEditPasteAndMatchStyle, action: #selector(NSTextView.pasteAsPlainText), keyEquivalent: [.option, .command, .shift, "v"]) + NSMenuItem(title: UserText.mainMenuEditPasteAndMatchStyle, action: #selector(NSTextView.pasteAsPlainText), keyEquivalent: [.command, .shift, "v"]) + .alternate() + + NSMenuItem(title: UserText.mainMenuEditDelete, action: #selector(NSText.delete)) + NSMenuItem(title: UserText.mainMenuEditSelectAll, action: #selector(NSText.selectAll), keyEquivalent: "a") + NSMenuItem.separator() + + NSMenuItem(title: UserText.mainMenuEditFind) { + NSMenuItem(title: UserText.findInPageMenuItem, action: #selector(MainViewController.findInPage), keyEquivalent: "f") + NSMenuItem(title: UserText.mainMenuEditFindFindNext, action: #selector(MainViewController.findInPageNext), keyEquivalent: "g") + NSMenuItem(title: UserText.mainMenuEditFindFindPrevious, action: #selector(MainViewController.findInPagePrevious), keyEquivalent: "G") + NSMenuItem.separator() + + NSMenuItem(title: UserText.mainMenuEditFindHideFind, action: #selector(MainViewController.findInPageDone), keyEquivalent: "F") + } + + NSMenuItem(title: UserText.mainMenuEditSpellingandGrammar) { + NSMenuItem(title: UserText.mainMenuEditSpellingandShowSpellingandGrammar, action: #selector(NSText.showGuessPanel), keyEquivalent: ":") + NSMenuItem(title: UserText.mainMenuEditSpellingandCheckDocumentNow, action: #selector(NSText.checkSpelling), keyEquivalent: ";") + NSMenuItem.separator() + + NSMenuItem(title: UserText.mainMenuEditSpellingandCheckSpellingWhileTyping, action: #selector(NSTextView.toggleContinuousSpellChecking)) + NSMenuItem(title: UserText.mainMenuEditSpellingandCheckGrammarWithSpelling, action: #selector(NSTextView.toggleGrammarChecking)) + NSMenuItem(title: UserText.mainMenuEditSpellingandCorrectSpellingAutomatically, action: #selector(NSTextView.toggleAutomaticSpellingCorrection)) + .hidden() + } + + NSMenuItem(title: UserText.mainMenuEditSubstitutions) { + NSMenuItem(title: UserText.mainMenuEditSubstitutionsShowSubstitutions, action: #selector(NSTextView.orderFrontSubstitutionsPanel)) + NSMenuItem.separator() + + NSMenuItem(title: UserText.mainMenuEditSubstitutionsSmartCopyPaste, action: #selector(NSTextView.toggleSmartInsertDelete)) + NSMenuItem(title: UserText.mainMenuEditSubstitutionsSmartQuotes, action: #selector(NSTextView.toggleAutomaticQuoteSubstitution)) + NSMenuItem(title: UserText.mainMenuEditSubstitutionsSmartDashes, action: #selector(NSTextView.toggleAutomaticDashSubstitution)) + NSMenuItem(title: UserText.mainMenuEditSubstitutionsSmartLinks, action: #selector(NSTextView.toggleAutomaticLinkDetection)) + NSMenuItem(title: UserText.mainMenuEditSubstitutionsDataDetectors, action: #selector(NSTextView.toggleAutomaticDataDetection)) + NSMenuItem(title: UserText.mainMenuEditSubstitutionsTextReplacement, action: #selector(NSTextView.toggleAutomaticTextReplacement)) + } + + NSMenuItem(title: UserText.mainMenuEditTransformations) { + NSMenuItem(title: UserText.mainMenuEditTransformationsMakeUpperCase, action: #selector(NSResponder.uppercaseWord)) + NSMenuItem(title: UserText.mainMenuEditTransformationsMakeLowerCase, action: #selector(NSResponder.lowercaseWord)) + NSMenuItem(title: UserText.mainMenuEditTransformationsCapitalize, action: #selector(NSResponder.capitalizeWord)) + } + + NSMenuItem(title: UserText.mainMenuEditSpeech) { + NSMenuItem(title: UserText.mainMenuEditSpeechStartSpeaking, action: #selector(NSTextView.startSpeaking)) + NSMenuItem(title: UserText.mainMenuEditSpeechStopSpeaking, action: #selector(NSTextView.stopSpeaking)) + } + } + + // MARK: View + NSMenuItem(title: UserText.mainMenuView) { + stopMenuItem + reloadMenuItem + NSMenuItem.separator() - private func setupHelpMenuItem() { -#if !FEEDBACK - guard let sendFeedbackMenuItem else { return } + NSMenuItem(title: UserText.mainMenuViewHome, action: #selector(MainViewController.home), keyEquivalent: "H") + NSMenuItem.separator() - sendFeedbackMenuItem.isHidden = true + toggleBookmarksBarMenuItem + + NSMenuItem(title: UserText.openDownloads, action: #selector(MainViewController.toggleDownloads), keyEquivalent: "j") + NSMenuItem.separator() + + toggleHomeButtonMenuItem + toggleAutofillShortcutMenuItem + toggleBookmarksShortcutMenuItem + toggleDownloadsShortcutMenuItem + +#if NETWORK_PROTECTION + toggleNetworkProtectionShortcutMenuItem #endif + + NSMenuItem.separator() + + toggleFullscreenMenuItem + NSMenuItem.separator() + + actualSizeMenuItem + zoomInMenuItem + zoomOutMenuItem + NSMenuItem.separator() + + NSMenuItem(title: UserText.mainMenuDeveloper) { + NSMenuItem(title: UserText.openDeveloperTools, action: #selector(MainViewController.toggleDeveloperTools), keyEquivalent: [.option, .command, "i"]) + NSMenuItem(title: UserText.mainMenuViewDeveloperJavaScriptConsole, action: #selector(MainViewController.openJavaScriptConsole), keyEquivalent: [.option, .command, "c"]) + NSMenuItem(title: UserText.mainMenuViewDeveloperShowPageSource, action: #selector(MainViewController.showPageSource), keyEquivalent: [.option, .command, "u"]) + NSMenuItem(title: UserText.mainMenuViewDeveloperShowResources, action: #selector(MainViewController.showPageResources), keyEquivalent: [.option, .command, "a"]) + } + } + + // MARK: History + NSMenuItem(title: UserText.mainMenuHistory) + .submenu(historyMenu) + + // MARK: Bookmarks + NSMenuItem(title: UserText.bookmarks).submenu(bookmarksMenu.buildItems { + NSMenuItem(title: UserText.bookmarkThisPage, action: #selector(MainViewController.bookmarkThisPage), keyEquivalent: "d") + manageBookmarksMenuItem + bookmarksMenuToggleBookmarksBarMenuItem + NSMenuItem.separator() + + importBookmarksMenuItem + NSMenuItem(title: UserText.exportBookmarks, action: #selector(AppDelegate.openExportBookmarks)) + NSMenuItem.separator() + + NSMenuItem(title: UserText.favorites) + .submenu(favoritesMenu.buildItems { + NSMenuItem(title: UserText.mainMenuHistoryFavoriteThisPage, action: #selector(MainViewController.favoriteThisPage)) + .withImage(NSImage(named: "Favorite")) + NSMenuItem.separator() + }) + .withImage(NSImage(named: "Favorite")) + + NSMenuItem.separator() + }) + + // MARK: Window + NSMenuItem(title: UserText.mainMenuWindow) + .submenu(windowsMenu.buildItems { + NSMenuItem(title: UserText.mainMenuWindowMinimize, action: #selector(NSWindow.performMiniaturize), keyEquivalent: "m") + NSMenuItem(title: UserText.zoom, action: #selector(NSWindow.performZoom)) + NSMenuItem.separator() + + NSMenuItem(title: UserText.pinTab, action: #selector(MainViewController.pinOrUnpinTab)) + NSMenuItem(title: UserText.moveTabToNewWindow, action: #selector(MainViewController.moveTabToNewWindow)) + NSMenuItem(title: UserText.mainMenuWindowMergeAllWindows, action: #selector(NSWindow.mergeAllWindows)) + NSMenuItem.separator() + + NSMenuItem(title: UserText.mainMenuWindowShowPreviousTab, action: #selector(MainViewController.showPreviousTab), keyEquivalent: [.control, .shift, .tab]) + NSMenuItem(title: "Show Previous Tab (Hidden)", action: #selector(MainViewController.showPreviousTab), keyEquivalent: [.command, .shift, "["]) + .hidden() + NSMenuItem(title: "Show Previous Tab (Hidden)", action: #selector(MainViewController.showPreviousTab), keyEquivalent: [.option, .command, .left]) + .hidden() + + NSMenuItem(title: UserText.mainMenuWindowShowNextTab, action: #selector(MainViewController.showNextTab), keyEquivalent: [.control, .tab]) + NSMenuItem(title: "Show Next Tab (Hidden)", action: #selector(MainViewController.showNextTab), keyEquivalent: [.command, .shift, "]"]) + .hidden() + NSMenuItem(title: "Show Next Tab (Hidden)", action: #selector(MainViewController.showNextTab), keyEquivalent: [.option, .command, .right]) + .hidden() + + NSMenuItem(title: "Show First Tab (Hidden)", action: #selector(MainViewController.showTab), keyEquivalent: "1") + .hidden() + NSMenuItem(title: "Show Second Tab (Hidden)", action: #selector(MainViewController.showTab), keyEquivalent: "2") + .hidden() + NSMenuItem(title: "Show Third Tab (Hidden)", action: #selector(MainViewController.showTab), keyEquivalent: "3") + .hidden() + NSMenuItem(title: "Show Fourth Tab (Hidden)", action: #selector(MainViewController.showTab), keyEquivalent: "4") + .hidden() + NSMenuItem(title: "Show Fifth Tab (Hidden)", action: #selector(MainViewController.showTab), keyEquivalent: "5") + .hidden() + NSMenuItem(title: "Show Sixth Tab (Hidden)", action: #selector(MainViewController.showTab), keyEquivalent: "6") + .hidden() + NSMenuItem(title: "Show Seventh Tab (Hidden)", action: #selector(MainViewController.showTab), keyEquivalent: "7") + .hidden() + NSMenuItem(title: "Show Eighth Tab (Hidden)", action: #selector(MainViewController.showTab), keyEquivalent: "8") + .hidden() + NSMenuItem(title: "Show Ninth Tab (Hidden)", action: #selector(MainViewController.showTab), keyEquivalent: "9") + .hidden() + NSMenuItem.separator() + + NSMenuItem(title: UserText.mainMenuWindowBringAllToFront, action: #selector(NSApplication.arrangeInFront)) + }) + + // MARK: Debug +#if DEBUG || REVIEW + NSMenuItem(title: "Debug") + .submenu(setupDebugMenu()) +#else + if featureFlagger.isFeatureOn(.debugMenu) { + NSMenuItem(title: "Debug") + .submenu(setupDebugMenu()) + } +#endif + + // MARK: Help + NSMenuItem(title: UserText.mainMenuHelp) + .submenu(helpMenu) + } + + subscribeToBookmarkList(bookmarkManager: bookmarkManager) + subscribeToFavicons(faviconManager: faviconManager) } - let sharingMenu = SharingMenu(title: UserText.shareMenuItem) + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } // MARK: - Lifecycle @@ -146,75 +370,57 @@ final class MainMenu: NSMenu { override func update() { super.update() - // Make sure Spotlight search is part of Help menu - if NSApplication.shared.helpMenu != helpMenuItem?.submenu { - NSApplication.shared.helpMenu = helpMenuItem?.submenu - } - +#if NETWORK_PROTECTION // To be safe, hide the NetP shortcut menu item by default. - toggleNetworkProtectionShortcutMenuItem?.isHidden = true + toggleNetworkProtectionShortcutMenuItem.isHidden = true +#endif updateBookmarksBarMenuItem() updateShortcutMenuItems() updateLoggingMenuItems() - -#if NETWORK_PROTECTION - updateNetworkProtectionItems() -#endif - } - - @MainActor - func setup(with featureFlagger: FeatureFlagger) { -#if !SPARKLE - checkForUpdatesMenuItem?.removeFromParent() - checkForUpdatesSeparatorItem?.removeFromParent() -#endif - - shareMenuItem.submenu = sharingMenu - setupHelpMenuItem() - setupDebugMenuItem(with: featureFlagger) - subscribeToBookmarkList() - subscribeToFavicons() } // MARK: - Bookmarks var faviconsCancellable: AnyCancellable? - private func subscribeToFavicons() { - faviconsCancellable = FaviconManager.shared.$faviconsLoaded - .receive(on: DispatchQueue.main).sink(receiveValue: { [weak self] loaded in - if loaded { - self?.updateFavicons(self?.bookmarksMenuItem) - self?.updateFavicons(self?.favoritesMenuItem) - } - }) + private func subscribeToFavicons(faviconManager: FaviconManagement) { + faviconsCancellable = faviconManager.faviconsLoadedPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] loaded in + guard let self, loaded else { return } + + self.updateFavicons(in: bookmarksMenu) + self.updateFavicons(in: favoritesMenu) + } } - private func updateFavicons(_ menuItem: NSMenuItem?) { - if let bookmark = menuItem?.representedObject as? Bookmark { - menuItem?.image = BookmarkViewModel(entity: bookmark).menuFavicon - } - menuItem?.submenu?.items.forEach { menuItem in - updateFavicons(menuItem) + private func updateFavicons(in menu: NSMenu) { + for menuItem in menu.items { + if let bookmark = menuItem.representedObject as? Bookmark { + menuItem.image = BookmarkViewModel(entity: bookmark).menuFavicon + } + if let submenu = menuItem.submenu { + updateFavicons(in: submenu) + } } } var bookmarkListCancellable: AnyCancellable? - private func subscribeToBookmarkList() { - bookmarkListCancellable = LocalBookmarkManager.shared.$list - .compactMap({ + private func subscribeToBookmarkList(bookmarkManager: BookmarkManager) { + bookmarkListCancellable = bookmarkManager.listPublisher + .compactMap { let favorites = $0?.favoriteBookmarks.compactMap(BookmarkViewModel.init(entity:)) ?? [] let topLevelEntities = $0?.topLevelEntities.compactMap(BookmarkViewModel.init(entity:)) ?? [] return (favorites, topLevelEntities) - }) - .receive(on: DispatchQueue.main).sink { [weak self] favorites, topLevel in + } + .receive(on: DispatchQueue.main) + .sink { [weak self] favorites, topLevel in self?.updateBookmarksMenu(favoriteViewModels: favorites, topLevelBookmarkViewModels: topLevel) } } // Nested recursing functions cause body length - // swiftlint:disable function_body_length func updateBookmarksMenu(favoriteViewModels: [BookmarkViewModel], topLevelBookmarkViewModels: [BookmarkViewModel]) { func bookmarkMenuItems(from bookmarkViewModels: [BookmarkViewModel], topLevel: Bool = true) -> [NSMenuItem] { @@ -262,12 +468,9 @@ final class MainMenu: NSMenu { } } - guard let bookmarksMenu = bookmarksMenuItem?.submenu, - let favoritesSeparatorIndex = bookmarksMenu.items.lastIndex(where: { $0.isSeparatorItem }), - let favoritesMenuItem = favoritesMenuItem, - let favoritesMenu = favoritesMenuItem.submenu, + guard let favoritesSeparatorIndex = bookmarksMenu.items.lastIndex(where: { $0.isSeparatorItem }), let favoriteThisPageSeparatorIndex = favoritesMenu.items.lastIndex(where: { $0.isSeparatorItem }) else { - os_log("MainMenuManager: Failed to reference bookmarks menu items", type: .error) + assertionFailure("MainMenuManager: Failed to reference bookmarks menu items") return } @@ -279,14 +482,18 @@ final class MainMenu: NSMenu { let favoriteItems = favoriteMenuItems(from: favoriteViewModels) favoritesMenu.items = Array(cleanedFavoriteItems) + favoriteItems } - // swiftlint:enable function_body_length private func updateBookmarksBarMenuItem() { - toggleBookmarksBarMenuItem = BookmarksBarMenuFactory.replace(toggleBookmarksBarMenuItem) - toggleBookmarksBarMenuItem?.target = self - toggleBookmarksBarMenuItem?.action = #selector(toggleBookmarksBarFromMenu(_:)) + guard let toggleBookmarksBarMenuItem = BookmarksBarMenuFactory.replace(toggleBookmarksBarMenuItem), + let bookmarksMenuToggleBookmarksBarMenuItem = BookmarksBarMenuFactory.replace(bookmarksMenuToggleBookmarksBarMenuItem) else { + assertionFailure("Could not replace toggleBookmarksBarMenuItem") + return + } + self.toggleBookmarksBarMenuItem = toggleBookmarksBarMenuItem + toggleBookmarksBarMenuItem.target = self + toggleBookmarksBarMenuItem.action = #selector(toggleBookmarksBarFromMenu(_:)) - bookmarksMenuToggleBookmarksBarMenuItem = BookmarksBarMenuFactory.replace(bookmarksMenuToggleBookmarksBarMenuItem) + self.bookmarksMenuToggleBookmarksBarMenuItem = bookmarksMenuToggleBookmarksBarMenuItem } @MainActor @@ -297,32 +504,73 @@ final class MainMenu: NSMenu { } private func updateShortcutMenuItems() { - toggleAutofillShortcutMenuItem?.title = LocalPinningManager.shared.toggleShortcutInterfaceTitle(for: .autofill) - toggleBookmarksShortcutMenuItem?.title = LocalPinningManager.shared.toggleShortcutInterfaceTitle(for: .bookmarks) - toggleDownloadsShortcutMenuItem?.title = LocalPinningManager.shared.toggleShortcutInterfaceTitle(for: .downloads) - toggleHomeButtonMenuItem?.title = LocalPinningManager.shared.toggleShortcutInterfaceTitle(for: .homeButton) + toggleAutofillShortcutMenuItem.title = LocalPinningManager.shared.toggleShortcutInterfaceTitle(for: .autofill) + toggleBookmarksShortcutMenuItem.title = LocalPinningManager.shared.toggleShortcutInterfaceTitle(for: .bookmarks) + toggleDownloadsShortcutMenuItem.title = LocalPinningManager.shared.toggleShortcutInterfaceTitle(for: .downloads) + toggleHomeButtonMenuItem.title = LocalPinningManager.shared.toggleShortcutInterfaceTitle(for: .homeButton) #if NETWORK_PROTECTION if NetworkProtectionKeychainTokenStore().isFeatureActivated { - toggleNetworkProtectionShortcutMenuItem?.isHidden = false - toggleNetworkProtectionShortcutMenuItem?.title = LocalPinningManager.shared.toggleShortcutInterfaceTitle(for: .networkProtection) + toggleNetworkProtectionShortcutMenuItem.isHidden = false + toggleNetworkProtectionShortcutMenuItem.title = LocalPinningManager.shared.toggleShortcutInterfaceTitle(for: .networkProtection) } else { - toggleNetworkProtectionShortcutMenuItem?.isHidden = true + toggleNetworkProtectionShortcutMenuItem.isHidden = true } -#else - toggleNetworkProtectionShortcutMenuItem?.isHidden = true #endif } - // MARK: - Logging + // MARK: - Debug + + private func setupDebugMenu() -> NSMenu { + let debugMenu = NSMenu(title: "Debug") { + NSMenuItem(title: "Reset Data") { + NSMenuItem(title: "Reset Default Browser Prompt", action: #selector(MainViewController.resetDefaultBrowserPrompt)) + NSMenuItem(title: "Reset Default Grammar Checks", action: #selector(MainViewController.resetDefaultGrammarChecks)) + NSMenuItem(title: "Reset Autofill Data", action: #selector(MainViewController.resetSecureVaultData)) + NSMenuItem(title: "Reset Bookmarks", action: #selector(MainViewController.resetBookmarks)) + NSMenuItem(title: "Reset Pinned Tabs", action: #selector(MainViewController.resetPinnedTabs)) + NSMenuItem(title: "Reset YouTube Overlay Interactions", action: #selector(MainViewController.resetDuckPlayerOverlayInteractions)) + NSMenuItem(title: "Reset MakeDuckDuckYours user settings", action: #selector(MainViewController.resetMakeDuckDuckGoYoursUserSettings)) + NSMenuItem(title: "Change Activation Date") { + NSMenuItem(title: "Today", action: #selector(MainViewController.changeInstallDateToToday), keyEquivalent: "N") + NSMenuItem(title: "Less Than a 21 days Ago", action: #selector(MainViewController.changeInstallDateToLessThan21DaysAgo)) + NSMenuItem(title: "More Than 21 Days Ago", action: #selector(MainViewController.changeInstallDateToMoreThan21DaysAgoButLessThan27)) + NSMenuItem(title: "More Than 27 Days Ago", action: #selector(MainViewController.changeInstallDateToMoreThan27DaysAgo)) + } + NSMenuItem(title: "Reset Email Protection InContext Signup Prompt", action: #selector(MainViewController.resetEmailProtectionInContextPrompt)) + NSMenuItem(title: "Reset Daily Pixels", action: #selector(MainViewController.resetDailyPixels)) + } + NSMenuItem(title: "UI Triggers") { + NSMenuItem(title: "Show Save Credentials Popover", action: #selector(MainViewController.showSaveCredentialsPopover)) + NSMenuItem(title: "Show Credentials Saved Popover", action: #selector(MainViewController.showCredentialsSavedPopover)) + NSMenuItem(title: "Show Pop Up Window", action: #selector(MainViewController.showPopUpWindow)) + } + NSMenuItem(title: "Remote Configuration") { + NSMenuItem(title: "Fetch Configuration Now", action: #selector(MainViewController.fetchConfigurationNow)) + } + NSMenuItem(title: "Sync") + .submenu(SyncDebugMenu()) + +#if NETWORK_PROTECTION + NSMenuItem(title: "Network Protection") + .submenu(NetworkProtectionDebugMenu()) +#endif + + NSMenuItem(title: "Trigger Fatal Error", action: #selector(MainViewController.triggerFatalError)) + +#if SUBSCRIPTION + SubscriptionDebugMenu(currentViewController: { + WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController + }) +#endif - private lazy var loggingMenuItem: NSMenuItem = { - let menuItem = NSMenuItem(title: "Logging") - menuItem.submenu = loggingMenu - return menuItem - }() + NSMenuItem(title: "Logging").submenu(setupLoggingMenu()) + } + debugMenu.autoenablesItems = false + return debugMenu + } - private lazy var loggingMenu: NSMenu = { + private func setupLoggingMenu() -> NSMenu { let menu = NSMenu(title: "") menu.addItem(NSMenuItem(title: "Enable All", action: #selector(enableAllLogsMenuItemAction), target: self)) @@ -344,11 +592,12 @@ final class MainMenu: NSMenu { menu.addItem(exportLogsMenuItem) } + self.loggingMenu = menu return menu - }() + } private func updateLoggingMenuItems() { - guard debugMenuItem != nil else { return } + guard let loggingMenu else { return } let enabledCategories = OSLog.loggingCategories for item in loggingMenu.items { @@ -358,23 +607,6 @@ final class MainMenu: NSMenu { } } -#if NETWORK_PROTECTION - private func updateNetworkProtectionItems() { - let waitlistStorage = WaitlistKeychainStore(waitlistIdentifier: NetworkProtectionWaitlist.identifier) - debugNetworkProtectionWaitlistTokenItem?.title = "Waitlist Token: \(waitlistStorage.getWaitlistToken() ?? "N/A")" - debugNetworkProtectionWaitlistInviteCodeItem?.title = "Waitlist Invite Code: \(waitlistStorage.getWaitlistInviteCode() ?? "N/A")" - - if let timestamp = waitlistStorage.getWaitlistTimestamp() { - debugNetworkProtectionWaitlistTimestampItem?.title = "Waitlist Timestamp: \(String(describing: timestamp))" - } else { - debugNetworkProtectionWaitlistTimestampItem?.title = "Waitlist Timestamp: N/A" - } - - let accepted = UserDefaults().bool(forKey: UserDefaultsWrapper.Key.networkProtectionTermsAndConditionsAccepted.rawValue) - debugNetworkProtectionWaitlistTermsAndConditionsAcceptedItem?.title = "T&C Accepted: \(accepted ? "Yes" : "No")" - } -#endif - @objc private func loggingMenuItemAction(_ sender: NSMenuItem) { guard let category = sender.identifier?.rawValue else { return } @@ -457,3 +689,9 @@ final class MainMenu: NSMenu { } } } + +#if DEBUG +#Preview { + return MenuPreview(menu: NSApp.mainMenu!) +} +#endif diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index cec12c4a8f..2cd87a9257 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -21,10 +21,6 @@ import Cocoa import Common import WebKit -#if NETWORK_PROTECTION -import NetworkProtection -#endif - // Actions are sent to objects of responder chain // MARK: - Main Menu Actions @@ -33,7 +29,7 @@ extension AppDelegate { // MARK: - DuckDuckGo - @IBAction func checkForUpdates(_ sender: Any?) { + @objc func checkForUpdates(_ sender: Any?) { #if SPARKLE updateController.checkForUpdates(sender) #endif @@ -41,33 +37,33 @@ extension AppDelegate { // MARK: - File - @IBAction func newWindow(_ sender: Any?) { + @objc func newWindow(_ sender: Any?) { WindowsManager.openNewWindow() } - @IBAction func newBurnerWindow(_ sender: Any?) { + @objc func newBurnerWindow(_ sender: Any?) { WindowsManager.openNewWindow(burnerMode: BurnerMode(isBurner: true)) } - @IBAction func newTab(_ sender: Any?) { + @objc func newTab(_ sender: Any?) { WindowsManager.openNewWindow() } - @IBAction func openLocation(_ sender: Any?) { + @objc func openLocation(_ sender: Any?) { WindowsManager.openNewWindow() } - @IBAction func closeAllWindows(_ sender: Any?) { + @objc func closeAllWindows(_ sender: Any?) { WindowsManager.closeWindows() } // MARK: - History - @IBAction func reopenLastClosedTab(_ sender: Any?) { + @objc func reopenLastClosedTab(_ sender: Any?) { RecentlyClosedCoordinator.shared.reopenItem() } - @IBAction func recentlyClosedAction(_ sender: Any?) { + @objc func recentlyClosedAction(_ sender: Any?) { guard let menuItem = sender as? NSMenuItem, let cacheItem = menuItem.representedObject as? RecentlyClosedCacheItem else { assertionFailure("Wrong represented object for recentlyClosedAction()") @@ -87,7 +83,7 @@ extension AppDelegate { WindowsManager.openNewWindow(with: Tab(content: .contentFromURL(url), shouldLoadInBackground: true)) } - @IBAction func clearAllHistory(_ sender: NSMenuItem) { + @objc func clearAllHistory(_ sender: NSMenuItem) { guard let window = WindowsManager.openNewWindow(with: Tab(content: .homePage)), let windowController = window.windowController as? MainWindowController else { assertionFailure("No reference to main window controller") @@ -109,7 +105,7 @@ extension AppDelegate { // MARK: - Window - @IBAction func reopenAllWindowsFromLastSession(_ sender: Any?) { + @objc func reopenAllWindowsFromLastSession(_ sender: Any?) { stateRestorationManager.restoreLastSessionState(interactive: true) } @@ -117,13 +113,13 @@ extension AppDelegate { #if FEEDBACK - @IBAction func openFeedback(_ sender: Any?) { + @objc func openFeedback(_ sender: Any?) { FeedbackPresenter.presentFeedbackForm() } #endif - @IBAction func navigateToBookmark(_ sender: Any?) { + @objc func navigateToBookmark(_ sender: Any?) { guard let menuItem = sender as? NSMenuItem else { os_log("AppDelegate: Casting to menu item failed", type: .error) return @@ -139,20 +135,20 @@ extension AppDelegate { WindowsManager.openNewWindow(with: tab) } - @IBAction func showManageBookmarks(_ sender: Any?) { + @objc func showManageBookmarks(_ sender: Any?) { let tabCollection = TabCollection(tabs: [Tab(content: .bookmarks)]) let tabCollectionViewModel = TabCollectionViewModel(tabCollection: tabCollection) WindowsManager.openNewWindow(with: tabCollectionViewModel) } - @IBAction func openPreferences(_ sender: Any?) { + @objc func openPreferences(_ sender: Any?) { let tabCollection = TabCollection(tabs: [Tab(content: .anyPreferencePane)]) let tabCollectionViewModel = TabCollectionViewModel(tabCollection: tabCollection) WindowsManager.openNewWindow(with: tabCollectionViewModel) } - @IBAction func openAbout(_ sender: Any?) { + @objc func openAbout(_ sender: Any?) { #if APPSTORE let options = [NSApplication.AboutPanelOptionKey.applicationName: UserText.duckDuckGoForMacAppStore] #else @@ -161,11 +157,11 @@ extension AppDelegate { NSApp.orderFrontStandardAboutPanel(options: options) } - @IBAction func openImportBrowserDataWindow(_ sender: Any?) { + @objc func openImportBrowserDataWindow(_ sender: Any?) { DataImportViewController.show() } - @IBAction func openExportLogins(_ sender: Any?) { + @objc func openExportLogins(_ sender: Any?) { guard let windowController = WindowControllersManager.shared.lastKeyMainWindowController, let window = windowController.window else { return } @@ -203,7 +199,7 @@ extension AppDelegate { } } - @IBAction func openExportBookmarks(_ sender: Any?) { + @objc func openExportBookmarks(_ sender: Any?) { guard let windowController = WindowControllersManager.shared.lastKeyMainWindowController, let window = windowController.window, let list = LocalBookmarkManager.shared.list else { return } @@ -225,11 +221,11 @@ extension AppDelegate { } } - @IBAction func fireButtonAction(_ sender: NSButton) { + @objc func fireButtonAction(_ sender: NSButton) { FireCoordinator.fireButtonAction() } - @IBAction func navigateToPrivateEmail(_ sender: Any?) { + @objc func navigateToPrivateEmail(_ sender: Any?) { guard let window = NSApplication.shared.keyWindow, let windowController = window.windowController as? MainWindowController else { assertionFailure("No reference to main window controller") @@ -244,7 +240,7 @@ extension MainViewController { /// Finds currently active Tab even if it‘s playing a Full Screen video private func getActiveTabAndIndex() -> (tab: Tab, index: TabIndex)? { - guard let tab = NSApp.keyWindow?.windowController?.activeTab else { + guard let tab = WindowControllersManager.shared.lastKeyMainWindowController?.activeTab else { assertionFailure("Could not get currently active Tab") return nil } @@ -267,19 +263,19 @@ extension MainViewController { // MARK: - Main Menu - @IBAction func openPreferences(_ sender: Any?) { + @objc func openPreferences(_ sender: Any?) { makeKeyIfNeeded() browserTabViewController.openNewTab(with: .anyPreferencePane) } // MARK: - File - @IBAction func newTab(_ sender: Any?) { + @objc func newTab(_ sender: Any?) { makeKeyIfNeeded() browserTabViewController.openNewTab(with: .homePage) } - @IBAction func openLocation(_ sender: Any?) { + @objc func openLocation(_ sender: Any?) { makeKeyIfNeeded() guard let addressBarTextField = navigationBarViewController?.addressBarViewController?.addressBarTextField else { os_log("MainViewController: Cannot reference address bar text field", type: .error) @@ -288,7 +284,7 @@ extension MainViewController { addressBarTextField.makeMeFirstResponder() } - @IBAction func closeTab(_ sender: Any?) { + @objc func closeTab(_ sender: Any?) { guard let (tab, index) = getActiveTabAndIndex() else { return } makeKeyIfNeeded() @@ -318,28 +314,28 @@ extension MainViewController { // MARK: - View - @IBAction func reloadPage(_ sender: Any) { + @objc func reloadPage(_ sender: Any) { makeKeyIfNeeded() activeTabViewModel?.reload() } - @IBAction func stopLoadingPage(_ sender: Any) { + @objc func stopLoadingPage(_ sender: Any) { getActiveTabAndIndex()?.tab.stopLoading() } - @IBAction func zoomIn(_ sender: Any) { + @objc func zoomIn(_ sender: Any) { getActiveTabAndIndex()?.tab.webView.zoomIn() } - @IBAction func zoomOut(_ sender: Any) { + @objc func zoomOut(_ sender: Any) { getActiveTabAndIndex()?.tab.webView.zoomOut() } - @IBAction func actualSize(_ sender: Any) { + @objc func actualSize(_ sender: Any) { getActiveTabAndIndex()?.tab.webView.resetZoomLevel() } - @IBAction func toggleDownloads(_ sender: Any) { + @objc func toggleDownloads(_ sender: Any) { var navigationBarViewController = self.navigationBarViewController if view.window?.isPopUpWindow == true { if let vc = WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.navigationBarViewController { @@ -356,7 +352,7 @@ extension MainViewController { navigationBarViewController?.toggleDownloadsPopover(keepButtonVisible: false) } - @IBAction func toggleBookmarksBarFromMenu(_ sender: Any) { + @objc func toggleBookmarksBarFromMenu(_ sender: Any) { // Leaving this keyboard shortcut in place. When toggled on it will use the previously set appearence which defaults to "always". // If the user sets it to "new tabs only" somewhere (e.g. preferences), then it'll be that. guard let mainVC = WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController else { return } @@ -370,23 +366,23 @@ extension MainViewController { } } - @IBAction func toggleAutofillShortcut(_ sender: Any) { + @objc func toggleAutofillShortcut(_ sender: Any) { LocalPinningManager.shared.togglePinning(for: .autofill) } - @IBAction func toggleBookmarksShortcut(_ sender: Any) { + @objc func toggleBookmarksShortcut(_ sender: Any) { LocalPinningManager.shared.togglePinning(for: .bookmarks) } - @IBAction func toggleDownloadsShortcut(_ sender: Any) { + @objc func toggleDownloadsShortcut(_ sender: Any) { LocalPinningManager.shared.togglePinning(for: .downloads) } - @IBAction func toggleNetworkProtectionShortcut(_ sender: Any) { + @objc func toggleNetworkProtectionShortcut(_ sender: Any) { LocalPinningManager.shared.togglePinning(for: .networkProtection) } - @IBAction func toggleHomeButton(_ sender: Any) { + @objc func toggleHomeButton(_ sender: Any) { LocalPinningManager.shared.togglePinning(for: .homeButton) if LocalPinningManager.shared.isPinned(.homeButton) { Pixel.fire(.enableHomeButton) @@ -397,17 +393,17 @@ extension MainViewController { // MARK: - History - @IBAction func back(_ sender: Any?) { + @objc func back(_ sender: Any?) { makeKeyIfNeeded() getActiveTabAndIndex()?.tab.goBack() } - @IBAction func forward(_ sender: Any?) { + @objc func forward(_ sender: Any?) { makeKeyIfNeeded() getActiveTabAndIndex()?.tab.goForward() } - @IBAction func home(_ sender: Any?) { + @objc func home(_ sender: Any?) { guard view.window?.isPopUpWindow == false, let (tab, _) = getActiveTabAndIndex(), tab === tabCollectionViewModel.selectedTab else { @@ -430,7 +426,7 @@ extension MainViewController { adjustFirstResponder() } - @IBAction func clearAllHistory(_ sender: NSMenuItem) { + @objc func clearAllHistory(_ sender: NSMenuItem) { guard let window = view.window else { assertionFailure("No window") return @@ -464,7 +460,7 @@ extension MainViewController { // MARK: - Bookmarks - @IBAction func bookmarkThisPage(_ sender: Any) { + @objc func bookmarkThisPage(_ sender: Any) { guard let tabIndex = getActiveTabAndIndex()?.index else { return } if tabCollectionViewModel.selectedTabIndex != tabIndex { tabCollectionViewModel.select(at: tabIndex) @@ -477,7 +473,7 @@ extension MainViewController { .openBookmarkPopover(setFavorite: false, accessPoint: .init(sender: sender, default: .moreMenu)) } - @IBAction func favoriteThisPage(_ sender: Any) { + @objc func favoriteThisPage(_ sender: Any) { guard let tabIndex = getActiveTabAndIndex()?.index else { return } if tabCollectionViewModel.selectedTabIndex != tabIndex { tabCollectionViewModel.select(at: tabIndex) @@ -490,7 +486,7 @@ extension MainViewController { .openBookmarkPopover(setFavorite: true, accessPoint: .init(sender: sender, default: .moreMenu)) } - @IBAction func openBookmark(_ sender: Any?) { + @objc func openBookmark(_ sender: Any?) { guard let menuItem = sender as? NSMenuItem else { os_log("MainViewController: Casting to menu item failed", type: .error) return @@ -502,7 +498,7 @@ extension MainViewController { WindowControllersManager.shared.open(bookmark: bookmark) } - @IBAction func openAllInTabs(_ sender: Any?) { + @objc func openAllInTabs(_ sender: Any?) { guard let menuItem = sender as? NSMenuItem else { os_log("MainViewController: Casting to menu item failed", type: .error) return @@ -520,14 +516,14 @@ extension MainViewController { tabCollectionViewModel.append(tabs: tabs) } - @IBAction func showManageBookmarks(_ sender: Any?) { + @objc func showManageBookmarks(_ sender: Any?) { makeKeyIfNeeded() browserTabViewController.openNewTab(with: .bookmarks) } // MARK: - Window - @IBAction func showPreviousTab(_ sender: Any?) { + @objc func showPreviousTab(_ sender: Any?) { makeKeyIfNeeded() guard let (tab, index) = getActiveTabAndIndex() else { return } if tabCollectionViewModel.selectedTab !== tab { @@ -536,7 +532,7 @@ extension MainViewController { tabCollectionViewModel.selectPrevious() } - @IBAction func showNextTab(_ sender: Any?) { + @objc func showNextTab(_ sender: Any?) { guard let (tab, index) = getActiveTabAndIndex() else { return } makeKeyIfNeeded() @@ -546,7 +542,7 @@ extension MainViewController { tabCollectionViewModel.selectNext() } - @IBAction func showTab(_ sender: Any?) { + @objc func showTab(_ sender: Any?) { makeKeyIfNeeded() guard let sender = sender as? NSMenuItem else { os_log("MainViewController: Casting to NSMenuItem failed", type: .error) @@ -564,14 +560,14 @@ extension MainViewController { } } - @IBAction func moveTabToNewWindow(_ sender: Any?) { + @objc func moveTabToNewWindow(_ sender: Any?) { guard let (tab, index) = getActiveTabAndIndex() else { return } tabCollectionViewModel.remove(at: index) WindowsManager.openNewWindow(with: tab) } - @IBAction func pinOrUnpinTab(_ sender: Any?) { + @objc func pinOrUnpinTab(_ sender: Any?) { guard let (_, selectedTabIndex) = getActiveTabAndIndex() else { return } switch selectedTabIndex { @@ -582,7 +578,7 @@ extension MainViewController { } } - @IBAction func mergeAllWindows(_ sender: Any?) { + @objc func mergeAllWindows(_ sender: Any?) { guard let mainWindowController = WindowControllersManager.shared.lastKeyMainWindowController else { return } assert(!self.isBurner) @@ -606,37 +602,32 @@ extension MainViewController { // MARK: - Printing - @IBAction func printWebView(_ sender: Any?) { + @objc func printWebView(_ sender: Any?) { getActiveTabAndIndex()?.tab.print() } // MARK: - Saving - @IBAction func saveAs(_ sender: Any) { + @objc func saveAs(_ sender: Any) { getActiveTabAndIndex()?.tab.saveWebContentAs() } // MARK: - Debug - @IBAction func resetDefaultBrowserPrompt(_ sender: Any?) { + @objc func resetDefaultBrowserPrompt(_ sender: Any?) { UserDefaultsWrapper.clear(.defaultBrowserDismissed) } - @IBAction func resetDefaultGrammarChecks(_ sender: Any?) { + @objc func resetDefaultGrammarChecks(_ sender: Any?) { UserDefaultsWrapper.clear(.spellingCheckEnabledOnce) UserDefaultsWrapper.clear(.grammarCheckEnabledOnce) } - @IBAction func openAppContainerInFinder(_ sender: Any?) { - let containerURL = URL.sandboxApplicationSupportURL - NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: containerURL.path) - } - - @IBAction func triggerFatalError(_ sender: Any?) { + @objc func triggerFatalError(_ sender: Any?) { fatalError("Fatal error triggered from the Debug menu") } - @IBAction func resetSecureVaultData(_ sender: Any?) { + @objc func resetSecureVaultData(_ sender: Any?) { let vault = try? AutofillSecureVaultFactory.makeVault(errorReporter: SecureVaultErrorReporter.shared) let accounts = (try? vault?.accounts()) ?? [] @@ -663,24 +654,24 @@ extension MainViewController { UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.homePageContinueSetUpImport.rawValue) } - @IBAction func resetBookmarks(_ sender: Any?) { + @objc func resetBookmarks(_ sender: Any?) { LocalBookmarkManager.shared.resetBookmarks() UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.homePageContinueSetUpImport.rawValue) } - @IBAction func resetPinnedTabs(_ sender: Any?) { + @objc func resetPinnedTabs(_ sender: Any?) { if tabCollectionViewModel.selectedTabIndex?.isPinnedTab == true, tabCollectionViewModel.tabCollection.tabs.count > 0 { tabCollectionViewModel.select(at: .unpinned(0)) } tabCollectionViewModel.pinnedTabsManager?.tabCollection.removeAll() } - @IBAction func resetDuckPlayerOverlayInteractions(_ sender: Any?) { + @objc func resetDuckPlayerOverlayInteractions(_ sender: Any?) { DuckPlayerPreferences.shared.youtubeOverlayAnyButtonPressed = false DuckPlayerPreferences.shared.youtubeOverlayInteracted = false } - @IBAction func resetMakeDuckDuckGoYoursUserSettings(_ sender: Any?) { + @objc func resetMakeDuckDuckGoYoursUserSettings(_ sender: Any?) { UserDefaults.standard.set(true, forKey: UserDefaultsWrapper.Key.homePageShowAllFeatures.rawValue) UserDefaults.standard.set(true, forKey: UserDefaultsWrapper.Key.homePageShowMakeDefault.rawValue) UserDefaults.standard.set(true, forKey: UserDefaultsWrapper.Key.homePageShowImport.rawValue) @@ -692,42 +683,42 @@ extension MainViewController { UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.homePageUserInteractedWithSurveyDay0.rawValue) } - @IBAction func resetDailyPixels(_ sender: Any?) { + @objc func resetDailyPixels(_ sender: Any?) { UserDefaults.standard.removePersistentDomain(forName: DailyPixel.Constant.dailyPixelStorageIdentifier) } - @IBAction func changeInstallDateToToday(_ sender: Any?) { + @objc func changeInstallDateToToday(_ sender: Any?) { UserDefaults.standard.set(Date(), forKey: UserDefaultsWrapper.Key.firstLaunchDate.rawValue) } - @IBAction func changeInstallDateToLessThan21DaysAgo(_ sender: Any?) { + @objc func changeInstallDateToLessThan21DaysAgo(_ sender: Any?) { let lessThanTwentyOneDaysAgo = Calendar.current.date(byAdding: .day, value: -20, to: Date()) UserDefaults.standard.set(lessThanTwentyOneDaysAgo, forKey: UserDefaultsWrapper.Key.firstLaunchDate.rawValue) } - @IBAction func changeInstallDateToMoreThan21DaysAgoButLessThan27(_ sender: Any?) { + @objc func changeInstallDateToMoreThan21DaysAgoButLessThan27(_ sender: Any?) { let twentyOneDaysAgo = Calendar.current.date(byAdding: .day, value: -21, to: Date()) UserDefaults.standard.set(twentyOneDaysAgo, forKey: UserDefaultsWrapper.Key.firstLaunchDate.rawValue) } - @IBAction func changeInstallDateToMoreThan27DaysAgo(_ sender: Any?) { + @objc func changeInstallDateToMoreThan27DaysAgo(_ sender: Any?) { let twentyEightDaysAgo = Calendar.current.date(byAdding: .day, value: -28, to: Date()) UserDefaults.standard.set(twentyEightDaysAgo, forKey: UserDefaultsWrapper.Key.firstLaunchDate.rawValue) } - @IBAction func showSaveCredentialsPopover(_ sender: Any?) { + @objc func showSaveCredentialsPopover(_ sender: Any?) { #if DEBUG || REVIEW NotificationCenter.default.post(name: .ShowSaveCredentialsPopover, object: nil) #endif } - @IBAction func showCredentialsSavedPopover(_ sender: Any?) { + @objc func showCredentialsSavedPopover(_ sender: Any?) { #if DEBUG || REVIEW NotificationCenter.default.post(name: .ShowCredentialsSavedPopover, object: nil) #endif } - @IBAction func showPopUpWindow(_ sender: Any?) { + @objc func showPopUpWindow(_ sender: Any?) { let tabURL = Tab.TabContent.url(URL(string: "https://duckduckgo.com")!) let tab = Tab(content: tabURL, webViewConfiguration: WKWebViewConfiguration(), @@ -738,86 +729,17 @@ extension MainViewController { WindowsManager.openPopUpWindow(with: tab, origin: nil, contentSize: nil) } - @IBAction func resetEmailProtectionInContextPrompt(_ sender: Any?) { + @objc func resetEmailProtectionInContextPrompt(_ sender: Any?) { EmailManager().resetEmailProtectionInContextPrompt() } - @IBAction func fetchConfigurationNow(_ sender: Any?) { + @objc func fetchConfigurationNow(_ sender: Any?) { ConfigurationManager.shared.forceRefresh() } - @IBAction func resetNetworkProtectionWaitlistState(_ sender: Any?) { -#if NETWORK_PROTECTION - NetworkProtectionWaitlist().waitlistStorage.deleteWaitlistState() - UserDefaults().removeObject(forKey: UserDefaultsWrapper.Key.networkProtectionTermsAndConditionsAccepted.rawValue) - NotificationCenter.default.post(name: .networkProtectionWaitlistAccessChanged, object: nil) -#endif - } - - @IBAction func resetNetworkProtectionTermsAndConditionsAcceptance(_ sender: Any?) { -#if NETWORK_PROTECTION - UserDefaults().removeObject(forKey: UserDefaultsWrapper.Key.networkProtectionTermsAndConditionsAccepted.rawValue) - NotificationCenter.default.post(name: .networkProtectionWaitlistAccessChanged, object: nil) -#endif - } - - @IBAction func showNetworkProtectionInviteCodePrompt(_ sender: Any?) { -#if NETWORK_PROTECTION - let code = getInviteCode() - - Task { - do { - let redeemer = NetworkProtectionCodeRedemptionCoordinator() - try await redeemer.redeem(code) - NetworkProtectionWaitlist().waitlistStorage.store(inviteCode: code) - NotificationCenter.default.post(name: .networkProtectionWaitlistAccessChanged, object: nil) - } catch { - // Do nothing here, this is just a debug menu - } - } -#endif - } - - @IBAction func sendNetworkProtectionWaitlistAvailableNotification(_ sender: Any?) { -#if NETWORK_PROTECTION - NetworkProtectionWaitlist().sendInviteCodeAvailableNotification() -#endif - } - - @IBAction func resetNetworkProtectionActivationDate(_ sender: Any?) { - overrideNetworkProtectionActivationDate(to: nil) - } - - @IBAction func resetNetworkProtectionRemoteMessages(_ sender: Any?) { - DefaultNetworkProtectionRemoteMessagingStorage().removeStoredAndDismissedMessages() - DefaultNetworkProtectionRemoteMessaging(minimumRefreshInterval: 0).resetLastRefreshTimestamp() - } - - @IBAction func overrideNetworkProtectionActivationDateToNow(_ sender: Any?) { - overrideNetworkProtectionActivationDate(to: Date()) - } - - @IBAction func overrideNetworkProtectionActivationDateTo5DaysAgo(_ sender: Any?) { - overrideNetworkProtectionActivationDate(to: Date.daysAgo(5)) - } - - @IBAction func overrideNetworkProtectionActivationDateTo10DaysAgo(_ sender: Any?) { - overrideNetworkProtectionActivationDate(to: Date.daysAgo(10)) - } - - private func overrideNetworkProtectionActivationDate(to date: Date?) { - let store = DefaultWaitlistActivationDateStore() - - if let date { - store.updateActivationDate(date) - } else { - store.removeDates() - } - } - // MARK: - Developer Tools - @IBAction func toggleDeveloperTools(_ sender: Any?) { + @objc func toggleDeveloperTools(_ sender: Any?) { guard let webView = getActiveTabAndIndex()?.tab.webView else { return } if webView.isInspectorShown == true { @@ -827,15 +749,15 @@ extension MainViewController { } } - @IBAction func openJavaScriptConsole(_ sender: Any?) { + @objc func openJavaScriptConsole(_ sender: Any?) { getActiveTabAndIndex()?.tab.webView.openJavaScriptConsole() } - @IBAction func showPageSource(_ sender: Any?) { + @objc func showPageSource(_ sender: Any?) { getActiveTabAndIndex()?.tab.webView.showPageSource() } - @IBAction func showPageResources(_ sender: Any?) { + @objc func showPageResources(_ sender: Any?) { getActiveTabAndIndex()?.tab.webView.showPageSource() } } @@ -937,39 +859,11 @@ extension MainViewController: NSMenuItemValidation { return true - // Network Protection Debugging - case #selector(MainViewController.showNetworkProtectionInviteCodePrompt(_:)): - // Only allow testers to enter a custom code if they're on the waitlist, to simulate the correct path through the flow -#if NETWORK_PROTECTION - let waitlist = NetworkProtectionWaitlist() - return waitlist.waitlistStorage.isOnWaitlist || waitlist.waitlistStorage.isInvited -#else - return false -#endif default: return true } } - func getInviteCode() -> String { - let alert = NSAlert() - alert.addButton(withTitle: "Use Invite Code") - alert.addButton(withTitle: "Cancel") - alert.messageText = "Enter Invite Code" - alert.informativeText = "Please grab a Network Protection invite code from Asana and enter it here." - - let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 24)) - alert.accessoryView = textField - - let response = alert.runModal() - - if response == .alertFirstButtonReturn { - return textField.stringValue - } else { - return "" - } - } - // swiftlint:enable function_body_length // swiftlint:enable cyclomatic_complexity } @@ -1030,19 +924,19 @@ extension AppDelegate: NSMenuItemValidation { extension MainViewController: FindInPageDelegate { - @IBAction func findInPage(_ sender: Any) { + @objc func findInPage(_ sender: Any) { activeTabViewModel?.showFindInPage() } - @IBAction func findInPageNext(_ sender: Any) { + @objc func findInPageNext(_ sender: Any) { activeTabViewModel?.findInPageNext() } - @IBAction func findInPagePrevious(_ sender: Any) { + @objc func findInPagePrevious(_ sender: Any) { activeTabViewModel?.findInPagePrevious() } - @IBAction func findInPageDone(_ sender: Any) { + @objc func findInPageDone(_ sender: Any) { activeTabViewModel?.closeFindInPage() } diff --git a/DuckDuckGo/Menus/MenuBuilder.swift b/DuckDuckGo/Menus/MenuBuilder.swift new file mode 100644 index 0000000000..69d59c05b2 --- /dev/null +++ b/DuckDuckGo/Menus/MenuBuilder.swift @@ -0,0 +1,71 @@ +// +// MenuBuilder.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppKit + +@resultBuilder +struct MenuBuilder { + static func buildBlock(_ components: [NSMenuItem]...) -> [NSMenuItem] { + return components.flatMap { $0 } + } + + static func buildOptional(_ components: [NSMenuItem]?) -> [NSMenuItem] { + return components ?? [] + } + + static func buildEither(first component: [NSMenuItem]) -> [NSMenuItem] { + component + } + + static func buildEither(second component: [NSMenuItem]) -> [NSMenuItem] { + component + } + + static func buildLimitedAvailability(_ component: [NSMenuItem]) -> [NSMenuItem] { + component + } + + static func buildArray(_ components: [[NSMenuItem]]) -> [NSMenuItem] { + components.flatMap { $0 } + } + + static func buildExpression(_ expression: [NSMenuItem]) -> [NSMenuItem] { + return expression + } + static func buildExpression(_ expression: NSMenuItem) -> [NSMenuItem] { + return [expression] + } + static func buildExpression(_ expression: Void) -> [NSMenuItem] { + return [] + } + +} + +extension NSMenu { + + convenience init(title string: String = "", @MenuBuilder items: () -> [NSMenuItem]) { + self.init(title: string, items: items()) + } + + @discardableResult + func buildItems(@MenuBuilder items: () -> [NSMenuItem]) -> NSMenu { + self.items = items() + return self + } + +} diff --git a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift index 0ba72a83ec..8b866655c0 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift @@ -547,11 +547,11 @@ final class AddressBarButtonsViewController: NSViewController { animationView?.removeFromSuperview() let newAnimationView: AnimationView - if NSApp.isRunningUnitTests { - newAnimationView = AnimationView() - } else { - // For unknown reason, this caused infinite execution of various unit tests. + // For unknown reason, this caused infinite execution of various unit tests. + if NSApp.runType.requiresEnvironment { newAnimationView = getAnimationView(for: animationName) ?? AnimationView() + } else { + newAnimationView = AnimationView() } animationWrapperView.addAndLayout(newAnimationView) newAnimationView.isHidden = true @@ -641,7 +641,7 @@ final class AddressBarButtonsViewController: NSViewController { private func subscribePrivacyDashboardPendingUpdates(privacyDashboardPopover: PrivacyDashboardPopover) { privacyDashboadPendingUpdatesCancellable?.cancel() - guard !NSApp.isRunningUnitTests else { return } + guard NSApp.runType.requiresEnvironment else { return } privacyDashboadPendingUpdatesCancellable = privacyDashboardPopover.viewController.rulesUpdateObserver .$pendingUpdates.dropFirst().receive(on: DispatchQueue.main).sink { [weak privacyDashboardPopover] _ in @@ -759,7 +759,7 @@ final class AddressBarButtonsViewController: NSViewController { } private func updatePrivacyEntryPointIcon() { - guard !NSApp.isRunningUnitTests else { return } + guard NSApp.runType.requiresEnvironment else { return } privacyEntryPointButton.image = nil guard let selectedTabViewModel = tabCollectionViewModel.selectedTabViewModel else { diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index 78bb8abd07..bfdc4978f3 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -542,18 +542,18 @@ final class ZoomSubMenu: NSMenu { private func updateMenuItems(with tabCollectionViewModel: TabCollectionViewModel, targetting target: AnyObject) { removeAllItems() - let fullScreenItem = (NSApplication.shared.mainMenuTyped.toggleFullscreenMenuItem?.copy() as? NSMenuItem)! + let fullScreenItem = (NSApp.mainMenuTyped.toggleFullscreenMenuItem.copy() as? NSMenuItem)! addItem(fullScreenItem) addItem(.separator()) - let zoomInItem = (NSApplication.shared.mainMenuTyped.zoomInMenuItem?.copy() as? NSMenuItem)! + let zoomInItem = (NSApp.mainMenuTyped.zoomInMenuItem.copy() as? NSMenuItem)! addItem(zoomInItem) - let zoomOutItem = (NSApplication.shared.mainMenuTyped.zoomOutMenuItem?.copy() as? NSMenuItem)! + let zoomOutItem = (NSApp.mainMenuTyped.zoomOutMenuItem.copy() as? NSMenuItem)! addItem(zoomOutItem) - let actualSizeItem = (NSApplication.shared.mainMenuTyped.actualSizeMenuItem?.copy() as? NSMenuItem)! + let actualSizeItem = (NSApp.mainMenuTyped.actualSizeMenuItem.copy() as? NSMenuItem)! addItem(actualSizeItem) addItem(.separator()) @@ -705,32 +705,4 @@ final class LoginsSubMenu: NSMenu { } -extension NSMenuItem { - - @discardableResult - func withImage(_ image: NSImage?) -> NSMenuItem { - self.image = image - return self - } - - @discardableResult - func targetting(_ target: AnyObject) -> NSMenuItem { - self.target = target - return self - } - - @discardableResult - func withSubmenu(_ submenu: NSMenu) -> NSMenuItem { - self.submenu = submenu - return self - } - - @discardableResult - func withModifierMask(_ mask: NSEvent.ModifierFlags) -> NSMenuItem { - self.keyEquivalentModifierMask = mask - return self - } - -} - extension MoreOptionsMenu: EmailManagerRequestDelegate { } diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarPopovers.swift b/DuckDuckGo/NavigationBar/View/NavigationBarPopovers.swift index cddd8e0812..0d0f567b5c 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarPopovers.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarPopovers.swift @@ -24,6 +24,7 @@ import AppKit import Combine import NetworkProtection import NetworkProtectionUI +import NetworkProtectionIPC #endif final class NavigationBarPopovers { @@ -40,7 +41,11 @@ final class NavigationBarPopovers { private(set) var downloadsPopover: DownloadsPopover? #if NETWORK_PROTECTION - private(set) var networkProtectionPopover: NetworkProtectionPopover? + private let networkProtectionPopoverManager: NetworkProtectionNavBarPopoverManager + + init(networkProtectionPopoverManager: NetworkProtectionNavBarPopoverManager) { + self.networkProtectionPopoverManager = networkProtectionPopoverManager + } #endif var passwordManagementDomain: String? { @@ -68,7 +73,7 @@ final class NavigationBarPopovers { @MainActor var isNetworkProtectionPopoverShown: Bool { #if NETWORK_PROTECTION - networkProtectionPopover?.isShown ?? false + networkProtectionPopoverManager.isShown #else return false #endif @@ -96,17 +101,7 @@ final class NavigationBarPopovers { func toggleNetworkProtectionPopover(usingView view: NSView, withDelegate delegate: NSPopoverDelegate) { #if NETWORK_PROTECTION - if let networkProtectionPopover, networkProtectionPopover.isShown { - networkProtectionPopover.close() - } else { - let featureVisibility = DefaultNetworkProtectionVisibility() - - if featureVisibility.isNetworkProtectionVisible() { - showNetworkProtectionPopover(usingView: view, withDelegate: delegate) - } else { - featureVisibility.disableForWaitlistUsers() - } - } + networkProtectionPopoverManager.toggle(positionedBelow: view, withDelegate: delegate) #endif } @@ -166,8 +161,8 @@ final class NavigationBarPopovers { } #if NETWORK_PROTECTION - if networkProtectionPopover?.isShown ?? false { - networkProtectionPopover?.close() + if networkProtectionPopoverManager.isShown { + networkProtectionPopoverManager.close() } #endif @@ -287,41 +282,10 @@ final class NavigationBarPopovers { // MARK: - Network Protection #if NETWORK_PROTECTION - func showNetworkProtectionPopover(usingView view: NSView, withDelegate delegate: NSPopoverDelegate) { - let popover = networkProtectionPopover ?? { - let controller = NetworkProtectionTunnelController() - let statusObserver = ConnectionStatusObserverThroughSession(platformNotificationCenter: NSWorkspace.shared.notificationCenter, - platformDidWakeNotification: NSWorkspace.didWakeNotification) - let statusInfoObserver = ConnectionServerInfoObserverThroughSession(platformNotificationCenter: NSWorkspace.shared.notificationCenter, - platformDidWakeNotification: NSWorkspace.didWakeNotification) - let connectionErrorObserver = ConnectionErrorObserverThroughSession(platformNotificationCenter: NSWorkspace.shared.notificationCenter, - platformDidWakeNotification: NSWorkspace.didWakeNotification) - let statusReporter = DefaultNetworkProtectionStatusReporter( - statusObserver: statusObserver, - serverInfoObserver: statusInfoObserver, - connectionErrorObserver: connectionErrorObserver, - connectivityIssuesObserver: ConnectivityIssueObserverThroughDistributedNotifications(), - controllerErrorMessageObserver: ControllerErrorMesssageObserverThroughDistributedNotifications() - ) - - let menuItems = [ - NetworkProtectionStatusView.Model.MenuItem( - name: UserText.networkProtectionNavBarStatusViewShareFeedback, - action: { - let appLauncher = AppLauncher(appBundleURL: Bundle.main.bundleURL) - await appLauncher.launchApp(withCommand: .shareFeedback) - }) - ] - - let onboardingStatusPublisher = UserDefaults.shared.networkProtectionOnboardingStatusPublisher - - let popover = NetworkProtectionPopover(controller: controller, onboardingStatusPublisher: onboardingStatusPublisher, statusReporter: statusReporter, menuItems: menuItems) - popover.delegate = delegate - - networkProtectionPopover = popover - return popover - }() - show(popover, positionedBelow: view) + func showNetworkProtectionPopover( + positionedBelow view: NSView, + withDelegate delegate: NSPopoverDelegate) { + networkProtectionPopoverManager.show(positionedBelow: view, withDelegate: delegate) } #endif } diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index be8f20b356..e4f88b8708 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -23,6 +23,7 @@ import BrowserServicesKit #if NETWORK_PROTECTION import NetworkProtection +import NetworkProtectionIPC import NetworkProtectionUI #endif @@ -78,7 +79,8 @@ final class NavigationBarViewController: NSViewController { private let goForwardButtonMenuDelegate: NavigationButtonMenuDelegate // swiftlint:enable weak_delegate - private var popovers = NavigationBarPopovers() + private var popovers: NavigationBarPopovers + var isDownloadsPopoverShown: Bool { popovers.isDownloadsPopoverShown } @@ -105,8 +107,16 @@ final class NavigationBarViewController: NSViewController { #if NETWORK_PROTECTION init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel, isBurner: Bool, networkProtectionFeatureActivation: NetworkProtectionFeatureActivation = NetworkProtectionKeychainTokenStore()) { + + let vpnBundleID = Bundle.main.vpnMenuAgentBundleId + let ipcClient = TunnelControllerIPCClient(machServiceName: vpnBundleID) + ipcClient.register() + + let networkProtectionPopoverManager = NetworkProtectionNavBarPopoverManager(ipcClient: ipcClient) + + self.popovers = NavigationBarPopovers(networkProtectionPopoverManager: networkProtectionPopoverManager) self.tabCollectionViewModel = tabCollectionViewModel - self.networkProtectionButtonModel = NetworkProtectionNavBarButtonModel(popovers: popovers) + self.networkProtectionButtonModel = NetworkProtectionNavBarButtonModel(popoverManager: networkProtectionPopoverManager) self.isBurner = isBurner self.networkProtectionFeatureActivation = networkProtectionFeatureActivation goBackButtonMenuDelegate = NavigationButtonMenuDelegate(buttonType: .back, tabCollectionViewModel: tabCollectionViewModel) @@ -115,6 +125,7 @@ final class NavigationBarViewController: NSViewController { } #else init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel, isBurner: Bool) { + self.popovers = NavigationBarPopovers() self.tabCollectionViewModel = tabCollectionViewModel self.isBurner = isBurner goBackButtonMenuDelegate = NavigationButtonMenuDelegate(buttonType: .back, tabCollectionViewModel: tabCollectionViewModel) @@ -254,15 +265,8 @@ final class NavigationBarViewController: NSViewController { selectedTabViewModel.tab.openHomePage() } - // swiftlint:disable force_cast @IBAction func optionsButtonAction(_ sender: NSButton) { - - guard let internalUserDecider = (NSApp.delegate as! AppDelegate).internalUserDecider else { - assertionFailure("\(className): internalUserDecider is nil") - os_log("%s: internalUserDecider is nil", type: .error, className) - return - } - + let internalUserDecider = NSApp.delegateTyped.internalUserDecider let menu = MoreOptionsMenu(tabCollectionViewModel: tabCollectionViewModel, passwordManagerCoordinator: PasswordManagerCoordinator.shared, internalUserDecider: internalUserDecider) @@ -270,7 +274,6 @@ final class NavigationBarViewController: NSViewController { let location = NSPoint(x: -menu.size.width + sender.bounds.width, y: sender.bounds.height + 4) menu.popUp(positioning: nil, at: location, in: sender) } - // swiftlint:enable force_cast @IBAction func bookmarksButtonAction(_ sender: NSButton) { popovers.bookmarksButtonPressed(anchorView: bookmarkListButton, @@ -851,7 +854,7 @@ extension NavigationBarViewController: NSMenuDelegate { let featureVisibility = DefaultNetworkProtectionVisibility() if featureVisibility.isNetworkProtectionVisible() { - popovers.showNetworkProtectionPopover(usingView: networkProtectionButton, + popovers.showNetworkProtectionPopover(positionedBelow: networkProtectionButton, withDelegate: networkProtectionButtonModel) } else { featureVisibility.disableForWaitlistUsers() diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionPixelEvent.swift b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift similarity index 80% rename from DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionPixelEvent.swift rename to DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift index 36c4668b78..a25cc9c7d9 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionPixelEvent.swift +++ b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift @@ -16,64 +16,14 @@ // limitations under the License. // +#if NETWORK_PROTECTION + import Foundation import PixelKit import NetworkProtection -extension Pixel { - - enum Parameters { - static let duration = "duration" - static let test = "test" - static let appVersion = "appVersion" - - static let keychainFieldName = "fieldName" - static let errorCode = "e" - static let errorDesc = "d" - static let errorCount = "c" - - static let function = "function" - static let line = "line" - - static let latency = "latency" - static let server = "server" - static let networkType = "net_type" - } +enum NetworkProtectionPixelEvent: PixelKitEvent { - enum Values { - static let test = "1" - } -} - -extension Pixel { - static func fire(_ event: NetworkProtectionPixelEvent, - frequency: PixelFrequency, - withAdditionalParameters parameters: [String: String]? = nil, - allowedQueryReservedCharacters: CharacterSet? = nil, - includeAppVersionParameter: Bool = true, - onComplete: @escaping (Error?) -> Void = {_ in }) { - let newParams: [String: String]? - switch (event.parameters, parameters) { - case (.some(let parameters), .none): - newParams = parameters - case (.none, .some(let parameters)): - newParams = parameters - case (.some(let params1), .some(let params2)): - newParams = params1.merging(params2) { $1 } - case (.none, .none): - newParams = nil - } - - Pixel.shared?.fire(pixelNamed: event.name, - frequency: frequency, - withAdditionalParameters: newParams, - allowedQueryReservedCharacters: allowedQueryReservedCharacters, - includeAppVersionParameter: includeAppVersionParameter, - onComplete: onComplete) - } -} - -enum NetworkProtectionPixelEvent { case networkProtectionActiveUser case networkProtectionTunnelConfigurationNoServerRegistrationInfo @@ -115,10 +65,13 @@ enum NetworkProtectionPixelEvent { case networkProtectionLatency(ms: Int, server: String, networkType: NetworkConnectionType) + case networkProtectionSystemExtensionUnknownActivationResult + case networkProtectionUnhandledError(function: String, line: Int, error: Error) var name: String { switch self { + case .networkProtectionActiveUser: return "m_mac_netp_daily_active" @@ -214,6 +167,10 @@ enum NetworkProtectionPixelEvent { case .networkProtectionLatency: return "m_mac_netp_latency" + + case .networkProtectionSystemExtensionUnknownActivationResult: + return "m_mac_netp_system_extension_unknown_activation_result" + case .networkProtectionUnhandledError: return "m_mac_netp_unhandled_error" } @@ -222,23 +179,23 @@ enum NetworkProtectionPixelEvent { var parameters: [String: String]? { switch self { case .networkProtectionKeychainErrorFailedToCastKeychainValueToData(let field): - return [Pixel.Parameters.keychainFieldName: field] + return [PixelKit.Parameters.keychainFieldName: field] case .networkProtectionKeychainReadError(let field, let status): return [ - Pixel.Parameters.keychainFieldName: field, - Pixel.Parameters.errorCode: String(status) + PixelKit.Parameters.keychainFieldName: field, + PixelKit.Parameters.errorCode: String(status) ] case .networkProtectionKeychainWriteError(let field, let status): return [ - Pixel.Parameters.keychainFieldName: field, - Pixel.Parameters.errorCode: String(status) + PixelKit.Parameters.keychainFieldName: field, + PixelKit.Parameters.errorCode: String(status) ] case .networkProtectionKeychainDeleteError(let status): return [ - Pixel.Parameters.errorCode: String(status) + PixelKit.Parameters.errorCode: String(status) ] case .networkProtectionServerListStoreFailedToWriteServerList(let error): @@ -258,15 +215,15 @@ enum NetworkProtectionPixelEvent { case .networkProtectionUnhandledError(let function, let line, let error): var parameters = error.pixelParameters - parameters[Pixel.Parameters.function] = function - parameters[Pixel.Parameters.line] = String(line) + parameters[PixelKit.Parameters.function] = function + parameters[PixelKit.Parameters.line] = String(line) return parameters case .networkProtectionLatency(ms: let latency, server: let server, networkType: let networkType): return [ - Pixel.Parameters.latency: String(latency), - Pixel.Parameters.server: server, - Pixel.Parameters.networkType: networkType.description + PixelKit.Parameters.latency: String(latency), + PixelKit.Parameters.server: server, + PixelKit.Parameters.networkType: networkType.description ] case .networkProtectionWireguardErrorCannotSetNetworkSettings(error: let error): @@ -274,7 +231,7 @@ enum NetworkProtectionPixelEvent { case .networkProtectionWireguardErrorCannotStartWireguardBackend(code: let code): return [ - Pixel.Parameters.errorCode: String(code) + PixelKit.Parameters.errorCode: String(code) ] case .networkProtectionTunnelConfigurationNoServerRegistrationInfo, @@ -296,9 +253,12 @@ enum NetworkProtectionPixelEvent { .networkProtectionWireguardErrorCannotLocateTunnelFileDescriptor, .networkProtectionWireguardErrorInvalidState, .networkProtectionWireguardErrorFailedDNSResolution, + .networkProtectionSystemExtensionUnknownActivationResult, .networkProtectionActiveUser: return nil } } } + +#endif diff --git a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionUserDefaultsConstants.swift b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionUserDefaultsConstants.swift index 345f00f30c..165f2c37db 100644 --- a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionUserDefaultsConstants.swift +++ b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionUserDefaultsConstants.swift @@ -25,7 +25,7 @@ enum NetworkProtectionUserDefaultsConstants { static let shouldConnectOnLogIn = false static let shouldEnforceRoutes = false static let shouldIncludeAllNetworks = false - static let shouldExcludeLocalRoutes = false + static let shouldExcludeLocalNetworks = false static let isConnectionTesterEnabled = true } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift index 7ff941f4cb..625cbc0c90 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift @@ -18,14 +18,14 @@ #if NETWORK_PROTECTION +import Common import Foundation import NetworkProtection -import Common +import PixelKit extension EventMapping where Event == NetworkProtectionError { static var networkProtectionAppDebugEvents: EventMapping = .init { event, _, _, _ in - - let domainEvent: Pixel.Event.Debug + let domainEvent: NetworkProtectionPixelEvent switch event { case .failedToEncodeRedeemRequest: @@ -78,7 +78,9 @@ extension EventMapping where Event == NetworkProtectionError { return } - Pixel.fire(.debug(event: domainEvent)) + + let debugEvent = DebugEvent(eventType: .custom(domainEvent)) + PixelKit.fire(debugEvent, frequency: .standard, includeAppVersionParameter: true) } } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/LoginItem+NetworkProtection.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/LoginItem+NetworkProtection.swift index 0c21abdf21..ee34e73d09 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/LoginItem+NetworkProtection.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/LoginItem+NetworkProtection.swift @@ -21,10 +21,9 @@ import LoginItems extension LoginItem { - - static let vpnMenu = LoginItem(bundleId: Bundle.main.vpnMenuAgentBundleId, url: Bundle.main.vpnMenuAgentURL, log: .networkProtection) + static let vpnMenu = LoginItem(bundleId: Bundle.main.vpnMenuAgentBundleId, log: .networkProtection) #if NETP_SYSTEM_EXTENSION - static let notificationsAgent = LoginItem(bundleId: Bundle.main.notificationsAgentBundleId, url: Bundle.main.notificationsAgentURL, log: .networkProtection) + static let notificationsAgent = LoginItem(bundleId: Bundle.main.notificationsAgentBundleId, log: .networkProtection) #endif } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionAppEvents.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionAppEvents.swift index abeb52d243..8091238710 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionAppEvents.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionAppEvents.swift @@ -16,16 +16,35 @@ // limitations under the License. // +#if NETWORK_PROTECTION import Common import Foundation - -#if NETWORK_PROTECTION +import LoginItems import NetworkProtection +import NetworkProtectionIPC +import NetworkExtension /// Implements the sequence of steps that Network Protection needs to execute when the App starts up. /// final class NetworkProtectionAppEvents { + // MARK: - Legacy VPN Item and Extension + +#if NETP_SYSTEM_EXTENSION +#if DEBUG + private let legacyAgentBundleID = "HKE973VLUW.com.duckduckgo.macos.browser.network-protection.system-extension.agent.debug" + private let legacySystemExtensionBundleID = "com.duckduckgo.macos.browser.debug.network-protection-extension" +#elseif REVIEW + private let legacyAgentBundleID = "HKE973VLUW.com.duckduckgo.macos.browser.network-protection.system-extension.agent.review" + private let legacySystemExtensionBundleID = "com.duckduckgo.macos.browser.review.network-protection-extension" +#else + private let legacyAgentBundleID = "HKE973VLUW.com.duckduckgo.macos.browser.network-protection.system-extension.agent" + private let legacySystemExtensionBundleID = "com.duckduckgo.macos.browser.network-protection-extension" +#endif // DEBUG || REVIEW || RELEASE +#endif // NETP_SYSTEM_EXTENSION + + // MARK: - Feature Visibility + private let featureVisibility: NetworkProtectionFeatureVisibility init(featureVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility()) { @@ -35,16 +54,20 @@ final class NetworkProtectionAppEvents { /// Call this method when the app finishes launching, to run the startup logic for NetP. /// func applicationDidFinishLaunching() { - migrateNetworkProtectionAuthTokenToSharedKeychainIfNecessary() + let loginItemsManager = LoginItemsManager() - guard featureVisibility.isNetworkProtectionVisible() else { - featureVisibility.disableForAllUsers() - return - } + Task { + await removeLegacyLoginItemAndVPNConfiguration() + migrateNetworkProtectionAuthTokenToSharedKeychainIfNecessary() - let loginItemsManager = LoginItemsManager() - restartNetworkProtectionIfVersionChanged(using: loginItemsManager) - refreshNetworkProtectionServers() + guard featureVisibility.isNetworkProtectionVisible() else { + featureVisibility.disableForAllUsers() + return + } + + restartNetworkProtectionIfVersionChanged(using: loginItemsManager) + refreshNetworkProtectionServers() + } } /// Call this method when the app becomes active to run the associated NetP logic. @@ -106,22 +129,22 @@ final class NetworkProtectionAppEvents { if lastVersionRun != currentVersion { os_log(.info, log: .networkProtection, "App updated from %{public}s to %{public}s: updating login items", lastVersionRun, currentVersion) restartNetworkProtectionTunnelAndMenu(using: loginItemsManager) - } else { - // If login items failed to launch (e.g. because of the App bundle rename), launch using NSWorkspace - loginItemsManager.ensureLoginItemsAreRunning(LoginItemsManager.networkProtectionLoginItems, log: .networkProtection, condition: .ifLoginItemsAreEnabled, after: 1) } } private func restartNetworkProtectionTunnelAndMenu(using loginItemsManager: LoginItemsManager) { + loginItemsManager.restartLoginItems(LoginItemsManager.networkProtectionLoginItems, log: .networkProtection) Task { - let provider = NetworkProtectionTunnelController() + let machServiceName = Bundle.main.vpnMenuAgentBundleId + let ipcClient = TunnelControllerIPCClient(machServiceName: machServiceName) + let controller = NetworkProtectionIPCTunnelController(ipcClient: ipcClient) // Restart NetP SysEx on app update - if await provider.isConnected { - await provider.stop() - await provider.start() + if controller.isConnected { + await controller.stop() + await controller.start() } } } @@ -141,6 +164,23 @@ final class NetworkProtectionAppEvents { os_log("Successfully updated Network Protection servers; total server count = %{public}d", log: .networkProtection, serverCount) } } + + // MARK: - Legacy Login Item and Extension + + private func removeLegacyLoginItemAndVPNConfiguration() async { + LoginItem(bundleId: legacyAgentBundleID).forceStop() + + let tunnels = try? await NETunnelProviderManager.loadAllFromPreferences() + let tunnel = tunnels?.first { + ($0.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == legacySystemExtensionBundleID + } + + guard let tunnel else { + return + } + + try? await tunnel.removeFromPreferences() + } } #endif diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift index 59be5e41a4..877775be80 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift @@ -16,84 +16,163 @@ // limitations under the License. // +#if NETWORK_PROTECTION + import AppKit import Common import Foundation +import NetworkProtection +import SwiftUI -#if !NETWORK_PROTECTION - -/// App store placedholder. Should be replaced with the actual thing once we enable NetP in App Store builds. +/// Controller for the Network Protection debug menu. /// -@objc +@MainActor final class NetworkProtectionDebugMenu: NSMenu { - /// This is just present so we can remove this menu item in App Store builds. - /// - @IBOutlet weak var mainMenuItem: NSMenuItem? - override func awakeFromNib() { - // Hide the entire NetP debug menu when the feature is disabled: - mainMenuItem?.removeFromParent() - } -} + private let preferredServerMenu: NSMenu + private let preferredServerAutomaticItem = NSMenuItem(title: "Automatic", action: #selector(NetworkProtectionDebugMenu.setSelectedServer)) -#else + private let registrationKeyValidityMenu: NSMenu + private let registrationKeyValidityAutomaticItem = NSMenuItem(title: "Automatic", action: #selector(NetworkProtectionDebugMenu.setRegistrationKeyValidity)) -import NetworkProtection + private let exclusionsMenu = NSMenu() -/// Controller for the Network Protection debug menu. -/// -@objc -@MainActor -final class NetworkProtectionDebugMenu: NSMenu { + private let shouldEnforceRoutesMenuItem = NSMenuItem(title: "Kill Switch (enforceRoutes)", action: #selector(NetworkProtectionDebugMenu.toggleEnforceRoutesAction)) + private let shouldIncludeAllNetworksMenuItem = NSMenuItem(title: "includeAllNetworks", action: #selector(NetworkProtectionDebugMenu.toggleIncludeAllNetworks)) + private let connectOnLogInMenuItem = NSMenuItem(title: "Connect on Log In", action: #selector(NetworkProtectionDebugMenu.toggleConnectOnLogInAction)) - // MARK: - Outlets: Menus + private let excludeLocalNetworksMenuItem = NSMenuItem(title: "excludeLocalNetworks", action: #selector(NetworkProtectionDebugMenu.toggleShouldExcludeLocalRoutes)) - @IBOutlet weak var preferredServerMenu: NSMenu? { - didSet { - populateNetworkProtectionServerListMenuItems() - } - } + private let enterWaitlistInviteCodeItem = NSMenuItem(title: "Enter Waitlist Invite Code", action: #selector(NetworkProtectionDebugMenu.showNetworkProtectionInviteCodePrompt)) - @IBOutlet weak var registrationKeyValidityMenu: NSMenu? { - didSet { - populateNetworkProtectionRegistrationKeyValidityMenuItems() + private let waitlistTokenItem = NSMenuItem(title: "Waitlist Token:") + private let waitlistTimestampItem = NSMenuItem(title: "Waitlist Timestamp:") + private let waitlistInviteCodeItem = NSMenuItem(title: "Waitlist Invite Code:") + private let waitlistTermsAndConditionsAcceptedItem = NSMenuItem(title: "T&C Accepted:") + + // swiftlint:disable:next function_body_length + init() { + preferredServerMenu = NSMenu { [preferredServerAutomaticItem] in + preferredServerAutomaticItem } - } + registrationKeyValidityMenu = NSMenu { [registrationKeyValidityAutomaticItem] in + registrationKeyValidityAutomaticItem + } + super.init(title: "Network Protection") + + buildItems { + NSMenuItem(title: "Reset All State Keeping Invite", action: #selector(NetworkProtectionDebugMenu.resetAllKeepingInvite)) + .targetting(self) + + NSMenuItem(title: "Reset All State", action: #selector(NetworkProtectionDebugMenu.resetAllState)) + .targetting(self) + + NSMenuItem(title: "Remove System Extension and Login Items", action: #selector(NetworkProtectionDebugMenu.removeSystemExtensionAndAgents)) + .targetting(self) + + NSMenuItem(title: "Reset Remote Messages", action: #selector(NetworkProtectionDebugMenu.resetNetworkProtectionRemoteMessages)) + .targetting(self) + NSMenuItem.separator() - @IBOutlet weak var exclusionsMenu: NSMenu! { - didSet { - populateExclusionsMenuItems() + connectOnLogInMenuItem + .targetting(self) + shouldEnforceRoutesMenuItem + .targetting(self) + NSMenuItem(title: "Excluded Routes").submenu(exclusionsMenu) + NSMenuItem.separator() + + NSMenuItem(title: "Send Test Notification", action: #selector(NetworkProtectionDebugMenu.sendTestNotification)) + .targetting(self) + + NSMenuItem(title: "Onboarding") + .submenu(NetworkProtectionOnboardingMenu()) + + NSMenuItem(title: "Preferred Server").submenu(preferredServerMenu) + + NSMenuItem(title: "Registration Key") { + NSMenuItem(title: "Expire Now", action: #selector(NetworkProtectionDebugMenu.expireRegistrationKeyNow)) + .targetting(self) + +#if DEBUG + NSMenuItem.separator() + NSMenuItem(title: "Validity").submenu(registrationKeyValidityMenu) +#endif + } + + NSMenuItem(title: "Simulate Failure") + .submenu(NetworkProtectionSimulateFailureMenu()) + + NSMenuItem(title: "Override NetP Activation Date") { + NSMenuItem(title: "Reset Activation Date", action: #selector(NetworkProtectionDebugMenu.resetNetworkProtectionActivationDate)) + .targetting(self) + NSMenuItem(title: "Set Activation Date to Now", action: #selector(NetworkProtectionDebugMenu.overrideNetworkProtectionActivationDateToNow)) + .targetting(self) + NSMenuItem(title: "Set Activation Date to 5 Days Ago", action: #selector(NetworkProtectionDebugMenu.overrideNetworkProtectionActivationDateTo5DaysAgo)) + .targetting(self) + NSMenuItem(title: "Set Activation Date to 10 Days Ago", action: #selector(NetworkProtectionDebugMenu.overrideNetworkProtectionActivationDateTo10DaysAgo)) + .targetting(self) + } + + NSMenuItem(title: "NetP Waitlist") { + NSMenuItem(title: "Reset Waitlist State", action: #selector(NetworkProtectionDebugMenu.resetNetworkProtectionWaitlistState)) + .targetting(self) + NSMenuItem(title: "Reset T&C Acceptance", action: #selector(NetworkProtectionDebugMenu.resetNetworkProtectionTermsAndConditionsAcceptance)) + .targetting(self) + + enterWaitlistInviteCodeItem + .targetting(self) + + NSMenuItem(title: "Send Waitlist Notification", action: #selector(NetworkProtectionDebugMenu.sendNetworkProtectionWaitlistAvailableNotification)) + .targetting(self) + NSMenuItem.separator() + + waitlistTokenItem + waitlistTimestampItem + waitlistInviteCodeItem + waitlistTermsAndConditionsAcceptedItem + } + + NSMenuItem(title: "NetP Waitlist Feature Flag Overrides") + .submenu(NetworkProtectionWaitlistFeatureFlagOverridesMenu()) + NSMenuItem.separator() + + NSMenuItem(title: "Kill Switch (alternative approach)") { + shouldIncludeAllNetworksMenuItem + .targetting(self) + excludeLocalNetworksMenuItem + .targetting(self) + } + + NSMenuItem(title: "Open App Container in Finder", action: #selector(NetworkProtectionDebugMenu.openAppContainerInFinder)) + .targetting(self) } - } - // MARK: - Outlets: Menu Items + preferredServerMenu.autoenablesItems = false + populateNetworkProtectionServerListMenuItems() + populateNetworkProtectionRegistrationKeyValidityMenuItems() - /// This is just present so we can remove this menu item in App Store builds. - /// - @IBOutlet weak var mainMenuItem: NSMenuItem! - @IBOutlet weak var registrationKeyValidityMenuSeparatorItem: NSMenuItem! - @IBOutlet weak var registrationKeyValidityMenuItem: NSMenuItem! - @IBOutlet weak var registrationKeyValidityAutomaticItem: NSMenuItem! - @IBOutlet weak var preferredServerAutomaticItem: NSMenuItem! + exclusionsMenu.delegate = self + exclusionsMenu.autoenablesItems = false + populateExclusionsMenuItems() + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - @IBOutlet weak var enableConnectOnDemandMenuItem: NSMenuItem! - @IBOutlet weak var shouldEnforceRoutesMenuItem: NSMenuItem! - @IBOutlet weak var shouldIncludeAllNetworksMenuItem: NSMenuItem! - @IBOutlet weak var connectOnLogInMenuItem: NSMenuItem! + // MARK: - Tunnel Settings - @IBOutlet weak var excludeDDGRouteMenuItem: NSMenuItem! - @IBOutlet weak var excludeLocalNetworksMenuItem: NSMenuItem! + private let settings = TunnelSettings(defaults: .shared) // MARK: - Debug Logic private let debugUtilities = NetworkProtectionDebugUtilities() - // MARK: - Debug Menu IBActions + // MARK: - Debug Menu Actions /// Resets all state for NetworkProtection. /// - @IBAction - func resetAllState(_ sender: Any?) { + @objc func resetAllState(_ sender: Any?) { Task { @MainActor in guard case .alertFirstButtonReturn = await NSAlert.resetNetworkProtectionAlert().runModal() else { return } @@ -107,8 +186,7 @@ final class NetworkProtectionDebugMenu: NSMenu { /// Resets all state for NetworkProtection. /// - @IBAction - func resetAllKeepingInvite(_ sender: Any?) { + @objc func resetAllKeepingInvite(_ sender: Any?) { Task { @MainActor in guard case .alertFirstButtonReturn = await NSAlert.resetNetworkProtectionAlert().runModal() else { return } @@ -122,8 +200,7 @@ final class NetworkProtectionDebugMenu: NSMenu { /// Removes the system extension and agents for Network Protection. /// - @IBAction - func removeSystemExtensionAndAgents(_ sender: Any?) { + @objc func removeSystemExtensionAndAgents(_ sender: Any?) { Task { @MainActor in guard case .alertFirstButtonReturn = await NSAlert.removeSystemExtensionAndAgentsAlert().runModal() else { return } @@ -137,8 +214,7 @@ final class NetworkProtectionDebugMenu: NSMenu { /// Sends a test user notification. /// - @IBAction - func sendTestNotification(_ sender: Any?) { + @objc func sendTestNotification(_ sender: Any?) { Task { @MainActor in do { try await debugUtilities.sendTestNotificationRequest() @@ -150,10 +226,9 @@ final class NetworkProtectionDebugMenu: NSMenu { /// Sets the selected server. /// - @IBAction - func setSelectedServer(_ menuItem: NSMenuItem) { + @objc func setSelectedServer(_ menuItem: NSMenuItem) { let title = menuItem.title - let selectedServer: SelectedNetworkProtectionServer + let selectedServer: TunnelSettings.SelectedServer if title == "Automatic" { selectedServer = .automatic @@ -162,13 +237,12 @@ final class NetworkProtectionDebugMenu: NSMenu { selectedServer = .endpoint(titleComponents.first!) } - debugUtilities.setSelectedServer(selectedServer: selectedServer) + settings.selectedServer = selectedServer } /// Expires the registration key immediately. /// - @IBAction - func expireRegistrationKeyNow(_ sender: Any?) { + @objc func expireRegistrationKeyNow(_ sender: Any?) { Task { await debugUtilities.expireRegistrationKeyNow() } @@ -176,59 +250,58 @@ final class NetworkProtectionDebugMenu: NSMenu { /// Sets the registration key validity. /// - @IBAction - func setRegistrationKeyValidity(_ menuItem: NSMenuItem) { - // nil means automatic - let validity = menuItem.representedObject as? TimeInterval + @objc func setRegistrationKeyValidity(_ menuItem: NSMenuItem) { + guard let timeInterval = menuItem.representedObject as? TimeInterval else { + settings.registrationKeyValidity = .automatic + return + } - debugUtilities.registrationKeyValidity = validity + settings.registrationKeyValidity = .custom(timeInterval) } - @IBAction - func toggleEnforceRoutesAction(_ sender: Any?) { - NetworkProtectionTunnelController().toggleShouldEnforceRoutes() + @objc func toggleEnforceRoutesAction(_ sender: Any?) { + settings.enforceRoutes.toggle() } - @IBAction - func toggleIncludeAllNetworks(_ sender: Any?) { - NetworkProtectionTunnelController().toggleShouldIncludeAllNetworks() + @objc func toggleIncludeAllNetworks(_ sender: Any?) { + settings.includeAllNetworks.toggle() } - @IBAction - func toggleShouldExcludeLocalRoutes(_ sender: Any?) { - NetworkProtectionTunnelController().toggleShouldExcludeLocalRoutes() + @objc func toggleShouldExcludeLocalRoutes(_ sender: Any?) { + settings.excludeLocalNetworks.toggle() } - @IBAction - func toggleConnectOnLogInAction(_ sender: Any?) { - NetworkProtectionTunnelController().toggleShouldAutoConnectOnLogIn() + @objc func toggleConnectOnLogInAction(_ sender: Any?) { + // Temporarily disabled: https://app.asana.com/0/0/1205766100762904/f } - @IBAction - func toggleExclusionAction(_ sender: NSMenuItem) { + @objc func toggleExclusionAction(_ sender: NSMenuItem) { + // Temporarily disabled: https://app.asana.com/0/0/1205766100762904/f + /* guard let addressRange = sender.representedObject as? String else { assertionFailure("Unexpected representedObject") return } - NetworkProtectionTunnelController().setExcludedRoute(addressRange, enabled: sender.state == .off) + + NetworkProtectionTunnelController().setExcludedRoute(addressRange, enabled: sender.state == .off)*/ + } + + @objc func openAppContainerInFinder(_ sender: Any?) { + let containerURL = URL.sandboxApplicationSupportURL + NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: containerURL.path) } // MARK: Populating Menu Items private func populateNetworkProtectionServerListMenuItems() { - guard let submenu = preferredServerMenu, - let automaticItem = preferredServerAutomaticItem else { - assertionFailure("\(#function): Failed to get submenu") - return - } - let networkProtectionServerStore = NetworkProtectionServerListFileSystemStore(errorEvents: nil) let servers = (try? networkProtectionServerStore.storedNetworkProtectionServerList()) ?? [] + preferredServerAutomaticItem.target = self if servers.isEmpty { - submenu.items = [automaticItem] + preferredServerMenu.items = [preferredServerAutomaticItem] } else { - submenu.items = [automaticItem, NSMenuItem.separator()] + servers.map({ server in + preferredServerMenu.items = [preferredServerAutomaticItem, NSMenuItem.separator()] + servers.map({ server in let title: String if server.isRegistered { @@ -262,18 +335,12 @@ final class NetworkProtectionDebugMenu: NSMenu { private func populateNetworkProtectionRegistrationKeyValidityMenuItems() { #if DEBUG - guard let menu = registrationKeyValidityMenu, - let automaticItem = registrationKeyValidityAutomaticItem else { - - assertionFailure("\(#function): Failed to get menu or automatic item") - return - } - + registrationKeyValidityAutomaticItem.target = self if Self.registrationKeyValidityOptions.isEmpty { // Not likely to happen as it's hard-coded, but still... - menu.items = [automaticItem] + registrationKeyValidityMenu.items = [registrationKeyValidityAutomaticItem] } else { - menu.items = [automaticItem, NSMenuItem.separator()] + Self.registrationKeyValidityOptions.map { option in + registrationKeyValidityMenu.items = [registrationKeyValidityAutomaticItem, NSMenuItem.separator()] + Self.registrationKeyValidityOptions.map { option in let menuItem = NSMenuItem(title: option.title, action: #selector(setRegistrationKeyValidity(_:)), target: self, @@ -283,22 +350,13 @@ final class NetworkProtectionDebugMenu: NSMenu { return menuItem } } -#else - guard let separator = registrationKeyValidityMenuSeparatorItem, - let validityMenu = registrationKeyValidityMenuItem else { - assertionFailure("\(#function): Failed to get menu or automatic item") - return - } - - separator.isHidden = true - validityMenu.isHidden = true #endif } private func populateExclusionsMenuItems() { exclusionsMenu.removeAllItems() - for item in NetworkProtectionTunnelController.exclusionList { + for item in settings.exclusionList { let menuItem: NSMenuItem switch item { case .section(let title): @@ -313,6 +371,11 @@ final class NetworkProtectionDebugMenu: NSMenu { } exclusionsMenu.addItem(menuItem) } + + // Only allow testers to enter a custom code if they're on the waitlist, to simulate the correct path through the flow + let waitlist = NetworkProtectionWaitlist() + enterWaitlistInviteCodeItem.isEnabled = waitlist.waitlistStorage.isOnWaitlist || waitlist.waitlistStorage.isInvited + } // MARK: - Menu State Update @@ -321,77 +384,162 @@ final class NetworkProtectionDebugMenu: NSMenu { updatePreferredServerMenu() updateRekeyValidityMenu() updateNetworkProtectionMenuItemsState() + updateNetworkProtectionItems() } private func updatePreferredServerMenu() { - guard let menu = preferredServerMenu else { - assertionFailure("Outlet not connected for preferredServerMenu") - return - } + let selectedServer = settings.selectedServer - let selectedServerName = debugUtilities.selectedServerName() + switch selectedServer { + case .automatic: + preferredServerMenu.items.first?.state = .on + case .endpoint(let selectedServerName): + preferredServerMenu.items.first?.state = .off - if selectedServerName == nil { - menu.items.first?.state = .on - } else { - menu.items.first?.state = .off - } + // We're skipping the first two items because they're the automatic menu item and + // the separator line. + let serverItems = preferredServerMenu.items.dropFirst(2) - // We're skipping the first two items because they're the automatic menu item and - // the separator line. - let serverItems = menu.items.dropFirst(2) - - for item in serverItems { - if let selectedServerName, - item.title.hasPrefix(selectedServerName) { - item.state = .on - } else { - item.state = .off + for item in serverItems { + if item.title.hasPrefix(selectedServerName) { + item.state = .on + } else { + item.state = .off + } } } } private func updateRekeyValidityMenu() { - guard let menu = registrationKeyValidityMenu else { - assertionFailure("Outlet not connected for preferredServerMenu") - return + switch settings.registrationKeyValidity { + case .automatic: + registrationKeyValidityMenu.items.first?.state = .on + case .custom(let timeInterval): + registrationKeyValidityMenu.items.first?.state = .off + + // We're skipping the first two items because they're the automatic menu item and + // the separator line. + let serverItems = registrationKeyValidityMenu.items.dropFirst(2) + + for item in serverItems { + if item.representedObject as? TimeInterval == timeInterval { + item.state = .on + } else { + item.state = .off + } + } } + } - let selectedValidity = debugUtilities.registrationKeyValidity + private func updateNetworkProtectionMenuItemsState() { + shouldEnforceRoutesMenuItem.state = settings.enforceRoutes ? .on : .off + shouldIncludeAllNetworksMenuItem.state = settings.includeAllNetworks ? .on : .off + excludeLocalNetworksMenuItem.state = settings.excludeLocalNetworks ? .on : .off + } + + private func updateNetworkProtectionItems() { + let waitlistStorage = WaitlistKeychainStore(waitlistIdentifier: NetworkProtectionWaitlist.identifier) + waitlistTokenItem.title = "Waitlist Token: \(waitlistStorage.getWaitlistToken() ?? "N/A")" + waitlistInviteCodeItem.title = "Waitlist Invite Code: \(waitlistStorage.getWaitlistInviteCode() ?? "N/A")" - if selectedValidity == nil { - menu.items.first?.state = .on + if let timestamp = waitlistStorage.getWaitlistTimestamp() { + waitlistTimestampItem.title = "Waitlist Timestamp: \(String(describing: timestamp))" } else { - menu.items.first?.state = .off + waitlistTimestampItem.title = "Waitlist Timestamp: N/A" } - // We're skipping the first two items because they're the automatic menu item and - // the separator line. - let serverItems = menu.items.dropFirst(2) + let accepted = UserDefaults().bool(forKey: UserDefaultsWrapper.Key.networkProtectionTermsAndConditionsAccepted.rawValue) + waitlistTermsAndConditionsAcceptedItem.title = "T&C Accepted: \(accepted ? "Yes" : "No")" + } + + // MARK: Waitlist - for item in serverItems { - if item.representedObject as? TimeInterval == selectedValidity { - item.state = .on - } else { - item.state = .off - } + @objc func sendNetworkProtectionWaitlistAvailableNotification(_ sender: Any?) { + NetworkProtectionWaitlist().sendInviteCodeAvailableNotification() + } + + @objc func resetNetworkProtectionActivationDate(_ sender: Any?) { + overrideNetworkProtectionActivationDate(to: nil) + } + + @objc func resetNetworkProtectionRemoteMessages(_ sender: Any?) { + DefaultNetworkProtectionRemoteMessagingStorage().removeStoredAndDismissedMessages() + DefaultNetworkProtectionRemoteMessaging(minimumRefreshInterval: 0).resetLastRefreshTimestamp() + } + + @objc func overrideNetworkProtectionActivationDateToNow(_ sender: Any?) { + overrideNetworkProtectionActivationDate(to: Date()) + } + + @objc func overrideNetworkProtectionActivationDateTo5DaysAgo(_ sender: Any?) { + overrideNetworkProtectionActivationDate(to: Date.daysAgo(5)) + } + + @objc func overrideNetworkProtectionActivationDateTo10DaysAgo(_ sender: Any?) { + overrideNetworkProtectionActivationDate(to: Date.daysAgo(10)) + } + + private func overrideNetworkProtectionActivationDate(to date: Date?) { + let store = DefaultWaitlistActivationDateStore() + + if let date { + store.updateActivationDate(date) + } else { + store.removeDates() } } - private func updateNetworkProtectionMenuItemsState() { - let controller = NetworkProtectionTunnelController() + @objc func resetNetworkProtectionWaitlistState(_ sender: Any?) { + NetworkProtectionWaitlist().waitlistStorage.deleteWaitlistState() + UserDefaults().removeObject(forKey: UserDefaultsWrapper.Key.networkProtectionTermsAndConditionsAccepted.rawValue) + NotificationCenter.default.post(name: .networkProtectionWaitlistAccessChanged, object: nil) + } - shouldEnforceRoutesMenuItem.state = controller.shouldEnforceRoutes ? .on : .off - shouldIncludeAllNetworksMenuItem.state = controller.shouldIncludeAllNetworks ? .on : .off - connectOnLogInMenuItem.state = controller.shouldAutoConnectOnLogIn ? .on : .off + @objc func resetNetworkProtectionTermsAndConditionsAcceptance(_ sender: Any?) { + UserDefaults().removeObject(forKey: UserDefaultsWrapper.Key.networkProtectionTermsAndConditionsAccepted.rawValue) + NotificationCenter.default.post(name: .networkProtectionWaitlistAccessChanged, object: nil) + } + + @objc func showNetworkProtectionInviteCodePrompt(_ sender: Any?) { + let code = getInviteCode() - excludeLocalNetworksMenuItem.state = controller.shouldExcludeLocalRoutes ? .on : .off + Task { + do { + let redeemer = NetworkProtectionCodeRedemptionCoordinator() + try await redeemer.redeem(code) + NetworkProtectionWaitlist().waitlistStorage.store(inviteCode: code) + NotificationCenter.default.post(name: .networkProtectionWaitlistAccessChanged, object: nil) + } catch { + // Do nothing here, this is just a debug menu + } + } } + private func getInviteCode() -> String { + let alert = NSAlert() + alert.addButton(withTitle: "Use Invite Code") + alert.addButton(withTitle: "Cancel") + alert.messageText = "Enter Invite Code" + alert.informativeText = "Please grab a Network Protection invite code from Asana and enter it here." + + let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 24)) + alert.accessoryView = textField + + let response = alert.runModal() + + if response == .alertFirstButtonReturn { + return textField.stringValue + } else { + return "" + } + } } + extension NetworkProtectionDebugMenu: NSMenuDelegate { func menuNeedsUpdate(_ menu: NSMenu) { + // Temporarily disabled: https://app.asana.com/0/0/1205766100762904/f + /* if menu === exclusionsMenu { let controller = NetworkProtectionTunnelController() for item in menu.items { @@ -401,8 +549,14 @@ extension NetworkProtectionDebugMenu: NSMenuDelegate { item.isEnabled = !(controller.shouldEnforceRoutes && route == "10.0.0.0/8") } } + */ } +} +#if DEBUG +#Preview { + return MenuPreview(menu: NetworkProtectionDebugMenu()) } +#endif #endif diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugUtilities.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugUtilities.swift index 9b0d941b82..4c2babed28 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugUtilities.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugUtilities.swift @@ -25,36 +25,28 @@ import NetworkProtectionUI import NetworkExtension import SystemExtensions import LoginItems +import NetworkProtectionIPC /// Utility code to help implement our debug menu options for Network Protection. /// final class NetworkProtectionDebugUtilities { - // MARK: - Registration Key Validity - - @UserDefaultsWrapper(key: .networkProtectionRegistrationKeyValidity, defaultValue: nil) - var registrationKeyValidity: TimeInterval? { - didSet { - Task { - await sendRegistrationKeyValidityToProvider() - } - } - } - - private let networkProtectionFeatureDisabler = NetworkProtectionFeatureDisabler() + private let ipcClient: TunnelControllerIPCClient + private let networkProtectionFeatureDisabler: NetworkProtectionFeatureDisabler // MARK: - Login Items Management private let loginItemsManager: LoginItemsManager - // MARK: - Server Selection - - private let selectedServerStore = NetworkProtectionSelectedServerUserDefaultsStore() - // MARK: - Initializers init(loginItemsManager: LoginItemsManager = .init()) { self.loginItemsManager = loginItemsManager + + let ipcClient = TunnelControllerIPCClient(machServiceName: Bundle.main.vpnMenuAgentBundleId) + + self.ipcClient = ipcClient + self.networkProtectionFeatureDisabler = NetworkProtectionFeatureDisabler(ipcClient: ipcClient) } // MARK: - Debug commands for the extension @@ -71,52 +63,16 @@ final class NetworkProtectionDebugUtilities { } func removeSystemExtensionAndAgents() async throws { + await networkProtectionFeatureDisabler.resetAllStateForVPNApp(uninstallSystemExtension: true) networkProtectionFeatureDisabler.disableLoginItems() - try await networkProtectionFeatureDisabler.disableSystemExtension() } func sendTestNotificationRequest() async throws { - guard let activeSession = try? await ConnectionSessionUtilities.activeSession() else { - return - } - - try? activeSession.sendProviderMessage(.triggerTestNotification) - } - - // MARK: - Registation Key - - private func sendRegistrationKeyValidityToProvider() async { - guard let activeSession = try? await ConnectionSessionUtilities.activeSession() else { - return - } - - try? activeSession.sendProviderMessage(.setKeyValidity(registrationKeyValidity)) + await ipcClient.debugCommand(.sendTestNotification) } func expireRegistrationKeyNow() async { - guard let activeSession = try? await ConnectionSessionUtilities.activeSession() else { - return - } - - try? activeSession.sendProviderMessage(.expireRegistrationKey) - } - - // MARK: - Server Selection - - func selectedServerName() -> String? { - selectedServerStore.selectedServer.stringValue - } - - func setSelectedServer(selectedServer: SelectedNetworkProtectionServer) { - selectedServerStore.selectedServer = selectedServer - - Task { - guard let activeSession = try? await ConnectionSessionUtilities.activeSession() else { - return - } - - try? activeSession.sendProviderMessage(.setSelectedServer(selectedServer.stringValue)) - } + await ipcClient.debugCommand(.expireRegistrationKey) } } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift index 118c29cdaa..ee11210d3f 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift @@ -22,6 +22,7 @@ import AppKit import Combine import Foundation import NetworkProtection +import NetworkProtectionIPC import NetworkProtectionUI /// Model for managing the NetP button in the Nav Bar. @@ -30,9 +31,15 @@ final class NetworkProtectionNavBarButtonModel: NSObject, ObservableObject { private let networkProtectionStatusReporter: NetworkProtectionStatusReporter private var status: NetworkProtection.ConnectionStatus = .disconnected - private let popovers: NavigationBarPopovers + private let popoverManager: NetworkProtectionNavBarPopoverManager private let waitlistActivationDateStore: DefaultWaitlistActivationDateStore + // MARK: - IPC + + public var ipcClient: TunnelControllerIPCClient { + popoverManager.ipcClient + } + // MARK: - Subscriptions private var statusChangeCancellable: AnyCancellable? @@ -67,26 +74,25 @@ final class NetworkProtectionNavBarButtonModel: NSObject, ObservableObject { // MARK: - Initialization - init(popovers: NavigationBarPopovers, + init(popoverManager: NetworkProtectionNavBarPopoverManager, pinningManager: PinningManager = LocalPinningManager.shared, statusReporter: NetworkProtectionStatusReporter? = nil, iconProvider: IconProvider = NavigationBarIconProvider()) { - let statusObserver = ConnectionStatusObserverThroughSession(platformNotificationCenter: NSWorkspace.shared.notificationCenter, - platformDidWakeNotification: NSWorkspace.didWakeNotification) - let statusInfoObserver = ConnectionServerInfoObserverThroughSession(platformNotificationCenter: NSWorkspace.shared.notificationCenter, - platformDidWakeNotification: NSWorkspace.didWakeNotification) - let connectionErrorObserver = ConnectionErrorObserverThroughSession(platformNotificationCenter: NSWorkspace.shared.notificationCenter, - platformDidWakeNotification: NSWorkspace.didWakeNotification) - self.networkProtectionStatusReporter = statusReporter ?? DefaultNetworkProtectionStatusReporter( - statusObserver: statusObserver, - serverInfoObserver: statusInfoObserver, - connectionErrorObserver: connectionErrorObserver, - connectivityIssuesObserver: ConnectivityIssueObserverThroughDistributedNotifications(), - controllerErrorMessageObserver: ControllerErrorMesssageObserverThroughDistributedNotifications() + let vpnBundleID = Bundle.main.vpnMenuAgentBundleId + self.popoverManager = popoverManager + + let ipcClient = popoverManager.ipcClient + + self.networkProtectionStatusReporter = statusReporter + ?? DefaultNetworkProtectionStatusReporter( + statusObserver: ipcClient.connectionStatusObserver, + serverInfoObserver: ipcClient.serverInfoObserver, + connectionErrorObserver: ipcClient.connectionErrorObserver, + connectivityIssuesObserver: ConnectivityIssueObserverThroughDistributedNotifications(), + controllerErrorMessageObserver: ControllerErrorMesssageObserverThroughDistributedNotifications() ) self.iconPublisher = NetworkProtectionIconPublisher(statusReporter: networkProtectionStatusReporter, iconProvider: iconProvider) - self.popovers = popovers self.pinningManager = pinningManager isPinned = pinningManager.isPinned(.networkProtection) @@ -185,7 +191,7 @@ final class NetworkProtectionNavBarButtonModel: NSObject, ObservableObject { } guard !isPinned, - !popovers.isNetworkProtectionPopoverShown else { + !popoverManager.isShown else { showButton = true return } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift new file mode 100644 index 0000000000..4a66937ea2 --- /dev/null +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarPopoverManager.swift @@ -0,0 +1,100 @@ +// +// NetworkProtectionNavBarPopoverModel.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import NetworkProtection +import NetworkProtectionIPC +import NetworkProtectionUI + +#if NETWORK_PROTECTION +final class NetworkProtectionNavBarPopoverManager { + private var networkProtectionPopover: NetworkProtectionPopover? + let ipcClient: TunnelControllerIPCClient + + init(ipcClient: TunnelControllerIPCClient) { + self.ipcClient = ipcClient + } + + var isShown: Bool { +#if NETWORK_PROTECTION + networkProtectionPopover?.isShown ?? false +#else + return false +#endif + } + + private func show(_ popover: NSPopover, positionedBelow view: NSView) { + view.isHidden = false + + popover.show(positionedBelow: view.bounds.insetFromLineOfDeath(flipped: view.isFlipped), in: view) + } + + func show(positionedBelow view: NSView, withDelegate delegate: NSPopoverDelegate) { + + let popover = networkProtectionPopover ?? { + + let controller = NetworkProtectionIPCTunnelController(ipcClient: ipcClient) + + let statusReporter = DefaultNetworkProtectionStatusReporter( + statusObserver: ipcClient.connectionStatusObserver, + serverInfoObserver: ipcClient.serverInfoObserver, + connectionErrorObserver: ipcClient.connectionErrorObserver, + connectivityIssuesObserver: ConnectivityIssueObserverThroughDistributedNotifications(), + controllerErrorMessageObserver: ControllerErrorMesssageObserverThroughDistributedNotifications() + ) + + let menuItems = [ + NetworkProtectionStatusView.Model.MenuItem( + name: UserText.networkProtectionNavBarStatusViewShareFeedback, + action: { + let appLauncher = AppLauncher(appBundleURL: Bundle.main.bundleURL) + await appLauncher.launchApp(withCommand: .shareFeedback) + }) + ] + + let onboardingStatusPublisher = UserDefaults.shared.networkProtectionOnboardingStatusPublisher + + let popover = NetworkProtectionPopover(controller: controller, onboardingStatusPublisher: onboardingStatusPublisher, statusReporter: statusReporter, menuItems: menuItems) + popover.delegate = delegate + + networkProtectionPopover = popover + return popover + }() + + show(popover, positionedBelow: view) + } + + func toggle(positionedBelow view: NSView, withDelegate delegate: NSPopoverDelegate) { + if let networkProtectionPopover, networkProtectionPopover.isShown { + networkProtectionPopover.close() + } else { + let featureVisibility = DefaultNetworkProtectionVisibility() + + if featureVisibility.isNetworkProtectionVisible() { + show(positionedBelow: view, withDelegate: delegate) + } else { + featureVisibility.disableForWaitlistUsers() + } + } + } + + func close() { + networkProtectionPopover?.close() + } +} +#endif diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionOnboardingMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionOnboardingMenu.swift index f1a7dbbfcb..a0fa2d8621 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionOnboardingMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionOnboardingMenu.swift @@ -16,52 +16,70 @@ // limitations under the License. // +#if NETWORK_PROTECTION + import AppKit import Foundation - -#if !NETWORK_PROTECTION - -@objc -final class NetworkProtectionOnboardingMenu: NSMenu { -} - -#else - import NetworkProtection import NetworkProtectionUI +import SwiftUI /// Implements the logic for Network Protection's onboarding menu. /// -@objc @MainActor final class NetworkProtectionOnboardingMenu: NSMenu { - @IBOutlet weak var resetMenuItem: NSMenuItem! - @IBOutlet weak var setStatusCompletedMenuItem: NSMenuItem! - @IBOutlet weak var setStatusAllowSystemExtensionMenuItem: NSMenuItem! - @IBOutlet weak var setStatusAllowVPNConfigurationMenuItem: NSMenuItem! + + private let resetMenuItem = NSMenuItem(title: "Reset Onboarding Status", + action: #selector(NetworkProtectionOnboardingMenu.reset)) + private let setStatusCompletedMenuItem = NSMenuItem(title: "Onboarding Completed", + action: #selector(NetworkProtectionOnboardingMenu.setStatusCompleted)) + private let setStatusAllowSystemExtensionMenuItem = NSMenuItem(title: "Allow System Extension", + action: #selector(NetworkProtectionOnboardingMenu.setStatusAllowSystemExtension)) + private let setStatusAllowVPNConfigurationMenuItem = NSMenuItem(title: "Allow VPN Configuration", + action: #selector(NetworkProtectionOnboardingMenu.setStatusAllowVPNConfiguration)) @UserDefaultsWrapper(key: .networkProtectionOnboardingStatusRawValue, defaultValue: OnboardingStatus.default.rawValue, defaults: .shared) var onboardingStatus: OnboardingStatus.RawValue - @IBAction - func reset(sender: NSMenuItem) { + init() { + super.init(title: "") + + buildItems { + resetMenuItem.targetting(self) + NSMenuItem.separator() + NSMenuItem(title: "Set Status") { + setStatusCompletedMenuItem.targetting(self) + setStatusAllowSystemExtensionMenuItem.targetting(self) + setStatusAllowVPNConfigurationMenuItem.targetting(self) + } + } + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func reset(sender: NSMenuItem) { onboardingStatus = OnboardingStatus.default.rawValue } - @IBAction - func setStatusCompleted(sender: NSMenuItem) { + @objc func setStatusCompleted(sender: NSMenuItem) { onboardingStatus = OnboardingStatus.completed.rawValue } - @IBAction - func setStatusAllowSystemExtension(sender: NSMenuItem) { + @objc func setStatusAllowSystemExtension(sender: NSMenuItem) { onboardingStatus = OnboardingStatus.isOnboarding(step: .userNeedsToAllowExtension).rawValue } - @IBAction - func setStatusAllowVPNConfiguration(sender: NSMenuItem) { + @objc func setStatusAllowVPNConfiguration(sender: NSMenuItem) { onboardingStatus = OnboardingStatus.isOnboarding(step: .userNeedsToAllowVPNConfiguration).rawValue } } +#if DEBUG +#Preview { + return MenuPreview(menu: NetworkProtectionOnboardingMenu()) +} +#endif + #endif diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionSimulateFailureMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionSimulateFailureMenu.swift index e770d08855..f495cac5d6 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionSimulateFailureMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionSimulateFailureMenu.swift @@ -16,57 +16,69 @@ // limitations under the License. // +#if NETWORK_PROTECTION + import AppKit import Foundation - -#if !NETWORK_PROTECTION - -@objc -final class NetworkProtectionSimulateFailureMenu: NSMenu { -} - -#else - import NetworkProtection +import SwiftUI /// Implements the logic for Network Protection's simulate failures menu. /// -@objc @MainActor final class NetworkProtectionSimulateFailureMenu: NSMenu { - @IBOutlet weak var simulateControllerFailureMenuItem: NSMenuItem! - @IBOutlet weak var simulateTunnelFailureMenuItem: NSMenuItem! - @IBOutlet weak var simulateTunnelCrashMenuItem: NSMenuItem! - @IBOutlet weak var simulateConnectionInterruptionMenuItem: NSMenuItem! + + private let simulateControllerFailureMenuItem = NSMenuItem(title: "Enable NetP > Controller Failure", + action: #selector(NetworkProtectionSimulateFailureMenu.simulateControllerFailure)) + private let simulateTunnelFailureMenuItem = NSMenuItem(title: "Tunnel Failure", + action: #selector(NetworkProtectionSimulateFailureMenu.simulateTunnelFailure)) + private let simulateTunnelCrashMenuItem = NSMenuItem(title: "Tunnel Crash", + action: #selector(NetworkProtectionSimulateFailureMenu.simulateTunnelCrash)) + private let simulateConnectionInterruptionMenuItem = NSMenuItem(title: "Connection Interruption", + action: #selector(NetworkProtectionSimulateFailureMenu.simulateConnectionInterruption)) + + init() { + super.init(title: "") + buildItems { + simulateControllerFailureMenuItem.targetting(self) + simulateTunnelFailureMenuItem.targetting(self) + simulateTunnelCrashMenuItem.targetting(self) + simulateConnectionInterruptionMenuItem.targetting(self) + } + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } private var simulationOptions: NetworkProtectionSimulationOptions { - NetworkProtectionTunnelController.simulationOptions + // Temporarily disabled: https://app.asana.com/0/0/1205766100762904/f + NetworkProtectionSimulationOptions() } /// Simulates a controller failure the next time Network Protection is started. /// - @IBAction - func simulateControllerFailure(_ menuItem: NSMenuItem) { + @objc func simulateControllerFailure(_ menuItem: NSMenuItem) { simulationOptions.setEnabled(menuItem.state == .off, option: .controllerFailure) } /// Simulates a tunnel failure the next time Network Protection is started. /// - @IBAction - func simulateTunnelFailure(_ menuItem: NSMenuItem) { - simulateFailure(NetworkProtectionTunnelController().toggleShouldSimulateTunnelFailure) + @objc func simulateTunnelFailure(_ menuItem: NSMenuItem) { + // Temporarily disabled: https://app.asana.com/0/0/1205766100762904/f + // simulateFailure(NetworkProtectionTunnelController().toggleShouldSimulateTunnelFailure) } /// Simulates a fatal error on the tunnel the next time Network Protection is started. /// - @IBAction - func simulateTunnelCrash(_ menuItem: NSMenuItem) { - simulateFailure(NetworkProtectionTunnelController().toggleShouldSimulateTunnelFatalError) + @objc func simulateTunnelCrash(_ menuItem: NSMenuItem) { + // Temporarily disabled: https://app.asana.com/0/0/1205766100762904/f + // simulateFailure(NetworkProtectionTunnelController().toggleShouldSimulateTunnelFatalError) } - @IBAction - func simulateConnectionInterruption(_ menuItem: NSMenuItem) { - simulateFailure(NetworkProtectionTunnelController().toggleShouldSimulateConnectionInterruption) + @objc func simulateConnectionInterruption(_ menuItem: NSMenuItem) { + // Temporarily disabled: https://app.asana.com/0/0/1205766100762904/f + // simulateFailure(NetworkProtectionTunnelController().toggleShouldSimulateConnectionInterruption) } private func simulateFailure(_ simulationFunction: @escaping () async throws -> Void) { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index db5ecd841c..bc5ac23bc7 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -25,15 +25,22 @@ import Common import NetworkExtension import NetworkProtection import NetworkProtectionUI +import SystemExtensionManager import SystemExtensions import Networking -import LoginItems +import PixelKit typealias NetworkProtectionStatusChangeHandler = (NetworkProtection.ConnectionStatus) -> Void typealias NetworkProtectionConfigChangeHandler = () -> Void final class NetworkProtectionTunnelController: NetworkProtection.TunnelController { + let settings: TunnelSettings + + // MARK: - Combine Cancellables + + private var cancellables = Set() + // MARK: - Debug Helpers /// Debug simulation options to aid with testing NetP. @@ -55,43 +62,18 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle /// Auth token store private let tokenStore: NetworkProtectionTokenStore - // MARK: - Login Items - - private let loginItemsManager = LoginItemsManager() - // MARK: - Debug Options Support - private let debugUtilities = NetworkProtectionDebugUtilities() + private let networkExtensionBundleID: String + private let networkExtensionController: NetworkExtensionController - /// Kill Switch: Enable enforceRoutes flag - /// - /// Applies enforceRoutes setting, sets up excludedRoutes in MacPacketTunnelProvider and disables disconnect on failure - @MainActor - @UserDefaultsWrapper(key: .networkProtectionShouldEnforceRoutes, defaultValue: NetworkProtectionUserDefaultsConstants.shouldEnforceRoutes) - private(set) var shouldEnforceRoutes: Bool - - @MainActor - @UserDefaultsWrapper(key: .networkProtectionShouldIncludeAllNetworks, defaultValue: NetworkProtectionUserDefaultsConstants.shouldIncludeAllNetworks) - private(set) var shouldIncludeAllNetworks + // MARK: - User Defaults /// Test setting to exclude duckduckgo route from VPN @MainActor @UserDefaultsWrapper(key: .networkProtectionExcludedRoutes, defaultValue: [:]) private(set) var excludedRoutesPreferences: [String: Bool] - @MainActor - @UserDefaultsWrapper(key: .networkProtectionShouldExcludeLocalRoutes, defaultValue: NetworkProtectionUserDefaultsConstants.shouldExcludeLocalRoutes) - private(set) var shouldExcludeLocalRoutes: Bool - - /// When enabled VPN connection will be automatically initiated by DuckDuckGoAgentAppDelegate on launch even if disconnected manually (Always On rule disabled) - @MainActor - @UserDefaultsWrapper(key: .networkProtectionConnectOnLogIn, defaultValue: NetworkProtectionUserDefaultsConstants.shouldConnectOnLogIn, defaults: .shared) - private(set) var shouldAutoConnectOnLogIn: Bool - - @MainActor - @UserDefaultsWrapper(key: .networkProtectionConnectionTesterEnabled, defaultValue: NetworkProtectionUserDefaultsConstants.isConnectionTesterEnabled, defaults: .shared) - private(set) var isConnectionTesterEnabled: Bool - @UserDefaultsWrapper(key: .networkProtectionOnboardingStatusRawValue, defaultValue: OnboardingStatus.default.rawValue, defaults: .shared) private(set) var onboardingStatusRawValue: OnboardingStatus.RawValue @@ -106,7 +88,10 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle /// a VPN-access popup to the user. /// private func loadTunnelManager() async -> NETunnelProviderManager? { - try? await NETunnelProviderManager.loadAllFromPreferences().first + let tunnels = try? await NETunnelProviderManager.loadAllFromPreferences() + return tunnels?.first { + ($0.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == networkExtensionBundleID + } } private func loadOrMakeTunnelManager() async throws -> NETunnelProviderManager { @@ -130,12 +115,96 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle /// - notificationCenter: (meant for testing) the notification center that this object will use. /// - logger: (meant for testing) the logger that this object will use. /// - init(notificationCenter: NotificationCenter = .default, + init(networkExtensionBundleID: String, + networkExtensionController: NetworkExtensionController, + settings: TunnelSettings, + notificationCenter: NotificationCenter = .default, tokenStore: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), logger: NetworkProtectionLogger = DefaultNetworkProtectionLogger()) { self.logger = logger + self.networkExtensionBundleID = networkExtensionBundleID + self.networkExtensionController = networkExtensionController + self.settings = settings self.tokenStore = tokenStore + + subscribeToSettingsChanges() + } + + // MARK: - Tunnel Settings + + private func subscribeToSettingsChanges() { + settings.changePublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] change in + guard let self else { return } + + Task { + // Offer the extension a chance to handle the settings change + try? await self.relaySettingsChange(change) + + // Handle the settings change right in the controller + try? await self.handleSettingsChange(change) + } + } + .store(in: &cancellables) + } + + /// This is where the tunnel has a chance to handle the settings change locally. + /// + /// The extension can also handle these changes so not everything needs to be handled here. + /// + private func handleSettingsChange(_ change: TunnelSettings.Change) async throws { + switch change { + case .setIncludeAllNetworks(let includeAllNetworks): + try await handleSetIncludeAllNetworks(includeAllNetworks) + case .setEnforceRoutes(let enforceRoutes): + try await handleSetEnforceRoutes(enforceRoutes) + case .setExcludeLocalNetworks(let excludeLocalNetworks): + try await handleSetExcludeLocalNetworks(excludeLocalNetworks) + case .setRegistrationKeyValidity, + .setSelectedServer: + // Intentional no-op as this is handled by the extension + break + } + } + + private func handleSetIncludeAllNetworks(_ includeAllNetworks: Bool) async throws { + guard let tunnelManager = await loadTunnelManager(), + tunnelManager.protocolConfiguration?.includeAllNetworks == !includeAllNetworks else { + return + } + + try await setupAndSave(tunnelManager) + } + + private func handleSetEnforceRoutes(_ enforceRoutes: Bool) async throws { + guard let tunnelManager = await loadTunnelManager(), + tunnelManager.protocolConfiguration?.enforceRoutes == !enforceRoutes else { + return + } + + try await setupAndSave(tunnelManager) + } + + private func handleSetExcludeLocalNetworks(_ excludeLocalNetworks: Bool) async throws { + guard let tunnelManager = await loadTunnelManager(), + tunnelManager.protocolConfiguration?.excludeLocalNetworks == !excludeLocalNetworks else { + return + } + + try await setupAndSave(tunnelManager) + updateRoutes() + } + + private func relaySettingsChange(_ change: TunnelSettings.Change) async throws { + guard await isConnected, + let activeSession = try await ConnectionSessionUtilities.activeSession(networkExtensionBundleID: networkExtensionBundleID) else { return } + + let errorMessage: ExtensionMessageString? = try await activeSession.sendProviderRequest(.changeTunnelSetting(change)) + if let errorMessage { + throw TunnelFailureError(errorDescription: errorMessage.value) + } } // MARK: - Tunnel Configuration @@ -166,11 +235,10 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle protocolConfiguration.disconnectOnSleep = false // kill switch - protocolConfiguration.enforceRoutes = shouldEnforceRoutes + protocolConfiguration.enforceRoutes = settings.enforceRoutes // this setting breaks Connection Tester - protocolConfiguration.includeAllNetworks = shouldIncludeAllNetworks - - protocolConfiguration.excludeLocalNetworks = shouldExcludeLocalRoutes + protocolConfiguration.includeAllNetworks = settings.includeAllNetworks + protocolConfiguration.excludeLocalNetworks = settings.excludeLocalNetworks return protocolConfiguration }() @@ -200,25 +268,47 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle // MARK: - Ensure things are working #if NETP_SYSTEM_EXTENSION - /// - Returns: `true` if the system extension and the background agent were activated successfully + /// Ensures that the system extension is activated if necessary. /// - private func ensureSystemExtensionIsActivated() async throws -> Bool { - var activated = false - - for try await event in SystemExtensionManager().activate() { - switch event { - case .waitingForUserApproval: - onboardingStatusRawValue = OnboardingStatus.isOnboarding(step: .userNeedsToAllowExtension).rawValue - case .activated: - self.controllerErrorStore.lastErrorMessage = nil - activated = true - case .willActivateAfterReboot: + private func activateSystemExtension(waitingForUserApproval: @escaping () -> Void) async throws { + do { + try await networkExtensionController.activateSystemExtension( + waitingForUserApproval: waitingForUserApproval) + } catch { + switch error { + case OSSystemExtensionError.requestSuperseded: + // Even if the installation request is superseded we want to show the message that tells the user + // to go to System Settings to allow the extension + controllerErrorStore.lastErrorMessage = UserText.networkProtectionSystemSettings + case SystemExtensionRequestError.unknownRequestResult: + controllerErrorStore.lastErrorMessage = UserText.networkProtectionUnknownActivationError + + PixelKit.fire( + NetworkProtectionPixelEvent.networkProtectionSystemExtensionUnknownActivationResult, + frequency: .standard, + includeAppVersionParameter: true) + case SystemExtensionRequestError.willActivateAfterReboot: controllerErrorStore.lastErrorMessage = UserText.networkProtectionPleaseReboot + default: + controllerErrorStore.lastErrorMessage = error.localizedDescription } + + return } - try? await Task.sleep(nanoseconds: 300 * NSEC_PER_MSEC) - return activated + self.controllerErrorStore.lastErrorMessage = nil + + // We'll only update to completed if we were showing the onboarding step to + // allow the system extension. Otherwise we may override the allow-VPN + // onboarding step. + // + // Additionally if the onboarding step was allowing the system extension, we won't + // start the tunnel at once, and instead require that the user enables the toggle. + // + if onboardingStatusRawValue == OnboardingStatus.isOnboarding(step: .userNeedsToAllowExtension).rawValue { + onboardingStatusRawValue = OnboardingStatus.isOnboarding(step: .userNeedsToAllowVPNConfiguration).rawValue + return + } } #endif @@ -245,31 +335,23 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle /// Starts the VPN connection used for Network Protection /// func start() async { - await start(enableLoginItems: true) - } - - func start(enableLoginItems: Bool) async { controllerErrorStore.lastErrorMessage = nil - do { #if NETP_SYSTEM_EXTENSION - guard try await ensureSystemExtensionIsActivated() else { - return - } - - // We'll only update to completed if we were showing the onboarding step to - // allow the system extension. Otherwise we may override the allow-VPN - // onboarding step. - // - // Additionally if the onboarding step was allowing the system extension, we won't - // start the tunnel at once, and instead require that the user enables the toggle. - // - if onboardingStatusRawValue == OnboardingStatus.isOnboarding(step: .userNeedsToAllowExtension).rawValue { - onboardingStatusRawValue = OnboardingStatus.isOnboarding(step: .userNeedsToAllowVPNConfiguration).rawValue - return + do { + try await activateSystemExtension { [weak self] in + // If we're waiting for user approval we wanna make sure the + // onboarding step is set correctly. This can be useful to + // help prevent the value from being de-synchronized. + self?.onboardingStatusRawValue = OnboardingStatus.isOnboarding(step: .userNeedsToAllowExtension).rawValue } + } catch { + await stop() + return + } #endif + do { let tunnelManager: NETunnelProviderManager do { @@ -283,10 +365,6 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle } onboardingStatusRawValue = OnboardingStatus.completed.rawValue - if enableLoginItems { - loginItemsManager.enableLoginItems(LoginItemsManager.networkProtectionLoginItems, log: .networkProtection) - } - switch tunnelManager.connection.status { case .invalid: throw StartError.connectionStatusInvalid @@ -296,11 +374,6 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle default: try await start(tunnelManager) } - } catch OSSystemExtensionError.requestSuperseded { - await stop() - // Even if the installation request is superseded we want to show the message that tells the user - // to go to System Settings to allow the extension - controllerErrorStore.lastErrorMessage = UserText.networkProtectionSystemSettings } catch { await stop() controllerErrorStore.lastErrorMessage = error.localizedDescription @@ -312,8 +385,11 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle options[NetworkProtectionOptionKey.activationAttemptId] = UUID().uuidString as NSString options[NetworkProtectionOptionKey.authToken] = try tokenStore.fetchToken() as NSString? - options[NetworkProtectionOptionKey.selectedServer] = debugUtilities.selectedServerName() as NSString? - options[NetworkProtectionOptionKey.keyValidity] = debugUtilities.registrationKeyValidity.map(String.init(describing:)) as NSString? + options[NetworkProtectionOptionKey.selectedServer] = settings.selectedServer.stringValue as? NSString + + if case .custom(let keyValidity) = settings.registrationKeyValidity { + options[NetworkProtectionOptionKey.keyValidity] = String(describing: keyValidity) as NSString + } if Self.simulationOptions.isEnabled(.tunnelFailure) { Self.simulationOptions.setEnabled(false, option: .tunnelFailure) @@ -366,7 +442,10 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle @MainActor func enableOnDemand(tunnelManager: NETunnelProviderManager) async throws { - tunnelManager.onDemandRules = [NEOnDemandRuleConnect(interfaceType: .any)] + let rule = NEOnDemandRuleConnect() + rule.interfaceTypeMatch = .any + + tunnelManager.onDemandRules = [rule] tunnelManager.isOnDemandEnabled = true try await tunnelManager.saveToPreferences() @@ -379,112 +458,16 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle try await tunnelManager.saveToPreferences() } - @MainActor - func enableEnforceRoutes() async throws { - shouldEnforceRoutes = true - - // calls setupAndSave where configuration is done - _=try await loadOrMakeTunnelManager() - } - - @MainActor - func disableEnforceRoutes() async throws { - shouldEnforceRoutes = false - - guard let tunnelManager = await loadTunnelManager(), - tunnelManager.protocolConfiguration?.enforceRoutes == true else { return } - - try await setupAndSave(tunnelManager) - } - - @MainActor - func enableIncludeAllNetworks() async throws { - shouldIncludeAllNetworks = true - - // calls setupAndSave where configuration is done - _=try await loadOrMakeTunnelManager() - } - - @MainActor - func disableIncludeAllNetworks() async throws { - shouldIncludeAllNetworks = false - - guard let tunnelManager = await loadTunnelManager(), - tunnelManager.protocolConfiguration?.includeAllNetworks == true else { return } - - try await setupAndSave(tunnelManager) - } - - @MainActor - func toggleShouldEnforceRoutes() { - shouldEnforceRoutes.toggle() - - // update configuration if connected - Task { [shouldEnforceRoutes] in - guard await isConnected else { return } - - if shouldEnforceRoutes { - try await enableEnforceRoutes() - } else { - try await disableEnforceRoutes() - } - } - } - - @MainActor - func toggleShouldIncludeAllNetworks() { - shouldIncludeAllNetworks.toggle() - - // update configuration if connected - Task { [shouldIncludeAllNetworks] in - guard await isConnected else { return } - - if shouldIncludeAllNetworks { - try await enableIncludeAllNetworks() - } else { - try await disableIncludeAllNetworks() - } - } - } - - // TO BE Refactored when the Exclusion List is added - enum ExclusionListItem { - case section(String) - case exclusion(range: NetworkProtection.IPAddressRange, description: String? = nil, `default`: Bool) - } - static let exclusionList: [ExclusionListItem] = [ - .section("IPv4 Local Routes"), - - .exclusion(range: "10.0.0.0/8" /* 255.0.0.0 */, description: "disabled for enforceRoutes", default: true), - .exclusion(range: "172.16.0.0/12" /* 255.240.0.0 */, default: true), - .exclusion(range: "192.168.0.0/16" /* 255.255.0.0 */, default: true), - .exclusion(range: "169.254.0.0/16" /* 255.255.0.0 */, description: "Link-local", default: true), - .exclusion(range: "127.0.0.0/8" /* 255.0.0.0 */, description: "Loopback", default: true), - .exclusion(range: "224.0.0.0/4" /* 240.0.0.0 (corrected subnet mask) */, description: "Multicast", default: true), - .exclusion(range: "100.64.0.0/16" /* 255.255.0.0 */, description: "Shared Address Space", default: true), - - .section("IPv6 Local Routes"), - .exclusion(range: "fe80::/10", description: "link local", default: false), - .exclusion(range: "ff00::/8", description: "multicast", default: false), - .exclusion(range: "fc00::/7", description: "local unicast", default: false), - .exclusion(range: "::1/128", description: "loopback", default: false), - - .section("duckduckgo.com"), - .exclusion(range: "52.142.124.215/32", default: false), - .exclusion(range: "52.250.42.157/32", default: false), - .exclusion(range: "40.114.177.156/32", default: false), - ] - @MainActor private func excludedRoutes() -> [NetworkProtection.IPAddressRange] { - Self.exclusionList.compactMap { [excludedRoutesPreferences] item -> NetworkProtection.IPAddressRange? in + settings.exclusionList.compactMap { [excludedRoutesPreferences] item -> NetworkProtection.IPAddressRange? in guard case .exclusion(range: let range, description: _, default: let defaultValue) = item, excludedRoutesPreferences[range.stringRepresentation, default: defaultValue] == true else { return nil } // TO BE fixed: // when 10.11.12.1 DNS is used 10.0.0.0/8 should be included (not excluded) // but marking 10.11.12.1 as an Included Route breaks tunnel (probably these routes are conflicting) - if shouldEnforceRoutes && range == "10.0.0.0/8" { + if settings.enforceRoutes && range == "10.0.0.0/8" { return nil } @@ -498,12 +481,6 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle [] } - @MainActor - func toggleShouldExcludeLocalRoutes() { - shouldExcludeLocalRoutes.toggle() - updateRoutes() - } - @MainActor func setExcludedRoute(_ route: String, enabled: Bool) { excludedRoutesPreferences[route] = enabled @@ -513,7 +490,7 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle @MainActor func isExcludedRouteEnabled(_ route: String) -> Bool { guard let range = IPAddressRange(from: route), - let exclusionListItem = Self.exclusionList.first(where: { + let exclusionListItem = settings.exclusionList.first(where: { if case .exclusion(range: range, description: _, default: _) = $0 { return true } return false }), @@ -523,7 +500,7 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle return false } // TO BE fixed: see excludedRoutes() - if shouldEnforceRoutes && route == "10.0.0.0/8" { + if settings.enforceRoutes && route == "10.0.0.0/8" { return false } return excludedRoutesPreferences[route, default: defaultValue] @@ -538,16 +515,6 @@ final class NetworkProtectionTunnelController: NetworkProtection.TunnelControlle } } - @MainActor - func toggleShouldAutoConnectOnLogIn() { - shouldAutoConnectOnLogIn.toggle() - } - - @MainActor - func toggleConnectionTesterEnabled() { - isConnectionTesterEnabled.toggle() - } - struct TunnelFailureError: LocalizedError { let errorDescription: String? } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionWaitlistMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift similarity index 58% rename from DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionWaitlistMenu.swift rename to DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift index a863a88503..3f5199d7ad 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionWaitlistMenu.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift @@ -1,5 +1,5 @@ // -// NetworkProtectionWaitlistMenu.swift +// NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -16,31 +16,24 @@ // limitations under the License. // +#if NETWORK_PROTECTION + import AppKit import Foundation - -#if !NETWORK_PROTECTION - -@objc -final class NetworkProtectionWaitlistMenu: NSMenu { -} - -#else - import NetworkProtection import NetworkProtectionUI +import SwiftUI /// Implements the logic for Network Protection's simulate failures menu. /// -@objc @MainActor -final class NetworkProtectionWaitlistMenu: NSMenu { +final class NetworkProtectionWaitlistFeatureFlagOverridesMenu: NSMenu { // MARK: - Waitlist Active Properties - @IBOutlet weak var waitlistActiveUseRemoteValueMenuItem: NSMenuItem! - @IBOutlet weak var waitlistActiveOverrideONMenuItem: NSMenuItem! - @IBOutlet weak var waitlistActiveOverrideOFFMenuItem: NSMenuItem! + private let waitlistActiveUseRemoteValueMenuItem: NSMenuItem + private let waitlistActiveOverrideONMenuItem: NSMenuItem + private let waitlistActiveOverrideOFFMenuItem: NSMenuItem @UserDefaultsWrapper(key: .networkProtectionWaitlistActiveOverrideRawValue, defaultValue: WaitlistOverride.default.rawValue, @@ -49,37 +42,65 @@ final class NetworkProtectionWaitlistMenu: NSMenu { // MARK: - Waitlist Enabled Properties - @IBOutlet weak var waitlistEnabledUseRemoteValueMenuItem: NSMenuItem! - @IBOutlet weak var waitlistEnabledOverrideONMenuItem: NSMenuItem! - @IBOutlet weak var waitlistEnabledOverrideOFFMenuItem: NSMenuItem! + private let waitlistEnabledUseRemoteValueMenuItem: NSMenuItem + private let waitlistEnabledOverrideONMenuItem: NSMenuItem + private let waitlistEnabledOverrideOFFMenuItem: NSMenuItem @UserDefaultsWrapper(key: .networkProtectionWaitlistEnabledOverrideRawValue, defaultValue: WaitlistOverride.default.rawValue, defaults: .shared) private var waitlistEnabledOverrideValue: Int + init() { + waitlistActiveUseRemoteValueMenuItem = NSMenuItem(title: "Remote Value", action: #selector(Self.waitlistEnabledUseRemoteValue)) + waitlistActiveOverrideONMenuItem = NSMenuItem(title: "ON", action: #selector(Self.waitlistEnabledOverrideON)) + waitlistActiveOverrideOFFMenuItem = NSMenuItem(title: "OFF", action: #selector(Self.waitlistEnabledOverrideOFF)) + + waitlistEnabledUseRemoteValueMenuItem = NSMenuItem(title: "Remote Value", action: #selector(Self.waitlistActiveUseRemoteValue)) + waitlistEnabledOverrideONMenuItem = NSMenuItem(title: "ON", action: #selector(Self.waitlistActiveOverrideON)) + waitlistEnabledOverrideOFFMenuItem = NSMenuItem(title: "OFF", action: #selector(Self.waitlistActiveOverrideOFF)) + + super.init(title: "") + buildItems { + NSMenuItem(title: "Reset Waitlist Overrides", action: #selector(Self.waitlistResetFeatureOverrides)).targetting(self) + NSMenuItem.separator() + + NSMenuItem(title: "Waitlist Enabled") { + waitlistActiveUseRemoteValueMenuItem.targetting(self) + waitlistActiveOverrideONMenuItem.targetting(self) + waitlistActiveOverrideOFFMenuItem.targetting(self) + } + + NSMenuItem(title: "Waitlist Active") { + waitlistEnabledUseRemoteValueMenuItem.targetting(self) + waitlistEnabledOverrideONMenuItem.targetting(self) + waitlistEnabledOverrideOFFMenuItem.targetting(self) + } + } + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + // MARK: - Misc IBActions - @IBAction - func waitlistResetFeatureOverrides(sender: NSMenuItem) { + @objc func waitlistResetFeatureOverrides(sender: NSMenuItem) { waitlistActiveOverrideValue = WaitlistOverride.default.rawValue waitlistEnabledOverrideValue = WaitlistOverride.default.rawValue } // MARK: - Waitlist Active IBActions - @IBAction - func waitlistActiveUseRemoteValue(sender: NSMenuItem) { + @objc func waitlistActiveUseRemoteValue(sender: NSMenuItem) { waitlistActiveOverrideValue = WaitlistOverride.useRemoteValue.rawValue } - @IBAction - func waitlistActiveOverrideON(sender: NSMenuItem) { + @objc func waitlistActiveOverrideON(sender: NSMenuItem) { waitlistActiveOverrideValue = WaitlistOverride.on.rawValue } - @IBAction - func waitlistActiveOverrideOFF(sender: NSMenuItem) { + @objc func waitlistActiveOverrideOFF(sender: NSMenuItem) { Task { @MainActor in guard case .alertFirstButtonReturn = await waitlistOFFAlert().runModal() else { return @@ -91,18 +112,15 @@ final class NetworkProtectionWaitlistMenu: NSMenu { // MARK: - Waitlist Enabled IBActions - @IBAction - func waitlistEnabledUseRemoteValue(sender: NSMenuItem) { + @objc func waitlistEnabledUseRemoteValue(sender: NSMenuItem) { waitlistEnabledOverrideValue = WaitlistOverride.useRemoteValue.rawValue } - @IBAction - func waitlistEnabledOverrideON(sender: NSMenuItem) { + @objc func waitlistEnabledOverrideON(sender: NSMenuItem) { waitlistEnabledOverrideValue = WaitlistOverride.on.rawValue } - @IBAction - func waitlistEnabledOverrideOFF(sender: NSMenuItem) { + @objc func waitlistEnabledOverrideOFF(sender: NSMenuItem) { Task { @MainActor in guard case .alertFirstButtonReturn = await waitlistOFFAlert().runModal() else { return @@ -143,4 +161,10 @@ final class NetworkProtectionWaitlistMenu: NSMenu { } } +#if DEBUG +#Preview { + return MenuPreview(menu: NetworkProtectionWaitlistFeatureFlagOverridesMenu()) +} +#endif + #endif diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift new file mode 100644 index 0000000000..e8b22a737b --- /dev/null +++ b/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/NetworkProtectionIPCTunnelController.swift @@ -0,0 +1,66 @@ +// +// NetworkProtectionIPCClient.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import NetworkProtection +import NetworkProtectionIPC + +final class NetworkProtectionIPCTunnelController: TunnelController { + + private let loginItemsManager: LoginItemsManager + private let ipcClient: TunnelControllerIPCClient + + init(loginItemsManager: LoginItemsManager = LoginItemsManager(), + ipcClient: TunnelControllerIPCClient) { + + self.loginItemsManager = loginItemsManager + self.ipcClient = ipcClient + } + + func start() async { + enableLoginItems() + + ipcClient.start() + } + + func stop() async { + enableLoginItems() + + ipcClient.stop() + } + + /// Queries Network Protection to know if its VPN is connected. + /// + /// - Returns: `true` if the VPN is connected, connecting or reasserting, and `false` otherwise. + /// + var isConnected: Bool { + get { + if case .connected = ipcClient.connectionStatusObserver.recentValue { + return true + } + + return false + } + } + + // MARK: - Login Items Manager + + private func enableLoginItems() { + loginItemsManager.enableLoginItems(LoginItemsManager.networkProtectionLoginItems, log: .networkProtection) + } +} diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 1c9a20c573..cea664b796 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -134,7 +134,8 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { case .unhandledError(function: let function, line: let line, error: let error): domainEvent = .networkProtectionUnhandledError(function: function, line: line, error: error) } - Pixel.fire(domainEvent, frequency: .dailyAndContinuous, includeAppVersionParameter: true) + + PixelKit.fire(domainEvent, frequency: .dailyAndContinuous, includeAppVersionParameter: true) } } @@ -143,13 +144,23 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { // MARK: - PacketTunnelProvider.Event reporting private static var packetTunnelProviderEvents: EventMapping = .init { event, _, _, _ in + switch event { case .userBecameActive: - Pixel.fire(.networkProtectionActiveUser, frequency: .dailyOnly, includeAppVersionParameter: true) + PixelKit.fire( + NetworkProtectionPixelEvent.networkProtectionActiveUser, + frequency: .dailyOnly, + includeAppVersionParameter: true) case .reportLatency(ms: let ms, server: let server, networkType: let networkType): - Pixel.fire(.networkProtectionLatency(ms: ms, server: server, networkType: networkType), frequency: .standard) + PixelKit.fire( + NetworkProtectionPixelEvent.networkProtectionLatency(ms: ms, server: server, networkType: networkType), + frequency: .standard, + includeAppVersionParameter: true) case .rekeyCompleted: - Pixel.fire(.networkProtectionRekeyCompleted, frequency: .dailyAndContinuous, includeAppVersionParameter: true) + PixelKit.fire( + NetworkProtectionPixelEvent.networkProtectionRekeyCompleted, + frequency: .dailyAndContinuous, + includeAppVersionParameter: true) } } @@ -317,13 +328,13 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { dryRun = false #endif - Pixel.setUp(dryRun: dryRun, - appVersion: AppVersion.shared.versionNumber, - defaultHeaders: defaultHeaders, - log: .networkProtectionPixel) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping (Error?) -> Void) in + PixelKit.setUp(dryRun: dryRun, + appVersion: AppVersion.shared.versionNumber, + defaultHeaders: defaultHeaders, + log: .networkProtectionPixel) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping (Error?) -> Void) in let url = URL.pixelUrl(forPixelNamed: pixelName) - let apiHeaders = APIRequest.Headers(additionalHeaders: headers) // workaround - Pixel class should really handle APIRequest.Headers by itself + let apiHeaders = APIRequest.Headers(additionalHeaders: headers) let configuration = APIRequest.Configuration(url: url, method: .get, queryParameters: parameters, headers: apiHeaders) let request = APIRequest(configuration: configuration) diff --git a/DuckDuckGo/Permissions/Model/PermissionStore.swift b/DuckDuckGo/Permissions/Model/PermissionStore.swift index 72c9d5ab6d..18d486f5bb 100644 --- a/DuckDuckGo/Permissions/Model/PermissionStore.swift +++ b/DuckDuckGo/Permissions/Model/PermissionStore.swift @@ -44,7 +44,7 @@ final class LocalPermissionStore: PermissionStore { private var context: NSManagedObjectContext? { if case .none = _context { #if DEBUG - if NSApp.isRunningUnitTests { + guard NSApp.runType.requiresEnvironment else { _context = .some(.none) return .none } diff --git a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift index c2610e298b..18d289d607 100644 --- a/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DefaultBrowserPreferences.swift @@ -75,9 +75,7 @@ final class DefaultBrowserPreferences: ObservableObject { didSet { // Temporary pixel for first time user import data #if DEBUG - if NSApp.isRunningUnitTests { - return - } + guard NSApp.runType.requiresEnvironment else { return } #endif if Pixel.isNewUser && isDefault { PixelExperiment.fireSetAsDefaultInitialPixel() diff --git a/DuckDuckGo/Preferences/Model/PreferencesSection.swift b/DuckDuckGo/Preferences/Model/PreferencesSection.swift index 15a9494b1a..d864752138 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSection.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSection.swift @@ -29,7 +29,7 @@ struct PreferencesSection: Hashable, Identifiable { #if SUBSCRIPTION var panes: [PreferencePaneIdentifier] = [.privacy, .subscription, .general, .appearance, .autofill, .downloads] - if (NSApp.delegate as? AppDelegate)?.internalUserDecider?.isInternalUser == true { + if NSApp.delegateTyped.internalUserDecider.isInternalUser { if let generalIndex = panes.firstIndex(of: .general) { panes.insert(.sync, at: generalIndex + 1) } @@ -37,7 +37,7 @@ struct PreferencesSection: Hashable, Identifiable { #else var panes: [PreferencePaneIdentifier] = [.general, .appearance, .privacy, .autofill, .downloads] - if (NSApp.delegate as? AppDelegate)?.internalUserDecider?.isInternalUser == true { + if NSApp.delegateTyped.internalUserDecider.isInternalUser { panes.insert(.sync, at: 1) } #endif diff --git a/DuckDuckGo/Preferences/Model/PrivacySecurityPreferences.swift b/DuckDuckGo/Preferences/Model/PrivacySecurityPreferences.swift index 705c215428..2c8d707598 100644 --- a/DuckDuckGo/Preferences/Model/PrivacySecurityPreferences.swift +++ b/DuckDuckGo/Preferences/Model/PrivacySecurityPreferences.swift @@ -43,11 +43,8 @@ final class PrivacySecurityPreferences { public var autoconsentEnabled: Bool? { didSet { // Temporary pixel for first time user enables cookies management -#if DEBUG - if NSApp.isRunningUnitTests { - return - } -#endif + guard NSApp.runType.requiresEnvironment else { return } + if Pixel.isNewUser && autoconsentEnabled ?? false { let repetition = Pixel.Event.Repetition(key: Pixel.Event.cookieManagementEnabledInitial.name) if repetition == .initial { diff --git a/DuckDuckGo/Preferences/Model/SyncPreferences.swift b/DuckDuckGo/Preferences/Model/SyncPreferences.swift index b19e0d8c7b..c42820ebe9 100644 --- a/DuckDuckGo/Preferences/Model/SyncPreferences.swift +++ b/DuckDuckGo/Preferences/Model/SyncPreferences.swift @@ -38,6 +38,10 @@ extension SyncDevice { final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { + struct Consts { + static let syncPausedStateChanged = Notification.Name("com.duckduckgo.app.SyncPausedStateChanged") + } + var isSyncEnabled: Bool { syncService.account != nil } @@ -65,6 +69,11 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { } } } + + @Published var isSyncBookmarksPaused: Bool + + @Published var isSyncCredentialsPaused: Bool + private var shouldRequestSyncOnFavoritesOptionChange: Bool = true var recoveryCode: String? { @@ -113,10 +122,25 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { } } + @MainActor + func manageBookmarks() { + guard let mainVC = WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController else { return } + mainVC.showManageBookmarks(self) + } + + @MainActor + func manageLogins() { + guard let parentWindowController = WindowControllersManager.shared.lastKeyMainWindowController else { return } + guard let navigationViewController = parentWindowController.mainViewController.navigationBarViewController else { return } + navigationViewController.showPasswordManagerPopover(selectedCategory: .allItems) + } + init(syncService: DDGSyncing, apperancePreferences: AppearancePreferences = .shared, managementDialogModel: ManagementDialogModel = ManagementDialogModel()) { self.syncService = syncService self.isUnifiedFavoritesEnabled = apperancePreferences.favoritesDisplayMode.isDisplayUnified + isSyncBookmarksPaused = UserDefaultsWrapper(key: .syncBookmarksPaused, defaultValue: false).wrappedValue + isSyncCredentialsPaused = UserDefaultsWrapper(key: .syncCredentialsPaused, defaultValue: false).wrappedValue self.managementDialogModel = managementDialogModel self.managementDialogModel.delegate = self @@ -158,6 +182,14 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { self?.onEndFlow() } .store(in: &cancellables) + + NotificationCenter.default.publisher(for: Self.Consts.syncPausedStateChanged) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.isSyncBookmarksPaused = UserDefaultsWrapper(key: .syncBookmarksPaused, defaultValue: false).wrappedValue + self?.isSyncCredentialsPaused = UserDefaultsWrapper(key: .syncCredentialsPaused, defaultValue: false).wrappedValue + } + .store(in: &cancellables) } // MARK: - Private @@ -201,7 +233,7 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { return } - if NSApp.isRunningUnitTests { + guard case .normal = NSApp.runType else { return } diff --git a/DuckDuckGo/Preferences/View/PreferencesRootView.swift b/DuckDuckGo/Preferences/View/PreferencesRootView.swift index b79f0b26c4..570ef2efe0 100644 --- a/DuckDuckGo/Preferences/View/PreferencesRootView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesRootView.swift @@ -123,7 +123,7 @@ extension Preferences { struct SyncView: View { var body: some View { - if let syncService = (NSApp.delegate as? AppDelegate)?.syncService { + if let syncService = NSApp.delegateTyped.syncService { SyncUI.ManagementView(model: SyncPreferences(syncService: syncService)) .onAppear { requestSync() diff --git a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift index 538096a36e..93fa21b119 100644 --- a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift +++ b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift @@ -171,16 +171,18 @@ final class PrivacyDashboardViewController: NSViewController { extension PrivacyDashboardViewController: PrivacyDashboardControllerDelegate { - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didChangeProtectionSwitch isEnabled: Bool) { + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didChangeProtectionSwitch protectionState: ProtectionState) { guard let domain = privacyDashboardController.privacyInfo?.url.host else { return } let configuration = ContentBlocking.shared.privacyConfigurationManager.privacyConfig - if isEnabled && configuration.isUserUnprotected(domain: domain) { + if protectionState.isProtected && configuration.isUserUnprotected(domain: domain) { configuration.userEnabledProtection(forDomain: domain) + Pixel.fire(.dashboardProtectionAllowlistRemove(triggerOrigin: protectionState.eventOrigin.screen.rawValue), includeAppVersionParameter: false) } else { configuration.userDisabledProtection(forDomain: domain) + Pixel.fire(.dashboardProtectionAllowlistAdd(triggerOrigin: protectionState.eventOrigin.screen.rawValue), includeAppVersionParameter: false) } let completionToken = ContentBlocking.shared.contentBlockingManager.scheduleCompilation() diff --git a/DuckDuckGo/RecentlyClosed/Model/RecentlyClosedCoordinator.swift b/DuckDuckGo/RecentlyClosed/Model/RecentlyClosedCoordinator.swift index 5ca31790bf..4ec087a994 100644 --- a/DuckDuckGo/RecentlyClosed/Model/RecentlyClosedCoordinator.swift +++ b/DuckDuckGo/RecentlyClosed/Model/RecentlyClosedCoordinator.swift @@ -40,9 +40,7 @@ final class RecentlyClosedCoordinator: RecentlyClosedCoordinating { init(windowControllerManager: WindowControllersManagerProtocol) { self.windowControllerManager = windowControllerManager - guard !NSApp.isRunningUnitTests else { - return - } + guard NSApp.runType.requiresEnvironment else { return } subscribeToWindowControllersManager() } diff --git a/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift b/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift index dc8d3ff40a..5b5722bf34 100644 --- a/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift +++ b/DuckDuckGo/SecureVault/SecureVaultErrorReporter.swift @@ -25,9 +25,8 @@ final class SecureVaultErrorReporter: SecureVaultErrorReporting { private init() {} func secureVaultInitFailed(_ error: SecureStorageError) { -#if DEBUG - guard !NSApp.isRunningUnitTests else { return } -#endif + guard NSApp.runType.requiresEnvironment else { return } + switch error { case .initFailed, .failedToOpenDatabase: Pixel.fire(.debug(event: .secureVaultInitError, error: error)) diff --git a/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift b/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift index 7dfcfd6888..8edb6f9311 100644 --- a/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift +++ b/DuckDuckGo/SecureVault/View/PasswordManagementViewController.swift @@ -181,7 +181,7 @@ final class PasswordManagementViewController: NSViewController { } private func bindSyncDidFinish() -> AnyCancellable? { - (NSApp.delegate as? AppDelegate)?.syncDataProviders?.credentialsAdapter.syncDidCompletePublisher + NSApp.delegateTyped.syncDataProviders?.credentialsAdapter.syncDidCompletePublisher .receive(on: DispatchQueue.main) .sink { [weak self] in self?.refreshData() @@ -970,7 +970,7 @@ final class PasswordManagementViewController: NSViewController { } private func requestSync() { - guard let syncService = (NSApp.delegate as? AppDelegate)?.syncService else { + guard let syncService = NSApp.delegateTyped.syncService else { return } os_log(.debug, log: OSLog.sync, "Requesting sync if enabled") diff --git a/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift b/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift index ea7f0206ae..15afb73e7b 100644 --- a/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift +++ b/DuckDuckGo/SecureVault/View/SaveCredentialsViewController.swift @@ -174,7 +174,7 @@ final class SaveCredentialsViewController: NSViewController { } } else { _ = try AutofillSecureVaultFactory.makeVault(errorReporter: SecureVaultErrorReporter.shared).storeWebsiteCredentials(credentials) - (NSApp.delegate as? AppDelegate)?.syncService?.scheduler.notifyDataChanged() + NSApp.delegateTyped.syncService?.scheduler.notifyDataChanged() os_log(.debug, log: OSLog.sync, "Requesting sync if enabled") } } catch { diff --git a/DuckDuckGo/Statistics/Pixel.swift b/DuckDuckGo/Statistics/Pixel.swift index c146551cee..df7e606be3 100644 --- a/DuckDuckGo/Statistics/Pixel.swift +++ b/DuckDuckGo/Statistics/Pixel.swift @@ -19,6 +19,7 @@ import Foundation import Networking import Common +import PixelKit final class Pixel { @@ -79,7 +80,13 @@ final class Pixel { } } - init(store: @escaping @autoclosure () -> PixelDataStore, requestSender: @escaping RequestSender) { + private let appVersion: String + + init(appVersion: String = AppVersion.shared.versionNumber, + store: @escaping @autoclosure () -> PixelDataStore, + requestSender: @escaping RequestSender) { + + self.appVersion = appVersion self.store = store self.sendRequest = requestSender } @@ -123,10 +130,10 @@ final class Pixel { } if includeAppVersionParameter { - newParams[Parameters.appVersion] = AppVersion.shared.versionNumber + newParams[PixelKit.Parameters.appVersion] = appVersion } #if DEBUG - newParams[Parameters.test] = Values.test + newParams[PixelKit.Parameters.test] = PixelKit.Values.test #endif sendRequest(event, newParams, allowedQueryReservedCharacters, headers, onComplete) diff --git a/DuckDuckGo/Statistics/PixelEvent.swift b/DuckDuckGo/Statistics/PixelEvent.swift index 97af14d165..93e92104cc 100644 --- a/DuckDuckGo/Statistics/PixelEvent.swift +++ b/DuckDuckGo/Statistics/PixelEvent.swift @@ -20,10 +20,16 @@ import Foundation import BrowserServicesKit import Bookmarks import Configuration +import PixelKit extension Pixel { indirect enum Event { + /// This is a convenience pixel that allows us to fire `PixelKitEvents` using our + /// regular `Pixel.fire()` calls. This is a convenience intermediate step to help ensure + /// nothing breaks in the migration towards `PixelKit`. + case pixelKitEvent(_ event: PixelKitEvent) + case crash case brokenSiteReport @@ -117,8 +123,6 @@ extension Pixel { case jsPixel(_ pixel: AutofillUserScript.JSPixel) - case networkProtectionSystemExtensionUnknownActivationResult - case debug(event: Debug, error: Error? = nil) // Activation Points @@ -149,6 +153,10 @@ extension Pixel { case duckPlayerSettingNever case duckPlayerSettingBackToDefault + // Dashboard + case dashboardProtectionAllowlistAdd(triggerOrigin: String?) + case dashboardProtectionAllowlistRemove(triggerOrigin: String?) + // Network Protection Waitlist case networkProtectionWaitlistUserActive case networkProtectionWaitlistEntryPointMenuItemDisplayed @@ -162,35 +170,24 @@ extension Pixel { case networkProtectionRemoteMessageDismissed(messageID: String) case networkProtectionRemoteMessageOpened(messageID: String) + // Sync + case syncBookmarksCountLimitExceededDaily + case syncCredentialsCountLimitExceededDaily + case syncBookmarksRequestSizeLimitExceededDaily + case syncCredentialsRequestSizeLimitExceededDaily + // 28-day Home Button case enableHomeButton case disableHomeButton case setnewHomePage case dailyPixel(Event, isFirst: Bool) -#if DBP - case parentChildMatches - // SLO and SLI Pixels: https://app.asana.com/0/1203581873609357/1205337273100857/f - - // Stage Pixels - case optOutStart - case optOutEmailGenerate - case optOutCaptchaParse - case optOutCaptchaSend - case optOutCaptchaSolve - case optOutSubmit - case optOutEmailReceive - case optOutEmailConfirm - case optOutValidate - case optOutFinish - - // Process Pixels - case optOutSubmitSuccess - case optOutSuccess - case optOutFailure -#endif enum Debug { + /// This is a convenience pixel that allows us to fire `PixelKitEvents` using our + /// regular `Pixel.fire()` calls. This is a convenience intermediate step to help ensure + /// nothing breaks in the migration towards `PixelKit`. + case pixelKitEvent(_ event: PixelKitEvent) case assertionFailure(message: String, file: StaticString, line: UInt) @@ -283,18 +280,6 @@ extension Pixel { case userSelectedToInstallUpdate case userSelectedToDismissUpdate - case networkProtectionClientFailedToEncodeRedeemRequest - case networkProtectionClientInvalidInviteCode - case networkProtectionClientFailedToRedeemInviteCode(error: Error?) - case networkProtectionClientFailedToParseRedeemResponse(error: Error) - case networkProtectionClientInvalidAuthToken - case networkProtectionKeychainErrorFailedToCastKeychainValueToData(field: String) - case networkProtectionKeychainReadError(field: String, status: Int32) - case networkProtectionKeychainWriteError(field: String, status: Int32) - case networkProtectionKeychainDeleteError(status: Int32) - case networkProtectionNoAuthTokenFoundError - case networkProtectionUnhandledError(function: String, line: Int, error: Error) - case faviconDecryptionFailedUnique case downloadListItemDecryptionFailedUnique case historyEntryDecryptionFailedUnique @@ -337,10 +322,6 @@ extension Pixel { case networkProtectionRemoteMessageFetchingFailed case networkProtectionRemoteMessageStorageFailed - -#if DBP - case dataBrokerProtectionError -#endif } } @@ -350,6 +331,9 @@ extension Pixel.Event { var name: String { switch self { + case .pixelKitEvent(let event): + return event.name + case .crash: return "m_mac_crash" @@ -426,8 +410,6 @@ extension Pixel.Event { return "m_mac.import-data.initial" case .newTabInitial: return "m_mac.new-tab-opened.initial" - case .networkProtectionSystemExtensionUnknownActivationResult: - return "m_mac_netp_system_extension_unknown_activation_result" case .favoriteSectionHidden: return "m_mac.favorite-section-hidden" case .recentActivitySectionHidden: @@ -460,6 +442,11 @@ extension Pixel.Event { case .duckPlayerSettingBackToDefault: return "m_mac_duck-player_setting_back-to-default" + case .dashboardProtectionAllowlistAdd(let triggerOrigin): + return "m_mac_mp_wla" + case .dashboardProtectionAllowlistRemove(let triggerOrigin): + return "m_mac_mp_wlr" + case .launchInitial: return "m.mac.first-launch" case .serpInitial: @@ -490,6 +477,12 @@ extension Pixel.Event { case .networkProtectionRemoteMessageOpened(let messageID): return "m_mac_netp_remote_message_opened_\(messageID)" + // Sync + case .syncBookmarksCountLimitExceededDaily: return "m.mac.sync_bookmarks_count_limit_exceeded_daily" + case .syncCredentialsCountLimitExceededDaily: return "m.mac.sync_credentials_count_limit_exceeded_daily" + case .syncBookmarksRequestSizeLimitExceededDaily: return "m.mac.sync_bookmarks_request_size_limit_exceeded_daily" + case .syncCredentialsRequestSizeLimitExceededDaily: return "m.mac.sync_credentials_request_size_limit_exceeded_daily" + // 28-day Home Button case .enableHomeButton: return "m_mac_enable_home_button" @@ -500,25 +493,6 @@ extension Pixel.Event { case .dailyPixel(let pixel, isFirst: let isFirst): return pixel.name + (isFirst ? "_d" : "_c") -#if DBP - case .parentChildMatches: return "dbp_macos_parent-child-broker-matches" - // Stage Pixels - case .optOutStart: return "dbp_macos_optout_stage_start" - case .optOutEmailGenerate: return "dbp_macos_optout_stage_email-generate" - case .optOutCaptchaParse: return "dbp_macos_optout_stage_captcha-parse" - case .optOutCaptchaSend: return "dbp_macos_optout_stage_captcha-send" - case .optOutCaptchaSolve: return "dbp_macos_optout_stage_captcha-solve" - case .optOutSubmit: return "dbp_macos_optout_stage_submit" - case .optOutEmailReceive: return "dbp_macos_optout_stage_email-receive" - case .optOutEmailConfirm: return "dbp_macos_optout_stage_email-confirm" - case .optOutValidate: return "dbp_macos_optout_stage_validate" - case .optOutFinish: return "dbp_macos_optout_stage_finish" - - // Process Pixels - case .optOutSubmitSuccess: return "dbp_macos_optout_process_submit-success" - case .optOutSuccess: return "dbp_macos_optout_process_success" - case .optOutFailure: return "dbp_macos_optout_process_failure" -#endif } } } @@ -535,6 +509,8 @@ extension Pixel.Event.Debug { var name: String { switch self { + case .pixelKitEvent(let event): + return event.name case .assertionFailure: return "assertion_failure" @@ -708,29 +684,6 @@ extension Pixel.Event.Debug { case .userSelectedToDismissUpdate: return "user_selected_to_dismiss_update" - case .networkProtectionClientFailedToEncodeRedeemRequest: - return "netp_backend_api_error_encoding_redeem_request_body_failed" - case .networkProtectionClientInvalidInviteCode: - return "netp_backend_api_error_invalid_invite_code" - case .networkProtectionClientFailedToRedeemInviteCode: - return "netp_backend_api_error_failed_to_redeem_invite_code" - case .networkProtectionClientFailedToParseRedeemResponse: - return "netp_backend_api_error_parsing_redeem_response_failed" - case .networkProtectionClientInvalidAuthToken: - return "netp_backend_api_error_invalid_auth_token" - case .networkProtectionKeychainErrorFailedToCastKeychainValueToData: - return "netp_keychain_error_failed_to_cast_keychain_value_to_data" - case .networkProtectionKeychainReadError: - return "netp_keychain_error_read_failed" - case .networkProtectionKeychainWriteError: - return "netp_keychain_error_write_failed" - case .networkProtectionKeychainDeleteError: - return "netp_keychain_error_delete_failed" - case .networkProtectionNoAuthTokenFoundError: - return "netp_no_auth_token_found_error" - case .networkProtectionUnhandledError: - return "netp_unhandled_error" - case .faviconDecryptionFailedUnique: return "favicon_decryption_failed_unique" case .downloadListItemDecryptionFailedUnique: @@ -778,10 +731,6 @@ extension Pixel.Event.Debug { case .networkProtectionRemoteMessageFetchingFailed: return "netp_remote_message_fetching_failed" case .networkProtectionRemoteMessageStorageFailed: return "netp_remote_message_storage_failed" - -#if DBP - case .dataBrokerProtectionError: return "data_broker_error" -#endif } } } diff --git a/DuckDuckGo/Statistics/PixelParameters.swift b/DuckDuckGo/Statistics/PixelParameters.swift index 388fb76a5a..4dfdb91832 100644 --- a/DuckDuckGo/Statistics/PixelParameters.swift +++ b/DuckDuckGo/Statistics/PixelParameters.swift @@ -16,52 +16,23 @@ // limitations under the License. // -extension Pixel { - - enum Parameters { - static let duration = "duration" - static let test = "test" - static let appVersion = "appVersion" - - static let errorCode = "e" - static let errorDesc = "d" - static let errorCount = "c" - static let errorSource = "error_source" - static let underlyingErrorCode = "ue" - static let underlyingErrorDesc = "ud" - static let underlyingErrorSQLiteCode = "sqlrc" - static let underlyingErrorSQLiteExtendedCode = "sqlerc" - static let keychainErrorCode = "keychain_error_code" - - static let emailCohort = "cohort" - static let emailLastUsed = "duck_address_last_used" - - static let assertionMessage = "message" - static let assertionFile = "file" - static let assertionLine = "line" - - // Pixel experiments - static let experimentCohort = "cohort" - } - - enum Values { - static let test = "1" - } - -} +import PixelKit extension Pixel.Event { var parameters: [String: String]? { switch self { + case .pixelKitEvent(let event): + return event.parameters + case .debug(event: let debugEvent, error: let error): var params = error?.pixelParameters ?? [:] if case let .assertionFailure(message, file, line) = debugEvent { - params[Pixel.Parameters.assertionMessage] = message - params[Pixel.Parameters.assertionFile] = String(file) - params[Pixel.Parameters.assertionLine] = String(line) + params[PixelKit.Parameters.assertionMessage] = message + params[PixelKit.Parameters.assertionFile] = String(file) + params[PixelKit.Parameters.assertionLine] = String(line) } return params @@ -70,17 +41,25 @@ extension Pixel.Event { return error.pixelParameters case .launchInitial(let cohort): - return [Pixel.Parameters.experimentCohort: cohort] + return [PixelKit.Parameters.experimentCohort: cohort] case .serpInitial(let cohort): - return [Pixel.Parameters.experimentCohort: cohort] + return [PixelKit.Parameters.experimentCohort: cohort] case .serpDay21to27(let cohort): - return [Pixel.Parameters.experimentCohort: cohort, "isDefault": DefaultBrowserPreferences().isDefault.description] + return [PixelKit.Parameters.experimentCohort: cohort, "isDefault": DefaultBrowserPreferences().isDefault.description] case .setAsDefaultInitial(let cohort): - return [Pixel.Parameters.experimentCohort: cohort] + return [PixelKit.Parameters.experimentCohort: cohort] case .dailyPixel(let pixel, isFirst: _): return pixel.parameters + case .dashboardProtectionAllowlistAdd(let triggerOrigin): + guard let trigger = triggerOrigin else { return nil } + return [PixelKit.Parameters.dashboardTriggerOrigin: trigger] + + case .dashboardProtectionAllowlistRemove(let triggerOrigin): + guard let trigger = triggerOrigin else { return nil } + return [PixelKit.Parameters.dashboardTriggerOrigin: trigger] + // Don't use default to force new items to be thought about case .crash, .brokenSiteReport, @@ -107,7 +86,6 @@ extension Pixel.Event { .watchInDuckPlayerInitial, .importDataInitial, .newTabInitial, - .networkProtectionSystemExtensionUnknownActivationResult, .favoriteSectionHidden, .recentActivitySectionHidden, .continueSetUpSectionHidden, @@ -135,25 +113,12 @@ extension Pixel.Event { .networkProtectionRemoteMessageOpened, .enableHomeButton, .disableHomeButton, - .setnewHomePage: + .setnewHomePage, + .syncBookmarksCountLimitExceededDaily, + .syncCredentialsCountLimitExceededDaily, + .syncBookmarksRequestSizeLimitExceededDaily, + .syncCredentialsRequestSizeLimitExceededDaily: return nil -#if DBP - case .optOutStart, - .optOutEmailGenerate, - .optOutCaptchaParse, - .optOutCaptchaSend, - .optOutCaptchaSolve, - .optOutSubmit, - .optOutEmailReceive, - .optOutEmailConfirm, - .optOutValidate, - .optOutFinish, - .optOutSubmitSuccess, - .optOutSuccess, - .optOutFailure, - .parentChildMatches: - return nil -#endif } } @@ -164,26 +129,26 @@ extension Error { var pixelParameters: [String: String] { var params = [String: String]() - if let errorWithUserInfo = self as? ErrorWithParameters { + if let errorWithUserInfo = self as? ErrorWithPixelParameters { params = errorWithUserInfo.errorParameters } let nsError = self as NSError - params[Pixel.Parameters.errorCode] = "\(nsError.code)" - params[Pixel.Parameters.errorDesc] = nsError.domain + params[PixelKit.Parameters.errorCode] = "\(nsError.code)" + params[PixelKit.Parameters.errorDesc] = nsError.domain if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? NSError { - params[Pixel.Parameters.underlyingErrorCode] = "\(underlyingError.code)" - params[Pixel.Parameters.underlyingErrorDesc] = underlyingError.domain + params[PixelKit.Parameters.underlyingErrorCode] = "\(underlyingError.code)" + params[PixelKit.Parameters.underlyingErrorDesc] = underlyingError.domain } if let sqlErrorCode = nsError.userInfo["SQLiteResultCode"] as? NSNumber { - params[Pixel.Parameters.underlyingErrorSQLiteCode] = "\(sqlErrorCode.intValue)" + params[PixelKit.Parameters.underlyingErrorSQLiteCode] = "\(sqlErrorCode.intValue)" } if let sqlExtendedErrorCode = nsError.userInfo["SQLiteExtendedResultCode"] as? NSNumber { - params[Pixel.Parameters.underlyingErrorSQLiteExtendedCode] = "\(sqlExtendedErrorCode.intValue)" + params[PixelKit.Parameters.underlyingErrorSQLiteExtendedCode] = "\(sqlExtendedErrorCode.intValue)" } return params diff --git a/DuckDuckGo/Statistics/TimedPixel.swift b/DuckDuckGo/Statistics/TimedPixel.swift deleted file mode 100644 index 291930ff59..0000000000 --- a/DuckDuckGo/Statistics/TimedPixel.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// TimedPixel.swift -// -// Copyright © 2021 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -struct TimedPixel { - - let pixel: Pixel.Event - let time: CFTimeInterval - - init(_ pixel: Pixel.Event, time: CFTimeInterval = CACurrentMediaTime()) { - self.pixel = pixel - self.time = time - } - - func fire(_ fireTime: CFTimeInterval = CACurrentMediaTime(), withAdditionalParmaeters params: [String: String] = [:]) { - let duration = String(format: "%.1f", fireTime - self.time) - var newParams = pixel.parameters ?? [:] - newParams.merge(params, uniquingKeysWith: { $1 }) - newParams[Pixel.Parameters.duration] = duration - Pixel.shared?.fire(pixel, withAdditionalParameters: newParams) - } - -} diff --git a/DuckDuckGo/Sync/SyncBookmarksAdapter.swift b/DuckDuckGo/Sync/SyncBookmarksAdapter.swift index d515e5edf7..b9cd8cbcbc 100644 --- a/DuckDuckGo/Sync/SyncBookmarksAdapter.swift +++ b/DuckDuckGo/Sync/SyncBookmarksAdapter.swift @@ -33,6 +33,13 @@ final class SyncBookmarksAdapter { } } + @UserDefaultsWrapper(key: .syncBookmarksPaused, defaultValue: false) + private var isSyncBookmarksPaused: Bool { + didSet { + NotificationCenter.default.post(name: SyncPreferences.Consts.syncPausedStateChanged, object: nil) + } + } + init( database: CoreDataDatabase, bookmarkManager: BookmarkManager = LocalBookmarkManager.shared, @@ -65,18 +72,30 @@ final class SyncBookmarksAdapter { let provider = BookmarksProvider( database: database, - metadataStore: metadataStore, - syncDidUpdateData: LocalBookmarkManager.shared.loadBookmarks - ) + metadataStore: metadataStore) { [weak self] in + LocalBookmarkManager.shared.loadBookmarks() + self?.isSyncBookmarksPaused = false + } + if shouldResetBookmarksSyncTimestamp { provider.lastSyncTimestamp = nil } syncErrorCancellable = provider.syncErrorPublisher - .sink { error in + .sink { [weak self] error in switch error { case let syncError as SyncError: Pixel.fire(.debug(event: .syncBookmarksFailed, error: syncError)) + // If bookmarks count limit has been exceeded + if syncError == .unexpectedStatusCode(409) { + self?.isSyncBookmarksPaused = true + Pixel.fire(.syncBookmarksCountLimitExceededDaily, limitTo: .dailyFirst) + } + // If bookmarks request size limit has been exceeded + if syncError == .unexpectedStatusCode(413) { + self?.isSyncBookmarksPaused = true + Pixel.fire(.syncBookmarksRequestSizeLimitExceededDaily, limitTo: .dailyFirst) + } default: let nsError = error as NSError if nsError.domain != NSURLErrorDomain { diff --git a/DuckDuckGo/Sync/SyncCredentialsAdapter.swift b/DuckDuckGo/Sync/SyncCredentialsAdapter.swift index 40a404b52e..dad6c820a6 100644 --- a/DuckDuckGo/Sync/SyncCredentialsAdapter.swift +++ b/DuckDuckGo/Sync/SyncCredentialsAdapter.swift @@ -29,6 +29,13 @@ final class SyncCredentialsAdapter { let databaseCleaner: CredentialsDatabaseCleaner let syncDidCompletePublisher: AnyPublisher + @UserDefaultsWrapper(key: .syncCredentialsPaused, defaultValue: false) + private var isSyncCredentialsPaused: Bool { + didSet { + NotificationCenter.default.post(name: SyncPreferences.Consts.syncPausedStateChanged, object: nil) + } + } + init(secureVaultFactory: AutofillVaultFactory = AutofillSecureVaultFactory) { syncDidCompletePublisher = syncDidCompleteSubject.eraseToAnyPublisher() databaseCleaner = CredentialsDatabaseCleaner( @@ -60,14 +67,26 @@ final class SyncCredentialsAdapter { metadataStore: metadataStore, syncDidUpdateData: { [weak self] in self?.syncDidCompleteSubject.send() + self?.isSyncCredentialsPaused = false } ) syncErrorCancellable = provider.syncErrorPublisher - .sink { error in + .sink { [weak self] error in switch error { case let syncError as SyncError: Pixel.fire(.debug(event: .syncCredentialsFailed, error: syncError)) + Pixel.fire(.debug(event: .syncBookmarksFailed, error: syncError)) + // If credentials count limit has been exceeded + if syncError == .unexpectedStatusCode(409) { + self?.isSyncCredentialsPaused = true + Pixel.fire(.syncCredentialsCountLimitExceededDaily, limitTo: .dailyFirst) + } + // If credentials request size limit has been exceeded + if syncError == .unexpectedStatusCode(413) { + self?.isSyncCredentialsPaused = true + Pixel.fire(.syncCredentialsRequestSizeLimitExceededDaily, limitTo: .dailyFirst) + } default: let nsError = error as NSError if nsError.domain != NSURLErrorDomain { diff --git a/DuckDuckGo/Sync/SyncDebugMenu.swift b/DuckDuckGo/Sync/SyncDebugMenu.swift index e920d1695f..36bbd4688a 100644 --- a/DuckDuckGo/Sync/SyncDebugMenu.swift +++ b/DuckDuckGo/Sync/SyncDebugMenu.swift @@ -19,15 +19,24 @@ import Foundation import DDGSync -@objc @MainActor +@MainActor final class SyncDebugMenu: NSMenu { - @IBOutlet private weak var environmentMenu: NSMenu! { - didSet { - populateEnvironmentMenu() + private let environmentMenu = NSMenu() + + init() { + super.init(title: "") + + buildItems { + NSMenuItem(title: "Environment") + .submenu(environmentMenu) } } + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func update() { populateEnvironmentMenu() } @@ -35,7 +44,7 @@ final class SyncDebugMenu: NSMenu { private func populateEnvironmentMenu() { environmentMenu.removeAllItems() - guard let syncService = (NSApp.delegate as? AppDelegate)?.syncService else { + guard let syncService = NSApp.delegateTyped.syncService else { return } @@ -56,7 +65,7 @@ final class SyncDebugMenu: NSMenu { @objc func switchSyncEnvironment(_ sender: NSMenuItem) { #if DEBUG || REVIEW - guard let syncService = (NSApp.delegate as? AppDelegate)?.syncService, + guard let syncService = NSApp.delegateTyped.syncService, let environment = sender.representedObject as? ServerEnvironment else { return diff --git a/DuckDuckGo/Tab/Model/NewWindowPolicy.swift b/DuckDuckGo/Tab/Model/NewWindowPolicy.swift index 8db6c89b1a..ca9e575d29 100644 --- a/DuckDuckGo/Tab/Model/NewWindowPolicy.swift +++ b/DuckDuckGo/Tab/Model/NewWindowPolicy.swift @@ -30,6 +30,15 @@ enum NewWindowPolicy { burner: isBurner) } else if windowFeatures.width != nil { self = .popup(origin: windowFeatures.origin, size: windowFeatures.size) + + } else + // This is a temporary fix for macOS 14.1 WKWindowFeatures being empty when opening a new regular tab + // Instead of defaulting to window policy, we default to tab policy, and allow popups in some limited scenarios. + // See https://app.asana.com/0/1177771139624306/1205690527704551/f. + if #available(macOS 14.1, *), + windowFeatures.statusBarVisibility == nil && windowFeatures.menuBarVisibility == nil { + self = .tab(selected: shouldSelectNewTab, burner: isBurner) + } else { self = .window(active: true, burner: isBurner) } diff --git a/DuckDuckGo/Tab/Model/Tab.swift b/DuckDuckGo/Tab/Model/Tab.swift index debdc40e14..f5dd8871d7 100644 --- a/DuckDuckGo/Tab/Model/Tab.swift +++ b/DuckDuckGo/Tab/Model/Tab.swift @@ -248,11 +248,11 @@ protocol NewWindowPolicyDecisionMaker { ) { let duckPlayer = duckPlayer - ?? (NSApp.isRunningUnitTests ? DuckPlayer.mock(withMode: .enabled) : DuckPlayer.shared) + ?? (NSApp.runType.requiresEnvironment ? DuckPlayer.shared : DuckPlayer.mock(withMode: .enabled)) let statisticsLoader = statisticsLoader - ?? (NSApp.isRunningUnitTests ? nil : StatisticsLoader.shared) + ?? (NSApp.runType.requiresEnvironment ? StatisticsLoader.shared : nil) let privacyFeatures = privacyFeatures ?? PrivacyFeatures - let internalUserDecider = (NSApp.delegate as? AppDelegate)?.internalUserDecider + let internalUserDecider = NSApp.delegateTyped.internalUserDecider var faviconManager = faviconManagement if burnerMode.isBurner { faviconManager = FaviconManager(cacheType: .inMemory) @@ -529,7 +529,7 @@ protocol NewWindowPolicyDecisionMaker { } @discardableResult - func setContent(_ newContent: TabContent) -> Task? { + func setContent(_ newContent: TabContent) -> ExpectedNavigation? { guard contentChangeEnabled else { return nil } let oldContent = self.content @@ -554,13 +554,11 @@ protocol NewWindowPolicyDecisionMaker { self.title = title } - return Task { - await reloadIfNeeded(shouldLoadInBackground: true) - } + return reloadIfNeeded(shouldLoadInBackground: true) } @discardableResult - func setUrl(_ url: URL?, userEntered: String?) -> Task? { + func setUrl(_ url: URL?, userEntered: String?) -> ExpectedNavigation? { if url == .welcome { OnboardingViewModel().restart() } @@ -697,7 +695,7 @@ protocol NewWindowPolicyDecisionMaker { let canGoBack = webView.canGoBack || self.error != nil let canGoForward = webView.canGoForward && self.error == nil - let canReload = (self.content.urlForWebView?.scheme ?? URL.NavigationalScheme.about.rawValue) != URL.NavigationalScheme.about.rawValue + let canReload = self.content.userEditableUrl != nil if canGoBack != self.canGoBack { self.canGoBack = canGoBack @@ -763,26 +761,18 @@ protocol NewWindowPolicyDecisionMaker { if webView.url == nil, content.isUrl { // load from cache or interactionStateData when called by lazy loader - Task { @MainActor [weak self] in - await self?.reloadIfNeeded(shouldLoadInBackground: true) - } + reloadIfNeeded(shouldLoadInBackground: true) } else { webView.reload() } } - @MainActor + @MainActor(unsafe) @discardableResult - private func reloadIfNeeded(shouldLoadInBackground: Bool = false) async -> ExpectedNavigation? { + private func reloadIfNeeded(shouldLoadInBackground: Bool = false) -> ExpectedNavigation? { + guard case .url(let url, credential: _, userEntered: let userEntered) = content, url.scheme != "about" else { return nil } - let content = self.content - guard let url = content.urlForWebView, - url.scheme.map(URL.NavigationalScheme.init) != .about else { return nil } - - var userForcedReload = false - if case .url(let url, _, let userEntered) = content, url.absoluteString == userEntered { - userForcedReload = shouldLoadInBackground - } + let userForcedReload = (url.absoluteString == userEntered) ? shouldLoadInBackground : false if userForcedReload || shouldReload(url, shouldLoadInBackground: shouldLoadInBackground) { let didRestore = restoreInteractionStateDataIfNeeded() @@ -867,7 +857,7 @@ protocol NewWindowPolicyDecisionMaker { } private func addHomePageToWebViewIfNeeded() { - guard !NSApp.isRunningUnitTests else { return } + guard NSApp.runType.requiresEnvironment else { return } if content == .homePage && webView.url == nil { webView.load(URLRequest(url: .homePage)) } @@ -902,9 +892,7 @@ protocol NewWindowPolicyDecisionMaker { webView.observe(\.superview, options: .old) { [weak self] _, change in // if the webView is being added to superview - reload if needed if case .some(.none) = change.oldValue { - Task { @MainActor [weak self] in - await self?.reloadIfNeeded() - } + self?.reloadIfNeeded() } }.store(in: &webViewCancellables) @@ -942,10 +930,10 @@ protocol NewWindowPolicyDecisionMaker { }.store(in: &webViewCancellables) // background tab loading should start immediately - Task { @MainActor in - await reloadIfNeeded(shouldLoadInBackground: shouldLoadInBackground) + DispatchQueue.main.async { + self.reloadIfNeeded(shouldLoadInBackground: shouldLoadInBackground) if !shouldLoadInBackground { - addHomePageToWebViewIfNeeded() + self.addHomePageToWebViewIfNeeded() } } } diff --git a/DuckDuckGo/Tab/Model/TabExtensionsBuilder.swift b/DuckDuckGo/Tab/Model/TabExtensionsBuilder.swift index a158989cf3..ef950d286a 100644 --- a/DuckDuckGo/Tab/Model/TabExtensionsBuilder.swift +++ b/DuckDuckGo/Tab/Model/TabExtensionsBuilder.swift @@ -34,7 +34,7 @@ struct TabExtensionsBuilder: TabExtensionsBuilderProtocol { static var `default`: TabExtensionsBuilderProtocol { #if DEBUG - return NSApp.isRunningUnitTests ? TestTabExtensionsBuilder.shared : TabExtensionsBuilder() + return NSApp.runType.requiresEnvironment ? TabExtensionsBuilder() : TestTabExtensionsBuilder.shared #else return TabExtensionsBuilder() #endif @@ -159,10 +159,10 @@ struct TabExtensionBuildingBlock { } init(_ makeTabExtension: @escaping () -> Extension) where Extension.PublicProtocol == T { - if NSApp.isRunningUnitTests { - state = .lazy(.init(makeTabExtension)) - } else { + if NSApp.runType.requiresEnvironment { state = .loaded(makeTabExtension().getPublicProtocol()) + } else { + state = .lazy(.init(makeTabExtension)) } } @@ -235,7 +235,7 @@ struct TabExtensions { let tabExtension = extensions[ObjectIdentifier(T.PublicProtocol.self)]?.getPublicProtocol() as? T.PublicProtocol guard isNullable != .nullable else { return tabExtension} #if DEBUG - assert(NSApp.isRunningUnitTests || tabExtension != nil) + assert(!NSApp.runType.requiresEnvironment || tabExtension != nil) #else os_log("%s Tab Extension not initialised for Unit Tests, activate it in TabExtensions.swift", log: .autoconsent, type: .debug, "\(T.self)") #endif diff --git a/DuckDuckGo/Tab/TabExtensions/ContextMenuManager.swift b/DuckDuckGo/Tab/TabExtensions/ContextMenuManager.swift index b8d1781c6c..fbabbba05e 100644 --- a/DuckDuckGo/Tab/TabExtensions/ContextMenuManager.swift +++ b/DuckDuckGo/Tab/TabExtensions/ContextMenuManager.swift @@ -285,7 +285,7 @@ private extension ContextMenuManager { let identifier = item.identifier.flatMap(WKMenuItemIdentifier.init) assert(identifier != nil && validIdentifiers.contains(identifier!)) - return NSMenuItem(title: title, action: action, target: self, keyEquivalent: keyEquivalent ?? item.keyEquivalent, representedObject: item) + return NSMenuItem(title: title, action: action, target: self, keyEquivalent: [.charCode(keyEquivalent ?? item.keyEquivalent)], representedObject: item) } } diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Controllers/AppLaunchingController.swift b/DuckDuckGo/UserAgent/Model/DuckDuckGoUserAgent.swift similarity index 59% rename from LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Controllers/AppLaunchingController.swift rename to DuckDuckGo/UserAgent/Model/DuckDuckGoUserAgent.swift index e56f6cee90..d5584675c2 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Controllers/AppLaunchingController.swift +++ b/DuckDuckGo/UserAgent/Model/DuckDuckGoUserAgent.swift @@ -1,5 +1,5 @@ // -// AppLaunchingController.swift +// DuckDuckGoUserAgent.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -17,20 +17,14 @@ // import Foundation -import NetworkProtection +import Common -public final class AppLaunchingController: TunnelController { - private let appLauncher: AppLaunching +enum UserAgent { - public init(appLauncher: AppLaunching) { - self.appLauncher = appLauncher + static func duckDuckGoUserAgent(appVersion: String = AppVersion.shared.versionNumber, + appID: String = AppVersion.shared.identifier, + systemVersion: String = ProcessInfo.processInfo.operatingSystemVersionString) -> String { + return "ddg_mac/\(appVersion) (\(appID); macOS \(systemVersion))" } - public func start() async { - await appLauncher.launchApp(withCommand: .startVPN) - } - - public func stop() async { - await appLauncher.launchApp(withCommand: .stopVPN) - } } diff --git a/DuckDuckGo/UserAgent/Model/UserAgent.swift b/DuckDuckGo/UserAgent/Model/UserAgent.swift index 97d59953b1..276f88fc9a 100644 --- a/DuckDuckGo/UserAgent/Model/UserAgent.swift +++ b/DuckDuckGo/UserAgent/Model/UserAgent.swift @@ -20,7 +20,7 @@ import Foundation import BrowserServicesKit import Common -enum UserAgent { +extension UserAgent { // MARK: - Fallback versions @@ -68,12 +68,6 @@ enum UserAgent { regex("https://duckduckgo\\.com/.*"): UserAgent.webViewDefault ] - static func duckDuckGoUserAgent(appVersion: String = AppVersion.shared.versionNumber, - appID: String = AppVersion.shared.identifier, - systemVersion: String = ProcessInfo.processInfo.operatingSystemVersionString) -> String { - return "ddg_mac/\(appVersion) (\(appID); macOS \(systemVersion))" - } - static func `for`(_ url: URL?, privacyConfig: PrivacyConfiguration = ContentBlocking.shared.privacyConfigurationManager.privacyConfig) -> String { guard let absoluteString = url?.absoluteString else { diff --git a/DuckDuckGo/Waitlist/Models/WaitlistViewModel.swift b/DuckDuckGo/Waitlist/Models/NetworkProtectionWaitlistViewModel.swift similarity index 98% rename from DuckDuckGo/Waitlist/Models/WaitlistViewModel.swift rename to DuckDuckGo/Waitlist/Models/NetworkProtectionWaitlistViewModel.swift index 4476e605ed..420b89b621 100644 --- a/DuckDuckGo/Waitlist/Models/WaitlistViewModel.swift +++ b/DuckDuckGo/Waitlist/Models/NetworkProtectionWaitlistViewModel.swift @@ -1,5 +1,5 @@ // -// WaitlistViewModel.swift +// NetworkProtectionWaitlistViewModel.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -27,7 +27,7 @@ protocol WaitlistViewModelDelegate: AnyObject { func viewHeightChanged(newHeight: CGFloat) } -final class WaitlistViewModel: ObservableObject { +final class NetworkProtectionWaitlistViewModel: ObservableObject { enum ViewState: Equatable { case notOnWaitlist diff --git a/DuckDuckGo/Waitlist/NetworkProtectionFeatureDisabler.swift b/DuckDuckGo/Waitlist/NetworkProtectionFeatureDisabler.swift index 828224bb14..6f21a07abc 100644 --- a/DuckDuckGo/Waitlist/NetworkProtectionFeatureDisabler.swift +++ b/DuckDuckGo/Waitlist/NetworkProtectionFeatureDisabler.swift @@ -22,6 +22,7 @@ import BrowserServicesKit import Common import NetworkExtension import NetworkProtection +import NetworkProtectionIPC import NetworkProtectionUI import SystemExtensions @@ -33,20 +34,23 @@ final class NetworkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling private let log: OSLog private let loginItemsManager: LoginItemsManager private let pinningManager: LocalPinningManager - private let selectedServerUserDefaultsStore: NetworkProtectionSelectedServerUserDefaultsStore + private let settings: TunnelSettings private let userDefaults: UserDefaults + private let ipcClient: TunnelControllerIPCClient init(loginItemsManager: LoginItemsManager = LoginItemsManager(), pinningManager: LocalPinningManager = .shared, userDefaults: UserDefaults = .shared, - selectedServerUserDefaultsStore: NetworkProtectionSelectedServerUserDefaultsStore = NetworkProtectionSelectedServerUserDefaultsStore(), + settings: TunnelSettings = .init(defaults: .shared), + ipcClient: TunnelControllerIPCClient = TunnelControllerIPCClient(machServiceName: Bundle.main.vpnMenuAgentBundleId), log: OSLog = .networkProtection) { self.log = log self.loginItemsManager = loginItemsManager self.pinningManager = pinningManager - self.selectedServerUserDefaultsStore = selectedServerUserDefaultsStore + self.settings = settings self.userDefaults = userDefaults + self.ipcClient = ipcClient } /// This method disables Network Protection and clear all of its state. @@ -58,6 +62,11 @@ final class NetworkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling func disable(keepAuthToken: Bool, uninstallSystemExtension: Bool) { Task { unpinNetworkProtection() + + if uninstallSystemExtension { + await resetAllStateForVPNApp(uninstallSystemExtension: uninstallSystemExtension) + } + disableLoginItems() await resetNetworkExtensionState() @@ -72,10 +81,6 @@ final class NetworkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling if !keepAuthToken { try? removeAppAuthToken() } - - if uninstallSystemExtension { - try? await disableSystemExtension() - } } } @@ -83,16 +88,11 @@ final class NetworkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling loginItemsManager.disableLoginItems(LoginItemsManager.networkProtectionLoginItems) } - func disableSystemExtension() async throws { + func resetAllStateForVPNApp(uninstallSystemExtension: Bool) async { + await ipcClient.resetAll(uninstallSystemExtension: uninstallSystemExtension) + #if NETP_SYSTEM_EXTENSION - do { - try await SystemExtensionManager().deactivate() - userDefaults.networkProtectionOnboardingStatusRawValue = OnboardingStatus.default.rawValue - } catch OSSystemExtensionError.extensionNotFound { - // This is an intentional no-op to silence this type of error - } catch { - throw error - } + userDefaults.networkProtectionOnboardingStatusRawValue = OnboardingStatus.default.rawValue #endif } @@ -124,8 +124,8 @@ final class NetworkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling } private func resetUserDefaults() { - selectedServerUserDefaultsStore.reset() - userDefaults.networkProtectionOnboardingStatusRawValue = OnboardingStatus.isOnboarding(step: .userNeedsToAllowVPNConfiguration).rawValue + settings.resetToDefaults() + userDefaults.networkProtectionOnboardingStatusRawValue = OnboardingStatus.default.rawValue } } diff --git a/DuckDuckGo/Waitlist/Views/WaitlistDialogView.swift b/DuckDuckGo/Waitlist/Views/WaitlistDialogView.swift index 70f22ec3ea..1730536a6e 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistDialogView.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistDialogView.swift @@ -24,7 +24,7 @@ struct WaitlistDialogView: View where Content: View, Buttons: let innerPadding: CGFloat - @EnvironmentObject var model: WaitlistViewModel + @EnvironmentObject var model: NetworkProtectionWaitlistViewModel @ViewBuilder let content: () -> Content @ViewBuilder let buttons: () -> Buttons diff --git a/DuckDuckGo/Waitlist/Views/WaitlistModalViewController.swift b/DuckDuckGo/Waitlist/Views/WaitlistModalViewController.swift index e6d2417897..725bbba045 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistModalViewController.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistModalViewController.swift @@ -36,12 +36,12 @@ final class WaitlistModalViewController: NSViewController { } private let defaultSize = CGSize(width: 360, height: 650) - private let model: WaitlistViewModel + private let model: NetworkProtectionWaitlistViewModel private var heightConstraint: NSLayoutConstraint? - init(notificationPermissionState: WaitlistViewModel.NotificationPermissionState) { - self.model = WaitlistViewModel(waitlist: NetworkProtectionWaitlist(), notificationPermissionState: notificationPermissionState) + init(notificationPermissionState: NetworkProtectionWaitlistViewModel.NotificationPermissionState) { + self.model = NetworkProtectionWaitlistViewModel(waitlist: NetworkProtectionWaitlist(), notificationPermissionState: notificationPermissionState) super.init(nibName: nil, bundle: nil) } @@ -94,7 +94,7 @@ final class WaitlistModalViewController: NSViewController { // preventing any state changing from occurring. UNUserNotificationCenter.current().getNotificationSettings { settings in let status = settings.authorizationStatus - let state = WaitlistViewModel.NotificationPermissionState.from(status) + let state = NetworkProtectionWaitlistViewModel.NotificationPermissionState.from(status) DispatchQueue.main.async { let viewController = WaitlistModalViewController(notificationPermissionState: state) diff --git a/DuckDuckGo/Waitlist/Views/WaitlistRootView.swift b/DuckDuckGo/Waitlist/Views/WaitlistRootView.swift index 5470c1833c..eac1b9371f 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistRootView.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistRootView.swift @@ -21,25 +21,24 @@ import SwiftUI struct WaitlistRootView: View { - @EnvironmentObject var model: WaitlistViewModel + @EnvironmentObject var model: NetworkProtectionWaitlistViewModel var body: some View { Group { switch model.viewState { case .notOnWaitlist, .joiningWaitlist: - JoinWaitlistView() + JoinWaitlistView(viewData: NetworkProtectionJoinWaitlistViewData()) case .joinedWaitlist(let state): - if state == .notificationAllowed { - JoinedWaitlistView(notificationsAllowed: true) - } else { - JoinedWaitlistView(notificationsAllowed: false) - } + JoinedWaitlistView(viewData: NetworkProtectionJoinedWaitlistViewData(), + notificationsAllowed: state == .notificationAllowed) case .invited: - InvitedToWaitlistView() + InvitedToWaitlistView(viewData: NetworkProtectionInvitedToWaitlistViewData()) case .termsAndConditions: - NetworkProtectionTermsAndConditionsView() + WaitlistTermsAndConditionsView(viewData: NetworkProtectionWaitlistTermsAndConditionsViewData()) { + NetworkProtectionTermsAndConditionsContentView() + } case .readyToEnable: - EnableNetworkProtectionView() + EnableWaitlistFeatureView(viewData: EnableNetworkProtectionViewData()) } } .environmentObject(model) diff --git a/DuckDuckGo/Waitlist/Views/WaitlistSteps/EnableNetworkProtectionView.swift b/DuckDuckGo/Waitlist/Views/WaitlistSteps/EnableNetworkProtectionView.swift deleted file mode 100644 index 9acb56b394..0000000000 --- a/DuckDuckGo/Waitlist/Views/WaitlistSteps/EnableNetworkProtectionView.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// EnableNetworkProtectionView.swift -// -// Copyright © 2023 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#if NETWORK_PROTECTION - -import SwiftUI -import SwiftUIExtensions - -struct EnableNetworkProtectionView: View { - @EnvironmentObject var model: WaitlistViewModel - - var body: some View { - WaitlistDialogView { - VStack(spacing: 16.0) { - Image("Network-Protection-256") - - Text(UserText.networkProtectionWaitlistEnableTitle) - .font(.system(size: 17, weight: .bold)) - - Text(UserText.networkProtectionWaitlistEnableSubtitle) - .multilineTextAlignment(.center) - .foregroundColor(Color("BlackWhite80")) - - Text(UserText.networkProtectionWaitlistAvailabilityDisclaimer) - .font(.system(size: 12)) - .foregroundColor(Color("BlackWhite60")) - } - } buttons: { - Button(UserText.networkProtectionWaitlistButtonGotIt) { - Task { - await model.perform(action: .closeAndPinNetworkProtection) - } - } - .buttonStyle(DefaultActionButtonStyle(enabled: true)) - } - .environmentObject(model) - } -} - -#endif diff --git a/DuckDuckGo/Waitlist/Views/WaitlistSteps/EnableWaitlistFeatureView.swift b/DuckDuckGo/Waitlist/Views/WaitlistSteps/EnableWaitlistFeatureView.swift new file mode 100644 index 0000000000..40e954446d --- /dev/null +++ b/DuckDuckGo/Waitlist/Views/WaitlistSteps/EnableWaitlistFeatureView.swift @@ -0,0 +1,89 @@ +// +// EnableWaitlistFeatureView.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if NETWORK_PROTECTION || DBP + +import SwiftUI +import SwiftUIExtensions + +protocol EnableWaitlistFeatureViewData { + var headerImageName: String { get } + var title: String { get } + var subtitle: String { get } + var availabilityDisclaimer: String { get } + var buttonConfirmLabel: String { get } +} + +struct EnableWaitlistFeatureView: View { + var viewData: EnableWaitlistFeatureViewData + @EnvironmentObject var model: NetworkProtectionWaitlistViewModel + + var body: some View { + WaitlistDialogView { + VStack(spacing: 16.0) { + Image(viewData.headerImageName) + + Text(viewData.title) + .font(.system(size: 17, weight: .bold)) + + Text(viewData.subtitle) + .multilineTextAlignment(.center) + .foregroundColor(Color("BlackWhite80")) + + Text(viewData.availabilityDisclaimer) + .multilineTextAlignment(.center) + .font(.system(size: 12)) + .foregroundColor(Color("BlackWhite60")) + } + } buttons: { + Button(viewData.buttonConfirmLabel) { + Task { + await model.perform(action: .closeAndPinNetworkProtection) + } + } + .buttonStyle(DefaultActionButtonStyle(enabled: true)) + } + .environmentObject(model) + } +} + +#endif + +#if NETWORK_PROTECTION + +struct EnableNetworkProtectionViewData: EnableWaitlistFeatureViewData { + var headerImageName: String = "Network-Protection-256" + var title: String = UserText.networkProtectionWaitlistEnableTitle + var subtitle: String = UserText.networkProtectionWaitlistEnableSubtitle + var availabilityDisclaimer: String = UserText.networkProtectionWaitlistAvailabilityDisclaimer + var buttonConfirmLabel: String = UserText.networkProtectionWaitlistButtonGotIt +} + +#endif + +#if DBP + +struct EnableDataBrokerProtectionViewData: EnableWaitlistFeatureViewData { + var headerImageName: String = "DBP-JoinWaitlistHeader" + var title: String = UserText.dataBrokerProtectionWaitlistEnableTitle + var subtitle: String = UserText.dataBrokerProtectionWaitlistEnableSubtitle + var availabilityDisclaimer: String = UserText.dataBrokerProtectionWaitlistAvailabilityDisclaimer + var buttonConfirmLabel: String = UserText.dataBrokerProtectionWaitlistButtonGotIt +} + +#endif diff --git a/DuckDuckGo/Waitlist/Views/WaitlistSteps/InvitedToWaitlistView.swift b/DuckDuckGo/Waitlist/Views/WaitlistSteps/InvitedToWaitlistView.swift index e813f97405..1efde7a6f2 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistSteps/InvitedToWaitlistView.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistSteps/InvitedToWaitlistView.swift @@ -22,58 +22,56 @@ import Foundation import SwiftUI import SwiftUIExtensions +protocol InvitedToWaitlistViewData { + var headerImageName: String { get } + var title: String { get } + var subtitle: String { get } + var entryViewViewDataList: [WaitlistEntryViewItemViewData] { get } + var availabilityDisclaimer: String { get } + var buttonDismissLabel: String { get } + var buttonGetStartedLabel: String { get } +} + struct InvitedToWaitlistView: View { - @EnvironmentObject var model: WaitlistViewModel + let viewData: InvitedToWaitlistViewData + @EnvironmentObject var model: NetworkProtectionWaitlistViewModel var body: some View { WaitlistDialogView { VStack(spacing: 16.0) { - Image("Gift-96") + Image(viewData.headerImageName) - Text(UserText.networkProtectionWaitlistInvitedTitle) + Text(viewData.title) .font(.system(size: 17, weight: .bold)) .multilineTextAlignment(.center) - Text(UserText.networkProtectionWaitlistInvitedSubtitle) + Text(viewData.subtitle) .multilineTextAlignment(.center) .foregroundColor(Color("BlackWhite80")) VStack(spacing: 16.0) { - WaitlistListEntryView( - imageName: "Shield-16", - title: UserText.networkProtectionWaitlistInvitedSection1Title, - subtitle: UserText.networkProtectionWaitlistInvitedSection1Subtitle - ) - - WaitlistListEntryView( - imageName: "Rocket-16", - title: UserText.networkProtectionWaitlistInvitedSection2Title, - subtitle: UserText.networkProtectionWaitlistInvitedSection2Subtitle - ) - - WaitlistListEntryView( - imageName: "Card-16", - title: UserText.networkProtectionWaitlistInvitedSection3Title, - subtitle: UserText.networkProtectionWaitlistInvitedSection3Subtitle - ) + ForEach(viewData.entryViewViewDataList) { itemData in + WaitlistListEntryView(viewData: itemData) + } } .padding(20.0) .frame(maxWidth: .infinity) .background(Color("BlackWhite1")) .border(Color("BlackWhite5")) - Text(UserText.networkProtectionWaitlistAvailabilityDisclaimer) + Text(viewData.availabilityDisclaimer) + .multilineTextAlignment(.center) .font(.system(size: 12)) .foregroundColor(Color("BlackWhite60")) } } buttons: { - Button(UserText.networkProtectionWaitlistButtonDismiss) { + Button(viewData.buttonDismissLabel) { Task { await model.perform(action: .close) } } - Button(UserText.networkProtectionWaitlistButtonGetStarted) { + Button(viewData.buttonGetStartedLabel) { Task { await model.perform(action: .showTermsAndConditions) } @@ -85,23 +83,21 @@ struct InvitedToWaitlistView: View { } private struct WaitlistListEntryView: View { - let imageName: String - let title: String - let subtitle: String + let viewData: WaitlistEntryViewItemViewData var body: some View { HStack(alignment: .top, spacing: 8) { - Image(imageName) + Image(viewData.imageName) .frame(maxWidth: 16, maxHeight: 16) VStack(alignment: .leading, spacing: 6) { - Text(title) + Text(viewData.title) .font(.system(size: 13, weight: .bold)) .foregroundColor(Color("BlackWhite80")) .multilineTextAlignment(.leading) .frame(maxWidth: .infinity, alignment: .leading) - Text(subtitle) + Text(viewData.subtitle) .font(.system(size: 13)) .foregroundColor(Color("BlackWhite60")) .multilineTextAlignment(.leading) @@ -114,4 +110,65 @@ private struct WaitlistListEntryView: View { } } +struct WaitlistEntryViewItemViewData: Identifiable { + let id = UUID() + let imageName: String + let title: String + let subtitle: String +} + +#endif + +#if NETWORK_PROTECTION + +struct NetworkProtectionInvitedToWaitlistViewData: InvitedToWaitlistViewData { + let headerImageName = "Gift-96" + let title = UserText.networkProtectionWaitlistInvitedTitle + let subtitle = UserText.networkProtectionWaitlistInvitedSubtitle + let buttonDismissLabel = UserText.networkProtectionWaitlistButtonDismiss + let buttonGetStartedLabel = UserText.networkProtectionWaitlistButtonGetStarted + let availabilityDisclaimer = UserText.networkProtectionWaitlistAvailabilityDisclaimer + let entryViewViewDataList: [WaitlistEntryViewItemViewData] = + [ + .init(imageName: "Shield-16", + title: UserText.networkProtectionWaitlistInvitedSection1Title, + subtitle: UserText.networkProtectionWaitlistInvitedSection1Subtitle), + + .init(imageName: "Rocket-16", + title: UserText.networkProtectionWaitlistInvitedSection2Title, + subtitle: UserText.networkProtectionWaitlistInvitedSection2Subtitle), + + .init(imageName: "Card-16", + title: UserText.networkProtectionWaitlistInvitedSection3Title, + subtitle: UserText.networkProtectionWaitlistInvitedSection3Subtitle), + ] +} + +#endif + +#if DBP + +struct DataBrokerProtectionInvitedToWaitlistViewData: InvitedToWaitlistViewData { + let headerImageName = "Gift-96" + let title = UserText.dataBrokerProtectionWaitlistInvitedTitle + let subtitle = UserText.dataBrokerProtectionWaitlistInvitedSubtitle + let buttonDismissLabel = UserText.dataBrokerProtectionWaitlistButtonDismiss + let buttonGetStartedLabel = UserText.dataBrokerProtectionWaitlistButtonGetStarted + let availabilityDisclaimer = UserText.dataBrokerProtectionWaitlistAvailabilityDisclaimer + let entryViewViewDataList: [WaitlistEntryViewItemViewData] = + [ + .init(imageName: "Shield-16", + title: UserText.dataBrokerProtectionWaitlistInvitedSection1Title, + subtitle: UserText.dataBrokerProtectionWaitlistInvitedSection1Subtitle), + + .init(imageName: "Rocket-16", + title: UserText.dataBrokerProtectionWaitlistInvitedSection2Title, + subtitle: UserText.dataBrokerProtectionWaitlistInvitedSection2Subtitle), + + .init(imageName: "Card-16", + title: UserText.dataBrokerProtectionWaitlistInvitedSection3Title, + subtitle: UserText.dataBrokerProtectionWaitlistInvitedSection3Subtitle), + ] +} + #endif diff --git a/DuckDuckGo/Waitlist/Views/WaitlistSteps/JoinWaitlistView.swift b/DuckDuckGo/Waitlist/Views/WaitlistSteps/JoinWaitlistView.swift index 57b351f96a..673b5e707e 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistSteps/JoinWaitlistView.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistSteps/JoinWaitlistView.swift @@ -16,40 +16,52 @@ // limitations under the License. // -#if NETWORK_PROTECTION +#if NETWORK_PROTECTION || DBP import SwiftUI import SwiftUIExtensions +protocol JoinWaitlistViewViewData { + var headerImageName: String { get } + var title: String { get } + var subtitle1: String { get } + var subtitle2: String { get } + var availabilityDisclaimer: String { get } + var buttonCloseLabel: String { get } + var buttonJoinWaitlistLabel: String { get } +} + struct JoinWaitlistView: View { - @EnvironmentObject var model: WaitlistViewModel + let viewData: JoinWaitlistViewViewData + @EnvironmentObject var model: NetworkProtectionWaitlistViewModel var body: some View { WaitlistDialogView { VStack(spacing: 16.0) { - Image("JoinWaitlistHeader") + Image(viewData.headerImageName) - Text(UserText.networkProtectionWaitlistJoinTitle) + Text(viewData.title) .font(.system(size: 17, weight: .bold)) - Text(UserText.networkProtectionWaitlistJoinSubtitle1) + Text(viewData.subtitle1) .multilineTextAlignment(.center) .foregroundColor(Color("BlackWhite80")) - Text(UserText.networkProtectionWaitlistJoinSubtitle2) + Text(viewData.subtitle2) .multilineTextAlignment(.center) .foregroundColor(Color("BlackWhite80")) - Text(UserText.networkProtectionWaitlistAvailabilityDisclaimer) + Text(viewData.availabilityDisclaimer) + .multilineTextAlignment(.center) .font(.system(size: 12)) .foregroundColor(Color("BlackWhite60")) } } buttons: { - Button(UserText.networkProtectionWaitlistButtonClose) { + Button(viewData.buttonCloseLabel) { Task { await model.perform(action: .close) } } - Button(UserText.networkProtectionWaitlistButtonJoinWaitlist) { + Button(viewData.buttonJoinWaitlistLabel) { Task { await model.perform(action: .joinQueue) } } .buttonStyle(DefaultActionButtonStyle(enabled: model.viewState == .notOnWaitlist)) @@ -59,3 +71,31 @@ struct JoinWaitlistView: View { } #endif + +#if NETWORK_PROTECTION + +struct NetworkProtectionJoinWaitlistViewData: JoinWaitlistViewViewData { + let headerImageName = "JoinWaitlistHeader" + let title = UserText.networkProtectionWaitlistJoinTitle + let subtitle1 = UserText.networkProtectionWaitlistJoinSubtitle1 + let subtitle2 = UserText.networkProtectionWaitlistJoinSubtitle2 + let availabilityDisclaimer = UserText.networkProtectionWaitlistAvailabilityDisclaimer + let buttonCloseLabel = UserText.networkProtectionWaitlistButtonClose + let buttonJoinWaitlistLabel = UserText.networkProtectionWaitlistButtonJoinWaitlist +} + +#endif + +#if DBP + +struct DataBrokerProtectionJoinWaitlistViewData: JoinWaitlistViewViewData { + let headerImageName = "DBP-JoinWaitlistHeader" + let title = UserText.dataBrokerProtectionWaitlistJoinTitle + let subtitle1 = UserText.dataBrokerProtectionWaitlistJoinSubtitle1 + let subtitle2 = UserText.dataBrokerProtectionWaitlistJoinSubtitle2 + let availabilityDisclaimer = UserText.dataBrokerProtectionWaitlistAvailabilityDisclaimer + let buttonCloseLabel = UserText.dataBrokerProtectionWaitlistButtonClose + let buttonJoinWaitlistLabel = UserText.dataBrokerProtectionWaitlistButtonJoinWaitlist +} + +#endif diff --git a/DuckDuckGo/Waitlist/Views/WaitlistSteps/JoinedWaitlistView.swift b/DuckDuckGo/Waitlist/Views/WaitlistSteps/JoinedWaitlistView.swift index 56959278ba..bc0b94b5ae 100644 --- a/DuckDuckGo/Waitlist/Views/WaitlistSteps/JoinedWaitlistView.swift +++ b/DuckDuckGo/Waitlist/Views/WaitlistSteps/JoinedWaitlistView.swift @@ -16,54 +16,66 @@ // limitations under the License. // -#if NETWORK_PROTECTION +#if NETWORK_PROTECTION || DBP import SwiftUI import SwiftUIExtensions +protocol JoinedWaitlistViewData { + var headerImageName: String { get } + var title: String { get } + var joinedWithNoNotificationSubtitle1: String { get } + var joinedWithNoNotificationSubtitle2: String { get } + var enableNotificationSubtitle: String { get } + var buttonConfirmLabel: String { get } + var buttonCancelLabel: String { get } + var buttonEnableNotificationLabel: String { get } +} + struct JoinedWaitlistView: View { - @EnvironmentObject var model: WaitlistViewModel + let viewData: JoinedWaitlistViewData + @EnvironmentObject var model: NetworkProtectionWaitlistViewModel let notificationsAllowed: Bool var body: some View { WaitlistDialogView { VStack(spacing: 16.0) { - Image("JoinedWaitlistHeader") + Image(viewData.headerImageName) - Text(UserText.networkProtectionWaitlistJoinedTitle) + Text(viewData.title) .font(.system(size: 17, weight: .bold)) if notificationsAllowed { VStack(spacing: 16) { - Text(UserText.networkProtectionWaitlistJoinedWithNotificationsSubtitle1) + Text(viewData.joinedWithNoNotificationSubtitle1) .multilineTextAlignment(.center) .frame(minHeight: 28) // Hack to force height calculation to work correctly - Text(UserText.networkProtectionWaitlistJoinedWithNotificationsSubtitle2) + Text(viewData.joinedWithNoNotificationSubtitle2) .multilineTextAlignment(.center) } } else { - Text(UserText.networkProtectionWaitlistEnableNotifications) + Text(viewData.enableNotificationSubtitle) .multilineTextAlignment(.center) } } } buttons: { if notificationsAllowed { - Button(UserText.networkProtectionWaitlistButtonDone) { + Button(viewData.buttonConfirmLabel) { Task { await model.perform(action: .close) } } } else { - Button(UserText.networkProtectionWaitlistButtonNoThanks) { + Button(viewData.buttonCancelLabel) { Task { await model.perform(action: .close) } } - Button(UserText.networkProtectionWaitlistButtonEnableNotifications) { + Button(viewData.buttonEnableNotificationLabel) { Task { await model.perform(action: .requestNotificationPermission) } @@ -76,3 +88,33 @@ struct JoinedWaitlistView: View { } #endif + +#if NETWORK_PROTECTION + +struct NetworkProtectionJoinedWaitlistViewData: JoinedWaitlistViewData { + let headerImageName = "JoinedWaitlistHeader" + var title = UserText.networkProtectionWaitlistJoinedTitle + var joinedWithNoNotificationSubtitle1 = UserText.networkProtectionWaitlistJoinedWithNotificationsSubtitle1 + var joinedWithNoNotificationSubtitle2 = UserText.networkProtectionWaitlistJoinedWithNotificationsSubtitle2 + var enableNotificationSubtitle = UserText.networkProtectionWaitlistEnableNotifications + var buttonConfirmLabel = UserText.networkProtectionWaitlistButtonDone + var buttonCancelLabel = UserText.networkProtectionWaitlistButtonNoThanks + var buttonEnableNotificationLabel = UserText.networkProtectionWaitlistButtonEnableNotifications +} + +#endif + +#if DBP + +struct DataBrokerProtectionJoinedWaitlistViewData: JoinedWaitlistViewData { + let headerImageName = "JoinedWaitlistHeader" + var title = UserText.dataBrokerProtectionWaitlistJoinedTitle + var joinedWithNoNotificationSubtitle1 = UserText.dataBrokerProtectionWaitlistJoinedWithNotificationsSubtitle1 + var joinedWithNoNotificationSubtitle2 = UserText.dataBrokerProtectionWaitlistJoinedWithNotificationsSubtitle2 + var enableNotificationSubtitle = UserText.dataBrokerProtectionWaitlistEnableNotifications + var buttonConfirmLabel = UserText.dataBrokerProtectionWaitlistButtonDone + var buttonCancelLabel = UserText.dataBrokerProtectionWaitlistButtonNoThanks + var buttonEnableNotificationLabel = UserText.dataBrokerProtectionWaitlistButtonEnableNotifications +} + +#endif diff --git a/DuckDuckGo/Waitlist/Views/WaitlistSteps/NetworkProtectionTermsAndConditionsView.swift b/DuckDuckGo/Waitlist/Views/WaitlistSteps/NetworkProtectionTermsAndConditionsView.swift deleted file mode 100644 index bd82a29f46..0000000000 --- a/DuckDuckGo/Waitlist/Views/WaitlistSteps/NetworkProtectionTermsAndConditionsView.swift +++ /dev/null @@ -1,135 +0,0 @@ -// -// NetworkProtectionTermsAndConditionsView.swift -// -// Copyright © 2023 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#if NETWORK_PROTECTION - -import SwiftUI -import SwiftUIExtensions - -struct NetworkProtectionTermsAndConditionsView: View { - - @EnvironmentObject var model: WaitlistViewModel - - var body: some View { - WaitlistDialogView(innerPadding: 0) { - VStack(spacing: 0) { - Text("Network Protection Beta\nService Terms and Privacy Policy") - .font(.system(size: 17, weight: .bold)) - .multilineTextAlignment(.center) - .padding(.bottom, 16.0) - - Divider() - - ScrollView { - VStack(alignment: .leading, spacing: 0) { - Text(UserText.networkProtectionPrivacyPolicyTitle) - .font(.system(size: 15, weight: .bold)) - .multilineTextAlignment(.leading) - - Group { - Text(UserText.networkProtectionPrivacyPolicySection1Title).titleStyle() - - if #available(macOS 12.0, *) { - Text(LocalizedStringKey(UserText.networkProtectionPrivacyPolicySection1ListMarkdown)).bodyStyle() - } else { - Text(UserText.networkProtectionPrivacyPolicySection1ListNonMarkdown).bodyStyle() - } - - Text(UserText.networkProtectionPrivacyPolicySection2Title).titleStyle() - Text(UserText.networkProtectionPrivacyPolicySection2List).bodyStyle() - Text(UserText.networkProtectionPrivacyPolicySection3Title).titleStyle() - Text(UserText.networkProtectionPrivacyPolicySection3List).bodyStyle() - Text(UserText.networkProtectionPrivacyPolicySection4Title).titleStyle() - Text(UserText.networkProtectionPrivacyPolicySection4List).bodyStyle() - Text(UserText.networkProtectionPrivacyPolicySection5Title).titleStyle() - Text(UserText.networkProtectionPrivacyPolicySection5List).bodyStyle() - } - - Text(UserText.networkProtectionTermsOfServiceTitle) - .font(.system(size: 15, weight: .bold)) - .multilineTextAlignment(.leading) - .padding(.top, 28) - .padding(.bottom, 14) - - Group { - Text(UserText.networkProtectionTermsOfServiceSection1Title).titleStyle(topPadding: 0) - Text(UserText.networkProtectionTermsOfServiceSection1List).bodyStyle() - Text(UserText.networkProtectionTermsOfServiceSection2Title).titleStyle() - - if #available(macOS 12.0, *) { - Text(LocalizedStringKey(UserText.networkProtectionTermsOfServiceSection2ListMarkdown)).bodyStyle() - } else { - Text(UserText.networkProtectionTermsOfServiceSection2ListNonMarkdown).bodyStyle() - } - - Text(UserText.networkProtectionTermsOfServiceSection3Title).titleStyle() - Text(UserText.networkProtectionTermsOfServiceSection3List).bodyStyle() - Text(UserText.networkProtectionTermsOfServiceSection4Title).titleStyle() - Text(UserText.networkProtectionTermsOfServiceSection4List).bodyStyle() - Text(UserText.networkProtectionTermsOfServiceSection5Title).titleStyle() - Text(UserText.networkProtectionTermsOfServiceSection5List).bodyStyle() - } - - Group { - Text(UserText.networkProtectionTermsOfServiceSection6Title).titleStyle() - Text(UserText.networkProtectionTermsOfServiceSection6List).bodyStyle() - Text(UserText.networkProtectionTermsOfServiceSection7Title).titleStyle() - Text(UserText.networkProtectionTermsOfServiceSection7List).bodyStyle() - Text(UserText.networkProtectionTermsOfServiceSection8Title).titleStyle() - Text(UserText.networkProtectionTermsOfServiceSection8List).bodyStyle() - } - } - .padding(.all, 20) - } - .frame(maxWidth: .infinity) - .frame(maxHeight: 500) - // .background(Color("BlackWhite1")) - // .border(Color("BlackWhite5")) - } - } buttons: { - Button(UserText.networkProtectionWaitlistButtonCancel) { - Task { await model.perform(action: .close) } - } - - Button(UserText.networkProtectionWaitlistButtonAgreeAndContinue) { - Task { await model.perform(action: .acceptTermsAndConditions) } - } - .buttonStyle(DefaultActionButtonStyle(enabled: true)) - } - .environmentObject(model) - } -} - -private extension Text { - - func titleStyle(topPadding: CGFloat = 24, bottomPadding: CGFloat = 14) -> some View { - self - .font(.system(size: 11, weight: .bold)) - .multilineTextAlignment(.leading) - .padding(.top, topPadding) - .padding(.bottom, bottomPadding) - } - - func bodyStyle() -> some View { - self - .font(.system(size: 11)) - } - -} - -#endif diff --git a/DuckDuckGo/Waitlist/Views/WaitlistSteps/WaitlistTermsAndConditionsView.swift b/DuckDuckGo/Waitlist/Views/WaitlistSteps/WaitlistTermsAndConditionsView.swift new file mode 100644 index 0000000000..9dc0afa028 --- /dev/null +++ b/DuckDuckGo/Waitlist/Views/WaitlistSteps/WaitlistTermsAndConditionsView.swift @@ -0,0 +1,204 @@ +// +// WaitlistTermsAndConditionsView.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if NETWORK_PROTECTION || DBP + +import SwiftUI +import SwiftUIExtensions + +protocol WaitlistTermsAndConditionsViewData { + var title: String { get } + var buttonCancelLabel: String { get } + var buttonAgreeAndContinueLabel: String { get } +} + +struct WaitlistTermsAndConditionsView: View { + let viewData: WaitlistTermsAndConditionsViewData + let content: Content + @EnvironmentObject var model: NetworkProtectionWaitlistViewModel + + init(viewData: WaitlistTermsAndConditionsViewData, @ViewBuilder content: () -> Content) { + self.viewData = viewData + self.content = content() + } + + var body: some View { + WaitlistDialogView(innerPadding: 0) { + VStack(spacing: 0) { + Text(viewData.title) + .font(.system(size: 17, weight: .bold)) + .multilineTextAlignment(.center) + .padding(.bottom, 16.0) + + Divider() + + ScrollView { + content + } + .frame(maxWidth: .infinity) + .frame(maxHeight: 500) + } + } buttons: { + Button(viewData.buttonCancelLabel) { + Task { await model.perform(action: .close) } + } + + Button(viewData.buttonAgreeAndContinueLabel) { + Task { await model.perform(action: .acceptTermsAndConditions) } + } + .buttonStyle(DefaultActionButtonStyle(enabled: true)) + } + .environmentObject(model) + } +} + +private extension Text { + + func titleStyle(topPadding: CGFloat = 24, bottomPadding: CGFloat = 14) -> some View { + self + .font(.system(size: 11, weight: .bold)) + .multilineTextAlignment(.leading) + .padding(.top, topPadding) + .padding(.bottom, bottomPadding) + } + + func bodyStyle() -> some View { + self + .font(.system(size: 11)) + } + +} + +#endif + +#if NETWORK_PROTECTION + +struct NetworkProtectionTermsAndConditionsContentView: View { + var body: some View { + VStack(alignment: .leading, spacing: 0) { + Text(UserText.networkProtectionPrivacyPolicyTitle) + .font(.system(size: 15, weight: .bold)) + .multilineTextAlignment(.leading) + + Group { + Text(UserText.networkProtectionPrivacyPolicySection1Title).titleStyle() + + if #available(macOS 12.0, *) { + Text(LocalizedStringKey(UserText.networkProtectionPrivacyPolicySection1ListMarkdown)).bodyStyle() + } else { + Text(UserText.networkProtectionPrivacyPolicySection1ListNonMarkdown).bodyStyle() + } + + Text(UserText.networkProtectionPrivacyPolicySection2Title).titleStyle() + Text(UserText.networkProtectionPrivacyPolicySection2List).bodyStyle() + Text(UserText.networkProtectionPrivacyPolicySection3Title).titleStyle() + Text(UserText.networkProtectionPrivacyPolicySection3List).bodyStyle() + Text(UserText.networkProtectionPrivacyPolicySection4Title).titleStyle() + Text(UserText.networkProtectionPrivacyPolicySection4List).bodyStyle() + Text(UserText.networkProtectionPrivacyPolicySection5Title).titleStyle() + Text(UserText.networkProtectionPrivacyPolicySection5List).bodyStyle() + } + + Text(UserText.networkProtectionTermsOfServiceTitle) + .font(.system(size: 15, weight: .bold)) + .multilineTextAlignment(.leading) + .padding(.top, 28) + .padding(.bottom, 14) + + Group { + Text(UserText.networkProtectionTermsOfServiceSection1Title).titleStyle(topPadding: 0) + Text(UserText.networkProtectionTermsOfServiceSection1List).bodyStyle() + Text(UserText.networkProtectionTermsOfServiceSection2Title).titleStyle() + + if #available(macOS 12.0, *) { + Text(LocalizedStringKey(UserText.networkProtectionTermsOfServiceSection2ListMarkdown)).bodyStyle() + } else { + Text(UserText.networkProtectionTermsOfServiceSection2ListNonMarkdown).bodyStyle() + } + + Text(UserText.networkProtectionTermsOfServiceSection3Title).titleStyle() + Text(UserText.networkProtectionTermsOfServiceSection3List).bodyStyle() + Text(UserText.networkProtectionTermsOfServiceSection4Title).titleStyle() + Text(UserText.networkProtectionTermsOfServiceSection4List).bodyStyle() + Text(UserText.networkProtectionTermsOfServiceSection5Title).titleStyle() + Text(UserText.networkProtectionTermsOfServiceSection5List).bodyStyle() + } + + Group { + Text(UserText.networkProtectionTermsOfServiceSection6Title).titleStyle() + Text(UserText.networkProtectionTermsOfServiceSection6List).bodyStyle() + Text(UserText.networkProtectionTermsOfServiceSection7Title).titleStyle() + Text(UserText.networkProtectionTermsOfServiceSection7List).bodyStyle() + Text(UserText.networkProtectionTermsOfServiceSection8Title).titleStyle() + Text(UserText.networkProtectionTermsOfServiceSection8List).bodyStyle() + } + } + .padding(.all, 20) + } +} + +struct NetworkProtectionWaitlistTermsAndConditionsViewData: WaitlistTermsAndConditionsViewData { + let title = "Network Protection Beta\nService Terms and Privacy Policy" + let buttonCancelLabel = UserText.networkProtectionWaitlistButtonCancel + let buttonAgreeAndContinueLabel = UserText.networkProtectionWaitlistButtonAgreeAndContinue +} + +#endif + +#if DBP + +struct DataBrokerProtectionTermsAndConditionsContentView: View { + let text = """ +Placeholder terms and conditions

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. + +Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. + +Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. + +Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +""" + + var body: some View { + Text(UserText.dataBrokerProtectionPrivacyPolicyTitle) + .font(.system(size: 15, weight: .bold)) + .multilineTextAlignment(.leading) + + Group { + Text(text).bodyStyle() + } + .padding(.all, 20) + } +} + +struct DataBrokerProtectionWaitlistTermsAndConditionsViewData: WaitlistTermsAndConditionsViewData { + let title = "Personal Information Removal Beta\nService Terms and Privacy Policy" + let buttonCancelLabel = UserText.dataBrokerProtectionWaitlistButtonCancel + let buttonAgreeAndContinueLabel = UserText.dataBrokerProtectionWaitlistButtonAgreeAndContinue +} + +#endif diff --git a/DuckDuckGo/Waitlist/Waitlist.swift b/DuckDuckGo/Waitlist/Waitlist.swift index 8321a79e37..39886b0505 100644 --- a/DuckDuckGo/Waitlist/Waitlist.swift +++ b/DuckDuckGo/Waitlist/Waitlist.swift @@ -209,7 +209,6 @@ struct NetworkProtectionWaitlist: Waitlist { NotificationCenter.default.post(name: .networkProtectionWaitlistAccessChanged, object: nil) completion(nil) } catch { - assertionFailure("Failed to redeem invite code") completion(.failure(error)) } } diff --git a/DuckDuckGo/YoutubePlayer/DuckPlayer.swift b/DuckDuckGo/YoutubePlayer/DuckPlayer.swift index 54f9c95ef5..309d507d96 100644 --- a/DuckDuckGo/YoutubePlayer/DuckPlayer.swift +++ b/DuckDuckGo/YoutubePlayer/DuckPlayer.swift @@ -253,7 +253,7 @@ extension DuckPlayer { let preferencesPersistor = DuckPlayerPreferencesPersistorMock(duckPlayerMode: mode, youtubeOverlayInteracted: true) let preferences = DuckPlayerPreferences(persistor: preferencesPersistor) // runtime mock-replacement for Unit Tests, to be redone when we‘ll be doing Dependency Injection - let privacyConfigurationManager = ((NSClassFromString("MockPrivacyConfigurationManager") as? NSObject.Type)!.init() as? PrivacyConfigurationManaging)! + let privacyConfigurationManager = MockPrivacyConfigurationManager() return DuckPlayer(preferences: preferences, privacyConfigurationManager: privacyConfigurationManager) } diff --git a/DuckDuckGoAgent/FeatureProtectedTunnelController.swift b/DuckDuckGoAgent/FeatureProtectedTunnelController.swift deleted file mode 100644 index bbff5da7a8..0000000000 --- a/DuckDuckGoAgent/FeatureProtectedTunnelController.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// FeatureProtectedTunnelController.swift -// -// Copyright © 2023 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import NetworkProtection -import NetworkProtectionUI - -/// A tunnel controller that will kill the current app if Network Protection is disabled. -/// -final class FeatureProtectingTunnelController: TunnelController { - private let controller: AppLaunchingController - private let bouncer: NetworkProtectionBouncer - - init(appLauncher: AppLauncher, bouncer: NetworkProtectionBouncer) { - self.controller = AppLaunchingController(appLauncher: appLauncher) - self.bouncer = bouncer - } - - func start() async { - bouncer.requireAuthTokenOrKillApp() - await controller.start() - } - - func stop() async { - bouncer.requireAuthTokenOrKillApp() - await controller.stop() - } -} diff --git a/DuckDuckGoDBPTests/DataBrokerProtectionPixelTests.swift b/DuckDuckGoDBPTests/DataBrokerProtectionPixelTests.swift new file mode 100644 index 0000000000..212606714a --- /dev/null +++ b/DuckDuckGoDBPTests/DataBrokerProtectionPixelTests.swift @@ -0,0 +1,145 @@ +// +// DataBrokerProtectionPixelTests.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import DataBrokerProtection +import Networking +import Foundation +import PixelKit +import PixelKitTestingUtilities +import XCTest +@testable import DuckDuckGo_DBP + +/// Tests to ensure that DBP pixels sent from the main app work well +/// +final class DataBrokerProtectionPixelTests: XCTestCase { + + struct PixelDataStoreMock: PixelDataStore { + func set(_ value: Int, forKey: String, completionHandler: ((Error?) -> Void)?) { + completionHandler?(nil) + } + + func set(_ value: String, forKey: String, completionHandler: ((Error?) -> Void)?) { + completionHandler?(nil) + } + + func set(_ value: Double, forKey: String, completionHandler: ((Error?) -> Void)?) { + completionHandler?(nil) + } + + func value(forKey key: String) -> Double? { + nil + } + + func value(forKey key: String) -> Int? { + nil + } + + func value(forKey key: String) -> String? { + nil + } + + func removeValue(forKey key: String, completionHandler: ((Error?) -> Void)?) { + completionHandler?(nil) + } + } + + func mapToPixelEvent(_ dbpPixel: DataBrokerProtectionPixels) -> Pixel.Event { + switch dbpPixel { + case .error(let error, _): + return .debug(event: .pixelKitEvent(dbpPixel), error: error) + default: + return .pixelKitEvent(dbpPixel) + } + } + + /// This method implements validation logic that can be used to test several events. + /// + func validatePixel(for dbpEvent: DataBrokerProtectionPixels) { + let inAppVersion = "1.0.1" + let inUserAgent = "ddg_mac/\(inAppVersion) (com.duckduckgo.macos.browser.dbp.debug; macOS Version 14.0 (Build 23A344))" + + // We want to make sure the callback is executed exactly once to validate + // all of the fire parameters + let callbackExecuted = expectation(description: "We expect the callback to be executed once") + callbackExecuted.expectedFulfillmentCount = 1 + callbackExecuted.assertForOverFulfill = true + + // This is annoyingly necessary to test the user agent right now + APIRequest.Headers.setUserAgent(inUserAgent) + + let storeMock = PixelDataStoreMock() + + let inEvent = mapToPixelEvent(dbpEvent) + + let pixel = Pixel(appVersion: inAppVersion, store: storeMock) { (event, parameters, _, headers, onComplete) in + + // Validate that the event is the one we expect + XCTAssertEqual(event, inEvent) + + // Validate that the basic params are present + let pixelRequestValidator = PixelRequestValidator() + pixelRequestValidator.validateBasicPixelParams(expectedAppVersion: inAppVersion, expectedUserAgent: inUserAgent, requestParameters: parameters, requestHeaders: headers.httpHeaders) + + // Validate that the debug params are present + if case .debug(let wrappedEvent, let error) = inEvent { + XCTAssertEqual("m_mac_debug_\(wrappedEvent.name)", inEvent.name) + + pixelRequestValidator.validateDebugPixelParams(expectedError: error, requestParameters: parameters) + } + + // Validate that the dbp-specific params are present in the fire event parameters + XCTAssertTrue( + dbpEvent.params?.allSatisfy({ key, value in + parameters[key] == value + }) ?? false) + + callbackExecuted.fulfill() + onComplete(nil) + } + + pixel.fire(inEvent, withAdditionalParameters: dbpEvent.params) + + waitForExpectations(timeout: 0.1) + } + + func testBasicPixelValidation() { + let inDataBroker = "inDataBroker" + + let eventsToTest: [DataBrokerProtectionPixels] = [ + .error(error: DataBrokerProtectionError.cancelled, dataBroker: inDataBroker), + .parentChildMatches(parent: "a", child: "b", value: 5), + .optOutStart(dataBroker: "a", attemptId: UUID()), + .optOutEmailGenerate(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutCaptchaParse(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutCaptchaSend(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutCaptchaSolve(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutSubmit(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutEmailReceive(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutEmailConfirm(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutValidate(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutFinish(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutSubmitSuccess(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutSuccess(dataBroker: "a", attemptId: UUID(), duration: 5), + .optOutFailure(dataBroker: "a", attemptId: UUID(), duration: 5, stage: "some") + ] + + for event in eventsToTest { + validatePixel(for: event) + } + } +} diff --git a/DuckDuckGoDBPTests/Info.plist b/DuckDuckGoDBPTests/Info.plist new file mode 100644 index 0000000000..64d65ca495 --- /dev/null +++ b/DuckDuckGoDBPTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/DuckDuckGoAgent/AppLauncher+DefaultInitializer.swift b/DuckDuckGoVPN/AppLauncher+DefaultInitializer.swift similarity index 100% rename from DuckDuckGoAgent/AppLauncher+DefaultInitializer.swift rename to DuckDuckGoVPN/AppLauncher+DefaultInitializer.swift diff --git a/DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-128.png b/DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-128.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-128.png rename to DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-128.png diff --git a/DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-128@2x.png b/DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-128@2x.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-128@2x.png rename to DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-128@2x.png diff --git a/DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-16.png b/DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-16.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-16.png rename to DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-16.png diff --git a/DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-16@2x.png b/DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-16@2x.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-16@2x.png rename to DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-16@2x.png diff --git a/DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-256.png b/DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-256.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-256.png rename to DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-256.png diff --git a/DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-256@2x.png b/DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-256@2x.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-256@2x.png rename to DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-256@2x.png diff --git a/DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-32.png b/DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-32.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-32.png rename to DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-32.png diff --git a/DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-32@2x.png b/DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-32@2x.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-32@2x.png rename to DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-32@2x.png diff --git a/DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-512.png b/DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-512.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-512.png rename to DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-512.png diff --git a/DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-512@2x.png b/DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-512@2x.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Browser-512@2x.png rename to DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Browser-512@2x.png diff --git a/DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Contents.json b/DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/AppIcon.appiconset/Contents.json rename to DuckDuckGoVPN/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/DuckDuckGoVPN/Assets.xcassets/Contents.json b/DuckDuckGoVPN/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/DuckDuckGoVPN/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Contents.json b/DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Contents.json similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Contents.json rename to DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Contents.json diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-1024.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-1024.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-1024.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-1024.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-128.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-128.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-128.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-128.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-16.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-16.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-16.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-16.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-256.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-256.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-256.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-256.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-257.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-257.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-257.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-257.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-32.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-32.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-32.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-32.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-33.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-33.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-33.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-33.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-512.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-512.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-512.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-512.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-513.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-513.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-513.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-513.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-64.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-64.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-64.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Debug.appiconset/Debug Icon-64.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-1024.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-1024.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-1024.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-1024.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-128.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-128.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-128.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-128.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-16.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-16.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-16.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-16.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-256.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-256.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-256.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-256.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-257.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-257.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-257.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-257.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-32.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-32.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-32.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-32.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-33.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-33.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-33.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-33.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-512.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-512.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-512.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-512.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-513.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-513.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-513.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-513.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-64.png b/DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-64.png similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Beta Icon-64.png rename to DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Beta Icon-64.png diff --git a/DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Contents.json b/DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Contents.json similarity index 100% rename from DuckDuckGoAgent/Assets.xcassets/Icon - Review.appiconset/Contents.json rename to DuckDuckGoVPN/Assets.xcassets/Icon - Review.appiconset/Contents.json diff --git a/Configuration/App/NetworkProtection/NetworkProtectionStopVPN.xcconfig b/DuckDuckGoVPN/Bundle+Configuration.swift similarity index 58% rename from Configuration/App/NetworkProtection/NetworkProtectionStopVPN.xcconfig rename to DuckDuckGoVPN/Bundle+Configuration.swift index b3ab9f58b4..936c44a4a8 100644 --- a/Configuration/App/NetworkProtection/NetworkProtectionStopVPN.xcconfig +++ b/DuckDuckGoVPN/Bundle+Configuration.swift @@ -1,3 +1,6 @@ +// +// Bundle+Configuration.swift +// // Copyright © 2023 DuckDuckGo. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,11 +16,16 @@ // limitations under the License. // -#include "NetworkProtectionVPNHelpersBase.xcconfig" +import Foundation + +extension Bundle { + private static let networkExtensionBundleIDKey = "SYSEX_BUNDLE_ID" -PRODUCT_BUNDLE_IDENTIFIER=com.duckduckgo.macos.browser.network-protection.stop-vpn + var networkExtensionBundleID: String { + guard let bundleID = object(forInfoDictionaryKey: Self.networkExtensionBundleIDKey) as? String else { + fatalError("Info.plist is missing \(Self.networkExtensionBundleIDKey)") + } -PROVISIONING_PROFILE_SPECIFIER[config=Debug][sdk=macosx*] = -PROVISIONING_PROFILE_SPECIFIER[config=CI][sdk=macosx*] = -PROVISIONING_PROFILE_SPECIFIER[config=Release][sdk=macosx*] = MacOS Browser NetP - Stop VPN -PROVISIONING_PROFILE_SPECIFIER[config=Review][sdk=macosx*] = MacOS Browser NetP - Stop VPN + return bundleID + } +} diff --git a/DuckDuckGoVPN/DuckDuckGoVPN.entitlements b/DuckDuckGoVPN/DuckDuckGoVPN.entitlements new file mode 100644 index 0000000000..653311b9ec --- /dev/null +++ b/DuckDuckGoVPN/DuckDuckGoVPN.entitlements @@ -0,0 +1,20 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider-systemextension + + com.apple.developer.system-extension.install + + com.apple.security.application-groups + + $(NETP_APP_GROUP) + + keychain-access-groups + + $(NETP_APP_GROUP) + + + diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift new file mode 100644 index 0000000000..39492c7f20 --- /dev/null +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -0,0 +1,180 @@ +// +// DuckDuckGoVPNAppDelegate.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Cocoa +import Combine +import Common +import Networking +import NetworkExtension +import NetworkProtection +import NetworkProtectionIPC +import NetworkProtectionUI +import ServiceManagement +import PixelKit + +@objc(Application) +final class DuckDuckGoVPNApplication: NSApplication { + private let _delegate = DuckDuckGoVPNAppDelegate() + + override init() { + os_log(.error, log: .networkProtection, "🟢 Status Bar Agent starting: %{public}d", NSRunningApplication.current.processIdentifier) + + // prevent agent from running twice + if let anotherInstance = NSRunningApplication.runningApplications(withBundleIdentifier: Bundle.main.bundleIdentifier!).first(where: { $0 != .current }) { + os_log(.error, log: .networkProtection, "🔴 Stopping: another instance is running: %{public}d.", anotherInstance.processIdentifier) + exit(0) + } + + super.init() + self.delegate = _delegate + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +@main +final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { + + private static let recentThreshold: TimeInterval = 5.0 + + private let appLauncher = AppLauncher() + private let bouncer = NetworkProtectionBouncer() + + var networkExtensionBundleID: String { + Bundle.main.networkExtensionBundleID + } + +#if NETP_SYSTEM_EXTENSION + private lazy var networkExtensionController = NetworkExtensionController(extensionBundleID: networkExtensionBundleID) +#endif + + private lazy var tunnelController = NetworkProtectionTunnelController( + networkExtensionBundleID: networkExtensionBundleID, + networkExtensionController: networkExtensionController, + settings: .init(defaults: .shared)) + + /// An IPC server that provides access to the tunnel controller. + /// + /// This is used by our main app to control the tunnel through the VPN login item. + /// + private lazy var tunnelControllerIPCService: TunnelControllerIPCService = { + let ipcServer = TunnelControllerIPCService( + tunnelController: tunnelController, + networkExtensionController: networkExtensionController, + statusReporter: statusReporter) + ipcServer.activate() + return ipcServer + }() + + private lazy var statusReporter: NetworkProtectionStatusReporter = { + let errorObserver = ConnectionErrorObserverThroughSession( + platformNotificationCenter: NSWorkspace.shared.notificationCenter, + platformDidWakeNotification: NSWorkspace.didWakeNotification) + + let statusObserver = ConnectionStatusObserverThroughSession( + platformNotificationCenter: NSWorkspace.shared.notificationCenter, + platformDidWakeNotification: NSWorkspace.didWakeNotification) + + let serverInfoObserver = ConnectionServerInfoObserverThroughSession( + platformNotificationCenter: NSWorkspace.shared.notificationCenter, + platformDidWakeNotification: NSWorkspace.didWakeNotification) + + return DefaultNetworkProtectionStatusReporter( + statusObserver: statusObserver, + serverInfoObserver: serverInfoObserver, + connectionErrorObserver: errorObserver, + connectivityIssuesObserver: ConnectivityIssueObserverThroughDistributedNotifications(), + controllerErrorMessageObserver: ControllerErrorMesssageObserverThroughDistributedNotifications() + ) + }() + + /// The status bar NetworkProtection menu + /// + /// For some reason the App will crash if this is initialized right away, which is why it was changed to be lazy. + /// + @MainActor + private lazy var networkProtectionMenu: StatusBarMenu = { + #if DEBUG + let iconProvider = DebugMenuIconProvider() + #elseif REVIEW + let iconProvider = ReviewMenuIconProvider() + #else + let iconProvider = MenuIconProvider() + #endif + + let menuItems = [ + StatusBarMenu.MenuItem(name: UserText.networkProtectionStatusMenuShareFeedback, action: { [weak self] in + await self?.appLauncher.launchApp(withCommand: .shareFeedback) + }), + StatusBarMenu.MenuItem(name: UserText.networkProtectionStatusMenuOpenDuckDuckGo, action: { [weak self] in + await self?.appLauncher.launchApp(withCommand: .justOpen) + }) + ] + + let onboardingStatusPublisher = UserDefaults.shared.publisher(for: \.networkProtectionOnboardingStatusRawValue).map { rawValue in + OnboardingStatus(rawValue: rawValue) ?? .default + }.eraseToAnyPublisher() + + return StatusBarMenu( + onboardingStatusPublisher: onboardingStatusPublisher, + statusReporter: statusReporter, + controller: tunnelController, + iconProvider: iconProvider, + menuItems: menuItems) + }() + + func applicationDidFinishLaunching(_ aNotification: Notification) { + APIRequest.Headers.setUserAgent(UserAgent.duckDuckGoUserAgent()) + + os_log("DuckDuckGoVPN started", log: .networkProtectionLoginItemLog, type: .info) + networkProtectionMenu.show() + + bouncer.requireAuthTokenOrKillApp() + + // Initialize the IPC server + _ = tunnelControllerIPCService + + PixelKit.setUp(dryRun: false, appVersion: AppVersion.shared.versionNumber, defaultHeaders: [:], log: .networkProtectionPixel) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping (Error?) -> Void) in + + let url = URL.pixelUrl(forPixelNamed: pixelName) + let apiHeaders = APIRequest.Headers(additionalHeaders: headers) // workaround - Pixel class should really handle APIRequest.Headers by itself + let configuration = APIRequest.Configuration(url: url, method: .get, queryParameters: parameters, headers: apiHeaders) + let request = APIRequest(configuration: configuration) + + request.fetch { _, error in + onComplete(error) + } + } + } +} + +extension NSApplication { + + enum RunType: Int, CustomStringConvertible { + case normal + var description: String { + switch self { + case .normal: return "normal" + } + } + } + static var runType: RunType { .normal } + +} diff --git a/DuckDuckGoAgent/DuckDuckGoAgentAppStore.entitlements b/DuckDuckGoVPN/DuckDuckGoVPNAppStore.entitlements similarity index 100% rename from DuckDuckGoAgent/DuckDuckGoAgentAppStore.entitlements rename to DuckDuckGoVPN/DuckDuckGoVPNAppStore.entitlements diff --git a/DuckDuckGoAgent/DuckDuckGoAgent.entitlements b/DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements similarity index 67% rename from DuckDuckGoAgent/DuckDuckGoAgent.entitlements rename to DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements index 789b6b3174..a6ed34f64f 100644 --- a/DuckDuckGoAgent/DuckDuckGoAgent.entitlements +++ b/DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements @@ -2,13 +2,19 @@ - keychain-access-groups + com.apple.developer.networking.networkextension - $(NETP_APP_GROUP) + packet-tunnel-provider + com.apple.developer.system-extension.install + com.apple.security.application-groups $(NETP_APP_GROUP) + keychain-access-groups + + $(NETP_APP_GROUP) + diff --git a/DuckDuckGoAgent/Info-AppStore.plist b/DuckDuckGoVPN/Info-AppStore.plist similarity index 81% rename from DuckDuckGoAgent/Info-AppStore.plist rename to DuckDuckGoVPN/Info-AppStore.plist index b6f094baff..1861d4c159 100644 --- a/DuckDuckGoAgent/Info-AppStore.plist +++ b/DuckDuckGoVPN/Info-AppStore.plist @@ -6,5 +6,7 @@ $(NETP_APP_GROUP) LSApplicationCategoryType public.app-category.productivity + CFBundleShortVersionString + $(MARKETING_VERSION) diff --git a/DuckDuckGoAgent/Info.plist b/DuckDuckGoVPN/Info.plist similarity index 75% rename from DuckDuckGoAgent/Info.plist rename to DuckDuckGoVPN/Info.plist index 821d91c50f..7627fdd9c9 100644 --- a/DuckDuckGoAgent/Info.plist +++ b/DuckDuckGoVPN/Info.plist @@ -6,7 +6,11 @@ $(DISTRIBUTED_NOTIFICATIONS_PREFIX) NETP_APP_GROUP $(NETP_APP_GROUP) + SYSEX_BUNDLE_ID + $(SYSEX_BUNDLE_ID) LSApplicationCategoryType public.app-category.productivity + CFBundleShortVersionString + $(MARKETING_VERSION) diff --git a/DuckDuckGoVPN/NetworkExtensionController.swift b/DuckDuckGoVPN/NetworkExtensionController.swift new file mode 100644 index 0000000000..954f47f8ee --- /dev/null +++ b/DuckDuckGoVPN/NetworkExtensionController.swift @@ -0,0 +1,55 @@ +// +// NetworkExtensionController.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import NetworkProtectionUI +import SystemExtensionManager +import SystemExtensions + +/// Network Protection's network extension session object. +/// +/// Through this class the app that owns the VPN can interact with the network extension. +/// +final class NetworkExtensionController { + + private let systemExtensionManager: SystemExtensionManager + + init(extensionBundleID: String) { + systemExtensionManager = SystemExtensionManager(extensionBundleID: extensionBundleID) + } +} + +extension NetworkExtensionController { + func activateSystemExtension(waitingForUserApproval: @escaping () -> Void) async throws { + try await systemExtensionManager.activate( + waitingForUserApproval: waitingForUserApproval) + + try? await Task.sleep(nanoseconds: 300 * NSEC_PER_MSEC) + } + + func deactivateSystemExtension() async throws { + do { + try await systemExtensionManager.deactivate() + } catch OSSystemExtensionError.extensionNotFound { + // This is an intentional no-op to silence this type of error + // since on deactivation this is ok. + } catch { + throw error + } + } +} diff --git a/DuckDuckGoAgent/NetworkProtectionBouncer.swift b/DuckDuckGoVPN/NetworkProtectionBouncer.swift similarity index 100% rename from DuckDuckGoAgent/NetworkProtectionBouncer.swift rename to DuckDuckGoVPN/NetworkProtectionBouncer.swift diff --git a/DuckDuckGoVPN/TunnelControllerIPCService.swift b/DuckDuckGoVPN/TunnelControllerIPCService.swift new file mode 100644 index 0000000000..7d6f8263dc --- /dev/null +++ b/DuckDuckGoVPN/TunnelControllerIPCService.swift @@ -0,0 +1,117 @@ +// +// TunnelControllerIPCService.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import Foundation +import NetworkProtection +import NetworkProtectionIPC + +/// Takes care of handling incoming IPC requests from clients that need to be relayed to the tunnel, and handling state +/// changes that need to be relayed back to IPC clients. +/// +/// This also includes the tunnel settings which are controller through shared `UserDefaults` as a form of IPC. +/// Clients can edit those defaults and this class will observe the changes and relay them to the runnel. +/// +final class TunnelControllerIPCService { + private let tunnelController: TunnelController + private let networkExtensionController: NetworkExtensionController + private let server: NetworkProtectionIPC.TunnelControllerIPCServer + private let statusReporter: NetworkProtectionStatusReporter + private var cancellables = Set() + + init(tunnelController: TunnelController, + networkExtensionController: NetworkExtensionController, + statusReporter: NetworkProtectionStatusReporter) { + + self.tunnelController = tunnelController + self.networkExtensionController = networkExtensionController + server = .init(machServiceName: Bundle.main.bundleIdentifier!) + self.statusReporter = statusReporter + + subscribeToErrorChanges() + subscribeToStatusUpdates() + subscribeToServerChanges() + + server.serverDelegate = self + } + + public func activate() { + server.activate() + } + + private func subscribeToErrorChanges() { + statusReporter.connectionErrorObserver.publisher + .subscribe(on: DispatchQueue.main) + .sink { [weak self] error in + self?.server.errorChanged(error) + } + .store(in: &cancellables) + } + + private func subscribeToServerChanges() { + statusReporter.serverInfoObserver.publisher + .subscribe(on: DispatchQueue.main) + .sink { [weak self] serverInfo in + self?.server.serverInfoChanged(serverInfo) + } + .store(in: &cancellables) + } + + private func subscribeToStatusUpdates() { + statusReporter.statusObserver.publisher + .subscribe(on: DispatchQueue.main) + .sink { [weak self] status in + self?.server.statusChanged(status) + } + .store(in: &cancellables) + } +} + +// MARK: - Requests from the client + +extension TunnelControllerIPCService: IPCServerInterface { + + func register() { + server.serverInfoChanged(statusReporter.serverInfoObserver.recentValue) + server.statusChanged(statusReporter.statusObserver.recentValue) + } + + func start() { + Task { + await tunnelController.start() + } + } + + func stop() { + Task { + await tunnelController.stop() + } + } + + func resetAll(uninstallSystemExtension: Bool) async { + try? await networkExtensionController.deactivateSystemExtension() + } + + func debugCommand(_ command: DebugCommand) async { + guard let activeSession = try? await ConnectionSessionUtilities.activeSession(networkExtensionBundleID: Bundle.main.networkExtensionBundleID) else { + return + } + + try? await activeSession.sendProviderRequest(.debugCommand(command)) + } +} diff --git a/DuckDuckGoAgent/UserText.swift b/DuckDuckGoVPN/UserText.swift similarity index 100% rename from DuckDuckGoAgent/UserText.swift rename to DuckDuckGoVPN/UserText.swift diff --git a/IntegrationTests/AutoconsentIntegrationTests.swift b/IntegrationTests/AutoconsentIntegrationTests.swift index ee99dfa5d2..e458fff93a 100644 --- a/IntegrationTests/AutoconsentIntegrationTests.swift +++ b/IntegrationTests/AutoconsentIntegrationTests.swift @@ -73,7 +73,7 @@ class AutoconsentIntegrationTests: XCTestCase { .first() .promise() - _=await tab.setUrl(url, userEntered: nil)?.value?.result + _=await tab.setUrl(url, userEntered: nil)?.result let cookieConsentManaged = try await cookieConsentManagedPromise.value XCTAssertTrue(cookieConsentManaged) @@ -87,7 +87,7 @@ class AutoconsentIntegrationTests: XCTestCase { let tab = self.tabViewModel.tab - _=await tab.setUrl(url, userEntered: nil)?.value?.result + _=await tab.setUrl(url, userEntered: nil)?.result // expect cookieConsent request to be published let cookieConsentPromptRequestPromise = tab.cookieConsentPromptRequestPublisher @@ -130,7 +130,7 @@ class AutoconsentIntegrationTests: XCTestCase { .first() .promise() - _=await tab.setUrl(url, userEntered: nil)?.value?.result + _=await tab.setUrl(url, userEntered: nil)?.result do { let cookieConsentManaged = try await cookieConsentManagedPromise.value @@ -182,7 +182,7 @@ class AutoconsentIntegrationTests: XCTestCase { .promise() os_log("starting navigation to http://privacy-test-pages.site/features/autoconsent/banner.html") - let navigation = await tab.setUrl(url, userEntered: nil)?.value + let navigation = tab.setUrl(url, userEntered: nil) navigation?.appendResponder(navigationResponse: { response in os_log("navigationResponse: %s", "\(String(describing: response))") diff --git a/IntegrationTests/Downloads/DownloadsIntegrationTests.swift b/IntegrationTests/Downloads/DownloadsIntegrationTests.swift index f279dd8fc1..3a59a6de02 100644 --- a/IntegrationTests/Downloads/DownloadsIntegrationTests.swift +++ b/IntegrationTests/Downloads/DownloadsIntegrationTests.swift @@ -61,7 +61,7 @@ class DownloadsIntegrationTests: XCTestCase { headers: ["Content-Disposition": "attachment; filename=\"fname_\(suffix).dat\"", "Content-Type": "text/html"]) let tab = tabViewModel.tab - _=await tab.setUrl(url, userEntered: nil)?.value?.result + _=await tab.setUrl(url, userEntered: nil)?.result let fileUrl = try await downloadTaskFuture.get().output .timeout(1, scheduler: DispatchQueue.main) { .init(TimeoutError() as NSError, isRetryable: false) }.first().promise().get() @@ -78,7 +78,7 @@ class DownloadsIntegrationTests: XCTestCase { let tab = tabViewModel.tab // load empty page let pageUrl = URL.testsServer.appendingTestParameters(data: data.html) - _=await tab.setUrl(pageUrl, userEntered: nil)?.value?.result + _=await tab.setUrl(pageUrl, userEntered: nil)?.result let downloadTaskFuture = FileDownloadManager.shared.downloadsPublisher.timeout(5).first().promise() let suffix = Int.random(in: 0.. 0, "no items") @@ -76,7 +76,7 @@ class NavigationProtectionIntegrationTests: XCTestCase { print("processing", i) // open test page if needed if tab.content.url != url { - _=try await tab.setUrl(url, userEntered: nil)?.value?.result.get() + _=try await tab.setUrl(url, userEntered: nil)?.result.get() } // extract "Expected" URL @@ -149,7 +149,7 @@ class NavigationProtectionIntegrationTests: XCTestCase { window = WindowsManager.openNewWindow(with: tab)! let url = URL(string: "https://privacy-test-pages.site/privacy-protections/referrer-trimming/")! - _=try await tab.setUrl(url, userEntered: nil)?.value?.result.get() + _=try await tab.setUrl(url, userEntered: nil)?.result.get() // run test _=try await tab.webView.evaluateJavaScript("(function() { document.getElementById('start').click(); return true })()") @@ -204,7 +204,7 @@ class NavigationProtectionIntegrationTests: XCTestCase { let url = URL(string: "https://privacy-test-pages.site/privacy-protections/gpc/")! // disable GPC redirects PrivacySecurityPreferences.shared.gpcEnabled = false - _=try await tab.setUrl(url, userEntered: nil)?.value?.result.get() + _=try await tab.setUrl(url, userEntered: nil)?.result.get() // enable GPC redirects PrivacySecurityPreferences.shared.gpcEnabled = true diff --git a/IntegrationTests/PrivacyDashboard/PrivacyDashboardIntegrationTests.swift b/IntegrationTests/PrivacyDashboard/PrivacyDashboardIntegrationTests.swift index 5d9f91aa0c..e491656a70 100644 --- a/IntegrationTests/PrivacyDashboard/PrivacyDashboardIntegrationTests.swift +++ b/IntegrationTests/PrivacyDashboard/PrivacyDashboardIntegrationTests.swift @@ -65,7 +65,7 @@ class PrivacyDashboardIntegrationTests: XCTestCase { // load the test page let url = URL(string: "http://privacy-test-pages.site/tracker-reporting/1major-via-script.html")! - _=await tab.setUrl(url, userEntered: nil)?.value?.result + _=await tab.setUrl(url, userEntered: nil)?.result let trackersCount = try await trackersCountPromise.value XCTAssertEqual(trackersCount, 1) @@ -78,7 +78,7 @@ class PrivacyDashboardIntegrationTests: XCTestCase { .timeout(10) .first() .promise() - _=await tab.setUrl(URL.testsServer, userEntered: nil)?.value?.result + _=await tab.setUrl(URL.testsServer, userEntered: nil)?.result let trackersCount2 = try await trackersCountPromise2.value XCTAssertEqual(trackersCount2, 0) diff --git a/LocalPackages/Account/Package.swift b/LocalPackages/Account/Package.swift index b7bf83f069..a994db0f39 100644 --- a/LocalPackages/Account/Package.swift +++ b/LocalPackages/Account/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["Account"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "81.3.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "82.0.1"), .package(path: "../Purchase") ], targets: [ diff --git a/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift b/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift index 196f5a1c20..465cc664ac 100644 --- a/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift +++ b/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift @@ -25,15 +25,13 @@ let nonSandboxedExtraInputFiles: Set = [ .init("BWEncryptionOutput.m", .source), .init("BWManager.swift", .source), .init("UpdateController.swift", .source), - .init("SystemExtensionManager.swift", .source), - .init("DuckDuckGo Agent.app", .unknown), + .init("DuckDuckGo VPN.app", .unknown), .init("DuckDuckGo Notifications.app", .unknown), - .init("startVPN.app", .unknown), - .init("stopVPN.app", .unknown), - .init("enableOnDemand.app", .unknown), .init("PFMoveApplication.m", .source), .init("NetworkProtectionBundle.swift", .source), .init("NetworkProtectionAppEvents.swift", .source), + .init("NetworkProtectionIPCTunnelController.swift", .source), + .init("NetworkProtectionNavBarPopoverManager.swift", .source), .init("KeychainType+ClientDefault.swift", .source) ] diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index b68e50ebc4..2131c4f397 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -22,14 +22,15 @@ import PackageDescription let package = Package( name: "DataBrokerProtection", - platforms: [ .macOS(.v11) ], + platforms: [ .macOS("11.4") ], products: [ .library( name: "DataBrokerProtection", targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "81.3.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "82.0.1"), + .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions") ], targets: [ @@ -37,6 +38,7 @@ let package = Package( name: "DataBrokerProtection", dependencies: [ .product(name: "BrowserServicesKit", package: "BrowserServicesKit"), + .product(name: "PixelKit", package: "PixelKit"), .product(name: "SwiftUIExtensions", package: "SwiftUIExtensions") ], resources: [.process("Resources")] diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/ActionsHandler.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/ActionsHandler.swift index ca9896d6cc..23b8cb68f6 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/ActionsHandler.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/ActionsHandler.swift @@ -27,6 +27,16 @@ final class ActionsHandler { self.step = step } + func currentAction() -> Action? { + guard let lastExecutedActionIndex = self.lastExecutedActionIndex else { return nil } + + if lastExecutedActionIndex < step.actions.count { + return step.actions[lastExecutedActionIndex] + } else { + return nil + } + } + func nextAction() -> Action? { guard let lastExecutedActionIndex = self.lastExecutedActionIndex else { // If last executed action index is nil. Means we didn't execute any action, so we return the first action. diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift index a40e65499d..eee5f834f9 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift @@ -112,6 +112,7 @@ public protocol InMemoryDataCacheDelegate: AnyObject { public final class InMemoryDataCache { var profile: DataBrokerProtectionProfile? var brokerProfileQueryData = [BrokerProfileQueryData]() + private let mapper = MapperToUI() weak var delegate: InMemoryDataCacheDelegate? weak var scanDelegate: DBPUIScanOps? @@ -127,12 +128,9 @@ public final class InMemoryDataCache { } extension InMemoryDataCache: DBPUICommunicationDelegate { - func setState() { + func setState() async { // other set state tasks - - Task { - await delegate?.flushCache(profile: profile) - } + await delegate?.flushCache(profile: profile) } private func indexForName(matching name: DBPUIUserProfileName, in profile: DataBrokerProtectionProfile) -> Int? { @@ -269,4 +267,16 @@ extension InMemoryDataCache: DBPUICommunicationDelegate { func startScanAndOptOut() -> Bool { return scanDelegate?.startScan() ?? false } + + func getInitialScanState() async -> DBPUIInitialScanState { + await scanDelegate?.updateCacheWithCurrentScans() + + return mapper.initialScanState(brokerProfileQueryData) + } + + func getMaintananceScanState() async -> DBPUIScanAndOptOutMaintenanceState { + await scanDelegate?.updateCacheWithCurrentScans() + + return mapper.maintenanceScanState(brokerProfileQueryData) + } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUICommunicationModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUICommunicationModel.swift index b0420ed1ad..dbaaf0de3a 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUICommunicationModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUICommunicationModel.swift @@ -65,26 +65,6 @@ struct DBPUISetState: Codable { let state: DBPUIState } -/// Enum representing possible scan and opt out states -enum DBPUIScanAndOptOutStatus: String, Codable { - case notRunning - case quickScan - case noProfileMatch - case removingProfile - case complete - - static func from(schedulerStatus status: DataBrokerProtectionSchedulerStatus) -> DBPUIScanAndOptOutStatus { - switch status { - case .idle: - return .notRunning - case .running: - return .removingProfile - case .stopped: - return .complete - } - } -} - /// Message Object representing a user profile name struct DBPUIUserProfileName: Codable { let first: String @@ -126,8 +106,12 @@ struct DBPUIAddressAtIndex: Codable { } /// Message Object representing a data broker -struct DBPUIDataBroker: Codable { +struct DBPUIDataBroker: Codable, Hashable { let name: String + + func hash(into hasher: inout Hasher) { + hasher.combine(name) + } } /// Message Object representing a requested change to the user profile's brith year @@ -140,8 +124,10 @@ struct DBPUIBirthYear: Codable { /// and addresses that were matched struct DBPUIDataBrokerProfileMatch: Codable { let dataBroker: DBPUIDataBroker - let names: [DBPUIUserProfileName] + let name: String let addresses: [DBPUIUserProfileAddress] + let alternativeNames: [String] + let relatives: [String] } /// Protocol to represent a message that can be passed from the host to the UI @@ -152,9 +138,50 @@ struct DBPUIWebSetState: DBPUISendableMessage { let state: DBPUIState } -/// Message representing the state of any scans and opt outs -struct DBPUIScanAndOptOutState: DBPUISendableMessage { - let status: DBPUIScanAndOptOutStatus +/// Message representing the state of any scans and opt outs without state and grouping removed profiles by broker +struct DBPUIScanAndOptOutMaintenanceState: DBPUISendableMessage { let inProgressOptOuts: [DBPUIDataBrokerProfileMatch] - let completedOptOuts: [DBPUIDataBrokerProfileMatch] + let completedOptOuts: [DBPUIOptOutMatch] + let scanSchedule: DBPUIScanSchedule + let scanHistory: DBPUIScanHistory +} + +struct DBPUIOptOutMatch: DBPUISendableMessage { + let dataBroker: DBPUIDataBroker + let matches: Int +} + +/// Data representing the initial scan progress +struct DBPUIScanProgress: DBPUISendableMessage { + let currentScans: Int + let totalScans: Int +} + +/// Data to represent the intial scan state +/// It will show the current scans + total, and the results found +struct DBPUIInitialScanState: DBPUISendableMessage { + let resultsFound: [DBPUIDataBrokerProfileMatch] + let scanProgress: DBPUIScanProgress +} + +struct DBUIScanDate: DBPUISendableMessage { + let date: Double + let dataBrokers: [DBPUIDataBroker] +} + +struct DBPUIScanSchedule: DBPUISendableMessage { + let lastScan: DBUIScanDate + let nextScan: DBUIScanDate +} + +struct DBPUIScanHistory: DBPUISendableMessage { + let sitesScanned: Int + let scansCompleted: Int +} + +extension DBPUIInitialScanState { + static var empty: DBPUIInitialScanState { + .init(resultsFound: [DBPUIDataBrokerProfileMatch](), + scanProgress: DBPUIScanProgress(currentScans: 0, totalScans: 0)) + } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift index 4b51b593d7..d0d5582032 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift @@ -23,53 +23,28 @@ import BrowserServicesKit protocol DBPUIScanOps: AnyObject { func startScan() -> Bool + func updateCacheWithCurrentScans() async } final class DBPUIViewModel { private let dataManager: DataBrokerProtectionDataManaging private let scheduler: DataBrokerProtectionScheduler - private let notificationCenter: NotificationCenter private let privacyConfig: PrivacyConfigurationManaging? private let prefs: ContentScopeProperties? private var communicationLayer: DBPUICommunicationLayer? private var webView: WKWebView? - private var cancellables = Set() - private var lastSchedulerStatus: DataBrokerProtectionSchedulerStatus = .idle - init(dataManager: DataBrokerProtectionDataManaging, scheduler: DataBrokerProtectionScheduler, - notificationCenter: NotificationCenter = .default, privacyConfig: PrivacyConfigurationManaging? = nil, - prefs: ContentScopeProperties? = nil, webView: WKWebView? = nil) { + init(dataManager: DataBrokerProtectionDataManaging, + scheduler: DataBrokerProtectionScheduler, + privacyConfig: PrivacyConfigurationManaging? = nil, + prefs: ContentScopeProperties? = nil, + webView: WKWebView? = nil) { self.dataManager = dataManager self.scheduler = scheduler - self.notificationCenter = notificationCenter self.privacyConfig = privacyConfig self.prefs = prefs self.webView = webView - - setupNotifications() - setupCancellable() - } - - private func setupNotifications() { - notificationCenter.addObserver(self, - selector: #selector(reloadData), - name: DataBrokerProtectionNotifications.didFinishScan, - object: nil) - - notificationCenter.addObserver(self, - selector: #selector(reloadData), - name: DataBrokerProtectionNotifications.didFinishOptOut, - object: nil) - } - - private func setupCancellable() { - scheduler.statusPublisher - .receive(on: DispatchQueue.main) - .sink { [weak self] status in - self?.lastSchedulerStatus = status - self?.reloadData() - }.store(in: &cancellables) } @MainActor func setupCommunicationLayer() -> WKWebViewConfiguration? { @@ -87,52 +62,6 @@ final class DBPUIViewModel { return configuration } - - @objc func reloadData() { - guard let webView = webView else { return } - - Task { - var inProgress: [DBPUIDataBrokerProfileMatch] = [] - var completed: [DBPUIDataBrokerProfileMatch] = [] - // Step 1 - Get Data from database (brokerInfo) - let brokerInfoData = await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) - - // Step 3 - For profileQueryData in brokerInfo - for profileQueryData in brokerInfoData { - // Step 3a - For optOut in profileQueryData - for optOutOperationData in profileQueryData.optOutOperationsData { - // if optOut.extractedProfile.removedData == nil - if optOutOperationData.extractedProfile.removedDate == nil { - // Add as a pending removal profile - inProgress.append(DBPUIDataBrokerProfileMatch( - dataBroker: DBPUIDataBroker(name: profileQueryData.dataBroker.name), - names: [DBPUIUserProfileName(first: optOutOperationData.extractedProfile.fullName ?? "", middle: nil, last: "", suffix: nil)], - addresses: optOutOperationData.extractedProfile.addresses?.map { - DBPUIUserProfileAddress(street: $0.fullAddress, city: $0.city, state: $0.state, zipCode: nil) - } ?? [] - )) - } else { - // else add as removed profile - completed.append(DBPUIDataBrokerProfileMatch( - dataBroker: DBPUIDataBroker(name: profileQueryData.dataBroker.name), - names: [DBPUIUserProfileName(first: optOutOperationData.extractedProfile.fullName ?? "", middle: nil, last: "", suffix: nil)], - addresses: optOutOperationData.extractedProfile.addresses?.map { - DBPUIUserProfileAddress(street: $0.fullAddress, city: $0.city, state: $0.state, zipCode: nil) - } ?? [] - )) - } - } - } - - let message = DBPUIScanAndOptOutState( - status: DBPUIScanAndOptOutStatus.from(schedulerStatus: self.lastSchedulerStatus), - inProgressOptOuts: inProgress, - completedOptOuts: completed - ) - - communicationLayer?.sendMessageToUI(method: .scanAndOptOutStatusChanged, params: message, into: webView) - } - } } extension DBPUIViewModel: DBPUIScanOps { @@ -140,4 +69,8 @@ extension DBPUIViewModel: DBPUIScanOps { scheduler.scanAllBrokers() return true } + + func updateCacheWithCurrentScans() async { + _ = await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) + } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift index 5b64fd7634..7b24475c7a 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift @@ -38,6 +38,7 @@ protocol DataBrokerOperation: CCFCommunicationDelegate { var continuation: CheckedContinuation? { get set } var extractedProfile: ExtractedProfile? { get set } var shouldRunNextStep: () -> Bool { get } + var retriesCountOnError: Int { get set } func run(inputValue: InputValue, webViewHandler: WebViewHandler?, @@ -46,6 +47,7 @@ protocol DataBrokerOperation: CCFCommunicationDelegate { showWebView: Bool) async throws -> ReturnValue func executeNextStep() async + func executeCurrentAction() async } extension DataBrokerOperation { @@ -82,6 +84,7 @@ extension DataBrokerOperation { if action as? SolveCaptchaAction != nil, let captchaTransactionId = actionsHandler?.captchaTransactionId { actionsHandler?.captchaTransactionId = nil + stageCalculator?.setStage(.captchaSolve) if let captchaData = try? await captchaService.submitCaptchaToBeResolved(for: captchaTransactionId, shouldRunNextStep: shouldRunNextStep) { stageCalculator?.fireOptOutCaptchaSolve() @@ -95,6 +98,7 @@ extension DataBrokerOperation { if action.needsEmail { do { + stageCalculator?.setStage(.emailGenerate) extractedProfile?.email = try await emailService.getEmail() stageCalculator?.fireOptOutEmailGenerate() } catch { @@ -103,6 +107,16 @@ extension DataBrokerOperation { } } + if action as? GetCaptchaInfoAction != nil { + // Captcha is a third-party resource that sometimes takes more time to load + // if we are not able to get the captcha information. We will try to run the action again + // instead of failing the whole thing. + // + // https://app.asana.com/0/1203581873609357/1205476538384291/f + retriesCountOnError = 3 + stageCalculator?.setStage(.captchaParse) + } + if let extractedProfile = self.extractedProfile { await webViewHandler?.execute(action: action, data: .extractedProfile(extractedProfile)) } else { @@ -112,6 +126,7 @@ extension DataBrokerOperation { private func runEmailConfirmationAction(action: EmailConfirmationAction) async throws { if let email = extractedProfile?.email { + stageCalculator?.setStage(.emailReceive) let url = try await emailService.getConfirmationLink( from: email, numberOfRetries: 100, // Move to constant @@ -119,6 +134,7 @@ extension DataBrokerOperation { shouldRunNextStep: shouldRunNextStep ) stageCalculator?.fireOptOutEmailReceive() + stageCalculator?.setStage(.emailReceive) try? await webViewHandler?.load(url: url) stageCalculator?.fireOptOutEmailConfirm() } else { @@ -167,6 +183,7 @@ extension DataBrokerOperation { func captchaInformation(captchaInfo: GetCaptchaInfoResponse) async { do { stageCalculator?.fireOptOutCaptchaParse() + stageCalculator?.setStage(.captchaSend) actionsHandler?.captchaTransactionId = try await captchaService.submitCaptchaInformation(captchaInfo, shouldRunNextStep: shouldRunNextStep) stageCalculator?.fireOptOutCaptchaSend() @@ -191,7 +208,23 @@ extension DataBrokerOperation { } func onError(error: DataBrokerProtectionError) async { - await webViewHandler?.finish() - failed(with: error) + if retriesCountOnError > 0 { + await executeCurrentAction() + } else { + await webViewHandler?.finish() + failed(with: error) + } + } + + func executeCurrentAction() async { + let waitTimeUntilRunningTheActionAgain: TimeInterval = 3 + try? await Task.sleep(nanoseconds: UInt64(waitTimeUntilRunningTheActionAgain) * 1_000_000_000) + + if let currentAction = self.actionsHandler?.currentAction() { + retriesCountOnError -= 1 + await runNextAction(currentAction) + } else { + await onError(error: .unknown("No current action to execute")) + } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift index 5dd14e563b..382b6e3825 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift @@ -177,6 +177,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { database.updateRemovedDate(Date(), on: extractedProfileId) try updateOperationDataDates( + origin: .scan, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, @@ -198,6 +199,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } } else { try updateOperationDataDates( + origin: .scan, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: nil, @@ -207,11 +209,13 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } } catch { - handleOperationError(brokerId: brokerId, + handleOperationError(origin: .scan, + brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: nil, error: error, - database: database) + database: database, + schedulingConfig: brokerProfileQueryData.dataBroker.schedulingConfig) throw error } } @@ -244,6 +248,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { database.updateLastRunDate(Date(), brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) do { try updateOperationDataDates( + origin: .optOut, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, @@ -252,11 +257,13 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { ) } catch { handleOperationError( + origin: .optOut, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, error: error, - database: database + database: database, + schedulingConfig: brokerProfileQueryData.dataBroker.schedulingConfig ) } notificationCenter.post(name: DataBrokerProtectionNotifications.didFinishOptOut, object: brokerProfileQueryData.dataBroker.name) @@ -284,17 +291,20 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } catch { stageDurationCalculator.fireOptOutFailure() handleOperationError( + origin: .optOut, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, error: error, - database: database + database: database, + schedulingConfig: brokerProfileQueryData.dataBroker.schedulingConfig ) throw error } } internal func updateOperationDataDates( + origin: OperationPreferredDateUpdaterOrigin, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64?, @@ -302,13 +312,20 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { database: DataBrokerProtectionRepository) throws { let dateUpdater = OperationPreferredDateUpdaterUseCase(database: database) - try dateUpdater.updateOperationDataDates(brokerId: brokerId, + try dateUpdater.updateOperationDataDates(origin: origin, + brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig) } - private func handleOperationError(brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64?, error: Error, database: DataBrokerProtectionRepository) { + private func handleOperationError(origin: OperationPreferredDateUpdaterOrigin, + brokerId: Int64, + profileQueryId: Int64, + extractedProfileId: Int64?, + error: Error, + database: DataBrokerProtectionRepository, + schedulingConfig: DataBrokerScheduleConfig) { let event: HistoryEvent if let extractedProfileId = extractedProfileId { @@ -327,6 +344,19 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { database.add(event) + do { + try updateOperationDataDates( + origin: origin, + brokerId: brokerId, + profileQueryId: profileQueryId, + extractedProfileId: extractedProfileId, + schedulingConfig: schedulingConfig, + database: database + ) + } catch { + os_log("Can't update operation date after error") + } + os_log("Error on operation : %{public}@", log: .dataBrokerProtection, error.localizedDescription) } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateCalculator.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateCalculator.swift index 8b936fb351..954c6b92fd 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateCalculator.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateCalculator.swift @@ -26,7 +26,6 @@ struct OperationPreferredDateCalculator { schedulingConfig: DataBrokerScheduleConfig, isDeprecated: Bool = false) throws -> Date? { - var newDate: Date? guard let lastEvent = historyEvents.last else { throw DataBrokerProtectionError.cantCalculatePreferredRunDate } @@ -37,19 +36,17 @@ struct OperationPreferredDateCalculator { if isDeprecated { return nil } else { - newDate = Date().addingTimeInterval(schedulingConfig.maintenanceScan.hoursToSeconds) + return Date().addingTimeInterval(schedulingConfig.maintenanceScan.hoursToSeconds) } case .noMatchFound, .matchesFound: - newDate = Date().addingTimeInterval(schedulingConfig.maintenanceScan.hoursToSeconds) + return Date().addingTimeInterval(schedulingConfig.maintenanceScan.hoursToSeconds) case .error: - newDate = Date().addingTimeInterval(schedulingConfig.retryError.hoursToSeconds) + return Date().addingTimeInterval(schedulingConfig.retryError.hoursToSeconds) case .optOutStarted, .scanStarted: - newDate = currentPreferredRunDate + return currentPreferredRunDate case .optOutRequested: - newDate = Date().addingTimeInterval(schedulingConfig.confirmOptOutScan.hoursToSeconds) + return Date().addingTimeInterval(schedulingConfig.confirmOptOutScan.hoursToSeconds) } - - return returnMostRecentDate(newDate, currentPreferredRunDate) } func dateForOptOutOperation(currentPreferredRunDate: Date?, @@ -57,38 +54,26 @@ struct OperationPreferredDateCalculator { extractedProfileID: Int64?, schedulingConfig: DataBrokerScheduleConfig) throws -> Date? { - var newDate: Date? - guard let lastEvent = historyEvents.last else { throw DataBrokerProtectionError.cantCalculatePreferredRunDate } switch lastEvent.type { - case .matchesFound: if let extractedProfileID = extractedProfileID, shouldScheduleNewOptOut(events: historyEvents, extractedProfileId: extractedProfileID, schedulingConfig: schedulingConfig) { - newDate = Date() + return Date() } else { - newDate = currentPreferredRunDate + return currentPreferredRunDate } case .error: - newDate = Date().addingTimeInterval(schedulingConfig.retryError.hoursToSeconds) + return Date().addingTimeInterval(schedulingConfig.retryError.hoursToSeconds) case .optOutStarted, .scanStarted, .noMatchFound: - newDate = currentPreferredRunDate + return currentPreferredRunDate case .optOutConfirmed, .optOutRequested: - newDate = nil + return nil } - - return returnMostRecentDate(newDate, currentPreferredRunDate) - } - - private func returnMostRecentDate(_ date1: Date?, _ date2: Date?) -> Date? { - guard let date1 = date1 else { return date2 } - guard let date2 = date2 else { return date1 } - - return min(date1, date2) } // If the time elapsed since the last profile removal exceeds the current date plus maintenance period (expired), we should proceed with scheduling a new opt-out request as the broker has failed to honor the previous one. diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift index f87f696928..1b27e2025d 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift @@ -19,10 +19,16 @@ import Foundation import Common +enum OperationPreferredDateUpdaterOrigin { + case optOut + case scan +} + protocol OperationPreferredDateUpdater { var database: DataBrokerProtectionRepository { get } - func updateOperationDataDates(brokerId: Int64, + func updateOperationDataDates(origin: OperationPreferredDateUpdaterOrigin, + brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64?, schedulingConfig: DataBrokerScheduleConfig) throws @@ -31,10 +37,12 @@ protocol OperationPreferredDateUpdater { } struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { + let database: DataBrokerProtectionRepository private let calculator = OperationPreferredDateCalculator() - func updateOperationDataDates(brokerId: Int64, + func updateOperationDataDates(origin: OperationPreferredDateUpdaterOrigin, + brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64?, schedulingConfig: DataBrokerScheduleConfig) throws { @@ -42,7 +50,8 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { guard let brokerProfileQuery = database.brokerProfileQueryData(for: brokerId, and: profileQueryId) else { return } - try updateScanOperationDataDates(brokerId: brokerId, + try updateScanOperationDataDates(origin: origin, + brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, @@ -50,7 +59,8 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { // We only need to update the optOut date if we have an extracted profile ID if let extractedProfileId = extractedProfileId { - try updateOptOutOperationDataDates(brokerId: brokerId, + try updateOptOutOperationDataDates(origin: origin, + brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, @@ -73,7 +83,8 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { } } - private func updateScanOperationDataDates(brokerId: Int64, + private func updateScanOperationDataDates(origin: OperationPreferredDateUpdaterOrigin, + brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64?, schedulingConfig: DataBrokerScheduleConfig, @@ -81,11 +92,14 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { let currentScanPreferredRunDate = brokerProfileQuery.scanOperationData.preferredRunDate - let newScanPreferredRunDate = try calculator.dateForScanOperation(currentPreferredRunDate: currentScanPreferredRunDate, + var newScanPreferredRunDate = try calculator.dateForScanOperation(currentPreferredRunDate: currentScanPreferredRunDate, historyEvents: brokerProfileQuery.events, extractedProfileID: extractedProfileId, schedulingConfig: schedulingConfig, isDeprecated: brokerProfileQuery.profileQuery.deprecated) + if let newDate = newScanPreferredRunDate, origin == .optOut { + newScanPreferredRunDate = returnMostRecentDate(currentScanPreferredRunDate, newDate) + } if newScanPreferredRunDate != currentScanPreferredRunDate { updatePreferredRunDate(newScanPreferredRunDate, @@ -95,7 +109,8 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { } } - private func updateOptOutOperationDataDates(brokerId: Int64, + private func updateOptOutOperationDataDates(origin: OperationPreferredDateUpdaterOrigin, + brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64?, schedulingConfig: DataBrokerScheduleConfig, @@ -104,11 +119,15 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { let optOutOperation = brokerProfileQuery.optOutOperationsData.filter { $0.extractedProfile.id == extractedProfileId }.first let currentOptOutPreferredRunDate = optOutOperation?.preferredRunDate - let newOptOutPreferredDate = try calculator.dateForOptOutOperation(currentPreferredRunDate: currentOptOutPreferredRunDate, + var newOptOutPreferredDate = try calculator.dateForOptOutOperation(currentPreferredRunDate: currentOptOutPreferredRunDate, historyEvents: brokerProfileQuery.events, extractedProfileID: extractedProfileId, schedulingConfig: schedulingConfig) + if let newDate = newOptOutPreferredDate, origin == .scan { + newOptOutPreferredDate = returnMostRecentDate(currentOptOutPreferredRunDate, newDate) + } + if newOptOutPreferredDate != currentOptOutPreferredRunDate { updatePreferredRunDate(newOptOutPreferredDate, brokerId: brokerId, @@ -117,6 +136,13 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { } } + private func returnMostRecentDate(_ date1: Date?, _ date2: Date?) -> Date? { + guard let date1 = date1 else { return date2 } + guard let date2 = date2 else { return date1 } + + return min(date1, date2) + } + private func updatePreferredRunDate( _ date: Date?, brokerId: Int64, profileQueryId: Int64, diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OptOutOperation.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OptOutOperation.swift index 9965cd164b..1fbc344f73 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OptOutOperation.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OptOutOperation.swift @@ -38,6 +38,7 @@ final class OptOutOperation: DataBrokerOperation { var stageCalculator: DataBrokerProtectionStageDurationCalculator? private let operationAwaitTime: TimeInterval let shouldRunNextStep: () -> Bool + var retriesCountOnError: Int = 0 init(privacyConfig: PrivacyConfigurationManaging, prefs: ContentScopeProperties, @@ -97,6 +98,7 @@ final class OptOutOperation: DataBrokerOperation { } func executeNextStep() async { + retriesCountOnError = 0 // We reset the retries on error when it is successful os_log("OPTOUT Waiting %{public}f seconds...", log: .action, operationAwaitTime) try? await Task.sleep(nanoseconds: UInt64(operationAwaitTime) * 1_000_000_000) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift index 66fa4bb3ec..66293f1552 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift @@ -38,13 +38,14 @@ final class ScanOperation: DataBrokerOperation { var stageCalculator: DataBrokerProtectionStageDurationCalculator? private let operationAwaitTime: TimeInterval let shouldRunNextStep: () -> Bool + var retriesCountOnError: Int = 0 init(privacyConfig: PrivacyConfigurationManaging, prefs: ContentScopeProperties, query: BrokerProfileQueryData, emailService: EmailServiceProtocol = EmailService(), captchaService: CaptchaServiceProtocol = CaptchaService(), - operationAwaitTime: TimeInterval = 1, + operationAwaitTime: TimeInterval = 3, shouldRunNextStep: @escaping () -> Bool ) { self.privacyConfig = privacyConfig @@ -92,6 +93,7 @@ final class ScanOperation: DataBrokerOperation { } func executeNextStep() async { + retriesCountOnError = 0 // We reset the retries on error when it is successful os_log("SCAN Waiting %{public}f seconds...", log: .action, operationAwaitTime) try? await Task.sleep(nanoseconds: UInt64(operationAwaitTime) * 1_000_000_000) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift index e91e82aa8a..9e19a6f408 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift @@ -19,14 +19,29 @@ import Foundation import Common import BrowserServicesKit +import PixelKit final class DataBrokerProtectionStageDurationCalculator { + enum Stage: String { + case start + case emailGenerate = "email-generate" + case captchaParse = "captcha-parse" + case captchaSend = "captcha-send" + case captchaSolve = "captcha-solve" + case submit + case emailReceive = "email-receive" + case emailConfirm = "email-confirm" + case validate + case other + } + let handler: EventMapping let attemptId: UUID let dataBroker: String let startTime: Date var lastStateTime: Date + var stage: Stage = .other init(attemptId: UUID = UUID(), startTime: Date = Date(), @@ -55,6 +70,7 @@ final class DataBrokerProtectionStageDurationCalculator { } func fireOptOutStart() { + setStage(.start) handler.fire(.optOutStart(dataBroker: dataBroker, attemptId: attemptId)) } @@ -75,6 +91,7 @@ final class DataBrokerProtectionStageDurationCalculator { } func fireOptOutSubmit() { + setStage(.submit) handler.fire(.optOutSubmit(dataBroker: dataBroker, attemptId: attemptId, duration: durationSinceLastStage())) } @@ -87,6 +104,7 @@ final class DataBrokerProtectionStageDurationCalculator { } func fireOptOutValidate() { + setStage(.validate) handler.fire(.optOutValidate(dataBroker: dataBroker, attemptId: attemptId, duration: durationSinceLastStage())) } @@ -95,7 +113,14 @@ final class DataBrokerProtectionStageDurationCalculator { } func fireOptOutFailure() { - handler.fire(.optOutFailure(dataBroker: dataBroker, attemptId: attemptId, duration: durationSinceStartTime())) + handler.fire(.optOutFailure(dataBroker: dataBroker, attemptId: attemptId, duration: durationSinceStartTime(), stage: stage.rawValue)) + } + + // Helper methods to set the stage that is about to run. This help us + // identifying the stage so we can know which one was the one that failed. + + func setStage(_ stage: Stage) { + self.stage = stage } } @@ -105,6 +130,7 @@ public enum DataBrokerProtectionPixels: Equatable { static let appVersionParamKey = "app_version" static let attemptIdParamKey = "attempt_id" static let durationParamKey = "duration" + static let stageKey = "stage" } case error(error: DataBrokerProtectionError, dataBroker: String) @@ -125,12 +151,41 @@ public enum DataBrokerProtectionPixels: Equatable { // Process Pixels case optOutSubmitSuccess(dataBroker: String, attemptId: UUID, duration: Double) case optOutSuccess(dataBroker: String, attemptId: UUID, duration: Double) - case optOutFailure(dataBroker: String, attemptId: UUID, duration: Double) + case optOutFailure(dataBroker: String, attemptId: UUID, duration: Double, stage: String) } -public extension DataBrokerProtectionPixels { +extension DataBrokerProtectionPixels: PixelKitEvent { + public var name: String { + switch self { + case .parentChildMatches: return "dbp_macos_parent-child-broker-matches" + // SLO and SLI Pixels: https://app.asana.com/0/1203581873609357/1205337273100857/f + // Stage Pixels + case .optOutStart: return "dbp_macos_optout_stage_start" + case .optOutEmailGenerate: return "dbp_macos_optout_stage_email-generate" + case .optOutCaptchaParse: return "dbp_macos_optout_stage_captcha-parse" + case .optOutCaptchaSend: return "dbp_macos_optout_stage_captcha-send" + case .optOutCaptchaSolve: return "dbp_macos_optout_stage_captcha-solve" + case .optOutSubmit: return "dbp_macos_optout_stage_submit" + case .optOutEmailReceive: return "dbp_macos_optout_stage_email-receive" + case .optOutEmailConfirm: return "dbp_macos_optout_stage_email-confirm" + case .optOutValidate: return "dbp_macos_optout_stage_validate" + case .optOutFinish: return "dbp_macos_optout_stage_finish" + + // Process Pixels + case .optOutSubmitSuccess: return "dbp_macos_optout_process_submit-success" + case .optOutSuccess: return "dbp_macos_optout_process_success" + case .optOutFailure: return "dbp_macos_optout_process_failure" + + // Debug Pixels + case .error: return "data_broker_error" + } + } - var params: [String: String] { + public var params: [String: String]? { + parameters + } + + public var parameters: [String: String]? { switch self { case .error(let error, let dataBroker): if case let .actionFailed(actionID, message) = error { @@ -167,8 +222,8 @@ public extension DataBrokerProtectionPixels { return [Consts.dataBrokerParamKey: dataBroker, Consts.attemptIdParamKey: attemptId.uuidString, Consts.durationParamKey: String(duration)] case .optOutSuccess(let dataBroker, let attemptId, let duration): return [Consts.dataBrokerParamKey: dataBroker, Consts.attemptIdParamKey: attemptId.uuidString, Consts.durationParamKey: String(duration)] - case .optOutFailure(let dataBroker, let attemptId, let duration): - return [Consts.dataBrokerParamKey: dataBroker, Consts.attemptIdParamKey: attemptId.uuidString, Consts.durationParamKey: String(duration)] + case .optOutFailure(let dataBroker, let attemptId, let duration, let stage): + return [Consts.dataBrokerParamKey: dataBroker, Consts.attemptIdParamKey: attemptId.uuidString, Consts.durationParamKey: String(duration), Consts.stageKey: stage] } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/centeda.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/centeda.com.json index a3a0533670..130c996369 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/centeda.com.json +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/centeda.com.json @@ -1,6 +1,6 @@ { "name": "centeda.com", - "version": "0.1.0", + "version": "0.1.1", "parent": "verecor.com", "addedDatetime": 1677733200000, "steps": [ @@ -10,8 +10,8 @@ "actions": [ { "actionType": "navigate", - "id": "ef932ac1-61d8-4a6b-8577-d407aed69b6e", - "url": "https://centeda.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${ageRange}", + "id": "25990359-3d58-45de-bdfd-d524b1946e57", + "url": "https://centeda.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}", "ageRange": [ "18-30", "31-40", @@ -24,7 +24,7 @@ }, { "actionType": "extract", - "id": "c21af767-a34d-43d8-aca6-e3eae7298ef4", + "id": "7108af78-dbbf-47ec-8bb9-e44be505993e", "selector": ".search-item", "profile": { "name": { @@ -42,6 +42,9 @@ "selector": ".//div[@class='col-sm-24 col-md-8 lived-in']//li", "findElements": true }, + "relativesList": { + "selector": ".//div[@class='col-sm-24 col-md-8 related-to']//li" + }, "profileUrl": { "selector": ".get-report-btn" } @@ -60,4 +63,4 @@ "confirmOptOutScan": 72, "maintenanceScan": 240 } -} \ No newline at end of file +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/clustrmaps.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/clustrmaps.com.json index 0fb4236e4d..0aca895c02 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/clustrmaps.com.json +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/clustrmaps.com.json @@ -1,6 +1,6 @@ { "name": "clustrmaps.com", - "version": "0.1.0", + "version": "0.1.1", "parent": "neighbor.report", "addedDatetime": 1692590400000, "steps": [ @@ -10,12 +10,12 @@ "actions": [ { "actionType": "navigate", - "id": "1605ede5-2592-4733-a4a3-9a90062eabf4", - "url": "https://clustrmaps.com/persons/${firstName}-${lastName}/${stateFullCapitalize}/${cityHyphenated}" + "id": "a39655de-5c23-477d-9887-1d34966a1069", + "url": "https://clustrmaps.com/persons/${firstName}-${lastName}/${state|stateFull|capitalize}/${city|hyphenated}" }, { "actionType": "extract", - "id": "82155f29-6d66-496f-b2e0-d6f8169349bf", + "id": "4e3a628e-3634-4a2b-b632-4fbb8ce0b52b", "selector": ".//div[@itemprop='Person']", "profile": { "name": { @@ -55,4 +55,4 @@ "confirmOptOutScan": 72, "maintenanceScan": 240 } -} \ No newline at end of file +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/dataveria.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/dataveria.com.json index cf130c99b4..3dfe8e5431 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/dataveria.com.json +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/dataveria.com.json @@ -1,6 +1,6 @@ { "name": "dataveria.com", - "version": "0.1.0", + "version": "0.1.1", "parent": "verecor.com", "addedDatetime": 1677733200000, "steps": [ @@ -10,8 +10,8 @@ "actions": [ { "actionType": "navigate", - "id": "fd7b2eb7-b10c-439b-b193-22213045ea8f", - "url": "https://dataveria.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${ageRange}", + "id": "fc449310-7b7b-45d4-bcf9-0c5d51c246f8", + "url": "https://dataveria.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}", "ageRange": [ "18-30", "31-40", @@ -24,7 +24,7 @@ }, { "actionType": "extract", - "id": "bf63f265-c0a5-484e-a46b-a5758e13d007", + "id": "0481dc49-43e8-4af0-b697-680fb57ec24b", "selector": ".search-item", "profile": { "name": { @@ -64,4 +64,4 @@ "confirmOptOutScan": 72, "maintenanceScan": 240 } -} \ No newline at end of file +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/inforver.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/inforver.com.json index 85f4b0eb38..961bb83ae3 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/inforver.com.json +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/inforver.com.json @@ -1,6 +1,6 @@ { "name": "inforver.com", - "version": "0.1.0", + "version": "0.1.1", "parent": "verecor.com", "addedDatetime": 1677733200000, "steps": [ @@ -10,8 +10,8 @@ "actions": [ { "actionType": "navigate", - "id": "9848d43d-b7ea-4bce-b337-fd166578f3e7", - "url": "https://inforver.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${ageRange}", + "id": "a56ab792-fc1b-4e60-b0b9-0bd4f580476f", + "url": "https://inforver.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}", "ageRange": [ "18-30", "31-40", @@ -24,7 +24,7 @@ }, { "actionType": "extract", - "id": "90800598-e7e4-4086-a107-12424582ca34", + "id": "591ba784-106c-421b-b188-a376f1f9cb01", "selector": ".search-item", "profile": { "name": { @@ -64,4 +64,4 @@ "confirmOptOutScan": 72, "maintenanceScan": 240 } -} \ No newline at end of file +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/neighbors.report.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/neighbors.report.json index b0d1be39e6..d10f0a6ad9 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/neighbors.report.json +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/neighbors.report.json @@ -1,6 +1,6 @@ { "name": "neighbor.report", - "version": "0.1.0", + "version": "0.1.2", "addedDatetime": 1692590400000, "steps": [ { @@ -9,12 +9,12 @@ "actions": [ { "actionType": "navigate", - "id": "762a26ed-5b41-4f68-88d2-6149f61d0752", - "url": "https://neighbor.report/${firstName}-${lastName}/${stateFullHyphenated}/${cityHyphenated}" + "id": "a3e625f2-f82e-435e-963a-f4b158bf7b3d", + "url": "https://neighbor.report/${firstName}-${lastName}/${state|stateFull|hyphenated}/${city|hyphenated}" }, { "actionType": "extract", - "id": "eaf8bf52-1336-4f3e-9dc3-237d98f71a32", + "id": "58817d0e-26f6-45d7-81ce-3f5d34b3f818", "selector": ".lstd", "profile": { "name": { @@ -50,12 +50,12 @@ "actions": [ { "actionType": "navigate", - "id": "057fcd2d-cd54-4cad-919d-35fdb5b33842", + "id": "951d0377-eee5-4571-b772-7ccdb7cf7f3b", "url": "https://neighbor.report/remove" }, { "actionType": "fillForm", - "id": "b12bface-f263-46e6-88b5-3306d288fb58", + "id": "913bd774-6892-4cf1-95ec-6981956c5642", "selector": ".form-horizontal", "elements": [ { @@ -74,17 +74,17 @@ }, { "actionType": "getCaptchaInfo", - "id": "6dad4f78-26a2-48fa-9c96-19e38ad62053", + "id": "85728ef5-b9a0-4576-b5c5-a1e22c358024", "selector": ".recaptcha-div" }, { "actionType": "solveCaptcha", - "id": "f94b2c54-37bc-48cd-aaf8-c4f1cc3074b5", + "id": "bead8189-0c3a-460a-ae59-0b14a568bb72", "selector": ".recaptcha-div" }, { "actionType": "click", - "id": "cfa554fc-5d9b-42ed-ae87-b20448074bdb", + "id": "7fcd3d16-1247-47a6-8ecc-d7aee62e041c", "elements": [ { "type": "button", @@ -94,7 +94,7 @@ }, { "actionType": "click", - "id": "35cee2ee-d5c6-4ffe-9a4c-cf50ef49d872", + "id": "9d26074d-dc8d-416e-8635-b27e5c60c234", "elements": [ { "type": "button", @@ -102,19 +102,9 @@ } ] }, - { - "actionType": "click", - "id": "0799cab9-7e76-4502-812d-4e94add0038a", - "elements": [ - { - "type": "button", - "selector": ".btn-success" - } - ] - }, { "actionType": "expectation", - "id": "9064235e-b469-4f0a-bd8a-a651bb1e1a51", + "id": "a448448b-e529-4d7d-917c-d0147adc5de2", "expectations": [ { "type": "text", diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/peoplefinders.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/peoplefinders.com.json index 7dd7bacdc5..7e690167c8 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/peoplefinders.com.json +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/peoplefinders.com.json @@ -9,20 +9,20 @@ "actions": [ { "actionType": "navigate", - "id": "ac90b3c5-36e5-4669-850c-3ad233d57b81", + "id": "aafba5bd-a157-4e35-b653-0797a732d94c", "url": "https://www.peoplefinders.com/people/${firstName}-${lastName}/${state}/${city}?landing=all&age=${age}" }, { "actionType": "extract", - "id": "1d1a5dec-55e8-4bd8-a631-f00cab612d95", + "id": "b8f10f20-3363-4781-a03b-c4958b6269c7", "selector": ".record", "profile": { "name": { "selector": ".//h4[@class='record__title']" }, "alternativeNamesList": { - "selector": "(.//ul[@class='clean-list mb-0 mt-2'])[1]//li//li[@class='text-dark']", - "findElements": true + "selector": "(.//ul[@class='clean-list mb-0 mt-2'])[1]//li//li[@class='text-dark']", + "findElements": true }, "age": { "selector": ".col-lg-1" @@ -31,6 +31,10 @@ "selector": ".//div[@class='col-lg-2 col-xl-3']//li[@class='text-dark']", "findElements": true }, + "relativesList": { + "selector": ".//div[@class='col-lg-3 col-md-2']//li//li[@class='text-dark']", + "findElements": true + }, "profileUrl": { "selector": "a" } @@ -44,12 +48,12 @@ "actions": [ { "actionType": "navigate", - "id": "5cd4836a-347f-4104-836c-2b5c2ab4c0f3", + "id": "f5fbd4f5-23f7-45ed-a9ce-3e9b0a5a7a0a", "url": "https://www.peoplefinders.com/opt-out" }, { "actionType": "click", - "id": "ed8e55df-38f6-4a0c-a1e1-184dba923255", + "id": "7b33cd1b-3948-4454-8434-e703cc235123", "elements": [ { "type": "button", @@ -59,7 +63,7 @@ }, { "actionType": "fillForm", - "id": "081632a5-93f4-4161-9787-272c1f24bf97", + "id": "32056b7a-dc80-4d5d-b9cc-dccd32cb56be", "selector": ".opt-out-form", "elements": [ { @@ -74,17 +78,17 @@ }, { "actionType": "getCaptchaInfo", - "id": "c285a2bb-0bc5-4019-8281-0fc49b141f09", + "id": "5d5068aa-5c16-4fdc-8f3b-e412ad4eabed", "selector": ".g-recaptcha" }, { "actionType": "solveCaptcha", - "id": "fddbb482-fb4d-4fb2-b9dd-ccf0637ab822", + "id": "3443e060-8aee-4bd0-ab2c-ea03f8b8f93c", "selector": ".g-recaptcha" }, { "actionType": "click", - "id": "9b5c23fd-4304-4aad-a3e6-c94c33f57d36", + "id": "cb9ef5b0-0155-42f1-a766-145b3c14586b", "elements": [ { "type": "button", @@ -94,22 +98,22 @@ }, { "actionType": "emailConfirmation", - "id": "f864fc4f-d2db-4941-8f7b-04c5156f84f4", + "id": "5cc7cfa5-e8ab-4dc1-b58b-973af3d3f364", "pollingTime": 30 }, { "actionType": "getCaptchaInfo", - "id": "c15dafe2-b9b0-4fa0-acc1-b8740198b0f5", + "id": "3a44c15d-1dd0-4e92-beba-bf3d8544c6e9", "selector": ".g-recaptcha" }, { "actionType": "solveCaptcha", - "id": "4dc1239f-6fa3-4dcc-afa2-2c8823651570", + "id": "d2566371-8b02-4414-9a24-1f9d2761eb1d", "selector": ".g-recaptcha" }, { "actionType": "click", - "id": "4d4b567c-ad9c-47be-8e63-6d6ebdf95f3c", + "id": "259d8895-ac58-46b0-a209-7f209171e13c", "elements": [ { "type": "button", @@ -119,7 +123,7 @@ }, { "actionType": "expectation", - "id": "69c2fa29-0adf-4886-a190-b353ed793c5f", + "id": "ed02f55b-67b3-4efc-a3cc-ce6b6c7ceeed", "expectations": [ { "type": "url", diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/pub360.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/pub360.com.json index 4063f34c45..503392f378 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/pub360.com.json +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/pub360.com.json @@ -1,6 +1,6 @@ { "name": "pub360.com", - "version": "0.1.0", + "version": "0.1.1", "parent": "verecor.com", "addedDatetime": 1677733200000, "steps": [ @@ -10,8 +10,8 @@ "actions": [ { "actionType": "navigate", - "id": "21fbc068-79e2-4470-85ab-acf8ec09172f", - "url": "https://pub360.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${ageRange}", + "id": "72fc91c4-e8bc-4656-8260-cd3bb15e2001", + "url": "https://pub360.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}", "ageRange": [ "18-30", "31-40", @@ -24,7 +24,7 @@ }, { "actionType": "extract", - "id": "cc4c1b6f-50c1-4d98-8bd5-ad9bdcaa17db", + "id": "2cb4778d-e3d6-4432-8421-84438c280e19", "selector": ".search-item", "profile": { "name": { @@ -64,4 +64,4 @@ "confirmOptOutScan": 72, "maintenanceScan": 240 } -} \ No newline at end of file +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/spokeo.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/spokeo.com.json index 777d796604..5016e1d02e 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/spokeo.com.json +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/spokeo.com.json @@ -1,6 +1,6 @@ { "name": "spokeo.com", - "version": "0.1.0", + "version": "0.1.2", "addedDatetime": 1692590400000, "steps": [ { @@ -9,12 +9,12 @@ "actions": [ { "actionType": "navigate", - "id": "9b9ca680-a65c-4ab7-a425-466140a13a2b", - "url": "https://www.spokeo.com/${firstName}-${lastName}/${stateFull}/${city}" + "id": "169c9600-433e-4de9-b357-8c659b113556", + "url": "https://www.spokeo.com/${firstName}-${lastName}/${state|stateFull}/${city}" }, { "actionType": "extract", - "id": "77b0db3b-e7c9-4a1f-879f-04380c0f5991", + "id": "4bb496d5-63d4-4d36-bab5-e923387f8c75", "selector": ".single-column-list-item", "profile": { "name": { @@ -46,12 +46,12 @@ "actions": [ { "actionType": "navigate", - "id": "0843c5ba-866a-4bf6-85c2-1f3361c259bf", + "id": "cc521fcb-a8ea-4820-8bc1-0ccce9dbd8fd", "url": "https://www.spokeo.com/optout" }, { "actionType": "fillForm", - "id": "a322b3e2-c717-439c-8791-4e46fe184875", + "id": "147ebdbd-e96a-4c39-b1d6-7f7e77e6369d", "selector": ".optout_container", "elements": [ { @@ -66,17 +66,17 @@ }, { "actionType": "getCaptchaInfo", - "id": "89e966cb-fbc3-4e1c-ad6d-816a021f9fdf", + "id": "2e4b92a1-12ab-482d-b83e-e9de82c2b992", "selector": "#g-recaptcha" }, { "actionType": "solveCaptcha", - "id": "3e350a87-b310-4790-9351-774f72e18fcb", + "id": "4a0bbc9a-a717-4b1d-b3a6-af8131621f52", "selector": "#g-recaptcha" }, { "actionType": "click", - "id": "6896ac98-8663-4564-8836-2610060224a1", + "id": "79cb3967-20d9-46c5-b4ec-f2da86b481fb", "elements": [ { "type": "button", @@ -86,7 +86,7 @@ }, { "actionType": "expectation", - "id": "046b0dee-d380-41c6-935a-a90ecec80381", + "id": "990e3970-6d50-4bbc-9c34-c26929646083", "expectations": [ { "type": "text", @@ -97,12 +97,12 @@ }, { "actionType": "emailConfirmation", - "id": "c8d03ce7-9d0a-48fb-b98e-7f29745546be", + "id": "8993da43-4b9b-4d5b-b59a-efcc609d2583", "pollingTime": 30 }, { "actionType": "expectation", - "id": "9bbcfbf2-da07-45f9-a493-f0baddb770bb", + "id": "69810d0a-8de0-4613-ad63-f549ea72ef29", "expectations": [ { "type": "text", diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usa-people-search.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usa-people-search.com.json index d6b7b3e514..71c8e711ed 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usa-people-search.com.json +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usa-people-search.com.json @@ -1,6 +1,6 @@ { "name": "usa-people-search.com", - "version": "0.1.0", + "version": "0.1.1", "parent": "peoplefinders.com", "addedDatetime": 1678078800000, "steps": [ @@ -10,12 +10,12 @@ "actions": [ { "actionType": "navigate", - "id": "2da14c82-c712-48a8-ba4f-19706c009bb9", - "url": "https://usa-people-search.com/name/${firstName}-${lastName}/${city}-${state}?age=${age}" + "id": "2c4b31a3-661b-4f30-a4d3-b5a4a13c95db", + "url": "https://usa-people-search.com/name/${firstName|downcase}-${lastName|downcase}/${city|downcase}-${state|stateFull|downcase}?age=${age}" }, { "actionType": "extract", - "id": "4254d010-f71f-419f-b38d-ef9895a9bd44", + "id": "20a4d510-56b6-46a8-92ce-be16ed3ce049", "selector": ".card-block", "profile": { "name": { @@ -62,4 +62,4 @@ "confirmOptOutScan": 72, "maintenanceScan": 240 } -} \ No newline at end of file +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usphonebook.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usphonebook.com.json index c23bf9898d..0770b2f474 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usphonebook.com.json +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usphonebook.com.json @@ -1,6 +1,6 @@ { "name": "usphonebook.com", - "version": "0.1.0", + "version": "0.1.1", "parent": "peoplefinders.com", "addedDatetime": 1678078800000, "steps": [ @@ -10,12 +10,12 @@ "actions": [ { "actionType": "navigate", - "id": "99ede9cb-96a1-42ed-b721-53432fe4c14f", - "url": "https://www.usphonebook.com/${firstName}-${lastName}/${stateFull}/${city}" + "id": "f214150b-4f02-46e1-b7ea-81f6bb1bf097", + "url": "https://www.usphonebook.com/${firstName}-${lastName}/${state|stateFull}/${city}" }, { "actionType": "extract", - "id": "1a35ecd2-883a-45fa-b6b9-04dab35a3249", + "id": "af98bb63-b885-4f47-bb47-5f9ec5b491a4", "selector": ".ls_contacts-people-finder-wrapper", "profile": { "name": { @@ -56,4 +56,4 @@ "confirmOptOutScan": 72, "maintenanceScan": 240 } -} \ No newline at end of file +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/verecor.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/verecor.com.json index e3b30cfb4c..f493fbd347 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/verecor.com.json +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/verecor.com.json @@ -1,6 +1,6 @@ { "name": "verecor.com", - "version": "0.1.1", + "version": "0.1.2", "addedDatetime": 1677128400000, "steps": [ { @@ -9,8 +9,8 @@ "actions": [ { "actionType": "navigate", - "id": "4d728277-5921-4f86-89f2-4b9e98d3f943", - "url": "https://verecor.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${ageRange}", + "id": "6f53d146-af6a-4bce-970d-f1dcbc496037", + "url": "https://verecor.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}", "ageRange": [ "18-30", "31-40", @@ -23,7 +23,7 @@ }, { "actionType": "extract", - "id": "d147242a-ac89-4355-9abf-e8a69a4e5483", + "id": "e8c09200-030c-492a-8e54-22bc6bdb6829", "selector": ".search-item", "profile": { "name": { @@ -58,12 +58,12 @@ "actions": [ { "actionType": "navigate", - "id": "49f1769a-3337-488d-b050-0b162478b60c", + "id": "f4bbe480-a6ff-40a5-aa25-8ff9ac40c9bf", "url": "https://verecor.com/ng/control/privacy" }, { "actionType": "fillForm", - "id": "a463425d-d9c3-4780-8036-2eb6775a1adf", + "id": "1e6302e1-daf9-49d6-951f-506ea5e266a0", "selector": ".ahm", "elements": [ { @@ -82,17 +82,17 @@ }, { "actionType": "getCaptchaInfo", - "id": "f7f0406a-14d0-4bd3-b5b0-7830a4aa2b85", + "id": "6a3dc470-3bf7-4b8b-bb44-f77ef1a2c540", "selector": ".g-recaptcha" }, { "actionType": "solveCaptcha", - "id": "0f80c2c3-f672-4c44-8167-ed4e7415eed3", + "id": "83157244-c5bf-44a9-979c-679e1404d67d", "selector": ".g-recaptcha" }, { "actionType": "click", - "id": "ce76b489-4496-455c-a384-a7a8e6da9729", + "id": "15c50d7f-0e72-4509-be2b-40cde34b48e6", "elements": [ { "type": "button", @@ -102,7 +102,7 @@ }, { "actionType": "expectation", - "id": "0083b987-5c66-43ad-b6a2-7df212c552a3", + "id": "2ed336a2-a7a9-4cbd-933c-cd463df4f553", "expectations": [ { "type": "text", @@ -113,12 +113,12 @@ }, { "actionType": "emailConfirmation", - "id": "2ab47906-561f-4581-b194-cce5e53a6031", + "id": "88c09081-e848-4e75-a7b9-3ee28e95a459", "pollingTime": 30 }, { "actionType": "expectation", - "id": "5f587144-88e2-4502-9873-9974c13cb0c8", + "id": "dd03cf9f-8227-4881-86bf-09ce158bf151", "expectations": [ { "type": "text", diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/vericora.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/vericora.com.json index 707bf1a26f..814e908fae 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/vericora.com.json +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/vericora.com.json @@ -1,6 +1,6 @@ { "name": "vericora.com", - "version": "0.1.0", + "version": "0.1.1", "parent": "verecor.com", "addedDatetime": 1677733200000, "steps": [ @@ -10,8 +10,8 @@ "actions": [ { "actionType": "navigate", - "id": "d59a6a08-8e9d-47f5-9fad-b43d3f2432c5", - "url": "https://vericora.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${ageRange}", + "id": "9488e141-d109-4cbf-bc65-1b9036728ff4", + "url": "https://vericora.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}", "ageRange": [ "18-30", "31-40", @@ -24,7 +24,7 @@ }, { "actionType": "extract", - "id": "9b670f06-63c8-40a8-b5c3-b1882ff842b7", + "id": "baaecb74-8d63-496c-a3e0-a8acbdee2c99", "selector": ".search-item", "profile": { "name": { @@ -64,4 +64,4 @@ "confirmOptOutScan": 72, "maintenanceScan": 240 } -} \ No newline at end of file +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/veriforia.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/veriforia.com.json index c2da1b3cfb..121bc68d3e 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/veriforia.com.json +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/veriforia.com.json @@ -1,6 +1,6 @@ { "name": "veriforia.com", - "version": "0.1.0", + "version": "0.1.1", "parent": "verecor.com", "addedDatetime": 1677733200000, "steps": [ @@ -10,8 +10,8 @@ "actions": [ { "actionType": "navigate", - "id": "fed79efb-bfcd-461a-8721-1c79f4d732a1", - "url": "https://veriforia.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${ageRange}", + "id": "ffb30e97-b03f-4157-a511-09ad8ffb8b54", + "url": "https://veriforia.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}", "ageRange": [ "18-30", "31-40", @@ -24,7 +24,7 @@ }, { "actionType": "extract", - "id": "7e2cf1d4-39dc-45ad-ba47-a6eff875958a", + "id": "1d8d9c20-9897-4386-8bc1-bd591abe7c81", "selector": ".search-item", "profile": { "name": { @@ -64,4 +64,4 @@ "confirmOptOutScan": 72, "maintenanceScan": 240 } -} \ No newline at end of file +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionSchedulerConfig.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionSchedulerConfig.swift index 19006f8a52..e76c97156f 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionSchedulerConfig.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionSchedulerConfig.swift @@ -25,6 +25,6 @@ protocol SchedulerConfig { struct DataBrokerProtectionSchedulerConfig: SchedulerConfig { // Arbitrary numbers for now - var concurrentOperationsDifferentBrokers: Int = 1 + var concurrentOperationsDifferentBrokers: Int = 2 var intervalBetweenSameBrokerOperations: TimeInterval = 2 } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift index e71dd3698d..75addf660e 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift @@ -23,7 +23,7 @@ import UserScript import Common protocol DBPUICommunicationDelegate: AnyObject { - func setState() + func setState() async func getUserProfile() -> DBPUIUserProfile? func deleteProfileData() func addNameToCurrentUserProfile(_ name: DBPUIUserProfileName) -> Bool @@ -34,6 +34,8 @@ protocol DBPUICommunicationDelegate: AnyObject { func setAddressAtIndexInCurrentUserProfile(_ payload: DBPUIAddressAtIndex) -> Bool func removeAddressAtIndexFromUserProfile(_ index: DBPUIIndex) -> Bool func startScanAndOptOut() -> Bool + func getInitialScanState() async -> DBPUIInitialScanState + func getMaintananceScanState() async -> DBPUIScanAndOptOutMaintenanceState } enum DBPUIReceivedMethodName: String { @@ -49,11 +51,12 @@ enum DBPUIReceivedMethodName: String { case setAddressAtIndexInCurrentUserProfile case removeAddressAtIndexFromCurrentUserProfile case startScanAndOptOut + case initialScanStatus + case maintenanceScanStatus } enum DBPUISendableMethodName: String { case setState - case scanAndOptOutStatusChanged } struct DBPUICommunicationLayer: Subfeature { @@ -87,6 +90,8 @@ struct DBPUICommunicationLayer: Subfeature { case .setAddressAtIndexInCurrentUserProfile: return setAddressAtIndexInCurrentUserProfile case .removeAddressAtIndexFromCurrentUserProfile: return removeAddressAtIndexFromCurrentUserProfile case .startScanAndOptOut: return startScanAndOptOut + case .initialScanStatus: return initialScanStatus + case .maintenanceScanStatus: return maintenanceScanStatus } } @@ -116,7 +121,7 @@ struct DBPUICommunicationLayer: Subfeature { os_log("Web UI requested new state: \(result.state.rawValue)", log: .dataBrokerProtection) - delegate?.setState() + await delegate?.setState() return nil } @@ -240,6 +245,22 @@ struct DBPUICommunicationLayer: Subfeature { return DBPUIStandardResponse(version: Constants.version, success: false) } + func initialScanStatus(params: Any, origin: WKScriptMessage) async throws -> Encodable? { + guard let initialScanState = await delegate?.getInitialScanState() else { + return DBPUIStandardResponse(version: Constants.version, success: false, id: "NOT_FOUND", message: "No initial scan data found") + } + + return initialScanState + } + + func maintenanceScanStatus(params: Any, origin: WKScriptMessage) async throws -> Encodable? { + guard let maintenanceScanStatus = await delegate?.getMaintananceScanState() else { + return DBPUIStandardResponse(version: Constants.version, success: false, id: "NOT_FOUND", message: "No maintenance data found") + } + + return maintenanceScanStatus + } + func sendMessageToUI(method: DBPUISendableMethodName, params: DBPUISendableMessage, into webView: WKWebView) { broker?.push(method: method.rawValue, params: params, for: self, into: webView) } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift index 6a62208641..b27e400acb 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift @@ -23,141 +23,33 @@ import WebKit import Combine final public class DataBrokerProtectionViewController: NSViewController { - private let navigationViewModel: ContainerNavigationViewModel - private let profileViewModel: ProfileViewModel + + private enum Constants { + static let dbpUiUrl = "https://use-devtesting18.duckduckgo.com/dbp" + } + private let dataManager: DataBrokerProtectionDataManaging - private let resultsViewModel: ResultsViewModel - private let containerViewModel: ContainerViewModel private let scheduler: DataBrokerProtectionScheduler - private let notificationCenter: NotificationCenter private var webView: WKWebView? private let webUIViewModel: DBPUIViewModel - private let debugPage: String = """ - - - - - - Document - - -
- - - - - - - -
- -

- - - - - """ + private let openURLHandler: (URL?) -> Void public init(scheduler: DataBrokerProtectionScheduler, dataManager: DataBrokerProtectionDataManaging, - notificationCenter: NotificationCenter = .default, - privacyConfig: PrivacyConfigurationManaging? = nil, prefs: ContentScopeProperties? = nil) { + privacyConfig: PrivacyConfigurationManaging? = nil, + prefs: ContentScopeProperties? = nil, + openURLHandler: @escaping (URL?) -> Void) { self.scheduler = scheduler self.dataManager = dataManager - self.notificationCenter = notificationCenter + self.openURLHandler = openURLHandler self.webUIViewModel = DBPUIViewModel(dataManager: dataManager, scheduler: scheduler, privacyConfig: privacyConfig, prefs: prefs, webView: webView) - navigationViewModel = ContainerNavigationViewModel(dataManager: dataManager) - profileViewModel = ProfileViewModel(dataManager: dataManager) - - resultsViewModel = ResultsViewModel(dataManager: dataManager, - notificationCenter: notificationCenter) - - containerViewModel = ContainerViewModel(scheduler: scheduler, - dataManager: dataManager) - - dataManager.fetchProfile(ignoresCache: true) + Task { + _ = dataManager.fetchProfile(ignoresCache: true) + } super.init(nibName: nil, bundle: nil) } @@ -167,31 +59,20 @@ final public class DataBrokerProtectionViewController: NSViewController { } override public func loadView() { -// let containerView = DataBrokerProtectionContainerView( -// containerViewModel: containerViewModel, -// navigationViewModel: navigationViewModel, -// profileViewModel: profileViewModel, -// resultsViewModel: resultsViewModel) -// -// let hostingController = NSHostingController(rootView: containerView) -// view = hostingController.view - guard let configuration = webUIViewModel.setupCommunicationLayer() else { return } webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 1024, height: 768), configuration: configuration) + webView?.uiDelegate = self view = webView! - // FOR LOCAL WEB UI DEVELOPMENT: - // Comment this line 👇 - webView?.loadHTMLString(debugPage, baseURL: nil) - // Uncomment this line and add your dev URL 👇 -// webView?.load(URL(string: "https://")!) - - let button = NSButton(title: "Set State", target: self, action: #selector(webUIViewModel.reloadData)) - button.setButtonType(.momentaryLight) - button.contentTintColor = .black - button.frame = CGRect(x: 10, y: 100, width: 100, height: 50) - view.addSubview(button) + webView?.load(URL(string: Constants.dbpUiUrl)!) } } + +extension DataBrokerProtectionViewController: WKUIDelegate { + public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { + openURLHandler(navigationAction.request.url) + return nil + } +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift new file mode 100644 index 0000000000..5c18a6a9c2 --- /dev/null +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift @@ -0,0 +1,153 @@ +// +// UIMapper.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +struct MapperToUI { + + func mapToUI(_ dataBroker: DataBroker, extractedProfile: ExtractedProfile) -> DBPUIDataBrokerProfileMatch { + DBPUIDataBrokerProfileMatch( + dataBroker: mapToUI(dataBroker), + name: extractedProfile.fullName ?? "No name", + addresses: extractedProfile.addresses?.map(mapToUI) ?? [], + alternativeNames: extractedProfile.alternativeNames ?? [String](), + relatives: extractedProfile.relatives ?? [String]() + ) + } + + func mapToUI(_ dataBroker: DataBroker) -> DBPUIDataBroker { + DBPUIDataBroker(name: dataBroker.name) + } + + func mapToUI(_ address: AddressCityState) -> DBPUIUserProfileAddress { + DBPUIUserProfileAddress(street: address.fullAddress, city: address.city, state: address.state, zipCode: nil) + } + + func initialScanState(_ brokerProfileQueryData: [BrokerProfileQueryData]) -> DBPUIInitialScanState { + /// In the future we need to take into account mirror sites when counting for the total scans + /// Tech design: https://app.asana.com/0/481882893211075/1205594901067225/f + let totalScans = brokerProfileQueryData.count + let currentScans = brokerProfileQueryData.filter { $0.scanOperationData.lastRunDate != nil }.count + let scanProgress = DBPUIScanProgress(currentScans: currentScans, totalScans: totalScans) + let matches = brokerProfileQueryData.compactMap { + for extractedProfile in $0.extractedProfiles { + return mapToUI($0.dataBroker, extractedProfile: extractedProfile) + } + + return nil + } + + return .init(resultsFound: matches, scanProgress: scanProgress) + } + + func maintenanceScanState(_ brokerProfileQueryData: [BrokerProfileQueryData]) -> DBPUIScanAndOptOutMaintenanceState { + var inProgressOptOuts = [DBPUIDataBrokerProfileMatch]() + var removedProfiles = [DBPUIDataBrokerProfileMatch]() + + let scansThatRanAtLeastOnce = brokerProfileQueryData.filter { $0.scanOperationData.lastRunDate != nil } + let sitesScanned = Dictionary.init(grouping: scansThatRanAtLeastOnce, by: { $0.dataBroker.name }).count + let scansCompleted = brokerProfileQueryData.reduce(0) { result, queryData in + return result + queryData.scanOperationData.historyEvents.filter { $0.type == .scanStarted }.count + } + + brokerProfileQueryData.forEach { + for extractedProfile in $0.extractedProfiles { + let profileMatch = mapToUI($0.dataBroker, extractedProfile: extractedProfile) + if extractedProfile.removedDate == nil { + inProgressOptOuts.append(profileMatch) + } else { + removedProfiles.append(profileMatch) + } + } + } + + let completedOptOutsDictionary = Dictionary.init(grouping: removedProfiles, by: { $0.dataBroker }) + let completedOptOuts = completedOptOutsDictionary.map { (key: DBPUIDataBroker, value: [DBPUIDataBrokerProfileMatch]) in + DBPUIOptOutMatch(dataBroker: key, matches: value.count) + } + let lastScans = getLastScanInformation(brokerProfileQueryData: brokerProfileQueryData) + let nextScans = getNextScansInformation(brokerProfileQueryData: brokerProfileQueryData) + + return DBPUIScanAndOptOutMaintenanceState( + inProgressOptOuts: inProgressOptOuts, + completedOptOuts: completedOptOuts, + scanSchedule: DBPUIScanSchedule(lastScan: lastScans, nextScan: nextScans), + scanHistory: DBPUIScanHistory(sitesScanned: sitesScanned, scansCompleted: scansCompleted) + ) + } + + private func getLastScanInformation(brokerProfileQueryData: [BrokerProfileQueryData], + currentDate: Date = Date(), + format: String = "dd/MM/yyyy") -> DBUIScanDate { + let scansGroupedByLastRunDate = Dictionary.init(grouping: brokerProfileQueryData, by: { $0.scanOperationData.lastRunDate?.toFormat(format) }) + let closestScansBeforeToday = scansGroupedByLastRunDate + .filter { $0.key != nil && $0.key!.toDate(using: format) < currentDate } + .sorted { $0.key! < $1.key! } + .flatMap { [$0.key?.toDate(using: format): $0.value] } + .last + + return scanDate(element: closestScansBeforeToday) + } + + private func getNextScansInformation(brokerProfileQueryData: [BrokerProfileQueryData], + currentDate: Date = Date(), + format: String = "dd/MM/yyyy") -> DBUIScanDate { + let scansGroupedByPreferredRunDate = Dictionary.init(grouping: brokerProfileQueryData, by: { $0.scanOperationData.preferredRunDate?.toFormat(format) }) + let closestScansAfterToday = scansGroupedByPreferredRunDate + .filter { $0.key != nil && $0.key!.toDate(using: format) > currentDate } + .sorted { $0.key! < $1.key! } + .flatMap { [$0.key?.toDate(using: format): $0.value] } + .first + + return scanDate(element: closestScansAfterToday) + } + + private func scanDate(element: Dictionary.Element?) -> DBUIScanDate { + if let element = element, let date = element.key { + return DBUIScanDate( + date: date.timeIntervalSince1970, + dataBrokers: element.value.map { DBPUIDataBroker(name: $0.dataBroker.name)} + ) + } else { + return DBUIScanDate(date: 0, dataBrokers: [DBPUIDataBroker]()) + } + } +} + +extension Date { + + func toFormat(_ format: String) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = format + return dateFormatter.string(from: self) + } +} + +extension String { + + func toDate(using format: String) -> Date { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = format + + if let date = dateFormatter.date(from: self) { + return date + } else { + fatalError("String should be on the correct date format") + } + } +} diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProfileQueryOperationManagerTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProfileQueryOperationManagerTests.swift index f39175f377..ae02957f3e 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProfileQueryOperationManagerTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProfileQueryOperationManagerTests.swift @@ -16,12 +16,13 @@ // limitations under the License. // +// swiftlint:disable type_body_length + import XCTest import BrowserServicesKit @testable import DataBrokerProtection final class DataBrokerProfileQueryOperationManagerTests: XCTestCase { - let sut = DataBrokerProfileQueryOperationManager() let mockWebOperationRunner = MockWebOperationRunner() let mockDatabase = MockDatabase() @@ -453,7 +454,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase { mockDatabase.lastHistoryEventToReturn = HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .error(error: .unknown("Test error"))) let schedulingConfig = DataBrokerScheduleConfig(retryError: 1, confirmOptOutScan: 0, maintenanceScan: 0) - try sut.updateOperationDataDates(brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, database: mockDatabase) + try sut.updateOperationDataDates(origin: .scan, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, database: mockDatabase) XCTAssertTrue(mockDatabase.wasUpdatedPreferredRunDateForOptOutCalled) XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: mockDatabase.lastPreferredRunDateOnOptOut, date2: Date().addingTimeInterval(schedulingConfig.retryError.hoursToSeconds))) @@ -465,7 +466,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase { mockDatabase.lastHistoryEventToReturn = HistoryEvent(extractedProfileId: nil, brokerId: brokerId, profileQueryId: profileQueryId, type: .error(error: .unknown("Test error"))) let schedulingConfig = DataBrokerScheduleConfig(retryError: 1, confirmOptOutScan: 0, maintenanceScan: 0) - try sut.updateOperationDataDates(brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: nil, schedulingConfig: schedulingConfig, database: mockDatabase) + try sut.updateOperationDataDates(origin: .scan, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: nil, schedulingConfig: schedulingConfig, database: mockDatabase) XCTAssertTrue(mockDatabase.wasUpdatedPreferredRunDateForScanCalled) XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: mockDatabase.lastPreferredRunDateOnScan, date2: Date().addingTimeInterval(schedulingConfig.retryError.hoursToSeconds))) @@ -478,7 +479,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase { mockDatabase.lastHistoryEventToReturn = HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested) let schedulingConfig = DataBrokerScheduleConfig(retryError: 0, confirmOptOutScan: 1, maintenanceScan: 0) - try sut.updateOperationDataDates(brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, database: mockDatabase) + try sut.updateOperationDataDates(origin: .scan, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, database: mockDatabase) XCTAssertTrue(mockDatabase.wasUpdatedPreferredRunDateForScanCalled) XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: mockDatabase.lastPreferredRunDateOnScan, date2: Date().addingTimeInterval(schedulingConfig.confirmOptOutScan.hoursToSeconds))) @@ -491,7 +492,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase { mockDatabase.lastHistoryEventToReturn = HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested) let schedulingConfig = DataBrokerScheduleConfig(retryError: 0, confirmOptOutScan: 1, maintenanceScan: 0) - try sut.updateOperationDataDates(brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, database: mockDatabase) + try sut.updateOperationDataDates(origin: .scan, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, database: mockDatabase) XCTAssertTrue(mockDatabase.wasUpdatedPreferredRunDateForScanCalled) XCTAssertNil(mockDatabase.lastPreferredRunDateOnOptOut) @@ -504,7 +505,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase { mockDatabase.lastHistoryEventToReturn = HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .matchesFound(count: 0)) let schedulingConfig = DataBrokerScheduleConfig(retryError: 0, confirmOptOutScan: 0, maintenanceScan: 1) - try sut.updateOperationDataDates(brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, database: mockDatabase) + try sut.updateOperationDataDates(origin: .scan, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, database: mockDatabase) XCTAssertTrue(mockDatabase.wasUpdatedPreferredRunDateForScanCalled) XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: mockDatabase.lastPreferredRunDateOnScan, date2: Date().addingTimeInterval(schedulingConfig.maintenanceScan.hoursToSeconds))) @@ -517,7 +518,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase { mockDatabase.lastHistoryEventToReturn = HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutStarted) let schedulingConfig = DataBrokerScheduleConfig(retryError: 0, confirmOptOutScan: 0, maintenanceScan: 1) - try sut.updateOperationDataDates(brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, database: mockDatabase) + try sut.updateOperationDataDates(origin: .scan, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, database: mockDatabase) XCTAssertFalse(mockDatabase.wasUpdatedPreferredRunDateForScanCalled) XCTAssertFalse(mockDatabase.wasUpdatedPreferredRunDateForOptOutCalled) @@ -532,13 +533,62 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase { mockDatabase.lastHistoryEventToReturn = HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .scanStarted) let schedulingConfig = DataBrokerScheduleConfig(retryError: 0, confirmOptOutScan: 0, maintenanceScan: 1) - try sut.updateOperationDataDates(brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, database: mockDatabase) + try sut.updateOperationDataDates(origin: .scan, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: schedulingConfig, database: mockDatabase) XCTAssertFalse(mockDatabase.wasUpdatedPreferredRunDateForScanCalled) XCTAssertFalse(mockDatabase.wasUpdatedPreferredRunDateForOptOutCalled) XCTAssertNil(mockDatabase.lastPreferredRunDateOnScan) XCTAssertNil(mockDatabase.lastPreferredRunDateOnOptOut) } + + func testUpdatingScanDateFromOptOut_thenScanRespectMostRecentDate() throws { + let config = DataBrokerScheduleConfig(retryError: 1000, confirmOptOutScan: 1000, maintenanceScan: 1000) + + let brokerId: Int64 = 1 + let profileQueryId: Int64 = 1 + let extractedProfileId: Int64 = 1 + let currentPreferredRunDate = Date() + + let mockDataBroker = DataBroker(name: "databroker", steps: [Step](), version: "1.0", schedulingConfig: config) + let mockProfileQuery = ProfileQuery(id: profileQueryId, firstName: "a", lastName: "b", city: "c", state: "d", birthYear: 1222) + + let historyEvents = [HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested)] + let mockScanOperation = ScanOperationData(brokerId: brokerId, profileQueryId: profileQueryId, preferredRunDate: currentPreferredRunDate, historyEvents: historyEvents) + + let mockBrokerProfileQuery = BrokerProfileQueryData(dataBroker: mockDataBroker, profileQuery: mockProfileQuery, scanOperationData: mockScanOperation) + mockDatabase.brokerProfileQueryDataToReturn = [mockBrokerProfileQuery] + + try sut.updateOperationDataDates(origin: .optOut, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: config, database: mockDatabase) + + // If the date is not going to be set, we don't call the database function + XCTAssertFalse(mockDatabase.wasUpdatedPreferredRunDateForScanCalled) + XCTAssertFalse(mockDatabase.wasUpdatedPreferredRunDateForOptOutCalled) + } + + func testUpdatingScanDateFromScan_thenScanDoesNotRespectMostRecentDate() throws { + let config = DataBrokerScheduleConfig(retryError: 1000, confirmOptOutScan: 1000, maintenanceScan: 1000) + + let brokerId: Int64 = 1 + let profileQueryId: Int64 = 1 + let extractedProfileId: Int64 = 1 + let currentPreferredRunDate = Date() + let expectedPreferredRunDate = Date().addingTimeInterval(config.confirmOptOutScan.hoursToSeconds) + + let mockDataBroker = DataBroker(name: "databroker", steps: [Step](), version: "1.0", schedulingConfig: config) + let mockProfileQuery = ProfileQuery(id: profileQueryId, firstName: "a", lastName: "b", city: "c", state: "d", birthYear: 1222) + + let historyEvents = [HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested)] + let mockScanOperation = ScanOperationData(brokerId: brokerId, profileQueryId: profileQueryId, preferredRunDate: currentPreferredRunDate, historyEvents: historyEvents) + + let mockBrokerProfileQuery = BrokerProfileQueryData(dataBroker: mockDataBroker, profileQuery: mockProfileQuery, scanOperationData: mockScanOperation) + mockDatabase.brokerProfileQueryDataToReturn = [mockBrokerProfileQuery] + + try sut.updateOperationDataDates(origin: .scan, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId, schedulingConfig: config, database: mockDatabase) + + XCTAssertTrue(mockDatabase.wasUpdatedPreferredRunDateForScanCalled) + XCTAssertFalse(mockDatabase.wasUpdatedPreferredRunDateForOptOutCalled) + XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: mockDatabase.lastPreferredRunDateOnScan, date2: expectedPreferredRunDate), "\(String(describing: mockDatabase.lastPreferredRunDateOnScan)) is not equal to \(expectedPreferredRunDate)") + } } final class MockWebOperationRunner: WebOperationRunner { @@ -649,3 +699,4 @@ extension ExtractedProfile { ExtractedProfile(name: "Some name", profileUrl: "someOtherURL") } } +// swiftlint:enable type_body_length diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToUITests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToUITests.swift new file mode 100644 index 0000000000..6901bcaae3 --- /dev/null +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToUITests.swift @@ -0,0 +1,139 @@ +// +// MapperToUITests.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +import Foundation +@testable import DataBrokerProtection + +final class MapperToUITests: XCTestCase { + + private let sut = MapperToUI() + + func testWhenNoScansRanYet_thenCurrentScansAndMatchesAreEmpty() { + let brokerProfileQueryData: [BrokerProfileQueryData] = [.mock(), .mock(), .mock()] + + let result = sut.initialScanState(brokerProfileQueryData) + + XCTAssertEqual(result.scanProgress.totalScans, brokerProfileQueryData.count) + XCTAssertEqual(result.scanProgress.currentScans, 0) + XCTAssertTrue(result.resultsFound.isEmpty) + } + + func testWhenAScanRan_thenCurrentScansGetsUpdated() { + let brokerProfileQueryData: [BrokerProfileQueryData] = [.mock(), .mock(), .mock(lastRunDate: Date())] + + let result = sut.initialScanState(brokerProfileQueryData) + + XCTAssertEqual(result.scanProgress.totalScans, brokerProfileQueryData.count) + XCTAssertEqual(result.scanProgress.currentScans, 1) + XCTAssertTrue(result.resultsFound.isEmpty) + } + + func testWhenAScanRanAndHasAMatch_thenResultsFoundIsUpdated() { + let brokerProfileQueryData: [BrokerProfileQueryData] = [.mock(), .mock(), .mock(lastRunDate: Date(), extractedProfile: .mockWithRemovedDate)] + + let result = sut.initialScanState(brokerProfileQueryData) + + XCTAssertEqual(result.scanProgress.totalScans, brokerProfileQueryData.count) + XCTAssertEqual(result.scanProgress.currentScans, 1) + XCTAssertEqual(result.resultsFound.count, 1) + } + + func testWhenAllScansRan_thenCurrentScansEqualsTotalScans() { + let brokerProfileQueryData: [BrokerProfileQueryData] = [.mock(lastRunDate: Date()), .mock(lastRunDate: Date()), .mock(lastRunDate: Date())] + + let result = sut.initialScanState(brokerProfileQueryData) + + XCTAssertEqual(result.scanProgress.totalScans, result.scanProgress.currentScans) + } + + func testInProgressAndCompletedOptOuts_areMappedCorrectly() { + let brokerProfileQueryData: [BrokerProfileQueryData] = [ + .mock(extractedProfile: .mockWithRemovedDate), + .mock(extractedProfile: .mockWithoutRemovedDate), + .mock(extractedProfile: .mockWithoutRemovedDate) + ] + + let result = sut.maintenanceScanState(brokerProfileQueryData) + + XCTAssertEqual(result.completedOptOuts.count, 1) + XCTAssertEqual(result.inProgressOptOuts.count, 2) + } + + func testSitesScannedAndCompleted_areMappedCorrectly() { + let brokerProfileQueryData: [BrokerProfileQueryData] = [ + .mock(dataBrokerName: "Broker #1", + lastRunDate: Date(), + scanHistoryEvents: [ + .init(brokerId: 1, profileQueryId: 1, type: .scanStarted), + .init(brokerId: 1, profileQueryId: 1, type: .scanStarted)]), + .mock(dataBrokerName: "Broker #2", + lastRunDate: Date(), + scanHistoryEvents: [ + .init(brokerId: 1, profileQueryId: 1, type: .scanStarted), + .init(brokerId: 1, profileQueryId: 1, type: .scanStarted)]), + .mock(dataBrokerName: "Broker #3") + ] + + let result = sut.maintenanceScanState(brokerProfileQueryData) + + XCTAssertEqual(result.scanHistory.scansCompleted, 4) + XCTAssertEqual(result.scanHistory.sitesScanned, 2) + } + + func testLastScans_areMappedCorrectly() { + let brokerProfileQueryData: [BrokerProfileQueryData] = [ + .mock(dataBrokerName: "Broker #1", lastRunDate: Date().yesterday), + .mock(dataBrokerName: "Broker #2", lastRunDate: Date().yesterday), + .mock(dataBrokerName: "Broker #3") + ] + + let result = sut.maintenanceScanState(brokerProfileQueryData) + + XCTAssertEqual(result.scanSchedule.lastScan.dataBrokers.count, 2) + XCTAssertTrue(areDatesEqualsOnDayMonthAndYear(date1: Date().yesterday, date2: Date(timeIntervalSince1970: result.scanSchedule.lastScan.date))) + } + + func testNextScans_areMappedCorrectly() { + let brokerProfileQueryData: [BrokerProfileQueryData] = [ + .mock(dataBrokerName: "Broker #1", preferredRunDate: Date().tomorrow), + .mock(dataBrokerName: "Broker #2", preferredRunDate: Date().tomorrow), + .mock(dataBrokerName: "Broker #3") + ] + + let result = sut.maintenanceScanState(brokerProfileQueryData) + + XCTAssertEqual(result.scanSchedule.nextScan.dataBrokers.count, 2) + XCTAssertTrue(areDatesEqualsOnDayMonthAndYear(date1: Date().tomorrow, date2: Date(timeIntervalSince1970: result.scanSchedule.nextScan.date))) + } +} + +extension Date { + + var yesterday: Date? { + let calendar = Calendar.current + + return calendar.date(byAdding: .day, value: -1, to: self) + } + + var tomorrow: Date? { + let calendar = Calendar.current + + return calendar.date(byAdding: .day, value: 1, to: self) + } +} diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift index 9b4555eb42..58629eafce 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift @@ -25,16 +25,26 @@ import GRDB @testable import DataBrokerProtection extension BrokerProfileQueryData { - static func mock(with steps: [Step] = [Step]()) -> BrokerProfileQueryData { + static func mock(with steps: [Step] = [Step](), + dataBrokerName: String = "test", + lastRunDate: Date? = nil, + preferredRunDate: Date? = nil, + extractedProfile: ExtractedProfile? = nil, + scanHistoryEvents: [HistoryEvent] = [HistoryEvent]()) -> BrokerProfileQueryData { BrokerProfileQueryData( dataBroker: DataBroker( - name: "test", + name: dataBrokerName, steps: steps, version: "1.0.0", schedulingConfig: DataBrokerScheduleConfig.mock ), profileQuery: ProfileQuery(firstName: "John", lastName: "Doe", city: "Miami", state: "FL", birthYear: 50), - scanOperationData: ScanOperationData(brokerId: 1, profileQueryId: 1, historyEvents: [HistoryEvent]()) + scanOperationData: ScanOperationData(brokerId: 1, + profileQueryId: 1, + preferredRunDate: preferredRunDate, + historyEvents: scanHistoryEvents, + lastRunDate: lastRunDate), + optOutOperationsData: extractedProfile != nil ? [.mock(with: extractedProfile!)] : [OptOutOperationData]() ) } } @@ -673,6 +683,10 @@ final class MockDatabase: DataBrokerProtectionRepository { func brokerProfileQueryData(for brokerId: Int64, and profileQueryId: Int64) -> BrokerProfileQueryData? { wasBrokerProfileQueryDataCalled = true + if !brokerProfileQueryDataToReturn.isEmpty { + return brokerProfileQueryDataToReturn.first + } + if let lastHistoryEventToReturn = self.lastHistoryEventToReturn { let scanOperationData = ScanOperationData(brokerId: brokerId, profileQueryId: profileQueryId, historyEvents: [lastHistoryEventToReturn]) @@ -718,7 +732,9 @@ final class MockDatabase: DataBrokerProtectionRepository { func fetchLastEvent(brokerId: Int64, profileQueryId: Int64) -> HistoryEvent? { wasFetchLastHistoryEventCalled = true - + if let event = brokerProfileQueryDataToReturn.first?.events.last { + return event + } return lastHistoryEventToReturn } diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateCalculatorTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateCalculatorTests.swift index ca980b2caf..c579c9ac55 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateCalculatorTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateCalculatorTests.swift @@ -18,7 +18,6 @@ import XCTest @testable import DataBrokerProtection -// swiftlint:disable type_body_length // https://app.asana.com/0/1204586965688315/1204834439855281/f final class OperationPreferredDateCalculatorTests: XCTestCase { @@ -59,7 +58,7 @@ final class OperationPreferredDateCalculatorTests: XCTestCase { let calculator = OperationPreferredDateCalculator() - let actualScanDate = try calculator.dateForScanOperation(currentPreferredRunDate: nil, + let actualScanDate = try calculator.dateForScanOperation(currentPreferredRunDate: Date(), historyEvents: historyEvents, extractedProfileID: nil, schedulingConfig: schedulingConfig, @@ -214,98 +213,6 @@ final class OperationPreferredDateCalculatorTests: XCTestCase { XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedScanDate, date2: actualScanDate)) } - // If we have a most recent date saved, the calculator should not change it no matter the case. - - func testNoMatchFoundWithRecentDate_thenScanDateDoesNotChange() throws { - let expectedScanDate = Date() - - let historyEvents = [ - HistoryEvent(extractedProfileId: 1, - brokerId: 1, - profileQueryId: 1, - type: .noMatchFound)] - - let calculator = OperationPreferredDateCalculator() - - let actualScanDate = try calculator.dateForScanOperation(currentPreferredRunDate: Date(), - historyEvents: historyEvents, - extractedProfileID: nil, - schedulingConfig: schedulingConfig) - - XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedScanDate, date2: actualScanDate)) - } - - /* - If the time elapsed since the last profile removal exceeds the current date plus maintenance period (expired), we should proceed with scheduling a new opt-out request as the broker has failed to honor the previous one. - */ - func testMatchFoundWithExpiredProfileWithRecentDate_thenScanDateDoesNotChange() throws { - let expiredDate = Date().addingTimeInterval(-schedulingConfig.maintenanceScan.hoursToSeconds) - - let expectedScanDate = Date() - - let historyEvents = [ - HistoryEvent(extractedProfileId: 1, - brokerId: 1, - profileQueryId: 1, - type: .optOutRequested, - date: expiredDate), - HistoryEvent(extractedProfileId: 1, - brokerId: 1, - profileQueryId: 1, - type: .matchesFound(count: 1))] - - let calculator = OperationPreferredDateCalculator() - - let actualScanDate = try calculator.dateForScanOperation(currentPreferredRunDate: Date(), - historyEvents: historyEvents, - extractedProfileID: nil, - schedulingConfig: schedulingConfig) - - XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedScanDate, date2: actualScanDate)) - } - - func testMatchFoundWithoutExpiredProfileWithRecentDate_thenScanDateDoesNotChange() throws { - let expectedScanDate = Date() - - let historyEvents = [ - HistoryEvent(extractedProfileId: 1, - brokerId: 1, - profileQueryId: 1, - type: .optOutRequested), - HistoryEvent(extractedProfileId: 1, - brokerId: 1, - profileQueryId: 1, - type: .matchesFound(count: 1))] - - let calculator = OperationPreferredDateCalculator() - - let actualScanDate = try calculator.dateForScanOperation(currentPreferredRunDate: Date(), - historyEvents: historyEvents, - extractedProfileID: nil, - schedulingConfig: schedulingConfig) - - XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedScanDate, date2: actualScanDate)) - } - - func testErrorWithRecentDate_thenScanDateDoesNotChange() throws { - let expectedScanDate = Date() - - let historyEvents = [ - HistoryEvent(extractedProfileId: 1, - brokerId: 1, - profileQueryId: 1, - type: .error(error: DataBrokerProtectionError.malformedURL))] - - let calculator = OperationPreferredDateCalculator() - - let actualScanDate = try calculator.dateForScanOperation(currentPreferredRunDate: Date(), - historyEvents: historyEvents, - extractedProfileID: nil, - schedulingConfig: schedulingConfig) - - XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedScanDate, date2: actualScanDate)) - } - func testOptOutStartedWithRecentDate_thenScanDateDoesNotChange() throws { let expectedScanDate = Date() @@ -325,44 +232,6 @@ final class OperationPreferredDateCalculatorTests: XCTestCase { XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedScanDate, date2: actualScanDate)) } - func testOptOutConfirmedWithRecentDate_thenScanDateDoesNotChange() throws { - let expectedScanDate = Date() - - let historyEvents = [ - HistoryEvent(extractedProfileId: 1, - brokerId: 1, - profileQueryId: 1, - type: .optOutConfirmed)] - - let calculator = OperationPreferredDateCalculator() - - let actualScanDate = try calculator.dateForScanOperation(currentPreferredRunDate: Date(), - historyEvents: historyEvents, - extractedProfileID: nil, - schedulingConfig: schedulingConfig) - - XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedScanDate, date2: actualScanDate)) - } - - func testOptOutRequestedWithRecentDate_thenScanDateDoesNotChange() throws { - let expectedScanDate = Date() - - let historyEvents = [ - HistoryEvent(extractedProfileId: 1, - brokerId: 1, - profileQueryId: 1, - type: .optOutRequested)] - - let calculator = OperationPreferredDateCalculator() - - let actualScanDate = try calculator.dateForScanOperation(currentPreferredRunDate: Date(), - historyEvents: historyEvents, - extractedProfileID: nil, - schedulingConfig: schedulingConfig) - - XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedScanDate, date2: actualScanDate)) - } - func testScanStartedWithRecentDate_thenScanDateDoesNotChange() throws { let expectedScanDate = Date() @@ -490,7 +359,26 @@ final class OperationPreferredDateCalculatorTests: XCTestCase { XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedOptOutDate, date2: actualOptOutDate)) } - func testOptOutConfirmed_thenOptOutIsNil() throws { + func testOptOutConfirmedWithCurrentPreferredDate_thenOptOutIsNil() throws { + let expectedOptOutDate: Date? = nil + + let historyEvents = [ + HistoryEvent(extractedProfileId: 1, + brokerId: 1, + profileQueryId: 1, + type: .optOutConfirmed)] + + let calculator = OperationPreferredDateCalculator() + + let actualOptOutDate = try calculator.dateForOptOutOperation(currentPreferredRunDate: Date(), + historyEvents: historyEvents, + extractedProfileID: nil, + schedulingConfig: schedulingConfig) + + XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedOptOutDate, date2: actualOptOutDate)) + } + + func testOptOutConfirmedWithoutCurrentPreferredDate_thenOptOutIsNil() throws { let expectedOptOutDate: Date? = nil let historyEvents = [ @@ -509,7 +397,26 @@ final class OperationPreferredDateCalculatorTests: XCTestCase { XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedOptOutDate, date2: actualOptOutDate)) } - func testOptOutRequested_thenOptOutIsNil() throws { + func testOptOutRequestedWithCurrentPreferredDate_thenOptOutIsNil() throws { + let expectedOptOutDate: Date? = nil + + let historyEvents = [ + HistoryEvent(extractedProfileId: 1, + brokerId: 1, + profileQueryId: 1, + type: .optOutRequested)] + + let calculator = OperationPreferredDateCalculator() + + let actualOptOutDate = try calculator.dateForOptOutOperation(currentPreferredRunDate: Date(), + historyEvents: historyEvents, + extractedProfileID: nil, + schedulingConfig: schedulingConfig) + + XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedOptOutDate, date2: actualOptOutDate)) + } + + func testOptOutRequestedWithoutCurrentPreferredDate_thenOptOutIsNil() throws { let expectedOptOutDate: Date? = nil let historyEvents = [ @@ -618,25 +525,6 @@ final class OperationPreferredDateCalculatorTests: XCTestCase { XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedOptOutDate, date2: actualOptOutDate)) } - func testErrorWithRecentDate_thenOptOutDateDoesNotChange() throws { - let expectedOptOutDate = Date() - - let historyEvents = [ - HistoryEvent(extractedProfileId: 1, - brokerId: 1, - profileQueryId: 1, - type: .error(error: DataBrokerProtectionError.malformedURL))] - - let calculator = OperationPreferredDateCalculator() - - let actualOptOutDate = try calculator.dateForOptOutOperation(currentPreferredRunDate: Date(), - historyEvents: historyEvents, - extractedProfileID: nil, - schedulingConfig: schedulingConfig) - - XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedOptOutDate, date2: actualOptOutDate)) - } - func testOptOutStartedWithRecentDate_thenOptOutDateDoesNotChange() throws { let expectedOptOutDate = Date() @@ -713,4 +601,3 @@ final class OperationPreferredDateCalculatorTests: XCTestCase { XCTAssertTrue(areDatesEqualIgnoringSeconds(date1: expectedOptOutDate, date2: actualOptOutDate)) } } -// swiftlint:enable type_body_length diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Utils.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Utils.swift index 82304fe8c4..80aeb9b5c2 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Utils.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Utils.swift @@ -37,6 +37,25 @@ func areDatesEqualIgnoringSeconds(date1: Date?, date2: Date?) -> Bool { return normalizedDate1 == normalizedDate2 } +func areDatesEqualsOnDayMonthAndYear(date1: Date?, date2: Date?) -> Bool { + if date1 == date2 { + return true + } + guard let date1 = date1, let date2 = date2 else { + return false + } + let calendar = Calendar.current + let components: Set = [.year, .month, .day] + + let date1Components = calendar.dateComponents(components, from: date1) + let date2Components = calendar.dateComponents(components, from: date2) + + let normalizedDate1 = calendar.date(from: date1Components) + let normalizedDate2 = calendar.date(from: date2Components) + + return normalizedDate1 == normalizedDate2 +} + extension HTTPURLResponse { static let ok = HTTPURLResponse(url: URL(string: "www.example.com")!, statusCode: 200, httpVersion: nil, headerFields: [String: String]())! } diff --git a/LocalPackages/LoginItems/Sources/LoginItems/LoginItem.swift b/LocalPackages/LoginItems/Sources/LoginItems/LoginItem.swift index 04b430f83b..a826b70904 100644 --- a/LocalPackages/LoginItems/Sources/LoginItems/LoginItem.swift +++ b/LocalPackages/LoginItems/Sources/LoginItems/LoginItem.swift @@ -26,7 +26,6 @@ import ServiceManagement public struct LoginItem: Equatable, Hashable { let agentBundleID: String - let url: URL private let log: OSLog public var isRunning: Bool { @@ -71,9 +70,8 @@ public struct LoginItem: Equatable, Hashable { return Status(SMAppService.loginItem(identifier: agentBundleID).status) } - public init(bundleId: String, url: URL, log: OSLog) { + public init(bundleId: String, log: OSLog = .disabled) { self.agentBundleID = bundleId - self.url = url self.log = log } @@ -95,7 +93,6 @@ public struct LoginItem: Equatable, Hashable { } else { SMLoginItemSetEnabled(agentBundleID as CFString, false) } - stop() } /// Restarts a login item. @@ -111,17 +108,11 @@ public struct LoginItem: Equatable, Hashable { try enable() } - public func launch() async throws { - os_log("🟢 launching login item %{public}@", log: log, self.debugDescription) - _ = try await NSWorkspace.shared.openApplication(at: url, configuration: .init()) - } - - private func stop() { + public func forceStop() { let runningApplications = runningApplications os_log("🟢 stopping %{public}@", log: log, runningApplications.map { $0.processIdentifier }.description) runningApplications.forEach { $0.terminate() } } - } extension LoginItem: CustomDebugStringConvertible { diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 874a4ee702..d8949c19a8 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -31,8 +31,8 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "81.3.1"), - .package(path: "../XPC"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "82.0.1"), + .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions") ], targets: [ @@ -42,7 +42,7 @@ let package = Package( name: "NetworkProtectionIPC", dependencies: [ .product(name: "NetworkProtection", package: "BrowserServicesKit"), - .product(name: "XPC", package: "XPC") + .product(name: "XPCHelper", package: "XPCHelper") ]), // MARK: - NetworkProtectionUI diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCClient.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCClient.swift index 26b547d6d7..8143bc7ca9 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCClient.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCClient.swift @@ -18,7 +18,7 @@ import Foundation import NetworkProtection -import XPC +import XPCHelper /// This protocol describes the client-side IPC interface for controlling the tunnel /// @@ -79,6 +79,18 @@ extension TunnelControllerIPCClient: IPCServerInterface { public func stop() { try? xpc.server().stop() } + + public func resetAll(uninstallSystemExtension: Bool) async { + try? await xpc.server().resetAll(uninstallSystemExtension: uninstallSystemExtension) + } + + public func debugCommand(_ command: DebugCommand) async { + guard let payload = try? JSONEncoder().encode(command) else { + return + } + + try? await xpc.server().debugCommand(payload) + } } // MARK: - Incoming communication from the server diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCServer.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCServer.swift index 46a4c33631..ccabc5f144 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCServer.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionIPC/TunnelControllerIPCServer.swift @@ -18,7 +18,7 @@ import Foundation import NetworkProtection -import XPC +import XPCHelper /// This protocol describes the server-side IPC interface for controlling the tunnel /// @@ -36,6 +36,14 @@ public protocol IPCServerInterface: AnyObject { /// Stop the VPN tunnel. /// func stop() + + /// Resets all of Network Protection's state that's handled by the server + /// + func resetAll(uninstallSystemExtension: Bool) async + + /// Debug commands + /// + func debugCommand(_ command: DebugCommand) async } /// This protocol describes the server-side XPC interface. @@ -58,6 +66,14 @@ protocol XPCServerInterface { /// Stop the VPN tunnel. /// func stop() + + /// Resets all of Network Protection's state that's handled by the server + /// + func resetAll(uninstallSystemExtension: Bool) async + + /// Debug commands + /// + func debugCommand(_ payload: Data) async } public final class TunnelControllerIPCServer { @@ -139,4 +155,16 @@ extension TunnelControllerIPCServer: XPCServerInterface { func stop() { serverDelegate?.stop() } + + func resetAll(uninstallSystemExtension: Bool) async { + await serverDelegate?.resetAll(uninstallSystemExtension: uninstallSystemExtension) + } + + func debugCommand(_ payload: Data) async { + guard let command = try? JSONDecoder().decode(DebugCommand.self, from: payload) else { + return + } + + await serverDelegate?.debugCommand(command) + } } diff --git a/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/NetworkProtectionStatusBarMenuTests.swift b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/NetworkProtectionStatusBarMenuTests.swift index 06611c6e95..39236a194e 100644 --- a/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/NetworkProtectionStatusBarMenuTests.swift +++ b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/NetworkProtectionStatusBarMenuTests.swift @@ -34,6 +34,10 @@ final class StatusBarMenuTests: XCTestCase { func stop() async { // no-op } + + var isConnected: Bool { + true + } } @MainActor diff --git a/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/TunnelControllerViewModelTests.swift b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/TunnelControllerViewModelTests.swift index 2070272881..f49264b5fb 100644 --- a/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/TunnelControllerViewModelTests.swift +++ b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/TunnelControllerViewModelTests.swift @@ -79,7 +79,7 @@ final class TunnelControllerViewModelTests: XCTestCase { var startCallback: (() -> Void)? var stopCallback: (() -> Void)? - func isConnected() async -> Bool { + var isConnected: Bool { connected } diff --git a/LocalPackages/PixelKit/Package.swift b/LocalPackages/PixelKit/Package.swift index 670af9731b..a12cae821d 100644 --- a/LocalPackages/PixelKit/Package.swift +++ b/LocalPackages/PixelKit/Package.swift @@ -13,7 +13,10 @@ let package = Package( // Products define the executables and libraries a package produces, and make them visible to other packages. .library( name: "PixelKit", - targets: ["PixelKit"]) + targets: ["PixelKit"]), + .library( + name: "PixelKitTestingUtilities", + targets: ["PixelKitTestingUtilities"]) ], dependencies: [ ], @@ -23,6 +26,9 @@ let package = Package( dependencies: []), .testTarget( name: "PixelKitTests", + dependencies: ["PixelKit", "PixelKitTestingUtilities"]), + .target( + name: "PixelKitTestingUtilities", dependencies: ["PixelKit"]) ] ) diff --git a/LocalPackages/PixelKit/README.md b/LocalPackages/PixelKit/README.md index c1a079c7b0..9d5094a7d8 100644 --- a/LocalPackages/PixelKit/README.md +++ b/LocalPackages/PixelKit/README.md @@ -1,3 +1,11 @@ # PixelKit -A temporary package created for Network Protection to be able to fire pixels across build targets. +This package is meant to provide basic support for firing pixel across different targets. + +This package was designed to not really know specific pixels. Those can be defined +individually by each target importing this package, or through more specialized +shared packages. + +This design decision is meant to make PixelKit lean and to make it possible to use it +for future apps we may decide to make, without it having to carry over all of the business +domain logic for any single app. diff --git a/NetworkProtectionVPNController/Main.swift b/LocalPackages/PixelKit/Sources/PixelKit/Extensions/String+StaticString.swift similarity index 77% rename from NetworkProtectionVPNController/Main.swift rename to LocalPackages/PixelKit/Sources/PixelKit/Extensions/String+StaticString.swift index 4b1d144bd7..19f9b8b692 100644 --- a/NetworkProtectionVPNController/Main.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/Extensions/String+StaticString.swift @@ -1,5 +1,5 @@ // -// Main.swift +// String+StaticString.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -18,8 +18,10 @@ import Foundation -@main -final class AppMain { - static func main() throws { +extension String { + init(_ staticString: StaticString) { + self = staticString.withUTF8Buffer { + String(decoding: $0, as: UTF8.self) + } } } diff --git a/DuckDuckGo/Common/Utilities/ErrorWithParameters.swift b/LocalPackages/PixelKit/Sources/PixelKit/Extensions/URL+PixelKit.swift similarity index 65% rename from DuckDuckGo/Common/Utilities/ErrorWithParameters.swift rename to LocalPackages/PixelKit/Sources/PixelKit/Extensions/URL+PixelKit.swift index 2ec967bca3..98bb77c2c4 100644 --- a/DuckDuckGo/Common/Utilities/ErrorWithParameters.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/Extensions/URL+PixelKit.swift @@ -1,5 +1,5 @@ // -// ErrorWithParameters.swift +// URL+PixelKit.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -18,8 +18,13 @@ import Foundation -protocol ErrorWithParameters { +extension URL { - var errorParameters: [String: String] { get } + static let pixelBase = ProcessInfo.processInfo.environment["PIXEL_BASE_URL", default: "https://improving.duckduckgo.com"] + + public static func pixelUrl(forPixelNamed pixelName: String) -> URL { + let urlString = "\(Self.pixelBase)/t/\(pixelName)" + return URL(string: urlString)! + } } diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift new file mode 100644 index 0000000000..e3ab83f59d --- /dev/null +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift @@ -0,0 +1,103 @@ +// +// PixelKit+Parameters.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +public extension PixelKit { + + enum Parameters { + public static let duration = "duration" + public static let test = "test" + public static let appVersion = "appVersion" + + public static let errorCode = "e" + public static let errorDesc = "d" + public static let errorCount = "c" + public static let errorSource = "error_source" + public static let underlyingErrorCode = "ue" + public static let underlyingErrorDesc = "ud" + public static let underlyingErrorSQLiteCode = "sqlrc" + public static let underlyingErrorSQLiteExtendedCode = "sqlerc" + + public static let keychainFieldName = "fieldName" + public static let keychainErrorCode = "keychain_error_code" + + public static let emailCohort = "cohort" + public static let emailLastUsed = "duck_address_last_used" + + public static let assertionMessage = "message" + public static let assertionFile = "file" + public static let assertionLine = "line" + + public static let function = "function" + public static let line = "line" + + public static let latency = "latency" + public static let server = "server" + public static let networkType = "net_type" + + // Pixel experiments + public static let experimentCohort = "cohort" + + // Dashboard + public static let dashboardTriggerOrigin = "trigger_origin" + } + + enum Values { + public static let test = "1" + } + +} + +public protocol ErrorWithPixelParameters { + + var errorParameters: [String: String] { get } + +} + +public extension Error { + + var pixelParameters: [String: String] { + var params = [String: String]() + + if let errorWithUserInfo = self as? ErrorWithPixelParameters { + params = errorWithUserInfo.errorParameters + } + + let nsError = self as NSError + + params[PixelKit.Parameters.errorCode] = "\(nsError.code)" + params[PixelKit.Parameters.errorDesc] = nsError.domain + + if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? NSError { + params[PixelKit.Parameters.underlyingErrorCode] = "\(underlyingError.code)" + params[PixelKit.Parameters.underlyingErrorDesc] = underlyingError.domain + } + + if let sqlErrorCode = nsError.userInfo["SQLiteResultCode"] as? NSNumber { + params[PixelKit.Parameters.underlyingErrorSQLiteCode] = "\(sqlErrorCode.intValue)" + } + + if let sqlExtendedErrorCode = nsError.userInfo["SQLiteExtendedResultCode"] as? NSNumber { + params[PixelKit.Parameters.underlyingErrorSQLiteExtendedCode] = "\(sqlExtendedErrorCode.intValue)" + } + + return params + } + +} diff --git a/LocalPackages/PixelKit/Sources/PixelKit/Pixel.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift similarity index 71% rename from LocalPackages/PixelKit/Sources/PixelKit/Pixel.swift rename to LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index 3a1f5d5ec4..8eeff1bec2 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/Pixel.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -1,5 +1,5 @@ // -// Pixel.swift +// PixelKit.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -19,36 +19,11 @@ import Foundation import os.log // swiftlint:disable:this enforce_os_log_wrapper -public protocol PixelEvent { - var name: String { get } - var parameters: [String: String]? { get } -} - -extension URL { - // MARK: Pixel - - static let pixelBase = ProcessInfo.processInfo.environment["PIXEL_BASE_URL", default: "https://improving.duckduckgo.com"] - - public static func pixelUrl(forPixelNamed pixelName: String) -> URL { - let urlString = "\(Self.pixelBase)/t/\(pixelName)" - let url = URL(string: urlString)! - // url = url.addParameter(name: \"atb\", value: statisticsStore.atbWithVariant ?? \"\")") - // https://app.asana.com/0/1177771139624306/1199951074455863/f - return url - } -} - -public final class Pixel { +public final class PixelKit { - enum Header { - static let acceptEncoding = "Accept-Encoding" - static let acceptLanguage = "Accept-Language" - static let userAgent = "User-Agent" - static let ifNoneMatch = "If-None-Match" - static let moreInfo = "X-DuckDuckGo-MoreInfo" - } - - public enum PixelFrequency { + /// The frequency with which a pixel is sent to our endpoint. + /// + public enum Frequency { /// The default frequency for pixels. This fires pixels with the event names as-is. case standard @@ -61,6 +36,14 @@ public final class Pixel { case dailyAndContinuous } + public enum Header { + public static let acceptEncoding = "Accept-Encoding" + public static let acceptLanguage = "Accept-Language" + public static let userAgent = "User-Agent" + public static let ifNoneMatch = "If-None-Match" + public static let moreInfo = "X-DuckDuckGo-MoreInfo" + } + /// A closure typealias to request sending pixels through the network. /// public typealias FireRequest = ( @@ -71,25 +54,26 @@ public final class Pixel { _ callBackOnMainThread: Bool, _ onComplete: @escaping (Error?) -> Void) -> Void - public typealias Event = PixelEvent + public typealias Event = PixelKitEvent + + public static let duckDuckGoMorePrivacyInfo = URL(string: "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/atb/")! - static let duckDuckGoMorePrivacyInfo = URL(string: "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/atb/")! + private let defaults: UserDefaults - private static let storage: UserDefaults = UserDefaults.standard private static let defaultDailyPixelCalendar: Calendar = { var calendar = Calendar.current calendar.timeZone = TimeZone(secondsFromGMT: 0)! return calendar }() - public private(set) static var shared: Pixel? + public private(set) static var shared: PixelKit? private let appVersion: String private let defaultHeaders: [String: String] private let log: OSLog private let fireRequest: FireRequest public static func setUp(dryRun: Bool = false, appVersion: String, defaultHeaders: [String: String], log: OSLog, fireRequest: @escaping FireRequest) { - shared = Pixel(dryRun: dryRun, appVersion: appVersion, defaultHeaders: defaultHeaders, log: log, fireRequest: fireRequest) + shared = PixelKit(dryRun: dryRun, appVersion: appVersion, defaultHeaders: defaultHeaders, log: log, fireRequest: fireRequest) } static func tearDown() { @@ -104,27 +88,26 @@ public final class Pixel { defaultHeaders: [String: String], log: OSLog, dailyPixelCalendar: Calendar? = nil, + defaults: UserDefaults = .standard, fireRequest: @escaping FireRequest) { + self.dryRun = dryRun self.appVersion = appVersion self.defaultHeaders = defaultHeaders self.log = log self.pixelCalendar = dailyPixelCalendar ?? Self.defaultDailyPixelCalendar + self.defaults = defaults self.fireRequest = fireRequest } - public func fire(pixelNamed pixelName: String, - frequency: PixelFrequency, - withHeaders headers: [String: String]? = nil, - withAdditionalParameters params: [String: String]? = nil, - allowedQueryReservedCharacters: CharacterSet? = nil, - includeAppVersionParameter: Bool = true, - dailyPixelCalendar: Calendar? = nil, - onComplete: @escaping (Error?) -> Void = {_ in }) { - if frequency == .dailyOnly, pixelHasBeenFiredToday(pixelName, dailyPixelStorage: Self.storage, calendar: self.pixelCalendar) { - onComplete(nil) - return - } + private func fire(pixelNamed pixelName: String, + frequency: Frequency, + withHeaders headers: [String: String]? = nil, + withAdditionalParameters params: [String: String]? = nil, + allowedQueryReservedCharacters: CharacterSet? = nil, + includeAppVersionParameter: Bool = true, + dailyPixelCalendar: Calendar? = nil, + onComplete: @escaping (Error?) -> Void = {_ in }) { var newParams = params ?? [:] if includeAppVersionParameter { @@ -155,7 +138,7 @@ public final class Pixel { updatePixelLastFireDate(pixelName: pixelName) fireRequest(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, onComplete) case .dailyAndContinuous: - if !pixelHasBeenFiredToday(pixelName, dailyPixelStorage: Self.storage, calendar: self.pixelCalendar) { + if !pixelHasBeenFiredToday(pixelName, dailyPixelStorage: defaults, calendar: self.pixelCalendar) { fireRequest(pixelName + "_d", headers, newParams, allowedQueryReservedCharacters, true, { _ in }) } @@ -165,15 +148,36 @@ public final class Pixel { } } - public static func fire(_ event: Pixel.Event, - frequency: PixelFrequency, - withHeaders headers: [String: String], - withAdditionalParameters parameters: [String: String]? = nil, - allowedQueryReservedCharacters: CharacterSet? = nil, - includeAppVersionParameter: Bool = true, - onComplete: @escaping (Error?) -> Void = {_ in }) { + private func prefixedName(for event: Event) -> String { + if event.name.hasPrefix("m_mac_") { + return event.name + } + + if let debugEvent = event as? DebugEvent { + return "m_mac_debug_\(debugEvent.name)" + } else { + return "m_mac_\(event.name)" + } + } + + public func fire(_ event: Event, + frequency: Frequency, + withHeaders headers: [String: String]? = nil, + withAdditionalParameters params: [String: String]? = nil, + allowedQueryReservedCharacters: CharacterSet? = nil, + includeAppVersionParameter: Bool = true, + dailyPixelCalendar: Calendar? = nil, + onComplete: @escaping (Error?) -> Void = {_ in }) { + + let pixelName = prefixedName(for: event) + + if frequency == .dailyOnly, pixelHasBeenFiredToday(pixelName, dailyPixelStorage: defaults, calendar: self.pixelCalendar) { + onComplete(nil) + return + } + let newParams: [String: String]? - switch (event.parameters, parameters) { + switch (event.parameters, params) { case (.some(let parameters), .none): newParams = parameters case (.none, .some(let parameters)): @@ -184,17 +188,35 @@ public final class Pixel { newParams = nil } - Self.shared?.fire(pixelNamed: event.name, + fire(pixelNamed: pixelName, + frequency: frequency, + withHeaders: headers, + withAdditionalParameters: newParams, + allowedQueryReservedCharacters: allowedQueryReservedCharacters, + includeAppVersionParameter: includeAppVersionParameter, + dailyPixelCalendar: dailyPixelCalendar, + onComplete: onComplete) + } + + public static func fire(_ event: Event, + frequency: Frequency, + withHeaders headers: [String: String] = [:], + withAdditionalParameters parameters: [String: String]? = nil, + allowedQueryReservedCharacters: CharacterSet? = nil, + includeAppVersionParameter: Bool = true, + onComplete: @escaping (Error?) -> Void = {_ in }) { + + Self.shared?.fire(event, frequency: frequency, withHeaders: headers, - withAdditionalParameters: newParams, + withAdditionalParameters: parameters, allowedQueryReservedCharacters: allowedQueryReservedCharacters, includeAppVersionParameter: includeAppVersionParameter, onComplete: onComplete) } private func updatePixelLastFireDate(pixelName: String) { - Self.storage.set(Date(), forKey: userDefaultsKeyName(forPixelName: pixelName)) + defaults.set(Date(), forKey: userDefaultsKeyName(forPixelName: pixelName)) } private func pixelHasBeenFiredToday(_ name: String, dailyPixelStorage: UserDefaults, calendar: Calendar) -> Bool { diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift new file mode 100644 index 0000000000..3b4fea332e --- /dev/null +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift @@ -0,0 +1,88 @@ +// +// PixelKitEvent.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// An event that can be fired using PixelKit. +/// +public protocol PixelKitEvent { + var name: String { get } + var parameters: [String: String]? { get } +} + +/// Implementation of ``PixelKitEvent`` with specific logic for debug events. +/// +public final class DebugEvent: PixelKitEvent { + public enum EventType { + case assertionFailure(message: String, file: StaticString, line: UInt) + case custom(_ event: PixelKitEvent) + } + + public let eventType: EventType + private let error: Error? + + public init(eventType: EventType, error: Error? = nil) { + self.eventType = eventType + self.error = error + } + + public var name: String { + switch eventType { + case .assertionFailure: + return "assertion_failure" + case .custom(let event): + return event.name + } + } + + public var parameters: [String: String]? { + var params = [String: String]() + + if let errorWithUserInfo = error as? ErrorWithPixelParameters { + params = errorWithUserInfo.errorParameters + } + + if case let .assertionFailure(message, file, line) = eventType { + params[PixelKit.Parameters.assertionMessage] = message + params[PixelKit.Parameters.assertionFile] = String(file) + params[PixelKit.Parameters.assertionLine] = String(line) + } + + if let error = error { + let nsError = error as NSError + + params[PixelKit.Parameters.errorCode] = "\(nsError.code)" + params[PixelKit.Parameters.errorDesc] = nsError.domain + + if let underlyingError = nsError.userInfo["NSUnderlyingError"] as? NSError { + params[PixelKit.Parameters.underlyingErrorCode] = "\(underlyingError.code)" + params[PixelKit.Parameters.underlyingErrorDesc] = underlyingError.domain + } + + if let sqlErrorCode = nsError.userInfo["SQLiteResultCode"] as? NSNumber { + params[PixelKit.Parameters.underlyingErrorSQLiteCode] = "\(sqlErrorCode.intValue)" + } + + if let sqlExtendedErrorCode = nsError.userInfo["SQLiteExtendedResultCode"] as? NSNumber { + params[PixelKit.Parameters.underlyingErrorSQLiteExtendedCode] = "\(sqlExtendedErrorCode.intValue)" + } + } + + return params + } +} diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelParameters.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelParameters.swift deleted file mode 100644 index b33a7ba3c6..0000000000 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelParameters.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// PixelParameters.swift -// -// Copyright © 2023 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -extension Pixel { - - enum Parameters { - static let duration = "duration" - static let test = "test" - static let appVersion = "appVersion" - - static let keychainFieldName = "fieldName" - static let errorCode = "e" - static let errorDesc = "d" - static let errorCount = "c" - static let underlyingErrorCode = "ue" - static let underlyingErrorDesc = "ud" - static let underlyingErrorSQLiteCode = "sqlrc" - static let underlyingErrorSQLiteExtendedCode = "sqlerc" - } - - enum Values { - static let test = "1" - } -} - -extension Error { - - public var pixelParameters: [String: String] { - var parameters = [String: String]() - let nsError = self as NSError - - parameters[Pixel.Parameters.errorCode] = "\(nsError.code)" - parameters[Pixel.Parameters.errorDesc] = nsError.domain - if let underlyingError = nsError.userInfo["NSUnderlyingError"] as? NSError { - parameters[Pixel.Parameters.underlyingErrorCode] = "\(underlyingError.code)" - parameters[Pixel.Parameters.underlyingErrorDesc] = underlyingError.domain - } - - if let sqlErrorCode = nsError.userInfo["SQLiteResultCode"] as? NSNumber { - parameters[Pixel.Parameters.underlyingErrorSQLiteCode] = "\(sqlErrorCode.intValue)" - } - - if let sqlExtendedErrorCode = nsError.userInfo["SQLiteExtendedResultCode"] as? NSNumber { - parameters[Pixel.Parameters.underlyingErrorSQLiteExtendedCode] = "\(sqlExtendedErrorCode.intValue)" - } - - return parameters - } - -} diff --git a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/ValidatePixel.swift b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/ValidatePixel.swift new file mode 100644 index 0000000000..a56f128454 --- /dev/null +++ b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/ValidatePixel.swift @@ -0,0 +1,50 @@ +// +// ValidatePixel.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import PixelKit +import XCTest + +public final class PixelRequestValidator { + public init() {} + + public func validateBasicPixelParams( + expectedAppVersion: String, + expectedUserAgent: String, + requestParameters parameters: [String: String], + requestHeaders headers: [String: String]) { + + XCTAssertEqual(parameters[PixelKit.Parameters.test], "1") + XCTAssertEqual(parameters[PixelKit.Parameters.appVersion], expectedAppVersion) + + XCTAssertEqual(headers[PixelKit.Header.userAgent], expectedUserAgent) + XCTAssertEqual(headers[PixelKit.Header.acceptEncoding], "gzip;q=1.0, compress;q=0.5") + XCTAssertNotNil(headers[PixelKit.Header.acceptLanguage]) + XCTAssertNotNil(headers[PixelKit.Header.moreInfo], PixelKit.duckDuckGoMorePrivacyInfo.absoluteString) + } + + public func validateDebugPixelParams( + expectedError: Error?, + requestParameters parameters: [String: String]) { + + if let error = expectedError as? NSError { + XCTAssertEqual(parameters[PixelKit.Parameters.errorCode], "\(error.code)") + XCTAssertEqual(parameters[PixelKit.Parameters.errorDesc], error.domain) + } + } +} diff --git a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift index ee7f33a7d4..cf00a8228b 100644 --- a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift +++ b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift @@ -18,7 +18,193 @@ import XCTest @testable import PixelKit +import os.log // swiftlint:disable:this enforce_os_log_wrapper final class PixelKitTests: XCTestCase { + private func userDefaults() -> UserDefaults { + UserDefaults(suiteName: "testing_\(UUID().uuidString)")! + } + + /// Test events for convenience + /// + private enum TestEvent: String, PixelKitEvent { + case testEvent + case testEventWithoutParameters + case dailyEvent + case dailyEventWithoutParameters + case dailyAndContinuousEvent + case dailyAndContinuousEventWithoutParameters + + var name: String { + rawValue + } + + var parameters: [String: String]? { + switch self { + case .testEvent, .dailyEvent, .dailyAndContinuousEvent: + return [ + "eventParam1": "eventParamValue1", + "eventParam2": "eventParamValue2" + ] + case .testEventWithoutParameters, .dailyEventWithoutParameters, .dailyAndContinuousEventWithoutParameters: + return nil + } + } + + var frequency: PixelKitEventFrequency { + switch self { + case .testEvent, .testEventWithoutParameters: + return .standard + case .dailyEvent, .dailyEventWithoutParameters: + return .dailyOnly + case .dailyAndContinuousEvent, .dailyAndContinuousEventWithoutParameters: + return .dailyAndContinuous + } + } + } + + /// Test that a dry run won't execute the fire request callback. + /// + func testDryRunWontExecuteCallback() async { + let appVersion = "1.0.5" + let headers: [String: String] = [:] + let log = OSLog.disabled + + let pixelKit = PixelKit(dryRun: true, appVersion: appVersion, defaultHeaders: headers, log: log, dailyPixelCalendar: nil) { _, _, _, _, _, _ in + + XCTFail("This callback should not be executed when doing a dry run") + } + + pixelKit.fire(TestEvent.testEvent) + } + + /// Tests firing a sample pixel and ensuring that all fields are properly set in the fire request callback. + /// + func testFiringASamplePixel() { + // Prepare test parameters + let appVersion = "1.0.5" + let headers = ["a": "2", "b": "3", "c": "2000"] + let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") + let event = TestEvent.testEvent + let userDefaults = userDefaults() + + // Set expectations + let expectedPixelName = "m_mac_\(event.name)" + let fireCallbackCalled = expectation(description: "Expect the pixel firing callback to be called") + + // Prepare mock to validate expectations + let pixelKit = PixelKit(dryRun: false, + appVersion: appVersion, + defaultHeaders: headers, + log: log, + dailyPixelCalendar: nil, + defaults: userDefaults) { firedPixelName, firedHeaders, parameters, _, _, _ in + + fireCallbackCalled.fulfill() + + XCTAssertEqual(expectedPixelName, firedPixelName) + XCTAssertTrue(headers.allSatisfy({ key, value in + firedHeaders[key] == value + })) + + XCTAssertEqual(firedHeaders[PixelKit.Header.moreInfo], "See \(PixelKit.duckDuckGoMorePrivacyInfo)") + + XCTAssertEqual(parameters[PixelKit.Parameters.appVersion], appVersion) + XCTAssertEqual(parameters[PixelKit.Parameters.test], PixelKit.Values.test) + } + + // Run test + pixelKit.fire(event) + + // Wait for expectations to be fulfilled + wait(for: [fireCallbackCalled], timeout: 0.5) + } + + /// We test firing a daily pixel for the first time executes the fire request callback with the right parameters + /// + func testFiringDailyPixelForTheFirstTime() { + // Prepare test parameters + let appVersion = "1.0.5" + let headers = ["a": "2", "b": "3", "c": "2000"] + let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") + let event = TestEvent.dailyEvent + let userDefaults = userDefaults() + + // Set expectations + let expectedPixelName = "m_mac_\(event.name)_d" + let expectedMoreInfoString = "See \(PixelKit.duckDuckGoMorePrivacyInfo)" + let fireCallbackCalled = expectation(description: "Expect the pixel firing callback to be called") + + // Prepare mock to validate expectations + let pixelKit = PixelKit(dryRun: false, + appVersion: appVersion, + defaultHeaders: headers, + log: log, + dailyPixelCalendar: nil, + defaults: userDefaults) { firedPixelName, firedHeaders, parameters, _, _, _ in + + fireCallbackCalled.fulfill() + + XCTAssertEqual(expectedPixelName, firedPixelName) + XCTAssertTrue(headers.allSatisfy({ key, value in + firedHeaders[key] == value + })) + + XCTAssertEqual(firedHeaders[PixelKit.Header.moreInfo], expectedMoreInfoString) + XCTAssertEqual(parameters[PixelKit.Parameters.appVersion], appVersion) + XCTAssertEqual(parameters[PixelKit.Parameters.test], PixelKit.Values.test) + } + + // Run test + pixelKit.fire(event) + + // Wait for expectations to be fulfilled + wait(for: [fireCallbackCalled], timeout: 0.5) + } + + /// We test firing a daily pixel a second time does not execute the fire request callback. + /// + func testDailyPixelFrequency() { + // Prepare test parameters + let appVersion = "1.0.5" + let headers = ["a": "2", "b": "3", "c": "2000"] + let log = OSLog(subsystem: "TestSubsystem", category: "TestCategory") + let event = TestEvent.dailyEvent + let userDefaults = userDefaults() + + // Set expectations + let expectedPixelName = "m_mac_\(event.name)_d" + let expectedMoreInfoString = "See \(PixelKit.duckDuckGoMorePrivacyInfo)" + let fireCallbackCalled = expectation(description: "Expect the pixel firing callback to be called") + fireCallbackCalled.expectedFulfillmentCount = 1 + fireCallbackCalled.assertForOverFulfill = true + + // Prepare mock to validate expectations + let pixelKit = PixelKit(dryRun: false, + appVersion: appVersion, + defaultHeaders: headers, + log: log, + dailyPixelCalendar: nil, + defaults: userDefaults) { firedPixelName, firedHeaders, parameters, _, _, _ in + + fireCallbackCalled.fulfill() + + XCTAssertEqual(expectedPixelName, firedPixelName) + XCTAssertTrue(headers.allSatisfy({ key, value in + firedHeaders[key] == value + })) + + XCTAssertEqual(firedHeaders[PixelKit.Header.moreInfo], expectedMoreInfoString) + XCTAssertEqual(parameters[PixelKit.Parameters.appVersion], appVersion) + XCTAssertEqual(parameters[PixelKit.Parameters.test], PixelKit.Values.test) + } + + // Run test + pixelKit.fire(event) + pixelKit.fire(event) + + // Wait for expectations to be fulfilled + wait(for: [fireCallbackCalled], timeout: 0.5) + } } diff --git a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/NativeCheckBoxToggle.swift b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/NativeCheckBoxToggle.swift index 8bbc889d7f..fa73a3082e 100644 --- a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/NativeCheckBoxToggle.swift +++ b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/NativeCheckBoxToggle.swift @@ -26,9 +26,9 @@ public struct NativeCheckboxToggle: NSViewRepresentable { var label: String public init(isOn: Binding, label: String) { - self._isOn = isOn - self.label = label - } + self._isOn = isOn + self.label = label + } public func makeNSView(context: Context) -> NSButton { let button = NSButton(checkboxWithTitle: label, target: context.coordinator, action: #selector(context.coordinator.toggleChecked)) diff --git a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift index 8164e36924..7a9cbf6bd2 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift @@ -24,6 +24,8 @@ public protocol ManagementViewModel: ObservableObject { var isCreatingAccount: Bool { get } var shouldShowErrorMessage: Bool { get set } var errorMessage: String? { get } + var isSyncBookmarksPaused: Bool { get } + var isSyncCredentialsPaused: Bool { get } var recoveryCode: String? { get } var codeToDisplay: String? { get } @@ -43,4 +45,7 @@ public protocol ManagementViewModel: ObservableObject { func turnOnSync() func startPollingForRecoveryKey() func stopPollingForRecoveryKey() + + func manageBookmarks() + func manageLogins() } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift index 261462c129..298b18c99e 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift @@ -23,6 +23,16 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { @EnvironmentObject var model: ViewModel var body: some View { + VStack(alignment: .leading, spacing: 16) { + if model.isSyncBookmarksPaused { + syncPaused(for: .bookmarks) + } + if model.isSyncCredentialsPaused { + syncPaused(for: .credentials) + } + } + .padding(.top, 20) + PreferencePaneSection(vericalPadding: 12) { SyncStatusView() .environmentObject(model) @@ -94,4 +104,51 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { .padding(16) } } + + @ViewBuilder + func syncPaused(for itemType: LimitedItemType) -> some View { + var description: String { + switch itemType { + case .bookmarks: + return UserText.bookmarksLimitExceededDescription + case .credentials: + return UserText.credentialsLimitExceededDescription + } + } + var actionTitle: String { + switch itemType { + case .bookmarks: + return UserText.bookmarksLimitExceededAction + case .credentials: + return UserText.credentialsLimitExceededAction + } + } + PreferencePaneSection(vericalPadding: 16) { + HStack(alignment: .top, spacing: 8) { + Text("⚠️") + VStack(alignment: .leading, spacing: 8) { + Text(UserText.syncLimitExceededTitle) + .bold() + Text(description) + Button(actionTitle) { + switch itemType { + case .bookmarks: + model.manageBookmarks() + case .credentials: + model.manageLogins() + } + } + .padding(.top, 8) + } + } + .padding(.horizontal, 16) + } + .frame(width: 512, alignment: .leading) + .background(RoundedRectangle(cornerRadius: 8).foregroundColor(Color("AlertBubbleBackground"))) + } + + enum LimitedItemType { + case bookmarks + case credentials + } } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift index f6a260e6d3..078affc8f1 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift @@ -101,4 +101,10 @@ enum UserText { static let optionsSectionTitle = NSLocalizedString("prefrences.sync.options-section-title", value: "Settings", comment: "Title for options settings") static let shareFavoritesOptionTitle = NSLocalizedString("prefrences.sync.share-favorite-option-title", value: "Share Favorites", comment: "Title for share favorite option") static let shareFavoritesOptionCaption = NSLocalizedString("prefrences.sync.share-favorite-option-caption", value: "Use the same favorites on all devices. Leave off to keep mobile and desktop favorites separate.", comment: "Caption for share favorite option") + + static let syncLimitExceededTitle = NSLocalizedString("prefrences.sync.limit-exceeded-title", value: "Sync Paused", comment: "Title for sync limits exceeded warning") + static let bookmarksLimitExceededDescription = NSLocalizedString("prefrences.sync.bookmarks-limit-exceeded-description", value: "Bookmark limit exceeded. Delete some to resume syncing.", comment: "Description for sync bookmarks limits exceeded warning") + static let credentialsLimitExceededDescription = NSLocalizedString("prefrences.sync.credentials-limit-exceeded-description", value: "Logins limit exceeded. Delete some to resume syncing.", comment: "Description for sync credentials limits exceeded warning") + static let bookmarksLimitExceededAction = NSLocalizedString("prefrences.sync.bookmarks-limit-exceeded-action", value: "Manage Bookmarks", comment: "Button title for sync bookmarks limits exceeded warning to manage bookmarks") + static let credentialsLimitExceededAction = NSLocalizedString("prefrences.sync.credentials-limit-exceeded-action", value: "Manage Logins", comment: "Button title for sync credentials limits exceeded warning to manage logins") } diff --git a/LocalPackages/SystemExtensionManager/.gitignore b/LocalPackages/SystemExtensionManager/.gitignore new file mode 100644 index 0000000000..0023a53406 --- /dev/null +++ b/LocalPackages/SystemExtensionManager/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/LocalPackages/XPC/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/LocalPackages/SystemExtensionManager/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from LocalPackages/XPC/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to LocalPackages/SystemExtensionManager/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/LocalPackages/SystemExtensionManager/Package.swift b/LocalPackages/SystemExtensionManager/Package.swift new file mode 100644 index 0000000000..a8797ae4a1 --- /dev/null +++ b/LocalPackages/SystemExtensionManager/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version: 5.8 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "SystemExtensionManager", + platforms: [ + .iOS("14.0"), + .macOS("11.4") + ], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "SystemExtensionManager", + targets: ["SystemExtensionManager"]), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "SystemExtensionManager") + ] +) diff --git a/LocalPackages/SystemExtensionManager/README.md b/LocalPackages/SystemExtensionManager/README.md new file mode 100644 index 0000000000..55d6a36ae9 --- /dev/null +++ b/LocalPackages/SystemExtensionManager/README.md @@ -0,0 +1,5 @@ +# SystemExtensionManager + +SystemExtensionManager library. + +- Offers convenience code for interacting and managing System Extensions diff --git a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/SystemExtensionManager.swift b/LocalPackages/SystemExtensionManager/Sources/SystemExtensionManager/SystemExtensionManager.swift similarity index 72% rename from DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/SystemExtensionManager.swift rename to LocalPackages/SystemExtensionManager/Sources/SystemExtensionManager/SystemExtensionManager.swift index c3198e08d2..64d7ca5a62 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/DeveloperIDTarget/SystemExtensionManager.swift +++ b/LocalPackages/SystemExtensionManager/Sources/SystemExtensionManager/SystemExtensionManager.swift @@ -21,30 +21,30 @@ import Cocoa import Combine @preconcurrency import SystemExtensions -struct SystemExtensionManager { +public enum SystemExtensionRequestError: Error { + case unknownRequestResult + case willActivateAfterReboot +} - enum ActivationRequestEvent { - case waitingForUserApproval - case activated - case willActivateAfterReboot - } +public struct SystemExtensionManager { private static let systemSettingsSecurityURL = "x-apple.systempreferences:com.apple.preference.security?Security" - private let bundleID: String + private let extensionBundleID: String private let manager: OSSystemExtensionManager private let workspace: NSWorkspace - init(bundleID: String = NetworkProtectionBundle.extensionBundle().bundleIdentifier!, - manager: OSSystemExtensionManager = .shared, - workspace: NSWorkspace = .shared) { + public init( + extensionBundleID: String, + manager: OSSystemExtensionManager = .shared, + workspace: NSWorkspace = .shared) { - self.bundleID = bundleID + self.extensionBundleID = extensionBundleID self.manager = manager self.workspace = workspace } - func activate() -> AsyncThrowingStream { + public func activate(waitingForUserApproval: @escaping () -> Void) async throws { /// Documenting a workaround for the issue discussed in https://app.asana.com/0/0/1205275221447702/f /// Background: For a lot of users, the system won't show the system-extension-blocked alert if there's a previous request /// to activate the extension. You can see active requests in your console using command `systemextensionsctl list`. @@ -62,11 +62,18 @@ struct SystemExtensionManager { openSystemSettingsSecurity() } - return SystemExtensionRequest.activationRequest(forExtensionWithIdentifier: bundleID, manager: manager).submit() + return try await SystemExtensionRequest.activationRequest( + forExtensionWithIdentifier: extensionBundleID, + manager: manager, + waitingForUserApproval: waitingForUserApproval) + .submit() } - func deactivate() async throws { - for try await _ in SystemExtensionRequest.deactivationRequest(forExtensionWithIdentifier: bundleID, manager: manager).submit() {} + public func deactivate() async throws { + try await SystemExtensionRequest.deactivationRequest( + forExtensionWithIdentifier: extensionBundleID, + manager: manager) + .submit() } // MARK: - Activation: Checking if there are pending requests @@ -84,7 +91,7 @@ struct SystemExtensionManager { task.standardOutput = pipe task.launchPath = "/bin/bash" // Specify the shell to use - task.arguments = ["-c", "$(which systemextensionsctl) list | $(which egrep) -c '(?:\(bundleID)).+(?:activated waiting for user)+'"] + task.arguments = ["-c", "$(which systemextensionsctl) list | $(which egrep) -c '(?:\(extensionBundleID)).+(?:activated waiting for user)+'"] task.launch() task.waitUntilExit() @@ -102,43 +109,40 @@ struct SystemExtensionManager { } final class SystemExtensionRequest: NSObject { - typealias Event = SystemExtensionManager.ActivationRequestEvent private let request: OSSystemExtensionRequest private let manager: OSSystemExtensionManager + private let waitingForUserApproval: (() -> Void)? - private var continuation: AsyncThrowingStream.Continuation? + private var continuation: CheckedContinuation? - private init(request: OSSystemExtensionRequest, manager: OSSystemExtensionManager) { + private init(request: OSSystemExtensionRequest, manager: OSSystemExtensionManager, waitingForUserApproval: (() -> Void)? = nil) { self.manager = manager self.request = request + self.waitingForUserApproval = waitingForUserApproval super.init() } - static func activationRequest(forExtensionWithIdentifier bundleId: String, manager: OSSystemExtensionManager) -> Self { - self.init(request: .activationRequest(forExtensionWithIdentifier: bundleId, queue: .global()), manager: manager) + static func activationRequest(forExtensionWithIdentifier bundleId: String, manager: OSSystemExtensionManager, waitingForUserApproval: (() -> Void)?) -> Self { + self.init(request: .activationRequest(forExtensionWithIdentifier: bundleId, queue: .global()), manager: manager, waitingForUserApproval: waitingForUserApproval) } static func deactivationRequest(forExtensionWithIdentifier bundleId: String, manager: OSSystemExtensionManager) -> Self { self.init(request: .deactivationRequest(forExtensionWithIdentifier: bundleId, queue: .global()), manager: manager) } - /// submitting the request returns an Async Iterator providing the OSSystemExtensionRequest state change events - /// until an Event is received. - func submit() -> AsyncThrowingStream { + /// Submit the request + /// + func submit() async throws { assert(continuation == nil, "Request can only be submitted once") - defer { + try await withCheckedThrowingContinuation { continuation in + self.continuation = continuation + request.delegate = self manager.submitRequest(request) } - return AsyncThrowingStream { [self /* keep the request delegate alive */] continuation in - continuation.onTermination = { _ in - withExtendedLifetime(self) {} - } - self.continuation = continuation - } } } @@ -150,26 +154,26 @@ extension SystemExtensionRequest: OSSystemExtensionRequestDelegate { } func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) { - continuation?.yield(.waitingForUserApproval) + waitingForUserApproval?() } func request(_ request: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) { switch result { case .completed: - continuation?.yield(.activated) + continuation?.resume() case .willCompleteAfterReboot: - continuation?.yield(.willActivateAfterReboot) + continuation?.resume(throwing: SystemExtensionRequestError.willActivateAfterReboot) + return @unknown default: - // Not much we can do about this, so let's assume it's a good result and not show any errors - continuation?.yield(.activated) - Pixel.fire(.networkProtectionSystemExtensionUnknownActivationResult) + // Not much we can do about this, so we just let the owning app decide + // what to do about this. + continuation?.resume(throwing: SystemExtensionRequestError.unknownRequestResult) + return } - - continuation?.finish() } func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) { - continuation?.finish(throwing: error) + continuation?.resume(throwing: error) } } diff --git a/LocalPackages/XPC/README.md b/LocalPackages/XPC/README.md deleted file mode 100644 index a60d3d9736..0000000000 --- a/LocalPackages/XPC/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# XPC - -XPC library. - -- Offers XPC Communication convenience classes. diff --git a/LocalPackages/XPC/.gitignore b/LocalPackages/XPCHelper/.gitignore similarity index 100% rename from LocalPackages/XPC/.gitignore rename to LocalPackages/XPCHelper/.gitignore diff --git a/LocalPackages/XPCHelper/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/LocalPackages/XPCHelper/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/LocalPackages/XPCHelper/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/LocalPackages/XPC/.swiftpm/xcode/xcshareddata/xcschemes/Intercom.xcscheme b/LocalPackages/XPCHelper/.swiftpm/xcode/xcshareddata/xcschemes/Intercom.xcscheme similarity index 100% rename from LocalPackages/XPC/.swiftpm/xcode/xcshareddata/xcschemes/Intercom.xcscheme rename to LocalPackages/XPCHelper/.swiftpm/xcode/xcshareddata/xcschemes/Intercom.xcscheme diff --git a/LocalPackages/XPCHelper/.swiftpm/xcode/xcshareddata/xcschemes/XPC.xcscheme b/LocalPackages/XPCHelper/.swiftpm/xcode/xcshareddata/xcschemes/XPC.xcscheme new file mode 100644 index 0000000000..f1880e533b --- /dev/null +++ b/LocalPackages/XPCHelper/.swiftpm/xcode/xcshareddata/xcschemes/XPC.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LocalPackages/XPC/Package.swift b/LocalPackages/XPCHelper/Package.swift similarity index 92% rename from LocalPackages/XPC/Package.swift rename to LocalPackages/XPCHelper/Package.swift index 7bb423e313..5eef7930ba 100644 --- a/LocalPackages/XPC/Package.swift +++ b/LocalPackages/XPCHelper/Package.swift @@ -21,14 +21,13 @@ import PackageDescription let package = Package( - name: "XPC", + name: "XPCHelper", platforms: [ - .iOS("14.0"), .macOS("11.4") ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. - .library(name: "XPC", targets: ["XPC"]), + .library(name: "XPCHelper", targets: ["XPCHelper"]), ], dependencies: [ // Dependencies declare other packages that this package depends on. @@ -38,7 +37,7 @@ let package = Package( // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( - name: "XPC", + name: "XPCHelper", dependencies: []) ] ) diff --git a/LocalPackages/XPCHelper/README.md b/LocalPackages/XPCHelper/README.md new file mode 100644 index 0000000000..463d4fb03c --- /dev/null +++ b/LocalPackages/XPCHelper/README.md @@ -0,0 +1,5 @@ +# XPCHelper + +XPC helpers library. + +- Offers XPC Communication helper code diff --git a/LocalPackages/XPC/Sources/XPC/XPCClient.swift b/LocalPackages/XPCHelper/Sources/XPCHelper/XPCClient.swift similarity index 100% rename from LocalPackages/XPC/Sources/XPC/XPCClient.swift rename to LocalPackages/XPCHelper/Sources/XPCHelper/XPCClient.swift diff --git a/LocalPackages/XPC/Sources/XPC/XPCServer.swift b/LocalPackages/XPCHelper/Sources/XPCHelper/XPCServer.swift similarity index 100% rename from LocalPackages/XPC/Sources/XPC/XPCServer.swift rename to LocalPackages/XPCHelper/Sources/XPCHelper/XPCServer.swift diff --git a/UnitTests/Common/TestRunHelper.swift b/UnitTests/Common/TestRunHelper.swift index 8ec1016994..cfe1a56889 100644 --- a/UnitTests/Common/TestRunHelper.swift +++ b/UnitTests/Common/TestRunHelper.swift @@ -28,8 +28,6 @@ final class TestRunHelper: NSObject { super.init() XCTestObservationCenter.shared.addTestObserver(self) - // set NSApp.runType to appropriate test run type - _=NSApplication.swizzleRunTypeOnce // allow mocking NSApp.currentEvent _=NSApplication.swizzleCurrentEventOnce @@ -86,19 +84,6 @@ extension TestRunHelper: XCTestObservation { extension NSApplication { - // NSApp.runType - returns .unitTests or .integrationTests when running tests - - static var swizzleRunTypeOnce: Void = { - let runTypeMethod = class_getClassMethod(NSApplication.self, #selector(getter: NSApplication.runType))! - let swizzledRunTypeMethod = class_getClassMethod(NSApplication.self, #selector(NSApplication.swizzled_runType))! - - method_exchangeImplementations(runTypeMethod, swizzledRunTypeMethod) - }() - - @objc dynamic class func swizzled_runType() -> NSApplication.RunType { - RunType(bundle: Bundle(for: TestRunHelper.self)) - } - // allow mocking NSApp.currentEvent static var swizzleCurrentEventOnce: Void = { @@ -120,17 +105,3 @@ extension NSApplication { } } - -extension NSApplication.RunType { - - init(bundle: Bundle) { - if bundle.displayName!.hasPrefix("Unit") { - self = .unitTests - } else if bundle.displayName!.hasPrefix("Integration") { - self = .integrationTests - } else { - self = .uiTests - } - } - -} diff --git a/UnitTests/HomePage/ContinueSetUpModelTests.swift b/UnitTests/HomePage/ContinueSetUpModelTests.swift index 690592f9bb..730ee61e7b 100644 --- a/UnitTests/HomePage/ContinueSetUpModelTests.swift +++ b/UnitTests/HomePage/ContinueSetUpModelTests.swift @@ -48,7 +48,7 @@ final class ContinueSetUpModelTests: XCTestCase { var duckPlayerPreferences: DuckPlayerPreferencesPersistor! var delegate: CapturingSetUpVewModelDelegate! var privacyConfigManager: MockPrivacyConfigurationManager! - let userDefaults = UserDefaults(suiteName: Bundle.main.bundleIdentifier! + "." + NSApp.runType.description)! + let userDefaults = UserDefaults(suiteName: "\(Bundle.main.bundleIdentifier!).\(NSApplication.runType)")! @MainActor override func setUp() { UserDefaultsWrapper.clearAll() diff --git a/UnitTests/Permissions/TabPermissionsTests.swift b/UnitTests/Permissions/TabPermissionsTests.swift index ef6e0d3000..3e62249108 100644 --- a/UnitTests/Permissions/TabPermissionsTests.swift +++ b/UnitTests/Permissions/TabPermissionsTests.swift @@ -77,7 +77,7 @@ final class TabPermissionsTests: XCTestCase { return .ok(.html("")) }] - _=await tab.setUrl(urls.url, userEntered: nil)?.value?.result + _=await tab.setUrl(urls.url, userEntered: nil)?.result workspace.appUrl = Bundle.main.bundleURL @@ -158,7 +158,7 @@ final class TabPermissionsTests: XCTestCase { return .ok(.html("")) }] - _=await tab.setUrl(urls.url, userEntered: nil)?.value?.result + _=await tab.setUrl(urls.url, userEntered: nil)?.result workspace.appUrl = Bundle.main.bundleURL @@ -277,7 +277,7 @@ final class TabPermissionsTests: XCTestCase { return .ok(.html("")) }] - _=await tab.setUrl(urls.url, userEntered: nil)?.value?.result + _=await tab.setUrl(urls.url, userEntered: nil)?.result workspace.appUrl = Bundle.main.bundleURL @@ -321,7 +321,7 @@ final class TabPermissionsTests: XCTestCase { return .ok(.html("")) }] - _=await tab.setUrl(urls.url, userEntered: nil)?.value?.result + _=await tab.setUrl(urls.url, userEntered: nil)?.result workspace.appUrl = nil @@ -337,7 +337,7 @@ final class TabPermissionsTests: XCTestCase { XCTFail("Unexpected permissions query \(query)") } - let result = await tab.setUrl(externalUrl, userEntered: nil)?.value?.result + let result = await tab.setUrl(externalUrl, userEntered: nil)?.result guard case .failure(let error) = result else { XCTFail("unexpected result \(String(describing: result))") @@ -359,7 +359,7 @@ final class TabPermissionsTests: XCTestCase { return .ok(.html("")) }] - _=await tab.setUrl(urls.url, userEntered: nil)?.value?.result + _=await tab.setUrl(urls.url, userEntered: nil)?.result workspace.appUrl = nil @@ -375,7 +375,7 @@ final class TabPermissionsTests: XCTestCase { XCTFail("Unexpected permissions query \(query)") } - let result = await tab.setUrl(externalUrl, userEntered: externalUrl.absoluteString)?.value?.result + let result = await tab.setUrl(externalUrl, userEntered: externalUrl.absoluteString)?.result guard case .failure(let error) = result, let error = error as? DidCancelError, diff --git a/UnitTests/Statistics/PixelTests.swift b/UnitTests/Statistics/PixelTests.swift index 2d7b5425e4..15e2a0e27a 100644 --- a/UnitTests/Statistics/PixelTests.swift +++ b/UnitTests/Statistics/PixelTests.swift @@ -38,34 +38,6 @@ class PixelTests: XCTestCase { super.tearDown() } - // Temporarily disabled, as this test gets caught in the Run Loop extension: - @MainActor - func testWhenTimedPixelFiredThenCorrectDurationIsSet() { - let expectation = XCTestExpectation() - - let date: CFTimeInterval = 0 - let now: CFTimeInterval = 1 - - stub(condition: { request -> Bool in - if let url = request.url { - XCTAssertEqual("1.0", url.getParameter(named: "duration")) - return true - } - - XCTFail("Did not find duration param") - return true - }, response: { _ -> HTTPStubsResponse in - expectation.fulfill() - return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) - }) - - let pixel = TimedPixel(.crash, time: date) - - pixel.fire(now) - - wait(for: [expectation], timeout: 1.0) - } - func testWhenPixelFiredThenAPIHeadersAreAdded() { let expectation = XCTestExpectation() diff --git a/UnitTests/Tab/Services/FaviconManagerMock.swift b/UnitTests/Tab/Services/FaviconManagerMock.swift index 0e79469898..45afa591fd 100644 --- a/UnitTests/Tab/Services/FaviconManagerMock.swift +++ b/UnitTests/Tab/Services/FaviconManagerMock.swift @@ -25,7 +25,8 @@ import Common final class FaviconManagerMock: FaviconManagement { func loadFavicons() {} - var areFaviconsLoaded: Bool { return true } + @Published var areFaviconsLoaded = true + var faviconsLoadedPublisher: Published.Publisher { $areFaviconsLoaded } func handleFaviconLinks(_ faviconLinks: [FaviconUserScript.FaviconLink], documentUrl: URL, completion: @escaping (Favicon?) -> Void) { completion(nil) diff --git a/UnitTests/Waitlist/WaitlistViewModelTests.swift b/UnitTests/Waitlist/WaitlistViewModelTests.swift index 157a00c026..72c665c996 100644 --- a/UnitTests/Waitlist/WaitlistViewModelTests.swift +++ b/UnitTests/Waitlist/WaitlistViewModelTests.swift @@ -29,7 +29,7 @@ final class WaitlistViewModelTests: XCTestCase { func testWhenTimestampIsNotPresent_ThenStateIsNotJoinedQueue() async { let request = MockWaitlistRequest.failure() let storage = MockWaitlistStorage.init() - let viewModel = WaitlistViewModel(waitlistRequest: request, + let viewModel = NetworkProtectionWaitlistViewModel(waitlistRequest: request, waitlistStorage: storage, notificationService: MockNotificationService()) @@ -45,7 +45,7 @@ final class WaitlistViewModelTests: XCTestCase { storage.store(waitlistTimestamp: 12345) let notificationService = MockNotificationService(authorizationStatus: .authorized) - let viewModel = WaitlistViewModel(waitlistRequest: request, + let viewModel = NetworkProtectionWaitlistViewModel(waitlistRequest: request, waitlistStorage: storage, notificationService: notificationService) @@ -63,7 +63,7 @@ final class WaitlistViewModelTests: XCTestCase { storage.store(inviteCode: "ABCD1234") let notificationService = MockNotificationService(authorizationStatus: .authorized) - let viewModel = WaitlistViewModel(waitlistRequest: request, + let viewModel = NetworkProtectionWaitlistViewModel(waitlistRequest: request, waitlistStorage: storage, notificationService: notificationService) @@ -80,11 +80,11 @@ final class WaitlistViewModelTests: XCTestCase { let storage = MockWaitlistStorage() var notificationService = MockNotificationService() notificationService.authorizationStatus = .notDetermined - let viewModel = WaitlistViewModel(waitlistRequest: request, + let viewModel = NetworkProtectionWaitlistViewModel(waitlistRequest: request, waitlistStorage: storage, notificationService: notificationService) - var stateUpdates: [WaitlistViewModel.ViewState] = [] + var stateUpdates: [NetworkProtectionWaitlistViewModel.ViewState] = [] let cancellable = viewModel.$viewState.sink { stateUpdates.append($0) } await viewModel.perform(action: .joinQueue) @@ -102,7 +102,7 @@ final class WaitlistViewModelTests: XCTestCase { storage.store(inviteCode: "ABCD1234") let notificationService = MockNotificationService(authorizationStatus: .authorized) - let viewModel = WaitlistViewModel(waitlistRequest: request, + let viewModel = NetworkProtectionWaitlistViewModel(waitlistRequest: request, waitlistStorage: storage, notificationService: notificationService) diff --git a/scripts/appcast_manager/appcastManager.swift b/scripts/appcast_manager/appcastManager.swift index d27156403c..115e668c7b 100755 --- a/scripts/appcast_manager/appcastManager.swift +++ b/scripts/appcast_manager/appcastManager.swift @@ -111,8 +111,7 @@ case .releaseToInternalChannel, .releaseHotfixToPublicChannel: print("DMG Path: \(dmgPath)") print("Release Notes Path: \(releaseNotesPath)") - // Download appcast and update files - AppcastDownloader().download() + performCommonChecksAndOperations() // Handle dmg file guard let dmgURL = handleDMGFile(dmgPath: dmgPath, updatesDirectoryURL: specificDir) else { @@ -141,8 +140,7 @@ case .releaseToPublicChannel: print("Action: Release to public channel") print("Version: \(version)") - // Download appcast and update files - AppcastDownloader().download() + performCommonChecksAndOperations() // Verify version if !verifyVersion(version: version, atDirectory: specificDir) { @@ -166,10 +164,87 @@ case .releaseToPublicChannel: runGenerateAppcast(withVersions: version, rolloutInterval: "43200") } +// MARK: - Common + +func performCommonChecksAndOperations() { + // Check if generate_appcast is recent + guard checkSparkleToolRecency(toolName: "generate_appcast"), + checkSparkleToolRecency(toolName: "generate_keys"), + checkSparkleToolRecency(toolName: "sign_update"), + checkSparkleToolRecency(toolName: "BinaryDelta") else { + exit(1) + } + + // Verify signing keys + guard verifySigningKeys() else { + exit(1) + } + + // Download appcast and update files + AppcastDownloader().download() +} + func getDmgFilename(for version: String) -> String { return "duckduckgo-\(version).dmg" } +// MARK: - Checking the recency of Sparkle tools + +func checkSparkleToolRecency(toolName: String) -> Bool { + let binaryPath = shell("which", toolName).trimmingCharacters(in: .whitespacesAndNewlines) + + if binaryPath.isEmpty { + print("Failed to find the path for \(toolName).") + return false + } + + guard let binaryAttributes = try? FileManager.default.attributesOfItem(atPath: binaryPath), + let modificationDate = binaryAttributes[.modificationDate] as? Date else { + print("Failed to get the modification date for \(toolName).") + return false + } + + // Get the current script's path and navigate to the root folder to get the release date file + let currentScriptPath = URL(fileURLWithPath: #file) + let rootDirectory = currentScriptPath.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() + let releaseDateFilePath = rootDirectory.appendingPathComponent(".sparkle_tools_release_date") + + guard let releaseDateString = try? String(contentsOf: releaseDateFilePath, encoding: .utf8).trimmingCharacters(in: .whitespacesAndNewlines), + let releaseDate = DateFormatter.yyyyMMdd.date(from: releaseDateString) else { + print("Failed to get the release date from .sparkle_tools_release_date.") + return false + } + + if modificationDate < releaseDate { + print("\(toolName) from Sparkle binary utilities is outdated. Please visit https://github.com/sparkle-project/Sparkle/releases and install tools from the latest version.") + return false + } + + return true +} + +extension DateFormatter { + static let yyyyMMdd: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + return formatter + }() +} + +// MARK: - Verification of the signing keys + +func verifySigningKeys() -> Bool { + let publicKeyOutput = shell("generate_keys", "-p").trimmingCharacters(in: .whitespacesAndNewlines) + let desiredPublicKey = "ZaO/DNMzMPBldh40b5xVrpNBmqRkuGY0BNRCUng2qRo=" + + if publicKeyOutput == desiredPublicKey { + return true + } else { + print("Incorrect or missing public signing key. Please ensure you have the correct keys installed.") + return false + } +} + // MARK: - Downloading of Appcast and Files final class AppcastDownloader { @@ -517,10 +592,31 @@ func runGenerateAppcast(withVersions versions: String, channel: String? = nil, r print("Error writing diff to file: \(error)") } + // Move files back to the original location + moveFiles(from: specificDir.appendingPathComponent("old_updates"), to: specificDir) + print("Old update files moved back to \(specificDir.path)") + // Open specific directory in Finder shell("open", specificDir.path) } +func moveFiles(from sourceDir: URL, to destinationDir: URL) { + let fileManager = FileManager.default + do { + let fileURLs = try fileManager.contentsOfDirectory(at: sourceDir, includingPropertiesForKeys: nil) + for fileURL in fileURLs { + let destinationURL = destinationDir.appendingPathComponent(fileURL.lastPathComponent) + if fileManager.fileExists(atPath: destinationURL.path) { + try fileManager.removeItem(at: destinationURL) + } + try fileManager.moveItem(at: fileURL, to: destinationURL) + } + } catch { + print("Failed to move files from \(sourceDir.path) to \(destinationDir.path): \(error).") + exit(1) + } +} + @discardableResult func shell(_ command: String, _ arguments: String...) -> String { let task = Process() task.launchPath = "/usr/bin/env" diff --git a/scripts/assets/ExportOptions.plist b/scripts/assets/ExportOptions.plist index e44b4b5089..ac199e54dd 100644 --- a/scripts/assets/ExportOptions.plist +++ b/scripts/assets/ExportOptions.plist @@ -18,14 +18,14 @@ Sandbox MacOS Browser Release com.duckduckgo.mobile.ios.review Sandbox MacOS Browser Product Review - com.duckduckgo.macos.browser.network-protection-extension - macOS NetP System Extension - Release - com.duckduckgo.macos.browser.review.network-protection-extension - macOS NetP System Extension - Review - HKE973VLUW.com.duckduckgo.macos.browser.network-protection.system-extension.agent - macOS Network Protection Agent App (Distribution) - HKE973VLUW.com.duckduckgo.macos.browser.network-protection.system-extension.agent.review - macOS Network Protection Agent App Product Review + com.duckduckgo.macos.vpn.network-extension + macOS NetP VPN SysEx - Release (XPC) + com.duckduckgo.macos.vpn.network-extension.review + macOS NetP VPN SysEx - Review (XPC) + com.duckduckgo.macos.vpn + macOS NetP VPN App - Release (XPC) + com.duckduckgo.macos.vpn.review + macOS NetP VPN App - Review (XPC) HKE973VLUW.com.duckduckgo.macos.browser.network-protection.notifications Mac Browser NetP Developer ID Not. (Distribution) HKE973VLUW.com.duckduckgo.macos.browser.network-protection.notifications.review diff --git a/scripts/update_embedded.sh b/scripts/update_embedded.sh index f76c3671c1..3a73b39d0b 100755 --- a/scripts/update_embedded.sh +++ b/scripts/update_embedded.sh @@ -2,6 +2,8 @@ set -eo pipefail +# The following URLs shall match the ones in AppConfigurationURLprovider.swift. +# Danger checks that the URLs match on every PR. If the code changes, the regex that Danger uses may need an update. TDS_URL="https://staticcdn.duckduckgo.com/trackerblocking/v5/current/macos-tds.json" CONFIG_URL="https://staticcdn.duckduckgo.com/trackerblocking/config/v3/macos-config.json"