@webex/contact-center 3.8.1 → 3.9.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 (89) hide show
  1. package/dist/cc.js +105 -63
  2. package/dist/cc.js.map +1 -1
  3. package/dist/index.js +13 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/logger-proxy.js +24 -1
  6. package/dist/logger-proxy.js.map +1 -1
  7. package/dist/metrics/MetricsManager.js +1 -1
  8. package/dist/metrics/MetricsManager.js.map +1 -1
  9. package/dist/metrics/behavioral-events.js +51 -0
  10. package/dist/metrics/behavioral-events.js.map +1 -1
  11. package/dist/metrics/constants.js +12 -1
  12. package/dist/metrics/constants.js.map +1 -1
  13. package/dist/services/AddressBook.js +271 -0
  14. package/dist/services/AddressBook.js.map +1 -0
  15. package/dist/services/EntryPoint.js +227 -0
  16. package/dist/services/EntryPoint.js.map +1 -0
  17. package/dist/services/Queue.js +261 -0
  18. package/dist/services/Queue.js.map +1 -0
  19. package/dist/services/config/constants.js +24 -2
  20. package/dist/services/config/constants.js.map +1 -1
  21. package/dist/services/config/index.js +1 -43
  22. package/dist/services/config/index.js.map +1 -1
  23. package/dist/services/config/types.js +0 -5
  24. package/dist/services/config/types.js.map +1 -1
  25. package/dist/services/core/GlobalTypes.js.map +1 -1
  26. package/dist/services/core/Utils.js +121 -2
  27. package/dist/services/core/Utils.js.map +1 -1
  28. package/dist/services/core/aqm-reqs.js +0 -4
  29. package/dist/services/core/aqm-reqs.js.map +1 -1
  30. package/dist/services/core/websocket/WebSocketManager.js +0 -4
  31. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  32. package/dist/services/task/TaskManager.js +1 -0
  33. package/dist/services/task/TaskManager.js.map +1 -1
  34. package/dist/services/task/index.js +145 -71
  35. package/dist/services/task/index.js.map +1 -1
  36. package/dist/types/cc.d.ts +77 -43
  37. package/dist/types/index.d.ts +8 -3
  38. package/dist/types/metrics/constants.d.ts +7 -0
  39. package/dist/types/services/AddressBook.d.ts +74 -0
  40. package/dist/types/services/EntryPoint.d.ts +67 -0
  41. package/dist/types/services/Queue.d.ts +76 -0
  42. package/dist/types/services/config/constants.d.ts +23 -1
  43. package/dist/types/services/config/index.d.ts +1 -14
  44. package/dist/types/services/config/types.d.ts +0 -64
  45. package/dist/types/services/core/GlobalTypes.d.ts +25 -0
  46. package/dist/types/services/core/Utils.d.ts +27 -1
  47. package/dist/types/services/task/index.d.ts +1 -1
  48. package/dist/types/types.d.ts +162 -0
  49. package/dist/types/utils/PageCache.d.ts +173 -0
  50. package/dist/types.js +17 -0
  51. package/dist/types.js.map +1 -1
  52. package/dist/utils/PageCache.js +192 -0
  53. package/dist/utils/PageCache.js.map +1 -0
  54. package/dist/webex.js +1 -1
  55. package/package.json +10 -10
  56. package/src/cc.ts +121 -81
  57. package/src/index.ts +19 -3
  58. package/src/logger-proxy.ts +24 -1
  59. package/src/metrics/MetricsManager.ts +1 -1
  60. package/src/metrics/behavioral-events.ts +54 -0
  61. package/src/metrics/constants.ts +15 -0
  62. package/src/services/AddressBook.ts +291 -0
  63. package/src/services/EntryPoint.ts +241 -0
  64. package/src/services/Queue.ts +277 -0
  65. package/src/services/config/constants.ts +26 -2
  66. package/src/services/config/index.ts +1 -55
  67. package/src/services/config/types.ts +0 -65
  68. package/src/services/core/GlobalTypes.ts +27 -0
  69. package/src/services/core/Utils.ts +155 -1
  70. package/src/services/core/aqm-reqs.ts +0 -5
  71. package/src/services/core/websocket/WebSocketManager.ts +0 -4
  72. package/src/services/task/TaskManager.ts +1 -0
  73. package/src/services/task/index.ts +172 -56
  74. package/src/types.ts +180 -0
  75. package/src/utils/PageCache.ts +252 -0
  76. package/test/unit/spec/cc.ts +30 -82
  77. package/test/unit/spec/metrics/MetricsManager.ts +0 -1
  78. package/test/unit/spec/metrics/behavioral-events.ts +14 -0
  79. package/test/unit/spec/services/AddressBook.ts +332 -0
  80. package/test/unit/spec/services/EntryPoint.ts +259 -0
  81. package/test/unit/spec/services/Queue.ts +323 -0
  82. package/test/unit/spec/services/config/index.ts +0 -71
  83. package/test/unit/spec/services/core/Utils.ts +50 -0
  84. package/test/unit/spec/services/core/aqm-reqs.ts +1 -3
  85. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +0 -4
  86. package/test/unit/spec/services/task/TaskManager.ts +8 -1
  87. package/test/unit/spec/services/task/index.ts +226 -122
  88. package/umd/contact-center.min.js +2 -2
  89. package/umd/contact-center.min.js.map +1 -1
