@telnyx/react-voice-commons-sdk 0.1.2 → 0.1.4

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +469 -483
  3. package/ios/CallKitBridge.swift +2 -7
  4. package/lib/callkit/callkit-coordinator.d.ts +110 -117
  5. package/lib/callkit/callkit-coordinator.js +664 -727
  6. package/lib/callkit/callkit.d.ts +41 -41
  7. package/lib/callkit/callkit.js +252 -242
  8. package/lib/callkit/index.js +15 -47
  9. package/lib/callkit/use-callkit.d.ts +19 -19
  10. package/lib/callkit/use-callkit.js +270 -310
  11. package/lib/context/TelnyxVoiceContext.d.ts +9 -9
  12. package/lib/context/TelnyxVoiceContext.js +10 -13
  13. package/lib/hooks/use-callkit-coordinator.d.ts +9 -17
  14. package/lib/hooks/use-callkit-coordinator.js +45 -50
  15. package/lib/hooks/useAppReadyNotifier.js +13 -15
  16. package/lib/hooks/useAppStateHandler.d.ts +6 -11
  17. package/lib/hooks/useAppStateHandler.js +95 -110
  18. package/lib/hooks/useNetworkStateHandler.d.ts +0 -0
  19. package/lib/hooks/useNetworkStateHandler.js +0 -0
  20. package/lib/index.d.ts +3 -21
  21. package/lib/index.js +50 -201
  22. package/lib/internal/CallKitHandler.d.ts +6 -6
  23. package/lib/internal/CallKitHandler.js +96 -104
  24. package/lib/internal/callkit-manager.d.ts +57 -57
  25. package/lib/internal/callkit-manager.js +299 -316
  26. package/lib/internal/calls/call-state-controller.d.ts +73 -86
  27. package/lib/internal/calls/call-state-controller.js +263 -307
  28. package/lib/internal/session/session-manager.d.ts +71 -75
  29. package/lib/internal/session/session-manager.js +360 -424
  30. package/lib/internal/user-defaults-helpers.js +49 -39
  31. package/lib/internal/voice-pn-bridge.d.ts +114 -12
  32. package/lib/internal/voice-pn-bridge.js +212 -5
  33. package/lib/models/call-state.d.ts +46 -44
  34. package/lib/models/call-state.js +70 -68
  35. package/lib/models/call.d.ts +161 -133
  36. package/lib/models/call.js +454 -382
  37. package/lib/models/config.d.ts +11 -18
  38. package/lib/models/config.js +37 -35
  39. package/lib/models/connection-state.d.ts +10 -10
  40. package/lib/models/connection-state.js +16 -16
  41. package/lib/telnyx-voice-app.d.ts +28 -28
  42. package/lib/telnyx-voice-app.js +513 -480
  43. package/lib/telnyx-voip-client.d.ts +167 -167
  44. package/lib/telnyx-voip-client.js +385 -390
  45. package/package.json +115 -104
  46. package/src/callkit/callkit-coordinator.ts +830 -846
  47. package/src/hooks/useNetworkStateHandler.ts +0 -0
  48. package/src/internal/calls/call-state-controller.ts +407 -384
  49. package/src/internal/session/session-manager.ts +483 -467
  50. package/src/internal/voice-pn-bridge.ts +266 -18
  51. package/src/models/call-state.ts +105 -98
  52. package/src/models/call.ts +502 -388
  53. package/src/telnyx-voice-app.tsx +788 -690
  54. package/src/telnyx-voip-client.ts +551 -539
  55. package/src/types/telnyx-sdk.d.ts +93 -79
