mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-29 22:08:29 +00:00
feat: clear all data dialog with local and network only options
This commit is contained in:
parent
05b0e5f308
commit
1df6fa46a4
@ -31,10 +31,8 @@ import org.session.libsession.utilities.ProfilePictureUtilities
|
|||||||
import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol
|
import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.ProfileKeyUtil
|
import org.session.libsession.utilities.ProfileKeyUtil
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.avatar.AvatarSelection
|
import org.thoughtcrime.securesms.avatar.AvatarSelection
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
|
||||||
import org.thoughtcrime.securesms.loki.dialogs.ChangeUiModeDialog
|
import org.thoughtcrime.securesms.loki.dialogs.ChangeUiModeDialog
|
||||||
import org.thoughtcrime.securesms.loki.dialogs.ClearAllDataDialog
|
import org.thoughtcrime.securesms.loki.dialogs.ClearAllDataDialog
|
||||||
import org.thoughtcrime.securesms.loki.dialogs.SeedDialog
|
import org.thoughtcrime.securesms.loki.dialogs.SeedDialog
|
||||||
@ -92,7 +90,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
helpTranslateButton.setOnClickListener { helpTranslate() }
|
helpTranslateButton.setOnClickListener { helpTranslate() }
|
||||||
seedButton.setOnClickListener { showSeed() }
|
seedButton.setOnClickListener { showSeed() }
|
||||||
clearAllDataButton.setOnClickListener { clearAllData() }
|
clearAllDataButton.setOnClickListener { clearAllData() }
|
||||||
clearAllDataAndNetworkButton.setOnClickListener { clearAllDataIncludingNetwork() }
|
|
||||||
versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
|
versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,11 +300,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun clearAllData() {
|
private fun clearAllData() {
|
||||||
ClearAllDataDialog(deleteNetworkMessages = false).show(supportFragmentManager, "Clear All Data Dialog")
|
ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog")
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearAllDataIncludingNetwork() {
|
|
||||||
ClearAllDataDialog(deleteNetworkMessages = true).show(supportFragmentManager, "Clear All Data Dialog")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
@ -23,18 +23,44 @@ import org.thoughtcrime.securesms.ApplicationContext
|
|||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class ClearAllDataDialog(val deleteNetworkMessages: Boolean) : DialogFragment() {
|
class ClearAllDataDialog : DialogFragment() {
|
||||||
|
|
||||||
|
enum class Steps {
|
||||||
|
INFO_PROMPT,
|
||||||
|
NETWORK_PROMPT,
|
||||||
|
DELETING
|
||||||
|
}
|
||||||
|
|
||||||
var clearJob: Job? = null
|
var clearJob: Job? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var step = Steps.INFO_PROMPT
|
||||||
|
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 {
|
||||||
contentView.clearAllDataButton.setOnClickListener { clearAllData() }
|
if (step == Steps.NETWORK_PROMPT) {
|
||||||
|
clearAllData(false)
|
||||||
|
} else {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentView.clearAllDataButton.setOnClickListener {
|
||||||
|
when(step) {
|
||||||
|
Steps.INFO_PROMPT -> step = Steps.NETWORK_PROMPT
|
||||||
|
Steps.NETWORK_PROMPT -> {
|
||||||
|
clearAllData(true)
|
||||||
|
}
|
||||||
|
Steps.DELETING -> { /* do nothing intentionally */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
builder.setView(contentView)
|
builder.setView(contentView)
|
||||||
builder.setCancelable(false)
|
builder.setCancelable(false)
|
||||||
val result = builder.create()
|
val result = builder.create()
|
||||||
@ -42,71 +68,70 @@ class ClearAllDataDialog(val deleteNetworkMessages: Boolean) : DialogFragment()
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDismiss(dialog: DialogInterface) {
|
private fun updateUI() {
|
||||||
super.onDismiss(dialog)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
isCancelable = false
|
|
||||||
dialog?.setCanceledOnTouchOutside(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateUI(isLoading: Boolean) {
|
|
||||||
dialog?.let { view ->
|
dialog?.let { view ->
|
||||||
|
|
||||||
|
val isLoading = step == Steps.DELETING
|
||||||
|
|
||||||
|
when (step) {
|
||||||
|
Steps.INFO_PROMPT -> {
|
||||||
|
view.dialogDescriptionText.setText(R.string.dialog_clear_all_data_explanation)
|
||||||
|
view.cancelButton.setText(R.string.cancel)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
view.dialogDescriptionText.setText(R.string.dialog_clear_all_data_network_explanation)
|
||||||
|
view.cancelButton.setText(R.string.dialog_clear_all_data_local_only)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
view.cancelButton.isVisible = !isLoading
|
view.cancelButton.isVisible = !isLoading
|
||||||
view.clearAllDataButton.isVisible = !isLoading
|
view.clearAllDataButton.isVisible = !isLoading
|
||||||
view.progressBar.isVisible = isLoading
|
view.progressBar.isVisible = isLoading
|
||||||
|
|
||||||
|
view.setCanceledOnTouchOutside(!isLoading)
|
||||||
|
isCancelable = !isLoading
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearAllData() {
|
private fun clearAllData(deleteNetworkMessages: Boolean) {
|
||||||
if (KeyPairUtilities.hasV2KeyPair(requireContext())) {
|
if (KeyPairUtilities.hasV2KeyPair(requireContext())) {
|
||||||
clearJob = lifecycleScope.launch(Dispatchers.IO) {
|
clearJob = lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val previousStep = step
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
updateUI(true)
|
step = Steps.DELETING
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!deleteNetworkMessages) {
|
if (!deleteNetworkMessages) {
|
||||||
try {
|
try {
|
||||||
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(requireContext()).get()
|
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(requireContext()).get()
|
||||||
ApplicationContext.getInstance(context).clearAllData(false)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Loki", "Failed to force sync", e)
|
Log.e("Loki", "Failed to force sync", e)
|
||||||
withContext(Dispatchers.Main) {
|
}
|
||||||
updateUI(false)
|
ApplicationContext.getInstance(context).clearAllData(false)
|
||||||
}
|
withContext(Dispatchers.Main) {
|
||||||
|
dismiss()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// finish
|
// finish
|
||||||
val promises = try {
|
val result = try {
|
||||||
SnodeAPI.deleteAllMessages(requireContext()).get()
|
SnodeAPI.deleteAllMessages(requireContext()).get()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val rawResponses = promises?.map {
|
if (result == null || result.values.any { !it } || result.isEmpty()) {
|
||||||
try {
|
// didn't succeed (at least one)
|
||||||
it.get()
|
withContext(Dispatchers.Main) {
|
||||||
} catch (e: Exception) {
|
step = previousStep
|
||||||
null
|
|
||||||
}
|
}
|
||||||
} ?: listOf(null)
|
} else if (result.values.all { it }) {
|
||||||
// TODO: process the responses here
|
|
||||||
if (rawResponses.all { it != null }) {
|
|
||||||
// don't force sync because all the messages are deleted?
|
// don't force sync because all the messages are deleted?
|
||||||
ApplicationContext.getInstance(context).clearAllData(false)
|
ApplicationContext.getInstance(context).clearAllData(false)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
} else if (rawResponses.any { it == null || it["failed"] as? Boolean == true }) {
|
|
||||||
// didn't succeed (at least one)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
updateUI(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,16 +185,6 @@
|
|||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="@string/activity_settings_clear_all_data_button_title" />
|
android:text="@string/activity_settings_clear_all_data_button_title" />
|
||||||
<TextView
|
|
||||||
android:id="@+id/clearAllDataAndNetworkButton"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/setting_button_height"
|
|
||||||
android:background="@drawable/setting_button_background"
|
|
||||||
android:textColor="@color/destructive"
|
|
||||||
android:textSize="@dimen/medium_font_size"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/activity_settings_clear_all_data_and_network_button_title" />
|
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
android:textSize="@dimen/medium_font_size" />
|
android:textSize="@dimen/medium_font_size" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/seedTextView"
|
android:id="@+id/dialogDescriptionText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/large_spacing"
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
@ -37,7 +37,6 @@
|
|||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
tools:visibility="gone"
|
|
||||||
style="@style/Widget.Session.Button.Dialog.Unimportant"
|
style="@style/Widget.Session.Button.Dialog.Unimportant"
|
||||||
android:id="@+id/cancelButton"
|
android:id="@+id/cancelButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@ -46,7 +45,6 @@
|
|||||||
android:text="@string/cancel" />
|
android:text="@string/cancel" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
tools:visibility="gone"
|
|
||||||
style="@style/Widget.Session.Button.Dialog.Destructive"
|
style="@style/Widget.Session.Button.Dialog.Destructive"
|
||||||
android:id="@+id/clearAllDataButton"
|
android:id="@+id/clearAllDataButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
@ -778,6 +778,8 @@
|
|||||||
|
|
||||||
<string name="dialog_clear_all_data_title">Clear All Data</string>
|
<string name="dialog_clear_all_data_title">Clear All Data</string>
|
||||||
<string name="dialog_clear_all_data_explanation">This will permanently delete your messages, sessions, and contacts.</string>
|
<string name="dialog_clear_all_data_explanation">This will permanently delete your messages, sessions, and contacts.</string>
|
||||||
|
<string name="dialog_clear_all_data_network_explanation">Do you also want to clear all your data from the network?</string>
|
||||||
|
<string name="dialog_clear_all_data_local_only">Delete Local Only</string>
|
||||||
|
|
||||||
<string name="activity_qr_code_title">QR Code</string>
|
<string name="activity_qr_code_title">QR Code</string>
|
||||||
<string name="activity_qr_code_view_my_qr_code_tab_title">View My QR Code</string>
|
<string name="activity_qr_code_view_my_qr_code_tab_title">View My QR Code</string>
|
||||||
|
@ -107,9 +107,9 @@ object SnodeAPI {
|
|||||||
val parameters = mapOf(
|
val parameters = mapOf(
|
||||||
"method" to "get_n_service_nodes",
|
"method" to "get_n_service_nodes",
|
||||||
"params" to mapOf(
|
"params" to mapOf(
|
||||||
"active_only" to true,
|
"active_only" to true,
|
||||||
"limit" to 256,
|
"limit" to 256,
|
||||||
"fields" to mapOf( "public_ip" to true, "storage_port" to true, "pubkey_x25519" to true, "pubkey_ed25519" to true )
|
"fields" to mapOf("public_ip" to true, "storage_port" to true, "pubkey_x25519" to true, "pubkey_ed25519" to true)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val deferred = deferred<Snode, Exception>()
|
val deferred = deferred<Snode, Exception>()
|
||||||
@ -185,8 +185,8 @@ object SnodeAPI {
|
|||||||
val base64EncodedNameHash = Base64.encodeBytes(nameHash)
|
val base64EncodedNameHash = Base64.encodeBytes(nameHash)
|
||||||
// Ask 3 different snodes for the Session ID associated with the given name hash
|
// Ask 3 different snodes for the Session ID associated with the given name hash
|
||||||
val parameters = mapOf(
|
val parameters = mapOf(
|
||||||
"endpoint" to "ons_resolve",
|
"endpoint" to "ons_resolve",
|
||||||
"params" to mapOf( "type" to 0, "name_hash" to base64EncodedNameHash )
|
"params" to mapOf( "type" to 0, "name_hash" to base64EncodedNameHash )
|
||||||
)
|
)
|
||||||
val promises = (1..validationCount).map {
|
val promises = (1..validationCount).map {
|
||||||
getRandomSnode().bind { snode ->
|
getRandomSnode().bind { snode ->
|
||||||
@ -293,7 +293,7 @@ object SnodeAPI {
|
|||||||
|
|
||||||
fun getNetworkTime(snode: Snode): Promise<Pair<Snode,Long>, Exception> {
|
fun getNetworkTime(snode: Snode): Promise<Pair<Snode,Long>, Exception> {
|
||||||
return invoke(Snode.Method.Info, snode, null, emptyMap()).map { rawResponse ->
|
return invoke(Snode.Method.Info, snode, null, emptyMap()).map { rawResponse ->
|
||||||
val timestamp = rawResponse["timestamp"] as Long
|
val timestamp = rawResponse["timestamp"] as? Long ?: -1
|
||||||
snode to timestamp
|
snode to timestamp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,39 +312,6 @@ object SnodeAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAllMessages(context: Context): Promise<Set<RawResponsePromise>, Exception> {
|
|
||||||
|
|
||||||
return retryIfNeeded(maxRetryCount) {
|
|
||||||
// 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 userED25519KeyPair = KeyPairUtilities.getUserED25519KeyPair(context) ?: return@retryIfNeeded Promise.ofFail(Error.Generic)
|
|
||||||
val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() ?: return@retryIfNeeded Promise.ofFail(Error.Generic)
|
|
||||||
|
|
||||||
val destination = if (useTestnet) userPublicKey.removing05PrefixIfNeeded() else userPublicKey
|
|
||||||
|
|
||||||
getSwarm(destination).map { swarm ->
|
|
||||||
val promise = swarm.first().let { snode ->
|
|
||||||
retryIfNeeded(maxRetryCount) {
|
|
||||||
getNetworkTime(snode).bind { (_, timestamp) ->
|
|
||||||
val signature = ByteArray(Sign.BYTES)
|
|
||||||
val data = (Snode.Method.DeleteAll.rawValue + timestamp.toString()).toByteArray()
|
|
||||||
sodium.cryptoSignDetached(signature, data, data.size.toLong(), userED25519KeyPair.secretKey.asBytes)
|
|
||||||
val deleteMessageParams = mapOf(
|
|
||||||
"pubkey" to userPublicKey,
|
|
||||||
"pubkey_ed25519" to userED25519KeyPair.publicKey.asHexString,
|
|
||||||
"timestamp" to timestamp,
|
|
||||||
"signature" to Base64.encodeBytes(signature)
|
|
||||||
)
|
|
||||||
invoke(Snode.Method.DeleteAll, snode, destination, deleteMessageParams).map { rawResponse -> parseDeletions(timestamp, rawResponse) }.fail { e ->
|
|
||||||
Log.e("Loki", "Failed to clear data",e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setOf(promise)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<*, *>
|
||||||
@ -370,6 +337,34 @@ object SnodeAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteAllMessages(context: Context): Promise<Map<String,Boolean>, Exception> {
|
||||||
|
|
||||||
|
return retryIfNeeded(maxRetryCount) {
|
||||||
|
// 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 userED25519KeyPair = KeyPairUtilities.getUserED25519KeyPair(context) ?: return@retryIfNeeded Promise.ofFail(Error.Generic)
|
||||||
|
val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() ?: return@retryIfNeeded Promise.ofFail(Error.Generic)
|
||||||
|
|
||||||
|
getSingleTargetSnode(userPublicKey).bind { snode ->
|
||||||
|
retryIfNeeded(maxRetryCount) {
|
||||||
|
getNetworkTime(snode).bind { (_, timestamp) ->
|
||||||
|
val signature = ByteArray(Sign.BYTES)
|
||||||
|
val data = (Snode.Method.DeleteAll.rawValue + timestamp.toString()).toByteArray()
|
||||||
|
sodium.cryptoSignDetached(signature, data, data.size.toLong(), userED25519KeyPair.secretKey.asBytes)
|
||||||
|
val deleteMessageParams = mapOf(
|
||||||
|
"pubkey" to userPublicKey,
|
||||||
|
"pubkey_ed25519" to userED25519KeyPair.publicKey.asHexString,
|
||||||
|
"timestamp" to timestamp,
|
||||||
|
"signature" to Base64.encodeBytes(signature)
|
||||||
|
)
|
||||||
|
invoke(Snode.Method.DeleteAll, snode, userPublicKey, deleteMessageParams).map { rawResponse -> parseDeletions(userPublicKey, timestamp, rawResponse) }.fail { e ->
|
||||||
|
Log.e("Loki", "Failed to clear data", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String): List<SignalServiceProtos.Envelope> {
|
fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String): List<SignalServiceProtos.Envelope> {
|
||||||
val messages = rawResponse["messages"] as? List<*>
|
val messages = rawResponse["messages"] as? List<*>
|
||||||
return if (messages != null) {
|
return if (messages != null) {
|
||||||
@ -429,10 +424,11 @@ object SnodeAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
private fun parseDeletions(timestamp: Long, rawResponse: RawResponse): Map<String, Any> {
|
private fun parseDeletions(userPublicKey: String, timestamp: Long, rawResponse: RawResponse): Map<String, Boolean> {
|
||||||
val swarms = rawResponse["swarms"] as? Map<String,Any> ?: return mapOf()
|
val swarms = rawResponse["swarm"] as? Map<String, Any> ?: return mapOf()
|
||||||
val swarmResponsesValid = swarms.mapNotNull { (nodePubKeyHex, rawMap) ->
|
val swarmResponsesValid = swarms.mapNotNull { (nodePubKeyHex, rawMap) ->
|
||||||
val map = rawMap as? Map<String,Any> ?: return@mapNotNull null
|
val map = rawMap as? Map<String, Any> ?: return@mapNotNull null
|
||||||
|
|
||||||
/** Deletes all messages owned by the given pubkey on this SN and broadcasts the delete request to
|
/** Deletes all messages owned by the given pubkey on this SN and broadcasts the delete request to
|
||||||
* all other swarm members.
|
* all other swarm members.
|
||||||
* Returns dict of:
|
* Returns dict of:
|
||||||
@ -448,17 +444,16 @@ object SnodeAPI {
|
|||||||
val reason = map["reason"] as? String
|
val reason = map["reason"] as? String
|
||||||
|
|
||||||
nodePubKeyHex to if (failed) {
|
nodePubKeyHex to if (failed) {
|
||||||
// return error probs
|
Log.e("Loki", "Failed to delete all from $nodePubKeyHex with error code $code and reason $reason")
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
// success
|
// success
|
||||||
val deleted = map["deleted"] as List<String> // list of deleted hashes
|
val deleted = map["deleted"] as List<String> // list of deleted hashes
|
||||||
Log.d("Loki", "node $nodePubKeyHex deleted ${deleted.size} messages")
|
|
||||||
val signature = map["signature"] as String
|
val signature = map["signature"] as String
|
||||||
val nodePubKeyBytes = Hex.fromStringCondensed(nodePubKeyHex)
|
val nodePubKey = Key.fromHexString(nodePubKeyHex)
|
||||||
// signature of ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] )
|
// signature of ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] )
|
||||||
val message = (signature + timestamp + deleted.fold("") { a, v -> a+v }).toByteArray()
|
val message = (userPublicKey + timestamp.toString() + deleted.fold("") { a, v -> a + v }).toByteArray()
|
||||||
sodium.cryptoSignVerifyDetached(Base64.decode(signature), message, message.size, nodePubKeyBytes)
|
sodium.cryptoSignVerifyDetached(Base64.decode(signature), message, message.size, nodePubKey.asBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return swarmResponsesValid.toMap()
|
return swarmResponsesValid.toMap()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user