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

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 (58) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/TelnyxVoiceCommons.podspec +31 -31
  3. package/ios/CallKitBridge.m +43 -43
  4. package/ios/CallKitBridge.swift +874 -879
  5. package/ios/VoicePnBridge.m +30 -30
  6. package/ios/VoicePnBridge.swift +86 -86
  7. package/lib/callkit/callkit-coordinator.d.ts +110 -117
  8. package/lib/callkit/callkit-coordinator.js +664 -727
  9. package/lib/callkit/callkit.d.ts +41 -41
  10. package/lib/callkit/callkit.js +252 -242
  11. package/lib/callkit/index.js +15 -47
  12. package/lib/callkit/use-callkit.d.ts +19 -19
  13. package/lib/callkit/use-callkit.js +270 -310
  14. package/lib/context/TelnyxVoiceContext.d.ts +9 -9
  15. package/lib/context/TelnyxVoiceContext.js +10 -13
  16. package/lib/hooks/use-callkit-coordinator.d.ts +9 -17
  17. package/lib/hooks/use-callkit-coordinator.js +45 -50
  18. package/lib/hooks/useAppReadyNotifier.js +13 -15
  19. package/lib/hooks/useAppStateHandler.d.ts +6 -11
  20. package/lib/hooks/useAppStateHandler.js +95 -110
  21. package/lib/hooks/useNetworkStateHandler.d.ts +0 -0
  22. package/lib/hooks/useNetworkStateHandler.js +0 -0
  23. package/lib/index.d.ts +3 -21
  24. package/lib/index.js +50 -201
  25. package/lib/internal/CallKitHandler.d.ts +6 -6
  26. package/lib/internal/CallKitHandler.js +96 -104
  27. package/lib/internal/callkit-manager.d.ts +57 -57
  28. package/lib/internal/callkit-manager.js +299 -316
  29. package/lib/internal/calls/call-state-controller.d.ts +73 -86
  30. package/lib/internal/calls/call-state-controller.js +263 -307
  31. package/lib/internal/session/session-manager.d.ts +71 -75
  32. package/lib/internal/session/session-manager.js +360 -424
  33. package/lib/internal/user-defaults-helpers.js +49 -39
  34. package/lib/internal/voice-pn-bridge.d.ts +114 -12
  35. package/lib/internal/voice-pn-bridge.js +212 -5
  36. package/lib/models/call-state.d.ts +46 -44
  37. package/lib/models/call-state.js +70 -68
  38. package/lib/models/call.d.ts +161 -133
  39. package/lib/models/call.js +454 -382
  40. package/lib/models/config.d.ts +11 -18
  41. package/lib/models/config.js +37 -35
  42. package/lib/models/connection-state.d.ts +10 -10
  43. package/lib/models/connection-state.js +16 -16
  44. package/lib/telnyx-voice-app.d.ts +28 -28
  45. package/lib/telnyx-voice-app.js +463 -481
  46. package/lib/telnyx-voip-client.d.ts +167 -167
  47. package/lib/telnyx-voip-client.js +385 -390
  48. package/package.json +11 -4
  49. package/src/callkit/callkit-coordinator.ts +18 -34
  50. package/src/hooks/useNetworkStateHandler.ts +0 -0
  51. package/src/internal/calls/call-state-controller.ts +81 -58
  52. package/src/internal/session/session-manager.ts +42 -26
  53. package/src/internal/voice-pn-bridge.ts +250 -2
  54. package/src/models/call-state.ts +8 -1
  55. package/src/models/call.ts +119 -5
  56. package/src/telnyx-voice-app.tsx +87 -40
  57. package/src/telnyx-voip-client.ts +15 -3
  58. package/src/types/telnyx-sdk.d.ts +16 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telnyx/react-voice-commons-sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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",
@@ -19,7 +19,8 @@
19
19
  "src",
20
20
  "ios",
21
21
  "TelnyxVoiceCommons.podspec",
22
- "README.md"
22
+ "README.md",
23
+ "CHANGELOG.md"
23
24
  ],
24
25
  "scripts": {
25
26
  "build": "tsc",
@@ -28,6 +29,10 @@
28
29
  "test:watch": "jest --watch",
29
30
  "lint": "eslint src --ext .ts,.tsx",
30
31
  "clean": "rm -rf lib",
32
+ "docs": "typedoc",
33
+ "docs:watch": "typedoc --watch",
34
+ "docs:markdown": "typedoc --options typedoc.markdown.json",
35
+ "docs:all": "npm run docs && npm run docs:markdown",
31
36
  "android": "expo run:android",
32
37
  "ios": "expo run:ios"
33
38
  },
