@webex/contact-center 0.0.0-next.1

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 (177) hide show
  1. package/README.md +81 -0
  2. package/__mocks__/workerMock.js +15 -0
  3. package/babel.config.js +15 -0
  4. package/dist/cc.js +1416 -0
  5. package/dist/cc.js.map +1 -0
  6. package/dist/config.js +72 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/constants.js +58 -0
  9. package/dist/constants.js.map +1 -0
  10. package/dist/index.js +142 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/logger-proxy.js +115 -0
  13. package/dist/logger-proxy.js.map +1 -0
  14. package/dist/metrics/MetricsManager.js +474 -0
  15. package/dist/metrics/MetricsManager.js.map +1 -0
  16. package/dist/metrics/behavioral-events.js +322 -0
  17. package/dist/metrics/behavioral-events.js.map +1 -0
  18. package/dist/metrics/constants.js +134 -0
  19. package/dist/metrics/constants.js.map +1 -0
  20. package/dist/services/WebCallingService.js +323 -0
  21. package/dist/services/WebCallingService.js.map +1 -0
  22. package/dist/services/agent/index.js +177 -0
  23. package/dist/services/agent/index.js.map +1 -0
  24. package/dist/services/agent/types.js +137 -0
  25. package/dist/services/agent/types.js.map +1 -0
  26. package/dist/services/config/Util.js +203 -0
  27. package/dist/services/config/Util.js.map +1 -0
  28. package/dist/services/config/constants.js +221 -0
  29. package/dist/services/config/constants.js.map +1 -0
  30. package/dist/services/config/index.js +607 -0
  31. package/dist/services/config/index.js.map +1 -0
  32. package/dist/services/config/types.js +334 -0
  33. package/dist/services/config/types.js.map +1 -0
  34. package/dist/services/constants.js +117 -0
  35. package/dist/services/constants.js.map +1 -0
  36. package/dist/services/core/Err.js +43 -0
  37. package/dist/services/core/Err.js.map +1 -0
  38. package/dist/services/core/GlobalTypes.js +6 -0
  39. package/dist/services/core/GlobalTypes.js.map +1 -0
  40. package/dist/services/core/Utils.js +126 -0
  41. package/dist/services/core/Utils.js.map +1 -0
  42. package/dist/services/core/WebexRequest.js +96 -0
  43. package/dist/services/core/WebexRequest.js.map +1 -0
  44. package/dist/services/core/aqm-reqs.js +246 -0
  45. package/dist/services/core/aqm-reqs.js.map +1 -0
  46. package/dist/services/core/constants.js +109 -0
  47. package/dist/services/core/constants.js.map +1 -0
  48. package/dist/services/core/types.js +6 -0
  49. package/dist/services/core/types.js.map +1 -0
  50. package/dist/services/core/websocket/WebSocketManager.js +187 -0
  51. package/dist/services/core/websocket/WebSocketManager.js.map +1 -0
  52. package/dist/services/core/websocket/connection-service.js +111 -0
  53. package/dist/services/core/websocket/connection-service.js.map +1 -0
  54. package/dist/services/core/websocket/keepalive.worker.js +94 -0
  55. package/dist/services/core/websocket/keepalive.worker.js.map +1 -0
  56. package/dist/services/core/websocket/types.js +6 -0
  57. package/dist/services/core/websocket/types.js.map +1 -0
  58. package/dist/services/index.js +78 -0
  59. package/dist/services/index.js.map +1 -0
  60. package/dist/services/task/AutoWrapup.js +88 -0
  61. package/dist/services/task/AutoWrapup.js.map +1 -0
  62. package/dist/services/task/TaskManager.js +369 -0
  63. package/dist/services/task/TaskManager.js.map +1 -0
  64. package/dist/services/task/constants.js +58 -0
  65. package/dist/services/task/constants.js.map +1 -0
  66. package/dist/services/task/contact.js +464 -0
  67. package/dist/services/task/contact.js.map +1 -0
  68. package/dist/services/task/dialer.js +60 -0
  69. package/dist/services/task/dialer.js.map +1 -0
  70. package/dist/services/task/index.js +1188 -0
  71. package/dist/services/task/index.js.map +1 -0
  72. package/dist/services/task/types.js +214 -0
  73. package/dist/services/task/types.js.map +1 -0
  74. package/dist/types/cc.d.ts +676 -0
  75. package/dist/types/config.d.ts +66 -0
  76. package/dist/types/constants.d.ts +45 -0
  77. package/dist/types/index.d.ts +178 -0
  78. package/dist/types/logger-proxy.d.ts +71 -0
  79. package/dist/types/metrics/MetricsManager.d.ts +223 -0
  80. package/dist/types/metrics/behavioral-events.d.ts +29 -0
  81. package/dist/types/metrics/constants.d.ts +127 -0
  82. package/dist/types/services/WebCallingService.d.ts +1 -0
  83. package/dist/types/services/agent/index.d.ts +46 -0
  84. package/dist/types/services/agent/types.d.ts +413 -0
  85. package/dist/types/services/config/Util.d.ts +19 -0
  86. package/dist/types/services/config/constants.d.ts +203 -0
  87. package/dist/types/services/config/index.d.ts +171 -0
  88. package/dist/types/services/config/types.d.ts +1113 -0
  89. package/dist/types/services/constants.d.ts +97 -0
  90. package/dist/types/services/core/Err.d.ts +119 -0
  91. package/dist/types/services/core/GlobalTypes.d.ts +33 -0
  92. package/dist/types/services/core/Utils.d.ts +36 -0
  93. package/dist/types/services/core/WebexRequest.d.ts +22 -0
  94. package/dist/types/services/core/aqm-reqs.d.ts +16 -0
  95. package/dist/types/services/core/constants.d.ts +85 -0
  96. package/dist/types/services/core/types.d.ts +47 -0
  97. package/dist/types/services/core/websocket/WebSocketManager.d.ts +34 -0
  98. package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
  99. package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
  100. package/dist/types/services/core/websocket/types.d.ts +37 -0
  101. package/dist/types/services/index.d.ts +52 -0
  102. package/dist/types/services/task/AutoWrapup.d.ts +40 -0
  103. package/dist/types/services/task/TaskManager.d.ts +1 -0
  104. package/dist/types/services/task/constants.d.ts +46 -0
  105. package/dist/types/services/task/contact.d.ts +59 -0
  106. package/dist/types/services/task/dialer.d.ts +28 -0
  107. package/dist/types/services/task/index.d.ts +569 -0
  108. package/dist/types/services/task/types.d.ts +1041 -0
  109. package/dist/types/types.d.ts +452 -0
  110. package/dist/types/webex-config.d.ts +53 -0
  111. package/dist/types/webex.d.ts +7 -0
  112. package/dist/types.js +292 -0
  113. package/dist/types.js.map +1 -0
  114. package/dist/webex-config.js +60 -0
  115. package/dist/webex-config.js.map +1 -0
  116. package/dist/webex.js +99 -0
  117. package/dist/webex.js.map +1 -0
  118. package/jest.config.js +45 -0
  119. package/package.json +83 -0
  120. package/src/cc.ts +1618 -0
  121. package/src/config.ts +65 -0
  122. package/src/constants.ts +51 -0
  123. package/src/index.ts +220 -0
  124. package/src/logger-proxy.ts +110 -0
  125. package/src/metrics/MetricsManager.ts +512 -0
  126. package/src/metrics/behavioral-events.ts +332 -0
  127. package/src/metrics/constants.ts +135 -0
  128. package/src/services/WebCallingService.ts +351 -0
  129. package/src/services/agent/index.ts +149 -0
  130. package/src/services/agent/types.ts +440 -0
  131. package/src/services/config/Util.ts +261 -0
  132. package/src/services/config/constants.ts +249 -0
  133. package/src/services/config/index.ts +743 -0
  134. package/src/services/config/types.ts +1117 -0
  135. package/src/services/constants.ts +111 -0
  136. package/src/services/core/Err.ts +126 -0
  137. package/src/services/core/GlobalTypes.ts +34 -0
  138. package/src/services/core/Utils.ts +132 -0
  139. package/src/services/core/WebexRequest.ts +103 -0
  140. package/src/services/core/aqm-reqs.ts +272 -0
  141. package/src/services/core/constants.ts +106 -0
  142. package/src/services/core/types.ts +48 -0
  143. package/src/services/core/websocket/WebSocketManager.ts +196 -0
  144. package/src/services/core/websocket/connection-service.ts +142 -0
  145. package/src/services/core/websocket/keepalive.worker.js +88 -0
  146. package/src/services/core/websocket/types.ts +40 -0
  147. package/src/services/index.ts +71 -0
  148. package/src/services/task/AutoWrapup.ts +86 -0
  149. package/src/services/task/TaskManager.ts +420 -0
  150. package/src/services/task/constants.ts +52 -0
  151. package/src/services/task/contact.ts +429 -0
  152. package/src/services/task/dialer.ts +52 -0
  153. package/src/services/task/index.ts +1375 -0
  154. package/src/services/task/types.ts +1113 -0
  155. package/src/types.ts +639 -0
  156. package/src/webex-config.ts +54 -0
  157. package/src/webex.js +96 -0
  158. package/test/unit/spec/cc.ts +1985 -0
  159. package/test/unit/spec/metrics/MetricsManager.ts +491 -0
  160. package/test/unit/spec/metrics/behavioral-events.ts +102 -0
  161. package/test/unit/spec/services/WebCallingService.ts +416 -0
  162. package/test/unit/spec/services/agent/index.ts +65 -0
  163. package/test/unit/spec/services/config/index.ts +1035 -0
  164. package/test/unit/spec/services/core/Utils.ts +279 -0
  165. package/test/unit/spec/services/core/WebexRequest.ts +144 -0
  166. package/test/unit/spec/services/core/aqm-reqs.ts +570 -0
  167. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +378 -0
  168. package/test/unit/spec/services/core/websocket/connection-service.ts +178 -0
  169. package/test/unit/spec/services/task/TaskManager.ts +1351 -0
  170. package/test/unit/spec/services/task/contact.ts +204 -0
  171. package/test/unit/spec/services/task/dialer.ts +157 -0
  172. package/test/unit/spec/services/task/index.ts +1474 -0
  173. package/tsconfig.json +6 -0
  174. package/typedoc.json +37 -0
  175. package/typedoc.md +240 -0
  176. package/umd/contact-center.min.js +3 -0
  177. package/umd/contact-center.min.js.map +1 -0
