@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/API.md ADDED
@@ -0,0 +1,949 @@
1
+ # API Reference
2
+
3
+ Complete API documentation for `@marianmeres/webrtc`.
4
+
5
+ ## Table of Contents
6
+
7
+ - [WebRtcManager](#webrtcmanager)
8
+ - [Constructor](#constructor)
9
+ - [Properties](#properties)
10
+ - [Lifecycle Methods](#lifecycle-methods)
11
+ - [Audio Methods](#audio-methods)
12
+ - [Data Channel Methods](#data-channel-methods)
13
+ - [Signaling Methods](#signaling-methods)
14
+ - [Event Methods](#event-methods)
15
+ - [Utility Methods](#utility-methods)
16
+ - [Types](#types)
17
+ - [WebRtcFactory](#webrtcfactory)
18
+ - [WebRtcManagerConfig](#webrtcmanagerconfig)
19
+ - [WebRtcState](#webrtcstate)
20
+ - [WebRtcFsmEvent](#webrtcfsmevent)
21
+ - [WebRtcEvents](#webrtcevents)
22
+ - [Event Constants](#event-constants)
23
+ - [State Machine](#state-machine)
24
+
25
+ ---
26
+
27
+ ## WebRtcManager
28
+
29
+ The main class for managing WebRTC connections.
30
+
31
+ ### Constructor
32
+
33
+ ```typescript
34
+ new WebRtcManager(factory: WebRtcFactory, config?: WebRtcManagerConfig)
35
+ ```
36
+
37
+ **Parameters:**
38
+
39
+ | Name | Type | Required | Description |
40
+ |------|------|----------|-------------|
41
+ | factory | `WebRtcFactory` | Yes | Factory for creating WebRTC primitives |
42
+ | config | `WebRtcManagerConfig` | No | Configuration options |
43
+
44
+ **Example:**
45
+
46
+ ```typescript
47
+ import { WebRtcManager } from '@marianmeres/webrtc';
48
+
49
+ const factory = {
50
+ createPeerConnection: (config) => new RTCPeerConnection(config),
51
+ getUserMedia: (constraints) => navigator.mediaDevices.getUserMedia(constraints),
52
+ enumerateDevices: () => navigator.mediaDevices.enumerateDevices(),
53
+ };
54
+
55
+ const manager = new WebRtcManager(factory, {
56
+ peerConfig: { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] },
57
+ enableMicrophone: true,
58
+ autoReconnect: true,
59
+ });
60
+ ```
61
+
62
+ ---
63
+
64
+ ### Properties
65
+
66
+ #### state
67
+
68
+ ```typescript
69
+ get state(): WebRtcState
70
+ ```
71
+
72
+ Returns the current state of the WebRTC connection.
73
+
74
+ **Returns:** `WebRtcState` - One of: `IDLE`, `INITIALIZING`, `CONNECTING`, `CONNECTED`, `RECONNECTING`, `DISCONNECTED`, `ERROR`
75
+
76
+ ---
77
+
78
+ #### localStream
79
+
80
+ ```typescript
81
+ get localStream(): MediaStream | null
82
+ ```
83
+
84
+ Returns the local media stream (microphone audio), or `null` if not initialized.
85
+
86
+ **Returns:** `MediaStream | null`
87
+
88
+ ---
89
+
90
+ #### remoteStream
91
+
92
+ ```typescript
93
+ get remoteStream(): MediaStream | null
94
+ ```
95
+
96
+ Returns the remote media stream received from peer, or `null` if not connected.
97
+
98
+ **Returns:** `MediaStream | null`
99
+
100
+ ---
101
+
102
+ #### dataChannels
103
+
104
+ ```typescript
105
+ get dataChannels(): ReadonlyMap<string, RTCDataChannel>
106
+ ```
107
+
108
+ Returns a readonly map of all active data channels, indexed by label.
109
+
110
+ **Returns:** `ReadonlyMap<string, RTCDataChannel>`
111
+
112
+ ---
113
+
114
+ #### peerConnection
115
+
116
+ ```typescript
117
+ get peerConnection(): RTCPeerConnection | null
118
+ ```
119
+
120
+ Returns the underlying RTCPeerConnection, or `null` if not initialized.
121
+
122
+ **Returns:** `RTCPeerConnection | null`
123
+
124
+ ---
125
+
126
+ ### Lifecycle Methods
127
+
128
+ #### initialize()
129
+
130
+ ```typescript
131
+ async initialize(): Promise<void>
132
+ ```
133
+
134
+ Initializes the WebRTC peer connection and sets up media tracks.
135
+
136
+ - Creates RTCPeerConnection using the factory
137
+ - Sets up connection state listeners
138
+ - Enables microphone if configured
139
+ - Creates default data channel if configured
140
+ - Sets up device change detection
141
+
142
+ **State Requirement:** Must be in `IDLE` state.
143
+
144
+ **Transitions:** `IDLE` → `INITIALIZING`
145
+
146
+ **Example:**
147
+
148
+ ```typescript
149
+ await manager.initialize();
150
+ console.log(manager.state); // "INITIALIZING"
151
+ ```
152
+
153
+ ---
154
+
155
+ #### connect()
156
+
157
+ ```typescript
158
+ async connect(): Promise<void>
159
+ ```
160
+
161
+ Transitions to the `CONNECTING` state. Automatically calls `initialize()` if in `IDLE` state. If `DISCONNECTED`, reinitializes the peer connection.
162
+
163
+ **State Transitions:**
164
+ - From `IDLE`: Initializes first, then transitions to `CONNECTING`
165
+ - From `DISCONNECTED`: Cleans up, resets, and reinitializes
166
+ - From `INITIALIZING`: Transitions to `CONNECTING`
167
+ - From `CONNECTED` or `CONNECTING`: No-op
168
+
169
+ **Example:**
170
+
171
+ ```typescript
172
+ await manager.connect();
173
+ // Now ready to create offer/answer
174
+ ```
175
+
176
+ ---
177
+
178
+ #### disconnect()
179
+
180
+ ```typescript
181
+ disconnect(): void
182
+ ```
183
+
184
+ Disconnects the peer connection and cleans up all resources:
185
+ - Closes all data channels
186
+ - Stops local media tracks
187
+ - Closes peer connection
188
+ - Clears reconnection timers
189
+
190
+ **Transitions:** Any state → `DISCONNECTED`
191
+
192
+ **Example:**
193
+
194
+ ```typescript
195
+ manager.disconnect();
196
+ console.log(manager.state); // "DISCONNECTED"
197
+ ```
198
+
199
+ ---
200
+
201
+ #### reset()
202
+
203
+ ```typescript
204
+ reset(): void
205
+ ```
206
+
207
+ Resets the manager to `IDLE` state from any state. Performs full cleanup and allows reinitialization.
208
+
209
+ **Transitions:** Any state → `IDLE`
210
+
211
+ **Example:**
212
+
213
+ ```typescript
214
+ manager.reset();
215
+ console.log(manager.state); // "IDLE"
216
+ // Can now call initialize() again
217
+ ```
218
+
219
+ ---
220
+
221
+ ### Audio Methods
222
+
223
+ #### enableMicrophone()
224
+
225
+ ```typescript
226
+ async enableMicrophone(enable: boolean): Promise<boolean>
227
+ ```
228
+
229
+ Enables or disables the microphone.
230
+
231
+ **Parameters:**
232
+
233
+ | Name | Type | Description |
234
+ |------|------|-------------|
235
+ | enable | `boolean` | `true` to enable, `false` to disable |
236
+
237
+ **Returns:** `boolean` - `true` if successful, `false` if failed
238
+
239
+ **Events Emitted:**
240
+ - `local_stream` - When stream changes
241
+ - `microphone_failed` - On failure
242
+
243
+ **Example:**
244
+
245
+ ```typescript
246
+ const success = await manager.enableMicrophone(true);
247
+ if (success) {
248
+ console.log('Microphone enabled');
249
+ }
250
+ ```
251
+
252
+ ---
253
+
254
+ #### switchMicrophone()
255
+
256
+ ```typescript
257
+ async switchMicrophone(deviceId: string): Promise<boolean>
258
+ ```
259
+
260
+ Switches the active microphone to a different audio input device.
261
+
262
+ **Parameters:**
263
+
264
+ | Name | Type | Description |
265
+ |------|------|-------------|
266
+ | deviceId | `string` | Device ID from `getAudioInputDevices()` |
267
+
268
+ **Returns:** `boolean` - `true` if successful, `false` otherwise
269
+
270
+ **Requirements:** Peer connection must be initialized and microphone must be enabled.
271
+
272
+ **Example:**
273
+
274
+ ```typescript
275
+ const devices = await manager.getAudioInputDevices();
276
+ const success = await manager.switchMicrophone(devices[1].deviceId);
277
+ ```
278
+
279
+ ---
280
+
281
+ #### getAudioInputDevices()
282
+
283
+ ```typescript
284
+ async getAudioInputDevices(): Promise<MediaDeviceInfo[]>
285
+ ```
286
+
287
+ Retrieves all available audio input devices.
288
+
289
+ **Returns:** `MediaDeviceInfo[]` - Array of audio input devices, empty array on error
290
+
291
+ **Example:**
292
+
293
+ ```typescript
294
+ const devices = await manager.getAudioInputDevices();
295
+ devices.forEach(d => console.log(d.label, d.deviceId));
296
+ ```
297
+
298
+ ---
299
+
300
+ ### Data Channel Methods
301
+
302
+ #### createDataChannel()
303
+
304
+ ```typescript
305
+ createDataChannel(label: string, options?: RTCDataChannelInit): RTCDataChannel | null
306
+ ```
307
+
308
+ Creates a new data channel with the specified label. Returns existing channel if one with the same label already exists.
309
+
310
+ **Parameters:**
311
+
312
+ | Name | Type | Required | Description |
313
+ |------|------|----------|-------------|
314
+ | label | `string` | Yes | Unique identifier for the channel |
315
+ | options | `RTCDataChannelInit` | No | Channel configuration |
316
+
317
+ **Returns:** `RTCDataChannel | null` - The channel, or `null` if peer connection not initialized
318
+
319
+ **Example:**
320
+
321
+ ```typescript
322
+ const chatChannel = manager.createDataChannel('chat');
323
+ const fileChannel = manager.createDataChannel('file-transfer', { ordered: true });
324
+ ```
325
+
326
+ ---
327
+
328
+ #### getDataChannel()
329
+
330
+ ```typescript
331
+ getDataChannel(label: string): RTCDataChannel | undefined
332
+ ```
333
+
334
+ Retrieves an existing data channel by label.
335
+
336
+ **Parameters:**
337
+
338
+ | Name | Type | Description |
339
+ |------|------|-------------|
340
+ | label | `string` | The channel label |
341
+
342
+ **Returns:** `RTCDataChannel | undefined`
343
+
344
+ **Example:**
345
+
346
+ ```typescript
347
+ const channel = manager.getDataChannel('chat');
348
+ if (channel?.readyState === 'open') {
349
+ // Channel is ready
350
+ }
351
+ ```
352
+
353
+ ---
354
+
355
+ #### sendData()
356
+
357
+ ```typescript
358
+ sendData(label: string, data: string | Blob | ArrayBuffer | ArrayBufferView): boolean
359
+ ```
360
+
361
+ Sends data through a data channel. Verifies channel exists and is open before sending.
362
+
363
+ **Parameters:**
364
+
365
+ | Name | Type | Description |
366
+ |------|------|-------------|
367
+ | label | `string` | The channel label |
368
+ | data | `string \| Blob \| ArrayBuffer \| ArrayBufferView` | Data to send |
369
+
370
+ **Returns:** `boolean` - `true` if sent, `false` otherwise
371
+
372
+ **Example:**
373
+
374
+ ```typescript
375
+ // Send string
376
+ manager.sendData('chat', 'Hello!');
377
+
378
+ // Send binary
379
+ const buffer = new ArrayBuffer(8);
380
+ manager.sendData('binary', buffer);
381
+ ```
382
+
383
+ ---
384
+
385
+ ### Signaling Methods
386
+
387
+ #### createOffer()
388
+
389
+ ```typescript
390
+ async createOffer(options?: RTCOfferOptions): Promise<RTCSessionDescriptionInit | null>
391
+ ```
392
+
393
+ Creates an SDP offer for initiating a WebRTC connection.
394
+
395
+ **Parameters:**
396
+
397
+ | Name | Type | Description |
398
+ |------|------|-------------|
399
+ | options | `RTCOfferOptions` | Optional offer configuration |
400
+
401
+ **Returns:** `RTCSessionDescriptionInit | null` - The offer, or `null` on error
402
+
403
+ **Example:**
404
+
405
+ ```typescript
406
+ const offer = await manager.createOffer();
407
+ await manager.setLocalDescription(offer);
408
+ // Send offer to remote peer via signaling channel
409
+ ```
410
+
411
+ ---
412
+
413
+ #### createAnswer()
414
+
415
+ ```typescript
416
+ async createAnswer(options?: RTCAnswerOptions): Promise<RTCSessionDescriptionInit | null>
417
+ ```
418
+
419
+ Creates an SDP answer in response to a received offer.
420
+
421
+ **Parameters:**
422
+
423
+ | Name | Type | Description |
424
+ |------|------|-------------|
425
+ | options | `RTCAnswerOptions` | Optional answer configuration |
426
+
427
+ **Returns:** `RTCSessionDescriptionInit | null` - The answer, or `null` on error
428
+
429
+ **Example:**
430
+
431
+ ```typescript
432
+ // After receiving and setting remote offer
433
+ const answer = await manager.createAnswer();
434
+ await manager.setLocalDescription(answer);
435
+ // Send answer to remote peer
436
+ ```
437
+
438
+ ---
439
+
440
+ #### setLocalDescription()
441
+
442
+ ```typescript
443
+ async setLocalDescription(description: RTCSessionDescriptionInit): Promise<boolean>
444
+ ```
445
+
446
+ Sets the local SDP description (offer or answer).
447
+
448
+ **Parameters:**
449
+
450
+ | Name | Type | Description |
451
+ |------|------|-------------|
452
+ | description | `RTCSessionDescriptionInit` | The SDP description |
453
+
454
+ **Returns:** `boolean` - `true` if successful
455
+
456
+ **Example:**
457
+
458
+ ```typescript
459
+ const offer = await manager.createOffer();
460
+ const success = await manager.setLocalDescription(offer);
461
+ ```
462
+
463
+ ---
464
+
465
+ #### setRemoteDescription()
466
+
467
+ ```typescript
468
+ async setRemoteDescription(description: RTCSessionDescriptionInit): Promise<boolean>
469
+ ```
470
+
471
+ Sets the remote SDP description received from the peer.
472
+
473
+ **Parameters:**
474
+
475
+ | Name | Type | Description |
476
+ |------|------|-------------|
477
+ | description | `RTCSessionDescriptionInit` | The remote SDP |
478
+
479
+ **Returns:** `boolean` - `true` if successful
480
+
481
+ **Example:**
482
+
483
+ ```typescript
484
+ // Received from signaling channel
485
+ await manager.setRemoteDescription(remoteOffer);
486
+ ```
487
+
488
+ ---
489
+
490
+ #### addIceCandidate()
491
+
492
+ ```typescript
493
+ async addIceCandidate(candidate: RTCIceCandidateInit | null): Promise<boolean>
494
+ ```
495
+
496
+ Adds an ICE candidate received from the remote peer.
497
+
498
+ **Parameters:**
499
+
500
+ | Name | Type | Description |
501
+ |------|------|-------------|
502
+ | candidate | `RTCIceCandidateInit \| null` | The ICE candidate, or `null` for end-of-candidates |
503
+
504
+ **Returns:** `boolean` - `true` if successful
505
+
506
+ **Example:**
507
+
508
+ ```typescript
509
+ // From signaling channel
510
+ await manager.addIceCandidate(remoteCandidate);
511
+ ```
512
+
513
+ ---
514
+
515
+ #### iceRestart()
516
+
517
+ ```typescript
518
+ async iceRestart(): Promise<boolean>
519
+ ```
520
+
521
+ Performs an ICE restart to recover from connection issues. Creates a new offer with the `iceRestart` flag.
522
+
523
+ **Returns:** `boolean` - `true` if successful
524
+
525
+ **Example:**
526
+
527
+ ```typescript
528
+ const success = await manager.iceRestart();
529
+ // New ICE candidates will be generated
530
+ ```
531
+
532
+ ---
533
+
534
+ #### getLocalDescription()
535
+
536
+ ```typescript
537
+ getLocalDescription(): RTCSessionDescription | null
538
+ ```
539
+
540
+ Returns the current local session description.
541
+
542
+ **Returns:** `RTCSessionDescription | null`
543
+
544
+ ---
545
+
546
+ #### getRemoteDescription()
547
+
548
+ ```typescript
549
+ getRemoteDescription(): RTCSessionDescription | null
550
+ ```
551
+
552
+ Returns the current remote session description.
553
+
554
+ **Returns:** `RTCSessionDescription | null`
555
+
556
+ ---
557
+
558
+ #### getStats()
559
+
560
+ ```typescript
561
+ async getStats(): Promise<RTCStatsReport | null>
562
+ ```
563
+
564
+ Retrieves WebRTC statistics for the peer connection.
565
+
566
+ **Returns:** `RTCStatsReport | null` - Statistics report, or `null` if not initialized
567
+
568
+ **Example:**
569
+
570
+ ```typescript
571
+ const stats = await manager.getStats();
572
+ stats?.forEach(report => {
573
+ console.log(report.type, report);
574
+ });
575
+ ```
576
+
577
+ ---
578
+
579
+ ### Event Methods
580
+
581
+ #### on()
582
+
583
+ ```typescript
584
+ on<K extends keyof WebRtcEvents>(
585
+ event: K,
586
+ handler: (data: WebRtcEvents[K]) => void
587
+ ): () => void
588
+ ```
589
+
590
+ Subscribe to a specific WebRTC event.
591
+
592
+ **Parameters:**
593
+
594
+ | Name | Type | Description |
595
+ |------|------|-------------|
596
+ | event | `keyof WebRtcEvents` | Event name |
597
+ | handler | `function` | Callback receiving event data |
598
+
599
+ **Returns:** `() => void` - Unsubscribe function
600
+
601
+ **Example:**
602
+
603
+ ```typescript
604
+ const unsub = manager.on('state_change', (state) => {
605
+ console.log('New state:', state);
606
+ });
607
+
608
+ // Later: unsub();
609
+ ```
610
+
611
+ ---
612
+
613
+ #### subscribe()
614
+
615
+ ```typescript
616
+ subscribe(handler: (state: OverallState) => void): () => void
617
+ ```
618
+
619
+ Subscribe to the overall state of the manager. Svelte store compatible - immediately calls handler with current state, then on changes.
620
+
621
+ **Parameters:**
622
+
623
+ | Name | Type | Description |
624
+ |------|------|-------------|
625
+ | handler | `function` | Callback receiving overall state |
626
+
627
+ **Overall State Object:**
628
+
629
+ ```typescript
630
+ {
631
+ state: WebRtcState;
632
+ localStream: MediaStream | null;
633
+ remoteStream: MediaStream | null;
634
+ dataChannels: ReadonlyMap<string, RTCDataChannel>;
635
+ peerConnection: RTCPeerConnection | null;
636
+ }
637
+ ```
638
+
639
+ **Returns:** `() => void` - Unsubscribe function
640
+
641
+ **Example (Svelte):**
642
+
643
+ ```svelte
644
+ <script>
645
+ const manager = new WebRtcManager(factory, config);
646
+ </script>
647
+
648
+ <p>State: {$manager.state}</p>
649
+ ```
650
+
651
+ **Example (Vanilla):**
652
+
653
+ ```typescript
654
+ const unsub = manager.subscribe((state) => {
655
+ console.log('Overall state:', state);
656
+ });
657
+ ```
658
+
659
+ ---
660
+
661
+ ### Utility Methods
662
+
663
+ #### toMermaid()
664
+
665
+ ```typescript
666
+ toMermaid(): string
667
+ ```
668
+
669
+ Returns a Mermaid diagram representation of the FSM state machine.
670
+
671
+ **Returns:** `string` - Mermaid diagram source
672
+
673
+ **Example:**
674
+
675
+ ```typescript
676
+ console.log(manager.toMermaid());
677
+ // Outputs Mermaid state diagram
678
+ ```
679
+
680
+ ---
681
+
682
+ ## Types
683
+
684
+ ### Logger
685
+
686
+ Console-compatible logger interface for custom logging implementations.
687
+
688
+ ```typescript
689
+ interface Logger {
690
+ debug: (...args: any[]) => string;
691
+ log: (...args: any[]) => string;
692
+ warn: (...args: any[]) => string;
693
+ error: (...args: any[]) => string;
694
+ }
695
+ ```
696
+
697
+ Each method accepts variadic arguments and returns a string representation of the first argument. This enables patterns like `throw new Error(logger.error("msg"))`.
698
+
699
+ **Example Custom Logger:**
700
+
701
+ ```typescript
702
+ import { clog } from '@marianmeres/clog';
703
+
704
+ const logger = clog('WebRTC');
705
+
706
+ const manager = new WebRtcManager(factory, {
707
+ debug: true,
708
+ logger: logger,
709
+ });
710
+ ```
711
+
712
+ ---
713
+
714
+ ### WebRtcFactory
715
+
716
+ Interface for dependency injection of WebRTC primitives.
717
+
718
+ ```typescript
719
+ interface WebRtcFactory {
720
+ createPeerConnection(config?: RTCConfiguration): RTCPeerConnection;
721
+ getUserMedia(constraints: MediaStreamConstraints): Promise<MediaStream>;
722
+ enumerateDevices(): Promise<MediaDeviceInfo[]>;
723
+ }
724
+ ```
725
+
726
+ **Browser Implementation:**
727
+
728
+ ```typescript
729
+ const factory: WebRtcFactory = {
730
+ createPeerConnection: (config) => new RTCPeerConnection(config),
731
+ getUserMedia: (constraints) => navigator.mediaDevices.getUserMedia(constraints),
732
+ enumerateDevices: () => navigator.mediaDevices.enumerateDevices(),
733
+ };
734
+ ```
735
+
736
+ ---
737
+
738
+ ### WebRtcManagerConfig
739
+
740
+ Configuration options for WebRtcManager.
741
+
742
+ ```typescript
743
+ interface WebRtcManagerConfig {
744
+ /** RTCConfiguration for ICE servers, certificates, etc. */
745
+ peerConfig?: RTCConfiguration;
746
+
747
+ /** Enable microphone on initialization. Default: false */
748
+ enableMicrophone?: boolean;
749
+
750
+ /** Create a data channel with this label on connect */
751
+ dataChannelLabel?: string;
752
+
753
+ /** Enable automatic reconnection on failure. Default: false */
754
+ autoReconnect?: boolean;
755
+
756
+ /** Maximum reconnection attempts. Default: 5 */
757
+ maxReconnectAttempts?: number;
758
+
759
+ /** Initial reconnection delay in ms (doubles each attempt). Default: 1000 */
760
+ reconnectDelay?: number;
761
+
762
+ /** Enable debug logging. Default: false */
763
+ debug?: boolean;
764
+
765
+ /** Custom logger instance. If not provided, falls back to console. */
766
+ logger?: Logger;
767
+ }
768
+ ```
769
+
770
+ ---
771
+
772
+ ### WebRtcState
773
+
774
+ Enum of possible connection states.
775
+
776
+ ```typescript
777
+ enum WebRtcState {
778
+ IDLE = "IDLE",
779
+ INITIALIZING = "INITIALIZING",
780
+ CONNECTING = "CONNECTING",
781
+ CONNECTED = "CONNECTED",
782
+ RECONNECTING = "RECONNECTING",
783
+ DISCONNECTED = "DISCONNECTED",
784
+ ERROR = "ERROR"
785
+ }
786
+ ```
787
+
788
+ | State | Description |
789
+ |-------|-------------|
790
+ | IDLE | Initial state, no resources allocated |
791
+ | INITIALIZING | Creating peer connection and setting up |
792
+ | CONNECTING | Performing offer/answer exchange |
793
+ | CONNECTED | Connection established, ready for communication |
794
+ | RECONNECTING | Attempting to restore failed connection |
795
+ | DISCONNECTED | Connection closed, can reconnect |
796
+ | ERROR | Error occurred, must call `reset()` |
797
+
798
+ ---
799
+
800
+ ### WebRtcFsmEvent
801
+
802
+ Internal FSM events (for reference).
803
+
804
+ ```typescript
805
+ enum WebRtcFsmEvent {
806
+ INIT = "initialize",
807
+ CONNECT = "connect",
808
+ CONNECTED = "connected",
809
+ RECONNECTING = "reconnecting",
810
+ DISCONNECT = "disconnect",
811
+ ERROR = "error",
812
+ RESET = "reset"
813
+ }
814
+ ```
815
+
816
+ ---
817
+
818
+ ### WebRtcEvents
819
+
820
+ Type definition for all events and their payloads.
821
+
822
+ ```typescript
823
+ interface WebRtcEvents {
824
+ state_change: WebRtcState;
825
+ local_stream: MediaStream | null;
826
+ remote_stream: MediaStream | null;
827
+ data_channel_open: RTCDataChannel;
828
+ data_channel_message: { channel: RTCDataChannel; data: any };
829
+ data_channel_close: RTCDataChannel;
830
+ ice_candidate: RTCIceCandidate | null;
831
+ reconnecting: { attempt: number; strategy: "ice-restart" | "full" };
832
+ reconnect_failed: { attempts: number };
833
+ device_changed: MediaDeviceInfo[];
834
+ microphone_failed: { error?: any; reason?: string };
835
+ error: Error;
836
+ }
837
+ ```
838
+
839
+ ---
840
+
841
+ ## Event Constants
842
+
843
+ Static event name constants on `WebRtcManager`.
844
+
845
+ | Constant | Value | Payload |
846
+ |----------|-------|---------|
847
+ | `EVENT_STATE_CHANGE` | `"state_change"` | `WebRtcState` |
848
+ | `EVENT_LOCAL_STREAM` | `"local_stream"` | `MediaStream \| null` |
849
+ | `EVENT_REMOTE_STREAM` | `"remote_stream"` | `MediaStream \| null` |
850
+ | `EVENT_DATA_CHANNEL_OPEN` | `"data_channel_open"` | `RTCDataChannel` |
851
+ | `EVENT_DATA_CHANNEL_MESSAGE` | `"data_channel_message"` | `{ channel, data }` |
852
+ | `EVENT_DATA_CHANNEL_CLOSE` | `"data_channel_close"` | `RTCDataChannel` |
853
+ | `EVENT_ICE_CANDIDATE` | `"ice_candidate"` | `RTCIceCandidate \| null` |
854
+ | `EVENT_RECONNECTING` | `"reconnecting"` | `{ attempt, strategy }` |
855
+ | `EVENT_RECONNECT_FAILED` | `"reconnect_failed"` | `{ attempts }` |
856
+ | `EVENT_DEVICE_CHANGED` | `"device_changed"` | `MediaDeviceInfo[]` |
857
+ | `EVENT_MICROPHONE_FAILED` | `"microphone_failed"` | `{ error?, reason? }` |
858
+ | `EVENT_ERROR` | `"error"` | `Error` |
859
+
860
+ **Usage:**
861
+
862
+ ```typescript
863
+ manager.on(WebRtcManager.EVENT_ICE_CANDIDATE, (candidate) => {
864
+ // Send to remote peer
865
+ });
866
+ ```
867
+
868
+ ---
869
+
870
+ ## State Machine
871
+
872
+ ### State Transition Diagram
873
+
874
+ ```
875
+ ┌─────────────────────────────────────────────────────────┐
876
+ │ │
877
+ ▼ │
878
+ ┌──────┐ INIT ┌──────────────┐ CONNECT ┌────────────┐ CONNECTED │
879
+ │ IDLE │────────▶│ INITIALIZING │──────────▶│ CONNECTING │─────────────┤
880
+ └──────┘ └──────────────┘ └────────────┘ │
881
+ ▲ │ │ │
882
+ │ │ ERROR │ ERROR │
883
+ │ ▼ ▼ ▼
884
+ │ ┌─────────┐◀──────────────────────────────────┌───────────┐
885
+ │ │ ERROR │ │ CONNECTED │
886
+ │ └─────────┘ └───────────┘
887
+ │ │ │
888
+ │ RESET DISCONNECT
889
+ │ │ │
890
+ │ ▼ ▼
891
+ │ ┌──────┐◀─────────────────────────────────┌──────────────┐
892
+ └──────────────│ IDLE │ RESET │ DISCONNECTED │
893
+ └──────┘◀────────────────────┬────────────└──────────────┘
894
+ │ │
895
+ │ RECONNECTING
896
+ │ │
897
+ RESET/ ▼
898
+ DISCONNECT ┌──────────────┐
899
+ │ │ RECONNECTING │
900
+ └─────────────└──────────────┘
901
+
902
+ CONNECT
903
+
904
+
905
+ ┌────────────┐
906
+ │ CONNECTING │
907
+ └────────────┘
908
+ ```
909
+
910
+ ### Valid Transitions
911
+
912
+ | From State | Event | To State |
913
+ |------------|-------|----------|
914
+ | IDLE | INIT | INITIALIZING |
915
+ | INITIALIZING | CONNECT | CONNECTING |
916
+ | INITIALIZING | ERROR | ERROR |
917
+ | CONNECTING | CONNECTED | CONNECTED |
918
+ | CONNECTING | DISCONNECT | DISCONNECTED |
919
+ | CONNECTING | ERROR | ERROR |
920
+ | CONNECTED | DISCONNECT | DISCONNECTED |
921
+ | CONNECTED | ERROR | ERROR |
922
+ | RECONNECTING | CONNECT | CONNECTING |
923
+ | RECONNECTING | DISCONNECT | DISCONNECTED |
924
+ | RECONNECTING | RESET | IDLE |
925
+ | DISCONNECTED | CONNECT | CONNECTING |
926
+ | DISCONNECTED | RECONNECTING | RECONNECTING |
927
+ | DISCONNECTED | RESET | IDLE |
928
+ | ERROR | RESET | IDLE |
929
+
930
+ ### Reconnection Strategy
931
+
932
+ When `autoReconnect: true`:
933
+
934
+ 1. **Attempts 1-2:** ICE restart (quick, preserves connection)
935
+ 2. **Attempts 3+:** Full reconnection (new peer connection)
936
+ 3. **Backoff:** `reconnectDelay * 2^(attempt-1)` milliseconds
937
+
938
+ For "full" strategy reconnections, listen for the `reconnecting` event and re-perform signaling:
939
+
940
+ ```typescript
941
+ manager.on('reconnecting', ({ attempt, strategy }) => {
942
+ if (strategy === 'full') {
943
+ // Re-do offer/answer exchange
944
+ const offer = await manager.createOffer();
945
+ await manager.setLocalDescription(offer);
946
+ sendToRemote(offer);
947
+ }
948
+ });
949
+ ```