@@ -55,7 +60,7 @@
55
60
  "peerDependencies": {
56
61
  "@react-native-async-storage/async-storage": "^2.1.0",
57
62
  "expo-router": "^5.1.0",
58
- "react": "^19.0.0",
63
+ "react": "19.0.0",
59
64
  "react-native": "^0.79.0",
60
65
  "react-native-webrtc": "^124.0.5"
61
66
  },
@@ -78,7 +83,7 @@
78
83
  },
79
84
  "dependencies": {
80
85
  "@react-native-community/eslint-config": "^3.2.0",
81
- "@telnyx/react-native-voice-sdk": "^0.1.0",
86
+ "@telnyx/react-native-voice-sdk": "^0.1.2",
82
87
  "eventemitter3": "^5.0.1",
83
88
  "expo": "~53.0.22",
84
89
  "react-native-voip-push-notification": "^3.3.3",
@@ -93,6 +98,8 @@
93
98
  "eslint": "^8.0.0",
94
99
  "jest": "^29.5.0",
95
100
  "ts-jest": "^29.1.0",
101
+ "typedoc": "^0.28.14",
102
+ "typedoc-plugin-markdown": "^4.9.0",
96
103
  "typescript": "^5.0.0"
97
104
  },
98
105
  "engines": {
@@ -3,9 +3,9 @@ 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
5
  import { router } from 'expo-router';
6
- import AsyncStorage from '@react-native-async-storage/async-storage';
7
6
  import { TelnyxVoipClient } from '../telnyx-voip-client';
8
7
  import { TelnyxConnectionState } from '../models/connection-state';
8
+ import { act } from 'react';
9
9
 
10
10
  /**
11
11
  * CallKit Coordinator - Manages the proper CallKit-first flow for iOS
@@ -31,6 +31,9 @@ class CallKitCoordinator {
31
31
 
32
32
  private isCallFromPush = false;
33
33
 
34
+ // Flag to auto-answer the next incoming call (set when answering push notifications via CallKit)
35
+ private shouldAutoAnswerNextCall = false;
36
+
34
37
  // Reference to the VoIP client for triggering reconnection when needed
35
38
  private voipClient: TelnyxVoipClient | null = null;
36
39
 
@@ -266,10 +269,8 @@ class CallKitCoordinator {
266
269
  currentState: call.state,
267
270
  });
268
271
 
269
- if (call.state === 'active' || call.state === 'connecting') {
270
- console.log(
271
- 'CallKitCoordinator: Call already active/connecting, skipping duplicate answer action'
272
- );
272
+ if (call.state === 'active') {
273
+ console.log('CallKitCoordinator: Call already active, skipping duplicate answer action');
273
274
  return;
274
275
  }
275
276
 
@@ -385,7 +386,7 @@ class CallKitCoordinator {
385
386
  }
386
387
 
387
388
  /**
388
- * Handle CallKit push received event - when a VoIP push notification has been processed
389
+ * Handle CallKit push received event
389
390
  * This allows us to coordinate between the push notification and any subsequent WebRTC calls
390
391
  */
391
392
  async handleCallKitPushReceived(callKitUUID: string, event?: any): Promise<void> {
@@ -438,15 +439,16 @@ class CallKitCoordinator {
438
439
  };
439
440
 
440
441
  // Check if auto-answer is set and add from_notification flag
441
- const autoAnswerFlag = await AsyncStorage.getItem('@auto_answer_next_call');
442
- const shouldAddFromNotification = autoAnswerFlag === 'true';
442
+ const shouldAddFromNotification = this.shouldAutoAnswerNextCall;
443
443
 
444
444
  let pushData;
445
445
  if (shouldAddFromNotification) {
446
446
  pushData = {
447
447
  metadata: enhancedMetadata,
448
448
  from_notification: true,
449
+ action: 'answer',
449
450
  };
451
+ voipClient.queueAnswerFromCallKit();
450
452
  } else {
451
453
  pushData = {
452
454
  metadata: enhancedMetadata,
@@ -476,13 +478,9 @@ class CallKitCoordinator {
476
478
  console.log('CallKitCoordinator: Processing iOS push notification answer');
477
479
 
478
480
  // Set auto-answer flag so when the WebRTC call comes in, it will be answered automatically
479
- await AsyncStorage.setItem('@auto_answer_next_call', 'true');
481
+ this.shouldAutoAnswerNextCall = true;
480
482
  console.log('CallKitCoordinator: ✅ Set auto-answer flag for next incoming call');
481
483
 
482
- // Store the CallKit UUID so we can link it when the WebRTC call arrives
483
- await AsyncStorage.setItem('@pending_callkit_uuid', callKitUUID);
484
- console.log('CallKitCoordinator: ✅ Stored pending CallKit UUID for linking');
485
-
486
484
  // Get VoIP client and trigger reconnection
487
485
  const voipClient = this.getSDKClient();
488
486
  if (!voipClient) {
@@ -534,9 +532,9 @@ class CallKitCoordinator {
534
532
  });
535
533
  }
536
534
 
537
- // Set the pending push action using VoicePnBridge
535
+ // Set the pending push action to be handled when app comes to foreground
538
536
  await VoicePnBridge.setPendingPushAction(pushAction, pushMetadata);
539
- console.log('CallKitCoordinator: ✅ Set pending push action for reconnection');
537
+ console.log('CallKitCoordinator: ✅ Set pending push action');
540
538
 
541
539
  return;
542
540
  }
@@ -742,29 +740,12 @@ class CallKitCoordinator {
742
740
  this.voipClient = voipClient;
743
741
  }
744
742
 
745
- /**
746
- * Helper method to handle auto-answer logic for push notification calls
747
- */
748
- private async handleAutoAnswer(call: Call): Promise<void> {
749
- const shouldAutoAnswer = await AsyncStorage.getItem('@auto_answer_next_call');
750
- if (shouldAutoAnswer === 'true') {
751
- console.log('CallKitCoordinator: Auto-answering call from push notification');
752
- await AsyncStorage.removeItem('@auto_answer_next_call');
753
-
754
- // Auto-answer the call after a brief delay to ensure CallKit is ready
755
- setTimeout(() => {
756
- call.answer();
757
- }, 100);
758
- }
759
- }
760
-
761
743
  /**
762
744
  * Helper method to clean up push notification state
763
745
  */
764
746
  private async cleanupPushNotificationState(): Promise<void> {
765
- console.log('CallKitCoordinator: ✅ Cleared pending CallKit UUID and auto-answer flag');
766
- await AsyncStorage.removeItem('@pending_callkit_uuid');
767
- await AsyncStorage.removeItem('@auto_answer_next_call');
747
+ console.log('CallKitCoordinator: ✅ Cleared auto-answer flag');
748
+ this.shouldAutoAnswerNextCall = false;
768
749
  }
769
750
 
770
751
  /**
@@ -814,6 +795,9 @@ class CallKitCoordinator {
814
795
  // Reset push notification flag
815
796
  this.isCallFromPush = false;
816
797
 
798
+ // Reset auto-answer flag
799
+ this.shouldAutoAnswerNextCall = false;
800
+
817
801
  console.log('CallKitCoordinator: ✅ Coordinator flags reset');
818
802
  }
819
803
 
File without changes
@@ -22,6 +22,7 @@ export class CallStateController {
22
22
  private _onInviteAutoAccepted?: () => void;
23
23
 
24
24
  constructor(private readonly _sessionManager: SessionManager) {
25
+ console.log('🔧 CallStateController: Constructor called - instance created');
25
26
  // Don't set up client listeners here - client doesn't exist yet
26
27
  // Will be called when client is available
27
28
  }
@@ -110,6 +111,10 @@ export class CallStateController {
110
111
  */
111
112
  initializeClientListeners(): void {
112
113
  console.log('🔧 CallStateController: initializeClientListeners called');
114
+ console.log(
115
+ '🔧 CallStateController: Current client exists:',
116
+ !!this._sessionManager.telnyxClient
117
+ );
113
118
  this._setupClientListeners();
114
119
 
115
120
  // CallKit integration now handled by CallKitCoordinator
@@ -123,7 +128,7 @@ export class CallStateController {
123
128
  destination: string,
124
129
  callerName?: string,
125
130
  callerNumber?: string,
126
- debug: boolean = false
131
+ customHeaders?: Record<string, string>
127
132
  ): Promise<Call> {
128
133
  if (this._disposed) {
129
134
  throw new Error('CallStateController has been disposed');
@@ -139,6 +144,7 @@ export class CallStateController {
139
144
  destinationNumber: destination,
140
145
  callerIdName: callerName,
141
146
  callerIdNumber: callerNumber,
147
+ customHeaders,
142
148
  };
143
149
  const telnyxCall = await this._sessionManager.telnyxClient.newCall(callOptions);
144
150
 
@@ -147,7 +153,10 @@ export class CallStateController {
147
153
  telnyxCall,
148
154
  telnyxCall.callId || this._generateCallId(),
149
155
  destination,
150
- false // outgoing call
156
+ false, // outgoing call
157
+ false, // not reattached
158
+ callerName || destination, // use destination as fallback for caller name
159
+ callerNumber // original caller number
151
160
  );
152
161
 
153
162
  // Add to our call tracking
@@ -202,16 +211,41 @@ export class CallStateController {
202
211
  }
203
212
 
204
213
  console.log('🔧 CallStateController: TelnyxClient found, setting up incoming call listener');
214
+ console.log(
215
+ '🔧 CallStateController: Client instance:',
216
+ this._sessionManager.telnyxClient.constructor.name
217
+ );
205
218
 
206
219
  // Listen for incoming calls
207
220
  this._sessionManager.telnyxClient.on(
208
221
  'telnyx.call.incoming',
209
222
  (telnyxCall: TelnyxCall, msg: any) => {
210
223
  console.log('📞 CallStateController: Incoming call received:', telnyxCall.callId);
211
- this._handleIncomingCall(telnyxCall, msg);
224
+ this._handleIncomingCall(telnyxCall, msg, false);
225
+ }
226
+ );
227
+
228
+ // Listen for reattached calls (after network reconnection)
229
+ this._sessionManager.telnyxClient.on(
230
+ 'telnyx.call.reattached',
231
+ (telnyxCall: TelnyxCall, msg: any) => {
232
+ console.log('📞 CallStateController: Reattached call received:', telnyxCall.callId);
233
+ this._handleIncomingCall(telnyxCall, msg, true);
212
234
  }
213
235
  );
214
236
 
237
+ // Verify listeners are set up
238
+ const incomingListeners =
239
+ this._sessionManager.telnyxClient.listenerCount('telnyx.call.incoming');
240
+ const reattachedListeners =
241
+ this._sessionManager.telnyxClient.listenerCount('telnyx.call.reattached');
242
+ console.log(
243
+ '🔧 CallStateController: Listeners registered - incoming:',
244
+ incomingListeners,
245
+ 'reattached:',
246
+ reattachedListeners
247
+ );
248
+
215
249
  // Listen for other call events if needed
216
250
  // this._sessionManager.telnyxClient.on('telnyx.call.stateChange', this._handleCallStateChange.bind(this));
217
251
 
@@ -219,28 +253,50 @@ export class CallStateController {
219
253
  }
220
254
 
221
255
  /**
222
- * Handle incoming call
256
+ * Handle incoming call or reattached call
223
257
  */
224
- private _handleIncomingCall(telnyxCall: TelnyxCall, inviteMsg?: any): void {
258
+ private _handleIncomingCall(
259
+ telnyxCall: TelnyxCall,
260
+ inviteMsg?: any,
261
+ isReattached: boolean = false
262
+ ): void {
225
263
  const callId = telnyxCall.callId || this._generateCallId();
226
264
 
227
- console.log('📞 CallStateController: Handling incoming call:', callId);
265
+ console.log(
266
+ '📞 CallStateController: Handling incoming call:',
267
+ callId,
268
+ 'isReattached:',
269
+ isReattached
270
+ );
228
271
  console.log('📞 CallStateController: TelnyxCall object:', telnyxCall);
229
272
  console.log('📞 CallStateController: Invite message:', inviteMsg);
230
273
 
231
- // Check if we already have this call
232
- if (this._callMap.has(callId)) {
274
+ // For reattached calls, remove existing call and create new one
275
+ if (isReattached && this._callMap.has(callId)) {
276
+ console.log('📞 CallStateController: Removing existing call for reattachment');
277
+ const existingCall = this._callMap.get(callId);
278
+ if (existingCall) {
279
+ console.log(
280
+ '📞 CallStateController: Existing call state before removal:',
281
+ existingCall.currentState
282
+ );
283
+ this._removeCall(callId);
284
+ }
285
+ }
286
+
287
+ // Check if we already have this call (for non-reattached calls)
288
+ if (this._callMap.has(callId) && !isReattached) {
233
289
  console.log('Call already exists:', callId);
234
290
  return;
235
291
  }
236
292
 
237
293
  // Get caller information from the invite message (preferred) or fallback to TelnyxCall
238
- let callerNumber = 'Unknown';
239
- let callerName = 'Unknown';
294
+ let callerNumber = '';
295
+ let callerName = '';
240
296
 
241
297
  if (inviteMsg && inviteMsg.params) {
242
- callerNumber = inviteMsg.params.caller_id_number || 'Unknown';
243
- callerName = inviteMsg.params.caller_id_name || callerNumber;
298
+ callerNumber = inviteMsg.params.caller_id_number || '';
299
+ callerName = inviteMsg.params.caller_id_name || '';
244
300
  console.log(
245
301
  '📞 CallStateController: Extracted caller info from invite - Number:',
246
302
  callerNumber,
@@ -249,8 +305,8 @@ export class CallStateController {
249
305
  );
250
306
  } else {
251
307
  // Fallback to TelnyxCall properties
252
- callerNumber = telnyxCall.remoteCallerIdNumber || 'Unknown';
253
- callerName = telnyxCall.remoteCallerIdName || callerNumber;
308
+ callerNumber = telnyxCall.remoteCallerIdNumber || '';
309
+ callerName = telnyxCall.remoteCallerIdName || '';
254
310
  console.log(
255
311
  '📞 CallStateController: Extracted caller info from TelnyxCall - Number:',
256
312
  callerNumber,
@@ -259,60 +315,25 @@ export class CallStateController {
259
315
  );
260
316
  }
261
317
 
318
+ // Use smart fallbacks - prefer caller number over "Unknown"
319
+ const finalCallerNumber = callerNumber || 'Unknown Number';
320
+ const finalCallerName = callerName || callerNumber || 'Unknown Caller';
321
+
262
322
  // Create our wrapper Call object
263
323
  const call = new Call(
264
324
  telnyxCall,
265
325
  callId,
266
- callerNumber, // Use caller number as destination for incoming calls
267
- true // incoming call
326
+ finalCallerNumber, // Use caller number as destination for incoming calls
327
+ true, // incoming call
328
+ isReattached, // pass the reattached flag
329
+ finalCallerName, // use caller name or fallback to number
330
+ finalCallerNumber // use caller number
268
331
  );
269
332
 
270
- // Check if we're waiting for an invite (push notification scenario)
271
- if (this._isWaitingForInvite && this._isWaitingForInvite()) {
272
- console.log('Auto-accepting call from push notification');
273
- call.answer().catch((error) => {
274
- console.error('Failed to auto-accept call:', error);
275
- });
276
-
277
- if (this._onInviteAutoAccepted) {
278
- this._onInviteAutoAccepted();
279
- }
280
- }
281
-
282
333
  // Add to our call tracking - CallKit integration happens in _addCall
283
334
  this._addCall(call);
284
335
  }
285
336
 
286
- /**
287
- * Handle call state changes from the Telnyx client
288
- */
289
- private _handleCallStateChange(event: any): void {
290
- const callId = event.callId || event.id;
291
- const call = this._callMap.get(callId);
292
-
293
- if (call) {
294
- // The Call object will handle its own state updates through its listeners
295
- console.log(`Call ${callId} state changed to ${event.state}`);
296
- } else {
297
- console.warn(`Received state change for unknown call: ${callId}`);
298
- }
299
- }
300
-
301
- /**
302
- * Handle call updates from notifications
303
- */
304
- private _handleCallUpdate(callData: any): void {
305
- const callId = callData.id;
306
- const call = this._callMap.get(callId);
307
-
308
- if (call) {
309
- // Update call state based on the notification
310
- console.log(`Call ${callId} updated:`, callData);
311
- } else {
312
- console.warn(`Received update for unknown call: ${callId}`);
313
- }
314
- }
315
-
316
337
  /**
317
338
  * Add a call to our tracking
318
339
  */
@@ -365,6 +386,8 @@ export class CallStateController {
365
386
  private _removeCall(callId: string): void {
366
387
  const call = this._callMap.get(callId);
367
388
  if (call) {
389
+ console.log('CallStateController: Removing call:', callId);
390
+
368
391
  // CallKit cleanup is handled automatically by CallKitCoordinator
369
392
 
370
393
  call.dispose();
@@ -276,7 +276,7 @@ export class SessionManager {
276
276
  }
277
277
 
278
278
  /**
279
- * Internal method to establish connection
279
+ * Internal method to establish connection with or without push notification handling
280
280
  */
281
281
  private async _connect(): Promise<void> {
282
282
  if (!this._currentConfig) {
@@ -363,8 +363,13 @@ export class SessionManager {
363
363
  console.log(
364
364
  '🔧 SessionManager: Setting up CallStateController listeners before connection...'
365
365
  );
366
+ console.log('🔧 SessionManager: _onClientReady callback exists:', !!this._onClientReady);
366
367
  if (this._onClientReady) {
368
+ console.log('🔧 SessionManager: Calling _onClientReady callback now...');
367
369
  this._onClientReady();
370
+ console.log('🔧 SessionManager: _onClientReady callback completed');
371
+ } else {
372
+ console.log('🔧 SessionManager: No _onClientReady callback found');
368
373
  }
369
374
 
370
375
  // Connect to the platform AFTER processing push notification
@@ -393,6 +398,21 @@ export class SessionManager {
393
398
  this._telnyxClient.on('telnyx.client.ready', () => {
394
399
  console.log('Telnyx client ready');
395
400
  this._connectionState.next(TelnyxConnectionState.CONNECTED);
401
+
402
+ // Ensure CallStateController listeners are set up when client becomes ready
403
+ // This handles both initial connection and automatic reconnection
404
+ console.log(
405
+ '🔧 SessionManager: Client ready event - reinitializing CallStateController listeners'
406
+ );
407
+ if (this._onClientReady) {
408
+ console.log(
409
+ '🔧 SessionManager: Calling _onClientReady callback from client ready event...'
410
+ );
411
+ this._onClientReady();
412
+ console.log('🔧 SessionManager: _onClientReady callback completed from client ready event');
413
+ } else {
414
+ console.log('🔧 SessionManager: No _onClientReady callback found in client ready event');
415
+ }
396
416
  });
397
417
 
398
418
  this._telnyxClient.on('telnyx.client.error', (error: Error) => {
@@ -404,28 +424,6 @@ export class SessionManager {
404
424
  // We'll rely on the client-level events for now
405
425
  }
406
426
 
407
- /**
408
- * Attempt to reconnect after connection loss
409
- */
410
- private async _attemptReconnection(): Promise<void> {
411
- if (this._disposed || !this._currentConfig) {
412
- return;
413
- }
414
-
415
- // Simple reconnection logic - in production, this should include
416
- // exponential backoff and maximum retry attempts
417
- setTimeout(async () => {
418
- if (this.currentState === TelnyxConnectionState.RECONNECTING) {
419
- try {
420
- await this._connect();
421
- } catch (error) {
422
- console.error('Reconnection failed:', error);
423
- // Could implement retry logic here
424
- }
425
- }
426
- }, 3000);
427
- }
428
-
429
427
  /**
430
428
  * Extract the actual payload metadata from wrapped push notification payload
431
429
  */
@@ -434,10 +432,19 @@ export class SessionManager {
434
432
  let actualPayload = payload;
435
433
 
436
434
  if (payload.metadata && typeof payload.metadata === 'object') {
437
- // If there's a metadata wrapper, use that
435
+ // If there's a metadata wrapper, use that but preserve wrapper-level flags
438
436
  actualPayload = payload.metadata;
437
+
438
+ // Preserve important flags from the wrapper level
439
+ if (payload.from_notification !== undefined) {
440
+ actualPayload.from_notification = payload.from_notification;
441
+ }
442
+ if (payload.action !== undefined) {
443
+ actualPayload.action = payload.action;
444
+ }
445
+
439
446
  console.log(
440
- 'SessionManager: RELEASE DEBUG - Using metadata portion of payload:',
447
+ 'SessionManager: RELEASE DEBUG - Using metadata portion of payload with preserved flags:',
441
448
  JSON.stringify(actualPayload)
442
449
  );
443
450
  } else if (payload.action === 'incoming_call' && payload.metadata) {
@@ -446,8 +453,17 @@ export class SessionManager {
446
453
  const parsedMetadata =
447
454
  typeof payload.metadata === 'string' ? JSON.parse(payload.metadata) : payload.metadata;
448
455
  actualPayload = parsedMetadata;
456
+
457
+ // Preserve important flags from the wrapper level
458
+ if (payload.from_notification !== undefined) {
459
+ actualPayload.from_notification = payload.from_notification;
460
+ }
461
+ if (payload.action !== undefined) {
462
+ actualPayload.action = payload.action;
463
+ }
464
+
449
465
  console.log(
450
- 'SessionManager: RELEASE DEBUG - Using parsed metadata:',
466
+ 'SessionManager: RELEASE DEBUG - Using parsed metadata with preserved flags:',
451
467
  JSON.stringify(actualPayload)
452
468
  );
453
469
  } catch (error) {