/// import EventEmitter from 'events'; import { CallId } from '@webex/calling/dist/types/common/types'; import routingContact from './contact'; import { ITask, TaskResponse, TaskData, TaskId, WrapupPayLoad, ResumeRecordingPayload, ConsultPayload, ConsultEndPayload, TransferPayLoad, ConsultTransferPayLoad } from './types'; import WebCallingService from '../WebCallingService'; import AutoWrapup from './AutoWrapup'; import { WrapupData } from '../config/types'; /** * Task class represents a contact center task/interaction that can be managed by an agent. * This class provides all the necessary methods to manage tasks in a contact center environment, * handling various call control operations and task lifecycle management. * * - Task Lifecycle Management: * - {@link accept} - Accept incoming task * - {@link decline} - Decline incoming task * - {@link end} - End active task * - Media Controls: * - {@link toggleMute} - Mute/unmute microphone for voice tasks * - {@link hold} - Place task on hold * - {@link resume} - Resume held task * - Recording Controls: * - {@link pauseRecording} - Pause task recording * - {@link resumeRecording} - Resume paused recording * - Task Transfer & Consultation: * - {@link consult} - Initiate consultation with another agent/queue * - {@link endConsult} - End ongoing consultation * - {@link transfer} - Transfer task to another agent/queue * - {@link consultTransfer} - Transfer after consultation * - Task Completion: * - {@link wrapup} - Complete task wrap-up * * Key events emitted by Task instances (see {@link TASK_EVENTS} for details): * * - Task Lifecycle: * - task:incoming — New task is being offered * - task:assigned — Task assigned to agent * - task:unassigned — Task unassigned from agent * - task:end — Task has ended * - task:wrapup — Task entered wrap-up state * - task:wrappedup — Task wrap-up completed * - task:rejected — Task was rejected/unanswered * - task:hydrate — Task data populated * * - Media & Controls: * - task:media — Voice call media track received * - task:hold — Task placed on hold * - task:unhold — Task resumed from hold * * - Consultation & Transfer: * - task:consultCreated — Consultation initiated * - task:consulting — Consultation in progress * - task:consultAccepted — Consultation accepted * - task:consultEnd — Consultation ended * - task:consultQueueCancelled — Queue consultation cancelled * - task:consultQueueFailed — Queue consultation failed * - task:offerConsult — Consultation offered * - task:offerContact — New contact offered * * - Recording: * - task:recordingPaused — Recording paused * - task:recordingPauseFailed — Recording pause failed * - task:recordingResumed — Recording resumed * - task:recordingResumeFailed — Recording resume failed * * @implements {ITask} * @example * ```typescript * // 1. Initialize task * const task = new Task(contact, webCallingService, taskData); * * // 2. Set up event listeners * task.on('task:media', (track) => { * // Handle voice call media * const audioElement = document.getElementById('remote-audio'); * audioElement.srcObject = new MediaStream([track]); * }); * * task.on('task:hold', () => { * console.log('Task is on hold'); * // Update UI to show hold state * }); * * task.on('task:end', () => { * console.log('Task ended'); * if (task.data.wrapUpRequired) { * // Show wrap-up form * } * }); * * // 3. Example task operations * await task.accept(); // Accept incoming task * await task.hold(); // Place on hold * await task.resume(); // Resume from hold * await task.end(); // End task * * // 4. Handle wrap-up if required * await task.wrapup({ * auxCodeId: 'RESOLVED', * wrapUpReason: 'Customer issue resolved' * }); * ``` */ export default class Task extends EventEmitter implements ITask { private contact; private localAudioStream; private webCallingService; data: TaskData; private metricsManager; webCallMap: Record; private wrapupData; autoWrapup?: AutoWrapup; private agentId; /** * Creates a new Task instance which provides the following features: * @param contact - The routing contact service instance * @param webCallingService - The web calling service instance * @param data - Initial task data * @param wrapupData - Wrap-up configuration data */ constructor(contact: ReturnType, webCallingService: WebCallingService, data: TaskData, wrapupData: WrapupData, agentId: string); /** * Sets up the automatic wrap-up timer if wrap-up is required * @private */ private setupAutoWrapupTimer; /** * Cancels the automatic wrap-up timer if it's running * @public - Public so it can be called externally when needed * Note: This is supported only in single session mode. Not supported in multi-session mode. */ cancelAutoWrapupTimer(): void; /** * @ignore * @private */ private handleRemoteMedia; /** * @ignore * @private */ private registerWebCallListeners; /** * @ignore */ unregisterWebCallListeners(): void; /** * Updates the task data with new information * @param updatedData - New task data to merge with existing data * @param shouldOverwrite - If true, completely replace data instead of merging * @returns The updated task instance * @example * ```typescript * task.updateTaskData(newData); * task.updateTaskData(newData, true); // completely replace data * ``` */ updateTaskData: (updatedData: TaskData, shouldOverwrite?: boolean) => this; /** * Recursively merges old data with new data * @private */ private reconcileData; /** * Agent accepts the incoming task. * After accepting, the task will emit task:assigned event and for voice calls, * a task:media event with the audio stream. * * @returns Promise * @throws Error if accepting task fails or media requirements not met * @example * ```typescript * // Set up event handlers before accepting * task.on(TASK_EVENTS.TASK_ASSIGNED, () => { * console.log('Task assigned, ID:', task.data.interactionId); * // Update UI to show active task * }); * * // For voice calls, handle media * task.on(TASK_EVENTS.TASK_MEDIA, (track) => { * const audioElement = document.getElementById('remote-audio'); * audioElement.srcObject = new MediaStream([track]); * }); * * // Accept the task * try { * await task.accept(); * console.log('Successfully accepted task'); * } catch (error) { * console.error('Failed to accept task:', error); * // Handle error (e.g., show error message to agent) * } * ``` */ accept(): Promise; /** * Agent can mute/unmute their microphone during a WebRTC task. * This method toggles between muted and unmuted states for the local audio stream. * * @returns Promise - Resolves when mute/unmute operation completes * @throws Error if toggling mute state fails or audio stream is not available * @example * ```typescript * // Toggle mute state * task.toggleMute() * .then(() => console.log('Mute state toggled successfully')) * .catch(error => console.error('Failed to toggle mute:', error)); * ``` */ toggleMute(): Promise; /** * Declines the incoming task. This will reject the task and notify the routing system. * For voice calls, this is equivalent to declining the incoming call. * * @returns Promise * @throws Error if the decline operation fails * @example * ```typescript * // Decline an incoming task * task.decline() * .then(() => console.log('Task declined successfully')) * .catch(error => console.error('Failed to decline task:', error)); * ``` */ decline(): Promise; /** * Puts the current task/interaction on hold. * Emits task:hold event when successful. For voice tasks, this mutes the audio. * * @param mediaResourceId - Optional media resource ID to use for the hold operation. If not provided, uses the task's current mediaResourceId * @returns Promise * @throws Error if hold operation fails * @example * ```typescript * // Set up hold event handler * task.on(TASK_EVENTS.TASK_HOLD, () => { * console.log('Task is now on hold'); * // Update UI to show hold state (e.g., enable resume button, show hold indicator) * document.getElementById('resume-btn').disabled = false; * document.getElementById('hold-indicator').style.display = 'block'; * }); * * // Place task on hold * try { * await task.hold(); * console.log('Successfully placed task on hold'); * } catch (error) { * console.error('Failed to place task on hold:', error); * // Handle error (e.g., show error message, reset UI state) * } * * // Place task on hold with custom mediaResourceId * try { * await task.hold('custom-media-resource-id'); * console.log('Successfully placed task on hold with custom mediaResourceId'); * } catch (error) { * console.error('Failed to place task on hold:', error); * } * ``` */ hold(mediaResourceId?: string): Promise; /** * Resumes the task/interaction that was previously put on hold. * Emits task:resume event when successful. For voice tasks, this restores the audio. * * @param mediaResourceId - Optional media resource ID to use for the resume operation. If not provided, uses the task's current mediaResourceId from interaction media * @returns Promise * @throws Error if resume operation fails * @example * ```typescript * // Set up resume event handler * task.on(TASK_EVENTS.TASK_RESUME, () => { * console.log('Task resumed from hold'); * // Update UI to show active state * document.getElementById('hold-btn').disabled = false; * document.getElementById('hold-indicator').style.display = 'none'; * }); * * // Resume task from hold * try { * await task.resume(); * console.log('Successfully resumed task from hold'); * } catch (error) { * console.error('Failed to resume task:', error); * // Handle error (e.g., show error message) * } * * // Resume task from hold with custom mediaResourceId * try { * await task.resume('custom-media-resource-id'); * console.log('Successfully resumed task from hold with custom mediaResourceId'); * } catch (error) { * console.error('Failed to resume task:', error); * } * ``` */ resume(mediaResourceId?: string): Promise; /** * Ends the task/interaction with the customer. * Emits task:end event when successful. If task requires wrap-up, * this will be indicated in the task:end event data. * * @returns Promise * @throws Error if ending task fails * @example * ```typescript * // Set up task end event handler * task.on(TASK_EVENTS.TASK_END, (data) => { * console.log('Task ended:', task.data.interactionId); * * if (data.wrapUpRequired) { * // Show wrap-up form * showWrapupForm(); * } else { * // Clean up and prepare for next task * cleanupTask(); * } * }); * * // End the task * try { * await task.end(); * console.log('Task end request successful'); * } catch (error) { * console.error('Failed to end task:', error); * // Handle error (e.g., show error message, retry option) * } * * function showWrapupForm() { * // Show wrap-up UI with required codes * document.getElementById('wrapup-form').style.display = 'block'; * } * * function cleanupTask() { * // Reset UI state * document.getElementById('active-task').style.display = 'none'; * document.getElementById('controls').style.display = 'none'; * } * ``` */ end(): Promise; /** * Wraps up the task/interaction with the customer. * This is called after task:end event if wrapUpRequired is true. * Emits task:wrappedup event when successful. * * @param wrapupPayload - WrapupPayLoad containing: * - auxCodeId: Required ID for the wrap-up code * - wrapUpReason: Required description of wrap-up reason * @returns Promise * @throws Error if task data is unavailable, auxCodeId is missing, or wrapUpReason is missing * @example * ```typescript * // Set up wrap-up events * task.on(TASK_EVENTS.TASK_WRAPUP, () => { * console.log('Task ready for wrap-up'); * // Show wrap-up form * document.getElementById('wrapup-form').style.display = 'block'; * }); * * task.on(TASK_EVENTS.TASK_WRAPPEDUP, () => { * console.log('Task wrap-up completed'); * // Clean up UI * document.getElementById('wrapup-form').style.display = 'none'; * }); * * // Submit wrap-up * try { * const wrapupPayload = { * auxCodeId: selectedCode, // e.g., 'ISSUE_RESOLVED' * wrapUpReason: 'Customer issue resolved successfully' * }; * await task.wrapup(wrapupPayload); * console.log('Successfully submitted wrap-up'); * } catch (error) { * console.error('Failed to submit wrap-up:', error); * // Handle validation errors * if (error.message.includes('required')) { * // Show validation error to agent * } * } * ``` */ wrapup(wrapupPayload: WrapupPayLoad): Promise; /** * Pauses the recording for the current voice task. * Emits task:recordingPaused event when successful. * * @returns Promise * @throws Error if pause recording fails * @example * ```typescript * // Set up recording events * task.on(TASK_EVENTS.TASK_RECORDING_PAUSED, () => { * console.log('Recording paused'); * // Update UI to show recording paused state * document.getElementById('recording-status').textContent = 'Recording Paused'; * document.getElementById('pause-recording-btn').style.display = 'none'; * document.getElementById('resume-recording-btn').style.display = 'block'; * }); * * task.on(TASK_EVENTS.TASK_RECORDING_PAUSE_FAILED, (error) => { * console.error('Failed to pause recording:', error); * // Show error to agent * }); * * // Pause recording * try { * await task.pauseRecording(); * console.log('Pause recording request sent'); * } catch (error) { * console.error('Error sending pause recording request:', error); * // Handle error * } * ``` */ pauseRecording(): Promise; /** * Resumes the recording for the voice task that was previously paused. * Emits task:recordingResumed event when successful. * * @param resumeRecordingPayload - Configuration for resuming recording: * - autoResumed: Indicates if resume was automatic (defaults to false) * @returns Promise * @throws Error if resume recording fails * @example * ```typescript * // Set up recording resume events * task.on(TASK_EVENTS.TASK_RECORDING_RESUMED, () => { * console.log('Recording resumed'); * // Update UI to show active recording state * document.getElementById('recording-status').textContent = 'Recording Active'; * document.getElementById('pause-recording-btn').style.display = 'block'; * document.getElementById('resume-recording-btn').style.display = 'none'; * }); * * task.on(TASK_EVENTS.TASK_RECORDING_RESUME_FAILED, (error) => { * console.error('Failed to resume recording:', error); * // Show error to agent * }); * * // Resume recording * try { * const resumePayload = { * autoResumed: false // Set to true if triggered by system * }; * await task.resumeRecording(resumePayload); * console.log('Resume recording request sent'); * } catch (error) { * console.error('Error sending resume recording request:', error); * // Handle error * } * ``` */ resumeRecording(resumeRecordingPayload: ResumeRecordingPayload): Promise; /** * Consults another agent or queue on an ongoing task for further assistance. * During consultation, the original customer is typically placed on hold while * the agent seeks guidance from another agent or queue. * * @param consultPayload - Configuration for the consultation containing: * - to: ID of the agent or queue to consult with * - destinationType: Type of destination (AGENT, QUEUE, etc.) * - holdParticipants: Whether to hold other participants (defaults to true) * @returns Promise - Resolves with consultation result * @throws Error if consultation fails or invalid parameters provided * @example * ```typescript * // Consult with another agent * const consultPayload = { * to: 'agentId123', * destinationType: DESTINATION_TYPE.AGENT, * holdParticipants: true * }; * task.consult(consultPayload) * .then(response => console.log('Consultation started successfully')) * .catch(error => console.error('Failed to start consultation:', error)); * * // Consult with a queue * const queueConsultPayload = { * to: 'salesQueue123', * destinationType: DESTINATION_TYPE.QUEUE * }; * task.consult(queueConsultPayload) * .then(response => console.log('Queue consultation started')) * .catch(error => console.error('Failed to start queue consultation:', error)); * ``` */ consult(consultPayload: ConsultPayload): Promise; /** * Ends an ongoing consultation session for the task. * This terminates the consultation while maintaining the original customer connection. * * @param consultEndPayload - Configuration for ending the consultation containing: * - isConsult: Must be true to indicate this is a consultation end * - taskId: ID of the task being consulted on * - queueId: (Optional) Queue ID if this was a queue consultation * - isSecondaryEpDnAgent: (Optional) Indicates if this involves a secondary entry point * @returns Promise - Resolves when consultation is ended * @throws Error if ending consultation fails or invalid parameters provided * @example * ```typescript * // End a direct agent consultation * const consultEndPayload = { * isConsult: true, * taskId: 'task123' * }; * task.endConsult(consultEndPayload) * .then(response => console.log('Consultation ended successfully')) * .catch(error => console.error('Failed to end consultation:', error)); * * // End a queue consultation * const queueConsultEndPayload = { * isConsult: true, * taskId: 'task123', * queueId: 'queue123' * }; * task.endConsult(queueConsultEndPayload) * .then(response => console.log('Queue consultation ended')) * .catch(error => console.error('Failed to end queue consultation:', error)); * ``` */ endConsult(consultEndPayload: ConsultEndPayload): Promise; /** * Transfer the task to an agent directly or to a queue. * This is a blind transfer that immediately redirects the task to the specified destination. * * @param transferPayload - Transfer configuration containing: * - to: ID of the agent or queue to transfer to * - destinationType: Type of destination (AGENT, QUEUE, etc.) * @returns Promise - Resolves when transfer is completed * @throws Error if transfer fails or invalid parameters provided * @example * ```typescript * // Transfer to a queue * const queueTransferPayload = { * to: 'salesQueue123', * destinationType: DESTINATION_TYPE.QUEUE * }; * task.transfer(queueTransferPayload) * .then(response => console.log('Task transferred to queue successfully')) * .catch(error => console.error('Failed to transfer to queue:', error)); * * // Transfer to an agent * const agentTransferPayload = { * to: 'agentId123', * destinationType: DESTINATION_TYPE.AGENT * }; * task.transfer(agentTransferPayload) * .then(response => console.log('Task transferred to agent successfully')) * .catch(error => console.error('Failed to transfer to agent:', error)); * ``` */ transfer(transferPayload: TransferPayLoad): Promise; /** * Transfer the task to the party that was consulted. * This completes a consultative transfer where the agent first consulted with the target * before transferring the task. For queue consultations, the transfer is automatically * directed to the agent who accepted the consultation. * * @param consultTransferPayload - Configuration for the consultation transfer containing: * - to: ID of the agent or queue to transfer to * - destinationType: Type of destination (AGENT, QUEUE, etc. from CONSULT_TRANSFER_DESTINATION_TYPE) * @returns Promise - Resolves when consultation transfer is completed * @throws Error if transfer fails, no agent has accepted a queue consultation, or other validation errors * @example * ```typescript * // Complete consultation transfer to an agent * const agentConsultTransfer = { * to: 'agentId123', * destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT * }; * task.consultTransfer(agentConsultTransfer) * .then(response => console.log('Consultation transfer to agent completed')) * .catch(error => console.error('Failed to complete agent consultation transfer:', error)); * * // Complete consultation transfer to a queue agent * const queueConsultTransfer = { * to: 'queue123', * destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.QUEUE * }; * task.consultTransfer(queueConsultTransfer) * .then(response => console.log('Consultation transfer to queue agent completed')) * .catch(error => console.error('Failed to complete queue consultation transfer:', error)); * ``` */ consultTransfer(consultTransferPayload?: ConsultTransferPayLoad): Promise; /** * Starts a consultation conference by merging the consultation call with the main call * * Creates a three-way conference between the agent, customer, and consulted party * Extracts required consultation data from the current task data * On success, emits a `task:conferenceStarted` event * * @returns Promise - Response from the consultation conference API * @throws Error if the operation fails or if consultation data is invalid * * @example * ```typescript * try { * await task.consultConference(); * console.log('Conference started successfully'); * } catch (error) { * console.error('Failed to start conference:', error); * } * ``` */ consultConference(): Promise; /** * Exits the current conference by removing the agent from the conference call * * Exits the agent from the conference, leaving the customer and consulted party connected * On success, emits a `task:conferenceEnded` event * * @returns Promise - Response from the conference exit API * @throws Error if the operation fails or if no active conference exists * * @example * ```typescript * try { * await task.exitConference(); * console.log('Successfully exited conference'); * } catch (error) { * console.error('Failed to exit conference:', error); * } * ``` */ exitConference(): Promise; /** * Transfers the current conference to another agent * * Moves the entire conference (including all participants) to a new agent, * while the current agent exits and goes to wrapup * On success, the current agent receives `task:conferenceEnded` event * * @returns Promise - Response from the conference transfer API * @throws Error if the operation fails or if no active conference exists * * @example * ```typescript * try { * await task.transferConference(); * console.log('Conference transferred successfully'); * } catch (error) { * console.error('Failed to transfer conference:', error); * } * ``` */ transferConference(): Promise; }