@cc-remote/iroh 1.0.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yarnrc.yml +3 -0
- package/Cargo.toml +30 -0
- package/README.md +38 -0
- package/index.d.ts +478 -0
- package/index.js +603 -0
- package/package.json +66 -0
- package/src/endpoint.rs +1018 -0
- package/src/key.rs +173 -0
- package/src/lib.rs +59 -0
- package/src/net.rs +106 -0
- package/src/path.rs +167 -0
- package/src/relay.rs +194 -0
- package/src/services.rs +167 -0
- package/src/ticket.rs +46 -0
- package/src/watch.rs +111 -0
- package/test/endpoint.mjs +176 -0
- package/test/key.mjs +71 -0
- package/test/relay.mjs +40 -0
- package/test/services.mjs +47 -0
- package/tsconfig.json +11 -0
- package/typedoc.json +9 -0
package/src/key.rs
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
use std::str::FromStr;
|
|
2
|
+
|
|
3
|
+
use napi::bindgen_prelude::*;
|
|
4
|
+
use napi_derive::napi;
|
|
5
|
+
|
|
6
|
+
/// An endpoint's identifier, a 32-byte ed25519 public key.
|
|
7
|
+
#[derive(Debug, Clone, Eq)]
|
|
8
|
+
#[napi]
|
|
9
|
+
pub struct EndpointId {
|
|
10
|
+
key: [u8; 32],
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
impl From<iroh::EndpointId> for EndpointId {
|
|
14
|
+
fn from(key: iroh::EndpointId) -> Self {
|
|
15
|
+
EndpointId {
|
|
16
|
+
key: *key.as_bytes(),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl From<&EndpointId> for iroh::EndpointId {
|
|
22
|
+
fn from(key: &EndpointId) -> Self {
|
|
23
|
+
iroh::EndpointId::from_bytes(&key.key).expect("EndpointId bytes are always valid")
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
impl EndpointId {
|
|
28
|
+
pub(crate) fn raw_bytes(&self) -> [u8; 32] {
|
|
29
|
+
self.key
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub(crate) fn from_raw_bytes(key: [u8; 32]) -> Self {
|
|
33
|
+
EndpointId { key }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#[napi]
|
|
38
|
+
impl EndpointId {
|
|
39
|
+
/// Returns true if both [`EndpointId`]s are equal.
|
|
40
|
+
#[napi]
|
|
41
|
+
pub fn equals(&self, other: &EndpointId) -> bool {
|
|
42
|
+
*self == *other
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Get the underlying 32 bytes.
|
|
46
|
+
#[napi]
|
|
47
|
+
pub fn to_bytes(&self) -> Vec<u8> {
|
|
48
|
+
self.key.to_vec()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Parse an [`EndpointId`] from its base32 representation.
|
|
52
|
+
#[napi(factory)]
|
|
53
|
+
pub fn from_string(s: String) -> Result<Self> {
|
|
54
|
+
let key = iroh::EndpointId::from_str(&s).map_err(anyhow::Error::from)?;
|
|
55
|
+
Ok(key.into())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Construct an [`EndpointId`] from raw bytes (32 bytes).
|
|
59
|
+
#[napi(factory)]
|
|
60
|
+
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
|
|
61
|
+
let bytes: [u8; 32] = bytes
|
|
62
|
+
.try_into()
|
|
63
|
+
.map_err(|_| anyhow::anyhow!("EndpointId requires exactly 32 bytes"))?;
|
|
64
|
+
let key = iroh::EndpointId::from_bytes(&bytes).map_err(anyhow::Error::from)?;
|
|
65
|
+
Ok(key.into())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Short base32 prefix.
|
|
69
|
+
#[napi]
|
|
70
|
+
pub fn fmt_short(&self) -> String {
|
|
71
|
+
iroh::EndpointId::from(self).fmt_short().to_string()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Base32 string form.
|
|
75
|
+
#[napi]
|
|
76
|
+
pub fn to_string(&self) -> String {
|
|
77
|
+
iroh::EndpointId::from(self).to_string()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Verify a signature on `message` against this endpoint's key.
|
|
81
|
+
#[napi]
|
|
82
|
+
pub fn verify(&self, message: Vec<u8>, signature: &Signature) -> Result<()> {
|
|
83
|
+
iroh::EndpointId::from(self)
|
|
84
|
+
.verify(&message, &signature.0)
|
|
85
|
+
.map_err(|e| anyhow::anyhow!("signature verification failed: {e:?}").into())
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
impl PartialEq for EndpointId {
|
|
90
|
+
fn eq(&self, other: &EndpointId) -> bool {
|
|
91
|
+
self.key == other.key
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// The secret key half of an endpoint identity.
|
|
96
|
+
#[derive(Debug, Clone)]
|
|
97
|
+
#[napi]
|
|
98
|
+
pub struct SecretKey(pub(crate) iroh::SecretKey);
|
|
99
|
+
|
|
100
|
+
impl From<iroh::SecretKey> for SecretKey {
|
|
101
|
+
fn from(key: iroh::SecretKey) -> Self {
|
|
102
|
+
SecretKey(key)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#[napi]
|
|
107
|
+
impl SecretKey {
|
|
108
|
+
/// Generate a new random secret key.
|
|
109
|
+
#[napi(factory)]
|
|
110
|
+
pub fn generate() -> Self {
|
|
111
|
+
SecretKey(iroh::SecretKey::generate())
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Construct from raw bytes (32 bytes).
|
|
115
|
+
#[napi(factory)]
|
|
116
|
+
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
|
|
117
|
+
let bytes: [u8; 32] = bytes
|
|
118
|
+
.try_into()
|
|
119
|
+
.map_err(|_| anyhow::anyhow!("SecretKey requires exactly 32 bytes"))?;
|
|
120
|
+
Ok(SecretKey(iroh::SecretKey::from_bytes(&bytes)))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Raw 32-byte form.
|
|
124
|
+
#[napi]
|
|
125
|
+
pub fn to_bytes(&self) -> Vec<u8> {
|
|
126
|
+
self.0.to_bytes().to_vec()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// The public [`EndpointId`] derived from this secret key.
|
|
130
|
+
#[napi]
|
|
131
|
+
pub fn public(&self) -> EndpointId {
|
|
132
|
+
self.0.public().into()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// Sign a message, producing an ed25519 signature.
|
|
136
|
+
#[napi]
|
|
137
|
+
pub fn sign(&self, message: Vec<u8>) -> Signature {
|
|
138
|
+
Signature(self.0.sign(&message))
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// An ed25519 signature over a message.
|
|
143
|
+
#[derive(Debug, Clone)]
|
|
144
|
+
#[napi]
|
|
145
|
+
pub struct Signature(pub(crate) iroh_base::Signature);
|
|
146
|
+
|
|
147
|
+
#[napi]
|
|
148
|
+
impl Signature {
|
|
149
|
+
/// Construct from raw bytes (64 bytes).
|
|
150
|
+
#[napi(factory)]
|
|
151
|
+
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
|
|
152
|
+
let bytes: [u8; 64] = bytes
|
|
153
|
+
.try_into()
|
|
154
|
+
.map_err(|_| anyhow::anyhow!("Signature requires exactly 64 bytes"))?;
|
|
155
|
+
Ok(Signature(iroh_base::Signature::from_bytes(&bytes)))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// Raw 64-byte form.
|
|
159
|
+
#[napi]
|
|
160
|
+
pub fn to_bytes(&self) -> Vec<u8> {
|
|
161
|
+
self.0.to_bytes().to_vec()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Lowercase hex representation.
|
|
165
|
+
#[napi]
|
|
166
|
+
pub fn to_string(&self) -> String {
|
|
167
|
+
self.0
|
|
168
|
+
.to_bytes()
|
|
169
|
+
.iter()
|
|
170
|
+
.map(|b| format!("{b:02x}"))
|
|
171
|
+
.collect()
|
|
172
|
+
}
|
|
173
|
+
}
|
package/src/lib.rs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
use napi_derive::napi;
|
|
2
|
+
use tracing_subscriber::filter::LevelFilter;
|
|
3
|
+
|
|
4
|
+
mod endpoint;
|
|
5
|
+
mod key;
|
|
6
|
+
mod net;
|
|
7
|
+
mod path;
|
|
8
|
+
mod relay;
|
|
9
|
+
mod services;
|
|
10
|
+
mod ticket;
|
|
11
|
+
mod watch;
|
|
12
|
+
|
|
13
|
+
pub use endpoint::*;
|
|
14
|
+
pub use key::*;
|
|
15
|
+
pub use net::*;
|
|
16
|
+
pub use path::*;
|
|
17
|
+
pub use relay::*;
|
|
18
|
+
pub use services::*;
|
|
19
|
+
pub use ticket::*;
|
|
20
|
+
pub use watch::*;
|
|
21
|
+
|
|
22
|
+
/// The logging level. See the rust (log crate)[https://docs.rs/log] for more information.
|
|
23
|
+
#[derive(Debug)]
|
|
24
|
+
#[napi(string_enum)]
|
|
25
|
+
pub enum LogLevel {
|
|
26
|
+
Trace,
|
|
27
|
+
Debug,
|
|
28
|
+
Info,
|
|
29
|
+
Warn,
|
|
30
|
+
Error,
|
|
31
|
+
Off,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
impl From<LogLevel> for LevelFilter {
|
|
35
|
+
fn from(level: LogLevel) -> LevelFilter {
|
|
36
|
+
match level {
|
|
37
|
+
LogLevel::Trace => LevelFilter::TRACE,
|
|
38
|
+
LogLevel::Debug => LevelFilter::DEBUG,
|
|
39
|
+
LogLevel::Info => LevelFilter::INFO,
|
|
40
|
+
LogLevel::Warn => LevelFilter::WARN,
|
|
41
|
+
LogLevel::Error => LevelFilter::ERROR,
|
|
42
|
+
LogLevel::Off => LevelFilter::OFF,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Set the logging level.
|
|
48
|
+
#[napi]
|
|
49
|
+
pub fn set_log_level(level: LogLevel) {
|
|
50
|
+
use tracing_subscriber::{fmt, prelude::*, reload};
|
|
51
|
+
let filter: LevelFilter = level.into();
|
|
52
|
+
let (filter, _) = reload::Layer::new(filter);
|
|
53
|
+
let mut layer = fmt::Layer::default();
|
|
54
|
+
layer.set_ansi(false);
|
|
55
|
+
tracing_subscriber::registry()
|
|
56
|
+
.with(filter)
|
|
57
|
+
.with(layer)
|
|
58
|
+
.init();
|
|
59
|
+
}
|
package/src/net.rs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
use std::{collections::BTreeSet, net::SocketAddr, str::FromStr};
|
|
2
|
+
|
|
3
|
+
use iroh_base::{RelayUrl, TransportAddr};
|
|
4
|
+
use napi_derive::napi;
|
|
5
|
+
|
|
6
|
+
use crate::EndpointId;
|
|
7
|
+
|
|
8
|
+
/// An endpoint's id together with the network-level addresses where it can be
|
|
9
|
+
/// reached. Mirrors the uniffi `EndpointAddr` object surface.
|
|
10
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
11
|
+
#[napi]
|
|
12
|
+
pub struct EndpointAddr {
|
|
13
|
+
pub(crate) id: [u8; 32],
|
|
14
|
+
pub(crate) relay_url: Option<String>,
|
|
15
|
+
pub(crate) addresses: Vec<String>,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#[napi]
|
|
19
|
+
impl EndpointAddr {
|
|
20
|
+
/// Create a new [`EndpointAddr`].
|
|
21
|
+
#[napi(constructor)]
|
|
22
|
+
pub fn new(id: &EndpointId, relay_url: Option<String>, addresses: Option<Vec<String>>) -> Self {
|
|
23
|
+
Self {
|
|
24
|
+
id: id.raw_bytes(),
|
|
25
|
+
relay_url,
|
|
26
|
+
addresses: addresses.unwrap_or_default(),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// The endpoint id.
|
|
31
|
+
#[napi]
|
|
32
|
+
pub fn id(&self) -> EndpointId {
|
|
33
|
+
EndpointId::from_raw_bytes(self.id)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// The direct (ip:port) addresses of this peer.
|
|
37
|
+
#[napi]
|
|
38
|
+
pub fn direct_addresses(&self) -> Vec<String> {
|
|
39
|
+
self.addresses.clone()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// The home relay URL for this peer, if known.
|
|
43
|
+
#[napi]
|
|
44
|
+
pub fn relay_url(&self) -> Option<String> {
|
|
45
|
+
self.relay_url.clone()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Returns true if both [`EndpointAddr`]s have the same values.
|
|
49
|
+
#[napi]
|
|
50
|
+
pub fn equal(&self, other: &EndpointAddr) -> bool {
|
|
51
|
+
self == other
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Clean string representation.
|
|
55
|
+
#[napi]
|
|
56
|
+
pub fn to_string(&self) -> String {
|
|
57
|
+
let id = iroh::EndpointId::from_bytes(&self.id)
|
|
58
|
+
.map(|i| i.to_string())
|
|
59
|
+
.unwrap_or_else(|_| "<invalid>".to_string());
|
|
60
|
+
let mut s = id;
|
|
61
|
+
if let Some(relay) = &self.relay_url {
|
|
62
|
+
s.push_str(&format!(" relay={relay}"));
|
|
63
|
+
}
|
|
64
|
+
if !self.addresses.is_empty() {
|
|
65
|
+
s.push_str(&format!(" addrs=[{}]", self.addresses.join(", ")));
|
|
66
|
+
}
|
|
67
|
+
s
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
impl TryFrom<&EndpointAddr> for iroh::EndpointAddr {
|
|
72
|
+
type Error = anyhow::Error;
|
|
73
|
+
|
|
74
|
+
fn try_from(value: &EndpointAddr) -> anyhow::Result<Self> {
|
|
75
|
+
let id = iroh::EndpointId::from_bytes(&value.id)?;
|
|
76
|
+
let mut addrs: BTreeSet<TransportAddr> = BTreeSet::new();
|
|
77
|
+
for addr in &value.addresses {
|
|
78
|
+
let socket = SocketAddr::from_str(addr)?;
|
|
79
|
+
addrs.insert(TransportAddr::Ip(socket));
|
|
80
|
+
}
|
|
81
|
+
if let Some(relay) = &value.relay_url {
|
|
82
|
+
let url = RelayUrl::from_str(relay)?;
|
|
83
|
+
addrs.insert(TransportAddr::Relay(url));
|
|
84
|
+
}
|
|
85
|
+
Ok(iroh::EndpointAddr { id, addrs })
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
impl From<iroh::EndpointAddr> for EndpointAddr {
|
|
90
|
+
fn from(value: iroh::EndpointAddr) -> Self {
|
|
91
|
+
let mut relay_url = None;
|
|
92
|
+
let mut addresses = Vec::new();
|
|
93
|
+
for addr in &value.addrs {
|
|
94
|
+
match addr {
|
|
95
|
+
TransportAddr::Relay(url) => relay_url = Some(url.to_string()),
|
|
96
|
+
TransportAddr::Ip(socket) => addresses.push(socket.to_string()),
|
|
97
|
+
_ => {}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
EndpointAddr {
|
|
101
|
+
id: *value.id.as_bytes(),
|
|
102
|
+
relay_url,
|
|
103
|
+
addresses,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
package/src/path.rs
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
use iroh::endpoint::LocalTransportAddr;
|
|
2
|
+
use iroh_base::TransportAddr;
|
|
3
|
+
use napi_derive::napi;
|
|
4
|
+
|
|
5
|
+
/// Flattened headline numbers from `noq::PathStats`.
|
|
6
|
+
#[derive(Debug, Clone)]
|
|
7
|
+
#[napi(object)]
|
|
8
|
+
pub struct PathStatsRecord {
|
|
9
|
+
pub rtt_ms: i64,
|
|
10
|
+
pub udp_tx_datagrams: i64,
|
|
11
|
+
pub udp_tx_bytes: i64,
|
|
12
|
+
pub udp_rx_datagrams: i64,
|
|
13
|
+
pub udp_rx_bytes: i64,
|
|
14
|
+
pub cwnd: i64,
|
|
15
|
+
pub congestion_events: i64,
|
|
16
|
+
pub lost_packets: i64,
|
|
17
|
+
pub lost_bytes: i64,
|
|
18
|
+
pub current_mtu: u32,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl From<iroh::endpoint::PathStats> for PathStatsRecord {
|
|
22
|
+
fn from(s: iroh::endpoint::PathStats) -> Self {
|
|
23
|
+
Self {
|
|
24
|
+
rtt_ms: s.rtt.as_millis() as i64,
|
|
25
|
+
udp_tx_datagrams: s.udp_tx.datagrams as i64,
|
|
26
|
+
udp_tx_bytes: s.udp_tx.bytes as i64,
|
|
27
|
+
udp_rx_datagrams: s.udp_rx.datagrams as i64,
|
|
28
|
+
udp_rx_bytes: s.udp_rx.bytes as i64,
|
|
29
|
+
cwnd: s.cwnd as i64,
|
|
30
|
+
congestion_events: s.congestion_events as i64,
|
|
31
|
+
lost_packets: s.lost_packets as i64,
|
|
32
|
+
lost_bytes: s.lost_bytes as i64,
|
|
33
|
+
current_mtu: s.current_mtu as u32,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// A flat snapshot of an open path's state.
|
|
39
|
+
#[derive(Debug, Clone)]
|
|
40
|
+
#[napi(object)]
|
|
41
|
+
pub struct PathSnapshot {
|
|
42
|
+
pub id: String,
|
|
43
|
+
pub is_selected: bool,
|
|
44
|
+
pub remote_addr: String,
|
|
45
|
+
pub is_ip: bool,
|
|
46
|
+
pub is_relay: bool,
|
|
47
|
+
pub rtt_ms: i64,
|
|
48
|
+
pub stats: PathStatsRecord,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub(crate) fn transport_addr_to_string(addr: &TransportAddr) -> String {
|
|
52
|
+
match addr {
|
|
53
|
+
TransportAddr::Ip(socket) => socket.to_string(),
|
|
54
|
+
TransportAddr::Relay(url) => url.to_string(),
|
|
55
|
+
_ => "unknown".to_string(),
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pub(crate) fn local_transport_addr_to_string(addr: &LocalTransportAddr) -> String {
|
|
60
|
+
match addr {
|
|
61
|
+
LocalTransportAddr::Ip(Some(ip)) => ip.to_string(),
|
|
62
|
+
LocalTransportAddr::Ip(None) => "unknown".to_string(),
|
|
63
|
+
LocalTransportAddr::Relay(url) => url.to_string(),
|
|
64
|
+
LocalTransportAddr::Custom(Some(c)) => format!("{c:?}"),
|
|
65
|
+
_ => "unknown".to_string(),
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// An event from `Connection::watchPathEvents`.
|
|
70
|
+
#[derive(Debug, Clone)]
|
|
71
|
+
#[napi(string_enum)]
|
|
72
|
+
pub enum PathEventKind {
|
|
73
|
+
Opened,
|
|
74
|
+
Closed,
|
|
75
|
+
Selected,
|
|
76
|
+
Lagged,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// A path event with its associated data.
|
|
80
|
+
#[derive(Debug, Clone)]
|
|
81
|
+
#[napi(object)]
|
|
82
|
+
pub struct PathEvent {
|
|
83
|
+
pub kind: PathEventKind,
|
|
84
|
+
pub id: Option<String>,
|
|
85
|
+
pub remote_addr: Option<String>,
|
|
86
|
+
pub local_addr: Option<String>,
|
|
87
|
+
pub last_stats: Option<PathStatsRecord>,
|
|
88
|
+
pub missed: Option<i64>,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
impl From<iroh::endpoint::PathEvent> for PathEvent {
|
|
92
|
+
fn from(e: iroh::endpoint::PathEvent) -> Self {
|
|
93
|
+
match e {
|
|
94
|
+
iroh::endpoint::PathEvent::Opened {
|
|
95
|
+
id,
|
|
96
|
+
remote_addr,
|
|
97
|
+
local_addr,
|
|
98
|
+
..
|
|
99
|
+
} => PathEvent {
|
|
100
|
+
kind: PathEventKind::Opened,
|
|
101
|
+
id: Some(id.to_string()),
|
|
102
|
+
remote_addr: Some(transport_addr_to_string(&remote_addr)),
|
|
103
|
+
local_addr: Some(local_transport_addr_to_string(&local_addr)),
|
|
104
|
+
last_stats: None,
|
|
105
|
+
missed: None,
|
|
106
|
+
},
|
|
107
|
+
iroh::endpoint::PathEvent::Closed {
|
|
108
|
+
id,
|
|
109
|
+
remote_addr,
|
|
110
|
+
local_addr,
|
|
111
|
+
last_stats,
|
|
112
|
+
..
|
|
113
|
+
} => PathEvent {
|
|
114
|
+
kind: PathEventKind::Closed,
|
|
115
|
+
id: Some(id.to_string()),
|
|
116
|
+
remote_addr: Some(transport_addr_to_string(&remote_addr)),
|
|
117
|
+
local_addr: Some(local_transport_addr_to_string(&local_addr)),
|
|
118
|
+
last_stats: Some((*last_stats).into()),
|
|
119
|
+
missed: None,
|
|
120
|
+
},
|
|
121
|
+
iroh::endpoint::PathEvent::Selected {
|
|
122
|
+
id,
|
|
123
|
+
remote_addr,
|
|
124
|
+
local_addr,
|
|
125
|
+
..
|
|
126
|
+
} => PathEvent {
|
|
127
|
+
kind: PathEventKind::Selected,
|
|
128
|
+
id: Some(id.to_string()),
|
|
129
|
+
remote_addr: Some(transport_addr_to_string(&remote_addr)),
|
|
130
|
+
local_addr: Some(local_transport_addr_to_string(&local_addr)),
|
|
131
|
+
last_stats: None,
|
|
132
|
+
missed: None,
|
|
133
|
+
},
|
|
134
|
+
iroh::endpoint::PathEvent::Lagged { missed, .. } => PathEvent {
|
|
135
|
+
kind: PathEventKind::Lagged,
|
|
136
|
+
id: None,
|
|
137
|
+
remote_addr: None,
|
|
138
|
+
local_addr: None,
|
|
139
|
+
last_stats: None,
|
|
140
|
+
missed: Some(missed as i64),
|
|
141
|
+
},
|
|
142
|
+
_ => PathEvent {
|
|
143
|
+
kind: PathEventKind::Lagged,
|
|
144
|
+
id: None,
|
|
145
|
+
remote_addr: None,
|
|
146
|
+
local_addr: None,
|
|
147
|
+
last_stats: None,
|
|
148
|
+
missed: Some(0),
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
pub(crate) fn snapshot_paths(conn: &iroh::endpoint::Connection) -> Vec<PathSnapshot> {
|
|
155
|
+
conn.paths()
|
|
156
|
+
.iter()
|
|
157
|
+
.map(|p| PathSnapshot {
|
|
158
|
+
id: p.id().to_string(),
|
|
159
|
+
is_selected: p.is_selected(),
|
|
160
|
+
remote_addr: transport_addr_to_string(p.remote_addr()),
|
|
161
|
+
is_ip: p.is_ip(),
|
|
162
|
+
is_relay: p.is_relay(),
|
|
163
|
+
rtt_ms: p.rtt().as_millis() as i64,
|
|
164
|
+
stats: p.stats().into(),
|
|
165
|
+
})
|
|
166
|
+
.collect()
|
|
167
|
+
}
|
package/src/relay.rs
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
use std::{str::FromStr, sync::Arc};
|
|
2
|
+
|
|
3
|
+
use napi::bindgen_prelude::*;
|
|
4
|
+
use napi_derive::napi;
|
|
5
|
+
|
|
6
|
+
/// Config for a single relay server.
|
|
7
|
+
#[derive(Debug, Clone)]
|
|
8
|
+
#[napi(object)]
|
|
9
|
+
pub struct RelayConfig {
|
|
10
|
+
pub url: String,
|
|
11
|
+
pub quic_port: Option<u16>,
|
|
12
|
+
pub auth_token: Option<String>,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
impl TryFrom<RelayConfig> for iroh::RelayConfig {
|
|
16
|
+
type Error = anyhow::Error;
|
|
17
|
+
|
|
18
|
+
fn try_from(value: RelayConfig) -> anyhow::Result<Self> {
|
|
19
|
+
let url = iroh::RelayUrl::from_str(&value.url)?;
|
|
20
|
+
let quic = value.quic_port.map(iroh_relay::RelayQuicConfig::new);
|
|
21
|
+
let mut config = iroh::RelayConfig::new(url, quic);
|
|
22
|
+
if let Some(token) = value.auth_token {
|
|
23
|
+
config = config.with_auth_token(token);
|
|
24
|
+
}
|
|
25
|
+
Ok(config)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl From<&iroh::RelayConfig> for RelayConfig {
|
|
30
|
+
fn from(value: &iroh::RelayConfig) -> Self {
|
|
31
|
+
Self {
|
|
32
|
+
url: value.url.to_string(),
|
|
33
|
+
quic_port: value.quic.as_ref().map(|q| q.port),
|
|
34
|
+
auth_token: value.auth_token.clone(),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// A collection of relay servers an endpoint should consider.
|
|
40
|
+
#[derive(Debug, Clone)]
|
|
41
|
+
#[napi]
|
|
42
|
+
pub struct RelayMap(pub(crate) iroh::RelayMap);
|
|
43
|
+
|
|
44
|
+
impl From<iroh::RelayMap> for RelayMap {
|
|
45
|
+
fn from(map: iroh::RelayMap) -> Self {
|
|
46
|
+
Self(map)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#[napi]
|
|
51
|
+
impl RelayMap {
|
|
52
|
+
/// Create an empty relay map.
|
|
53
|
+
#[napi(factory)]
|
|
54
|
+
pub fn empty() -> Self {
|
|
55
|
+
Self(iroh::RelayMap::empty())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Build a relay map from a list of relay URLs.
|
|
59
|
+
#[napi(factory)]
|
|
60
|
+
pub fn from_urls(urls: Vec<String>) -> Result<Self> {
|
|
61
|
+
let map = iroh::RelayMap::try_from_iter(urls.iter().map(|s| s.as_str()))
|
|
62
|
+
.map_err(anyhow::Error::from)?;
|
|
63
|
+
Ok(Self(map))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Insert a relay (replacing any prior entry for the same URL).
|
|
67
|
+
#[napi]
|
|
68
|
+
pub fn insert(&self, config: RelayConfig) -> Result<()> {
|
|
69
|
+
let config: iroh::RelayConfig = config.try_into()?;
|
|
70
|
+
let url = config.url.clone();
|
|
71
|
+
self.0.insert(url, Arc::new(config));
|
|
72
|
+
Ok(())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Remove the entry for the given relay URL. Returns true if removed.
|
|
76
|
+
#[napi]
|
|
77
|
+
pub fn remove(&self, url: String) -> Result<bool> {
|
|
78
|
+
let url = iroh::RelayUrl::from_str(&url).map_err(anyhow::Error::from)?;
|
|
79
|
+
Ok(self.0.remove(&url).is_some())
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Check whether the given relay URL is in the map.
|
|
83
|
+
#[napi]
|
|
84
|
+
pub fn contains(&self, url: String) -> Result<bool> {
|
|
85
|
+
let url = iroh::RelayUrl::from_str(&url).map_err(anyhow::Error::from)?;
|
|
86
|
+
Ok(self.0.contains(&url))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Look up the configuration for the given relay URL.
|
|
90
|
+
#[napi]
|
|
91
|
+
pub fn get(&self, url: String) -> Result<Option<RelayConfig>> {
|
|
92
|
+
let url = iroh::RelayUrl::from_str(&url).map_err(anyhow::Error::from)?;
|
|
93
|
+
Ok(self.0.get(&url).map(|c| (c.as_ref()).into()))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// All relay URLs currently in the map.
|
|
97
|
+
#[napi]
|
|
98
|
+
pub fn urls(&self) -> Vec<String> {
|
|
99
|
+
self.0
|
|
100
|
+
.urls::<Vec<_>>()
|
|
101
|
+
.into_iter()
|
|
102
|
+
.map(|u| u.to_string())
|
|
103
|
+
.collect()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// Number of relays in the map.
|
|
107
|
+
#[napi]
|
|
108
|
+
pub fn len(&self) -> u32 {
|
|
109
|
+
self.0.len() as _
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// True if the map has no relays.
|
|
113
|
+
#[napi]
|
|
114
|
+
pub fn is_empty(&self) -> bool {
|
|
115
|
+
self.0.is_empty()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Clean string representation.
|
|
119
|
+
#[napi]
|
|
120
|
+
pub fn to_string(&self) -> String {
|
|
121
|
+
let urls: Vec<String> = self
|
|
122
|
+
.0
|
|
123
|
+
.urls::<Vec<_>>()
|
|
124
|
+
.into_iter()
|
|
125
|
+
.map(|u| u.to_string())
|
|
126
|
+
.collect();
|
|
127
|
+
format!("RelayMap([{}])", urls.join(", "))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Configuration for which relay servers an endpoint uses.
|
|
132
|
+
#[derive(Debug, Clone)]
|
|
133
|
+
#[napi]
|
|
134
|
+
pub struct RelayMode(pub(crate) iroh::RelayMode);
|
|
135
|
+
|
|
136
|
+
impl From<iroh::RelayMode> for RelayMode {
|
|
137
|
+
fn from(mode: iroh::RelayMode) -> Self {
|
|
138
|
+
Self(mode)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#[napi]
|
|
143
|
+
impl RelayMode {
|
|
144
|
+
/// No relays.
|
|
145
|
+
#[napi(factory)]
|
|
146
|
+
pub fn disabled() -> Self {
|
|
147
|
+
Self(iroh::RelayMode::Disabled)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// Use the n0 production relay map.
|
|
151
|
+
#[napi(factory)]
|
|
152
|
+
pub fn default_mode() -> Self {
|
|
153
|
+
Self(iroh::RelayMode::Default)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// Use the n0 staging relay map.
|
|
157
|
+
#[napi(factory)]
|
|
158
|
+
pub fn staging() -> Self {
|
|
159
|
+
Self(iroh::RelayMode::Staging)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Use a custom relay map.
|
|
163
|
+
#[napi(factory)]
|
|
164
|
+
pub fn custom(map: &RelayMap) -> Self {
|
|
165
|
+
Self(iroh::RelayMode::Custom(map.0.clone()))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// Build a custom relay mode from a list of relay URLs.
|
|
169
|
+
#[napi(factory)]
|
|
170
|
+
pub fn custom_from_urls(urls: Vec<String>) -> Result<Self> {
|
|
171
|
+
let urls: Vec<iroh::RelayUrl> = urls
|
|
172
|
+
.into_iter()
|
|
173
|
+
.map(|s| iroh::RelayUrl::from_str(&s).map_err(anyhow::Error::from))
|
|
174
|
+
.collect::<anyhow::Result<Vec<_>>>()?;
|
|
175
|
+
Ok(Self(iroh::RelayMode::custom(urls)))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/// The relay map this mode resolves to.
|
|
179
|
+
#[napi]
|
|
180
|
+
pub fn relay_map(&self) -> RelayMap {
|
|
181
|
+
self.0.relay_map().into()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// Clean string representation.
|
|
185
|
+
#[napi]
|
|
186
|
+
pub fn to_string(&self) -> String {
|
|
187
|
+
match &self.0 {
|
|
188
|
+
iroh::RelayMode::Disabled => "disabled".to_string(),
|
|
189
|
+
iroh::RelayMode::Default => "default".to_string(),
|
|
190
|
+
iroh::RelayMode::Staging => "staging".to_string(),
|
|
191
|
+
iroh::RelayMode::Custom(map) => format!("custom({} relays)", map.len()),
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|