3.9 KiB
Offer / Answer Primitives
(no submodules)
Purpose
The offer/answer module exposes three pure async wrappers around the
spec-standard WebRTC handshake calls. createOffer covers the outbound
half (local offer + setLocalDescription), handleOffer covers the
inbound half (setRemoteDescription + answer + setLocalDescription),
and handleAnswer finalises an outbound handshake by applying the
remote answer. Each helper takes an RTCPeerConnection plus an optional
AbortSignal and never owns peer state itself — that lives in
WebRTCClient.
Splitting these out keeps WebRTCClient thin and testable. The handshake
logic is a deterministic three- or four-step pipeline that maps
beautifully to property tests against a structural FakeRTCPeerConnection;
the orchestrator can then focus on lifecycle, eventing, and connection
caching without having to re-stub the same pc.createOffer().then(...)
chain in every code path.
Features
- Spec-faithful call ordering:
createOffer → setLocalDescription,setRemoteDescription → createAnswer → setLocalDescription. - Every failure surfaces as
DriveWebRtcError(PEER_HANDSHAKE_FAILED)with the underlying error preserved viacause. AbortSignalis checked before every async step, so a pre-aborted signal rejects immediately and a signal aborted mid-handshake rejects at the next step boundary.- Pure functions over an injected
RTCPeerConnection— no module-level state, no globals, no hidden allocations.
Usage: Basic Example
import { createOffer, handleAnswer } from "@suaveplan/drive-webrtc";
const pc = new RTCPeerConnection();
const offer = await createOffer(pc);
sendToPeer({ type: "offer", payload: offer });
const answer = await receiveAnswerFromPeer();
await handleAnswer(pc, answer);
Usage: Advanced Example
import { createOffer, handleOffer } from "@suaveplan/drive-webrtc";
const pc = new RTCPeerConnection({ iceServers });
// Inbound caller — respond to a remote offer with an abort budget.
const ac = new AbortController();
setTimeout(() => ac.abort(), 5_000);
try {
const answer = await handleOffer(pc, remoteOffer, ac.signal);
sendToPeer({ type: "answer", payload: answer });
} catch (err) {
if (err instanceof DriveWebRtcError && err.code === DRIVE_WEBRTC_CODES.PEER_HANDSHAKE_FAILED) {
// Handshake failed — log + tear down the peer connection.
}
throw err;
}
// Outbound caller — create an offer and survive a transient
// setLocalDescription error.
try {
const offer = await createOffer(pc);
sendToPeer({ type: "offer", payload: offer });
} catch (err) {
pc.close();
throw err;
}
API Reference
createOffer(pc, signal?): Promise<RTCSessionDescriptionInit>
Creates an SDP offer on pc, applies it as local description, returns
the offer. Throws DriveWebRtcError(PEER_HANDSHAKE_FAILED) on abort or
any step failure (original error preserved via cause).
handleOffer(pc, offer, signal?): Promise<RTCSessionDescriptionInit>
Applies offer as remote description, generates an answer, applies it
as local description, returns the answer. Same error semantics as
createOffer.
handleAnswer(pc, answer, signal?): Promise<void>
Applies answer as remote description on a pc that has already sent
an offer. Same error semantics as createOffer.
Implementation Notes
- Abort checks happen between every awaited step so an abort that lands
during
createOfferdoes not silently proceed intosetLocalDescription. - The wrapper never mutates
pcoutside of the documented calls and never registers event listeners — handler wiring is the orchestrator's responsibility (seeclient.ts). - The structural typing of
RTCPeerConnectionmeans these helpers work unchanged against the project'sFakeRTCPeerConnectiontest double; the no-mocks rule is satisfied by injection at the call site, not by monkey-patching globals.