@webex/contact-center 3.12.0-next.67 → 3.12.0-next.69

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/src/cc.ts CHANGED
@@ -357,6 +357,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
357
357
  this.$webex.once(READY, () => {
358
358
  // @ts-ignore
359
359
  this.$config = this.config;
360
+ this.validatePluginConfig();
360
361
 
361
362
  /**
362
363
  * This is used for handling the async requests by sending webex.request and wait for corresponding websocket event.
@@ -430,6 +431,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
430
431
  this.trigger(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, task);
431
432
  };
432
433
 
434
+ /**
435
+ * Handles multi-login hydrate events emitted when browser login accepts an incoming call.
436
+ * @private
437
+ * @param {ITask} task The task object associated with multi-login hydrate
438
+ */
439
+ private handleTaskMultiLoginHydrate = (task: ITask) => {
440
+ // @ts-ignore
441
+ this.trigger(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
442
+ };
443
+
433
444
  private handleRTDWebsocketMessage = (payload: string) => {
434
445
  this.taskManager.handleRealtimeWebsocketEvent(payload);
435
446
  };
@@ -443,6 +454,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
443
454
  this.taskManager.on(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
444
455
  this.taskManager.on(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
445
456
  this.taskManager.on(TASK_EVENTS.TASK_MERGED, this.handleTaskMerged);
457
+ this.taskManager.on(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this.handleTaskMultiLoginHydrate);
446
458
  this.taskManager.on(
447
459
  TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
448
460
  this.handleCampaignPreviewReservation
@@ -576,6 +588,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
576
588
 
577
589
  this.taskManager.off(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
578
590
  this.taskManager.off(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
591
+ this.taskManager.off(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this.handleTaskMultiLoginHydrate);
579
592
  this.taskManager.off(
580
593
  TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
581
594
  this.handleCampaignPreviewReservation
@@ -724,6 +737,30 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
724
737
  }
725
738
  }
726
739
 
740
+ /**
741
+ * Checks whether WebRTC registration should be skipped by config.
742
+ * @returns {boolean}
743
+ * @private
744
+ */
745
+ private isWebRTCRegistrationDisabled(): boolean {
746
+ return this.$config?.disableWebRTCRegistration === true;
747
+ }
748
+
749
+ /**
750
+ * Validates contact-center plugin configuration before service initialization
751
+ * @private
752
+ */
753
+ private validatePluginConfig(): void {
754
+ if (
755
+ this.$config?.disableWebRTCRegistration === true &&
756
+ this.$config?.allowMultiLogin === false
757
+ ) {
758
+ throw new Error(
759
+ 'Invalid Contact Center configuration: disableWebRTCRegistration cannot be true when allowMultiLogin is false. Enable allowMultiLogin or allow WebRTC registration so an SDK instance can receive Mobius/WebRTC task events.'
760
+ );
761
+ }
762
+ }
763
+
727
764
  /**
728
765
  * Connects to the websocket and fetches the agent profile
729
766
  * @returns {Promise<Profile>} Agent profile information
@@ -785,7 +822,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
785
822
 
786
823
  if (
787
824
  this.agentConfig.webRtcEnabled &&
788
- this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER)
825
+ this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER) &&
826
+ !this.isWebRTCRegistrationDisabled()
789
827
  ) {
790
828
  try {
791
829
  await this.$webex.internal.mercury.connect();
@@ -799,6 +837,14 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
799
837
  method: METHODS.CONNECT_WEBSOCKET,
800
838
  });
801
839
  }
840
+ } else if (this.isWebRTCRegistrationDisabled()) {
841
+ LoggerProxy.info(
842
+ 'Skipping Mobius registration because disableWebRTCRegistration is enabled',
843
+ {
844
+ module: CC_FILE,
845
+ method: METHODS.CONNECT_WEBSOCKET,
846
+ }
847
+ );
802
848
  }
803
849
 
804
850
  if (this.$config && this.$config.allowAutomatedRelogin) {
@@ -899,8 +945,24 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
899
945
  },
900
946
  });
901
947
 
902
- if (this.agentConfig.webRtcEnabled && data.loginOption === LoginOption.BROWSER) {
948
+ if (
949
+ this.agentConfig.webRtcEnabled &&
950
+ data.loginOption === LoginOption.BROWSER &&
951
+ !this.isWebRTCRegistrationDisabled()
952
+ ) {
903
953
  await this.webCallingService.registerWebCallingLine();
954
+ } else if (
955
+ this.agentConfig.webRtcEnabled &&
956
+ data.loginOption === LoginOption.BROWSER &&
957
+ this.isWebRTCRegistrationDisabled()
958
+ ) {
959
+ LoggerProxy.info(
960
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
961
+ {
962
+ module: CC_FILE,
963
+ method: METHODS.STATION_LOGIN,
964
+ }
965
+ );
904
966
  }
905
967
 
906
968
  const resp = await loginResponse;
@@ -1405,6 +1467,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1405
1467
  this.agentConfig.deviceType = deviceType;
1406
1468
  switch (deviceType) {
1407
1469
  case LoginOption.BROWSER:
1470
+ if (this.isWebRTCRegistrationDisabled()) {
1471
+ LoggerProxy.info(
1472
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
1473
+ {
1474
+ module: CC_FILE,
1475
+ method: METHODS.HANDLE_DEVICE_TYPE,
1476
+ }
1477
+ );
1478
+ break;
1479
+ }
1408
1480
  try {
1409
1481
  await this.webCallingService.registerWebCallingLine();
1410
1482
  } catch (error) {
@@ -301,6 +301,8 @@ export default class TaskManager extends EventEmitter {
301
301
  task = this.updateTaskData(task, payload.data);
302
302
  task.emit(TASK_EVENTS.TASK_ASSIGNED, task);
303
303
  }
304
+ // We emit the event to the task manager to send the task data to the instance without mobius registration in case of multi login with desktop login
305
+ this.emit(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
304
306
  break;
305
307
  case CC_EVENTS.AGENT_CONTACT_UNASSIGNED:
306
308
  task = this.updateTaskData(task, {
@@ -506,6 +508,8 @@ export default class TaskManager extends EventEmitter {
506
508
  // Fire only if you are the agent who initiated the consult
507
509
  task.emit(TASK_EVENTS.TASK_CONSULTING, task);
508
510
  }
511
+ // Also hydrate multi-login listeners when consulting state is received.
512
+ this.emit(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
509
513
  break;
510
514
  case CC_EVENTS.AGENT_CONSULT_FAILED:
511
515
  // This can only be received by the agent who initiated the consult.
@@ -602,6 +602,20 @@ export enum TASK_EVENTS {
602
602
  * ```
603
603
  */
604
604
  TASK_CAMPAIGN_CONTACT_UPDATED = 'task:campaignContactUpdated',
605
+
606
+ /**
607
+ * Triggered when a we get accept an incoming web call
608
+ * This is used to send task data to the instance without mobius registration in case of multi login
609
+ * @example
610
+ * ```typescript
611
+ * task.on(TASK_EVENTS.TASK_MULIT_LOGIN_HYDRATE, (task: ITask) => {
612
+ * console.log('Mulit login detected:', task.data.interactionId);
613
+ * // Handle mulit login
614
+ * });
615
+ * ```
616
+ */
617
+
618
+ TASK_MULTI_LOGIN_HYDRATE = 'task:multiLoginHydrate',
605
619
  }
606
620
 
607
621
  /**
package/src/types.ts CHANGED
@@ -156,6 +156,8 @@ export interface CCPluginConfig {
156
156
  };
157
157
  /** Configuration for the calling client */
158
158
  callingClientConfig: CallingClientConfig;
159
+ /** Whether to skip Mobius/WebRTC registration for browser login flows */
160
+ disableWebRTCRegistration?: boolean;
159
161
  }
160
162
 
161
163
  /**
@@ -202,6 +202,35 @@ describe('webex.cc', () => {
202
202
  webex.emit('ready');
203
203
  });
204
204
 
205
+ it('should throw when WebRTC registration is disabled without multi-login', () => {
206
+ const invalidWebex = MockWebex({
207
+ children: {
208
+ mercury: Mercury,
209
+ },
210
+ logger: {
211
+ log: jest.fn(),
212
+ error: jest.fn(),
213
+ info: jest.fn(),
214
+ },
215
+ credentials: {
216
+ getOrgId: jest.fn(() => 'mockOrgId'),
217
+ },
218
+ config: {
219
+ ...config,
220
+ cc: {
221
+ ...config.cc,
222
+ allowMultiLogin: false,
223
+ disableWebRTCRegistration: true,
224
+ },
225
+ },
226
+ once: jest.fn((event, callback) => callback()),
227
+ }) as unknown as WebexSDK;
228
+
229
+ expect(() => new ContactCenter({parent: invalidWebex})).toThrow(
230
+ 'Invalid Contact Center configuration: disableWebRTCRegistration cannot be true when allowMultiLogin is false. Enable allowMultiLogin or allow WebRTC registration so an SDK instance can receive Mobius/WebRTC task events.'
231
+ );
232
+ });
233
+
205
234
  describe('cc.getDeviceId', () => {
206
235
  it('should return dialNumber when loginOption is EXTENSION', () => {
207
236
  const loginOption = LoginOption.EXTENSION;
@@ -336,6 +365,10 @@ describe('webex.cc', () => {
336
365
  TASK_EVENTS.TASK_HYDRATE,
337
366
  expect.any(Function)
338
367
  );
368
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
369
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
370
+ expect.any(Function)
371
+ );
339
372
  expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
340
373
  expect(webex.cc.services.rtdWebSocketManager.initWebSocket).toHaveBeenCalledWith({
341
374
  body: {
@@ -484,6 +517,10 @@ describe('webex.cc', () => {
484
517
  TASK_EVENTS.TASK_HYDRATE,
485
518
  expect.any(Function)
486
519
  );
520
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
521
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
522
+ expect.any(Function)
523
+ );
487
524
  expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
488
525
 
489
526
  expect(configSpy).toHaveBeenCalled();
@@ -1586,6 +1623,10 @@ describe('webex.cc', () => {
1586
1623
  TASK_EVENTS.TASK_HYDRATE,
1587
1624
  expect.any(Function)
1588
1625
  );
1626
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1627
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1628
+ expect.any(Function)
1629
+ );
1589
1630
  expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
1590
1631
  expect(webex.cc.services.rtdWebSocketManager.off).toHaveBeenCalledWith(
1591
1632
  'message',
@@ -1642,6 +1683,13 @@ describe('webex.cc', () => {
1642
1683
  const [, hydrateCallback] = hydrateCalls[0];
1643
1684
  expect(hydrateCallback).toBe(webex.cc['handleTaskHydrate']);
1644
1685
 
1686
+ const multiLoginHydrateCalls = mockTaskManager.off.mock.calls.filter(
1687
+ ([evt]) => evt === TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE
1688
+ );
1689
+ expect(multiLoginHydrateCalls).toHaveLength(1);
1690
+ const [, multiLoginHydrateCallback] = multiLoginHydrateCalls[0];
1691
+ expect(multiLoginHydrateCallback).toBe(webex.cc['handleTaskMultiLoginHydrate']);
1692
+
1645
1693
  const messageCalls = mockWebSocketManager.off.mock.calls.filter(([evt]) => evt === 'message');
1646
1694
  expect(messageCalls).toHaveLength(1);
1647
1695
  const [, messageCallback] = messageCalls[0];
@@ -1667,6 +1715,10 @@ describe('webex.cc', () => {
1667
1715
  TASK_EVENTS.TASK_HYDRATE,
1668
1716
  expect.any(Function)
1669
1717
  );
1718
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1719
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1720
+ expect.any(Function)
1721
+ );
1670
1722
  expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
1671
1723
  expect(webex.cc.services.connectionService.off).toHaveBeenCalledWith(
1672
1724
  'connectionLost',
@@ -1710,6 +1762,10 @@ describe('webex.cc', () => {
1710
1762
  TASK_EVENTS.TASK_HYDRATE,
1711
1763
  expect.any(Function)
1712
1764
  );
1765
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1766
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1767
+ expect.any(Function)
1768
+ );
1713
1769
 
1714
1770
  expect(LoggerProxy.error).toHaveBeenCalledWith(`Error during deregister: ${mockError}`, {
1715
1771
  module: CC_FILE,
@@ -319,6 +319,10 @@ describe('TaskManager', () => {
319
319
  TASK_EVENTS.TASK_ASSIGNED,
320
320
  taskManager.getTask(taskId)
321
321
  );
322
+ expect(taskIncomingSpy).toHaveBeenCalledWith(
323
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
324
+ taskManager.getTask(taskId)
325
+ );
322
326
  });
323
327
 
324
328
  it('should handle WebSocket message for AGENT_CONTACT_RESERVED and emit task:incoming for extension case', () => {
@@ -1537,6 +1541,7 @@ describe('TaskManager', () => {
1537
1541
  });
1538
1542
 
1539
1543
  const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
1544
+ const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
1540
1545
  webSocketManagerMock.emit('message', JSON.stringify(initialConsultingPayload));
1541
1546
  webSocketManagerMock.emit('message', JSON.stringify(consultingPayload));
1542
1547
  expect(taskManager.getTask(taskId).data.isConsulted).toBe(true);
@@ -1544,6 +1549,10 @@ describe('TaskManager', () => {
1544
1549
  TASK_EVENTS.TASK_CONSULT_ACCEPTED,
1545
1550
  taskManager.getTask(taskId)
1546
1551
  );
1552
+ expect(taskManagerEmitSpy).toHaveBeenCalledWith(
1553
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1554
+ taskManager.getTask(taskId)
1555
+ );
1547
1556
  });
1548
1557
 
1549
1558
  it('should emit TASK_CONSULT_ENDED event on AGENT_CONSULT_ENDED event', () => {
@@ -1812,6 +1821,7 @@ describe('TaskManager', () => {
1812
1821
  webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
1813
1822
  taskManager.getTask(taskId).data.isConsulted = false;
1814
1823
  const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
1824
+ const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
1815
1825
  const consultingPayload = {
1816
1826
  data: {
1817
1827
  ...initalPayload.data,
@@ -1825,6 +1835,10 @@ describe('TaskManager', () => {
1825
1835
  TASK_EVENTS.TASK_CONSULTING,
1826
1836
  taskManager.getTask(taskId)
1827
1837
  );
1838
+ expect(taskManagerEmitSpy).toHaveBeenCalledWith(
1839
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1840
+ taskManager.getTask(taskId)
1841
+ );
1828
1842
  });
1829
1843
 
1830
1844
  it('should emit TASK_END event on AGENT_CONTACT_UNASSIGNED', () => {