Skip to content

Commit

Permalink
ffi(sdk): add async/future support
Browse files Browse the repository at this point in the history
* Add `org.jetbrains.kotlinx:kotlinx-coroutines-core` dependency to `android` bindings
* No longer spawn a thread when calling `handle_notifications`
* Remove `AbortHandle`

Signed-off-by: Yuki Kishimoto <yukikishimoto@protonmail.com>
  • Loading branch information
yukibtc committed Jun 4, 2024
1 parent eb4993f commit d583fae
Show file tree
Hide file tree
Showing 38 changed files with 1,352 additions and 1,346 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
* ffi(nostr): set default args values where possible for `EventBuilder` constructors ([Yuki Kishimoto])
* ffi(nostr): convert `verify_nip05` and `get_nip05_profile` to async functions ([Yuki Kishimoto])
* ffi(nostr): convert `RelayInformationDocument::get` to async ([Yuki Kishimoto])
* ffi(sdk): add `async/future` support (convert from blocking to async) ([Yuki Kishimoto])
* ffi(sdk): no longer spawn a thread when calling `handle_notifications` ([Yuki Kishimoto])
* js(sdk): change `JsNostrZapper::nwc` fingerprint ([Yuki Kishimoto])
* js(sdk): rename `JsNip46Signer::new` to `JsNip46Signer::init` ([Yuki Kishimoto])