@@ -0,0 +1,277 @@
1
+ import {HTTP_METHODS, WebexSDK} from '../types';
2
+ import type {
3
+ ContactServiceQueue,
4
+ ContactServiceQueuesResponse,
5
+ ContactServiceQueueSearchParams,
6
+ } from '../types';
7
+ import LoggerProxy from '../logger-proxy';
8
+ import WebexRequest from './core/WebexRequest';
9
+ import PageCache, {PAGINATION_DEFAULTS} from '../utils/PageCache';
10
+ import MetricsManager from '../metrics/MetricsManager';
11
+ import {WCC_API_GATEWAY} from './constants';
12
+ import {endPointMap} from './config/constants';
13
+ import {METRIC_EVENT_NAMES} from '../metrics/constants';
14
+ import {METHODS} from '../constants';
15
+
16
+ /**
17
+ * Queue API class for managing Webex Contact Center contact service queues.
18
+ * Provides functionality to fetch contact service queues using the queue API.
19
+ *
20
+ * @class Queue
21
+ * @public
22
+ * @example
23
+ * ```typescript
24
+ * import Webex from 'webex';
25
+ *
26
+ * const webex = new Webex({ credentials: 'YOUR_ACCESS_TOKEN' });
27
+ * const cc = webex.cc;
28
+ *
29
+ * // Register and login first
30
+ * await cc.register();
31
+ * await cc.stationLogin({ teamId: 'team123', loginOption: 'BROWSER' });
32
+ *
33
+ * // Get Queue API instance from ContactCenter
34
+ * const queueAPI = cc.queue;
35
+ *
36
+ * // Get all queues
37
+ * const queues = await queueAPI.getQueues();
38
+ *
39
+ * // Get queues with pagination
40
+ * const queues = await queueAPI.getQueues({
41
+ * page: 0,
42
+ * pageSize: 50
43
+ * });
44
+ *
45
+ * // Search for specific queues
46
+ * const searchResults = await queueAPI.getQueues({
47
+ * search: 'support',
48
+ * filter: 'name=="Support Queue"'
49
+ * });
50
+ * ```
51
+ */
52
+ export class Queue {
53
+ private webexRequest: WebexRequest;
54
+ private webex: WebexSDK;
55
+ private metricsManager: MetricsManager;
56
+
57
+ // Page cache using the common utility
58
+ private pageCache: PageCache<ContactServiceQueue>;
59
+
60
+ /**
61
+ * Creates an instance of Queue
62
+ * @param {WebexSDK} webex - The Webex SDK instance
63
+ * @public
64
+ */
65
+ constructor(webex: WebexSDK) {
66
+ this.webex = webex;
67
+ this.webexRequest = WebexRequest.getInstance({webex});
68
+ this.pageCache = new PageCache<ContactServiceQueue>('Queue');
69
+ this.metricsManager = MetricsManager.getInstance({webex});
70
+ }
71
+
72
+ /**
73
+ * Fetches contact service queues for the organization
74
+ * @param {ContactServiceQueueSearchParams} [params] - Search and pagination parameters
75
+ * @returns {Promise<ContactServiceQueuesResponse>} Promise resolving to contact service queues
76
+ * @throws {Error} If the API call fails
77
+ * @public
78
+ * @example
79
+ * ```typescript
80
+ * // Get all queues with default pagination
81
+ * const response = await queueAPI.getQueues();
82
+ *
83
+ * // Get queues with specific pagination
84
+ * const response = await queueAPI.getQueues({
85
+ * page: 0,
86
+ * pageSize: 25
87
+ * });
88
+ *
89
+ * // Search for queues
90
+ * const response = await queueAPI.getQueues({
91
+ * search: 'support',
92
+ * filter: 'queueType=="INBOUND"'
93
+ * });
94
+ * ```
95
+ */
96
+ public async getQueues(
97
+ params: ContactServiceQueueSearchParams = {}
98
+ ): Promise<ContactServiceQueuesResponse> {
99
+ const startTime = Date.now();
100
+ const {
101
+ page = PAGINATION_DEFAULTS.PAGE,
102
+ pageSize = PAGINATION_DEFAULTS.PAGE_SIZE,
103
+ search,
104
+ filter,
105
+ attributes,
106
+ sortBy,
107
+ sortOrder,
108
+ desktopProfileFilter,
109
+ provisioningView,
110
+ singleObjectResponse,
111
+ } = params;
112
+
113
+ const orgId = this.webex.credentials.getOrgId();
114
+ const isSearchRequest = !!(search || filter || attributes || sortBy);
115
+
116
+ LoggerProxy.info('Fetching contact service queues', {
117
+ module: 'Queue',
118
+ method: METHODS.GET_QUEUES,
119
+ data: {
120
+ orgId,
121
+ page,
122
+ pageSize,
123
+ isSearchRequest,
124
+ },
125
+ });
126
+
127
+ // Check if we can use cache for simple pagination (no search/filter/attributes/sort)
128
+ if (this.pageCache.canUseCache({search, filter, attributes, sortBy})) {
129
+ const cacheKey = this.pageCache.buildCacheKey(orgId, page, pageSize);
130
+ const cachedPage = this.pageCache.getCachedPage(cacheKey);
131
+
132
+ if (cachedPage) {
133
+ const duration = Date.now() - startTime;
134
+
135
+ LoggerProxy.log(`Returning page ${page} from cache`, {
136
+ module: 'Queue',
137
+ method: 'getQueues',
138
+ data: {
139
+ cacheHit: true,
140
+ duration,
141
+ recordCount: cachedPage.data.length,
142
+ page,
143
+ pageSize,
144
+ },
145
+ });
146
+
147
+ return {
148
+ data: cachedPage.data,
149
+ meta: {
150
+ page,
151
+ pageSize,
152
+ totalPages: cachedPage.totalMeta?.totalPages,
153
+ totalRecords: cachedPage.totalMeta?.totalRecords,
154
+ orgid: orgId,
155
+ },
156
+ };
157
+ }
158
+ }
159
+
160
+ // Start timing only for actual API calls (not cache hits)
161
+ this.metricsManager.timeEvent(METRIC_EVENT_NAMES.QUEUE_FETCH_SUCCESS);
162
+
163
+ try {
164
+ // Build query parameters according to spec
165
+ const queryParams = new URLSearchParams({
166
+ page: page.toString(),
167
+ pageSize: pageSize.toString(),
168
+ });
169
+
170
+ if (filter) queryParams.append('filter', filter);
171
+ if (attributes) queryParams.append('attributes', attributes);
172
+ if (search) queryParams.append('search', search);
173
+ if (sortBy) queryParams.append('sortBy', sortBy);
174
+ if (sortOrder) queryParams.append('sortOrder', sortOrder);
175
+ if (desktopProfileFilter !== undefined)
176
+ queryParams.append('desktopProfileFilter', desktopProfileFilter.toString());
177
+ if (provisioningView !== undefined)
178
+ queryParams.append('provisioningView', provisioningView.toString());
179
+ if (singleObjectResponse !== undefined)
180
+ queryParams.append('singleObjectResponse', singleObjectResponse.toString());
181
+
182
+ const resource = endPointMap.queueList(orgId, queryParams.toString());
183
+
184
+ LoggerProxy.log('Making API request to fetch contact service queues', {
185
+ module: 'Queue',
186
+ method: METHODS.GET_QUEUES,
187
+ data: {
188
+ resource,
189
+ service: WCC_API_GATEWAY,
190
+ },
191
+ });
192
+
193
+ const response = await this.webexRequest.request({
194
+ service: WCC_API_GATEWAY,
195
+ resource,
196
+ method: HTTP_METHODS.GET,
197
+ });
198
+
199
+ const duration = Date.now() - startTime;
200
+
201
+ const recordCount = response.body?.data?.length || 0;
202
+ const totalRecords = response.body?.meta?.totalRecords;
203
+
204
+ LoggerProxy.log(`Successfully retrieved ${recordCount} contact service queues`, {
205
+ module: 'Queue',
206
+ method: METHODS.GET_QUEUES,
207
+ data: {
208
+ statusCode: response.statusCode,
209
+ duration,
210
+ recordCount,
211
+ totalRecords,
212
+ isSearchRequest,
213
+ page,
214
+ pageSize,
215
+ },
216
+ });
217
+
218
+ // Only track metrics for search requests or first page loads to reduce metric volume
219
+ if (isSearchRequest || page === 0) {
220
+ this.metricsManager.trackEvent(
221
+ METRIC_EVENT_NAMES.QUEUE_FETCH_SUCCESS,
222
+ {
223
+ orgId,
224
+ statusCode: response.statusCode,
225
+ recordCount,
226
+ totalRecords,
227
+ isSearchRequest,
228
+ isFirstPage: page === 0,
229
+ },
230
+ ['behavioral', 'operational']
231
+ );
232
+ }
233
+
234
+ // Cache the page data for simple pagination (no search/filter/attributes/sort)
235
+ if (this.pageCache.canUseCache({search, filter, attributes, sortBy}) && response.body?.data) {
236
+ const cacheKey = this.pageCache.buildCacheKey(orgId, page, pageSize);
237
+ this.pageCache.cachePage(cacheKey, response.body.data, response.body.meta);
238
+
239
+ LoggerProxy.log('Cached contact service queues for future requests', {
240
+ module: 'Queue',
241
+ method: METHODS.GET_QUEUES,
242
+ data: {
243
+ cacheKey,
244
+ recordCount,
245
+ },
246
+ });
247
+ }
248
+
249
+ return response.body;
250
+ } catch (error) {
251
+ const errorData = {
252
+ orgId,
253
+ error: error instanceof Error ? error.message : String(error),
254
+ isSearchRequest,
255
+ page,
256
+ pageSize,
257
+ };
258
+
259
+ LoggerProxy.error('Failed to fetch contact service queues', {
260
+ module: 'Queue',
261
+ method: METHODS.GET_QUEUES,
262
+ data: errorData,
263
+ error,
264
+ });
265
+
266
+ // Track all failures for troubleshooting
267
+ this.metricsManager.trackEvent(METRIC_EVENT_NAMES.QUEUE_FETCH_FAILED, errorData, [
268
+ 'behavioral',
269
+ 'operational',
270
+ ]);
271
+
272
+ throw error;
273
+ }
274
+ }
275
+ }
276
+
277
+ export default Queue;
@@ -67,7 +67,6 @@ export const METHODS = {
67
67
  GET_TENANT_DATA: 'getTenantData',
68
68
  GET_URL_MAPPING: 'getURLMapping',
69
69
  GET_DIAL_PLAN_DATA: 'getDialPlanData',
70
- GET_QUEUES: 'getQueues',
71
70
 
72
71
  // Util methods
73
72
  PARSE_AGENT_CONFIGS: 'parseAgentConfigs',
@@ -245,5 +244,30 @@ export const endPointMap = {
245
244
  * @ignore
246
245
  */
247
246
  queueList: (orgId: string, queryParams: string) =>
248
- `organization/${orgId}/v2/contact-service-queue?${queryParams}`,
247
+ `/organization/${orgId}/v2/contact-service-queue?${queryParams}`,
248
+ /**
249
+ * Gets the endpoint for entry points list with custom query parameters.
250
+ * @param orgId - Organization ID.
251
+ * @param queryParams - Query parameters string.
252
+ * @returns The endpoint URL string.
253
+ * @public
254
+ * @example
255
+ * const url = endPointMap.entryPointList('org123', 'page=0&pageSize=10');
256
+ * @ignore
257
+ */
258
+ entryPointList: (orgId: string, queryParams: string) =>
259
+ `/organization/${orgId}/v2/entry-point?${queryParams}`,
260
+ /**
261
+ * Gets the endpoint for address book entries with custom query parameters.
262
+ * @param orgId - Organization ID.
263
+ * @param addressBookId - Address book ID.
264
+ * @param queryParams - Query parameters string.
265
+ * @returns The endpoint URL string.
266
+ * @public
267
+ * @example
268
+ * const url = endPointMap.addressBookEntries('org123', 'book456', 'page=0&pageSize=10');
269
+ * @ignore
270
+ */
271
+ addressBookEntries: (orgId: string, addressBookId: string, queryParams: string) =>
272
+ `/organization/${orgId}/v2/address-book/${addressBookId}/entry?${queryParams}`,
249
273
  };
@@ -20,7 +20,6 @@ import {
20
20
  AuxCode,
21
21
  MultimediaProfileResponse,
22
22
  SiteInfo,
23
- ContactServiceQueue,
24
23
  } from './types';
25
24
  import WebexRequest from '../core/WebexRequest';
26
25
  import {WCC_API_GATEWAY} from '../constants';
@@ -686,58 +685,5 @@ export default class AgentConfigService {
686
685
  }
687
686
  }
