Skip to main content

ApplicationNodeBuilder

Struct ApplicationNodeBuilder 

Source
pub struct ApplicationNodeBuilder<K: KeyCustody = NoOpCustody, D: DidMethod = NoOpDidMethod, S: Storage = NoOpStorage, Dom = NoDomain, Id = NoIdentity> { /* private fields */ }
Expand description

Builder for ApplicationNode.

Uses a type-state pattern to enforce required fields at compile time. The builder starts with Dom = NoDomain, Id = NoIdentity. Calling domain transitions Dom to HasDomain, and calling generate_identity_with, identity, or identity_with_storage transitions Id to HasIdentity. build is only available when both are set.

§Required fields

§Optional fields

Implementations§

Source§

impl ApplicationNodeBuilder

Source

pub fn new() -> Self

Creates a new builder with all fields unset.

The relay uses BlobStorageBackend::default() (in-memory) by default. Call blob_storage to use a different backend.

Source§

impl<K: KeyCustody + 'static, D: DidMethod + 'static, S: Storage + 'static, Id> ApplicationNodeBuilder<K, D, S, NoDomain, Id>

Source

pub fn domain( self, domain: &str, ) -> ApplicationNodeBuilder<K, D, S, HasDomain, Id>

Sets the domain this node serves.

The relay URL is derived as wss://<domain>/scp/v1 (spec section 18.5.2). Either .domain() or .no_domain() must be called — the builder cannot be built without one (§10.12.8).

Source

pub fn no_domain(self) -> ApplicationNodeBuilder<K, D, S, HasNoDomain, Id>

Zero-config NAT-traversed mode (§10.12.8).

When set: skip ACME TLS provisioning, probe NAT type via STUN, attempt UPnP (Tier 1), fallback to STUN address (Tier 2), register with bridge (Tier 3), publish DID document with ws:// relay URL, do NOT serve .well-known/scp.

This is the zero-config deployment path for self-hosted relays behind residential NAT.

Source§

impl<K: KeyCustody + 'static, D: DidMethod + 'static, S: Storage + 'static, Dom, Id> ApplicationNodeBuilder<K, D, S, Dom, Id>

Source

pub const fn bind_addr(self, addr: SocketAddr) -> Self

Sets the socket address for the relay server to bind to.

Defaults to 127.0.0.1:0 (OS-assigned port) if not specified.

Source

pub fn acme_email(self, email: &str) -> Self

Sets the ACME email for TLS certificate provisioning.

Used for Let’s Encrypt certificate requests (spec section 18.6.3). When .domain() is set, the email is passed to AcmeProvider during build() for ACME account registration (SCP-246). Optional – if omitted, the ACME account is created without a contact email.

Source

pub fn stun_server(self, url: &str) -> Self

Override the STUN endpoint used for NAT type probing (§10.12.8).

Default: bootstrap relay with STUN support. The value should be a socket address (e.g., "stun.l.google.com:19302").

Source

pub fn bridge_relay(self, url: &str) -> Self

Override the bridge relay used for Tier 3 fallback (§10.12.8).

Default: first bridge-capable relay in the fallback relay list. The value should be a wss:// URL.

Source

pub fn nat_strategy(self, strategy: Arc<dyn NatStrategy>) -> Self

Sets a custom NAT strategy for testability.

Production code uses DefaultNatStrategy (created automatically during build()). Tests can inject mock strategies.

Source

pub fn port_mapper(self, mapper: Arc<dyn PortMapper>) -> Self

Sets a UPnP/NAT-PMP port mapper for Tier 1 NAT traversal (spec 10.12.2).

When set, the DefaultNatStrategy will attempt UPnP port mapping before falling through to STUN (Tier 2). Has no effect if a custom NatStrategy is provided via nat_strategy.

Source

pub fn reachability_probe(self, probe: Arc<dyn ReachabilityProbe>) -> Self

Sets a reachability probe for self-test verification (SCP-242).

The self-test verifies that an external address is actually reachable before publishing it in the DID document (spec 10.12.2 step 4). When not set, the DefaultNatStrategy constructs a DefaultReachabilityProbe from the first configured STUN endpoint. Has no effect if a custom NatStrategy is provided via nat_strategy.

Source

pub fn tls_provider(self, provider: Arc<dyn TlsProvider>) -> Self

Sets a custom TLS provider for testability.

Production code uses AcmeProvider (created automatically during domain build()). Tests can inject mock providers that succeed or fail deterministically.

Source

