Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Commit 43e6df4

Browse files
authored
Integrate Identity Theft Restoration into macOS app (#2186)
Task/Issue URL: https://app.asana.com/0/0/1206566157509119/f **Description**: Integrate Identity Theft Restoration into macOS app --- ###### 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)
1 parent 84c536a commit 43e6df4

18 files changed

+185
-20
lines changed

DuckDuckGo.xcodeproj/project.pbxproj

+8
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@
114114
1E950E3F2912A10D0051A99B /* ContentBlocking in Frameworks */ = {isa = PBXBuildFile; productRef = 1E950E3E2912A10D0051A99B /* ContentBlocking */; };
115115
1E950E412912A10D0051A99B /* PrivacyDashboard in Frameworks */ = {isa = PBXBuildFile; productRef = 1E950E402912A10D0051A99B /* PrivacyDashboard */; };
116116
1E950E432912A10D0051A99B /* UserScript in Frameworks */ = {isa = PBXBuildFile; productRef = 1E950E422912A10D0051A99B /* UserScript */; };
117+
1ED910D52B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */; };
118+
1ED910D62B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */; };
119+
1ED910D72B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */; };
117120
310E79BF294A19A8007C49E8 /* FireproofingReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310E79BE294A19A8007C49E8 /* FireproofingReferenceTests.swift */; };
118121
311B262728E73E0A00FD181A /* TabShadowConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311B262628E73E0A00FD181A /* TabShadowConfig.swift */; };
119122
31267C692B640C4200FEF811 /* DataBrokerProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31C5FFB82AF64D120008A79F /* DataBrokerProtectionFeatureVisibility.swift */; };
@@ -3317,6 +3320,7 @@
33173320
1E7E2E932902AC0E00C01B54 /* PrivacyDashboardPermissionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardPermissionHandler.swift; sourceTree = "<group>"; };
33183321
1E862A882A9FC01200F84D4B /* SubscriptionUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SubscriptionUI; sourceTree = "<group>"; };
33193322
1E8F997E2B221B3600AC5D34 /* Subscription */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Subscription; sourceTree = "<group>"; };
3323+
1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesUserScript.swift; sourceTree = "<group>"; };
33203324
310E79BE294A19A8007C49E8 /* FireproofingReferenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FireproofingReferenceTests.swift; sourceTree = "<group>"; };
33213325
311B262628E73E0A00FD181A /* TabShadowConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabShadowConfig.swift; sourceTree = "<group>"; };
33223326
3139A1512AA4B3C000969C7D /* DataBrokerProtectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionManager.swift; sourceTree = "<group>"; };
@@ -8025,6 +8029,7 @@
80258029
856CADEF271710F400E79BB0 /* HoverUserScript.swift */,
80268030
4B2E7D6226FF9D6500D2DB17 /* PrintingUserScript.swift */,
80278031
1E0C72052ABC63BD00802009 /* SubscriptionPagesUserScript.swift */,
8032+
1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */,
80288033
85AC3AEE25D5CE9800C7D2AA /* UserScripts.swift */,
80298034
);
80308035
path = UserScripts;
@@ -9669,6 +9674,7 @@
96699674
3706FB3D293F65D500E42796 /* FocusRingView.swift in Sources */,
96709675
3706FB3E293F65D500E42796 /* BookmarksBarViewModel.swift in Sources */,
96719676
3706FB3F293F65D500E42796 /* NSPopUpButtonView.swift in Sources */,
9677+
1ED910D62B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */,
96729678
3706FB40293F65D500E42796 /* ContextualMenu.swift in Sources */,
96739679
3706FB41293F65D500E42796 /* NavigationBarViewController.swift in Sources */,
96749680
4B7534CC2A1FD7EA00158A99 /* NetworkProtectionInviteDialog.swift in Sources */,
@@ -10746,6 +10752,7 @@
1074610752
4B9579D12AC7AE700062CA31 /* SafariVersionReader.swift in Sources */,
1074710753
4B9579D22AC7AE700062CA31 /* LoginFaviconView.swift in Sources */,
1074810754
4B9579D32AC7AE700062CA31 /* FireproofDomainsViewController.swift in Sources */,
10755+
1ED910D72B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */,
1074910756
4B9579D42AC7AE700062CA31 /* URLEventHandler.swift in Sources */,
1075010757
3158B15E2B0BF76F00AF130C /* DataBrokerProtectionAppEvents.swift in Sources */,
1075110758
4B9579D52AC7AE700062CA31 /* SupportedOsChecker.swift in Sources */,
@@ -11374,6 +11381,7 @@
1137411381
3158B15C2B0BF76D00AF130C /* DataBrokerProtectionAppEvents.swift in Sources */,
1137511382
4B723E0E26B0006300E14D75 /* LoginImport.swift in Sources */,
1137611383
4B9DB03E2A983B24000927DB /* JoinWaitlistView.swift in Sources */,
11384+
1ED910D52B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */,
1137711385
37534CA028113101002621E7 /* LazyLoadable.swift in Sources */,
1137811386
EAE42800275D47FA00DAC26B /* ClickToLoadModel.swift in Sources */,
1137911387
0230C0A3272080090018F728 /* KeyedCodingExtension.swift in Sources */,