Expand Down
17 changes: 15 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bindings/nostr-ffi/bindings-android/lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ android {

dependencies {
implementation("net.java.dev.jna:jna:5.12.0@aar")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("androidx.appcompat:appcompat:1.6.1")
}

Expand Down
21 changes: 20 additions & 1 deletion bindings/nostr-ffi/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,23 @@ impl Deref for Event {

#[uniffi::export]
impl Event {
#[inline]
pub fn id(&self) -> EventId {
self.inner.id().into()
}

/// Get event author (`pubkey` field)
#[inline]
pub fn author(&self) -> PublicKey {
self.inner.author().into()
}

#[inline]
pub fn created_at(&self) -> Timestamp {
self.inner.created_at().into()
}

#[inline]
pub fn kind(&self) -> Kind {
self.inner.kind().into()
}
Expand All @@ -71,25 +75,30 @@ impl Event {
.collect()
}

#[inline]
pub fn content(&self) -> String {
self.inner.content().to_string()
}

#[inline]
pub fn signature(&self) -> String {
self.inner.signature().to_string()
}

/// Verify both `EventId` and `Signature`
#[inline]
pub fn verify(&self) -> bool {
self.inner.verify().is_ok()
}

/// Verify if the `EventId` it's composed correctly
#[inline]
pub fn verify_id(&self) -> Result<()> {
Ok(self.inner.verify_id()?)
}

/// Verify only event `Signature`
#[inline]
pub fn verify_signature(&self) -> Result<()> {
Ok(self.inner.verify_signature()?)
}
Expand All @@ -103,53 +112,61 @@ impl Event {
/// If an event has no `Expiration` tag, then it will return `false`.
///
/// <https://github.com/nostr-protocol/nips/blob/master/40.md>
#[inline]
pub fn is_expired(&self) -> bool {
self.inner.is_expired()
}

/// Check if `Kind` is a NIP90 job request
///
/// <https://github.com/nostr-protocol/nips/blob/master/90.md>
#[inline]
pub fn is_job_request(&self) -> bool {
self.inner.is_job_request()
}

/// Check if `Kind` is a NIP90 job result
///
/// <https://github.com/nostr-protocol/nips/blob/master/90.md>
#[inline]
pub fn is_job_result(&self) -> bool {
self.inner.is_job_result()
}

/// Check if event `Kind` is `Regular`
///
/// <https://github.com/nostr-protocol/nips/blob/master/01.md>
#[inline]
pub fn is_regular(&self) -> bool {
self.inner.is_regular()
}

/// Check if event `Kind` is `Replaceable`
///
/// <https://github.com/nostr-protocol/nips/blob/master/01.md>
#[inline]
pub fn is_replaceable(&self) -> bool {
self.inner.is_replaceable()
}

/// Check if event `Kind` is `Ephemeral`
///
/// <https://github.com/nostr-protocol/nips/blob/master/01.md>
#[inline]
pub fn is_ephemeral(&self) -> bool {
self.inner.is_ephemeral()
}

/// Check if event `Kind` is `Parameterized replaceable`
///
/// <https://github.com/nostr-protocol/nips/blob/master/01.md>
#[inline]
pub fn is_parameterized_replaceable(&self) -> bool {
self.inner.is_parameterized_replaceable()
}

/// Extract identifier (`d` tag), if exists.
#[inline]
pub fn identifier(&self) -> Option<String> {
self.inner.identifier().map(|i| i.to_string())
}
Expand Down Expand Up @@ -187,13 +204,15 @@ impl Event {
.collect()
}

#[inline]
#[uniffi::constructor]
pub fn from_json(json: String) -> Result<Self> {
pub fn from_json(json: &str) -> Result<Self> {
Ok(Self {
inner: nostr::Event::from_json(json)?,
})
}

#[inline]
pub fn as_json(&self) -> Result<String> {
Ok(self.inner.try_as_json()?)
}
Expand Down
6 changes: 5 additions & 1 deletion bindings/nostr-ffi/src/nips/nip05.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ use crate::nips::nip19::Nip19Profile;
use crate::PublicKey;

#[uniffi::export(default(proxy = None))]
pub async fn verify_nip05(public_key: &PublicKey, nip05: &str, proxy: Option<String>) -> Result<bool> {
pub async fn verify_nip05(
public_key: &PublicKey,
nip05: &str,
proxy: Option<String>,
) -> Result<bool> {
let proxy: Option<SocketAddr> = match proxy {
Some(proxy) => Some(proxy.parse()?),
None => None,
Expand Down
5 changes: 3 additions & 2 deletions bindings/nostr-sdk-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ sqlite = ["nostr-sdk/sqlite"]
ndb = ["nostr-sdk/ndb"]

[dependencies]
async-trait.workspace = true
async-utility.workspace = true
nostr-ffi = { path = "../nostr-ffi" }
nostr-sdk = { path = "../../crates/nostr-sdk", default-features = false, features = ["all-nips", "blocking"] }
nostr-sdk = { path = "../../crates/nostr-sdk", default-features = false, features = ["all-nips"] }
tracing = { workspace = true, features = ["std"] }
tracing-subscriber.workspace = true
uniffi.workspace = true
uniffi = { workspace = true, features = ["tokio"] }

[target.'cfg(target_os = "android")'.dependencies]
paranoid-android = "0.2"
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ android {

dependencies {
implementation("net.java.dev.jna:jna:5.12.0@aar")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("androidx.appcompat:appcompat:1.6.1")
}

Expand Down
97 changes: 51 additions & 46 deletions bindings/nostr-sdk-ffi/bindings-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,55 +17,60 @@ pip install nostr-sdk
```

```python
from nostr_sdk import Keys, Client, NostrSigner, EventBuilder, Filter, Metadata, Nip46Signer, init_logger, LogLevel, \
NostrConnectUri
import asyncio
from datetime import timedelta
from nostr_sdk import Keys, Client, NostrSigner, EventBuilder, Filter, Metadata, Nip46Signer, init_logger, LogLevel
import time

# Init logger
init_logger(LogLevel.INFO)

# Initialize client without signer
# client = Client(None)

# Or, initialize with Keys signer
keys = Keys.generate()
signer = NostrSigner.keys(keys)

# Or, initialize with NIP46 signer
# app_keys = Keys.parse("..")
# uri = NostrConnectUri.parse("bunker://.. or nostrconnect://..")
# nip46 = Nip46Signer(uri, app_keys, timedelta(seconds=60), None)
# signer = NostrSigner.nip46(nip46)

client = Client(signer)

# Add relays and connect
client.add_relays(["wss://relay.damus.io", "wss://nos.lol"])
client.connect()

# Send an event using the Nostr Signer
builder = EventBuilder.text_note("Test from Rust Nostr Python!", [])
client.send_event_builder(builder)
client.set_metadata(Metadata().set_name("Testing Rust Nostr"))

# Mine a POW event and sign it with custom keys
custom_keys = Keys.generate()
print("Mining a POW text note...")
event = EventBuilder.text_note("Hello from Rust Nostr Python bindings!", []).to_pow_event(custom_keys, 20)
event_id = client.send_event(event)
print("Event sent:")
print(f" hex: {event_id.to_hex()}")
print(f" bech32: {event_id.to_bech32()}")

time.sleep(2.0)

# Get events from relays
print("Getting events from relays...")
f = Filter().authors([keys.public_key(), custom_keys.public_key()])
events = client.get_events_of([f], timedelta(seconds=10))
for event in events:
print(event.as_json())

async def main():
# Init logger
init_logger(LogLevel.INFO)

# Initialize client without signer
# client = Client()

# Or, initialize with Keys signer
keys = Keys.generate()
signer = NostrSigner.keys(keys)

# Or, initialize with NIP46 signer
# app_keys = Keys.parse("..")
# uri = NostrConnectUri.parse("bunker://.. or nostrconnect://..")
# nip46 = await Nip46Signer.init(uri, app_keys, timedelta(seconds=60), None)
# signer = NostrSigner.nip46(nip46)

client = Client(signer)

# Add relays and connect
await client.add_relays(["wss://relay.damus.io", "wss://nos.lol"])
await client.connect()

# Send an event using the Nostr Signer
builder = EventBuilder.text_note("Test from Rust Nostr Python!", [])
await client.send_event_builder(builder)
await client.set_metadata(Metadata().set_name("Testing Rust Nostr"))

# Mine a POW event and sign it with custom keys
custom_keys = Keys.generate()
print("Mining a POW text note...")
event = EventBuilder.text_note("Hello from Rust Nostr Python bindings!", []).to_pow_event(custom_keys, 20)
event_id = await client.send_event(event)
print("Event sent:")
print(f" hex: {event_id.to_hex()}")
print(f" bech32: {event_id.to_bech32()}")

time.sleep(2.0)

# Get events from relays
print("Getting events from relays...")
f = Filter().authors([keys.public_key(), custom_keys.public_key()])
events = await client.get_events_of([f], timedelta(seconds=10))
for event in events:
print(event.as_json())


asyncio.run(main())
```

More examples can be found at:
Expand Down
36 changes: 20 additions & 16 deletions bindings/nostr-sdk-ffi/bindings-python/examples/blacklist.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import asyncio
from nostr_sdk import PublicKey, Client, Filter, Kind, init_logger, LogLevel
from datetime import timedelta
import time

# Init logger
init_logger(LogLevel.INFO)

# Init client
client = Client()
client.add_relay("wss://relay.damus.io")
client.add_relay("wss://nos.lol")
client.connect()
async def main():
# Init logger
init_logger(LogLevel.INFO)

muted_public_key = PublicKey.parse("npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft")
other_public_key = PublicKey.parse("npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s")
# Init client
client = Client()
await client.add_relays(["wss://relay.damus.io", "wss://nos.lol"])
await client.connect()

# Mute public key
client.mute_public_keys([muted_public_key])
muted_public_key = PublicKey.parse("npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft")
other_public_key = PublicKey.parse("npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s")

# Get events
f = Filter().authors([muted_public_key, other_public_key]).kind(Kind(0))
events = client.get_events_of([f], timedelta(seconds=10))
print(f"Received {events.__len__()} events")
# Mute public key
await client.mute_public_keys([muted_public_key])

# Get events
f = Filter().authors([muted_public_key, other_public_key]).kind(Kind(0))
events = await client.get_events_of([f], timedelta(seconds=10))
print(f"Received {events.__len__()} events")


asyncio.run(main())
Loading

0 comments on commit d583fae

Please sign in to comment.