update quartz

This commit is contained in:
greenart7c3
2025-11-17 08:53:14 -03:00
parent ea39b7e734
commit 91c5246e16
9 changed files with 95 additions and 179 deletions
@@ -35,8 +35,9 @@ import com.greenart7c3.nostrsigner.service.crashreports.UnexpectedCrashSaver
import com.greenart7c3.nostrsigner.ui.RememberType
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayAuthenticator
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.sendAndWaitForResponse
import com.vitorpamplona.quartz.nip01Core.relay.client.auth.RelayAuthenticator
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
@@ -97,16 +98,14 @@ class Amber : Application(), LifecycleObserver {
}
// Authenticates with relays.
val authCoordinator = RelayAuthenticator(client, applicationIOScope) { challenge, relay ->
LocalPreferences.allAccounts(this).forEach { account ->
val authed = account.createAuthEvent(
relayUrl = relay.url,
challenge = challenge,
)
client.sendIfExists(authed, relay.url)
val authCoordinator = RelayAuthenticator(client, applicationIOScope) { event ->
LocalPreferences.allAccounts(this).map { account ->
account.signer.signerSync.sign(event)
}
}
val relayStats = RelayStats(client)
// TODO: I assume you want this sub to stay active in the background
// TODO: Find a way to call either client.reconnect or notificationSubscription.updateFilters() to
// keep the filter live when the relay inevitably disconnects. NostrClient doesn't try to reconnect
@@ -41,8 +41,7 @@ class OkHttpWebSocket(
override fun needsReconnect(): Boolean {
if (socket == null) return true
val myUsingOkHttp = usingOkHttp
if (myUsingOkHttp == null) return true
val myUsingOkHttp = usingOkHttp ?: return true
val currentOkHttp = httpClient(url)
@@ -80,12 +79,6 @@ class OkHttpWebSocket(
text: String,
) = out.onMessage(text)
override fun onClosing(
webSocket: okhttp3.WebSocket,
code: Int,
reason: String,
) = out.onClosing(code, reason)
override fun onClosed(
webSocket: okhttp3.WebSocket,
code: Int,
@@ -19,7 +19,6 @@ import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.service.KillSwitchReceiver
import com.greenart7c3.nostrsigner.service.ReconnectReceiver
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
@@ -79,7 +78,7 @@ class AmberRelayStats(
val message = available.joinToString("\n") {
val connected = "${it.url} ${if (it in connected) appContext.getString(R.string.connected) else appContext.getString(R.string.disconnected)}\n"
val ping = appContext.getString(R.string.ping_ms, RelayStats.get(it).pingInMs)
val ping = appContext.getString(R.string.ping_ms, Amber.instance.relayStats.get(it).pingInMs)
val sent = if (get(it).sent > 0) {
appContext.getString(R.string.sent, get(it).sent)
} else {
@@ -95,7 +94,7 @@ class AmberRelayStats(
} else {
""
}
val error = if (RelayStats.get(it).errorCounter > 0) appContext.getString(R.string.error, RelayStats.get(it).errorCounter) else ""
val error = if (Amber.instance.relayStats.get(it).errorCounter > 0) appContext.getString(R.string.error, Amber.instance.relayStats.get(it).errorCounter) else ""
connected + ping + sent + received + failed + error
}
@@ -1,47 +0,0 @@
package com.greenart7c3.nostrsigner.relays
import android.util.Log
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.models.Account
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayState
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
class BunkerValidationRelayListener(
val account: Account,
val onReceiveEvent: (relay: IRelayClient, subscriptionId: String, event: Event) -> Unit,
) : IRelayClientListener {
override fun onAuth(relay: IRelayClient, challenge: String) {
Log.d(Amber.TAG, "Received auth challenge $challenge from relay ${relay.url}")
}
override fun onBeforeSend(relay: IRelayClient, event: Event) {
Log.d(Amber.TAG, "Sending event ${event.id} to relay ${relay.url}")
}
override fun onError(relay: IRelayClient, subId: String, error: Error) {
Log.d(Amber.TAG, "Received error $error from subscription $subId")
}
override fun onEvent(relay: IRelayClient, subId: String, event: Event, arrivalTime: Long, afterEOSE: Boolean) {
Log.d(Amber.TAG, "Received event ${event.toJson()} from subscription $subId afterEOSE: $afterEOSE")
onReceiveEvent(relay, subId, event)
}
override fun onNotify(relay: IRelayClient, description: String) {
Log.d(Amber.TAG, "Received notify $description from relay ${relay.url.url}")
}
override fun onRelayStateChange(relay: IRelayClient, type: RelayState) {
Log.d(Amber.TAG, "Relay ${relay.url} state changed to $type")
}
override fun onSend(relay: IRelayClient, msg: String, success: Boolean) {
Log.d(Amber.TAG, "Sent message $msg to relay ${relay.url} success: $success")
}
override fun onSendResponse(relay: IRelayClient, eventId: String, success: Boolean, message: String) {
Log.d(Amber.TAG, "Sent response to event $eventId to relay ${relay.url} success: $success message: $message")
}
}
@@ -5,14 +5,14 @@ import android.app.NotificationManager
import android.content.Context
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.LocalPreferences
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.database.LogEntity
import com.greenart7c3.nostrsigner.service.NotificationUtils.sendErrorNotification
import com.greenart7c3.nostrsigner.ui.AccountStateViewModel
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayState
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.OkMessage
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -47,39 +47,24 @@ class NostrClientLoggerListener(
val stats: AmberRelayStats,
val scope: CoroutineScope,
) : IRelayClientListener {
override fun onAuth(relay: IRelayClient, challenge: String) {
override fun onCannotConnect(relay: IRelayClient, errorMessage: String) {
scope.launch {
LocalPreferences.currentAccount(context)?.let { account ->
Amber.instance.getLogDatabase(account).dao().insertLog(
LogEntity(
id = 0,
url = relay.url.url,
type = "onAuth",
message = "Authenticating",
type = "onCannotConnect",
message = errorMessage,
time = System.currentTimeMillis(),
),
)
}
}
super.onCannotConnect(relay, errorMessage)
}
override fun onBeforeSend(relay: IRelayClient, event: Event) {
scope.launch {
LocalPreferences.currentAccount(context)?.let { account ->
Amber.instance.getLogDatabase(account).dao().insertLog(
LogEntity(
id = 0,
url = relay.url.url,
type = "onBeforeSend",
message = "Sending event ${event.id}",
time = System.currentTimeMillis(),
),
)
}
}
}
override fun onSend(relay: IRelayClient, msg: String, success: Boolean) {
override fun onSent(relay: IRelayClient, cmdStr: String, cmd: Command, success: Boolean) {
scope.launch {
LocalPreferences.currentAccount(context)?.let { account ->
Amber.instance.getLogDatabase(account).dao().insertLog(
@@ -87,38 +72,14 @@ class NostrClientLoggerListener(
id = 0,
url = relay.url.url,
type = "onSend",
message = "message: $msg success: $success",
message = "message: $cmdStr success: $success",
time = System.currentTimeMillis(),
),
)
}
}
if (!success) {
if (msg.isNotBlank()) {
AmberListenerSingleton.latestErrorMessages.add("Failed to send event.\n$msg")
} else {
AmberListenerSingleton.latestErrorMessages.add("Failed to send event. Try again.")
}
}
}
override fun onSendResponse(relay: IRelayClient, eventId: String, success: Boolean, message: String) {
scope.launch {
LocalPreferences.currentAccount(context)?.let { account ->
Amber.instance.getLogDatabase(account).dao().insertLog(
LogEntity(
id = 0,
url = relay.url.url,
type = "onSendResponse",
message = "Success: $success Message: $message",
time = System.currentTimeMillis(),
),
)
}
}
if (!success) {
AmberListenerSingleton.latestErrorMessages.add(message)
AmberListenerSingleton.latestErrorMessages.add("Failed to send event. Try again.")
}
if (success) {
@@ -126,81 +87,80 @@ class NostrClientLoggerListener(
} else {
stats.addFailed(relay.url)
}
super.onSent(relay, cmdStr, cmd, success)
}
override fun onError(relay: IRelayClient, subId: String, error: Error) {
if (error.message?.trim()?.equals("Relay sent notice:") == true) return
override fun onIncomingMessage(relay: IRelayClient, msgStr: String, msg: Message) {
scope.launch {
LocalPreferences.currentAccount(context)?.let { account ->
Amber.instance.getLogDatabase(account).dao().insertLog(
LogEntity(
id = 0,
url = relay.url.url,
type = "onError",
message = "${error.message}",
type = "onIncomingMessage",
message = "message: $msgStr",
time = System.currentTimeMillis(),
),
)
}
}
if (error.message?.contains("EACCES (Permission denied)") == true) {
AmberListenerSingleton.latestErrorMessages.add(context.getString(R.string.network_permission_message))
} else if (error.message?.contains("socket failed: EPERM (Operation not permitted)") == true) {
AmberListenerSingleton.latestErrorMessages.add(context.getString(R.string.network_permission_message))
} else if (Amber.instance.settings.useProxy && error.message?.contains("(port ${Amber.instance.settings.proxyPort})") == true) {
AmberListenerSingleton.latestErrorMessages.add(context.getString(R.string.failed_to_connect_to_tor_orbot))
} else {
AmberListenerSingleton.latestErrorMessages.add(error.message ?: "Unknown error")
if (msg is OkMessage && !msg.success) {
AmberListenerSingleton.latestErrorMessages.add(msg.message)
}
super.onIncomingMessage(relay, msgStr, msg)
}
override fun onEvent(relay: IRelayClient, subId: String, event: Event, arrivalTime: Long, afterEOSE: Boolean) {
override fun onDisconnected(relay: IRelayClient) {
scope.launch {
LocalPreferences.currentAccount(context)?.let { account ->
Amber.instance.getLogDatabase(account).dao().insertLog(
LogEntity(
id = 0,
url = relay.url.url,
type = "onEvent",
message = "Received event ${event.id} from subscription $subId afterEOSE: $afterEOSE",
type = "onDisconnected",
message = "Disconnected from relay ${relay.url.url}",
time = System.currentTimeMillis(),
),
)
}
Amber.instance.stats.addReceived(relay.url)
}
super.onDisconnected(relay)
}
override fun onNotify(relay: IRelayClient, description: String) {
override fun onConnecting(relay: IRelayClient) {
scope.launch {
LocalPreferences.currentAccount(context)?.let { account ->
Amber.instance.getLogDatabase(account).dao().insertLog(
LogEntity(
id = 0,
url = relay.url.url,
type = "onNotify",
message = description,
type = "onConnecting",
message = "Connecting to relay ${relay.url.url}",
time = System.currentTimeMillis(),
),
)
}
}
super.onConnecting(relay)
}
override fun onRelayStateChange(relay: IRelayClient, type: RelayState) {
override fun onConnected(relay: IRelayClient, pingMillis: Int, compressed: Boolean) {
scope.launch {
LocalPreferences.currentAccount(context)?.let { account ->
Amber.instance.getLogDatabase(account).dao().insertLog(
LogEntity(
id = 0,
url = relay.url.url,
type = "onRelayStateChange",
message = type.name,
type = "onConnected",
message = "Connected to relay ${relay.url.url}",
time = System.currentTimeMillis(),
),
)
}
}
super.onConnected(relay, pingMillis, compressed)
}
}
@@ -24,11 +24,13 @@ import android.content.Context
import android.util.Log
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.LocalPreferences
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
@@ -48,15 +50,18 @@ class NotificationSubscription(
client.subscribe(this)
}
override fun onEvent(relay: IRelayClient, subId: String, event: Event, arrivalTime: Long, afterEOSE: Boolean) {
if (this.subId == subId) {
eventNotificationConsumer.consume(event, relay.url)
override fun onIncomingMessage(relay: IRelayClient, msgStr: String, msg: Message) {
if (msg is EventMessage) {
if (msg.subId == subId) {
eventNotificationConsumer.consume(msg.event, relay.url)
}
}
super.onIncomingMessage(relay, msgStr, msg)
}
override fun onSend(relay: IRelayClient, msg: String, success: Boolean) {
Log.d("NotificationSubscription", "onSend: ${relay.url}, $msg, $success")
super.onSend(relay, msg, success)
override fun onSent(relay: IRelayClient, cmdStr: String, cmd: Command, success: Boolean) {
Log.d("NotificationSubscription", "onSend: ${relay.url}, $cmdStr, $success")
super.onSent(relay, cmdStr, cmd, success)
}
/**
@@ -24,13 +24,15 @@ import android.content.Context
import com.greenart7c3.nostrsigner.AccountInfo
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.LocalPreferences
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EoseMessage
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
@@ -54,45 +56,46 @@ class ProfileSubscription(
client.subscribe(this)
}
override fun onEOSE(relay: IRelayClient, subId: String, arrivalTime: Long) {
monitoringAccounts.forEach {
LocalPreferences.setLastCheck(Amber.instance, it.npub, TimeUtils.now())
override fun onIncomingMessage(relay: IRelayClient, msgStr: String, msg: Message) {
if (msg is EoseMessage) {
monitoringAccounts.forEach {
LocalPreferences.setLastCheck(Amber.instance, it.npub, TimeUtils.now())
}
}
}
if (msg is EventMessage) {
if (this.subId == msg.subId) {
if (msg.event.kind == MetadataEvent.KIND) {
(msg.event as MetadataEvent).contactMetaData()?.let { metadata ->
val npub = msg.event.pubKey.hexToByteArray().toNpub()
val account = LocalPreferences.loadFromEncryptedStorageSync(appContext, npub) ?: return
override fun onEvent(relay: IRelayClient, subId: String, event: Event, arrivalTime: Long, afterEOSE: Boolean) {
if (this.subId == subId) {
if (event is MetadataEvent) {
event.contactMetaData()?.let { metadata ->
val npub = event.pubKey.hexToByteArray().toNpub()
val account = LocalPreferences.loadFromEncryptedStorageSync(appContext, npub)
if (account == null) return
var atLeastOne = false
var atLeastOne = false
metadata.name?.let { name ->
if (account.name.value != name) {
account.name.update { name }
atLeastOne = true
metadata.name?.let { name ->
if (account.name.value != name) {
account.name.update { name }
atLeastOne = true
}
}
}
metadata.profilePicture()?.let { url ->
if (account.picture.value != url) {
account.picture.update { url }
atLeastOne = true
metadata.profilePicture()?.let { url ->
if (account.picture.value != url) {
account.picture.update { url }
atLeastOne = true
}
}
}
if (atLeastOne) {
scope.launch {
LocalPreferences.saveToEncryptedStorage(appContext, account)
LocalPreferences.setLastMetadataUpdate(appContext, npub, TimeUtils.now())
if (atLeastOne) {
scope.launch {
LocalPreferences.saveToEncryptedStorage(appContext, account)
LocalPreferences.setLastMetadataUpdate(appContext, npub, TimeUtils.now())
}
}
}
}
}
}
super.onIncomingMessage(relay, msgStr, msg)
}
/**
@@ -64,7 +64,8 @@ import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.sendAndWaitForResponse
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
@@ -327,10 +328,13 @@ fun onAddRelay(
val ncSub = UUID.randomUUID().toString().substring(0, 4)
val listener = object : IRelayClientListener {
override fun onEvent(relay: IRelayClient, subId: String, event: Event, arrivalTime: Long, afterEOSE: Boolean) {
if (ncSub == subId && event.kind == NostrConnectEvent.KIND && event.id == signedEvent.id) {
filterResult = true
override fun onIncomingMessage(relay: IRelayClient, msgStr: String, msg: Message) {
if (msg is EventMessage) {
if (ncSub == msg.subId && msg.event.kind == NostrConnectEvent.KIND && msg.event.id == signedEvent.id) {
filterResult = true
}
}
super.onIncomingMessage(relay, msgStr, msg)
}
}
@@ -556,7 +560,7 @@ fun ActiveRelaysScreen(
) {
Text(
modifier = Modifier.padding(top = 4.dp, bottom = 16.dp),
text = if (isConnected) "${RelayStats.get(relay).pingInMs}ms ping" else "Unavailable",
text = if (isConnected) "${Amber.instance.relayStats.get(relay).pingInMs}ms ping" else "Unavailable",
fontSize = 16.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
+1 -1
View File
@@ -12,7 +12,7 @@ junitVersion = "1.3.0"
lifecycle_version = "2.9.4"
material3 = "1.4.0"
nav_version = "2.9.5"
quartz = "1.03.0"
quartz = "1.04.2"
compose_ui = "1.9.3"
richtextUi = "e1151c8"
roomKtx = "2.8.2"