DuckDuckGo/Common/Extensions/URLExtension.swift

+4
Original file line numberDiff line numberDiff line change
@@ -472,4 +472,8 @@ extension URL {
472472
}
473473
return self.absoluteString
474474
}
475+
476+
public func isChild(of url: URL) -> Bool {
477+
self.absoluteString.hasPrefix(url.absoluteString)
478+
}
475479
}

DuckDuckGo/MainWindow/MainViewController.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ final class MainViewController: NSViewController {
414414
navigationBarViewController.addressBarViewController?.addressBarTextField.makeMeFirstResponder()
415415
case .onboarding:
416416
self.view.makeMeFirstResponder()
417-
case .url:
417+
case .url, .subscription:
418418
browserTabViewController.makeWebViewFirstResponder()
419419
case .settings:
420420
browserTabViewController.preferencesViewController?.view.makeMeFirstResponder()

DuckDuckGo/NavigationBar/View/AddressBarTextField.swift

+8
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,14 @@ final class AddressBarTextField: NSTextField {
316316
}
317317
#endif
318318

319+
#if SUBSCRIPTION
320+
if providedUrl.isChild(of: URL.subscriptionBaseURL) || providedUrl.isChild(of: URL.identityTheftRestoration) {
321+
selectedTabViewModel.updateAddressBarStrings()
322+
self.window?.makeFirstResponder(nil)
323+
return
324+
}
325+
#endif
326+
319327
selectedTabViewModel.tab.setUrl(providedUrl, source: .userEntered(userEnteredValue))
320328
selectedTabViewModel.updateAddressBarStrings()
321329

DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -1031,7 +1031,7 @@ extension NavigationBarViewController: OptionsButtonMenuDelegate {
10311031

10321032
#if SUBSCRIPTION
10331033
func optionsButtonMenuRequestedSubscriptionPurchasePage(_ menu: NSMenu) {
1034-
WindowControllersManager.shared.show(url: .purchaseSubscription, source: .ui, newTab: true)
1034+
WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase))
10351035
}
10361036
#endif
10371037