688
687
 
689
- /**
690
- * Fetches the list of queues for the given orgId.
691
- * @ignore
692
- * @param {string} orgId - organization ID for which the queues are to be fetched.
693
- * @param {number} page - the page number to fetch.
694
- * @param {number} pageSize - the number of queues to fetch per page.
695
- * @param {string} search - optional search string
696
- * @param {string} filter - optional filter string
697
- * @returns Promise<ContactServiceQueue[]> - A promise that resolves to the list of contact service queues.
698
- * @throws {Error} - Throws an error if the API call fails or if the response status is not 200.
699
- * @private
700
- */
701
- public async getQueues(
702
- orgId: string,
703
- page: number,
704
- pageSize: number,
705
- search?: string,
706
- filter?: string
707
- ): Promise<ContactServiceQueue[]> {
708
- LoggerProxy.info('Fetching queue list', {
709
- module: CONFIG_FILE_NAME,
710
- method: METHODS.GET_QUEUES,
711
- });
712
-
713
- try {
714
- let queryParams = `page=${page}&pageSize=${pageSize}&desktopProfileFilter=true`;
715
- if (search) queryParams += `&search=${search}`;
716
- if (filter) queryParams += `&filter=${filter}`;
717
-
718
- const resource = endPointMap.queueList(orgId, queryParams);
719
- const response = await this.webexReq.request({
720
- service: WCC_API_GATEWAY,
721
- resource,
722
- method: HTTP_METHODS.GET,
723
- });
724
-
725
- if (response.statusCode !== 200) {
726
- throw new Error(`API call failed with ${response.statusCode}`);
727
- }
728
-
729
- LoggerProxy.log('getQueues API success.', {
730
- module: CONFIG_FILE_NAME,
731
- method: METHODS.GET_QUEUES,
732
- });
733
-
734
- return response.body?.data;
735
- } catch (error) {
736
- LoggerProxy.error(`getQueues API call failed with ${error}`, {
737
- module: CONFIG_FILE_NAME,
738
- method: METHODS.GET_QUEUES,
739
- });
740
- throw error;
741
- }
742
- }
688
+ // getQueues removed - use Queue instead
743
689
  }
