@telnyx/react-voice-commons-sdk 0.2.0 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # CHANGELOG.md
2
2
 
3
+ ## [0.3.0] (2026-04-15)
4
+
5
+ ### ⚠️ Breaking changes
6
+
7
+ - **`expo-router` is no longer a dependency of the SDK.** The SDK previously navigated the host app in a few places (`useAppStateHandler` on background disconnect, `CallKitHandler` after CallKit answer/end). Those calls have been removed — navigation is now exclusively the host app's responsibility.
8
+ - **Migration for Expo consumers:** subscribe to `voipClient.connectionState$` and `voipClient.activeCall$` in your app and navigate there. Example:
9
+ ```tsx
10
+ useEffect(() => {
11
+ const sub = voipClient.connectionState$.subscribe((state) => {
12
+ if (state === TelnyxConnectionState.DISCONNECTED) {
13
+ router.replace('/');
14
+ }
15
+ });
16
+ return () => sub.unsubscribe();
17
+ }, []);
18
+ ```
19
+ - `CallKitHandler` already exposed `onNavigateToDialer` / `onNavigateBack` callback props; those are now the only way to wire navigation.
20
+ - The `navigateToLoginOnDisconnect` option on `useAppStateHandler` is retained in the type signature for source compatibility but no longer has any effect.
21
+ - **Bare React Native (non-Expo) projects are now supported.** Metro bundling no longer fails on missing `expo-router`; SDK-level behavior is identical for Expo and bare RN consumers.
22
+
23
+ ### Enhancement
24
+
25
+ - **`react-native-url-polyfill` is now a direct dependency and auto-loaded** from the SDK entry point. Previously, apps running Hermes crashed on the second login attempt with `URLSearchParams.set is not implemented` because the SDK constructs the WebSocket URL via `URLSearchParams`, which is incomplete in Hermes. No action required from consumers.
26
+
3
27
  ## [0.2.0](https://github.com/team-telnyx/react-native-voice-commons/releases/tag/commons-sdk-v0.2.0) (2026-04-01)
4
28
 
5
29
  ### Enhancement
package/README.md CHANGED
@@ -11,6 +11,7 @@ A high-level, state-agnostic, drop-in module for the Telnyx React Native SDK tha
11
11
  - **Reactive State Management**: RxJS-based state streams for real-time UI updates
12
12
  - **TypeScript Support**: Full TypeScript definitions for better developer experience
13
13
  - **Cross-Platform**: Built for both iOS and Android with React Native
