Skip to content

Commit

Permalink
Merge branch 'main' into sam/vpn-ui-improvements
Browse files Browse the repository at this point in the history
* main:
  Subscriptions: Implement Caching for Subscription Info (#710)
  add history to ios (#693)
  Reports on toggle protections off (#696)
  Move vpnFirstEnabled and networkPathChange out of VPNSettings (#707)
  Implement simple Subscription Caching mechanism using UserDefaults (#703)
  • Loading branch information
samsymons committed Mar 13, 2024
2 parents 2082e9b + 4042a8e commit cd3e17d
Show file tree
Hide file tree
Showing 54 changed files with 2,539 additions and 343 deletions.
8 changes: 4 additions & 4 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/content-scope-scripts",
"state" : {
"revision" : "59752eb7973d3e3b0c23255ff51359f48b343f15",
"version" : "5.2.0"
"revision" : "f6241631fc14cc2d0f47950bfdc4d6c30bf90130",
"version" : "5.4.0"
}
},
{
Expand All @@ -50,8 +50,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/privacy-dashboard",
"state" : {
"revision" : "c67d268bf234760f49034a0fe7a6137a1b216b05",
"version" : "3.2.0"
"revision" : "43a6e1c1864846679a254e60c91332c3fbd922ee",
"version" : "3.3.0"
}
},
{
Expand Down
16 changes: 13 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ let package = Package(
.package(url: "https://github.com/duckduckgo/TrackerRadarKit", exact: "1.2.2"),
.package(url: "https://github.com/duckduckgo/sync_crypto", exact: "0.2.0"),
.package(url: "https://github.com/gumob/PunycodeSwift.git", exact: "2.1.0"),
.package(url: "https://github.com/duckduckgo/privacy-dashboard", exact: "3.2.0"),
.package(url: "https://github.com/duckduckgo/privacy-dashboard", exact: "3.3.0"),
.package(url: "https://github.com/duckduckgo/content-scope-scripts", exact: "5.4.0"),
.package(url: "https://github.com/httpswift/swifter.git", exact: "1.5.0"),
.package(url: "https://github.com/duckduckgo/bloom_cpp.git", exact: "3.0.0"),
Expand Down Expand Up @@ -105,6 +105,9 @@ let package = Package(
"Persistence",
"Common"
],
resources: [
.process("CoreData/BrowsingHistory.xcdatamodeld")
],
swiftSettings: [
.define("DEBUG", .when(configuration: .debug))
],
Expand Down Expand Up @@ -217,7 +220,8 @@ let package = Package(
"UserScript",
"ContentBlocking",
"Persistence",
.product(name: "PrivacyDashboardResources", package: "privacy-dashboard"),
"BrowserServicesKit",
.product(name: "PrivacyDashboardResources", package: "privacy-dashboard")
],
path: "Sources/PrivacyDashboard",
swiftSettings: [
Expand Down Expand Up @@ -335,7 +339,13 @@ let package = Package(
),

// MARK: - Test Targets

.testTarget(
name: "HistoryTests",
dependencies: [
"History",
],
plugins: [swiftlintPlugin]
),
.testTarget(
name: "BookmarksTests",
dependencies: [
Expand Down
2 changes: 1 addition & 1 deletion Sources/Bookmarks/BookmarkUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public struct BookmarkUtils {
request.resultType = .dictionaryResultType
request.propertiesToFetch = [#keyPath(BookmarkEntity.uuid)]

let result = (try? context.fetch(request) as? [Dictionary<String, Any>]) ?? []
let result = (try? context.fetch(request) as? [[String: Any]]) ?? []
return result.compactMap { $0[#keyPath(BookmarkEntity.uuid)] as? String }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public class BookmarkCoreDataImporter {

fetch.propertiesToFetch = [idDescription, #keyPath(BookmarkEntity.url)]

let dict = try context.fetch(fetch) as? [Dictionary<String, Any>]
let dict = try context.fetch(fetch) as? [[String: Any]]

if let result = dict?.reduce(into: [String: NSManagedObjectID](), { partialResult, data in
guard let urlString = data[#keyPath(BookmarkEntity.url)] as? String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ public enum ContentBlockerDebugEvents {
case contentBlockingCompilationFailed(listName: String, component: Component)

case contentBlockingCompilationTime

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// ToggleProtectionsCounter.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 Persistence
import Common

public enum ToggleProtectionsCounterEvent {

enum Parameter {

static let onCountKey = "onCount"
static let offCountKey = "offCount"

}

case toggleProtectionsCounterDaily

}

/// This class aggregates protection toggles and stores that count over 24 hours.
public class ToggleProtectionsCounter {

public enum Constant {

public static let onCountKey = "ToggleProtectionsCounter_On_Count"
public static let offCountKey = "ToggleProtectionsCounter_Off_Count"
public static let lastSendAtKey = "ToggleProtectionsCounter_Date"
public static let sendInterval: Double = 60 * 60 * 24 // 24 hours

}

private let store: KeyValueStoring
private let sendInterval: TimeInterval
private let eventReporting: EventMapping<ToggleProtectionsCounterEvent>?

public init(store: KeyValueStoring = ToggleProtectionsCounterStore(),
sendInterval: TimeInterval = Constant.sendInterval,
eventReporting: EventMapping<ToggleProtectionsCounterEvent>?) {
self.store = store
self.sendInterval = sendInterval
self.eventReporting = eventReporting
}

public func onToggleOn(currentTime: Date = Date()) {
save(toggleOnCount: toggleOnCount + 1)
sendEventsIfNeeded(currentTime: currentTime)
}

public func onToggleOff(currentTime: Date = Date()) {
save(toggleOffCount: toggleOffCount + 1)
sendEventsIfNeeded(currentTime: currentTime)
}

public func sendEventsIfNeeded(currentTime: Date = Date()) {
guard let lastSendAt else {
save(lastSendAt: currentTime)
return
}

if currentTime.timeIntervalSince(lastSendAt) > sendInterval {
eventReporting?.fire(.toggleProtectionsCounterDaily, parameters: [
ToggleProtectionsCounterEvent.Parameter.onCountKey: String(toggleOnCount),
ToggleProtectionsCounterEvent.Parameter.offCountKey: String(toggleOffCount)
])
resetStats(currentTime: currentTime)
}
}

private func resetStats(currentTime: Date = Date()) {
save(toggleOnCount: 0)
save(toggleOffCount: 0)
save(lastSendAt: currentTime)
}

// MARK: - Store

private var lastSendAt: Date? { store.object(forKey: Constant.lastSendAtKey) as? Date }
private var toggleOnCount: Int { store.object(forKey: Constant.onCountKey) as? Int ?? 0 }
private var toggleOffCount: Int { store.object(forKey: Constant.offCountKey) as? Int ?? 0 }

private func save(lastSendAt: Date) {
store.set(lastSendAt, forKey: Constant.lastSendAtKey)
}

private func save(toggleOnCount: Int) {
store.set(toggleOnCount, forKey: Constant.onCountKey)
}

private func save(toggleOffCount: Int) {
store.set(toggleOffCount, forKey: Constant.offCountKey)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// ToggleProtectionsCounterStore.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 Persistence

public struct ToggleProtectionsCounterStore: KeyValueStoring {

private var userDefaults: UserDefaults? { UserDefaults(suiteName: "com.duckduckgo.app.toggleProtectionsCounter") }

public init() {}

public func object(forKey defaultName: String) -> Any? { userDefaults?.object(forKey: defaultName) }
public func set(_ value: Any?, forKey defaultName: String) { userDefaults?.set(value, forKey: defaultName) }
public func removeObject(forKey defaultName: String) { userDefaults?.removeObject(forKey: defaultName) }

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public struct AppPrivacyConfiguration: PrivacyConfiguration {
private let data: PrivacyConfigurationData
private let locallyUnprotected: DomainsProtectionStore
private let internalUserDecider: InternalUserDecider
private let toggleProtectionsCounter: ToggleProtectionsCounter
private let userDefaults: UserDefaults
private let installDate: Date?

Expand All @@ -40,12 +41,14 @@ public struct AppPrivacyConfiguration: PrivacyConfiguration {
localProtection: DomainsProtectionStore,
internalUserDecider: InternalUserDecider,
userDefaults: UserDefaults = UserDefaults(),
toggleProtectionsCounter: ToggleProtectionsCounter,
installDate: Date? = nil) {
self.data = data
self.identifier = identifier
self.locallyUnprotected = localProtection
self.internalUserDecider = internalUserDecider
self.userDefaults = userDefaults
self.toggleProtectionsCounter = toggleProtectionsCounter
self.installDate = installDate
}

Expand Down Expand Up @@ -288,10 +291,12 @@ public struct AppPrivacyConfiguration: PrivacyConfiguration {
unprotectedDomain.punycodeEncodedHostname.lowercased() == domain
}
locallyUnprotected.enableProtection(forDomain: domainToRemove ?? domain)
toggleProtectionsCounter.onToggleOn()
}

public func userDisabledProtection(forDomain domain: String) {
locallyUnprotected.disableProtection(forDomain: domain.punycodeEncodedHostname.lowercased())
toggleProtectionsCounter.onToggleOff()
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public enum PrivacyFeature: String {
case networkProtection
case dbp
case sync
case privacyDashboard
case history
}

Expand Down Expand Up @@ -95,3 +96,11 @@ public enum SyncSubfeature: String, PrivacySubfeature {
case level2AllowSetupFlows
case level3AllowCreateAccount
}

public enum PrivacyDashboardSubfeature: String, PrivacySubfeature {

public var parent: PrivacyFeature { .privacyDashboard }

case toggleReports

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public protocol PrivacyConfigurationManaging: AnyObject {
var updatesPublisher: AnyPublisher<Void, Never> { get }
var privacyConfig: PrivacyConfiguration { get }
var internalUserDecider: InternalUserDecider { get }
var toggleProtectionsCounter: ToggleProtectionsCounter { get }

@discardableResult func reload(etag: String?, data: Data?) -> PrivacyConfigurationManager.ReloadResult
}
Expand All @@ -54,9 +55,11 @@ public class PrivacyConfigurationManager: PrivacyConfigurationManaging {
private let embeddedDataProvider: EmbeddedDataProvider
private let localProtection: DomainsProtectionStore
private let errorReporting: EventMapping<ContentBlockerDebugEvents>?
public let internalUserDecider: InternalUserDecider
private let installDate: Date?

public let toggleProtectionsCounter: ToggleProtectionsCounter
public let internalUserDecider: InternalUserDecider

private let updatesSubject = PassthroughSubject<Void, Never>()
public var updatesPublisher: AnyPublisher<Void, Never> {
updatesSubject.eraseToAnyPublisher()
Expand Down Expand Up @@ -108,6 +111,7 @@ public class PrivacyConfigurationManager: PrivacyConfigurationManaging {
embeddedDataProvider: EmbeddedDataProvider,
localProtection: DomainsProtectionStore,
errorReporting: EventMapping<ContentBlockerDebugEvents>? = nil,
toggleProtectionsCounterEventReporting: EventMapping<ToggleProtectionsCounterEvent>? = nil,
internalUserDecider: InternalUserDecider,
installDate: Date? = nil
) {
Expand All @@ -116,6 +120,7 @@ public class PrivacyConfigurationManager: PrivacyConfigurationManaging {
self.errorReporting = errorReporting
self.internalUserDecider = internalUserDecider
self.installDate = installDate
self.toggleProtectionsCounter = ToggleProtectionsCounter(eventReporting: toggleProtectionsCounterEventReporting)

reload(etag: fetchedETag, data: fetchedData)
}
Expand All @@ -126,13 +131,15 @@ public class PrivacyConfigurationManager: PrivacyConfigurationManaging {
identifier: fetchedData.etag,
localProtection: localProtection,
internalUserDecider: internalUserDecider,
toggleProtectionsCounter: toggleProtectionsCounter,
installDate: installDate)
}

return AppPrivacyConfiguration(data: embeddedConfigData.data,
identifier: embeddedConfigData.etag,
localProtection: localProtection,
internalUserDecider: internalUserDecider,
toggleProtectionsCounter: toggleProtectionsCounter,
installDate: installDate)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public protocol SuggestionLoading: AnyObject {

public class SuggestionLoader: SuggestionLoading {

static let remoteSuggestionsUrl = #URL("https://duckduckgo.com/ac/")
static let remoteSuggestionsUrl = URL(string: "https://duckduckgo.com/ac/")!
static let searchParameter = "q"

public enum SuggestionLoaderError: Error {
Expand Down
6 changes: 6 additions & 0 deletions Sources/Common/Extensions/ArrayExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,10 @@ extension Array where Element: Hashable {
return result
}

public func chunked(into size: Int) -> [[Element]] {
return stride(from: 0, to: count, by: size).map {
Array(self[$0 ..< Swift.min($0 + size, count)])
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23B92" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
<entity name="BrowsingHistoryEntryManagedObject" representedClassName="BrowsingHistoryEntryManagedObject" syncable="YES">
<attribute name="blockedTrackingEntities" optional="YES" attributeType="String"/>
<attribute name="failedToLoad" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="lastVisit" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="numberOfTotalVisits" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES" elementID="numberOfVisits"/>
<attribute name="numberOfTrackersBlocked" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="title" optional="YES" attributeType="String" valueTransformerName="NSStringTransformer"/>
<attribute name="trackersFound" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="url" attributeType="URI" valueTransformerName="NSURLTransformer"/>
<relationship name="visits" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="PageVisitManagedObject" inverseName="historyEntry" inverseEntity="PageVisitManagedObject"/>
</entity>
<entity name="PageVisitManagedObject" representedClassName="PageVisitManagedObject" syncable="YES">
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="historyEntry" maxCount="1" deletionRule="Nullify" destinationEntity="BrowsingHistoryEntryManagedObject" inverseName="visits" inverseEntity="BrowsingHistoryEntryManagedObject"/>
</entity>
</model>
Loading

0 comments on commit cd3e17d

Please sign in to comment.