Skip to content

Commit ee2865a

Browse files
committed
Code tidy from review
1 parent b50b974 commit ee2865a

File tree

4 files changed

+230
-73
lines changed

4 files changed

+230
-73
lines changed

sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncAccountRepository.kt

Lines changed: 62 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import com.duckduckgo.sync.impl.API_CODE.NOT_FOUND
2929
import com.duckduckgo.sync.impl.AccountErrorCodes.ALREADY_SIGNED_IN
3030
import com.duckduckgo.sync.impl.AccountErrorCodes.CONNECT_FAILED
3131
import com.duckduckgo.sync.impl.AccountErrorCodes.CREATE_ACCOUNT_FAILED
32+
import com.duckduckgo.sync.impl.AccountErrorCodes.EXCHANGE_FAILED
3233
import com.duckduckgo.sync.impl.AccountErrorCodes.GENERIC_ERROR
3334
import com.duckduckgo.sync.impl.AccountErrorCodes.INVALID_CODE
3435
import com.duckduckgo.sync.impl.AccountErrorCodes.LOGIN_FAILED
@@ -87,14 +88,14 @@ class AppSyncAccountRepository @Inject constructor(
8788
) : SyncAccountRepository {
8889

8990
/**
90-
* If there is a key-exchange invitation in progress, we need to keep a reference to them
91-
* There are separate invitation details for the inviter and the receiver
91+
* If there is a key-exchange flow in progress, we need to keep a reference to them
92+
* There are separate device details for the inviter and the receiver
9293
*
9394
* Inviter is reset every time a new exchange invitation code is created.
9495
* Receiver is reset every time an exchange invitation is received.
9596
*/
96-
private var pendingInvitationAsInviter: PendingInvitation? = null
97-
private var pendingInvitationAsReceiver: PendingInvitation? = null
97+
private var exchangeDeviceDetailsAsInviter: DeviceDetailsForKeyExchange? = null
98+
private var exchangeDeviceDetailsAsReceiver: DeviceDetailsForKeyExchange? = null
9899

99100
private val connectedDevicesCached: MutableList<ConnectedDevice> = mutableListOf()
100101

@@ -144,7 +145,7 @@ class AppSyncAccountRepository @Inject constructor(
144145
return@let null
145146
}
146147

147-
return completeExchange(it)
148+
return onInvitationCodeReceived(it)
148149
}
149150

150151
Timber.e("Sync: code is not supported")
@@ -165,50 +166,39 @@ class AppSyncAccountRepository @Inject constructor(
165166
}.getOrDefault(UNKNOWN)
166167
}
167168

