v0.4.0 — QRSession, and acceptAnswer dials
The release where the package stops shipping only half the problem.
QRSession
The session state machine that drives the transport now lives in the package. It
existed three times before that: twice in this repository's examples, and once in
an unrelated project that re-derived it bug for bug — including its own
dial-retry loop with its own delay, reached the same way, by hitting the bug.
import { QRSession } from '@le-space/libp2p-webrtc-qr'
const session = new QRSession(node, { rtcConfiguration })
const offer = await session.createOffer() // show this
const { peerId, connection } = await session.acceptAnswer(reply)Plus acceptOffer, dial, dialProtocol, forget(peerId), and connect /
error events.
Three things that are not obvious from the transport API, now handled once:
- the init data channel must be negotiated, or the remote muxer adopts it as
an incoming stream and no real protocol stream ever arrives - the upgrade must wait for
connected, in the rightdirection - the first dial must be retried while the answering peer attaches its muxer
Both examples lost their copies: 336 lines from the demo, 197 from the Helia
example, with no change in behaviour.
acceptAnswer dials
Until something dials there is no libp2p connection — only a WebRTC one with an
upgrade context beside it. An app with a protocol of its own never notices,
because dialling that protocol does it. An app that uses whatever connection
exists — a replicating database, a pubsub topic — sees the handshake succeed and
no peer.
A second consumer hit this within an hour of existing. { dial: false } opts out
for callers that open a protocol stream themselves.
Two bugs fixed in shared code
getOutboundSession returns the newest session for a peer rather than the
first, so a reconnect no longer hands the transport a closed peer connection.
forget() drops the libp2p connection together with the offer session — leaving
either behind makes the next attempt fail with Remote closed connection during opening, which points nowhere near the cause.
Errors now carry an ICE summary — local: 6 host, 1 srflx; remote: …; ice: failed
— because a failure after clean signalling is almost always about candidate types.
Upgrading
From 0.3.0: QRSession is additive. Existing code that drives the transport
by hand keeps working.
From 0.1.x: the wire format changed in 0.2.0. PAYLOAD_VERSION went 1 → 2 and
the signature prefix with it, so a 0.1.x peer and a 0.4.0 peer cannot verify each
other's payloads — the mismatch surfaces as "signature is invalid", which reads
like forgery rather than a version difference. Both sides have to move together.
Verified
33 unit tests, 123 browser tests across Chromium, Firefox and WebKit, 6 Helia
transfer tests. Three consumers on this version: the demo, the Helia example, and
a Svelte 5 application.