14
+ - **Framework-agnostic**: Works in both Expo and bare React Native projects. See the [bare RN reference demo](https://github.com/team-telnyx/telnyx-react-native-bare-demo) for non-Expo integration.
14
15
 
15
16
  ## About @telnyx/react-voice-commons-sdk
16
17
 
@@ -115,6 +116,29 @@ call.callState$.subscribe((state) => {
115
116
  });
116
117
  ```
117
118
 
119
+ ### Navigation
120
+
121
+ As of **v0.3.0**, the SDK no longer navigates the host app. Routing on state transitions (e.g. redirecting to a login screen on disconnect, surfacing a dialer screen after answering a call via CallKit) is entirely the host app's responsibility. Subscribe to `connectionState$` and `activeCall$` and invoke your own navigator.
122
+
123
+ Example using `expo-router`:
124
+
125
+ ```tsx
126
+ import { router } from 'expo-router';
127
+ import { useEffect } from 'react';
128
+ import { TelnyxConnectionState } from '@telnyx/react-voice-commons-sdk';
129
+
130
+ useEffect(() => {
131
+ const sub = voipClient.connectionState$.subscribe((state) => {
132
+ if (state === TelnyxConnectionState.DISCONNECTED) {
133
+ router.replace('/');
134
+ }
135
+ });
136
+ return () => sub.unsubscribe();
137
+ }, []);
138
+ ```
139
+
140
+ The same pattern works with `react-navigation`, React Router, or any other navigator — the SDK is agnostic.
141
+
118
142
  ### 4. Call Management
119
143
 
120
144
  ```tsx
@@ -8,7 +8,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
8
8
  exports.useAppStateHandler = void 0;
9
9
  const react_1 = require('react');
10
10
  const react_native_1 = require('react-native');
11
- const expo_router_1 = require('expo-router');
12
11
  const async_storage_1 = __importDefault(require('@react-native-async-storage/async-storage'));
13
12
  const connection_state_1 = require('../models/connection-state');
14
13
  const call_state_1 = require('../models/call-state');
@@ -70,9 +69,6 @@ const useAppStateHandler = ({
70
69
  if (!stillInProgress) {
71
70
  log('AppStateHandler: Push notification call completed, now disconnecting socket');
72
71
  await voipClient.logout();
73
- if (navigateToLoginOnDisconnect) {
74
- expo_router_1.router.replace('/');
75
- }
76
72
  }
77
73
  }, 5000); // Wait 5 seconds
78
74
  appState.current = nextAppState;
@@ -83,14 +79,6 @@ const useAppStateHandler = ({
83
79
  // Disconnect the socket with background reason
84
80
  await voipClient.logout();
85
81
  log('AppStateHandler: Socket disconnected successfully');
86
- // Navigate to login screen
87
- if (navigateToLoginOnDisconnect) {
88
- // Use a small delay to ensure the disconnect completes
89
- setTimeout(() => {
90
- log('AppStateHandler: Navigating to login screen');
91
- expo_router_1.router.replace('/');
92
- }, 100);
93
- }
94
82
  } catch (error) {
95
83
  console.error('AppStateHandler: Error during background disconnect:', error);
96
84
  }
package/lib/index.d.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  * call state transitions, push notification processing, and native call UI
9
9
  * integration.
10
10
  */
11
+ import 'react-native-url-polyfill/auto';
11
12
  export {
12
13
  TelnyxVoipClient,
13
14
  createTelnyxVoipClient,
package/lib/index.js CHANGED
@@ -62,6 +62,7 @@ exports.useAppReadyNotifier =
62
62
  exports.createTelnyxVoipClient =
63
63
  exports.TelnyxVoipClient =
64
64
  void 0;
65
+ require('react-native-url-polyfill/auto');
65
66
  // Main client
66
67
  var telnyx_voip_client_1 = require('./telnyx-voip-client');
67
68
  Object.defineProperty(exports, 'TelnyxVoipClient', {
@@ -8,7 +8,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
8
8
  exports.CallKitHandler = void 0;
9
9
  const react_1 = require('react');
10
10
  const react_native_1 = require('react-native');
11
- const expo_router_1 = require('expo-router');
12
11
  const async_storage_1 = __importDefault(require('@react-native-async-storage/async-storage'));
13
12
  const TelnyxVoiceContext_1 = require('../context/TelnyxVoiceContext');
14
13
  // Global flag to ensure only one CallKitHandler is active
@@ -20,7 +19,6 @@ let isCallKitHandlerActive = false;
20
19
  * @internal - Users should not use this component directly
21
20
  */
22
21
  const CallKitHandler = ({ onLoginRequired, onNavigateToDialer, onNavigateBack }) => {
23
- const router = (0, expo_router_1.useRouter)();
24
22
  const { voipClient } = (0, TelnyxVoiceContext_1.useTelnyxVoice)();
25
23
  // Store active calls by CallKit UUID for coordination
26
24
  const activeCallsRef = (0, react_1.useRef)(new Map());
@@ -90,11 +88,8 @@ const CallKitHandler = ({ onLoginRequired, onNavigateToDialer, onNavigateBack })
90
88
  callUUID: eventData.callUUID,
91
89
  isTrackedCall: activeCallsRef.current.has(eventData.callUUID),
92
90
  });
93
- // Navigate to dialer after answering
94
91
  if (onNavigateToDialer) {
95
92
  onNavigateToDialer();
96
- } else {
97
- router.replace('/dialer');
98
93
  }
99
94
  };
100
95
  const handleEndCall = async (eventData) => {
@@ -105,11 +100,8 @@ const CallKitHandler = ({ onLoginRequired, onNavigateToDialer, onNavigateBack })
105
100
  // Clean up our local tracking info
106
101
  activeCallsRef.current.delete(eventData.callUUID);
107
102
  await async_storage_1.default.removeItem('@push_notification_payload');
108
- // Navigate back after call ends
109
103
  if (onNavigateBack) {
110
104
  onNavigateBack();
111
- } else {
112
- router.replace('/dialer');
113
105
  }
114
106
  };
115
107
  // This component doesn't render anything, it just handles events
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telnyx/react-voice-commons-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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",
@@ -66,7 +66,6 @@
66
66
  },
67
67
  "peerDependencies": {
68
68
  "@react-native-async-storage/async-storage": "^2.1.0",
69
- "expo-router": "^5.1.0",
70
69
  "react": ">=19.0.0 <20.0.0",
71
70
  "react-native": ">=0.79.0 <1.0.0",
72
71
  "react-native-webrtc": "^124.0.5"
@@ -75,9 +74,6 @@
75
74
  "@react-native-async-storage/async-storage": {
76
75
  "optional": false
77
76
  },
78
- "expo-router": {
79
- "optional": false
80
- },
81
77
  "react": {
82
78
  "optional": false
83
79
  },
@@ -93,6 +89,7 @@
93
89
  "@telnyx/react-native-voice-sdk": ">=0.4.1",
94
90
  "eventemitter3": "^5.0.1",
95
91
  "expo": "~53.0.22",
92
+ "react-native-url-polyfill": "^3.0.0",
96
93
  "react-native-voip-push-notification": "^3.3.3",
97
94
  "rxjs": "^7.8.2"
98
95
  },
@@ -104,7 +101,6 @@
104
101
  "@typescript-eslint/eslint-plugin": "^6.0.0",
105
102
  "@typescript-eslint/parser": "^6.0.0",
106
103
  "eslint": "^8.0.0",
107
- "expo-router": "^5.1.0",
108
104
  "jest": "^29.5.0",
109
105
  "prettier": "^3.0.0",
110
106
  "ts-jest": "^29.1.0",
@@ -2,7 +2,6 @@ import { Platform, AppState } from 'react-native';
2
2
  import CallKit, { CallEndReason } from './callkit';
3
3
  import { Call } from '@telnyx/react-native-voice-sdk';
4
4
  import { VoicePnBridge } from '../internal/voice-pn-bridge';
5
- import { router } from 'expo-router';
6
5
  import { TelnyxVoipClient } from '../telnyx-voip-client';
7
6
  import { TelnyxConnectionState } from '../models/connection-state';
8
7
  import { act } from 'react';
@@ -1,6 +1,5 @@
1
1
  import { useEffect, useRef } from 'react';
2
2
  import { AppState, AppStateStatus } from 'react-native';
3
- import { router } from 'expo-router';
4
3
  import AsyncStorage from '@react-native-async-storage/async-storage';
5
4
  import { TelnyxVoipClient } from '../telnyx-voip-client';
6
5
  import { TelnyxConnectionState } from '../models/connection-state';
@@ -76,9 +75,6 @@ export const useAppStateHandler = ({
76
75
  if (!stillInProgress) {
77
76
  log('AppStateHandler: Push notification call completed, now disconnecting socket');
78
77
  await voipClient.logout();
79
- if (navigateToLoginOnDisconnect) {
80
- router.replace('/');
81
- }
82
78
  }
83
79
  }, 5000); // Wait 5 seconds
84
80
  appState.current = nextAppState;
@@ -92,15 +88,6 @@ export const useAppStateHandler = ({
92
88
  await voipClient.logout();
93
89
 
94
90
  log('AppStateHandler: Socket disconnected successfully');
95
-
96
- // Navigate to login screen
97
- if (navigateToLoginOnDisconnect) {
98
- // Use a small delay to ensure the disconnect completes
99
- setTimeout(() => {
100
- log('AppStateHandler: Navigating to login screen');
101
- router.replace('/');
102
- }, 100);
103
- }
104
91
  } catch (error) {
105
92
  console.error('AppStateHandler: Error during background disconnect:', error);
106
93
  }
package/src/index.ts CHANGED
@@ -9,6 +9,8 @@
9
9
  * integration.
10
10
  */
11
11
 
12
+ import 'react-native-url-polyfill/auto';
13
+
12
14
  // Main client
13
15
  export {
14
16
  TelnyxVoipClient,
@@ -1,6 +1,5 @@
1
1
  import React, { useEffect, useRef } from 'react';
2
2
  import { Platform, DeviceEventEmitter } from 'react-native';
3
- import { useRouter } from 'expo-router';
4
3
  import AsyncStorage from '@react-native-async-storage/async-storage';
5
4
  import { useTelnyxVoice } from '../context/TelnyxVoiceContext';
6
5
  import { callKitCoordinator } from '../callkit';
@@ -36,7 +35,6 @@ export const CallKitHandler: React.FC<CallKitHandlerProps> = ({
36
35
  onNavigateToDialer,
37
36
  onNavigateBack,
38
37
  }) => {
39
- const router = useRouter();
40
38
  const { voipClient } = useTelnyxVoice();
41
39
 
42
40
  // Store active calls by CallKit UUID for coordination
@@ -118,11 +116,8 @@ export const CallKitHandler: React.FC<CallKitHandlerProps> = ({
118
116
  isTrackedCall: activeCallsRef.current.has(eventData.callUUID),
119
117
  });
120
118
 
121
- // Navigate to dialer after answering
122
119
  if (onNavigateToDialer) {
123
120
  onNavigateToDialer();
124
- } else {
125
- router.replace('/dialer');
126
121
  }
127
122
  };
128
123
 
@@ -136,11 +131,8 @@ export const CallKitHandler: React.FC<CallKitHandlerProps> = ({
136
131
  activeCallsRef.current.delete(eventData.callUUID);
137
132
  await AsyncStorage.removeItem('@push_notification_payload');
138
133
 
139
- // Navigate back after call ends
140
134
  if (onNavigateBack) {
141
135
  onNavigateBack();
142
- } else {
143
- router.replace('/dialer');
144
136
  }
145
137
  };
146
138