@webex/contact-center 3.10.0-next.1 → 3.10.0-next.10

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 (41) hide show
  1. package/dist/cc.js +11 -0
  2. package/dist/cc.js.map +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/services/config/types.js +2 -2
  5. package/dist/services/config/types.js.map +1 -1
  6. package/dist/services/core/Utils.js +90 -71
  7. package/dist/services/core/Utils.js.map +1 -1
  8. package/dist/services/core/constants.js +17 -1
  9. package/dist/services/core/constants.js.map +1 -1
  10. package/dist/services/task/TaskManager.js +62 -29
  11. package/dist/services/task/TaskManager.js.map +1 -1
  12. package/dist/services/task/TaskUtils.js +33 -5
  13. package/dist/services/task/TaskUtils.js.map +1 -1
  14. package/dist/services/task/index.js +45 -39
  15. package/dist/services/task/index.js.map +1 -1
  16. package/dist/services/task/types.js +2 -4
  17. package/dist/services/task/types.js.map +1 -1
  18. package/dist/types/cc.d.ts +6 -0
  19. package/dist/types/index.d.ts +1 -1
  20. package/dist/types/services/config/types.d.ts +4 -4
  21. package/dist/types/services/core/Utils.d.ts +32 -17
  22. package/dist/types/services/core/constants.d.ts +14 -0
  23. package/dist/types/services/task/TaskUtils.d.ts +17 -3
  24. package/dist/types/services/task/types.d.ts +25 -13
  25. package/dist/webex.js +1 -1
  26. package/package.json +8 -8
  27. package/src/cc.ts +11 -0
  28. package/src/index.ts +1 -0
  29. package/src/services/config/types.ts +2 -2
  30. package/src/services/core/Utils.ts +101 -85
  31. package/src/services/core/constants.ts +16 -0
  32. package/src/services/task/TaskManager.ts +77 -22
  33. package/src/services/task/TaskUtils.ts +37 -5
  34. package/src/services/task/index.ts +50 -63
  35. package/src/services/task/types.ts +26 -13
  36. package/test/unit/spec/services/core/Utils.ts +262 -31
  37. package/test/unit/spec/services/task/TaskManager.ts +370 -1
  38. package/test/unit/spec/services/task/TaskUtils.ts +6 -6
  39. package/test/unit/spec/services/task/index.ts +323 -68
  40. package/umd/contact-center.min.js +2 -2
  41. package/umd/contact-center.min.js.map +1 -1
@@ -479,6 +479,73 @@ describe('TaskManager', () => {
479
479
  );
480
480
  });
481
481
 
