chore: merge the develop branch into master #1
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user