@marianmeres/webrtc 1.0.2 → 1.1.0
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 +10 -10
- package/API.md +39 -4
- package/README.md +32 -0
- package/dist/types.d.ts +78 -5
- package/dist/types.js +22 -0
- package/dist/webrtc-manager.js +39 -4
- package/package.json +9 -3
package/AGENTS.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
```yaml
|
|
6
6
|
name: "@marianmeres/webrtc"
|
|
7
|
-
version: "
|
|
7
|
+
version: "1.0.2"
|
|
8
8
|
license: MIT
|
|
9
9
|
author: Marian Meres
|
|
10
10
|
repository: https://github.com/marianmeres/webrtc
|
|
@@ -27,12 +27,12 @@ A lightweight, framework-agnostic WebRTC manager providing:
|
|
|
27
27
|
|
|
28
28
|
```yaml
|
|
29
29
|
production:
|
|
30
|
-
- "@marianmeres/fsm": "^2.
|
|
31
|
-
- "@marianmeres/pubsub": "^2.4.
|
|
30
|
+
- "@marianmeres/fsm": "^2.11.0"
|
|
31
|
+
- "@marianmeres/pubsub": "^2.4.4"
|
|
32
32
|
development:
|
|
33
|
-
- "@std/assert":
|
|
34
|
-
- "@std/fs":
|
|
35
|
-
- "@std/path":
|
|
33
|
+
- "@std/assert": "^1.0.16"
|
|
34
|
+
- "@std/fs": "^1.0.20"
|
|
35
|
+
- "@std/path": "^1.1.3"
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
## File Structure
|
|
@@ -123,10 +123,10 @@ Console-compatible logger interface for custom logging implementations.
|
|
|
123
123
|
|
|
124
124
|
```typescript
|
|
125
125
|
interface Logger {
|
|
126
|
-
debug: (...args: any[]) =>
|
|
127
|
-
log: (...args: any[]) =>
|
|
128
|
-
warn: (...args: any[]) =>
|
|
129
|
-
error: (...args: any[]) =>
|
|
126
|
+
debug: (...args: any[]) => any;
|
|
127
|
+
log: (...args: any[]) => any;
|
|
128
|
+
warn: (...args: any[]) => any;
|
|
129
|
+
error: (...args: any[]) => any;
|
|
130
130
|
}
|
|
131
131
|
```
|
|
132
132
|
|
package/API.md
CHANGED
|
@@ -687,10 +687,10 @@ Console-compatible logger interface for custom logging implementations.
|
|
|
687
687
|
|
|
688
688
|
```typescript
|
|
689
689
|
interface Logger {
|
|
690
|
-
debug: (...args: any[]) =>
|
|
691
|
-
log: (...args: any[]) =>
|
|
692
|
-
warn: (...args: any[]) =>
|
|
693
|
-
error: (...args: any[]) =>
|
|
690
|
+
debug: (...args: any[]) => any;
|
|
691
|
+
log: (...args: any[]) => any;
|
|
692
|
+
warn: (...args: any[]) => any;
|
|
693
|
+
error: (...args: any[]) => any;
|
|
694
694
|
}
|
|
695
695
|
```
|
|
696
696
|
|
|
@@ -759,6 +759,13 @@ interface WebRtcManagerConfig {
|
|
|
759
759
|
/** Initial reconnection delay in ms (doubles each attempt). Default: 1000 */
|
|
760
760
|
reconnectDelay?: number;
|
|
761
761
|
|
|
762
|
+
/** Callback to control whether reconnection should proceed */
|
|
763
|
+
shouldReconnect?: (context: {
|
|
764
|
+
attempt: number;
|
|
765
|
+
maxAttempts: number;
|
|
766
|
+
strategy: "ice-restart" | "full";
|
|
767
|
+
}) => boolean;
|
|
768
|
+
|
|
762
769
|
/** Enable debug logging. Default: false */
|
|
763
770
|
debug?: boolean;
|
|
764
771
|
|
|
@@ -947,3 +954,31 @@ manager.on('reconnecting', ({ attempt, strategy }) => {
|
|
|
947
954
|
}
|
|
948
955
|
});
|
|
949
956
|
```
|
|
957
|
+
|
|
958
|
+
### Conditional Reconnection
|
|
959
|
+
|
|
960
|
+
Use the `shouldReconnect` callback to suppress reconnection when the peer disconnected intentionally:
|
|
961
|
+
|
|
962
|
+
```typescript
|
|
963
|
+
let peerLeftIntentionally = false;
|
|
964
|
+
|
|
965
|
+
const manager = new WebRtcManager(factory, {
|
|
966
|
+
autoReconnect: true,
|
|
967
|
+
shouldReconnect: ({ attempt, maxAttempts, strategy }) => {
|
|
968
|
+
// Return false to suppress reconnection
|
|
969
|
+
return !peerLeftIntentionally;
|
|
970
|
+
},
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
// Track intentional disconnects via data channel
|
|
974
|
+
manager.on('data_channel_message', ({ data }) => {
|
|
975
|
+
if (JSON.parse(data).type === 'bye') {
|
|
976
|
+
peerLeftIntentionally = true;
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
The callback receives:
|
|
982
|
+
- `attempt`: Current attempt number (1-based)
|
|
983
|
+
- `maxAttempts`: Configured maximum attempts
|
|
984
|
+
- `strategy`: `"ice-restart"` or `"full"`
|
package/README.md
CHANGED
|
@@ -51,6 +51,7 @@ const manager = new WebRtcManager(factory, config);
|
|
|
51
51
|
- `autoReconnect`: Enable automatic reconnection (default: false)
|
|
52
52
|
- `maxReconnectAttempts`: Max reconnection attempts (default: 5)
|
|
53
53
|
- `reconnectDelay`: Initial reconnection delay in ms (default: 1000)
|
|
54
|
+
- `shouldReconnect`: Callback to control whether reconnection should proceed (see below)
|
|
54
55
|
- `debug`: Enable debug logging (default: false)
|
|
55
56
|
- `logger`: Custom logger instance implementing `Logger` interface (default: console)
|
|
56
57
|
|
|
@@ -129,6 +130,37 @@ const unsub = manager.subscribe((state) => {
|
|
|
129
130
|
- `EVENT_MICROPHONE_FAILED`
|
|
130
131
|
- `EVENT_ERROR`
|
|
131
132
|
|
|
133
|
+
### Controlling Reconnection
|
|
134
|
+
|
|
135
|
+
When `autoReconnect` is enabled, you can use the `shouldReconnect` callback to conditionally suppress reconnection attempts. This is useful when the remote peer disconnected intentionally (e.g., left the call) rather than due to network failure.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
let peerLeftIntentionally = false;
|
|
139
|
+
|
|
140
|
+
const manager = new WebRtcManager(factory, {
|
|
141
|
+
autoReconnect: true,
|
|
142
|
+
shouldReconnect: ({ attempt, maxAttempts, strategy }) => {
|
|
143
|
+
if (peerLeftIntentionally) {
|
|
144
|
+
return false; // Don't reconnect
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Listen for "goodbye" message from peer before they disconnect
|
|
151
|
+
manager.on(WebRtcManager.EVENT_DATA_CHANNEL_MESSAGE, ({ data }) => {
|
|
152
|
+
const msg = JSON.parse(data);
|
|
153
|
+
if (msg.type === 'bye') {
|
|
154
|
+
peerLeftIntentionally = true;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The callback receives:
|
|
160
|
+
- `attempt`: Current reconnection attempt (1-based)
|
|
161
|
+
- `maxAttempts`: Configured maximum attempts
|
|
162
|
+
- `strategy`: `"ice-restart"` (attempts 1-2) or `"full"` (attempts 3+)
|
|
163
|
+
|
|
132
164
|
## Examples
|
|
133
165
|
|
|
134
166
|
### Basic Usage (Vanilla JavaScript)
|
package/dist/types.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Console-compatible logger interface.
|
|
2
|
+
* Console-compatible logger interface (see @marianmeres/clog ).
|
|
3
3
|
* Each method accepts variadic arguments and returns a string representation of the first argument.
|
|
4
4
|
* This enables patterns like `throw new Error(logger.error("msg"))`.
|
|
5
5
|
*/
|
|
6
6
|
export interface Logger {
|
|
7
|
-
debug: (...args: any[]) =>
|
|
8
|
-
log: (...args: any[]) =>
|
|
9
|
-
warn: (...args: any[]) =>
|
|
10
|
-
error: (...args: any[]) =>
|
|
7
|
+
debug: (...args: any[]) => any;
|
|
8
|
+
log: (...args: any[]) => any;
|
|
9
|
+
warn: (...args: any[]) => any;
|
|
10
|
+
error: (...args: any[]) => any;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Configuration options for WebRtcManager.
|
|
14
|
+
* All options are optional with sensible defaults.
|
|
15
|
+
*/
|
|
12
16
|
export interface WebRtcManagerConfig {
|
|
13
17
|
/** Initial peer configuration (ICE servers, etc.) */
|
|
14
18
|
peerConfig?: RTCConfiguration;
|
|
@@ -22,44 +26,109 @@ export interface WebRtcManagerConfig {
|
|
|
22
26
|
maxReconnectAttempts?: number;
|
|
23
27
|
/** Initial reconnection delay in ms. Doubles with each attempt. Defaults to 1000. */
|
|
24
28
|
reconnectDelay?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Callback to determine whether reconnection should be attempted.
|
|
31
|
+
* Called before each reconnection attempt when autoReconnect is enabled.
|
|
32
|
+
* Return false to suppress reconnection (e.g., when peer disconnected intentionally).
|
|
33
|
+
* If not provided, reconnection proceeds automatically up to maxReconnectAttempts.
|
|
34
|
+
*/
|
|
35
|
+
shouldReconnect?: (context: {
|
|
36
|
+
/** Current reconnection attempt number (1-based) */
|
|
37
|
+
attempt: number;
|
|
38
|
+
/** Maximum configured reconnection attempts */
|
|
39
|
+
maxAttempts: number;
|
|
40
|
+
/** Strategy that will be used: "ice-restart" for first attempts, "full" for later */
|
|
41
|
+
strategy: "ice-restart" | "full";
|
|
42
|
+
}) => boolean;
|
|
25
43
|
/** Enable debug logging. Defaults to false. */
|
|
26
44
|
debug?: boolean;
|
|
27
45
|
/** Custom logger instance. If not provided, falls back to console. */
|
|
28
46
|
logger?: Logger;
|
|
29
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Factory interface for creating WebRTC primitives.
|
|
50
|
+
* Allows dependency injection of browser APIs for testing and flexibility.
|
|
51
|
+
*/
|
|
30
52
|
export interface WebRtcFactory {
|
|
53
|
+
/**
|
|
54
|
+
* Creates a new RTCPeerConnection instance.
|
|
55
|
+
* @param config - Optional RTCConfiguration for ICE servers, certificates, etc.
|
|
56
|
+
* @returns A new RTCPeerConnection instance.
|
|
57
|
+
*/
|
|
31
58
|
createPeerConnection(config?: RTCConfiguration): RTCPeerConnection;
|
|
59
|
+
/**
|
|
60
|
+
* Requests access to user media (microphone/camera).
|
|
61
|
+
* @param constraints - Media constraints specifying audio/video requirements.
|
|
62
|
+
* @returns Promise resolving to a MediaStream.
|
|
63
|
+
*/
|
|
32
64
|
getUserMedia(constraints: MediaStreamConstraints): Promise<MediaStream>;
|
|
65
|
+
/**
|
|
66
|
+
* Enumerates all available media input and output devices.
|
|
67
|
+
* @returns Promise resolving to an array of MediaDeviceInfo objects.
|
|
68
|
+
*/
|
|
33
69
|
enumerateDevices(): Promise<MediaDeviceInfo[]>;
|
|
34
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Possible states of the WebRTC connection lifecycle.
|
|
73
|
+
* The manager transitions between these states based on connection events.
|
|
74
|
+
*/
|
|
35
75
|
export declare enum WebRtcState {
|
|
76
|
+
/** Initial state, no resources allocated. */
|
|
36
77
|
IDLE = "IDLE",
|
|
78
|
+
/** Creating peer connection and setting up media tracks. */
|
|
37
79
|
INITIALIZING = "INITIALIZING",
|
|
80
|
+
/** Performing SDP offer/answer exchange. */
|
|
38
81
|
CONNECTING = "CONNECTING",
|
|
82
|
+
/** Connection established, communication active. */
|
|
39
83
|
CONNECTED = "CONNECTED",
|
|
84
|
+
/** Automatic reconnection in progress. */
|
|
40
85
|
RECONNECTING = "RECONNECTING",
|
|
86
|
+
/** Connection closed, can be restarted. */
|
|
41
87
|
DISCONNECTED = "DISCONNECTED",
|
|
88
|
+
/** Error state, requires reset() to recover. */
|
|
42
89
|
ERROR = "ERROR"
|
|
43
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Internal FSM events that trigger state transitions.
|
|
93
|
+
* These events are dispatched internally by the manager methods.
|
|
94
|
+
*/
|
|
44
95
|
export declare enum WebRtcFsmEvent {
|
|
96
|
+
/** Triggers transition from IDLE to INITIALIZING. */
|
|
45
97
|
INIT = "initialize",
|
|
98
|
+
/** Triggers transition to CONNECTING state. */
|
|
46
99
|
CONNECT = "connect",
|
|
100
|
+
/** Signals successful connection establishment. */
|
|
47
101
|
CONNECTED = "connected",
|
|
102
|
+
/** Triggers transition to RECONNECTING state. */
|
|
48
103
|
RECONNECTING = "reconnecting",
|
|
104
|
+
/** Triggers transition to DISCONNECTED state. */
|
|
49
105
|
DISCONNECT = "disconnect",
|
|
106
|
+
/** Triggers transition to ERROR state. */
|
|
50
107
|
ERROR = "error",
|
|
108
|
+
/** Resets the manager to IDLE state. */
|
|
51
109
|
RESET = "reset"
|
|
52
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Type definitions for all WebRTC manager events and their payloads.
|
|
113
|
+
* Use with the `on()` method to subscribe to specific events.
|
|
114
|
+
*/
|
|
53
115
|
export interface WebRtcEvents {
|
|
116
|
+
/** Emitted when connection state changes. Payload: the new WebRtcState. */
|
|
54
117
|
state_change: WebRtcState;
|
|
118
|
+
/** Emitted when local media stream changes. Payload: MediaStream or null if disabled. */
|
|
55
119
|
local_stream: MediaStream | null;
|
|
120
|
+
/** Emitted when remote media stream is received. Payload: MediaStream or null. */
|
|
56
121
|
remote_stream: MediaStream | null;
|
|
122
|
+
/** Emitted when a data channel opens. Payload: the RTCDataChannel. */
|
|
57
123
|
data_channel_open: RTCDataChannel;
|
|
124
|
+
/** Emitted when a data channel receives a message. Payload: channel and data. */
|
|
58
125
|
data_channel_message: {
|
|
59
126
|
channel: RTCDataChannel;
|
|
60
127
|
data: any;
|
|
61
128
|
};
|
|
129
|
+
/** Emitted when a data channel closes. Payload: the RTCDataChannel. */
|
|
62
130
|
data_channel_close: RTCDataChannel;
|
|
131
|
+
/** Emitted when an ICE candidate is generated. Payload: RTCIceCandidate or null. */
|
|
63
132
|
ice_candidate: RTCIceCandidate | null;
|
|
64
133
|
/**
|
|
65
134
|
* Emitted when reconnection is being attempted.
|
|
@@ -71,13 +140,17 @@ export interface WebRtcEvents {
|
|
|
71
140
|
attempt: number;
|
|
72
141
|
strategy: "ice-restart" | "full";
|
|
73
142
|
};
|
|
143
|
+
/** Emitted when all reconnection attempts have failed. Payload: total attempts. */
|
|
74
144
|
reconnect_failed: {
|
|
75
145
|
attempts: number;
|
|
76
146
|
};
|
|
147
|
+
/** Emitted when audio devices change. Payload: array of available audio inputs. */
|
|
77
148
|
device_changed: MediaDeviceInfo[];
|
|
149
|
+
/** Emitted when microphone access or switching fails. */
|
|
78
150
|
microphone_failed: {
|
|
79
151
|
error?: any;
|
|
80
152
|
reason?: string;
|
|
81
153
|
};
|
|
154
|
+
/** Emitted when an error occurs. Payload: the Error object. */
|
|
82
155
|
error: Error;
|
|
83
156
|
}
|
package/dist/types.js
CHANGED
|
@@ -1,20 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Possible states of the WebRTC connection lifecycle.
|
|
3
|
+
* The manager transitions between these states based on connection events.
|
|
4
|
+
*/
|
|
1
5
|
export var WebRtcState;
|
|
2
6
|
(function (WebRtcState) {
|
|
7
|
+
/** Initial state, no resources allocated. */
|
|
3
8
|
WebRtcState["IDLE"] = "IDLE";
|
|
9
|
+
/** Creating peer connection and setting up media tracks. */
|
|
4
10
|
WebRtcState["INITIALIZING"] = "INITIALIZING";
|
|
11
|
+
/** Performing SDP offer/answer exchange. */
|
|
5
12
|
WebRtcState["CONNECTING"] = "CONNECTING";
|
|
13
|
+
/** Connection established, communication active. */
|
|
6
14
|
WebRtcState["CONNECTED"] = "CONNECTED";
|
|
15
|
+
/** Automatic reconnection in progress. */
|
|
7
16
|
WebRtcState["RECONNECTING"] = "RECONNECTING";
|
|
17
|
+
/** Connection closed, can be restarted. */
|
|
8
18
|
WebRtcState["DISCONNECTED"] = "DISCONNECTED";
|
|
19
|
+
/** Error state, requires reset() to recover. */
|
|
9
20
|
WebRtcState["ERROR"] = "ERROR";
|
|
10
21
|
})(WebRtcState || (WebRtcState = {}));
|
|
22
|
+
/**
|
|
23
|
+
* Internal FSM events that trigger state transitions.
|
|
24
|
+
* These events are dispatched internally by the manager methods.
|
|
25
|
+
*/
|
|
11
26
|
export var WebRtcFsmEvent;
|
|
12
27
|
(function (WebRtcFsmEvent) {
|
|
28
|
+
/** Triggers transition from IDLE to INITIALIZING. */
|
|
13
29
|
WebRtcFsmEvent["INIT"] = "initialize";
|
|
30
|
+
/** Triggers transition to CONNECTING state. */
|
|
14
31
|
WebRtcFsmEvent["CONNECT"] = "connect";
|
|
32
|
+
/** Signals successful connection establishment. */
|
|
15
33
|
WebRtcFsmEvent["CONNECTED"] = "connected";
|
|
34
|
+
/** Triggers transition to RECONNECTING state. */
|
|
16
35
|
WebRtcFsmEvent["RECONNECTING"] = "reconnecting";
|
|
36
|
+
/** Triggers transition to DISCONNECTED state. */
|
|
17
37
|
WebRtcFsmEvent["DISCONNECT"] = "disconnect";
|
|
38
|
+
/** Triggers transition to ERROR state. */
|
|
18
39
|
WebRtcFsmEvent["ERROR"] = "error";
|
|
40
|
+
/** Resets the manager to IDLE state. */
|
|
19
41
|
WebRtcFsmEvent["RESET"] = "reset";
|
|
20
42
|
})(WebRtcFsmEvent || (WebRtcFsmEvent = {}));
|
package/dist/webrtc-manager.js
CHANGED
|
@@ -2,22 +2,26 @@ import { FSM } from "@marianmeres/fsm";
|
|
|
2
2
|
import { PubSub } from "@marianmeres/pubsub";
|
|
3
3
|
import { WebRtcState, WebRtcFsmEvent, } from "./types.js";
|
|
4
4
|
/**
|
|
5
|
-
* Default console-based logger that wraps console methods
|
|
5
|
+
* Default console-based logger that wraps console methods.
|
|
6
6
|
* Returns string representation of the first argument for chaining.
|
|
7
7
|
*/
|
|
8
8
|
const createDefaultLogger = () => ({
|
|
9
|
+
// deno-lint-ignore no-explicit-any
|
|
9
10
|
debug: (...args) => {
|
|
10
11
|
console.debug(...args);
|
|
11
12
|
return String(args[0] ?? "");
|
|
12
13
|
},
|
|
14
|
+
// deno-lint-ignore no-explicit-any
|
|
13
15
|
log: (...args) => {
|
|
14
16
|
console.log(...args);
|
|
15
17
|
return String(args[0] ?? "");
|
|
16
18
|
},
|
|
19
|
+
// deno-lint-ignore no-explicit-any
|
|
17
20
|
warn: (...args) => {
|
|
18
21
|
console.warn(...args);
|
|
19
22
|
return String(args[0] ?? "");
|
|
20
23
|
},
|
|
24
|
+
// deno-lint-ignore no-explicit-any
|
|
21
25
|
error: (...args) => {
|
|
22
26
|
console.error(...args);
|
|
23
27
|
return String(args[0] ?? "");
|
|
@@ -173,6 +177,7 @@ export class WebRtcManager {
|
|
|
173
177
|
* @param handler - Callback function that receives the event data.
|
|
174
178
|
* @returns Unsubscribe function to remove the event listener.
|
|
175
179
|
*/
|
|
180
|
+
// deno-lint-ignore no-explicit-any
|
|
176
181
|
on(event, handler) {
|
|
177
182
|
return this.#pubsub.subscribe(event, handler);
|
|
178
183
|
}
|
|
@@ -387,7 +392,9 @@ export class WebRtcManager {
|
|
|
387
392
|
}
|
|
388
393
|
catch (e) {
|
|
389
394
|
this.#logger.error("[WebRtcManager] Failed to get user media:", e);
|
|
390
|
-
this.#pubsub.publish(WebRtcManager.EVENT_MICROPHONE_FAILED, {
|
|
395
|
+
this.#pubsub.publish(WebRtcManager.EVENT_MICROPHONE_FAILED, {
|
|
396
|
+
error: e,
|
|
397
|
+
});
|
|
391
398
|
return false;
|
|
392
399
|
}
|
|
393
400
|
}
|
|
@@ -678,11 +685,13 @@ export class WebRtcManager {
|
|
|
678
685
|
this.#pubsub.publish(WebRtcManager.EVENT_STATE_CHANGE, newState);
|
|
679
686
|
}
|
|
680
687
|
}
|
|
688
|
+
// deno-lint-ignore no-explicit-any
|
|
681
689
|
#debug(...args) {
|
|
682
690
|
if (this.#config.debug) {
|
|
683
691
|
this.#logger.debug("[WebRtcManager]", ...args);
|
|
684
692
|
}
|
|
685
693
|
}
|
|
694
|
+
// deno-lint-ignore no-explicit-any
|
|
686
695
|
#error(error) {
|
|
687
696
|
this.#logger.error("[WebRtcManager]", error);
|
|
688
697
|
this.#dispatch(WebRtcFsmEvent.ERROR);
|
|
@@ -705,7 +714,12 @@ export class WebRtcManager {
|
|
|
705
714
|
this.#handleConnectionFailure();
|
|
706
715
|
}
|
|
707
716
|
else if (state === "disconnected" || state === "closed") {
|
|
708
|
-
|
|
717
|
+
// Only dispatch if not already in a terminal state
|
|
718
|
+
if (this.state !== WebRtcState.DISCONNECTED &&
|
|
719
|
+
this.state !== WebRtcState.ERROR &&
|
|
720
|
+
this.state !== WebRtcState.IDLE) {
|
|
721
|
+
this.#dispatch(WebRtcFsmEvent.DISCONNECT);
|
|
722
|
+
}
|
|
709
723
|
}
|
|
710
724
|
};
|
|
711
725
|
this.#pc.ontrack = (event) => {
|
|
@@ -766,7 +780,12 @@ export class WebRtcManager {
|
|
|
766
780
|
}
|
|
767
781
|
#handleConnectionFailure() {
|
|
768
782
|
this.#debug("Handling connection failure");
|
|
769
|
-
|
|
783
|
+
// Only dispatch DISCONNECT if not already in a terminal state
|
|
784
|
+
if (this.state !== WebRtcState.DISCONNECTED &&
|
|
785
|
+
this.state !== WebRtcState.ERROR &&
|
|
786
|
+
this.state !== WebRtcState.IDLE) {
|
|
787
|
+
this.#dispatch(WebRtcFsmEvent.DISCONNECT);
|
|
788
|
+
}
|
|
770
789
|
// Check if auto-reconnect is enabled
|
|
771
790
|
if (!this.#config.autoReconnect) {
|
|
772
791
|
this.#debug("Auto-reconnect disabled, not attempting reconnection");
|
|
@@ -781,6 +800,21 @@ export class WebRtcManager {
|
|
|
781
800
|
});
|
|
782
801
|
return;
|
|
783
802
|
}
|
|
803
|
+
// Determine strategy for this attempt (next attempt number is current + 1)
|
|
804
|
+
const nextAttempt = this.#reconnectAttempts + 1;
|
|
805
|
+
const strategy = nextAttempt <= 2 ? "ice-restart" : "full";
|
|
806
|
+
// Check shouldReconnect callback if provided
|
|
807
|
+
if (this.#config.shouldReconnect) {
|
|
808
|
+
const shouldProceed = this.#config.shouldReconnect?.({
|
|
809
|
+
attempt: nextAttempt,
|
|
810
|
+
maxAttempts,
|
|
811
|
+
strategy,
|
|
812
|
+
});
|
|
813
|
+
if (!shouldProceed) {
|
|
814
|
+
this.#debug("Reconnection suppressed by shouldReconnect callback");
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
784
818
|
// Transition to RECONNECTING state
|
|
785
819
|
this.#dispatch(WebRtcFsmEvent.RECONNECTING);
|
|
786
820
|
// Attempt reconnection with exponential backoff
|
|
@@ -863,6 +897,7 @@ export class WebRtcManager {
|
|
|
863
897
|
this.#pubsub.publish(WebRtcManager.EVENT_DATA_CHANNEL_CLOSE, dc);
|
|
864
898
|
this.#dataChannels.delete(dc.label);
|
|
865
899
|
};
|
|
900
|
+
// deno-lint-ignore no-explicit-any
|
|
866
901
|
dc.onerror = (error) => {
|
|
867
902
|
// Ignore "User-Initiated Abort" errors which occur during intentional close()
|
|
868
903
|
const isUserAbort = error?.error?.message?.includes("User-Initiated Abort");
|
package/package.json
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marianmeres/webrtc",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/mod.js",
|
|
6
6
|
"types": "dist/mod.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/mod.d.ts",
|
|
10
|
+
"import": "./dist/mod.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
7
13
|
"author": "Marian Meres",
|
|
8
14
|
"license": "MIT",
|
|
9
15
|
"dependencies": {
|
|
10
|
-
"@marianmeres/fsm": "^2.
|
|
11
|
-
"@marianmeres/pubsub": "^2.4.
|
|
16
|
+
"@marianmeres/fsm": "^2.11.0",
|
|
17
|
+
"@marianmeres/pubsub": "^2.4.4"
|
|
12
18
|
},
|
|
13
19
|
"repository": {
|
|
14
20
|
"type": "git",
|