Skip to content

PeerConnection: complete W3C §4.3 method/accessor surface#47

Merged
nus merged 3 commits into
mainfrom
pc-ice-gathering-state
Jun 9, 2026
Merged

PeerConnection: complete W3C §4.3 method/accessor surface#47
nus merged 3 commits into
mainfrom
pc-ice-gathering-state

Conversation

@nus

@nus nus commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Completes the W3C RTCPeerConnection API surface (BACKLOG #5). Three commits on this branch:

  1. iceGatheringState + onIceGatheringStateChange + getConfiguration()
  2. restartIce() (ICE-agent restart + answerer auto-restart on a changed-ufrag offer; DTLS/SCTP persist)
  3. The remaining members (this batch)

What's new in the final batch

Member Behavior
removeTrack(sender) Detaches the track and downgrades the owning transceiver (sendrecvrecvonly, sendonlyinactive); keeps the transceiver/receiver; fires negotiationneeded. No-op for a foreign/already-detached sender.
setConfiguration(config) configuration is now a mutable holder. Rejects bundlePolicy/rtcpMuxPolicy changes (W3C InvalidModificationError) and rejects when closed.
current{Local,Remote}Description / pending{Local,Remote}Description Derived from the last-set description + signalingState — a side is pending while it owns the in-flight offer/pranswer, current otherwise.
onNegotiationNeeded Microtask-coalesced flag, set on addTrack/addTransceiver/removeTrack/first data channel, cleared when a local description is applied.
onIceCandidateError New IceCandidateError (url/address/port/errorCode/errorText). STUN gather timeout → 701; server error response → its STUN error code. Plumbed via an onCandidateError callback on the ICE state machine, mirroring onLocalCandidate.
connectionState Now starts in newState (the spec new; new is a Dart keyword, so the value mirrors IceConnectionState.iceNew).

Tests

  • test/peer_connection/pc_spec_methods_test.dart (new) — connectionState new, setConfiguration (replace/reject/closed), current/pending split (pre-negotiation, mid-negotiation, post-handshake), onNegotiationNeeded (fire/coalesce/re-fire), onIceCandidateError end-to-end via PeerConnection (unroutable STUN → 701).
  • test/peer_connection/transceiver_test.dart — removeTrack (detach+downgrade, sendonly→inactive, foreign no-op, SDP reflection).
  • test/ice/ice_test.dartonCandidateError at the ICE-machine layer (timeout 701, STUN error response).

Verification

  • dart analyze → No issues found
  • dart test (full unit suite) → all green
  • dart test test/e2e/ → 22 passed (browser interop)

🤖 Generated with Claude Code

nus and others added 3 commits June 9, 2026 15:21
Part of the PeerConnection W3C surface gap. The ICE agent already tracks
iceNew / iceGathering / iceGatheringComplete internally; expose it as the
W3C `iceGatheringState`.

- `IceGatheringState` enum (newState/gathering/complete — `newState` dodges
  the `new` keyword, mirroring `IceConnectionState.iceNew`).
- `iceGatheringState` getter + `onIceGatheringStateChange` stream, derived
  from the ICE agent's combined state in _onIceStateChange.
- `getConfiguration()` returning the connection's configuration.

Remaining PeerConnection items (restartIce, removeTrack, setConfiguration,
current/pendingLocalDescription, onNegotiationNeeded, onIceCandidateError,
connectionState `new`) stay in BACKLOG — restartIce is the highest-value
next one.

Tested: gathering state transitions new → gathering → complete with the
event firing (loopback), and getConfiguration round-trips. analyze clean;
667 unit + 22 e2e pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds ICE restart on top of the iceGatheringState/getConfiguration change.

- ICE agent `restart(localParams, {hosts, clearRemote})`: discards local
  candidates, pairs, pending checks, the selected pair, and consent
  tracking, then re-gathers under the new credentials over the same
  sockets. `clearRemote` distinguishes the initiator (drops the peer's
  stale credentials, awaits new ones) from the answerer (keeps the peer's
  freshly-applied credentials).
- `restartIce()` regenerates the local ICE ufrag/pwd (now mutable) and
  flags a pending restart; the next `setLocalDescription` routes through
  `_ice.restart` instead of `startGathering`.
- `setRemoteDescription` auto-restarts the answerer when it sees a changed
  ufrag/pwd in an *offer* (RFC 8445 §9), regenerating its own credentials
  so the answer carries them.
- DTLS handshake is now started once (`_dtlsHandshakeStarted` guard): an
  ICE restart re-reaches `iceConnected`, but DTLS/SCTP/SRTP persist over
  the new pair and must not be re-handshaked.

Tested: a loopback test establishes a data channel, exchanges a message,
restarts ICE (asserting both peers get new ufrags), renegotiates, and
confirms data flows again over the freshly-validated pair. analyze clean;
668 unit + 22 e2e pass (Chrome/Firefox handshake unaffected by the DTLS
guard).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Finish the remaining PeerConnection spec members on top of the already-shipped
iceGatheringState/getConfiguration/restartIce work:

- removeTrack(): detach the track and downgrade the transceiver direction
  (sendrecv→recvonly, sendonly→inactive); fires negotiationneeded.
- setConfiguration(): configuration is now a mutable holder; bundlePolicy /
  rtcpMuxPolicy changes are rejected (W3C InvalidModificationError).
- current/pending {local,remote}Description: derived from the last-set
  description + signaling state (no parallel bookkeeping fields).
- onNegotiationNeeded: microtask-coalesced flag, cleared on local apply.
- onIceCandidateError: STUN gather timeout (701) or server error code,
  surfaced from the ICE machine via an onCandidateError callback.
- connectionState now starts in newState (the spec `new`).

Tests: PC-level spec methods, removeTrack SDP reflection, onNegotiationNeeded
re-fire, onIceCandidateError (ICE-machine + PC). dart analyze clean; full unit
+ e2e suites green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@nus nus merged commit 3ebeabb into main Jun 9, 2026
29 of 30 checks passed
@nus nus deleted the pc-ice-gathering-state branch June 9, 2026 17:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant