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::collections::HashSet;
7use std::path::PathBuf;
8use std::sync::{Arc, RwLock};
9
10use crate::error::{Result, SyncwebError};
11
12use super::discovery::TopicTracker;
13use super::identity::IdentityManager;
14use super::membership_hook::MembershipHook;
15use super::{blob_store::BlobStore, docs_engine::DocsEngine, gossip_service::GossipService};
16
17#[non_exhaustive]
18#[derive(Clone, Debug)]
19pub enum RelayMode {
20 Default,
21 Custom { map: iroh::RelayMap, insecure: bool },
22 None,
23}
24
25pub struct IrohNode {
26 endpoint: iroh::Endpoint,
27 router: Arc<Router>,
28 blobs: iroh_blobs::BlobsProtocol,
29 docs: Docs,
30 gossip: Arc<Gossip>,
31 blob_store: BlobStore,
32 docs_engine: DocsEngine,
33 gossip_service: GossipService,
34 topic_tracker: TopicTracker,
35}
36
37impl IrohNode {
38 pub async fn new(
44 identity: IdentityManager,
45 data_dir: PathBuf,
46 relay_mode: RelayMode,
47 member_keys: Arc<RwLock<HashSet<iroh::PublicKey>>>,
48 ) -> Result<Self> {
49 Self::new_with_address_lookup(identity, data_dir, relay_mode, MemoryLookup::new(), member_keys).await
50 }
51
52 pub async fn new_with_address_lookup(
56 identity: IdentityManager,
57 data_dir: PathBuf,
58 relay_mode: RelayMode,
59 address_lookup: MemoryLookup,
60 member_keys: Arc<RwLock<HashSet<iroh::PublicKey>>>,
61 ) -> Result<Self> {
62 tokio::fs::create_dir_all(&data_dir)
63 .await
64 .map_err(|error| SyncwebError::operation("failed to create node data directory", error))?;
65 let docs_dir = data_dir.join("docs");
66 tokio::fs::create_dir_all(&docs_dir)
67 .await
68 .map_err(|error| SyncwebError::operation("failed to create docs directory", error))?;
69
70 let builder = match relay_mode {
71 RelayMode::Default => iroh::Endpoint::builder(iroh::endpoint::presets::N0),
72 RelayMode::Custom { map, insecure } => {
73 let mut b = iroh::Endpoint::builder(iroh::endpoint::presets::N0)
74 .relay_mode(iroh::endpoint::RelayMode::Custom(map));
75 if insecure {
76 b = b.ca_tls_config(iroh::tls::CaTlsConfig::insecure_skip_verify());
77 }
78 b
79 }
80 RelayMode::None => {
81 iroh::Endpoint::builder(iroh::endpoint::presets::N0).relay_mode(iroh::endpoint::RelayMode::Disabled)
82 }
83 };
84
85 let hook = MembershipHook {
86 member_keys: member_keys.clone(),
87 };
88 let endpoint = builder
89 .address_lookup(address_lookup.clone())
90 .secret_key(identity.secret_key().clone())
91 .hooks(hook)
92 .bind()
93 .await
94 .map_err(|error| SyncwebError::operation("failed to bind Iroh endpoint", error))?;
95
96 if let Ok(_mdns) = iroh_mdns_address_lookup::MdnsAddressLookup::builder()
98 .build(endpoint.id())
99 .map_err(|error| SyncwebError::operation("failed to build mDNS address lookup", error))
100 .and_then(|mdns| {
101 endpoint
102 .address_lookup()
103 .map_err(|error| SyncwebError::operation("no address lookup service available", error))
104 .map(|lookup| lookup.add(mdns))
105 })
106 {
107 tracing::debug!("mDNS address lookup registered");
108 }
109
110 let fs_blob_store = iroh_blobs::store::fs::FsStore::load(data_dir.join("blobs"))
111 .await
112 .map_err(|error| SyncwebError::operation("failed to open blob store", error))?;
113 let blobs = BlobsProtocol::new(fs_blob_store.as_ref(), None);
114
115 let gossip = Arc::new(Gossip::builder().spawn(endpoint.clone()));
116
117 let docs = Docs::persistent(docs_dir)
118 .spawn(endpoint.clone(), blobs.store().clone(), gossip.as_ref().clone())
119 .await
120 .map_err(|error| SyncwebError::operation("failed to open docs store", error))?;
121
122 let router = Router::builder(endpoint.clone())
123 .accept(iroh_blobs::protocol::ALPN, blobs.clone())
124 .accept(iroh_docs::ALPN, docs.clone())
125 .accept(iroh_gossip::ALPN, gossip.clone())
126 .spawn();
127
128 let blob_store = BlobStore::new_with_address_lookup(&blobs, address_lookup);
129 let docs_engine = DocsEngine::new(&docs);
130 let gossip_service = GossipService::new(&gossip);
131 let topic_tracker = TopicTracker::new(&gossip, &endpoint);
132
133 Ok(Self {
134 endpoint,
135 router: Arc::new(router),
136 blobs,
137 docs,
138 gossip,
139 blob_store,
140 docs_engine,
141 gossip_service,
142 topic_tracker,
143 })
144 }
145
146 #[must_use]
147 pub const fn endpoint(&self) -> &iroh::Endpoint {
148 &self.endpoint
149 }
150
151 #[must_use]
152 pub const fn blobs(&self) -> &BlobsProtocol {
153 &self.blobs
154 }
155
156 #[must_use]
157 pub const fn docs(&self) -> &Docs {
158 &self.docs
159 }
160
161 #[must_use]
162 pub fn gossip(&self) -> &Gossip {
163 &self.gossip
164 }
165
166 #[must_use]
167 pub const fn blob_store(&self) -> &BlobStore {
168 &self.blob_store
169 }
170
171 #[must_use]
172 pub const fn docs_engine(&self) -> &DocsEngine {
173 &self.docs_engine
174 }
175
176 #[must_use]
177 pub const fn gossip_service(&self) -> &GossipService {
178 &self.gossip_service
179 }
180
181 #[must_use]
182 pub const fn topic_tracker(&self) -> &TopicTracker {
183 &self.topic_tracker
184 }
185
186 #[must_use]
187 pub fn is_running(&self) -> bool {
188 !self.router.is_shutdown()
189 }
190
191 pub async fn stop(&self) -> Result<()> {
195 self.router
196 .shutdown()
197 .await
198 .map_err(|error| SyncwebError::operation("failed to stop node router", error))?;
199 Ok(())
200 }
201
202 pub async fn shutdown(self) -> Result<()> {
206 self.stop().await
207 }
208}