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

Simplify connecting sync accounts when two exist #3680

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3141,6 +3141,13 @@
EEE11C5F2C7F54AD000ABD7E /* AutofillLoginImportState.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE11C5D2C7F54AD000ABD7E /* AutofillLoginImportState.swift */; };
EEE50C292C38249C003DD7FF /* OptionalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE50C282C38249C003DD7FF /* OptionalExtension.swift */; };
EEE50C2A2C38249C003DD7FF /* OptionalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE50C282C38249C003DD7FF /* OptionalExtension.swift */; };
EEEFA3402D142AB1006A3F8A /* CombineTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEEFA33F2D142AA6006A3F8A /* CombineTestHelpers.swift */; };
EEEFA3412D142AB1006A3F8A /* CombineTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEEFA33F2D142AA6006A3F8A /* CombineTestHelpers.swift */; };
EEEFA3422D142AB1006A3F8A /* CombineTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEEFA33F2D142AA6006A3F8A /* CombineTestHelpers.swift */; };
EEEFA3432D142AB1006A3F8A /* CombineTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEEFA33F2D142AA6006A3F8A /* CombineTestHelpers.swift */; };
EEEFA3442D142AB1006A3F8A /* CombineTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEEFA33F2D142AA6006A3F8A /* CombineTestHelpers.swift */; };
EEEFA3462D145328006A3F8A /* SyncPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEEFA3452D145322006A3F8A /* SyncPixels.swift */; };
EEEFA3472D145328006A3F8A /* SyncPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEEFA3452D145322006A3F8A /* SyncPixels.swift */; };
EEF12E6F2A2111880023E6BF /* MacPacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */; };
EEF53E182950CED5002D78F4 /* JSAlertViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */; };
F10C99422C7E20A1005568B4 /* Logger+DBPBackgroundAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17E7DDB2C7C7F8100907A84 /* Logger+DBPBackgroundAgent.swift */; };
Expand Down Expand Up @@ -4977,6 +4984,8 @@
EEE0E1CE2C32F6530058E148 /* mock_login_data_large.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = mock_login_data_large.csv; sourceTree = "<group>"; };
EEE11C5D2C7F54AD000ABD7E /* AutofillLoginImportState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginImportState.swift; sourceTree = "<group>"; };
EEE50C282C38249C003DD7FF /* OptionalExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionalExtension.swift; sourceTree = "<group>"; };
EEEFA33F2D142AA6006A3F8A /* CombineTestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineTestHelpers.swift; sourceTree = "<group>"; };
EEEFA3452D145322006A3F8A /* SyncPixels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPixels.swift; sourceTree = "<group>"; };
EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPacketTunnelProvider.swift; sourceTree = "<group>"; };
EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = "<group>"; };
F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5857,6 +5866,7 @@
3775913429AB99DA00E26367 /* Sync */ = {
isa = PBXGroup;
children = (
EEEFA3452D145322006A3F8A /* SyncPixels.swift */,
C1935A162C88F9AA001AD72D /* Promotion */,
566B73672BECBF4400FF1959 /* Utilities */,
377D801A2AB47FA1002AF251 /* SettingSyncHandlers */,
Expand Down Expand Up @@ -7602,6 +7612,7 @@
85F69B3A25EDE7F800978E59 /* Common */ = {
isa = PBXGroup;
children = (
EEEFA33F2D142AA6006A3F8A /* CombineTestHelpers.swift */,
4B723E1726B000DC00E14D75 /* TemporaryFileCreator.swift */,
4BBF09222830812900EE1418 /* FileSystemDSL.swift */,
4BBF0924283083EC00EE1418 /* FileSystemDSLTests.swift */,
Expand Down Expand Up @@ -11383,6 +11394,7 @@
56BA1E8B2BB1CB5B001CF69F /* CertificateTrustEvaluator.swift in Sources */,
B6E1491129A5C30A00AAFBE8 /* FBProtectionTabExtension.swift in Sources */,
1EC711502D033D200009EB5C /* UserText+NetworkProtection+Shared.swift in Sources */,
EEEFA3472D145328006A3F8A /* SyncPixels.swift in Sources */,
3706FAC4293F65D500E42796 /* PrintingUserScript.swift in Sources */,
1D01A3D92B88DF8B00FE8150 /* PreferencesSyncView.swift in Sources */,
9D9AE86C2AA76D1B0026E7DC /* LoginItemsManager.swift in Sources */,
Expand Down Expand Up @@ -12325,6 +12337,7 @@
3706FE13293F661700E42796 /* ConfigurationStorageTests.swift in Sources */,
3706FE15293F661700E42796 /* PrivacyIconViewModelTests.swift in Sources */,
56A0543C2C20878E007D8FAB /* DuckSchemeHandlerTests.swift in Sources */,
EEEFA3442D142AB1006A3F8A /* CombineTestHelpers.swift in Sources */,
370270C12C78EB13002E44E4 /* HomePageSettingsModelTests.swift in Sources */,
B68412212B6A30680092F66A /* StringExtensionTests.swift in Sources */,
1D8C2FEE2B70F5D0005E4BBD /* MockViewSnapshotRenderer.swift in Sources */,
Expand Down Expand Up @@ -12595,6 +12608,7 @@
B630E80229C887ED00363609 /* NSErrorAdditionalInfo.swift in Sources */,
3706FEA3293F662100E42796 /* CoreDataEncryptionTests.swift in Sources */,
B60C6F8929B1CAB7007BFAA8 /* TestRunHelperInitializer.m in Sources */,
EEEFA3422D142AB1006A3F8A /* CombineTestHelpers.swift in Sources */,
560C6ECE2CCA5BCB00D411E2 /* ContextualDaxDialogFactoryIntegrationTests.swift in Sources */,
560C6ED92CCA5CE700D411E2 /* FindInView.swift in Sources */,
B60C6F8B29B1CAC0007BFAA8 /* FileManagerTempDirReplacement.swift in Sources */,
Expand Down Expand Up @@ -12644,6 +12658,7 @@
1D8B7D6A2A38BF050045C6F6 /* FireproofDomainsStoreMock.swift in Sources */,
B630E7FF29C887ED00363609 /* NSErrorAdditionalInfo.swift in Sources */,
7BA4727D26F01BC400EAA165 /* CoreDataTestUtilities.swift in Sources */,
EEEFA3432D142AB1006A3F8A /* CombineTestHelpers.swift in Sources */,
560C6ECD2CCA5BCB00D411E2 /* ContextualDaxDialogFactoryIntegrationTests.swift in Sources */,
560C6ED82CCA5CE700D411E2 /* FindInView.swift in Sources */,
B60C6F8829B1CAB6007BFAA8 /* TestRunHelperInitializer.m in Sources */,
Expand Down Expand Up @@ -12897,6 +12912,7 @@
9D84E4492CD4EE780046CD8B /* TestNavigationDelegate.swift in Sources */,
9D84E4442CD4EE600046CD8B /* EncryptionKeyStoreMock.swift in Sources */,
9D0668C92CD4F04600D6C9EA /* FireproofDomainsStoreMock.swift in Sources */,
EEEFA3402D142AB1006A3F8A /* CombineTestHelpers.swift in Sources */,
9D84E44A2CD4EE7C0046CD8B /* TestRunHelperInitializer.m in Sources */,
9D0668CC2CD4F06900D6C9EA /* FindInView.swift in Sources */,
);
Expand Down Expand Up @@ -13737,6 +13753,7 @@
B69B503F2726A12500758A2B /* LocalStatisticsStore.swift in Sources */,
B689ECD526C247DB006FB0C5 /* BackForwardListItem.swift in Sources */,
B69B50572727D16900758A2B /* AtbAndVariantCleanup.swift in Sources */,
EEEFA3462D145328006A3F8A /* SyncPixels.swift in Sources */,
AA3D531527A1ED9300074EC1 /* FeedbackWindow.swift in Sources */,
B6685E3F29A606190043D2EE /* WorkspaceProtocol.swift in Sources */,
B6ABC5962B4861D4008343B9 /* FocusableTextField.swift in Sources */,
Expand Down Expand Up @@ -14127,6 +14144,7 @@
4BA1A6FE258C5C1300F6F690 /* EncryptedValueTransformerTests.swift in Sources */,
85F69B3C25EDE81F00978E59 /* URLExtensionTests.swift in Sources */,
4B9292BA2667103100AD2C21 /* BookmarkNodePathTests.swift in Sources */,
EEEFA3412D142AB1006A3F8A /* CombineTestHelpers.swift in Sources */,
562532A02BC069180034D316 /* ZoomPopoverViewModelTests.swift in Sources */,
4B9292C02667103100AD2C21 /* BookmarkManagedObjectTests.swift in Sources */,
373A1AB228451ED400586521 /* BookmarksHTMLImporterTests.swift in Sources */,
Expand Down
52 changes: 50 additions & 2 deletions DuckDuckGo/Preferences/Model/SyncPreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -577,8 +577,11 @@ extension SyncPreferences: ManagementDialogModelDelegate {
do {
try await loginAndShowPresentedDialog(recoveryKey, isRecovery: fromRecoveryScreen)
} catch {
managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToMergeTwoAccounts, description: "")
PixelKit.fire(DebugEvent(GeneralPixel.syncLoginExistingAccountError(error: error)))
if case SyncError.accountAlreadyExists = error {
handleAccountAlreadyExists(recoveryKey)
} else {
managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToSyncToOtherDevice)
}
}
} else if let connectKey = syncCode.connect {
do {
Expand Down Expand Up @@ -755,4 +758,49 @@ extension SyncPreferences: ManagementDialogModelDelegate {
func recoveryCodePasted(_ code: String, fromRecoveryScreen: Bool) {
recoverDevice(recoveryCode: code, fromRecoveryScreen: fromRecoveryScreen)
}

private func handleAccountAlreadyExists(_ recoveryKey: SyncCode.RecoveryKey) {
if devices.count > 1 {
managementDialogModel.shouldShowSwitchAccountsMessage = true
PixelKit.fire(SyncSwitchAccountPixelKitEvent.syncAskUserToSwitchAccount.withoutMacPrefix)
} else {
Task { @MainActor in
await switchAccounts(recoveryKey: recoveryKey)
managementDialogModel.endFlow()
}
}
PixelKit.fire(DebugEvent(GeneralPixel.syncLoginExistingAccountError(error: SyncError.accountAlreadyExists)))
}

func userConfirmedSwitchAccounts(recoveryCode: String) {
PixelKit.fire(SyncSwitchAccountPixelKitEvent.syncUserAcceptedSwitchingAccount.withoutMacPrefix)
guard let recoveryKey = try? SyncCode.decodeBase64String(recoveryCode).recovery else {
return
}
Task {
await switchAccounts(recoveryKey: recoveryKey)
managementDialogModel.endFlow()
}
}

private func switchAccounts(recoveryKey: SyncCode.RecoveryKey) async {
do {
try await syncService.disconnect()
} catch {
PixelKit.fire(SyncSwitchAccountPixelKitEvent.syncUserSwitchedLogoutError.withoutMacPrefix)
}

do {
let device = deviceInfo()
let registeredDevices = try await syncService.login(recoveryKey, deviceName: device.name, deviceType: device.type)
await mapDevices(registeredDevices)
} catch {
PixelKit.fire(SyncSwitchAccountPixelKitEvent.syncUserSwitchedLoginError.withoutMacPrefix)
}
PixelKit.fire(SyncSwitchAccountPixelKitEvent.syncUserSwitchedAccount.withoutMacPrefix)
}

func switchAccountsCancelled() {
PixelKit.fire(SyncSwitchAccountPixelKitEvent.syncUserCancelledSwitchingAccount.withoutMacPrefix)
}
}
51 changes: 51 additions & 0 deletions DuckDuckGo/Sync/SyncPixels.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// SyncPixels.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 PixelKit

enum SyncSwitchAccountPixelKitEvent: PixelKitEventV2 {
case syncAskUserToSwitchAccount
case syncUserAcceptedSwitchingAccount
case syncUserCancelledSwitchingAccount
case syncUserSwitchedAccount
case syncUserSwitchedLogoutError
case syncUserSwitchedLoginError

var name: String {
switch self {
case .syncAskUserToSwitchAccount: return "sync_ask_user_to_switch_account"
case .syncUserAcceptedSwitchingAccount: return "sync_user_accepted_switching_account"
case .syncUserCancelledSwitchingAccount: return "sync_user_cancelled_switching_account"
case .syncUserSwitchedAccount: return "sync_user_switched_account"
case .syncUserSwitchedLogoutError: return "sync_user_switched_logout_error"
case .syncUserSwitchedLoginError: return "sync_user_switched_login_error"
}
}

var parameters: [String: String]? {
nil
}

var error: (any Error)? {
nil
}

var withoutMacPrefix: NonStandardEvent {
NonStandardEvent(self)
}
}
180 changes: 180 additions & 0 deletions LocalPackages/SyncUI/Sources/SyncUI/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,186 @@
}
}
},
"alert.sync-switch-account-button" : {
"comment" : "Switch account title in alert",
"extractionState" : "extracted_with_value",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Zu einer anderen Synchronisierung wechseln?"
}
},
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Switch to a different Sync?"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "¿Cambiar a una sincronización diferente?"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Passer à une autre synchronisation ?"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Vuoi passare a una sincronizzazione diversa?"
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Overschakelen naar een andere synchronisatie?"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Przełączyć na inną synchronizację?"
}
},
"pt" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mudar para uma sincronização diferente?"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Переключиться на другую синхронизацию?"
}
}
}
},
"alert.sync-switch-account-message" : {
"comment" : "Description for switching sync accounts when there's two",
"extractionState" : "extracted_with_value",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Dieses Gerät ist bereits synchronisiert. Bist du sicher, dass du es mit einem anderen Back-up oder Gerät synchronisieren möchtest? Beim Wechsel werden keine bereits mit diesem Gerät synchronisierten Daten entfernt."
}
},
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "This device is already synced, are you sure you want to sync it with a different backup or device? Switching won't remove any data already synced to this device."
}
},
"es" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Este dispositivo ya está sincronizado. ¿Seguro que deseas sincronizarlo con una copia de seguridad o con un dispositivo diferente? Cambiar no eliminará ningún dato ya sincronizado en este dispositivo."
}
},
"fr" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Cet appareil est déjà synchronisé. Voulez-vous vraiment le synchroniser avec une autre sauvegarde ou un autre appareil ? Ce changement ne supprimera aucune donnée déjà synchronisée sur cet appareil."
}
},
"it" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Questo dispositivo è già sincronizzato. Vuoi davvero sincronizzarlo con un backup o un dispositivo diverso? L'operazione non rimuoverà alcun dato già sincronizzato su questo dispositivo."
}
},
"nl" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Dit apparaat is al gesynchroniseerd, weet je zeker dat je het met een andere back-up of een ander apparaat wilt synchroniseren? Als je overschakelt, worden er geen gegevens verwijderd die al met dit apparaat zijn gesynchroniseerd."
}
},
"pl" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "To urządzenie jest już synchronizowane, czy na pewno chcesz je synchronizować z inną kopią zapasową lub urządzeniem? Przełączenie nie usunie żadnych danych już zsynchronizowanych z tym urządzeniem."
}
},
"pt" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Este dispositivo já está sincronizado, tens a certeza de que queres sincronizá-lo com uma cópia de segurança ou um dispositivo diferente? A mudança não vai remover nenhum dado já sincronizado com este dispositivo."
}
},
"ru" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Это устройство уже синхронизировано. Действительно синхронизировать его с другой резервной копией или устройством? Переключение не приведет к удалению данных, синхронизированных с этим устройством ранее."
}
}
}
},
"alert.sync-switch-sync-button" : {
"comment" : "Switch account button in alert",
"extractionState" : "extracted_with_value",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Synchronisierung wechseln"
}
},
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Switch Sync"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cambiar sincronización"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Changer de synchronisation"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cambia sincronizzazione"
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Schakelen tussen synchronisatie"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Przełącz synchronizację"
}
},
"pt" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mudar sincronização"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Переключить синхронизацию"
}
}
}
},
"alert.unable-to-authenticate-device" : {
"comment" : "Description for unable to authenticate error",
"extractionState" : "extracted_with_value",
Expand Down
Loading
Loading