482
+ it('should set isConferenceInProgress correctly when creating task via AGENT_CONTACT with conference in progress', () => {
483
+ const testAgentId = '723a8ffb-a26e-496d-b14a-ff44fb83b64f';
484
+ taskManager.setAgentId(testAgentId);
485
+ taskManager.taskCollection = [];
486
+
487
+ const payload = {
488
+ data: {
489
+ ...initalPayload.data,
490
+ type: CC_EVENTS.AGENT_CONTACT,
491
+ interaction: {
492
+ mediaType: 'telephony',
493
+ state: 'conference',
494
+ participants: {
495
+ [testAgentId]: { pType: 'Agent', hasLeft: false },
496
+ 'agent-2': { pType: 'Agent', hasLeft: false },
497
+ 'customer-1': { pType: 'Customer', hasLeft: false },
498
+ },
499
+ media: {
500
+ [taskId]: {
501
+ mType: 'mainCall',
502
+ participants: [testAgentId, 'agent-2', 'customer-1'],
503
+ },
504
+ },
505
+ },
506
+ },
507
+ };
508
+
509
+ webSocketManagerMock.emit('message', JSON.stringify(payload));
510
+
511
+ const createdTask = taskManager.getTask(taskId);
512
+ expect(createdTask).toBeDefined();
513
+ expect(createdTask.data.isConferenceInProgress).toBe(true);
514
+ });
515
+
516
+ it('should set isConferenceInProgress to false when creating task via AGENT_CONTACT with only one agent', () => {
517
+ const testAgentId = '723a8ffb-a26e-496d-b14a-ff44fb83b64f';
518
+ taskManager.setAgentId(testAgentId);
519
+ taskManager.taskCollection = [];
520
+
521
+ const payload = {
522
+ data: {
523
+ ...initalPayload.data,
524
+ type: CC_EVENTS.AGENT_CONTACT,
525
+ interaction: {
526
+ mediaType: 'telephony',
527
+ state: 'connected',
528
+ participants: {
529
+ [testAgentId]: { pType: 'Agent', hasLeft: false },
530
+ 'customer-1': { pType: 'Customer', hasLeft: false },
531
+ },
532
+ media: {
533
+ [taskId]: {
534
+ mType: 'mainCall',
535
+ participants: [testAgentId, 'customer-1'],
536
+ },
537
+ },
538
+ },
539
+ },
540
+ };
541
+
542
+ webSocketManagerMock.emit('message', JSON.stringify(payload));
543
+
544
+ const createdTask = taskManager.getTask(taskId);
545
+ expect(createdTask).toBeDefined();
546
+ expect(createdTask.data.isConferenceInProgress).toBe(false);
547
+ });
548
+
482
549
  it('should emit TASK_END event on AGENT_WRAPUP event', () => {
483
550
  webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
484
551
 
@@ -1428,6 +1495,18 @@ describe('TaskManager', () => {
1428
1495
  interactionId: taskId,
1429
1496
  participantId: 'new-participant-123',
1430
1497
  participantType: 'agent',
1498
+ interaction: {
1499
+ participants: {
1500
+ [agentId]: { pType: 'Agent', hasLeft: false },
1501
+ 'new-participant-123': { pType: 'Agent', hasLeft: false },
1502
+ },
1503
+ media: {
1504
+ [taskId]: {
1505
+ mType: 'mainCall',
1506
+ participants: [agentId, 'new-participant-123'],
1507
+ },
1508
+ },
1509
+ },
1431
1510
  },
1432
1511
  };
1433
1512
 
@@ -1438,7 +1517,86 @@ describe('TaskManager', () => {
1438
1517
  // No specific task event emission for participant joined - just data update
1439
1518
  });
1440
1519
 
1520
+ it('should call updateTaskData only once for PARTICIPANT_JOINED_CONFERENCE with pre-calculated isConferenceInProgress', () => {
1521
+ const payload = {
1522
+ data: {
1523
+ type: CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE,
1524
+ interactionId: taskId,
1525
+ participantId: 'new-agent-789',
1526
+ interaction: {
1527
+ participants: {
1528
+ [agentId]: { pType: 'Agent', hasLeft: false },
1529
+ 'agent-2': { pType: 'Agent', hasLeft: false },
1530
+ 'new-agent-789': { pType: 'Agent', hasLeft: false },
1531
+ 'customer-1': { pType: 'Customer', hasLeft: false },
1532
+ },
1533
+ media: {
1534
+ [taskId]: {
1535
+ mType: 'mainCall',
1536
+ participants: [agentId, 'agent-2', 'new-agent-789', 'customer-1'],
1537
+ },
1538
+ },
1539
+ },
1540
+ },
1541
+ };
1542
+
1543
+ const updateTaskDataSpy = jest.spyOn(task, 'updateTaskData');
1544
+
1545
+ webSocketManagerMock.emit('message', JSON.stringify(payload));
1546
+
1547
+ // Verify updateTaskData was called exactly once
1548
+ expect(updateTaskDataSpy).toHaveBeenCalledTimes(1);
1549
+
1550
+ // Verify it was called with isConferenceInProgress already calculated
1551
+ expect(updateTaskDataSpy).toHaveBeenCalledWith(
1552
+ expect.objectContaining({
1553
+ participantId: 'new-agent-789',
1554
+ isConferenceInProgress: true, // 3 active agents
1555
+ })
1556
+ );
1557
+
1558
+ expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_JOINED, task);
1559
+ });
1560
+
1441
1561
  describe('PARTICIPANT_LEFT_CONFERENCE event handling', () => {
1562
+ it('should call updateTaskData only once for PARTICIPANT_LEFT_CONFERENCE with pre-calculated isConferenceInProgress', () => {
1563
+ const payload = {
1564
+ data: {
1565
+ type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
1566
+ interactionId: taskId,
1567
+ interaction: {
1568
+ participants: {
1569
+ [agentId]: { pType: 'Agent', hasLeft: false },
1570
+ 'agent-2': { pType: 'Agent', hasLeft: true }, // This agent left
1571
+ 'customer-1': { pType: 'Customer', hasLeft: false },
1572
+ },
1573
+ media: {
1574
+ [taskId]: {
1575
+ mType: 'mainCall',
1576
+ participants: [agentId, 'customer-1'], // agent-2 removed from participants
1577
+ },
1578
+ },
1579
+ },
1580
+ },
1581
+ };
1582
+
1583
+ const updateTaskDataSpy = jest.spyOn(task, 'updateTaskData');
1584
+
1585
+ webSocketManagerMock.emit('message', JSON.stringify(payload));
1586
+
1587
+ // Verify updateTaskData was called exactly once
1588
+ expect(updateTaskDataSpy).toHaveBeenCalledTimes(1);
1589
+
1590
+ // Verify it was called with isConferenceInProgress already calculated
1591
+ expect(updateTaskDataSpy).toHaveBeenCalledWith(
1592
+ expect.objectContaining({
1593
+ isConferenceInProgress: false, // Only 1 active agent remains
1594
+ })
1595
+ );
1596
+
1597
+ expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
1598
+ });
1599
+
1442
1600
  it('should emit TASK_PARTICIPANT_LEFT event when participant leaves conference', () => {
1443
1601
  const payload = {
1444
1602
  data: {
@@ -1735,6 +1893,217 @@ describe('TaskManager', () => {
1735
1893
  expect(otherTask.data.isConferencing).toBeUndefined();
1736
1894
  expect(otherTask.emit).not.toHaveBeenCalled();
1737
1895
  });
1738
- });
1896
+ });
1897
+
1898
+ describe('CONTACT_MERGED event handling', () => {
1899
+ let task;
1900
+ let taskEmitSpy;
1901
+ let managerEmitSpy;
1902
+
1903
+ beforeEach(() => {
1904
+ // Create initial task
1905
+ webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
1906
+ task = taskManager.getTask(taskId);
1907
+ taskEmitSpy = jest.spyOn(task, 'emit');
1908
+ managerEmitSpy = jest.spyOn(taskManager, 'emit');
1909
+ });
1910
+
1911
+ it('should update existing task data and emit TASK_MERGED event when CONTACT_MERGED is received', () => {
1912
+ const mergedPayload = {
1913
+ data: {
1914
+ type: CC_EVENTS.CONTACT_MERGED,
1915
+ interactionId: taskId,
1916
+ agentId: taskDataMock.agentId,
1917
+ interaction: {
1918
+ ...taskDataMock.interaction,
1919
+ state: 'merged',
1920
+ customField: 'updated-value',
1921
+ },
1922
+ },
1923
+ };
1924
+
1925
+ webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
1926
+
1927
+ const updatedTask = taskManager.getTask(taskId);
1928
+ expect(updatedTask).toBeDefined();
1929
+ expect(updatedTask.data.interaction.customField).toBe('updated-value');
1930
+ expect(updatedTask.data.interaction.state).toBe('merged');
1931
+ expect(managerEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MERGED, updatedTask);
1932
+ });
1933
+
1934
+ it('should create new task when CONTACT_MERGED is received for non-existing task', () => {
1935
+ const newMergedTaskId = 'new-merged-task-id';
1936
+ const mergedPayload = {
1937
+ data: {
1938
+ type: CC_EVENTS.CONTACT_MERGED,
1939
+ interactionId: newMergedTaskId,
1940
+ agentId: taskDataMock.agentId,
1941
+ interaction: {
1942
+ mediaType: 'telephony',
1943
+ state: 'merged',
1944
+ participants: {
1945
+ [taskDataMock.agentId]: {
1946
+ isWrapUp: false,
1947
+ hasJoined: true,
1948
+ },
1949
+ },
1950
+ },
1951
+ },
1952
+ };
1953
+
1954
+ // Verify task doesn't exist before
1955
+ expect(taskManager.getTask(newMergedTaskId)).toBeUndefined();
1956
+
1957
+ webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
1958
+
1959
+ // Verify task was created
1960
+ const newTask = taskManager.getTask(newMergedTaskId);
1961
+ expect(newTask).toBeDefined();
1962
+ expect(newTask.data.interactionId).toBe(newMergedTaskId);
1963
+ expect(managerEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MERGED, newTask);
1964
+ });
1965
+
1966
+ it('should remove child task when childInteractionId is present in CONTACT_MERGED', () => {
1967
+ const childTaskId = 'child-task-id';
1968
+ const parentTaskId = 'parent-task-id';
1969
+
1970
+ // Create child task
1971
+ const childPayload = {
1972
+ data: {
1973
+ type: CC_EVENTS.AGENT_CONTACT_RESERVED,
1974
+ interactionId: childTaskId,
1975
+ agentId: taskDataMock.agentId,
1976
+ interaction: {mediaType: 'telephony'},
1977
+ },
1978
+ };
1979
+ webSocketManagerMock.emit('message', JSON.stringify(childPayload));
1980
+
1981
+ // Verify child task exists
1982
+ expect(taskManager.getTask(childTaskId)).toBeDefined();
1983
+
1984
+ // Create parent task
1985
+ const parentPayload = {
1986
+ data: {
1987
+ type: CC_EVENTS.AGENT_CONTACT_RESERVED,
1988
+ interactionId: parentTaskId,
1989
+ agentId: taskDataMock.agentId,
1990
+ interaction: {mediaType: 'telephony'},
1991
+ },
1992
+ };
1993
+ webSocketManagerMock.emit('message', JSON.stringify(parentPayload));
1994
+
1995
+ // Send CONTACT_MERGED with childInteractionId
1996
+ const mergedPayload = {
1997
+ data: {
1998
+ type: CC_EVENTS.CONTACT_MERGED,
1999
+ interactionId: parentTaskId,
2000
+ childInteractionId: childTaskId,
2001
+ agentId: taskDataMock.agentId,
2002
+ interaction: {
2003
+ mediaType: 'telephony',
2004
+ state: 'merged',
2005
+ },
2006
+ },
2007
+ };
2008
+
2009
+ webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
2010
+
2011
+ // Verify child task was removed
2012
+ expect(taskManager.getTask(childTaskId)).toBeUndefined();
2013
+
2014
+ // Verify parent task still exists
2015
+ expect(taskManager.getTask(parentTaskId)).toBeDefined();
2016
+
2017
+ // Verify TASK_MERGED event was emitted
2018
+ expect(managerEmitSpy).toHaveBeenCalledWith(
2019
+ TASK_EVENTS.TASK_MERGED,
2020
+ expect.objectContaining({
2021
+ data: expect.objectContaining({
2022
+ interactionId: parentTaskId,
2023
+ }),
2024
+ })
2025
+ );
2026
+ });
2027
+
2028
+ it('should handle CONTACT_MERGED with EP-DN participant correctly', () => {
2029
+ const epdnTaskId = 'epdn-merged-task';
2030
+ const mergedPayload = {
2031
+ data: {
2032
+ type: CC_EVENTS.CONTACT_MERGED,
2033
+ interactionId: epdnTaskId,
2034
+ agentId: taskDataMock.agentId,
2035
+ interaction: {
2036
+ mediaType: 'telephony',
2037
+ state: 'merged',
2038
+ participants: {
2039
+ [taskDataMock.agentId]: {
2040
+ type: 'Agent',
2041
+ isWrapUp: false,
2042
+ hasJoined: true,
2043
+ },
2044
+ 'epdn-participant': {
2045
+ type: 'EpDn',
2046
+ epId: 'entry-point-123',
2047
+ isWrapUp: false,
2048
+ },
2049
+ },
2050
+ },
2051
+ },
2052
+ };
2053
+
2054
+ webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
2055
+
2056
+ const mergedTask = taskManager.getTask(epdnTaskId);
2057
+ expect(mergedTask).toBeDefined();
2058
+ expect(mergedTask.data.interaction.participants['epdn-participant']).toBeDefined();
2059
+ expect(mergedTask.data.interaction.participants['epdn-participant'].type).toBe('EpDn');
2060
+ expect(managerEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MERGED, mergedTask);
2061
+ });
2062
+
2063
+ it('should not affect other tasks when CONTACT_MERGED is received', () => {
2064
+ const otherTaskId = 'other-unrelated-task';
2065
+ const otherPayload = {
2066
+ data: {
2067
+ type: CC_EVENTS.AGENT_CONTACT_RESERVED,
2068
+ interactionId: otherTaskId,
2069
+ agentId: taskDataMock.agentId,
2070
+ interaction: {mediaType: 'chat'},
2071
+ },
2072
+ };
2073
+ webSocketManagerMock.emit('message', JSON.stringify(otherPayload));
2074
+
2075
+ const otherTask = taskManager.getTask(otherTaskId);
2076
+ const otherTaskEmitSpy = jest.spyOn(otherTask, 'emit');
2077
+
2078
+ // Send CONTACT_MERGED for the original task
2079
+ const mergedPayload = {
2080
+ data: {
2081
+ type: CC_EVENTS.CONTACT_MERGED,
2082
+ interactionId: taskId,
2083
+ agentId: taskDataMock.agentId,
2084
+ interaction: {
2085
+ mediaType: 'telephony',
2086
+ state: 'merged',
2087
+ },
2088
+ },
2089
+ };
2090
+
2091
+ webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
2092
+
2093
+ // Verify other task was not affected
2094
+ expect(otherTaskEmitSpy).not.toHaveBeenCalled();
2095
+ expect(otherTask.data.interaction.mediaType).toBe('chat');
2096
+
2097
+ // Verify original task was updated
2098
+ expect(managerEmitSpy).toHaveBeenCalledWith(
2099
+ TASK_EVENTS.TASK_MERGED,
2100
+ expect.objectContaining({
2101
+ data: expect.objectContaining({
2102
+ interactionId: taskId,
2103
+ }),
2104
+ })
2105
+ );
2106
+ });
2107
+ });
1739
2108
  });
1740
2109
 
@@ -98,34 +98,34 @@ describe('TaskUtils', () => {
98
98
  });
99
99
 
100
100
  it('should return true when there are 2 or more active agents', () => {
101
- expect(getIsConferenceInProgress(mockTask)).toBe(true);
101
+ expect(getIsConferenceInProgress(mockTask.data)).toBe(true);
102
102
  });
103
103
 
104
104
  it('should return false when there is only 1 active agent', () => {
105
105
  mockTask.data.interaction.participants[mockOtherAgentId].hasLeft = true;
106
- expect(getIsConferenceInProgress(mockTask)).toBe(false);
106
+ expect(getIsConferenceInProgress(mockTask.data)).toBe(false);
107
107
  });
108
108
 
109
109
  it('should exclude customers from agent count', () => {
110
110
  // Remove one agent, should still be false with only 1 agent + customer
111
111
  delete mockTask.data.interaction.participants[mockOtherAgentId];
112
112
  mockTask.data.interaction.media[mockTask.data.interactionId].participants = [mockAgentId, 'customer-123'];
113
- expect(getIsConferenceInProgress(mockTask)).toBe(false);
113
+ expect(getIsConferenceInProgress(mockTask.data)).toBe(false);
114
114
  });
115
115
 
116
116
  it('should exclude supervisors from agent count', () => {
117
117
  mockTask.data.interaction.participants[mockOtherAgentId].pType = 'Supervisor';
118
- expect(getIsConferenceInProgress(mockTask)).toBe(false);
118
+ expect(getIsConferenceInProgress(mockTask.data)).toBe(false);
119
119
  });
120
120
 
121
121
  it('should exclude VVA from agent count', () => {
122
122
  mockTask.data.interaction.participants[mockOtherAgentId].pType = 'VVA';
123
- expect(getIsConferenceInProgress(mockTask)).toBe(false);
123
+ expect(getIsConferenceInProgress(mockTask.data)).toBe(false);
124
124
  });
125
125
 
126
126
  it('should return false when no main call media exists', () => {
127
127
  mockTask.data.interaction.media = {};
128
- expect(getIsConferenceInProgress(mockTask)).toBe(false);
128
+ expect(getIsConferenceInProgress(mockTask.data)).toBe(false);
129
129
  });
130
130
  });
131
131
  });