add nostr foreground service

This commit is contained in:
2026-05-18 12:34:46 +07:00
parent 955da2fea6
commit 1c85e26e7f
7 changed files with 189 additions and 24 deletions

View File

@@ -52,7 +52,12 @@ import rust.nostr.sdk.initLogger
import rust.nostr.sdk.nip17ExtractRelayList
import kotlin.time.Duration
object NostrManager {
val instance = Nostr()
}
class Nostr {
private var isInitialized = false
var client: Client? = null
private set
var signer: UniversalSigner = UniversalSigner(Keys.generate())
@@ -64,6 +69,8 @@ class Nostr {
suspend fun init(dbPath: String) {
try {
if (isInitialized) return
// Initialize the logger for nostr client
initLogger(LogLevel.DEBUG)
@@ -105,6 +112,8 @@ class Nostr {
// Connect to all bootstrap relays and wait for all connections to be established
client?.connect(Duration.parse("3s"))
isInitialized = true
} catch (e: Exception) {
throw IllegalStateException("Failed to initialize Nostr client: ${e.message}", e)
}
@@ -119,9 +128,9 @@ class Nostr {
deviceSigner = null
}
suspend fun setSigner(keys: AsyncNostrSigner) {
suspend fun setSigner(new: AsyncNostrSigner) {
try {
signer.switch(keys)
signer.switch(new)
// Fetch metadata for current user
getUserMetadata()
} catch (e: Exception) {
@@ -184,18 +193,69 @@ class Nostr {
client?.subscribe(
target = ReqTarget.manual(target),
id = "messages"
id = "all-gift-wraps"
)
} catch (e: Exception) {
throw IllegalStateException("Failed to fetch user messages: ${e.message}", e)
}
}
suspend fun handleLiteNotifications(
onNewMessage: (UnsignedEvent) -> Unit,
) {
val now = Timestamp.now()
val processedEvent = mutableSetOf<EventId>()
val notifications = client?.notifications() ?: return
while (true) {
val notification = notifications.next() ?: continue
when (notification) {
is ClientNotification.Message -> {
val relayUrl = notification.relayUrl
when (val message = notification.message.asEnum()) {
is RelayMessageEnum.EventMsg -> {
val event = message.event
val subscriptionId = message.subscriptionId
// Ignore events not from the newest gift wraps subscription
if (subscriptionId != "newest-gift-wraps") continue
// Prevent processing duplicate events
if (processedEvent.contains(event.id())) continue
processedEvent.add(event.id())
if (event.kind().asStd()?.equals(KindStandard.GIFT_WRAP) == true) {
try {
val rumor = extractRumor(event)
// Handle new message
rumor?.createdAt()?.asSecs()?.let {
if (it >= now.asSecs()) {
onNewMessage(rumor)
}
}
} catch (e: Exception) {
println("Failed to extract rumor: $e")
}
}
}
else -> {}
}
}
else -> {}
}
}
}
suspend fun handleNotifications(
onMetadataUpdate: (PublicKey, Metadata) -> Unit,
onContactListUpdate: (List<PublicKey>) -> Unit,
onNewMessage: (UnsignedEvent) -> Unit,
onEose: () -> Unit,
onSubscriptionClose: () -> Unit,
) = coroutineScope {
val now = Timestamp.now()
val processedEvent = mutableSetOf<EventId>()
@@ -251,7 +311,7 @@ class Nostr {
// Start a new tracker
eoseTrackerJob = launch {
delay(10000) // Wait for 10 seconds
onEose()
onSubscriptionClose()
}
// Handle new message
@@ -270,7 +330,7 @@ class Nostr {
val subscriptionId = message.subscriptionId
if (subscriptionId == "messages") {
onEose()
onSubscriptionClose()
}
}
@@ -612,7 +672,9 @@ class Nostr {
signer = signer,
receiverPubkey = receiver,
rumor = rumor,
extraTags = tags
extraTags = listOf(
Tag.custom(TagKind.Unknown("k"), listOf("14"))
)
)
// Send the event to receiver's NIP-17 relays

View File

@@ -131,14 +131,11 @@ class NostrViewModel(
_metadataStore.getOrPut(pubkey) { MutableStateFlow(null) }.value = metadata
}
suspend fun initAndConnect(dbPath: String) {
suspend fun login() {
try {
// Initialize nostr client
nostr.init(dbPath)
// Get user's secret
getUserSecret()
} catch (e: Exception) {
showError("Failed to initialize Nostr: ${e.message}")
showError("Failed to login: ${e.message}")
}
}
@@ -151,7 +148,7 @@ class NostrViewModel(
onContactListUpdate = { contactList ->
_contactList.value = contactList.toSet()
},
onEose = {
onSubscriptionClose = {
getChatRooms()
},
onNewMessage = { event ->