syncweb_core/node/
iroh_node.rs1use 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 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 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 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 pub async fn shutdown(self) -> Result<()> {
175 self.stop().await
176 }
177}