chore: merge the develop branch into master #1

Merged
reya merged 43 commits from develop into master 2026-05-23 00:50:13 +00:00
3 changed files with 126 additions and 48 deletions
Showing only changes of commit 3376e71bda - Show all commits

View File

@@ -26,7 +26,7 @@ kotlin {
commonMain.dependencies { commonMain.dependencies {
implementation("org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0") implementation("org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation("org.rust-nostr:nostr-sdk-kmp:0.44.3") implementation("su.reya:nostr-sdk-kmp:0.1")
} }
commonTest.dependencies { commonTest.dependencies {
implementation(libs.kotlin.test) implementation(libs.kotlin.test)

View File

@@ -2,11 +2,11 @@ package su.reya.coop
import rust.nostr.sdk.Client import rust.nostr.sdk.Client
import rust.nostr.sdk.ClientBuilder import rust.nostr.sdk.ClientBuilder
import rust.nostr.sdk.ClientOptions import rust.nostr.sdk.ClientNotification
import rust.nostr.sdk.Event import rust.nostr.sdk.Contact
import rust.nostr.sdk.EventBuilder import rust.nostr.sdk.EventBuilder
import rust.nostr.sdk.Filter import rust.nostr.sdk.Filter
import rust.nostr.sdk.HandleNotification import rust.nostr.sdk.GossipConfig
import rust.nostr.sdk.Keys import rust.nostr.sdk.Keys
import rust.nostr.sdk.Kind import rust.nostr.sdk.Kind
import rust.nostr.sdk.KindStandard import rust.nostr.sdk.KindStandard
@@ -16,9 +16,12 @@ import rust.nostr.sdk.NostrConnect
import rust.nostr.sdk.NostrDatabase import rust.nostr.sdk.NostrDatabase
import rust.nostr.sdk.NostrGossip import rust.nostr.sdk.NostrGossip
import rust.nostr.sdk.NostrSigner import rust.nostr.sdk.NostrSigner
import rust.nostr.sdk.RelayMessage import rust.nostr.sdk.PublicKey
import rust.nostr.sdk.RelayCapabilities
import rust.nostr.sdk.RelayMetadata
import rust.nostr.sdk.RelayUrl import rust.nostr.sdk.RelayUrl
import rust.nostr.sdk.ReqExitPolicy import rust.nostr.sdk.ReqExitPolicy
import rust.nostr.sdk.ReqTarget
import rust.nostr.sdk.SubscribeAutoCloseOptions import rust.nostr.sdk.SubscribeAutoCloseOptions
import rust.nostr.sdk.Timestamp import rust.nostr.sdk.Timestamp
@@ -28,39 +31,59 @@ class Nostr {
var signer: NostrSigner? = null var signer: NostrSigner? = null
private set private set
fun init(dbPath: String) { suspend fun init(dbPath: String) {
val lmdb = NostrDatabase.lmdb(dbPath) val lmdb = NostrDatabase.lmdb(dbPath)
val gossip = NostrGossip.inMemory() val gossip = NostrGossip.inMemory()
val opts = ClientOptions().automaticAuthentication(false)
client = ClientBuilder().database(lmdb).gossip(gossip).opts(opts).build() client =
ClientBuilder()
.database(lmdb)
.gossip(gossip)
.gossipConfig(GossipConfig().noBackgroundRefresh())
.maxRelays(20u)
.verifySubscriptions(false)
.automaticAuthentication(false)
.build()
} }
suspend fun connect() { suspend fun connect() {
this.client?.addRelay(RelayUrl.parse("wss://relay.damus.io")) client?.addRelay(
this.client?.addRelay(RelayUrl.parse("wss://relay.primal.net")) url = RelayUrl.parse("wss://relay.damus.io"),
this.client?.addRelay(RelayUrl.parse("wss://user.kindpag.es")) capabilities = RelayCapabilities.none()
this.client?.connect() )
client?.addRelay(
url = RelayUrl.parse("wss://relay.primal.net"),
capabilities = RelayCapabilities.none()
)
client?.addRelay(
url = RelayUrl.parse("wss://user.kindpag.es"),
capabilities = RelayCapabilities.none()
)
client?.addRelay(
url = RelayUrl.parse("https://indexer.coracle.social"),
capabilities = RelayCapabilities.gossip()
)
client?.connect()
} }
suspend fun disconnect() { suspend fun disconnect() {
this.client?.shutdown() client?.shutdown()
} }
suspend fun setKeySigner(keys: Keys) { suspend fun setKeySigner(keys: Keys) {
signer = NostrSigner.keys(keys) signer = NostrSigner.keys(keys)
this.getMetadata() getUserMetadata()
} }
suspend fun setRemoteSigner(signer: NostrConnect) { suspend fun setRemoteSigner(remote: NostrConnect) {
this.signer = NostrSigner.nostrConnect(signer) signer = NostrSigner.nostrConnect(remote)
this.getMetadata() getUserMetadata()
} }
suspend fun getMetadata() { suspend fun getUserMetadata() {
val currentUserPubKey = this.signer?.getPublicKey() ?: return val userPubkey = signer?.getPublicKey() ?: return
val opts = SubscribeAutoCloseOptions().exitPolicy(ReqExitPolicy.ExitOnEose)
val filter = Filter().author(currentUserPubKey).limit(10u).kinds( val filter = Filter().author(userPubkey).limit(10u).kinds(
listOf( listOf(
Kind.fromStd(KindStandard.METADATA), Kind.fromStd(KindStandard.METADATA),
Kind.fromStd(KindStandard.CONTACT_LIST), Kind.fromStd(KindStandard.CONTACT_LIST),
@@ -68,44 +91,99 @@ class Nostr {
) )
) )
this.client?.subscribe(filter, opts) val target = ReqTarget.auto(listOf(filter))
val opts = SubscribeAutoCloseOptions().exitPolicy(ReqExitPolicy.ExitOnEose)
client?.subscribe(target = target, id = "user-metadata", closeOn = opts)
} }
suspend fun handleNotifications() { suspend fun handleNotifications() {
val now = Timestamp.now() val now = Timestamp.now()
val notifications = client?.notifications()
this.client?.handleNotifications(object : HandleNotification { while (true) {
override suspend fun handle(relayUrl: RelayUrl, subscriptionId: String, event: Event) { val notification = notifications?.next() ?: break
TODO("Not yet implemented")
when (notification) {
is ClientNotification.Message -> {
// TODO: Handle message
} }
override suspend fun handleMsg( is ClientNotification.NewEvent -> {
relayUrl: RelayUrl, // TODO: Handle new event
msg: RelayMessage
) {
TODO("Not yet implemented")
} }
})
is ClientNotification.Shutdown -> {
break
}
}
}
}
suspend fun getDefaultRelayList(): Map<RelayUrl, RelayMetadata> {
// Construct a list of relays
val relayList = mapOf<RelayUrl, RelayMetadata>(
RelayUrl.parse("wss://relay.damus.io") to RelayMetadata.READ,
RelayUrl.parse("wss://relay.primal.net") to RelayMetadata.READ,
RelayUrl.parse("wss://relay.nostr.net") to RelayMetadata.WRITE,
RelayUrl.parse("wss://nostr.superfriends.online") to RelayMetadata.WRITE
)
// Ensure all relays are added and connected
relayList.forEach { (relay, metadata) ->
client?.addRelay(
url = relay,
capabilities =
if (metadata == RelayMetadata.READ) RelayCapabilities.read()
else if (metadata == RelayMetadata.WRITE) RelayCapabilities.write()
else RelayCapabilities.none()
)
client?.connectRelay(relay)
}
return relayList
}
suspend fun getMsgRelayList(): List<RelayUrl> {
// Construct a list of messaging relays
val msgRelayList = listOf(
RelayUrl.parse("wss://relay.0xchat.com"),
RelayUrl.parse("wss://nip17.com"),
)
// Ensure all relays are added and connected
msgRelayList.forEach { relay ->
client?.addRelay(relay, RelayCapabilities.none())
client?.connectRelay(relay)
}
return msgRelayList
} }
suspend fun createIdentity(keys: Keys, name: String, bio: String, picture: String?) { suspend fun createIdentity(keys: Keys, name: String, bio: String, picture: String?) {
// Set signer // Set signer
signer = NostrSigner.keys(keys) signer = NostrSigner.keys(keys)
// Construct metadata records // Send relay list event
val records = MetadataRecord( val relayList = getDefaultRelayList()
name = name, val relayListEvent = EventBuilder.relayList(relayList).sign(signer!!);
displayName = name, client?.sendEvent(relayListEvent)
about = bio,
picture = picture
)
// Construct a nostr event and sign it // Send messaging relay list event
val metadata = Metadata.fromRecord(records) val msgRelayList = getMsgRelayList()
val builder = EventBuilder.metadata(metadata).build(keys.publicKey()) val msgRelayListEvent = EventBuilder.nip17RelayList(msgRelayList).sign(signer!!)
val event = this.signer?.signEvent(builder) ?: return client?.sendEventNoWait(msgRelayListEvent)
// Send event to relays // Send metadata event
this.client?.sendEvent(event) val metadata =
Metadata.fromRecord(MetadataRecord(name = name, about = bio, picture = picture))
val metadataEvent = EventBuilder.metadata(metadata).sign(signer!!)
client?.sendEventNoWait(metadataEvent)
// Send contact list event
val defaultContact =
listOf(Contact(publicKey = PublicKey.parse("npub1j3rz3ndl902lya6ywxvy5c983lxs8mpukqnx4pa4lt5wrykwl5ys7wpw3x")))
val contactListEvent = EventBuilder.contactList(defaultContact).sign(signer!!)
client?.sendEventNoWait(contactListEvent)
} }
} }

View File

@@ -24,11 +24,11 @@ class NostrViewModel(
val isCreating = _isCreating.asStateFlow() val isCreating = _isCreating.asStateFlow()
fun initAndConnect(dbPath: String) { fun initAndConnect(dbPath: String) {
viewModelScope.launch {
try {
// Initialize nostr client // Initialize nostr client
nostr.init(dbPath) nostr.init(dbPath)
viewModelScope.launch {
try {
// Connect to bootstrap relays // Connect to bootstrap relays
nostr.connect() nostr.connect()