@@ -1,539 +1,551 @@
1
- import { Observable } from 'rxjs';
2
- import { TelnyxConnectionState } from './models/connection-state';
3
- import { Call } from './models/call';
4
- import { Config, CredentialConfig, TokenConfig, validateConfig } from './models/config';
5
- import { SessionManager } from './internal/session/session-manager';
6
- import { CallStateController } from './internal/calls/call-state-controller';
7
-
8
- /**
9
- * Configuration options for TelnyxVoipClient
10
- */
11
- export interface TelnyxVoipClientOptions {
12
- /** Enable automatic app state management (background/foreground behavior) - default: true */
13
- enableAppStateManagement?: boolean;
14
-
15
- /** Enable debug logging */
16
- debug?: boolean;
17
- }
18
-
19
- /**
20
- * The main public interface for the react-voice-commons module.
21
- *
22
- * This class serves as the Façade for the entire module, providing a simplified
23
- * API that completely hides the underlying complexity. It is the sole entry point
24
- * for developers using the react-voice-commons package.
25
- *
26
- * The TelnyxVoipClient is designed to be state-management agnostic, exposing
27
- * all observable state via RxJS streams. This allows developers to integrate it
28
- * into their chosen state management solution naturally.
29
- */
30
- export class TelnyxVoipClient {
31
- private readonly _sessionManager: SessionManager;
32
- private readonly _callStateController: CallStateController;
33
- private readonly _options: Required<TelnyxVoipClientOptions>;
34
- private _disposed = false;
35
-
36
- /**
37
- * Creates a new TelnyxVoipClient instance.
38
- *
39
- * @param options Configuration options for the client
40
- */
41
- constructor(options: TelnyxVoipClientOptions = {}) {
42
- this._options = {
43
- enableAppStateManagement: true,
44
- debug: false,
45
- ...options,
46
- };
47
-
48
- // Initialize core components
49
- this._sessionManager = new SessionManager();
50
- this._callStateController = new CallStateController(this._sessionManager);
51
-
52
- // Set up callback to initialize call state controller listeners when client is ready
53
- this._sessionManager.setOnClientReady(() => {
54
- console.log(
55
- '🔧 TelnyxVoipClient: Client ready, initializing call state controller listeners'
56
- );
57
- this._callStateController.initializeClientListeners();
58
- });
59
-
60
- if (this._options.debug) {
61
- console.log('TelnyxVoipClient initialized with options:', this._options);
62
- }
63
- }
64
-
65
- // ========== Observable Streams ==========
66
-
67
- /**
68
- * Stream of connection state changes.
69
- *
70
- * Emits the current status of the connection to the Telnyx backend.
71
- * Values include connecting, connected, disconnected, and error states.
72
- * Listen to this to show connection indicators in your UI.
73
- */
74
- get connectionState$(): Observable<TelnyxConnectionState> {
75
- return this._sessionManager.connectionState$;
76
- }
77
-
78
- /**
79
- * Stream of all current calls.
80
- *
81
- * Emits a list of all current Call objects. Use this for applications
82
- * that need to support multiple simultaneous calls (e.g., call waiting,
83
- * conference calls).
84
- */
85
- get calls$(): Observable<Call[]> {
86
- return this._callStateController.calls$;
87
- }
88
-
89
- /**
90
- * Stream of the currently active call.
91
- *
92
- * A convenience stream that emits the currently active Call object.
93
- * It emits null when no call is in progress. Ideal for applications
94
- * that only handle a single call at a time.
95
- */
96
- get activeCall$(): Observable<Call | null> {
97
- return this._callStateController.activeCall$;
98
- }
99
-
100
- // ========== Synchronous State Access ==========
101
-
102
- /**
103
- * Current connection state (synchronous access).
104
- */
105
- get currentConnectionState(): TelnyxConnectionState {
106
- return this._sessionManager.currentState;
107
- }
108
-
109
- /**
110
- * Current list of calls (synchronous access).
111
- */
112
- get currentCalls(): Call[] {
113
- return this._callStateController.currentCalls;
114
- }
115
-
116
- /**
117
- * Current active call (synchronous access).
118
- */
119
- get currentActiveCall(): Call | null {
120
- return this._callStateController.currentActiveCall;
121
- }
122
-
123
- /**
124
- * Current session ID (UUID) for this connection.
125
- */
126
- get sessionId(): string {
127
- return this._sessionManager.sessionId;
128
- }
129
-
130
- /**
131
- * Configuration options for this client instance.
132
- */
133
- get options(): Required<TelnyxVoipClientOptions> {
134
- return this._options;
135
- }
136
-
137
- // ========== Authentication Methods ==========
138
-
139
- /**
140
- * Connects to the Telnyx platform using credential authentication.
141
- *
142
- * @param config The credential configuration containing SIP username and password
143
- * @returns A Promise that completes when the connection attempt is initiated
144
- *
145
- * Listen to connectionState$ to monitor the actual connection status.
146
- * Credentials are automatically stored for future reconnection.
147
- */
148
- async login(config: CredentialConfig): Promise<void> {
149
- this._throwIfDisposed();
150
-
151
- const errors = validateConfig(config);
152
- if (errors.length > 0) {
153
- throw new Error(`Invalid configuration: ${errors.join(', ')}`);
154
- }
155
-
156
- if (this._options.debug) {
157
- console.log('TelnyxVoipClient: Logging in with credentials for user:', config.sipUser);
158
- }
159
-
160
- // Store credentials for future reconnection
161
- await this._storeCredentials(config);
162
-
163
- await this._sessionManager.connectWithCredential(config);
164
- }
165
-
166
- /**
167
- * Connects to the Telnyx platform using token authentication.
168
- *
169
- * @param config The token configuration containing the authentication token
170
- * @returns A Promise that completes when the connection attempt is initiated
171
- *
172
- * Listen to connectionState$ to monitor the actual connection status.
173
- * Token is automatically stored for future reconnection.
174
- */
175
- async loginWithToken(config: TokenConfig): Promise<void> {
176
- this._throwIfDisposed();
177
-
178
- const errors = validateConfig(config);
179
- if (errors.length > 0) {
180
- throw new Error(`Invalid configuration: ${errors.join(', ')}`);
181
- }
182
-
183
- if (this._options.debug) {
184
- console.log('TelnyxVoipClient: Logging in with token');
185
- }
186
-
187
- // Store token for future reconnection
188
- await this._storeToken(config);
189
-
190
- await this._sessionManager.connectWithToken(config);
191
- }
192
-
193
- /**
194
- * Disconnects from the Telnyx platform.
195
- *
196
- * This method terminates the connection, ends any active calls, and
197
- * cleans up all related resources.
198
- */
199
- async logout(): Promise<void> {
200
- if (this._disposed) {
201
- return;
202
- }
203
-
204
- if (this._options.debug) {
205
- console.log('TelnyxVoipClient: Logging out');
206
- }
207
-
208
- await this._sessionManager.disconnect();
209
- }
210
-
211
- /**
212
- * Attempts to reconnect using previously stored configuration.
213
- *
214
- * This method is used for auto-reconnection scenarios where the app
215
- * comes back to the foreground and needs to restore the connection.
216
- *
217
- * @returns Promise<boolean> - true if reconnection was successful, false otherwise
218
- */
219
- async loginFromStoredConfig(): Promise<boolean> {
220
- this._throwIfDisposed();
221
-
222
- if (this._options.debug) {
223
- console.log('TelnyxVoipClient: Attempting to login from stored config');
224
- }
225
-
226
- try {
227
- // Try to retrieve stored credentials and token from AsyncStorage
228
- const AsyncStorage = require('@react-native-async-storage/async-storage').default;
229
-
230
- const storedUsername = await AsyncStorage.getItem('@telnyx_username');
231
- const storedPassword = await AsyncStorage.getItem('@telnyx_password');
232
- const storedCredentialToken = await AsyncStorage.getItem('@credential_token');
233
- const storedPushToken = await AsyncStorage.getItem('@push_token');
234
-
235
- // Check if we have credential-based authentication data
236
- if (storedUsername && storedPassword) {
237
- // Create credential config from stored data
238
- const { createCredentialConfig } = require('./models/config');
239
- const config = createCredentialConfig(storedUsername, storedPassword, {
240
- pushNotificationDeviceToken: storedPushToken,
241
- });
242
-
243
- if (this._options.debug) {
244
- console.log(
245
- 'TelnyxVoipClient: Reconnecting with stored credentials for user:',
246
- storedUsername
247
- );
248
- }
249
-
250
- await this._sessionManager.connectWithCredential(config);
251
- return true;
252
- }
253
-
254
- // Check if we have token-based authentication data
255
- if (storedCredentialToken) {
256
- // Create token config from stored data
257
- const { createTokenConfig } = require('./models/config');
258
- const config = createTokenConfig(storedCredentialToken, {
259
- pushNotificationDeviceToken: storedPushToken,
260
- });
261
-
262
- if (this._options.debug) {
263
- console.log('TelnyxVoipClient: Reconnecting with stored token');
264
- }
265
-
266
- await this._sessionManager.connectWithToken(config);
267
- return true;
268
- }
269
-
270
- // No stored authentication data found
271
- if (this._options.debug) {
272
- console.log('TelnyxVoipClient: No stored credentials or token found');
273
- }
274
- return false;
275
- } catch (error) {
276
- if (this._options.debug) {
277
- console.log('TelnyxVoipClient: Failed to login from stored config:', error);
278
- }
279
- return false;
280
- }
281
- }
282
-
283
- // ========== Call Management Methods ==========
284
-
285
- /**
286
- * Initiates a new outgoing call.
287
- *
288
- * @param destination The destination number or SIP URI to call
289
- * @param debug Optional flag to enable call quality metrics for this call
290
- * @returns A Promise that completes with the Call object once the invitation has been sent
291
- *
292
- * The call's state can be monitored through the returned Call object's streams.
293
- */
294
- async newCall(destination: string, debug: boolean = false): Promise<Call> {
295
- this._throwIfDisposed();
296
-
297
- if (!destination || destination.trim() === '') {
298
- throw new Error('Destination is required');
299
- }
300
-
301
- if (this.currentConnectionState !== TelnyxConnectionState.CONNECTED) {
302
- throw new Error(`Cannot make call when connection state is: ${this.currentConnectionState}`);
303
- }
304
-
305
- if (this._options.debug) {
306
- console.log('TelnyxVoipClient: Creating new call to:', destination);
307
- }
308
-
309
- return await this._callStateController.newCall(destination, undefined, undefined, debug);
310
- }
311
-
312
- // ========== Push Notification Methods ==========
313
-
314
- /**
315
- * Handle push notification payload.
316
- *
317
- * This is the unified entry point for all push notifications. It intelligently
318
- * determines whether to show a new incoming call UI or to process an already
319
- * actioned (accepted/declined) call upon app launch.
320
- *
321
- * @param payload The push notification payload
322
- */
323
- async handlePushNotification(payload: Record<string, any>): Promise<void> {
324
- this._throwIfDisposed();
325
-
326
- if (this._options.debug) {
327
- console.log('TelnyxVoipClient: Handling push notification:', payload);
328
- }
329
-
330
- try {
331
- // First, pass the push notification to the session manager for processing
332
- // This will set the isCallFromPush flag on the TelnyxRTC client
333
- await this._sessionManager.handlePushNotification(payload);
334
-
335
- // Connect if not already connected
336
- const currentState = this.currentConnectionState;
337
- if (currentState !== TelnyxConnectionState.CONNECTED) {
338
- // Try to login from stored config - now the push flags should be set
339
- const loginSuccess = await this.loginFromStoredConfig();
340
- if (!loginSuccess) {
341
- console.warn(
342
- 'TelnyxVoipClient: Could not login from stored config for push notification'
343
- );
344
- return;
345
- }
346
- }
347
- } catch (error) {
348
- console.error('TelnyxVoipClient: Error handling push notification:', error);
349
- throw error;
350
- }
351
- }
352
-
353
- /**
354
- * Disables push notifications for the current session.
355
- *
356
- * This method sends a request to the Telnyx backend to disable push
357
- * notifications for the current registered device/session.
358
- */
359
- disablePushNotifications(): void {
360
- this._throwIfDisposed();
361
-
362
- if (this._options.debug) {
363
- console.log('TelnyxVoipClient: Disabling push notifications');
364
- }
365
-
366
- this._sessionManager.disablePushNotifications();
367
- }
368
-
369
- // ========== CallKit Integration Methods ==========
370
-
371
- /**
372
- * Set a call to connecting state (used for push notification calls when answered via CallKit)
373
- * @param callId The ID of the call to set to connecting state
374
- * @internal
375
- */
376
- setCallConnecting(callId: string): void {
377
- this._callStateController.setCallConnecting(callId);
378
- }
379
-
380
- /**
381
- * Find a call by its underlying Telnyx call object
382
- * @param telnyxCall The Telnyx call object to find
383
- * @internal
384
- */
385
- findCallByTelnyxCall(telnyxCall: any): Call | null {
386
- return this._callStateController.findCallByTelnyxCall(telnyxCall);
387
- }
388
-
389
- /**
390
- * Queue an answer action for when the call invite arrives (for CallKit integration)
391
- * This should be called when the user answers from CallKit before the socket connection is established
392
- * @param customHeaders Optional custom headers to include with the answer
393
- */
394
- queueAnswerFromCallKit(customHeaders: Record<string, string> = {}): void {
395
- this._throwIfDisposed();
396
-
397
- if (this._options.debug) {
398
- console.log('TelnyxVoipClient: Queuing answer action from CallKit', customHeaders);
399
- }
400
-
401
- const telnyxClient = this._sessionManager.telnyxClient;
402
- if (telnyxClient && typeof (telnyxClient as any).queueAnswerFromCallKit === 'function') {
403
- (telnyxClient as any).queueAnswerFromCallKit(customHeaders);
404
- } else {
405
- console.warn(
406
- 'TelnyxVoipClient: TelnyxRTC client not available or method not found for queueAnswerFromCallKit'
407
- );
408
- }
409
- }
410
-
411
- /**
412
- * Queue an end action for when the call invite arrives (for CallKit integration)
413
- * This should be called when the user ends from CallKit before the socket connection is established
414
- */
415
- queueEndFromCallKit(): void {
416
- this._throwIfDisposed();
417
-
418
- if (this._options.debug) {
419
- console.log('TelnyxVoipClient: Queuing end action from CallKit');
420
- }
421
-
422
- const telnyxClient = this._sessionManager.telnyxClient;
423
- if (telnyxClient && typeof (telnyxClient as any).queueEndFromCallKit === 'function') {
424
- (telnyxClient as any).queueEndFromCallKit();
425
- } else {
426
- console.warn(
427
- 'TelnyxVoipClient: TelnyxRTC client not available or method not found for queueEndFromCallKit'
428
- );
429
- }
430
- }
431
-
432
- // ========== Lifecycle Methods ==========
433
-
434
- /**
435
- * Dispose of the client and clean up all resources.
436
- *
437
- * After calling this method, the client instance should not be used anymore.
438
- * This is particularly important for background clients that should be
439
- * disposed after handling push notifications.
440
- */
441
- dispose(): void {
442
- if (this._disposed) {
443
- return;
444
- }
445
-
446
- if (this._options.debug) {
447
- console.log('TelnyxVoipClient: Disposing client');
448
- }
449
-
450
- this._disposed = true;
451
- this._callStateController.dispose();
452
- this._sessionManager.dispose();
453
- }
454
-
455
- // ========== Private Methods ==========
456
-
457
- /**
458
- * Store credential configuration for automatic reconnection
459
- */
460
- private async _storeCredentials(config: CredentialConfig): Promise<void> {
461
- try {
462
- const AsyncStorage = require('@react-native-async-storage/async-storage').default;
463
-
464
- await AsyncStorage.setItem('@telnyx_username', config.sipUser);
465
- await AsyncStorage.setItem('@telnyx_password', config.sipPassword);
466
-
467
- if (config.pushNotificationDeviceToken) {
468
- await AsyncStorage.setItem('@push_token', config.pushNotificationDeviceToken);
469
- }
470
-
471
- // Clear any existing token since we're using credentials
472
- await AsyncStorage.removeItem('@credential_token');
473
-
474
- if (this._options.debug) {
475
- console.log('TelnyxVoipClient: Stored credentials for user:', config.sipUser);
476
- }
477
- } catch (error) {
478
- if (this._options.debug) {
479
- console.log('TelnyxVoipClient: Failed to store credentials:', error);
480
- }
481
- // Don't throw here - storage failure shouldn't prevent login
482
- }
483
- }
484
-
485
- /**
486
- * Store token configuration for automatic reconnection
487
- */
488
- private async _storeToken(config: TokenConfig): Promise<void> {
489
- try {
490
- const AsyncStorage = require('@react-native-async-storage/async-storage').default;
491
-
492
- await AsyncStorage.setItem('@credential_token', config.token);
493
-
494
- if (config.pushNotificationDeviceToken) {
495
- await AsyncStorage.setItem('@push_token', config.pushNotificationDeviceToken);
496
- }
497
-
498
- // Clear any existing credentials since we're using token
499
- await AsyncStorage.removeItem('@telnyx_username');
500
- await AsyncStorage.removeItem('@telnyx_password');
501
-
502
- if (this._options.debug) {
503
- console.log('TelnyxVoipClient: Stored authentication token');
504
- }
505
- } catch (error) {
506
- if (this._options.debug) {
507
- console.log('TelnyxVoipClient: Failed to store token:', error);
508
- }
509
- // Don't throw here - storage failure shouldn't prevent login
510
- }
511
- }
512
-
513
- /**
514
- * Throw an error if the client has been disposed
515
- */
516
- private _throwIfDisposed(): void {
517
- if (this._disposed) {
518
- throw new Error('TelnyxVoipClient has been disposed');
519
- }
520
- }
521
- }
522
-
523
- // ========== Factory Functions ==========
524
-
525
- /**
526
- * Create a new TelnyxVoipClient instance for normal app usage
527
- */
528
- export function createTelnyxVoipClient(options?: TelnyxVoipClientOptions): TelnyxVoipClient {
529
- return new TelnyxVoipClient(options);
530
- }
531
-
532
- /**
533
- * Create a new TelnyxVoipClient instance for background push notification handling
534
- */
535
- export function createBackgroundTelnyxVoipClient(
536
- options?: TelnyxVoipClientOptions
537
- ): TelnyxVoipClient {
538
- return new TelnyxVoipClient(options);
539
- }
1
+ import { Observable } from 'rxjs';
2
+ import { TelnyxConnectionState } from './models/connection-state';
3
+ import { Call } from './models/call';
4
+ import { Config, CredentialConfig, TokenConfig, validateConfig } from './models/config';
5
+ import { SessionManager } from './internal/session/session-manager';
6
+ import { CallStateController } from './internal/calls/call-state-controller';
7
+
8
+ /**
9
+ * Configuration options for TelnyxVoipClient
10
+ */
11
+ export interface TelnyxVoipClientOptions {
12
+ /** Enable automatic app state management (background/foreground behavior) - default: true */
13
+ enableAppStateManagement?: boolean;
14
+
15
+ /** Enable debug logging */
16
+ debug?: boolean;
17
+ }
18
+
19
+ /**
20
+ * The main public interface for the react-voice-commons module.
21
+ *
22
+ * This class serves as the Façade for the entire module, providing a simplified
23
+ * API that completely hides the underlying complexity. It is the sole entry point
24
+ * for developers using the react-voice-commons package.
25
+ *
26
+ * The TelnyxVoipClient is designed to be state-management agnostic, exposing
27
+ * all observable state via RxJS streams. This allows developers to integrate it
28
+ * into their chosen state management solution naturally.
29
+ */
30
+ export class TelnyxVoipClient {
31
+ private readonly _sessionManager: SessionManager;
32
+ private readonly _callStateController: CallStateController;
33
+ private readonly _options: Required<TelnyxVoipClientOptions>;
34
+ private _disposed = false;
35
+
36
+ /**
37
+ * Creates a new TelnyxVoipClient instance.
38
+ *
39
+ * @param options Configuration options for the client
40
+ */
41
+ constructor(options: TelnyxVoipClientOptions = {}) {
42
+ this._options = {
43
+ enableAppStateManagement: true,
44
+ debug: false,
45
+ ...options,
46
+ };
47
+
48
+ // Initialize core components
49
+ this._sessionManager = new SessionManager();
50
+ this._callStateController = new CallStateController(this._sessionManager);
51
+
52
+ // Set up callback to initialize call state controller listeners when client is ready
53
+ this._sessionManager.setOnClientReady(() => {
54
+ console.log(
55
+ '🔧 TelnyxVoipClient: Client ready, initializing call state controller listeners'
56
+ );
57
+ this._callStateController.initializeClientListeners();
58
+ });
59
+
60
+ if (this._options.debug) {
61
+ console.log('TelnyxVoipClient initialized with options:', this._options);
62
+ }
63
+ }
64
+
65
+ // ========== Observable Streams ==========
66
+
67
+ /**
68
+ * Stream of connection state changes.
69
+ *
70
+ * Emits the current status of the connection to the Telnyx backend.
71
+ * Values include connecting, connected, disconnected, and error states.
72
+ * Listen to this to show connection indicators in your UI.
73
+ */
74
+ get connectionState$(): Observable<TelnyxConnectionState> {
75
+ return this._sessionManager.connectionState$;
76
+ }
77
+
78
+ /**
79
+ * Stream of all current calls.
80
+ *
81
+ * Emits a list of all current Call objects. Use this for applications
82
+ * that need to support multiple simultaneous calls (e.g., call waiting,
83
+ * conference calls).
84
+ */
85
+ get calls$(): Observable<Call[]> {
86
+ return this._callStateController.calls$;
87
+ }
88
+
89
+ /**
90
+ * Stream of the currently active call.
91
+ *
92
+ * A convenience stream that emits the currently active Call object.
93
+ * It emits null when no call is in progress. Ideal for applications
94
+ * that only handle a single call at a time.
95
+ */
96
+ get activeCall$(): Observable<Call | null> {
97
+ return this._callStateController.activeCall$;
98
+ }
99
+
100
+ // ========== Synchronous State Access ==========
101
+
102
+ /**
103
+ * Current connection state (synchronous access).
104
+ */
105
+ get currentConnectionState(): TelnyxConnectionState {
106
+ return this._sessionManager.currentState;
107
+ }
108
+
109
+ /**
110
+ * Current list of calls (synchronous access).
111
+ */
112
+ get currentCalls(): Call[] {
113
+ return this._callStateController.currentCalls;
114
+ }
115
+
116
+ /**
117
+ * Current active call (synchronous access).
118
+ */
119
+ get currentActiveCall(): Call | null {
120
+ return this._callStateController.currentActiveCall;
121
+ }
122
+
123
+ /**
124
+ * Current session ID (UUID) for this connection.
125
+ */
126
+ get sessionId(): string {
127
+ return this._sessionManager.sessionId;
128
+ }
129
+
130
+ /**
131
+ * Configuration options for this client instance.
132
+ */
133
+ get options(): Required<TelnyxVoipClientOptions> {
134
+ return this._options;
135
+ }
136
+
137
+ // ========== Authentication Methods ==========
138
+
139
+ /**
140
+ * Connects to the Telnyx platform using credential authentication.
141
+ *
142
+ * @param config The credential configuration containing SIP username and password
143
+ * @returns A Promise that completes when the connection attempt is initiated
144
+ *
145
+ * Listen to connectionState$ to monitor the actual connection status.
146
+ * Credentials are automatically stored for future reconnection.
147
+ */
148
+ async login(config: CredentialConfig): Promise<void> {
149
+ this._throwIfDisposed();
150
+
151
+ const errors = validateConfig(config);
152
+ if (errors.length > 0) {
153
+ throw new Error(`Invalid configuration: ${errors.join(', ')}`);
154
+ }
155
+
156
+ if (this._options.debug) {
157
+ console.log('TelnyxVoipClient: Logging in with credentials for user:', config.sipUser);
158
+ }
159
+
160
+ // Store credentials for future reconnection
161
+ await this._storeCredentials(config);
162
+
163
+ await this._sessionManager.connectWithCredential(config);
164
+ }
165
+
166
+ /**
167
+ * Connects to the Telnyx platform using token authentication.
168
+ *
169
+ * @param config The token configuration containing the authentication token
170
+ * @returns A Promise that completes when the connection attempt is initiated
171
+ *
172
+ * Listen to connectionState$ to monitor the actual connection status.
173
+ * Token is automatically stored for future reconnection.
174
+ */
175
+ async loginWithToken(config: TokenConfig): Promise<void> {
176
+ this._throwIfDisposed();
177
+
178
+ const errors = validateConfig(config);
179
+ if (errors.length > 0) {
180
+ throw new Error(`Invalid configuration: ${errors.join(', ')}`);
181
+ }
182
+
183
+ if (this._options.debug) {
184
+ console.log('TelnyxVoipClient: Logging in with token');
185
+ }
186
+
187
+ // Store token for future reconnection
188
+ await this._storeToken(config);
189
+
190
+ await this._sessionManager.connectWithToken(config);
191
+ }
192
+
193
+ /**
194
+ * Disconnects from the Telnyx platform.
195
+ *
196
+ * This method terminates the connection, ends any active calls, and
197
+ * cleans up all related resources.
198
+ */
199
+ async logout(): Promise<void> {
200
+ if (this._disposed) {
201
+ return;
202
+ }
203
+
204
+ if (this._options.debug) {
205
+ console.log('TelnyxVoipClient: Logging out');
206
+ }
207
+
208
+ await this._sessionManager.disconnect();
209
+ }
210
+
211
+ /**
212
+ * Attempts to reconnect using previously stored configuration.
213
+ *
214
+ * This method is used for auto-reconnection scenarios where the app
215
+ * comes back to the foreground and needs to restore the connection.
216
+ *
217
+ * @returns Promise<boolean> - true if reconnection was successful, false otherwise
218
+ */
219
+ async loginFromStoredConfig(): Promise<boolean> {
220
+ this._throwIfDisposed();
221
+
222
+ if (this._options.debug) {
223
+ console.log('TelnyxVoipClient: Attempting to login from stored config');
224
+ }
225
+
226
+ try {
227
+ // Try to retrieve stored credentials and token from AsyncStorage
228
+ const AsyncStorage = require('@react-native-async-storage/async-storage').default;
229
+
230
+ const storedUsername = await AsyncStorage.getItem('@telnyx_username');
231
+ const storedPassword = await AsyncStorage.getItem('@telnyx_password');
232
+ const storedCredentialToken = await AsyncStorage.getItem('@credential_token');
233
+ const storedPushToken = await AsyncStorage.getItem('@push_token');
234
+
235
+ // Check if we have credential-based authentication data
236
+ if (storedUsername && storedPassword) {
237
+ // Create credential config from stored data
238
+ const { createCredentialConfig } = require('./models/config');
239
+ const config = createCredentialConfig(storedUsername, storedPassword, {
240
+ pushNotificationDeviceToken: storedPushToken,
241
+ });
242
+
243
+ if (this._options.debug) {
244
+ console.log(
245
+ 'TelnyxVoipClient: Reconnecting with stored credentials for user:',
246
+ storedUsername
247
+ );
248
+ }
249
+
250
+ await this._sessionManager.connectWithCredential(config);
251
+ return true;
252
+ }
253
+
254
+ // Check if we have token-based authentication data
255
+ if (storedCredentialToken) {
256
+ // Create token config from stored data
257
+ const { createTokenConfig } = require('./models/config');
258
+ const config = createTokenConfig(storedCredentialToken, {
259
+ pushNotificationDeviceToken: storedPushToken,
260
+ });
261
+
262
+ if (this._options.debug) {
263
+ console.log('TelnyxVoipClient: Reconnecting with stored token');
264
+ }
265
+
266
+ await this._sessionManager.connectWithToken(config);
267
+ return true;
268
+ }
269
+
270
+ // No stored authentication data found
271
+ if (this._options.debug) {
272
+ console.log('TelnyxVoipClient: No stored credentials or token found');
273
+ }
274
+ return false;
275
+ } catch (error) {
276
+ if (this._options.debug) {
277
+ console.log('TelnyxVoipClient: Failed to login from stored config:', error);
278
+ }
279
+ return false;
280
+ }
281
+ }
282
+
283
+ // ========== Call Management Methods ==========
284
+
285
+ /**
286
+ * Initiates a new outgoing call.
287
+ *
288
+ * @param destination The destination number or SIP URI to call
289
+ * @param callerName Optional caller name to display
290
+ * @param callerNumber Optional caller ID number
291
+ * @param customHeaders Optional custom headers to include with the call
292
+ * @returns A Promise that completes with the Call object once the invitation has been sent
293
+ *
294
+ * The call's state can be monitored through the returned Call object's streams.
295
+ */
296
+ async newCall(
297
+ destination: string,
298
+ callerName?: string,
299
+ callerNumber?: string,
300
+ customHeaders?: Record<string, string>
301
+ ): Promise<Call> {
302
+ this._throwIfDisposed();
303
+
304
+ if (!destination || destination.trim() === '') {
305
+ throw new Error('Destination is required');
306
+ }
307
+
308
+ if (this.currentConnectionState !== TelnyxConnectionState.CONNECTED) {
309
+ throw new Error(`Cannot make call when connection state is: ${this.currentConnectionState}`);
310
+ }
311
+
312
+ if (this._options.debug) {
313
+ console.log('TelnyxVoipClient: Creating new call to:', destination);
314
+ }
315
+
316
+ return await this._callStateController.newCall(
317
+ destination,
318
+ callerName,
319
+ callerNumber,
320
+ customHeaders
321
+ );
322
+ }
323
+
324
+ // ========== Push Notification Methods ==========
325
+
326
+ /**
327
+ * Handle push notification payload.
328
+ *
329
+ * This is the unified entry point for all push notifications. It intelligently
330
+ * determines whether to show a new incoming call UI or to process an already
331
+ * actioned (accepted/declined) call upon app launch.
332
+ *
333
+ * @param payload The push notification payload
334
+ */
335
+ async handlePushNotification(payload: Record<string, any>): Promise<void> {
336
+ this._throwIfDisposed();
337
+
338
+ if (this._options.debug) {
339
+ console.log('TelnyxVoipClient: Handling push notification:', payload);
340
+ }
341
+
342
+ try {
343
+ // First, pass the push notification to the session manager for processing
344
+ // This will set the isCallFromPush flag on the TelnyxRTC client
345
+ await this._sessionManager.handlePushNotification(payload);
346
+
347
+ // Connect if not already connected
348
+ const currentState = this.currentConnectionState;
349
+ if (currentState !== TelnyxConnectionState.CONNECTED) {
350
+ // Try to login from stored config - now the push flags should be set
351
+ const loginSuccess = await this.loginFromStoredConfig();
352
+ if (!loginSuccess) {
353
+ console.warn(
354
+ 'TelnyxVoipClient: Could not login from stored config for push notification'
355
+ );
356
+ return;
357
+ }
358
+ }
359
+ } catch (error) {
360
+ console.error('TelnyxVoipClient: Error handling push notification:', error);
361
+ throw error;
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Disables push notifications for the current session.
367
+ *
368
+ * This method sends a request to the Telnyx backend to disable push
369
+ * notifications for the current registered device/session.
370
+ */
371
+ disablePushNotifications(): void {
372
+ this._throwIfDisposed();
373
+
374
+ if (this._options.debug) {
375
+ console.log('TelnyxVoipClient: Disabling push notifications');
376
+ }
377
+
378
+ this._sessionManager.disablePushNotifications();
379
+ }
380
+
381
+ // ========== CallKit Integration Methods ==========
382
+
383
+ /**
384
+ * Set a call to connecting state (used for push notification calls when answered via CallKit)
385
+ * @param callId The ID of the call to set to connecting state
386
+ * @internal
387
+ */
388
+ setCallConnecting(callId: string): void {
389
+ this._callStateController.setCallConnecting(callId);
390
+ }
391
+
392
+ /**
393
+ * Find a call by its underlying Telnyx call object
394
+ * @param telnyxCall The Telnyx call object to find
395
+ * @internal
396
+ */
397
+ findCallByTelnyxCall(telnyxCall: any): Call | null {
398
+ return this._callStateController.findCallByTelnyxCall(telnyxCall);
399
+ }
400
+
401
+ /**
402
+ * Queue an answer action for when the call invite arrives (for CallKit integration)
403
+ * This should be called when the user answers from CallKit before the socket connection is established
404
+ * @param customHeaders Optional custom headers to include with the answer
405
+ */
406
+ queueAnswerFromCallKit(customHeaders: Record<string, string> = {}): void {
407
+ this._throwIfDisposed();
408
+
409
+ if (this._options.debug) {
410
+ console.log('TelnyxVoipClient: Queuing answer action from CallKit', customHeaders);
411
+ }
412
+
413
+ const telnyxClient = this._sessionManager.telnyxClient;
414
+ if (telnyxClient && typeof (telnyxClient as any).queueAnswerFromCallKit === 'function') {
415
+ (telnyxClient as any).queueAnswerFromCallKit(customHeaders);
416
+ } else {
417
+ console.warn(
418
+ 'TelnyxVoipClient: TelnyxRTC client not available or method not found for queueAnswerFromCallKit'
419
+ );
420
+ }
421
+ }
422
+
423
+ /**
424
+ * Queue an end action for when the call invite arrives (for CallKit integration)
425
+ * This should be called when the user ends from CallKit before the socket connection is established
426
+ */
427
+ queueEndFromCallKit(): void {
428
+ this._throwIfDisposed();
429
+
430
+ if (this._options.debug) {
431
+ console.log('TelnyxVoipClient: Queuing end action from CallKit');
432
+ }
433
+
434
+ const telnyxClient = this._sessionManager.telnyxClient;
435
+ if (telnyxClient && typeof (telnyxClient as any).queueEndFromCallKit === 'function') {
436
+ (telnyxClient as any).queueEndFromCallKit();
437
+ } else {
438
+ console.warn(
439
+ 'TelnyxVoipClient: TelnyxRTC client not available or method not found for queueEndFromCallKit'
440
+ );
441
+ }
442
+ }
443
+
444
+ // ========== Lifecycle Methods ==========
445
+
446
+ /**
447
+ * Dispose of the client and clean up all resources.
448
+ *
449
+ * After calling this method, the client instance should not be used anymore.
450
+ * This is particularly important for background clients that should be
451
+ * disposed after handling push notifications.
452
+ */
453
+ dispose(): void {
454
+ if (this._disposed) {
455
+ return;
456
+ }
457
+
458
+ if (this._options.debug) {
459
+ console.log('TelnyxVoipClient: Disposing client');
460
+ }
461
+
462
+ this._disposed = true;
463
+ this._callStateController.dispose();
464
+ this._sessionManager.dispose();
465
+ }
466
+
467
+ // ========== Private Methods ==========
468
+
469
+ /**
470
+ * Store credential configuration for automatic reconnection
471
+ */
472
+ private async _storeCredentials(config: CredentialConfig): Promise<void> {
473
+ try {
474
+ const AsyncStorage = require('@react-native-async-storage/async-storage').default;
475
+
476
+ await AsyncStorage.setItem('@telnyx_username', config.sipUser);
477
+ await AsyncStorage.setItem('@telnyx_password', config.sipPassword);
478
+
479
+ if (config.pushNotificationDeviceToken) {
480
+ await AsyncStorage.setItem('@push_token', config.pushNotificationDeviceToken);
481
+ }
482
+
483
+ // Clear any existing token since we're using credentials
484
+ await AsyncStorage.removeItem('@credential_token');
485
+
486
+ if (this._options.debug) {
487
+ console.log('TelnyxVoipClient: Stored credentials for user:', config.sipUser);
488
+ }
489
+ } catch (error) {
490
+ if (this._options.debug) {
491
+ console.log('TelnyxVoipClient: Failed to store credentials:', error);
492
+ }
493
+ // Don't throw here - storage failure shouldn't prevent login
494
+ }
495
+ }
496
+
497
+ /**
498
+ * Store token configuration for automatic reconnection
499
+ */
500
+ private async _storeToken(config: TokenConfig): Promise<void> {
501
+ try {
502
+ const AsyncStorage = require('@react-native-async-storage/async-storage').default;
503
+
504
+ await AsyncStorage.setItem('@credential_token', config.token);
505
+
506
+ if (config.pushNotificationDeviceToken) {
507
+ await AsyncStorage.setItem('@push_token', config.pushNotificationDeviceToken);
508
+ }
509
+
510
+ // Clear any existing credentials since we're using token
511
+ await AsyncStorage.removeItem('@telnyx_username');
512
+ await AsyncStorage.removeItem('@telnyx_password');
513
+
514
+ if (this._options.debug) {
515
+ console.log('TelnyxVoipClient: Stored authentication token');
516
+ }
517
+ } catch (error) {
518
+ if (this._options.debug) {
519
+ console.log('TelnyxVoipClient: Failed to store token:', error);
520
+ }
521
+ // Don't throw here - storage failure shouldn't prevent login
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Throw an error if the client has been disposed
527
+ */
528
+ private _throwIfDisposed(): void {
529
+ if (this._disposed) {
530
+ throw new Error('TelnyxVoipClient has been disposed');
531
+ }
532
+ }
533
+ }
534
+
535
+ // ========== Factory Functions ==========
536
+
537
+ /**
538
+ * Create a new TelnyxVoipClient instance for normal app usage
539
+ */
540
+ export function createTelnyxVoipClient(options?: TelnyxVoipClientOptions): TelnyxVoipClient {
541
+ return new TelnyxVoipClient(options);
542
+ }
543
+
544
+ /**
545
+ * Create a new TelnyxVoipClient instance for background push notification handling
546
+ */
547
+ export function createBackgroundTelnyxVoipClient(
548
+ options?: TelnyxVoipClientOptions
549
+ ): TelnyxVoipClient {
550
+ return new TelnyxVoipClient(options);
551
+ }