pub fn network_detector(self, detector: Arc<dyn NetworkChangeDetector>) -> Self

Sets a network change detector for tier re-evaluation (§10.12.1, SCP-243).

When provided, network change events (IP change, interface up/down) trigger immediate re-evaluation of the reachability tier. Without a detector, only the periodic 30-minute timer triggers re-evaluation.

Use ChannelNetworkChangeDetector for channel-based event injection, or implement NetworkChangeDetector for platform-specific detection.

Source

pub fn local_api(self, addr: SocketAddr) -> Self

Enables the local dev API on the specified address.

When set, a bearer token is generated at build time and logged at INFO level. The dev API listens on a separate port from the public HTTPS listener, typically bound to 127.0.0.1:<port>.

If not called, the dev API is disabled (production default).

See spec section 18.10.2 and 18.10.5.

§Panics

Panics if addr is not a loopback address (127.0.0.1 or ::1). The dev API must never be exposed on a non-loopback interface.

Source

pub const fn http_bind_addr(self, addr: SocketAddr) -> Self

Sets the bind address for the public HTTP server.

This is the address where ApplicationNode::serve listens for incoming HTTP/HTTPS connections (.well-known/scp, /scp/v1 WebSocket upgrade, broadcast projection endpoints, and any application routes).

This is distinct from the relay’s internal bind address (set via bind_addr), which is a localhost-only listener used for the internal WebSocket bridge.

Defaults to DEFAULT_HTTP_BIND_ADDR (0.0.0.0:8443) if not specified.

Source

pub fn cors_origins(self, origins: Vec<String>) -> Self

Sets the allowed CORS origins for public endpoints.

Public endpoints (.well-known/scp, broadcast projection feeds and messages) include Access-Control-Allow-Origin headers so that browser-based JavaScript and WASM clients can read responses cross-origin.

  • If not called, or called with an empty list: permissive CORS (Access-Control-Allow-Origin: *). This is the default because broadcast content is public by design (spec section 18.11.6).
  • If called with a non-empty list: restricts to exactly those origins (e.g., ["https://example.com"]).

CORS is NOT applied to the WebSocket relay endpoint (/scp/v1) because WebSocket upgrades have their own origin mechanism, nor to the dev API (localhost-only).

See issue #231.

Source

pub const fn projection_rate_limit(self, rate: u32) -> Self

Sets the per-IP rate limit for broadcast projection endpoints.