package/src/cc.ts ADDED
@@ -0,0 +1,1618 @@
1
+ /**
2
+ * @module CCPlugin
3
+ * @packageDocumentation
4
+ * Contact Center Plugin module that provides functionality for managing contact center agents,
5
+ * handling tasks, and interacting with contact center services. This module enables integration
6
+ * with Webex Contact Center features through the WebexSDK.
7
+ */
8
+
9
+ import {WebexPlugin} from '@webex/webex-core';
10
+ import EventEmitter from 'events';
11
+ import {v4 as uuidv4} from 'uuid';
12
+ import {
13
+ SetStateResponse,
14
+ CCPluginConfig,
15
+ IContactCenter,
16
+ WebexSDK,
17
+ LoginOption,
18
+ AgentLogin,
19
+ AgentProfileUpdate,
20
+ StationLoginResponse,
21
+ StationLogoutResponse,
22
+ BuddyAgentsResponse,
23
+ BuddyAgents,
24
+ SubscribeRequest,
25
+ UploadLogsResponse,
26
+ UpdateDeviceTypeResponse,
27
+ GenericError,
28
+ } from './types';
29
+ import {
30
+ READY,
31
+ CC_FILE,
32
+ EMPTY_STRING,
33
+ OUTDIAL_DIRECTION,
34
+ ATTRIBUTES,
35
+ OUTDIAL_MEDIA_TYPE,
36
+ OUTBOUND_TYPE,
37
+ UNKNOWN_ERROR,
38
+ MERCURY_DISCONNECTED_SUCCESS,
39
+ METHODS,
40
+ } from './constants';
41
+ import {AGENT, WEB_RTC_PREFIX} from './services/constants';
42
+ import Services from './services';
43
+ import WebexRequest from './services/core/WebexRequest';
44
+ import LoggerProxy from './logger-proxy';
45
+ import {StateChange, Logout, StateChangeSuccess, AGENT_EVENTS} from './services/agent/types';
46
+ import {getErrorDetails, isValidDialNumber} from './services/core/Utils';
47
+ import {Profile, WelcomeEvent, CC_EVENTS, ContactServiceQueue} from './services/config/types';
48
+ import {
49
+ AGENT_STATE_AVAILABLE,
50
+ AGENT_STATE_AVAILABLE_ID,
51
+ DEFAULT_PAGE,
52
+ DEFAULT_PAGE_SIZE,
53
+ } from './services/config/constants';
54
+ import {ConnectionLostDetails} from './services/core/websocket/types';
55
+ import TaskManager from './services/task/TaskManager';
56
+ import WebCallingService from './services/WebCallingService';
57
+ import {ITask, TASK_EVENTS, TaskResponse, DialerPayload} from './services/task/types';
58
+ import MetricsManager from './metrics/MetricsManager';
59
+ import {METRIC_EVENT_NAMES} from './metrics/constants';
60
+ import {Failure} from './services/core/GlobalTypes';
61
+
62
+ /**
63
+ * The main Contact Center plugin class that enables integration with Webex Contact Center.
64
+ *
65
+ * @class ContactCenter
66
+ * @extends WebexPlugin
67
+ * @implements IContactCenter
68
+ * @description
69
+ * Features:
70
+ *
71
+ * 1. Session Management:
72
+ * - {@link register} - Initialize and register SDK with contact center
73
+ * - {@link deregister} - Cleanup and disconnect SDK resources
74
+ *
75
+ * 2. Agent Login/Logout:
76
+ * - {@link stationLogin} - Login with browser or desk phone
77
+ * - {@link stationLogout} - Logout from current station
78
+ * - {@link updateAgentProfile} - Update device type and settings
79
+ *
80
+ * 3. Agent State Control:
81
+ * - {@link setAgentState} - Change agent state (Available/Idle)
82
+ *
83
+ * 4. Task Management:
84
+ * - Inbound task handling via events
85
+ * - {@link startOutdial} - Make outbound calls
86
+ *
87
+ * 5. Routing & Distribution:
88
+ * - {@link getQueues} - Get available queues for routing
89
+ * - {@link getBuddyAgents} - Get available buddy agents
90
+ *
91
+ * 6. Diagnostics:
92
+ * - {@link uploadLogs} - Upload logs for troubleshooting
93
+ *
94
+ * * Key Events:
95
+ * - Agent State Events:
96
+ * - `agent:stateChange` - Agent's state has changed (Available, Idle, etc.)
97
+ * - `agent:stateChangeSuccess` - Agent state change was successful
98
+ * - `agent:stateChangeFailed` - Agent state change failed
99
+ *
100
+ * - Session Events:
101
+ * - `agent:stationLoginSuccess` - Agent login was successful
102
+ * - `agent:stationLoginFailed` - Agent login failed
103
+ * - `agent:logoutSuccess` - Agent logout was successful
104
+ * - `agent:logoutFailed` - Agent logout failed
105
+ *
106
+ * - Task Events:
107
+ * - `task:incoming` - New task is being offered
108
+ * - `task:hydrate` - Task data has been updated
109
+ * - `task:established` - Task/call has been connected
110
+ * - `task:ended` - Task/call has ended
111
+ * - `task:error` - An error occurred during task handling
112
+ *
113
+ * @public
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * import Webex from 'webex';
118
+ *
119
+ * // Initialize SDK with access token
120
+ * const webex = new Webex({
121
+ * credentials: 'YOUR_ACCESS_TOKEN'
122
+ * });
123
+ *
124
+ * // Get Contact Center plugin instance
125
+ * const cc = webex.cc;
126
+ *
127
+ * // Setup event handlers
128
+ * cc.on('agent:stateChange', (event) => {
129
+ * console.log('Agent state changed:', event.state);
130
+ * });
131
+ *
132
+ * cc.on('task:incoming', (task) => {
133
+ * console.log('New task received:', task.interactionId);
134
+ * });
135
+ *
136
+ * // Initialize agent session
137
+ * async function initializeAgent() {
138
+ * try {
139
+ * // Register with contact center
140
+ * const profile = await cc.register();
141
+ *
142
+ * // Login with browser-based calling
143
+ * await cc.stationLogin({
144
+ * teamId: profile.teams[0].teamId,
145
+ * loginOption: 'BROWSER'
146
+ * });
147
+ *
148
+ * // Set agent to Available state
149
+ * await cc.setAgentState({
150
+ * state: 'Available',
151
+ * auxCodeId: '0'
152
+ * });
153
+ *
154
+ * console.log('Agent initialized and ready');
155
+ * } catch (error) {
156
+ * console.error('Initialization failed:', error);
157
+ * await cc.uploadLogs(); // Upload logs for troubleshooting
158
+ * }
159
+ * }
160
+ *
161
+ * initializeAgent();
162
+ * ```
163
+ *
164
+ * @public
165
+ */
166
+ export default class ContactCenter extends WebexPlugin implements IContactCenter {
167
+ /**
168
+ * The plugin's unique namespace identifier in the Webex SDK.
169
+ * Used to access the plugin via webex.cc
170
+ * @type {string}
171
+ * @public
172
+ */
173
+ namespace = 'cc';
174
+
175
+ /**
176
+ * Plugin configuration settings including connection and authentication options
177
+ * @type {CCPluginConfig}
178
+ * @private
179
+ */
180
+ private $config: CCPluginConfig;
181
+
182
+ /**
183
+ * Reference to the parent Webex SDK instance
184
+ * Used to access core Webex functionality and credentials
185
+ * @type {WebexSDK}
186
+ * @private
187
+ */
188
+ private $webex: WebexSDK;
189
+
190
+ /**
191
+ * Event emitter for handling internal plugin events
192
+ * Manages event subscriptions and notifications
193
+ * @type {EventEmitter}
194
+ * @private
195
+ */
196
+ private eventEmitter: EventEmitter;
197
+
198
+ /**
199
+ * Agent's profile and configuration data
200
+ * Includes capabilities, teams, settings, and current state
201
+ * @type {Profile}
202
+ * @private
203
+ */
204
+ private agentConfig: Profile;
205
+
206
+ /**
207
+ * Service for managing browser-based calling (WebRTC)
208
+ * Handles audio/video streaming and device management
209
+ * @type {WebCallingService}
210
+ * @private
211
+ */
212
+ private webCallingService: WebCallingService;
213
+
214
+ /**
215
+ * Core service managers for Contact Center operations
216
+ * Includes agent, connection, and configuration services
217
+ * @type {Services}
218
+ * @private
219
+ */
220
+ private services: Services;
221
+
222
+ /**
223
+ * Service for making authenticated HTTP requests to Webex APIs
224
+ * Handles request/response lifecycle and error handling
225
+ * @type {WebexRequest}
226
+ * @private
227
+ */
228
+ private webexRequest: WebexRequest;
229
+
230
+ /**
231
+ * Manager for handling contact center tasks (calls, chats, etc.)
232
+ * Coordinates task lifecycle events and state
233
+ * @type {TaskManager}
234
+ * @private
235
+ */
236
+ private taskManager: TaskManager;
237
+
238
+ /**
239
+ * Manager for tracking and reporting SDK metrics and analytics
240
+ * Monitors performance, errors, and usage patterns
241
+ * @type {MetricsManager}
242
+ * @private
243
+ */
244
+ private metricsManager: MetricsManager;
245
+
246
+ /**
247
+ * Logger utility for Contact Center plugin
248
+ * Provides consistent logging across the plugin
249
+ * @type {LoggerProxy}
250
+ * @public
251
+ */
252
+ public LoggerProxy = LoggerProxy;
253
+
254
+ /**
255
+ * @ignore
256
+ * Creates an instance of ContactCenter plugin
257
+ * @param {any[]} args Arguments passed to plugin constructor
258
+ */
259
+ constructor(...args) {
260
+ super(...args);
261
+
262
+ this.eventEmitter = new EventEmitter();
263
+ // @ts-ignore
264
+ this.$webex = this.webex;
265
+
266
+ this.$webex.once(READY, () => {
267
+ // @ts-ignore
268
+ this.$config = this.config;
269
+
270
+ /**
271
+ * This is used for handling the async requests by sending webex.request and wait for corresponding websocket event.
272
+ */
273
+ this.webexRequest = WebexRequest.getInstance({
274
+ webex: this.$webex,
275
+ });
276
+
277
+ this.services = Services.getInstance({
278
+ webex: this.$webex,
279
+ connectionConfig: this.getConnectionConfig(),
280
+ });
281
+ this.services.webSocketManager.on('message', this.handleWebsocketMessage);
282
+
283
+ this.webCallingService = new WebCallingService(this.$webex);
284
+ this.metricsManager = MetricsManager.getInstance({webex: this.$webex});
285
+ this.taskManager = TaskManager.getTaskManager(
286
+ this.services.contact,
287
+ this.webCallingService,
288
+ this.services.webSocketManager
289
+ );
290
+ this.incomingTaskListener();
291
+
292
+ LoggerProxy.initialize(this.$webex.logger);
293
+ });
294
+ }
295
+
296
+ /**
297
+ * Handles incoming task events and triggers appropriate notifications
298
+ * @private
299
+ * @param {ITask} task The incoming task object containing task details
300
+ */
301
+ private handleIncomingTask = (task: ITask) => {
302
+ // @ts-ignore
303
+ this.trigger(TASK_EVENTS.TASK_INCOMING, task);
304
+ };
305
+
306
+ /**
307
+ * Handles task hydration events for updating task data
308
+ * @private
309
+ * @param {ITask} task The task object to be hydrated with additional data
310
+ */
311
+ private handleTaskHydrate = (task: ITask) => {
312
+ // @ts-ignore
313
+ this.trigger(TASK_EVENTS.TASK_HYDRATE, task);
314
+ };
315
+
316
+ /**
317
+ * Sets up event listeners for incoming tasks and task hydration
318
+ * Subscribes to task events from the task manager
319
+ * @private
320
+ */
321
+ private incomingTaskListener() {
322
+ this.taskManager.on(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
323
+ this.taskManager.on(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
324
+ }
325
+
326
+ /**
327
+ * Initializes the Contact Center SDK by setting up the web socket connections.
328
+ * This method must be called before performing any agent operations such as login, state change, or handling tasks.
329
+ *
330
+ * @returns {Promise<Profile>} Agent profile information after successful registration.
331
+ * The returned `Profile` object contains details such as:
332
+ * - `agentId`: The unique identifier for the agent.
333
+ * - `defaultDn`: The default dial number associated with the agent.
334
+ * - `teams`: Array of teams the agent belongs to.
335
+ * - `webRtcEnabled`: Indicates if WebRTC (browser calling) is enabled.
336
+ * - `loginVoiceOptions`: Supported login options for the agent (e.g., BROWSER, EXTENSION).
337
+ * - ...and other agent configuration details.
338
+ *
339
+ * @throws {Error} If registration fails.
340
+ *
341
+ * @public
342
+ * @example
343
+ * ```typescript
344
+ * import Webex from 'webex';
345
+ *
346
+ * const webex = Webex.init({ credentials: 'YOUR_ACCESS_TOKEN' });
347
+ * const cc = webex.cc;
348
+ *
349
+ * // Register the SDK and fetch agent profile
350
+ * const profile = await cc.register();
351
+ *
352
+ * console.log('Agent ID:', profile.agentId);
353
+ * console.log('Default DN:', profile.defaultDn);
354
+ * console.log('Teams:', profile.teams.map(t => t.teamId));
355
+ * console.log('WebRTC Enabled:', profile.webRtcEnabled);
356
+ * console.log('Supported Login Options:', profile.loginVoiceOptions);
357
+ *
358
+ * // Now you can proceed with station login, state changes, etc.
359
+ * await cc.stationLogin({ teamId: profile.teams[0].teamId, loginOption: 'BROWSER' });
360
+ * ```
361
+ */
362
+ public async register(): Promise<Profile> {
363
+ LoggerProxy.info('Starting CC SDK registration', {
364
+ module: CC_FILE,
365
+ method: METHODS.REGISTER,
366
+ });
367
+ try {
368
+ this.metricsManager.timeEvent([
369
+ METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_SUCCESS,
370
+ METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
371
+ ]);
372
+ this.setupEventListeners();
373
+
374
+ const resp = await this.connectWebsocket();
375
+ // Ensure 'dn' is always populated from 'defaultDn'
376
+ resp.dn = resp.defaultDn;
377
+ this.metricsManager.trackEvent(
378
+ METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_SUCCESS,
379
+ {
380
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp),
381
+ deviceType: resp.deviceType || EMPTY_STRING,
382
+ },
383
+ ['operational']
384
+ );
385
+
386
+ LoggerProxy.log(`CC SDK registration completed successfully with agentId: ${resp.agentId}`, {
387
+ module: CC_FILE,
388
+ method: METHODS.REGISTER,
389
+ });
390
+
391
+ return resp;
392
+ } catch (error) {
393
+ this.metricsManager.trackEvent(
394
+ METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
395
+ {
396
+ orgId: error.orgId,
397
+ },
398
+ ['operational']
399
+ );
400
+ LoggerProxy.error(`Error during register: ${error}`, {
401
+ module: CC_FILE,
402
+ method: METHODS.REGISTER,
403
+ });
404
+ this.webexRequest.uploadLogs({
405
+ correlationId: error?.trackingId,
406
+ });
407
+
408
+ throw error;
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Unregisters the Contact Center SDK by closing all web socket connections, removing event listeners,
414
+ * and cleaning up internal state.
415
+ *
416
+ * @remarks
417
+ * This method only disconnects the SDK from the backend and cleans up resources. It does NOT perform a station logout
418
+ * (i.e., the agent remains logged in to the contact center unless you explicitly call {@link stationLogout}).
419
+ * Use this when you want to fully tear down the SDK instance, such as during application shutdown or user sign-out.
420
+ *
421
+ * @returns {Promise<void>} Resolves when deregistration and cleanup are complete.
422
+ * @throws {Error} If deregistration fails.
423
+ *
424
+ * @public
425
+ * @example
426
+ * // Typical usage: clean up SDK before application exit or user logout
427
+ * import Webex from 'webex';
428
+ *
429
+ * const webex = Webex.init({ credentials: 'YOUR_ACCESS_TOKEN' });
430
+ * const cc = webex.cc;
431
+ *
432
+ * await cc.register();
433
+ * await cc.stationLogin({ teamId: 'team123', loginOption: 'BROWSER' });
434
+ * // ... perform agent operations ...
435
+ *
436
+ * // If you want to log out the agent as well, call:
437
+ * // await cc.stationLogout({ logoutReason: 'User signed out' });
438
+ * // On application shutdown or user sign-out:
439
+ * await cc.deregister();
440
+ *
441
+
442
+ */
443
+ public async deregister(): Promise<void> {
444
+ try {
445
+ this.metricsManager.timeEvent([
446
+ METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_SUCCESS,
447
+ METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_FAIL,
448
+ ]);
449
+
450
+ this.taskManager.off(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
451
+ this.taskManager.off(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
452
+ this.taskManager.unregisterIncomingCallEvent();
453
+
454
+ this.services.webSocketManager.off('message', this.handleWebsocketMessage);
455
+ this.services.connectionService.off('connectionLost', this.handleConnectionLost);
456
+
457
+ if (
458
+ this.agentConfig.webRtcEnabled &&
459
+ this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER)
460
+ ) {
461
+ if (this.$webex.internal.mercury.connected) {
462
+ this.$webex.internal.mercury.off('online');
463
+ this.$webex.internal.mercury.off('offline');
464
+ await this.$webex.internal.mercury.disconnect();
465
+ // @ts-ignore
466
+ await this.$webex.internal.device.unregister();
467
+ LoggerProxy.log(MERCURY_DISCONNECTED_SUCCESS, {
468
+ module: CC_FILE,
469
+ method: METHODS.DEREGISTER,
470
+ });
471
+ }
472
+ }
473
+
474
+ if (!this.services.webSocketManager.isSocketClosed) {
475
+ this.services.webSocketManager.close(false, 'Unregistering the SDK');
476
+ }
477
+
478
+ // Clear any cached agent configuration
479
+ this.agentConfig = null;
480
+
481
+ LoggerProxy.log('Deregistered successfully', {
482
+ module: CC_FILE,
483
+ method: METHODS.DEREGISTER,
484
+ });
485
+
486
+ this.metricsManager.trackEvent(METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_SUCCESS, {}, [
487
+ 'operational',
488
+ ]);
489
+ } catch (error) {
490
+ this.metricsManager.trackEvent(
491
+ METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_FAIL,
492
+ {
493
+ error: error.message || UNKNOWN_ERROR,
494
+ },
495
+ ['operational']
496
+ );
497
+
498
+ LoggerProxy.error(`Error during deregister: ${error}`, {
499
+ module: CC_FILE,
500
+ method: METHODS.DEREGISTER,
501
+ });
502
+
503
+ throw error;
504
+ }
505
+ }
506
+
507
+ /**
508
+ * Returns the list of buddy agents who are in the given user state and media type based on their agent profile settings
509
+ * @param {BuddyAgents} data The data required to fetch buddy agents
510
+ * @returns {Promise<BuddyAgentsResponse>} A promise resolving to the buddy agents information
511
+ * @throws {Error} If fetching buddy agents fails
512
+ * @example
513
+ * ```typescript
514
+ * // Get list of available agents for consultation or transfer
515
+ * const cc = webex.cc;
516
+ *
517
+ * // First ensure you're registered and logged in
518
+ * await cc.register();
519
+ * await cc.stationLogin({ teamId: 'team123', loginOption: 'BROWSER' });
520
+ *
521
+ * // Get buddy agents filtered by state and media type
522
+ * const response = await cc.getBuddyAgents({
523
+ * state: 'Available', // Filter by agent state ('Available', 'Idle', etc.)
524
+ * mediaType: 'telephony' // Filter by media type ('telephony', 'chat', 'email', 'social')
525
+ * });
526
+ *
527
+ * // Process the buddy agents list
528
+ * if (response.data.agentList.length > 0) {
529
+ * const buddyAgents = response.data.agentList;
530
+ * console.log(`Found ${buddyAgents.length} available agents`);
531
+ *
532
+ * // Access agent details
533
+ * buddyAgents.forEach(agent => {
534
+ * console.log(`Agent ID: ${agent.agentId}`);
535
+ * console.log(`Name: ${agent.firstName} ${agent.lastName}`);
536
+ * console.log(`State: ${agent.state}`);
537
+ * console.log(`Team: ${agent.teamName}`);
538
+ * });
539
+ * }
540
+ * ```
541
+ */
542
+ public async getBuddyAgents(data: BuddyAgents): Promise<BuddyAgentsResponse> {
543
+ LoggerProxy.info('Fetching buddy agents', {
544
+ module: CC_FILE,
545
+ method: METHODS.GET_BUDDY_AGENTS,
546
+ });
547
+ try {
548
+ this.metricsManager.timeEvent([
549
+ METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_SUCCESS,
550
+ METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED,
551
+ ]);
552
+ const resp = await this.services.agent.buddyAgents({
553
+ data: {agentProfileId: this.agentConfig.agentProfileID, ...data},
554
+ });
555
+
556
+ this.metricsManager.trackEvent(
557
+ METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_SUCCESS,
558
+ {
559
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp),
560
+ mediaType: data.mediaType,
561
+ buddyAgentState: data.state,
562
+ buddyAgentCount: resp.data.agentList.length,
563
+ },
564
+ ['operational']
565
+ );
566
+
567
+ LoggerProxy.log(`Successfully retrieved ${resp.data.agentList.length} buddy agents`, {
568
+ module: CC_FILE,
569
+ method: METHODS.GET_BUDDY_AGENTS,
570
+ trackingId: resp.trackingId,
571
+ });
572
+
573
+ return resp;
574
+ } catch (error) {
575
+ const failureResp = error.details as Failure;
576
+
577
+ this.metricsManager.trackEvent(
578
+ METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED,
579
+ {
580
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failureResp),
581
+ mediaType: data.mediaType,
582
+ buddyAgentState: data.state,
583
+ },
584
+ ['operational']
585
+ );
586
+ const {error: detailedError} = getErrorDetails(error, METHODS.GET_BUDDY_AGENTS, CC_FILE);
587
+ throw detailedError;
588
+ }
589
+ }
590
+
591
+ /**
592
+ * Connects to the websocket and fetches the agent profile
593
+ * @returns {Promise<Profile>} Agent profile information
594
+ * @throws {Error} If connection fails or profile cannot be fetched
595
+ * @private
596
+ */
597
+ private async connectWebsocket() {
598
+ LoggerProxy.info('Connecting to websocket', {
599
+ module: CC_FILE,
600
+ method: METHODS.CONNECT_WEBSOCKET,
601
+ });
602
+ try {
603
+ return this.services.webSocketManager
604
+ .initWebSocket({
605
+ body: this.getConnectionConfig(),
606
+ })
607
+ .then(async (data: WelcomeEvent) => {
608
+ const agentId = data.agentId;
609
+ const orgId = this.$webex.credentials.getOrgId();
610
+ this.agentConfig = await this.services.config.getAgentConfig(orgId, agentId);
611
+ LoggerProxy.log(`Agent config is fetched successfully`, {
612
+ module: CC_FILE,
613
+ method: METHODS.CONNECT_WEBSOCKET,
614
+ });
615
+ // TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere
616
+ this.taskManager.setWrapupData(this.agentConfig.wrapUpData);
617
+
618
+ if (
619
+ this.agentConfig.webRtcEnabled &&
620
+ this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER)
621
+ ) {
622
+ this.$webex.internal.mercury
623
+ .connect()
624
+ .then(() => {
625
+ LoggerProxy.log('Authentication: webex.internal.mercury.connect successful', {
626
+ module: CC_FILE,
627
+ method: METHODS.CONNECT_WEBSOCKET,
628
+ });
629
+ })
630
+ .catch((error) => {
631
+ LoggerProxy.error(`Error occurred during mercury.connect() ${error}`, {
632
+ module: CC_FILE,
633
+ method: METHODS.CONNECT_WEBSOCKET,
634
+ });
635
+ });
636
+ }
637
+ if (this.$config && this.$config.allowAutomatedRelogin) {
638
+ await this.silentRelogin();
639
+ }
640
+
641
+ return this.agentConfig;
642
+ })
643
+ .catch((error) => {
644
+ throw error;
645
+ });
646
+ } catch (error) {
647
+ LoggerProxy.error(`Error during register: ${error}`, {
648
+ module: CC_FILE,
649
+ method: METHODS.CONNECT_WEBSOCKET,
650
+ });
651
+
652
+ throw error;
653
+ }
654
+ }
655
+
656
+ /**
657
+ * Performs agent login with specified credentials and device type
658
+ * @param {AgentLogin} data Login parameters including teamId, loginOption and dialNumber
659
+ * @returns {Promise<StationLoginResponse>} Response containing login status and profile
660
+ * @throws {Error} If login fails
661
+ * @public
662
+ * @example
663
+ * ```typescript
664
+ * const cc = webex.cc;
665
+ * await cc.register();
666
+ *
667
+ * // Primary usage: using Promise response
668
+ * try {
669
+ * const response = await cc.stationLogin({
670
+ * teamId: 'team123',
671
+ * loginOption: 'EXTENSION',
672
+ * dialNumber: '1002'
673
+ * });
674
+ * console.log('Login successful:', response);
675
+ * } catch (error) {
676
+ * console.error('Login failed:', error);
677
+ * }
678
+ *
679
+ * // Optional: Also listen for events elsewhere in your application
680
+ * // cc.on('agent:stationLoginSuccess', (data) => { ... });
681
+ * // cc.on('agent:stationLoginFailed', (error) => { ... });
682
+ * ```
683
+ */
684
+ public async stationLogin(data: AgentLogin): Promise<StationLoginResponse> {
685
+ LoggerProxy.info('Starting agent station login', {
686
+ module: CC_FILE,
687
+ method: METHODS.STATION_LOGIN,
688
+ });
689
+ try {
690
+ this.metricsManager.timeEvent([
691
+ METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
692
+ METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
693
+ ]);
694
+
695
+ if (data.loginOption === LoginOption.AGENT_DN && !isValidDialNumber(data.dialNumber)) {
696
+ const error = new Error('INVALID_DIAL_NUMBER');
697
+ // @ts-ignore - adding custom key to the error object
698
+ error.details = {data: {reason: 'INVALID_DIAL_NUMBER'}} as Failure;
699
+
700
+ throw error;
701
+ }
702
+
703
+ const loginResponse = this.services.agent.stationLogin({
704
+ data: {
705
+ dialNumber:
706
+ data.loginOption === LoginOption.BROWSER ? this.agentConfig.agentId : data.dialNumber,
707
+ teamId: data.teamId,
708
+ deviceType: data.loginOption,
709
+ isExtension: data.loginOption === LoginOption.EXTENSION,
710
+ deviceId: this.getDeviceId(data.loginOption, data.dialNumber),
711
+ roles: [AGENT],
712
+ teamName: EMPTY_STRING,
713
+ siteId: EMPTY_STRING,
714
+ usesOtherDN: false,
715
+ auxCodeId: EMPTY_STRING,
716
+ },
717
+ });
718
+
719
+ if (this.agentConfig.webRtcEnabled && data.loginOption === LoginOption.BROWSER) {
720
+ await this.webCallingService.registerWebCallingLine();
721
+ }
722
+
723
+ const resp = await loginResponse;
724
+ const {channelsMap, ...loginData} = resp.data;
725
+ this.agentConfig.currentTeamId = resp.data.teamId;
726
+ const response = {
727
+ ...loginData,
728
+ mmProfile: {
729
+ chat: channelsMap.chat?.length,
730
+ email: channelsMap.email?.length,
731
+ social: channelsMap.social?.length,
732
+ telephony: channelsMap.telephony?.length,
733
+ },
734
+ notifsTrackingId: resp.trackingId,
735
+ };
736
+
737
+ this.webCallingService.setLoginOption(data.loginOption);
738
+ this.metricsManager.trackEvent(
739
+ METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
740
+ {
741
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp),
742
+ loginType: data.loginOption,
743
+ status: resp.data.status,
744
+ type: resp.data.type,
745
+ roles: resp.data.roles?.join(',') || EMPTY_STRING,
746
+ },
747
+ ['behavioral', 'business', 'operational']
748
+ );
749
+
750
+ LoggerProxy.log(
751
+ `Agent station login completed successfully agentId: ${resp.data.agentId} loginOption: ${data.loginOption} teamId: ${data.teamId}`,
752
+ {
753
+ module: CC_FILE,
754
+ method: METHODS.STATION_LOGIN,
755
+ trackingId: resp.trackingId,
756
+ }
757
+ );
758
+
759
+ return response;
760
+ } catch (error) {
761
+ const failure = error.details as Failure;
762
+ this.metricsManager.trackEvent(
763
+ METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
764
+ {
765
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
766
+ loginType: data.loginOption,
767
+ },
768
+ ['behavioral', 'business', 'operational']
769
+ );
770
+ error.loginOption = data.loginOption;
771
+ const {error: detailedError} = getErrorDetails(error, METHODS.STATION_LOGIN, CC_FILE);
772
+
773
+ throw detailedError;
774
+ }
775
+ }
776
+
777
+ /**
778
+ * Performs a station logout operation for the agent
779
+ * @remarks
780
+ * A logout operation cannot happen if the agent is in an interaction or haven't logged in yet.
781
+ * @param {Logout} data Logout parameters with logoutReason - a string explaining why the agent is logging out
782
+ * @returns {Promise<StationLogoutResponse>} Response indicating logout status
783
+ * @throws {Error} If logout fails
784
+ * @public
785
+ * @example
786
+ * ```typescript
787
+ * // Basic logout
788
+ * try {
789
+ * await cc.stationLogout({
790
+ * logoutReason: 'End of shift'
791
+ * });
792
+ * console.log('Logged out successfully');
793
+ * } catch (error) {
794
+ * console.error('Logout failed:', error);
795
+ * }
796
+ * ```
797
+ */
798
+ public async stationLogout(data: Logout): Promise<StationLogoutResponse> {
799
+ LoggerProxy.info('Starting agent station logout', {
800
+ module: CC_FILE,
801
+ method: METHODS.STATION_LOGOUT,
802
+ });
803
+ try {
804
+ this.metricsManager.timeEvent([
805
+ METRIC_EVENT_NAMES.STATION_LOGOUT_SUCCESS,
806
+ METRIC_EVENT_NAMES.STATION_LOGOUT_FAILED,
807
+ ]);
808
+ const logoutResponse = this.services.agent.logout({
809
+ data,
810
+ });
811
+
812
+ const resp = await logoutResponse;
813
+
814
+ this.metricsManager.trackEvent(
815
+ METRIC_EVENT_NAMES.STATION_LOGOUT_SUCCESS,
816
+ {
817
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp),
818
+ logoutReason: data.logoutReason,
819
+ },
820
+ ['behavioral', 'business', 'operational']
821
+ );
822
+
823
+ if (this.webCallingService) {
824
+ this.webCallingService.deregisterWebCallingLine();
825
+ }
826
+
827
+ LoggerProxy.log(`Agent station logout completed successfully`, {
828
+ module: CC_FILE,
829
+ method: METHODS.STATION_LOGOUT,
830
+ trackingId: resp.trackingId,
831
+ });
832
+
833
+ return resp;
834
+ } catch (error) {
835
+ const failure = error.details as Failure;
836
+ this.metricsManager.trackEvent(
837
+ METRIC_EVENT_NAMES.STATION_LOGOUT_FAILED,
838
+ {
839
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
840
+ logoutReason: data.logoutReason,
841
+ },
842
+ ['behavioral', 'business', 'operational']
843
+ );
844
+ const {error: detailedError} = getErrorDetails(error, METHODS.STATION_LOGOUT, CC_FILE);
845
+ throw detailedError;
846
+ }
847
+ }
848
+
849
+ /**
850
+ * Gets the device ID based on login option and dial number
851
+ * @param {string} loginOption The login option (BROWSER, EXTENSION, etc)
852
+ * @param {string} dialNumber The dial number if applicable
853
+ * @returns {string} The device ID
854
+ * @private
855
+ */
856
+ private getDeviceId(loginOption: string, dialNumber: string): string {
857
+ if (loginOption === LoginOption.EXTENSION || loginOption === LoginOption.AGENT_DN) {
858
+ return dialNumber;
859
+ }
860
+
861
+ return WEB_RTC_PREFIX + this.agentConfig.agentId;
862
+ }
863
+
864
+ /**
865
+ * Sets the state of the agent to Available or any of the Idle states.
866
+ * After a state change attempt, one of the following events will be emitted:
867
+ * - agent:stateChange: Emitted when agent's state changes (triggered for both local and remote changes)
868
+ * - agent:stateChangeSuccess: Emitted when agent state change is successful
869
+ * - agent:stateChangeFailed: Emitted when agent state change attempt fails
870
+ *
871
+ * @param {StateChange} data State change parameters including the new state
872
+ * @returns {Promise<SetStateResponse>} Response with updated state information
873
+ * @throws {Error} If state change fails
874
+ * @public
875
+ * @example
876
+ * ```typescript
877
+ * const cc = webex.cc;
878
+ * await cc.register();
879
+ * await cc.stationLogin({ teamId: 'team123', loginOption: 'BROWSER' });
880
+ *
881
+ * // Using promise-based approach
882
+ * try {
883
+ * await cc.setAgentState({
884
+ * state: 'Available',
885
+ * auxCodeId: '12345',
886
+ * lastStateChangeReason: 'Manual state change',
887
+ * agentId: 'agent123',
888
+ * });
889
+ * } catch (error) {
890
+ * console.error('State change failed:', error);
891
+ * }
892
+ *
893
+ * // Optionally, listen for events
894
+ * cc.on('agent:stateChange', (eventData) => {
895
+ * // Triggered for both local and remote state changes
896
+ * console.log('State changed:', eventData);
897
+ * });
898
+ *
899
+ * cc.on('agent:stateChangeSuccess', (eventData) => {
900
+ * console.log('State change succeeded:', eventData);
901
+ * });
902
+ *
903
+ * cc.on('agent:stateChangeFailed', (error) => {
904
+ * console.error('State change failed:', error);
905
+ * });
906
+ * ```
907
+ */
908
+ public async setAgentState(data: StateChange): Promise<SetStateResponse> {
909
+ LoggerProxy.info('Setting agent state', {
910
+ module: CC_FILE,
911
+ method: METHODS.SET_AGENT_STATE,
912
+ });
913
+ try {
914
+ this.metricsManager.timeEvent([
915
+ METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_SUCCESS,
916
+ METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_FAILED,
917
+ ]);
918
+ const agentStatusResponse = await this.services.agent.stateChange({
919
+ data: {...data, agentId: data.agentId || this.agentConfig.agentId},
920
+ });
921
+
922
+ this.metricsManager.trackEvent(
923
+ METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_SUCCESS,
924
+ {
925
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(agentStatusResponse),
926
+ requestedState: data.state,
927
+ teamId: this.agentConfig?.teams[0]?.teamId ?? EMPTY_STRING,
928
+ status: agentStatusResponse.data?.status,
929
+ subStatus: agentStatusResponse.data?.subStatus,
930
+ auxCodeId: data.auxCodeId,
931
+ lastStateChangeReason: data.lastStateChangeReason || EMPTY_STRING,
932
+ },
933
+ ['behavioral', 'business', 'operational']
934
+ );
935
+
936
+ LoggerProxy.log(
937
+ `Agent state changed successfully to auxCodeId: ${agentStatusResponse.data.auxCodeId}`,
938
+ {
939
+ module: CC_FILE,
940
+ method: METHODS.SET_AGENT_STATE,
941
+ trackingId: agentStatusResponse.trackingId,
942
+ }
943
+ );
944
+
945
+ return agentStatusResponse;
946
+ } catch (error) {
947
+ const failure = error.details as Failure;
948
+ this.metricsManager.trackEvent(
949
+ METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_FAILED,
950
+ {
951
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
952
+ state: data.state,
953
+ auxCodeId: data.auxCodeId,
954
+ lastStateChangeReason: data.lastStateChangeReason || EMPTY_STRING,
955
+ },
956
+ ['behavioral', 'business', 'operational']
957
+ );
958
+ const {error: detailedError} = getErrorDetails(error, METHODS.SET_AGENT_STATE, CC_FILE);
959
+ throw detailedError;
960
+ }
961
+ }
962
+
963
+ /**
964
+ * Processes incoming websocket messages and emits corresponding events
965
+ * Handles various event types including agent state changes, login events,
966
+ * and other agent-related notifications
967
+ * @private
968
+ * @param {string} event The raw websocket event message
969
+ */
970
+ private handleWebsocketMessage = (event: string) => {
971
+ const eventData = JSON.parse(event);
972
+ // Re-emit all the events related to agent except keep-alives
973
+ if (!eventData.keepalive && eventData.data && eventData.data.type) {
974
+ // @ts-ignore
975
+ this.emit(eventData.data.type, eventData.data);
976
+ }
977
+
978
+ if (!eventData.type) {
979
+ return;
980
+ }
981
+
982
+ LoggerProxy.log(`Received event: ${eventData.type}`, {
983
+ module: CC_FILE,
984
+ method: METHODS.HANDLE_WEBSOCKET_MESSAGE,
985
+ });
986
+
987
+ switch (eventData.type) {
988
+ case CC_EVENTS.AGENT_MULTI_LOGIN:
989
+ // @ts-ignore
990
+ this.emit(AGENT_EVENTS.AGENT_MULTI_LOGIN, eventData.data);
991
+ break;
992
+ case CC_EVENTS.AGENT_STATE_CHANGE:
993
+ // @ts-ignore
994
+ this.emit(AGENT_EVENTS.AGENT_STATE_CHANGE, eventData.data);
995
+ break;
996
+ default:
997
+ break;
998
+ }
999
+
1000
+ if (!(eventData.data && eventData.data.type)) {
1001
+ return;
1002
+ }
1003
+
1004
+ switch (eventData.data.type) {
1005
+ case CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS: {
1006
+ const {channelsMap, ...loginData} = eventData.data;
1007
+ const stationLoginData = {
1008
+ ...loginData,
1009
+ mmProfile: {
1010
+ chat: channelsMap.chat?.length,
1011
+ email: channelsMap.email?.length,
1012
+ social: channelsMap.social?.length,
1013
+ telephony: channelsMap.telephony?.length,
1014
+ },
1015
+ notifsTrackingId: eventData.trackingId,
1016
+ };
1017
+ this.webCallingService.setLoginOption(loginData.deviceType as LoginOption);
1018
+ // @ts-ignore
1019
+ this.emit(AGENT_EVENTS.AGENT_STATION_LOGIN_SUCCESS, stationLoginData);
1020
+ break;
1021
+ }
1022
+ case CC_EVENTS.AGENT_RELOGIN_SUCCESS:
1023
+ {
1024
+ const {channelsMap, ...loginData} = eventData.data;
1025
+ const stationReLoginData = {
1026
+ ...loginData,
1027
+ mmProfile: {
1028
+ chat: channelsMap.chat?.length,
1029
+ email: channelsMap.email?.length,
1030
+ social: channelsMap.social?.length,
1031
+ telephony: channelsMap.telephony?.length,
1032
+ },
1033
+ notifsTrackingId: eventData.trackingId,
1034
+ };
1035
+ // @ts-ignore
1036
+ this.emit(AGENT_EVENTS.AGENT_RELOGIN_SUCCESS, stationReLoginData);
1037
+ }
1038
+ break;
1039
+ case CC_EVENTS.AGENT_STATE_CHANGE_SUCCESS:
1040
+ // @ts-ignore
1041
+ this.emit(AGENT_EVENTS.AGENT_STATE_CHANGE_SUCCESS, eventData.data);
1042
+ break;
1043
+ case CC_EVENTS.AGENT_STATE_CHANGE_FAILED:
1044
+ // @ts-ignore
1045
+ this.emit(AGENT_EVENTS.AGENT_STATE_CHANGE_FAILED, eventData.data);
1046
+ break;
1047
+ case CC_EVENTS.AGENT_STATION_LOGIN_FAILED:
1048
+ // @ts-ignore
1049
+ this.emit(AGENT_EVENTS.AGENT_STATION_LOGIN_FAILED, eventData.data);
1050
+ break;
1051
+ case CC_EVENTS.AGENT_LOGOUT_SUCCESS:
1052
+ // @ts-ignore
1053
+ this.emit(AGENT_EVENTS.AGENT_LOGOUT_SUCCESS, eventData.data);
1054
+ break;
1055
+ case CC_EVENTS.AGENT_LOGOUT_FAILED:
1056
+ // @ts-ignore
1057
+ this.emit(AGENT_EVENTS.AGENT_LOGOUT_FAILED, eventData.data);
1058
+ break;
1059
+ case CC_EVENTS.AGENT_DN_REGISTERED:
1060
+ // @ts-ignore
1061
+ this.emit(AGENT_EVENTS.AGENT_DN_REGISTERED, eventData.data);
1062
+ break;
1063
+ default:
1064
+ break;
1065
+ }
1066
+ };
1067
+
1068
+ /**
1069
+ * Initializes event listeners for the Contact Center service
1070
+ * Sets up handlers for connection state changes and other core events
1071
+ * @private
1072
+ */
1073
+ private setupEventListeners() {
1074
+ this.services.connectionService.on('connectionLost', this.handleConnectionLost.bind(this));
1075
+ }
1076
+
1077
+ /**
1078
+ * Returns the connection configuration
1079
+ * @returns {SubscribeRequest} Connection configuration
1080
+ * @private
1081
+ */
1082
+ private getConnectionConfig(): SubscribeRequest {
1083
+ return {
1084
+ force: this.$config?.force ?? true,
1085
+ isKeepAliveEnabled: this.$config?.isKeepAliveEnabled ?? false,
1086
+ clientType: this.$config?.clientType ?? 'WebexCCSDK',
1087
+ allowMultiLogin: this.$config?.allowMultiLogin ?? true,
1088
+ };
1089
+ }
1090
+
1091
+ /**
1092
+ * Handles connection lost events and reconnection attempts
1093
+ * @param {ConnectionLostDetails} msg Connection lost details
1094
+ * @private
1095
+ */
1096
+ private async handleConnectionLost(msg: ConnectionLostDetails): Promise<void> {
1097
+ if (msg.isConnectionLost) {
1098
+ // TODO: Emit an event saying connection is lost
1099
+ LoggerProxy.info('event=handleConnectionLost | Connection lost', {
1100
+ module: CC_FILE,
1101
+ method: METHODS.HANDLE_CONNECTION_LOST,
1102
+ });
1103
+ } else if (msg.isSocketReconnected) {
1104
+ // TODO: Emit an event saying connection is re-estabilished
1105
+ LoggerProxy.info(
1106
+ 'event=handleConnectionReconnect | Connection reconnected attempting to request silent relogin',
1107
+ {module: CC_FILE, method: METHODS.HANDLE_CONNECTION_LOST}
1108
+ );
1109
+ if (this.$config && this.$config.allowAutomatedRelogin) {
1110
+ await this.silentRelogin();
1111
+ }
1112
+ }
1113
+ }
1114
+
1115
+ /**
1116
+ * Handles silent relogin after registration completion
1117
+ * @private
1118
+ */
1119
+ private async silentRelogin(): Promise<void> {
1120
+ LoggerProxy.info('Starting silent relogin process', {
1121
+ module: CC_FILE,
1122
+ method: METHODS.SILENT_RELOGIN,
1123
+ });
1124
+
1125
+ try {
1126
+ const reLoginResponse = await this.services.agent.reload();
1127
+ const {
1128
+ agentId,
1129
+ lastStateChangeReason,
1130
+ deviceType,
1131
+ dn,
1132
+ lastStateChangeTimestamp,
1133
+ lastIdleCodeChangeTimestamp,
1134
+ } = reLoginResponse.data;
1135
+ let {auxCodeId} = reLoginResponse.data;
1136
+ this.agentConfig.lastStateChangeTimestamp = lastStateChangeTimestamp;
1137
+ this.agentConfig.lastIdleCodeChangeTimestamp = lastIdleCodeChangeTimestamp;
1138
+ this.agentConfig.currentTeamId = reLoginResponse.data.teamId;
1139
+ await this.handleDeviceType(deviceType as LoginOption, dn);
1140
+
1141
+ if (lastStateChangeReason === 'agent-wss-disconnect') {
1142
+ LoggerProxy.info(
1143
+ 'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1144
+ {module: CC_FILE, method: METHODS.SILENT_RELOGIN}
1145
+ );
1146
+ auxCodeId = AGENT_STATE_AVAILABLE_ID;
1147
+ const stateChangeData: StateChange = {
1148
+ state: AGENT_STATE_AVAILABLE,
1149
+ auxCodeId,
1150
+ lastStateChangeReason,
1151
+ agentId,
1152
+ };
1153
+ try {
1154
+ const agentStatusResponse = (await this.setAgentState(
1155
+ stateChangeData
1156
+ )) as StateChangeSuccess;
1157
+ this.agentConfig.lastStateChangeTimestamp =
1158
+ agentStatusResponse.data.lastStateChangeTimestamp;
1159
+
1160
+ this.agentConfig.lastIdleCodeChangeTimestamp =
1161
+ agentStatusResponse.data.lastIdleCodeChangeTimestamp;
1162
+ } catch (error) {
1163
+ LoggerProxy.error(
1164
+ `event=requestAutoStateChange | Error requesting state change to available on socket reconnect: ${error}`,
1165
+ {module: CC_FILE, method: METHODS.SILENT_RELOGIN}
1166
+ );
1167
+ }
1168
+ }
1169
+ this.agentConfig.lastStateAuxCodeId = auxCodeId;
1170
+ this.agentConfig.isAgentLoggedIn = true;
1171
+ // TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
1172
+ this.services.webSocketManager.on('message', this.handleWebsocketMessage);
1173
+
1174
+ LoggerProxy.log(
1175
+ `Silent relogin process completed successfully with login Option: ${reLoginResponse.data.deviceType} teamId: ${reLoginResponse.data.teamId}`,
1176
+ {
1177
+ module: CC_FILE,
1178
+ method: METHODS.SILENT_RELOGIN,
1179
+ trackingId: reLoginResponse.trackingId,
1180
+ }
1181
+ );
1182
+ } catch (error) {
1183
+ const {reason, error: detailedError} = getErrorDetails(
1184
+ error,
1185
+ METHODS.SILENT_RELOGIN,
1186
+ CC_FILE
1187
+ );
1188
+ if (reason === 'AGENT_NOT_FOUND') {
1189
+ LoggerProxy.log('Agent not found during relogin, handling silently', {
1190
+ module: CC_FILE,
1191
+ method: METHODS.SILENT_RELOGIN,
1192
+ });
1193
+
1194
+ return;
1195
+ }
1196
+ throw detailedError;
1197
+ }
1198
+ }
1199
+
1200
+ /**
1201
+ * Handles device type specific configuration and setup
1202
+ * Configures services and settings based on the login device type
1203
+ * @param {LoginOption} deviceType The type of device being used for login
1204
+ * @param {string} dn The dial number associated with the device
1205
+ * @returns {Promise<void>}
1206
+ * @private
1207
+ */
1208
+ private async handleDeviceType(deviceType: LoginOption, dn: string): Promise<void> {
1209
+ this.webCallingService.setLoginOption(deviceType);
1210
+ this.agentConfig.deviceType = deviceType;
1211
+ switch (deviceType) {
1212
+ case LoginOption.BROWSER:
1213
+ try {
1214
+ await this.webCallingService.registerWebCallingLine();
1215
+ } catch (error) {
1216
+ LoggerProxy.error(`Error registering web calling line: ${error}`, {
1217
+ module: CC_FILE,
1218
+ method: METHODS.HANDLE_DEVICE_TYPE,
1219
+ });
1220
+ throw error;
1221
+ }
1222
+ break;
1223
+ case LoginOption.AGENT_DN:
1224
+ case LoginOption.EXTENSION:
1225
+ this.agentConfig.defaultDn = dn;
1226
+ this.agentConfig.dn = dn;
1227
+ break;
1228
+ default:
1229
+ LoggerProxy.error(`Unsupported device type: ${deviceType}`, {
1230
+ module: CC_FILE,
1231
+ method: METHODS.HANDLE_DEVICE_TYPE,
1232
+ });
1233
+ throw new Error(`Unsupported device type: ${deviceType}`);
1234
+ }
1235
+ }
1236
+
1237
+ /**
1238
+ * Makes an outbound call to a specified phone number.
1239
+ *
1240
+ * @param {string} destination - The phone number to dial (e.g., '+1234567890').
1241
+ * Should include country code and be in E.164 format.
1242
+ * @returns {Promise<TaskResponse>} Resolves with the task response containing:
1243
+ * - interactionId: Unique identifier for the outbound call
1244
+ * - taskId: Identifier for the task instance
1245
+ * - data: Task details including state, queue info, and media properties
1246
+ * @throws {Error} If the outdial operation fails:
1247
+ * - "Agent not configured for outbound calls" if isOutboundEnabledForAgent is false
1248
+ * - "Invalid phone number format" if destination is not in E.164 format
1249
+ * - "Agent not in Available state" if agent's state is not Available
1250
+ * @public
1251
+ * @example
1252
+ * ```typescript
1253
+ * // Initialize and prepare agent
1254
+ * const cc = webex.cc;
1255
+ * await cc.register();
1256
+ * await cc.stationLogin({
1257
+ * teamId: 'team123',
1258
+ * loginOption: 'BROWSER'
1259
+ * });
1260
+ *
1261
+ * // Set Available state before outbound call
1262
+ * await cc.setAgentState({
1263
+ * state: 'Available',
1264
+ * auxCodeId: '0'
1265
+ * });
1266
+ *
1267
+ * // Make outbound call with full error handling
1268
+ * try {
1269
+ * // Verify agent is properly configured for outdial
1270
+ * if (!cc.agentConfig.isOutboundEnabledForAgent) {
1271
+ * throw new Error('Agent not configured for outbound calls');
1272
+ * }
1273
+ *
1274
+ * // Start the outbound call
1275
+ * const destination = '+1234567890';
1276
+ * const task = await cc.startOutdial(destination);
1277
+ *
1278
+ * // Listen for all relevant task events
1279
+ * task.on('task:ringing', () => {
1280
+ * console.log('Call is ringing');
1281
+ * updateCallStatus('Ringing...');
1282
+ * });
1283
+ *
1284
+ * task.on('task:established', () => {
1285
+ * console.log('Call connected');
1286
+ * updateCallStatus('Connected');
1287
+ * enableCallControls(); // Show mute, hold, transfer buttons
1288
+ * });
1289
+ *
1290
+ * task.on('task:hold', () => {
1291
+ * console.log('Call placed on hold');
1292
+ * updateCallStatus('On Hold');
1293
+ * });
1294
+ *
1295
+ * task.on('task:error', (error) => {
1296
+ * console.error('Call error:', error);
1297
+ * updateCallStatus('Error');
1298
+ * showErrorDialog(error.message);
1299
+ * });
1300
+ *
1301
+ * task.on('task:ended', () => {
1302
+ * console.log('Call ended');
1303
+ * updateCallStatus('Call Ended');
1304
+ * resetCallControls();
1305
+ *
1306
+ * // Handle wrap-up if required
1307
+ * if (task.data.wrapUpRequired) {
1308
+ * showWrapupForm();
1309
+ * }
1310
+ * });
1311
+ *
1312
+ * // Example call control usage
1313
+ * function handleMuteToggle() {
1314
+ * await task.toggleMute();
1315
+ * }
1316
+ *
1317
+ * function handleHoldToggle() {
1318
+ * if (task.data.isOnHold) {
1319
+ * await task.resume();
1320
+ * } else {
1321
+ * await task.hold();
1322
+ * }
1323
+ * }
1324
+ *
1325
+ * async function handleTransfer() {
1326
+ * // Get available queues for transfer
1327
+ * const queues = await cc.getQueues();
1328
+ *
1329
+ * // Transfer to first available queue
1330
+ * if (queues.length > 0) {
1331
+ * await task.transfer({
1332
+ * to: queues[0].queueId,
1333
+ * destinationType: 'QUEUE'
1334
+ * });
1335
+ * }
1336
+ * }
1337
+ *
1338
+ * } catch (error) {
1339
+ * console.error('Outdial failed:', error);
1340
+ * showErrorNotification('Failed to place call: ' + error.message);
1341
+ * }
1342
+ * ```
1343
+ */
1344
+ public async startOutdial(destination: string): Promise<TaskResponse> {
1345
+ LoggerProxy.info('Starting outbound dial', {
1346
+ module: CC_FILE,
1347
+ method: METHODS.START_OUTDIAL,
1348
+ });
1349
+ try {
1350
+ this.metricsManager.timeEvent([
1351
+ METRIC_EVENT_NAMES.TASK_OUTDIAL_SUCCESS,
1352
+ METRIC_EVENT_NAMES.TASK_OUTDIAL_FAILED,
1353
+ ]);
1354
+
1355
+ // Construct the outdial payload.
1356
+ const outDialPayload: DialerPayload = {
1357
+ destination,
1358
+ entryPointId: this.agentConfig.outDialEp,
1359
+ direction: OUTDIAL_DIRECTION,
1360
+ attributes: ATTRIBUTES,
1361
+ mediaType: OUTDIAL_MEDIA_TYPE,
1362
+ outboundType: OUTBOUND_TYPE,
1363
+ };
1364
+
1365
+ const result = await this.services.dialer.startOutdial({data: outDialPayload});
1366
+
1367
+ this.metricsManager.trackEvent(
1368
+ METRIC_EVENT_NAMES.TASK_OUTDIAL_SUCCESS,
1369
+ {
1370
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
1371
+ destination,
1372
+ mediaType: OUTDIAL_MEDIA_TYPE,
1373
+ },
1374
+ ['behavioral', 'business', 'operational']
1375
+ );
1376
+
1377
+ LoggerProxy.log(`Outbound dial completed successfully`, {
1378
+ module: CC_FILE,
1379
+ method: METHODS.START_OUTDIAL,
1380
+ trackingId: result.trackingId,
1381
+ interactionId: result.data?.interactionId,
1382
+ });
1383
+
1384
+ return result;
1385
+ } catch (error) {
1386
+ const failure = error.details as Failure;
1387
+ this.metricsManager.trackEvent(
1388
+ METRIC_EVENT_NAMES.TASK_OUTDIAL_FAILED,
1389
+ {
1390
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
1391
+ destination,
1392
+ mediaType: OUTDIAL_MEDIA_TYPE,
1393
+ },
1394
+ ['behavioral', 'business', 'operational']
1395
+ );
1396
+ const {error: detailedError} = getErrorDetails(error, METHODS.START_OUTDIAL, CC_FILE);
1397
+ throw detailedError;
1398
+ }
1399
+ }
1400
+
1401
+ /**
1402
+ * This is used for getting the list of queues to which a task can be consulted or transferred.
1403
+ * @param {string} [search] - Optional search string to filter queues by name
1404
+ * @param {string} [filter] - Optional OData filter expression (e.g., 'teamId eq "team123"')
1405
+ * @param {number} [page=0] - Page number for paginated results, starting at 0
1406
+ * @param {number} [pageSize=100] - Number of queues to return per page
1407
+ * @returns Promise<ContactServiceQueue[]> Resolves with the list of queues
1408
+ * @throws Error If the operation fails
1409
+ * @public
1410
+ * @example
1411
+ * ```typescript
1412
+ * const cc = webex.cc;
1413
+ * await cc.register();
1414
+ * await cc.stationLogin({ teamId: 'team123', loginOption: 'BROWSER' });
1415
+ *
1416
+ * // Basic usage - get all queues
1417
+ * const allQueues = await cc.getQueues();
1418
+ *
1419
+ * // Search for specific queues
1420
+ * const salesQueues = await cc.getQueues('sales'); // Search for 'sales' in queue names
1421
+ *
1422
+ * // Use filtering and pagination
1423
+ * const filteredQueues = await cc.getQueues(
1424
+ * '', // No search term
1425
+ * 'teamId eq "team123"', // Filter by team
1426
+ * 0, // First page
1427
+ * 50 // 50 items per page
1428
+ * );
1429
+ *
1430
+ * // Process queue results
1431
+ * queues.forEach(queue => {
1432
+ * console.log('Queue:', {
1433
+ * id: queue.queueId,
1434
+ * name: queue.queueName,
1435
+ * channelType: queue.channelType,
1436
+ * isActive: queue.isActive,
1437
+ * description: queue.description
1438
+ * });
1439
+ * });
1440
+ * ```
1441
+ */
1442
+ public async getQueues(
1443
+ search?: string,
1444
+ filter?: string,
1445
+ page = DEFAULT_PAGE,
1446
+ pageSize = DEFAULT_PAGE_SIZE
1447
+ ): Promise<ContactServiceQueue[]> {
1448
+ LoggerProxy.info('Fetching queues', {
1449
+ module: CC_FILE,
1450
+ method: METHODS.GET_QUEUES,
1451
+ });
1452
+
1453
+ const orgId = this.$webex.credentials.getOrgId();
1454
+
1455
+ if (!orgId) {
1456
+ LoggerProxy.error('Org ID not found.', {
1457
+ module: CC_FILE,
1458
+ method: METHODS.GET_QUEUES,
1459
+ });
1460
+
1461
+ throw new Error('Org ID not found.');
1462
+ }
1463
+
1464
+ const result = await this.services.config.getQueues(orgId, page, pageSize, search, filter);
1465
+
1466
+ LoggerProxy.log(`Successfully retrieved ${result?.length} queues`, {
1467
+ module: CC_FILE,
1468
+ method: METHODS.GET_QUEUES,
1469
+ });
1470
+
1471
+ return result;
1472
+ }
1473
+
1474
+ /**
1475
+ * Uploads logs to help troubleshoot SDK issues.
1476
+ *
1477
+ * This method collects the current SDK logs including network requests, WebSocket
1478
+ * messages, and client-side events, then securely submits them to Webex's diagnostics
1479
+ * service. The returned tracking ID, feedbackID can be provided to Webex support for faster
1480
+ * issue resolution.
1481
+ * @returns Promise<UploadLogsResponse> Resolves with the upload logs response
1482
+ * @throws Error If the upload fails
1483
+ * @public
1484
+ * @example
1485
+ * ```typescript
1486
+ * const cc = webex.cc;
1487
+ * try {
1488
+ * await cc.register();
1489
+ * } catch (error) {
1490
+ * console.error('Error:', error);
1491
+ * const result = await cc.uploadLogs();
1492
+ * console.log('Logs uploaded. Tracking ID:', result.trackingId);
1493
+ * }
1494
+ * ```
1495
+ */
1496
+ public async uploadLogs(): Promise<UploadLogsResponse> {
1497
+ return this.webexRequest.uploadLogs();
1498
+ }
1499
+
1500
+ /**
1501
+ * Updates the agent device type and login configuration.
1502
+ * Use this method to change how an agent connects to the contact center system (e.g., switching from browser-based calling to a desk phone extension).
1503
+ *
1504
+ * @param {AgentDeviceUpdate} data Configuration containing:
1505
+ * - loginOption: New device type ('BROWSER', 'EXTENSION', 'AGENT_DN')
1506
+ * - dialNumber: Required phone number when using EXTENSION or AGENT_DN
1507
+ * - teamId: Optional team ID (defaults to current team if not specified)
1508
+ * @returns Promise<UpdateDeviceTypeResponse> Resolves with the device type update response
1509
+ * @throws Error If the update fails
1510
+ * @example
1511
+ * ```typescript
1512
+ * const cc = webex.cc;
1513
+ *
1514
+ * // Switch from browser to extension
1515
+ * try {
1516
+ * await cc.updateAgentProfile({
1517
+ * loginOption: 'EXTENSION',
1518
+ * dialNumber: '1234', // Required for EXTENSION
1519
+ * teamId: 'currentTeam' // Optional: uses current team if not specified
1520
+ * });
1521
+ * } catch (error) {
1522
+ * console.error('Failed to update device:', error.message);
1523
+ * }
1524
+ * ```
1525
+ * @public
1526
+ */
1527
+ public async updateAgentProfile(data: AgentProfileUpdate): Promise<UpdateDeviceTypeResponse> {
1528
+ this.metricsManager.timeEvent([
1529
+ METRIC_EVENT_NAMES.AGENT_DEVICE_TYPE_UPDATE_SUCCESS,
1530
+ METRIC_EVENT_NAMES.AGENT_DEVICE_TYPE_UPDATE_FAILED,
1531
+ ]);
1532
+
1533
+ const trackingId = `WX_CC_SDK_${uuidv4()}`;
1534
+
1535
+ LoggerProxy.info(`starting profile update`, {
1536
+ module: CC_FILE,
1537
+ method: METHODS.UPDATE_AGENT_PROFILE,
1538
+ trackingId,
1539
+ });
1540
+
1541
+ try {
1542
+ // Only block if both loginOption AND teamId remain unchanged
1543
+ if (
1544
+ this.webCallingService?.loginOption === data.loginOption &&
1545
+ data.teamId === this.agentConfig.currentTeamId
1546
+ ) {
1547
+ const message =
1548
+ 'Will not proceed with device update as new Device type is same as current device type and teamId is same as current teamId';
1549
+ const err = new Error(message) as GenericError;
1550
+ err.details = {
1551
+ type: 'Identical Device Change Failure',
1552
+ orgId: this.$webex.credentials.getOrgId(),
1553
+ trackingId,
1554
+ data: {
1555
+ agentId: this.agentConfig.agentId,
1556
+ reasonCode: 'R002',
1557
+ reason: message,
1558
+ },
1559
+ };
1560
+ throw err;
1561
+ }
1562
+
1563
+ await this.stationLogout({
1564
+ logoutReason: 'User requested agent device change',
1565
+ });
1566
+
1567
+ const loginPayload: AgentLogin = {
1568
+ teamId: data.teamId,
1569
+ loginOption: data.loginOption,
1570
+ dialNumber: data.dialNumber,
1571
+ };
1572
+
1573
+ const resp = await this.stationLogin(loginPayload);
1574
+
1575
+ this.metricsManager.trackEvent(
1576
+ METRIC_EVENT_NAMES.AGENT_DEVICE_TYPE_UPDATE_SUCCESS,
1577
+ {
1578
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp),
1579
+ loginType: data.loginOption,
1580
+ },
1581
+ ['behavioral', 'business', 'operational']
1582
+ );
1583
+
1584
+ LoggerProxy.log(
1585
+ `profile updated successfully with ${loginPayload.loginOption} teamId: ${loginPayload.teamId}`,
1586
+ {
1587
+ module: CC_FILE,
1588
+ method: METHODS.UPDATE_AGENT_PROFILE,
1589
+ trackingId,
1590
+ }
1591
+ );
1592
+
1593
+ const deviceTypeUpdateResponse: UpdateDeviceTypeResponse = {
1594
+ ...resp,
1595
+ type: 'AgentDeviceTypeUpdateSuccess',
1596
+ };
1597
+
1598
+ return deviceTypeUpdateResponse;
1599
+ } catch (error) {
1600
+ const failure = (error as GenericError).details as Failure;
1601
+ this.metricsManager.trackEvent(
1602
+ METRIC_EVENT_NAMES.AGENT_DEVICE_TYPE_UPDATE_FAILED,
1603
+ {
1604
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
1605
+ loginType: data.loginOption,
1606
+ },
1607
+ ['behavioral', 'business', 'operational']
1608
+ );
1609
+
1610
+ LoggerProxy.error(`error updating profile: ${error}`, {
1611
+ module: CC_FILE,
1612
+ method: METHODS.UPDATE_AGENT_PROFILE,
1613
+ trackingId,
1614
+ });
1615
+ throw error;
1616
+ }
1617
+ }
1618
+ }