@@ -1050,68 +1050,3 @@ export type CallDistributionGroup = {
1050
1050
  /** Distribution time duration in seconds */
1051
1051
  duration: number;
1052
1052
  };
1053
-
1054
- /**
1055
- * Comprehensive configuration for a contact service queue
1056
- * @public
1057
- */
1058
- export type ContactServiceQueue = {
1059
- /** Unique identifier for the queue */
1060
- id: string;
1061
- /** Queue name */
1062
- name: string;
1063
- /** Queue description */
1064
- description: string;
1065
- /** Type of queue */
1066
- queueType: string;
1067
- /** Whether to check agent availability before routing */
1068
- checkAgentAvailability: boolean;
1069
- /** Type of channel this queue handles */
1070
- channelType: string;
1071
- /** Service level threshold in seconds */
1072
- serviceLevelThreshold: number;
1073
- /** Maximum number of active contacts allowed */
1074
- maxActiveContacts: number;
1075
- /** Maximum time contacts can wait in queue (seconds) */
1076
- maxTimeInQueue: number;
1077
- /** Default music on hold media file ID */
1078
- defaultMusicInQueueMediaFileId: string;
1079
- /** Queue timezone */
1080
- timezone: string;
1081
- /** Whether queue is active */
1082
- active: boolean;
1083
- /** Whether outbound campaign routing is enabled */
1084
- outdialCampaignEnabled: boolean;
1085
- /** Whether monitoring is permitted */
1086
- monitoringPermitted: boolean;
1087
- /** Whether parking is permitted */
1088
- parkingPermitted: boolean;
1089
- /** Whether recording is permitted */
1090
- recordingPermitted: boolean;
1091
- /** Whether recording all calls is permitted */
1092
- recordingAllCallsPermitted: boolean;
1093
- /** Whether pausing recordings is permitted */
1094
- pauseRecordingPermitted: boolean;
1095
- /** Maximum recording pause duration in seconds */
1096
- recordingPauseDuration: number;
1097
- /** Control flow script URL */
1098
- controlFlowScriptUrl: string;
1099
- /** IVR requeue URL */
1100
- ivrRequeueUrl: string;
1101
- /** Type of routing strategy */
1102
- routingType: string;
1103
- /** Queue-specific routing type */
1104
- queueRoutingType: string;
1105
- /** Queue skill requirements for routing */
1106
- queueSkillRequirements: object[];
1107
- /** Associated agents */
1108
- agents: object[];
1109
- /** Call distribution group configurations */
1110
- callDistributionGroups: CallDistributionGroup[];
1111
- /** Associated resource links */
1112
- links: Array<string>;
1113
- /** Timestamp when queue was created */
1114
- createdTime: string;
1115
- /** Timestamp when queue was last updated */
1116
- lastUpdatedTime: string;
1117
- };
@@ -32,3 +32,30 @@ export type Failure = Msg<{
32
32
  /** Human-readable description of the failure reason */
33
33
  reason: string;
34
34
  }>;
