From ec61d40bee277432f50a00125a4c71748c749acf Mon Sep 17 00:00:00 2001
From: Tom Strba <57389842+tomasstrba@users.noreply.github.com>
Date: Wed, 14 Feb 2024 11:08:34 +0100
Subject: [PATCH 01/12] Positioning of the preview fixed for external display
(#2200)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Task/Issue URL:
https://app.asana.com/0/1177771139624306/1206591283698064/f
**Description**:
This PR fixes an issue with previews being at the wrong position on
external display when the main MacBook display is set as main
---
DuckDuckGo/TabPreview/TabPreviewWindowController.swift | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/DuckDuckGo/TabPreview/TabPreviewWindowController.swift b/DuckDuckGo/TabPreview/TabPreviewWindowController.swift
index 681a6da639..6e4e5f4456 100644
--- a/DuckDuckGo/TabPreview/TabPreviewWindowController.swift
+++ b/DuckDuckGo/TabPreview/TabPreviewWindowController.swift
@@ -129,7 +129,8 @@ final class TabPreviewWindowController: NSWindowController {
// Make sure preview is presented within screen
if let screenVisibleFrame = window.screen?.visibleFrame {
- topLeftPoint.x = min(topLeftPoint.x, screenVisibleFrame.width - window.frame.width)
+ topLeftPoint.x = min(topLeftPoint.x, screenVisibleFrame.origin.x + screenVisibleFrame.width - window.frame.width)
+ topLeftPoint.x = max(topLeftPoint.x, screenVisibleFrame.origin.x)
let windowHeight = window.frame.size.height
if topLeftPoint.y <= windowHeight + screenVisibleFrame.origin.y {
From 394378e8f3503edefd97384107f50e9c70550667 Mon Sep 17 00:00:00 2001
From: Tomas Strba
Date: Tue, 20 Feb 2024 18:05:17 +0100
Subject: [PATCH 02/12] 1.75.1 (121)
---
Configuration/BuildNumber.xcconfig | 2 +-
Configuration/Version.xcconfig | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig
index 459e731b44..ba1a9b2a82 100644
--- a/Configuration/BuildNumber.xcconfig
+++ b/Configuration/BuildNumber.xcconfig
@@ -1 +1 @@
-CURRENT_PROJECT_VERSION = 120
+CURRENT_PROJECT_VERSION = 121
diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig
index 39ca579f82..18e593a3b9 100644
--- a/Configuration/Version.xcconfig
+++ b/Configuration/Version.xcconfig
@@ -1 +1 @@
-MARKETING_VERSION = 1.75.0
+MARKETING_VERSION = 1.75.1
From f76e8cc1ef9b836c8775a9509b73d3f34f9350e3 Mon Sep 17 00:00:00 2001
From: Alexey Martemyanov
Date: Wed, 21 Feb 2024 14:45:10 +0600
Subject: [PATCH 03/12] fix onboarding on first launch (#2229)
Task/Issue URL: https://app.asana.com/0/1177771139624306/1206646111353020/f
Description:
Fixes onboarding opening on first launch
---
DuckDuckGo/Tab/Model/Tab.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/DuckDuckGo/Tab/Model/Tab.swift b/DuckDuckGo/Tab/Model/Tab.swift
index 57cad4d4db..aba8f89859 100644
--- a/DuckDuckGo/Tab/Model/Tab.swift
+++ b/DuckDuckGo/Tab/Model/Tab.swift
@@ -904,7 +904,7 @@ protocol NewWindowPolicyDecisionMaker {
func startOnboarding() {
userInteractionDialog = nil
- webView.load(URLRequest(url: .welcome))
+ setContent(.onboarding)
}
@MainActor(unsafe)
From 3295013191ca51a78537915684a313ebd605683b Mon Sep 17 00:00:00 2001
From: Dax the Duck
Date: Wed, 21 Feb 2024 08:58:53 +0000
Subject: [PATCH 04/12] Bump version to 1.76.0 (122)
---
Configuration/BuildNumber.xcconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig
index ba1a9b2a82..060339a249 100644
--- a/Configuration/BuildNumber.xcconfig
+++ b/Configuration/BuildNumber.xcconfig
@@ -1 +1 @@
-CURRENT_PROJECT_VERSION = 121
+CURRENT_PROJECT_VERSION = 122
From 2ff496cc62982263656a1ee9742b5b8db5366f75 Mon Sep 17 00:00:00 2001
From: Dominik Kapusta
Date: Wed, 21 Feb 2024 11:18:40 +0100
Subject: [PATCH 05/12] Fix S3 access secrets
---
.github/workflows/publish_dmg_release.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/publish_dmg_release.yml b/.github/workflows/publish_dmg_release.yml
index 86c787cb1d..8ccbe6905b 100644
--- a/.github/workflows/publish_dmg_release.yml
+++ b/.github/workflows/publish_dmg_release.yml
@@ -109,8 +109,8 @@ jobs:
- name: Upload to S3
id: upload
env:
- AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_RELEASE_S3 }}
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_RELEASE_S3 }}
AWS_DEFAULT_REGION: ${{ vars.AWS_DEFAULT_REGION }}
run: |
# Back up existing appcast2.xml
From 60c5f366ba3978e7b975a9a4a422781c0673a79a Mon Sep 17 00:00:00 2001
From: Dominik Kapusta
Date: Wed, 21 Feb 2024 11:46:56 +0100
Subject: [PATCH 06/12] Update upload_to_s3.sh to use curl for checking files
existence on CDN
---
scripts/upload_to_s3/upload_to_s3.sh | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/scripts/upload_to_s3/upload_to_s3.sh b/scripts/upload_to_s3/upload_to_s3.sh
index ac7979f4c2..ddce4555c6 100755
--- a/scripts/upload_to_s3/upload_to_s3.sh
+++ b/scripts/upload_to_s3/upload_to_s3.sh
@@ -2,6 +2,7 @@
# Constants
S3_PATH="s3://ddg-staticcdn/macos-desktop-browser/"
+CDN_PATH="https://staticcdn.duckduckgo.com/macos-desktop-browser/"
# Defaults
if [[ -n "$CI" ]]; then
@@ -149,13 +150,13 @@ for FILENAME in $FILES_TO_UPLOAD; do
fi
# Check if the file exists on S3
- AWS_CMD="$AWS s3 ls ${S3_PATH}${FILENAME}"
- echo "Checking S3 for ${S3_PATH}${FILENAME}..."
- if ! $AWS s3 ls "${S3_PATH}${FILENAME}" > /dev/null 2>&1; then
- echo "$FILENAME not found on S3. Marking for upload."
- MISSING_FILES+=("$FILENAME")
+ printf '%s' "Checking CDN for ${CDN_PATH}${FILENAME} ... "
+ if curl -fLSsI "${CDN_PATH}${FILENAME}" >/dev/null 2>&1; then
+ echo "✅"
else
- echo "$FILENAME exists on S3. Skipping."
+ echo "❌"
+ echo "🚢 Marking $FILENAME for upload."
+ MISSING_FILES+=("$FILENAME")
fi
done
From 0ae321b117f224ae71a9e10cabe64ce88f4828a6 Mon Sep 17 00:00:00 2001
From: Michal Smaga
Date: Wed, 21 Feb 2024 18:28:57 +0100
Subject: [PATCH 07/12] Fix refreshing of subscription preference pane when
switching between panes (#2230)
Task/Issue URL:
https://app.asana.com/0/1199230911884351/1206654425458681/f
Tech Design URL:
CC:
**Description**:
Fix refreshing of subscription preference pane when switching between
panes.
Also the task var was not nilled so it was only fired once per viewModel
instance.
Additionally the check for the expiry date of subscription was wrong and
it preemptively cleared cached entitlements resulting in blinking and
refresh for every settings pane switch.
---
###### Internal references:
[Pull Request Review
Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f)
[Software Engineering
Expectations](https://app.asana.com/0/59792373528535/199064865822552)
[Technical Design
Template](https://app.asana.com/0/59792373528535/184709971311943)
[Pull Request
Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f)
---
.../PreferencesSubscriptionModel.swift | 20 +++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift
index 15b6c3ba93..22ab992e79 100644
--- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift
+++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Preferences/PreferencesSubscriptionModel.swift
@@ -173,14 +173,18 @@ public final class PreferencesSubscriptionModel: ObservableObject {
func fetchAndUpdateSubscriptionDetails() {
guard fetchSubscriptionDetailsTask == nil else { return }
- fetchSubscriptionDetailsTask = Task {
- guard let token = accountManager.accessToken else { return }
+ fetchSubscriptionDetailsTask = Task { [weak self] in
+ defer {
+ self?.fetchSubscriptionDetailsTask = nil
+ }
+
+ guard let token = self?.accountManager.accessToken else { return }
if let cachedDate = SubscriptionService.cachedSubscriptionDetailsResponse?.expiresOrRenewsAt {
- updateDescription(for: cachedDate)
+ self?.updateDescription(for: cachedDate)
- if cachedDate.timeIntervalSinceNow > 0 {
- self.cachedEntitlements = []
+ if cachedDate.timeIntervalSinceNow < 0 {
+ self?.cachedEntitlements = []
}
}
@@ -190,13 +194,13 @@ public final class PreferencesSubscriptionModel: ObservableObject {
return
}
- updateDescription(for: response.expiresOrRenewsAt)
+ self?.updateDescription(for: response.expiresOrRenewsAt)
- subscriptionPlatform = response.platform
+ self?.subscriptionPlatform = response.platform
}
if case let .success(entitlements) = await AccountManager().fetchEntitlements() {
- self.cachedEntitlements = entitlements
+ self?.cachedEntitlements = entitlements
}
}
}
From 5e2a8c0c225cc11787b8422c015b05cf292ee464 Mon Sep 17 00:00:00 2001
From: Fernando Bunn
Date: Wed, 21 Feb 2024 18:39:18 +0000
Subject: [PATCH 08/12] DBP API changes for WebUI - Release (#2233)
Cherry picked from https://github.com/duckduckgo/macos-browser/pull/2146
Task/Issue URL:
https://app.asana.com/0/1204167627774280/1206480273655878/f
**Description**:
Add url property to data broker model, and changes result for WebUI call
to support new UI designs
---
.../DataBrokerProtectionDataManager.swift | 22 +++
.../DataBrokerDatabaseBrowserView.swift | 12 +-
.../Model/DBPUICommunicationModel.swift | 17 ++
.../Model/DataBroker.swift | 51 +++++
.../Operations/DataBrokerOperation.swift | 2 +-
.../DataBrokerProtectionBrokerUpdater.swift | 2 +-
.../JSON/advancedbackgroundchecks.com.json | 11 +-
.../Resources/JSON/backgroundcheck.run.json | 7 +-
.../Resources/JSON/centeda.com.json | 13 +-
.../Resources/JSON/clubset.com.json | 9 +-
.../Resources/JSON/clustrmaps.com.json | 13 +-
.../Resources/JSON/councilon.com.json | 9 +-
.../Resources/JSON/curadvisor.com.json | 11 +-
.../JSON/cyberbackgroundchecks.com.json | 9 +-
.../Resources/JSON/dataveria.com.json | 13 +-
.../JSON/fastbackgroundcheck.com.json | 11 +-
.../JSON/freepeopledirectory.com.json | 9 +-
.../Resources/JSON/inforver.com.json | 13 +-
.../Resources/JSON/kwold.com.json | 9 +-
.../Resources/JSON/neighbor.report.json | 25 +--
.../Resources/JSON/newenglandfacts.com.json | 9 +-
.../Resources/JSON/officialusa.com.json | 11 +-
.../JSON/people-background-check.com.json | 9 +-
.../Resources/JSON/peoplefinders.com.json | 35 ++--
.../Resources/JSON/peoplesearchnow.com.json | 9 +-
.../Resources/JSON/pub360.com.json | 13 +-
.../Resources/JSON/publicreports.com.json | 9 +-
.../Resources/JSON/quickpeopletrace.com.json | 12 +-
.../Resources/JSON/searchpeoplefree.com.json | 9 +-
.../JSON/smartbackgroundchecks.com.json | 9 +-
.../Resources/JSON/spokeo.com.json | 55 ++++--
.../Resources/JSON/truepeoplesearch.com.json | 9 +-
.../Resources/JSON/usa-people-search.com.json | 13 +-
.../Resources/JSON/usatrace.com.json | 9 +-
.../Resources/JSON/usphonebook.com.json | 13 +-
.../Resources/JSON/verecor.com.json | 29 +--
.../Resources/JSON/vericora.com.json | 13 +-
.../Resources/JSON/veriforia.com.json | 13 +-
.../Resources/JSON/veripages.com.json | 13 +-
.../Resources/JSON/virtory.com.json | 9 +-
.../Resources/JSON/wellnut.com.json | 9 +-
.../DataBrokerProtectionProcessor.swift | 1 -
.../Services/EmailService.swift | 6 +-
...DataBrokerProtectionDatabaseProvider.swift | 18 +-
.../Storage/Mappers.swift | 3 +-
.../Storage/SchedulerSchema.swift | 4 +
.../UI/DBPUICommunicationLayer.swift | 10 +-
.../DataBrokerProtection/UI/UIMapper.swift | 80 ++++++--
.../BrokerJSONCodableTests.swift | 187 +++++++++++++++++-
...kerProfileQueryOperationManagerTests.swift | 13 +-
.../DataBrokerProtectionUpdaterTests.swift | 6 +-
.../EmailServiceTests.swift | 6 +-
.../MapperToUITests.swift | 54 ++---
.../MismatchCalculatorUseCaseTests.swift | 2 +
.../DataBrokerProtectionTests/Mocks.swift | 8 +-
.../OperationPreferredDateUpdaterTests.swift | 1 +
56 files changed, 691 insertions(+), 286 deletions(-)
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift
index 857f174360..52eabbae92 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift
@@ -290,4 +290,26 @@ extension InMemoryDataCache: DBPUICommunicationDelegate {
return mapper.maintenanceScanState(brokerProfileQueryData)
}
+
+ func getDataBrokers() async -> [DBPUIDataBroker] {
+ brokerProfileQueryData
+ // 1. We get all brokers (in this list brokers are repeated)
+ .map { $0.dataBroker }
+ // 2. We map the brokers to the UI model
+ .flatMap { dataBroker -> [DBPUIDataBroker] in
+ var result: [DBPUIDataBroker] = []
+ result.append(DBPUIDataBroker(name: dataBroker.name, url: dataBroker.url))
+
+ for mirrorSite in dataBroker.mirrorSites {
+ result.append(DBPUIDataBroker(name: mirrorSite.name, url: mirrorSite.url))
+ }
+ return result
+ }
+ // 3. We delete duplicates
+ .reduce(into: [DBPUIDataBroker]()) { (result, dataBroker) in
+ if !result.contains(where: { $0.url == dataBroker.url }) {
+ result.append(dataBroker)
+ }
+ }
+ }
}
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBaseBrowser/DataBrokerDatabaseBrowserView.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBaseBrowser/DataBrokerDatabaseBrowserView.swift
index 18b5ffa6b6..fd09e77417 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBaseBrowser/DataBrokerDatabaseBrowserView.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBaseBrowser/DataBrokerDatabaseBrowserView.swift
@@ -49,6 +49,7 @@ struct DatabaseView: View {
@State private var isPopoverVisible = false
@State private var selectedData: String = ""
let data: [DataBrokerDatabaseBrowserData.Row]
+ let rowHeight: CGFloat = 40.0
var body: some View {
if data.count > 0 {
@@ -62,6 +63,11 @@ struct DatabaseView: View {
}
}
+ private func spacerHeight(_ geometry: GeometryProxy) -> CGFloat {
+ let result = geometry.size.height - CGFloat(data.count) * rowHeight
+ return max(0, result)
+ }
+
private func dataView() -> some View {
GeometryReader { geometry in
ScrollView([.horizontal, .vertical]) {
@@ -86,7 +92,8 @@ struct DatabaseView: View {
ForEach(row.data.keys.sorted(), id: \.self) { key in
VStack {
Text("\(row.data[key]?.description ?? "")")
- .frame(maxWidth: 200, maxHeight: 50)
+ .frame(maxWidth: 200)
+ .frame(height: rowHeight)
.frame(minWidth: 60)
.onTapGesture {
selectedData = row.data[key]?.description ?? ""
@@ -100,7 +107,8 @@ struct DatabaseView: View {
}
}
}
- Spacer(minLength: geometry.size.height)
+ Spacer()
+ .frame(height: spacerHeight(geometry))
}
.frame(minWidth: geometry.size.width, minHeight: 0, alignment: .topLeading)
}
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUICommunicationModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUICommunicationModel.swift
index 353e3912df..5f242b34a2 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUICommunicationModel.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUICommunicationModel.swift
@@ -103,12 +103,24 @@ struct DBPUIAddressAtIndex: Codable {
/// Message Object representing a data broker
struct DBPUIDataBroker: Codable, Hashable {
let name: String
+ let url: String
+ let date: Double?
+
+ init(name: String, url: String, date: Double? = nil) {
+ self.name = name
+ self.url = url
+ self.date = date
+ }
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
}
+struct DBPUIDataBrokerList: DBPUISendableMessage {
+ let dataBrokers: [DBPUIDataBroker]
+}
+
/// Message Object representing a requested change to the user profile's brith year
struct DBPUIBirthYear: Codable {
let year: Int
@@ -123,6 +135,7 @@ struct DBPUIDataBrokerProfileMatch: Codable {
let addresses: [DBPUIUserProfileAddress]
let alternativeNames: [String]
let relatives: [String]
+ let date: Double? // Used in some methods to set the removedDate or found date
}
/// Protocol to represent a message that can be passed from the host to the UI
@@ -139,6 +152,10 @@ struct DBPUIScanAndOptOutMaintenanceState: DBPUISendableMessage {
struct DBPUIOptOutMatch: DBPUISendableMessage {
let dataBroker: DBPUIDataBroker
let matches: Int
+ let name: String
+ let alternativeNames: [String]
+ let addresses: [DBPUIUserProfileAddress]
+ let date: Double
}
/// Data representing the initial scan progress
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift
index fb96638206..e1a17f590b 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift
@@ -32,13 +32,46 @@ extension Int {
struct MirrorSite: Codable, Sendable {
let name: String
+ let url: String
let addedAt: Date
let removedAt: Date?
+
+ enum CodingKeys: CodingKey {
+ case name
+ case url
+ case addedAt
+ case removedAt
+ }
+
+ init(name: String, url: String, addedAt: Date, removedAt: Date? = nil) {
+ self.name = name
+ self.url = url
+ self.addedAt = addedAt
+ self.removedAt = removedAt
+ }
+
+ init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ name = try container.decode(String.self, forKey: .name)
+
+ // The older versions of the JSON file did not have a URL property.
+ // When decoding those cases, we fallback to its name, since the name was the URL.
+ do {
+ url = try container.decode(String.self, forKey: .url)
+ } catch {
+ url = name
+ }
+
+ addedAt = try container.decode(Date.self, forKey: .addedAt)
+ removedAt = try? container.decode(Date.self, forKey: .removedAt)
+
+ }
}
struct DataBroker: Codable, Sendable {
let id: Int64?
let name: String
+ let url: String
let steps: [Step]
let version: String
let schedulingConfig: DataBrokerScheduleConfig
@@ -51,6 +84,7 @@ struct DataBroker: Codable, Sendable {
enum CodingKeys: CodingKey {
case name
+ case url
case steps
case version
case schedulingConfig
@@ -60,6 +94,7 @@ struct DataBroker: Codable, Sendable {
init(id: Int64? = nil,
name: String,
+ url: String,
steps: [Step],
version: String,
schedulingConfig: DataBrokerScheduleConfig,
@@ -68,6 +103,13 @@ struct DataBroker: Codable, Sendable {
) {
self.id = id
self.name = name
+
+ if url.isEmpty {
+ self.url = name
+ } else {
+ self.url = url
+ }
+
self.steps = steps
self.version = version
self.schedulingConfig = schedulingConfig
@@ -78,6 +120,15 @@ struct DataBroker: Codable, Sendable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
+
+ // The older versions of the JSON file did not have a URL property.
+ // When decoding those cases, we fallback to its name, since the name was the URL.
+ do {
+ url = try container.decode(String.self, forKey: .url)
+ } catch {
+ url = name
+ }
+
version = try container.decode(String.self, forKey: .version)
steps = try container.decode([Step].self, forKey: .steps)
schedulingConfig = try container.decode(DataBrokerScheduleConfig.self, forKey: .schedulingConfig)
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift
index 5777596838..37cd385e4e 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperation.swift
@@ -99,7 +99,7 @@ extension DataBrokerOperation {
if action.needsEmail {
do {
stageCalculator?.setStage(.emailGenerate)
- extractedProfile?.email = try await emailService.getEmail(dataBrokerName: query.dataBroker.name)
+ extractedProfile?.email = try await emailService.getEmail(dataBrokerURL: query.dataBroker.url)
stageCalculator?.fireOptOutEmailGenerate()
} catch {
await onError(error: DataBrokerProtectionError.emailError(error as? EmailError))
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift
index 28c2128a00..f3203334e2 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift
@@ -152,7 +152,7 @@ public struct DataBrokerProtectionBrokerUpdater {
// 2. If does exist, we check the number version, if the version number is new, we update it
// 3. If it does not exist, we add it, and we create the scan operations related to it
private func update(_ broker: DataBroker) throws {
- guard let savedBroker = try vault.fetchBroker(with: broker.name) else {
+ guard let savedBroker = try vault.fetchBroker(with: broker.url) else {
// The broker does not exist in the current storage. We need to add it.
try add(broker)
return
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/advancedbackgroundchecks.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/advancedbackgroundchecks.com.json
index 60823c78ac..ee35af6d17 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/advancedbackgroundchecks.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/advancedbackgroundchecks.com.json
@@ -1,8 +1,9 @@
{
- "name": "advancedbackgroundchecks.com",
- "version": "0.1.0",
+ "name": "AdvancedBackgroundChecks",
+ "url": "advancedbackgroundchecks.com",
+ "version": "0.1.4",
"parent": "peoplefinders.com",
- "addedDatetime": 1678078800000,
+ "addedDatetime": 1678082400000,
"steps": [
{
"stepType": "scan",
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "ef8031e6-5e61-4183-b57e-7df156c7129a",
+ "id": "c73ba931-9e01-4d37-9e15-2fd7a14eefa3",
"url": "https://www.advancedbackgroundchecks.com/names/${firstName}-${lastName}_${city}-${state}_age_${age}"
},
{
"actionType": "extract",
- "id": "f3ed744c-6cfc-4a99-b46e-6095587eadfc",
+ "id": "94003082-0a9d-4418-ac88-68595c7f4953",
"selector": ".card-block",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/backgroundcheck.run.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/backgroundcheck.run.json
index 5b3800cfcf..552923ca1f 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/backgroundcheck.run.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/backgroundcheck.run.json
@@ -1,6 +1,7 @@
{
"name": "backgroundcheck.run",
- "version": "0.1.1",
+ "url": "backgroundcheck.run",
+ "version": "0.1.4",
"parent": "verecor.com",
"addedDatetime": 1677736800000,
"steps": [
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "aa12b430-8e5d-4c64-bb77-2961f19a1bc8",
+ "id": "5f90e39f-cb94-4b8d-94ed-48ba0060dc08",
"url": "https://backgroundcheck.run/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}"
},
{
"actionType": "extract",
- "id": "75fd2e16-d84a-4bbe-9cf1-79c6d1cc4dec",
+ "id": "3225fa15-4e00-4e6a-bfc7-a85dfb504c86",
"selector": ".b-pfl-list",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/centeda.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/centeda.com.json
index 130c996369..bb15f0093f 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/centeda.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/centeda.com.json
@@ -1,8 +1,9 @@
{
- "name": "centeda.com",
- "version": "0.1.1",
+ "name": "Centeda",
+ "url": "centeda.com",
+ "version": "0.1.4",
"parent": "verecor.com",
- "addedDatetime": 1677733200000,
+ "addedDatetime": 1677736800000,
"steps": [
{
"stepType": "scan",
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "25990359-3d58-45de-bdfd-d524b1946e57",
+ "id": "2f6639c0-201f-4d5e-8467-ae0ba457b409",
"url": "https://centeda.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "7108af78-dbbf-47ec-8bb9-e44be505993e",
+ "id": "e2e236b0-515b-43b3-9154-0432ed9b7566",
"selector": ".search-item",
"profile": {
"name": {
@@ -63,4 +64,4 @@
"confirmOptOutScan": 72,
"maintenanceScan": 240
}
-}
+}
\ No newline at end of file
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/clubset.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/clubset.com.json
index f871133c15..8b6801fc48 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/clubset.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/clubset.com.json
@@ -1,6 +1,7 @@
{
- "name": "clubset.com",
- "version": "0.1.1",
+ "name": "Clubset",
+ "url": "clubset.com",
+ "version": "0.1.4",
"parent": "verecor.com",
"addedDatetime": 1702965600000,
"steps": [
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "917f5d40-2011-4fe5-9ef6-136d6bfaea35",
+ "id": "5c559c67-c13c-4055-a318-6ba35d62a2cf",
"url": "https://clubset.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state|upcase}&city=${city|capitalize}&fage=${age|ageRange}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "06e37215-ef34-4971-bf86-e5a03dfe46e8",
+ "id": "866bdfc5-069e-4734-9ce0-a19976fa796b",
"selector": ".card",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/clustrmaps.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/clustrmaps.com.json
index 0aca895c02..4c2bd20999 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/clustrmaps.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/clustrmaps.com.json
@@ -1,8 +1,9 @@
{
- "name": "clustrmaps.com",
- "version": "0.1.1",
+ "name": "ClustrMaps",
+ "url": "clustrmaps.com",
+ "version": "0.1.4",
"parent": "neighbor.report",
- "addedDatetime": 1692590400000,
+ "addedDatetime": 1692594000000,
"steps": [
{
"stepType": "scan",
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "a39655de-5c23-477d-9887-1d34966a1069",
+ "id": "e6929e37-4764-450a-be2a-73479f11842a",
"url": "https://clustrmaps.com/persons/${firstName}-${lastName}/${state|stateFull|capitalize}/${city|hyphenated}"
},
{
"actionType": "extract",
- "id": "4e3a628e-3634-4a2b-b632-4fbb8ce0b52b",
+ "id": "06f39df7-89c2-40da-b288-cdf3ed0e4bfd",
"selector": ".//div[@itemprop='Person']",
"profile": {
"name": {
@@ -55,4 +56,4 @@
"confirmOptOutScan": 72,
"maintenanceScan": 240
}
-}
+}
\ No newline at end of file
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/councilon.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/councilon.com.json
index d68f6b9f4c..3df8d7f195 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/councilon.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/councilon.com.json
@@ -1,6 +1,7 @@
{
- "name": "councilon.com",
- "version": "0.1.1",
+ "name": "Councilon",
+ "url": "councilon.com",
+ "version": "0.1.4",
"parent": "verecor.com",
"addedDatetime": 1702965600000,
"steps": [
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "295418e5-e1da-43b4-af50-75576ca4f843",
+ "id": "a5052dda-d4e7-4d3f-97bc-ef9f0aa9ae5f",
"url": "https://councilon.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "eead1b72-7d6b-4cdc-988d-5ea66eb398f1",
+ "id": "55a50a37-9b1b-40fa-8533-af1273a26258",
"selector": ".card",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/curadvisor.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/curadvisor.com.json
index 33f27d0c79..3b59ed585e 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/curadvisor.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/curadvisor.com.json
@@ -1,8 +1,9 @@
{
- "name": "curadvisor.com",
- "version": "0.1.1",
+ "name": "CurAdvisor",
+ "url": "curadvisor.com",
+ "version": "0.1.4",
"parent": "verecor.com",
- "addedDatetime": 1677736800000,
+ "addedDatetime": 1703052000000,
"steps": [
{
"stepType": "scan",
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "bdb69f52-8ece-4d65-9b78-543fef0e90ae",
+ "id": "ab5503c7-bd11-4320-b38e-c637b239182e",
"url": "https://curadvisor.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "3b9bc992-ecc0-4dc5-b716-fcea021cbcdb",
+ "id": "d273c1cf-2635-40d7-b26f-6f34467282cf",
"selector": ".card",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/cyberbackgroundchecks.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/cyberbackgroundchecks.com.json
index e27b744790..f930834db9 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/cyberbackgroundchecks.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/cyberbackgroundchecks.com.json
@@ -1,6 +1,7 @@
{
- "name": "cyberbackgroundchecks.com",
- "version": "0.1.1",
+ "name": "Cyber Background Checks",
+ "url": "cyberbackgroundchecks.com",
+ "version": "0.1.4",
"parent": "peoplefinders.com",
"addedDatetime": 1705644000000,
"steps": [
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "4b7037f3-9c6a-42b5-929e-621256e0a044",
+ "id": "d8c84470-d8b3-4c46-a645-01cc6b139b3b",
"url": "https://www.cyberbackgroundchecks.com/people/${firstName}-${lastName}/${state}/${city}"
},
{
"actionType": "extract",
- "id": "f36a73d7-9efb-452e-8c60-6d9df2964bcf",
+ "id": "b4c12cf2-0fd6-4209-8816-3bf2cce23cde",
"selector": ".card",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/dataveria.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/dataveria.com.json
index 3dfe8e5431..9949e04675 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/dataveria.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/dataveria.com.json
@@ -1,8 +1,9 @@
{
- "name": "dataveria.com",
- "version": "0.1.1",
+ "name": "Dataveria",
+ "url": "dataveria.com",
+ "version": "0.1.4",
"parent": "verecor.com",
- "addedDatetime": 1677733200000,
+ "addedDatetime": 1677736800000,
"steps": [
{
"stepType": "scan",
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "fc449310-7b7b-45d4-bcf9-0c5d51c246f8",
+ "id": "a8f3a259-2d39-4ae3-ac13-65aa63a53331",
"url": "https://dataveria.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "0481dc49-43e8-4af0-b697-680fb57ec24b",
+ "id": "e810cc23-2d2a-4e6e-b06f-dfc8a2e1e85d",
"selector": ".search-item",
"profile": {
"name": {
@@ -64,4 +65,4 @@
"confirmOptOutScan": 72,
"maintenanceScan": 240
}
-}
+}
\ No newline at end of file
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/fastbackgroundcheck.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/fastbackgroundcheck.com.json
index 4462e0c86d..9c1129a333 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/fastbackgroundcheck.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/fastbackgroundcheck.com.json
@@ -1,8 +1,9 @@
{
- "name": "fastbackgroundcheck.com",
- "version": "0.1.1",
+ "name": "FastBackgroundCheck.com",
+ "url": "fastbackgroundcheck.com",
+ "version": "0.1.4",
"parent": "peoplefinders.com",
- "addedDatetime": 1678082400000,
+ "addedDatetime": 1706248800000,
"steps": [
{
"stepType": "scan",
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "2a3a5979-9de0-44b2-ae03-f25422f0c2aa",
+ "id": "997adf8d-023c-409e-9206-57871cd25f0a",
"url": "https://www.fastbackgroundcheck.com/people/${firstName}-${lastName}/${city}-${state}"
},
{
"actionType": "extract",
- "id": "4818ff1c-d419-44c2-8168-501b456c6c6a",
+ "id": "2f531e34-2ac0-4743-a760-065187d6c951",
"selector": ".person-container",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/freepeopledirectory.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/freepeopledirectory.com.json
index 2c215abdf6..c448989448 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/freepeopledirectory.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/freepeopledirectory.com.json
@@ -1,6 +1,7 @@
{
- "name": "freepeopledirectory.com",
- "version": "0.1.1",
+ "name": "FreePeopleDirectory",
+ "url": "freepeopledirectory.com",
+ "version": "0.1.4",
"parent": "spokeo.com",
"addedDatetime": 1674540000000,
"steps": [
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "815a1cd3-2577-4f43-a163-0cf4d22e66a4",
+ "id": "4c607417-36bc-47d4-8562-9c2244db354d",
"url": "https://www.freepeopledirectory.com/name/${firstName}-${lastName}/${state|upcase}/${city}"
},
{
"actionType": "extract",
- "id": "10738ba0-bc6b-42ba-a37c-487ff3927dd5",
+ "id": "a1637310-ca7a-40b0-b2f5-db22b43b5d54",
"selector": ".whole-card",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/inforver.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/inforver.com.json
index 961bb83ae3..2c035a980c 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/inforver.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/inforver.com.json
@@ -1,8 +1,9 @@
{
- "name": "inforver.com",
- "version": "0.1.1",
+ "name": "Inforver",
+ "url": "inforver.com",
+ "version": "0.1.4",
"parent": "verecor.com",
- "addedDatetime": 1677733200000,
+ "addedDatetime": 1677736800000,
"steps": [
{
"stepType": "scan",
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "a56ab792-fc1b-4e60-b0b9-0bd4f580476f",
+ "id": "85fac850-36ad-4d9c-ad7c-c1250c7b5585",
"url": "https://inforver.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "591ba784-106c-421b-b188-a376f1f9cb01",
+ "id": "e5e9c1b0-4af4-4fb6-bd2d-7d026ffd95e7",
"selector": ".search-item",
"profile": {
"name": {
@@ -64,4 +65,4 @@
"confirmOptOutScan": 72,
"maintenanceScan": 240
}
-}
+}
\ No newline at end of file
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/kwold.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/kwold.com.json
index 31b5dc20a8..5f7e750909 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/kwold.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/kwold.com.json
@@ -1,6 +1,7 @@
{
- "name": "kwold.com",
- "version": "0.1.1",
+ "name": "Kwold",
+ "url": "kwold.com",
+ "version": "0.1.4",
"parent": "verecor.com",
"addedDatetime": 1702965600000,
"steps": [
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "47152fc1-79d5-4bcc-b930-6b5cdc66e972",
+ "id": "936eee30-d31e-48fb-8cc4-9391869934b9",
"url": "https://kwold.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "50507ab4-2e75-4f1d-af23-9725b9955bc3",
+ "id": "870ee174-275a-4ea8-b2d7-a222418e5de9",
"selector": ".card",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/neighbor.report.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/neighbor.report.json
index d640212852..92a0d2af57 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/neighbor.report.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/neighbor.report.json
@@ -1,7 +1,8 @@
{
- "name": "neighbor.report",
+ "name": "Neighbor Report",
+ "url": "neighbor.report",
"version": "0.1.4",
- "addedDatetime": 1703559600000,
+ "addedDatetime": 1703570400000,
"steps": [
{
"stepType": "scan",
@@ -9,12 +10,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "a554d7d2-f348-487a-97de-8d4f0d1d35c0",
+ "id": "bbaf8a18-fef8-42a6-9682-747b8ff485b2",
"url": "https://neighbor.report/${firstName}-${lastName}/${state|stateFull|hyphenated}/${city|hyphenated}"
},
{
"actionType": "extract",
- "id": "17f80250-1e3c-4e55-8e50-68fe98a6ce23",
+ "id": "0dac4a6d-1291-47c3-97b8-56200f751ac8",
"selector": ".lstd",
"profile": {
"name": {
@@ -50,12 +51,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "59cc488d-e317-4fb4-8aaa-a20cb71f7480",
+ "id": "b1f7f4ab-51b0-4885-ba73-97be0822d0ba",
"url": "https://neighbor.report/remove"
},
{
"actionType": "fillForm",
- "id": "3a4c1775-941a-4f48-873d-c780f5ea25a0",
+ "id": "743afa6c-7dea-4115-934b-bea369307acd",
"selector": ".form-horizontal",
"elements": [
{
@@ -74,17 +75,17 @@
},
{
"actionType": "getCaptchaInfo",
- "id": "f470e245-d5ee-4908-bd6c-16e604a1a29b",
+ "id": "24ce0da0-7cc3-47e7-bf8e-6f5fe98b7a91",
"selector": ".recaptcha-div"
},
{
"actionType": "solveCaptcha",
- "id": "7f0a8fc6-32a3-4f4a-b61d-267f9666de91",
+ "id": "b720de9a-f519-466f-980d-d9c52d8870a2",
"selector": ".recaptcha-div"
},
{
"actionType": "click",
- "id": "7fbc5a97-bc57-41bc-a556-0fdfd8a0845d",
+ "id": "46690938-f112-4091-bd07-b5641e38151f",
"elements": [
{
"type": "button",
@@ -94,7 +95,7 @@
},
{
"actionType": "click",
- "id": "d1513f65-a746-4597-9ed2-4cd5e40dead3",
+ "id": "07cfed17-9d75-471a-b6a0-0522add35ffa",
"elements": [
{
"type": "button",
@@ -108,7 +109,7 @@
},
{
"actionType": "expectation",
- "id": "8acd9c96-443d-4593-a3a7-9efc9fd5070a",
+ "id": "ebd61347-60e1-4c19-bc41-dd1ce36d3138",
"expectations": [
{
"type": "text",
@@ -125,4 +126,4 @@
"confirmOptOutScan": 72,
"maintenanceScan": 240
}
-}
+}
\ No newline at end of file
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/newenglandfacts.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/newenglandfacts.com.json
index ddb83134f9..54f8d23ac8 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/newenglandfacts.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/newenglandfacts.com.json
@@ -1,6 +1,7 @@
{
- "name": "newenglandfacts.com",
- "version": "0.1.1",
+ "name": "New England Facts",
+ "url": "newenglandfacts.com",
+ "version": "0.1.4",
"parent": "verecor.com",
"addedDatetime": 1703052000000,
"steps": [
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "8bd7953c-ee22-49be-8937-a1798046a0c1",
+ "id": "05725a5a-ec3f-49c8-875b-ab9787b9385f",
"url": "https://newenglandfacts.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "4012d312-2f7f-4cc1-bf7a-b7655f550c1a",
+ "id": "7f41b78a-bb65-4bb2-a6ca-1a6ab55890ce",
"selector": ".b-pfl-list",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/officialusa.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/officialusa.com.json
index a7b4efd714..9cb63483be 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/officialusa.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/officialusa.com.json
@@ -1,8 +1,9 @@
{
- "name": "officialusa.com",
- "version": "0.1.0",
+ "name": "OfficialUSA",
+ "url": "officialusa.com",
+ "version": "0.1.4",
"parent": "neighbor.report",
- "addedDatetime": 1692590400000,
+ "addedDatetime": 1692594000000,
"steps": [
{
"stepType": "scan",
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "dad25b4c-743b-4bca-a395-05f1e76ef5c9",
+ "id": "b430e29e-89f0-4994-96b2-08d0cbdc388c",
"url": "https://officialusa.com/names/${firstName}-${lastName}/"
},
{
"actionType": "extract",
- "id": "b867d570-6124-40d9-9076-7ee0fa5b4d68",
+ "id": "d989f3b7-9b8a-44a6-a51e-70762255f3fc",
"selector": ".person",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/people-background-check.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/people-background-check.com.json
index 7b1d26eb38..b8550c93e4 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/people-background-check.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/people-background-check.com.json
@@ -1,6 +1,7 @@
{
- "name": "people-background-check.com",
- "version": "0.1.1",
+ "name": "People Background Check",
+ "url": "people-background-check.com",
+ "version": "0.1.4",
"parent": "verecor.com",
"addedDatetime": 1702965600000,
"steps": [
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "18e35c3b-b837-40e9-b353-20230d36bc4d",
+ "id": "6fee90c5-5f7e-4fd0-badf-069e2b94a65d",
"url": "https://people-background-check.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}"
},
{
"actionType": "extract",
- "id": "7a23b927-acfc-4d29-b4b6-3f204687619c",
+ "id": "ee03ba42-e9a5-4489-a7d6-d50bf21238aa",
"selector": ".b-pfl-list",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/peoplefinders.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/peoplefinders.com.json
index 7e690167c8..34bc5b8770 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/peoplefinders.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/peoplefinders.com.json
@@ -1,7 +1,8 @@
{
- "name": "peoplefinders.com",
- "version": "0.1.0",
- "addedDatetime": 1677128400000,
+ "name": "PeopleFinders",
+ "url": "peoplefinders.com",
+ "version": "0.1.4",
+ "addedDatetime": 1677132000000,
"steps": [
{
"stepType": "scan",
@@ -9,12 +10,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "aafba5bd-a157-4e35-b653-0797a732d94c",
+ "id": "71c7cb2f-14fe-43b8-9623-452b8bd10d4e",
"url": "https://www.peoplefinders.com/people/${firstName}-${lastName}/${state}/${city}?landing=all&age=${age}"
},
{
"actionType": "extract",
- "id": "b8f10f20-3363-4781-a03b-c4958b6269c7",
+ "id": "5c5af912-091f-4f48-922f-ba554951ddd9",
"selector": ".record",
"profile": {
"name": {
@@ -48,12 +49,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "f5fbd4f5-23f7-45ed-a9ce-3e9b0a5a7a0a",
+ "id": "4b065fde-35c7-43d7-aed6-3abcdac94f08",
"url": "https://www.peoplefinders.com/opt-out"
},
{
"actionType": "click",
- "id": "7b33cd1b-3948-4454-8434-e703cc235123",
+ "id": "b5c0929e-e362-4570-815b-0433ef97fddf",
"elements": [
{
"type": "button",
@@ -63,7 +64,7 @@
},
{
"actionType": "fillForm",
- "id": "32056b7a-dc80-4d5d-b9cc-dccd32cb56be",
+ "id": "2fb91804-e5ea-414e-9354-fba98f3c00e1",
"selector": ".opt-out-form",
"elements": [
{
@@ -78,17 +79,17 @@
},
{
"actionType": "getCaptchaInfo",
- "id": "5d5068aa-5c16-4fdc-8f3b-e412ad4eabed",
+ "id": "4b9706ef-dd9b-47d6-b337-12f66a5f9138",
"selector": ".g-recaptcha"
},
{
"actionType": "solveCaptcha",
- "id": "3443e060-8aee-4bd0-ab2c-ea03f8b8f93c",
+ "id": "770019d3-fa88-400a-8480-7cc31d6b3382",
"selector": ".g-recaptcha"
},
{
"actionType": "click",
- "id": "cb9ef5b0-0155-42f1-a766-145b3c14586b",
+ "id": "a7285f44-6c99-44b1-8199-eb6c383fe12b",
"elements": [
{
"type": "button",
@@ -98,22 +99,22 @@
},
{
"actionType": "emailConfirmation",
- "id": "5cc7cfa5-e8ab-4dc1-b58b-973af3d3f364",
+ "id": "05cc08ea-fb80-40fb-8cce-3ca674eea03b",
"pollingTime": 30
},
{
"actionType": "getCaptchaInfo",
- "id": "3a44c15d-1dd0-4e92-beba-bf3d8544c6e9",
+ "id": "8cb4256a-b162-407f-8434-5536c7560c98",
"selector": ".g-recaptcha"
},
{
"actionType": "solveCaptcha",
- "id": "d2566371-8b02-4414-9a24-1f9d2761eb1d",
+ "id": "38c64eec-6bd9-4751-a7cf-8cbe9901b0f6",
"selector": ".g-recaptcha"
},
{
"actionType": "click",
- "id": "259d8895-ac58-46b0-a209-7f209171e13c",
+ "id": "d1d25423-912b-4828-825b-eb83809ada08",
"elements": [
{
"type": "button",
@@ -123,7 +124,7 @@
},
{
"actionType": "expectation",
- "id": "ed02f55b-67b3-4efc-a3cc-ce6b6c7ceeed",
+ "id": "fdb755da-8970-426f-b09e-12165c2169dd",
"expectations": [
{
"type": "url",
@@ -139,4 +140,4 @@
"confirmOptOutScan": 72,
"maintenanceScan": 240
}
-}
+}
\ No newline at end of file
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/peoplesearchnow.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/peoplesearchnow.com.json
index 6e477c6e13..6189f3d311 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/peoplesearchnow.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/peoplesearchnow.com.json
@@ -1,6 +1,7 @@
{
- "name": "peoplesearchnow.com",
- "version": "0.1.1",
+ "name": "People Search Now",
+ "url": "peoplesearchnow.com",
+ "version": "0.1.4",
"parent": "peoplefinders.com",
"addedDatetime": 1705989600000,
"steps": [
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "b6994b26-9904-407b-9bcf-0fd6f809771d",
+ "id": "db9e093d-68c2-45e1-a529-29a2dc67dfab",
"url": "https://peoplesearchnow.com/person/${firstName}-${lastName}_${city}_${state}/"
},
{
"actionType": "extract",
- "id": "4e7f0e9a-1d24-47c0-886f-a08d88074878",
+ "id": "78912133-761b-4971-9780-4e16c8dd43b2",
"selector": ".result-search-block",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/pub360.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/pub360.com.json
index 503392f378..815fdcb8fc 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/pub360.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/pub360.com.json
@@ -1,8 +1,9 @@
{
- "name": "pub360.com",
- "version": "0.1.1",
+ "name": "Pub360",
+ "url": "pub360.com",
+ "version": "0.1.4",
"parent": "verecor.com",
- "addedDatetime": 1677733200000,
+ "addedDatetime": 1677736800000,
"steps": [
{
"stepType": "scan",
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "72fc91c4-e8bc-4656-8260-cd3bb15e2001",
+ "id": "8e2a1251-2685-476a-b4c1-53d138331abe",
"url": "https://pub360.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "2cb4778d-e3d6-4432-8421-84438c280e19",
+ "id": "9ce62e6f-b103-45f6-9f92-56785eb22320",
"selector": ".search-item",
"profile": {
"name": {
@@ -64,4 +65,4 @@
"confirmOptOutScan": 72,
"maintenanceScan": 240
}
-}
+}
\ No newline at end of file
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/publicreports.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/publicreports.com.json
index 991d4d2b2c..5ea3d241e4 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/publicreports.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/publicreports.com.json
@@ -1,6 +1,7 @@
{
- "name": "publicreports.com",
- "version": "0.1.1",
+ "name": "PublicReports",
+ "url": "publicreports.com",
+ "version": "0.1.4",
"parent": "verecor.com",
"addedDatetime": 1703052000000,
"steps": [
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "ae0104a2-a75c-4d97-bada-dda4f21dd446",
+ "id": "b995b1bf-6610-4085-9d07-d38857807535",
"url": "https://publicreports.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "388c55e3-fa12-4376-9a02-01190b8a30fd",
+ "id": "7fb121fb-e2a0-4fa2-9b97-51130104971c",
"selector": ".card",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/quickpeopletrace.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/quickpeopletrace.com.json
index 7409fd240b..e8b18f9ec8 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/quickpeopletrace.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/quickpeopletrace.com.json
@@ -1,6 +1,7 @@
{
- "name": "quickpeopletrace.com",
- "version": "0.1.1",
+ "name": "Quick People Trace",
+ "url": "quickpeopletrace.com",
+ "version": "0.1.4",
"parent": "peoplefinders.com",
"addedDatetime": 1674540000000,
"steps": [
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "45443ab2-7563-4c7d-8bf2-b1b550f4b825",
+ "id": "2db8c120-a8c3-4aa0-a9ce-b075ca85fc68",
"url": "https://www.quickpeopletrace.com/search/?addresssearch=1&tabid=1&teaser-firstname=${firstName}&teaser-middlename=&teaser-lastname=${lastName}&teaser-city=${city}&teaser-state=${state|upcase}&teaser-submitted=Search"
},
{
"actionType": "extract",
- "id": "08607047-96e8-4fbb-9af9-bf7b8e163b20",
+ "id": "bd48b737-89c4-408a-a28c-2dfa828aebd8",
"selector": "//table/tbody/tr[position() > 1]",
"profile": {
"name": {
@@ -24,9 +25,6 @@
"age": {
"selector": ".//td[3]"
},
- "addressCityState": {
- "selector": ".//td[4]/strong"
- },
"addressCityStateList": {
"selector": ".//td[4]"
},
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/searchpeoplefree.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/searchpeoplefree.com.json
index 535ec0f63d..4a68c912e3 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/searchpeoplefree.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/searchpeoplefree.com.json
@@ -1,6 +1,7 @@
{
- "name": "searchpeoplefree.com",
- "version": "0.1.1",
+ "name": "Search People FREE",
+ "url": "searchpeoplefree.com",
+ "version": "0.1.4",
"parent": "peoplefinders.com",
"addedDatetime": 1703052000000,
"steps": [
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "2b537ff2-8967-465c-ad5c-c4c2d31f60e1",
+ "id": "f5bad072-6f55-4357-b23b-1df4c9584e67",
"url": "https://searchpeoplefree.com/find/${firstName}-${lastName}/${state}/${city}"
},
{
"actionType": "extract",
- "id": "70728718-fe02-43f6-b86f-6d6c6bbbf009",
+ "id": "749fb8fe-9994-41e2-a0ea-ae6334c5aee0",
"selector": "//li[@class='toc l-i mb-5']",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/smartbackgroundchecks.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/smartbackgroundchecks.com.json
index 31424db72d..23f588c796 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/smartbackgroundchecks.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/smartbackgroundchecks.com.json
@@ -1,6 +1,7 @@
{
- "name": "smartbackgroundchecks.com",
- "version": "0.1.1",
+ "name": "SmartBackgroundChecks",
+ "url": "smartbackgroundchecks.com",
+ "version": "0.1.4",
"parent": "peoplefinders.com",
"addedDatetime": 1678082400000,
"steps": [
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "97b307c8-e3e4-4090-a6ab-c5eeb599d248",
+ "id": "1c6bdc6e-12dd-47db-b5b0-13055c1f3d5d",
"url": "https://www.smartbackgroundchecks.com/people/${firstName}-${lastName}/${city}/${state}"
},
{
"actionType": "extract",
- "id": "ca20a933-b703-427e-8cbf-e2f25cd763a6",
+ "id": "ac554b4f-e4a0-44c5-81a6-c04e46e4ce3b",
"selector": ".card-block",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/spokeo.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/spokeo.com.json
index 2618099829..3c0f0008f8 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/spokeo.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/spokeo.com.json
@@ -1,12 +1,33 @@
{
- "name": "spokeo.com",
- "version": "0.1.3",
+ "name": "Spokeo",
+ "url": "spokeo.com",
+ "version": "0.1.4",
"addedDatetime": 1692594000000,
"mirrorSites": [
- { "name": "callersmart.com", "addedAt": 1705599286529, "removedAt": null },
- { "name": "selfie.network", "addedAt": 1705599286529, "removedAt": null },
- { "name": "selfie.systems", "addedAt": 1705599286529, "removedAt": null },
- { "name": "peoplewin.com", "addedAt": 1705599286529, "removedAt": null }
+ {
+ "name": "CallerSmart",
+ "url": "callersmart.com",
+ "addedAt": 1705599286529,
+ "removedAt": null
+ },
+ {
+ "name": "Selfie Network",
+ "url": "selfie.network",
+ "addedAt": 1705599286529,
+ "removedAt": null
+ },
+ {
+ "name": "Selfie Systems",
+ "url": "selfie.systems",
+ "addedAt": 1705599286529,
+ "removedAt": null
+ },
+ {
+ "name": "PeopleWin",
+ "url": "peoplewin.com",
+ "addedAt": 1705599286529,
+ "removedAt": null
+ }
],
"steps": [
{
@@ -15,12 +36,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "d3174bd8-3253-45e3-88f0-1366882a2df7",
+ "id": "9b617d27-b330-46fc-bdb0-6239c0873897",
"url": "https://www.spokeo.com/${firstName}-${lastName}/${state|stateFull}/${city}"
},
{
"actionType": "extract",
- "id": "e47f5f27-dfbf-4f2c-8d7a-43f581abdaa2",
+ "id": "4f7124c2-bd8c-4649-84f2-04f0962225b5",
"selector": ".single-column-list-item",
"profile": {
"name": {
@@ -52,12 +73,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "dba8f444-a433-4ad2-9819-3c555bfedd9c",
+ "id": "df75e4fb-f14b-4b65-afe2-82e03b71c6a9",
"url": "https://www.spokeo.com/optout"
},
{
"actionType": "fillForm",
- "id": "b1145fca-3e35-4ee9-86f2-e35c393846d3",
+ "id": "42cbfc2b-d96b-4bd6-8d16-0542a672d869",
"selector": ".optout_container",
"elements": [
{
@@ -72,17 +93,17 @@
},
{
"actionType": "getCaptchaInfo",
- "id": "8f9608b4-bbf7-4540-8b22-5c381225cd02",
+ "id": "e1581b9e-7460-4bbd-a010-634c2db12ca1",
"selector": "#g-recaptcha"
},
{
"actionType": "solveCaptcha",
- "id": "a5a884b8-12f6-4029-aa80-244f1a163f67",
+ "id": "01ca39d9-e842-41cf-b0f9-a7d517bc0dd6",
"selector": "#g-recaptcha"
},
{
"actionType": "click",
- "id": "a7d4fdd4-30b8-46f7-8700-67c633da1f91",
+ "id": "7556edd5-570b-4c4a-acc7-f1066138d513",
"elements": [
{
"type": "button",
@@ -92,7 +113,7 @@
},
{
"actionType": "expectation",
- "id": "d4a804a3-de62-4f66-a56e-e9d7e65fb8bb",
+ "id": "f7b5125e-0dda-4a14-8943-8c20c09125bc",
"expectations": [
{
"type": "text",
@@ -103,12 +124,12 @@
},
{
"actionType": "emailConfirmation",
- "id": "5138062c-99d3-4523-b222-8123b13bc524",
+ "id": "dbd875b6-bdc7-48ca-962b-885941e6284a",
"pollingTime": 30
},
{
"actionType": "expectation",
- "id": "cc14f3ea-35f8-4d31-a280-dc97526de12a",
+ "id": "b2f1c371-d779-4b3b-8516-0d13169cf873",
"expectations": [
{
"type": "text",
@@ -125,4 +146,4 @@
"confirmOptOutScan": 72,
"maintenanceScan": 240
}
-}
+}
\ No newline at end of file
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/truepeoplesearch.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/truepeoplesearch.com.json
index 84d468f943..a226c959a0 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/truepeoplesearch.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/truepeoplesearch.com.json
@@ -1,6 +1,7 @@
{
- "name": "truepeoplesearch.com",
- "version": "0.1.1",
+ "name": "TruePeopleSearch",
+ "url": "truepeoplesearch.com",
+ "version": "0.1.4",
"parent": "peoplefinders.com",
"addedDatetime": 1703138400000,
"steps": [
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "4b7d8751-d3cd-4e4b-b2a2-66219eb6a8e8",
+ "id": "12eb70c1-53d5-4881-9dce-74ed4fada583",
"url": "https://www.truepeoplesearch.com/results?name=${firstName}%20${lastName}&citystatezip=${city|capitalize},${state|upcase}"
},
{
"actionType": "extract",
- "id": "cdb5940a-8505-4b28-9699-d98235e1fff1",
+ "id": "881e0e21-c375-4083-a9be-86f82063849b",
"selector": ".card-summary",
"profile": {
"name": {
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 71c8e711ed..b8fba84277 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,8 +1,9 @@
{
- "name": "usa-people-search.com",
- "version": "0.1.1",
+ "name": "USA People Search",
+ "url": "usa-people-search.com",
+ "version": "0.1.4",
"parent": "peoplefinders.com",
- "addedDatetime": 1678078800000,
+ "addedDatetime": 1678082400000,
"steps": [
{
"stepType": "scan",
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "2c4b31a3-661b-4f30-a4d3-b5a4a13c95db",
+ "id": "67e80e69-f542-4714-8705-c43af630ac72",
"url": "https://usa-people-search.com/name/${firstName|downcase}-${lastName|downcase}/${city|downcase}-${state|stateFull|downcase}?age=${age}"
},
{
"actionType": "extract",
- "id": "20a4d510-56b6-46a8-92ce-be16ed3ce049",
+ "id": "c0a82b15-7564-4e12-8c4e-084174242623",
"selector": ".card-block",
"profile": {
"name": {
@@ -62,4 +63,4 @@
"confirmOptOutScan": 72,
"maintenanceScan": 240
}
-}
+}
\ No newline at end of file
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usatrace.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usatrace.com.json
index 412a14d7d9..4645c85dd0 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usatrace.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usatrace.com.json
@@ -1,6 +1,7 @@
{
- "name": "usatrace.com",
- "version": "0.1.1",
+ "name": "USA Trace",
+ "url": "usatrace.com",
+ "version": "0.1.4",
"parent": "peoplefinders.com",
"addedDatetime": 1674540000000,
"steps": [
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "5071e480-88ae-49b5-91b6-1daf26c55acf",
+ "id": "17217b04-28ae-4262-aa33-ee3695bb6bd6",
"url": "https://www.usatrace.com/people-search/${firstName}-${lastName}/${city}-${state|upcase}"
},
{
"actionType": "extract",
- "id": "3237fc09-247c-4942-9920-9bbb937f6ac2",
+ "id": "426d8e8a-2f32-46f3-9d1d-e7f6e2fddadb",
"selector": "//table/tbody/tr[position() > 1]",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usphonebook.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usphonebook.com.json
index 0770b2f474..5aff073d4a 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usphonebook.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/usphonebook.com.json
@@ -1,8 +1,9 @@
{
- "name": "usphonebook.com",
- "version": "0.1.1",
+ "name": "USPhoneBook",
+ "url": "usphonebook.com",
+ "version": "0.1.4",
"parent": "peoplefinders.com",
- "addedDatetime": 1678078800000,
+ "addedDatetime": 1678082400000,
"steps": [
{
"stepType": "scan",
@@ -10,12 +11,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "f214150b-4f02-46e1-b7ea-81f6bb1bf097",
+ "id": "6ee93554-95da-4a36-a7f7-c059d8f53ca3",
"url": "https://www.usphonebook.com/${firstName}-${lastName}/${state|stateFull}/${city}"
},
{
"actionType": "extract",
- "id": "af98bb63-b885-4f47-bb47-5f9ec5b491a4",
+ "id": "fffae12f-4ca1-4a8f-81b9-00adf0487129",
"selector": ".ls_contacts-people-finder-wrapper",
"profile": {
"name": {
@@ -56,4 +57,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 f493fbd347..5aff5bd46e 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/verecor.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/verecor.com.json
@@ -1,7 +1,8 @@
{
- "name": "verecor.com",
- "version": "0.1.2",
- "addedDatetime": 1677128400000,
+ "name": "Verecor",
+ "url": "verecor.com",
+ "version": "0.1.4",
+ "addedDatetime": 1677132000000,
"steps": [
{
"stepType": "scan",
@@ -9,7 +10,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "6f53d146-af6a-4bce-970d-f1dcbc496037",
+ "id": "37fc63a6-e434-4ba0-9e9e-d80898e4dfa4",
"url": "https://verecor.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}",
"ageRange": [
"18-30",
@@ -23,7 +24,7 @@
},
{
"actionType": "extract",
- "id": "e8c09200-030c-492a-8e54-22bc6bdb6829",
+ "id": "a955924c-7959-48c8-9511-3f843baed729",
"selector": ".search-item",
"profile": {
"name": {
@@ -58,12 +59,12 @@
"actions": [
{
"actionType": "navigate",
- "id": "f4bbe480-a6ff-40a5-aa25-8ff9ac40c9bf",
+ "id": "85cd9682-94d8-46ac-9999-e03dfa9f8d4e",
"url": "https://verecor.com/ng/control/privacy"
},
{
"actionType": "fillForm",
- "id": "1e6302e1-daf9-49d6-951f-506ea5e266a0",
+ "id": "ed45c76b-e537-4072-9f46-9515c6e215be",
"selector": ".ahm",
"elements": [
{
@@ -82,17 +83,17 @@
},
{
"actionType": "getCaptchaInfo",
- "id": "6a3dc470-3bf7-4b8b-bb44-f77ef1a2c540",
+ "id": "0e1474f0-24fe-4f6a-8d2e-2dfd91cf574b",
"selector": ".g-recaptcha"
},
{
"actionType": "solveCaptcha",
- "id": "83157244-c5bf-44a9-979c-679e1404d67d",
+ "id": "52a858f5-7dc5-40aa-aaa7-7090e06ea55e",
"selector": ".g-recaptcha"
},
{
"actionType": "click",
- "id": "15c50d7f-0e72-4509-be2b-40cde34b48e6",
+ "id": "759e0dd2-3a93-42a8-9a83-5e3408f5566b",
"elements": [
{
"type": "button",
@@ -102,7 +103,7 @@
},
{
"actionType": "expectation",
- "id": "2ed336a2-a7a9-4cbd-933c-cd463df4f553",
+ "id": "089924be-5ea3-48a9-a325-8976d262f39b",
"expectations": [
{
"type": "text",
@@ -113,12 +114,12 @@
},
{
"actionType": "emailConfirmation",
- "id": "88c09081-e848-4e75-a7b9-3ee28e95a459",
+ "id": "8094718e-412a-418f-b74d-cd4fc5e42c56",
"pollingTime": 30
},
{
"actionType": "expectation",
- "id": "dd03cf9f-8227-4881-86bf-09ce158bf151",
+ "id": "af8fb89b-88d2-4901-b90c-eaac3c7566db",
"expectations": [
{
"type": "text",
@@ -135,4 +136,4 @@
"confirmOptOutScan": 72,
"maintenanceScan": 240
}
-}
+}
\ No newline at end of file
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/vericora.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/vericora.com.json
index 814e908fae..c7a3bad5c2 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/vericora.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/vericora.com.json
@@ -1,8 +1,9 @@
{
- "name": "vericora.com",
- "version": "0.1.1",
+ "name": "Vericora",
+ "url": "vericora.com",
+ "version": "0.1.4",
"parent": "verecor.com",
- "addedDatetime": 1677733200000,
+ "addedDatetime": 1677736800000,
"steps": [
{
"stepType": "scan",
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "9488e141-d109-4cbf-bc65-1b9036728ff4",
+ "id": "69175f1a-0024-4efd-ab3e-67bcf915a770",
"url": "https://vericora.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "baaecb74-8d63-496c-a3e0-a8acbdee2c99",
+ "id": "bd941009-4462-4d59-ba44-46250f580531",
"selector": ".search-item",
"profile": {
"name": {
@@ -64,4 +65,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 121bc68d3e..5f4f307f92 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/veriforia.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/veriforia.com.json
@@ -1,8 +1,9 @@
{
- "name": "veriforia.com",
- "version": "0.1.1",
+ "name": "Veriforia",
+ "url": "veriforia.com",
+ "version": "0.1.4",
"parent": "verecor.com",
- "addedDatetime": 1677733200000,
+ "addedDatetime": 1677736800000,
"steps": [
{
"stepType": "scan",
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "ffb30e97-b03f-4157-a511-09ad8ffb8b54",
+ "id": "17442975-944c-4b01-8518-7f1dff171ad2",
"url": "https://veriforia.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "1d8d9c20-9897-4386-8bc1-bd591abe7c81",
+ "id": "32e963e1-4959-4e5e-981b-550f1bf36f9a",
"selector": ".search-item",
"profile": {
"name": {
@@ -64,4 +65,4 @@
"confirmOptOutScan": 72,
"maintenanceScan": 240
}
-}
+}
\ No newline at end of file
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/veripages.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/veripages.com.json
index 43d95caf8a..61becad701 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/veripages.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/veripages.com.json
@@ -1,8 +1,9 @@
{
- "name": "veripages.com",
- "version": "0.1.2",
+ "name": "Veripages",
+ "url": "veripages.com",
+ "version": "0.1.4",
"parent": "verecor.com",
- "addedDatetime": 1691982000000,
+ "addedDatetime": 1691989200000,
"steps": [
{
"stepType": "scan",
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "5bf98772-1804-4939-a06b-dbf9cd31f198",
+ "id": "2346b569-1c46-4ef9-8ea0-fa18bea967fa",
"url": "https://veripages.com/inner/profile/search?fname=${firstName}&lname=${lastName}&fage=${age|ageRange}&state=${state}&city=${city}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "f3d53642-9a58-4275-b97f-5547e3ef8e55",
+ "id": "c4281ca8-d4d0-4091-b6c2-3094801e99c0",
"selector": ".search-item",
"profile": {
"name": {
@@ -66,4 +67,4 @@
"confirmOptOutScan": 72,
"maintenanceScan": 240
}
-}
+}
\ No newline at end of file
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/virtory.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/virtory.com.json
index d12865681f..3d94019338 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/virtory.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/virtory.com.json
@@ -1,6 +1,7 @@
{
- "name": "virtory.com",
- "version": "0.1.1",
+ "name": "Virtory",
+ "url": "virtory.com",
+ "version": "0.1.4",
"parent": "verecor.com",
"addedDatetime": 1703052000000,
"steps": [
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "99465bd6-ce87-4fc6-96a2-eea8137e4a30",
+ "id": "0568e4f5-73c2-4b1a-9eb6-ac3571b1a01e",
"url": "https://virtory.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "8d66ac98-f788-4fbd-acec-56034682b4b1",
+ "id": "df2216f3-0890-4d13-b2aa-233084167720",
"selector": ".card",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/wellnut.com.json b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/wellnut.com.json
index b4fd3669ec..12c43b7fa4 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/wellnut.com.json
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Resources/JSON/wellnut.com.json
@@ -1,6 +1,7 @@
{
- "name": "wellnut.com",
- "version": "0.1.1",
+ "name": "Wellnut",
+ "url": "wellnut.com",
+ "version": "0.1.4",
"parent": "verecor.com",
"addedDatetime": 1703052000000,
"steps": [
@@ -10,7 +11,7 @@
"actions": [
{
"actionType": "navigate",
- "id": "b9db3c1e-ece6-45d1-94ec-1143da9607aa",
+ "id": "a38752f3-ae69-45c3-ba3f-3a73e549e644",
"url": "https://wellnut.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${age|ageRange}",
"ageRange": [
"18-30",
@@ -24,7 +25,7 @@
},
{
"actionType": "extract",
- "id": "e4b7c983-c96e-4ce8-8703-3cb319454db7",
+ "id": "b7747e92-5fe5-46f7-b083-5df6fbdc2b84",
"selector": ".card",
"profile": {
"name": {
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift
index 0e6b930d71..c61178c0f9 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift
@@ -106,7 +106,6 @@ final class DataBrokerProtectionProcessor {
completion: @escaping () -> Void) {
// Before running new operations we check if there is any updates to the broker files.
- // This runs only once per 24 hours.
if let vault = try? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) {
let brokerUpdater = DataBrokerProtectionBrokerUpdater(vault: vault)
brokerUpdater.checkForUpdatesInBrokerJSONFiles()
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/EmailService.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/EmailService.swift
index 75b80b195a..038c72c2d8 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/EmailService.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Services/EmailService.swift
@@ -30,7 +30,7 @@ public enum EmailError: Error, Equatable, Codable {
}
protocol EmailServiceProtocol {
- func getEmail(dataBrokerName: String?) async throws -> String
+ func getEmail(dataBrokerURL: String?) async throws -> String
func getConfirmationLink(from email: String,
numberOfRetries: Int,
pollingIntervalInSeconds: Int,
@@ -51,10 +51,10 @@ struct EmailService: EmailServiceProtocol {
self.redeemUseCase = redeemUseCase
}
- func getEmail(dataBrokerName: String? = nil) async throws -> String {
+ func getEmail(dataBrokerURL: String? = nil) async throws -> String {
var urlString = Constants.baseUrl + "/generate"
- if let dataBrokerValue = dataBrokerName {
+ if let dataBrokerValue = dataBrokerURL {
urlString += "?dataBroker=\(dataBrokerValue)"
}
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/DataBrokerProtectionDatabaseProvider.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/DataBrokerProtectionDatabaseProvider.swift
index ab59f166cc..a70ad31818 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/DataBrokerProtectionDatabaseProvider.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/DataBrokerProtectionDatabaseProvider.swift
@@ -34,7 +34,7 @@ protocol DataBrokerProtectionDatabaseProvider: SecureStorageDatabaseProvider {
func save(_ broker: BrokerDB) throws -> Int64
func update(_ broker: BrokerDB) throws
func fetchBroker(with id: Int64) throws -> BrokerDB?
- func fetchBroker(with name: String) throws -> BrokerDB?
+ func fetchBroker(with url: String) throws -> BrokerDB?
func fetchAllBrokers() throws -> [BrokerDB]
func save(_ profileQuery: ProfileQueryDB) throws -> Int64
@@ -85,6 +85,8 @@ final class DefaultDataBrokerProtectionDatabaseProvider: GRDBSecureStorageDataba
public init(file: URL = DefaultDataBrokerProtectionDatabaseProvider.defaultDatabaseURL(), key: Data) throws {
try super.init(file: file, key: key, writerType: .pool) { migrator in
migrator.registerMigration("v1", migrate: Self.migrateV1(database:))
+ migrator.registerMigration("v2", migrate: Self.migrateV2(database:))
+
}
}
@@ -259,6 +261,16 @@ final class DefaultDataBrokerProtectionDatabaseProvider: GRDBSecureStorageDataba
$0.column(OptOutAttemptDB.Columns.startDate.name, .date).notNull()
}
}
+
+ static func migrateV2(database: Database) throws {
+ try database.alter(table: BrokerDB.databaseTableName) {
+ $0.add(column: BrokerDB.Columns.url.name, .text)
+ }
+ try database.execute(sql: """
+ UPDATE \(BrokerDB.databaseTableName) SET \(BrokerDB.Columns.url.name) = \(BrokerDB.Columns.name.name)
+ """)
+ }
+
// swiftlint:enable function_body_length
func updateProfile(profile: DataBrokerProtectionProfile, mapperToDB: MapperToDB) throws -> Int64 {
@@ -359,10 +371,10 @@ final class DefaultDataBrokerProtectionDatabaseProvider: GRDBSecureStorageDataba
}
}
- func fetchBroker(with name: String) throws -> BrokerDB? {
+ func fetchBroker(with url: String) throws -> BrokerDB? {
try db.read { db in
return try BrokerDB
- .filter(Column(BrokerDB.Columns.name.name) == name)
+ .filter(Column(BrokerDB.Columns.url.name) == url)
.fetchOne(db)
}
}
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/Mappers.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/Mappers.swift
index 267b013ed8..56b08dd5f2 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/Mappers.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/Mappers.swift
@@ -58,7 +58,7 @@ struct MapperToDB {
func mapToDB(_ broker: DataBroker, id: Int64? = nil) throws -> BrokerDB {
let encodedBroker = try jsonEncoder.encode(broker)
- return .init(id: id, name: broker.name, json: encodedBroker, version: broker.version)
+ return .init(id: id, name: broker.name, json: encodedBroker, version: broker.version, url: broker.url)
}
func mapToDB(_ profileQuery: ProfileQuery, relatedTo profileId: Int64) throws -> ProfileQueryDB {
@@ -171,6 +171,7 @@ struct MapperToModel {
return DataBroker(
id: brokerDB.id,
name: decodedBroker.name,
+ url: decodedBroker.url,
steps: decodedBroker.steps,
version: decodedBroker.version,
schedulingConfig: decodedBroker.schedulingConfig,
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/SchedulerSchema.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/SchedulerSchema.swift
index 348d99180d..a772e4d543 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/SchedulerSchema.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Storage/SchedulerSchema.swift
@@ -94,6 +94,7 @@ struct BrokerDB: Codable {
let name: String
let json: Data
let version: String
+ let url: String
}
extension BrokerDB: PersistableRecord, FetchableRecord {
@@ -104,6 +105,7 @@ extension BrokerDB: PersistableRecord, FetchableRecord {
case name
case json
case version
+ case url
}
init(row: Row) throws {
@@ -111,6 +113,7 @@ extension BrokerDB: PersistableRecord, FetchableRecord {
name = row[Columns.name]
json = row[Columns.json]
version = row[Columns.version]
+ url = row[Columns.url]
}
func encode(to container: inout PersistenceContainer) throws {
@@ -118,6 +121,7 @@ extension BrokerDB: PersistableRecord, FetchableRecord {
container[Columns.name] = name
container[Columns.json] = json
container[Columns.version] = version
+ container[Columns.url] = url
}
}
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift
index 63b1b4a47e..9f77b8a675 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift
@@ -36,6 +36,7 @@ protocol DBPUICommunicationDelegate: AnyObject {
func startScanAndOptOut() -> Bool
func getInitialScanState() async -> DBPUIInitialScanState
func getMaintananceScanState() async -> DBPUIScanAndOptOutMaintenanceState
+ func getDataBrokers() async -> [DBPUIDataBroker]
}
enum DBPUIReceivedMethodName: String {
@@ -53,6 +54,7 @@ enum DBPUIReceivedMethodName: String {
case startScanAndOptOut
case initialScanStatus
case maintenanceScanStatus
+ case getDataBrokers
}
enum DBPUISendableMethodName: String {
@@ -69,7 +71,7 @@ struct DBPUICommunicationLayer: Subfeature {
weak var delegate: DBPUICommunicationDelegate?
private enum Constants {
- static let version = 1
+ static let version = 2
}
internal init(webURLSettings: DataBrokerProtectionWebUIURLSettingsRepresentable) {
@@ -101,6 +103,7 @@ struct DBPUICommunicationLayer: Subfeature {
case .startScanAndOptOut: return startScanAndOptOut
case .initialScanStatus: return initialScanStatus
case .maintenanceScanStatus: return maintenanceScanStatus
+ case .getDataBrokers: return getDataBrokers
}
}
@@ -264,6 +267,11 @@ struct DBPUICommunicationLayer: Subfeature {
return maintenanceScanStatus
}
+ func getDataBrokers(params: Any, origin: WKScriptMessage) async throws -> Encodable? {
+ let dataBrokers = await delegate?.getDataBrokers() ?? [DBPUIDataBroker]()
+ return DBPUIDataBrokerList(dataBrokers: dataBrokers)
+ }
+
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/UIMapper.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift
index 836bd52a30..454f1c75a9 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/UIMapper.swift
@@ -26,22 +26,24 @@ struct MapperToUI {
name: extractedProfile.fullName ?? "No name",
addresses: extractedProfile.addresses?.map(mapToUI) ?? [],
alternativeNames: extractedProfile.alternativeNames ?? [String](),
- relatives: extractedProfile.relatives ?? [String]()
+ relatives: extractedProfile.relatives ?? [String](),
+ date: extractedProfile.removedDate?.timeIntervalSince1970
)
}
- func mapToUI(_ dataBrokerName: String, extractedProfile: ExtractedProfile) -> DBPUIDataBrokerProfileMatch {
+ func mapToUI(_ dataBrokerName: String, databrokerURL: String, extractedProfile: ExtractedProfile) -> DBPUIDataBrokerProfileMatch {
DBPUIDataBrokerProfileMatch(
- dataBroker: DBPUIDataBroker(name: dataBrokerName),
+ dataBroker: DBPUIDataBroker(name: dataBrokerName, url: databrokerURL),
name: extractedProfile.fullName ?? "No name",
addresses: extractedProfile.addresses?.map(mapToUI) ?? [],
alternativeNames: extractedProfile.alternativeNames ?? [String](),
- relatives: extractedProfile.relatives ?? [String]()
+ relatives: extractedProfile.relatives ?? [String](),
+ date: extractedProfile.removedDate?.timeIntervalSince1970
)
}
func mapToUI(_ dataBroker: DataBroker) -> DBPUIDataBroker {
- DBPUIDataBroker(name: dataBroker.name)
+ DBPUIDataBroker(name: dataBroker.name, url: dataBroker.url)
}
func mapToUI(_ address: AddressCityState) -> DBPUIUserProfileAddress {
@@ -75,7 +77,7 @@ struct MapperToUI {
if !$0.dataBroker.mirrorSites.isEmpty {
let mirrorSitesMatches = $0.dataBroker.mirrorSites.compactMap { mirrorSite in
if mirrorSite.shouldWeIncludeMirrorSite() {
- return mapToUI(mirrorSite.name, extractedProfile: extractedProfile)
+ return mapToUI(mirrorSite.name, databrokerURL: mirrorSite.url, extractedProfile: extractedProfile)
}
return nil
@@ -110,7 +112,7 @@ struct MapperToUI {
if let closestMatchesFoundEvent = scanOperation.closestMatchesFoundEvent() {
for mirrorSite in dataBroker.mirrorSites where mirrorSite.shouldWeIncludeMirrorSite(for: closestMatchesFoundEvent.date) {
- let mirrorSiteMatch = mapToUI(mirrorSite.name, extractedProfile: extractedProfile)
+ let mirrorSiteMatch = mapToUI(mirrorSite.name, databrokerURL: mirrorSite.url, extractedProfile: extractedProfile)
if let extractedProfileRemovedDate = extractedProfile.removedDate,
mirrorSite.shouldWeIncludeMirrorSite(for: extractedProfileRemovedDate) {
@@ -124,11 +126,21 @@ struct MapperToUI {
}
let completedOptOutsDictionary = Dictionary(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)
+ let completedOptOuts: [DBPUIOptOutMatch] = completedOptOutsDictionary.compactMap { (key: DBPUIDataBroker, value: [DBPUIDataBrokerProfileMatch]) in
+ value.compactMap { match in
+ guard let removedDate = match.date else { return nil }
+ return DBPUIOptOutMatch(dataBroker: key,
+ matches: value.count,
+ name: match.name,
+ alternativeNames: match.alternativeNames,
+ addresses: match.addresses,
+ date: removedDate)
+ }
+ }.flatMap { $0 }
+
+ let nearestScanByBrokerURL = nearestRunDates(for: brokerProfileQueryData)
+ let lastScans = getLastScanInformation(brokerProfileQueryData: brokerProfileQueryData, nearestScanOperationByBroker: nearestScanByBrokerURL)
+ let nextScans = getNextScansInformation(brokerProfileQueryData: brokerProfileQueryData, nearestScanOperationByBroker: nearestScanByBrokerURL)
return DBPUIScanAndOptOutMaintenanceState(
inProgressOptOuts: inProgressOptOuts,
@@ -140,7 +152,8 @@ struct MapperToUI {
private func getLastScanInformation(brokerProfileQueryData: [BrokerProfileQueryData],
currentDate: Date = Date(),
- format: String = "dd/MM/yyyy") -> DBUIScanDate {
+ format: String = "dd/MM/yyyy",
+ nearestScanOperationByBroker: [String: Date]) -> DBUIScanDate {
let scansGroupedByLastRunDate = Dictionary(grouping: brokerProfileQueryData, by: { $0.scanOperationData.lastRunDate?.toFormat(format) })
let closestScansBeforeToday = scansGroupedByLastRunDate
.filter { $0.key != nil && $0.key!.toDate(using: format) < currentDate }
@@ -148,12 +161,13 @@ struct MapperToUI {
.flatMap { [$0.key?.toDate(using: format): $0.value] }
.last
- return scanDate(element: closestScansBeforeToday)
+ return scanDate(element: closestScansBeforeToday, nearestScanOperationByBroker: nearestScanOperationByBroker)
}
private func getNextScansInformation(brokerProfileQueryData: [BrokerProfileQueryData],
currentDate: Date = Date(),
- format: String = "dd/MM/yyyy") -> DBUIScanDate {
+ format: String = "dd/MM/yyyy",
+ nearestScanOperationByBroker: [String: Date]) -> DBUIScanDate {
let scansGroupedByPreferredRunDate = Dictionary(grouping: brokerProfileQueryData, by: { $0.scanOperationData.preferredRunDate?.toFormat(format) })
let closestScansAfterToday = scansGroupedByPreferredRunDate
.filter { $0.key != nil && $0.key!.toDate(using: format) > currentDate }
@@ -161,22 +175,50 @@ struct MapperToUI {
.flatMap { [$0.key?.toDate(using: format): $0.value] }
.first
- return scanDate(element: closestScansAfterToday)
+ return scanDate(element: closestScansAfterToday, nearestScanOperationByBroker: nearestScanOperationByBroker)
+ }
+
+ // A dictionary containing the closest scan by broker
+ private func nearestRunDates(for brokerData: [BrokerProfileQueryData]) -> [String: Date] {
+ let today = Date()
+ let nearestDates = brokerData.reduce(into: [String: Date]()) { result, data in
+ let url = data.dataBroker.url
+ if let operationDate = data.scanOperationData.preferredRunDate {
+ if operationDate > today {
+ if let existingDate = result[url] {
+ if operationDate < existingDate {
+ result[url] = operationDate
+ }
+ } else {
+ result[url] = operationDate
+ }
+ }
+ }
+ }
+ return nearestDates
}
- private func scanDate(element: Dictionary.Element?) -> DBUIScanDate {
+ private func scanDate(element: Dictionary.Element?,
+ nearestScanOperationByBroker: [String: Date]) -> DBUIScanDate {
if let element = element, let date = element.key {
return DBUIScanDate(
date: date.timeIntervalSince1970,
dataBrokers: element.value.flatMap {
- var brokers = [DBPUIDataBroker(name: $0.dataBroker.name)]
+ let brokerOperationDate = nearestScanOperationByBroker[$0.dataBroker.url]
+ var brokers = [DBPUIDataBroker(name: $0.dataBroker.name, url: $0.dataBroker.url, date: brokerOperationDate?.timeIntervalSince1970 ?? nil)]
for mirrorSite in $0.dataBroker.mirrorSites where mirrorSite.shouldWeIncludeMirrorSite(for: date) {
- brokers.append(DBPUIDataBroker(name: mirrorSite.name))
+ brokers.append(DBPUIDataBroker(name: mirrorSite.name, url: mirrorSite.url, date: brokerOperationDate?.timeIntervalSince1970 ?? nil))
}
return brokers
}
+ .reduce(into: []) { result, dataBroker in // Remove dupes
+ guard !result.contains(where: { $0.url == dataBroker.url }) else {
+ return
+ }
+ result.append(dataBroker)
+ }
)
} else {
return DBUIScanDate(date: 0, dataBrokers: [DBPUIDataBroker]())
diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/BrokerJSONCodableTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/BrokerJSONCodableTests.swift
index 2a6108f826..a604a0a4d7 100644
--- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/BrokerJSONCodableTests.swift
+++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/BrokerJSONCodableTests.swift
@@ -20,11 +20,174 @@ import XCTest
@testable import DataBrokerProtection
final class BrokerJSONCodableTests: XCTestCase {
- let verecorJSONString = """
+ let verecorWithURLJSONString = """
+ {
+ "name": "Verecor",
+ "url": "verecor.com",
+ "version": "0.1.0",
+ "addedDatetime": 1677128400000,
+ "mirrorSites": [
+ {
+ "name": "Potato",
+ "url": "potato.com",
+ "addedAt": 1705599286529,
+ "removedAt": null
+ },
+ {
+ "name": "Tomato",
+ "url": "tomato.com",
+ "addedAt": 1705599286529,
+ "removedAt": null
+ }
+ ],
+ "steps": [
+ {
+ "stepType": "scan",
+ "scanType": "templatedUrl",
+ "actions": [
+ {
+ "actionType": "navigate",
+ "id": "84aa05bc-1ca0-4f16-ae74-dfb352ce0eee",
+ "url": "https://verecor.com/profile/search?fname=${firstName}&lname=${lastName}&state=${state}&city=${city}&fage=${ageRange}",
+ "ageRange": [
+ "18-30",
+ "31-40",
+ "41-50",
+ "51-60",
+ "61-70",
+ "71-80",
+ "81+"
+ ]
+ },
+ {
+ "actionType": "extract",
+ "id": "92252eb5-ccaf-4b00-a3fe-019110ce0534",
+ "selector": ".search-item",
+ "profile": {
+ "name": {
+ "selector": "h4"
+ },
+ "alternativeNamesList": {
+ "selector": ".//div[@class='col-sm-24 col-md-16 name']//li",
+ "findElements": true
+ },
+ "age": {
+ "selector": ".age"
+ },
+ "addressCityStateList": {
+ "selector": ".//div[@class='col-sm-24 col-md-8 location']//li",
+ "findElements": true
+ },
+ "profileUrl": {
+ "selector": "a"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "stepType": "optOut",
+ "optOutType": "formOptOut",
+ "actions": [
+ {
+ "actionType": "navigate",
+ "id": "49f9aa73-4f97-47c0-b8bf-1729e9c169c0",
+ "url": "https://verecor.com/ng/control/privacy"
+ },
+ {
+ "actionType": "fillForm",
+ "id": "55b1d0bb-d303-4b6f-bf9e-3fd96746f27e",
+ "selector": ".ahm",
+ "elements": [
+ {
+ "type": "fullName",
+ "selector": "#user_name"
+ },
+ {
+ "type": "email",
+ "selector": "#user_email"
+ },
+ {
+ "type": "profileUrl",
+ "selector": "#url"
+ }
+ ]
+ },
+ {
+ "actionType": "getCaptchaInfo",
+ "id": "9efb1153-8f52-41e4-a8fb-3077a97a586d",
+ "selector": ".g-recaptcha"
+ },
+ {
+ "actionType": "solveCaptcha",
+ "id": "ed49e4c3-0cfa-4f1e-b3d1-06ad7b8b9ba4",
+ "selector": ".g-recaptcha"
+ },
+ {
+ "actionType": "click",
+ "id": "6b986aa4-3d1b-44d5-8b2b-5463ee8916c9",
+ "elements": [
+ {
+ "type": "button",
+ "selector": ".btn-sbmt"
+ }
+ ]
+ },
+ {
+ "actionType": "expectation",
+ "id": "d4c64d9b-1004-487e-ab06-ae74869bc9a7",
+ "expectations": [
+ {
+ "type": "text",
+ "selector": "body",
+ "expect": "Your removal request has been received"
+ }
+ ]
+ },
+ {
+ "actionType": "emailConfirmation",
+ "id": "3b4c611a-61ab-4792-810e-d5b3633ea203",
+ "pollingTime": 30
+ },
+ {
+ "actionType": "expectation",
+ "id": "afe805a0-d422-473c-b47f-995a8672d476",
+ "expectations": [
+ {
+ "type": "text",
+ "selector": "body",
+ "expect": "Your information control request has been confirmed."
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "schedulingConfig": {
+ "retryError": 48,
+ "confirmOptOutScan": 72,
+ "maintenanceScan": 240
+ }
+ }
+
+ """
+ let verecorNoURLJSONString = """
{
"name": "verecor.com",
"version": "0.1.0",
"addedDatetime": 1677128400000,
+ "mirrorSites": [
+ {
+ "name": "tomato.com",
+ "addedAt": 1705599286529,
+ "removedAt": null
+ },
+ {
+ "name": "potato.com",
+ "addedAt": 1705599286529,
+ "removedAt": null
+ }
+ ],
"steps": [
{
"stepType": "scan",
@@ -157,9 +320,27 @@ final class BrokerJSONCodableTests: XCTestCase {
"""
- func testVerecorJSON_isCorrectlyParsed() {
+ func testVerecorJSONNoURL_isCorrectlyParsed() {
do {
- _ = try JSONDecoder().decode(DataBroker.self, from: verecorJSONString.data(using: .utf8)!)
+ let broker = try JSONDecoder().decode(DataBroker.self, from: verecorNoURLJSONString.data(using: .utf8)!)
+ XCTAssertEqual(broker.url, broker.name)
+ for mirror in broker.mirrorSites {
+ XCTAssertEqual(mirror.url, mirror.name)
+ }
+ } catch {
+ XCTFail("JSON string should be parsed correctly.")
+ }
+ }
+
+ func testVerecorJSONWithURL_isCorrectlyParsed() {
+ do {
+ let broker = try JSONDecoder().decode(DataBroker.self, from: verecorWithURLJSONString.data(using: .utf8)!)
+ XCTAssertEqual(broker.url, "verecor.com")
+ XCTAssertEqual(broker.name, "Verecor")
+
+ for mirror in broker.mirrorSites {
+ XCTAssertNotEqual(mirror.url, mirror.name)
+ }
} catch {
XCTFail("JSON string should be parsed correctly.")
}
diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProfileQueryOperationManagerTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProfileQueryOperationManagerTests.swift
index 3a65f7ae9a..5fa7f4b069 100644
--- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProfileQueryOperationManagerTests.swift
+++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProfileQueryOperationManagerTests.swift
@@ -44,7 +44,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase {
let extractedProfileId: Int64 = 1
let currentPreferredRunDate = Date()
- let mockDataBroker = DataBroker(name: "databroker", steps: [Step](), version: "1.0", schedulingConfig: config)
+ let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", 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)]
@@ -92,7 +92,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase {
let extractedProfileId: Int64 = 1
let currentPreferredRunDate = Date()
- let mockDataBroker = DataBroker(name: "databroker", steps: [Step](), version: "1.0", schedulingConfig: config)
+ let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", 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)]
@@ -143,7 +143,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase {
let extractedProfileId: Int64 = 1
let currentPreferredRunDate = Date()
- let mockDataBroker = DataBroker(name: "databroker", steps: [Step](), version: "1.0", schedulingConfig: config)
+ let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", 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)]
@@ -745,7 +745,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase {
let extractedProfileId: Int64 = 1
let currentPreferredRunDate = Date()
- let mockDataBroker = DataBroker(name: "databroker", steps: [Step](), version: "1.0", schedulingConfig: config)
+ let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", 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)]
@@ -770,7 +770,7 @@ final class DataBrokerProfileQueryOperationManagerTests: XCTestCase {
let currentPreferredRunDate = Date()
let expectedPreferredRunDate = Date().addingTimeInterval(config.confirmOptOutScan.hoursToSeconds)
- let mockDataBroker = DataBroker(name: "databroker", steps: [Step](), version: "1.0", schedulingConfig: config)
+ let mockDataBroker = DataBroker(name: "databroker", url: "databroker.com", 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)]
@@ -846,6 +846,7 @@ extension DataBroker {
DataBroker(
id: 1,
name: "Test broker",
+ url: "testbroker.com",
steps: [
Step(type: .scan, actions: [Action]()),
Step(type: .optOut, actions: [Action]())
@@ -863,6 +864,7 @@ extension DataBroker {
DataBroker(
id: 1,
name: "Test broker",
+ url: "testbroker.com",
steps: [
Step(type: .scan, actions: [Action]()),
Step(type: .optOut, actions: [Action](), optOutType: .parentSiteOptOut)
@@ -879,6 +881,7 @@ extension DataBroker {
static var mockWithoutId: DataBroker {
DataBroker(
name: "Test broker",
+ url: "testbroker.com",
steps: [Step](),
version: "1.0",
schedulingConfig: DataBrokerScheduleConfig(
diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionUpdaterTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionUpdaterTests.swift
index dcbc31a911..2036ac9a1d 100644
--- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionUpdaterTests.swift
+++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionUpdaterTests.swift
@@ -111,7 +111,7 @@ final class DataBrokerProtectionUpdaterTests: XCTestCase {
if let vault = self.vault {
let sut = DataBrokerProtectionBrokerUpdater(repository: repository, resources: resources, vault: vault)
repository.lastCheckedVersion = nil
- resources.brokersList = [.init(id: 1, name: "Broker", steps: [Step](), version: "1.0.1", schedulingConfig: .mock)]
+ resources.brokersList = [.init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.1", schedulingConfig: .mock)]
vault.shouldReturnOldVersionBroker = true
sut.checkForUpdatesInBrokerJSONFiles()
@@ -129,7 +129,7 @@ final class DataBrokerProtectionUpdaterTests: XCTestCase {
if let vault = self.vault {
let sut = DataBrokerProtectionBrokerUpdater(repository: repository, resources: resources, vault: vault)
repository.lastCheckedVersion = nil
- resources.brokersList = [.init(id: 1, name: "Broker", steps: [Step](), version: "1.0.1", schedulingConfig: .mock)]
+ resources.brokersList = [.init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.1", schedulingConfig: .mock)]
vault.shouldReturnNewVersionBroker = true
sut.checkForUpdatesInBrokerJSONFiles()
@@ -146,7 +146,7 @@ final class DataBrokerProtectionUpdaterTests: XCTestCase {
if let vault = self.vault {
let sut = DataBrokerProtectionBrokerUpdater(repository: repository, resources: resources, vault: vault)
repository.lastCheckedVersion = nil
- resources.brokersList = [.init(id: 1, name: "Broker", steps: [Step](), version: "1.0.0", schedulingConfig: .mock)]
+ resources.brokersList = [.init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.0", schedulingConfig: .mock)]
vault.profileQueries = [.mock]
sut.checkForUpdatesInBrokerJSONFiles()
diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/EmailServiceTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/EmailServiceTests.swift
index cc23589ec8..c82680d1e9 100644
--- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/EmailServiceTests.swift
+++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/EmailServiceTests.swift
@@ -41,7 +41,7 @@ final class EmailServiceTests: XCTestCase {
let sut = EmailService(urlSession: mockURLSession, redeemUseCase: MockRedeemUseCase())
do {
- _ = try await sut.getEmail(dataBrokerName: "fakeBroker")
+ _ = try await sut.getEmail(dataBrokerURL: "fakeBroker")
XCTFail("Expected an error to be thrown")
} catch {
if let error = error as? EmailError,
@@ -62,7 +62,7 @@ final class EmailServiceTests: XCTestCase {
let sut = EmailService(urlSession: mockURLSession, redeemUseCase: MockRedeemUseCase())
do {
- _ = try await sut.getEmail(dataBrokerName: "fakeBroker")
+ _ = try await sut.getEmail(dataBrokerURL: "fakeBroker")
XCTFail("Expected an error to be thrown")
} catch {
if let error = error as? EmailError, case .cantFindEmail = error {
@@ -81,7 +81,7 @@ final class EmailServiceTests: XCTestCase {
let sut = EmailService(urlSession: mockURLSession, redeemUseCase: MockRedeemUseCase())
do {
- let email = try await sut.getEmail(dataBrokerName: "fakeBroker")
+ let email = try await sut.getEmail(dataBrokerURL: "fakeBroker")
XCTAssertEqual("test@ddg.com", email)
} catch {
XCTFail("Unexpected. It should not throw")
diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToUITests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToUITests.swift
index 4d1ff6b8f8..c59ee80486 100644
--- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToUITests.swift
+++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MapperToUITests.swift
@@ -140,9 +140,9 @@ final class MapperToUITests: XCTestCase {
func testLastScans_areMappedCorrectly() {
let brokerProfileQueryData: [BrokerProfileQueryData] = [
- .mock(dataBrokerName: "Broker #1", lastRunDate: Date().yesterday),
- .mock(dataBrokerName: "Broker #2", lastRunDate: Date().yesterday),
- .mock(dataBrokerName: "Broker #3")
+ .mock(dataBrokerName: "Broker #1", url: "broker1.com", lastRunDate: Date().yesterday),
+ .mock(dataBrokerName: "Broker #2", url: "broker2.com", lastRunDate: Date().yesterday),
+ .mock(dataBrokerName: "Broker #3", url: "broker3.com")
]
let result = sut.maintenanceScanState(brokerProfileQueryData)
@@ -153,9 +153,9 @@ final class MapperToUITests: XCTestCase {
func testNextScans_areMappedCorrectly() {
let brokerProfileQueryData: [BrokerProfileQueryData] = [
- .mock(dataBrokerName: "Broker #1", preferredRunDate: Date().tomorrow),
- .mock(dataBrokerName: "Broker #2", preferredRunDate: Date().tomorrow),
- .mock(dataBrokerName: "Broker #3")
+ .mock(dataBrokerName: "Broker #1", url: "broker1.com", preferredRunDate: Date().tomorrow),
+ .mock(dataBrokerName: "Broker #2", url: "broker2.com", preferredRunDate: Date().tomorrow),
+ .mock(dataBrokerName: "Broker #3", url: "broker3.com")
]
let result = sut.maintenanceScanState(brokerProfileQueryData)
@@ -165,7 +165,7 @@ final class MapperToUITests: XCTestCase {
}
func testWhenMirrorSiteIsNotInRemovedPeriod_thenItShouldBeAddedToTotalScans() {
- let brokerProfileQueryWithMirrorSite: BrokerProfileQueryData = .mock(dataBrokerName: "Broker #1", mirrorSites: [.init(name: "mirror", addedAt: Date(), removedAt: nil)])
+ let brokerProfileQueryWithMirrorSite: BrokerProfileQueryData = .mock(dataBrokerName: "Broker #1", mirrorSites: [.init(name: "mirror", url: "mirror1.com", addedAt: Date(), removedAt: nil)])
let brokerProfileQueryData: [BrokerProfileQueryData] = [
brokerProfileQueryWithMirrorSite,
brokerProfileQueryWithMirrorSite,
@@ -178,7 +178,7 @@ final class MapperToUITests: XCTestCase {
}
func testWhenMirrorSiteIsInRemovedPeriod_thenItShouldNotBeAddedToTotalScans() {
- let brokerWithMirrorSiteThatWasRemoved = BrokerProfileQueryData.mock(dataBrokerName: "Broker #1", mirrorSites: [.init(name: "mirror", addedAt: Date(), removedAt: Date().yesterday)])
+ let brokerWithMirrorSiteThatWasRemoved = BrokerProfileQueryData.mock(dataBrokerName: "Broker #1", mirrorSites: [.init(name: "mirror", url: "mirror1.com", addedAt: Date(), removedAt: Date().yesterday)])
let brokerProfileQueryData: [BrokerProfileQueryData] = [.mock(dataBrokerName: "Broker #1"), brokerWithMirrorSiteThatWasRemoved, .mock(dataBrokerName: "Broker #2")]
let result = sut.initialScanState(brokerProfileQueryData)
@@ -190,7 +190,7 @@ final class MapperToUITests: XCTestCase {
let brokerWithMirrorSiteNotRemovedAndWithScan = BrokerProfileQueryData.mock(
dataBrokerName: "Broker #1",
lastRunDate: Date(),
- mirrorSites: [.init(name: "mirror", addedAt: Date(), removedAt: nil)]
+ mirrorSites: [.init(name: "mirror", url: "mirror.com", addedAt: Date(), removedAt: nil)]
)
let brokerProfileQueryData: [BrokerProfileQueryData] = [
brokerWithMirrorSiteNotRemovedAndWithScan,
@@ -207,7 +207,7 @@ final class MapperToUITests: XCTestCase {
let brokerWithMirrorSiteRemovedAndWithScan = BrokerProfileQueryData.mock(
dataBrokerName: "Broker #2",
lastRunDate: Date(),
- mirrorSites: [.init(name: "mirror", addedAt: Date(), removedAt: Date().yesterday)]
+ mirrorSites: [.init(name: "mirror", url: "mirror1.com", addedAt: Date(), removedAt: Date().yesterday)]
)
let brokerProfileQueryData: [BrokerProfileQueryData] = [
.mock(dataBrokerName: "Broker #1"),
@@ -223,7 +223,7 @@ final class MapperToUITests: XCTestCase {
func testWhenMirrorSiteIsNotInRemovedPeriod_thenMatchIsAdded() {
let brokerWithMirrorSiteNotRemovedAndWithMatch = BrokerProfileQueryData.mock(
extractedProfile: .mockWithoutRemovedDate,
- mirrorSites: [.init(name: "mirror", addedAt: Date(), removedAt: nil)]
+ mirrorSites: [.init(name: "mirror", url: "mirror1.com", addedAt: Date(), removedAt: nil)]
)
let brokerProfileQueryData: [BrokerProfileQueryData] = [.mock(), .mock(), brokerWithMirrorSiteNotRemovedAndWithMatch]
@@ -235,7 +235,7 @@ final class MapperToUITests: XCTestCase {
func testWhenMirrorSiteIsInRemovedPeriod_thenMatchIsNotAdded() {
let brokerWithMirrorSiteRemovedAndWithMatch = BrokerProfileQueryData.mock(
extractedProfile: .mockWithoutRemovedDate,
- mirrorSites: [.init(name: "mirror", addedAt: Date(), removedAt: Date().yesterday)]
+ mirrorSites: [.init(name: "mirror", url: "mirror1.com", addedAt: Date(), removedAt: Date().yesterday)]
)
let brokerProfileQueryData: [BrokerProfileQueryData] = [.mock(), .mock(), brokerWithMirrorSiteRemovedAndWithMatch]
@@ -246,8 +246,8 @@ final class MapperToUITests: XCTestCase {
func testMirrorSites_areCorrectlyMappedToInProgressOptOuts() {
let scanHistoryEventsWithMatchesFound: [HistoryEvent] = [.init(brokerId: 1, profileQueryId: 1, type: .matchesFound(count: 1), date: Date())]
- let mirrorSiteNotRemoved = MirrorSite(name: "mirror #1", addedAt: Date.distantPast, removedAt: nil)
- let mirrorSiteRemoved = MirrorSite(name: "mirror #2", addedAt: Date.distantPast, removedAt: Date().yesterday) // Should not be added
+ let mirrorSiteNotRemoved = MirrorSite(name: "mirror #1", url: "mirror1.com", addedAt: Date.distantPast, removedAt: nil)
+ let mirrorSiteRemoved = MirrorSite(name: "mirror #2", url: "mirror2.com", addedAt: Date.distantPast, removedAt: Date().yesterday) // Should not be added
let brokerProfileQueryData: [BrokerProfileQueryData] = [
.mock(extractedProfile: .mockWithoutRemovedDate,
scanHistoryEvents: scanHistoryEventsWithMatchesFound,
@@ -261,10 +261,10 @@ final class MapperToUITests: XCTestCase {
func testWhenMirrorSiteRemovedIsInRangeToPastRemovedProfile_thenIsAddedToCompletedOptOuts() {
let scanHistoryEventsWithMatchesFound: [HistoryEvent] = [.init(brokerId: 1, profileQueryId: 1, type: .matchesFound(count: 1), date: Date().yesterday!)]
- let mirrorSiteRemoved = MirrorSite(name: "mirror #1", addedAt: Date.distantPast, removedAt: Date()) // Should be added
+ let mirrorSiteRemoved = MirrorSite(name: "mirror #1", url: "mirror1.com", addedAt: Date.distantPast, removedAt: Date()) // Should be added
// The next two mirror sites should not be added. New mirror sites should not count for old opt-outs
- let newMirrorSiteOne = MirrorSite(name: "mirror #2", addedAt: Date(), removedAt: nil)
- let newMirrorSiteTwo = MirrorSite(name: "mirror #3", addedAt: Date(), removedAt: nil)
+ let newMirrorSiteOne = MirrorSite(name: "mirror #2", url: "mirror2.com", addedAt: Date(), removedAt: nil)
+ let newMirrorSiteTwo = MirrorSite(name: "mirror #3", url: "mirror3.com", addedAt: Date(), removedAt: nil)
let brokerProfileQuery = BrokerProfileQueryData.mock(extractedProfile: .mockWithRemoveDate(Date().yesterday!),
scanHistoryEvents: scanHistoryEventsWithMatchesFound,
mirrorSites: [mirrorSiteRemoved, newMirrorSiteOne, newMirrorSiteTwo])
@@ -276,12 +276,12 @@ final class MapperToUITests: XCTestCase {
}
func testLastScansWithMirrorSites_areMappedCorrectly() {
- let includedMirrorSite = MirrorSite(name: "mirror #1", addedAt: Date.distantPast, removedAt: nil)
- let notIncludedMirrorSite = MirrorSite(name: "mirror #2", addedAt: Date(), removedAt: nil)
+ let includedMirrorSite = MirrorSite(name: "mirror #1", url: "mirror1.com", addedAt: Date.distantPast, removedAt: nil)
+ let notIncludedMirrorSite = MirrorSite(name: "mirror #2", url: "mirror2.com", addedAt: Date(), removedAt: nil)
let brokerProfileQueryData: [BrokerProfileQueryData] = [
- .mock(dataBrokerName: "Broker #1", lastRunDate: Date().yesterday, mirrorSites: [includedMirrorSite, notIncludedMirrorSite]),
- .mock(dataBrokerName: "Broker #2", lastRunDate: Date().yesterday),
- .mock(dataBrokerName: "Broker #3")
+ .mock(dataBrokerName: "Broker #1", url: "broker1.com", lastRunDate: Date().yesterday, mirrorSites: [includedMirrorSite, notIncludedMirrorSite]),
+ .mock(dataBrokerName: "Broker #2", url: "broker2.com", lastRunDate: Date().yesterday),
+ .mock(dataBrokerName: "Broker #3", url: "broker3.com")
]
let result = sut.maintenanceScanState(brokerProfileQueryData)
@@ -291,12 +291,12 @@ final class MapperToUITests: XCTestCase {
}
func testNextScansWithMirrorSites_areMappedCorrectly() {
- let includedMirrorSite = MirrorSite(name: "mirror #1", addedAt: Date.distantPast, removedAt: nil)
- let notIncludedMirrorSite = MirrorSite(name: "mirror #2", addedAt: Date.distantPast, removedAt: Date())
+ let includedMirrorSite = MirrorSite(name: "mirror #1", url: "mirror1.com", addedAt: Date.distantPast, removedAt: nil)
+ let notIncludedMirrorSite = MirrorSite(name: "mirror #2", url: "mirror2.com", addedAt: Date.distantPast, removedAt: Date())
let brokerProfileQueryData: [BrokerProfileQueryData] = [
- .mock(dataBrokerName: "Broker #1", preferredRunDate: Date().tomorrow, mirrorSites: [includedMirrorSite, notIncludedMirrorSite]),
- .mock(dataBrokerName: "Broker #2", preferredRunDate: Date().tomorrow),
- .mock(dataBrokerName: "Broker #3")
+ .mock(dataBrokerName: "Broker #1", url: "broker1.com", preferredRunDate: Date().tomorrow, mirrorSites: [includedMirrorSite, notIncludedMirrorSite]),
+ .mock(dataBrokerName: "Broker #2", url: "broker2.com", preferredRunDate: Date().tomorrow),
+ .mock(dataBrokerName: "Broker #3", url: "broker3.com")
]
let result = sut.maintenanceScanState(brokerProfileQueryData)
diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MismatchCalculatorUseCaseTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MismatchCalculatorUseCaseTests.swift
index 8585792744..12b7120077 100644
--- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MismatchCalculatorUseCaseTests.swift
+++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/MismatchCalculatorUseCaseTests.swift
@@ -152,6 +152,7 @@ extension BrokerProfileQueryData {
BrokerProfileQueryData(
dataBroker: DataBroker(
name: "parent",
+ url: "parent.com",
steps: [Step](),
version: "1.0.0",
schedulingConfig: DataBrokerScheduleConfig.mock
@@ -165,6 +166,7 @@ extension BrokerProfileQueryData {
BrokerProfileQueryData(
dataBroker: DataBroker(
name: "child",
+ url: "child.com",
steps: [Step](),
version: "1.0.0",
schedulingConfig: DataBrokerScheduleConfig.mock,
diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift
index d053404255..95ea0d1493 100644
--- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift
+++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift
@@ -27,6 +27,7 @@ import GRDB
extension BrokerProfileQueryData {
static func mock(with steps: [Step] = [Step](),
dataBrokerName: String = "test",
+ url: String = "test.com",
lastRunDate: Date? = nil,
preferredRunDate: Date? = nil,
extractedProfile: ExtractedProfile? = nil,
@@ -36,6 +37,7 @@ extension BrokerProfileQueryData {
BrokerProfileQueryData(
dataBroker: DataBroker(
name: dataBrokerName,
+ url: url,
steps: steps,
version: "1.0.0",
schedulingConfig: DataBrokerScheduleConfig.mock,
@@ -232,7 +234,7 @@ final class EmailServiceMock: EmailServiceProtocol {
var shouldThrow: Bool = false
- func getEmail(dataBrokerName: String?) async throws -> String {
+ func getEmail(dataBrokerURL: String?) async throws -> String {
if shouldThrow {
throw DataBrokerProtectionError.emailError(nil)
}
@@ -491,9 +493,9 @@ final class DataBrokerProtectionSecureVaultMock: DataBrokerProtectionSecureVault
func fetchBroker(with name: String) throws -> DataBroker? {
if shouldReturnOldVersionBroker {
- return .init(id: 1, name: "Broker", steps: [Step](), version: "1.0.0", schedulingConfig: .mock)
+ return .init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.0", schedulingConfig: .mock)
} else if shouldReturnNewVersionBroker {
- return .init(id: 1, name: "Broker", steps: [Step](), version: "1.0.1", schedulingConfig: .mock)
+ return .init(id: 1, name: "Broker", url: "broker.com", steps: [Step](), version: "1.0.1", schedulingConfig: .mock)
}
return nil
diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateUpdaterTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateUpdaterTests.swift
index b9472a9ab9..369f80ee32 100644
--- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateUpdaterTests.swift
+++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateUpdaterTests.swift
@@ -35,6 +35,7 @@ final class OperationPreferredDateUpdaterTests: XCTestCase {
let childBroker = DataBroker(
id: 1,
name: "Child broker",
+ url: "childbroker.com",
steps: [Step](),
version: "1.0",
schedulingConfig: DataBrokerScheduleConfig(
From 3002b7dfe73703eda29eb3997254afa097ff1de3 Mon Sep 17 00:00:00 2001
From: Dax the Duck
Date: Wed, 21 Feb 2024 18:51:13 +0000
Subject: [PATCH 09/12] Bump version to 1.76.0 (123)
---
Configuration/BuildNumber.xcconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig
index 060339a249..550b71a3ee 100644
--- a/Configuration/BuildNumber.xcconfig
+++ b/Configuration/BuildNumber.xcconfig
@@ -1 +1 @@
-CURRENT_PROJECT_VERSION = 122
+CURRENT_PROJECT_VERSION = 123
From 883db580de28433681a5bf97f5ddfab4600ebebe Mon Sep 17 00:00:00 2001
From: Juan Manuel Pereira
Date: Wed, 21 Feb 2024 16:00:31 -0300
Subject: [PATCH 10/12] DBP: Add initial loading indicator when loading web UI
(#2227)
---
.../DataBrokerProtectionViewController.swift | 40 ++++++++++++++++++-
1 file changed, 38 insertions(+), 2 deletions(-)
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift
index accf119bb7..ce258c3565 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift
@@ -26,6 +26,7 @@ final public class DataBrokerProtectionViewController: NSViewController {
private let dataManager: DataBrokerProtectionDataManaging
private let scheduler: DataBrokerProtectionScheduler
private var webView: WKWebView?
+ private var loader: NSProgressIndicator!
private let webUISettings: DataBrokerProtectionWebUIURLSettingsRepresentable
private let webUIViewModel: DBPUIViewModel
@@ -63,9 +64,10 @@ final public class DataBrokerProtectionViewController: NSViewController {
public override func viewDidLoad() {
super.viewDidLoad()
+ addLoadingIndicator()
reloadObserver = NotificationCenter.default.addObserver(forName: DataBrokerProtectionNotifications.shouldReloadUI,
- object: nil,
- queue: .main) { [weak self] _ in
+ object: nil,
+ queue: .main) { [weak self] _ in
self?.webView?.reload()
}
}
@@ -75,16 +77,39 @@ final public class DataBrokerProtectionViewController: NSViewController {
webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 1024, height: 768), configuration: configuration)
webView?.uiDelegate = self
+ webView?.navigationDelegate = self
view = webView!
if let url = URL(string: webUISettings.selectedURL) {
webView?.load(url)
} else {
+ removeLoadingIndicator()
assertionFailure("Selected URL is not valid \(webUISettings.selectedURL)")
}
}
+ private func addLoadingIndicator() {
+ loader = NSProgressIndicator()
+ loader.wantsLayer = true
+ loader.style = .spinning
+ loader.controlSize = .regular
+ loader.sizeToFit()
+ loader.translatesAutoresizingMaskIntoConstraints = false
+ loader.controlSize = .large
+ view.addSubview(loader)
+
+ NSLayoutConstraint.activate([
+ loader.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+ loader.centerYAnchor.constraint(equalTo: view.centerYAnchor),
+ ])
+ }
+
+ private func removeLoadingIndicator() {
+ loader.stopAnimation(nil)
+ loader.removeFromSuperview()
+ }
+
deinit {
if let reloadObserver {
NotificationCenter.default.removeObserver(reloadObserver)
@@ -98,3 +123,14 @@ extension DataBrokerProtectionViewController: WKUIDelegate {
return nil
}
}
+
+extension DataBrokerProtectionViewController: WKNavigationDelegate {
+
+ public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
+ loader.startAnimation(nil)
+ }
+
+ public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
+ removeLoadingIndicator()
+ }
+}
From d5c1756e2264d08cf308f79abe8fd22d126fccd1 Mon Sep 17 00:00:00 2001
From: Diego Rey Mendez
Date: Wed, 21 Feb 2024 21:54:34 +0100
Subject: [PATCH 11/12] macOS: Transparent proxy for excluding VPN traffic.
(#2128)
Task/Issue URL: https://app.asana.com/0/0/1206462407536023/f
Tech Design URLs:
- [Tech Design: How to exclude Data Broker
traffic?](https://app.asana.com/0/481882893211075/1206363506060150/f)
- [Tech Design: Mechanism to allow PIR to start excluding its traffic
from the VPN
tunnel](https://app.asana.com/0/481882893211075/1206446978081253/f)
- [Tech Design: How will the proxy recover from
failure?](https://app.asana.com/0/481882893211075/1206446978546262)
iOS PR: https://github.com/duckduckgo/iOS/pull/2429
BSK PR: https://github.com/duckduckgo/BrowserServicesKit/pull/652
## Description
Adds a transparent proxy that allows excluding app and domain traffic
from the VPN.
## Known issues / limitations
### Issue 1: Exclusion delay on existing flows
When switching off an exclusion, connection flows seem to switch
immediately to routing through the tunnel interface again.
However when turning the exclusion back ON, connection flows seem to
take a bit before routing back through the proxy.
This should not be a big problem as eventually connections start being
excluded correctly again. It's unclear at this point if this is a macOS
bug, or a bug on our proxy - but I don't think this should be a blocker
by any means.
---
Configuration/App/DuckDuckGoAppStore.xcconfig | 5 -
Configuration/AppStore.xcconfig | 26 +-
Configuration/DeveloperID.xcconfig | 14 +
.../NetworkProtectionAppExtension.xcconfig | 40 +-
.../VPNProxyExtension.xcconfig | 52 +++
DuckDuckGo.xcodeproj/project.pbxproj | 287 +++++++++++--
.../xcshareddata/swiftpm/Package.resolved | 4 +-
.../DBP/DataBrokerProtectionDebugMenu.swift | 10 +
.../DBP/DataBrokerProtectionManager.swift | 10 +-
.../DBP/LoginItem+DataBrokerProtection.swift | 1 +
DuckDuckGo/DuckDuckGo.entitlements | 1 +
DuckDuckGo/DuckDuckGoAppStore.entitlements | 5 +
DuckDuckGo/DuckDuckGoAppStoreCI.entitlements | 4 -
DuckDuckGo/DuckDuckGoDebug.entitlements | 1 +
DuckDuckGo/DuckDuckGo_NetP_Debug.entitlements | 30 --
.../DuckDuckGo_NetP_Release.entitlements | 38 --
DuckDuckGo/InfoPlist.xcstrings | 2 +-
.../Bundle+VPN.swift | 65 +++
.../NetworkProtectionBundle.swift | 78 ----
.../NetworkProtectionDebugMenu.swift | 46 ++-
.../NetworkProtectionTunnelController.swift | 19 +-
.../MacPacketTunnelProvider.swift | 20 +-
.../MacTransparentProxyProvider.swift | 94 +++++
...NetworkProtectionAppExtension.entitlements | 1 +
.../BrowserWindowManager.swift | 64 +++
.../IPCServiceManager.swift | 13 +-
DuckDuckGoVPN/DuckDuckGoVPN.entitlements | 1 +
DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 93 ++++-
DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements | 1 +
DuckDuckGoVPN/Info-AppStore.plist | 6 +-
DuckDuckGoVPN/Info.plist | 6 +-
DuckDuckGoVPN/VPNProxyLauncher.swift | 149 +++++++
.../DataBrokerProtection/Package.swift | 2 +-
.../IPC/DataBrokerProtectionIPCClient.swift | 13 +-
.../IPC/DataBrokerProtectionIPCServer.swift | 16 +
.../Pixels/DataBrokerProtectionPixels.swift | 1 -
LocalPackages/LoginItems/Package.swift | 2 +-
.../NetworkProtectionMac/Package.resolved | 104 +++++
.../NetworkProtectionMac/Package.swift | 16 +-
.../FlowManagers/TCPFlowManager.swift | 242 +++++++++++
.../FlowManagers/UDPFlowManager.swift | 329 +++++++++++++++
.../TransparentProxyAppMessageHandler.swift | 82 ++++
.../IPC/TransparentProxyRequest.swift | 67 +++
.../TransparentProxyControllerPixel.swift | 89 ++++
.../TransparentProxyProviderPixel.swift | 93 +++++
.../RoutingRules/VPNAppRoutingRules.swift | 16 +-
.../RoutingRules/VPNRoutingRule.swift | 20 +-
.../Settings/TransparentProxySettings.swift | 134 ++++++
.../UserDefaults+excludedApps.swift | 79 ++++
.../UserDefaults+excludedDomains.swift | 51 +++
.../TransparentProxyController.swift | 293 +++++++++++++
.../TransparentProxyProvider.swift | 389 ++++++++++++++++++
...ransparentProxyProviderConfiguration.swift | 40 ++
.../NetworkProtectionStatusView.swift | 4 +
.../NetworkProtectionStatusViewModel.swift | 34 +-
...TransparentProxyControllerPixelTests.swift | 120 ++++++
.../TransparentProxyProviderPixelTests.swift | 66 +++
LocalPackages/PixelKit/Package.swift | 2 +-
.../PixelKit/PixelKit+Parameters.swift | 6 +-
.../PixelKit/Sources/PixelKit/PixelKit.swift | 24 +-
.../Sources/PixelKit/PixelKitEvent.swift | 2 +-
.../Sources/PixelKit/PixelKitEventV2.swift | 70 ++++
.../PixelFireExpectations.swift | 36 ++
.../XCTestCase+PixelKit.swift | 148 +++++++
LocalPackages/SubscriptionUI/Package.swift | 2 +-
LocalPackages/SwiftUIExtensions/Package.swift | 2 +-
LocalPackages/SyncUI/Package.swift | 2 +-
.../SystemExtensionManager/Package.swift | 2 +-
LocalPackages/XPCHelper/Package.swift | 2 +-
NetworkProtectionSystemExtension/Info.plist | 2 +
...otectionSystemExtension_Debug.entitlements | 1 +
...ectionSystemExtension_Release.entitlements | 1 +
VPNProxyExtension/Info.plist | 17 +
.../VPNProxyExtension.entitlements | 25 ++
fastlane/Matchfile | 2 +
scripts/assets/AppStoreExportOptions.plist | 4 +
76 files changed, 3497 insertions(+), 341 deletions(-)
create mode 100644 Configuration/Extensions/NetworkProtection/VPNProxyExtension.xcconfig
delete mode 100644 DuckDuckGo/DuckDuckGo_NetP_Debug.entitlements
delete mode 100644 DuckDuckGo/DuckDuckGo_NetP_Release.entitlements
create mode 100644 DuckDuckGo/NetworkProtection/AppAndExtensionTargets/AppAndExtensionAndNotificationTargets/Bundle+VPN.swift
delete mode 100644 DuckDuckGo/NetworkProtection/AppAndExtensionTargets/AppAndExtensionAndNotificationTargets/NetworkProtectionBundle.swift
create mode 100644 DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacTransparentProxyProvider.swift
create mode 100644 DuckDuckGoDBPBackgroundAgent/BrowserWindowManager.swift
create mode 100644 DuckDuckGoVPN/VPNProxyLauncher.swift
create mode 100644 LocalPackages/NetworkProtectionMac/Package.resolved
create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/FlowManagers/TCPFlowManager.swift
create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/FlowManagers/UDPFlowManager.swift
create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/IPC/TransparentProxyAppMessageHandler.swift
create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/IPC/TransparentProxyRequest.swift
create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Pixels/TransparentProxyControllerPixel.swift
create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Pixels/TransparentProxyProviderPixel.swift
rename DuckDuckGoVPN/Bundle+Configuration.swift => LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/RoutingRules/VPNAppRoutingRules.swift (56%)
rename DuckDuckGo/NetworkProtection/NetworkExtensionTargets/SystemExtensionAndNotificationTargets/NetworkProtectionExtensionMachService.swift => LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/RoutingRules/VPNRoutingRule.swift (59%)
create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Settings/TransparentProxySettings.swift
create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Settings/UserDefaultsExtensions/UserDefaults+excludedApps.swift
create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Settings/UserDefaultsExtensions/UserDefaults+excludedDomains.swift
create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/TransparentProxyController.swift
create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/TransparentProxyProvider.swift
create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/TransparentProxyProviderConfiguration.swift
create mode 100644 LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/TransparentProxyControllerPixelTests.swift
create mode 100644 LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/TransparentProxyProviderPixelTests.swift
create mode 100644 LocalPackages/PixelKit/Sources/PixelKit/PixelKitEventV2.swift
create mode 100644 LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/PixelFireExpectations.swift
create mode 100644 LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift
create mode 100644 VPNProxyExtension/Info.plist
create mode 100644 VPNProxyExtension/VPNProxyExtension.entitlements
diff --git a/Configuration/App/DuckDuckGoAppStore.xcconfig b/Configuration/App/DuckDuckGoAppStore.xcconfig
index 3ee212ad5e..904caca8c5 100644
--- a/Configuration/App/DuckDuckGoAppStore.xcconfig
+++ b/Configuration/App/DuckDuckGoAppStore.xcconfig
@@ -17,11 +17,6 @@
#include "../AppStore.xcconfig"
#include "ManualAppStoreRelease.xcconfig"
-AGENT_BUNDLE_ID[sdk=*] = com.duckduckgo.mobile.ios.vpn.agent
-AGENT_BUNDLE_ID[config=Debug][sdk=*] = com.duckduckgo.mobile.ios.vpn.agent.debug
-AGENT_BUNDLE_ID[config=CI][sdk=*] = com.duckduckgo.mobile.ios.vpn.agent.debug
-AGENT_BUNDLE_ID[config=Review][sdk=*] = com.duckduckgo.mobile.ios.vpn.agent.review
-
PRODUCT_BUNDLE_IDENTIFIER = $(MAIN_BUNDLE_IDENTIFIER)
CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAppStore.entitlements
diff --git a/Configuration/AppStore.xcconfig b/Configuration/AppStore.xcconfig
index c2ae87c9b5..0ad3f9f6b5 100644
--- a/Configuration/AppStore.xcconfig
+++ b/Configuration/AppStore.xcconfig
@@ -50,21 +50,23 @@ AGENT_BUNDLE_ID[config=Review][sdk=*] = $(AGENT_BUNDLE_ID_BASE).review
AGENT_PRODUCT_NAME = DuckDuckGo VPN App Store
AGENT_RELEASE_PRODUCT_NAME = DuckDuckGo VPN
-SYSEX_BUNDLE_ID[sdk=*] = com.duckduckgo.mobile.ios.vpn.agent
-SYSEX_BUNDLE_ID[config=Debug][sdk=*] = com.duckduckgo.mobile.ios.vpn.agent.debug.network-protection-extension
-SYSEX_BUNDLE_ID[config=CI][sdk=*] = com.duckduckgo.mobile.ios.vpn.agent.debug.network-protection-extension
-SYSEX_BUNDLE_ID[config=Review][sdk=*] = com.duckduckgo.mobile.ios.vpn.agent.review.network-protection-extension
-SYSEX_BUNDLE_ID[config=Release][sdk=*] = com.duckduckgo.mobile.ios.vpn.agent.network-protection-extension
+// Extensions
-// Distributed Notifications Prefix
+PROXY_EXTENSION_BUNDLE_ID[sdk=*] = $(AGENT_BUNDLE_ID).proxy
+PROXY_EXTENSION_BUNDLE_ID[config=Debug][sdk=*] = $(AGENT_BUNDLE_ID).proxy
+PROXY_EXTENSION_BUNDLE_ID[config=CI][sdk=*] = $(AGENT_BUNDLE_ID).proxy
+PROXY_EXTENSION_BUNDLE_ID[config=Review][sdk=*] = $(AGENT_BUNDLE_ID).proxy
+PROXY_EXTENSION_BUNDLE_ID[config=Release][sdk=*] = $(AGENT_BUNDLE_ID).proxy
+
+TUNNEL_EXTENSION_BUNDLE_ID[sdk=*] = $(AGENT_BUNDLE_ID).network-protection-extension
+TUNNEL_EXTENSION_BUNDLE_ID[config=Debug][sdk=*] = $(AGENT_BUNDLE_ID).network-protection-extension
+TUNNEL_EXTENSION_BUNDLE_ID[config=CI][sdk=*] = $(AGENT_BUNDLE_ID).network-protection-extension
+TUNNEL_EXTENSION_BUNDLE_ID[config=Review][sdk=*] = $(AGENT_BUNDLE_ID).network-protection-extension
+TUNNEL_EXTENSION_BUNDLE_ID[config=Release][sdk=*] = $(AGENT_BUNDLE_ID).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
-DISTRIBUTED_NOTIFICATIONS_PREFIX_BASE = $(SYSEX_BUNDLE_ID_BASE)
+DISTRIBUTED_NOTIFICATIONS_PREFIX_BASE = $(AGENT_BUNDLE_ID_BASE).network-extension
DISTRIBUTED_NOTIFICATIONS_PREFIX[config=CI][sdk=*] = $(DISTRIBUTED_NOTIFICATIONS_PREFIX_BASE).ci
DISTRIBUTED_NOTIFICATIONS_PREFIX[config=Review][sdk=*] = $(DISTRIBUTED_NOTIFICATIONS_PREFIX_BASE).review
diff --git a/Configuration/DeveloperID.xcconfig b/Configuration/DeveloperID.xcconfig
index 0bfb9bb8cb..b66acc76d2 100644
--- a/Configuration/DeveloperID.xcconfig
+++ b/Configuration/DeveloperID.xcconfig
@@ -65,6 +65,20 @@ 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 VPN
+// Extensions
+
+PROXY_EXTENSION_BUNDLE_ID[sdk=*] = $(SYSEX_BUNDLE_ID)
+PROXY_EXTENSION_BUNDLE_ID[config=Debug][sdk=*] = $(SYSEX_BUNDLE_ID)
+PROXY_EXTENSION_BUNDLE_ID[config=CI][sdk=*] = $(SYSEX_BUNDLE_ID)
+PROXY_EXTENSION_BUNDLE_ID[config=Review][sdk=*] = $(SYSEX_BUNDLE_ID)
+PROXY_EXTENSION_BUNDLE_ID[config=Release][sdk=*] = $(SYSEX_BUNDLE_ID)
+
+TUNNEL_EXTENSION_BUNDLE_ID[sdk=*] = $(SYSEX_BUNDLE_ID)
+TUNNEL_EXTENSION_BUNDLE_ID[config=Debug][sdk=*] = $(SYSEX_BUNDLE_ID)
+TUNNEL_EXTENSION_BUNDLE_ID[config=CI][sdk=*] = $(SYSEX_BUNDLE_ID)
+TUNNEL_EXTENSION_BUNDLE_ID[config=Review][sdk=*] = $(SYSEX_BUNDLE_ID)
+TUNNEL_EXTENSION_BUNDLE_ID[config=Release][sdk=*] = $(SYSEX_BUNDLE_ID)
+
// DBP
DBP_BACKGROUND_AGENT_PRODUCT_NAME = DuckDuckGo Personal Information Removal
diff --git a/Configuration/Extensions/NetworkProtection/NetworkProtectionAppExtension.xcconfig b/Configuration/Extensions/NetworkProtection/NetworkProtectionAppExtension.xcconfig
index 60ad407569..2fb095fc56 100644
--- a/Configuration/Extensions/NetworkProtection/NetworkProtectionAppExtension.xcconfig
+++ b/Configuration/Extensions/NetworkProtection/NetworkProtectionAppExtension.xcconfig
@@ -14,10 +14,7 @@
//
#include "../ExtensionBase.xcconfig"
-
-// Since we're using nonstandard bundle IDs we'll just define them here, but we should consider
-// standardizing the bundle IDs so we can just define BUNDLE_IDENTIFIER_PREFIX
-BUNDLE_IDENTIFIER_PREFIX = com.duckduckgo.mobile.ios.vpn.agent
+#include "../../AppStore.xcconfig"
CODE_SIGN_ENTITLEMENTS[config=CI][sdk=macosx*] =
CODE_SIGN_ENTITLEMENTS[config=Debug][sdk=macosx*] = DuckDuckGo/NetworkProtectionAppExtension.entitlements
@@ -38,17 +35,11 @@ FEATURE_FLAGS[config=CI][arch=*][sdk=*] = NETWORK_EXTENSION NETWORK_PROTECTION
FEATURE_FLAGS[config=Debug][arch=*][sdk=*] = NETWORK_EXTENSION NETWORK_PROTECTION
FEATURE_FLAGS[config=Review][arch=*][sdk=*] = NETWORK_EXTENSION NETWORK_PROTECTION
-NETP_BASE_APP_GROUP = $(DEVELOPMENT_TEAM).com.duckduckgo.macos.browser.network-protection
-NETP_APP_GROUP[config=CI][sdk=macos*] = $(NETP_BASE_APP_GROUP).debug
-NETP_APP_GROUP[config=Review][sdk=macos*] = $(NETP_BASE_APP_GROUP).review
-NETP_APP_GROUP[config=Debug][sdk=macos*] = $(NETP_BASE_APP_GROUP).debug
-NETP_APP_GROUP[config=Release][sdk=macos*] = $(NETP_BASE_APP_GROUP)
-
PRODUCT_BUNDLE_IDENTIFIER[sdk=*] =
-PRODUCT_BUNDLE_IDENTIFIER[config=CI][sdk=*] = $(BUNDLE_IDENTIFIER_PREFIX).debug.network-protection-extension
-PRODUCT_BUNDLE_IDENTIFIER[config=Debug][sdk=*] = $(BUNDLE_IDENTIFIER_PREFIX).debug.network-protection-extension
-PRODUCT_BUNDLE_IDENTIFIER[config=Release][sdk=*] = $(BUNDLE_IDENTIFIER_PREFIX).network-protection-extension
-PRODUCT_BUNDLE_IDENTIFIER[config=Review][sdk=*] = $(BUNDLE_IDENTIFIER_PREFIX).review.network-protection-extension
+PRODUCT_BUNDLE_IDENTIFIER[config=CI][sdk=*] = $(TUNNEL_EXTENSION_BUNDLE_ID)
+PRODUCT_BUNDLE_IDENTIFIER[config=Debug][sdk=*] = $(TUNNEL_EXTENSION_BUNDLE_ID)
+PRODUCT_BUNDLE_IDENTIFIER[config=Release][sdk=*] = $(TUNNEL_EXTENSION_BUNDLE_ID)
+PRODUCT_BUNDLE_IDENTIFIER[config=Review][sdk=*] = $(TUNNEL_EXTENSION_BUNDLE_ID)
PROVISIONING_PROFILE_SPECIFIER[config=CI][sdk=macosx*] =
PROVISIONING_PROFILE_SPECIFIER[config=Release][sdk=macosx*] = match AppStore com.duckduckgo.mobile.ios.vpn.agent.network-protection-extension macos
@@ -59,24 +50,3 @@ SKIP_INSTALL = YES
SWIFT_EMIT_LOC_STRINGS = YES
LD_RUNPATH_SEARCH_PATHS = @executable_path/../Frameworks @executable_path/../../../../Frameworks
-
-// Distributed Notifications:
-
-AGENT_BUNDLE_ID_BASE[sdk=*] = com.duckduckgo.mobile.ios.vpn.agent
-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
-
-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 = $(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
-DISTRIBUTED_NOTIFICATIONS_PREFIX[config=Release][sdk=*] = $(DISTRIBUTED_NOTIFICATIONS_PREFIX_BASE)
diff --git a/Configuration/Extensions/NetworkProtection/VPNProxyExtension.xcconfig b/Configuration/Extensions/NetworkProtection/VPNProxyExtension.xcconfig
new file mode 100644
index 0000000000..5f70d87091
--- /dev/null
+++ b/Configuration/Extensions/NetworkProtection/VPNProxyExtension.xcconfig
@@ -0,0 +1,52 @@
+// 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 "../ExtensionBase.xcconfig"
+#include "../../AppStore.xcconfig"
+
+CODE_SIGN_ENTITLEMENTS[config=CI][sdk=macosx*] =
+CODE_SIGN_ENTITLEMENTS[config=Debug][sdk=macosx*] = VPNProxyExtension/VPNProxyExtension.entitlements
+CODE_SIGN_ENTITLEMENTS[config=Release][sdk=macosx*] = VPNProxyExtension/VPNProxyExtension.entitlements
+CODE_SIGN_ENTITLEMENTS[config=Review][sdk=macosx*] = VPNProxyExtension/VPNProxyExtension.entitlements
+CODE_SIGN_STYLE[config=Debug][sdk=*] = Automatic
+
+CODE_SIGN_IDENTITY[sdk=macosx*] = 3rd Party Mac Developer Application
+CODE_SIGN_IDENTITY[config=Debug][sdk=macosx*] = Apple Development
+CODE_SIGN_IDENTITY[config=CI][sdk=macosx*] =
+
+GENERATE_INFOPLIST_FILE = YES
+INFOPLIST_FILE = VPNProxyExtension/Info.plist
+INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2023 DuckDuckGo. All rights reserved.
+
+FEATURE_FLAGS[arch=*][sdk=*] = NETWORK_EXTENSION NETWORK_PROTECTION
+FEATURE_FLAGS[config=CI][arch=*][sdk=*] = NETWORK_EXTENSION NETWORK_PROTECTION
+FEATURE_FLAGS[config=Debug][arch=*][sdk=*] = NETWORK_EXTENSION NETWORK_PROTECTION
+FEATURE_FLAGS[config=Review][arch=*][sdk=*] = NETWORK_EXTENSION NETWORK_PROTECTION
+
+PRODUCT_BUNDLE_IDENTIFIER[sdk=*] =
+PRODUCT_BUNDLE_IDENTIFIER[config=CI][sdk=*] = $(PROXY_EXTENSION_BUNDLE_ID)
+PRODUCT_BUNDLE_IDENTIFIER[config=Debug][sdk=*] = $(PROXY_EXTENSION_BUNDLE_ID)
+PRODUCT_BUNDLE_IDENTIFIER[config=Release][sdk=*] = $(PROXY_EXTENSION_BUNDLE_ID)
+PRODUCT_BUNDLE_IDENTIFIER[config=Review][sdk=*] = $(PROXY_EXTENSION_BUNDLE_ID)
+
+PROVISIONING_PROFILE_SPECIFIER[config=CI][sdk=macosx*] =
+PROVISIONING_PROFILE_SPECIFIER[config=Release][sdk=macosx*] = match AppStore $(AGENT_BUNDLE_ID).proxy macos
+PROVISIONING_PROFILE_SPECIFIER[config=Review][sdk=macosx*] = match AppStore $(AGENT_BUNDLE_ID).proxy macos
+
+SDKROOT = macosx
+SKIP_INSTALL = YES
+SWIFT_EMIT_LOC_STRINGS = YES
+
+LD_RUNPATH_SEARCH_PATHS = @executable_path/../Frameworks @executable_path/../../../../Frameworks
diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj
index 0f1e8a2d3e..0a586a3376 100644
--- a/DuckDuckGo.xcodeproj/project.pbxproj
+++ b/DuckDuckGo.xcodeproj/project.pbxproj
@@ -1099,12 +1099,11 @@
4B25375B2A11BE7300610219 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B4D603E2A0B290200BCD287 /* NetworkExtension.framework */; };
4B2537722A11BF8B00610219 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B25376F2A11BF8B00610219 /* main.swift */; };
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 */; };
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 */; };
- 4B2D06292A11C0C900DE1F49 /* NetworkProtectionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* NetworkProtectionBundle.swift */; };
+ 4B2D06292A11C0C900DE1F49 /* Bundle+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* Bundle+VPN.swift */; };
4B2D062A2A11C0C900DE1F49 /* NetworkProtectionOptionKeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */; };
4B2D062C2A11C0E100DE1F49 /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2D062B2A11C0E100DE1F49 /* Networking */; };
4B2D062D2A11C12300DE1F49 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85799C1725DEBB3F0007EC87 /* Logging.swift */; };
@@ -1168,17 +1167,16 @@
4B44FEF52B1FEF5A000619D8 /* FocusableTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B44FEF22B1FEF5A000619D8 /* FocusableTextEditor.swift */; };
4B4BEC3D2A11B56B001D9AC5 /* DuckDuckGoNotificationsAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4BEC382A11B509001D9AC5 /* DuckDuckGoNotificationsAppDelegate.swift */; };
4B4BEC3E2A11B56E001D9AC5 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4BEC322A11B509001D9AC5 /* Logging.swift */; };
- 4B4BEC402A11B5B5001D9AC5 /* NetworkProtectionExtensionMachService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60782A0B29FA00BCD287 /* NetworkProtectionExtensionMachService.swift */; };
4B4BEC412A11B5BD001D9AC5 /* NetworkProtectionUNNotificationsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60762A0B29FA00BCD287 /* NetworkProtectionUNNotificationsPresenter.swift */; };
4B4BEC422A11B5C7001D9AC5 /* NetworkProtectionOptionKeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */; };
- 4B4BEC432A11B5C7001D9AC5 /* NetworkProtectionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* NetworkProtectionBundle.swift */; };
+ 4B4BEC432A11B5C7001D9AC5 /* Bundle+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* Bundle+VPN.swift */; };
4B4BEC452A11B5EE001D9AC5 /* UserText+NetworkProtectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D607C2A0B29FA00BCD287 /* UserText+NetworkProtectionExtensions.swift */; };
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 */; };
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 */; };
+ 4B4D60A02A0B2D5B00BCD287 /* Bundle+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* Bundle+VPN.swift */; };
4B4D60A12A0B2D6100BCD287 /* NetworkProtectionOptionKeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */; };
4B4D60A52A0B2EC000BCD287 /* UserText+NetworkProtectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D607C2A0B29FA00BCD287 /* UserText+NetworkProtectionExtensions.swift */; };
4B4D60AC2A0C804B00BCD287 /* OptionalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B637273C26CCF0C200C8CB02 /* OptionalExtension.swift */; };
@@ -1202,7 +1200,7 @@
4B4D60D42A0C84F700BCD287 /* UserText+NetworkProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60D22A0C84F700BCD287 /* UserText+NetworkProtection.swift */; };
4B4D60DD2A0C875E00BCD287 /* NetworkProtectionOptionKeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */; };
4B4D60DF2A0C875F00BCD287 /* NetworkProtectionOptionKeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */; };
- 4B4D60E02A0C875F00BCD287 /* NetworkProtectionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* NetworkProtectionBundle.swift */; };
+ 4B4D60E02A0C875F00BCD287 /* Bundle+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* Bundle+VPN.swift */; };
4B4D60E22A0C883A00BCD287 /* AppMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60E12A0C883A00BCD287 /* AppMain.swift */; };
4B4D60E32A0C883A00BCD287 /* AppMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60E12A0C883A00BCD287 /* AppMain.swift */; };
4B4F72EC266B2ED300814C60 /* CollectionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F72EB266B2ED300814C60 /* CollectionExtension.swift */; };
@@ -1528,7 +1526,7 @@
4B957A422AC7AE700062CA31 /* SafariFaviconsReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0AACAD28BC6FD0001038AC /* SafariFaviconsReader.swift */; };
4B957A432AC7AE700062CA31 /* NSScreenExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B3E0DC2657E9CF0040E0A2 /* NSScreenExtension.swift */; };
4B957A442AC7AE700062CA31 /* NSBezierPathExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65E6B9F26D9F10600095F96 /* NSBezierPathExtension.swift */; };
- 4B957A452AC7AE700062CA31 /* NetworkProtectionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* NetworkProtectionBundle.swift */; };
+ 4B957A452AC7AE700062CA31 /* Bundle+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* Bundle+VPN.swift */; };
4B957A462AC7AE700062CA31 /* WebsiteDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA6820E325502F19005ED0D5 /* WebsiteDataStore.swift */; };
4B957A472AC7AE700062CA31 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD8679A2A9E9E000063B9F7 /* NetworkProtectionFeatureVisibility.swift */; };
4B957A482AC7AE700062CA31 /* PermissionContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64C852926942AC90048FEBE /* PermissionContextMenu.swift */; };
@@ -2049,7 +2047,7 @@
4BA7C4DA2B3F639800AFE511 /* NetworkProtectionTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8F52402A18326600BE7131 /* NetworkProtectionTunnelController.swift */; };
4BA7C4DB2B3F63AE00AFE511 /* NetworkExtensionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD1688D2AD4A4C400D24876 /* NetworkExtensionController.swift */; };
4BA7C4DD2B3F64E500AFE511 /* LoginItems in Frameworks */ = {isa = PBXBuildFile; productRef = 4BA7C4DC2B3F64E500AFE511 /* LoginItems */; };
- 4BA7C4E12B3F6F8600AFE511 /* NetworkProtectionAppExtension.appex in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4B4D603D2A0B290200BCD287 /* NetworkProtectionAppExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 4BA7C4E12B3F6F8600AFE511 /* NetworkProtectionAppExtension.appex in Embed Network Extensions */ = {isa = PBXBuildFile; fileRef = 4B4D603D2A0B290200BCD287 /* NetworkProtectionAppExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4BB6CE5F26B77ED000EC5860 /* Cryptography.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB6CE5E26B77ED000EC5860 /* Cryptography.swift */; };
4BB88B4525B7B55C006F6B06 /* DebugUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB88B4425B7B55C006F6B06 /* DebugUserScript.swift */; };
4BB88B4A25B7B690006F6B06 /* SequenceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB88B4925B7B690006F6B06 /* SequenceExtensions.swift */; };
@@ -2133,7 +2131,7 @@
4BF97AD62B43C45800EB4240 /* NetworkProtectionNavBarPopoverManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3618C12ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift */; };
4BF97AD72B43C53D00EB4240 /* NetworkProtectionIPCTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA7CC4D2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift */; };
4BF97AD82B43C5B300EB4240 /* NetworkProtectionAppEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2DDCF72A93A8BB0039D884 /* NetworkProtectionAppEvents.swift */; };
- 4BF97AD92B43C5C000EB4240 /* NetworkProtectionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* NetworkProtectionBundle.swift */; };
+ 4BF97AD92B43C5C000EB4240 /* Bundle+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* Bundle+VPN.swift */; };
4BF97ADA2B43C5DC00EB4240 /* VPNFeedbackCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0526632B1D55D80054955A /* VPNFeedbackCategory.swift */; };
4BF97ADB2B43C5E000EB4240 /* VPNMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B05265D2B1AE5C70054955A /* VPNMetadataCollector.swift */; };
4BF97ADC2B43C5E200EB4240 /* VPNFeedbackSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0526602B1D55320054955A /* VPNFeedbackSender.swift */; };
@@ -2174,6 +2172,14 @@
56D145F229E6F06D00E3488A /* MockBookmarkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D145F029E6F06D00E3488A /* MockBookmarkManager.swift */; };
56D6A3D629DB2BAB0055215A /* ContinueSetUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D6A3D529DB2BAB0055215A /* ContinueSetUpView.swift */; };
56D6A3D729DB2BAB0055215A /* ContinueSetUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D6A3D529DB2BAB0055215A /* ContinueSetUpView.swift */; };
+ 7B0099792B65013800FE7C31 /* BrowserWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0099782B65013800FE7C31 /* BrowserWindowManager.swift */; };
+ 7B00997D2B6508B700FE7C31 /* NetworkProtectionProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 7B00997C2B6508B700FE7C31 /* NetworkProtectionProxy */; };
+ 7B00997F2B6508C200FE7C31 /* NetworkProtectionProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 7B00997E2B6508C200FE7C31 /* NetworkProtectionProxy */; };
+ 7B0099822B65C6B300FE7C31 /* MacTransparentProxyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0099802B65C6B300FE7C31 /* MacTransparentProxyProvider.swift */; };
+ 7B0694982B6E980F00FA4DBA /* VPNProxyLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0694972B6E980F00FA4DBA /* VPNProxyLauncher.swift */; };
+ 7B1459542B7D437200047F2C /* BrowserWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0099782B65013800FE7C31 /* BrowserWindowManager.swift */; };
+ 7B1459552B7D438F00047F2C /* VPNProxyLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0694972B6E980F00FA4DBA /* VPNProxyLauncher.swift */; };
+ 7B1459572B7D43E500047F2C /* NetworkProtectionProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 7B1459562B7D43E500047F2C /* NetworkProtectionProxy */; };
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 */; };
@@ -2190,15 +2196,25 @@
7B4CE8E726F02135009134B1 /* TabBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4CE8E626F02134009134B1 /* TabBarTests.swift */; };
7B5DD69A2AE51FFA001DE99C /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7B5DD6992AE51FFA001DE99C /* PixelKit */; };
7B5F9A752AE2BE4E002AEBC0 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7B5F9A742AE2BE4E002AEBC0 /* PixelKit */; };
+ 7B7DFB202B7E736B009EA1A3 /* MacPacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */; };
+ 7B7DFB222B7E7473009EA1A3 /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = 7B7DFB212B7E7473009EA1A3 /* Networking */; };
7B8C083C2AE1268E00F4C67F /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7B8C083B2AE1268E00F4C67F /* PixelKit */; };
7B8DB31A2B504D7500EC16DA /* VPNAppEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8DB3192B504D7500EC16DA /* VPNAppEventsHandler.swift */; };
7B934C412A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; };
+ 7B94E1652B7ED95100E32B96 /* NetworkProtectionProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 7B94E1642B7ED95100E32B96 /* NetworkProtectionProxy */; };
+ 7B97CD592B7E0B57004FEF43 /* NetworkProtectionProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 7B97CD582B7E0B57004FEF43 /* NetworkProtectionProxy */; };
+ 7B97CD5B2B7E0B85004FEF43 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 7B97CD5A2B7E0B85004FEF43 /* Common */; };
+ 7B97CD5C2B7E0BBB004FEF43 /* UserDefaultsWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C6A29525CC1FFD00EEB5F1 /* UserDefaultsWrapper.swift */; };
+ 7B97CD5D2B7E0BCE004FEF43 /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106B9D26A565DA0013B453 /* BundleExtension.swift */; };
+ 7B97CD5E2B7E0BEA004FEF43 /* OptionalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B637273C26CCF0C200C8CB02 /* OptionalExtension.swift */; };
+ 7B97CD5F2B7E0BF7004FEF43 /* NSApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5C8F622591021700748EB7 /* NSApplicationExtension.swift */; };
+ 7B97CD602B7E0C2E004FEF43 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85799C1725DEBB3F0007EC87 /* Logging.swift */; };
+ 7B97CD622B7E0C4B004FEF43 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7B97CD612B7E0C4B004FEF43 /* PixelKit */; };
+ 7BA076BB2B65D61400D7FB72 /* NetworkProtectionProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 7BA076BA2B65D61400D7FB72 /* NetworkProtectionProxy */; };
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 */; };
@@ -2214,8 +2230,8 @@
7BA7CC4C2AD11EC70042E5CE /* NetworkProtectionControllerErrorStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D606A2A0B29FA00BCD287 /* NetworkProtectionControllerErrorStore.swift */; };
7BA7CC4E2AD11F6F0042E5CE /* 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 */; };
+ 7BA7CC532AD11FCE0042E5CE /* Bundle+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* Bundle+VPN.swift */; };
+ 7BA7CC542AD11FCE0042E5CE /* Bundle+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605E2A0B29FA00BCD287 /* Bundle+VPN.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 */; };
@@ -2233,9 +2249,13 @@
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 */; };
+ 7BBE2B7B2B61663C00697445 /* NetworkProtectionProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 7BBE2B7A2B61663C00697445 /* NetworkProtectionProxy */; };
7BD01C192AD8319C0088B32E /* IPCServiceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD01C182AD8319C0088B32E /* IPCServiceManager.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 */; };
+ 7BDA36E62B7E037100AD5388 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B4D603E2A0B290200BCD287 /* NetworkExtension.framework */; };
+ 7BDA36F52B7E055800AD5388 /* MacTransparentProxyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0099802B65C6B300FE7C31 /* MacTransparentProxyProvider.swift */; };
+ 7BDA36F92B7E084A00AD5388 /* VPNProxyExtension.appex in Embed Network Extensions */ = {isa = PBXBuildFile; fileRef = 7BDA36E52B7E037100AD5388 /* VPNProxyExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
7BE146072A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */; };
7BE146082A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */; };
7BEC182F2AD5D8DC00D30536 /* SystemExtensionManager in Frameworks */ = {isa = PBXBuildFile; productRef = 7BEC182E2AD5D8DC00D30536 /* SystemExtensionManager */; };
@@ -3104,7 +3124,6 @@
EEC8EB402982CD550065AA39 /* JSAlertViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */; };
EECE10E529DD77E60044D027 /* FeatureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECE10E429DD77E60044D027 /* FeatureFlag.swift */; };
EECE10E629DD77E60044D027 /* FeatureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECE10E429DD77E60044D027 /* FeatureFlag.swift */; };
- EEF12E6E2A2111880023E6BF /* MacPacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */; };
EEF12E6F2A2111880023E6BF /* MacPacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */; };
EEF53E182950CED5002D78F4 /* JSAlertViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */; };
F41D174125CB131900472416 /* NSColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41D174025CB131900472416 /* NSColorExtension.swift */; };
@@ -3185,6 +3204,13 @@
remoteGlobalIDString = AA585D7D248FD31100E9A3E2;
remoteInfo = "DuckDuckGo Privacy Browser";
};
+ 7BDA36F72B7E082100AD5388 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = AA585D76248FD31100E9A3E2 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 7BDA36E42B7E037100AD5388;
+ remoteInfo = VPNProxyExtension;
+ };
7BEC18302AD5DA3300D30536 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = AA585D76248FD31100E9A3E2 /* Project object */;
@@ -3242,14 +3268,16 @@
name = "Embed Login Items";
runOnlyForDeploymentPostprocessing = 0;
};
- 4BA7C4E02B3F6F7500AFE511 /* CopyFiles */ = {
+ 4BA7C4E02B3F6F7500AFE511 /* Embed Network Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
- 4BA7C4E12B3F6F8600AFE511 /* NetworkProtectionAppExtension.appex in CopyFiles */,
+ 7BDA36F92B7E084A00AD5388 /* VPNProxyExtension.appex in Embed Network Extensions */,
+ 4BA7C4E12B3F6F8600AFE511 /* NetworkProtectionAppExtension.appex in Embed Network Extensions */,
);
+ name = "Embed Network Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
B6EC37E629B5DA2A001ACE79 /* CopyFiles */ = {
@@ -3546,7 +3574,7 @@
4B4D604F2A0B293C00BCD287 /* NetworkProtectionSystemExtension.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetworkProtectionSystemExtension.xcconfig; sourceTree = ""; };
4B4D60502A0B293C00BCD287 /* NetworkProtectionAppExtension.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetworkProtectionAppExtension.xcconfig; sourceTree = ""; };
4B4D60512A0B293C00BCD287 /* ExtensionBase.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ExtensionBase.xcconfig; sourceTree = ""; };
- 4B4D605E2A0B29FA00BCD287 /* NetworkProtectionBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionBundle.swift; sourceTree = ""; };
+ 4B4D605E2A0B29FA00BCD287 /* Bundle+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+VPN.swift"; sourceTree = ""; };
4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionOptionKeyExtension.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 = ""; };
@@ -3556,10 +3584,7 @@
4B4D60702A0B29FA00BCD287 /* NetworkProtectionInviteCodeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionInviteCodeViewModel.swift; sourceTree = ""; };
4B4D60722A0B29FA00BCD287 /* EventMapping+NetworkProtectionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EventMapping+NetworkProtectionError.swift"; sourceTree = ""; };
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 = ""; };
- 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 = ""; };
4B4D60E12A0C883A00BCD287 /* AppMain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppMain.swift; sourceTree = ""; };
4B4F72EB266B2ED300814C60 /* CollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtension.swift; sourceTree = ""; };
@@ -3766,7 +3791,10 @@
56D145ED29E6DAD900E3488A /* DataImportProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataImportProviderTests.swift; sourceTree = ""; };
56D145F029E6F06D00E3488A /* MockBookmarkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBookmarkManager.swift; sourceTree = ""; };
56D6A3D529DB2BAB0055215A /* ContinueSetUpView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContinueSetUpView.swift; sourceTree = ""; };
+ 7B0099782B65013800FE7C31 /* BrowserWindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserWindowManager.swift; sourceTree = ""; };
+ 7B0099802B65C6B300FE7C31 /* MacTransparentProxyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacTransparentProxyProvider.swift; sourceTree = ""; };
7B05829D2A812AC000AC3F7C /* NetworkProtectionOnboardingMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionOnboardingMenu.swift; sourceTree = ""; };
+ 7B0694972B6E980F00FA4DBA /* VPNProxyLauncher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNProxyLauncher.swift; sourceTree = ""; };
7B1E819B27C8874900FF0E60 /* ContentOverlayPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentOverlayPopover.swift; sourceTree = ""; };
7B1E819C27C8874900FF0E60 /* ContentOverlay.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = ContentOverlay.storyboard; sourceTree = ""; };
7B1E819D27C8874900FF0E60 /* ContentOverlayViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentOverlayViewController.swift; sourceTree = ""; };
@@ -3790,7 +3818,6 @@
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 = ""; };
@@ -3809,6 +3836,10 @@
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 = ""; };
+ 7BDA36E52B7E037100AD5388 /* VPNProxyExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = VPNProxyExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ 7BDA36EA2B7E037200AD5388 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 7BDA36EB2B7E037200AD5388 /* VPNProxyExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = VPNProxyExtension.entitlements; sourceTree = ""; };
+ 7BDA36F62B7E06A300AD5388 /* VPNProxyExtension.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = VPNProxyExtension.xcconfig; sourceTree = ""; };
7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugMenu.swift; sourceTree = ""; };
7BEC182D2AD5D89C00D30536 /* SystemExtensionManager */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SystemExtensionManager; sourceTree = ""; };
7BEC20402B0F505F00243D3E /* AddBookmarkPopoverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddBookmarkPopoverView.swift; sourceTree = ""; };
@@ -4443,6 +4474,7 @@
373FB4B32B4D6C4B004C88D6 /* PreferencesViews in Frameworks */,
7B5F9A752AE2BE4E002AEBC0 /* PixelKit in Frameworks */,
4BF97AD32B43C43F00EB4240 /* NetworkProtectionUI in Frameworks */,
+ 7B1459572B7D43E500047F2C /* NetworkProtectionProxy in Frameworks */,
B6F7128229F6820A00594A45 /* QuickLookUI.framework in Frameworks */,
984FD3BF299ACF35007334DD /* Bookmarks in Frameworks */,
37A5E2F0298AA1B20047046B /* Persistence in Frameworks */,
@@ -4510,6 +4542,7 @@
37269F012B332FC8005E8E46 /* Common in Frameworks */,
EE7295E92A545BC4008C0991 /* NetworkProtection in Frameworks */,
4B2537772A11BFE100610219 /* PixelKit in Frameworks */,
+ 7BBE2B7B2B61663C00697445 /* NetworkProtectionProxy in Frameworks */,
4B2D062C2A11C0E100DE1F49 /* Networking in Frameworks */,
4B25375B2A11BE7300610219 /* NetworkExtension.framework in Frameworks */,
);
@@ -4520,6 +4553,7 @@
buildActionMask = 2147483647;
files = (
4B41EDAB2B1544B2001EEDF4 /* LoginItems in Frameworks */,
+ 7B00997D2B6508B700FE7C31 /* NetworkProtectionProxy in Frameworks */,
7BEEA5122AD1235B00A9E72B /* NetworkProtectionIPC in Frameworks */,
7BA7CC5F2AD1210C0042E5CE /* Networking in Frameworks */,
7BEEA5162AD1236E00A9E72B /* NetworkProtectionUI in Frameworks */,
@@ -4536,6 +4570,7 @@
7BFCB7502ADE7E2300DA3EA7 /* PixelKit in Frameworks */,
7BA7CC612AD1211C0042E5CE /* Networking in Frameworks */,
7BEEA5142AD1236300A9E72B /* NetworkProtectionIPC in Frameworks */,
+ 7B00997F2B6508C200FE7C31 /* NetworkProtectionProxy in Frameworks */,
EE7295EF2A545C12008C0991 /* NetworkProtection in Frameworks */,
4B2D067F2A1334D700DE1F49 /* NetworkProtectionUI in Frameworks */,
4BA7C4DD2B3F64E500AFE511 /* LoginItems in Frameworks */,
@@ -4571,6 +4606,7 @@
3143C8792B0D1F3D00382627 /* DataBrokerProtection in Frameworks */,
372217842B33380E00B8E9C2 /* TestUtils in Frameworks */,
4B957BD62AC7AE700062CA31 /* LoginItems in Frameworks */,
+ 7B94E1652B7ED95100E32B96 /* NetworkProtectionProxy in Frameworks */,
4B957BD72AC7AE700062CA31 /* NetworkProtection in Frameworks */,
4B957BD82AC7AE700062CA31 /* BrowserServicesKit in Frameworks */,
4B957BDA2AC7AE700062CA31 /* Bookmarks in Frameworks */,
@@ -4613,6 +4649,18 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 7BDA36E22B7E037100AD5388 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7BDA36E62B7E037100AD5388 /* NetworkExtension.framework in Frameworks */,
+ 7B97CD592B7E0B57004FEF43 /* NetworkProtectionProxy in Frameworks */,
+ 7B97CD622B7E0C4B004FEF43 /* PixelKit in Frameworks */,
+ 7B7DFB222B7E7473009EA1A3 /* Networking in Frameworks */,
+ 7B97CD5B2B7E0B85004FEF43 /* Common in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
9D9AE8C62AAA39A70026E7DC /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -4661,6 +4709,7 @@
37DF000529F9C056002B7D3E /* SyncDataProviders in Frameworks */,
37BA812D29B3CD690053F1A3 /* SyncUI in Frameworks */,
372217802B3337FE00B8E9C2 /* TestUtils in Frameworks */,
+ 7BA076BB2B65D61400D7FB72 /* NetworkProtectionProxy in Frameworks */,
4B4D60B12A0C83B900BCD287 /* NetworkProtectionUI in Frameworks */,
98A50964294B691800D10880 /* Persistence in Frameworks */,
);
@@ -5240,8 +5289,9 @@
4B18E32C2A1ECF1F005D0AAA /* NetworkProtection */ = {
isa = PBXGroup;
children = (
- 4B4D604F2A0B293C00BCD287 /* NetworkProtectionSystemExtension.xcconfig */,
4B4D60502A0B293C00BCD287 /* NetworkProtectionAppExtension.xcconfig */,
+ 4B4D604F2A0B293C00BCD287 /* NetworkProtectionSystemExtension.xcconfig */,
+ 7BDA36F62B7E06A300AD5388 /* VPNProxyExtension.xcconfig */,
);
path = NetworkProtection;
sourceTree = "";
@@ -5379,7 +5429,7 @@
4B4D605D2A0B29FA00BCD287 /* AppAndExtensionAndNotificationTargets */ = {
isa = PBXGroup;
children = (
- 4B4D605E2A0B29FA00BCD287 /* NetworkProtectionBundle.swift */,
+ 4B4D605E2A0B29FA00BCD287 /* Bundle+VPN.swift */,
4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */,
B602E8152A1E2570006D261F /* URL+NetworkProtection.swift */,
);
@@ -5463,7 +5513,6 @@
isa = PBXGroup;
children = (
B602E81F2A1E2603006D261F /* Bundle+NetworkProtectionExtensions.swift */,
- 4B4D60782A0B29FA00BCD287 /* NetworkProtectionExtensionMachService.swift */,
);
path = SystemExtensionAndNotificationTargets;
sourceTree = "";
@@ -5481,6 +5530,7 @@
children = (
4B41ED9F2B15437A001EEDF4 /* NetworkProtectionNotificationsPresenterFactory.swift */,
EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */,
+ 7B0099802B65C6B300FE7C31 /* MacTransparentProxyProvider.swift */,
);
path = NetworkExtensionTargets;
sourceTree = "";
@@ -6126,11 +6176,11 @@
isa = PBXGroup;
children = (
7BA7CC132AD11DC80042E5CE /* AppLauncher+DefaultInitializer.swift */,
- 7BA7CC0F2AD11DC80042E5CE /* Bundle+Configuration.swift */,
7BA7CC0E2AD11DC80042E5CE /* DuckDuckGoVPNAppDelegate.swift */,
7BD1688D2AD4A4C400D24876 /* NetworkExtensionController.swift */,
7BA7CC152AD11DC80042E5CE /* NetworkProtectionBouncer.swift */,
7B8DB3192B504D7500EC16DA /* VPNAppEventsHandler.swift */,
+ 7B0694972B6E980F00FA4DBA /* VPNProxyLauncher.swift */,
7BA7CC112AD11DC80042E5CE /* TunnelControllerIPCService.swift */,
7BA7CC172AD11DC80042E5CE /* UserText.swift */,
7BA7CC122AD11DC80042E5CE /* Assets.xcassets */,
@@ -6160,6 +6210,15 @@
path = LetsMove1.25;
sourceTree = "";
};
+ 7BDA36E72B7E037200AD5388 /* VPNProxyExtension */ = {
+ isa = PBXGroup;
+ children = (
+ 7BDA36EA2B7E037200AD5388 /* Info.plist */,
+ 7BDA36EB2B7E037200AD5388 /* VPNProxyExtension.entitlements */,
+ );
+ path = VPNProxyExtension;
+ sourceTree = "";
+ };
853014D425E6709500FB8205 /* Support */ = {
isa = PBXGroup;
children = (
@@ -6523,6 +6582,7 @@
9D9AE9152AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift */,
9D9AE9282AAA43EB0026E7DC /* DataBrokerProtectionBackgroundManager.swift */,
7BD01C182AD8319C0088B32E /* IPCServiceManager.swift */,
+ 7B0099782B65013800FE7C31 /* BrowserWindowManager.swift */,
9D9AE92B2AAB84FF0026E7DC /* DBPMocks.swift */,
9D9AE9172AAA3B450026E7DC /* UserText.swift */,
9D9AE9162AAA3B450026E7DC /* Assets.xcassets */,
@@ -6646,6 +6706,7 @@
B6EC37E929B5DA2A001ACE79 /* tests-server */,
7B96D0D02ADFDA7F007E02C8 /* DuckDuckGoDBPTests */,
4B5F14F72A148B230060320F /* NetworkProtectionAppExtension */,
+ 7BDA36E72B7E037200AD5388 /* VPNProxyExtension */,
4B25375C2A11BE7500610219 /* NetworkProtectionSystemExtension */,
9D9AE9132AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgent */,
7BA7CC0D2AD11DC80042E5CE /* DuckDuckGoVPN */,
@@ -6676,6 +6737,7 @@
4B957C412AC7AE700062CA31 /* DuckDuckGo Privacy Pro.app */,
565E46DD2B2725DC0013AC2A /* SyncE2EUITests.xctest */,
376113D42B29CD5B00E794BB /* SyncE2EUITests App Store.xctest */,
+ 7BDA36E52B7E037100AD5388 /* VPNProxyExtension.appex */,
);
name = Products;
sourceTree = "";
@@ -6746,8 +6808,6 @@
4B5F15032A1570F10060320F /* DuckDuckGoDebug.entitlements */,
37D9BBA329376EE8000B99F9 /* DuckDuckGoAppStore.entitlements */,
377E54382937B7C400780A0A /* DuckDuckGoAppStoreCI.entitlements */,
- 4B4D609E2A0B2C2300BCD287 /* DuckDuckGo_NetP_Debug.entitlements */,
- 4B4D609C2A0B2C2300BCD287 /* DuckDuckGo_NetP_Release.entitlements */,
4B2D06642A132F3A00DE1F49 /* NetworkProtectionAppExtension.entitlements */,
4B5F14C42A145D6A0060320F /* NetworkProtectionVPNController.entitlements */,
56CEE9092B7A66C500CF10AA /* Info.plist */,
@@ -8280,6 +8340,7 @@
4BF97AD42B43C43F00EB4240 /* NetworkProtection */,
373FB4B22B4D6C4B004C88D6 /* PreferencesViews */,
312978892B64131200B67619 /* DataBrokerProtection */,
+ 7B1459562B7D43E500047F2C /* NetworkProtectionProxy */,
);
productName = DuckDuckGo;
productReference = 3706FD05293F65D500E42796 /* DuckDuckGo App Store.app */;
@@ -8395,6 +8456,7 @@
4B2D062B2A11C0E100DE1F49 /* Networking */,
EE7295E82A545BC4008C0991 /* NetworkProtection */,
37269F002B332FC8005E8E46 /* Common */,
+ 7BBE2B7A2B61663C00697445 /* NetworkProtectionProxy */,
);
productName = NetworkProtectionSystemExtension;
productReference = 4B25375A2A11BE7300610219 /* com.duckduckgo.macos.vpn.network-extension.debug.systemextension */;
@@ -8425,6 +8487,7 @@
7BEC182E2AD5D8DC00D30536 /* SystemExtensionManager */,
7BFCB74D2ADE7E1A00DA3EA7 /* PixelKit */,
4B41EDAA2B1544B2001EEDF4 /* LoginItems */,
+ 7B00997C2B6508B700FE7C31 /* NetworkProtectionProxy */,
);
productName = DuckDuckGoAgent;
productReference = 4B2D06392A11CFBB00DE1F49 /* DuckDuckGo VPN.app */;
@@ -8438,11 +8501,12 @@
4B2D06662A13318400DE1F49 /* Frameworks */,
4B2D06672A13318400DE1F49 /* Resources */,
4B2D067D2A13341200DE1F49 /* ShellScript */,
- 4BA7C4E02B3F6F7500AFE511 /* CopyFiles */,
+ 4BA7C4E02B3F6F7500AFE511 /* Embed Network Extensions */,
);
buildRules = (
);
dependencies = (
+ 7BDA36F82B7E082100AD5388 /* PBXTargetDependency */,
4BA7C4DF2B3F6F4900AFE511 /* PBXTargetDependency */,
B6080BA52B20AF8800B418EF /* PBXTargetDependency */,
);
@@ -8453,6 +8517,7 @@
7BA7CC602AD1211C0042E5CE /* Networking */,
7BEEA5132AD1236300A9E72B /* NetworkProtectionIPC */,
7BFCB74F2ADE7E2300DA3EA7 /* PixelKit */,
+ 7B00997E2B6508C200FE7C31 /* NetworkProtectionProxy */,
4BA7C4DC2B3F64E500AFE511 /* LoginItems */,
);
productName = DuckDuckGoAgentAppStore;
@@ -8552,6 +8617,7 @@
372217832B33380E00B8E9C2 /* TestUtils */,
373FB4B42B4D6C57004C88D6 /* PreferencesViews */,
1E21F8E22B73E48600FB272E /* Subscription */,
+ 7B94E1642B7ED95100E32B96 /* NetworkProtectionProxy */,
);
productName = DuckDuckGo;
productReference = 4B957C412AC7AE700062CA31 /* DuckDuckGo Privacy Pro.app */;
@@ -8596,6 +8662,29 @@
productReference = 7B4CE8DA26F02108009134B1 /* UI Tests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
+ 7BDA36E42B7E037100AD5388 /* VPNProxyExtension */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 7BDA36F42B7E037200AD5388 /* Build configuration list for PBXNativeTarget "VPNProxyExtension" */;
+ buildPhases = (
+ 7BDA36E12B7E037100AD5388 /* Sources */,
+ 7BDA36E22B7E037100AD5388 /* Frameworks */,
+ 7BDA36E32B7E037100AD5388 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = VPNProxyExtension;
+ packageProductDependencies = (
+ 7B97CD582B7E0B57004FEF43 /* NetworkProtectionProxy */,
+ 7B97CD5A2B7E0B85004FEF43 /* Common */,
+ 7B97CD612B7E0C4B004FEF43 /* PixelKit */,
+ 7B7DFB212B7E7473009EA1A3 /* Networking */,
+ );
+ productName = VPNProxyExtension;
+ productReference = 7BDA36E52B7E037100AD5388 /* VPNProxyExtension.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
9D9AE8B22AAA39A70026E7DC /* DuckDuckGoDBPBackgroundAgent */ = {
isa = PBXNativeTarget;
buildConfigurationList = 9D9AE8CC2AAA39A70026E7DC /* Build configuration list for PBXNativeTarget "DuckDuckGoDBPBackgroundAgent" */;
@@ -8690,6 +8779,7 @@
37269EFA2B332F9E005E8E46 /* Common */,
3722177F2B3337FE00B8E9C2 /* TestUtils */,
373FB4B02B4D6C42004C88D6 /* PreferencesViews */,
+ 7BA076BA2B65D61400D7FB72 /* NetworkProtectionProxy */,
);
productName = DuckDuckGo;
productReference = AA585D7E248FD31100E9A3E2 /* DuckDuckGo.app */;
@@ -8746,7 +8836,7 @@
AA585D76248FD31100E9A3E2 /* Project object */ = {
isa = PBXProject;
attributes = {
- LastSwiftUpdateCheck = 1500;
+ LastSwiftUpdateCheck = 1520;
LastUpgradeCheck = 1400;
ORGANIZATIONNAME = DuckDuckGo;
TargetAttributes = {
@@ -8788,6 +8878,9 @@
CreatedOnToolsVersion = 12.5.1;
TestTargetID = AA585D7D248FD31100E9A3E2;
};
+ 7BDA36E42B7E037100AD5388 = {
+ CreatedOnToolsVersion = 15.2;
+ };
AA585D7D248FD31100E9A3E2 = {
CreatedOnToolsVersion = 11.5;
};
@@ -8833,6 +8926,7 @@
3706FE9B293F662100E42796 /* Integration Tests App Store */,
B6EC37E729B5DA2A001ACE79 /* tests-server */,
4B4D603C2A0B290200BCD287 /* NetworkProtectionAppExtension */,
+ 7BDA36E42B7E037100AD5388 /* VPNProxyExtension */,
4B2537592A11BE7300610219 /* NetworkProtectionSystemExtension */,
4B4BEC1F2A11B4E2001D9AC5 /* DuckDuckGoNotifications */,
4B2D06382A11CFBA00DE1F49 /* DuckDuckGoVPN */,
@@ -9050,6 +9144,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 7BDA36E32B7E037100AD5388 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
9D9AE8C92AAA39A70026E7DC /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -9861,7 +9962,7 @@
B66260E829ACD0C900E9E3EE /* DuckPlayerTabExtension.swift in Sources */,
3706FBAA293F65D500E42796 /* HoverUserScript.swift in Sources */,
3706FBAC293F65D500E42796 /* MainMenuActions.swift in Sources */,
- 4BF97AD92B43C5C000EB4240 /* NetworkProtectionBundle.swift in Sources */,
+ 4BF97AD92B43C5C000EB4240 /* Bundle+VPN.swift in Sources */,
3706FBAE293F65D500E42796 /* DataImport.swift in Sources */,
3706FBAF293F65D500E42796 /* FireproofDomains.xcdatamodeld in Sources */,
B626A7552991413000053070 /* SerpHeadersNavigationResponder.swift in Sources */,
@@ -10520,11 +10621,11 @@
4B2D06332A11C1E300DE1F49 /* OptionalExtension.swift in Sources */,
4BF0E50B2AD2552200FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */,
4B41EDA12B15437A001EEDF4 /* NetworkProtectionNotificationsPresenterFactory.swift in Sources */,
- 4B2537782A11C00F00610219 /* NetworkProtectionExtensionMachService.swift in Sources */,
+ 7B0099822B65C6B300FE7C31 /* MacTransparentProxyProvider.swift in Sources */,
B65DA5F32A77D3C700CBEE8D /* UserDefaultsWrapper.swift in Sources */,
4B2537722A11BF8B00610219 /* main.swift in Sources */,
EEF12E6F2A2111880023E6BF /* MacPacketTunnelProvider.swift in Sources */,
- 4B2D06292A11C0C900DE1F49 /* NetworkProtectionBundle.swift in Sources */,
+ 4B2D06292A11C0C900DE1F49 /* Bundle+VPN.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -10543,14 +10644,14 @@
7BA7CC4C2AD11EC70042E5CE /* NetworkProtectionControllerErrorStore.swift in Sources */,
B6F92BAC2A6937B3002ABA6B /* OptionalExtension.swift in Sources */,
7B8DB31A2B504D7500EC16DA /* VPNAppEventsHandler.swift in Sources */,
- 7BA7CC532AD11FCE0042E5CE /* NetworkProtectionBundle.swift in Sources */,
- 7BA7CC3C2AD11E330042E5CE /* Bundle+Configuration.swift in Sources */,
+ 7BA7CC532AD11FCE0042E5CE /* Bundle+VPN.swift in Sources */,
7BFE95562A9DF29B0081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */,
7BA7CC5D2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */,
7BA7CC4A2AD11EA00042E5CE /* NetworkProtectionTunnelController.swift in Sources */,
7BD1688E2AD4A4C400D24876 /* NetworkExtensionController.swift in Sources */,
7BA7CC3E2AD11E380042E5CE /* TunnelControllerIPCService.swift in Sources */,
7BA7CC402AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */,
+ 7B0694982B6E980F00FA4DBA /* VPNProxyLauncher.swift in Sources */,
EEC589DB2A4F1CE700BCD60C /* AppLauncher.swift in Sources */,
B65DA5EF2A77CC3A00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */,
4BF0E5072AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */,
@@ -10569,10 +10670,10 @@
B6F92BA32A691583002ABA6B /* UserDefaultsWrapper.swift in Sources */,
4BA7C4DB2B3F63AE00AFE511 /* NetworkExtensionController.swift in Sources */,
4B2D067C2A13340900DE1F49 /* Logging.swift in Sources */,
+ 7B1459552B7D438F00047F2C /* VPNProxyLauncher.swift in Sources */,
B6F92BAD2A6937B5002ABA6B /* OptionalExtension.swift in Sources */,
4BA7C4D92B3F61FB00AFE511 /* BundleExtension.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 */,
4B0EF7292B5780EB009D6481 /* VPNAppEventsHandler.swift in Sources */,
@@ -10590,7 +10691,7 @@
7BA7CC3D2AD11E380042E5CE /* TunnelControllerIPCService.swift in Sources */,
4BA7C4DA2B3F639800AFE511 /* NetworkProtectionTunnelController.swift in Sources */,
7BA7CC432AD11E480042E5CE /* UserText.swift in Sources */,
- 7BA7CC542AD11FCE0042E5CE /* NetworkProtectionBundle.swift in Sources */,
+ 7BA7CC542AD11FCE0042E5CE /* Bundle+VPN.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -10601,12 +10702,11 @@
4B4BEC3D2A11B56B001D9AC5 /* DuckDuckGoNotificationsAppDelegate.swift in Sources */,
4B4BEC3E2A11B56E001D9AC5 /* Logging.swift in Sources */,
4B4BEC412A11B5BD001D9AC5 /* NetworkProtectionUNNotificationsPresenter.swift in Sources */,
- 4B4BEC432A11B5C7001D9AC5 /* NetworkProtectionBundle.swift in Sources */,
+ 4B4BEC432A11B5C7001D9AC5 /* Bundle+VPN.swift in Sources */,
4B4BEC452A11B5EE001D9AC5 /* UserText+NetworkProtectionExtensions.swift in Sources */,
4B4BEC422A11B5C7001D9AC5 /* NetworkProtectionOptionKeyExtension.swift in Sources */,
B602E8222A1E2603006D261F /* Bundle+NetworkProtectionExtensions.swift in Sources */,
B602E81A2A1E2570006D261F /* URL+NetworkProtection.swift in Sources */,
- 4B4BEC402A11B5B5001D9AC5 /* NetworkProtectionExtensionMachService.swift in Sources */,
EEAD7A7C2A1D3E20002A24E7 /* AppLauncher.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -10617,14 +10717,14 @@
files = (
4B41EDA02B15437A001EEDF4 /* NetworkProtectionNotificationsPresenterFactory.swift in Sources */,
4B4D609F2A0B2C7300BCD287 /* Logging.swift in Sources */,
+ 7B7DFB202B7E736B009EA1A3 /* MacPacketTunnelProvider.swift in Sources */,
4B4D60A12A0B2D6100BCD287 /* NetworkProtectionOptionKeyExtension.swift in Sources */,
B602E8182A1E2570006D261F /* URL+NetworkProtection.swift in Sources */,
B65DA5F52A77D3FA00CBEE8D /* BundleExtension.swift in Sources */,
4B4D60892A0B2A1C00BCD287 /* NetworkProtectionUNNotificationsPresenter.swift in Sources */,
- 4B4D60A02A0B2D5B00BCD287 /* NetworkProtectionBundle.swift in Sources */,
+ 4B4D60A02A0B2D5B00BCD287 /* Bundle+VPN.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 */,
B65DA5F22A77D3C600CBEE8D /* UserDefaultsWrapper.swift in Sources */,
@@ -10909,7 +11009,7 @@
4B957A422AC7AE700062CA31 /* SafariFaviconsReader.swift in Sources */,
4B957A432AC7AE700062CA31 /* NSScreenExtension.swift in Sources */,
4B957A442AC7AE700062CA31 /* NSBezierPathExtension.swift in Sources */,
- 4B957A452AC7AE700062CA31 /* NetworkProtectionBundle.swift in Sources */,
+ 4B957A452AC7AE700062CA31 /* Bundle+VPN.swift in Sources */,
B68D21CA2ACBC971002DA3C2 /* MockPrivacyConfiguration.swift in Sources */,
4B957A462AC7AE700062CA31 /* WebsiteDataStore.swift in Sources */,
4B957A472AC7AE700062CA31 /* NetworkProtectionFeatureVisibility.swift in Sources */,
@@ -11378,11 +11478,25 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 7BDA36E12B7E037100AD5388 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7B97CD5C2B7E0BBB004FEF43 /* UserDefaultsWrapper.swift in Sources */,
+ 7B97CD602B7E0C2E004FEF43 /* Logging.swift in Sources */,
+ 7B97CD5E2B7E0BEA004FEF43 /* OptionalExtension.swift in Sources */,
+ 7B97CD5F2B7E0BF7004FEF43 /* NSApplicationExtension.swift in Sources */,
+ 7BDA36F52B7E055800AD5388 /* MacTransparentProxyProvider.swift in Sources */,
+ 7B97CD5D2B7E0BCE004FEF43 /* BundleExtension.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
9D9AE8B62AAA39A70026E7DC /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9D9AE92C2AAB84FF0026E7DC /* DBPMocks.swift in Sources */,
+ 7B0099792B65013800FE7C31 /* BrowserWindowManager.swift in Sources */,
9D9AE9292AAA43EB0026E7DC /* DataBrokerProtectionBackgroundManager.swift in Sources */,
9D9AE91D2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */,
9D9AE9212AAA3B450026E7DC /* UserText.swift in Sources */,
@@ -11394,6 +11508,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 7B1459542B7D437200047F2C /* BrowserWindowManager.swift in Sources */,
9D9AE92D2AAB84FF0026E7DC /* DBPMocks.swift in Sources */,
9D9AE92A2AAA43EB0026E7DC /* DataBrokerProtectionBackgroundManager.swift in Sources */,
9D9AE91E2AAA3B450026E7DC /* DuckDuckGoDBPBackgroundAgentAppDelegate.swift in Sources */,
@@ -11677,7 +11792,7 @@
4B0AACAE28BC6FD0001038AC /* SafariFaviconsReader.swift in Sources */,
B6B3E0E12657EA7A0040E0A2 /* NSScreenExtension.swift in Sources */,
B65E6BA026D9F10600095F96 /* NSBezierPathExtension.swift in Sources */,
- 4B4D60E02A0C875F00BCD287 /* NetworkProtectionBundle.swift in Sources */,
+ 4B4D60E02A0C875F00BCD287 /* Bundle+VPN.swift in Sources */,
AA6820E425502F19005ED0D5 /* WebsiteDataStore.swift in Sources */,
B6F9BDDC2B45B7EE00677B33 /* WebsiteInfo.swift in Sources */,
4B67854A2AA8DE75008A5004 /* NetworkProtectionFeatureVisibility.swift in Sources */,
@@ -12449,6 +12564,11 @@
target = AA585D7D248FD31100E9A3E2 /* DuckDuckGo Privacy Browser */;
targetProxy = 7B4CE8DF26F02108009134B1 /* PBXContainerItemProxy */;
};
+ 7BDA36F82B7E082100AD5388 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 7BDA36E42B7E037100AD5388 /* VPNProxyExtension */;
+ targetProxy = 7BDA36F72B7E082100AD5388 /* PBXContainerItemProxy */;
+ };
7BEC18312AD5DA3300D30536 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4B2537592A11BE7300610219 /* NetworkProtectionSystemExtension */;
@@ -12918,6 +13038,34 @@
};
name = Release;
};
+ 7BDA36F02B7E037200AD5388 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7BDA36F62B7E06A300AD5388 /* VPNProxyExtension.xcconfig */;
+ buildSettings = {
+ };
+ name = Debug;
+ };
+ 7BDA36F12B7E037200AD5388 /* CI */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7BDA36F62B7E06A300AD5388 /* VPNProxyExtension.xcconfig */;
+ buildSettings = {
+ };
+ name = CI;
+ };
+ 7BDA36F22B7E037200AD5388 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7BDA36F62B7E06A300AD5388 /* VPNProxyExtension.xcconfig */;
+ buildSettings = {
+ };
+ name = Release;
+ };
+ 7BDA36F32B7E037200AD5388 /* Review */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7BDA36F62B7E06A300AD5388 /* VPNProxyExtension.xcconfig */;
+ buildSettings = {
+ };
+ name = Review;
+ };
9D9AE8CD2AAA39A70026E7DC /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7B6EC5E52AE2D8AF004FE6DF /* DuckDuckGoDBPAgent.xcconfig */;
@@ -13225,6 +13373,17 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 7BDA36F42B7E037200AD5388 /* Build configuration list for PBXNativeTarget "VPNProxyExtension" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 7BDA36F02B7E037200AD5388 /* Debug */,
+ 7BDA36F12B7E037200AD5388 /* CI */,
+ 7BDA36F22B7E037200AD5388 /* Release */,
+ 7BDA36F32B7E037200AD5388 /* Review */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
9D9AE8CC2AAA39A70026E7DC /* Build configuration list for PBXNativeTarget "DuckDuckGoDBPBackgroundAgent" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -13379,7 +13538,7 @@
repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit";
requirement = {
kind = exactVersion;
- version = 109.0.0;
+ version = 109.0.1;
};
};
AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = {
@@ -13768,6 +13927,18 @@
package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */;
productName = NetworkProtection;
};
+ 7B00997C2B6508B700FE7C31 /* NetworkProtectionProxy */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = NetworkProtectionProxy;
+ };
+ 7B00997E2B6508C200FE7C31 /* NetworkProtectionProxy */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = NetworkProtectionProxy;
+ };
+ 7B1459562B7D43E500047F2C /* NetworkProtectionProxy */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = NetworkProtectionProxy;
+ };
7B31FD8B2AD125620086AA24 /* NetworkProtectionIPC */ = {
isa = XCSwiftPackageProductDependency;
productName = NetworkProtectionIPC;
@@ -13784,10 +13955,36 @@
isa = XCSwiftPackageProductDependency;
productName = PixelKit;
};
+ 7B7DFB212B7E7473009EA1A3 /* Networking */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */;
+ productName = Networking;
+ };
7B8C083B2AE1268E00F4C67F /* PixelKit */ = {
isa = XCSwiftPackageProductDependency;
productName = PixelKit;
};
+ 7B94E1642B7ED95100E32B96 /* NetworkProtectionProxy */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = NetworkProtectionProxy;
+ };
+ 7B97CD582B7E0B57004FEF43 /* NetworkProtectionProxy */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = NetworkProtectionProxy;
+ };
+ 7B97CD5A2B7E0B85004FEF43 /* Common */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */;
+ productName = Common;
+ };
+ 7B97CD612B7E0C4B004FEF43 /* PixelKit */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = PixelKit;
+ };
+ 7BA076BA2B65D61400D7FB72 /* NetworkProtectionProxy */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = NetworkProtectionProxy;
+ };
7BA59C9A2AE18B49009A97B1 /* SystemExtensionManager */ = {
isa = XCSwiftPackageProductDependency;
productName = SystemExtensionManager;
@@ -13806,6 +14003,10 @@
isa = XCSwiftPackageProductDependency;
productName = PixelKit;
};
+ 7BBE2B7A2B61663C00697445 /* NetworkProtectionProxy */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = NetworkProtectionProxy;
+ };
7BEC182E2AD5D8DC00D30536 /* SystemExtensionManager */ = {
isa = XCSwiftPackageProductDependency;
productName = SystemExtensionManager;
diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 4796599b2d..e0a1c42442 100644
--- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -14,8 +14,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/BrowserServicesKit",
"state" : {
- "revision" : "5ecf4fe56f334be6eaecb65f6d55632a6d53921c",
- "version" : "109.0.0"
+ "revision" : "da6a822844922401d80e26963b8b11dcd6ef221a",
+ "version" : "109.0.1"
}
},
{
diff --git a/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift b/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift
index ebe3179a76..2f5d747ade 100644
--- a/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift
+++ b/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift
@@ -23,6 +23,7 @@ import Foundation
import AppKit
import Common
import LoginItems
+import NetworkProtectionProxy
@MainActor
final class DataBrokerProtectionDebugMenu: NSMenu {
@@ -82,6 +83,11 @@ final class DataBrokerProtectionDebugMenu: NSMenu {
NSMenuItem(title: "Restart", action: #selector(DataBrokerProtectionDebugMenu.backgroundAgentRestart))
.targetting(self)
+
+ NSMenuItem.separator()
+
+ NSMenuItem(title: "Show agent IP address", action: #selector(DataBrokerProtectionDebugMenu.showAgentIPAddress))
+ .targetting(self)
}
NSMenuItem(title: "Operations") {
@@ -253,6 +259,10 @@ final class DataBrokerProtectionDebugMenu: NSMenu {
window.delegate = self
}
+ @objc private func showAgentIPAddress() {
+ DataBrokerProtectionManager.shared.showAgentIPAddress()
+ }
+
@objc private func showForceOptOutWindow() {
let viewController = DataBrokerForceOptOutViewController()
let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 500, height: 400),
diff --git a/DuckDuckGo/DBP/DataBrokerProtectionManager.swift b/DuckDuckGo/DBP/DataBrokerProtectionManager.swift
index f2c9bebd4d..75f8c3e855 100644
--- a/DuckDuckGo/DBP/DataBrokerProtectionManager.swift
+++ b/DuckDuckGo/DBP/DataBrokerProtectionManager.swift
@@ -41,8 +41,10 @@ public final class DataBrokerProtectionManager {
return dataManager
}()
+ private lazy var ipcClient = DataBrokerProtectionIPCClient(machServiceName: Bundle.main.dbpBackgroundAgentBundleId, pixelHandler: pixelHandler)
+
lazy var scheduler: DataBrokerProtectionLoginItemScheduler = {
- let ipcClient = DataBrokerProtectionIPCClient(machServiceName: Bundle.main.dbpBackgroundAgentBundleId, pixelHandler: pixelHandler)
+
let ipcScheduler = DataBrokerProtectionIPCScheduler(ipcClient: ipcClient)
return DataBrokerProtectionLoginItemScheduler(ipcScheduler: ipcScheduler, pixelHandler: pixelHandler)
@@ -57,6 +59,12 @@ public final class DataBrokerProtectionManager {
public func shouldAskForInviteCode() -> Bool {
redeemUseCase.shouldAskForInviteCode()
}
+
+ // MARK: - Debugging Features
+
+ public func showAgentIPAddress() {
+ ipcClient.openBrowser(domain: "https://www.whatismyip.com")
+ }
}
extension DataBrokerProtectionManager: DataBrokerProtectionDataManagerDelegate {
diff --git a/DuckDuckGo/DBP/LoginItem+DataBrokerProtection.swift b/DuckDuckGo/DBP/LoginItem+DataBrokerProtection.swift
index e1e00e38c7..cdbfa623a9 100644
--- a/DuckDuckGo/DBP/LoginItem+DataBrokerProtection.swift
+++ b/DuckDuckGo/DBP/LoginItem+DataBrokerProtection.swift
@@ -16,6 +16,7 @@
// limitations under the License.
//
+import Foundation
import LoginItems
#if DBP
diff --git a/DuckDuckGo/DuckDuckGo.entitlements b/DuckDuckGo/DuckDuckGo.entitlements
index 7b79b8b2fe..757dc88e2c 100644
--- a/DuckDuckGo/DuckDuckGo.entitlements
+++ b/DuckDuckGo/DuckDuckGo.entitlements
@@ -5,6 +5,7 @@
com.apple.developer.networking.networkextension
packet-tunnel-provider-systemextension
+ app-proxy-provider-systemextension
com.apple.developer.system-extension.install
diff --git a/DuckDuckGo/DuckDuckGoAppStore.entitlements b/DuckDuckGo/DuckDuckGoAppStore.entitlements
index e419bc0920..97443cb452 100644
--- a/DuckDuckGo/DuckDuckGoAppStore.entitlements
+++ b/DuckDuckGo/DuckDuckGoAppStore.entitlements
@@ -19,6 +19,11 @@
com.apple.security.files.user-selected.read-write
+ com.apple.developer.networking.networkextension
+
+ packet-tunnel-provider
+ app-proxy-provider
+
com.apple.security.network.client
com.apple.security.personal-information.location
diff --git a/DuckDuckGo/DuckDuckGoAppStoreCI.entitlements b/DuckDuckGo/DuckDuckGoAppStoreCI.entitlements
index a2c7bd6bd5..13ea43d233 100644
--- a/DuckDuckGo/DuckDuckGoAppStoreCI.entitlements
+++ b/DuckDuckGo/DuckDuckGoAppStoreCI.entitlements
@@ -2,10 +2,6 @@
- com.apple.developer.networking.networkextension
-
- packet-tunnel-provider
-
com.apple.security.app-sandbox
com.apple.security.application-groups
diff --git a/DuckDuckGo/DuckDuckGoDebug.entitlements b/DuckDuckGo/DuckDuckGoDebug.entitlements
index dcffb16791..dad1686cba 100644
--- a/DuckDuckGo/DuckDuckGoDebug.entitlements
+++ b/DuckDuckGo/DuckDuckGoDebug.entitlements
@@ -5,6 +5,7 @@
com.apple.developer.networking.networkextension
packet-tunnel-provider
+ app-proxy-provider
com.apple.developer.system-extension.install
diff --git a/DuckDuckGo/DuckDuckGo_NetP_Debug.entitlements b/DuckDuckGo/DuckDuckGo_NetP_Debug.entitlements
deleted file mode 100644
index 069c866e05..0000000000
--- a/DuckDuckGo/DuckDuckGo_NetP_Debug.entitlements
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
- com.apple.developer.networking.networkextension
-
- packet-tunnel-provider
-
- com.apple.developer.system-extension.install
-
- com.apple.developer.usernotifications.time-sensitive
-
- com.apple.security.application-groups
-
- HKE973VLUW.com.duckduckgo.network-protection
- $(NETP_APP_GROUP)
-
- com.apple.security.device.audio-input
-
- com.apple.security.device.camera
-
- com.apple.security.personal-information.location
-
- keychain-access-groups
-
- $(AppIdentifierPrefix)com.duckduckgo.macos.browser
- $(AppIdentifierPrefix)com.duckduckgo.network-protection
-
-
-
diff --git a/DuckDuckGo/DuckDuckGo_NetP_Release.entitlements b/DuckDuckGo/DuckDuckGo_NetP_Release.entitlements
deleted file mode 100644
index a2226d1f8d..0000000000
--- a/DuckDuckGo/DuckDuckGo_NetP_Release.entitlements
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
- com.apple.developer.networking.networkextension
-
- packet-tunnel-provider-systemextension
-
- com.apple.developer.system-extension.install
-
- com.apple.developer.usernotifications.time-sensitive
-
- com.apple.security.application-groups
-
- $(TeamIdentifierPrefix)com.duckduckgo.macos.browser.network-protection
- $(NETP_APP_GROUP)
-
- com.apple.security.device.audio-input
-
- com.apple.security.device.camera
-
- com.apple.security.personal-information.location
-
- keychain-access-groups
-
- $(AppIdentifierPrefix)com.duckduckgo.macos.browser
- $(AppIdentifierPrefix)com.duckduckgo.network-protection
-
- com.apple.security.personal-information.location
-
- com.apple.developer.networking.networkextension
-
- packet-tunnel-provider-systemextension
-
- com.apple.developer.system-extension.install
-
-
-
diff --git a/DuckDuckGo/InfoPlist.xcstrings b/DuckDuckGo/InfoPlist.xcstrings
index 4d7c2d94c0..70d7389fb7 100644
--- a/DuckDuckGo/InfoPlist.xcstrings
+++ b/DuckDuckGo/InfoPlist.xcstrings
@@ -8,7 +8,7 @@
"en" : {
"stringUnit" : {
"state" : "new",
- "value" : "DuckDuckGo"
+ "value" : "DuckDuckGo Privacy Pro"
}
}
}
diff --git a/DuckDuckGo/NetworkProtection/AppAndExtensionTargets/AppAndExtensionAndNotificationTargets/Bundle+VPN.swift b/DuckDuckGo/NetworkProtection/AppAndExtensionTargets/AppAndExtensionAndNotificationTargets/Bundle+VPN.swift
new file mode 100644
index 0000000000..169f0ceb50
--- /dev/null
+++ b/DuckDuckGo/NetworkProtection/AppAndExtensionTargets/AppAndExtensionAndNotificationTargets/Bundle+VPN.swift
@@ -0,0 +1,65 @@
+//
+// Bundle+VPN.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
+
+extension Bundle {
+
+ private enum VPNInfoKey: String {
+ case tunnelExtensionBundleID = "TUNNEL_EXTENSION_BUNDLE_ID"
+ case proxyExtensionBundleID = "PROXY_EXTENSION_BUNDLE_ID"
+ }
+
+ static var tunnelExtensionBundleID: String {
+ string(for: .tunnelExtensionBundleID)
+ }
+
+ static var proxyExtensionBundleID: String {
+ string(for: .proxyExtensionBundleID)
+ }
+
+ private static func string(for key: VPNInfoKey) -> String {
+ guard let bundleID = Bundle.main.object(forInfoDictionaryKey: key.rawValue) as? String else {
+ fatalError("Info.plist is missing \(key)")
+ }
+
+ return bundleID
+ }
+
+#if !NETWORK_EXTENSION
+ // for the Main or Launcher Agent app
+ static func mainAppBundle() -> Bundle {
+ return Bundle.main
+ }
+#elseif NETP_SYSTEM_EXTENSION
+ // for the System Extension (Developer ID)
+ static func mainAppBundle() -> Bundle {
+ return Bundle(url: .mainAppBundleURL)!
+ }
+ // AppEx (App Store) can‘t access Main App Bundle
+#endif
+
+ static let keychainType: KeychainType = {
+#if NETP_SYSTEM_EXTENSION
+ .system
+#else
+ .dataProtection(.named(Bundle.main.appGroup(bundle: .netP)))
+#endif
+ }()
+}
diff --git a/DuckDuckGo/NetworkProtection/AppAndExtensionTargets/AppAndExtensionAndNotificationTargets/NetworkProtectionBundle.swift b/DuckDuckGo/NetworkProtection/AppAndExtensionTargets/AppAndExtensionAndNotificationTargets/NetworkProtectionBundle.swift
deleted file mode 100644
index e14b7f1e84..0000000000
--- a/DuckDuckGo/NetworkProtection/AppAndExtensionTargets/AppAndExtensionAndNotificationTargets/NetworkProtectionBundle.swift
+++ /dev/null
@@ -1,78 +0,0 @@
-//
-// NetworkProtectionBundle.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
-
-enum NetworkProtectionBundle {
-
-#if !NETWORK_EXTENSION
- // for the Main or Launcher Agent app
- static func mainAppBundle() -> Bundle {
- return Bundle.main
- }
-#elseif NETP_SYSTEM_EXTENSION
- // for the System Extension (Developer ID)
- static func mainAppBundle() -> Bundle {
- return Bundle(url: .mainAppBundleURL)!
- }
- // AppEx (App Store) can‘t access Main App Bundle
-#endif
-
- static func extensionBundle() -> Bundle {
-#if NETWORK_EXTENSION // When this code is compiled for any network-extension
- return Bundle.main
-#elseif NETP_SYSTEM_EXTENSION // When this code is compiled for the app when configured to use the sysex
- let extensionsDirectoryURL = URL(fileURLWithPath: "Contents/Library/SystemExtensions", relativeTo: Bundle.main.bundleURL)
- return extensionBundle(at: extensionsDirectoryURL)
-#else // When this code is compiled for the app when configured to use the appex
- let extensionsDirectoryURL = URL(fileURLWithPath: "Contents/Plugins", relativeTo: Bundle.main.bundleURL)
- return extensionBundle(at: extensionsDirectoryURL)
-#endif
- }
-
- static func extensionBundle(at url: URL) -> Bundle {
- let extensionURLs: [URL]
- do {
- extensionURLs = try FileManager.default.contentsOfDirectory(at: url,
- includingPropertiesForKeys: nil,
- options: .skipsHiddenFiles)
- } catch let error {
- fatalError("🔵 Failed to get the contents of \(url.absoluteString): \(error.localizedDescription)")
- }
-
- // This should be updated to work well with other extensions
- guard let extensionURL = extensionURLs.first else {
- fatalError("🔵 Failed to find any system extensions")
- }
-
- guard let extensionBundle = Bundle(url: extensionURL) else {
- fatalError("🔵 Failed to create a bundle with URL \(extensionURL.absoluteString)")
- }
-
- return extensionBundle
- }
-
- static let keychainType: KeychainType = {
-#if NETP_SYSTEM_EXTENSION
- .system
-#else
- .dataProtection(.named(Bundle.main.appGroup(bundle: .netP)))
-#endif
- }()
-}
diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift
index e1696a7aba..f54236f387 100644
--- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift
+++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionDebugMenu.swift
@@ -22,6 +22,7 @@ import AppKit
import Common
import Foundation
import NetworkProtection
+import NetworkProtectionProxy
import SwiftUI
/// Controller for the Network Protection debug menu.
@@ -29,6 +30,10 @@ import SwiftUI
@MainActor
final class NetworkProtectionDebugMenu: NSMenu {
+ private let transparentProxySettings = TransparentProxySettings(defaults: .netP)
+
+ // MARK: - Menus
+
private let environmentMenu = NSMenu()
private let preferredServerMenu: NSMenu
@@ -39,7 +44,9 @@ final class NetworkProtectionDebugMenu: NSMenu {
private let resetToDefaults = NSMenuItem(title: "Reset Settings to defaults", action: #selector(NetworkProtectionDebugMenu.resetSettings))
- private let exclusionsMenu = NSMenu()
+ private let excludedRoutesMenu = NSMenu()
+ private let excludeDDGBrowserTrafficFromVPN = NSMenuItem(title: "DDG Browser", action: #selector(toggleExcludeDDGBrowser))
+ private let excludeDBPTrafficFromVPN = NSMenuItem(title: "DBP Background Agent", action: #selector(toggleExcludeDBPBackgroundAgent))
private let shouldEnforceRoutesMenuItem = NSMenuItem(title: "Kill Switch (enforceRoutes)", action: #selector(NetworkProtectionDebugMenu.toggleEnforceRoutesAction))
private let shouldIncludeAllNetworksMenuItem = NSMenuItem(title: "includeAllNetworks", action: #selector(NetworkProtectionDebugMenu.toggleIncludeAllNetworks))
@@ -89,7 +96,6 @@ final class NetworkProtectionDebugMenu: NSMenu {
.targetting(self)
shouldEnforceRoutesMenuItem
.targetting(self)
- NSMenuItem(title: "Excluded Routes").submenu(exclusionsMenu)
NSMenuItem.separator()
NSMenuItem(title: "Send Test Notification", action: #selector(NetworkProtectionDebugMenu.sendTestNotification))
@@ -104,6 +110,14 @@ final class NetworkProtectionDebugMenu: NSMenu {
NSMenuItem(title: "Environment")
.submenu(environmentMenu)
+ NSMenuItem(title: "Exclusions") {
+ NSMenuItem(title: "Excluded Apps") {
+ excludeDDGBrowserTrafficFromVPN.targetting(self)
+ excludeDBPTrafficFromVPN.targetting(self)
+ }
+ NSMenuItem(title: "Excluded Routes").submenu(excludedRoutesMenu)
+ }
+
NSMenuItem(title: "Preferred Server").submenu(preferredServerMenu)
NSMenuItem(title: "Registration Key") {
@@ -172,8 +186,8 @@ final class NetworkProtectionDebugMenu: NSMenu {
populateNetworkProtectionServerListMenuItems()
populateNetworkProtectionRegistrationKeyValidityMenuItems()
- exclusionsMenu.delegate = self
- exclusionsMenu.autoenablesItems = false
+ excludedRoutesMenu.delegate = self
+ excludedRoutesMenu.autoenablesItems = false
populateExclusionsMenuItems()
}
@@ -391,7 +405,7 @@ final class NetworkProtectionDebugMenu: NSMenu {
}
private func populateExclusionsMenuItems() {
- exclusionsMenu.removeAllItems()
+ excludedRoutesMenu.removeAllItems()
for item in settings.excludedRoutes {
let menuItem: NSMenuItem
@@ -406,7 +420,7 @@ final class NetworkProtectionDebugMenu: NSMenu {
target: self,
representedObject: range.stringRepresentation)
}
- exclusionsMenu.addItem(menuItem)
+ excludedRoutesMenu.addItem(menuItem)
}
// Only allow testers to enter a custom code if they're on the waitlist, to simulate the correct path through the flow
@@ -419,6 +433,7 @@ final class NetworkProtectionDebugMenu: NSMenu {
override func update() {
updateEnvironmentMenu()
+ updateExclusionsMenu()
updatePreferredServerMenu()
updateRekeyValidityMenu()
updateNetworkProtectionMenuItemsState()
@@ -588,6 +603,7 @@ final class NetworkProtectionDebugMenu: NSMenu {
}
// MARK: Environment
+
@objc func setSelectedEnvironment(_ menuItem: NSMenuItem) {
let title = menuItem.title
let selectedEnvironment: VPNSettings.SelectedEnvironment
@@ -608,6 +624,24 @@ final class NetworkProtectionDebugMenu: NSMenu {
settings.selectedServer = .automatic
}
}
+
+ // MARK: - Exclusions
+
+ private let dbpBackgroundAppIdentifier = Bundle.main.dbpBackgroundAgentBundleId
+ private let ddgBrowserAppIdentifier = Bundle.main.bundleIdentifier!
+
+ private func updateExclusionsMenu() {
+ excludeDBPTrafficFromVPN.state = transparentProxySettings.isExcluding(dbpBackgroundAppIdentifier) ? .on : .off
+ excludeDDGBrowserTrafficFromVPN.state = transparentProxySettings.isExcluding(ddgBrowserAppIdentifier) ? .on : .off
+ }
+
+ @objc private func toggleExcludeDBPBackgroundAgent() {
+ transparentProxySettings.toggleExclusion(for: dbpBackgroundAppIdentifier)
+ }
+
+ @objc private func toggleExcludeDDGBrowser() {
+ transparentProxySettings.toggleExclusion(for: ddgBrowserAppIdentifier)
+ }
}
extension NetworkProtectionDebugMenu: NSMenuDelegate {
diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift
index f67223a545..0bd4975196 100644
--- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift
+++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift
@@ -24,6 +24,7 @@ import SwiftUI
import Common
import NetworkExtension
import NetworkProtection
+import NetworkProtectionProxy
import NetworkProtectionUI
import Networking
import PixelKit
@@ -38,6 +39,8 @@ typealias NetworkProtectionConfigChangeHandler = () -> Void
final class NetworkProtectionTunnelController: TunnelController, TunnelSessionProvider {
+ // MARK: - Settings
+
let settings: VPNSettings
// MARK: - Combine Cancellables
@@ -60,6 +63,8 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr
///
private let controllerErrorStore = NetworkProtectionControllerErrorStore()
+ private let notificationCenter: NotificationCenter
+
// MARK: - VPN Tunnel & Configuration
/// Auth token store
@@ -95,6 +100,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr
/// Loads the configuration matching our ``extensionID``.
///
+ @MainActor
public var manager: NETunnelProviderManager? {
get async {
if let internalManager {
@@ -139,13 +145,14 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr
init(networkExtensionBundleID: String,
networkExtensionController: NetworkExtensionController,
settings: VPNSettings,
- notificationCenter: NotificationCenter = .default,
tokenStore: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(),
+ notificationCenter: NotificationCenter = .default,
logger: NetworkProtectionLogger = DefaultNetworkProtectionLogger()) {
self.logger = logger
self.networkExtensionBundleID = networkExtensionBundleID
self.networkExtensionController = networkExtensionController
+ self.notificationCenter = notificationCenter
self.settings = settings
self.tokenStore = tokenStore
@@ -254,7 +261,7 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr
tunnelManager.protocolConfiguration = {
let protocolConfiguration = tunnelManager.protocolConfiguration as? NETunnelProviderProtocol ?? NETunnelProviderProtocol()
protocolConfiguration.serverAddress = "127.0.0.1" // Dummy address... the NetP service will take care of grabbing a real server
- protocolConfiguration.providerBundleIdentifier = NetworkProtectionBundle.extensionBundle().bundleIdentifier
+ protocolConfiguration.providerBundleIdentifier = Bundle.tunnelExtensionBundleID
protocolConfiguration.providerConfiguration = [
NetworkProtectionOptionKey.defaultPixelHeaders: APIRequest.Headers().httpHeaders,
NetworkProtectionOptionKey.includedRoutes: includedRoutes().map(\.stringRepresentation) as NSArray
@@ -304,6 +311,14 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr
}
}
+ // MARK: - Connection
+
+ public var status: NEVPNStatus {
+ get async {
+ await connection?.status ?? .disconnected
+ }
+ }
+
// MARK: - Connection Status Querying
/// Queries Network Protection to know if its VPN is connected.
diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift
index 5770b78a2f..3a3a392736 100644
--- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift
+++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift
@@ -224,7 +224,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider {
let tunnelHealthStore = NetworkProtectionTunnelHealthStore(notificationCenter: notificationCenter)
let controllerErrorStore = NetworkProtectionTunnelErrorStore(notificationCenter: notificationCenter)
let debugEvents = Self.networkProtectionDebugEvents(controllerErrorStore: controllerErrorStore)
- let tokenStore = NetworkProtectionKeychainTokenStore(keychainType: NetworkProtectionBundle.keychainType,
+ let tokenStore = NetworkProtectionKeychainTokenStore(keychainType: Bundle.keychainType,
serviceName: Self.tokenServiceName,
errorEvents: debugEvents)
let notificationsPresenter = NetworkProtectionNotificationsPresenterFactory().make(settings: settings)
@@ -232,7 +232,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider {
super.init(notificationsPresenter: notificationsPresenter,
tunnelHealthStore: tunnelHealthStore,
controllerErrorStore: controllerErrorStore,
- keychainType: NetworkProtectionBundle.keychainType,
+ keychainType: Bundle.keychainType,
tokenStore: tokenStore,
debugEvents: debugEvents,
providerEvents: Self.packetTunnelProviderEvents,
@@ -323,13 +323,6 @@ final class MacPacketTunnelProvider: PacketTunnelProvider {
case missingPixelHeaders
}
- override func prepareToConnect(using provider: NETunnelProviderProtocol?) {
- super.prepareToConnect(using: provider)
-
- guard PixelKit.shared == nil, let options = provider?.providerConfiguration else { return }
- try? loadDefaultPixelHeaders(from: options)
- }
-
public override func loadVendorOptions(from provider: NETunnelProviderProtocol?) throws {
try super.loadVendorOptions(from: provider)
@@ -350,6 +343,15 @@ final class MacPacketTunnelProvider: PacketTunnelProvider {
setupPixels(defaultHeaders: defaultPixelHeaders)
}
+ // MARK: - Overrideable Connection Events
+
+ override func prepareToConnect(using provider: NETunnelProviderProtocol?) {
+ super.prepareToConnect(using: provider)
+
+ guard PixelKit.shared == nil, let options = provider?.providerConfiguration else { return }
+ try? loadDefaultPixelHeaders(from: options)
+ }
+
// MARK: - Start/Stop Tunnel
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacTransparentProxyProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacTransparentProxyProvider.swift
new file mode 100644
index 0000000000..d300309ec6
--- /dev/null
+++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacTransparentProxyProvider.swift
@@ -0,0 +1,94 @@
+//
+// MacTransparentProxyProvider.swift
+//
+// Copyright © 2024 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 Common
+import Foundation
+import Networking
+import NetworkExtension
+import NetworkProtectionProxy
+import os.log // swiftlint:disable:this enforce_os_log_wrapper
+import PixelKit
+
+final class MacTransparentProxyProvider: TransparentProxyProvider {
+
+ static var vpnProxyLogger = Logger(subsystem: OSLog.subsystem, category: "VPN Proxy")
+
+ private var cancellables = Set()
+
+ @objc init() {
+ let loadSettingsFromStartupOptions: Bool = {
+#if NETP_SYSTEM_EXTENSION
+ true
+#else
+ false
+#endif
+ }()
+
+ let settings: TransparentProxySettings = {
+#if NETP_SYSTEM_EXTENSION
+ /// Because our System Extension is running in the system context and doesn't have access
+ /// to shared user defaults, we just make it use the `.standard` defaults.
+ TransparentProxySettings(defaults: .standard)
+#else
+ /// Because our App Extension is running in the user context and has access
+ /// to shared user defaults, we take advantage of this and use the `.netP` defaults.
+ TransparentProxySettings(defaults: .netP)
+#endif
+ }()
+
+ let configuration = TransparentProxyProvider.Configuration(
+ loadSettingsFromProviderConfiguration: loadSettingsFromStartupOptions)
+
+ super.init(settings: settings,
+ configuration: configuration,
+ logger: Self.vpnProxyLogger)
+
+ eventHandler = eventHandler(_:)
+
+#if !NETP_SYSTEM_EXTENSION
+ let dryRun: Bool
+#if DEBUG
+ dryRun = true
+#else
+ dryRun = false
+#endif
+
+ PixelKit.setUp(dryRun: dryRun,
+ appVersion: AppVersion.shared.versionNumber,
+ source: "vpnProxyExtension",
+ defaultHeaders: [:],
+ log: .networkProtectionPixel,
+ defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in
+
+ let url = URL.pixelUrl(forPixelNamed: pixelName)
+ let apiHeaders = APIRequest.Headers(additionalHeaders: headers)
+ let configuration = APIRequest.Configuration(url: url, method: .get, queryParameters: parameters, headers: apiHeaders)
+ let request = APIRequest(configuration: configuration)
+
+ request.fetch { _, error in
+ onComplete(error == nil, error)
+ }
+ }
+#endif
+ }
+
+ private func eventHandler(_ event: TransparentProxyProvider.Event) {
+ PixelKit.fire(event)
+ }
+}
diff --git a/DuckDuckGo/NetworkProtectionAppExtension.entitlements b/DuckDuckGo/NetworkProtectionAppExtension.entitlements
index 13dd983ca1..d37610bb07 100644
--- a/DuckDuckGo/NetworkProtectionAppExtension.entitlements
+++ b/DuckDuckGo/NetworkProtectionAppExtension.entitlements
@@ -5,6 +5,7 @@
com.apple.developer.networking.networkextension
packet-tunnel-provider
+ app-proxy-provider
com.apple.security.app-sandbox
diff --git a/DuckDuckGoDBPBackgroundAgent/BrowserWindowManager.swift b/DuckDuckGoDBPBackgroundAgent/BrowserWindowManager.swift
new file mode 100644
index 0000000000..85891c6604
--- /dev/null
+++ b/DuckDuckGoDBPBackgroundAgent/BrowserWindowManager.swift
@@ -0,0 +1,64 @@
+//
+// BrowserWindowManager.swift
+//
+// Copyright © 2024 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
+import WebKit
+
+/// A class that offers functionality to quickly show an interactive browser window.
+///
+/// This class is meant to aid with debugging and should not be included in release builds.
+/// .
+final class BrowserWindowManager: NSObject {
+ private var interactiveBrowserWindow: NSWindow?
+
+ @MainActor
+ func show(domain: String) {
+ if let interactiveBrowserWindow, interactiveBrowserWindow.isVisible {
+ return
+ }
+
+ let window = NSWindow(
+ contentRect: NSRect(x: 0, y: 0, width: 800, height: 600),
+ styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
+ backing: .buffered, defer: false)
+ window.center()
+ window.title = "Web Browser"
+ window.delegate = self
+ interactiveBrowserWindow = window
+
+ // Create the WKWebView.
+ let webView = WKWebView(frame: window.contentView!.bounds)
+ webView.autoresizingMask = [.width, .height]
+ window.contentView!.addSubview(webView)
+
+ // Load a URL.
+ let url = URL(string: domain)!
+ let request = URLRequest(url: url)
+ webView.load(request)
+
+ // Show the window.
+ window.makeKeyAndOrderFront(nil)
+ }
+}
+
+extension BrowserWindowManager: NSWindowDelegate {
+ func windowWillClose(_ notification: Notification) {
+ interactiveBrowserWindow = nil
+ }
+}
diff --git a/DuckDuckGoDBPBackgroundAgent/IPCServiceManager.swift b/DuckDuckGoDBPBackgroundAgent/IPCServiceManager.swift
index 1d7c0403fb..c452200f7b 100644
--- a/DuckDuckGoDBPBackgroundAgent/IPCServiceManager.swift
+++ b/DuckDuckGoDBPBackgroundAgent/IPCServiceManager.swift
@@ -17,10 +17,10 @@
//
import Combine
-import Foundation
+import Common
import DataBrokerProtection
+import Foundation
import PixelKit
-import Common
/// Manages the IPC service for the Agent app
///
@@ -28,6 +28,7 @@ import Common
/// demand interaction with.
///
final class IPCServiceManager {
+ private var browserWindowManager: BrowserWindowManager
private let ipcServer: DataBrokerProtectionIPCServer
private let scheduler: DataBrokerProtectionScheduler
private let pixelHandler: EventMapping
@@ -41,6 +42,8 @@ final class IPCServiceManager {
self.scheduler = scheduler
self.pixelHandler = pixelHandler
+ browserWindowManager = BrowserWindowManager()
+
ipcServer.serverDelegate = self
ipcServer.activate()
}
@@ -102,4 +105,10 @@ extension IPCServiceManager: IPCServerInterface {
pixelHandler.fire(.ipcServerRunAllOperations)
scheduler.runAllOperations(showWebView: showWebView)
}
+
+ func openBrowser(domain: String) {
+ Task { @MainActor in
+ browserWindowManager.show(domain: domain)
+ }
+ }
}
diff --git a/DuckDuckGoVPN/DuckDuckGoVPN.entitlements b/DuckDuckGoVPN/DuckDuckGoVPN.entitlements
index 653311b9ec..2797c3f947 100644
--- a/DuckDuckGoVPN/DuckDuckGoVPN.entitlements
+++ b/DuckDuckGoVPN/DuckDuckGoVPN.entitlements
@@ -5,6 +5,7 @@
com.apple.developer.networking.networkextension
packet-tunnel-provider-systemextension
+ app-proxy-provider-systemextension
com.apple.developer.system-extension.install
diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift
index add4af8a6d..f14bbac165 100644
--- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift
+++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift
@@ -23,7 +23,7 @@ import LoginItems
import Networking
import NetworkExtension
import NetworkProtection
-import NetworkProtectionIPC
+import NetworkProtectionProxy
import NetworkProtectionUI
import ServiceManagement
import PixelKit
@@ -60,18 +60,82 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate {
private var cancellables = Set()
- var networkExtensionBundleID: String {
- Bundle.main.networkExtensionBundleID
+ var proxyExtensionBundleID: String {
+ Bundle.proxyExtensionBundleID
}
-#if NETWORK_PROTECTION
- private lazy var networkExtensionController = NetworkExtensionController(extensionBundleID: networkExtensionBundleID)
+ var tunnelExtensionBundleID: String {
+ Bundle.tunnelExtensionBundleID
+ }
+
+ private lazy var networkExtensionController = NetworkExtensionController(extensionBundleID: tunnelExtensionBundleID)
+
+ private var storeProxySettingsInProviderConfiguration: Bool {
+#if NETP_SYSTEM_EXTENSION
+ true
+#else
+ false
#endif
+ }
private lazy var tunnelSettings = VPNSettings(defaults: .netP)
+ private lazy var proxySettings = TransparentProxySettings(defaults: .netP)
+
+ @MainActor
+ private lazy var vpnProxyLauncher = VPNProxyLauncher(
+ tunnelController: tunnelController,
+ proxyController: proxyController)
+
+ @MainActor
+ private lazy var proxyController: TransparentProxyController = {
+ let controller = TransparentProxyController(
+ extensionID: proxyExtensionBundleID,
+ storeSettingsInProviderConfiguration: storeProxySettingsInProviderConfiguration,
+ settings: proxySettings) { [weak self] manager in
+ guard let self else { return }
+
+ manager.localizedDescription = "DuckDuckGo VPN Proxy"
+
+ if !manager.isEnabled {
+ manager.isEnabled = true
+ }
+
+ manager.protocolConfiguration = {
+ let protocolConfiguration = manager.protocolConfiguration as? NETunnelProviderProtocol ?? NETunnelProviderProtocol()
+ protocolConfiguration.serverAddress = "127.0.0.1" // Dummy address... the NetP service will take care of grabbing a real server
+ protocolConfiguration.providerBundleIdentifier = self.proxyExtensionBundleID
+
+ // always-on
+ protocolConfiguration.disconnectOnSleep = false
+
+ // kill switch
+ // protocolConfiguration.enforceRoutes = false
+
+ // this setting breaks Connection Tester
+ // protocolConfiguration.includeAllNetworks = settings.includeAllNetworks
+
+ // This is intentionally not used but left here for documentation purposes.
+ // The reason for this is that we want to have full control of the routes that
+ // are excluded, so instead of using this setting we're just configuring the
+ // excluded routes through our VPNSettings class, which our extension reads directly.
+ // protocolConfiguration.excludeLocalNetworks = settings.excludeLocalNetworks
+ return protocolConfiguration
+ }()
+ }
+
+ controller.eventHandler = handleControllerEvent(_:)
+
+ return controller
+ }()
+
+ private func handleControllerEvent(_ event: TransparentProxyController.Event) {
+ PixelKit.fire(event)
+ }
+
+ @MainActor
private lazy var tunnelController = NetworkProtectionTunnelController(
- networkExtensionBundleID: networkExtensionBundleID,
+ networkExtensionBundleID: tunnelExtensionBundleID,
networkExtensionController: networkExtensionController,
settings: tunnelSettings)
@@ -79,6 +143,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate {
///
/// This is used by our main app to control the tunnel through the VPN login item.
///
+ @MainActor
private lazy var tunnelControllerIPCService: TunnelControllerIPCService = {
let ipcServer = TunnelControllerIPCService(
tunnelController: tunnelController,
@@ -88,17 +153,19 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate {
return ipcServer
}()
+ @MainActor
+ private lazy var statusObserver = ConnectionStatusObserverThroughSession(
+ tunnelSessionProvider: tunnelController,
+ platformNotificationCenter: NSWorkspace.shared.notificationCenter,
+ platformDidWakeNotification: NSWorkspace.didWakeNotification)
+
+ @MainActor
private lazy var statusReporter: NetworkProtectionStatusReporter = {
let errorObserver = ConnectionErrorObserverThroughSession(
tunnelSessionProvider: tunnelController,
platformNotificationCenter: NSWorkspace.shared.notificationCenter,
platformDidWakeNotification: NSWorkspace.didWakeNotification)
- let statusObserver = ConnectionStatusObserverThroughSession(
- tunnelSessionProvider: tunnelController,
- platformNotificationCenter: NSWorkspace.shared.notificationCenter,
- platformDidWakeNotification: NSWorkspace.didWakeNotification)
-
let serverInfoObserver = ConnectionServerInfoObserverThroughSession(
tunnelSessionProvider: tunnelController,
platformNotificationCenter: NSWorkspace.shared.notificationCenter,
@@ -113,6 +180,7 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate {
)
}()
+ @MainActor
private lazy var vpnAppEventsHandler = {
VPNAppEventsHandler(tunnelController: tunnelController)
}()
@@ -175,8 +243,9 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate {
bouncer.requireAuthTokenOrKillApp()
- // Initialize the IPC server
+ // Initialize lazy properties
_ = tunnelControllerIPCService
+ _ = vpnProxyLauncher
let dryRun: Bool
diff --git a/DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements b/DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements
index a6ed34f64f..f531d0bc0c 100644
--- a/DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements
+++ b/DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements
@@ -5,6 +5,7 @@
com.apple.developer.networking.networkextension
packet-tunnel-provider
+ app-proxy-provider
com.apple.developer.system-extension.install
diff --git a/DuckDuckGoVPN/Info-AppStore.plist b/DuckDuckGoVPN/Info-AppStore.plist
index 7627fdd9c9..a1b2b02a02 100644
--- a/DuckDuckGoVPN/Info-AppStore.plist
+++ b/DuckDuckGoVPN/Info-AppStore.plist
@@ -6,8 +6,10 @@
$(DISTRIBUTED_NOTIFICATIONS_PREFIX)
NETP_APP_GROUP
$(NETP_APP_GROUP)
- SYSEX_BUNDLE_ID
- $(SYSEX_BUNDLE_ID)
+ PROXY_EXTENSION_BUNDLE_ID
+ $(PROXY_EXTENSION_BUNDLE_ID)
+ TUNNEL_EXTENSION_BUNDLE_ID
+ $(TUNNEL_EXTENSION_BUNDLE_ID)
LSApplicationCategoryType
public.app-category.productivity
CFBundleShortVersionString
diff --git a/DuckDuckGoVPN/Info.plist b/DuckDuckGoVPN/Info.plist
index 7627fdd9c9..a1b2b02a02 100644
--- a/DuckDuckGoVPN/Info.plist
+++ b/DuckDuckGoVPN/Info.plist
@@ -6,8 +6,10 @@
$(DISTRIBUTED_NOTIFICATIONS_PREFIX)
NETP_APP_GROUP
$(NETP_APP_GROUP)
- SYSEX_BUNDLE_ID
- $(SYSEX_BUNDLE_ID)
+ PROXY_EXTENSION_BUNDLE_ID
+ $(PROXY_EXTENSION_BUNDLE_ID)
+ TUNNEL_EXTENSION_BUNDLE_ID
+ $(TUNNEL_EXTENSION_BUNDLE_ID)
LSApplicationCategoryType
public.app-category.productivity
CFBundleShortVersionString
diff --git a/DuckDuckGoVPN/VPNProxyLauncher.swift b/DuckDuckGoVPN/VPNProxyLauncher.swift
new file mode 100644
index 0000000000..c99d187cf2
--- /dev/null
+++ b/DuckDuckGoVPN/VPNProxyLauncher.swift
@@ -0,0 +1,149 @@
+//
+// VPNProxyLauncher.swift
+//
+// Copyright © 2024 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 NetworkProtectionProxy
+import NetworkExtension
+
+/// Starts and stops the VPN proxy component.
+///
+/// This class looks at the tunnel and the proxy components and their status and settings, and decides based on
+/// a number of conditions whether to start the proxy, stop it, or just leave it be.
+///
+@MainActor
+final class VPNProxyLauncher {
+ private let tunnelController: NetworkProtectionTunnelController
+ private let proxyController: TransparentProxyController
+ private let notificationCenter: NotificationCenter
+ private var cancellables = Set()
+
+ init(tunnelController: NetworkProtectionTunnelController,
+ proxyController: TransparentProxyController,
+ notificationCenter: NotificationCenter = .default) {
+
+ self.notificationCenter = notificationCenter
+ self.proxyController = proxyController
+ self.tunnelController = tunnelController
+
+ subscribeToStatusChanges()
+ subscribeToProxySettingChanges()
+ }
+
+ // MARK: - Status Changes
+
+ private func subscribeToStatusChanges() {
+ notificationCenter.publisher(for: .NEVPNStatusDidChange)
+ .receive(on: DispatchQueue.main)
+ .removeDuplicates()
+ .sink(receiveValue: statusChanged(notification:))
+ .store(in: &cancellables)
+ }
+
+ private func statusChanged(notification: Notification) {
+ Task { @MainActor in
+ let isProxyConnectionStatusChange = await proxyController.connection == notification.object as? NEVPNConnection
+
+ try await startOrStopProxyIfNeeded(isProxyConnectionStatusChange: isProxyConnectionStatusChange)
+ }
+ }
+
+ // MARK: - Proxy Settings Changes
+
+ private func subscribeToProxySettingChanges() {
+ proxyController.settings.changePublisher
+ .sink(receiveValue: proxySettingChanged(_:))
+ .store(in: &cancellables)
+ }
+
+ private func proxySettingChanged(_ change: TransparentProxySettings.Change) {
+ Task { @MainActor in
+ try await startOrStopProxyIfNeeded()
+ }
+ }
+
+ // MARK: - Auto starting & stopping the proxy component
+
+ private var isControllingProxy = false
+
+ private func startOrStopProxyIfNeeded(isProxyConnectionStatusChange: Bool = false) async throws {
+ if await shouldStartProxy {
+ guard !isControllingProxy else {
+ return
+ }
+
+ isControllingProxy = true
+
+ // When we're auto-starting the proxy because its own status changed to
+ // disconnected, we want to give it a pause because if it fails to connect again
+ // we risk the proxy entering a frenetic connect / disconnect loop
+ if isProxyConnectionStatusChange {
+ // If the proxy connection was stopped, let's wait a bit before trying to enable it again
+ try await Task.sleep(interval: .seconds(10))
+
+ // And we want to check again if the proxy still needs to start after waiting
+ guard await shouldStartProxy else {
+ return
+ }
+ }
+
+ do {
+ try await proxyController.start()
+ isControllingProxy = false
+ } catch {
+ isControllingProxy = false
+ throw error
+ }
+ } else if await shouldStopProxy {
+ guard !isControllingProxy else {
+ return
+ }
+
+ isControllingProxy = true
+ await proxyController.stop()
+ isControllingProxy = false
+ }
+ }
+
+ private var shouldStartProxy: Bool {
+ get async {
+ let proxyIsDisconnected = await proxyController.status == .disconnected
+ let tunnelIsConnected = await tunnelController.status == .connected
+
+ // Starting the proxy only when it's required for active features
+ // is a product decision. It may change once we decide the proxy
+ // is stable enough to be running at all times.
+ return proxyIsDisconnected
+ && tunnelIsConnected
+ && proxyController.isRequiredForActiveFeatures
+ }
+ }
+
+ private var shouldStopProxy: Bool {
+ get async {
+ let proxyIsConnected = await proxyController.status == .connected
+ let tunnelIsDisconnected = await tunnelController.status == .disconnected
+
+ // Stopping the proxy when it's not required for active features
+ // is a product decision. It may change once we decide the proxy
+ // is stable enough to be running at all times.
+ return proxyIsConnected
+ && (tunnelIsDisconnected || !proxyController.isRequiredForActiveFeatures)
+ }
+ }
+}
diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift
index fb62529ee7..64fa721012 100644
--- a/LocalPackages/DataBrokerProtection/Package.swift
+++ b/LocalPackages/DataBrokerProtection/Package.swift
@@ -29,7 +29,7 @@ let package = Package(
targets: ["DataBrokerProtection"])
],
dependencies: [
- .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"),
+ .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"),
.package(path: "../PixelKit"),
.package(path: "../SwiftUIExtensions"),
.package(path: "../XPCHelper")
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCClient.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCClient.swift
index 4a6f58ac28..2aa3953437 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCClient.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCClient.swift
@@ -17,9 +17,9 @@
//
import Combine
+import Common
import Foundation
import XPCHelper
-import Common
/// This protocol describes the server-side IPC interface for controlling the tunnel
///
@@ -150,6 +150,17 @@ extension DataBrokerProtectionIPCClient: IPCServerInterface {
// If you add a completion block, please remember to call it here too!
})
}
+
+ public func openBrowser(domain: String) {
+ self.pixelHandler.fire(.ipcServerRunAllOperations)
+ xpc.execute(call: { server in
+ server.openBrowser(domain: domain)
+ }, xpcReplyErrorHandler: { error in
+ os_log("Error \(error.localizedDescription)")
+ // Intentional no-op as there's no completion block
+ // If you add a completion block, please remember to call it here too!
+ })
+ }
}
// MARK: - Incoming communication from the server
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCServer.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCServer.swift
index fde0274a5f..a2bc3d0e56 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCServer.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCServer.swift
@@ -42,6 +42,12 @@ public protocol IPCServerInterface: AnyObject {
func scanAllBrokers(showWebView: Bool, completion: @escaping ((Error?) -> Void))
func runQueuedOperations(showWebView: Bool, completion: @escaping ((Error?) -> Void))
func runAllOperations(showWebView: Bool)
+
+ // MARK: - Debugging Features
+
+ /// Opens a browser window with the specified domain
+ ///
+ func openBrowser(domain: String)
}
/// This protocol describes the server-side XPC interface.
@@ -71,6 +77,12 @@ protocol XPCServerInterface {
func scanAllBrokers(showWebView: Bool, completion: @escaping ((Error?) -> Void))
func runQueuedOperations(showWebView: Bool, completion: @escaping ((Error?) -> Void))
func runAllOperations(showWebView: Bool)
+
+ // MARK: - Debugging Features
+
+ /// Opens a browser window with the specified domain
+ ///
+ func openBrowser(domain: String)
}
public final class DataBrokerProtectionIPCServer {
@@ -146,4 +158,8 @@ extension DataBrokerProtectionIPCServer: XPCServerInterface {
func runAllOperations(showWebView: Bool) {
serverDelegate?.runAllOperations(showWebView: showWebView)
}
+
+ func openBrowser(domain: String) {
+ serverDelegate?.openBrowser(domain: domain)
+ }
}
diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift
index ad3dfda484..9f12bbab0c 100644
--- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift
+++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift
@@ -279,7 +279,6 @@ public enum DataBrokerProtectionPixels {
}
extension DataBrokerProtectionPixels: PixelKitEvent {
-
public var name: String {
switch self {
case .parentChildMatches: return "m_mac_dbp_macos_parent-child-broker-matches"
diff --git a/LocalPackages/LoginItems/Package.swift b/LocalPackages/LoginItems/Package.swift
index bbe7e894b8..a631a199d9 100644
--- a/LocalPackages/LoginItems/Package.swift
+++ b/LocalPackages/LoginItems/Package.swift
@@ -13,7 +13,7 @@ let package = Package(
),
],
dependencies: [
- .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"),
+ .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"),
],
targets: [
.target(
diff --git a/LocalPackages/NetworkProtectionMac/Package.resolved b/LocalPackages/NetworkProtectionMac/Package.resolved
new file mode 100644
index 0000000000..08c5add0f4
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Package.resolved
@@ -0,0 +1,104 @@
+{
+ "pins" : [
+ {
+ "identity" : "bloom_cpp",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/duckduckgo/bloom_cpp.git",
+ "state" : {
+ "revision" : "8076199456290b61b4544bf2f4caf296759906a0",
+ "version" : "3.0.0"
+ }
+ },
+ {
+ "identity" : "browserserviceskit",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/duckduckgo/BrowserServicesKit",
+ "state" : {
+ "revision" : "1f7932fe67a0d8b1ae97e62cb333639353d4772f",
+ "version" : "101.2.2"
+ }
+ },
+ {
+ "identity" : "content-scope-scripts",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/duckduckgo/content-scope-scripts",
+ "state" : {
+ "revision" : "0b68b0d404d8d4f32296cd84fa160b18b0aeaf44",
+ "version" : "4.59.1"
+ }
+ },
+ {
+ "identity" : "duckduckgo-autofill",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git",
+ "state" : {
+ "revision" : "b972bc0ab6ee1d57a0a18a197dcc31e40ae6ac57",
+ "version" : "10.0.3"
+ }
+ },
+ {
+ "identity" : "grdb.swift",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/duckduckgo/GRDB.swift.git",
+ "state" : {
+ "revision" : "9f049d7b97b1e68ffd86744b500660d34a9e79b8",
+ "version" : "2.3.0"
+ }
+ },
+ {
+ "identity" : "privacy-dashboard",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/duckduckgo/privacy-dashboard",
+ "state" : {
+ "revision" : "38336a574e13090764ba09a6b877d15ee514e371",
+ "version" : "3.1.1"
+ }
+ },
+ {
+ "identity" : "punycodeswift",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/gumob/PunycodeSwift.git",
+ "state" : {
+ "revision" : "4356ec54e073741449640d3d50a1fd24fd1e1b8b",
+ "version" : "2.1.0"
+ }
+ },
+ {
+ "identity" : "swift-argument-parser",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-argument-parser",
+ "state" : {
+ "revision" : "c8ed701b513cf5177118a175d85fbbbcd707ab41",
+ "version" : "1.3.0"
+ }
+ },
+ {
+ "identity" : "sync_crypto",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/duckduckgo/sync_crypto",
+ "state" : {
+ "revision" : "2ab6ab6f0f96b259c14c2de3fc948935fc16ac78",
+ "version" : "0.2.0"
+ }
+ },
+ {
+ "identity" : "trackerradarkit",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/duckduckgo/TrackerRadarKit",
+ "state" : {
+ "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff",
+ "version" : "1.2.2"
+ }
+ },
+ {
+ "identity" : "wireguard-apple",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/duckduckgo/wireguard-apple",
+ "state" : {
+ "revision" : "2d8172c11478ab11b0f5ad49bdb4f93f4b3d5e0d",
+ "version" : "1.1.1"
+ }
+ }
+ ],
+ "version" : 2
+}
diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift
index a1edbe7388..e2dc908671 100644
--- a/LocalPackages/NetworkProtectionMac/Package.swift
+++ b/LocalPackages/NetworkProtectionMac/Package.swift
@@ -27,10 +27,11 @@ let package = Package(
],
products: [
.library(name: "NetworkProtectionIPC", targets: ["NetworkProtectionIPC"]),
+ .library(name: "NetworkProtectionProxy", targets: ["NetworkProtectionProxy"]),
.library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"])
],
dependencies: [
- .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"),
+ .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"),
.package(path: "../XPCHelper"),
.package(path: "../SwiftUIExtensions"),
.package(path: "../LoginItems")
@@ -50,6 +51,19 @@ let package = Package(
plugins: [.plugin(name: "SwiftLintPlugin", package: "BrowserServicesKit")]
),
+ // MARK: - NetworkProtectionProxy
+
+ .target(
+ name: "NetworkProtectionProxy",
+ dependencies: [
+ .product(name: "NetworkProtection", package: "BrowserServicesKit")
+ ],
+ swiftSettings: [
+ .define("DEBUG", .when(configuration: .debug))
+ ],
+ plugins: [.plugin(name: "SwiftLintPlugin", package: "BrowserServicesKit")]
+ ),
+
// MARK: - NetworkProtectionUI
.target(
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/FlowManagers/TCPFlowManager.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/FlowManagers/TCPFlowManager.swift
new file mode 100644
index 0000000000..882eb19734
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/FlowManagers/TCPFlowManager.swift
@@ -0,0 +1,242 @@
+//
+// TCPFlowManager.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 NetworkExtension
+import OSLog // swiftlint:disable:this enforce_os_log_wrapper
+
+/// A private global actor to handle UDP flows management
+///
+@globalActor
+struct TCPFlowActor {
+ actor ActorType { }
+
+ static let shared: ActorType = ActorType()
+}
+
+@TCPFlowActor
+enum RemoteConnectionError: Error {
+ case complete
+ case cancelled
+ case couldNotEstablishConnection(_ error: Error)
+ case unhandledError(_ error: Error)
+}
+
+final class TCPFlowManager {
+ private let flow: NEAppProxyTCPFlow
+ private var connectionTask: Task?
+ private var connection: NWConnection?
+
+ init(flow: NEAppProxyTCPFlow) {
+ self.flow = flow
+ }
+
+ deinit {
+ // Just making extra sure we don't have any unexpected retain cycle
+ connection?.stateUpdateHandler = nil
+ connection?.cancel()
+ }
+
+ func start(interface: NWInterface) async throws {
+ guard let remoteEndpoint = flow.remoteEndpoint as? NWHostEndpoint else {
+ return
+ }
+
+ try await connectAndStartRunLoop(remoteEndpoint: remoteEndpoint, interface: interface)
+ }
+
+ private func connectAndStartRunLoop(remoteEndpoint: NWHostEndpoint, interface: NWInterface) async throws {
+ let remoteConnection = try await connect(to: remoteEndpoint, interface: interface)
+ try await flow.open(withLocalEndpoint: nil)
+
+ do {
+ try await startDataCopyLoop(for: remoteConnection)
+
+ remoteConnection.cancel()
+ flow.closeReadWithError(nil)
+ flow.closeWriteWithError(nil)
+ } catch {
+ remoteConnection.cancel()
+ flow.closeReadWithError(error)
+ flow.closeWriteWithError(error)
+ }
+ }
+
+ func connect(to remoteEndpoint: NWHostEndpoint, interface: NWInterface) async throws -> NWConnection {
+ try await withCheckedThrowingContinuation { continuation in
+ connect(to: remoteEndpoint, interface: interface) { result in
+ switch result {
+ case .success(let connection):
+ continuation.resume(returning: connection)
+ case .failure(let error):
+ continuation.resume(throwing: error)
+ }
+ }
+ }
+ }
+
+ func connect(to remoteEndpoint: NWHostEndpoint, interface: NWInterface, completion: @escaping @TCPFlowActor (Result) -> Void) {
+ let host = Network.NWEndpoint.Host(remoteEndpoint.hostname)
+ let port = Network.NWEndpoint.Port(remoteEndpoint.port)!
+
+ let parameters = NWParameters.tcp
+ parameters.preferNoProxies = true
+ parameters.requiredInterface = interface
+ parameters.prohibitedInterfaceTypes = [.other]
+
+ let connection = NWConnection(host: host, port: port, using: parameters)
+ self.connection = connection
+
+ connection.stateUpdateHandler = { (state: NWConnection.State) in
+ Task { @TCPFlowActor in
+ switch state {
+ case .ready:
+ connection.stateUpdateHandler = nil
+ completion(.success(connection))
+ case .cancelled:
+ connection.stateUpdateHandler = nil
+ completion(.failure(RemoteConnectionError.cancelled))
+ case .failed(let error), .waiting(let error):
+ connection.stateUpdateHandler = nil
+ completion(.failure(RemoteConnectionError.couldNotEstablishConnection(error)))
+ default:
+ break
+ }
+ }
+ }
+
+ connection.start(queue: .global())
+ }
+
+ private func startDataCopyLoop(for remoteConnection: NWConnection) async throws {
+ try await withThrowingTaskGroup(of: Void.self) { group in
+ group.addTask { [weak self] in
+ while true {
+ guard let self else {
+ throw RemoteConnectionError.cancelled
+ }
+
+ try Task.checkCancellation()
+ try await self.copyOutboundTraffic(to: remoteConnection)
+ }
+ }
+
+ group.addTask { [weak self] in
+ while true {
+ guard let self else {
+ throw RemoteConnectionError.cancelled
+ }
+
+ try Task.checkCancellation()
+ try await self.copyInboundTraffic(from: remoteConnection)
+ }
+ }
+
+ while !group.isEmpty {
+ do {
+ try await group.next()
+
+ } catch {
+ group.cancelAll()
+ throw error
+ }
+ }
+ }
+ }
+
+ @MainActor
+ func closeFlow(remoteConnection: NWConnection, error: Error?) {
+ remoteConnection.forceCancel()
+ flow.closeReadWithError(error)
+ flow.closeWriteWithError(error)
+ }
+
+ static let maxReceiveSize: Int = Int(Measurement(value: 2, unit: UnitInformationStorage.megabytes).converted(to: .bytes).value)
+
+ func copyInboundTraffic(from remoteConnection: NWConnection) async throws {
+ try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in
+ Task { @TCPFlowActor in
+ remoteConnection.receive(minimumIncompleteLength: 1,
+ maximumLength: Self.maxReceiveSize) { [weak flow] (data, _, isComplete, error) in
+ guard let flow else {
+ continuation.resume(throwing: RemoteConnectionError.cancelled)
+ return
+ }
+
+ switch (data, isComplete, error) {
+ case (.some(let data), _, _) where !data.isEmpty:
+ flow.write(data) { writeError in
+ if let writeError {
+ continuation.resume(throwing: writeError)
+ remoteConnection.cancel()
+ } else {
+ continuation.resume()
+ }
+ }
+ case (_, isComplete, _) where isComplete == true:
+ continuation.resume(throwing: RemoteConnectionError.complete)
+ remoteConnection.cancel()
+ case (_, _, .some(let error)):
+ continuation.resume(throwing: RemoteConnectionError.unhandledError(error))
+ remoteConnection.cancel()
+ default:
+ continuation.resume(throwing: RemoteConnectionError.complete)
+ remoteConnection.cancel()
+ }
+ }
+ }
+ }
+ }
+
+ func copyOutboundTraffic(to remoteConnection: NWConnection) async throws {
+ try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in
+ Task { @TCPFlowActor in
+ flow.readData { data, error in
+ switch (data, error) {
+ case (.some(let data), _) where !data.isEmpty:
+ remoteConnection.send(content: data, completion: .contentProcessed({ error in
+ if let error {
+ continuation.resume(throwing: error)
+ remoteConnection.cancel()
+ return
+ }
+
+ continuation.resume()
+ }))
+ case (_, .some(let error)):
+ continuation.resume(throwing: error)
+ remoteConnection.cancel()
+ default:
+ continuation.resume(throwing: RemoteConnectionError.complete)
+ remoteConnection.cancel()
+ }
+ }
+ }
+ }
+ }
+}
+
+extension TCPFlowManager: Hashable {
+ static func == (lhs: TCPFlowManager, rhs: TCPFlowManager) -> Bool {
+ lhs === rhs
+ }
+
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(flow)
+ }
+}
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/FlowManagers/UDPFlowManager.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/FlowManagers/UDPFlowManager.swift
new file mode 100644
index 0000000000..000f37d20e
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/FlowManagers/UDPFlowManager.swift
@@ -0,0 +1,329 @@
+//
+// UDPFlowManager.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 NetworkExtension
+import OSLog // swiftlint:disable:this enforce_os_log_wrapper
+
+/// A private global actor to handle UDP flows management
+///
+@globalActor
+struct UDPFlowActor {
+ actor ActorType { }
+
+ static let shared: ActorType = ActorType()
+}
+
+/// Class to handle UDP connections
+///
+/// This is necessary because as described in the reference comment for this implementation (see ``UDPFlowManager``'s documentation)
+/// it's noted that a single UDP flow can have to manage multiple connections.
+///
+@UDPFlowActor
+final class UDPConnectionManager {
+ let endpoint: NWEndpoint
+ private let connection: NWConnection
+ private let onReceive: (_ endpoint: NWEndpoint, _ result: Result) async -> Void
+
+ init(endpoint: NWHostEndpoint, interface: NWInterface?, onReceive: @UDPFlowActor @escaping (_ endpoint: NWEndpoint, _ result: Result) async -> Void) {
+ let host = Network.NWEndpoint.Host(endpoint.hostname)
+ let port = Network.NWEndpoint.Port(endpoint.port)!
+
+ let parameters = NWParameters.udp
+ parameters.preferNoProxies = true
+ parameters.requiredInterface = interface
+ parameters.prohibitedInterfaceTypes = [.other]
+
+ let connection = NWConnection(host: host, port: port, using: parameters)
+
+ self.connection = connection
+ self.endpoint = endpoint
+ self.onReceive = onReceive
+ }
+
+ deinit {
+ // Just making extra sure we don't retain anything we don't need to
+ connection.stateUpdateHandler = nil
+ connection.cancel()
+ }
+
+ // MARK: - General Operation
+
+ /// Starts the operation of this connection manager
+ ///
+ /// Can be called multiple times safely.
+ ///
+ private func start() async throws {
+ guard connection.state == .setup else {
+ return
+ }
+
+ try await connect()
+
+ Task {
+ while true {
+ do {
+ let datagram = try await receive()
+ await onReceive(endpoint, .success(datagram))
+ } catch {
+ connection.cancel()
+ await onReceive(endpoint, .failure(error))
+ break
+ }
+ }
+ }
+ }
+
+ // MARK: - Connection Management
+
+ private func connect() async throws {
+ try await withCheckedThrowingContinuation { continuation in
+ connect { result in
+ switch result {
+ case .success:
+ continuation.resume()
+ case .failure(let error):
+ continuation.resume(throwing: error)
+ }
+ }
+ }
+ }
+
+ private func connect(completion: @escaping (Result) -> Void) {
+ connection.stateUpdateHandler = { [connection] (state: NWConnection.State) in
+ switch state {
+ case .ready:
+ connection.stateUpdateHandler = nil
+ completion(.success(()))
+ case .cancelled:
+ connection.stateUpdateHandler = nil
+ completion(.failure(RemoteConnectionError.cancelled))
+ case .failed(let error), .waiting(let error):
+ connection.stateUpdateHandler = nil
+ completion(.failure(RemoteConnectionError.couldNotEstablishConnection(error)))
+ default:
+ break
+ }
+ }
+
+ connection.start(queue: .global())
+ }
+
+ // MARK: - Receiving from remote
+
+ private func receive() async throws -> Data {
+ try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in
+ connection.receiveMessage { [weak self] data, _, isComplete, error in
+
+ guard self != nil else {
+ continuation.resume(throwing: RemoteConnectionError.cancelled)
+ return
+ }
+
+ switch (data, isComplete, error) {
+ case (let data?, _, _):
+ continuation.resume(returning: data)
+ case (_, true, _):
+ continuation.resume(throwing: RemoteConnectionError.cancelled)
+ case (_, _, let error?):
+ continuation.resume(throwing: RemoteConnectionError.unhandledError(error))
+ default:
+ continuation.resume(throwing: RemoteConnectionError.cancelled)
+ }
+ }
+ }
+ }
+
+ // MARK: - Writing datagrams
+
+ func write(datagram: Data) async throws {
+ try await start()
+
+ try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in
+ connection.send(content: datagram, completion: .contentProcessed({ error in
+ if let error {
+ continuation.resume(throwing: error)
+ return
+ }
+
+ continuation.resume()
+ }))
+ }
+ }
+}
+
+extension UDPConnectionManager: Hashable, Equatable {
+ // MARK: - Equatable
+
+ static func == (lhs: UDPConnectionManager, rhs: UDPConnectionManager) -> Bool {
+ lhs.endpoint == rhs.endpoint
+ }
+
+ // MARK: - Hashable
+
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(endpoint)
+ }
+}
+
+/// UDP flow manager class
+///
+/// There is documentation explaining how to handle TCP flows here:
+/// https://developer.apple.com/documentation/networkextension/app_proxy_provider/handling_flow_copying?changes=_8
+///
+/// Unfortunately there isn't good official documentation showcasing how to implement UDP flow management.
+/// The best we could fine are two comments by an Apple engineer that shine some light on how that implementation should be like:
+/// https://developer.apple.com/forums/thread/678464?answerId=671531022#671531022
+/// https://developer.apple.com/forums/thread/678464?answerId=671892022#671892022
+///
+/// This class is the result of implementing the description found in that comment.
+///
+@UDPFlowActor
+final class UDPFlowManager {
+ private let flow: NEAppProxyUDPFlow
+ private var interface: NWInterface?
+
+ private var connectionManagers = [NWEndpoint: UDPConnectionManager]()
+
+ init(flow: NEAppProxyUDPFlow) {
+ self.flow = flow
+ }
+
+ func start(interface: NWInterface) async throws {
+ self.interface = interface
+ try await connectAndStartRunLoop()
+ }
+
+ private func connectAndStartRunLoop() async throws {
+ do {
+ try await flow.open(withLocalEndpoint: nil)
+ try await startDataCopyLoop()
+
+ flow.closeReadWithError(nil)
+ flow.closeWriteWithError(nil)
+ } catch {
+ flow.closeReadWithError(error)
+ flow.closeWriteWithError(error)
+ }
+ }
+
+ private func startDataCopyLoop() async throws {
+ while true {
+ try await copyOutoundTraffic()
+ }
+ }
+
+ func copyInboundTraffic(endpoint: NWEndpoint, result: Result) async {
+ switch result {
+ case .success(let data):
+ do {
+ try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in
+ flow.writeDatagrams([data], sentBy: [endpoint]) { error in
+ if let error {
+ continuation.resume(throwing: error)
+ return
+ }
+
+ continuation.resume()
+ }
+ }
+ } catch {
+ // Any failure means we close the connection
+ connectionManagers.removeValue(forKey: endpoint)
+ }
+ case .failure:
+ // Any failure means we close the connection
+ connectionManagers.removeValue(forKey: endpoint)
+ }
+ }
+
+ func copyOutoundTraffic() async throws {
+ let (datagrams, endpoints) = try await read()
+
+ // Ref: https://developer.apple.com/documentation/networkextension/neappproxyudpflow/1406576-readdatagrams
+ if datagrams.isEmpty || endpoints.isEmpty {
+ throw NEAppProxyFlowError(.aborted)
+ }
+
+ for (datagram, endpoint) in zip(datagrams, endpoints) {
+ guard let endpoint = endpoint as? NWHostEndpoint else {
+ // Not sure what to do about this...
+ continue
+ }
+
+ let manager = connectionManagers[endpoint] ?? {
+ let manager = UDPConnectionManager(endpoint: endpoint, interface: interface, onReceive: copyInboundTraffic)
+ connectionManagers[endpoint] = manager
+ return manager
+ }()
+
+ do {
+ try await manager.write(datagram: datagram)
+ } catch {
+ // Any failure means we close the connection
+ connectionManagers.removeValue(forKey: endpoint)
+ }
+ }
+ }
+
+ /// Reads datagrams from the flow.
+ ///
+ /// Apple's documentation is very bad here, but it seems each datagram is corresponded with an endpoint at the same position in the array
+ /// as mentioned here: https://developer.apple.com/forums/thread/75893
+ ///
+ private func read() async throws -> (datagrams: [Data], endpoints: [NWEndpoint]) {
+ try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<([Data], [NWEndpoint]), Error>) in
+ flow.readDatagrams { datagrams, endpoints, error in
+ if let error {
+ continuation.resume(throwing: error)
+ return
+ }
+
+ guard let datagrams, let endpoints else {
+ continuation.resume(throwing: NEAppProxyFlowError(.aborted))
+ return
+ }
+
+ continuation.resume(returning: (datagrams, endpoints))
+ }
+ }
+ }
+
+ private func send(datagram: Data, through remoteConnection: NWConnection) async throws {
+ try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in
+ remoteConnection.send(content: datagram, completion: .contentProcessed({ error in
+ if let error {
+ continuation.resume(throwing: error)
+ return
+ }
+
+ continuation.resume()
+ }))
+ }
+ }
+}
+
+extension UDPFlowManager: Hashable {
+ static func == (lhs: UDPFlowManager, rhs: UDPFlowManager) -> Bool {
+ lhs === rhs
+ }
+
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(flow)
+ }
+}
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/IPC/TransparentProxyAppMessageHandler.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/IPC/TransparentProxyAppMessageHandler.swift
new file mode 100644
index 0000000000..12339a673d
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/IPC/TransparentProxyAppMessageHandler.swift
@@ -0,0 +1,82 @@
+//
+// TransparentProxyAppMessageHandler.swift
+//
+// Copyright © 2024 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 OSLog // swiftlint:disable:this enforce_os_log_wrapper
+
+/// Handles app messages
+///
+final class TransparentProxyAppMessageHandler {
+
+ private let settings: TransparentProxySettings
+
+ init(settings: TransparentProxySettings) {
+ self.settings = settings
+ }
+
+ func handle(_ data: Data) async -> Data? {
+ do {
+ let message = try JSONDecoder().decode(TransparentProxyMessage.self, from: data)
+ return await handle(message)
+ } catch {
+ return nil
+ }
+ }
+
+ /// Handles a message.
+ ///
+ /// This method will wrap the message into a request with a completion handler, and will process it.
+ /// The reason why this method wraps the message in a request is to ensure that the response
+ /// type stays syncrhonized between app and provider.
+ ///
+ private func handle(_ message: TransparentProxyMessage) async -> Data? {
+ await withCheckedContinuation { continuation in
+ var request: TransparentProxyRequest
+
+ switch message {
+ case .changeSetting(let change):
+ request = .changeSetting(change, responseHandler: {
+ continuation.resume(returning: nil)
+ })
+ }
+
+ handle(request)
+ }
+ }
+
+ /// Handles a request and calls the response handler when done.
+ ///
+ private func handle(_ request: TransparentProxyRequest) {
+ switch request {
+ case .changeSetting(let change, let responseHandler):
+ handle(change)
+ responseHandler()
+ }
+ }
+
+ /// Handles a settings change.
+ ///
+ private func handle(_ settingChange: TransparentProxySettings.Change) {
+ switch settingChange {
+ case .appRoutingRules(let routingRules):
+ settings.appRoutingRules = routingRules
+ case .excludedDomains(let excludedDomains):
+ settings.excludedDomains = excludedDomains
+ }
+ }
+}
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/IPC/TransparentProxyRequest.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/IPC/TransparentProxyRequest.swift
new file mode 100644
index 0000000000..0881dba3b1
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/IPC/TransparentProxyRequest.swift
@@ -0,0 +1,67 @@
+//
+// TransparentProxyRequest.swift
+//
+// Copyright © 2024 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 NetworkExtension
+
+public enum TransparentProxyMessage: Codable {
+ case changeSetting(_ change: TransparentProxySettings.Change)
+}
+
+/// A request for the TransparentProxyProvider.
+///
+/// This enum associates a request with a response handler making XPC communication simpler.
+/// Once the request completes, `responseHandler` will be called with the result.
+///
+public enum TransparentProxyRequest {
+ case changeSetting(_ settingChange: TransparentProxySettings.Change, responseHandler: () -> Void)
+
+ var message: TransparentProxyMessage {
+ switch self {
+ case .changeSetting(let change, _):
+ return .changeSetting(change)
+ }
+ }
+
+ func handleResponse(data: Data?) {
+ switch self {
+ case .changeSetting(_, let handleResponse):
+ handleResponse()
+ }
+ }
+}
+
+/// Respresents a transparent proxy session.
+///
+/// Offers basic IPC communication support for the app that owns the proxy. This mechanism
+/// is implemented through `NETunnelProviderSession` which means only the app that
+/// owns the proxy can use this class.
+///
+public class TransparentProxySession {
+
+ private let session: NETunnelProviderSession
+
+ init(_ session: NETunnelProviderSession) {
+ self.session = session
+ }
+
+ func send(_ request: TransparentProxyRequest) throws {
+ let payload = try JSONEncoder().encode(request.message)
+ try session.sendProviderMessage(payload, responseHandler: request.handleResponse(data:))
+ }
+}
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Pixels/TransparentProxyControllerPixel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Pixels/TransparentProxyControllerPixel.swift
new file mode 100644
index 0000000000..b4c9b8cebb
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Pixels/TransparentProxyControllerPixel.swift
@@ -0,0 +1,89 @@
+//
+// TransparentProxyControllerPixel.swift
+//
+// Copyright © 2024 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
+
+extension TransparentProxyController.StartError: PixelKitEventErrorDetails {
+ public var underlyingError: Error? {
+ switch self {
+ case .failedToLoadConfiguration(let underlyingError),
+ .failedToSaveConfiguration(let underlyingError),
+ .failedToStartProvider(let underlyingError):
+ return underlyingError
+ default:
+ return nil
+ }
+ }
+}
+
+extension TransparentProxyController {
+
+ public enum Event: PixelKitEventV2 {
+ case startInitiated
+ case startSuccess
+ case startFailure(_ error: Error)
+
+ // MARK: - PixelKit.Event
+
+ public var name: String {
+ namePrefix + "_" + nameSuffix
+ }
+
+ public var parameters: [String: String]? {
+ switch self {
+ case .startInitiated:
+ return nil
+ case .startSuccess:
+ return nil
+ case .startFailure:
+ return nil
+ }
+ }
+
+ // MARK: - PixelKit Support
+
+ private static let pixelNamePrefix = "vpn_proxy_controller"
+
+ private var namePrefix: String {
+ Self.pixelNamePrefix
+ }
+
+ private var nameSuffix: String {
+ switch self {
+ case .startInitiated:
+ return "start_initiated"
+ case .startFailure:
+ return "start_failure"
+ case .startSuccess:
+ return "start_success"
+ }
+ }
+
+ public var error: Error? {
+ switch self {
+ case .startInitiated:
+ return nil
+ case .startFailure(let error):
+ return error
+ case .startSuccess:
+ return nil
+ }
+ }
+ }
+}
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Pixels/TransparentProxyProviderPixel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Pixels/TransparentProxyProviderPixel.swift
new file mode 100644
index 0000000000..aff7421bea
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Pixels/TransparentProxyProviderPixel.swift
@@ -0,0 +1,93 @@
+//
+// TransparentProxyProviderPixel.swift
+//
+// Copyright © 2024 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
+
+extension TransparentProxyProvider.StartError: ErrorWithPixelParameters {
+ public var errorParameters: [String: String] {
+ switch self {
+ case .failedToUpdateNetworkSettings(let underlyingError):
+ return [
+ PixelKit.Parameters.underlyingErrorCode: "\((underlyingError as NSError).code)",
+ PixelKit.Parameters.underlyingErrorDesc: (underlyingError as NSError).domain,
+ ]
+ default:
+ return [:]
+ }
+ }
+}
+
+extension TransparentProxyProvider {
+
+ public enum Event: PixelKitEventV2 {
+ case failedToUpdateNetworkSettings(_ error: Error)
+ case startInitiated
+ case startSuccess
+ case startFailure(_ error: Error)
+
+ private static let pixelNamePrefix = "vpn_proxy_provider"
+
+ private var namePrefix: String {
+ Self.pixelNamePrefix
+ }
+
+ private var namePostfix: String {
+ switch self {
+ case .failedToUpdateNetworkSettings:
+ return "failed_to_update_network_settings"
+ case .startFailure:
+ return "start_failure"
+ case .startInitiated:
+ return "start_initiated"
+ case .startSuccess:
+ return "start_success"
+ }
+ }
+
+ public var name: String {
+ namePrefix + "_" + namePostfix
+ }
+
+ public var parameters: [String: String]? {
+ switch self {
+ case.failedToUpdateNetworkSettings:
+ return nil
+ case .startFailure:
+ return nil
+ case .startInitiated:
+ return nil
+ case .startSuccess:
+ return nil
+ }
+ }
+
+ public var error: Error? {
+ switch self {
+ case .failedToUpdateNetworkSettings(let error):
+ return error
+ case .startInitiated:
+ return nil
+ case .startFailure(let error):
+ return error
+ case .startSuccess:
+ return nil
+ }
+ }
+ }
+}
diff --git a/DuckDuckGoVPN/Bundle+Configuration.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/RoutingRules/VPNAppRoutingRules.swift
similarity index 56%
rename from DuckDuckGoVPN/Bundle+Configuration.swift
rename to LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/RoutingRules/VPNAppRoutingRules.swift
index 936c44a4a8..15d4a4e1a1 100644
--- a/DuckDuckGoVPN/Bundle+Configuration.swift
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/RoutingRules/VPNAppRoutingRules.swift
@@ -1,7 +1,7 @@
//
-// Bundle+Configuration.swift
+// VPNAppRoutingRules.swift
//
-// Copyright © 2023 DuckDuckGo. All rights reserved.
+// Copyright © 2024 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.
@@ -18,14 +18,4 @@
import Foundation
-extension Bundle {
- private static let networkExtensionBundleIDKey = "SYSEX_BUNDLE_ID"
-
- var networkExtensionBundleID: String {
- guard let bundleID = object(forInfoDictionaryKey: Self.networkExtensionBundleIDKey) as? String else {
- fatalError("Info.plist is missing \(Self.networkExtensionBundleIDKey)")
- }
-
- return bundleID
- }
-}
+public typealias VPNAppRoutingRules = [String: VPNRoutingRule]
diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/SystemExtensionAndNotificationTargets/NetworkProtectionExtensionMachService.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/RoutingRules/VPNRoutingRule.swift
similarity index 59%
rename from DuckDuckGo/NetworkProtection/NetworkExtensionTargets/SystemExtensionAndNotificationTargets/NetworkProtectionExtensionMachService.swift
rename to LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/RoutingRules/VPNRoutingRule.swift
index e90627cbc2..b96a0773cf 100644
--- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/SystemExtensionAndNotificationTargets/NetworkProtectionExtensionMachService.swift
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/RoutingRules/VPNRoutingRule.swift
@@ -1,7 +1,7 @@
//
-// NetworkProtectionExtensionMachService.swift
+// VPNRoutingRule.swift
//
-// Copyright © 2023 DuckDuckGo. All rights reserved.
+// Copyright © 2024 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.
@@ -18,14 +18,12 @@
import Foundation
-/// Helper methods associated with mach services.
+/// Routing rules
///
-final class NetworkProtectionExtensionMachService {
-
- /// Retrieves the mach service name from a network extension bundle.
- ///
- static func serviceName() -> String {
- NetworkProtectionBundle.extensionBundle().machServiceName
- }
-
+/// Note that there's no need for an `ignore` case because that's achieved by not having a rule
+/// in the first place.
+///
+public enum VPNRoutingRule: Codable, Equatable {
+ case block
+ case exclude
}
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Settings/TransparentProxySettings.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Settings/TransparentProxySettings.swift
new file mode 100644
index 0000000000..db010ec2b5
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Settings/TransparentProxySettings.swift
@@ -0,0 +1,134 @@
+//
+// TransparentProxySettings.swift
+//
+// Copyright © 2024 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
+
+public final class TransparentProxySettings {
+ public enum Change: Codable {
+ case appRoutingRules(_ routingRules: VPNAppRoutingRules)
+ case excludedDomains(_ excludedDomains: [String])
+ }
+
+ let defaults: UserDefaults
+
+ private(set) public lazy var changePublisher: AnyPublisher = {
+ Publishers.MergeMany(
+ defaults.vpnProxyAppRoutingRulesPublisher
+ .dropFirst()
+ .removeDuplicates()
+ .map { routingRules in
+ Change.appRoutingRules(routingRules)
+ }.eraseToAnyPublisher(),
+ defaults.vpnProxyExcludedDomainsPublisher
+ .dropFirst()
+ .removeDuplicates()
+ .map { excludedDomains in
+ Change.excludedDomains(excludedDomains)
+ }.eraseToAnyPublisher()
+ ).eraseToAnyPublisher()
+ }()
+
+ public init(defaults: UserDefaults) {
+ self.defaults = defaults
+ }
+
+ // MARK: - Settings
+
+ public var appRoutingRules: VPNAppRoutingRules {
+ get {
+ defaults.vpnProxyAppRoutingRules
+ }
+
+ set {
+ defaults.vpnProxyAppRoutingRules = newValue
+ }
+ }
+
+ public var excludedDomains: [String] {
+ get {
+ defaults.vpnProxyExcludedDomains
+ }
+
+ set {
+ defaults.vpnProxyExcludedDomains = newValue
+ }
+ }
+
+ // MARK: - Reset to factory defaults
+
+ public func resetAll() {
+ defaults.resetVPNProxyAppRoutingRules()
+ defaults.resetVPNProxyExcludedDomains()
+ }
+
+ // MARK: - App routing rules logic
+
+ public func isBlocking(_ appIdentifier: String) -> Bool {
+ appRoutingRules[appIdentifier] == .block
+ }
+
+ public func isExcluding(_ appIdentifier: String) -> Bool {
+ appRoutingRules[appIdentifier] == .exclude
+ }
+
+ public func toggleBlocking(for appIdentifier: String) {
+ if isBlocking(appIdentifier) {
+ appRoutingRules.removeValue(forKey: appIdentifier)
+ } else {
+ appRoutingRules[appIdentifier] = .block
+ }
+ }
+
+ public func toggleExclusion(for appIdentifier: String) {
+ if isExcluding(appIdentifier) {
+ appRoutingRules.removeValue(forKey: appIdentifier)
+ } else {
+ appRoutingRules[appIdentifier] = .exclude
+ }
+ }
+
+ // MARK: - Snapshot support
+
+ public func snapshot() -> TransparentProxySettingsSnapshot {
+ .init(appRoutingRules: appRoutingRules, excludedDomains: excludedDomains)
+ }
+
+ public func apply(_ snapshot: TransparentProxySettingsSnapshot) {
+ appRoutingRules = snapshot.appRoutingRules
+ excludedDomains = snapshot.excludedDomains
+ }
+}
+
+extension TransparentProxySettings: CustomStringConvertible {
+ public var description: String {
+ """
+ TransparentProxySettings {\n
+ appRoutingRules: \(appRoutingRules)\n
+ excludedDomains: \(excludedDomains)\n
+ }
+ """
+ }
+}
+
+public struct TransparentProxySettingsSnapshot: Codable {
+ public static let key = "com.duckduckgo.TransparentProxySettingsSnapshot"
+
+ public let appRoutingRules: VPNAppRoutingRules
+ public let excludedDomains: [String]
+}
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Settings/UserDefaultsExtensions/UserDefaults+excludedApps.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Settings/UserDefaultsExtensions/UserDefaults+excludedApps.swift
new file mode 100644
index 0000000000..1090ed1626
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Settings/UserDefaultsExtensions/UserDefaults+excludedApps.swift
@@ -0,0 +1,79 @@
+//
+// UserDefaults+excludedApps.swift
+//
+// Copyright © 2024 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
+
+extension UserDefaults {
+ private var vpnProxyAppRoutingRulesDataKey: String {
+ "vpnProxyAppRoutingRulesData"
+ }
+
+ @objc
+ dynamic var vpnProxyAppRoutingRulesData: Data? {
+ get {
+ object(forKey: vpnProxyAppRoutingRulesDataKey) as? Data
+ }
+
+ set {
+ guard let newValue,
+ newValue.count > 0 else {
+
+ removeObject(forKey: vpnProxyAppRoutingRulesDataKey)
+ return
+ }
+
+ set(newValue, forKey: vpnProxyAppRoutingRulesDataKey)
+ }
+ }
+
+ var vpnProxyAppRoutingRules: VPNAppRoutingRules {
+ get {
+ guard let data = vpnProxyAppRoutingRulesData,
+ let routingRules = try? JSONDecoder().decode(VPNAppRoutingRules.self, from: data) else {
+ return [:]
+ }
+
+ return routingRules
+ }
+
+ set {
+ if newValue.isEmpty {
+ vpnProxyAppRoutingRulesData = nil
+ return
+ }
+
+ guard let data = try? JSONEncoder().encode(newValue) else {
+ vpnProxyAppRoutingRulesData = nil
+ return
+ }
+
+ vpnProxyAppRoutingRulesData = data
+ }
+ }
+
+ var vpnProxyAppRoutingRulesPublisher: AnyPublisher {
+ publisher(for: \.vpnProxyAppRoutingRulesData).map { [weak self] _ in
+ self?.vpnProxyAppRoutingRules ?? [:]
+ }.eraseToAnyPublisher()
+ }
+
+ func resetVPNProxyAppRoutingRules() {
+ removeObject(forKey: vpnProxyAppRoutingRulesDataKey)
+ }
+}
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Settings/UserDefaultsExtensions/UserDefaults+excludedDomains.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Settings/UserDefaultsExtensions/UserDefaults+excludedDomains.swift
new file mode 100644
index 0000000000..7500178da7
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/Settings/UserDefaultsExtensions/UserDefaults+excludedDomains.swift
@@ -0,0 +1,51 @@
+//
+// UserDefaults+excludedDomains.swift
+//
+// Copyright © 2024 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
+
+extension UserDefaults {
+ private var vpnProxyExcludedDomainsKey: String {
+ "vpnProxyExcludedDomains"
+ }
+
+ @objc
+ dynamic var vpnProxyExcludedDomains: [String] {
+ get {
+ object(forKey: vpnProxyExcludedDomainsKey) as? [String] ?? []
+ }
+
+ set {
+ guard newValue.count > 0 else {
+
+ removeObject(forKey: vpnProxyExcludedDomainsKey)
+ return
+ }
+
+ set(newValue, forKey: vpnProxyExcludedDomainsKey)
+ }
+ }
+
+ var vpnProxyExcludedDomainsPublisher: AnyPublisher<[String], Never> {
+ publisher(for: \.vpnProxyExcludedDomains).eraseToAnyPublisher()
+ }
+
+ func resetVPNProxyExcludedDomains() {
+ removeObject(forKey: vpnProxyExcludedDomainsKey)
+ }
+}
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/TransparentProxyController.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/TransparentProxyController.swift
new file mode 100644
index 0000000000..fdc7fb3177
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/TransparentProxyController.swift
@@ -0,0 +1,293 @@
+//
+// TransparentProxyController.swift
+//
+// Copyright © 2024 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 NetworkExtension
+import NetworkProtection
+import OSLog // swiftlint:disable:this enforce_os_log_wrapper
+import PixelKit
+import SystemExtensions
+
+/// Controller for ``TransparentProxyProvider``
+///
+@MainActor
+public final class TransparentProxyController {
+
+ public enum StartError: Error {
+ case attemptToStartWithoutBackingActiveFeatures
+ case couldNotRetrieveProtocolConfiguration
+ case couldNotEncodeSettingsSnapshot
+ case failedToLoadConfiguration(_ error: Error)
+ case failedToSaveConfiguration(_ error: Error)
+ case failedToStartProvider(_ error: Error)
+ }
+
+ public typealias EventCallback = (Event) -> Void
+ public typealias ManagerSetupCallback = (_ manager: NETransparentProxyManager) async -> Void
+
+ /// Dry mode means this won't really do anything to start or stop the proxy.
+ ///
+ /// This is useful for testing.
+ ///
+ private let dryMode: Bool
+
+ /// The bundleID of the extension that contains the ``TransparentProxyProvider``.
+ ///
+ private let extensionID: String
+
+ /// The event handler
+ ///
+ public var eventHandler: EventCallback?
+
+ /// Callback to set up a ``NETransparentProxyManager``.
+ ///
+ public let setup: ManagerSetupCallback
+
+ private var internalManager: NETransparentProxyManager?
+
+ /// Whether the proxy settings should be stored in the provider configuration.
+ ///
+ /// We recommend setting this to true if the provider is running in a System Extension and can't access
+ /// shared `TransparentProxySettings`. If the provider is in an App Extension you should instead
+ /// use a shared `TransparentProxySettings` and set this to false.
+ ///
+ private let storeSettingsInProviderConfiguration: Bool
+ public let settings: TransparentProxySettings
+ private let notificationCenter: NotificationCenter
+ private var cancellables = Set()
+
+ // MARK: - Initializers
+
+ /// Default initializer.
+ ///
+ /// - Parameters:
+ /// - extensionID: the bundleID for the extension that contains the ``TransparentProxyProvider``.
+ /// This class DOES NOT take any responsibility in installing the system extension. It only uses
+ /// the extensionID to identify the appropriate manager configuration to load / save.
+ /// - storeSettingsInProviderConfiguration: whether the provider configuration will be used for storing
+ /// the proxy settings. Should be `true` when using a System Extension and `false` when using
+ /// an App Extension.
+ /// - settings: the settings to use for this proxy.
+ /// - dryMode: whether this class is initialized in dry mode.
+ /// - setup: a callback that will be called whenever a ``NETransparentProxyManager`` needs
+ /// to be setup.
+ ///
+ public init(extensionID: String,
+ storeSettingsInProviderConfiguration: Bool,
+ settings: TransparentProxySettings,
+ notificationCenter: NotificationCenter = .default,
+ dryMode: Bool = false,
+ setup: @escaping ManagerSetupCallback) {
+
+ self.dryMode = dryMode
+ self.extensionID = extensionID
+ self.notificationCenter = notificationCenter
+ self.settings = settings
+ self.setup = setup
+ self.storeSettingsInProviderConfiguration = storeSettingsInProviderConfiguration
+
+ subscribeToProviderConfigurationChanges()
+ subscribeToSettingsChanges()
+ }
+
+ // MARK: - Relay Settings Changes
+
+ private func subscribeToProviderConfigurationChanges() {
+ notificationCenter.publisher(for: .NEVPNConfigurationChange)
+ .receive(on: DispatchQueue.main)
+ .sink { _ in
+ self.reloadProviderConfiguration()
+ }
+ .store(in: &cancellables)
+ }
+
+ private func reloadProviderConfiguration() {
+ Task { @MainActor in
+ try? await self.manager?.loadFromPreferences()
+ }
+ }
+
+ private func subscribeToSettingsChanges() {
+ settings.changePublisher
+ .receive(on: DispatchQueue.main)
+ .sink(receiveValue: relay(_:))
+ .store(in: &cancellables)
+ }
+
+ private func relay(_ change: TransparentProxySettings.Change) {
+ Task { @MainActor in
+ guard let session = await session else {
+ return
+ }
+
+ switch session.status {
+ case .connected, .connecting, .reasserting:
+ break
+ default:
+ return
+ }
+
+ try TransparentProxySession(session).send(.changeSetting(change, responseHandler: {
+ // no-op
+ }))
+ }
+ }
+
+ // MARK: - Setting up NETransparentProxyManager
+
+ /// Loads a saved manager
+ ///
+ /// This is a bit of a hack that will be run just once for the instance. The reason we want this to run only once is that
+ /// `NETransparentProxyManager.loadAllFromPreferences()` has a bug where it triggers status change
+ /// notifications. If the code trying to retrieve the manager is the result of a notification, we may soon find outselves
+ /// in an infinite loop.
+ ///
+ private var triedLoadingManager = false
+
+ /// Loads the configuration matching our ``extensionID``.
+ ///
+ public var manager: NETransparentProxyManager? {
+ get async {
+ if let internalManager {
+ return internalManager
+ }
+
+ if !triedLoadingManager {
+ triedLoadingManager = true
+
+ let manager = try? await NETransparentProxyManager.loadAllFromPreferences().first { manager in
+ (manager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == extensionID
+ }
+ self.internalManager = manager
+ }
+
+ return internalManager
+ }
+ }
+
+ /// Loads an existing configuration or creates a new one, if one doesn't exist.
+ ///
+ /// - Returns a properly configured `NETransparentProxyManager`.
+ ///
+ public func loadOrCreateConfiguration() async throws -> NETransparentProxyManager {
+ let manager = await manager ?? {
+ let manager = NETransparentProxyManager()
+ internalManager = manager
+ return manager
+ }()
+
+ await setup(manager)
+ try setupAdditionalProviderConfiguration(manager)
+
+ try await manager.saveToPreferences()
+ try await manager.loadFromPreferences()
+
+ return manager
+ }
+
+ private func setupAdditionalProviderConfiguration(_ manager: NETransparentProxyManager) throws {
+ guard storeSettingsInProviderConfiguration else {
+ return
+ }
+
+ guard let providerProtocol = manager.protocolConfiguration as? NETunnelProviderProtocol else {
+ throw StartError.couldNotRetrieveProtocolConfiguration
+ }
+
+ var providerConfiguration = providerProtocol.providerConfiguration ?? [String: Any]()
+
+ guard let encodedSettings = try? JSONEncoder().encode(settings.snapshot()),
+ let encodedSettingsString = String(data: encodedSettings, encoding: .utf8) else {
+
+ throw StartError.couldNotEncodeSettingsSnapshot
+ }
+
+ providerConfiguration[TransparentProxySettingsSnapshot.key] = encodedSettingsString as NSString
+ providerProtocol.providerConfiguration = providerConfiguration
+
+ }
+
+ // MARK: - Connection & Session
+
+ public var connection: NEVPNConnection? {
+ get async {
+ await manager?.connection
+ }
+ }
+
+ public var session: NETunnelProviderSession? {
+ get async {
+ guard let manager = await manager,
+ let session = manager.connection as? NETunnelProviderSession else {
+
+ // The active connection is not running, so there's no session, this is acceptable
+ return nil
+ }
+
+ return session
+ }
+ }
+
+ // MARK: - Connection
+
+ public var status: NEVPNStatus {
+ get async {
+ await connection?.status ?? .disconnected
+ }
+ }
+
+ // MARK: - Start & stop the proxy
+
+ public var isRequiredForActiveFeatures: Bool {
+ settings.appRoutingRules.count > 0 || settings.excludedDomains.count > 0
+ }
+
+ public func start() async throws {
+ eventHandler?(.startInitiated)
+
+ guard isRequiredForActiveFeatures else {
+ let error = StartError.attemptToStartWithoutBackingActiveFeatures
+ eventHandler?(.startFailure(error))
+ throw error
+ }
+
+ let manager: NETransparentProxyManager
+
+ do {
+ manager = try await loadOrCreateConfiguration()
+ } catch {
+ eventHandler?(.startFailure(error))
+ throw error
+ }
+
+ do {
+ try manager.connection.startVPNTunnel(options: [:])
+ } catch {
+ let error = StartError.failedToStartProvider(error)
+ eventHandler?(.startFailure(error))
+ throw error
+ }
+
+ eventHandler?(.startSuccess)
+ }
+
+ public func stop() async {
+ await connection?.stopVPNTunnel()
+ }
+}
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/TransparentProxyProvider.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/TransparentProxyProvider.swift
new file mode 100644
index 0000000000..33b75fd73b
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/TransparentProxyProvider.swift
@@ -0,0 +1,389 @@
+//
+// TransparentProxyProvider.swift
+//
+// Copyright © 2024 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 NetworkExtension
+import NetworkProtection
+import os.log // swiftlint:disable:this enforce_os_log_wrapper
+import SystemConfiguration
+
+open class TransparentProxyProvider: NETransparentProxyProvider {
+
+ public enum StartError: Error {
+ case missingProviderConfiguration
+ case failedToUpdateNetworkSettings(underlyingError: Error)
+ }
+
+ public typealias EventCallback = (Event) -> Void
+ public typealias LoadOptionsCallback = (_ options: [String: Any]?) throws -> Void
+
+ static let dnsPort = 53
+
+ @TCPFlowActor
+ var tcpFlowManagers = Set()
+
+ @UDPFlowActor
+ var udpFlowManagers = Set()
+
+ private let monitor = nw_path_monitor_create()
+ var directInterface: nw_interface_t?
+
+ private let bMonitor = NWPathMonitor()
+ var interface: NWInterface?
+
+ public let configuration: Configuration
+ public let settings: TransparentProxySettings
+
+ @MainActor
+ public var isRunning = false
+
+ public var eventHandler: EventCallback?
+ private let logger: Logger
+
+ private lazy var appMessageHandler = TransparentProxyAppMessageHandler(settings: settings)
+
+ // MARK: - Init
+
+ public init(settings: TransparentProxySettings,
+ configuration: Configuration,
+ logger: Logger) {
+
+ self.configuration = configuration
+ self.logger = logger
+ self.settings = settings
+
+ logger.debug("[+] \(String(describing: Self.self), privacy: .public)")
+ }
+
+ deinit {
+ logger.debug("[-] \(String(describing: Self.self), privacy: .public)")
+ }
+
+ private func loadProviderConfiguration() throws {
+ guard configuration.loadSettingsFromProviderConfiguration else {
+ return
+ }
+
+ guard let providerConfiguration = (protocolConfiguration as? NETunnelProviderProtocol)?.providerConfiguration,
+ let encodedSettingsString = providerConfiguration[TransparentProxySettingsSnapshot.key] as? String,
+ let encodedSettings = encodedSettingsString.data(using: .utf8) else {
+
+ throw StartError.missingProviderConfiguration
+ }
+
+ let snapshot = try JSONDecoder().decode(TransparentProxySettingsSnapshot.self, from: encodedSettings)
+ settings.apply(snapshot)
+ }
+
+ @MainActor
+ public func updateNetworkSettings() async throws {
+ try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in
+ Task { @MainActor in
+ let networkSettings = makeNetworkSettings()
+ logger.log("Updating network settings: \(String(describing: networkSettings), privacy: .public)")
+
+ setTunnelNetworkSettings(networkSettings) { [eventHandler, logger] error in
+ if let error {
+ logger.error("Failed to update network settings: \(String(describing: error), privacy: .public)")
+ eventHandler?(.failedToUpdateNetworkSettings(error))
+ continuation.resume(throwing: error)
+ return
+ }
+
+ logger.log("Successfully Updated network settings: \(String(describing: error), privacy: .public))")
+ continuation.resume()
+ }
+ }
+ }
+ }
+
+ private func makeNetworkSettings() -> NETransparentProxyNetworkSettings {
+ let networkSettings = NETransparentProxyNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
+
+ networkSettings.includedNetworkRules = [
+ NENetworkRule(remoteNetwork: NWHostEndpoint(hostname: "127.0.0.1", port: ""), remotePrefix: 0, localNetwork: nil, localPrefix: 0, protocol: .any, direction: .outbound)
+ ]
+
+ return networkSettings
+ }
+
+ override public func startProxy(options: [String: Any]?,
+ completionHandler: @escaping (Error?) -> Void) {
+
+ eventHandler?(.startInitiated)
+
+ logger.log(
+ """
+ Starting proxy\n
+ > configuration: \(String(describing: self.configuration), privacy: .public)\n
+ > settings: \(String(describing: self.settings), privacy: .public)\n
+ > options: \(String(describing: options), privacy: .public)
+ """)
+
+ do {
+ try loadProviderConfiguration()
+ } catch {
+ logger.error("Failed to load provider configuration, bailing out")
+ eventHandler?(.startFailure(error))
+ completionHandler(error)
+ return
+ }
+
+ Task { @MainActor in
+ do {
+ startMonitoringNetworkInterfaces()
+
+ try await updateNetworkSettings()
+ logger.log("Proxy started successfully")
+ isRunning = true
+ eventHandler?(.startSuccess)
+ completionHandler(nil)
+ } catch {
+ let error = StartError.failedToUpdateNetworkSettings(underlyingError: error)
+ logger.error("Proxy failed to start \(String(reflecting: error), privacy: .public)")
+ eventHandler?(.startFailure(error))
+ completionHandler(error)
+ }
+ }
+ }
+
+ override public func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
+ logger.log("Stopping proxy with reason: \(String(reflecting: reason), privacy: .public)")
+
+ Task { @MainActor in
+ stopMonitoringNetworkInterfaces()
+ isRunning = false
+ completionHandler()
+ }
+ }
+
+ override public func sleep(completionHandler: @escaping () -> Void) {
+ Task { @MainActor in
+ stopMonitoringNetworkInterfaces()
+ logger.log("The proxy is now sleeping")
+ completionHandler()
+ }
+ }
+
+ override public func wake() {
+ Task { @MainActor in
+ logger.log("The proxy is now awake")
+ startMonitoringNetworkInterfaces()
+ }
+ }
+
+ override public func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
+ guard let flow = flow as? NEAppProxyTCPFlow else {
+ logger.info("Expected a TCP flow, but got something else. We're ignoring it.")
+ return false
+ }
+
+ guard let remoteEndpoint = flow.remoteEndpoint as? NWHostEndpoint,
+ !isDnsServer(remoteEndpoint) else {
+ return false
+ }
+
+ let printableRemote = flow.remoteHostname ?? (flow.remoteEndpoint as? NWHostEndpoint)?.hostname ?? "unknown"
+
+ logger.debug(
+ """
+ [TCP] New flow: \(String(describing: flow), privacy: .public)
+ - remote: \(printableRemote, privacy: .public)
+ - flowID: \(String(describing: flow.metaData.filterFlowIdentifier?.uuidString), privacy: .public)
+ - appID: \(String(describing: flow.metaData.sourceAppSigningIdentifier), privacy: .public)
+ """)
+
+ guard let interface else {
+ logger.error("[TCP: \(String(describing: flow), privacy: .public)] Expected an interface to exclude traffic through")
+ return false
+ }
+
+ switch path(for: flow) {
+ case .block(let reason):
+ switch reason {
+ case .appRule:
+ logger.debug("[TCP: \(String(describing: flow), privacy: .public)] Blocking traffic due to app rule")
+ case .domainRule:
+ logger.debug("[TCP: \(String(describing: flow), privacy: .public)] Blocking traffic due to domain rule")
+ }
+ case .excludeFromVPN(let reason):
+ switch reason {
+ case .appRule:
+ logger.debug("[TCP: \(String(describing: flow), privacy: .public)] Excluding traffic due to app rule")
+ case .domainRule:
+ logger.debug("[TCP: \(String(describing: flow), privacy: .public)] Excluding traffic due to domain rule")
+ }
+ case .routeThroughVPN:
+ return false
+ }
+
+ flow.networkInterface = directInterface
+
+ Task { @TCPFlowActor in
+ let flowManager = TCPFlowManager(flow: flow)
+ tcpFlowManagers.insert(flowManager)
+
+ try? await flowManager.start(interface: interface)
+ tcpFlowManagers.remove(flowManager)
+ }
+
+ return true
+ }
+
+ override public func handleNewUDPFlow(_ flow: NEAppProxyUDPFlow, initialRemoteEndpoint remoteEndpoint: NWEndpoint) -> Bool {
+
+ guard let remoteEndpoint = remoteEndpoint as? NWHostEndpoint,
+ !isDnsServer(remoteEndpoint) else {
+ return false
+ }
+
+ let printableRemote = remoteEndpoint.hostname
+
+ logger.log(
+ """
+ [UDP] New flow: \(String(describing: flow), privacy: .public)
+ - remote: \(printableRemote, privacy: .public)
+ - flowID: \(String(describing: flow.metaData.filterFlowIdentifier?.uuidString), privacy: .public)
+ - appID: \(String(describing: flow.metaData.sourceAppSigningIdentifier), privacy: .public)
+ """)
+
+ guard let interface else {
+ logger.error("[UDP: \(String(describing: flow), privacy: .public)] Expected an interface to exclude traffic through")
+ return false
+ }
+
+ switch path(for: flow) {
+ case .block(let reason):
+ switch reason {
+ case .appRule:
+ logger.debug("[UDP: \(String(describing: flow), privacy: .public)] Blocking traffic due to app rule")
+ case .domainRule:
+ logger.debug("[UDP: \(String(describing: flow), privacy: .public)] Blocking traffic due to domain rule")
+ }
+ case .excludeFromVPN(let reason):
+ switch reason {
+ case .appRule:
+ logger.debug("[UDP: \(String(describing: flow), privacy: .public)] Excluding traffic due to app rule")
+ case .domainRule:
+ logger.debug("[UDP: \(String(describing: flow), privacy: .public)] Excluding traffic due to domain rule")
+ }
+ case .routeThroughVPN:
+ return false
+ }
+
+ flow.networkInterface = directInterface
+
+ Task { @UDPFlowActor in
+ let flowManager = UDPFlowManager(flow: flow)
+ udpFlowManagers.insert(flowManager)
+
+ try? await flowManager.start(interface: interface)
+ udpFlowManagers.remove(flowManager)
+ }
+
+ return true
+ }
+
+ // MARK: - Path Monitors
+
+ @MainActor
+ private func startMonitoringNetworkInterfaces() {
+ bMonitor.pathUpdateHandler = { [weak self, logger] path in
+ logger.log("Available interfaces updated: \(String(reflecting: path.availableInterfaces), privacy: .public)")
+
+ self?.interface = path.availableInterfaces.first { interface in
+ interface.type != .other
+ }
+ }
+ bMonitor.start(queue: .main)
+
+ nw_path_monitor_set_queue(monitor, .main)
+ nw_path_monitor_set_update_handler(monitor) { [weak self, logger] path in
+ guard let self else { return }
+
+ let interfaces = SCNetworkInterfaceCopyAll()
+ logger.log("Available interfaces updated: \(String(reflecting: interfaces), privacy: .public)")
+
+ nw_path_enumerate_interfaces(path) { interface in
+ guard nw_interface_get_type(interface) != nw_interface_type_other else {
+ return true
+ }
+
+ self.directInterface = interface
+ return false
+ }
+ }
+
+ nw_path_monitor_start(monitor)
+ }
+
+ @MainActor
+ private func stopMonitoringNetworkInterfaces() {
+ bMonitor.cancel()
+ nw_path_monitor_cancel(monitor)
+ }
+
+ // MARK: - Ignoring DNS flows
+
+ private func isDnsServer(_ endpoint: NWHostEndpoint) -> Bool {
+ Int(endpoint.port) == Self.dnsPort
+ }
+
+ // MARK: - VPN exclusions logic
+
+ private enum FlowPath {
+ case block(dueTo: Reason)
+ case excludeFromVPN(dueTo: Reason)
+ case routeThroughVPN
+
+ enum Reason {
+ case appRule
+ case domainRule
+ }
+ }
+
+ private func path(for flow: NEAppProxyFlow) -> FlowPath {
+ let appIdentifier = flow.metaData.sourceAppSigningIdentifier
+
+ switch settings.appRoutingRules[appIdentifier] {
+ case .none:
+ if let hostname = flow.remoteHostname,
+ isExcludedDomain(hostname) {
+ return .excludeFromVPN(dueTo: .domainRule)
+ }
+
+ return .routeThroughVPN
+ case .block:
+ return .block(dueTo: .appRule)
+ case .exclude:
+ return .excludeFromVPN(dueTo: .domainRule)
+ }
+ }
+
+ private func isExcludedDomain(_ hostname: String) -> Bool {
+ settings.excludedDomains.contains { excludedDomain in
+ hostname.hasSuffix(excludedDomain)
+ }
+ }
+
+ // MARK: - Communication with App
+
+ override public func handleAppMessage(_ messageData: Data) async -> Data? {
+ await appMessageHandler.handle(messageData)
+ }
+}
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/TransparentProxyProviderConfiguration.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/TransparentProxyProviderConfiguration.swift
new file mode 100644
index 0000000000..3e841faeca
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionProxy/TransparentProxyProviderConfiguration.swift
@@ -0,0 +1,40 @@
+//
+// TransparentProxyProviderConfiguration.swift
+//
+// Copyright © 2024 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 TransparentProxyProvider {
+ /// Configuration to define behaviour for the provider based on the parent process'
+ /// business domain.
+ ///
+ /// This should not be passed in the startup options dictionary.
+ ///
+ public struct Configuration {
+ /// Whether the proxy settings should be loaded from the provider configuration in the startup options.
+ ///
+ /// We recommend setting this to true if the provider is running in a System Extension and can't access
+ /// shared `TransparentProxySettings`. If the provider is in an App Extension you should instead
+ /// use a shared `TransparentProxySettings` and set this to false.
+ ///
+ let loadSettingsFromProviderConfiguration: Bool
+
+ public init(loadSettingsFromProviderConfiguration: Bool) {
+ self.loadSettingsFromProviderConfiguration = loadSettingsFromProviderConfiguration
+ }
+ }
+}
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusView.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusView.swift
index 66f9bb15ea..2575803866 100644
--- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusView.swift
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusView.swift
@@ -54,9 +54,11 @@ public struct NetworkProtectionStatusView: View {
PromptActionView(model: promptActionViewModel)
.padding(.horizontal, 5)
.padding(.top, 5)
+ .transition(.slide)
} else {
if let healthWarning = model.issueDescription {
connectionHealthWarningView(message: healthWarning)
+ .transition(.slide)
}
}
@@ -67,12 +69,14 @@ public struct NetworkProtectionStatusView: View {
if model.showDebugInformation {
DebugInformationView(model: DebugInformationViewModel())
+ .transition(.slide)
}
bottomMenuView()
}
.padding(5)
.frame(maxWidth: 350, alignment: .top)
+ .transition(.slide)
}
// MARK: - Composite Views
diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusViewModel.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusViewModel.swift
index 2a86e999d0..754ca81034 100644
--- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusViewModel.swift
+++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusViewModel.swift
@@ -143,9 +143,9 @@ extension NetworkProtectionStatusView {
onboardingStatusPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] status in
- self?.onboardingStatus = status
- }
- .store(in: &cancellables)
+ self?.onboardingStatus = status
+ }
+ .store(in: &cancellables)
}
func refreshLoginItemStatus() {
@@ -184,14 +184,14 @@ extension NetworkProtectionStatusView {
.subscribe(on: Self.tunnelErrorDispatchQueue)
.sink { [weak self] errorMessage in
- guard let self else {
- return
- }
+ guard let self else {
+ return
+ }
- Task { @MainActor in
- self.lastTunnelErrorMessage = errorMessage
- }
- }.store(in: &cancellables)
+ Task { @MainActor in
+ self.lastTunnelErrorMessage = errorMessage
+ }
+ }.store(in: &cancellables)
}
private func subscribeToControllerErrorMessages() {
@@ -199,14 +199,14 @@ extension NetworkProtectionStatusView {
.subscribe(on: Self.controllerErrorDispatchQueue)
.sink { [weak self] errorMessage in
- guard let self else {
- return
- }
+ guard let self else {
+ return
+ }
- Task { @MainActor in
- self.lastControllerErrorMessage = errorMessage
- }
- }.store(in: &cancellables)
+ Task { @MainActor in
+ self.lastControllerErrorMessage = errorMessage
+ }
+ }.store(in: &cancellables)
}
private func subscribeToDebugInformationChanges() {
diff --git a/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/TransparentProxyControllerPixelTests.swift b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/TransparentProxyControllerPixelTests.swift
new file mode 100644
index 0000000000..bd21fe50d1
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/TransparentProxyControllerPixelTests.swift
@@ -0,0 +1,120 @@
+//
+// TransparentProxyControllerPixelTests.swift
+//
+// Copyright © 2024 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
+@testable import NetworkProtectionProxy
+import PixelKit
+import PixelKitTestingUtilities
+import XCTest
+
+extension TransparentProxyController.Event: Hashable {
+ public static func == (lhs: NetworkProtectionProxy.TransparentProxyController.Event, rhs: NetworkProtectionProxy.TransparentProxyController.Event) -> Bool {
+
+ lhs.name == rhs.name && lhs.parameters == rhs.parameters
+ }
+
+ public func hash(into hasher: inout Hasher) {
+ name.hash(into: &hasher)
+ parameters.hash(into: &hasher)
+ }
+}
+
+extension TransparentProxyController.StartError: Hashable {
+ public static func == (lhs: NetworkProtectionProxy.TransparentProxyController.StartError, rhs: NetworkProtectionProxy.TransparentProxyController.StartError) -> Bool {
+
+ let lhs = lhs as NSError
+ let rhs = rhs as NSError
+
+ return lhs.code == rhs.code && lhs.domain == rhs.domain
+ }
+
+ public func hash(into hasher: inout Hasher) {
+ (self as NSError).hash(into: &hasher)
+ (underlyingError as? NSError)?.hash(into: &hasher)
+ }
+}
+
+final class TransparentProxyControllerPixelTests: XCTestCase {
+
+ static let startFailureFullPixelName = "m_mac_vpn_proxy_controller_start_failure"
+ static let startInitiatedFullPixelName = "m_mac_vpn_proxy_controller_start_initiated"
+ static let startSuccessFullPixelName = "m_mac_vpn_proxy_controller_start_success"
+
+ enum TestError: PixelKitEventErrorDetails {
+ case testError
+
+ static let underlyingError = NSError(domain: "test", code: 1)
+
+ var underlyingError: Error? {
+ Self.underlyingError
+ }
+ }
+
+ // MARK: - Test Firing Pixels
+
+ func testFiringPixelsWithoutParameters() {
+ let tests: [TransparentProxyController.Event: PixelFireExpectations] = [
+ .startInitiated: PixelFireExpectations(pixelName: Self.startInitiatedFullPixelName),
+ .startSuccess: PixelFireExpectations(pixelName: Self.startSuccessFullPixelName)
+ ]
+
+ for (event, expectations) in tests {
+ verifyThat(event,
+ meets: expectations,
+ file: #filePath,
+ line: #line)
+ }
+ }
+
+ func testFiringStartFailures() {
+ // Just a convenience method to return the right expectation for each error
+ func expectaton(forError error: TransparentProxyController.StartError) -> PixelFireExpectations {
+ switch error {
+ case .attemptToStartWithoutBackingActiveFeatures,
+ .couldNotEncodeSettingsSnapshot,
+ .couldNotRetrieveProtocolConfiguration:
+ return PixelFireExpectations(
+ pixelName: Self.startFailureFullPixelName,
+ error: error)
+ case .failedToLoadConfiguration(let underlyingError),
+ .failedToSaveConfiguration(let underlyingError),
+ .failedToStartProvider(let underlyingError):
+ return PixelFireExpectations(
+ pixelName: Self.startFailureFullPixelName,
+ error: error,
+ underlyingError: underlyingError)
+ }
+ }
+
+ let errors: [TransparentProxyController.StartError] = [
+ .attemptToStartWithoutBackingActiveFeatures,
+ .couldNotEncodeSettingsSnapshot,
+ .couldNotRetrieveProtocolConfiguration,
+ .failedToLoadConfiguration(TestError.underlyingError),
+ .failedToSaveConfiguration(TestError.underlyingError),
+ .failedToStartProvider(TestError.underlyingError)
+ ]
+
+ for error in errors {
+ verifyThat(TransparentProxyController.Event.startFailure(error),
+ meets: expectaton(forError: error),
+ file: #filePath,
+ line: #line)
+ }
+ }
+}
diff --git a/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/TransparentProxyProviderPixelTests.swift b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/TransparentProxyProviderPixelTests.swift
new file mode 100644
index 0000000000..faf31729a4
--- /dev/null
+++ b/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/TransparentProxyProviderPixelTests.swift
@@ -0,0 +1,66 @@
+//
+// TransparentProxyProviderPixelTests.swift
+//
+// Copyright © 2024 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
+@testable import NetworkProtectionProxy
+import PixelKit
+import PixelKitTestingUtilities
+import XCTest
+
+extension TransparentProxyProvider.Event: Hashable {
+ public static func == (lhs: NetworkProtectionProxy.TransparentProxyProvider.Event, rhs: NetworkProtectionProxy.TransparentProxyProvider.Event) -> Bool {
+
+ lhs.name == rhs.name && lhs.parameters == rhs.parameters
+ }
+
+ public func hash(into hasher: inout Hasher) {
+ name.hash(into: &hasher)
+ parameters.hash(into: &hasher)
+ }
+}
+
+final class TransparentProxyProviderPixelTests: XCTestCase {
+
+ static let startFailureFullPixelName = "m_mac_vpn_proxy_provider_start_failure"
+ static let startInitiatedFullPixelName = "m_mac_vpn_proxy_provider_start_initiated"
+ static let startSuccessFullPixelName = "m_mac_vpn_proxy_provider_start_success"
+
+ enum TestError: Error {
+ case testError
+ }
+
+ // MARK: - Test Firing Pixels
+
+ func testFiringPixels() {
+ let tests: [TransparentProxyProvider.Event: PixelFireExpectations] = [
+ .startInitiated: PixelFireExpectations(pixelName: Self.startInitiatedFullPixelName),
+ .startFailure(TestError.testError):
+ PixelFireExpectations(
+ pixelName: Self.startFailureFullPixelName,
+ error: TestError.testError),
+ .startSuccess: PixelFireExpectations(pixelName: Self.startSuccessFullPixelName)
+ ]
+
+ for (event, expectations) in tests {
+ verifyThat(event,
+ meets: expectations,
+ file: #filePath,
+ line: #line)
+ }
+ }
+}
diff --git a/LocalPackages/PixelKit/Package.swift b/LocalPackages/PixelKit/Package.swift
index 61de75cd32..1222931baa 100644
--- a/LocalPackages/PixelKit/Package.swift
+++ b/LocalPackages/PixelKit/Package.swift
@@ -20,7 +20,7 @@ let package = Package(
)
],
dependencies: [
- .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"),
+ .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"),
],
targets: [
.target(
diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift
index ffd9d88796..7a25f133ff 100644
--- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift
+++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift
@@ -94,11 +94,13 @@ public extension Error {
let nsError = self as NSError
params[PixelKit.Parameters.errorCode] = "\(nsError.code)"
- params[PixelKit.Parameters.errorDesc] = nsError.domain
+ params[PixelKit.Parameters.errorDomain] = nsError.domain
+ params[PixelKit.Parameters.errorDesc] = nsError.localizedDescription
if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? NSError {
params[PixelKit.Parameters.underlyingErrorCode] = "\(underlyingError.code)"
- params[PixelKit.Parameters.underlyingErrorDesc] = underlyingError.domain
+ params[PixelKit.Parameters.underlyingErrorDomain] = underlyingError.domain
+ params[PixelKit.Parameters.underlyingErrorDesc] = underlyingError.localizedDescription
}
if let sqlErrorCode = nsError.userInfo["SQLiteResultCode"] as? NSNumber {
diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift
index b13aea7b17..9c616d0060 100644
--- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift
+++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift
@@ -275,11 +275,23 @@ public final class PixelKit {
newParams = nil
}
+ let newError: Error?
+
+ if let event = event as? PixelKitEventV2 {
+ // For v2 events we only consider the error specified in the event
+ // and purposedly ignore the parameter in this call.
+ // This is to encourage moving the error over to the protocol error
+ // instead of still relying on the parameter of this call.
+ newError = event.error
+ } else {
+ newError = error
+ }
+
fire(pixelNamed: pixelName,
frequency: frequency,
withHeaders: headers,
withAdditionalParameters: newParams,
- withError: error,
+ withError: newError,
allowedQueryReservedCharacters: allowedQueryReservedCharacters,
includeAppVersionParameter: includeAppVersionParameter,
onComplete: onComplete)
@@ -365,8 +377,16 @@ extension Dictionary where Key == String, Value == String {
self[PixelKit.Parameters.errorCode] = "\(nsError.code)"
self[PixelKit.Parameters.errorDomain] = nsError.domain
+ self[PixelKit.Parameters.errorDesc] = nsError.localizedDescription
+
+ if let error = error as? PixelKitEventErrorDetails,
+ let underlyingError = error.underlyingError {
- if let underlyingError = nsError.userInfo["NSUnderlyingError"] as? NSError {
+ let underlyingNSError = underlyingError as NSError
+ self[PixelKit.Parameters.underlyingErrorCode] = "\(underlyingNSError.code)"
+ self[PixelKit.Parameters.underlyingErrorDomain] = underlyingNSError.domain
+ self[PixelKit.Parameters.underlyingErrorDesc] = underlyingNSError.localizedDescription
+ } else if let underlyingError = nsError.userInfo["NSUnderlyingError"] as? NSError {
self[PixelKit.Parameters.underlyingErrorCode] = "\(underlyingError.code)"
self[PixelKit.Parameters.underlyingErrorDomain] = underlyingError.domain
} else if let sqlErrorCode = nsError.userInfo["NSSQLiteErrorDomain"] as? NSNumber {
diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift
index 83965ba999..bc87070df3 100644
--- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift
+++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEvent.swift
@@ -34,7 +34,7 @@ public final class DebugEvent: PixelKitEvent {
}
public let eventType: EventType
- private let error: Error?
+ public let error: Error?
public init(eventType: EventType, error: Error? = nil) {
self.eventType = eventType
diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEventV2.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEventV2.swift
new file mode 100644
index 0000000000..7048519e32
--- /dev/null
+++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKitEventV2.swift
@@ -0,0 +1,70 @@
+//
+// PixelKitEventV2.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 protocol PixelKitEventErrorDetails: Error {
+ var underlyingError: Error? { get }
+}
+
+extension PixelKitEventErrorDetails {
+ var underlyingErrorParameters: [String: String] {
+ guard let nsError = underlyingError as? NSError else {
+ return [:]
+ }
+
+ return [
+ PixelKit.Parameters.underlyingErrorCode: "\(nsError.code)",
+ PixelKit.Parameters.underlyingErrorDomain: nsError.domain,
+ PixelKit.Parameters.underlyingErrorDesc: nsError.localizedDescription
+ ]
+ }
+}
+
+/// New version of this protocol that allows us to maintain backwards-compatibility with PixelKitEvent
+///
+/// This new implementation seeks to unify the handling of standard pixel parameters inside PixelKit.
+/// The starting example of how this can be useful is error parameter handling - this protocol allows
+/// the implementer to speciy an error without having to know about the parametrization of the error.
+///
+/// The reason this wasn't done directly in `PixelKitEvent` is to reduce the risk of breaking existing
+/// pixels, and to allow us to migrate towards this incrementally.
+///
+public protocol PixelKitEventV2: PixelKitEvent {
+ var error: Error? { get }
+}
+
+extension PixelKitEventV2 {
+ var pixelParameters: [String: String] {
+ guard let error else {
+ return [:]
+ }
+
+ let nsError = error as NSError
+ var parameters = [
+ PixelKit.Parameters.errorCode: "\(nsError.code)",
+ PixelKit.Parameters.errorDomain: nsError.domain,
+ ]
+
+ if let error = error as? PixelKitEventErrorDetails {
+ parameters.merge(error.underlyingErrorParameters, uniquingKeysWith: { $1 })
+ }
+
+ return parameters
+ }
+}
diff --git a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/PixelFireExpectations.swift b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/PixelFireExpectations.swift
new file mode 100644
index 0000000000..067eee091e
--- /dev/null
+++ b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/PixelFireExpectations.swift
@@ -0,0 +1,36 @@
+//
+// PixelFireExpectations.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
+
+/// Structure containing information about a pixel fire event.
+///
+/// This is useful for test validation for libraries that rely on PixelKit, to make sure the pixels contain
+/// all of the fields they are supposed to contain..
+///
+public struct PixelFireExpectations {
+ let pixelName: String
+ var error: Error?
+ var underlyingError: Error?
+
+ public init(pixelName: String, error: Error? = nil, underlyingError: Error? = nil) {
+ self.pixelName = pixelName
+ self.error = error
+ self.underlyingError = underlyingError
+ }
+}
diff --git a/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift
new file mode 100644
index 0000000000..5088ba1371
--- /dev/null
+++ b/LocalPackages/PixelKit/Sources/PixelKitTestingUtilities/XCTestCase+PixelKit.swift
@@ -0,0 +1,148 @@
+//
+// XCTestCase+PixelKit.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
+@testable import PixelKit
+import XCTest
+
+public extension XCTestCase {
+
+ // MARK: - Parameters
+
+ /// List of standard pixel parameters.
+ ///
+ /// This is useful to support filtering these parameters out if needed.
+ ///
+ private static var standardPixelParameters = [
+ PixelKit.Parameters.appVersion,
+ PixelKit.Parameters.pixelSource,
+ PixelKit.Parameters.test
+ ]
+
+ /// List of errror pixel parameters
+ ///
+ private static var errorPixelParameters = [
+ PixelKit.Parameters.errorCode,
+ PixelKit.Parameters.errorDomain,
+ PixelKit.Parameters.errorDesc
+ ]
+
+ /// List of underlying error pixel parameters
+ ///
+ private static var underlyingErrorPixelParameters = [
+ PixelKit.Parameters.underlyingErrorCode,
+ PixelKit.Parameters.underlyingErrorDomain,
+ PixelKit.Parameters.underlyingErrorDesc
+ ]
+
+ /// Filter out the standard parameters.
+ ///
+ private static func filterStandardPixelParameters(from parameters: [String: String]) -> [String: String] {
+ parameters.filter { element in
+ !standardPixelParameters.contains(element.key)
+ }
+ }
+
+ static var pixelPlatformPrefix: String {
+#if os(macOS)
+ return "m_mac_"
+#else
+ // Intentionally left blank for now because PixelKit currently doesn't support
+ // other platforms, but if we decide to implement another platform this'll fail
+ // and indicate that we need a value here.
+#endif
+ }
+
+ func expectedParameters(for event: PixelKitEventV2) -> [String: String] {
+ var expectedParameters = [String: String]()
+
+ if let error = event.error {
+ let nsError = error as NSError
+ expectedParameters[PixelKit.Parameters.errorCode] = "\(nsError.code)"
+ expectedParameters[PixelKit.Parameters.errorDomain] = nsError.domain
+ expectedParameters[PixelKit.Parameters.errorDesc] = nsError.localizedDescription
+
+ if let underlyingError = (error as? PixelKitEventErrorDetails)?.underlyingError {
+ let underlyingNSError = underlyingError as NSError
+ expectedParameters[PixelKit.Parameters.underlyingErrorCode] = "\(underlyingNSError.code)"
+ expectedParameters[PixelKit.Parameters.underlyingErrorDomain] = underlyingNSError.domain
+ expectedParameters[PixelKit.Parameters.underlyingErrorDesc] = underlyingNSError.localizedDescription
+ }
+ }
+
+ return expectedParameters
+ }
+
+ // MARK: - Misc Convenience
+
+ private var userDefaults: UserDefaults {
+ UserDefaults(suiteName: "testing_\(UUID().uuidString)")!
+ }
+
+ // MARK: - Pixel Firing Expectations
+
+ /// Provides some snapshot of a fired pixel so that external libraries can validate all the expected info is included.
+ ///
+ /// This method also checks that there is internal consistency in the expected fields.
+ ///
+ func verifyThat(_ event: PixelKitEventV2, meets expectations: PixelFireExpectations, file: StaticString, line: UInt) {
+
+ let expectedPixelName = Self.pixelPlatformPrefix + event.name
+ let expectedParameters = expectedParameters(for: event)
+ let callbackExecutedExpectation = expectation(description: "The PixelKit callback has been executed")
+
+ PixelKit.setUp(dryRun: false,
+ appVersion: "1.0.5",
+ source: "test-app",
+ defaultHeaders: [:],
+ log: .disabled,
+ defaults: userDefaults) { firedPixelName, _, firedParameters, _, _, completion in
+ callbackExecutedExpectation.fulfill()
+
+ let firedParameters = Self.filterStandardPixelParameters(from: firedParameters)
+
+ // Internal validations
+
+ XCTAssertEqual(firedPixelName, expectedPixelName, file: file, line: line)
+ XCTAssertEqual(firedParameters, expectedParameters, file: file, line: line)
+
+ // Expectations
+
+ XCTAssertEqual(firedPixelName, expectations.pixelName)
+
+ if let error = expectations.error {
+ let nsError = error as NSError
+ XCTAssertEqual(firedParameters[PixelKit.Parameters.errorCode], String(nsError.code), file: file, line: line)
+ XCTAssertEqual(firedParameters[PixelKit.Parameters.errorDomain], nsError.domain, file: file, line: line)
+ XCTAssertEqual(firedParameters[PixelKit.Parameters.errorDesc], nsError.localizedDescription, file: file, line: line)
+ }
+
+ if let underlyingError = expectations.underlyingError {
+ let nsError = underlyingError as NSError
+ XCTAssertEqual(firedParameters[PixelKit.Parameters.underlyingErrorCode], String(nsError.code), file: file, line: line)
+ XCTAssertEqual(firedParameters[PixelKit.Parameters.underlyingErrorDomain], nsError.domain, file: file, line: line)
+ XCTAssertEqual(firedParameters[PixelKit.Parameters.underlyingErrorDesc], nsError.localizedDescription, file: file, line: line)
+ }
+
+ completion(true, nil)
+ }
+
+ PixelKit.fire(event)
+ waitForExpectations(timeout: 0.1)
+ }
+}
diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift
index 197a2c4eee..e5192fc149 100644
--- a/LocalPackages/SubscriptionUI/Package.swift
+++ b/LocalPackages/SubscriptionUI/Package.swift
@@ -12,7 +12,7 @@ let package = Package(
targets: ["SubscriptionUI"]),
],
dependencies: [
- .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"),
+ .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"),
.package(path: "../SwiftUIExtensions")
],
targets: [
diff --git a/LocalPackages/SwiftUIExtensions/Package.swift b/LocalPackages/SwiftUIExtensions/Package.swift
index 6f8c716bc3..10d667a750 100644
--- a/LocalPackages/SwiftUIExtensions/Package.swift
+++ b/LocalPackages/SwiftUIExtensions/Package.swift
@@ -11,7 +11,7 @@ let package = Package(
.library(name: "PreferencesViews", targets: ["PreferencesViews"]),
],
dependencies: [
- .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"),
+ .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"),
],
targets: [
.target(
diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift
index 678077f716..6da8332d6a 100644
--- a/LocalPackages/SyncUI/Package.swift
+++ b/LocalPackages/SyncUI/Package.swift
@@ -14,7 +14,7 @@ let package = Package(
],
dependencies: [
.package(path: "../SwiftUIExtensions"),
- .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"),
+ .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"),
],
targets: [
.target(
diff --git a/LocalPackages/SystemExtensionManager/Package.swift b/LocalPackages/SystemExtensionManager/Package.swift
index 188a15c3ad..43bafef378 100644
--- a/LocalPackages/SystemExtensionManager/Package.swift
+++ b/LocalPackages/SystemExtensionManager/Package.swift
@@ -16,7 +16,7 @@ let package = Package(
),
],
dependencies: [
- .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"),
+ .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
diff --git a/LocalPackages/XPCHelper/Package.swift b/LocalPackages/XPCHelper/Package.swift
index fb49a79f25..e62141ec7a 100644
--- a/LocalPackages/XPCHelper/Package.swift
+++ b/LocalPackages/XPCHelper/Package.swift
@@ -30,7 +30,7 @@ let package = Package(
.library(name: "XPCHelper", targets: ["XPCHelper"]),
],
dependencies: [
- .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"),
+ .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"),
],
targets: [
.target(
diff --git a/NetworkProtectionSystemExtension/Info.plist b/NetworkProtectionSystemExtension/Info.plist
index c35c8be1ca..43844d5702 100644
--- a/NetworkProtectionSystemExtension/Info.plist
+++ b/NetworkProtectionSystemExtension/Info.plist
@@ -14,6 +14,8 @@
com.apple.networkextension.packet-tunnel
$(PRODUCT_MODULE_NAME).MacPacketTunnelProvider
+ com.apple.networkextension.app-proxy
+ $(PRODUCT_MODULE_NAME).MacTransparentProxyProvider
MAIN_BUNDLE_IDENTIFIER
$(MAIN_BUNDLE_IDENTIFIER)
diff --git a/NetworkProtectionSystemExtension/NetworkProtectionSystemExtension_Debug.entitlements b/NetworkProtectionSystemExtension/NetworkProtectionSystemExtension_Debug.entitlements
index a049fa6886..4252e67c8e 100644
--- a/NetworkProtectionSystemExtension/NetworkProtectionSystemExtension_Debug.entitlements
+++ b/NetworkProtectionSystemExtension/NetworkProtectionSystemExtension_Debug.entitlements
@@ -5,6 +5,7 @@
com.apple.developer.networking.networkextension
packet-tunnel-provider
+ app-proxy-provider
com.apple.security.app-sandbox
diff --git a/NetworkProtectionSystemExtension/NetworkProtectionSystemExtension_Release.entitlements b/NetworkProtectionSystemExtension/NetworkProtectionSystemExtension_Release.entitlements
index f7d87546d2..23068f001f 100644
--- a/NetworkProtectionSystemExtension/NetworkProtectionSystemExtension_Release.entitlements
+++ b/NetworkProtectionSystemExtension/NetworkProtectionSystemExtension_Release.entitlements
@@ -5,6 +5,7 @@
com.apple.developer.networking.networkextension
packet-tunnel-provider-systemextension
+ app-proxy-provider-systemextension
com.apple.security.app-sandbox
diff --git a/VPNProxyExtension/Info.plist b/VPNProxyExtension/Info.plist
new file mode 100644
index 0000000000..7f2489c298
--- /dev/null
+++ b/VPNProxyExtension/Info.plist
@@ -0,0 +1,17 @@
+
+
+
+
+ DISTRIBUTED_NOTIFICATIONS_PREFIX
+ $(DISTRIBUTED_NOTIFICATIONS_PREFIX)
+ NETP_APP_GROUP
+ $(NETP_APP_GROUP)
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.networkextension.app-proxy
+ NSExtensionPrincipalClass
+ $(PRODUCT_MODULE_NAME).MacTransparentProxyProvider
+
+
+
diff --git a/VPNProxyExtension/VPNProxyExtension.entitlements b/VPNProxyExtension/VPNProxyExtension.entitlements
new file mode 100644
index 0000000000..968c758f97
--- /dev/null
+++ b/VPNProxyExtension/VPNProxyExtension.entitlements
@@ -0,0 +1,25 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ $(TeamIdentifierPrefix)com.duckduckgo.macos.browser.network-protection
+ $(NETP_APP_GROUP)
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.network.server
+
+ keychain-access-groups
+
+ $(NETP_APP_GROUP)
+
+ com.apple.developer.networking.networkextension
+
+ app-proxy-provider
+
+ com.apple.security.network.client
+
+
+
diff --git a/fastlane/Matchfile b/fastlane/Matchfile
index 8dbddfccb8..2af9eed1fe 100644
--- a/fastlane/Matchfile
+++ b/fastlane/Matchfile
@@ -11,6 +11,8 @@ app_identifier [
"com.duckduckgo.mobile.ios.review",
"com.duckduckgo.mobile.ios.vpn.agent.review",
"com.duckduckgo.mobile.ios.vpn.agent.review.network-protection-extension",
+ "com.duckduckgo.mobile.ios.vpn.agent.proxy",
+ "com.duckduckgo.mobile.ios.vpn.agent.review.proxy",
"com.duckduckgo.mobile.ios.DBP.backgroundAgent.review",
"com.duckduckgo.mobile.ios.DBP.backgroundAgent"
diff --git a/scripts/assets/AppStoreExportOptions.plist b/scripts/assets/AppStoreExportOptions.plist
index b9395f914a..9ebf3d7885 100644
--- a/scripts/assets/AppStoreExportOptions.plist
+++ b/scripts/assets/AppStoreExportOptions.plist
@@ -14,12 +14,16 @@
match AppStore com.duckduckgo.mobile.ios.vpn.agent macos
com.duckduckgo.mobile.ios.vpn.agent.network-protection-extension
match AppStore com.duckduckgo.mobile.ios.vpn.agent.network-protection-extension macos
+ com.duckduckgo.mobile.ios.vpn.agent.proxy
+ match AppStore com.duckduckgo.mobile.ios.vpn.agent.proxy macos
com.duckduckgo.mobile.ios.review
match AppStore com.duckduckgo.mobile.ios.review macos
com.duckduckgo.mobile.ios.vpn.agent.review
match AppStore com.duckduckgo.mobile.ios.vpn.agent.review macos
com.duckduckgo.mobile.ios.vpn.agent.review.network-protection-extension
match AppStore com.duckduckgo.mobile.ios.vpn.agent.review.network-protection-extension macos
+ com.duckduckgo.mobile.ios.vpn.agent.review.proxy
+ match AppStore com.duckduckgo.mobile.ios.vpn.agent.review.proxy macos
com.duckduckgo.mobile.ios.DBP.backgroundAgent
match AppStore com.duckduckgo.mobile.ios.DBP.backgroundAgent macos
com.duckduckgo.mobile.ios.DBP.backgroundAgent.review
From 7c2e3e4057681ecacb972eb8d7eecda0f3762c12 Mon Sep 17 00:00:00 2001
From: Sabrina Tardio <44158575+SabrinaTardio@users.noreply.github.com>
Date: Thu, 22 Feb 2024 11:01:25 +0100
Subject: [PATCH 12/12] add bundle to sync strings (#2232)
Task/Issue URL: https://app.asana.com/0/0/1206643467042714/f
**Description**: Adds bundle to sync strings
**Steps to test this PR**:
1. Check the app builds as expected
---
LocalPackages/SyncUI/Package.swift | 3 +
.../Localizable.xcstrings | 8 +-
.../Sources/SyncUI/internal/UserText.swift | 197 +++++++++---------
3 files changed, 106 insertions(+), 102 deletions(-)
rename LocalPackages/SyncUI/Sources/SyncUI/{internal => Resources}/Localizable.xcstrings (99%)
diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift
index 6da8332d6a..b927525124 100644
--- a/LocalPackages/SyncUI/Package.swift
+++ b/LocalPackages/SyncUI/Package.swift
@@ -23,6 +23,9 @@ let package = Package(
.product(name: "PreferencesViews", package: "SwiftUIExtensions"),
.product(name: "SwiftUIExtensions", package: "SwiftUIExtensions")
],
+ resources: [
+ .process("Resources")
+ ],
swiftSettings: [
.define("DEBUG", .when(configuration: .debug))
],
diff --git a/LocalPackages/SyncUI/Sources/SyncUI/internal/Localizable.xcstrings b/LocalPackages/SyncUI/Sources/SyncUI/Resources/Localizable.xcstrings
similarity index 99%
rename from LocalPackages/SyncUI/Sources/SyncUI/internal/Localizable.xcstrings
rename to LocalPackages/SyncUI/Sources/SyncUI/Resources/Localizable.xcstrings
index abcf797258..340db4147a 100644
--- a/LocalPackages/SyncUI/Sources/SyncUI/internal/Localizable.xcstrings
+++ b/LocalPackages/SyncUI/Sources/SyncUI/Resources/Localizable.xcstrings
@@ -209,7 +209,7 @@
}
},
"paste-from-clipboard" : {
- "comment" : "Paste button",
+ "comment" : "Paste from Clipboard button",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
@@ -245,7 +245,7 @@
}
},
"preferences.begin-sync.card-footer" : {
- "comment" : "Footer / captoin on the Begin Syncing card in sync settings",
+ "comment" : "Footer / caption on the Begin Syncing card in sync settings",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
@@ -353,7 +353,7 @@
}
},
"preferences.preparing-to-sync.dialog-title" : {
- "comment" : "Peparing to sync dialog title during sync set up",
+ "comment" : "Preparing to sync dialog title during sync set up",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
@@ -389,7 +389,7 @@
}
},
"preferences.recover-synced-data.dialog-subtitle" : {
- "comment" : "Recover synced data during Sync revoery process dialog subtitle",
+ "comment" : "Recover synced data during Sync recovery process dialog subtitle",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
diff --git a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift
index ca0babbe0f..3d9356af3e 100644
--- a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift
+++ b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift
@@ -21,150 +21,151 @@ import Foundation
enum UserText {
// Generic Buttons
- static let ok = NSLocalizedString("ok", value: "OK", comment: "OK button")
- static let notNow = NSLocalizedString("notnow", value: "Not Now", comment: "Not Now button")
- static let cancel = NSLocalizedString("cancel", value: "Cancel", comment: "Cancel button")
- static let submit = NSLocalizedString("submit", value: "Submit", comment: "Submit button")
- static let next = NSLocalizedString("next", value: "Next", comment: "Next button")
- static let copy = NSLocalizedString("copy", value: "Copy", comment: "Copy button")
- static let share = NSLocalizedString("share", value: "Share", comment: "Share button")
- static let paste = NSLocalizedString("paste", value: "Paste", comment: "Paste button")
- static let pasteFromClipboard = NSLocalizedString("paste-from-clipboard", value: "Paste from Clipboard", comment: "Paste button")
- static let done = NSLocalizedString("done", value: "Done", comment: "Done button")
+ static let ok = NSLocalizedString("ok", bundle: Bundle.module, value: "OK", comment: "OK button")
+ static let notNow = NSLocalizedString("notnow", bundle: Bundle.module, value: "Not Now", comment: "Not Now button")
+ static let cancel = NSLocalizedString("cancel", bundle: Bundle.module, value: "Cancel", comment: "Cancel button")
+ static let submit = NSLocalizedString("submit", bundle: Bundle.module, value: "Submit", comment: "Submit button")
+ static let next = NSLocalizedString("next", bundle: Bundle.module, value: "Next", comment: "Next button")
+ static let copy = NSLocalizedString("copy", bundle: Bundle.module, value: "Copy", comment: "Copy button")
+ static let share = NSLocalizedString("share", bundle: Bundle.module, value: "Share", comment: "Share button")
+ static let paste = NSLocalizedString("paste", bundle: Bundle.module, value: "Paste", comment: "Paste button")
+ static let pasteFromClipboard = NSLocalizedString("paste-from-clipboard", bundle: Bundle.module, value: "Paste from Clipboard", comment: "Paste from Clipboard button")
+ static let done = NSLocalizedString("done", bundle: Bundle.module, value: "Done", comment: "Done button")
// Sync Set Up View
// Begin Sync card
- static let beginSyncTitle = NSLocalizedString("preferences.begin-sync.card-title", value: "Begin Syncing", comment: "Begin Syncing card title in sync settings")
- static let beginSyncDescription = NSLocalizedString("preferences.begin-sync.card-description", value: "Securely sync bookmarks and passwords between your devices.", comment: "Begin Syncing card description in sync settings")
- static let beginSyncButton = NSLocalizedString("preferences.begin-sync.card-button", value: "Sync With Another Device", comment: "Button text on the Begin Syncing card in sync settings")
- static let beginSyncFooter = NSLocalizedString("preferences.begin-sync.card-footer", value: "Your data is end-to-end encrypted, and DuckDuckGo does not have access to the encryption key.", comment: "Footer / captoin on the Begin Syncing card in sync settings")
+ static let beginSyncTitle = NSLocalizedString("preferences.begin-sync.card-title", bundle: Bundle.module, value: "Begin Syncing", comment: "Begin Syncing card title in sync settings")
+ static let beginSyncDescription = NSLocalizedString("preferences.begin-sync.card-description", bundle: Bundle.module, value: "Securely sync bookmarks and passwords between your devices.", comment: "Begin Syncing card description in sync settings")
+ static let beginSyncButton = NSLocalizedString("preferences.begin-sync.card-button", bundle: Bundle.module, value: "Sync With Another Device", comment: "Button text on the Begin Syncing card in sync settings")
+ static let beginSyncFooter = NSLocalizedString("preferences.begin-sync.card-footer", bundle: Bundle.module, value: "Your data is end-to-end encrypted, and DuckDuckGo does not have access to the encryption key.", comment: "Footer / caption on the Begin Syncing card in sync settings")
+
// Options
- static let otherOptionsSectionTitle = NSLocalizedString("preferences.other-options.section-title", value: "Other Options", comment: "Sync settings. Other Options section title")
- static let syncThisDeviceLink = NSLocalizedString("preferences.sync-this-device.link-title", value: "Sync and Back Up This Device", comment: "Sync settings. Title of a link to start setting up sync and backup the device")
- static let recoverDataLink = NSLocalizedString("preferences.recover-data.link-title", value: "Recover Synced Data", comment: "Sync settings. Link to recover synced data.")
+ static let otherOptionsSectionTitle = NSLocalizedString("preferences.other-options.section-title", bundle: Bundle.module, value: "Other Options", comment: "Sync settings. Other Options section title")
+ static let syncThisDeviceLink = NSLocalizedString("preferences.sync-this-device.link-title", bundle: Bundle.module, value: "Sync and Back Up This Device", comment: "Sync settings. Title of a link to start setting up sync and backup the device")
+ static let recoverDataLink = NSLocalizedString("preferences.recover-data.link-title", bundle: Bundle.module, value: "Recover Synced Data", comment: "Sync settings. Link to recover synced data.")
// Preparing to sync dialog
- static let preparingToSyncDialogTitle = NSLocalizedString("preferences.preparing-to-sync.dialog-title", value: "Preparing To Sync", comment: "Peparing to sync dialog title during sync set up")
- static let preparingToSyncDialogSubTitle = NSLocalizedString("preferences.preparing-to-sync.dialog-subtitle", value: "We're setting up the connection to synchronize your bookmarks and saved logins with the other device.", comment: "Preparing to sync dialog subtitle during sync set up")
- static let preparingToSyncDialogAction = NSLocalizedString("preferences.preparing-to-sync.dialog-action", value: "Connecting…", comment: "Sync preparing to sync dialog action")
+ static let preparingToSyncDialogTitle = NSLocalizedString("preferences.preparing-to-sync.dialog-title", bundle: Bundle.module, value: "Preparing To Sync", comment: "Preparing to sync dialog title during sync set up")
+ static let preparingToSyncDialogSubTitle = NSLocalizedString("preferences.preparing-to-sync.dialog-subtitle", bundle: Bundle.module, value: "We're setting up the connection to synchronize your bookmarks and saved logins with the other device.", comment: "Preparing to sync dialog subtitle during sync set up")
+ static let preparingToSyncDialogAction = NSLocalizedString("preferences.preparing-to-sync.dialog-action", bundle: Bundle.module, value: "Connecting…", comment: "Sync preparing to sync dialog action")
// Enter recovery code dialog
- static let enterRecoveryCodeDialogTitle = NSLocalizedString("preferences.enter-recovery-code.dialog-title", value: "Enter Code", comment: "Sync enter recovery code dialog title")
- static let enterRecoveryCodeDialogSubtitle = NSLocalizedString("preferences.enter-recovery-code.dialog-subtitle", value: "Enter the code on your Recovery PDF, or another synced device, to recover your synced data.", comment: "Sync enter recovery code dialog subtitle")
- static let enterRecoveryCodeDialogAction1 = NSLocalizedString("preferences.enter-recovery-code.dialog-action1", value: "Paste Code Here", comment: "Sync enter recovery code dialog first possible action")
- static let enterRecoveryCodeDialogAction2 = NSLocalizedString("preferences.enter-recovery-code.dialog-action2", value: "or scan QR code with a device that is still connected", comment: "Sync enter recovery code dialog second possible action")
+ static let enterRecoveryCodeDialogTitle = NSLocalizedString("preferences.enter-recovery-code.dialog-title", bundle: Bundle.module, value: "Enter Code", comment: "Sync enter recovery code dialog title")
+ static let enterRecoveryCodeDialogSubtitle = NSLocalizedString("preferences.enter-recovery-code.dialog-subtitle", bundle: Bundle.module, value: "Enter the code on your Recovery PDF, or another synced device, to recover your synced data.", comment: "Sync enter recovery code dialog subtitle")
+ static let enterRecoveryCodeDialogAction1 = NSLocalizedString("preferences.enter-recovery-code.dialog-action1", bundle: Bundle.module, value: "Paste Code Here", comment: "Sync enter recovery code dialog first possible action")
+ static let enterRecoveryCodeDialogAction2 = NSLocalizedString("preferences.enter-recovery-code.dialog-action2", bundle: Bundle.module, value: "or scan QR code with a device that is still connected", comment: "Sync enter recovery code dialog second possible action")
// Recover synced data dialog
- static let reciverSyncedDataDialogTitle = NSLocalizedString("preferences.recover-synced-data.dialog-title", value: "Recover Synced Data", comment: "Sync recover synced data dialog title")
- static let reciverSyncedDataDialogSubitle = NSLocalizedString("preferences.recover-synced-data.dialog-subtitle", value: "To restore your synced data, you'll need the Recovery Code you saved when you first set up Sync. This code may have been saved as a PDF on the device you originally used to set up Sync.", comment: "Recover synced data during Sync revoery process dialog subtitle")
- static let reciverSyncedDataDialogButton = NSLocalizedString("preferences.recover-synced-data.dialog-button", value: "Get Started", comment: "Sync recover synced data dialog button")
+ static let reciverSyncedDataDialogTitle = NSLocalizedString("preferences.recover-synced-data.dialog-title", bundle: Bundle.module, value: "Recover Synced Data", comment: "Sync recover synced data dialog title")
+ static let reciverSyncedDataDialogSubitle = NSLocalizedString("preferences.recover-synced-data.dialog-subtitle", bundle: Bundle.module, value: "To restore your synced data, you'll need the Recovery Code you saved when you first set up Sync. This code may have been saved as a PDF on the device you originally used to set up Sync.", comment: "Recover synced data during Sync recovery process dialog subtitle")
+ static let reciverSyncedDataDialogButton = NSLocalizedString("preferences.recover-synced-data.dialog-button", bundle: Bundle.module, value: "Get Started", comment: "Sync recover synced data dialog button")
// Sync Title
- static let sync = NSLocalizedString("preferences.sync", value: "Sync & Backup", comment: "Show sync preferences")
- static let syncRollOutBannerDescription = NSLocalizedString("preferences.sync.rollout-banner.description", value: "Sync & Backup is rolling out gradually and may not be available yet within DuckDuckGo on your other devices.", comment: "Description of rollout banner")
+ static let sync = NSLocalizedString("preferences.sync", bundle: Bundle.module, value: "Sync & Backup", comment: "Show sync preferences")
+ static let syncRollOutBannerDescription = NSLocalizedString("preferences.sync.rollout-banner.description", bundle: Bundle.module, value: "Sync & Backup is rolling out gradually and may not be available yet within DuckDuckGo on your other devices.", comment: "Description of rollout banner")
- static let turnOff = NSLocalizedString("preferences.sync.turn-off", value: "Turn Off", comment: "Turn off sync confirmation dialog button title")
- static let turnOffSync = NSLocalizedString("preferences.sync.turn-off.ellipsis", value: "Turn Off Sync…", comment: "Disable sync button caption")
+ static let turnOff = NSLocalizedString("preferences.sync.turn-off", bundle: Bundle.module, value: "Turn Off", comment: "Turn off sync confirmation dialog button title")
+ static let turnOffSync = NSLocalizedString("preferences.sync.turn-off.ellipsis", bundle: Bundle.module, value: "Turn Off Sync…", comment: "Disable sync button caption")
// Sync Enabled View
// Turn off sync dialog
- static let turnOffSyncConfirmTitle = NSLocalizedString("preferences.sync.turn-off.confirm.title", value: "Turn off sync?", comment: "Turn off sync confirmation dialog title")
- static let turnOffSyncConfirmMessage = NSLocalizedString("preferences.sync.turn-off.confirm.message", value: "This device will no longer be able to access your synced data.", comment: "Turn off sync confirmation dialog message")
+ static let turnOffSyncConfirmTitle = NSLocalizedString("preferences.sync.turn-off.confirm.title", bundle: Bundle.module, value: "Turn off sync?", comment: "Turn off sync confirmation dialog title")
+ static let turnOffSyncConfirmMessage = NSLocalizedString("preferences.sync.turn-off.confirm.message", bundle: Bundle.module, value: "This device will no longer be able to access your synced data.", comment: "Turn off sync confirmation dialog message")
// Delete server data
- static let turnOffAndDeleteServerData = NSLocalizedString("preferences.sync.turn-off-and-delete-data", value: "Turn Off and Delete Server Data…", comment: "Disable and delete data sync button caption")
+ static let turnOffAndDeleteServerData = NSLocalizedString("preferences.sync.turn-off-and-delete-data", bundle: Bundle.module, value: "Turn Off and Delete Server Data…", comment: "Disable and delete data sync button caption")
// sync connected
- static let syncConnected = NSLocalizedString("preferences.sync.connected", value: "Sync Enabled", comment: "Sync state is enabled")
+ static let syncConnected = NSLocalizedString("preferences.sync.connected", bundle: Bundle.module, value: "Sync Enabled", comment: "Sync state is enabled")
// synced devices
- static let syncedDevices = NSLocalizedString("preferences.sync.synced-devices", value: "Synced Devices", comment: "Settings section title")
- static let thisDevice = NSLocalizedString("preferences.sync.this-device", value: "This Device", comment: "Indicator of a current user's device on the list")
- static let currentDeviceDetails = NSLocalizedString("preferences.sync.current-device-details", value: "Details...", comment: "Sync Settings device details button")
- static let removeDeviceButton = NSLocalizedString("preferences.sync.remove-device", value: "Remove...", comment: "Button to remove a device")
+ static let syncedDevices = NSLocalizedString("preferences.sync.synced-devices", bundle: Bundle.module, value: "Synced Devices", comment: "Settings section title")
+ static let thisDevice = NSLocalizedString("preferences.sync.this-device", bundle: Bundle.module, value: "This Device", comment: "Indicator of a current user's device on the list")
+ static let currentDeviceDetails = NSLocalizedString("preferences.sync.current-device-details", bundle: Bundle.module, value: "Details...", comment: "Sync Settings device details button")
+ static let removeDeviceButton = NSLocalizedString("preferences.sync.remove-device", bundle: Bundle.module, value: "Remove...", comment: "Button to remove a device")
// Remove device dialog
- static let removeDeviceConfirmTitle = NSLocalizedString("preferences.sync.remove-device-title", value: "Remove device?", comment: "Title on remove a device confirmation")
- static let removeDeviceConfirmButton = NSLocalizedString("preferences.sync.remove-device-button", value: "Remove Device", comment: "Button text on remove a device confirmation button")
+ static let removeDeviceConfirmTitle = NSLocalizedString("preferences.sync.remove-device-title", bundle: Bundle.module, value: "Remove device?", comment: "Title on remove a device confirmation")
+ static let removeDeviceConfirmButton = NSLocalizedString("preferences.sync.remove-device-button", bundle: Bundle.module, value: "Remove Device", comment: "Button text on remove a device confirmation button")
static func removeDeviceConfirmMessage(_ deviceName: String) -> String {
let localized = NSLocalizedString("preferences.sync.remove-device-message",
- value: "\"%@\" will no longer be able to access your synced data.",
+ bundle: Bundle.module, value: "\"%@\" will no longer be able to access your synced data.",
comment: "Message to confirm the device will no longer be able to access the synced data - devoce name item inserted")
return String(format: localized, deviceName)
}
- static let recovery = NSLocalizedString("prefrences.sync.recovery", value: "Recovery", comment: "Sync settings section title")
- static let recoveryInstructions = NSLocalizedString("prefrences.sync.recovery-instructions", value: "If you lose your device, you will need this recovery code to restore your synced data.", comment: "Instructions on how to restore synced data")
+ static let recovery = NSLocalizedString("prefrences.sync.recovery", bundle: Bundle.module, value: "Recovery", comment: "Sync settings section title")
+ static let recoveryInstructions = NSLocalizedString("prefrences.sync.recovery-instructions", bundle: Bundle.module, value: "If you lose your device, you will need this recovery code to restore your synced data.", comment: "Instructions on how to restore synced data")
// Sync with another device dialog
- static let syncWithAnotherDeviceTitle = NSLocalizedString("preferences.sync.sync-with-another-device.dialog-title", value: "Sync With Another Device", comment: "Sync with another device dialog title")
+ static let syncWithAnotherDeviceTitle = NSLocalizedString("preferences.sync.sync-with-another-device.dialog-title", bundle: Bundle.module, value: "Sync With Another Device", comment: "Sync with another device dialog title")
static func syncWithAnotherDeviceSubtitle(syncMenuPath: String) -> String {
- let localized = NSLocalizedString("preferences.sync.sync-with-another-device.dialog-subtitle1", value: "Go to %@ in the DuckDuckGo Browser on another device and select Sync With Another Device.", comment: "Sync with another device dialog subtitle - Instruction with sync menu path item inserted")
+ let localized = NSLocalizedString("preferences.sync.sync-with-another-device.dialog-subtitle1", bundle: Bundle.module, value: "Go to %@ in the DuckDuckGo Browser on another device and select Sync With Another Device.", comment: "Sync with another device dialog subtitle - Instruction with sync menu path item inserted")
return String(format: localized, syncMenuPath)
}
- static let syncMenuPath = NSLocalizedString("sync.menu.path", value: "Settings › Sync & Backup", comment: "Sync Menu Path")
- static let syncWithAnotherDeviceShowCodeButton = NSLocalizedString("preferences.sync.sync-with-another-device.show-code-button", value: "Show Code", comment: "Text on show code button on Sync with another device dialog")
- static let syncWithAnotherDeviceEnterCodeButton = NSLocalizedString("preferences.sync.sync-with-another-device.enter-code-button", value: "Enter Code", comment: "Text on enter code button on Sync with another device dialog")
- static let syncWithAnotherDeviceShowQRCodeExplanation = NSLocalizedString("preferences.sync.sync-with-another-device.show-qr-code-explanation", value: "Scan this QR code to connect.", comment: "Sync with another device dialog show qr code explanation")
- static let syncWithAnotherDeviceEnterCodeExplanation = NSLocalizedString("preferences.sync.sync-with-another-device.enter-code-explanation", value: "Paste the code here to sync.", comment: "Sync with another device dialog enter code explanation")
- static let syncWithAnotherDeviceShowCodeExplanation = NSLocalizedString("preferences.sync.sync-with-another-device.show-code-explanation", value: "Share this code to connect with a desktop machine.", comment: "Sync with another device dialog show code explanation")
- static let syncWithAnotherDeviceViewQRCode = NSLocalizedString("preferences.sync.sync-with-another-device.view-qr-code-link", value: "View QR Code", comment: "Sync with another device dialog view qr code link")
- static let syncWithAnotherDeviceViewTextCode = NSLocalizedString("preferences.sync.sync-with-another-device.view-text-code-link", value: "View Text Code", comment: "Sync with another device dialog view text code link")
+ static let syncMenuPath = NSLocalizedString("sync.menu.path", bundle: Bundle.module, value: "Settings › Sync & Backup", comment: "Sync Menu Path")
+ static let syncWithAnotherDeviceShowCodeButton = NSLocalizedString("preferences.sync.sync-with-another-device.show-code-button", bundle: Bundle.module, value: "Show Code", comment: "Text on show code button on Sync with another device dialog")
+ static let syncWithAnotherDeviceEnterCodeButton = NSLocalizedString("preferences.sync.sync-with-another-device.enter-code-button", bundle: Bundle.module, value: "Enter Code", comment: "Text on enter code button on Sync with another device dialog")
+ static let syncWithAnotherDeviceShowQRCodeExplanation = NSLocalizedString("preferences.sync.sync-with-another-device.show-qr-code-explanation", bundle: Bundle.module, value: "Scan this QR code to connect.", comment: "Sync with another device dialog show qr code explanation")
+ static let syncWithAnotherDeviceEnterCodeExplanation = NSLocalizedString("preferences.sync.sync-with-another-device.enter-code-explanation", bundle: Bundle.module, value: "Paste the code here to sync.", comment: "Sync with another device dialog enter code explanation")
+ static let syncWithAnotherDeviceShowCodeExplanation = NSLocalizedString("preferences.sync.sync-with-another-device.show-code-explanation", bundle: Bundle.module, value: "Share this code to connect with a desktop machine.", comment: "Sync with another device dialog show code explanation")
+ static let syncWithAnotherDeviceViewQRCode = NSLocalizedString("preferences.sync.sync-with-another-device.view-qr-code-link", bundle: Bundle.module, value: "View QR Code", comment: "Sync with another device dialog view qr code link")
+ static let syncWithAnotherDeviceViewTextCode = NSLocalizedString("preferences.sync.sync-with-another-device.view-text-code-link", bundle: Bundle.module, value: "View Text Code", comment: "Sync with another device dialog view text code link")
// Save recovery PDF dialog
- static let saveRecoveryPDF = NSLocalizedString("prefrences.sync.save-recovery-pdf", value: "Save Your Recovery Code", comment: "Caption for a button to save Sync recovery PDF")
- static let recoveryPDFExplanation = NSLocalizedString("prefrences.sync.recovery-pdf-explanation", value: "If you lose access to your devices, you will need this code to recover your synced data. You can save this code to your device as a PDF.", comment: "Sync recovery PDF explanation")
- static let recoveryPDFCopyCodeButton = NSLocalizedString("prefrences.sync.recovery-pdf-copy-code-button", value: "Copy Code", comment: "Sync recovery PDF copy code button")
- static let recoveryPDFSavePDFButton = NSLocalizedString("prefrences.sync.recovery-pdf-save-pdf-button", value: "Save PDF", comment: "Sync recovery PDF save pdf button")
- static let recoveryPDFWarning = NSLocalizedString("prefrences.sync.recovery-pdf-warning", value: "Anyone with access to this code can access your synced data, so please keep it in a safe place.", comment: "Sync recovery PDF warning")
+ static let saveRecoveryPDF = NSLocalizedString("prefrences.sync.save-recovery-pdf", bundle: Bundle.module, value: "Save Your Recovery Code", comment: "Caption for a button to save Sync recovery PDF")
+ static let recoveryPDFExplanation = NSLocalizedString("prefrences.sync.recovery-pdf-explanation", bundle: Bundle.module, value: "If you lose access to your devices, you will need this code to recover your synced data. You can save this code to your device as a PDF.", comment: "Sync recovery PDF explanation")
+ static let recoveryPDFCopyCodeButton = NSLocalizedString("prefrences.sync.recovery-pdf-copy-code-button", bundle: Bundle.module, value: "Copy Code", comment: "Sync recovery PDF copy code button")
+ static let recoveryPDFSavePDFButton = NSLocalizedString("prefrences.sync.recovery-pdf-save-pdf-button", bundle: Bundle.module, value: "Save PDF", comment: "Sync recovery PDF save pdf button")
+ static let recoveryPDFWarning = NSLocalizedString("prefrences.sync.recovery-pdf-warning", bundle: Bundle.module, value: "Anyone with access to this code can access your synced data, so please keep it in a safe place.", comment: "Sync recovery PDF warning")
// Sync with server dialog
- static let syncWithServerTitle = NSLocalizedString("preferences.sync.sync-with-server-title", value: "Sync and Back Up This Device", comment: "Sync with server dialog title")
- static let syncWithServerSubtitle1 = NSLocalizedString("preferences.sync.sync-with-server-subtitle1", value: "This creates an encrypted backup of your bookmarks and passwords on DuckDuckGo’s secure server, which can be synced with your other devices.", comment: "Sync with server dialog first subtitle")
- static let syncWithServerSubtitle2 = NSLocalizedString("preferences.sync.sync-with-server-subtitle2", value: "The encryption key is only stored on your device, DuckDuckGo cannot access it.", comment: "Sync with server dialog second subtitle")
- static let syncWithServerButton = NSLocalizedString("preferences.sync.sync-with-server-button", value: "Turn On Sync & Backup", comment: "Sync with server dialog button")
+ static let syncWithServerTitle = NSLocalizedString("preferences.sync.sync-with-server-title", bundle: Bundle.module, value: "Sync and Back Up This Device", comment: "Sync with server dialog title")
+ static let syncWithServerSubtitle1 = NSLocalizedString("preferences.sync.sync-with-server-subtitle1", bundle: Bundle.module, value: "This creates an encrypted backup of your bookmarks and passwords on DuckDuckGo’s secure server, which can be synced with your other devices.", comment: "Sync with server dialog first subtitle")
+ static let syncWithServerSubtitle2 = NSLocalizedString("preferences.sync.sync-with-server-subtitle2", bundle: Bundle.module, value: "The encryption key is only stored on your device, DuckDuckGo cannot access it.", comment: "Sync with server dialog second subtitle")
+ static let syncWithServerButton = NSLocalizedString("preferences.sync.sync-with-server-button", bundle: Bundle.module, value: "Turn On Sync & Backup", comment: "Sync with server dialog button")
// Device synced dialog
- static let deviceSynced = NSLocalizedString("prefrences.sync.device-synced", value: "Your data is synced!", comment: "Sync setup confirmation dialog title")
+ static let deviceSynced = NSLocalizedString("prefrences.sync.device-synced", bundle: Bundle.module, value: "Your data is synced!", comment: "Sync setup confirmation dialog title")
// Device details
- static let deviceDetailsTitle = NSLocalizedString("prefrences.sync.device-details.title", value: "Device Details", comment: "The title of the device details dialog")
- static let deviceDetailsLabel = NSLocalizedString("prefrences.sync.device-details.label", value: "Name", comment: "The text entry label to name the device")
- static let deviceDetailsPrompt = NSLocalizedString("prefrences.sync.device-details.prompt", value: "Device name", comment: "The text entry prompt to name the device")
+ static let deviceDetailsTitle = NSLocalizedString("prefrences.sync.device-details.title", bundle: Bundle.module, value: "Device Details", comment: "The title of the device details dialog")
+ static let deviceDetailsLabel = NSLocalizedString("prefrences.sync.device-details.label", bundle: Bundle.module, value: "Name", comment: "The text entry label to name the device")
+ static let deviceDetailsPrompt = NSLocalizedString("prefrences.sync.device-details.prompt", bundle: Bundle.module, value: "Device name", comment: "The text entry prompt to name the device")
// Delete Account Dialog
- static let deleteAccountTitle = NSLocalizedString("prefrences.sync.delete-account.title", value: "Delete server data?", comment: "Title for delete account confirmation pop up")
- static let deleteAccountMessage = NSLocalizedString("prefrences.sync.delete-account.message", value: "These devices will be disconnected and your synced data will be deleted from the server.", comment: "Message for delete account confirmation pop up")
- static let deleteAccountButton = NSLocalizedString("prefrences.sync.delete-account.button", value: "Delete Data", comment: "Label for delete account button")
+ static let deleteAccountTitle = NSLocalizedString("prefrences.sync.delete-account.title", bundle: Bundle.module, value: "Delete server data?", comment: "Title for delete account confirmation pop up")
+ static let deleteAccountMessage = NSLocalizedString("prefrences.sync.delete-account.message", bundle: Bundle.module, value: "These devices will be disconnected and your synced data will be deleted from the server.", comment: "Message for delete account confirmation pop up")
+ static let deleteAccountButton = NSLocalizedString("prefrences.sync.delete-account.button", bundle: Bundle.module, value: "Delete Data", comment: "Label for delete account button")
// Sync enabled options
- static let optionsSectionTitle = NSLocalizedString("prefrences.sync.options-section-title", value: "Options", comment: "Title for options settings")
- static let shareFavoritesOptionTitle = NSLocalizedString("prefrences.sync.share-favorite-option-title", value: "Unify Favorites Across Devices", comment: "Title for share favorite option")
- static let shareFavoritesOptionCaption = NSLocalizedString("prefrences.sync.share-favorite-option-caption", value: "Use the same favorite bookmarks on all your devices. Leave off to keep mobile and desktop favorites separate.", comment: "Caption for share favorite option")
- static let fetchFaviconsOptionTitle = NSLocalizedString("prefrences.sync.fetch-favicons-option-title", value: "Auto-Download Icons", comment: "Title for fetch favicons option")
- static let fetchFaviconsOptionCaption = NSLocalizedString("prefrences.sync.fetch-favicons-option-caption", value: "Automatically download icons for synced bookmarks. Icon downloads are exposed to your network.", comment: "Caption for fetch favicons option")
+ static let optionsSectionTitle = NSLocalizedString("prefrences.sync.options-section-title", bundle: Bundle.module, value: "Options", comment: "Title for options settings")
+ static let shareFavoritesOptionTitle = NSLocalizedString("prefrences.sync.share-favorite-option-title", bundle: Bundle.module, value: "Unify Favorites Across Devices", comment: "Title for share favorite option")
+ static let shareFavoritesOptionCaption = NSLocalizedString("prefrences.sync.share-favorite-option-caption", bundle: Bundle.module, value: "Use the same favorite bookmarks on all your devices. Leave off to keep mobile and desktop favorites separate.", comment: "Caption for share favorite option")
+ static let fetchFaviconsOptionTitle = NSLocalizedString("prefrences.sync.fetch-favicons-option-title", bundle: Bundle.module, value: "Auto-Download Icons", comment: "Title for fetch favicons option")
+ static let fetchFaviconsOptionCaption = NSLocalizedString("prefrences.sync.fetch-favicons-option-caption", bundle: Bundle.module, value: "Automatically download icons for synced bookmarks. Icon downloads are exposed to your network.", comment: "Caption for fetch favicons option")
// sync enabled errors
- 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 go to manage bookmarks")
- static let credentialsLimitExceededAction = NSLocalizedString("prefrences.sync.credentials-limit-exceeded-action", value: "Manage passwords…", comment: "Button title for sync credentials limits exceeded warning to go to manage passwords")
- static let syncErrorAlertTitle = NSLocalizedString("alert.sync-error", value: "Sync & Backup Error", comment: "Title for sync error alert")
- static let unableToSyncToServerDescription = NSLocalizedString("alert.unable-to-sync-to-server-description", value: "Unable to connect to the server.", comment: "Description for unable to sync to server error")
- static let unableToSyncWithAnotherDeviceDescription = NSLocalizedString("alert.unable-to-sync-with-another-device-description", value: "Unable to Sync with another device.", comment: "Description for unable to sync with another device error")
- static let unableToMergeTwoAccountsDescription = NSLocalizedString("alert.unable-to-merge-two-accounts-description", value: "To pair these devices, turn off Sync & Backup on one device then tap \"Sync With Another Device\" on the other device.", comment: "Description for unable to merge two accounts error")
- static let unableToUpdateDeviceNameDescription = NSLocalizedString("alert.unable-to-update-device-name-description", value: "Unable to update the device name.", comment: "Description for unable to update device name error")
- static let unableToTurnSyncOffDescription = NSLocalizedString("alert.unable-to-turn-sync-off-description", value: "Unable to turn Sync & Backup off.", comment: "Description for unable to turn sync off error")
- static let unableToDeleteDataDescription = NSLocalizedString("alert.unable-to-delete-data-description", value: "Unable to delete data on the server.", comment: "Description for unable to delete data error")
- static let unableToRemoveDeviceDescription = NSLocalizedString("alert.unable-to-remove-device-description", value: "Unable to remove this device from Sync & Backup.", comment: "Description for unable to remove device error")
- static let invalidCodeDescription = NSLocalizedString("alert.invalid-code-description", value: "Sorry, this code is invalid. Please make sure it was entered correctly.", comment: "Description for invalid code error")
- static let unableCreateRecoveryPdfDescription = NSLocalizedString("alert.unable-to-create-recovery-pdf-description", value: "Unable to create the recovery PDF.", comment: "Description for unable to create recovery pdf error")
-
- static let fetchFaviconsOnboardingTitle = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-title", value: "Download Missing Icons?", comment: "Title for fetch favicons onboarding dialog")
- static let fetchFaviconsOnboardingMessage = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-message", value: "Do you want this device to automatically download icons for any new bookmarks synced from your other devices? This will expose the download to your network any time a bookmark is synced.", comment: "Text for fetch favicons onboarding dialog")
- static let keepFaviconsUpdated = NSLocalizedString("prefrences.sync.keep-favicons-updated", value: "Keep Bookmarks Icons Updated", comment: "Title of the confirmation button for favicons fetching")
+ static let syncLimitExceededTitle = NSLocalizedString("prefrences.sync.limit-exceeded-title", bundle: Bundle.module, value: "Sync Paused", comment: "Title for sync limits exceeded warning")
+ static let bookmarksLimitExceededDescription = NSLocalizedString("prefrences.sync.bookmarks-limit-exceeded-description", bundle: Bundle.module, 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", bundle: Bundle.module, 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", bundle: Bundle.module, value: "Manage Bookmarks", comment: "Button title for sync bookmarks limits exceeded warning to go to manage bookmarks")
+ static let credentialsLimitExceededAction = NSLocalizedString("prefrences.sync.credentials-limit-exceeded-action", bundle: Bundle.module, value: "Manage passwords…", comment: "Button title for sync credentials limits exceeded warning to go to manage passwords")
+ static let syncErrorAlertTitle = NSLocalizedString("alert.sync-error", bundle: Bundle.module, value: "Sync & Backup Error", comment: "Title for sync error alert")
+ static let unableToSyncToServerDescription = NSLocalizedString("alert.unable-to-sync-to-server-description", bundle: Bundle.module, value: "Unable to connect to the server.", comment: "Description for unable to sync to server error")
+ static let unableToSyncWithAnotherDeviceDescription = NSLocalizedString("alert.unable-to-sync-with-another-device-description", bundle: Bundle.module, value: "Unable to Sync with another device.", comment: "Description for unable to sync with another device error")
+ static let unableToMergeTwoAccountsDescription = NSLocalizedString("alert.unable-to-merge-two-accounts-description", bundle: Bundle.module, value: "To pair these devices, turn off Sync & Backup on one device then tap \"Sync With Another Device\" on the other device.", comment: "Description for unable to merge two accounts error")
+ static let unableToUpdateDeviceNameDescription = NSLocalizedString("alert.unable-to-update-device-name-description", bundle: Bundle.module, value: "Unable to update the device name.", comment: "Description for unable to update device name error")
+ static let unableToTurnSyncOffDescription = NSLocalizedString("alert.unable-to-turn-sync-off-description", bundle: Bundle.module, value: "Unable to turn Sync & Backup off.", comment: "Description for unable to turn sync off error")
+ static let unableToDeleteDataDescription = NSLocalizedString("alert.unable-to-delete-data-description", bundle: Bundle.module, value: "Unable to delete data on the server.", comment: "Description for unable to delete data error")
+ static let unableToRemoveDeviceDescription = NSLocalizedString("alert.unable-to-remove-device-description", bundle: Bundle.module, value: "Unable to remove this device from Sync & Backup.", comment: "Description for unable to remove device error")
+ static let invalidCodeDescription = NSLocalizedString("alert.invalid-code-description", bundle: Bundle.module, value: "Sorry, this code is invalid. Please make sure it was entered correctly.", comment: "Description for invalid code error")
+ static let unableCreateRecoveryPdfDescription = NSLocalizedString("alert.unable-to-create-recovery-pdf-description", bundle: Bundle.module, value: "Unable to create the recovery PDF.", comment: "Description for unable to create recovery pdf error")
+
+ static let fetchFaviconsOnboardingTitle = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-title", bundle: Bundle.module, value: "Download Missing Icons?", comment: "Title for fetch favicons onboarding dialog")
+ static let fetchFaviconsOnboardingMessage = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-message", bundle: Bundle.module, value: "Do you want this device to automatically download icons for any new bookmarks synced from your other devices? This will expose the download to your network any time a bookmark is synced.", comment: "Text for fetch favicons onboarding dialog")
+ static let keepFaviconsUpdated = NSLocalizedString("prefrences.sync.keep-favicons-updated", bundle: Bundle.module, value: "Keep Bookmarks Icons Updated", comment: "Title of the confirmation button for favicons fetching")
// Sync Feature Flags
- static let syncUnavailableTitle = NSLocalizedString("sync.warning.sync-unavailable", value: "Sync & Backup is Unavailable", comment: "Title of the warning message that sync and backup are unavailable")
- static let syncPausedTitle = NSLocalizedString("sync.warning.sync-paused", value: "Sync & Backup is Paused", comment: "Title of the warning message that Sync & Backup is Paused")
- static let syncUnavailableMessage = NSLocalizedString("sync.warning.sync-unavailable-message", value: "Sorry, but Sync & Backup is currently unavailable. Please try again later.", comment: "Data syncing unavailable warning message")
- static let syncUnavailableMessageUpgradeRequired = NSLocalizedString("sync.warning.data-syncing-disabled-upgrade-required", value: "Sorry, but Sync & Backup is no longer available in this app version. Please update DuckDuckGo to the latest version to continue.", comment: "Data syncing unavailable warning message")
+ static let syncUnavailableTitle = NSLocalizedString("sync.warning.sync-unavailable", bundle: Bundle.module, value: "Sync & Backup is Unavailable", comment: "Title of the warning message that sync and backup are unavailable")
+ static let syncPausedTitle = NSLocalizedString("sync.warning.sync-paused", bundle: Bundle.module, value: "Sync & Backup is Paused", comment: "Title of the warning message that Sync & Backup is Paused")
+ static let syncUnavailableMessage = NSLocalizedString("sync.warning.sync-unavailable-message", bundle: Bundle.module, value: "Sorry, but Sync & Backup is currently unavailable. Please try again later.", comment: "Data syncing unavailable warning message")
+ static let syncUnavailableMessageUpgradeRequired = NSLocalizedString("sync.warning.data-syncing-disabled-upgrade-required", bundle: Bundle.module, value: "Sorry, but Sync & Backup is no longer available in this app version. Please update DuckDuckGo to the latest version to continue.", comment: "Data syncing unavailable warning message")
}