~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs-webrtc

WebRTC transport for atlibs: com.fedfork.exec.webrtc.connect in the browser, and webrtc-peer, its guest side

0
0
main
Go to filet
Code
README.md

atlibs-webrtc

The WebRTC transport for atlibs (knotless://johnandersen777.bsky.social/atlibs): com.fedfork.exec.webrtc.connect in a browser page. WebrtcHost finds a guest's offer, answers, opens a datachannel and can run ssh over it. It also fetches webrtc: URLs: HTTP/1.1 to a guest's tunnel tag over datachannels (the guest agent's MCP server is webrtc:///<ref>/shell/mcp). Signalling goes through the module (sealed with age); connectivity is STUN only.

webrtc-peer is the guest side on your machine, for local checks: it answers sealed offers and forwards each datachannel label to a TCP port.

Layout

  • js/: the transport for pages (WebrtcHost, localSignals, httpChannel: HTTP/1.1 over a datachannel), no dependencies
  • src/: webrtc-peer (str0m), a crate on atlibs-host
  • web/loopback.html: a page that drives the transport against a local webrtc-peer
  • tests/: the loopback check; lexicons/: com.fedfork.exec.webrtc.connect

Get the core

The crate takes atlibs-host as a git dependency (pinned by Cargo.lock). The page and the tests import the core's JavaScript from a sibling checkout:

git clone https://knotless.dev/johnandersen777.bsky.social/atlibs ../atlibs && git -C ../atlibs checkout -q 88b2ba8

Build

cargo build --release

Run a guest

Forward the ssh datachannel to a local sshd, and let one page origin signal it:

target/release/webrtc-peer --tunnel ssh=22 --origin http://127.0.0.1:8000

It prints {signal, fingerprint, ...}: open web/loopback.html?signal=<signal>&user=$USER from that origin.

Use it from a page

npm install @publicdomainrelay/atlibs-webrtc @publicdomainrelay/atlibs (Node >= 25), or map both in the page's import map: @publicdomainrelay/atlibs-webrtc/ to js/ (here, web/webrtc is a symlink to ../js) and @publicdomainrelay/atlibs/ to the core's javascript/src:

<script type="importmap">
{ "imports": { "@publicdomainrelay/atlibs-webrtc/": "./webrtc/", "@publicdomainrelay/atlibs/": "./atlibs/" } }
</script>

Then route the calls to the transport, as web/loopback.html does:

import { WEBRTC_STATE, WebrtcHost, localSignals } from "@publicdomainrelay/atlibs-webrtc/index.js";

const host = new WebrtcHost({ store, events, signal, makePeer: (c) => new RTCPeerConnection(c), age, ssh });
const call = hostCall({ ...rest, calls: host.calls });   // webrtc.connect
await runModule({ ...rest, fetchers: { ...host.fetchers() } });  // webrtc: URLs for com.fedfork.exec.http.fetch

events({receipt, tag}) returns the guest events of that receipt and tag: a fetch peer is its own peer connection, beside the page's. fetchers(options) is new WebrtcHost(options).fetchers() for a page that only fetches (store is optional).

Mesh peers: recordSignals

recordSignals({repo, subhandle}) (js/record-signals.js) is {events, signal} for WebrtcHost over exact-key records instead of a requester: the receipt is a peer's computer.socialweb.peer/<subhandle> strong ref (peerReceipt(repo, did, subhandle)); its webrtc DID's computer.socialweb.ingress.listen/<subhandle> gives the age key and DTLS fingerprint; the sealed offer goes to computer.socialweb.ingress.offer/<that DID>.<tid> in the page's repo and the answer comes back at computer.socialweb.ingress.answer/<page DID>.<tid>. repo is the page's PDS access (did, getRecord, putRecord, deleteRecord). meshChannel(host, receipt, {channel}) then speaks atirc's mesh channel lines over the atirc/mesh/1 datachannel; atirc's tests/webrtc_loopback.ts drives it in headless Chrome against atirc mesh run.

HTTP to a bidder: endpoint URLs

A kcp bidder's offering lists {kind: "webrtc", address}, address = base64url of {"did", "listen"}. webrtc://<address>/<path...> (endpointUrl({did, listen}, path)) is HTTP/1.1 to that bidder's market XRPC over a market datachannel, signalled with new WebrtcHost({records: recordSignals({repo, subhandle}), ...}): the receipt is its computer.socialweb.ingress.listen/<listen> uri, so the answerer is that DID itself. atlibs hosts send module calls with endpoints there with service auth aud <did>#pdr_temp_market.

HTTP to a guest: webrtc: URLs

webrtc:///<ref>/<tag>/<path...>, ref = base64url of the JSON {"receipt": {"uri", "cid"}, "bidder"?} (webrtcUrl({receipt, bidder}, tag, path) builds one, parseWebrtcUrl reads it). The fetcher is async ({method, url, headers, body, signal}) => ({status, headers, body}) (body text). A module reaches it through com.fedfork.exec.http.fetch with webrtc://* in its http list and the host's allowlist.

Each (receipt, tag) gets one peer connection, kept for later fetches and dialled again once it fails, and a pool of datachannels, each one HTTP/1.1 connection serving one request at a time (keep-alive; a Connection: close response retires it): a request in flight holds its channel, so concurrent requests (a long-poll read beside an exec) each open or reuse their own. MCP session headers are the client's (Mcp-Session-Id rides through like any header). host.close() closes them all.

Test

tests/loopback.test.mjs runs web/loopback.html in headless Chrome against webrtc-peer in front of a throwaway sshd and a stub MCP server. It skips (with the reason) without Chrome (CHROME=<binary>), OpenSSH or the build.

cargo build --release && cargo test --release
node --test js/*.test.mjs tests/*.test.mjs

Publish

js/ is @publicdomainrelay/atlibs-webrtc on npm (npm publish --access public; the tests stay out of the tarball). The crate is not on crates.io: depend on it by git.

atlibs-webrtc = { git = "https://knotless.dev/johnandersen777.bsky.social/atlibs-webrtc" }

STUN only. There is no TURN relay: two peers behind symmetric NATs will not connect.