From 64de43815fc1a670b5e2dc7eb220b30bdc2ef874 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 30 Dec 2024 12:04:59 -0300 Subject: [PATCH] move bunker and nostrconnect to its own classes --- .../greenart7c3/nostrsigner/MainActivity.kt | 4 +- .../greenart7c3/nostrsigner/MainViewModel.kt | 5 +- .../nostrsigner/service/AmberUtils.kt | 2 +- .../nostrsigner/service/BunkerRequestUtils.kt | 361 +++++++++++++ .../service/EventNotificationConsumer.kt | 24 +- .../nostrsigner/service/IntentUtils.kt | 488 +----------------- .../nostrsigner/service/NostrConnectUtils.kt | 164 ++++++ .../nostrsigner/service/NotificationUtils.kt | 2 +- .../greenart7c3/nostrsigner/ui/MainScreen.kt | 8 +- .../ui/components/MultiEventHomeScreen.kt | 28 +- 10 files changed, 564 insertions(+), 522 deletions(-) create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerRequestUtils.kt create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/service/NostrConnectUtils.kt diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt b/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt index 4c46a7d2..4e5e7a15 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt @@ -35,7 +35,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.rememberNavController import com.anggrayudi.storage.SimpleStorageHelper import com.greenart7c3.nostrsigner.service.Biometrics -import com.greenart7c3.nostrsigner.service.IntentUtils +import com.greenart7c3.nostrsigner.service.BunkerRequestUtils import com.greenart7c3.nostrsigner.ui.AccountScreen import com.greenart7c3.nostrsigner.ui.AccountStateViewModel import com.greenart7c3.nostrsigner.ui.BiometricsTimeType @@ -230,7 +230,7 @@ class MainActivity : AppCompatActivity() { } } launch { - IntentUtils.state + BunkerRequestUtils.state .receiveAsFlow() .collectLatest { mainViewModel.showBunkerRequests(null) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt b/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt index 6313204d..44518f7e 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt @@ -9,6 +9,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.NavHostController import com.greenart7c3.nostrsigner.models.IntentData +import com.greenart7c3.nostrsigner.service.BunkerRequestUtils import com.greenart7c3.nostrsigner.service.IntentUtils import com.greenart7c3.nostrsigner.ui.navigation.Route import com.vitorpamplona.quartz.encoders.toNpub @@ -35,7 +36,7 @@ class MainViewModel(val context: Context) : ViewModel() { fun showBunkerRequests(callingPackage: String?) { val requests = - IntentUtils.getBunkerRequests().map { + BunkerRequestUtils.getBunkerRequests().map { it.value.copy() } @@ -80,7 +81,7 @@ class MainViewModel(val context: Context) : ViewModel() { viewModelScope.launch(Dispatchers.IO) { delay(10000) - IntentUtils.clearRequests() + BunkerRequestUtils.clearRequests() } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt index 3c9a5c13..03a795c1 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt @@ -131,7 +131,7 @@ object AmberUtils { onRemoveIntentData: (IntentData, IntentResultType) -> Unit, onLoading: (Boolean) -> Unit, ) { - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( context, account, bunkerRequest, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerRequestUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerRequestUtils.kt new file mode 100644 index 00000000..f5b2b9a2 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerRequestUtils.kt @@ -0,0 +1,361 @@ +package com.greenart7c3.nostrsigner.service + +import android.content.Context +import android.content.Intent +import android.util.Log +import androidx.compose.runtime.mutableStateOf +import com.fasterxml.jackson.databind.ObjectMapper +import com.greenart7c3.nostrsigner.LocalPreferences +import com.greenart7c3.nostrsigner.NostrSigner +import com.greenart7c3.nostrsigner.models.Account +import com.greenart7c3.nostrsigner.models.BunkerRequest +import com.greenart7c3.nostrsigner.models.BunkerResponse +import com.greenart7c3.nostrsigner.models.CompressionType +import com.greenart7c3.nostrsigner.models.EncryptionType +import com.greenart7c3.nostrsigner.models.IntentData +import com.greenart7c3.nostrsigner.models.Permission +import com.greenart7c3.nostrsigner.models.ReturnType +import com.greenart7c3.nostrsigner.models.SignerType +import com.greenart7c3.nostrsigner.models.TimeUtils +import com.greenart7c3.nostrsigner.models.containsNip +import com.greenart7c3.nostrsigner.relays.AmberListenerSingleton +import com.greenart7c3.nostrsigner.service.IntentUtils.getUnsignedEvent +import com.greenart7c3.nostrsigner.service.model.AmberEvent +import com.vitorpamplona.ammolite.relays.Client +import com.vitorpamplona.ammolite.relays.RelayPool +import com.vitorpamplona.ammolite.relays.RelaySetupInfo +import com.vitorpamplona.quartz.encoders.toNpub +import com.vitorpamplona.quartz.events.Event +import java.util.concurrent.ConcurrentHashMap +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch + +object BunkerRequestUtils { + val state = Channel(Channel.CONFLATED) + private val bunkerRequests = ConcurrentHashMap() + + fun addRequest(request: BunkerRequest) { + bunkerRequests[request.id] = request + state.trySend("") + } + + fun clearRequests() { + bunkerRequests.clear() + state.trySend("") + } + + fun remove(id: String) { + bunkerRequests.remove(id) + state.trySend("") + } + + fun getBunkerRequests(): Map { + return bunkerRequests + } + + fun sendBunkerResponse( + context: Context, + account: Account, + bunkerRequest: BunkerRequest, + bunkerResponse: BunkerResponse, + relays: List, + onLoading: (Boolean) -> Unit, + onDone: (Boolean) -> Unit, + ) { + AmberListenerSingleton.getListener()?.let { + Client.unsubscribe(it) + } + AmberListenerSingleton.setListener( + context, + AmberListenerSingleton.accountStateViewModel, + ) + Client.subscribe( + AmberListenerSingleton.getListener()!!, + ) + + when (bunkerRequest.encryptionType) { + EncryptionType.NIP04 -> { + account.signer.nip04Encrypt( + ObjectMapper().writeValueAsString(bunkerResponse), + bunkerRequest.localKey, + ) { encryptedContent -> + sendBunkerResponse( + bunkerRequest = bunkerRequest, + account = account, + localKey = bunkerRequest.localKey, + encryptedContent = encryptedContent, + relays = relays.ifEmpty { NostrSigner.getInstance().getSavedRelays().toList() }, + onLoading = onLoading, + onDone = onDone, + ) + } + } + EncryptionType.NIP44 -> { + account.signer.nip44Encrypt( + ObjectMapper().writeValueAsString(bunkerResponse), + bunkerRequest.localKey, + ) { encryptedContent -> + sendBunkerResponse( + bunkerRequest = bunkerRequest, + account = account, + localKey = bunkerRequest.localKey, + encryptedContent = encryptedContent, + relays = relays.ifEmpty { NostrSigner.getInstance().getSavedRelays().toList() }, + onLoading = onLoading, + onDone = onDone, + ) + } + } + } + } + + private fun sendBunkerResponse( + bunkerRequest: BunkerRequest? = null, + account: Account, + localKey: String, + encryptedContent: String, + relays: List, + onLoading: (Boolean) -> Unit, + onDone: (Boolean) -> Unit, + ) { + account.signer.sign( + TimeUtils.now(), + 24133, + arrayOf(arrayOf("p", localKey)), + encryptedContent, + ) { + NostrSigner.getInstance().applicationIOScope.launch { + Log.d("IntentUtils", "Sending response to relays ${relays.map { it.url }} type ${bunkerRequest?.method}") + + if (RelayPool.getAll().any { !it.isConnected() }) { + NostrSigner.getInstance().checkForNewRelays( + newRelays = relays.toSet(), + ) + } + + val success = Client.sendAndWaitForResponse(it, relayList = relays) + if (success) { + Log.d("IntentUtils", "Success response to relays ${relays.map { it.url }} type ${bunkerRequest?.method}") + onDone(true) + } else { + onDone(false) + Log.d("IntentUtils", "Failed response to relays ${relays.map { it.url }} type ${bunkerRequest?.method}") + } + AmberListenerSingleton.getListener()?.let { + Client.unsubscribe(it) + } + onLoading(false) + } + } + } + + fun getTypeFromBunker(bunkerRequest: BunkerRequest): SignerType { + return when (bunkerRequest.method) { + "sign_message" -> SignerType.SIGN_MESSAGE + "connect" -> SignerType.CONNECT + "sign_event" -> SignerType.SIGN_EVENT + "get_public_key" -> SignerType.GET_PUBLIC_KEY + "nip04_encrypt" -> SignerType.NIP04_ENCRYPT + "nip04_decrypt" -> SignerType.NIP04_DECRYPT + "nip44_encrypt" -> SignerType.NIP44_ENCRYPT + "nip44_decrypt" -> SignerType.NIP44_DECRYPT + else -> SignerType.SIGN_EVENT + } + } + + fun getDataFromBunker(bunkerRequest: BunkerRequest): String { + return when (bunkerRequest.method) { + "sign_message" -> bunkerRequest.params.first() + "connect" -> "ack" + "sign_event" -> { + val amberEvent = AmberEvent.fromJson(bunkerRequest.params.first()) + AmberEvent.toEvent(amberEvent).toJson() + } + "nip04_encrypt", "nip04_decrypt", "nip44_encrypt", "nip44_decrypt" -> bunkerRequest.params.getOrElse(1) { "" } + else -> "" + } + } + + fun getIntentDataFromBunkerRequest( + context: Context, + intent: Intent, + bunkerRequest: BunkerRequest, + route: String?, + onReady: (IntentData?) -> Unit, + ) { + val type = getTypeFromBunker(bunkerRequest) + val data = getDataFromBunker(bunkerRequest) + val account = LocalPreferences.loadFromEncryptedStorage(context, bunkerRequest.currentAccount)!! + + val pubKey = + if (bunkerRequest.method.endsWith("encrypt") || bunkerRequest.method.endsWith("decrypt")) { + bunkerRequest.params.first() + } else { + "" + } + val id = bunkerRequest.id + val permissions = mutableListOf() + if (type == SignerType.CONNECT && bunkerRequest.params.size > 2) { + val split = bunkerRequest.params[2].split(",") + split.forEach { + if (it.isNotBlank()) { + val split2 = it.split(":") + val permissionType = split2.first() + val kind = + try { + split2[1].toInt() + } catch (_: Exception) { + null + } + + permissions.add( + Permission( + permissionType, + kind, + ), + ) + } + } + } + + permissions.removeIf { it.kind == null && (it.type == "sign_event" || it.type == "nip") } + permissions.removeIf { it.type == "nip" && (it.kind == null || !it.kind.containsNip()) } + + when (type) { + SignerType.CONNECT -> { + onReady( + IntentData( + data, + "", + type, + pubKey, + id, + intent.extras?.getString("callbackUrl"), + CompressionType.NONE, + ReturnType.EVENT, + permissions, + bunkerRequest.currentAccount, + mutableStateOf(true), + mutableStateOf(false), + bunkerRequest, + route, + null, + null, + ), + ) + } + + SignerType.SIGN_EVENT -> { + val unsignedEvent = getUnsignedEvent(data, account) + account.signer.sign( + unsignedEvent.createdAt, + unsignedEvent.kind, + unsignedEvent.tags, + unsignedEvent.content, + ) { + onReady( + IntentData( + data, + "", + type, + pubKey, + id, + intent.extras?.getString("callbackUrl"), + CompressionType.NONE, + ReturnType.EVENT, + permissions, + bunkerRequest.currentAccount, + mutableStateOf(true), + mutableStateOf(false), + bunkerRequest, + route, + it, + null, + ), + ) + } + } + + SignerType.NIP04_ENCRYPT, SignerType.NIP04_DECRYPT, SignerType.NIP44_ENCRYPT, SignerType.NIP44_DECRYPT, SignerType.DECRYPT_ZAP_EVENT -> { + val result = + try { + AmberUtils.encryptOrDecryptData( + data, + type, + account, + pubKey, + ) ?: "Could not decrypt the message" + } catch (e: Exception) { + "Could not decrypt the message" + } + + onReady( + IntentData( + data, + "", + type, + pubKey, + id, + intent.extras?.getString("callbackUrl"), + CompressionType.NONE, + ReturnType.EVENT, + permissions, + bunkerRequest.currentAccount, + mutableStateOf(true), + mutableStateOf(false), + bunkerRequest, + route, + null, + result, + ), + ) + } + + SignerType.GET_PUBLIC_KEY -> { + onReady( + IntentData( + account.signer.keyPair.pubKey.toNpub(), + "", + type, + pubKey, + id, + intent.extras?.getString("callbackUrl"), + CompressionType.NONE, + ReturnType.EVENT, + permissions, + bunkerRequest.currentAccount, + mutableStateOf(true), + mutableStateOf(false), + bunkerRequest, + route, + null, + null, + ), + ) + } + + SignerType.SIGN_MESSAGE -> { + onReady( + IntentData( + data, + "", + type, + pubKey, + id, + intent.extras?.getString("callbackUrl"), + CompressionType.NONE, + ReturnType.EVENT, + permissions, + bunkerRequest.currentAccount, + mutableStateOf(true), + mutableStateOf(false), + bunkerRequest, + route, + null, + null, + ), + ) + } + } + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/EventNotificationConsumer.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/EventNotificationConsumer.kt index f15ebd8d..1c7fb767 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/EventNotificationConsumer.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/EventNotificationConsumer.kt @@ -118,8 +118,8 @@ class EventNotificationConsumer(private val applicationContext: Context) { bunkerRequest.currentAccount = acc.signer.keyPair.pubKey.toNpub() bunkerRequest.encryptionType = encryptionType - val type = IntentUtils.getTypeFromBunker(bunkerRequest) - val data = IntentUtils.getDataFromBunker(bunkerRequest) + val type = BunkerRequestUtils.getTypeFromBunker(bunkerRequest) + val data = BunkerRequestUtils.getDataFromBunker(bunkerRequest) var amberEvent: AmberEvent? = null @@ -150,7 +150,7 @@ class EventNotificationConsumer(private val applicationContext: Context) { ), ) } - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( applicationContext, acc, bunkerRequest, @@ -159,7 +159,7 @@ class EventNotificationConsumer(private val applicationContext: Context) { onLoading = {}, onDone = { if (!it) { - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( applicationContext, acc, bunkerRequest, @@ -196,7 +196,7 @@ class EventNotificationConsumer(private val applicationContext: Context) { } else { "secret not in use" } - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( applicationContext, acc, bunkerRequest, @@ -205,7 +205,7 @@ class EventNotificationConsumer(private val applicationContext: Context) { onLoading = { }, onDone = { if (!it) { - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( applicationContext, acc, bunkerRequest, @@ -257,7 +257,7 @@ class EventNotificationConsumer(private val applicationContext: Context) { val relays = permission?.application?.relays ?: applicationWithSecret?.application?.relays ?: NostrSigner.getInstance().getSavedRelays().toList() if (permission == null && applicationWithSecret == null && !acc.allowNewConnections) { - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( applicationContext, acc, bunkerRequest, @@ -266,7 +266,7 @@ class EventNotificationConsumer(private val applicationContext: Context) { onLoading = { }, onDone = { if (!it) { - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( applicationContext, acc, bunkerRequest, @@ -296,7 +296,7 @@ class EventNotificationConsumer(private val applicationContext: Context) { } else { if (localCursor.moveToFirst()) { if (localCursor.getColumnIndex("rejected") > -1) { - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( applicationContext, acc, bunkerRequest, @@ -305,7 +305,7 @@ class EventNotificationConsumer(private val applicationContext: Context) { onLoading = { }, onDone = { if (!it) { - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( applicationContext, acc, bunkerRequest, @@ -324,7 +324,7 @@ class EventNotificationConsumer(private val applicationContext: Context) { } val result = localCursor.getString(index) - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( applicationContext, acc, bunkerRequest, @@ -333,7 +333,7 @@ class EventNotificationConsumer(private val applicationContext: Context) { onLoading = { }, onDone = { if (!it) { - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( applicationContext, acc, bunkerRequest, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt index 33541eb0..c08397a3 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt @@ -4,7 +4,6 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.provider.Browser -import android.util.Log import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.toLowerCase @@ -19,35 +18,20 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.greenart7c3.nostrsigner.LocalPreferences -import com.greenart7c3.nostrsigner.NostrSigner -import com.greenart7c3.nostrsigner.database.LogEntity import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.BunkerRequest -import com.greenart7c3.nostrsigner.models.BunkerResponse import com.greenart7c3.nostrsigner.models.CompressionType -import com.greenart7c3.nostrsigner.models.EncryptionType import com.greenart7c3.nostrsigner.models.IntentData import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.models.ReturnType import com.greenart7c3.nostrsigner.models.SignerType -import com.greenart7c3.nostrsigner.models.TimeUtils import com.greenart7c3.nostrsigner.models.containsNip -import com.greenart7c3.nostrsigner.relays.AmberListenerSingleton import com.greenart7c3.nostrsigner.service.model.AmberEvent -import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES -import com.vitorpamplona.ammolite.relays.Client -import com.vitorpamplona.ammolite.relays.RelayPool -import com.vitorpamplona.ammolite.relays.RelaySetupInfo import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.encoders.toNpub import com.vitorpamplona.quartz.events.Event import fr.acinq.secp256k1.Hex import java.net.URLDecoder -import java.util.UUID -import java.util.concurrent.ConcurrentHashMap -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.launch data class BunkerMetadata( val name: String, @@ -85,29 +69,7 @@ data class BunkerMetadata( } object IntentUtils { - val state = Channel(Channel.CONFLATED) - private val bunkerRequests = ConcurrentHashMap() - - fun addRequest(request: BunkerRequest) { - bunkerRequests[request.id] = request - state.trySend("") - } - - fun clearRequests() { - bunkerRequests.clear() - state.trySend("") - } - - fun remove(id: String) { - bunkerRequests.remove(id) - state.trySend("") - } - - fun getBunkerRequests(): Map { - return bunkerRequests - } - - private fun decodeData(data: String, replace: Boolean = true): String { + fun decodeData(data: String, replace: Boolean = true): String { if (!replace) return URLDecoder.decode(data.replace("nostrsigner:", ""), "utf-8") return URLDecoder.decode(data.replace("nostrsigner:", "").replace("+", "%2b"), "utf-8") } @@ -301,309 +263,6 @@ object IntentUtils { } } - fun getTypeFromBunker(bunkerRequest: BunkerRequest): SignerType { - return when (bunkerRequest.method) { - "sign_message" -> SignerType.SIGN_MESSAGE - "connect" -> SignerType.CONNECT - "sign_event" -> SignerType.SIGN_EVENT - "get_public_key" -> SignerType.GET_PUBLIC_KEY - "nip04_encrypt" -> SignerType.NIP04_ENCRYPT - "nip04_decrypt" -> SignerType.NIP04_DECRYPT - "nip44_encrypt" -> SignerType.NIP44_ENCRYPT - "nip44_decrypt" -> SignerType.NIP44_DECRYPT - else -> SignerType.SIGN_EVENT - } - } - - fun getDataFromBunker(bunkerRequest: BunkerRequest): String { - return when (bunkerRequest.method) { - "sign_message" -> bunkerRequest.params.first() - "connect" -> "ack" - "sign_event" -> { - val amberEvent = AmberEvent.fromJson(bunkerRequest.params.first()) - AmberEvent.toEvent(amberEvent).toJson() - } - "nip04_encrypt", "nip04_decrypt", "nip44_encrypt", "nip44_decrypt" -> bunkerRequest.params.getOrElse(1) { "" } - else -> "" - } - } - - fun sendBunkerResponse( - context: Context, - account: Account, - bunkerRequest: BunkerRequest, - bunkerResponse: BunkerResponse, - relays: List, - onLoading: (Boolean) -> Unit, - onDone: (Boolean) -> Unit, - ) { - AmberListenerSingleton.getListener()?.let { - Client.unsubscribe(it) - } - AmberListenerSingleton.setListener( - context, - AmberListenerSingleton.accountStateViewModel, - ) - Client.subscribe( - AmberListenerSingleton.getListener()!!, - ) - - when (bunkerRequest.encryptionType) { - EncryptionType.NIP04 -> { - account.signer.nip04Encrypt( - ObjectMapper().writeValueAsString(bunkerResponse), - bunkerRequest.localKey, - ) { encryptedContent -> - sendBunkerResponse( - bunkerRequest = bunkerRequest, - account = account, - localKey = bunkerRequest.localKey, - encryptedContent = encryptedContent, - relays = relays.ifEmpty { NostrSigner.getInstance().getSavedRelays().toList() }, - onLoading = onLoading, - onDone = onDone, - ) - } - } - EncryptionType.NIP44 -> { - account.signer.nip44Encrypt( - ObjectMapper().writeValueAsString(bunkerResponse), - bunkerRequest.localKey, - ) { encryptedContent -> - sendBunkerResponse( - bunkerRequest = bunkerRequest, - account = account, - localKey = bunkerRequest.localKey, - encryptedContent = encryptedContent, - relays = relays.ifEmpty { NostrSigner.getInstance().getSavedRelays().toList() }, - onLoading = onLoading, - onDone = onDone, - ) - } - } - } - } - - @OptIn(DelicateCoroutinesApi::class) - private fun sendBunkerResponse( - bunkerRequest: BunkerRequest? = null, - account: Account, - localKey: String, - encryptedContent: String, - relays: List, - onLoading: (Boolean) -> Unit, - onDone: (Boolean) -> Unit, - ) { - account.signer.sign( - TimeUtils.now(), - 24133, - arrayOf(arrayOf("p", localKey)), - encryptedContent, - ) { - NostrSigner.getInstance().applicationIOScope.launch { - Log.d("IntentUtils", "Sending response to relays ${relays.map { it.url }} type ${bunkerRequest?.method}") - - if (RelayPool.getAll().any { !it.isConnected() }) { - NostrSigner.getInstance().checkForNewRelays( - newRelays = relays.toSet(), - ) - } - - val success = Client.sendAndWaitForResponse(it, relayList = relays) - if (success) { - Log.d("IntentUtils", "Success response to relays ${relays.map { it.url }} type ${bunkerRequest?.method}") - onDone(true) - } else { - onDone(false) - Log.d("IntentUtils", "Failed response to relays ${relays.map { it.url }} type ${bunkerRequest?.method}") - } - AmberListenerSingleton.getListener()?.let { - Client.unsubscribe(it) - } - onLoading(false) - } - } - } - - private fun getIntentDataFromBunkerRequest( - context: Context, - intent: Intent, - bunkerRequest: BunkerRequest, - route: String?, - onReady: (IntentData?) -> Unit, - ) { - val type = getTypeFromBunker(bunkerRequest) - val data = getDataFromBunker(bunkerRequest) - val account = LocalPreferences.loadFromEncryptedStorage(context, bunkerRequest.currentAccount)!! - - val pubKey = - if (bunkerRequest.method.endsWith("encrypt") || bunkerRequest.method.endsWith("decrypt")) { - bunkerRequest.params.first() - } else { - "" - } - val id = bunkerRequest.id - val permissions = mutableListOf() - if (type == SignerType.CONNECT && bunkerRequest.params.size > 2) { - val split = bunkerRequest.params[2].split(",") - split.forEach { - if (it.isNotBlank()) { - val split2 = it.split(":") - val permissionType = split2.first() - val kind = - try { - split2[1].toInt() - } catch (_: Exception) { - null - } - - permissions.add( - Permission( - permissionType, - kind, - ), - ) - } - } - } - - permissions.removeIf { it.kind == null && (it.type == "sign_event" || it.type == "nip") } - permissions.removeIf { it.type == "nip" && (it.kind == null || !it.kind.containsNip()) } - - when (type) { - SignerType.CONNECT -> { - onReady( - IntentData( - data, - "", - type, - pubKey, - id, - intent.extras?.getString("callbackUrl"), - CompressionType.NONE, - ReturnType.EVENT, - permissions, - bunkerRequest.currentAccount, - mutableStateOf(true), - mutableStateOf(false), - bunkerRequest, - route, - null, - null, - ), - ) - } - SignerType.SIGN_EVENT -> { - val unsignedEvent = getUnsignedEvent(data, account) - account.signer.sign( - unsignedEvent.createdAt, - unsignedEvent.kind, - unsignedEvent.tags, - unsignedEvent.content, - ) { - onReady( - IntentData( - data, - "", - type, - pubKey, - id, - intent.extras?.getString("callbackUrl"), - CompressionType.NONE, - ReturnType.EVENT, - permissions, - bunkerRequest.currentAccount, - mutableStateOf(true), - mutableStateOf(false), - bunkerRequest, - route, - it, - null, - ), - ) - } - } - SignerType.NIP04_ENCRYPT, SignerType.NIP04_DECRYPT, SignerType.NIP44_ENCRYPT, SignerType.NIP44_DECRYPT, SignerType.DECRYPT_ZAP_EVENT -> { - val result = - try { - AmberUtils.encryptOrDecryptData( - data, - type, - account, - pubKey, - ) ?: "Could not decrypt the message" - } catch (e: Exception) { - "Could not decrypt the message" - } - - onReady( - IntentData( - data, - "", - type, - pubKey, - id, - intent.extras?.getString("callbackUrl"), - CompressionType.NONE, - ReturnType.EVENT, - permissions, - bunkerRequest.currentAccount, - mutableStateOf(true), - mutableStateOf(false), - bunkerRequest, - route, - null, - result, - ), - ) - } - SignerType.GET_PUBLIC_KEY -> { - onReady( - IntentData( - account.signer.keyPair.pubKey.toNpub(), - "", - type, - pubKey, - id, - intent.extras?.getString("callbackUrl"), - CompressionType.NONE, - ReturnType.EVENT, - permissions, - bunkerRequest.currentAccount, - mutableStateOf(true), - mutableStateOf(false), - bunkerRequest, - route, - null, - null, - ), - ) - } - SignerType.SIGN_MESSAGE -> { - onReady( - IntentData( - data, - "", - type, - pubKey, - id, - intent.extras?.getString("callbackUrl"), - CompressionType.NONE, - ReturnType.EVENT, - permissions, - bunkerRequest.currentAccount, - mutableStateOf(true), - mutableStateOf(false), - bunkerRequest, - route, - null, - null, - ), - ) - } - } - } - private fun getIntentDataFromIntent( context: Context, intent: Intent, @@ -801,147 +460,6 @@ object IntentUtils { } } - private fun metaDataFromJson(json: String): BunkerMetadata { - return BunkerMetadata.mapper.readValue(json, BunkerMetadata::class.java) - } - - private fun getIntentFromNostrConnect( - intent: Intent, - route: String?, - account: Account, - onReady: (IntentData?) -> Unit, - ) { - try { - val data = intent.dataString.toString().replace("nostrconnect://", "") - val split = data.split("?") - val relays: MutableList = mutableListOf() - var name = "" - val pubKey = split.first() - val parsedData = decodeData(split.drop(1).joinToString { it }) - val splitParsedData = parsedData.split("&") - val permissions = mutableListOf() - var nostrConnectSecret = "" - splitParsedData.forEach { - val internalSplit = it.split("=") - val paramName = internalSplit.first() - val json = internalSplit.mapIndexedNotNull { index, s -> - if (index == 0) null else s - }.joinToString { data -> data } - if (paramName == "relay") { - var relayUrl = json - if (relayUrl.endsWith("/")) relayUrl = relayUrl.dropLast(1) - relays.add( - RelaySetupInfo(relayUrl, read = true, write = true, feedTypes = COMMON_FEED_TYPES), - ) - } - if (paramName == "name") { - name = json - } - - if (paramName == "secret") { - nostrConnectSecret = json - } - - if (paramName == "perms") { - if (json.isNotEmpty()) { - val splitPerms = json.split(",") - splitPerms.forEach { perm -> - val split2 = perm.split(":") - val permissionType = split2.first() - val kind = - try { - split2[1].toInt() - } catch (_: Exception) { - null - } - - permissions.add( - Permission( - permissionType, - kind, - ), - ) - } - } - } - - if (paramName == "metadata") { - val bunkerMetada = metaDataFromJson(json) - name = bunkerMetada.name - if (bunkerMetada.perms.isNotEmpty()) { - val splitPerms = bunkerMetada.perms.split(",") - splitPerms.forEach { perm -> - val split2 = perm.split(":") - val permissionType = split2.first() - val kind = - try { - split2[1].toInt() - } catch (_: Exception) { - null - } - - permissions.add( - Permission( - permissionType, - kind, - ), - ) - } - } - } - } - - permissions.removeIf { it.kind == null && (it.type == "sign_event" || it.type == "nip") } - permissions.removeIf { it.type == "nip" && (it.kind == null || !it.kind.containsNip()) } - - onReady( - IntentData( - "ack", - name, - SignerType.CONNECT, - pubKey, - "", - null, - CompressionType.NONE, - ReturnType.EVENT, - permissions, - "", - mutableStateOf(true), - mutableStateOf(false), - BunkerRequest( - UUID.randomUUID().toString().substring(0, 4), - "connect", - arrayOf(pubKey), - pubKey, - relays.ifEmpty { NostrSigner.getInstance().getSavedRelays().toList() }, - "", - account.signer.keyPair.pubKey.toNpub(), - EncryptionType.NIP04, - nostrConnectSecret, - intent.getBooleanExtra("closeApplication", true), - ), - route, - null, - null, - ), - ) - } catch (e: Exception) { - Log.e("nostrconnect", e.message, e) - NostrSigner.getInstance().applicationIOScope.launch { - NostrSigner.getInstance().getDatabase(account.signer.keyPair.pubKey.toNpub()).applicationDao().insertLog( - LogEntity( - 0, - "nostrconnect", - "nostrconnect", - e.message ?: "", - System.currentTimeMillis(), - ), - ) - } - onReady(null) - } - } - fun getIntentData( context: Context, intent: Intent, @@ -983,9 +501,9 @@ object IntentUtils { } if (intent.dataString?.startsWith("nostrconnect:") == true) { - getIntentFromNostrConnect(intent, route, localAccount, onReady) + NostrConnectUtils.getIntentFromNostrConnect(intent, route, localAccount, onReady) } else if (bunkerRequest != null) { - getIntentDataFromBunkerRequest(context, intent, bunkerRequest, route, onReady) + BunkerRequestUtils.getIntentDataFromBunkerRequest(context, intent, bunkerRequest, route, onReady) } else if (intent.extras?.getString(Browser.EXTRA_APPLICATION_ID) == null) { getIntentDataFromIntent(context, intent, packageName, route, localAccount, onReady) } else { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/NostrConnectUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/NostrConnectUtils.kt new file mode 100644 index 00000000..446b3f90 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/NostrConnectUtils.kt @@ -0,0 +1,164 @@ +package com.greenart7c3.nostrsigner.service + +import android.content.Intent +import android.util.Log +import androidx.compose.runtime.mutableStateOf +import com.greenart7c3.nostrsigner.NostrSigner +import com.greenart7c3.nostrsigner.database.LogEntity +import com.greenart7c3.nostrsigner.models.Account +import com.greenart7c3.nostrsigner.models.BunkerRequest +import com.greenart7c3.nostrsigner.models.CompressionType +import com.greenart7c3.nostrsigner.models.EncryptionType +import com.greenart7c3.nostrsigner.models.IntentData +import com.greenart7c3.nostrsigner.models.Permission +import com.greenart7c3.nostrsigner.models.ReturnType +import com.greenart7c3.nostrsigner.models.SignerType +import com.greenart7c3.nostrsigner.models.containsNip +import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES +import com.vitorpamplona.ammolite.relays.RelaySetupInfo +import com.vitorpamplona.quartz.encoders.toNpub +import java.util.UUID +import kotlinx.coroutines.launch + +object NostrConnectUtils { + private fun metaDataFromJson(json: String): BunkerMetadata { + return BunkerMetadata.mapper.readValue(json, BunkerMetadata::class.java) + } + + fun getIntentFromNostrConnect( + intent: Intent, + route: String?, + account: Account, + onReady: (IntentData?) -> Unit, + ) { + try { + val data = intent.dataString.toString().replace("nostrconnect://", "") + val split = data.split("?") + val relays: MutableList = mutableListOf() + var name = "" + val pubKey = split.first() + val parsedData = IntentUtils.decodeData(split.drop(1).joinToString { it }) + val splitParsedData = parsedData.split("&") + val permissions = mutableListOf() + var nostrConnectSecret = "" + splitParsedData.forEach { + val internalSplit = it.split("=") + val paramName = internalSplit.first() + val json = internalSplit.mapIndexedNotNull { index, s -> + if (index == 0) null else s + }.joinToString { data -> data } + if (paramName == "relay") { + var relayUrl = json + if (relayUrl.endsWith("/")) relayUrl = relayUrl.dropLast(1) + relays.add( + RelaySetupInfo(relayUrl, read = true, write = true, feedTypes = COMMON_FEED_TYPES), + ) + } + if (paramName == "name") { + name = json + } + + if (paramName == "secret") { + nostrConnectSecret = json + } + + if (paramName == "perms") { + if (json.isNotEmpty()) { + val splitPerms = json.split(",") + splitPerms.forEach { perm -> + val split2 = perm.split(":") + val permissionType = split2.first() + val kind = + try { + split2[1].toInt() + } catch (_: Exception) { + null + } + + permissions.add( + Permission( + permissionType, + kind, + ), + ) + } + } + } + + if (paramName == "metadata") { + val bunkerMetada = metaDataFromJson(json) + name = bunkerMetada.name + if (bunkerMetada.perms.isNotEmpty()) { + val splitPerms = bunkerMetada.perms.split(",") + splitPerms.forEach { perm -> + val split2 = perm.split(":") + val permissionType = split2.first() + val kind = + try { + split2[1].toInt() + } catch (_: Exception) { + null + } + + permissions.add( + Permission( + permissionType, + kind, + ), + ) + } + } + } + } + + permissions.removeIf { it.kind == null && (it.type == "sign_event" || it.type == "nip") } + permissions.removeIf { it.type == "nip" && (it.kind == null || !it.kind.containsNip()) } + + onReady( + IntentData( + "ack", + name, + SignerType.CONNECT, + pubKey, + "", + null, + CompressionType.NONE, + ReturnType.EVENT, + permissions, + "", + mutableStateOf(true), + mutableStateOf(false), + BunkerRequest( + UUID.randomUUID().toString().substring(0, 4), + "connect", + arrayOf(pubKey), + pubKey, + relays.ifEmpty { NostrSigner.getInstance().getSavedRelays().toList() }, + "", + account.signer.keyPair.pubKey.toNpub(), + EncryptionType.NIP04, + nostrConnectSecret, + intent.getBooleanExtra("closeApplication", true), + ), + route, + null, + null, + ), + ) + } catch (e: Exception) { + Log.e("nostrconnect", e.message, e) + NostrSigner.getInstance().applicationIOScope.launch { + NostrSigner.getInstance().getDatabase(account.signer.keyPair.pubKey.toNpub()).applicationDao().insertLog( + LogEntity( + 0, + "nostrconnect", + "nostrconnect", + e.message ?: "", + System.currentTimeMillis(), + ), + ) + } + onReady(null) + } + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/NotificationUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/NotificationUtils.kt index 13a0a509..67e7a11e 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/NotificationUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/NotificationUtils.kt @@ -111,7 +111,7 @@ object NotificationUtils { PendingIntent.FLAG_MUTABLE, ) - IntentUtils.addRequest(bunkerRequest) + BunkerRequestUtils.addRequest(bunkerRequest) // Build the notification val builderPublic = diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt index 8856ee0c..2970ea1b 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -99,8 +99,8 @@ import com.greenart7c3.nostrsigner.models.ReturnType import com.greenart7c3.nostrsigner.models.SignerType import com.greenart7c3.nostrsigner.models.TimeUtils import com.greenart7c3.nostrsigner.models.basicPermissions +import com.greenart7c3.nostrsigner.service.BunkerRequestUtils import com.greenart7c3.nostrsigner.service.EventNotificationConsumer -import com.greenart7c3.nostrsigner.service.IntentUtils import com.greenart7c3.nostrsigner.service.NotificationDataSource import com.greenart7c3.nostrsigner.service.getAppCompatActivity import com.greenart7c3.nostrsigner.service.toShortenHex @@ -272,7 +272,7 @@ fun sendResult( if (intentData.bunkerRequest != null) { val localIntentData = intentData.copy() - IntentUtils.clearRequests() + BunkerRequestUtils.clearRequests() // assume that everything worked and try to revert it if it fails EventNotificationConsumer(context).notificationManager().cancelAll() @@ -291,7 +291,7 @@ fun sendResult( EventNotificationConsumer(context).notificationManager().cancelAll() onRemoveIntentData(intentData, IntentResultType.REMOVE) - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( context, account, intentData.bunkerRequest, @@ -312,7 +312,7 @@ fun sendResult( } onLoading(false) - IntentUtils.addRequest(localIntentData.bunkerRequest!!) + BunkerRequestUtils.addRequest(localIntentData.bunkerRequest!!) } else { activity?.intent = null if (application.application.closeApplication) { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MultiEventHomeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MultiEventHomeScreen.kt index 668fcf7f..18fd0260 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MultiEventHomeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MultiEventHomeScreen.kt @@ -28,7 +28,6 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -62,8 +61,8 @@ import com.greenart7c3.nostrsigner.models.SignerType import com.greenart7c3.nostrsigner.models.TimeUtils import com.greenart7c3.nostrsigner.service.AmberUtils import com.greenart7c3.nostrsigner.service.ApplicationNameCache +import com.greenart7c3.nostrsigner.service.BunkerRequestUtils import com.greenart7c3.nostrsigner.service.EventNotificationConsumer -import com.greenart7c3.nostrsigner.service.IntentUtils import com.greenart7c3.nostrsigner.service.MultiEventScreenIntents import com.greenart7c3.nostrsigner.service.getAppCompatActivity import com.greenart7c3.nostrsigner.service.toShortenHex @@ -88,7 +87,6 @@ fun MultiEventHomeScreen( onRemoveIntentData: (IntentData, IntentResultType) -> Unit, onLoading: (Boolean) -> Unit, ) { - val coroutineScope = rememberCoroutineScope() val context = LocalContext.current val grouped = intents.groupBy { it.type }.filter { it.key != SignerType.SIGN_EVENT } val grouped2 = intents.filter { it.type == SignerType.SIGN_EVENT }.groupBy { it.event?.kind } @@ -247,10 +245,10 @@ fun MultiEventHomeScreen( if (intentData.bunkerRequest != null) { val localIntentData = intentData.copy() onRemoveIntentData(intentData, IntentResultType.REMOVE) - IntentUtils.remove(intentData.bunkerRequest.id) + BunkerRequestUtils.remove(intentData.bunkerRequest.id) if (intentData.checked.value) { - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( context, localAccount, intentData.bunkerRequest, @@ -259,7 +257,7 @@ fun MultiEventHomeScreen( onLoading = {}, onDone = { if (!it) { - IntentUtils.addRequest(localIntentData.bunkerRequest!!) + BunkerRequestUtils.addRequest(localIntentData.bunkerRequest!!) } }, ) @@ -332,10 +330,10 @@ fun MultiEventHomeScreen( if (intentData.bunkerRequest != null) { val localIntentData = intentData.copy() onRemoveIntentData(intentData, IntentResultType.REMOVE) - IntentUtils.remove(intentData.bunkerRequest.id) + BunkerRequestUtils.remove(intentData.bunkerRequest.id) if (intentData.checked.value) { - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( context, localAccount, intentData.bunkerRequest, @@ -344,7 +342,7 @@ fun MultiEventHomeScreen( onLoading = {}, onDone = { if (!it) { - IntentUtils.addRequest(localIntentData.bunkerRequest!!) + BunkerRequestUtils.addRequest(localIntentData.bunkerRequest!!) } }, ) @@ -392,9 +390,9 @@ fun MultiEventHomeScreen( if (intentData.bunkerRequest != null) { val localIntentData = intentData.copy() onRemoveIntentData(intentData, IntentResultType.REMOVE) - IntentUtils.remove(intentData.bunkerRequest.id) + BunkerRequestUtils.remove(intentData.bunkerRequest.id) if (intentData.checked.value) { - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( context, localAccount, intentData.bunkerRequest, @@ -403,7 +401,7 @@ fun MultiEventHomeScreen( onLoading = {}, onDone = { if (!it) { - IntentUtils.addRequest(localIntentData.bunkerRequest!!) + BunkerRequestUtils.addRequest(localIntentData.bunkerRequest!!) } }, ) @@ -462,9 +460,9 @@ fun MultiEventHomeScreen( if (intentData.bunkerRequest != null) { val localIntentData = intentData.copy() onRemoveIntentData(intentData, IntentResultType.REMOVE) - IntentUtils.remove(intentData.bunkerRequest.id) + BunkerRequestUtils.remove(intentData.bunkerRequest.id) if (intentData.checked.value) { - IntentUtils.sendBunkerResponse( + BunkerRequestUtils.sendBunkerResponse( context, localAccount, intentData.bunkerRequest, @@ -473,7 +471,7 @@ fun MultiEventHomeScreen( onLoading = {}, onDone = { if (!it) { - IntentUtils.addRequest(localIntentData.bunkerRequest!!) + BunkerRequestUtils.addRequest(localIntentData.bunkerRequest!!) } }, )