35
+
36
+ /**
37
+ * Represents task API error details in a structured format
38
+ * @public
39
+ */
40
+ export interface TaskError {
41
+ /** Original error object for throwing */
42
+ error: Error;
43
+ /** Unique tracking identifier for correlation */
44
+ trackingId: string;
45
+ /** Detailed error message from the API */
46
+ errorMessage: string;
47
+ /** Type/category of the error (e.g., "Bad Request") */
48
+ errorType: string;
49
+ /** Additional error context data */
50
+ errorData: string;
51
+ /** Numeric reason code */
52
+ reasonCode: number;
53
+ }
54
+
55
+ /**
56
+ * An Error object augmented with a flexible data field for additional context.
57
+ * Use this to attach structured data to thrown errors without ts-ignore.
58
+ */
59
+ export interface AugmentedError extends Error {
60
+ data?: Record<string, any>;
61
+ }
@@ -1,8 +1,14 @@
1
1
  import * as Err from './Err';
2
2
  import {LoginOption, WebexRequestPayload} from '../../types';
3
- import {Failure} from './GlobalTypes';
3
+ import {Failure, AugmentedError} from './GlobalTypes';
4
4
  import LoggerProxy from '../../logger-proxy';
5
5
  import WebexRequest from './WebexRequest';
6
+ import {
7
+ TaskData,
8
+ ConsultTransferPayLoad,
9
+ CONSULT_TRANSFER_DESTINATION_TYPE,
10
+ Interaction,
11
+ } from '../task/types';
6
12
 
