diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt index cb1bf22566..ac18e224a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt @@ -4,38 +4,77 @@ import android.app.Dialog import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.os.Bundle -import androidx.fragment.app.DialogFragment -import androidx.appcompat.app.AlertDialog 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.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import network.loki.messenger.R -import org.thoughtcrime.securesms.ApplicationContext -import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol +import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.snode.SnodeAPI +import org.session.libsession.snode.SnodeDeleteMessage import org.session.libsession.utilities.KeyPairUtilities class ClearAllDataDialog : DialogFragment() { + var clearJob: Job? = null + set(value) { + field = value + updateUI() + } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val builder = AlertDialog.Builder(requireContext()) val contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_clear_all_data, null) contentView.cancelButton.setOnClickListener { dismiss() } contentView.clearAllDataButton.setOnClickListener { clearAllData() } builder.setView(contentView) + builder.setCancelable(false) val result = builder.create() result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) 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() { if (KeyPairUtilities.hasV2KeyPair(requireContext())) { - MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(requireContext()) - ApplicationContext.getInstance(context).clearAllData(false) + clearJob = lifecycleScope.launch { + 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 { val dialog = AlertDialog.Builder(requireContext()) val message = "We’ve upgraded the way Session IDs are generated, so you will be unable to restore your current Session ID." dialog.setMessage(message) dialog.setPositiveButton("Yes") { _, _ -> - ApplicationContext.getInstance(context).clearAllData(false) + // TODO: re-add the clear data here + // ApplicationContext.getInstance(context).clearAllData(false) } dialog.setNegativeButton("Cancel") { _, _ -> // Do nothing diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index f126871a40..32c221b0e3 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -6,7 +6,6 @@ import android.os.Build import com.goterl.lazysodium.LazySodiumAndroid import com.goterl.lazysodium.SodiumAndroid import com.goterl.lazysodium.exceptions.SodiumException -import com.goterl.lazysodium.interfaces.AEAD import com.goterl.lazysodium.interfaces.GenericHash import com.goterl.lazysodium.interfaces.PwHash 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, 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 private fun parseSnodes(rawResponse: Any): List { val json = rawResponse as? Map<*, *> diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeDeleteMessage.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeDeleteMessage.kt new file mode 100644 index 0000000000..a9f11fef08 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeDeleteMessage.kt @@ -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 { + return mapOf( + "pubkey" to if (SnodeAPI.useTestnet) pubKey.removing05PrefixIfNeeded() else pubKey, + "timestamp" to timestamp.toString(), + "signature" to signature + ) + } + +} \ No newline at end of file diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt b/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt index 92c30095ef..251116f317 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt @@ -7,7 +7,8 @@ class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) { GetSwarm("get_snodes_for_pubkey"), GetMessages("retrieve"), SendMessage("store"), - OxenDaemonRPCCall("oxend_request") + OxenDaemonRPCCall("oxend_request"), + DeleteAll("delete_all") } data class KeySet(val ed25519Key: String, val x25519Key: String)