Skip to content

v0.2.0 — invite links

Choose a tag to compare

@NiKrause NiKrause released this 31 Jul 21:38
8ea2b32

The connect step is rebuilt. The published package is unchanged@le-space/libp2p-webrtc-qr@0.1.0 remains current on npm, and no file under packages/webrtc-qr was touched.

Live: https://webrtc-qr.le-space.de

Why

A field test with someone unfamiliar with the process, over Telegram, never produced a connection. It produced seven distinct dead ends instead:

  1. They could not find the input — "But where is the qr payload textarea?" The label read QR PAYLOAD OR COPY/PASTE FALLBACK: jargon, and "fallback" reads as side path when for a remote pair it is the only path.
  2. They sent their Peer ID twice instead of the payload. Not carelessness — the Peer ID was the prominent, labelled, copyable-looking string on the page.
  3. They gave up on the copy button and selected page text by hand, sending the log and the page footer along with it.
  4. The same box was output and input: they had to be told to delete their own payload before pasting.
  5. Both sides drifted out of step, ending in Called in wrong state: stable.
  6. Errors were raw WebRTC exceptions that say nothing about what to do.
  7. 27 minutes passed between invite and reply. The offer was long dead, and nothing had ever said an invite has a shelf life.

What changed

The payload travels as a link. Create an invite, send it with one tap through the system share sheet, and opening it does the rest — the page starts, verifies the invite and produces a reply link by itself.

Dead ends 1–6 do not get better error messages; they stop existing. There is no text area to find, no role to know, nothing to clear, and only one string that could be the right one.

The QR encodes the link, so a phone's own camera app opens the page with everything loaded. Cost: 29–37 characters over the raw payload — 1096 in production with STUN, against a 2200 budget. The payload rides in the URL fragment, which browsers never send to a server.

Dead end 7 cannot be designed away — an invite goes stale as its ICE candidates expire. The page says how long it stays fresh, marks it when it is not, and offers a new one.

Also: pasting connects without a second click; links wrapped by a chat app are un-wrapped before parsing; raw WebRTC errors are translated into instructions; and the Peer ID is demoted into a collapsed block that says outright it is not the thing to send.

Two bugs the field found

The answering peer was killing its own connection. After replying it waited CONNECTION_TIMEOUT — 30 seconds — for the connection to come up, then closed the peer connection. Thirty seconds is right for two phones in one room and hopelessly short over a messenger. By the time the invite creator opened the reply, the other side was already gone. That wait is now six minutes.

A reply opened in a second tab said nothing. Tapping a reply link in a messenger opens a fresh tab, which can never finish the handshake — the pending connection lives in the tab that made the invite. It hands the reply over via BroadcastChannel, but the tab in front of the user then showed nothing for it. A banner above everything now reports the outcome: "Connected in your other tab. You can close this one."

Correction: cross-engine coverage was not what #16 claimed

The three Playwright projects were not running three engines. A leftover browserName: 'chromium' in the shared use block overrode every project, so all three launched Chromium, with the firefox and webkit projects merely wearing the user-agent strings from their device descriptors. The evidence given in #16navigator.userAgent — was exactly the value being spoofed.

Verified now with browser.browserType().name(), which cannot be spoofed. Once the real engines started, Firefox could not launch in the container at all until HOME was set — an error that had never appeared, because Firefox had never run.

And a real gap surfaced: Playwright's WebKit build for Linux has no working WebRTC. WebKit fails exactly the four specs that need a peer connection and passes everything else. Those four now skip on Linux WebKit with that reason attached, rather than dropping WebKit or hiding the gap.

So, precisely:

Engine Verified
Chromium end to end, in CI
Firefox end to end, in CI — genuinely, for the first time
WebKit everything except peer connections in CI; end to end only locally on macOS

The README and roadmap now state this instead of implying uniform coverage.

Tests

79: 18 unit, 55 browser across three real engines, 6 Helia.

Nine browser specs are new, each pinning a specific failure from the field protocol — including one that waits out the old 30-second teardown before completing a handshake, and one that opens a third tab and checks the connection landed in the original one.