7
13
  /**
8
14
  * Extracts common error details from a Webex request payload.
@@ -19,6 +25,28 @@ const getCommonErrorDetails = (errObj: WebexRequestPayload) => {
19
25
  };
20
26
  };
21
27
 
28
+ /**
29
+ * Checks if the destination type represents an entry point variant (EPDN or ENTRYPOINT).
30
+ */
31
+ const isEntryPointOrEpdn = (destAgentType?: string): boolean => {
32
+ return destAgentType === 'EPDN' || destAgentType === 'ENTRYPOINT';
33
+ };
34
+
35
+ /**
36
+ * Determines if the task involves dialing a number based on the destination type.
37
+ * Returns 'DIAL_NUMBER' for dial-related destinations, empty string otherwise.
38
+ */
39
+ const getAgentActionTypeFromTask = (taskData?: TaskData): 'DIAL_NUMBER' | '' => {
40
+ const destAgentType = taskData?.destinationType;
41
+
42
+ // Check if destination requires dialing: direct dial number or entry point variants
43
+ const isDialNumber = destAgentType === 'DN';
44
+ const isEntryPointVariant = isEntryPointOrEpdn(destAgentType);
45
+
46
+ // If the destination type is a dial number or an entry point variant, return 'DIAL_NUMBER'
47
+ return isDialNumber || isEntryPointVariant ? 'DIAL_NUMBER' : '';
48
+ };
49
+
22
50
  export const isValidDialNumber = (input: string): boolean => {
23
51
  // This regex checks for a valid dial number format for only few countries such as US, Canada.
24
52
  const regexForDn = /1[0-9]{3}[2-9][0-9]{6}([,]{1,10}[0-9]+){0,1}/;
@@ -115,6 +143,62 @@ export const getErrorDetails = (error: any, methodName: string, moduleName: stri
115
143
  };
116
144
  };
