Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync form factor specific favorites #2029

Merged
merged 57 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
1a82668
[WIP] Support for form factor specific favorites
ayoy Sep 8, 2023
3522cc7
Add favorites display mode switcher to settings
ayoy Sep 10, 2023
8cd0f21
Update BSK ref
ayoy Sep 10, 2023
997e861
Reload Home view collection view when favorites display mode changes
ayoy Sep 10, 2023
bc9d688
Apply most recent designs to favorites display mode settings screen
ayoy Sep 11, 2023
e00562c
Implement migration to form-factor-specific favorites
ayoy Sep 11, 2023
0ee52ce
Reload widgets on update to favorites display mode
ayoy Sep 11, 2023
a3420f6
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Sep 14, 2023
698d23a
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Sep 14, 2023
557cf9a
Add Sync options to Sync view
ayoy Sep 14, 2023
8586145
Revert changes to Settings.storyboard
ayoy Sep 14, 2023
754fe12
Revert changes to SettingsViewController
ayoy Sep 14, 2023
a1f1f33
Add FavoritesDisplayModeSyncHandler
ayoy Sep 15, 2023
c6ee017
Update BSK ref
ayoy Sep 15, 2023
52d6d69
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Sep 18, 2023
30815c8
Update BSK ref
ayoy Sep 18, 2023
21be09d
Fix unit tests to compile and pass
ayoy Sep 19, 2023
d9288ca
Update BSK ref
ayoy Sep 20, 2023
baafc69
Implement handling favorites after disabling Sync
ayoy Sep 20, 2023
2f55c18
Make app responsive to favorites display mode changes
ayoy Sep 20, 2023
8f58d3b
Properly share favorites display mode setting with the Widget
ayoy Sep 20, 2023
a701352
Update BSK ref
ayoy Sep 21, 2023
0da5324
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Sep 21, 2023
9297e19
Update BSK ref
ayoy Sep 21, 2023
f5fafe4
Update BSK ref
ayoy Sep 21, 2023
e70d33e
Fix tests compilation
ayoy Sep 21, 2023
3e123f5
Remove unnecessary changes
ayoy Sep 21, 2023
b331786
Address review feedback, part 1
ayoy Sep 21, 2023
d9b7b8b
Use FavoritesDisplayModeSyncHandlerBase
ayoy Sep 21, 2023
46c2b80
Use prepareLegacyFoldersStructure for legacy migration
ayoy Sep 21, 2023
160e49e
Initialize EmailProtectionSyncHandler in SyncSettingsAdapter
ayoy Sep 21, 2023
3581ee0
Fire pixel before crashing when migration to FFS favorites fails'
ayoy Sep 21, 2023
e6caba6
Handle favorites after disabling sync as part of 'cleaning up databas…
ayoy Sep 21, 2023
380cc92
Parametrize BookmarksExporter with favorites display mode
ayoy Sep 21, 2023
297a170
Parametrize AddOrEditBookmarkViewController with appSettings
ayoy Sep 21, 2023
451d815
Use favorites display mode in BookmarkFoldersTableViewController
ayoy Sep 21, 2023
da018f4
Parametrize appSettings in more places
ayoy Sep 21, 2023
26db43b
Use favoritesDisplayMode in BookmarksDataSource
ayoy Sep 21, 2023
8cd0a4e
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Sep 21, 2023
38f7738
Update bookmark delete undo for FFS favorites
ayoy Sep 21, 2023
253cadc
Prepare legacy folders structure also in another place in LegacyBookm…
ayoy Sep 21, 2023
eebdd48
Update BSK ref
ayoy Sep 23, 2023
4d3c3d6
Refresh bookmarks views on display mode change
bwaresiak Sep 26, 2023
77cb4c0
new sync flow (#2071)
SabrinaTardio Oct 11, 2023
afa6102
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Oct 11, 2023
1c278a5
Update BSK ref
ayoy Oct 11, 2023
9f2fb8a
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Oct 13, 2023
bb2834c
Update BSK ref
ayoy Oct 13, 2023
dc23f73
Sync limit exceeded (#2105)
SabrinaTardio Oct 20, 2023
707b6c6
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Oct 29, 2023
dcd4704
Reset bookmarks sync timestamp after migrating to FFS favorites (#2091)
ayoy Nov 3, 2023
efbb57b
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Nov 7, 2023
f054600
Sync paused alert (#2129)
SabrinaTardio Nov 10, 2023
cda7497
Update BSK ref
ayoy Nov 10, 2023
cbe2ed9
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Nov 10, 2023
63d855c
Fix compilation for when APPTP is disabled
ayoy Nov 10, 2023
4a5dded
Set BSK version to 83.0.0
ayoy Nov 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Core/BookmarksCachingSearch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ public class CoreDataBookmarksSearchStore: BookmarksSearchStore {
fetchRequest.resultType = .dictionaryResultType
fetchRequest.propertiesToFetch = [#keyPath(BookmarkEntity.title),
#keyPath(BookmarkEntity.url),
#keyPath(BookmarkEntity.favoriteFolder),
#keyPath(BookmarkEntity.objectID)]
fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(BookmarkEntity.favoriteFolders)]

context.perform {
let result = try? context.fetch(fetchRequest) as? [Dictionary<String, Any>]
Expand Down Expand Up @@ -131,7 +131,7 @@ public class BookmarksCachingSearch: BookmarksStringSearch {
self.init(objectID: objectID,
title: title,
url: url,
isFavorite: bookmark[#keyPath(BookmarkEntity.favoriteFolder)] != nil)
isFavorite: (bookmark[#keyPath(BookmarkEntity.favoriteFolders)] as? Set<NSManagedObject>)?.isEmpty != true)
}

public func togglingFavorite() -> BookmarksStringSearchResult {
Expand Down
6 changes: 4 additions & 2 deletions Core/BookmarksExporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ public enum BookmarksExporterError: Error {
public struct BookmarksExporter {

private(set) var coreDataStorage: CoreDataDatabase
private let favoritesDisplayMode: FavoritesDisplayMode

public init(coreDataStore: CoreDataDatabase) {
public init(coreDataStore: CoreDataDatabase, favoritesDisplayMode: FavoritesDisplayMode) {
coreDataStorage = coreDataStore
self.favoritesDisplayMode = favoritesDisplayMode
}

public func exportBookmarksTo(url: URL) throws {
Expand Down Expand Up @@ -64,7 +66,7 @@ public struct BookmarksExporter {
content.append(Template.bookmark(level: level,
title: entity.title!.escapedForHTML,
url: entity.url!,
isFavorite: entity.isFavorite))
isFavorite: entity.isFavorite(on: favoritesDisplayMode.displayedFolder)))
}
}
return content
Expand Down
4 changes: 2 additions & 2 deletions Core/BookmarksImporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ final public class BookmarksImporter {
private(set) var importedBookmarks: [BookmarkOrFolder] = []
private(set) var coreDataStorage: BookmarkCoreDataImporter

public init(coreDataStore: CoreDataDatabase) {
coreDataStorage = BookmarkCoreDataImporter(database: coreDataStore)
public init(coreDataStore: CoreDataDatabase, favoritesDisplayMode: FavoritesDisplayMode) {
coreDataStorage = BookmarkCoreDataImporter(database: coreDataStore, favoritesDisplayMode: favoritesDisplayMode)
}

func isDocumentInSafariFormat(_ document: Document) -> Bool {
Expand Down
14 changes: 9 additions & 5 deletions Core/BookmarksModelsErrorHandling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,22 @@ public extension BookmarkEditorViewModel {

convenience init(editingEntityID: NSManagedObjectID,
bookmarksDatabase: CoreDataDatabase,
favoritesDisplayMode: FavoritesDisplayMode,
syncService: DDGSyncing?) {
self.init(editingEntityID: editingEntityID,
bookmarksDatabase: bookmarksDatabase,
favoritesDisplayMode: favoritesDisplayMode,
errorEvents: BookmarksModelsErrorHandling(syncService: syncService))

}

convenience init(creatingFolderWithParentID parentFolderID: NSManagedObjectID?,
bookmarksDatabase: CoreDataDatabase,
favoritesDisplayMode: FavoritesDisplayMode,
syncService: DDGSyncing?) {
self.init(creatingFolderWithParentID: parentFolderID,
bookmarksDatabase: bookmarksDatabase,
favoritesDisplayMode: favoritesDisplayMode,
errorEvents: BookmarksModelsErrorHandling(syncService: syncService))
}
}
Expand All @@ -102,25 +106,25 @@ public extension BookmarkListViewModel {

convenience init(bookmarksDatabase: CoreDataDatabase,
parentID: NSManagedObjectID?,
favoritesDisplayMode: FavoritesDisplayMode,
syncService: DDGSyncing?) {
self.init(bookmarksDatabase: bookmarksDatabase,
parentID: parentID,
favoritesDisplayMode: favoritesDisplayMode,
errorEvents: BookmarksModelsErrorHandling(syncService: syncService))
}
}

public extension FavoritesListViewModel {

convenience init(bookmarksDatabase: CoreDataDatabase) {
self.init(bookmarksDatabase: bookmarksDatabase,
errorEvents: BookmarksModelsErrorHandling())
convenience init(bookmarksDatabase: CoreDataDatabase, favoritesDisplayMode: FavoritesDisplayMode) {
self.init(bookmarksDatabase: bookmarksDatabase, errorEvents: BookmarksModelsErrorHandling(), favoritesDisplayMode: favoritesDisplayMode)
}
}

public extension MenuBookmarksViewModel {

convenience init(bookmarksDatabase: CoreDataDatabase, syncService: DDGSyncing?) {
self.init(bookmarksDatabase: bookmarksDatabase,
errorEvents: BookmarksModelsErrorHandling(syncService: syncService))
self.init(bookmarksDatabase: bookmarksDatabase, errorEvents: BookmarksModelsErrorHandling(syncService: syncService))
}
}
9 changes: 6 additions & 3 deletions Core/LegacyBookmarksStoreMigration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class LegacyBookmarksStoreMigration {
}
} else {
// Initialize structure if needed
BookmarkUtils.prepareFoldersStructure(in: context)
BookmarkUtils.prepareLegacyFoldersStructure(in: context)
if context.hasChanges {
do {
try context.save(onErrorFire: .bookmarksCouldNotPrepareDatabase)
Expand Down Expand Up @@ -82,7 +82,8 @@ public class LegacyBookmarksStoreMigration {
BookmarkUtils.prepareFoldersStructure(in: destination)

guard let newRoot = BookmarkUtils.fetchRootFolder(destination),
let newFavoritesRoot = BookmarkUtils.fetchFavoritesFolder(destination) else {
let newFavoritesRoot = BookmarkUtils.fetchFavoritesFolder(withUUID: FavoritesFolderID.unified.rawValue, in: destination),
let newMobileFavoritesRoot = BookmarkUtils.fetchFavoritesFolder(withUUID: FavoritesFolderID.mobile.rawValue, in: destination) else {
Pixel.fire(pixel: .bookmarksMigrationCouldNotPrepareDatabase)
Thread.sleep(forTimeInterval: 2)
fatalError("Could not write to Bookmarks DB")
Expand Down Expand Up @@ -169,14 +170,16 @@ public class LegacyBookmarksStoreMigration {
}()
bookmark.addToFavorites(insertAt: 0,
favoritesRoot: newFavoritesRoot)
bookmark.addToFavorites(insertAt: 0,
favoritesRoot: newMobileFavoritesRoot)
}

do {
try destination.save(onErrorFire: .bookmarksMigrationFailed)
} catch {
destination.reset()

BookmarkUtils.prepareFoldersStructure(in: destination)
BookmarkUtils.prepareLegacyFoldersStructure(in: destination)
do {
try destination.save(onErrorFire: .bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration)
} catch {
Expand Down
12 changes: 12 additions & 0 deletions Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -473,10 +473,15 @@ extension Pixel {
case bookmarksMigrationCouldNotPrepareDatabaseOnFailedMigration
case bookmarksMigrationCouldNotValidateDatabase
case bookmarksMigrationCouldNotRemoveOldStore
case bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders

case syncFailedToMigrate
case syncFailedToLoadAccount
case syncFailedToSetupEngine
case syncBookmarksCountLimitExceededDaily
case syncCredentialsCountLimitExceededDaily
case syncBookmarksRequestSizeLimitExceededDaily
case syncCredentialsRequestSizeLimitExceededDaily

case syncSentUnauthenticatedRequest
case syncMetadataCouldNotLoadDatabase
Expand All @@ -489,6 +494,7 @@ extension Pixel {

case bookmarksCleanupFailed
case bookmarksCleanupAttemptedWhileSyncWasEnabled
case favoritesCleanupFailed

case credentialsDatabaseCleanupFailed
case credentialsCleanupAttemptedWhileSyncWasEnabled
Expand Down Expand Up @@ -951,10 +957,15 @@ extension Pixel.Event {
return "m_d_bookmarks_migration_could_not_prepare_database_on_failed_migration"
case .bookmarksMigrationCouldNotValidateDatabase: return "m_d_bookmarks_migration_could_not_validate_database"
case .bookmarksMigrationCouldNotRemoveOldStore: return "m_d_bookmarks_migration_could_not_remove_old_store"
case .bookmarksMigrationCouldNotPrepareMultipleFavoriteFolders: return "m_d_bookmarks_migration_could_not_prepare_multiple_favorite_folders"

case .syncFailedToMigrate: return "m_d_sync_failed_to_migrate"
case .syncFailedToLoadAccount: return "m_d_sync_failed_to_load_account"
case .syncFailedToSetupEngine: return "m_d_sync_failed_to_setup_engine"
case .syncBookmarksCountLimitExceededDaily: return "m_d_sync_bookmarks_count_limit_exceeded_daily"
case .syncCredentialsCountLimitExceededDaily: return "m_d_sync_credentials_count_limit_exceeded_daily"
case .syncBookmarksRequestSizeLimitExceededDaily: return "m_d_sync_bookmarks_request_size_limit_exceeded_daily"
case .syncCredentialsRequestSizeLimitExceededDaily: return "m_d_sync_credentials_request_size_limit_exceeded_daily"

case .syncSentUnauthenticatedRequest: return "m_d_sync_sent_unauthenticated_request"
case .syncMetadataCouldNotLoadDatabase: return "m_d_sync_metadata_could_not_load_database"
Expand All @@ -968,6 +979,7 @@ extension Pixel.Event {

case .bookmarksCleanupFailed: return "m_d_bookmarks_cleanup_failed"
case .bookmarksCleanupAttemptedWhileSyncWasEnabled: return "m_d_bookmarks_cleanup_attempted_while_sync_was_enabled"
case .favoritesCleanupFailed: return "m_d_favorites_cleanup_failed"

case .credentialsDatabaseCleanupFailed: return "m_d_credentials_database_cleanup_failed_2"
case .credentialsCleanupAttemptedWhileSyncWasEnabled: return "m_d_credentials_cleanup_attempted_while_sync_was_enabled"
Expand Down
76 changes: 75 additions & 1 deletion Core/SyncBookmarksAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,38 @@ import Persistence
import SyncDataProviders
import WidgetKit

public protocol FavoritesDisplayModeStoring: AnyObject {
var favoritesDisplayMode: FavoritesDisplayMode { get set }
}

public final class SyncBookmarksAdapter {

public private(set) var provider: BookmarksProvider?
public let databaseCleaner: BookmarkDatabaseCleaner
public let syncDidCompletePublisher: AnyPublisher<Void, Never>
public let widgetRefreshCancellable: AnyCancellable
public static let syncBookmarksPausedStateChanged = Notification.Name("com.duckduckgo.app.SyncPausedStateChanged")
public static let bookmarksSyncLimitReached = Notification.Name("com.duckduckgo.app.SyncBookmarksLimitReached")

public var shouldResetBookmarksSyncTimestamp: Bool = false {
willSet {
assert(provider == nil, "Setting this value has no effect after provider has been instantiated")
}
}

public init(database: CoreDataDatabase) {
@UserDefaultsWrapper(key: .syncBookmarksPaused, defaultValue: false)
static public var isSyncBookmarksPaused: Bool {
didSet {
NotificationCenter.default.post(name: syncBookmarksPausedStateChanged, object: nil)
}
}

@UserDefaultsWrapper(key: .syncBookmarksPausedErrorDisplayed, defaultValue: false)
static private var didShowBookmarksSyncPausedError: Bool

public init(database: CoreDataDatabase, favoritesDisplayModeStorage: FavoritesDisplayModeStoring) {
self.database = database
self.favoritesDisplayModeStorage = favoritesDisplayModeStorage
syncDidCompletePublisher = syncDidCompleteSubject.eraseToAnyPublisher()
databaseCleaner = BookmarkDatabaseCleaner(
bookmarkDatabase: database,
Expand All @@ -49,6 +73,7 @@ public final class SyncBookmarksAdapter {
databaseCleaner.cleanUpDatabaseNow()
if shouldEnable {
databaseCleaner.scheduleRegularCleaning()
handleFavoritesAfterDisablingSync()
} else {
databaseCleaner.cancelCleaningSchedule()
}
Expand All @@ -64,14 +89,33 @@ public final class SyncBookmarksAdapter {
metadataStore: metadataStore,
syncDidUpdateData: { [syncDidCompleteSubject] in
syncDidCompleteSubject.send()
Self.isSyncBookmarksPaused = false
Self.didShowBookmarksSyncPausedError = false
}
)
if shouldResetBookmarksSyncTimestamp {
provider.lastSyncTimestamp = nil
}

syncErrorCancellable = provider.syncErrorPublisher
.sink { error in
switch error {
case let syncError as SyncError:
Pixel.fire(pixel: .syncBookmarksFailed, error: syncError)
switch syncError {
case .unexpectedStatusCode(409):
// If bookmarks count limit has been exceeded
Self.isSyncBookmarksPaused = true
DailyPixel.fire(pixel: .syncBookmarksCountLimitExceededDaily)
Self.notifyBookmarksSyncLimitReached()
case .unexpectedStatusCode(413):
// If bookmarks request size limit has been exceeded
Self.isSyncBookmarksPaused = true
DailyPixel.fire(pixel: .syncBookmarksRequestSizeLimitExceededDaily)
Self.notifyBookmarksSyncLimitReached()
default:
break
}
default:
let nsError = error as NSError
if nsError.domain != NSURLErrorDomain {
Expand All @@ -86,6 +130,36 @@ public final class SyncBookmarksAdapter {
self.provider = provider
}

static private func notifyBookmarksSyncLimitReached() {
if !Self.didShowBookmarksSyncPausedError {
NotificationCenter.default.post(name: Self.bookmarksSyncLimitReached, object: nil)
Self.didShowBookmarksSyncPausedError = true
}
}

private func handleFavoritesAfterDisablingSync() {
let context = database.makeContext(concurrencyType: .privateQueueConcurrencyType)

context.performAndWait {
do {
if favoritesDisplayModeStorage.favoritesDisplayMode.isDisplayUnified {
BookmarkUtils.copyFavorites(from: .unified, to: .mobile, clearingNonNativeFavoritesFolder: .desktop, in: context)
favoritesDisplayModeStorage.favoritesDisplayMode = .displayNative(.mobile)
} else {
BookmarkUtils.copyFavorites(from: .mobile, to: .unified, clearingNonNativeFavoritesFolder: .desktop, in: context)
}
try context.save()
} catch {
let nsError = error as NSError
let processedErrors = CoreDataErrorsParser.parse(error: nsError)
let params = processedErrors.errorPixelParameters
Pixel.fire(pixel: .favoritesCleanupFailed, error: error, withAdditionalParameters: params)
}
}
}

private var syncDidCompleteSubject = PassthroughSubject<Void, Never>()
private var syncErrorCancellable: AnyCancellable?
private let database: CoreDataDatabase
private let favoritesDisplayModeStorage: FavoritesDisplayModeStoring
}
35 changes: 35 additions & 0 deletions Core/SyncCredentialsAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ public final class SyncCredentialsAdapter {
public private(set) var provider: CredentialsProvider?
public let databaseCleaner: CredentialsDatabaseCleaner
public let syncDidCompletePublisher: AnyPublisher<Void, Never>
public static let syncCredentialsPausedStateChanged = SyncBookmarksAdapter.syncBookmarksPausedStateChanged
public static let credentialsSyncLimitReached = Notification.Name("com.duckduckgo.app.SyncCredentialsLimitReached")

@UserDefaultsWrapper(key: .syncCredentialsPaused, defaultValue: false)
static public var isSyncCredentialsPaused: Bool {
didSet {
NotificationCenter.default.post(name: syncCredentialsPausedStateChanged, object: nil)
}
}
@UserDefaultsWrapper(key: .syncCredentialsPausedErrorDisplayed, defaultValue: false)
static private var didShowCredentialsSyncPausedError: Bool

public init(secureVaultFactory: AutofillVaultFactory = AutofillSecureVaultFactory, secureVaultErrorReporter: SecureVaultErrorReporting) {
syncDidCompletePublisher = syncDidCompleteSubject.eraseToAnyPublisher()
Expand Down Expand Up @@ -63,6 +74,8 @@ public final class SyncCredentialsAdapter {
metadataStore: metadataStore,
syncDidUpdateData: { [weak self] in
self?.syncDidCompleteSubject.send()
Self.isSyncCredentialsPaused = false
Self.didShowCredentialsSyncPausedError = false
}
)

Expand All @@ -71,6 +84,21 @@ public final class SyncCredentialsAdapter {
switch error {
case let syncError as SyncError:
Pixel.fire(pixel: .syncCredentialsFailed, error: syncError)

switch syncError {
case .unexpectedStatusCode(409):
// If credentials count limit has been exceeded
Self.isSyncCredentialsPaused = true
DailyPixel.fire(pixel: .syncCredentialsCountLimitExceededDaily)
Self.notifyCredentialsSyncLimitReached()
case .unexpectedStatusCode(413):
// If credentials request size limit has been exceeded
Self.isSyncCredentialsPaused = true
DailyPixel.fire(pixel: .syncCredentialsRequestSizeLimitExceededDaily)
Self.notifyCredentialsSyncLimitReached()
default:
break
}
default:
let nsError = error as NSError
if nsError.domain != NSURLErrorDomain {
Expand All @@ -91,6 +119,13 @@ public final class SyncCredentialsAdapter {
}
}

static private func notifyCredentialsSyncLimitReached() {
if !Self.didShowCredentialsSyncPausedError {
NotificationCenter.default.post(name: Self.credentialsSyncLimitReached, object: nil)
Self.didShowCredentialsSyncPausedError = true
}
}

private var syncDidCompleteSubject = PassthroughSubject<Void, Never>()
private var syncErrorCancellable: AnyCancellable?
private let secureVaultErrorReporter: SecureVaultErrorReporting
Expand Down
Loading