Add link+sync error flows.
This commit is contained in:
parent
e1d4566dfd
commit
df5ef06109
8 changed files with 104 additions and 25 deletions
|
@ -179,7 +179,7 @@ class LinkDeviceFragment : ComposeFragment() {
|
|||
onLinkNewDeviceClicked = { navController.navigateToQrScannerIfAuthed() },
|
||||
onDeviceSelectedForRemoval = { device -> viewModel.setDeviceToRemove(device) },
|
||||
onDeviceRemovalConfirmed = { device -> viewModel.removeDevice(device) },
|
||||
onSyncFailureRetryRequested = { deviceId -> viewModel.onSyncErrorRetryRequested(deviceId) },
|
||||
onSyncFailureRetryRequested = { viewModel.onSyncErrorRetryRequested() },
|
||||
onSyncFailureIgnored = { viewModel.onSyncErrorIgnored() },
|
||||
onEditDevice = { device ->
|
||||
viewModel.setDeviceToEdit(device)
|
||||
|
@ -228,7 +228,7 @@ fun DeviceListScreen(
|
|||
onLinkNewDeviceClicked: () -> Unit = {},
|
||||
onDeviceSelectedForRemoval: (Device?) -> Unit = {},
|
||||
onDeviceRemovalConfirmed: (Device) -> Unit = {},
|
||||
onSyncFailureRetryRequested: (Int?) -> Unit = {},
|
||||
onSyncFailureRetryRequested: () -> Unit = {},
|
||||
onSyncFailureIgnored: () -> Unit = {},
|
||||
onEditDevice: (Device) -> Unit = {}
|
||||
) {
|
||||
|
@ -253,15 +253,10 @@ fun DeviceListScreen(
|
|||
title = stringResource(R.string.LinkDeviceFragment__sync_failure_title),
|
||||
body = stringResource(R.string.LinkDeviceFragment__sync_failure_body),
|
||||
confirm = stringResource(R.string.LinkDeviceFragment__sync_failure_retry_button),
|
||||
onConfirm = {
|
||||
if (state.dialogState is DialogState.SyncingFailed) {
|
||||
onSyncFailureRetryRequested(state.dialogState.deviceId)
|
||||
} else {
|
||||
onSyncFailureRetryRequested(null)
|
||||
}
|
||||
},
|
||||
onConfirm = onSyncFailureRetryRequested,
|
||||
dismiss = stringResource(R.string.LinkDeviceFragment__sync_failure_dismiss_button),
|
||||
onDismiss = onSyncFailureIgnored
|
||||
onDismissRequest = onSyncFailureIgnored,
|
||||
onDeny = onSyncFailureIgnored
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.registration.secondary.DeviceNameCipher
|
|||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import org.whispersystems.signalservice.api.backup.MessageBackupKey
|
||||
import org.whispersystems.signalservice.api.link.LinkedDeviceVerificationCodeResponse
|
||||
import org.whispersystems.signalservice.api.link.TransferArchiveError
|
||||
import org.whispersystems.signalservice.api.link.WaitForLinkedDeviceResponse
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
|
@ -351,6 +352,24 @@ object LinkDeviceRepository {
|
|||
return NetworkResult.NetworkError(IOException("Hit max retries!"))
|
||||
}
|
||||
|
||||
/**
|
||||
* If [createAndUploadArchive] fails to upload an archive, alert the linked device of the failure and if the user will try again
|
||||
*/
|
||||
fun sendTransferArchiveError(deviceId: Int, deviceCreatedAt: Long, error: TransferArchiveError) {
|
||||
val archiveErrorResult = SignalNetwork.linkDevice.setTransferArchiveError(
|
||||
destinationDeviceId = deviceId,
|
||||
destinationDeviceCreated = deviceCreatedAt,
|
||||
error = error
|
||||
)
|
||||
|
||||
when (archiveErrorResult) {
|
||||
is NetworkResult.Success -> Log.i(TAG, "[sendTransferArchiveError] Successfully sent transfer archive error.")
|
||||
is NetworkResult.ApplicationError -> throw archiveErrorResult.throwable
|
||||
is NetworkResult.NetworkError -> Log.w(TAG, "[sendTransferArchiveError] Network error when sending transfer archive error.", archiveErrorResult.exception)
|
||||
is NetworkResult.StatusCodeError -> Log.w(TAG, "[sendTransferArchiveError] Status code error when sending transfer archive error.", archiveErrorResult.exception)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the name of a linked device and sends a sync message if successful
|
||||
*/
|
||||
|
|
|
@ -27,7 +27,7 @@ data class LinkDeviceSettingsState(
|
|||
data object Unlinking : DialogState
|
||||
data object SyncingMessages : DialogState
|
||||
data object SyncingTimedOut : DialogState
|
||||
data class SyncingFailed(val deviceId: Int) : DialogState
|
||||
data class SyncingFailed(val deviceId: Int, val deviceCreatedAt: Long) : DialogState
|
||||
}
|
||||
|
||||
sealed interface OneTimeEvent {
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.linkdevice.LinkDeviceSettingsState.QrCodeState
|
|||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.backup.MessageBackupKey
|
||||
import org.whispersystems.signalservice.api.link.TransferArchiveError
|
||||
import org.whispersystems.signalservice.api.link.WaitForLinkedDeviceResponse
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
@ -260,7 +261,7 @@ class LinkDeviceViewModel : ViewModel() {
|
|||
Log.w(TAG, "[addDeviceWithSync] Failed to upload the archive! Result: $uploadResult")
|
||||
_state.update {
|
||||
it.copy(
|
||||
dialogState = DialogState.SyncingFailed(waitResult.id)
|
||||
dialogState = DialogState.SyncingFailed(waitResult.id, waitResult.created)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -309,16 +310,29 @@ class LinkDeviceViewModel : ViewModel() {
|
|||
return this.getQueryParameter("capabilities")?.split(",")?.contains("backup") == true
|
||||
}
|
||||
|
||||
fun onSyncErrorIgnored() {
|
||||
fun onSyncErrorIgnored() = viewModelScope.launch(Dispatchers.IO) {
|
||||
val dialogState = _state.value.dialogState
|
||||
if (dialogState is DialogState.SyncingFailed) {
|
||||
Log.i(TAG, "Alerting linked device of sync failure - will not retry")
|
||||
LinkDeviceRepository.sendTransferArchiveError(dialogState.deviceId, dialogState.deviceCreatedAt, TransferArchiveError.CONTINUE_WITHOUT_UPLOAD)
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(dialogState = DialogState.None)
|
||||
it.copy(
|
||||
linkDeviceResult = LinkDeviceResult.None,
|
||||
dialogState = DialogState.None
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onSyncErrorRetryRequested(deviceId: Int?) = viewModelScope.launch(Dispatchers.IO) {
|
||||
if (deviceId != null) {
|
||||
fun onSyncErrorRetryRequested() = viewModelScope.launch(Dispatchers.IO) {
|
||||
val dialogState = _state.value.dialogState
|
||||
if (dialogState is DialogState.SyncingFailed) {
|
||||
Log.i(TAG, "Alerting linked device of sync failure - will retry")
|
||||
LinkDeviceRepository.sendTransferArchiveError(dialogState.deviceId, dialogState.deviceCreatedAt, TransferArchiveError.RELINK_REQUESTED)
|
||||
|
||||
Log.i(TAG, "Need to unlink device first...")
|
||||
val success = LinkDeviceRepository.removeDevice(deviceId)
|
||||
val success = LinkDeviceRepository.removeDevice(dialogState.deviceId)
|
||||
if (!success) {
|
||||
Log.w(TAG, "Failed to remove device! We did our best. Continuing.")
|
||||
}
|
||||
|
@ -326,6 +340,7 @@ class LinkDeviceViewModel : ViewModel() {
|
|||
|
||||
_state.update {
|
||||
it.copy(
|
||||
linkDeviceResult = LinkDeviceResult.None,
|
||||
dialogState = DialogState.None,
|
||||
oneTimeEvent = OneTimeEvent.LaunchQrCodeScanner
|
||||
)
|
||||
|
|
|
@ -76,8 +76,9 @@ object Dialogs {
|
|||
body: String,
|
||||
confirm: String,
|
||||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
onDismiss: () -> Unit = {},
|
||||
onDismissRequest: () -> Unit = onDismiss,
|
||||
onDeny: () -> Unit = {},
|
||||
modifier: Modifier = Modifier,
|
||||
dismiss: String = NoDismiss,
|
||||
confirmColor: Color = Color.Unspecified,
|
||||
|
@ -104,7 +105,13 @@ object Dialogs {
|
|||
},
|
||||
dismissButton = if (dismiss.isNotEmpty()) {
|
||||
{
|
||||
TextButton(onClick = onDismiss) {
|
||||
TextButton(
|
||||
onClick =
|
||||
{
|
||||
onDismiss()
|
||||
onDeny()
|
||||
}
|
||||
) {
|
||||
Text(text = dismiss, color = dismissColor)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ class LinkDeviceApi(private val pushServiceSocket: PushServiceSocket) {
|
|||
SetLinkedDeviceTransferArchiveRequest(
|
||||
destinationDeviceId = destinationDeviceId,
|
||||
destinationDeviceCreated = destinationDeviceCreated,
|
||||
transferArchive = SetLinkedDeviceTransferArchiveRequest.CdnInfo(
|
||||
transferArchive = SetLinkedDeviceTransferArchiveRequest.TransferArchive.CdnInfo(
|
||||
cdn = cdn,
|
||||
key = cdnKey
|
||||
)
|
||||
|
@ -132,6 +132,30 @@ class LinkDeviceApi(private val pushServiceSocket: PushServiceSocket) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If creating an archive has failed after linking a device, notify the linked
|
||||
* device of the failure and if you are going to try relinking or skip syncing
|
||||
*
|
||||
* PUT /v1/devices/transfer_archive
|
||||
*
|
||||
* - 204: Success.
|
||||
* - 422: Bad inputs.
|
||||
* - 429: Rate-limited.
|
||||
*/
|
||||
fun setTransferArchiveError(destinationDeviceId: Int, destinationDeviceCreated: Long, error: TransferArchiveError): NetworkResult<Unit> {
|
||||
return NetworkResult.fromFetch {
|
||||
pushServiceSocket.setLinkedDeviceTransferArchive(
|
||||
SetLinkedDeviceTransferArchiveRequest(
|
||||
destinationDeviceId = destinationDeviceId,
|
||||
destinationDeviceCreated = destinationDeviceCreated,
|
||||
transferArchive = SetLinkedDeviceTransferArchiveRequest.TransferArchive.Error(
|
||||
error
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name for a linked device
|
||||
*
|
||||
|
|
|
@ -13,10 +13,15 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
|||
data class SetLinkedDeviceTransferArchiveRequest(
|
||||
@JsonProperty val destinationDeviceId: Int,
|
||||
@JsonProperty val destinationDeviceCreated: Long,
|
||||
@JsonProperty val transferArchive: CdnInfo
|
||||
@JsonProperty val transferArchive: TransferArchive
|
||||
) {
|
||||
data class CdnInfo(
|
||||
@JsonProperty val cdn: Int,
|
||||
@JsonProperty val key: String
|
||||
)
|
||||
sealed class TransferArchive {
|
||||
data class CdnInfo(
|
||||
@JsonProperty val cdn: Int,
|
||||
@JsonProperty val key: String
|
||||
) : TransferArchive()
|
||||
data class Error(
|
||||
@JsonProperty val error: TransferArchiveError
|
||||
) : TransferArchive()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.link
|
||||
|
||||
/**
|
||||
* Error response options chosen by a user. Response is sent to a linked device after its transfer archive has failed
|
||||
*/
|
||||
enum class TransferArchiveError {
|
||||
RELINK_REQUESTED,
|
||||
CONTINUE_WITHOUT_UPLOAD
|
||||
}
|
Loading…
Add table
Reference in a new issue