117
145
 
146
+ /**
147
+ * Extracts error details from task API errors and logs them. Also uploads logs for the error.
148
+ * This handles the specific error format returned by task API calls.
149
+ *
150
+ * @param error - The error object from task API calls with structure: {id: string, details: {trackingId: string, msg: {...}}}
151
+ * @param methodName - The name of the method where the error occurred.
152
+ * @param moduleName - The name of the module where the error occurred.
153
+ * @returns AugmentedError containing structured error details on err.data for metrics and logging
154
+ * @public
155
+ * @example
156
+ * const taskError = generateTaskErrorObject(error, 'transfer', 'TaskModule');
157
+ * throw taskError.error;
158
+ * @ignore
159
+ */
160
+ export const generateTaskErrorObject = (
161
+ error: any,
162
+ methodName: string,
163
+ moduleName: string
164
+ ): AugmentedError => {
165
+ const trackingId = error?.details?.trackingId || error?.trackingId || '';
166
+ const errorMsg = error?.details?.msg;
167
+
168
+ const fallbackMessage =
169
+ (error && typeof error.message === 'string' && error.message) ||
170
+ `Error while performing ${methodName}`;
171
+ const errorMessage = errorMsg?.errorMessage || fallbackMessage;
172
+ const errorType =
173
+ errorMsg?.errorType ||
174
+ (error && typeof error.name === 'string' && error.name) ||
175
+ 'Unknown Error';
176
+ const errorData = errorMsg?.errorData || '';
177
+ const reasonCode = errorMsg?.reasonCode || 0;
178
+
179
+ // Log and upload for Task API formatted errors
180
+ LoggerProxy.error(`${methodName} failed: ${errorMessage} (${errorType})`, {
181
+ module: moduleName,
182
+ method: methodName,
183
+ trackingId,
184
+ });
185
+ WebexRequest.getInstance().uploadLogs({
186
+ correlationId: trackingId,
187
+ });
188
+
189
+ const reason = `${errorType}: ${errorMessage}${errorData ? ` (${errorData})` : ''}`;
190
+ const err: AugmentedError = new Error(reason);
191
+ err.data = {
192
+ message: errorMessage,
193
+ errorType,
194
+ errorData,
195
+ reasonCode,
196
+ trackingId,
197
+ };
198
+
199
+ return err;
200
+ };
201
+
118
202
  /**
119
203
  * Creates an error details object suitable for use with the Err.Details class.
120
204
  *
@@ -130,3 +214,73 @@ export const createErrDetailsObject = (errObj: WebexRequestPayload) => {
130
214
 
131
215
  return new Err.Details('Service.reqs.generic.failure', details);
132
216
  };
217
+
218
+ /**
219
+ * Derives the consult transfer destination type based on the provided task data.
220
+ *
221
+ * Logic parity with desktop behavior:
222
+ * - If agent action is dialing a number (DN/EPDN/ENTRYPOINT):
223
+ * - ENTRYPOINT/EPDN map to ENTRYPOINT
224
+ * - DN maps to DIALNUMBER
225
+ * - Otherwise defaults to AGENT
226
+ *
227
+ * @param taskData - The task data used to infer the agent action and destination type
228
+ * @returns The normalized destination type to be used for consult transfer
229
+ */
230
+ /**
231
+ * Checks if a participant type represents a non-customer participant.
232
+ * Non-customer participants include agents, dial numbers, entry point dial numbers,
233
+ * and entry points.
234
+ */
235
+ const isNonCustomerParticipant = (participantType: string): boolean => {
236
+ return (
237
+ participantType === 'Agent' ||
238
+ participantType === 'DN' ||
239
+ participantType === 'EpDn' ||
240
+ participantType === 'entryPoint'
241
+ );
242
+ };
243
+
244
+ /**
245
+ * Gets the destination agent ID from participants data by finding the first
246
+ * non-customer participant that is not the current agent and is not in wrap-up state.
247
+ *
248
+ * @param participants - The participants data from the interaction
249
+ * @param agentId - The current agent's ID to exclude from the search
250
+ * @returns The destination agent ID, or empty string if none found
251
+ */
252
+ export const getDestinationAgentId = (
253
+ participants: Interaction['participants'],
254
+ agentId: string
255
+ ): string => {
256
+ let id = '';
257
+
258
+ if (participants) {
259
+ Object.keys(participants).forEach((participant) => {
260
+ const participantData = participants[participant];
261
+ if (
262
+ isNonCustomerParticipant(participantData.type) &&
263
+ participantData.id !== agentId &&
264
+ !participantData.isWrapUp
265
+ ) {
266
+ id = participantData.id;
267
+ }
268
+ });
269
+ }
270
+
271
+ return id;
272
+ };
273
+
274
+ export const deriveConsultTransferDestinationType = (
275
+ taskData?: TaskData
276
+ ): ConsultTransferPayLoad['destinationType'] => {
277
+ const agentActionType = getAgentActionTypeFromTask(taskData);
278
+
279
+ if (agentActionType === 'DIAL_NUMBER') {
280
+ return isEntryPointOrEpdn(taskData?.destinationType)
281
+ ? CONSULT_TRANSFER_DESTINATION_TYPE.ENTRYPOINT
282
+ : CONSULT_TRANSFER_DESTINATION_TYPE.DIALNUMBER;
283
+ }
284
+
285
+ return CONSULT_TRANSFER_DESTINATION_TYPE.AGENT;
286
+ };
@@ -224,11 +224,6 @@ export default class AqmReqs {
224
224
  }