Controls the maximum number of requests per second from a single IP address to the /scp/broadcast/* endpoints. Exceeding this rate returns HTTP 429 Too Many Requests.

Default: 60 req/s. Also configurable via SCP_NODE_PROJECTION_RATE_LIMIT.

See spec section 18.11.6.

Source§

impl<K: KeyCustody + 'static, D: DidMethod + 'static, Dom, Id> ApplicationNodeBuilder<K, D, NoOpStorage, Dom, Id>

Source

pub fn storage<S2: Storage + 'static>( self, storage: S2, ) -> ApplicationNodeBuilder<K, D, S2, Dom, Id>

Sets an explicit storage backend.

If not called, .build() uses a default no-op storage.

Source§

impl<K: KeyCustody + 'static, D: DidMethod + 'static, S: Storage + 'static, Dom, Id> ApplicationNodeBuilder<K, D, S, Dom, Id>

Source

pub fn blob_storage(self, blob_storage: impl Into<BlobStorageBackend>) -> Self

Sets a custom blob storage backend for the relay server.

If not called, the relay uses in-memory storage (all blobs lost on restart). Accepts any type that converts into BlobStorageBackend.

Source§

impl<S: Storage + 'static, Dom> ApplicationNodeBuilder<NoOpCustody, NoOpDidMethod, S, Dom, NoIdentity>

Source

pub fn identity<D2: DidMethod + 'static>( self, identity: ScpIdentity, document: DidDocument, did_method: Arc<D2>, ) -> ApplicationNodeBuilder<NoOpCustody, D2, S, Dom, HasIdentity>

Sets an explicit identity and DID document to use.

The identity will be published to the DHT with SCPRelay entries pointing to this node’s relay URL.

Source

pub fn generate_identity_with<K2: KeyCustody + 'static, D2: DidMethod + 'static>( self, key_custody: Arc<K2>, did_method: Arc<D2>, ) -> ApplicationNodeBuilder<K2, D2, S, Dom, HasIdentity>

Configures the builder to generate a new DID identity on .build().

Uses the provided key custody and DID method implementations.

Source

pub fn identity_with_storage<K2: KeyCustody + 'static, D2: DidMethod + 'static>( self, key_custody: Arc<K2>, did_method: Arc<D2>, ) -> ApplicationNodeBuilder<K2, D2, S, Dom, HasIdentity>

Configures the builder to automatically persist and reload identity.

This is the recommended way to manage identity for long-lived applications. On the first run it behaves like generate_identity_with: it creates a new DID via the provided key_custody and did_method, then persists the resulting ScpIdentity and DidDocument to the builder’s storage backend under the key "scp/identity".

On subsequent runs with the same storage backend, the persisted identity is deserialized and reused — no new DID is created. This ensures the node keeps the same DID across restarts.

§Lifecycle
  1. At .build() time, check storage for key "scp/identity".
  2. Found: Deserialize the stored ScpIdentity and DidDocument, use the .identity() path internally.
  3. Not found: Call did_method.create(custody), persist the result to storage, then continue.

[KeyHandle] indices remain valid across restarts because the custody backend (e.g., FileKeyCustody) reloads the same keyring from disk.

§Requirements

A real storage backend must be set via .storage() before calling .build(). The default no-op storage will not persist anything.

§Example
// let custody = Arc::new(FileKeyCustody::open("keys.db")?);
// let did_method = Arc::new(DidDht::new(...));
// let storage = SqliteStorage::open("node.db")?;
//
// let node = ApplicationNodeBuilder::new()
//     .storage(storage)
//     .domain("example.com")
//     .identity_with_storage(custody, did_method)
//     .build()
//     .await?;
// // First run: creates new DID, persists to storage.
// // Subsequent runs: reloads same DID from storage.
Source§

impl<K: KeyCustody + 'static, D: DidMethod + 'static, S: EncryptedStorage + 'static> ApplicationNodeBuilder<K, D, S, HasDomain, HasIdentity>

Source

pub async fn build(self) -> Result<ApplicationNode<S>, NodeError>

Builds the ApplicationNode.

Requires S: EncryptedStorage — compile-time enforcement that the storage backend encrypts data at rest. For testing with unencrypted backends, use build_for_testing.

See issue #695.

§Errors

Returns NodeError if storage, identity, relay, or TLS setup fails.

Source§

impl<K: KeyCustody + 'static, D: DidMethod + 'static, S: EncryptedStorage + 'static> ApplicationNodeBuilder<K, D, S, HasNoDomain, HasIdentity>

Source

pub async fn build(self) -> Result<ApplicationNode<S>, NodeError>

Builds the ApplicationNode in zero-config no-domain mode (§10.12.8).

Requires S: EncryptedStorage. For testing, use build_for_testing.

§Errors

Returns NodeError if storage, identity, relay, or NAT setup fails.

Auto Trait Implementations§

§

impl<K, D, S, Dom, Id> Freeze for ApplicationNodeBuilder<K, D, S, Dom, Id>
where S: Freeze,

§

impl<K = NoOpCustody, D = NoOpDidMethod, S = NoOpStorage, Dom = NoDomain, Id = NoIdentity> !RefUnwindSafe for ApplicationNodeBuilder<K, D, S, Dom, Id>

§

impl<K, D, S, Dom, Id> Send for ApplicationNodeBuilder<K, D, S, Dom, Id>
where Dom: Send, Id: Send,

§

impl<K, D, S, Dom, Id> Sync for ApplicationNodeBuilder<K, D, S, Dom, Id>
where Dom: Sync, Id: Sync,

§

impl<K, D, S, Dom, Id> Unpin for ApplicationNodeBuilder<K, D, S, Dom, Id>
where S: Unpin, Dom: Unpin, Id: Unpin,

§

impl<K, D, S, Dom, Id> UnsafeUnpin for ApplicationNodeBuilder<K, D, S, Dom, Id>
where S: UnsafeUnpin,

§

impl<K = NoOpCustody, D = NoOpDidMethod, S = NoOpStorage, Dom = NoDomain, Id = NoIdentity> !UnwindSafe for ApplicationNodeBuilder<K, D, S, Dom, Id>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

Source§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

Source§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

Source§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Classify for T

Source§

type Classified = T

Source§

fn classify(self) -> T

Source§

impl<T> Declassify for T

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<Unshared, Shared> IntoShared<Shared> for Unshared
where Shared: FromUnshared<Unshared>,

Source§

fn into_shared(self) -> Shared

Creates a shared type from an unshared type.
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more