Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: expand node constructor options #186

Merged
merged 5 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 110 additions & 187 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ tracing-subscriber = { version = "0.3.17" }
serde = { version = "1.0.196", features = ["derive"] }
serde_json = "1.0.113"
futures-lite = "2.3.0"
derive_more = { version = "=1.0.0-beta.7", features = ["debug"] }

[dev-dependencies]
rand = "0.8"
Expand Down
13 changes: 13 additions & 0 deletions iroh-js/__test__/node.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,16 @@ test('node status', async (t) => {

t.is(status.version, '0.24.0')
})

test('rpc client memory node', async (t) => {
const node = await Iroh.memory({
enableRpc: true
})

const nodeId = await node.net.nodeId()

const client = await Iroh.client()
const clientId = await client.net.nodeId()

t.is(nodeId, clientId)
})
42 changes: 42 additions & 0 deletions iroh-js/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ export declare class Iroh {
* All data will be only persistet in memory.
*/
static memory(opts?: NodeOptions | undefined | null): Promise<Iroh>
/** Create a new iroh client, connecting to an existing node. */
static client(addr?: string | undefined | null): Promise<Iroh>
/** Access to node specific funtionaliy. */
get node(): Node
}
Expand Down Expand Up @@ -1168,6 +1170,34 @@ export interface NodeAddr {
addresses?: Array<string>
}

export declare const enum NodeDiscoveryConfig {
/** Use no node discovery mechanism. */
None = 'None',
/**
* Use the default discovery mechanism.
*
* This uses two discovery services concurrently:
*
* - It publishes to a pkarr service operated by [number 0] which makes the information
* available via DNS in the `iroh.link` domain.
*
* - It uses an mDNS-like system to announce itself on the local network.
*
* # Usage during tests
*
* Note that the default changes when compiling with `cfg(test)` or the `test-utils`
* cargo feature from [iroh-net] is enabled. In this case only the Pkarr/DNS service
* is used, but on the `iroh.test` domain. This domain is not integrated with the
* global DNS network and thus node discovery is effectively disabled. To use node
* discovery in a test use the [`iroh_net::test_utils::DnsPkarrServer`] in the test and
* configure it here as a custom discovery mechanism ([`DiscoveryConfig::Custom`]).
*
* [number 0]: https://n0.computer
* [iroh-net]: crate::net
*/
Default = 'Default'
}

/** Options passed to [`IrohNode.new`]. Controls the behaviour of an iroh node.# */
export interface NodeOptions {
/**
Expand All @@ -1177,6 +1207,18 @@ export interface NodeOptions {
gcIntervalMillis?: number
/** Provide a callback to hook into events when the blobs component adds and provides blobs. */
blobEvents?: (err: Error | null, arg: BlobProvideEvent) => void
/** Should docs be enabled? Defaults to `true`. */
enableDocs?: boolean
/** Overwrites the default bind port if set. */
port?: number
/** Enable RPC. Defaults to `false`. */
enableRpc?: boolean
/** Overwrite the default RPC address. */
rpcAddr?: string
/** Configure the node discovery. */
nodeDiscovery?: NodeDiscoveryConfig
/** Provide a specific secret key, identifying this node. Must be 32 bytes long. */
secretKey?: Array<number>
}

