Sync using the new logic.
This commit is contained in:
1 parent
56ba03c2b3
commit
403b245880
12 files changed
+193
-188
No files matched your search
@@ -45,6 +45,7 @@ kotlin {
|
|||||||
commonMain.dependencies {
|
commonMain.dependencies {
|
||||||
api("fr.acinq.lightning:lightning-kmp-core:1.11.5")
|
api("fr.acinq.lightning:lightning-kmp-core:1.11.5")
|
||||||
|
|
||||||
|
implementation(libs.androidx.datastore)
|
||||||
implementation(libs.androidx.datastore.preferences)
|
implementation(libs.androidx.datastore.preferences)
|
||||||
|
|
||||||
implementation(libs.androidx.lifecycle.viewmodelCompose)
|
implementation(libs.androidx.lifecycle.viewmodelCompose)
|
||||||
|
|||||||
@@ -20,15 +20,28 @@ import kotlinx.coroutines.flow.timeout
|
|||||||
import kotlinx.coroutines.flow.transform
|
import kotlinx.coroutines.flow.transform
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ac.aux.compose.exceptions.NostrPublishException
|
import ac.aux.compose.exceptions.NostrPublishException
|
||||||
|
import ac.aux.compose.network.dto.toRelayDTO
|
||||||
import ac.aux.compose.network.sockets.NostrIncomingMessage
|
import ac.aux.compose.network.sockets.NostrIncomingMessage
|
||||||
import ac.aux.compose.network.sockets.NostrSocketClient
|
import ac.aux.compose.network.sockets.NostrSocketClient
|
||||||
import ac.aux.compose.network.sockets.NostrSocketClientFactory
|
import ac.aux.compose.network.sockets.NostrSocketClientFactory
|
||||||
import ac.aux.compose.network.sockets.SocketConnectionClosedCallback
|
import ac.aux.compose.network.sockets.SocketConnectionClosedCallback
|
||||||
import ac.aux.compose.network.sockets.SocketConnectionOpenedCallback
|
import ac.aux.compose.network.sockets.SocketConnectionOpenedCallback
|
||||||
import ac.aux.compose.network.sockets.filterByEventId
|
import ac.aux.compose.network.sockets.filterByEventId
|
||||||
import ac.aux.compose.network.sockets.parseIncomingMessage
|
import ac.aux.compose.network.sockets.filterBySubscriptionId
|
||||||
|
import ac.aux.compose.network.sockets.verifyOrThrow
|
||||||
import ac.aux.compose.repository.CachingImportRepository
|
import ac.aux.compose.repository.CachingImportRepository
|
||||||
import co.touchlab.kermit.Logger
|
import co.touchlab.kermit.Logger
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.toList
|
||||||
|
import kotlinx.coroutines.flow.transformWhile
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As seen in Primal
|
* As seen in Primal
|
||||||
@@ -114,10 +127,11 @@ class RelayPool(
|
|||||||
|
|
||||||
val toAddRelayUrls = newRelayUrls.filter { it !in existingRelayUrls }
|
val toAddRelayUrls = newRelayUrls.filter { it !in existingRelayUrls }
|
||||||
val toAddSocketClients = relays.filter { it.url in toAddRelayUrls }.mapAsNostrSocketClient()
|
val toAddSocketClients = relays.filter { it.url in toAddRelayUrls }.mapAsNostrSocketClient()
|
||||||
|
logger.d("toAddSocketClients: ${toAddSocketClients.map { it.socketUrl }}" )
|
||||||
val newSocketClients = socketClients.toMutableList().apply {
|
val newSocketClients = socketClients.toMutableList().apply {
|
||||||
addAll(toAddSocketClients)
|
addAll(toAddSocketClients)
|
||||||
}
|
}
|
||||||
|
logger.d("newSocketClients: ${newSocketClients.map { it.socketUrl }}")
|
||||||
|
|
||||||
socketClients = newSocketClients
|
socketClients = newSocketClients
|
||||||
|
|
||||||
@@ -165,30 +179,60 @@ class RelayPool(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleBroadcastEventThroughCachingProxy(relayUrls: List<String>, nostrEvent: NostrEvent) {
|
@OptIn(FlowPreview::class)
|
||||||
val result =
|
suspend fun query(reqCommand: ReqCmd, relayUrl: String): Pair<NostrIncomingMessage, List<NostrEvent>> {
|
||||||
cachingImportRepository.broadcastEvents(
|
addRelays(
|
||||||
events = listOf(nostrEvent),
|
setOf(
|
||||||
relays = relayUrls,
|
NormalizedRelayUrl(relayUrl).url.toRelayDTO()
|
||||||
).getOrNull()
|
|
||||||
?: throw NostrPublishException(
|
|
||||||
cause = NetworkException(message = "Primal NostrEvent 10_000_149 not found or invalid."),
|
|
||||||
)
|
|
||||||
|
|
||||||
result.find { response -> response.eventId == nostrEvent.id }?.responses
|
|
||||||
?.mapNotNull { relayResponse ->
|
|
||||||
val relay = relayResponse.firstOrNull()
|
|
||||||
val responseMessage = relayResponse.getOrNull(index = 1)?.parseIncomingMessage()
|
|
||||||
if (relay != null && responseMessage != null) {
|
|
||||||
relay to responseMessage
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?.find { (_, relayMessage) -> relayMessage is NostrIncomingMessage.OkMessage && relayMessage.success }
|
|
||||||
?: throw NostrPublishException(
|
|
||||||
cause = NetworkException("Event broadcast failed. Could not find success response from relays."),
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.d("socketClients: ${socketClients.map { it.socketUrl }}")
|
||||||
|
val nostrSocketClient = socketClients.find { NormalizedRelayUrl(it.socketUrl).displayUrl() == NormalizedRelayUrl(relayUrl).displayUrl() }
|
||||||
|
|
||||||
|
val filterRequest = OptimizedJsonMapper.toJson(reqCommand)
|
||||||
|
|
||||||
|
if (nostrSocketClient == null) {
|
||||||
|
throw NetworkException("$relayUrl is not connected")
|
||||||
|
}
|
||||||
|
return coroutineScope {
|
||||||
|
val deferredQueryResult = async { nostrSocketClient.collectQueryResult(reqCommand.subId) }
|
||||||
|
with(nostrSocketClient) {
|
||||||
|
sendMESSAGE(filterRequest)
|
||||||
|
}
|
||||||
|
deferredQueryResult.await()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Flow<NostrIncomingMessage>.transformWhileEventsAreIncoming() =
|
||||||
|
transformWhile {
|
||||||
|
emit(it)
|
||||||
|
it is NostrIncomingMessage.EventMessage || it is NostrIncomingMessage.EventsMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
private suspend fun NostrSocketClient.collectQueryResult(subscriptionId: String): Pair<NostrIncomingMessage, List<NostrEvent>> {
|
||||||
|
val messages = this.incomingMessages
|
||||||
|
.filterBySubscriptionId(id = subscriptionId)
|
||||||
|
.transformWhileEventsAreIncoming()
|
||||||
|
.timeout(15.seconds)
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
val terminationMessage = messages.lastOrNull()
|
||||||
|
terminationMessage.verifyOrThrow(subscriptionId)
|
||||||
|
checkNotNull(terminationMessage)
|
||||||
|
|
||||||
|
val eventMessages = messages.filterIsInstance<NostrIncomingMessage.EventMessage>()
|
||||||
|
val eventsMessage = messages.filterIsInstance<NostrIncomingMessage.EventsMessage>()
|
||||||
|
|
||||||
|
val allNostrEvents = eventMessages.mapNotNull { it.nostrEvent } +
|
||||||
|
eventsMessage.map { it.nostrEvents }.flatten()
|
||||||
|
|
||||||
|
|
||||||
|
return Pair(
|
||||||
|
terminationMessage,
|
||||||
|
allNostrEvents,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
|
|||||||
+25
-18
@@ -16,6 +16,12 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import ac.aux.compose.exceptions.NostrPublishException
|
import ac.aux.compose.exceptions.NostrPublishException
|
||||||
|
import ac.aux.compose.managers.CredentialsManager
|
||||||
|
import ac.aux.compose.managers.SeedManager
|
||||||
|
import ac.aux.compose.managers.bech32ToHexOrNull
|
||||||
|
import ac.aux.compose.managers.toHex
|
||||||
|
import ac.aux.compose.network.sockets.NostrIncomingMessage
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,17 +32,12 @@ import ac.aux.compose.exceptions.NostrPublishException
|
|||||||
class RelaysSocketManager constructor(
|
class RelaysSocketManager constructor(
|
||||||
private val nostrSocketClientFactory: NostrSocketClientFactory,
|
private val nostrSocketClientFactory: NostrSocketClientFactory,
|
||||||
private val cachingImportRepository: CachingImportRepository,
|
private val cachingImportRepository: CachingImportRepository,
|
||||||
// private val activeAccountStore: ActiveAccountStore,
|
|
||||||
// private val usersDatabase: UsersDatabase,
|
|
||||||
private val relayRepository: RelayRepository
|
private val relayRepository: RelayRepository
|
||||||
) {
|
) {
|
||||||
val logger = Logger.withTag("RelaysSocketManager")
|
val logger = Logger.withTag("RelaysSocketManager")
|
||||||
private val scope = CoroutineScope(Dispatchers.IO)
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
private val relayPoolsMutex = Mutex()
|
private val relayPoolsMutex = Mutex()
|
||||||
|
|
||||||
private var relaysObserverJob: Job? = null
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concrete relay pool
|
* Concrete relay pool
|
||||||
*/
|
*/
|
||||||
@@ -56,27 +57,26 @@ class RelaysSocketManager constructor(
|
|||||||
observeActiveUserId()
|
observeActiveUserId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val observeRelayJobs = mutableMapOf<String, Job>()
|
||||||
|
|
||||||
private fun observeActiveUserId() =
|
private fun observeActiveUserId() =
|
||||||
scope.launch {
|
scope.launch {
|
||||||
// TODO activeAccountStore.activeUserId.collect { userId ->
|
SeedManager.activePublicKey().toHex()?.let { publicKey ->
|
||||||
// when {
|
observeRelayJobs[publicKey]?.cancel()
|
||||||
// userId.isEmpty() -> {
|
|
||||||
// relaysObserverJob?.cancel()
|
observeRelayJobs[publicKey] = observeRelays(publicKey)
|
||||||
// relaysObserverJob = null
|
}
|
||||||
// clearRelayPools()
|
// credentialsManager.credentials.collect { credentials ->
|
||||||
// }
|
// credentials.forEach { credential ->
|
||||||
|
// credential.npub.bech32ToHexOrNull()?.let { publicKey ->
|
||||||
|
// observeRelayJobs[publicKey]?.cancel()
|
||||||
//
|
//
|
||||||
// else -> {
|
// observeRelayJobs[publicKey] = observeRelays(publicKey)
|
||||||
// relaysObserverJob?.cancel()
|
|
||||||
// relaysObserverJob = observeRelays(userId)
|
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun isCachingProxyEnabled() = false // TODO: activeAccountStore.activeUserAccount().cachingProxyEnabled
|
|
||||||
|
|
||||||
private fun observeRelays(publicKey: String): Job =
|
private fun observeRelays(publicKey: String): Job =
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
@@ -129,7 +129,7 @@ class RelaysSocketManager constructor(
|
|||||||
// customPool.closePool()
|
// customPool.closePool()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tryConnectingToAllUserRelays() {
|
fun tryConnectingToAllRelays() {
|
||||||
relayPool.relays.forEach {
|
relayPool.relays.forEach {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
relayPool.tryConnectingToRelay(it.url)
|
relayPool.tryConnectingToRelay(it.url)
|
||||||
@@ -138,4 +138,11 @@ class RelaysSocketManager constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun tryConnectingToUserRelay(url: String) = relayPool.tryConnectingToRelay(url)
|
suspend fun tryConnectingToUserRelay(url: String) = relayPool.tryConnectingToRelay(url)
|
||||||
|
|
||||||
|
suspend fun query(reqCommand: ReqCmd, relayUrl: String): Pair<NostrIncomingMessage, List<NostrEvent>> {
|
||||||
|
return relayPool.query(
|
||||||
|
reqCommand = reqCommand,
|
||||||
|
relayUrl = relayUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+15
@@ -1,6 +1,8 @@
|
|||||||
package ac.aux.compose.network.sockets
|
package ac.aux.compose.network.sockets
|
||||||
|
|
||||||
import ac.aux.compose.database.model.NostrEvent
|
import ac.aux.compose.database.model.NostrEvent
|
||||||
|
import ac.aux.compose.exceptions.NetworkException
|
||||||
|
import ac.aux.compose.exceptions.NostrNoticeException
|
||||||
|
|
||||||
sealed class NostrIncomingMessage {
|
sealed class NostrIncomingMessage {
|
||||||
|
|
||||||
@@ -38,3 +40,16 @@ sealed class NostrIncomingMessage {
|
|||||||
val nostrEvents: List<NostrEvent> = emptyList(),
|
val nostrEvents: List<NostrEvent> = emptyList(),
|
||||||
) : NostrIncomingMessage()
|
) : NostrIncomingMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun NostrIncomingMessage?.verifyOrThrow(subscriptionId: String) {
|
||||||
|
if (this == null) {
|
||||||
|
throw NetworkException("No messages received.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this is NostrIncomingMessage.NoticeMessage) {
|
||||||
|
throw NostrNoticeException(
|
||||||
|
reason = this.message,
|
||||||
|
subscriptionId = subscriptionId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,4 +26,6 @@ interface NostrSocketClient {
|
|||||||
suspend fun sendEVENT(signedEvent: JsonObject)
|
suspend fun sendEVENT(signedEvent: JsonObject)
|
||||||
|
|
||||||
suspend fun sendREQ(subscriptionId: String, data: JsonObject)
|
suspend fun sendREQ(subscriptionId: String, data: JsonObject)
|
||||||
|
|
||||||
|
suspend fun sendMESSAGE(text: String, ensureSessionBeforeSend: Boolean = true)
|
||||||
}
|
}
|
||||||
+7
-7
@@ -73,7 +73,7 @@ internal class NostrSocketClientImpl(
|
|||||||
onSocketConnectionOpened?.invoke(url)
|
onSocketConnectionOpened?.invoke(url)
|
||||||
if (incomingCompressionEnabled) {
|
if (incomingCompressionEnabled) {
|
||||||
val id = Uuid.generateV4().toHexDashString()
|
val id = Uuid.generateV4().toHexDashString()
|
||||||
sendMessage(
|
sendMESSAGE(
|
||||||
text = """["REQ","$id",{"cache":["set_primal_protocol",{"compression":"zlib"}]}]""",
|
text = """["REQ","$id",{"cache":["set_primal_protocol",{"compression":"zlib"}]}]""",
|
||||||
ensureSessionBeforeSend = false,
|
ensureSessionBeforeSend = false,
|
||||||
)
|
)
|
||||||
@@ -155,7 +155,7 @@ internal class NostrSocketClientImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendMessage(text: String, ensureSessionBeforeSend: Boolean = true) {
|
override suspend fun sendMESSAGE(text: String, ensureSessionBeforeSend: Boolean) {
|
||||||
if (ensureSessionBeforeSend) {
|
if (ensureSessionBeforeSend) {
|
||||||
ensureSocketConnectionOrThrow()
|
ensureSocketConnectionOrThrow()
|
||||||
}
|
}
|
||||||
@@ -165,21 +165,21 @@ internal class NostrSocketClientImpl(
|
|||||||
|
|
||||||
override suspend fun sendREQ(subscriptionId: String, data: JsonObject) {
|
override suspend fun sendREQ(subscriptionId: String, data: JsonObject) {
|
||||||
val reqMessage = data.buildNostrREQMessage(subscriptionId)
|
val reqMessage = data.buildNostrREQMessage(subscriptionId)
|
||||||
return sendMessage(text = reqMessage)
|
return sendMESSAGE(text = reqMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun sendCOUNT(data: JsonObject): String {
|
override suspend fun sendCOUNT(data: JsonObject): String {
|
||||||
val subscriptionId: String = Uuid.generateV4().toHexDashString()
|
val subscriptionId: String = Uuid.generateV4().toHexDashString()
|
||||||
val reqMessage = data.buildNostrCOUNTMessage(subscriptionId)
|
val reqMessage = data.buildNostrCOUNTMessage(subscriptionId)
|
||||||
sendMessage(text = reqMessage)
|
sendMESSAGE(text = reqMessage)
|
||||||
return subscriptionId
|
return subscriptionId
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun sendCLOSE(subscriptionId: String) = sendMessage(text = subscriptionId.buildNostrCLOSEMessage())
|
override suspend fun sendCLOSE(subscriptionId: String) = sendMESSAGE(text = subscriptionId.buildNostrCLOSEMessage())
|
||||||
|
|
||||||
override suspend fun sendEVENT(signedEvent: JsonObject) = sendMessage(text = signedEvent.buildNostrEVENTMessage())
|
override suspend fun sendEVENT(signedEvent: JsonObject) = sendMESSAGE(text = signedEvent.buildNostrEVENTMessage())
|
||||||
|
|
||||||
override suspend fun sendAUTH(signedEvent: JsonObject) = sendMessage(text = signedEvent.buildNostrAUTHMessage())
|
override suspend fun sendAUTH(signedEvent: JsonObject) = sendMESSAGE(text = signedEvent.buildNostrAUTHMessage())
|
||||||
|
|
||||||
private fun logLargeText(
|
private fun logLargeText(
|
||||||
text: String,
|
text: String,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ object Relays {
|
|||||||
|
|
||||||
val bootstrapInboxRelaySet = setOf(damus, primal, mom, nos, bitcoiner, oxtr, yabu)
|
val bootstrapInboxRelaySet = setOf(damus, primal, mom, nos, bitcoiner, oxtr, yabu)
|
||||||
val eventFinderRelaySet = setOf(wine, damus, primal, mom, nos, bitcoiner, oxtr)
|
val eventFinderRelaySet = setOf(wine, damus, primal, mom, nos, bitcoiner, oxtr)
|
||||||
val eventPublishRelaySet = setOf(damus, primal, mom, nos, bitcoiner, oxtr)
|
val eventPublishRelaySet = setOf(primal, mom, nos, bitcoiner, oxtr)
|
||||||
|
|
||||||
|
|
||||||
val DefaultNIP65RelaySet = setOf(mom, nos, bitcoiner)
|
val DefaultNIP65RelaySet = setOf(mom, nos, bitcoiner)
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import ac.aux.compose.exceptions.SignatureException
|
|||||||
import ac.aux.compose.exceptions.SigningKeyNotFoundException
|
import ac.aux.compose.exceptions.SigningKeyNotFoundException
|
||||||
import ac.aux.compose.exceptions.SigningRejectedException
|
import ac.aux.compose.exceptions.SigningRejectedException
|
||||||
import ac.aux.compose.managers.CredentialsManager
|
import ac.aux.compose.managers.CredentialsManager
|
||||||
|
import ac.aux.compose.managers.SeedManager
|
||||||
import ac.aux.compose.managers.hexToNpubHrp
|
import ac.aux.compose.managers.hexToNpubHrp
|
||||||
import ac.aux.compose.network.UserAgent
|
import ac.aux.compose.network.UserAgent
|
||||||
import ac.aux.compose.network.asClientTag
|
import ac.aux.compose.network.asClientTag
|
||||||
import ac.aux.compose.network.dto.RelayDTO
|
import ac.aux.compose.network.dto.RelayDTO
|
||||||
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||||
|
import com.vitorpamplona.quartz.nip19Bech32.toNsec
|
||||||
import com.vitorpamplona.quartz.nip51Lists.relayLists.RelayFeedsListEvent
|
import com.vitorpamplona.quartz.nip51Lists.relayLists.RelayFeedsListEvent
|
||||||
import fr.acinq.secp256k1.Hex
|
import fr.acinq.secp256k1.Hex
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -31,7 +33,6 @@ import kotlinx.coroutines.sync.withLock
|
|||||||
*/
|
*/
|
||||||
class NostrNotaryRepository(
|
class NostrNotaryRepository(
|
||||||
private val nostrRepository: NostrRepository,
|
private val nostrRepository: NostrRepository,
|
||||||
private val credentialsStore: CredentialsManager,
|
|
||||||
) {
|
) {
|
||||||
private val scope = CoroutineScope(Dispatchers.Main)
|
private val scope = CoroutineScope(Dispatchers.Main)
|
||||||
|
|
||||||
@@ -75,13 +76,15 @@ class NostrNotaryRepository(
|
|||||||
|
|
||||||
private fun findNsecOrThrow(pubkey: String): String =
|
private fun findNsecOrThrow(pubkey: String): String =
|
||||||
runCatching {
|
runCatching {
|
||||||
val npub = Hex.decode(pubkey).toNpub()
|
// val npub = Hex.decode(pubkey).toNpub()
|
||||||
credentialsStore.findOrThrow(npub = npub).nsec
|
// credentialsStore.findOrThrow(npub = npub).nsec
|
||||||
|
SeedManager.activeKeyPair().privKey?.toNsec()
|
||||||
}.getOrNull() ?: throw SigningKeyNotFoundException()
|
}.getOrNull() ?: throw SigningKeyNotFoundException()
|
||||||
|
|
||||||
private fun signNostrEvent(publicKey: String, event: UnsignedNostrEvent): NostrEvent? {
|
private fun signNostrEvent(publicKey: String, event: UnsignedNostrEvent): NostrEvent? {
|
||||||
val isExternalSignerLogin = runCatching {
|
val isExternalSignerLogin = runCatching {
|
||||||
credentialsStore.isExternalSignerCredential(npub = publicKey.hexToNpubHrp())
|
// credentialsStore.isExternalSignerCredential(npub = publicKey.hexToNpubHrp())
|
||||||
|
false
|
||||||
}.getOrDefault(false)
|
}.getOrDefault(false)
|
||||||
|
|
||||||
if (isExternalSignerLogin) {
|
if (isExternalSignerLogin) {
|
||||||
|
|||||||
+17
-133
@@ -59,11 +59,9 @@ class NavigationViewModel(
|
|||||||
val relaysSocketManager = RelaysSocketManager(
|
val relaysSocketManager = RelaysSocketManager(
|
||||||
nostrSocketClientFactory = NostrSocketClientFactory,
|
nostrSocketClientFactory = NostrSocketClientFactory,
|
||||||
cachingImportRepository = CachingImportRepository.NO_OP_CACHING_IMPORT_REPOSITORY,
|
cachingImportRepository = CachingImportRepository.NO_OP_CACHING_IMPORT_REPOSITORY,
|
||||||
relayRepository = relayRepository
|
relayRepository = relayRepository,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "NavigationViewModel"
|
private const val TAG = "NavigationViewModel"
|
||||||
|
|
||||||
@@ -86,8 +84,6 @@ class NavigationViewModel(
|
|||||||
|
|
||||||
private val logger = Logger.withTag(TAG)
|
private val logger = Logger.withTag(TAG)
|
||||||
|
|
||||||
private val syncingJobs = mutableMapOf<String, Job>()
|
|
||||||
|
|
||||||
val httpClient = HttpClient() {
|
val httpClient = HttpClient() {
|
||||||
install(WebSockets) {
|
install(WebSockets) {
|
||||||
contentConverter = KotlinxWebsocketSerializationConverter(Json {
|
contentConverter = KotlinxWebsocketSerializationConverter(Json {
|
||||||
@@ -98,10 +94,6 @@ class NavigationViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val nostrEventBroadcaster = NostrEventBroadcaster(
|
|
||||||
scope = scope
|
|
||||||
)
|
|
||||||
|
|
||||||
private val _navigationUIState = MutableStateFlow(
|
private val _navigationUIState = MutableStateFlow(
|
||||||
initialNavigationUIState
|
initialNavigationUIState
|
||||||
)
|
)
|
||||||
@@ -156,8 +148,6 @@ class NavigationViewModel(
|
|||||||
private fun observePendingSyncNostrEventRequests() {
|
private fun observePendingSyncNostrEventRequests() {
|
||||||
logger.i { "observePendingSyncNostrEventRequests" }
|
logger.i { "observePendingSyncNostrEventRequests" }
|
||||||
|
|
||||||
val syncEventCache = mutableMapOf<String, NostrEvent>()
|
|
||||||
|
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
nostrRepository.observePendingSynchronizeNostrEventRequests().collect { synchronizeNostrEventRequestOrNull ->
|
nostrRepository.observePendingSynchronizeNostrEventRequests().collect { synchronizeNostrEventRequestOrNull ->
|
||||||
synchronizeNostrEventRequestOrNull?.let { synchronizeNostrEventRequest ->
|
synchronizeNostrEventRequestOrNull?.let { synchronizeNostrEventRequest ->
|
||||||
@@ -179,120 +169,32 @@ class NavigationViewModel(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
val webSocketSession = httpClient.webSocketSession(
|
|
||||||
urlString = synchronizeNostrEventRequest.relayURL
|
val result = relaysSocketManager.query(
|
||||||
|
reqCommand,
|
||||||
|
synchronizeNostrEventRequest.relayURL
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: don't make a new sync job if there's an active job?
|
logger.d("Result: $result")
|
||||||
syncingJobs[synchronizeNostrEventRequest.relayURL] = scope.launch {
|
|
||||||
val filterRequest = OptimizedJsonMapper.toJson(reqCommand)
|
result.second.forEach { nostrEvent ->
|
||||||
logger.d("Syncing: $filterRequest")
|
nostrRepository.saveNostrEvent(
|
||||||
webSocketSession.send(
|
nostrEvent = nostrEvent,
|
||||||
frame = Frame.Text(filterRequest)
|
synchronizeNostrEventRequest,
|
||||||
|
synchronizationRelayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url } + synchronizeNostrEventRequest.relayURL
|
||||||
)
|
)
|
||||||
|
|
||||||
nostrRepository.synchronizeNostrEventRequestProcessed(synchronizeNostrEventRequest)
|
|
||||||
|
|
||||||
webSocketSession.incoming.consumeAsFlow().collect { frame ->
|
|
||||||
when(frame) {
|
|
||||||
is Frame.Text -> {
|
|
||||||
frame.readText().let { text ->
|
|
||||||
try {
|
|
||||||
logger.d("WebSocket Read Text: $text")
|
|
||||||
val result = Json.decodeFromString<JsonArray>(text)
|
|
||||||
|
|
||||||
if (result.firstOrNull()?.text == "EVENT") {
|
|
||||||
result.getOrNull(1)?.text?.let { subscriptionId ->
|
|
||||||
if (subscriptionId == synchronizeNostrEventRequest.id) {
|
|
||||||
|
|
||||||
result.getOrNull(2)?.jsonObject?.toString()?.let { jsonText ->
|
|
||||||
logger.i("Processing: $jsonText")
|
|
||||||
val event = Event.fromJson(
|
|
||||||
jsonText
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: Do profile specific cache (to avoid wiping an updated profile)
|
|
||||||
syncEventCache.getOrPut(event.id, {
|
|
||||||
val taggedEvent = event.firstTaggedEvent()
|
|
||||||
val taggedUser = event.firstTaggedUser()
|
|
||||||
|
|
||||||
val nostrEvent = NostrEvent(
|
|
||||||
id = event.id,
|
|
||||||
pubKey = event.pubKey,
|
|
||||||
kind = event.kind,
|
|
||||||
content = event.content,
|
|
||||||
tags = event.tags,
|
|
||||||
createdAt = Instant.fromEpochSeconds(event.createdAt),
|
|
||||||
unsignedNostrEventId = synchronizeNostrEventRequest.unsignedNostrEventId,
|
|
||||||
sig = event.sig,
|
|
||||||
|
|
||||||
taggedNostrEventId = taggedEvent?.eventId,
|
|
||||||
taggedNostrEventRelayUrl = taggedEvent?.relay?.url,
|
|
||||||
|
|
||||||
taggedPublicKey = taggedUser?.pubKey,
|
|
||||||
taggedPublicKeyRelayUrl = taggedUser?.relayHint?.url
|
|
||||||
)
|
|
||||||
|
|
||||||
nostrRepository.saveNostrEvent(
|
|
||||||
nostrEvent = nostrEvent,
|
|
||||||
synchronizeNostrEventRequest,
|
|
||||||
synchronizationRelayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url }
|
|
||||||
)
|
|
||||||
|
|
||||||
nostrEvent
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(result.firstOrNull()?.text == "EOSE") {
|
|
||||||
result.getOrNull(1)?.text?.let { subscriptionId ->
|
|
||||||
if (subscriptionId == synchronizeNostrEventRequest.id) {
|
|
||||||
// Close the job
|
|
||||||
logger.d("We need to close the job")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.d("Unsupported: $text")
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
logger.e("Error processing receipt: $text", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Syncing doesn't necessary have to close after result..
|
|
||||||
webSocketSession.close()
|
|
||||||
syncingJobs.remove(synchronizeNostrEventRequest.relayURL)?.cancel()
|
|
||||||
}
|
|
||||||
is Frame.Close -> {
|
|
||||||
logger.d("Close $frame")
|
|
||||||
}
|
|
||||||
is Frame.Ping -> {
|
|
||||||
logger.d("Ping: $frame")
|
|
||||||
}
|
|
||||||
is Frame.Pong -> {
|
|
||||||
logger.d("Pong: $frame")
|
|
||||||
}
|
|
||||||
is Frame.Binary -> {
|
|
||||||
logger.d("Binary Message: $frame")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
logger.d("Unsupported frame: $frame")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.i("Websocket exit")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
syncingJobs[synchronizeNostrEventRequest.relayURL]?.join()
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
logger.e("Relay Error (${synchronizeNostrEventRequest.relayURL})", e)
|
|
||||||
// TODO: Have it failed instead of processed...
|
|
||||||
nostrRepository.synchronizeNostrEventRequestProcessed(synchronizeNostrEventRequest)
|
nostrRepository.synchronizeNostrEventRequestProcessed(synchronizeNostrEventRequest)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
logger.e("Failed to sync", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -325,24 +227,6 @@ class NavigationViewModel(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// nostrEventBroadcaster.broadcastEvent(
|
|
||||||
// localBroadcastNostrEventRequest = localBroadcastNostrEventRequest,
|
|
||||||
// onBroadcastRequestProcessed = { broadcastNostrEventRequest ->
|
|
||||||
// scope.launch(Dispatchers.IO) {
|
|
||||||
// nostrRepository.broadcastProcessed(
|
|
||||||
// broadcastNostrEventRequest
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// onBroadcastReceipt = { broadcastNostrEventReceipt ->
|
|
||||||
// scope.launch(Dispatchers.IO) {
|
|
||||||
// nostrRepository.saveBroadcastReceipt(
|
|
||||||
// broadcastNostrEventReceipt
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package ac.aux.compose.managers
|
||||||
|
|
||||||
|
import ac.aux.compose.PlatformContext
|
||||||
|
import androidx.datastore.core.Storage
|
||||||
|
import androidx.datastore.core.okio.OkioSerializer
|
||||||
|
import androidx.datastore.core.okio.OkioStorage
|
||||||
|
import okio.FileSystem
|
||||||
|
import okio.Path.Companion.toPath
|
||||||
|
import platform.Foundation.NSDocumentDirectory
|
||||||
|
import platform.Foundation.NSFileManager
|
||||||
|
import platform.Foundation.NSURL
|
||||||
|
import platform.Foundation.NSUserDomainMask
|
||||||
|
|
||||||
|
actual fun computeDatastoreStorage(
|
||||||
|
platformContext: PlatformContext,
|
||||||
|
dataStoreFileName: String
|
||||||
|
): Storage<Set<CredentialsManager.Credential>> {
|
||||||
|
return OkioStorage(
|
||||||
|
fileSystem = FileSystem.SYSTEM,
|
||||||
|
serializer = OkioSerializer<Set<CredentialsManager.Credential>>,
|
||||||
|
producePath = {
|
||||||
|
val documentDirectory: NSURL? = NSFileManager.defaultManager.URLsForDirectory(
|
||||||
|
directory = NSDocumentDirectory,
|
||||||
|
inDomains = NSUserDomainMask,
|
||||||
|
appropriateForURL = null,
|
||||||
|
create = false,
|
||||||
|
error = null
|
||||||
|
)
|
||||||
|
|
||||||
|
(requireNotNull(documentDirectory).path + "/$dataStoreFileName").toPath()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package ac.aux.compose.managers
|
||||||
|
|
||||||
|
import ac.aux.compose.PlatformContext
|
||||||
|
import androidx.datastore.core.Storage
|
||||||
|
import io.ktor.client.plugins.cache.storage.FileStorage
|
||||||
|
|
||||||
|
actual fun computeDatastoreStorage(
|
||||||
|
platformContext: PlatformContext,
|
||||||
|
dataStoreFileName: String
|
||||||
|
): Storage<Set<CredentialsManager.Credential>> {
|
||||||
|
FileStorage(
|
||||||
|
directory = ""
|
||||||
|
)
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
@@ -40,6 +40,7 @@ androidx-testExt-junit = { module = "androidx.test.ext:junit", version.ref = "an
|
|||||||
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" }
|
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" }
|
||||||
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
|
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
|
||||||
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
|
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
|
||||||
|
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastorePreferences" }
|
||||||
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
||||||
androidx-paging-common = { module = "androidx.paging:paging-common", version.ref = "pagingCommon" }
|
androidx-paging-common = { module = "androidx.paging:paging-common", version.ref = "pagingCommon" }
|
||||||
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pagingCommon" }
|
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pagingCommon" }
|
||||||
|
|||||||
Reference in new issue
Block a user