first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -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.