@telnyx/react-voice-commons-sdk 0.4.2 → 1.0.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.
@@ -17,6 +17,7 @@ export class CallStateController {
17
17
  private readonly _calls = new BehaviorSubject<Call[]>([]);
18
18
  private readonly _callMap = new Map<string, Call>();
19
19
  private _disposed = false;
20
+ private _listenerClient?: TelnyxRTC;
20
21
 
21
22
  // Callbacks for waiting for invite logic (used for push notifications)
22
23
  private _isWaitingForInvite?: () => boolean;
@@ -155,6 +156,9 @@ export class CallStateController {
155
156
  callerIdName: callerName,
156
157
  callerIdNumber: callerNumber,
157
158
  customHeaders,
159
+ peerConnectionOptions: {
160
+ useTrickleIce: this._sessionManager.useTrickleIce,
161
+ },
158
162
  };
159
163
  const telnyxCall = await this._sessionManager.telnyxClient.newCall(callOptions);
160
164
 
@@ -225,6 +229,7 @@ export class CallStateController {
225
229
  }
226
230
 
227
231
  this._disposed = true;
232
+ this._removeClientListeners();
228
233
 
229
234
  // Dispose of all calls
230
235
  this.currentCalls.forEach((call) => call.dispose());
@@ -241,40 +246,28 @@ export class CallStateController {
241
246
  private _setupClientListeners(): void {
242
247
  console.log('CallStateController: Setting up client listeners...');
243
248
 
244
- if (!this._sessionManager.telnyxClient) {
249
+ const telnyxClient = this._sessionManager.telnyxClient;
250
+
251
+ if (!telnyxClient) {
245
252
  console.log('CallStateController: No telnyxClient available yet, skipping listener setup');
246
253
  return;
247
254
  }
248
255
 
256
+ this._removeClientListeners();
257
+ this._listenerClient = telnyxClient;
258
+
249
259
  console.log('CallStateController: TelnyxClient found, setting up incoming call listener');
250
- console.log(
251
- 'CallStateController: Client instance:',
252
- this._sessionManager.telnyxClient.constructor.name
253
- );
260
+ console.log('CallStateController: Client instance:', telnyxClient.constructor.name);
254
261
 
255
262
  // Listen for incoming calls
256
- this._sessionManager.telnyxClient.on(
257
- 'telnyx.call.incoming',
258
- (telnyxCall: TelnyxCall, msg: any) => {
259
- console.log('CallStateController: Incoming call received:', telnyxCall.callId);
260
- this._handleIncomingCall(telnyxCall, msg, false);
261
- }
262
- );
263
+ telnyxClient.on('telnyx.call.incoming', this._handleTelnyxIncomingCall);
263
264
 
264
265
  // Listen for reattached calls (after network reconnection)
265
- this._sessionManager.telnyxClient.on(
266
- 'telnyx.call.reattached',
267
- (telnyxCall: TelnyxCall, msg: any) => {
268
- console.log('CallStateController: Reattached call received:', telnyxCall.callId);
269
- this._handleIncomingCall(telnyxCall, msg, true);
270
- }
271
- );
266
+ telnyxClient.on('telnyx.call.reattached', this._handleTelnyxReattachedCall);
272
267
 
273
268
  // Verify listeners are set up
274
- const incomingListeners =
275
- this._sessionManager.telnyxClient.listenerCount('telnyx.call.incoming');
276
- const reattachedListeners =
277
- this._sessionManager.telnyxClient.listenerCount('telnyx.call.reattached');
269
+ const incomingListeners = telnyxClient.listenerCount('telnyx.call.incoming');
270
+ const reattachedListeners = telnyxClient.listenerCount('telnyx.call.reattached');
278
271
  console.log(
279
272
  'CallStateController: Listeners registered - incoming:',
280
273
  incomingListeners,
@@ -283,34 +276,57 @@ export class CallStateController {
283
276
  );
284
277
 
285
278
  // Listen for call state changes from the TelnyxRTC client (multi-call support)
286
- this._sessionManager.telnyxClient.on(
287
- 'telnyx.call.stateChanged',
288
- (telnyxCall: TelnyxCall, state: string) => {
289
- console.log(
290
- 'CallStateController: Call state changed from TelnyxRTC:',
291
- telnyxCall.callId,
292
- state
293
- );
294
- // Find our wrapper call and update if needed
295
- const call = this.findCallByTelnyxCall(telnyxCall);
296
- if (call) {
297
- console.log(
298
- 'CallStateController: Found wrapper call, state sync handled by Call subscription'
299
- );
300
- }
301
- }
302
- );
279
+ telnyxClient.on('telnyx.call.stateChanged', this._handleTelnyxCallStateChanged);
303
280
 
304
281
  // Listen for call removal events from TelnyxRTC (multi-call support)
305
- this._sessionManager.telnyxClient.on('telnyx.call.removed', (callId: string) => {
306
- console.log('CallStateController: Call removed from TelnyxRTC:', callId);
307
- // The call cleanup is already handled by our call state subscription
308
- // This event is informational for logging/debugging
309
- });
282
+ telnyxClient.on('telnyx.call.removed', this._handleTelnyxCallRemoved);
310
283
 
311
284
  console.log('CallStateController: Client listeners set up successfully');
312
285
  }
313
286
 
287
+ private _removeClientListeners(): void {
288
+ if (!this._listenerClient) {
289
+ return;
290
+ }
291
+
292
+ this._listenerClient.off('telnyx.call.incoming', this._handleTelnyxIncomingCall);
293
+ this._listenerClient.off('telnyx.call.reattached', this._handleTelnyxReattachedCall);
294
+ this._listenerClient.off('telnyx.call.stateChanged', this._handleTelnyxCallStateChanged);
295
+ this._listenerClient.off('telnyx.call.removed', this._handleTelnyxCallRemoved);
296
+ this._listenerClient = undefined;
297
+ }
298
+
299
+ private _handleTelnyxIncomingCall = (telnyxCall: TelnyxCall, msg: any): void => {
300
+ console.log('CallStateController: Incoming call received:', telnyxCall.callId);
301
+ this._handleIncomingCall(telnyxCall, msg, false);
302
+ };
303
+
304
+ private _handleTelnyxReattachedCall = (telnyxCall: TelnyxCall, msg: any): void => {
305
+ console.log('CallStateController: Reattached call received:', telnyxCall.callId);
306
+ this._handleIncomingCall(telnyxCall, msg, true);
307
+ };
308
+
309
+ private _handleTelnyxCallStateChanged = (telnyxCall: TelnyxCall, state: string): void => {
310
+ console.log(
311
+ 'CallStateController: Call state changed from TelnyxRTC:',
312
+ telnyxCall.callId,
313
+ state
314
+ );
315
+ // Find our wrapper call and update if needed
316
+ const call = this.findCallByTelnyxCall(telnyxCall);
317
+ if (call) {
318
+ console.log(
319
+ 'CallStateController: Found wrapper call, state sync handled by Call subscription'
320
+ );
321
+ }
322
+ };
323
+
324
+ private _handleTelnyxCallRemoved = (callId: string): void => {
325
+ console.log('CallStateController: Call removed from TelnyxRTC:', callId);
326
+ // The call cleanup is already handled by our call state subscription.
327
+ // This event is informational for logging/debugging.
328
+ };
329
+
314
330
  /**
315
331
  * Handle incoming call or reattached call
316
332
  */
@@ -11,6 +11,9 @@ import {
11
11
  isTokenConfig,
12
12
  } from '../../models/config';
13
13
 
14
+ const USE_TRICKLE_ICE_STORAGE_KEY = '@use_trickle_ice';
15
+ const MISSED_CALL_NOTIFICATIONS_STORAGE_KEY = '@enable_missed_call_notifications';
16
+
14
17
  /**
15
18
  * Manages the connection lifecycle to the Telnyx platform.
16
19
  *
@@ -75,6 +78,10 @@ export class SessionManager {
75
78
  return this._telnyxClient;
76
79
  }
77
80
 
81
+ get useTrickleIce(): boolean {
82
+ return Boolean(this._currentConfig?.useTrickleIce);
83
+ }
84
+
78
85
  /**
79
86
  * Connect using credential authentication
80
87
  */
@@ -178,39 +185,22 @@ export class SessionManager {
178
185
  // Store the push notification payload for when the client is created
179
186
  (this as any)._pendingPushPayload = payload;
180
187
 
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;
188
+ // Each push always rebuilds the TelnyxRTC client. The voice_sdk_id is
189
+ // baked into the WebSocket URL at socket-open time and can't be mutated
190
+ // mid-flight; reusing a connection from a previous push means the
191
+ // gateway routes THIS push's INVITE to a different (correctly-stamped)
192
+ // client and we sit on the wrong socket forever.
196
193
  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
- }
194
+ try {
195
+ await this._telnyxClient.disconnect();
196
+ } catch (err) {
197
+ console.warn('SessionManager: disconnect of prior client threw:', err);
212
198
  }
213
199
  }
200
+ this._telnyxClient = undefined;
201
+ if (this.currentState !== TelnyxConnectionState.DISCONNECTED) {
202
+ this._connectionState.next(TelnyxConnectionState.DISCONNECTED);
203
+ }
214
204
 
215
205
  // If we don't have a config yet but we're processing a push notification,
216
206
  // attempt to load stored config first (for terminated app startup)
@@ -227,6 +217,12 @@ export class SessionManager {
227
217
  const storedPassword = await AsyncStorage.getItem('@telnyx_password');
228
218
  const storedCredentialToken = await AsyncStorage.getItem('@credential_token');
229
219
  const storedPushToken = await AsyncStorage.getItem('@push_token');
220
+ const storedUseTrickleIce = await AsyncStorage.getItem(USE_TRICKLE_ICE_STORAGE_KEY);
221
+ const storedMissedCallNotifications = await AsyncStorage.getItem(
222
+ MISSED_CALL_NOTIFICATIONS_STORAGE_KEY
223
+ );
224
+ const useTrickleIce = storedUseTrickleIce === 'true';
225
+ const enableMissedCallNotifications = storedMissedCallNotifications === 'true';
230
226
 
231
227
  // Check if we have credential-based authentication data
232
228
  if (storedUsername && storedPassword) {
@@ -234,6 +230,8 @@ export class SessionManager {
234
230
  const { createCredentialConfig } = require('../../models/config');
235
231
  this._currentConfig = createCredentialConfig(storedUsername, storedPassword, {
236
232
  pushNotificationDeviceToken: storedPushToken,
233
+ useTrickleIce,
234
+ enableMissedCallNotifications,
237
235
  });
238
236
  }
239
237
  // Check if we have token-based authentication data
@@ -242,6 +240,8 @@ export class SessionManager {
242
240
  const { createTokenConfig } = require('../../models/config');
243
241
  this._currentConfig = createTokenConfig(storedCredentialToken, {
244
242
  pushNotificationDeviceToken: storedPushToken,
243
+ useTrickleIce,
244
+ enableMissedCallNotifications,
245
245
  });
246
246
  }
247
247
 
@@ -370,6 +370,8 @@ export class SessionManager {
370
370
  logLevel: this._currentConfig.debug ? 'debug' : 'warn',
371
371
  debug: this._currentConfig.debug ?? false,
372
372
  pushNotificationDeviceToken: this._currentConfig.pushNotificationDeviceToken,
373
+ enableMissedCallNotifications: this._currentConfig.enableMissedCallNotifications ?? false,
374
+ useTrickleIce: this._currentConfig.useTrickleIce,
373
375
  enableCallReports: this._currentConfig.enableCallReports,
374
376
  callReportInterval: this._currentConfig.callReportInterval,
375
377
  callReportLogLevel: this._currentConfig.callReportLogLevel,
@@ -388,6 +390,8 @@ export class SessionManager {
388
390
  logLevel: this._currentConfig.debug ? 'debug' : 'warn',
389
391
  debug: this._currentConfig.debug ?? false,
390
392
  pushNotificationDeviceToken: this._currentConfig.pushNotificationDeviceToken,
393
+ enableMissedCallNotifications: this._currentConfig.enableMissedCallNotifications ?? false,
394
+ useTrickleIce: this._currentConfig.useTrickleIce,
391
395
  enableCallReports: this._currentConfig.enableCallReports,
392
396
  callReportInterval: this._currentConfig.callReportInterval,
393
397
  callReportLogLevel: this._currentConfig.callReportLogLevel,
@@ -38,6 +38,10 @@ export interface VoicePnBridgeInterface {
38
38
  clearPendingVoipPush(): Promise<boolean>;
39
39
  getPendingVoipAction(): Promise<string | null>;
40
40
  clearPendingVoipAction(): Promise<boolean>;
41
+ setMissedCallNotificationsEnabled(enabled: boolean): Promise<boolean>;
42
+ getMissedCallNotificationsEnabled(): Promise<boolean>;
43
+ setSpeakerEnabled(enabled: boolean): Promise<boolean>;
44
+ isSpeakerEnabled(): Promise<boolean>;
41
45
  }
42
46
 
43
47
  const NativeBridge: VoicePnBridgeInterface = NativeModules.VoicePnBridge;
@@ -244,6 +248,66 @@ export class VoicePnBridge {
244
248
  }
245
249
  }
246
250
 
251
+ /**
252
+ * Enable or disable local missed call notifications on iOS.
253
+ * Missed-call VoIP pushes are still handled through CallKit either way.
254
+ */
255
+ static async setMissedCallNotificationsEnabled(enabled: boolean): Promise<boolean> {
256
+ try {
257
+ return await NativeBridge.setMissedCallNotificationsEnabled(enabled);
258
+ } catch (error) {
259
+ console.error('VoicePnBridge: Error setting missed call notifications:', error);
260
+ return false;
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Returns true by default, matching the sample-app behavior for missed calls.
266
+ */
267
+ static async getMissedCallNotificationsEnabled(): Promise<boolean> {
268
+ try {
269
+ return await NativeBridge.getMissedCallNotificationsEnabled();
270
+ } catch (error) {
271
+ console.error('VoicePnBridge: Error getting missed call notifications setting:', error);
272
+ return true;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Route call audio through the loudspeaker when enabled, or back to the
278
+ * platform default voice route when disabled.
279
+ */
280
+ static async setSpeakerEnabled(enabled: boolean): Promise<boolean> {
281
+ try {
282
+ return await NativeBridge.setSpeakerEnabled(enabled);
283
+ } catch (error) {
284
+ console.error('VoicePnBridge: Error setting speaker route:', error);
285
+ throw error;
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Returns whether the current audio route is using the loudspeaker.
291
+ */
292
+ static async isSpeakerEnabled(): Promise<boolean> {
293
+ try {
294
+ return await NativeBridge.isSpeakerEnabled();
295
+ } catch (error) {
296
+ console.error('VoicePnBridge: Error getting speaker route:', error);
297
+ throw error;
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Toggle between loudspeaker and the platform default voice route.
303
+ */
304
+ static async toggleSpeaker(): Promise<boolean> {
305
+ // This is intentionally read-then-write because route changes can also come from
306
+ // the OS, Bluetooth, wired accessories, or the active call session.
307
+ const enabled = await VoicePnBridge.isSpeakerEnabled();
308
+ return VoicePnBridge.setSpeakerEnabled(!enabled);
309
+ }
310
+
247
311
  /**
248
312
  * Android → React Native: Listen for immediate call action events from notification buttons
249
313
  * Use this for active calls where immediate response is needed (e.g., ending ongoing calls)
@@ -7,6 +7,10 @@ export interface CredentialConfig {
7
7
  sipPassword: string;
8
8
  debug?: boolean;
9
9
  pushNotificationDeviceToken?: string;
10
+ /** Enable native missed call push notifications. Default: false */
11
+ enableMissedCallNotifications?: boolean;
12
+ /** Enable Trickle ICE. Default: false */
13
+ useTrickleIce?: boolean;
10
14
  /** Enable automatic call quality reporting. Default: true */
11
15
  enableCallReports?: boolean;
12
16
  /** Stats collection interval in seconds. Default: 5 */
@@ -25,6 +29,10 @@ export interface TokenConfig {
25
29
  token: string;
26
30
  debug?: boolean;
27
31
  pushNotificationDeviceToken?: string;
32
+ /** Enable native missed call push notifications. Default: false */
33
+ enableMissedCallNotifications?: boolean;
34
+ /** Enable Trickle ICE. Default: false */
35
+ useTrickleIce?: boolean;
28
36
  /** Enable automatic call quality reporting. Default: true */
29
37
  enableCallReports?: boolean;
30
38
  /** Stats collection interval in seconds. Default: 5 */
@@ -7,6 +7,9 @@ import { SessionManager } from './internal/session/session-manager';
7
7
  import { CallStateController } from './internal/calls/call-state-controller';
8
8
  import { VoicePnBridge } from './internal/voice-pn-bridge';
9
9
 
10
+ const USE_TRICKLE_ICE_STORAGE_KEY = '@use_trickle_ice';
11
+ const MISSED_CALL_NOTIFICATIONS_STORAGE_KEY = '@enable_missed_call_notifications';
12
+
10
13
  /**
11
14
  * Configuration options for TelnyxVoipClient
12
15
  */
@@ -16,6 +19,9 @@ export interface TelnyxVoipClientOptions {
16
19
 
17
20
  /** Enable debug logging */
18
21
  debug?: boolean;
22
+
23
+ /** Enable Trickle ICE for calls created by this client */
24
+ useTrickleIce?: boolean;
19
25
  }
20
26
 
21
27
  /**
@@ -67,6 +73,7 @@ export class TelnyxVoipClient {
67
73
  this._options = {
68
74
  enableAppStateManagement: true,
69
75
  debug: false,
76
+ useTrickleIce: false,
70
77
  ...options,
71
78
  };
72
79
 
@@ -218,10 +225,15 @@ export class TelnyxVoipClient {
218
225
  console.log('TelnyxVoipClient: Logging in with credentials for user:', config.sipUser);
219
226
  }
220
227
 
228
+ const loginConfig = {
229
+ ...config,
230
+ useTrickleIce: config.useTrickleIce ?? this._options.useTrickleIce,
231
+ };
232
+
221
233
  // Store credentials for future reconnection
222
- await this._storeCredentials(config);
234
+ await this._storeCredentials(loginConfig);
223
235
 
224
- await this._sessionManager.connectWithCredential(config);
236
+ await this._sessionManager.connectWithCredential(loginConfig);
225
237
  }
226
238
 
227
239
  /**
@@ -245,10 +257,15 @@ export class TelnyxVoipClient {
245
257
  console.log('TelnyxVoipClient: Logging in with token');
246
258
  }
247
259
 
260
+ const loginConfig = {
261
+ ...config,
262
+ useTrickleIce: config.useTrickleIce ?? this._options.useTrickleIce,
263
+ };
264
+
248
265
  // Store token for future reconnection
249
- await this._storeToken(config);
266
+ await this._storeToken(loginConfig);
250
267
 
251
- await this._sessionManager.connectWithToken(config);
268
+ await this._sessionManager.connectWithToken(loginConfig);
252
269
  }
253
270
 
254
271
  /**
@@ -292,6 +309,13 @@ export class TelnyxVoipClient {
292
309
  const storedPassword = await AsyncStorage.getItem('@telnyx_password');
293
310
  const storedCredentialToken = await AsyncStorage.getItem('@credential_token');
294
311
  const storedPushToken = await AsyncStorage.getItem('@push_token');
312
+ const storedUseTrickleIce = await AsyncStorage.getItem(USE_TRICKLE_ICE_STORAGE_KEY);
313
+ const storedMissedCallNotifications = await AsyncStorage.getItem(
314
+ MISSED_CALL_NOTIFICATIONS_STORAGE_KEY
315
+ );
316
+ const useTrickleIce =
317
+ storedUseTrickleIce === null ? this._options.useTrickleIce : storedUseTrickleIce === 'true';
318
+ const enableMissedCallNotifications = storedMissedCallNotifications === 'true';
295
319
 
296
320
  // Check if we have credential-based authentication data
297
321
  if (storedUsername && storedPassword) {
@@ -299,6 +323,8 @@ export class TelnyxVoipClient {
299
323
  const { createCredentialConfig } = require('./models/config');
300
324
  const config = createCredentialConfig(storedUsername, storedPassword, {
301
325
  pushNotificationDeviceToken: storedPushToken,
326
+ useTrickleIce,
327
+ enableMissedCallNotifications,
302
328
  });
303
329
 
304
330
  if (this._options.debug) {
@@ -318,6 +344,8 @@ export class TelnyxVoipClient {
318
344
  const { createTokenConfig } = require('./models/config');
319
345
  const config = createTokenConfig(storedCredentialToken, {
320
346
  pushNotificationDeviceToken: storedPushToken,
347
+ useTrickleIce,
348
+ enableMissedCallNotifications,
321
349
  });
322
350
 
323
351
  if (this._options.debug) {
@@ -401,22 +429,14 @@ export class TelnyxVoipClient {
401
429
  }
402
430
 
403
431
  try {
404
- // First, pass the push notification to the session manager for processing
405
- // This will set the isCallFromPush flag on the TelnyxRTC client
432
+ // SessionManager owns the entire push lifecycle: it disposes any prior
433
+ // client, loads stored credentials if needed, and rebuilds the
434
+ // TelnyxRTC bound to THIS push's voice_sdk_id. We deliberately do not
435
+ // fall back to a generic loginFromStoredConfig() here — that would
436
+ // create a parallel client with no voice_sdk_id awareness, which the
437
+ // gateway then has to `punt` once the SessionManager-driven session
438
+ // also registers, dropping the active call.
406
439
  await this._sessionManager.handlePushNotification(payload);
407
-
408
- // Connect if not already connected
409
- const currentState = this.currentConnectionState;
410
- if (currentState !== TelnyxConnectionState.CONNECTED) {
411
- // Try to login from stored config - now the push flags should be set
412
- const loginSuccess = await this.loginFromStoredConfig();
413
- if (!loginSuccess) {
414
- console.warn(
415
- 'TelnyxVoipClient: Could not login from stored config for push notification'
416
- );
417
- return;
418
- }
419
- }
420
440
  } catch (error) {
421
441
  console.error('TelnyxVoipClient: Error handling push notification:', error);
422
442
  throw error;
@@ -536,6 +556,14 @@ export class TelnyxVoipClient {
536
556
 
537
557
  await AsyncStorage.setItem('@telnyx_username', config.sipUser);
538
558
  await AsyncStorage.setItem('@telnyx_password', config.sipPassword);
559
+ await AsyncStorage.setItem(
560
+ USE_TRICKLE_ICE_STORAGE_KEY,
561
+ String(config.useTrickleIce ?? false)
562
+ );
563
+ await AsyncStorage.setItem(
564
+ MISSED_CALL_NOTIFICATIONS_STORAGE_KEY,
565
+ String(config.enableMissedCallNotifications ?? false)
566
+ );
539
567
 
540
568
  if (config.pushNotificationDeviceToken) {
541
569
  await AsyncStorage.setItem('@push_token', config.pushNotificationDeviceToken);
@@ -563,6 +591,14 @@ export class TelnyxVoipClient {
563
591
  const AsyncStorage = require('@react-native-async-storage/async-storage').default;
564
592
 
565
593
  await AsyncStorage.setItem('@credential_token', config.token);
594
+ await AsyncStorage.setItem(
595
+ USE_TRICKLE_ICE_STORAGE_KEY,
596
+ String(config.useTrickleIce ?? false)
597
+ );
598
+ await AsyncStorage.setItem(
599
+ MISSED_CALL_NOTIFICATIONS_STORAGE_KEY,
600
+ String(config.enableMissedCallNotifications ?? false)
601
+ );
566
602
 
567
603
  if (config.pushNotificationDeviceToken) {
568
604
  await AsyncStorage.setItem('@push_token', config.pushNotificationDeviceToken);
@@ -14,6 +14,14 @@ declare module '@telnyx/react-native-voice-sdk' {
14
14
  destinationNumber?: string;
15
15
  audio?: boolean;
16
16
  video?: boolean;
17
+ peerConnectionOptions?: {
18
+ prefetchIceCandidates?: boolean;
19
+ useTrickleIce?: boolean;
20
+ iceServers?: any[];
21
+ iceTransportPolicy?: string;
22
+ bundlePolicy?: string;
23
+ rtcpMuxPolicy?: string;
24
+ };
17
25
  }
18
26
 
19
27
  export interface ClientOptions {
@@ -26,6 +34,8 @@ declare module '@telnyx/react-native-voice-sdk' {
26
34
  debug?: boolean;
27
35
  logLevel?: string;
28
36
  pushNotificationDeviceToken?: string;
37
+ enableMissedCallNotifications?: boolean;
38
+ useTrickleIce?: boolean;
29
39
  enableCallReports?: boolean;
30
40
  callReportInterval?: number;
31
41
  callReportLogLevel?: string;