@marianmeres/webrtc 0.0.2 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,373 @@
1
+ # AGENTS.md - Machine-Readable Package Documentation
2
+
3
+ ## Package Metadata
4
+
5
+ ```yaml
6
+ name: "@marianmeres/webrtc"
7
+ version: "0.0.2"
8
+ license: MIT
9
+ author: Marian Meres
10
+ repository: https://github.com/marianmeres/webrtc
11
+ runtime: Deno (source), Node.js/Browser (distribution)
12
+ type: WebRTC connection management library
13
+ ```
14
+
15
+ ## Purpose
16
+
17
+ A lightweight, framework-agnostic WebRTC manager providing:
18
+ - FSM-based connection lifecycle management
19
+ - Event-driven architecture with PubSub pattern
20
+ - Svelte store compatibility
21
+ - Audio device management (microphone switching)
22
+ - Data channel support
23
+ - Auto-reconnection with exponential backoff
24
+ - Full TypeScript type safety
25
+
26
+ ## Dependencies
27
+
28
+ ```yaml
29
+ production:
30
+ - "@marianmeres/fsm": "^2.3.0"
31
+ - "@marianmeres/pubsub": "^2.4.0"
32
+ development:
33
+ - "@std/assert": testing
34
+ - "@std/fs": file operations
35
+ - "@std/path": path utilities
36
+ ```
37
+
38
+ ## File Structure
39
+
40
+ ```
41
+ src/
42
+ mod.ts # Entry point, re-exports all public APIs
43
+ types.ts # Type definitions (interfaces, enums)
44
+ webrtc-manager.ts # Main WebRtcManager class
45
+
46
+ tests/
47
+ mocks.ts # Mock WebRtcFactory for testing
48
+ webrtc-manager.test.ts # Deno unit tests
49
+ browser/
50
+ p2p-tests.ts # Browser integration tests
51
+
52
+ example/
53
+ peer.ts # Two-peer example with localStorage signaling
54
+ p2p.ts # Single-page P2P example
55
+ audio-peer.ts # Audio testing implementation
56
+ main.ts # Signaling server example
57
+
58
+ scripts/
59
+ build-npm.ts # npm distribution build
60
+ build-example.ts # Example bundling
61
+ build-browser-tests.ts
62
+ serve-browser-tests.ts
63
+ signaling-server.ts
64
+ ```
65
+
66
+ ## State Machine
67
+
68
+ ### States (WebRtcState)
69
+
70
+ | State | Description | Valid Outgoing Transitions |
71
+ |-------|-------------|---------------------------|
72
+ | IDLE | Initial state, no resources allocated | INITIALIZING |
73
+ | INITIALIZING | Creating peer connection and setting up | CONNECTING, ERROR |
74
+ | CONNECTING | Performing SDP offer/answer exchange | CONNECTED, DISCONNECTED, ERROR |
75
+ | CONNECTED | Connection established, communication active | DISCONNECTED, ERROR |
76
+ | RECONNECTING | Auto-reconnection in progress | CONNECTING, DISCONNECTED, IDLE |
77
+ | DISCONNECTED | Connection closed, resources may be cleaned up | CONNECTING, RECONNECTING, IDLE |
78
+ | ERROR | Error state, requires reset() to recover | IDLE |
79
+
80
+ ### Events (WebRtcFsmEvent)
81
+
82
+ | Event | Value | Description |
83
+ |-------|-------|-------------|
84
+ | INIT | "initialize" | Start initialization |
85
+ | CONNECT | "connect" | Begin connection |
86
+ | CONNECTED | "connected" | Connection succeeded |
87
+ | RECONNECTING | "reconnecting" | Start reconnection |
88
+ | DISCONNECT | "disconnect" | Close connection |
89
+ | ERROR | "error" | Error occurred |
90
+ | RESET | "reset" | Return to IDLE |
91
+
92
+ ### Transition Matrix
93
+
94
+ ```
95
+ IDLE --INIT--> INITIALIZING
96
+ INITIALIZING --CONNECT--> CONNECTING
97
+ INITIALIZING --ERROR--> ERROR
98
+ CONNECTING --CONNECTED--> CONNECTED
99
+ CONNECTING --DISCONNECT--> DISCONNECTED
100
+ CONNECTING --ERROR--> ERROR
101
+ CONNECTED --DISCONNECT--> DISCONNECTED
102
+ CONNECTED --ERROR--> ERROR
103
+ RECONNECTING --CONNECT--> CONNECTING
104
+ RECONNECTING --DISCONNECT--> DISCONNECTED
105
+ RECONNECTING --RESET--> IDLE
106
+ DISCONNECTED --CONNECT--> CONNECTING
107
+ DISCONNECTED --RECONNECTING-->RECONNECTING
108
+ DISCONNECTED --RESET--> IDLE
109
+ ERROR --RESET--> IDLE
110
+ ```
111
+
112
+ ## Public API Reference
113
+
114
+ ### Constructor
115
+
116
+ ```typescript
117
+ new WebRtcManager(factory: WebRtcFactory, config?: WebRtcManagerConfig)
118
+ ```
119
+
120
+ ### Logger Interface
121
+
122
+ Console-compatible logger interface for custom logging implementations.
123
+
124
+ ```typescript
125
+ interface Logger {
126
+ debug: (...args: any[]) => string;
127
+ log: (...args: any[]) => string;
128
+ warn: (...args: any[]) => string;
129
+ error: (...args: any[]) => string;
130
+ }
131
+ ```
132
+
133
+ Each method returns a string representation of the first argument, enabling patterns like `throw new Error(logger.error("msg"))`.
134
+
135
+ ### WebRtcFactory Interface
136
+
137
+ ```typescript
138
+ interface WebRtcFactory {
139
+ createPeerConnection(config?: RTCConfiguration): RTCPeerConnection;
140
+ getUserMedia(constraints: MediaStreamConstraints): Promise<MediaStream>;
141
+ enumerateDevices(): Promise<MediaDeviceInfo[]>;
142
+ }
143
+ ```
144
+
145
+ ### WebRtcManagerConfig Interface
146
+
147
+ ```typescript
148
+ interface WebRtcManagerConfig {
149
+ peerConfig?: RTCConfiguration; // ICE servers, certificates
150
+ enableMicrophone?: boolean; // Default: false
151
+ dataChannelLabel?: string; // Auto-create data channel
152
+ autoReconnect?: boolean; // Default: false
153
+ maxReconnectAttempts?: number; // Default: 5
154
+ reconnectDelay?: number; // Default: 1000ms
155
+ debug?: boolean; // Default: false
156
+ logger?: Logger; // Custom logger, falls back to console
157
+ }
158
+ ```
159
+
160
+ ### Properties (Getters)
161
+
162
+ | Property | Type | Description |
163
+ |----------|------|-------------|
164
+ | state | WebRtcState | Current FSM state |
165
+ | localStream | MediaStream \| null | Local audio stream |
166
+ | remoteStream | MediaStream \| null | Remote audio stream |
167
+ | dataChannels | ReadonlyMap<string, RTCDataChannel> | Active data channels |
168
+ | peerConnection | RTCPeerConnection \| null | Underlying connection |
169
+
170
+ ### Lifecycle Methods
171
+
172
+ | Method | Signature | Description |
173
+ |--------|-----------|-------------|
174
+ | initialize | `(): Promise<void>` | Create peer connection, setup tracks |
175
+ | connect | `(): Promise<void>` | Transition to CONNECTING (auto-initializes if IDLE) |
176
+ | disconnect | `(): void` | Close connection, cleanup resources |
177
+ | reset | `(): void` | Reset to IDLE from any state |
178
+
179
+ ### Audio Methods
180
+
181
+ | Method | Signature | Description |
182
+ |--------|-----------|-------------|
183
+ | enableMicrophone | `(enable: boolean): Promise<boolean>` | Enable/disable microphone |
184
+ | switchMicrophone | `(deviceId: string): Promise<boolean>` | Switch audio input device |
185
+ | getAudioInputDevices | `(): Promise<MediaDeviceInfo[]>` | List available audio inputs |
186
+
187
+ ### Data Channel Methods
188
+
189
+ | Method | Signature | Description |
190
+ |--------|-----------|-------------|
191
+ | createDataChannel | `(label: string, options?: RTCDataChannelInit): RTCDataChannel \| null` | Create/get data channel |
192
+ | getDataChannel | `(label: string): RTCDataChannel \| undefined` | Get existing channel |
193
+ | sendData | `(label: string, data: string \| Blob \| ArrayBuffer \| ArrayBufferView): boolean` | Send through channel |
194
+
195
+ ### Signaling Methods
196
+
197
+ | Method | Signature | Description |
198
+ |--------|-----------|-------------|
199
+ | createOffer | `(options?: RTCOfferOptions): Promise<RTCSessionDescriptionInit \| null>` | Create SDP offer |
200
+ | createAnswer | `(options?: RTCAnswerOptions): Promise<RTCSessionDescriptionInit \| null>` | Create SDP answer |
201
+ | setLocalDescription | `(description: RTCSessionDescriptionInit): Promise<boolean>` | Set local SDP |
202
+ | setRemoteDescription | `(description: RTCSessionDescriptionInit): Promise<boolean>` | Set remote SDP |
203
+ | addIceCandidate | `(candidate: RTCIceCandidateInit \| null): Promise<boolean>` | Add ICE candidate |
204
+ | iceRestart | `(): Promise<boolean>` | Perform ICE restart |
205
+ | getLocalDescription | `(): RTCSessionDescription \| null` | Get local SDP |
206
+ | getRemoteDescription | `(): RTCSessionDescription \| null` | Get remote SDP |
207
+ | getStats | `(): Promise<RTCStatsReport \| null>` | Get connection statistics |
208
+
209
+ ### Event Methods
210
+
211
+ | Method | Signature | Description |
212
+ |--------|-----------|-------------|
213
+ | on | `(event: keyof WebRtcEvents, handler: (data: any) => void): () => void` | Subscribe to specific event |
214
+ | subscribe | `(handler: (state: OverallState) => void): () => void` | Subscribe to overall state (Svelte compatible) |
215
+
216
+ ### Utility Methods
217
+
218
+ | Method | Signature | Description |
219
+ |--------|-----------|-------------|
220
+ | toMermaid | `(): string` | Get FSM as Mermaid diagram |
221
+
222
+ ## Event Constants
223
+
224
+ | Constant | Value | Payload Type |
225
+ |----------|-------|--------------|
226
+ | EVENT_STATE_CHANGE | "state_change" | WebRtcState |
227
+ | EVENT_LOCAL_STREAM | "local_stream" | MediaStream \| null |
228
+ | EVENT_REMOTE_STREAM | "remote_stream" | MediaStream \| null |
229
+ | EVENT_DATA_CHANNEL_OPEN | "data_channel_open" | RTCDataChannel |
230
+ | EVENT_DATA_CHANNEL_MESSAGE | "data_channel_message" | { channel: RTCDataChannel; data: any } |
231
+ | EVENT_DATA_CHANNEL_CLOSE | "data_channel_close" | RTCDataChannel |
232
+ | EVENT_ICE_CANDIDATE | "ice_candidate" | RTCIceCandidate \| null |
233
+ | EVENT_RECONNECTING | "reconnecting" | { attempt: number; strategy: "ice-restart" \| "full" } |
234
+ | EVENT_RECONNECT_FAILED | "reconnect_failed" | { attempts: number } |
235
+ | EVENT_DEVICE_CHANGED | "device_changed" | MediaDeviceInfo[] |
236
+ | EVENT_MICROPHONE_FAILED | "microphone_failed" | { error?: any; reason?: string } |
237
+ | EVENT_ERROR | "error" | Error |
238
+
239
+ ## Signaling Flow (User Responsibility)
240
+
241
+ The library does NOT handle signaling transport. Users must implement:
242
+
243
+ 1. Create signaling channel (WebSocket, HTTP, localStorage, etc.)
244
+ 2. Listen for `ice_candidate` events and send to remote peer
245
+ 3. Send offers/answers through signaling channel
246
+ 4. Receive remote offers/answers and call setRemoteDescription
247
+ 5. Receive remote ICE candidates and call addIceCandidate
248
+
249
+ ### Initiator Flow
250
+
251
+ ```
252
+ 1. initialize()
253
+ 2. connect()
254
+ 3. createOffer()
255
+ 4. setLocalDescription(offer)
256
+ 5. [send offer via signaling]
257
+ 6. [receive answer]
258
+ 7. setRemoteDescription(answer)
259
+ 8. [exchange ICE candidates via addIceCandidate]
260
+ 9. CONNECTED
261
+ ```
262
+
263
+ ### Responder Flow
264
+
265
+ ```
266
+ 1. initialize()
267
+ 2. [receive offer]
268
+ 3. setRemoteDescription(offer)
269
+ 4. createAnswer()
270
+ 5. setLocalDescription(answer)
271
+ 6. [send answer via signaling]
272
+ 7. [exchange ICE candidates via addIceCandidate]
273
+ 8. CONNECTED
274
+ ```
275
+
276
+ ## Reconnection Strategy
277
+
278
+ When `autoReconnect: true`:
279
+
280
+ | Attempt | Strategy | Description |
281
+ |---------|----------|-------------|
282
+ | 1-2 | ice-restart | Quick recovery, preserves connection |
283
+ | 3+ | full | New peer connection required |
284
+
285
+ Backoff formula: `reconnectDelay * 2^(attempt-1)` milliseconds
286
+
287
+ For "full" strategy reconnections, consumers MUST:
288
+ 1. Listen for `reconnecting` event with `strategy: "full"`
289
+ 2. Re-perform signaling handshake (create new offer/answer)
290
+
291
+ ## Error Handling
292
+
293
+ | Pattern | Description |
294
+ |---------|-------------|
295
+ | Boolean returns | Methods return `true` for success, `false` for failure |
296
+ | ERROR state | Critical errors transition to ERROR state |
297
+ | Recovery | ERROR state requires `reset()` to recover |
298
+ | Events | EVENT_ERROR emitted for all errors |
299
+ | Specific events | EVENT_MICROPHONE_FAILED, EVENT_RECONNECT_FAILED |
300
+
301
+ ## Build Commands
302
+
303
+ ```bash
304
+ deno task test # Run unit tests
305
+ deno task test:browser # Run browser integration tests
306
+ deno task npm:build # Build npm distribution
307
+ deno task npm:publish # Build and publish to npm
308
+ deno task build:example # Build examples
309
+ deno task serve:example # Run signaling server
310
+ ```
311
+
312
+ ## Implementation Notes
313
+
314
+ 1. `subscribe()` is Svelte store compatible (immediate callback + updates)
315
+ 2. Data channels auto-cleanup on close
316
+ 3. Device change listener auto-setup on initialize
317
+ 4. "User-Initiated Abort" errors from intentional `close()` are ignored
318
+ 5. `recvonly` transceiver added when microphone disabled (ensures audio SDP)
319
+ 6. Private fields use `#` syntax (true ES2022 private fields)
320
+ 7. Signaling transport NOT included - users implement their own
321
+
322
+ ## Common Usage Patterns
323
+
324
+ ### Minimal P2P Setup
325
+
326
+ ```typescript
327
+ const manager = new WebRtcManager(factory, { enableMicrophone: true });
328
+ await manager.initialize();
329
+ await manager.connect();
330
+ const offer = await manager.createOffer();
331
+ await manager.setLocalDescription(offer);
332
+ // Send offer, receive answer, exchange ICE candidates...
333
+ ```
334
+
335
+ ### With Data Channel
336
+
337
+ ```typescript
338
+ const manager = new WebRtcManager(factory, { dataChannelLabel: "chat" });
339
+ manager.on("data_channel_message", ({ data }) => console.log(data));
340
+ // After connection...
341
+ manager.sendData("chat", "Hello!");
342
+ ```
343
+
344
+ ### Svelte Integration
345
+
346
+ ```svelte
347
+ <script>
348
+ const manager = new WebRtcManager(factory, config);
349
+ // $manager reactive access to state
350
+ </script>
351
+ {$manager.state}
352
+ ```
353
+
354
+ ### Auto-reconnection Handling
355
+
356
+ ```typescript
357
+ const manager = new WebRtcManager(factory, {
358
+ autoReconnect: true,
359
+ maxReconnectAttempts: 5,
360
+ reconnectDelay: 1000,
361
+ });
362
+
363
+ manager.on("reconnecting", ({ attempt, strategy }) => {
364
+ console.log(`Reconnecting: attempt ${attempt}, strategy ${strategy}`);
365
+ if (strategy === "full") {
366
+ // Re-do signaling handshake
367
+ }
368
+ });
369
+
370
+ manager.on("reconnect_failed", ({ attempts }) => {
371
+ console.log(`Reconnection failed after ${attempts} attempts`);
372
+ });
373
+ ```