Skip to main content

syncweb_core/node/
iroh_node.rs

1use iroh::address_lookup::memory::MemoryLookup;
2use iroh::protocol::Router;
3use iroh_blobs::BlobsProtocol;
4use iroh_docs::protocol::Docs;
5use iroh_gossip::net::Gossip;
6use std::path::PathBuf;
7use std::sync::Arc;
8
9use crate::error::{Result, SyncwebError};
10
11use super::discovery::TopicTracker;
12use super::identity::IdentityManager;
13use super::{blob_store::BlobStore, docs_engine::DocsEngine, gossip_service::GossipService};
14
15#[non_exhaustive]
16pub enum RelayMode {
17    Default,
18    Custom { map: iroh::RelayMap, insecure: bool },
19}
20
21pub struct IrohNode {
22    endpoint: iroh::Endpoint,
23    router: Arc<Router>,
24    blobs: iroh_blobs::BlobsProtocol,
25    docs: Docs,
26    gossip: Arc<Gossip>,
27    blob_store: BlobStore,
28    docs_engine: DocsEngine,
29    gossip_service: GossipService,
30    topic_tracker: TopicTracker,
31}
32
33impl IrohNode {
34    /// Creates a node and starts accepting the blobs, docs, and gossip protocols.
35    ///
36    /// # Errors
37    ///
38    /// Returns an error if the directory structure cannot be created, if binding the endpoint fails, or if starting the background services fails.
39    pub async fn new(identity: IdentityManager, data_dir: PathBuf, relay_mode: RelayMode) -> Result<Self> {
40        Self::new_with_address_lookup(identity, data_dir, relay_mode, MemoryLookup::new()).await
41    }
42
43    /// # Errors
44    ///
45    /// Returns an error if the directory structure cannot be created, if binding the endpoint fails, or if starting the background services fails.
46    pub async fn new_with_address_lookup(
47        identity: IdentityManager,
48        data_dir: PathBuf,
49        relay_mode: RelayMode,
50        address_lookup: MemoryLookup,
51    ) -> Result<Self> {
52        tokio::fs::create_dir_all(&data_dir)
53            .await
54            .map_err(|error| SyncwebError::operation("failed to create node data directory", error))?;
55        let docs_dir = data_dir.join("docs");
56        tokio::fs::create_dir_all(&docs_dir)
57            .await
58            .map_err(|error| SyncwebError::operation("failed to create docs directory", error))?;
59
60        let builder = match relay_mode {
61            RelayMode::Default => iroh::Endpoint::builder(iroh::endpoint::presets::N0),
62            RelayMode::Custom { map, insecure } => {
63                let mut b = iroh::Endpoint::builder(iroh::endpoint::presets::N0)
64                    .relay_mode(iroh::endpoint::RelayMode::Custom(map));
65                if insecure {
66                    b = b.ca_tls_config(iroh::tls::CaTlsConfig::insecure_skip_verify());
67                }
68                b
69            }
70        };
71
72        let endpoint = builder
73            .address_lookup(address_lookup.clone())
74            .secret_key(identity.secret_key().clone())
75            .bind()
76            .await
77            .map_err(|error| SyncwebError::operation("failed to bind Iroh endpoint", error))?;
78
79        let fs_blob_store = iroh_blobs::store::fs::FsStore::load(data_dir.join("blobs"))
80            .await
81            .map_err(|error| SyncwebError::operation("failed to open blob store", error))?;
82        let blobs = BlobsProtocol::new(fs_blob_store.as_ref(), None);
83
84        let gossip = Arc::new(Gossip::builder().spawn(endpoint.clone()));
85
86        let docs = Docs::persistent(docs_dir)
87            .spawn(endpoint.clone(), blobs.store().clone(), gossip.as_ref().clone())
88            .await
89            .map_err(|error| SyncwebError::operation("failed to open docs store", error))?;
90
91        let router = Router::builder(endpoint.clone())
92            .accept(iroh_blobs::protocol::ALPN, blobs.clone())
93            .accept(iroh_docs::ALPN, docs.clone())
94            .accept(iroh_gossip::ALPN, gossip.clone())
95            .spawn();
96
97        let blob_store = BlobStore::new_with_address_lookup(&blobs, address_lookup);
98        let docs_engine = DocsEngine::new(&docs);
99        let gossip_service = GossipService::new(&gossip);
100        let topic_tracker = TopicTracker::new(&gossip, &endpoint);
101
102        Ok(Self {
103            endpoint,
104            router: Arc::new(router),
105            blobs,
106            docs,
107            gossip,
108            blob_store,
109            docs_engine,
110            gossip_service,
111            topic_tracker,
112        })
113    }
114
115    #[must_use]
116    pub const fn endpoint(&self) -> &iroh::Endpoint {
117        &self.endpoint
118    }
119
120    #[must_use]
121    pub const fn blobs(&self) -> &BlobsProtocol {
122        &self.blobs
123    }
124
125    #[must_use]
126    pub const fn docs(&self) -> &Docs {
127        &self.docs
128    }
129
130    #[must_use]
131    pub fn gossip(&self) -> &Gossip {
132        &self.gossip
133    }
134
135    #[must_use]
136    pub const fn blob_store(&self) -> &BlobStore {
137        &self.blob_store
138    }
139
140    #[must_use]
141    pub const fn docs_engine(&self) -> &DocsEngine {
142        &self.docs_engine
143    }
144
145    #[must_use]
146    pub const fn gossip_service(&self) -> &GossipService {
147        &self.gossip_service
148    }
149
150    #[must_use]
151    pub const fn topic_tracker(&self) -> &TopicTracker {
152        &self.topic_tracker
153    }
154
155    #[must_use]
156    pub fn is_running(&self) -> bool {
157        !self.router.is_shutdown()
158    }
159
160    /// # Errors
161    ///
162    /// Returns an error if the router fails to shutdown properly.
163    pub async fn stop(&self) -> Result<()> {
164        self.router
165            .shutdown()
166            .await
167            .map_err(|error| SyncwebError::operation("failed to stop node router", error))?;
168        Ok(())
169    }
170
171    /// # Errors
172    ///
173    /// Returns an error if stopping the node fails.
174    pub async fn shutdown(self) -> Result<()> {
175        self.stop().await
176    }
177}