/** The response to a status request */
Expand Down
1 change: 1 addition & 0 deletions iroh-js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ module.exports.ContentStatus = nativeBinding.ContentStatus
module.exports.DocImportProgressType = nativeBinding.DocImportProgressType
module.exports.keyToPath = nativeBinding.keyToPath
module.exports.LogLevel = nativeBinding.LogLevel
module.exports.NodeDiscoveryConfig = nativeBinding.NodeDiscoveryConfig
module.exports.Origin = nativeBinding.Origin
module.exports.pathToKey = nativeBinding.pathToKey
module.exports.setLogLevel = nativeBinding.setLogLevel
Expand Down
2 changes: 1 addition & 1 deletion iroh-js/src/author.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl Iroh {

impl Authors {
fn client(&self) -> &iroh::client::Iroh {
self.node.client()
self.node.inner_client()
}
}

Expand Down
2 changes: 1 addition & 1 deletion iroh-js/src/blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl Iroh {

impl Blobs {
fn client(&self) -> &iroh::client::Iroh {
self.node.client()
self.node.inner_client()
}
}

Expand Down
2 changes: 1 addition & 1 deletion iroh-js/src/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl Iroh {

impl Docs {
fn client(&self) -> &iroh::client::Iroh {
self.node.client()
self.node.inner_client()
}
}

Expand Down
2 changes: 1 addition & 1 deletion iroh-js/src/gossip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ impl Iroh {

impl Gossip {
fn client(&self) -> &iroh::client::Iroh {
self.node.client()
self.node.inner_client()
}
}

Expand Down
2 changes: 1 addition & 1 deletion iroh-js/src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl Iroh {

impl Net {
fn client(&self) -> &iroh::client::Iroh {
self.node.client()
self.node.inner_client()
}
}

Expand Down
150 changes: 122 additions & 28 deletions iroh-js/src/node.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{collections::HashMap, path::PathBuf, time::Duration};

use iroh::node::{FsNode, MemNode};
use iroh::node::{FsNode, MemNode, DEFAULT_RPC_ADDR};
use napi::{
bindgen_prelude::*,
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
Expand All @@ -17,17 +17,64 @@ pub struct NodeOptions {
pub gc_interval_millis: Option<u32>,
/// Provide a callback to hook into events when the blobs component adds and provides blobs.
pub blob_events: Option<ThreadsafeFunction<BlobProvideEvent, ()>>,
/// Should docs be enabled? Defaults to `true`.
pub enable_docs: Option<bool>,
/// Overwrites the default bind port if set.
pub port: Option<u16>,
/// Enable RPC. Defaults to `false`.
pub enable_rpc: Option<bool>,
/// Overwrite the default RPC address.
pub rpc_addr: Option<String>,
/// Configure the node discovery.
pub node_discovery: Option<NodeDiscoveryConfig>,
/// Provide a specific secret key, identifying this node. Must be 32 bytes long.
pub secret_key: Option<Vec<u8>>,
}

impl Default for NodeOptions {
fn default() -> Self {
NodeOptions {
gc_interval_millis: None,
blob_events: None,
enable_docs: None,
enable_rpc: None,
rpc_addr: None,
port: None,
node_discovery: None,
secret_key: None,
}
}
}

#[derive(Debug, Default)]
#[napi(string_enum)]
pub enum NodeDiscoveryConfig {
/// Use no node discovery mechanism.
None,
/// Use the default discovery mechanism.
///
/// This uses two discovery services concurrently:
///
/// - It publishes to a pkarr service operated by [number 0] which makes the information
/// available via DNS in the `iroh.link` domain.
///
/// - It uses an mDNS-like system to announce itself on the local network.
///
/// # Usage during tests
///
/// Note that the default changes when compiling with `cfg(test)` or the `test-utils`
/// cargo feature from [iroh-net] is enabled. In this case only the Pkarr/DNS service
/// is used, but on the `iroh.test` domain. This domain is not integrated with the
/// global DNS network and thus node discovery is effectively disabled. To use node
/// discovery in a test use the [`iroh_net::test_utils::DnsPkarrServer`] in the test and
/// configure it here as a custom discovery mechanism ([`DiscoveryConfig::Custom`]).
///
/// [number 0]: https://n0.computer
/// [iroh-net]: crate::net
#[default]
Default,
}

/// An Iroh node. Allows you to sync, store, and transfer data.
#[derive(Debug, Clone)]
#[napi]
Expand All @@ -37,13 +84,15 @@ pub struct Iroh(InnerIroh);
enum InnerIroh {
Fs(FsNode),
Memory(MemNode),
Client(iroh::client::Iroh),
}

impl Iroh {
pub(crate) fn client(&self) -> &iroh::client::Iroh {
pub(crate) fn inner_client(&self) -> &iroh::client::Iroh {
match &self.0 {
InnerIroh::Fs(node) => node,
InnerIroh::Memory(node) => node,
InnerIroh::Client(client) => client,
}
}
}
Expand All @@ -60,18 +109,8 @@ impl Iroh {

let path = PathBuf::from(path);

let mut builder = iroh::node::Builder::default().persist(path).await?;
if let Some(millis) = options.gc_interval_millis {
let policy = match millis {
0 => iroh::node::GcPolicy::Disabled,
millis => iroh::node::GcPolicy::Interval(Duration::from_millis(millis as _)),
};
builder = builder.gc_policy(policy);
}
if let Some(blob_events_cb) = options.blob_events {
builder = builder.blobs_events(BlobProvideEvents::new(blob_events_cb))
}

let builder = iroh::node::Builder::default().persist(path).await?;
let builder = apply_options(builder, options).await?;
let node = builder.spawn().await?;

Ok(Iroh(InnerIroh::Fs(node)))
Expand All @@ -84,31 +123,85 @@ impl Iroh {
pub async fn memory(opts: Option<NodeOptions>) -> Result<Self> {
let options = opts.unwrap_or_default();

let mut builder = iroh::node::Builder::default();
if let Some(millis) = options.gc_interval_millis {
let policy = match millis {
0 => iroh::node::GcPolicy::Disabled,
millis => iroh::node::GcPolicy::Interval(Duration::from_millis(millis as _)),
};
builder = builder.gc_policy(policy);
}

if let Some(blob_events_cb) = options.blob_events {
builder = builder.blobs_events(BlobProvideEvents::new(blob_events_cb))
}

let builder = iroh::node::Builder::default();
let builder = apply_options(builder, options).await?;
let node = builder.spawn().await?;

Ok(Iroh(InnerIroh::Memory(node)))
}

/// Create a new iroh client, connecting to an existing node.
#[napi(factory)]
pub async fn client(addr: Option<String>) -> Result<Self> {
let addr = match addr {
Some(addr) => addr.parse().map_err(anyhow::Error::from)?,
None => DEFAULT_RPC_ADDR,
};
let client = iroh::client::Iroh::connect_addr(addr).await?;

Ok(Iroh(InnerIroh::Client(client)))
}

/// Access to node specific funtionaliy.
#[napi(getter)]
pub fn node(&self) -> Node {
Node { node: self.clone() }
}
}

async fn apply_options<S: iroh::blobs::store::Store>(
mut builder: iroh::node::Builder<S>,
options: NodeOptions,
) -> Result<iroh::node::Builder<S>> {
if let Some(millis) = options.gc_interval_millis {
let policy = match millis {
0 => iroh::node::GcPolicy::Disabled,
millis => iroh::node::GcPolicy::Interval(Duration::from_millis(millis as _)),
};
builder = builder.gc_policy(policy);
}

if let Some(blob_events_cb) = options.blob_events {
builder = builder.blobs_events(BlobProvideEvents::new(blob_events_cb))
}

if !options.enable_docs.unwrap_or(true) {
builder = builder.disable_docs();
}

if let Some(port) = options.port {
builder = builder.bind_port(port);
}

if options.enable_rpc.unwrap_or(false) {
builder = builder.enable_rpc().await?;
}

if let Some(addr) = options.rpc_addr {
builder = builder
.enable_rpc_with_addr(addr.parse().map_err(anyhow::Error::from)?)
.await?;
}
builder = match options.node_discovery {
Some(NodeDiscoveryConfig::None) => {
builder.node_discovery(iroh::node::DiscoveryConfig::None)
}
Some(NodeDiscoveryConfig::Default) | None => {
builder.node_discovery(iroh::node::DiscoveryConfig::Default)
}
};

if let Some(secret_key) = options.secret_key {
let key: [u8; 32] = AsRef::<[u8]>::as_ref(&secret_key)
.try_into()
.map_err(anyhow::Error::from)?;
let key = iroh::net::key::SecretKey::from_bytes(&key);
builder = builder.secret_key(key);
}

Ok(builder)
}

/// Iroh node client.
#[napi]
pub struct Node {
Expand All @@ -117,7 +210,7 @@ pub struct Node {

impl Node {
fn node(&self) -> &iroh::client::Iroh {
self.node.client()
self.node.inner_client()
}
}

Expand Down Expand Up @@ -162,6 +255,7 @@ impl Node {
let addr = match self.node.0 {
InnerIroh::Fs(ref n) => n.my_rpc_addr(),
InnerIroh::Memory(ref n) => n.my_rpc_addr(),
InnerIroh::Client(_) => None, // Not yet available
};
addr.map(|a| a.to_string())
}
Expand Down
Loading
Loading