@telnyx/react-voice-commons-sdk 0.4.0 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # CHANGELOG.md
2
2
 
3
+ ## [0.4.2] (2026-04-27)
4
+
5
+ ### Bug Fixing
6
+
7
+ - `Call.dtmf()` now sends the full `dialogParams` payload on `telnyx_rtc.info` to match the Android Voice SDK's wire format (via `@telnyx/react-native-voice-sdk@0.4.4`).
8
+
9
+ ## [0.4.1] (2026-04-27)
10
+
11
+ ### Bug Fixing
12
+
13
+ - **Recover from stale TelnyxRTC client on incoming VoIP push.** `SessionManager.handlePushNotification` now calls `client.isFresh(30_000)` before reusing an existing client. If the socket has been silent past the threshold (typical after an iOS app freeze), the client is disposed, state is flipped to `DISCONNECTED`, and the push falls through to the full `_connect()` path so login runs with the new push's `voice_sdk_id`. This resolves the case where an answered call would hang on "connecting" until CallKit timed out, because `processVoIPNotification()` was being called on a socket that had silently died.
14
+ - Widened the reconnect-trigger state check in `handlePushNotification` to include `ERROR`, not only `DISCONNECTED`, so a push that arrives after a failed session re-establishes the connection instead of being dropped.
15
+
16
+ ### Dependencies
17
+
18
+ - Now requires `@telnyx/react-native-voice-sdk >= 0.4.3`, which adds the `isFresh()` / `connectionIdleMs` API and auto-reconnects on unexpected socket errors. See the [voice-sdk 0.4.3 changelog](../package/CHANGELOG.md#043-2026-04-27) for details.
19
+
3
20
  ## [0.4.0] (2026-04-19)
4
21
 
5
22
  ### Enhancement
@@ -202,6 +202,39 @@ class SessionManager {
202
202
  );
203
203
  // Store the push notification payload for when the client is created
204
204
  this._pendingPushPayload = payload;
205
+ // If we have a TelnyxRTC instance but its socket isn't fresh — either not
206
+ // connected at all, or connected but with no traffic for longer than the
207
+ // threshold — dispose it so we go through the full _connect() path below
208
+ // instead of calling processVoIPNotification on a stale client.
209
+ //
210
+ // This handles two cases iOS gives us with the same code path:
211
+ // 1. Terminated-then-cold-launched: a client exists (constructed at
212
+ // app boot) but its connection was never opened (idleMs=Infinity).
213
+ // 2. Suspended-then-thawed: a client exists with `connected=true`,
214
+ // but the kernel killed the TLS session during freeze without
215
+ // notifying the JS WebSocket wrapper, so no error event fired.
216
+ //
217
+ // 30s threshold matches the server's keep-alive ping cadence (~20s),
218
+ // so a live session always remains fresh.
219
+ const STALE_THRESHOLD_MS = 30000;
220
+ if (this._telnyxClient) {
221
+ const client = this._telnyxClient;
222
+ const isFresh =
223
+ typeof client.isFresh === 'function'
224
+ ? client.isFresh(STALE_THRESHOLD_MS)
225
+ : !!client.connected;
226
+ if (!isFresh) {
227
+ try {
228
+ await this._telnyxClient.disconnect();
229
+ } catch (err) {
230
+ console.warn('SessionManager: disconnect of stale client threw:', err);
231
+ }
232
+ this._telnyxClient = undefined;
233
+ if (this.currentState !== connection_state_1.TelnyxConnectionState.DISCONNECTED) {
234
+ this._connectionState.next(connection_state_1.TelnyxConnectionState.DISCONNECTED);
235
+ }
236
+ }
237
+ }
205
238
  // If we don't have a config yet but we're processing a push notification,
206
239
  // attempt to load stored config first (for terminated app startup)
207
240
  if (!this._currentConfig && !this._telnyxClient) {
@@ -269,11 +302,16 @@ class SessionManager {
269
302
  console.log(
270
303
  'SessionManager: RELEASE DEBUG - No client available, checking if we can trigger immediate connection'
271
304
  );
272
- // If we have config (either existing or newly loaded from storage) and are disconnected, trigger immediate connection
273
- // The _connect() method will process the pending push payload BEFORE calling connect()
305
+ // If we have config (either existing or newly loaded from storage) and
306
+ // are not currently connected/connecting, trigger immediate connection.
307
+ // We accept DISCONNECTED and ERROR (a socket failure bumps state to
308
+ // ERROR) so a push after a failed session still re-establishes the
309
+ // connection. The _connect() method will process the pending push
310
+ // payload BEFORE calling connect().
274
311
  if (
275
312
  this._currentConfig &&
276
- this.currentState === connection_state_1.TelnyxConnectionState.DISCONNECTED
313
+ (this.currentState === connection_state_1.TelnyxConnectionState.DISCONNECTED ||
314
+ this.currentState === connection_state_1.TelnyxConnectionState.ERROR)
277
315
  ) {
278
316
  console.log(
279
317
  'SessionManager: RELEASE DEBUG - Triggering immediate connection for push notification with config type:',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telnyx/react-voice-commons-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
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",
@@ -39,7 +39,7 @@
39
39
  "android": "expo run:android",
40
40
  "ios": "expo run:ios",
41
41
  "dev:local": "npm pkg set dependencies.@telnyx/react-native-voice-sdk=file:../package",
42
- "dev:published": "npm pkg set \"dependencies.@telnyx/react-native-voice-sdk\"=\">=0.4.2\"",
42
+ "dev:published": "node ./scripts/set-voice-sdk-floor.mjs",
43
43
  "prepublishOnly": "npm run dev:published && npm install --legacy-peer-deps",
44
44
  "postpublish": "npm run dev:local && npm install --legacy-peer-deps"
45
45
  },
@@ -86,7 +86,7 @@
86
86
  },
87
87
  "dependencies": {
88
88
  "@react-native-community/eslint-config": "^3.2.0",
89
- "@telnyx/react-native-voice-sdk": ">=0.4.2",
89
+ "@telnyx/react-native-voice-sdk": ">=0.4.4",
90
90
  "eventemitter3": "^5.0.1",
91
91
  "expo": "~53.0.22",
92
92
  "react-native-url-polyfill": "^3.0.0",
@@ -178,6 +178,40 @@ export class SessionManager {
178
178
  // Store the push notification payload for when the client is created
179
179
  (this as any)._pendingPushPayload = payload;
180
180
 
181
+ // If we have a TelnyxRTC instance but its socket isn't fresh — either not
182
+ // connected at all, or connected but with no traffic for longer than the
183
+ // threshold — dispose it so we go through the full _connect() path below
184
+ // instead of calling processVoIPNotification on a stale client.
185
+ //
186
+ // This handles two cases iOS gives us with the same code path:
187
+ // 1. Terminated-then-cold-launched: a client exists (constructed at
188
+ // app boot) but its connection was never opened (idleMs=Infinity).
189
+ // 2. Suspended-then-thawed: a client exists with `connected=true`,
190
+ // but the kernel killed the TLS session during freeze without
191
+ // notifying the JS WebSocket wrapper, so no error event fired.
192
+ //
193
+ // 30s threshold matches the server's keep-alive ping cadence (~20s),
194
+ // so a live session always remains fresh.
195
+ const STALE_THRESHOLD_MS = 30000;
196
+ if (this._telnyxClient) {
197
+ const client = this._telnyxClient as any;
198
+ const isFresh =
199
+ typeof client.isFresh === 'function'
200
+ ? client.isFresh(STALE_THRESHOLD_MS)
201
+ : !!client.connected;
202
+ if (!isFresh) {
203
+ try {
204
+ await this._telnyxClient.disconnect();
205
+ } catch (err) {
206
+ console.warn('SessionManager: disconnect of stale client threw:', err);
207
+ }
208
+ this._telnyxClient = undefined;
209
+ if (this.currentState !== TelnyxConnectionState.DISCONNECTED) {
210
+ this._connectionState.next(TelnyxConnectionState.DISCONNECTED);
211
+ }
212
+ }
213
+ }
214
+
181
215
  // If we don't have a config yet but we're processing a push notification,
182
216
  // attempt to load stored config first (for terminated app startup)
183
217
  if (!this._currentConfig && !this._telnyxClient) {
@@ -255,9 +289,17 @@ export class SessionManager {
255
289
  'SessionManager: RELEASE DEBUG - No client available, checking if we can trigger immediate connection'
256
290
  );
257
291
 
258
- // If we have config (either existing or newly loaded from storage) and are disconnected, trigger immediate connection
259
- // The _connect() method will process the pending push payload BEFORE calling connect()
260
- if (this._currentConfig && this.currentState === TelnyxConnectionState.DISCONNECTED) {
292
+ // If we have config (either existing or newly loaded from storage) and
293
+ // are not currently connected/connecting, trigger immediate connection.
294
+ // We accept DISCONNECTED and ERROR (a socket failure bumps state to
295
+ // ERROR) so a push after a failed session still re-establishes the
296
+ // connection. The _connect() method will process the pending push
297
+ // payload BEFORE calling connect().
298
+ if (
299
+ this._currentConfig &&
300
+ (this.currentState === TelnyxConnectionState.DISCONNECTED ||
301
+ this.currentState === TelnyxConnectionState.ERROR)
302
+ ) {
261
303
  console.log(
262
304
  'SessionManager: RELEASE DEBUG - Triggering immediate connection for push notification with config type:',
263
305
  (this._currentConfig as any).type || 'credential'