@vgroup/dialbox 0.7.56 → 0.7.57

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.
@@ -1487,11 +1487,23 @@ class TwilioService {
1487
1487
  this.pendingIncomingDevices = new Map();
1488
1488
  this.extraDevices = new Map();
1489
1489
  this.incomingCallsMap = new Map();
1490
+ this.tokenRefreshInProgress = false;
1491
+ this.recoveryAttempts = 0;
1492
+ this.recoveryTimer = null;
1490
1493
  // User-controlled switch (in the call-progress UI). When ON, a new incoming
1491
1494
  // call's built-in Twilio ringtone is silenced so it does not disturb the
1492
1495
  // ongoing call. Source of truth so newly created (spare) devices inherit it.
1493
1496
  this.isIncomingMuted = false;
1494
1497
  this.initializeToken();
1498
+ // Safety net for long idle/sleep periods: the signaling socket can die silently
1499
+ // (OS suspends timers, tab is backgrounded) without ever firing 'error'/'unregistered'.
1500
+ // Re-check device health whenever the app becomes active/online again.
1501
+ document.addEventListener('visibilitychange', () => {
1502
+ if (document.visibilityState === 'visible') {
1503
+ this.ensureDeviceHealthy();
1504
+ }
1505
+ });
1506
+ window.addEventListener('online', () => this.ensureDeviceHealthy());
1495
1507
  }
