@marianmeres/webrtc 1.1.1 → 1.2.1
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 +4 -1
- package/API.md +44 -3
- package/README.md +56 -12
- package/dist/types.d.ts +2 -0
- package/dist/webrtc-manager.d.ts +19 -1
- package/dist/webrtc-manager.js +40 -2
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -114,9 +114,11 @@ ERROR --RESET--> IDLE
|
|
|
114
114
|
### Constructor
|
|
115
115
|
|
|
116
116
|
```typescript
|
|
117
|
-
new WebRtcManager(factory: WebRtcFactory, config?: WebRtcManagerConfig)
|
|
117
|
+
new WebRtcManager<TContext = unknown>(factory: WebRtcFactory, config?: WebRtcManagerConfig)
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
+
**Type Parameter:** `TContext` - Optional type for the `context` property (default: `unknown`)
|
|
121
|
+
|
|
120
122
|
### Logger Interface
|
|
121
123
|
|
|
122
124
|
Console-compatible logger interface for custom logging implementations.
|
|
@@ -166,6 +168,7 @@ interface WebRtcManagerConfig {
|
|
|
166
168
|
| remoteStream | MediaStream \| null | Remote audio stream |
|
|
167
169
|
| dataChannels | ReadonlyMap<string, RTCDataChannel> | Active data channels |
|
|
168
170
|
| peerConnection | RTCPeerConnection \| null | Underlying connection |
|
|
171
|
+
| context | TContext \| null | User-defined context for arbitrary data |
|
|
169
172
|
|
|
170
173
|
### Lifecycle Methods
|
|
171
174
|
|
package/API.md
CHANGED
|
@@ -31,9 +31,15 @@ The main class for managing WebRTC connections.
|
|
|
31
31
|
### Constructor
|
|
32
32
|
|
|
33
33
|
```typescript
|
|
34
|
-
new WebRtcManager(factory: WebRtcFactory, config?: WebRtcManagerConfig)
|
|
34
|
+
new WebRtcManager<TContext = unknown>(factory: WebRtcFactory, config?: WebRtcManagerConfig)
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
**Type Parameters:**
|
|
38
|
+
|
|
39
|
+
| Name | Default | Description |
|
|
40
|
+
|------|---------|-------------|
|
|
41
|
+
| TContext | `unknown` | Type for the `context` property |
|
|
42
|
+
|
|
37
43
|
**Parameters:**
|
|
38
44
|
|
|
39
45
|
| Name | Type | Required | Description |
|
|
@@ -123,6 +129,33 @@ Returns the underlying RTCPeerConnection, or `null` if not initialized.
|
|
|
123
129
|
|
|
124
130
|
---
|
|
125
131
|
|
|
132
|
+
#### context
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
context: TContext | null
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
User-defined context object for storing arbitrary data associated with this manager. The class accepts an optional generic type parameter for type-safe context access.
|
|
139
|
+
|
|
140
|
+
**Type Parameter:** `TContext` - The type of the context object (default: `unknown`)
|
|
141
|
+
|
|
142
|
+
**Default:** `null`
|
|
143
|
+
|
|
144
|
+
**Example:**
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// With type parameter for full type safety:
|
|
148
|
+
const manager = new WebRtcManager<{ audioStream: MediaStream; sessionId: string }>(factory);
|
|
149
|
+
manager.context = { audioStream: myStream, sessionId: '123' };
|
|
150
|
+
manager.context.audioStream; // typed as MediaStream
|
|
151
|
+
|
|
152
|
+
// Without type parameter (backwards compatible):
|
|
153
|
+
const manager = new WebRtcManager(factory);
|
|
154
|
+
manager.context = { anything: 'goes' };
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
126
159
|
### Lifecycle Methods
|
|
127
160
|
|
|
128
161
|
#### initialize()
|
|
@@ -759,6 +792,9 @@ interface WebRtcManagerConfig {
|
|
|
759
792
|
/** Initial reconnection delay in ms (doubles each attempt). Default: 1000 */
|
|
760
793
|
reconnectDelay?: number;
|
|
761
794
|
|
|
795
|
+
/** Timeout in ms for full reconnection to reach connected state. Default: 30000 */
|
|
796
|
+
fullReconnectTimeout?: number;
|
|
797
|
+
|
|
762
798
|
/** Callback to control whether reconnection should proceed */
|
|
763
799
|
shouldReconnect?: (context: {
|
|
764
800
|
attempt: number;
|
|
@@ -942,19 +978,24 @@ When `autoReconnect: true`:
|
|
|
942
978
|
2. **Attempts 3+:** Full reconnection (new peer connection)
|
|
943
979
|
3. **Backoff:** `reconnectDelay * 2^(attempt-1)` milliseconds
|
|
944
980
|
|
|
945
|
-
|
|
981
|
+
#### Full Reconnection and Signaling
|
|
982
|
+
|
|
983
|
+
**Important:** For "full" strategy reconnections, the manager creates a new peer connection but **cannot automatically complete the signaling handshake**. You must listen for the `reconnecting` event and re-perform signaling when `strategy === 'full'`:
|
|
946
984
|
|
|
947
985
|
```typescript
|
|
948
|
-
manager.on('reconnecting', ({ attempt, strategy }) => {
|
|
986
|
+
manager.on('reconnecting', async ({ attempt, strategy }) => {
|
|
949
987
|
if (strategy === 'full') {
|
|
950
988
|
// Re-do offer/answer exchange
|
|
951
989
|
const offer = await manager.createOffer();
|
|
952
990
|
await manager.setLocalDescription(offer);
|
|
953
991
|
sendToRemote(offer);
|
|
954
992
|
}
|
|
993
|
+
// For 'ice-restart', the manager handles it automatically
|
|
955
994
|
});
|
|
956
995
|
```
|
|
957
996
|
|
|
997
|
+
If the connection doesn't reach `CONNECTED` state within `fullReconnectTimeout` (default: 30 seconds), it's treated as a failed attempt and the next reconnection attempt begins. When all attempts are exhausted, `EVENT_RECONNECT_FAILED` is emitted.
|
|
998
|
+
|
|
958
999
|
### Conditional Reconnection
|
|
959
1000
|
|
|
960
1001
|
Use the `shouldReconnect` callback to suppress reconnection when the peer disconnected intentionally:
|
package/README.md
CHANGED
|
@@ -38,9 +38,11 @@ The manager doesn't handle the signaling transport layer - you're responsible fo
|
|
|
38
38
|
### Constructor
|
|
39
39
|
|
|
40
40
|
```typescript
|
|
41
|
-
const manager = new WebRtcManager(factory, config);
|
|
41
|
+
const manager = new WebRtcManager<TContext>(factory, config);
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
- `TContext`: Optional type parameter for the `context` property (default: `unknown`)
|
|
45
|
+
|
|
44
46
|
- `factory`: Object implementing `WebRtcFactory` interface (provides `createPeerConnection`, `getUserMedia`, `enumerateDevices`)
|
|
45
47
|
- `config`: Optional configuration object
|
|
46
48
|
|
|
@@ -51,6 +53,7 @@ const manager = new WebRtcManager(factory, config);
|
|
|
51
53
|
- `autoReconnect`: Enable automatic reconnection (default: false)
|
|
52
54
|
- `maxReconnectAttempts`: Max reconnection attempts (default: 5)
|
|
53
55
|
- `reconnectDelay`: Initial reconnection delay in ms (default: 1000)
|
|
56
|
+
- `fullReconnectTimeout`: Timeout in ms for full reconnection to succeed (default: 30000)
|
|
54
57
|
- `shouldReconnect`: Callback to control whether reconnection should proceed (see below)
|
|
55
58
|
- `debug`: Enable debug logging (default: false)
|
|
56
59
|
- `logger`: Custom logger instance implementing `Logger` interface (default: console)
|
|
@@ -63,6 +66,7 @@ manager.localStream // MediaStream | null
|
|
|
63
66
|
manager.remoteStream // MediaStream | null
|
|
64
67
|
manager.dataChannels // ReadonlyMap<string, RTCDataChannel>
|
|
65
68
|
manager.peerConnection // RTCPeerConnection | null
|
|
69
|
+
manager.context // TContext | null - user-defined data
|
|
66
70
|
```
|
|
67
71
|
|
|
68
72
|
### Lifecycle Methods
|
|
@@ -161,6 +165,39 @@ The callback receives:
|
|
|
161
165
|
- `maxAttempts`: Configured maximum attempts
|
|
162
166
|
- `strategy`: `"ice-restart"` (attempts 1-2) or `"full"` (attempts 3+)
|
|
163
167
|
|
|
168
|
+
### Reconnection Strategies
|
|
169
|
+
|
|
170
|
+
The manager uses two reconnection strategies with exponential backoff:
|
|
171
|
+
|
|
172
|
+
1. **ICE Restart** (attempts 1-2): Lightweight reconnection that keeps the existing peer connection and restarts ICE negotiation. Works when the network path changed but the remote peer is still available.
|
|
173
|
+
|
|
174
|
+
2. **Full Reconnection** (attempts 3+): Creates a completely new peer connection. This is necessary when ICE restart fails, but **requires consumer action** to complete the signaling handshake.
|
|
175
|
+
|
|
176
|
+
#### Handling Full Reconnection
|
|
177
|
+
|
|
178
|
+
When a full reconnection is triggered, the manager will:
|
|
179
|
+
1. Clean up the old peer connection
|
|
180
|
+
2. Create a new peer connection
|
|
181
|
+
3. Emit `EVENT_RECONNECTING` with `strategy: 'full'`
|
|
182
|
+
|
|
183
|
+
**Important:** The manager cannot automatically complete the signaling handshake for full reconnections. You must listen for the `reconnecting` event and re-establish signaling when the strategy is `'full'`:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
manager.on(WebRtcManager.EVENT_RECONNECTING, async ({ attempt, strategy }) => {
|
|
187
|
+
console.log(`Reconnecting (attempt ${attempt}, strategy: ${strategy})`);
|
|
188
|
+
|
|
189
|
+
if (strategy === 'full') {
|
|
190
|
+
// Re-do the signaling handshake
|
|
191
|
+
const offer = await manager.createOffer();
|
|
192
|
+
await manager.setLocalDescription(offer);
|
|
193
|
+
signalingChannel.send({ type: 'offer', offer });
|
|
194
|
+
}
|
|
195
|
+
// For 'ice-restart', the manager handles it automatically
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
If the full reconnection doesn't reach `CONNECTED` state within `fullReconnectTimeout` (default: 30 seconds), it's treated as a failed attempt and the next reconnection attempt begins (or `EVENT_RECONNECT_FAILED` is emitted if max attempts reached).
|
|
200
|
+
|
|
164
201
|
## Examples
|
|
165
202
|
|
|
166
203
|
### Basic Usage (Vanilla JavaScript)
|
|
@@ -422,24 +459,31 @@ Mock-based tests for the manager's logic and state transitions:
|
|
|
422
459
|
deno task test
|
|
423
460
|
```
|
|
424
461
|
|
|
425
|
-
###
|
|
462
|
+
### Interactive Example
|
|
426
463
|
|
|
427
|
-
|
|
464
|
+
The `example/` directory contains a working demo of two peers communicating via WebRTC data channels:
|
|
428
465
|
|
|
429
466
|
```bash
|
|
430
|
-
|
|
467
|
+
# Build the example bundle
|
|
468
|
+
deno task example:build
|
|
469
|
+
|
|
470
|
+
# Serve the example directory
|
|
471
|
+
cd example && deno run -A jsr:@std/http/file-server
|
|
431
472
|
```
|
|
432
473
|
|
|
433
|
-
|
|
474
|
+
Then open `http://localhost:8000/` in your browser.
|
|
434
475
|
|
|
435
|
-
|
|
436
|
-
-
|
|
437
|
-
-
|
|
438
|
-
-
|
|
439
|
-
- Connection state transitions
|
|
440
|
-
- Resource cleanup
|
|
476
|
+
**Structure:**
|
|
477
|
+
- `index.html` - Parent page with two side-by-side iframes, acts as signaling relay via `postMessage`
|
|
478
|
+
- `peer1.html` - The "offerer" peer (click "Connect" to initiate)
|
|
479
|
+
- `peer2.html` - The "answerer" peer (waits for connection)
|
|
441
480
|
|
|
442
|
-
|
|
481
|
+
This example demonstrates:
|
|
482
|
+
- P2P connection establishment without a signaling server (uses `postMessage` between iframes)
|
|
483
|
+
- SDP offer/answer exchange
|
|
484
|
+
- ICE candidate exchange
|
|
485
|
+
- Data channel creation and message passing
|
|
486
|
+
- State change monitoring
|
|
443
487
|
|
|
444
488
|
## License
|
|
445
489
|
|
package/dist/types.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface WebRtcManagerConfig {
|
|
|
26
26
|
maxReconnectAttempts?: number;
|
|
27
27
|
/** Initial reconnection delay in ms. Doubles with each attempt. Defaults to 1000. */
|
|
28
28
|
reconnectDelay?: number;
|
|
29
|
+
/** Timeout in ms for full reconnection strategy to reach connected state. Defaults to 30000. */
|
|
30
|
+
fullReconnectTimeout?: number;
|
|
29
31
|
/**
|
|
30
32
|
* Callback to determine whether reconnection should be attempted.
|
|
31
33
|
* Called before each reconnection attempt when autoReconnect is enabled.
|
package/dist/webrtc-manager.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { type WebRtcFactory, type WebRtcManagerConfig, WebRtcState, type WebRtcE
|
|
|
25
25
|
* await manager.setLocalDescription(offer);
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
|
-
export declare class WebRtcManager {
|
|
28
|
+
export declare class WebRtcManager<TContext = unknown> {
|
|
29
29
|
#private;
|
|
30
30
|
/** Event emitted when connection state changes. Payload: {@link WebRtcState} */
|
|
31
31
|
static readonly EVENT_STATE_CHANGE = "state_change";
|
|
@@ -51,6 +51,24 @@ export declare class WebRtcManager {
|
|
|
51
51
|
static readonly EVENT_MICROPHONE_FAILED = "microphone_failed";
|
|
52
52
|
/** Event emitted when an error occurs. Payload: `Error` */
|
|
53
53
|
static readonly EVENT_ERROR = "error";
|
|
54
|
+
/**
|
|
55
|
+
* User-defined context object for storing arbitrary data associated with this manager.
|
|
56
|
+
* Useful for attaching application-specific state (e.g., audio streams, metadata)
|
|
57
|
+
* without modifying the manager internals.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* // With type parameter for full type safety:
|
|
62
|
+
* const manager = new WebRtcManager<{ audioStream: MediaStream; sessionId: string }>(factory);
|
|
63
|
+
* manager.context = { audioStream: myStream, sessionId: '123' };
|
|
64
|
+
* manager.context.audioStream; // typed as MediaStream
|
|
65
|
+
*
|
|
66
|
+
* // Without type parameter (backwards compatible):
|
|
67
|
+
* const manager = new WebRtcManager(factory);
|
|
68
|
+
* manager.context = { anything: 'goes' };
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
context: TContext | null;
|
|
54
72
|
/**
|
|
55
73
|
* Creates a new WebRtcManager instance.
|
|
56
74
|
* @param factory - Factory object providing WebRTC primitives (peer connection, media, devices).
|
package/dist/webrtc-manager.js
CHANGED
|
@@ -88,7 +88,26 @@ export class WebRtcManager {
|
|
|
88
88
|
#remoteStream = null;
|
|
89
89
|
#dataChannels = new Map();
|
|
90
90
|
#reconnectAttempts = 0;
|
|
91
|
+
/**
|
|
92
|
+
* User-defined context object for storing arbitrary data associated with this manager.
|
|
93
|
+
* Useful for attaching application-specific state (e.g., audio streams, metadata)
|
|
94
|
+
* without modifying the manager internals.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```typescript
|
|
98
|
+
* // With type parameter for full type safety:
|
|
99
|
+
* const manager = new WebRtcManager<{ audioStream: MediaStream; sessionId: string }>(factory);
|
|
100
|
+
* manager.context = { audioStream: myStream, sessionId: '123' };
|
|
101
|
+
* manager.context.audioStream; // typed as MediaStream
|
|
102
|
+
*
|
|
103
|
+
* // Without type parameter (backwards compatible):
|
|
104
|
+
* const manager = new WebRtcManager(factory);
|
|
105
|
+
* manager.context = { anything: 'goes' };
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
context = null;
|
|
91
109
|
#reconnectTimer = null;
|
|
110
|
+
#fullReconnectTimeoutTimer = null;
|
|
92
111
|
#deviceChangeHandler = null;
|
|
93
112
|
/**
|
|
94
113
|
* Creates a new WebRtcManager instance.
|
|
@@ -705,8 +724,12 @@ export class WebRtcManager {
|
|
|
705
724
|
const state = this.#pc.connectionState;
|
|
706
725
|
this.#debug("Connection state changed:", state);
|
|
707
726
|
if (state === "connected") {
|
|
708
|
-
// Connection successful - reset reconnect attempts
|
|
727
|
+
// Connection successful - reset reconnect attempts and clear any pending timeout
|
|
709
728
|
this.#reconnectAttempts = 0;
|
|
729
|
+
if (this.#fullReconnectTimeoutTimer !== null) {
|
|
730
|
+
clearTimeout(this.#fullReconnectTimeoutTimer);
|
|
731
|
+
this.#fullReconnectTimeoutTimer = null;
|
|
732
|
+
}
|
|
710
733
|
this.#dispatch(WebRtcFsmEvent.CONNECTED);
|
|
711
734
|
}
|
|
712
735
|
else if (state === "failed") {
|
|
@@ -747,6 +770,11 @@ export class WebRtcManager {
|
|
|
747
770
|
clearTimeout(this.#reconnectTimer);
|
|
748
771
|
this.#reconnectTimer = null;
|
|
749
772
|
}
|
|
773
|
+
// Clear any pending full reconnection timeout
|
|
774
|
+
if (this.#fullReconnectTimeoutTimer !== null) {
|
|
775
|
+
clearTimeout(this.#fullReconnectTimeoutTimer);
|
|
776
|
+
this.#fullReconnectTimeoutTimer = null;
|
|
777
|
+
}
|
|
750
778
|
// Remove device change listener
|
|
751
779
|
if (this.#deviceChangeHandler) {
|
|
752
780
|
navigator.mediaDevices.removeEventListener("devicechange", this.#deviceChangeHandler);
|
|
@@ -857,7 +885,17 @@ export class WebRtcManager {
|
|
|
857
885
|
this.#cleanup();
|
|
858
886
|
this.#dispatch(WebRtcFsmEvent.RESET);
|
|
859
887
|
await this.connect();
|
|
860
|
-
//
|
|
888
|
+
// Start timeout for full reconnection - if connection doesn't succeed
|
|
889
|
+
// within the timeout, treat it as a failure
|
|
890
|
+
const timeout = this.#config.fullReconnectTimeout ?? 30000;
|
|
891
|
+
this.#fullReconnectTimeoutTimer = setTimeout(() => {
|
|
892
|
+
this.#fullReconnectTimeoutTimer = null;
|
|
893
|
+
// Only trigger failure if still not connected
|
|
894
|
+
if (this.state !== WebRtcState.CONNECTED) {
|
|
895
|
+
this.#debug("Full reconnection timeout reached, connection not established");
|
|
896
|
+
this.#handleConnectionFailure();
|
|
897
|
+
}
|
|
898
|
+
}, timeout);
|
|
861
899
|
}
|
|
862
900
|
catch (e) {
|
|
863
901
|
this.#logger.error("[WebRtcManager] Reconnection failed:", e);
|