DuckDuckGo/Preferences/View/PreferencesRootView.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ enum Preferences {
100100
#if SUBSCRIPTION
101101
private func makeSubscriptionView() -> some View {
102102
let openURL: (URL) -> Void = { url in
103-
WindowControllersManager.shared.show(url: url, source: .ui, newTab: true)
103+
WindowControllersManager.shared.showTab(with: .subscription(url))
104104
}
105105

106106
let sheetActionHandler = SubscriptionAccessActionHandlers(restorePurchases: { SubscriptionPagesUseSubscriptionFeature.startAppStoreRestoreFlow() },

DuckDuckGo/RecentlyClosed/Model/RecentlyClosedCoordinator.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ extension Tab.TabContent {
242242
switch self {
243243
case .url(let url, credential: let credential, source: _):
244244
.url(url, credential: credential, source: .pendingStateRestoration)
245-
case .newtab, .settings, .bookmarks, .onboarding, .none, .dataBrokerProtection:
245+
case .newtab, .settings, .bookmarks, .onboarding, .none, .dataBrokerProtection, .subscription:
246246
self
247247
}
248248
}
@@ -252,7 +252,7 @@ extension Tab.TabContent {
252252
case .url(let url, credential: let credential, source: let source):
253253
let newSource: URLSource = source == .pendingStateRestoration ? .loadedByStateRestoration : .reload
254254
return .url(url, credential: credential, source: newSource)
255-
case .newtab, .settings, .bookmarks, .onboarding, .none, .dataBrokerProtection:
255+
case .newtab, .settings, .bookmarks, .onboarding, .none, .dataBrokerProtection, .subscription:
256256
return self
257257
}
258258
}

DuckDuckGo/RecentlyClosed/View/RecentlyClosedMenu.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ private extension NSMenuItem {
7676
case .bookmarks:
7777
image = TabViewModel.Favicon.preferences
7878
title = UserText.tabPreferencesTitle
79-
case .url:
79+
case .url, .subscription:
8080
image = recentlyClosedTab.favicon
8181
image?.size = NSSize.faviconSize
8282
title = recentlyClosedTab.title ?? recentlyClosedTab.tabContent.url?.absoluteString ?? ""

DuckDuckGo/StateRestoration/Tab+NSSecureCoding.swift

+6
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,10 @@ private extension Tab.TabContent {
9292
case onboarding = 4
9393
case duckPlayer = 5
9494
case dataBrokerProtection = 6
95+
case subscription = 7
9596
}
9697

98+
// swiftlint:disable:next cyclomatic_complexity
9799
init?(type: ContentType, url: URL?, videoID: String?, timestamp: String?, preferencePane: PreferencePaneIdentifier?) {
98100
switch type {
99101
case .newtab:
@@ -112,6 +114,9 @@ private extension Tab.TabContent {
112114
self = .url(.duckPlayer(videoID, timestamp: timestamp), source: .pendingStateRestoration)
113115
case .dataBrokerProtection:
114116
self = .dataBrokerProtection
117+
case .subscription:
118+
guard let url = url else { return nil }
119+
self = .subscription(url)
115120
}
116121
}
117122

@@ -124,6 +129,7 @@ private extension Tab.TabContent {
124129
case .onboarding: return .onboarding
125130
case .none: return .newtab
126131
case .dataBrokerProtection: return .dataBrokerProtection
132+
case .subscription: return .subscription
127133
}
128134
}
129135

DuckDuckGo/Tab/Model/Tab.swift

+18-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ import Navigation
2525
import UserScript
2626
import WebKit
2727

28+
#if SUBSCRIPTION
29+
import Subscription
30+
#endif
31+
2832
#if NETWORK_PROTECTION
2933
import NetworkProtection
3034
import NetworkProtectionIPC
@@ -57,6 +61,7 @@ protocol NewWindowPolicyDecisionMaker {
5761
case onboarding
5862
case none
5963
case dataBrokerProtection
64+
case subscription(URL)
6065

6166
enum URLSource: Equatable {
6267
case pendingStateRestoration
@@ -109,6 +114,7 @@ protocol NewWindowPolicyDecisionMaker {
109114

110115
}
111116

117+
// swiftlint:disable:next cyclomatic_complexity
112118
static func contentFromURL(_ url: URL?, source: URLSource) -> TabContent {
113119
switch url {
114120
case URL.newtab, URL.Invalid.aboutNewtab, URL.Invalid.duckHome:
@@ -129,6 +135,14 @@ protocol NewWindowPolicyDecisionMaker {
129135
default: break
130136
}
131137

138+
#if SUBSCRIPTION
139+
if let url {
140+
if url.isChild(of: URL.subscriptionBaseURL) || url.isChild(of: URL.identityTheftRestoration) {
141+
return .subscription(url)
142+
}
143+
}
144+
#endif
145+
132146
if let settingsPane = url.flatMap(PreferencePaneIdentifier.init(url:)) {
133147
return .settings(pane: settingsPane)
134148
} else if let url, let credential = url.basicAuthCredential {
@@ -184,6 +198,7 @@ protocol NewWindowPolicyDecisionMaker {
184198
case .bookmarks: return UserText.tabBookmarksTitle
185199
case .onboarding: return UserText.tabOnboardingTitle
186200
case .dataBrokerProtection: return UserText.tabDataBrokerProtectionTitle
201+
case .subscription: return nil
187202
}
188203
}
189204

@@ -215,14 +230,16 @@ protocol NewWindowPolicyDecisionMaker {
215230
return .welcome
216231
case .dataBrokerProtection:
217232
return .dataBrokerProtection
233+
case .subscription(let url):
234+
return url
218235
case .none:
219236
return nil
220237
}
221238
}
222239

223240
var isUrl: Bool {
224241
switch self {
225-
case .url:
242+
case .url, .subscription:
226243
return true
227244
default:
228245
return false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//
2+
// IdentityTheftRestorationPagesUserScript.swift
3+
//
4+
// Copyright © 2024 DuckDuckGo. All rights reserved.
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
19+
#if SUBSCRIPTION
20+
21+
import BrowserServicesKit
22+
import Common
23+
import Combine
24+
import Foundation
25+
import WebKit
26+
import Subscription
27+
import UserScript
28+
29+
///
30+
/// The user script that will be the broker for all subscription features
31+
///
32+
public final class IdentityTheftRestorationPagesUserScript: NSObject, UserScript, UserScriptMessaging {
33+
public var source: String = ""
34+
35+
public static let context = "identityTheftRestorationPages"
36+
37+
// special pages messaging cannot be isolated as we'll want regular page-scripts to be able to communicate
38+
public let broker = UserScriptMessageBroker(context: IdentityTheftRestorationPagesUserScript.context, requiresRunInPageContentWorld: true )
39+
40+
public let messageNames: [String] = [
41+
IdentityTheftRestorationPagesUserScript.context
42+
]
43+
44+
public let injectionTime: WKUserScriptInjectionTime = .atDocumentStart
45+
public let forMainFrameOnly = true
46+
public let requiresRunInPageContentWorld = true
47+
}
48+
49+
extension IdentityTheftRestorationPagesUserScript: WKScriptMessageHandlerWithReply {
50+
@MainActor
51+
public func userContentController(_ userContentController: WKUserContentController,
52+
didReceive message: WKScriptMessage) async -> (Any?, String?) {
53+
let action = broker.messageHandlerFor(message)
54+
do {
55+
let json = try await broker.execute(action: action, original: message)
56+
return (json, nil)
57+
} catch {
58+
// forward uncaught errors to the client
59+
return (nil, error.localizedDescription)
60+
}
61+
}
62+
}
63+
64+
// MARK: - Fallback for macOS 10.15
65+
extension IdentityTheftRestorationPagesUserScript: WKScriptMessageHandler {
66+
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
67+
// unsupported
68+
}
69+
}
70+
71+
///
72+
/// Use Subscription sub-feature
73+
///
74+
final class IdentityTheftRestorationPagesFeature: Subfeature {
75+
var broker: UserScriptMessageBroker?
76+
77+
var featureName = "useIdentityTheftRestoration"
78+
79+
var messageOriginPolicy: MessageOriginPolicy = .only(rules: [
80+
.exact(hostname: "duckduckgo.com"),
81+
.exact(hostname: "abrown.duckduckgo.com")
82+
])
83+
84+
func with(broker: UserScriptMessageBroker) {
85+
self.broker = broker
86+
}
87+
88+
func handler(forMethodNamed methodName: String) -> Subfeature.Handler? {
89+
switch methodName {
90+
case "getAccessToken": return getAccessToken
91+
default:
92+
return nil
93+
}
94+
}
95+
96+
func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? {
97+
if let accessToken = AccountManager().accessToken {
98+
return ["token": accessToken]
99+
} else {
100+
return [String: String]()
101+
}
102+
}
103+
}
104+
105+
#endif

DuckDuckGo/Tab/UserScripts/SubscriptionPagesUserScript.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature {
306306
case .personalInformationRemoval:
307307
NotificationCenter.default.post(name: .openPersonalInformationRemoval, object: self, userInfo: nil)
308308
case .identityTheftRestoration:
309-
NotificationCenter.default.post(name: .openIdentityTheftRestoration, object: self, userInfo: nil)
309+
await WindowControllersManager.shared.showTab(with: .subscription(.identityTheftRestoration))
310310
}
311311

312312
return nil
@@ -391,7 +391,7 @@ extension MainWindowController {
391391
guard let window else { return }
392392

393393
window.show(.subscriptionNotFoundAlert(), firstButtonAction: {
394-
WindowControllersManager.shared.show(url: .purchaseSubscription, source: .ui, newTab: true)
394+
WindowControllersManager.shared.show(url: .subscriptionPurchase, source: .ui, newTab: true)
395395
})
396396
}
397397

@@ -400,7 +400,7 @@ extension MainWindowController {
400400
guard let window else { return }
401401

402402
window.show(.subscriptionInactiveAlert(), firstButtonAction: {
403-
WindowControllersManager.shared.show(url: .purchaseSubscription, source: .ui, newTab: true)
403+
WindowControllersManager.shared.show(url: .subscriptionPurchase, source: .ui, newTab: true)
404404
})
405405
}
406406

DuckDuckGo/Tab/UserScripts/UserScripts.swift

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ final class UserScripts: UserScriptsProvider {
3232
let debugScript = DebugUserScript()
3333
#if SUBSCRIPTION
3434
let subscriptionPagesUserScript = SubscriptionPagesUserScript()
35+
let identityTheftRestorationPagesUserScript = IdentityTheftRestorationPagesUserScript()
3536
#endif
3637
let clickToLoadScript: ClickToLoadUserScript
3738

@@ -88,6 +89,9 @@ final class UserScripts: UserScriptsProvider {
8889
#if SUBSCRIPTION
8990
subscriptionPagesUserScript.registerSubfeature(delegate: SubscriptionPagesUseSubscriptionFeature())
9091
userScripts.append(subscriptionPagesUserScript)
92+
93+
identityTheftRestorationPagesUserScript.registerSubfeature(delegate: IdentityTheftRestorationPagesFeature())
94+
userScripts.append(identityTheftRestorationPagesUserScript)
9195
#endif
9296
}
9397

DuckDuckGo/Tab/View/BrowserTabViewController.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ final class BrowserTabViewController: NSViewController {
506506
}
507507
showTransientTabContentController(OnboardingViewController.create(withDelegate: self))
508508

509-
case .url:
509+
case .url, .subscription:
510510
if shouldReplaceWebView(for: tabViewModel) {
511511
removeAllTabContent(includingWebView: true)
512512
changeWebView(tabViewModel: tabViewModel)

0 commit comments

Comments
 (0)