1496
1508
  initializeToken() {
1497
1509
  if (this.tokenInitialized) {
@@ -1534,6 +1546,14 @@ class TwilioService {
1534
1546
  this.device = new Device(this.incomingCallToken, { allowIncomingWhileBusy: true });
1535
1547
  this.device.on('registered', () => {
1536
1548
  console.log('Twilio Device registered successfully');
1549
+ this.recoveryAttempts = 0;
1550
+ });
1551
+ // Twilio fires this ~3 minutes before the access token expires. Swap in a
1552
+ // fresh one via updateToken() so the signaling connection never has to drop
1553
+ // in the first place (this is the main cause of "idle for a while, BE still
1554
+ // notifies but Twilio incoming event never fires").
1555
+ this.device.on('tokenWillExpire', () => {
1556
+ this.refreshDeviceToken();
1537
1557
  });
1538
1558
  this.device.on('incoming', (call) => {
1539
1559
  if (this.acceptedCallList.length > 0 || this.primaryHasCall) {
@@ -1574,16 +1594,81 @@ class TwilioService {
1574
1594
  console.error('Twilio Device Error:', error);
1575
1595
  this.isShowIncomingCall = false;
1576
1596
  this.tokenInitialized = false;
1597
+ this.scheduleDeviceRecovery();
1577
1598
  });
1578
1599
  this.device.on('unregistered', () => {
1579
1600
  console.log('Twilio Device unregistered');
1580
1601
  this.isShowIncomingCall = false;
1581
1602
  this.tokenInitialized = false;
1603
+ this.scheduleDeviceRecovery();
1582
1604
  });
1583
1605
  // Register AFTER attaching all listeners
1584
1606
  this.device.register();
1585
1607
  console.log('device created', this.device);
1586
1608
  }
1609
+ // Proactively swap in a fresh access token ahead of expiry (see 'tokenWillExpire'
1610
+ // above) so the Device's signaling connection survives long idle periods.
1611
+ refreshDeviceToken() {
1612
+ if (this.tokenRefreshInProgress) {
1613
+ return;
1614
+ }
1615
+ this.tokenRefreshInProgress = true;
1616
+ this.extensionService.getIncomingCallToken(this.deviceId).subscribe({
1617
+ next: (data) => {
1618
+ this.tokenRefreshInProgress = false;
1619
+ this.incomingCallToken = data.token;
1620
+ localStorage.setItem('in-token', data.token);
1621
+ this.tokenInitialized = true;
1622
+ try {
1623
+ this.device?.updateToken(this.incomingCallToken);
1624
+ }
1625
+ catch (err) {
1626
+ console.error('Failed to apply refreshed Twilio token:', err);
1627
+ }
1628
+ },
1629
+ error: (err) => {
1630
+ this.tokenRefreshInProgress = false;
1631
+ console.error('Failed to refresh Twilio token ahead of expiry:', err);
1632
+ }
1633
+ });
1634
+ }
1635
+ // Self-heal after the Device dies (expired token, dropped signaling socket while
1636
+ // idle/asleep, etc). Never runs while a call is actually active, and backs off so a
1637
+ // genuinely logged-out/offline user doesn't retry forever.
1638
+ scheduleDeviceRecovery() {
1639
+ if (this.acceptedCallList.length > 0 || this.recoveryTimer) {
1640
+ return;
1641
+ }
1642
+ const delay = Math.min(30000, 2000 * Math.pow(2, this.recoveryAttempts));
1643
+ this.recoveryAttempts++;
1644
+ this.recoveryTimer = setTimeout(() => {
1645
+ this.recoveryTimer = null;
1646
+ const authToken = localStorage.getItem('ext_token');
1647
+ if (!authToken || authToken === 'logout') {
1648
+ this.recoveryAttempts = 0;
1649
+ return;
1650
+ }
1651
+ this.tokenInitialized = false;
1652
+ this.initializeToken().subscribe({
1653
+ error: (err) => console.error('Twilio device recovery attempt failed:', err)
1654
+ });
1655
+ }, delay);
1656
+ }
1657
+ // Safety net for the case where the underlying socket dies silently during a long
1658
+ // sleep/idle period without ever firing 'error'/'unregistered' (the OS can suspend
1659
+ // timers/sockets outright) — re-check device health whenever the app wakes up.
1660
+ ensureDeviceHealthy() {
1661
+ if (this.acceptedCallList.length > 0) {
1662
+ return;
1663
+ }
1664
+ if (!this.device || this.device.state !== 'registered') {
1665
+ console.log('Twilio device unhealthy after wake/visibility change, re-initializing');
1666
+ this.tokenInitialized = false;
1667
+ this.initializeToken().subscribe({
1668
+ error: (err) => console.error('Twilio device recovery failed:', err)
1669
+ });
1670
+ }
1671
+ }
1587
1672
  saveContact(payload) {
1588
1673
  const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json', 'Auth-Key': "Bearer " + localStorage.getItem('ext_token') }) };
1589
1674
  return this.http.post(environment.apiUrl + '/utilities/phonebook/add/contacts/manually', payload, httpOptions);
@@ -2769,6 +2854,9 @@ class CallProgressComponent {
2769
2854
  deviceId: this.deviceId,
2770
2855
  clientSid: this.call?.parameters['CallSid']
2771
2856
  });
2857
+ if (addClientParticipantRes?.status != 200) {
2858
+ swal("Error", response?.message.join("<br/>"), "error");
2859
+ }
2772
2860
  this.addRes = await this.addParticipantToCall({
2773
2861
  from: callData?.from,
2774
2862
  route: "OUTGOING",
@@ -3209,10 +3297,13 @@ class CallProgressComponent {
3209
3297
  mute: ourNumberInfo?.mute || false
3210
3298
  };
3211
3299
  this.extensionService.holdParticipant(payload).subscribe((res) => {
3300
+ console.log('holdParticipant res', res);
3212
3301
  if (res?.status != 200) {
3213
3302
  swal("Error", res?.message?.join?.("<br/>") || 'Internal Server Error', "error");
3214
3303
  }
3215
- this.add(incomingCallInfo, ourNumberInfo);
3304
+ else {
3305
+ this.add(incomingCallInfo, ourNumberInfo);
3306
+ }
3216
3307
  });
3217
3308
  this.isConferenceCallHold = true;
3218
3309
  }
@@ -4371,11 +4462,9 @@ class DialboxComponent {
4371
4462
  // intercepted with a custom modal. Triggers the native confirmation so the
4372
4463
  // user does not lose the call accidentally. (Browser controls the wording.)
4373
4464
  if (this.isCallInProgress || this.incomingCallsList?.length) {
4374
- // $event.preventDefault();
4375
4465
  const confirmationMessage = 'Reloading this page will disconnect your active call. Are you sure you want to continue?';
4376
4466
  $event.returnValue = confirmationMessage;
4377
4467
  $event.preventDefault();
4378
- // this.confirmReloadDuringCall();
4379
4468
  return confirmationMessage;
4380
4469
  }
4381
4470
  return undefined;
@@ -4385,27 +4474,7 @@ class DialboxComponent {
4385
4474
  if (this.isReloadConfirmed) {
4386
4475
  return;
4387
4476
  }
4388
- // Fires only after the user confirmed the native reload/navigation. Remove
4389
- // the conference participants using a keepalive request so it survives the
4390
- // page being torn down (a normal HttpClient call would be cancelled).
4391
- if (this.isCallInProgress || this.incomingCallsList?.length) {
4392
- // this.removeParticipantsOnUnload();
4393
- }
4394
4477
  }
4395
- // private removeParticipantsOnUnload() {
4396
- // const conferenceId = this.incomingCallsList?.[0]?.conferenceId;
4397
- // if (!conferenceId) {
4398
- // return;
4399
- // }
4400
- // try {
4401
- // fetch(
4402
- // `${environment.apiUrl}/utilities/ext/ur/remove/participant/all/${conferenceId}`,
4403
- // { method: 'GET', keepalive: true }
4404
- // );
4405
- // } catch {
4406
- // // Best-effort cleanup during unload; nothing else we can do here.
4407
- // }
4408
- // }
4409
4478
  constructor(twilioService, extService, ipService, extensionService, cdk, router, incomeingCallSocketService) {
4410
4479
  this.twilioService = twilioService;
4411
4480
  this.extService = extService;
@@ -4476,6 +4545,7 @@ class DialboxComponent {
4476
4545
  // Set once the user confirms an intentional reload via our custom modal,
4477
4546
  // so the native beforeunload prompt is not shown on top of it.
4478
4547
  this.isReloadConfirmed = false;
4548
+ this.nullParticipantCallList = [];
4479
4549
  this.contactNameForLoader = '';
4480
4550
  this.contactImgForLoader = 'assets/images/user.jpg';
4481
4551
  this.incomingFromImage = '';
@@ -4487,9 +4557,6 @@ class DialboxComponent {
4487
4557
  }
4488
4558
  this.incomeingCallSocketService.createAudioBase();
4489
4559
  }
4490
- async getRemoveParticipants(participantId, conferenceId) {
4491
- return await this.extensionService.getRemoveParticipants(participantId, conferenceId).toPromise();
4492
- }
4493
4560
  initializeTwilio() {
4494
4561
  if (!this.isInitialized) {
4495
4562
  this.token = localStorage.getItem('ext_token') || '';
@@ -4525,7 +4592,11 @@ class DialboxComponent {
4525
4592
  const socketConferenceIds = incomingCallData.map((c) => c.conferenceId);
4526
4593
  incomingCallData.forEach((callInfo) => {
4527
4594
  // Check if WE are an active participant in this conference
4528
- const ourNumber = callInfo.participants.find((resData) => ((this.deviceNumberList.includes(resData?.from) && resData?.direction == 'outgoing-call' && resData?.client) ||
4595
+ if (!callInfo?.participants?.length && !this.nullParticipantCallList.includes(callInfo?.conferenceId)) {
4596
+ this.nullParticipantCallList.push(callInfo?.conferenceId);
4597
+ swal("Error", 'Call not connected facing some issue', "error");
4598
+ }
4599
+ const ourNumber = callInfo?.participants.find((resData) => ((this.deviceNumberList.includes(resData?.from) && resData?.direction == 'outgoing-call' && resData?.client) ||
4529
4600
  (this.deviceNumberList.includes(resData?.to) && resData?.direction == 'incoming-call' && resData?.client &&
4530
4601
  resData?.callStatus !== 'completed' &&
4531
4602
  (resData?.callStatus == 'participant-join' || resData?.callStatus == 'participant-mute' ||