225
225
 
226
226
  if (event.keepalive === 'true') {
227
- LoggerProxy.info(`Keepalive from web socket`, {
228
- module: AQM_REQS_FILE,
229
- method: METHODS.ON_MESSAGE,
230
- });
231
-
232
227
  return;
233
228
  }
234
229
 
@@ -178,10 +178,6 @@ export class WebSocketManager extends EventEmitter {
178
178
  issueReason = 'WebSocket auto close timed out. Forcefully closed websocket.';
179
179
  } else {
180
180
  const onlineStatus = navigator.onLine;
181
- LoggerProxy.info(`[WebSocketStatus] | desktop online status is ${onlineStatus}`, {
182
- module: WEB_SOCKET_MANAGER_FILE,
183
- method: METHODS.WEB_SOCKET_ON_CLOSE_HANDLER,
184
- });
185
181
  issueReason = !onlineStatus
186
182
  ? 'network issue'
187
183
  : 'missing keepalive from either desktop or notif service';
@@ -270,6 +270,7 @@ export default class TaskManager extends EventEmitter {
270
270
  break;
271
271
  case CC_EVENTS.AGENT_CONSULTING:
272
272
  // Received when agent is in an active consult state
273
+ // TODO: Check if we can use backend consult state instead of isConsulted
273
274
  task = this.updateTaskData(task, payload.data);
274
275
  if (task.data.isConsulted) {
275
276
  // Fire only if you are the agent who received the consult request