168-
private fun completeExchange(invitationCode: InvitationCode): Result<Boolean> {
169+
private fun onInvitationCodeReceived(invitationCode: InvitationCode): Result<Boolean> {
169170
// Sync: InviteFlow - B (https://app.asana.com/0/72649045549333/1209571867429615)
170171
Timber.d("Sync-exchange: InviteFlow - B. code is an exchange code $invitationCode")
171172

172-
// generate new ID and and public/private key-pair for receiving device
173-
val thisDeviceKeyId = deviceKeyGenerator.generate()
174-
val thisDeviceKeyPair = nativeLib.prepareForConnect()
175-
176-
pendingInvitationAsReceiver = PendingInvitation(
177-
keyId = thisDeviceKeyId,
178-
privateKey = thisDeviceKeyPair.secretKey,
179-
publicKey = thisDeviceKeyPair.publicKey,
180-
)
181-
val deviceName = syncDeviceIds.deviceName()
182-
183-
Timber.i(
184-
"Sync: details for this (receiver) device:" +
185-
"\n\tkey ID is $thisDeviceKeyId" +
186-
"\n\tpublic key is ${thisDeviceKeyPair.publicKey}" +
187-
"\n\tdevice name is $deviceName",
188-
)
173+
// generate new ID and public/private key-pair for receiving device
174+
val deviceDetailsAsReceiver = kotlin.runCatching {
175+
generateReceiverDeviceDetails()
176+
}.getOrElse {
177+
return Error(code = EXCHANGE_FAILED.code, reason = "Error generating receiver key-pair").alsoFireAccountErrorPixel()
178+
}
189179

190180
val invitedDeviceDetails = InvitedDeviceDetails(
191-
keyId = thisDeviceKeyId,
192-
publicKey = thisDeviceKeyPair.publicKey,
193-
deviceName = deviceName,
181+
keyId = deviceDetailsAsReceiver.keyId,
182+
publicKey = deviceDetailsAsReceiver.publicKey,
183+
deviceName = syncDeviceIds.deviceName(),
194184
)
195185

196-
kotlin.runCatching {
186+
val encryptedPayload = kotlin.runCatching {
197187
val payload = Adapters.invitedDeviceAdapter.toJson(invitedDeviceDetails)
198-
val encrypted = nativeLib.seal(payload, invitationCode.publicKey)
199-
return syncApi.sendEncryptedMessage(invitationCode.keyId, encrypted)
188+
nativeLib.seal(payload, invitationCode.publicKey)
200189
}.getOrElse { throwable ->
201190
throwable.asErrorResult().alsoFireAccountErrorPixel()
202-
return Error(code = GENERIC_ERROR.code, reason = "Exchange: Error encrypting payload")
191+
return Error(code = EXCHANGE_FAILED.code, reason = "Exchange: Error encrypting payload")
203192
}
193+
return syncApi.sendEncryptedMessage(invitationCode.keyId, encryptedPayload)
204194
}
205195

206196
override fun pollForRecoveryCodeAndLogin(): Result<ExchangeResult> {
207197
// Sync: InviteFlow - E (https://app.asana.com/0/72649045549333/1209571867429615)
208198
Timber.d("Sync-exchange: InviteFlow - E")
209199

210-
val pendingInvite = pendingInvitationAsReceiver
211-
?: return Error(code = CONNECT_FAILED.code, reason = "Connect: No pending invite initialized").also {
200+
val pendingInvite = exchangeDeviceDetailsAsReceiver
201+
?: return Error(code = EXCHANGE_FAILED.code, reason = "Exchange: No pending invite initialized").also {
212202
Timber.w("Sync-exchange: no pending invite initialized")
213203
}
214204

@@ -221,13 +211,13 @@ class AppSyncAccountRepository @Inject constructor(
221211
}
222212
GONE.code -> {
223213
Timber.w("Sync-exchange: keys expired: ${result.reason}")
224-
return Error(code = CONNECT_FAILED.code, reason = "Connect: keys expired").alsoFireAccountErrorPixel()
214+
return Error(code = EXCHANGE_FAILED.code, reason = "Exchange: keys expired").alsoFireAccountErrorPixel()
225215
}
226216
else -> {
227217
Timber.e("Sync-exchange: error getting encrypted recovery code: ${result.reason}")
218+
result.alsoFireAccountErrorPixel()
228219
}
229220
}
230-
result.alsoFireAccountErrorPixel()
231221
}
232222

233223
is Success -> {
@@ -236,12 +226,12 @@ class AppSyncAccountRepository @Inject constructor(
236226
val decryptedJson = kotlin.runCatching {
237227
nativeLib.sealOpen(result.data, pendingInvite.publicKey, pendingInvite.privateKey)
238228
}.getOrNull()
239-
?: return Error(code = CONNECT_FAILED.code, reason = "Connect: Error opening seal").alsoFireAccountErrorPixel()
229+
?: return Error(code = EXCHANGE_FAILED.code, reason = "Connect: Error opening seal").alsoFireAccountErrorPixel()
240230

241231
val recoveryData = kotlin.runCatching {
242232
Adapters.recoveryCodeAdapter.fromJson(decryptedJson)?.recovery
243233
}.getOrNull()
244-
?: return Error(code = CONNECT_FAILED.code, reason = "Connect: Error reading recovery code").alsoFireAccountErrorPixel()
234+
?: return Error(code = EXCHANGE_FAILED.code, reason = "Connect: Error reading recovery code").alsoFireAccountErrorPixel()
245235

246236
return when (val loginResult = login(recoveryData)) {
247237
is Success -> Success(LoggedIn)
@@ -320,20 +310,21 @@ class AppSyncAccountRepository @Inject constructor(
320310
// Sync: InviteFlow - A (https://app.asana.com/0/72649045549333/1209571867429615)
321311
Timber.d("Sync-exchange: InviteFlow - A. Generating invitation code")
322312

323-
// generate new ID and and public/private key-pair
324-
generateInviterDeviceDetails()
325-
326-
val pendingInvitation = pendingInvitationAsInviter
327-
?: return Error(code = GENERIC_ERROR.code, reason = "Exchange: No pending invitation initialized").alsoFireAccountErrorPixel()
313+
// generate new ID and and public/private key-pair for inviter device
314+
val deviceDetailsAsInviter = kotlin.runCatching {
315+
generateInviterDeviceDetails()
316+
}.getOrElse {
317+
return Error(code = EXCHANGE_FAILED.code, reason = "Error generating inviter key-pair").alsoFireAccountErrorPixel()
318+
}
328319

329-
val invitationCode = InvitationCode(keyId = pendingInvitation.keyId, publicKey = pendingInvitation.publicKey)
320+
val invitationCode = InvitationCode(keyId = deviceDetailsAsInviter.keyId, publicKey = deviceDetailsAsInviter.publicKey)
330321
val invitationWrapper = InvitationCodeWrapper(invitationCode)
331322

332323
return kotlin.runCatching {
333324
val code = Adapters.invitationCodeAdapter.toJson(invitationWrapper).encodeB64()
334325
Success(code)
335326
}.getOrElse {
336-
Error(code = GENERIC_ERROR.code, reason = "Error generating invitation code").alsoFireAccountErrorPixel()
327+
Error(code = EXCHANGE_FAILED.code, reason = "Error generating invitation code").alsoFireAccountErrorPixel()
337328
}
338329
}
339330

@@ -405,15 +396,15 @@ class AppSyncAccountRepository @Inject constructor(
405396
// Sync: InviteFlow - C (https://app.asana.com/0/72649045549333/1209571867429615)
406397
Timber.d("Sync-exchange: InviteFlow - C")
407398

408-
val keyId = pendingInvitationAsInviter?.keyId ?: return Error(reason = "No pending invitation initialized")
399+
val keyId = exchangeDeviceDetailsAsInviter?.keyId ?: return Error(reason = "No pending invitation initialized")
409400

410401
return when (val result = syncApi.getEncryptedMessage(keyId)) {
411402
is Error -> {
412403
if (result.code == NOT_FOUND.code) {
413404
return Success(false)
414405
} else if (result.code == GONE.code) {
415406
return Error(
416-
code = CONNECT_FAILED.code,
407+
code = EXCHANGE_FAILED.code,
417408
reason = "Connect: keys expired",
418409
).alsoFireAccountErrorPixel()
419410
}
@@ -424,20 +415,20 @@ class AppSyncAccountRepository @Inject constructor(
424415
Timber.v("Sync-exchange: Found invitation acceptance for keyId: $keyId} ${result.data}")
425416

426417
val decrypted = kotlin.runCatching {
427-
val pending = pendingInvitationAsInviter
428-
?: return Error(code = CONNECT_FAILED.code, reason = "Exchange: No pending invitation initialized")
418+
val pending = exchangeDeviceDetailsAsInviter
419+
?: return Error(code = EXCHANGE_FAILED.code, reason = "Exchange: No pending invitation initialized")
429420
.alsoFireAccountErrorPixel()
430421

431422
nativeLib.sealOpen(result.data, pending.publicKey, pending.privateKey)
432423
}.getOrElse { throwable ->
433424
throwable.asErrorResult().alsoFireAccountErrorPixel()
434-
return Error(code = CONNECT_FAILED.code, reason = "Connect: Error opening seal")
425+
return Error(code = EXCHANGE_FAILED.code, reason = "Connect: Error opening seal")
435426
}
436427

437428
Timber.v("Sync-exchange: invitation acceptance received: $decrypted")
438429

439430
val response = Adapters.invitedDeviceAdapter.fromJson(decrypted)
440-
?: return Error(code = GENERIC_ERROR.code, reason = "Connect: Error reading invitation response").alsoFireAccountErrorPixel()
431+
?: return Error(code = EXCHANGE_FAILED.code, reason = "Connect: Error reading invitation response").alsoFireAccountErrorPixel()
441432

442433
val otherDevicePublicKey = response.publicKey
443434
val otherDeviceKeyId = response.keyId
@@ -450,7 +441,7 @@ class AppSyncAccountRepository @Inject constructor(
450441
// we encrypt our secrets with otherDevicePublicKey, and send them to the backend endpoint
451442
return sendSecrets(otherDeviceKeyId, otherDevicePublicKey).onFailure {
452443
Timber.w("Sync-exchange: failed to send secrets. error code: ${it.code} ${it.reason}")
453-
return it.copy(code = LOGIN_FAILED.code)
444+
return it.copy(code = EXCHANGE_FAILED.code)
454445
}
455446
}
456447
}
@@ -610,23 +601,38 @@ class AppSyncAccountRepository @Inject constructor(
610601
}
611602
}
612603

613-
private fun generateInviterDeviceDetails() {
604+
private fun generateInviterDeviceDetails(): DeviceDetailsForKeyExchange {
614605
Timber.i("Sync-exchange: Generating inviter device details")
615-
// generate new ID and and public/private key-pair
616606
val keyId = deviceKeyGenerator.generate()
617607
val prepareForConnect = nativeLib.prepareForConnect()
618608

619-
PendingInvitation(
609+
return DeviceDetailsForKeyExchange(
620610
keyId = keyId,
621611
privateKey = prepareForConnect.secretKey,
622612
publicKey = prepareForConnect.publicKey,
623613
).also {
624-
pendingInvitationAsInviter = it
614+
exchangeDeviceDetailsAsInviter = it
625615
Timber.w("Sync-exchange: this (inviter) device's key ID is $keyId")
626616
Timber.w("Sync-exchange: this (inviter) device's public key is ${it.publicKey}")
627617
}
628618
}
629619

620+
private fun generateReceiverDeviceDetails(): DeviceDetailsForKeyExchange {
621+
Timber.i("Sync-exchange: Generating receiver device details")
622+
val thisDeviceKeyId = deviceKeyGenerator.generate()
623+
val thisDeviceKeyPair = nativeLib.prepareForConnect()
624+
625+
return DeviceDetailsForKeyExchange(
626+
keyId = thisDeviceKeyId,
627+
privateKey = thisDeviceKeyPair.secretKey,
628+
publicKey = thisDeviceKeyPair.publicKey,
629+
).also {
630+
exchangeDeviceDetailsAsReceiver = it
631+
Timber.w("Sync-exchange: this (receiver) device's key ID is ${it.keyId}")
632+
Timber.w("Sync-exchange: this (receiver) device's public key is ${it.publicKey}")
633+
}
634+
}
635+
630636
private fun performCreateAccount(): Result<Boolean> {
631637
val userId = syncDeviceIds.userId()
632638
val account: AccountKeys = kotlin.runCatching {
@@ -869,6 +875,7 @@ enum class AccountErrorCodes(val code: Int) {
869875
CREATE_ACCOUNT_FAILED(53),
870876
CONNECT_FAILED(54),
871877
INVALID_CODE(55),
878+
EXCHANGE_FAILED(56),
872879
}
873880

874881
enum class CodeType {
@@ -917,7 +924,7 @@ inline fun <T> Result<T>.onFailure(action: (error: Error) -> Unit): Result<T> {
917924
return this
918925
}
919926

920-
private data class PendingInvitation(
927+
private data class DeviceDetailsForKeyExchange(
921928
val keyId: String,
922929
val privateKey: String,
923930
var publicKey: String,

sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/SyncConnectViewModel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ class SyncConnectViewModel @Inject constructor(
114114
}
115115
is LoggedIn -> {
116116
polling = false
117+
syncPixels.fireLoginPixel()
117118
command.send(LoginSuccess)
118119
}
119120
}

sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/SyncWithAnotherActivityViewModel.kt

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import com.duckduckgo.sync.impl.Result.Error
4141
import com.duckduckgo.sync.impl.Result.Success
4242
import com.duckduckgo.sync.impl.SyncAccountRepository
4343
import com.duckduckgo.sync.impl.SyncFeature
44-
import com.duckduckgo.sync.impl.getOrNull
4544
import com.duckduckgo.sync.impl.onFailure
4645
import com.duckduckgo.sync.impl.onSuccess
4746
import com.duckduckgo.sync.impl.pixels.SyncPixels
@@ -78,7 +77,7 @@ class SyncWithAnotherActivityViewModel @Inject constructor(
7877
private val command = Channel<Command>(1, DROP_OLDEST)
7978
fun commands(): Flow<Command> = command.receiveAsFlow()
8079

81-
private var exchangeInvitationCode: String? = null
80+
private var barcodeContents: String? = null
8281

8382
private val viewState = MutableStateFlow(ViewState())
8483
fun viewState(): Flow<ViewState> = viewState.onStart {
@@ -114,10 +113,9 @@ class SyncWithAnotherActivityViewModel @Inject constructor(
114113
if (!shouldExchangeKeysToSyncAnotherDevice) {
115114
syncAccountRepository.getRecoveryCode()
116115
} else {
117-
syncAccountRepository.generateExchangeInvitationCode().also {
118-
exchangeInvitationCode = it.getOrNull()
119-
}
116+
syncAccountRepository.generateExchangeInvitationCode()
120117
}.onSuccess { connectQR ->
118+
barcodeContents = connectQR
121119
val qrBitmap = withContext(dispatchers.io()) {
122120
qrEncoder.encodeAsBitmap(connectQR, dimen.qrSizeSmall, dimen.qrSizeSmall)
123121
}
@@ -135,18 +133,7 @@ class SyncWithAnotherActivityViewModel @Inject constructor(
135133

136134
fun onCopyCodeClicked() {
137135
viewModelScope.launch(dispatchers.io()) {
138-
val shouldExchangeKeysToSyncAnotherDevice = syncFeature.exchangeKeysToSyncWithAnotherDevice().isEnabled()
139-
if (!shouldExchangeKeysToSyncAnotherDevice) {
140-
syncAccountRepository.getRecoveryCode()
141-
} else {
142-
if (exchangeInvitationCode != null) {
143-
Success(exchangeInvitationCode)
144-
} else {
145-
Error(reason = "Exchange code is null").also {
146-
Timber.e("Sync-exchange: ${it.reason}")
147-
}
148-
}
149-
}.getOrNull()?.let { code ->
136+
barcodeContents?.let { code ->
150137
clipboard.copyToClipboard(code)
151138
command.send(ShowMessage(string.sync_code_copied_message))
152139
} ?: command.send(FinishWithError)

0 commit comments

Comments
 (0)