diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 3fc6578624..b8bb964ef9 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -44,6 +44,9 @@ jobs: runs-on: macos-13 timeout-minutes: 30 + outputs: + commit_author: ${{ steps.fetch_commit_author.outputs.commit_author }} + steps: - name: Check out the code uses: actions/checkout@v3 @@ -112,6 +115,16 @@ jobs: | sort -u -k 1,2 \ | xargs -L 1 ./scripts/report-failed-unit-test.sh -s ${{ vars.APPLE_CI_FAILING_TESTS_FAILED_TESTS_SECTION_ID }} + - name: Fetch latest commit author + if: always() && github.ref_name == 'develop' + id: fetch_commit_author + env: + GH_TOKEN: ${{ github.token }} + run: | + head_commit=$(git rev-parse HEAD) + author=$(gh api https://api.github.com/repos/${{ github.repository }}/commits/${head_commit} --jq .author.login) + echo "commit_author=${author}" >> $GITHUB_OUTPUT + release-build: name: Make Release Build @@ -174,24 +187,35 @@ jobs: -configuration "Release" \ | xcbeautify - asana: + create-asana-task: name: Create Asana Task needs: [swiftlint, unit-tests, shellcheck, release-build] - if: failure() && github.ref_name == 'develop' - - env: - WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - + if: failure() && github.ref_name == 'develop' && github.run_attempt == 1 + runs-on: ubuntu-latest steps: - name: Create Asana Task - uses: malmstein/github-asana-action@master + uses: duckduckgo/BrowserServicesKit/.github/actions/asana-failed-pr-checks@main + with: + action: create-task + asana-access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} + asana-section-id: ${{ vars.APPLE_CI_FAILING_TESTS_IOS_POST_MERGE_SECTION_ID }} + commit-author: ${{ needs.unit-tests.outputs.commit_author }} + + close-asana-task: + name: Close Asana Task + needs: [swiftlint, unit-tests, shellcheck, release-build] + + if: success() && github.ref_name == 'develop' && github.run_attempt > 1 + + runs-on: ubuntu-latest + + steps: + - name: Close Asana Task + uses: duckduckgo/BrowserServicesKit/.github/actions/asana-failed-pr-checks@main with: - asana-pat: ${{ secrets.ASANA_ACCESS_TOKEN }} - asana-project: ${{ vars.APPLE_CI_FAILING_TESTS_PROJECT_ID }} - asana-section: ${{ vars.APPLE_CI_FAILING_TESTS_IOS_POST_MERGE_SECTION_ID }} - asana-task-name: 'PR Check is failing on develop' - action: create-asana-task - asana-task-description: PR Checks conducted after merging have failed. See ${{ env.WORKFLOW_URL }}. Follow the steps on https://app.asana.com/0/1202500774821704/1205317064731691 to resolve this issue. + action: close-task + asana-access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} + asana-section-id: ${{ vars.APPLE_CI_FAILING_TESTS_IOS_POST_MERGE_SECTION_ID }} diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 7292a29c28..05ce74308f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -746,6 +746,7 @@ EE0153ED2A6FF9E6002A8B26 /* NetworkProtectionRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153EC2A6FF9E6002A8B26 /* NetworkProtectionRootView.swift */; }; EE0153EF2A70021E002A8B26 /* NetworkProtectionInviteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153EE2A70021E002A8B26 /* NetworkProtectionInviteView.swift */; }; EE276BEA2A77F823009167B6 /* NetworkProtectionRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE276BE92A77F823009167B6 /* NetworkProtectionRootViewController.swift */; }; + EE3766DE2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */; }; EE3B226B29DE0F110082298A /* MockInternalUserStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3B226A29DE0F110082298A /* MockInternalUserStoring.swift */; }; EE3B226C29DE0FD30082298A /* MockInternalUserStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3B226A29DE0F110082298A /* MockInternalUserStoring.swift */; }; EE41BD192A729E9C00546C57 /* NetworkProtectionInviteViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE41BD182A729E9C00546C57 /* NetworkProtectionInviteViewModelTests.swift */; }; @@ -763,6 +764,7 @@ EEEB80A32A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEEB80A22A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift */; }; EEF0F8CC2ABC832300630031 /* NetworkProtectionDebugFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF0F8CB2ABC832200630031 /* NetworkProtectionDebugFeatures.swift */; }; EEFAB4672A73C230008A38E4 /* NetworkProtectionTestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = EEFAB4662A73C230008A38E4 /* NetworkProtectionTestUtils */; }; + EEFC6A602AC0F2F80065027D /* UserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEFC6A5F2AC0F2F80065027D /* UserText.swift */; }; EEFD562F2A65B6CA00DAEC48 /* NetworkProtectionInviteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEFD562E2A65B6CA00DAEC48 /* NetworkProtectionInviteViewModel.swift */; }; EEFE9C732A603CE9005B0A26 /* NetworkProtectionStatusViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEFE9C722A603CE9005B0A26 /* NetworkProtectionStatusViewModelTests.swift */; }; F103073B1E7C91330059FEC7 /* BookmarksDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F103073A1E7C91330059FEC7 /* BookmarksDataSource.swift */; }; @@ -2332,6 +2334,7 @@ EE0153EC2A6FF9E6002A8B26 /* NetworkProtectionRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRootView.swift; sourceTree = ""; }; EE0153EE2A70021E002A8B26 /* NetworkProtectionInviteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionInviteView.swift; sourceTree = ""; }; EE276BE92A77F823009167B6 /* NetworkProtectionRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRootViewController.swift; sourceTree = ""; }; + EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionUNNotificationPresenter.swift; sourceTree = ""; }; EE3B226A29DE0F110082298A /* MockInternalUserStoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockInternalUserStoring.swift; sourceTree = ""; }; EE3B98EA2A9634CC002F63A0 /* DuckDuckGoAlpha.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DuckDuckGoAlpha.entitlements; sourceTree = ""; }; EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetsExtensionAlpha.entitlements; sourceTree = ""; }; @@ -2350,6 +2353,7 @@ EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Configuration-Alpha.xcconfig"; path = "Configuration/Configuration-Alpha.xcconfig"; sourceTree = ""; }; EEEB80A22A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionPacketTunnelProvider.swift; sourceTree = ""; }; EEF0F8CB2ABC832200630031 /* NetworkProtectionDebugFeatures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugFeatures.swift; sourceTree = ""; }; + EEFC6A5F2AC0F2F80065027D /* UserText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserText.swift; sourceTree = ""; }; EEFD562E2A65B6CA00DAEC48 /* NetworkProtectionInviteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionInviteViewModel.swift; sourceTree = ""; }; EEFE9C722A603CE9005B0A26 /* NetworkProtectionStatusViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionStatusViewModelTests.swift; sourceTree = ""; }; F103073A1E7C91330059FEC7 /* BookmarksDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksDataSource.swift; sourceTree = ""; }; @@ -2604,13 +2608,14 @@ 02025665298818B200E694E7 /* PacketTunnelProvider */ = { isa = PBXGroup; children = ( + EE3766DC2AC5940A00AAB575 /* NetworkProtection */, EE3B98EC2A963538002F63A0 /* PacketTunnelProviderAlpha.entitlements */, 02025670298818CB00E694E7 /* ProxyServer */, 02025666298818B200E694E7 /* AppTrackingProtectionPacketTunnelProvider.swift */, 02025B1429884EA500E694E7 /* DDGObserverFactory.swift */, 02025668298818B200E694E7 /* Info.plist */, 02025669298818B200E694E7 /* PacketTunnelProvider.entitlements */, - EEEB80A22A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift */, + EEFC6A5F2AC0F2F80065027D /* UserText.swift */, ); path = PacketTunnelProvider; sourceTree = ""; @@ -4344,6 +4349,15 @@ name = Root; sourceTree = ""; }; + EE3766DC2AC5940A00AAB575 /* NetworkProtection */ = { + isa = PBXGroup; + children = ( + EEEB80A22A421CE600386378 /* NetworkProtectionPacketTunnelProvider.swift */, + EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */, + ); + path = NetworkProtection; + sourceTree = ""; + }; EE3B226929DE0EE10082298A /* FeatureFlags */ = { isa = PBXGroup; children = ( @@ -5962,6 +5976,7 @@ 4BEF656D2989C2FC00B650CB /* EventType.swift in Sources */, 02025AAC2988229800E694E7 /* GCDHTTPProxyServer.swift in Sources */, 02025AAD2988229800E694E7 /* NWUDPSocket.swift in Sources */, + EE3766DE2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift in Sources */, 02025AAE2988229800E694E7 /* RawTCPSocketProtocol.swift in Sources */, 02025AAF2988229800E694E7 /* NWTCPSocket.swift in Sources */, 02025AB12988229800E694E7 /* RawSocketFactory.swift in Sources */, @@ -6005,6 +6020,7 @@ 02025AEB2988229800E694E7 /* Utils.swift in Sources */, 02025AEC2988229800E694E7 /* AppTrackingProtectionPacketTunnelProvider.swift in Sources */, 02025B1029884DC500E694E7 /* AppTrackerDataParser.swift in Sources */, + EEFC6A602AC0F2F80065027D /* UserText.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -7587,7 +7603,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -7624,7 +7640,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -7716,7 +7732,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -7743,7 +7759,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -7889,7 +7905,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -7913,7 +7929,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -7977,7 +7993,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8012,7 +8028,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8046,7 +8062,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -8076,7 +8092,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8362,7 +8378,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8388,7 +8404,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8420,7 +8436,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8457,7 +8473,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8493,7 +8509,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8528,11 +8544,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -8706,11 +8722,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -8739,10 +8755,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8d5c1e1681..7c8a292a91 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -156,7 +156,7 @@ }, { "package": "TrackerRadarKit", - "repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit", + "repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit.git", "state": { "branch": null, "revision": "4684440d03304e7638a2c8086895367e90987463", diff --git a/DuckDuckGo/NetworkProtectionDebugUtilities.swift b/DuckDuckGo/NetworkProtectionDebugUtilities.swift index 83062a1b3e..a3d47161b9 100644 --- a/DuckDuckGo/NetworkProtectionDebugUtilities.swift +++ b/DuckDuckGo/NetworkProtectionDebugUtilities.swift @@ -37,6 +37,46 @@ final class NetworkProtectionDebugUtilities { try? activeSession.sendProviderMessage(.expireRegistrationKey) } + + // MARK: - Notifications + + func sendTestNotificationRequest() async throws { + guard let activeSession = try? await ConnectionSessionUtilities.activeSession() else { + return + } + + try? activeSession.sendProviderMessage(.triggerTestNotification) + } + + // MARK: - Failure Simulation + + func triggerSimulation(_ option: NetworkProtectionSimulationOption) async { + guard let activeSession = try? await ConnectionSessionUtilities.activeSession() else { + return + } + + guard let message = option.extensionMessage else { + return + } + try? activeSession.sendProviderMessage(message) + } +} + +private extension NetworkProtectionSimulationOption { + var extensionMessage: ExtensionMessage? { + switch self { + case .crashFatalError: + return .simulateTunnelFatalError + case .crashMemory: + return .simulateTunnelMemoryOveruse + case .tunnelFailure: + return .simulateTunnelFailure + case .controllerFailure: + return nil + case .connectionInterruption: + return .simulateConnectionInterruption + } + } } #endif diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index f3210ba03f..ffa9d42df0 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -34,7 +34,8 @@ final class NetworkProtectionDebugViewController: UITableViewController { Sections.keychain: "Keychain", Sections.debugFeature: "Debug Features", Sections.simulateFailure: "Simulate Failure", - Sections.registrationKey: "Registration Key" + Sections.registrationKey: "Registration Key", + Sections.notifications: "Notifications" ] @@ -44,6 +45,7 @@ final class NetworkProtectionDebugViewController: UITableViewController { case debugFeature case simulateFailure case registrationKey + case notifications } @@ -63,7 +65,7 @@ final class NetworkProtectionDebugViewController: UITableViewController { case controllerFailure case crashFatalError case crashMemory - + case connectionInterruption } enum RegistrationKeyRows: Int, CaseIterable { @@ -72,6 +74,12 @@ final class NetworkProtectionDebugViewController: UITableViewController { } + enum NotificationsRows: Int, CaseIterable { + + case triggerTestNotification + + } + private let debugFeatures: NetworkProtectionDebugFeatures private let tokenStore: NetworkProtectionTokenStore @@ -122,6 +130,9 @@ final class NetworkProtectionDebugViewController: UITableViewController { case .registrationKey: configure(cell, forRegistrationKeyRow: indexPath.row) + case .notifications: + configure(cell, forNotificationRow: indexPath.row) + case.none: break } @@ -135,6 +146,7 @@ final class NetworkProtectionDebugViewController: UITableViewController { case .debugFeature: return DebugFeatureRows.allCases.count case .simulateFailure: return SimulateFailureRows.allCases.count case .registrationKey: return RegistrationKeyRows.allCases.count + case .notifications: return NotificationsRows.allCases.count case .none: return 0 } @@ -153,6 +165,8 @@ final class NetworkProtectionDebugViewController: UITableViewController { didSelectSimulateFailure(at: indexPath) case .registrationKey: didSelectRegistationKeyAction(at: indexPath) + case .notifications: + didSelectTestNotificationAction(at: indexPath) case .none: break } @@ -172,6 +186,8 @@ final class NetworkProtectionDebugViewController: UITableViewController { cell.textLabel?.text = "Tunnel: Crash (Fatal Error)" case .crashMemory: cell.textLabel?.text = "Tunnel: Crash (CPU/Memory)" + case .connectionInterruption: + cell.textLabel?.text = "Connection Interruption" case .none: break } @@ -179,11 +195,24 @@ final class NetworkProtectionDebugViewController: UITableViewController { private func didSelectSimulateFailure(at indexPath: IndexPath) { switch SimulateFailureRows(rawValue: indexPath.row) { - case .controllerFailure: simulateFailure(option: .controllerFailure) - case .tunnelFailure: simulateFailure(option: .tunnelFailure) - case .crashFatalError: simulateFailure(option: .crashFatalError) - case .crashMemory: simulateFailure(option: .crashMemory) - case .none: return + case .controllerFailure: + NetworkProtectionTunnelController.shouldSimulateFailure = true + case .tunnelFailure: + triggerSimulation(.tunnelFailure) + case .crashFatalError: + triggerSimulation(.crashFatalError) + case .crashMemory: + triggerSimulation(.crashMemory) + case .connectionInterruption: + triggerSimulation(.connectionInterruption) + case .none: + break + } + } + + private func triggerSimulation(_ option: NetworkProtectionSimulationOption) { + Task { + await NetworkProtectionDebugUtilities().triggerSimulation(option) } } @@ -236,22 +265,32 @@ final class NetworkProtectionDebugViewController: UITableViewController { } } - // MARK: Selection Actions + // MARK: Notifications - private func clearAuthToken() { - try? tokenStore.deleteToken() + private func configure(_ cell: UITableViewCell, forNotificationRow row: Int) { + switch NotificationsRows(rawValue: row) { + case .triggerTestNotification: + cell.textLabel?.text = "Test Notification" + case .none: + break + } } - private func simulateControllerFailure() { - NetworkProtectionTunnelController.enabledSimulationOption = .controllerFailure + private func didSelectTestNotificationAction(at indexPath: IndexPath) { + switch NotificationsRows(rawValue: indexPath.row) { + case .triggerTestNotification: + Task { + try await NetworkProtectionDebugUtilities().sendTestNotificationRequest() + } + case .none: + break + } } - private func simulaterTunnelFailure() { - NetworkProtectionTunnelController.enabledSimulationOption = .crashFatalError - } + // MARK: Selection Actions - private func simulateFailure(option: NetworkProtectionSimulationOption) { - NetworkProtectionTunnelController.enabledSimulationOption = .crashMemory + private func clearAuthToken() { + try? tokenStore.deleteToken() } } diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index d53263188a..6a5bef318b 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -26,8 +26,7 @@ import NetworkExtension import NetworkProtection final class NetworkProtectionTunnelController: TunnelController { - static var simulationOptions = NetworkProtectionSimulationOptions() - static var enabledSimulationOption: NetworkProtectionSimulationOption? + static var shouldSimulateFailure: Bool = false private let debugFeatures = NetworkProtectionDebugFeatures() private let tokenStore = NetworkProtectionKeychainTokenStore() @@ -98,19 +97,14 @@ final class NetworkProtectionTunnelController: TunnelController { private func start(_ tunnelManager: NETunnelProviderManager) throws { var options = [String: NSObject]() - if Self.simulationOptions.isEnabled(.controllerFailure) { - Self.simulationOptions.setEnabled(false, option: .controllerFailure) + if Self.shouldSimulateFailure { + Self.shouldSimulateFailure = false throw StartError.simulateControllerFailureError } options["activationAttemptId"] = UUID().uuidString as NSString options["authToken"] = try tokenStore.fetchToken() as NSString? - if let optionKey = Self.enabledSimulationOption?.optionKey { - options[optionKey] = NSNumber(value: true) - Self.enabledSimulationOption = nil - } - do { try tunnelManager.connection.startVPNTunnel(options: options) } catch { @@ -227,19 +221,4 @@ final class NetworkProtectionTunnelController: TunnelController { } } -private extension NetworkProtectionSimulationOption { - var optionKey: String? { - switch self { - case .crashFatalError: - return NetworkProtectionOptionKey.tunnelFatalErrorCrashSimulation - case .crashMemory: - return NetworkProtectionOptionKey.tunnelMemoryCrashSimulation - case .tunnelFailure: - return NetworkProtectionOptionKey.tunnelFailureSimulation - default: - return nil - } - } -} - #endif diff --git a/PacketTunnelProvider/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift similarity index 96% rename from PacketTunnelProvider/NetworkProtectionPacketTunnelProvider.swift rename to PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 3132cabffc..ccb15449ae 100644 --- a/PacketTunnelProvider/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -166,7 +166,9 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { let tokenStore = NetworkProtectionKeychainTokenStore(keychainType: .dataProtection(.unspecified), errorEvents: nil) let errorStore = NetworkProtectionTunnelErrorStore() - super.init(notificationsPresenter: DefaultNotificationPresenter(), + let notificationsPresenter = NetworkProtectionUNNotificationPresenter() + notificationsPresenter.requestAuthorization() + super.init(notificationsPresenter: notificationsPresenter, tunnelHealthStore: NetworkProtectionTunnelHealthStore(), controllerErrorStore: errorStore, keychainType: .dataProtection(.unspecified), @@ -201,21 +203,3 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } } } - -final class DefaultNotificationPresenter: NetworkProtectionNotificationsPresenter { - - func showTestNotification() { - } - - func showReconnectedNotification() { - } - - func showReconnectingNotification() { - } - - func showConnectionFailureNotification() { - } - - func showSupersededNotification() { - } -} diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift new file mode 100644 index 0000000000..fb7d192c76 --- /dev/null +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift @@ -0,0 +1,115 @@ +// +// NetworkProtectionUNNotificationPresenter.swift +// DuckDuckGo +// +// 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 UIKit +import NetworkProtection + +/// This class takes care of requesting the presentation of notifications using UNNotificationCenter +/// +final class NetworkProtectionUNNotificationPresenter: NSObject, NetworkProtectionNotificationsPresenter { + + private let userNotificationCenter: UNUserNotificationCenter + + private var threadIdentifier: String { + let bundleId = Bundle(for: Self.self).bundleIdentifier ?? "com.duckduckgo.mobile.ios.NetworkExtension" + return bundleId + ".threadIdentifier" + } + + init(userNotificationCenter: UNUserNotificationCenter = .current()) { + self.userNotificationCenter = userNotificationCenter + + super.init() + } + + // MARK: - Setup + + func requestAuthorization() { + userNotificationCenter.delegate = self + requestAlertAuthorization() + } + + // MARK: - Notification Utility methods + + private func requestAlertAuthorization(completionHandler: ((Bool) -> Void)? = nil) { + let options: UNAuthorizationOptions = .alert + + userNotificationCenter.requestAuthorization(options: options) { authorized, _ in + completionHandler?(authorized) + } + } + + private func notificationContent(body: String) -> UNNotificationContent { + let content = UNMutableNotificationContent() + + content.threadIdentifier = threadIdentifier + content.title = UserText.networkProtectionNotificationsTitle + content.body = body + + if #available(iOSApplicationExtension 15.0, *) { + content.interruptionLevel = .timeSensitive + content.relevanceScore = 0 + } + + return content + } + + func showTestNotification() { + // Debug only string. Doesn't need localized + let content = notificationContent(body: "Test notification") + showNotification(content) + } + + func showReconnectedNotification() { + let content = notificationContent(body: UserText.networkProtectionConnectionSuccessNotificationBody) + showNotification(content) + } + + func showReconnectingNotification() { + let content = notificationContent(body: UserText.networkProtectionConnectionInterruptedNotificationBody) + showNotification(content) + } + + func showConnectionFailureNotification() { + let content = notificationContent(body: UserText.networkProtectionConnectionFailureNotificationBody) + showNotification(content) + } + + func showSupersededNotification() { + } + + private func showNotification(_ content: UNNotificationContent) { + let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: .none) + + requestAlertAuthorization { authorized in + guard authorized else { + return + } + + self.userNotificationCenter.add(request) + } + } +} + +extension NetworkProtectionUNNotificationPresenter: UNUserNotificationCenterDelegate { + + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions { + return .banner + } + +} diff --git a/PacketTunnelProvider/UserText.swift b/PacketTunnelProvider/UserText.swift new file mode 100644 index 0000000000..0d2bd15fae --- /dev/null +++ b/PacketTunnelProvider/UserText.swift @@ -0,0 +1,35 @@ +// +// UserText.swift +// DuckDuckGo +// +// 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 + +// swiftlint:disable line_length +final class UserText { + + // MARK: - Network Protection Notifications + + static let networkProtectionNotificationsTitle = NSLocalizedString("network.protection.notification.title", value: "DuckDuckGo", comment: "The title of the notifications shown from Network Protection") + + static let networkProtectionConnectionSuccessNotificationBody = NSLocalizedString("network.protection.success.notification.body", value: "Network Protection is On. Your location and online activity are protected.", comment: "The body of the notification shown when Network Protection reconnects successfully") + + static let networkProtectionConnectionInterruptedNotificationBody = NSLocalizedString("network.protection.interrupted.notification.body", value: "Network Protection was interrupted. Attempting to reconnect now...", comment: "The body of the notification shown when Network Protection's connection is interrupted") + + static let networkProtectionConnectionFailureNotificationBody = NSLocalizedString("network.protection.failure.notification.body", value: "Network Protection failed to connect. Please try again later.", comment: "The body of the notification shown when Network Protection fails to reconnect") +} +// swiftlint:enable line_length