feat: add snode method delete_all with data class for params, refactoring ClearAllDataDialog.kt to handle async requests better and prevent ANR

This commit is contained in:
Harris 2021-06-17 18:29:05 +10:00
parent ac4b576abe
commit 11f64a1d1a
4 changed files with 102 additions and 9 deletions

View File

@ -4,38 +4,77 @@ import android.app.Dialog
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.dialog_clear_all_data.*
import kotlinx.android.synthetic.main.dialog_clear_all_data.view.* import kotlinx.android.synthetic.main.dialog_clear_all_data.view.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext import org.session.libsession.messaging.MessagingModuleConfiguration
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeDeleteMessage
import org.session.libsession.utilities.KeyPairUtilities import org.session.libsession.utilities.KeyPairUtilities
class ClearAllDataDialog : DialogFragment() { class ClearAllDataDialog : DialogFragment() {
var clearJob: Job? = null
set(value) {
field = value
updateUI()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireContext()) val builder = AlertDialog.Builder(requireContext())
val contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_clear_all_data, null) val contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_clear_all_data, null)
contentView.cancelButton.setOnClickListener { dismiss() } contentView.cancelButton.setOnClickListener { dismiss() }
contentView.clearAllDataButton.setOnClickListener { clearAllData() } contentView.clearAllDataButton.setOnClickListener { clearAllData() }
builder.setView(contentView) builder.setView(contentView)
builder.setCancelable(false)
val result = builder.create() val result = builder.create()
result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
return result return result
} }
private fun updateUI() {
if (clearJob?.isActive == true) {
// clear background job is running, prevent interaction
dialog?.let { view ->
view.cancelButton.isVisible = false
view.clearAllDataButton.isVisible = false
}
} else {
dialog?.let { view ->
view.cancelButton.isVisible = false
view.clearAllDataButton.isVisible = false
}
}
}
private fun clearAllData() { private fun clearAllData() {
if (KeyPairUtilities.hasV2KeyPair(requireContext())) { if (KeyPairUtilities.hasV2KeyPair(requireContext())) {
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(requireContext()) clearJob = lifecycleScope.launch {
ApplicationContext.getInstance(context).clearAllData(false) delay(5_000)
// finish
val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey()
val deleteMessage = SnodeDeleteMessage(userKey, System.currentTimeMillis(), )
SnodeAPI.deleteAllMessages()
// TODO: re-add the clear data here
//ApplicationContext.getInstance(context).clearAllData(false)
}
} else { } else {
val dialog = AlertDialog.Builder(requireContext()) val dialog = AlertDialog.Builder(requireContext())
val message = "Weve upgraded the way Session IDs are generated, so you will be unable to restore your current Session ID." val message = "Weve upgraded the way Session IDs are generated, so you will be unable to restore your current Session ID."
dialog.setMessage(message) dialog.setMessage(message)
dialog.setPositiveButton("Yes") { _, _ -> dialog.setPositiveButton("Yes") { _, _ ->
ApplicationContext.getInstance(context).clearAllData(false) // TODO: re-add the clear data here
// ApplicationContext.getInstance(context).clearAllData(false)
} }
dialog.setNegativeButton("Cancel") { _, _ -> dialog.setNegativeButton("Cancel") { _, _ ->
// Do nothing // Do nothing

View File

@ -6,7 +6,6 @@ import android.os.Build
import com.goterl.lazysodium.LazySodiumAndroid import com.goterl.lazysodium.LazySodiumAndroid
import com.goterl.lazysodium.SodiumAndroid import com.goterl.lazysodium.SodiumAndroid
import com.goterl.lazysodium.exceptions.SodiumException import com.goterl.lazysodium.exceptions.SodiumException
import com.goterl.lazysodium.interfaces.AEAD
import com.goterl.lazysodium.interfaces.GenericHash import com.goterl.lazysodium.interfaces.GenericHash
import com.goterl.lazysodium.interfaces.PwHash import com.goterl.lazysodium.interfaces.PwHash
import com.goterl.lazysodium.interfaces.SecretBox import com.goterl.lazysodium.interfaces.SecretBox
@ -298,6 +297,30 @@ object SnodeAPI {
} }
} }
/** Deletes all messages owned by the given pubkey on this SN and broadcasts the delete request to
* all other swarm members.
* Returns dict of:
* - "swarms" dict mapping ed25519 pubkeys (in hex) of swarm members to dict values of:
* - "failed" and other failure keys -- see `recursive`.
* - "deleted": hashes of deleted messages.
* - "signature": signature of ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] ), signed
* by the node's ed25519 pubkey.
*/
fun deleteAllMessages(deleteMessage: SnodeDeleteMessage): Promise<Set<RawResponsePromise>, Exception> {
// considerations: timestamp off in retrying logic, not being able to re-sign with latest timestamp? do we just not retry this as it will be synchronous
val destination = if (useTestnet) deleteMessage.pubKey.removing05PrefixIfNeeded() else deleteMessage.pubKey
return retryIfNeeded(maxRetryCount) {
getTargetSnodes(destination).map { swarm ->
swarm.map { snode ->
val parameters = deleteMessage.toJSON()
retryIfNeeded(maxRetryCount) {
invoke(Snode.Method.DeleteAll, snode, destination, parameters)
}
}.toSet()
}
}
}
// Parsing // Parsing
private fun parseSnodes(rawResponse: Any): List<Snode> { private fun parseSnodes(rawResponse: Any): List<Snode> {
val json = rawResponse as? Map<*, *> val json = rawResponse as? Map<*, *>

View File

@ -0,0 +1,30 @@
package org.session.libsession.snode
import org.session.libsignal.utilities.removing05PrefixIfNeeded
data class SnodeDeleteMessage(
/**
* The hex encoded public key of the user.
*/
val pubKey: String,
/**
* The timestamp at which this request was initiated, in milliseconds since unix epoch.
* Must be within Must be within ±60s of the current time.
* (For clients it is recommended to retrieve a timestamp via `info` first, to avoid client time sync issues).
*/
val timestamp: Long,
/**
* an Ed25519 signature of ( "delete_all" || timestamp ), signed by the ed25519
*/
val signature: String,
) {
internal fun toJSON(): Map<String, String> {
return mapOf(
"pubkey" to if (SnodeAPI.useTestnet) pubKey.removing05PrefixIfNeeded() else pubKey,
"timestamp" to timestamp.toString(),
"signature" to signature
)
}
}

View File

@ -7,7 +7,8 @@ class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) {
GetSwarm("get_snodes_for_pubkey"), GetSwarm("get_snodes_for_pubkey"),
GetMessages("retrieve"), GetMessages("retrieve"),
SendMessage("store"), SendMessage("store"),
OxenDaemonRPCCall("oxend_request") OxenDaemonRPCCall("oxend_request"),
DeleteAll("delete_all")
} }
data class KeySet(val ed25519Key: String, val x25519Key: String) data class KeySet(val ed25519Key: String, val x25519Key: String)