move bunker and nostrconnect to its own classes

This commit is contained in:
greenart7c3
2024-12-30 12:04:59 -03:00
parent 1e9fac0b61
commit 64de43815f
10 changed files with 564 additions and 522 deletions
@@ -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)
@@ -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()
}
}
}
@@ -131,7 +131,7 @@ object AmberUtils {
onRemoveIntentData: (IntentData, IntentResultType) -> Unit,
onLoading: (Boolean) -> Unit,
) {
IntentUtils.sendBunkerResponse(
BunkerRequestUtils.sendBunkerResponse(
context,
account,
bunkerRequest,
@@ -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<String>(Channel.CONFLATED)
private val bunkerRequests = ConcurrentHashMap<String, BunkerRequest>()
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<String, BunkerRequest> {
return bunkerRequests
}
fun sendBunkerResponse(
context: Context,
account: Account,
bunkerRequest: BunkerRequest,
bunkerResponse: BunkerResponse,
relays: List<RelaySetupInfo>,
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<RelaySetupInfo>,
onLoading: (Boolean) -> Unit,
onDone: (Boolean) -> Unit,
) {
account.signer.sign<Event>(
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<Permission>()
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<Event>(
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,
),
)
}
}
}
}
@@ -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,
@@ -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<String>(Channel.CONFLATED)
private val bunkerRequests = ConcurrentHashMap<String, BunkerRequest>()
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<String, BunkerRequest> {
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<RelaySetupInfo>,
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<RelaySetupInfo>,
onLoading: (Boolean) -> Unit,
onDone: (Boolean) -> Unit,
) {
account.signer.sign<Event>(
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<Permission>()
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<Event>(
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<RelaySetupInfo> = mutableListOf()
var name = ""
val pubKey = split.first()
val parsedData = decodeData(split.drop(1).joinToString { it })
val splitParsedData = parsedData.split("&")
val permissions = mutableListOf<Permission>()
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 {
@@ -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<RelaySetupInfo> = mutableListOf()
var name = ""
val pubKey = split.first()
val parsedData = IntentUtils.decodeData(split.drop(1).joinToString { it })
val splitParsedData = parsedData.split("&")
val permissions = mutableListOf<Permission>()
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)
}
}
}
@@ -111,7 +111,7 @@ object NotificationUtils {
PendingIntent.FLAG_MUTABLE,
)
IntentUtils.addRequest(bunkerRequest)
BunkerRequestUtils.addRequest(bunkerRequest)
// Build the notification
val builderPublic =
@@ -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) {
@@ -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!!)
}
},
)