Transports
Some modules need more than XRPC: a shell on a VM over iroh, or a WebRTC datachannel from a page. A
transport serves com.fedfork.exec.<name>.connect calls for a host. Hosts take transports as plugins;
the core never imports one, and a host without it refuses its calls.
| transport | NSID, URLs | hosts | repo |
|---|---|---|---|
| iroh | com.fedfork.exec.iroh.connect, iroh: | Node, Rust (cargo feature iroh, on by default) | atlibs-iroh |
| WebRTC | com.fedfork.exec.webrtc.connect, webrtc: | browser | atlibs-webrtc |
Fetch a URL
A transport can also carry plain HTTP: com.fedfork.exec.http.fetch sends a URL of its scheme to it
(other URL schemes), so a module talks to a service in a guest VM,
the socialweb.computer guest agent's MCP server (Streamable HTTP on a unix socket), as it would to any
HTTP server. The URL is self-contained; its ref is base64url JSON of what the transport needs, nothing secret:
webrtc:///<ref>/<tag>/<path>: ref{ "receipt": { "uri", "cid" }, "bidder"? }, tag the guest's datachannel tag (shell), e.g.webrtc:///<ref>/shell/mcpiroh:///<ref>/<socket>/<path>: ref{ "ticket", "hostKeys": [...] }, socketshell(/run/socialweb-guest-agent/shell.sock) or a percent-encoded absolute unix path
The module's http lists webrtc://* or iroh://*, and so must the host's allowlist.
Require a transport
A module that needs one lists the NSID in calls and declares it as a required hook, so a host without
the transport refuses it at load rather than mid-run:
examples/rust/echo/module.json
"hooks": [
{ "nsid": "com.fedfork.exec.iroh.connect", "required": true, "entries": ["session", "connect"], "reason": "session and connect dial the guest over iroh: a host built without iroh refuses them at load" }
]
The Node CLI loads no transport unless asked, so echo's iroh entries are refused:
node javascript/src/server/cli.mjs run target/wasm32-unknown-unknown/release/echo.wasm examples/rust/echo/module.json session '{"ticket":"x"}' | jq -r .error
Echo needs hook com.fedfork.exec.iroh.connect (…): this host was built without iroh
Plug one in
JS: a transport is a map of NSID to call, passed as hostCall's calls, and its fetchers(), a map
of scheme to fetch, { "webrtc:": async ({ method, url, headers, body, signal }) => ({ status, headers, body }) }, spread into runModule's (or runCompose's) fetchers. The Node CLI loads modules named in
ATLIBS_TRANSPORTS, each exporting transport and/or fetchers({ env, stateDir }) (atlibs-iroh's is js/index.js):
ATLIBS_TRANSPORTS=../atlibs-iroh/js/index.js node javascript/src/server/cli.mjs run target/wasm32-unknown-unknown/release/echo.wasm examples/rust/echo/module.json session '{"ticket":"<an iroh ticket>"}'
Rust: a Transport says which NSIDs it provides and the Call that serves them; the CLI registers
the ones it was built with. Its Call may also fetch URL schemes of its own, gathered by
transport::fetchers() into the http allowlist (ATLIBS_HTTP names them, {"iroh://*": {}}):
rust/host/src/transport.rs
/// A transport: the NSIDs it provides (request and subscribe) and the call that serves them.
#[derive(Clone)]
pub struct Transport {
pub provides: &'static [&'static str],
pub call: Arc<dyn Call>,
}
rust/host/src/run.rs
/// The URL schemes (no colon: "iroh") whose HTTP_NSID requests this call serves with fetch(): a
/// transport's (transport::fetchers). None by default.
fn schemes(&self) -> &'static [&'static str] {
&[]
}
/// One HTTP_NSID request to a URL of one of schemes() (JS: a fetchers entry), as host::Fetch::fetch.
fn fetch(&self, _method: &str, url: &str, _headers: Vec<(String, String)>, _body: Option<Vec<u8>>) -> BoxFut<'_, Result<crate::host::Response, String>> {
rust/cli/src/main.rs
pub fn transports(state_dir: &Path) -> Transports {
#[allow(unused_mut)]
let mut transports: Transports = vec![];
#[cfg(feature = "iroh")]
transports.push(atlibs_iroh::transport(env("ATLIBS_IROH_KEY").map(PathBuf::from).unwrap_or_else(|| state_dir.join("iroh_ed25519"))));
transports
}
The CLI's iroh feature takes atlibs-iroh by git; rust/Cargo.toml patches that crate's
atlibs-host to this checkout, so one atlibs-host is built. cargo loopback builds atlibs without
iroh, for checking that refusal.
iroh
atlibs-iroh dials an iroh ticket with dumbpipe (brew install dumbpipe): an ssh command on the
guest, or a raw ALPN exchange. With subscribe: true it is a session: output arrives as messages, and
send writes stdin. The host's ssh key never enters wasm (ATLIBS_IROH_KEY, else
<state>/iroh_ed25519). Its tests run both CLIs from an atlibs checkout next to it:
cd ../atlibs-iroh && node --test js/*.test.mjs examples/*.test.mjs tests/*.test.mjs # tests/loopback: real dumbpipe into a throwaway sshd
WebRTC
atlibs-webrtc gives a page WebrtcHost (js/index.js): it finds the guest's offer, answers, opens a
datachannel and can run ssh over it. Signalling goes through the module (sealed with age); connectivity
is STUN only. Its loopback test runs headless Chrome against its webrtc-peer into a throwaway sshd:
cd ../atlibs-webrtc && cargo build --release && node --test js/*.test.mjs tests/*.test.mjs
STUN only. There is no TURN relay: two peers behind symmetric NATs will not connect.