first commit
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
# Offer / Answer Primitives
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## 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 via `cause`.
|
||||
- `AbortSignal` is 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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 `createOffer` does not silently proceed into
|
||||
`setLocalDescription`.
|
||||
- The wrapper never mutates `pc` outside of the documented calls and never
|
||||
registers event listeners — handler wiring is the orchestrator's
|
||||
responsibility (see `client.ts`).
|
||||
- The structural typing of `RTCPeerConnection` means these helpers work
|
||||
unchanged against the project's `FakeRTCPeerConnection` test double;
|
||||
the no-mocks rule is satisfied by injection at the call site, not by
|
||||
monkey-patching globals.
|
||||
Reference in New Issue
Block a user