@telnyx/react-voice-commons-sdk 0.3.0 → 0.3.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/CHANGELOG.md +7 -0
- package/lib/internal/calls/call-state-controller.d.ts +7 -0
- package/lib/internal/calls/call-state-controller.js +23 -0
- package/lib/internal/session/session-manager.d.ts +14 -1
- package/lib/internal/session/session-manager.js +23 -2
- package/lib/telnyx-voip-client.js +5 -0
- package/package.json +4 -3
- package/src/internal/calls/call-state-controller.ts +26 -0
- package/src/internal/session/session-manager.ts +26 -3
- package/src/telnyx-voip-client.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# CHANGELOG.md
|
|
2
2
|
|
|
3
|
+
## [0.3.1] (2026-04-16)
|
|
4
|
+
|
|
5
|
+
### Bug Fixing
|
|
6
|
+
|
|
7
|
+
- Fixed stale `CONNECTED` state during background disconnect: `SessionManager.disconnect()` now emits `DISCONNECTED` before awaiting the underlying client teardown. Previously, observers (including the auto-reconnect logic in `TelnyxVoiceApp`) could read a stale `CONNECTED` value while the socket was being torn down, causing auto-reconnection to be skipped and subsequent `newCall()` attempts to fail with `Cannot make call when connection state is: DISCONNECTED` or `No connection exists. Please connect first.`
|
|
8
|
+
- Tracked calls are now cleared on disconnect. Previously, calls left in non-terminal states when the socket was torn down would accumulate across background/foreground cycles, since a dead socket never emits the `ENDED`/`FAILED` events that normally trigger per-call cleanup.
|
|
9
|
+
|
|
3
10
|
## [0.3.0] (2026-04-15)
|
|
4
11
|
|
|
5
12
|
### ⚠️ Breaking changes
|
|
@@ -71,6 +71,13 @@ export declare class CallStateController {
|
|
|
71
71
|
isWaitingForInvite: () => boolean;
|
|
72
72
|
onInviteAutoAccepted: () => void;
|
|
73
73
|
}): void;
|
|
74
|
+
/**
|
|
75
|
+
* Clear all tracked calls. Called when the session disconnects so that
|
|
76
|
+
* calls left in non-terminal states (because the socket died before
|
|
77
|
+
* their ENDED/FAILED events could arrive) don't accumulate as ghosts
|
|
78
|
+
* across reconnect cycles.
|
|
79
|
+
*/
|
|
80
|
+
clearAllCalls(): void;
|
|
74
81
|
/**
|
|
75
82
|
* Dispose of the controller and clean up resources
|
|
76
83
|
*/
|
|
@@ -161,6 +161,29 @@ class CallStateController {
|
|
|
161
161
|
this._isWaitingForInvite = callbacks.isWaitingForInvite;
|
|
162
162
|
this._onInviteAutoAccepted = callbacks.onInviteAutoAccepted;
|
|
163
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Clear all tracked calls. Called when the session disconnects so that
|
|
166
|
+
* calls left in non-terminal states (because the socket died before
|
|
167
|
+
* their ENDED/FAILED events could arrive) don't accumulate as ghosts
|
|
168
|
+
* across reconnect cycles.
|
|
169
|
+
*/
|
|
170
|
+
clearAllCalls() {
|
|
171
|
+
if (this._callMap.size === 0) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
console.log(
|
|
175
|
+
`CallStateController: Clearing ${this._callMap.size} tracked call(s) on disconnect`
|
|
176
|
+
);
|
|
177
|
+
for (const call of this._callMap.values()) {
|
|
178
|
+
try {
|
|
179
|
+
call.dispose();
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.warn('CallStateController: Error disposing call during clear:', error);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
this._callMap.clear();
|
|
185
|
+
this._calls.next([]);
|
|
186
|
+
}
|
|
164
187
|
/**
|
|
165
188
|
* Dispose of the controller and clean up resources
|
|
166
189
|
*/
|
|
@@ -15,6 +15,7 @@ export declare class SessionManager {
|
|
|
15
15
|
private _sessionId;
|
|
16
16
|
private _disposed;
|
|
17
17
|
private _onClientReady?;
|
|
18
|
+
private _onDisconnect?;
|
|
18
19
|
constructor();
|
|
19
20
|
/**
|
|
20
21
|
* Observable stream of connection state changes
|
|
@@ -24,6 +25,11 @@ export declare class SessionManager {
|
|
|
24
25
|
* Set callback to be called when the Telnyx client is ready
|
|
25
26
|
*/
|
|
26
27
|
setOnClientReady(callback: () => void): void;
|
|
28
|
+
/**
|
|
29
|
+
* Set callback to be called when the session disconnects, so dependent
|
|
30
|
+
* subsystems (e.g. the call state controller) can clear their state.
|
|
31
|
+
*/
|
|
32
|
+
setOnDisconnect(callback: () => void): void;
|
|
27
33
|
/**
|
|
28
34
|
* Current connection state (synchronous access)
|
|
29
35
|
*/
|
|
@@ -45,7 +51,14 @@ export declare class SessionManager {
|
|
|
45
51
|
*/
|
|
46
52
|
connectWithToken(config: TokenConfig): Promise<void>;
|
|
47
53
|
/**
|
|
48
|
-
* Disconnect from the Telnyx platform
|
|
54
|
+
* Disconnect from the Telnyx platform.
|
|
55
|
+
*
|
|
56
|
+
* The DISCONNECTED state is emitted BEFORE awaiting the underlying
|
|
57
|
+
* client teardown so that observers (including the auto-reconnect logic
|
|
58
|
+
* in TelnyxVoiceApp) cannot read a stale CONNECTED value during the
|
|
59
|
+
* short window while the socket is being torn down. Tracked calls are
|
|
60
|
+
* cleared here too, since a torn-down socket will never emit the
|
|
61
|
+
* ENDED/FAILED events that normally trigger per-call cleanup.
|
|
49
62
|
*/
|
|
50
63
|
disconnect(): Promise<void>;
|
|
51
64
|
/**
|
|
@@ -85,6 +85,13 @@ class SessionManager {
|
|
|
85
85
|
setOnClientReady(callback) {
|
|
86
86
|
this._onClientReady = callback;
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Set callback to be called when the session disconnects, so dependent
|
|
90
|
+
* subsystems (e.g. the call state controller) can clear their state.
|
|
91
|
+
*/
|
|
92
|
+
setOnDisconnect(callback) {
|
|
93
|
+
this._onDisconnect = callback;
|
|
94
|
+
}
|
|
88
95
|
/**
|
|
89
96
|
* Current connection state (synchronous access)
|
|
90
97
|
*/
|
|
@@ -124,13 +131,28 @@ class SessionManager {
|
|
|
124
131
|
await this._connect();
|
|
125
132
|
}
|
|
126
133
|
/**
|
|
127
|
-
* Disconnect from the Telnyx platform
|
|
134
|
+
* Disconnect from the Telnyx platform.
|
|
135
|
+
*
|
|
136
|
+
* The DISCONNECTED state is emitted BEFORE awaiting the underlying
|
|
137
|
+
* client teardown so that observers (including the auto-reconnect logic
|
|
138
|
+
* in TelnyxVoiceApp) cannot read a stale CONNECTED value during the
|
|
139
|
+
* short window while the socket is being torn down. Tracked calls are
|
|
140
|
+
* cleared here too, since a torn-down socket will never emit the
|
|
141
|
+
* ENDED/FAILED events that normally trigger per-call cleanup.
|
|
128
142
|
*/
|
|
129
143
|
async disconnect() {
|
|
130
144
|
if (this._disposed) {
|
|
131
145
|
return;
|
|
132
146
|
}
|
|
133
147
|
this._currentConfig = undefined;
|
|
148
|
+
this._connectionState.next(connection_state_1.TelnyxConnectionState.DISCONNECTED);
|
|
149
|
+
if (this._onDisconnect) {
|
|
150
|
+
try {
|
|
151
|
+
this._onDisconnect();
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error('Error in onDisconnect callback:', error);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
134
156
|
if (this._telnyxClient) {
|
|
135
157
|
try {
|
|
136
158
|
await this._telnyxClient.disconnect();
|
|
@@ -138,7 +160,6 @@ class SessionManager {
|
|
|
138
160
|
console.error('Error during disconnect:', error);
|
|
139
161
|
}
|
|
140
162
|
}
|
|
141
|
-
this._connectionState.next(connection_state_1.TelnyxConnectionState.DISCONNECTED);
|
|
142
163
|
}
|
|
143
164
|
/**
|
|
144
165
|
* Disable push notifications for the current session.
|
|
@@ -66,6 +66,11 @@ class TelnyxVoipClient {
|
|
|
66
66
|
);
|
|
67
67
|
this._callStateController.initializeClientListeners();
|
|
68
68
|
});
|
|
69
|
+
// Clear any tracked calls when the session disconnects, so ghosts
|
|
70
|
+
// don't accumulate across background → foreground reconnect cycles.
|
|
71
|
+
this._sessionManager.setOnDisconnect(() => {
|
|
72
|
+
this._callStateController.clearAllCalls();
|
|
73
|
+
});
|
|
69
74
|
if (this._options.debug) {
|
|
70
75
|
console.log('TelnyxVoipClient initialized with options:', this._options);
|
|
71
76
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telnyx/react-voice-commons-sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "A high-level, state-agnostic, drop-in module for the Telnyx React Native SDK that simplifies WebRTC voice calling integration",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/index.js",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"homepage": "https://github.com/team-telnyx/react-native-voice-commons",
|
|
60
60
|
"repository": {
|
|
61
61
|
"type": "git",
|
|
62
|
-
"url": "
|
|
62
|
+
"url": "https://github.com/team-telnyx/react-native-voice-commons.git"
|
|
63
63
|
},
|
|
64
64
|
"bugs": {
|
|
65
65
|
"url": "https://github.com/team-telnyx/react-native-voice-commons/issues"
|
|
@@ -112,6 +112,7 @@
|
|
|
112
112
|
"node": ">=16"
|
|
113
113
|
},
|
|
114
114
|
"publishConfig": {
|
|
115
|
-
"access": "public"
|
|
115
|
+
"access": "public",
|
|
116
|
+
"provenance": true
|
|
116
117
|
}
|
|
117
118
|
}
|
|
@@ -190,6 +190,32 @@ export class CallStateController {
|
|
|
190
190
|
this._onInviteAutoAccepted = callbacks.onInviteAutoAccepted;
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Clear all tracked calls. Called when the session disconnects so that
|
|
195
|
+
* calls left in non-terminal states (because the socket died before
|
|
196
|
+
* their ENDED/FAILED events could arrive) don't accumulate as ghosts
|
|
197
|
+
* across reconnect cycles.
|
|
198
|
+
*/
|
|
199
|
+
clearAllCalls(): void {
|
|
200
|
+
if (this._callMap.size === 0) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
console.log(
|
|
205
|
+
`CallStateController: Clearing ${this._callMap.size} tracked call(s) on disconnect`
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
for (const call of this._callMap.values()) {
|
|
209
|
+
try {
|
|
210
|
+
call.dispose();
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.warn('CallStateController: Error disposing call during clear:', error);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
this._callMap.clear();
|
|
216
|
+
this._calls.next([]);
|
|
217
|
+
}
|
|
218
|
+
|
|
193
219
|
/**
|
|
194
220
|
* Dispose of the controller and clean up resources
|
|
195
221
|
*/
|
|
@@ -26,6 +26,7 @@ export class SessionManager {
|
|
|
26
26
|
private _sessionId: string;
|
|
27
27
|
private _disposed = false;
|
|
28
28
|
private _onClientReady?: () => void;
|
|
29
|
+
private _onDisconnect?: () => void;
|
|
29
30
|
|
|
30
31
|
constructor() {
|
|
31
32
|
this._sessionId = this._generateSessionId();
|
|
@@ -45,6 +46,14 @@ export class SessionManager {
|
|
|
45
46
|
this._onClientReady = callback;
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Set callback to be called when the session disconnects, so dependent
|
|
51
|
+
* subsystems (e.g. the call state controller) can clear their state.
|
|
52
|
+
*/
|
|
53
|
+
setOnDisconnect(callback: () => void): void {
|
|
54
|
+
this._onDisconnect = callback;
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
/**
|
|
49
58
|
* Current connection state (synchronous access)
|
|
50
59
|
*/
|
|
@@ -91,7 +100,14 @@ export class SessionManager {
|
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
/**
|
|
94
|
-
* Disconnect from the Telnyx platform
|
|
103
|
+
* Disconnect from the Telnyx platform.
|
|
104
|
+
*
|
|
105
|
+
* The DISCONNECTED state is emitted BEFORE awaiting the underlying
|
|
106
|
+
* client teardown so that observers (including the auto-reconnect logic
|
|
107
|
+
* in TelnyxVoiceApp) cannot read a stale CONNECTED value during the
|
|
108
|
+
* short window while the socket is being torn down. Tracked calls are
|
|
109
|
+
* cleared here too, since a torn-down socket will never emit the
|
|
110
|
+
* ENDED/FAILED events that normally trigger per-call cleanup.
|
|
95
111
|
*/
|
|
96
112
|
async disconnect(): Promise<void> {
|
|
97
113
|
if (this._disposed) {
|
|
@@ -99,6 +115,15 @@ export class SessionManager {
|
|
|
99
115
|
}
|
|
100
116
|
|
|
101
117
|
this._currentConfig = undefined;
|
|
118
|
+
this._connectionState.next(TelnyxConnectionState.DISCONNECTED);
|
|
119
|
+
|
|
120
|
+
if (this._onDisconnect) {
|
|
121
|
+
try {
|
|
122
|
+
this._onDisconnect();
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error('Error in onDisconnect callback:', error);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
102
127
|
|
|
103
128
|
if (this._telnyxClient) {
|
|
104
129
|
try {
|
|
@@ -107,8 +132,6 @@ export class SessionManager {
|
|
|
107
132
|
console.error('Error during disconnect:', error);
|
|
108
133
|
}
|
|
109
134
|
}
|
|
110
|
-
|
|
111
|
-
this._connectionState.next(TelnyxConnectionState.DISCONNECTED);
|
|
112
135
|
}
|
|
113
136
|
|
|
114
137
|
/**
|
|
@@ -82,6 +82,12 @@ export class TelnyxVoipClient {
|
|
|
82
82
|
this._callStateController.initializeClientListeners();
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
+
// Clear any tracked calls when the session disconnects, so ghosts
|
|
86
|
+
// don't accumulate across background → foreground reconnect cycles.
|
|
87
|
+
this._sessionManager.setOnDisconnect(() => {
|
|
88
|
+
this._callStateController.clearAllCalls();
|
|
89
|
+
});
|
|
90
|
+
|
|
85
91
|
if (this._options.debug) {
|
|
86
92
|
console.log('TelnyxVoipClient initialized with options:', this._options);
|
|
87
93
|
}
|