diff --git a/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.tsx b/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.tsx index 62b8382c4..3b46d68c9 100644 --- a/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.tsx +++ b/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.tsx @@ -5,13 +5,13 @@ import {withMetrics} from '@webex/cc-ui-logging'; import {extractIncomingTaskData} from './incoming-task.utils'; const IncomingTaskComponent: React.FunctionComponent = (props) => { - const {incomingTask, isBrowser, accept, reject, logger} = props; + const {incomingTask, isBrowser, accept, reject, logger, isDeclineButtonEnabled} = props; if (!incomingTask) { return <>; // hidden component } // Extract all task data using the utility function - const taskData = extractIncomingTaskData(incomingTask, isBrowser, logger); + const taskData = extractIncomingTaskData(incomingTask, isBrowser, logger, isDeclineButtonEnabled); return ( ronaTimeout={taskData.ronaTimeout} acceptText={taskData.acceptText} disableAccept={taskData.disableAccept} + disableDecline={taskData.disableDecline} declineText={taskData.declineText} styles="task-list-hover" mediaType={taskData.mediaType as MEDIA_CHANNEL} diff --git a/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.utils.tsx b/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.utils.tsx index 391719f17..4da1f8745 100644 --- a/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.utils.tsx +++ b/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.utils.tsx @@ -15,6 +15,7 @@ export interface IncomingTaskData { declineText: string | undefined; title: string; disableAccept: boolean; + disableDecline: boolean; } /** @@ -23,7 +24,12 @@ export interface IncomingTaskData { * @param isBrowser - Whether the device type is browser * @returns Processed task data with computed values */ -export const extractIncomingTaskData = (incomingTask: ITask, isBrowser: boolean, logger?): IncomingTaskData => { +export const extractIncomingTaskData = ( + incomingTask: ITask, + isBrowser: boolean, + logger?, + isDeclineButtonEnabled?: boolean +): IncomingTaskData => { try { // Extract basic data from task //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 @@ -52,8 +58,12 @@ export const extractIncomingTaskData = (incomingTask: ITask, isBrowser: boolean, // Compute title based on media type const title = isSocial ? customerName : ani; + // Compute disable state for accept button when auto-answering + const isAutoAnswering = incomingTask.data.isAutoAnswering || false; // Compute disable state for accept button - const disableAccept = isTelephony && !isBrowser; + const disableAccept = (isTelephony && !isBrowser) || isAutoAnswering; + + const disableDecline = (isTelephony && !isBrowser) || (isAutoAnswering && !isDeclineButtonEnabled); return { ani, @@ -69,6 +79,7 @@ export const extractIncomingTaskData = (incomingTask: ITask, isBrowser: boolean, declineText, title, disableAccept, + disableDecline, }; } catch (error) { logger?.error('CC-Widgets: IncomingTask: Error in extractIncomingTaskData', { @@ -91,6 +102,7 @@ export const extractIncomingTaskData = (incomingTask: ITask, isBrowser: boolean, declineText: undefined, title: '', disableAccept: false, + disableDecline: false, }; } }; diff --git a/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.style.scss b/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.style.scss index a96be78f3..540916f68 100644 --- a/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.style.scss +++ b/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.style.scss @@ -13,6 +13,32 @@ .outdial-input { width: 100%; } + + .outdial-ani-select-container { + position: relative; + width: 100%; + } + + .outDialCallButton { + margin-top: 1.5rem; + } + + .outdial-select-arrow-icon { + position: absolute; + right: 0; + top: 83%; + transform: translateY(-50%); + display: flex; + align-items: center; + pointer-events: none; + z-index: 1; +} + +.outdial-ani-option-name { + display: flex; + align-items: center; + gap: 0.25rem; +} .keys { display: grid; diff --git a/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx b/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx index b1e4b8424..dbb32807d 100644 --- a/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx +++ b/packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx @@ -2,7 +2,12 @@ import React, {useEffect, useMemo, useState} from 'react'; import {OutdialAniEntry, OutdialCallComponentProps} from '../task.types'; import './outdial-call.style.scss'; import {withMetrics} from '@webex/cc-ui-logging'; -import {Input, Button, Option, Select} from '@momentum-design/components/dist/react'; +import {Input, Button, Icon} from '@momentum-design/components/dist/react'; +// Migrate from @momentum-ui/react-collaboration to @momentum-design/components +// Currently using SelectNext for controlled selection behavior with proper onSelectionChange and onOpenChange support +// bug ticket: https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6990 +import {SelectNext} from '@momentum-ui/react-collaboration'; +import {Item} from '@react-stately/collections'; import {OutdialStrings, KEY_LIST} from './constants'; /** @@ -14,15 +19,17 @@ import {OutdialStrings, KEY_LIST} from './constants'; * * @param props - Properties for the OutdialCallComponent. * @property startOutdial - Function to initiate the outdial call with the entered destination number. + * @property isTelephonyTaskActive - Boolean indicating if there's an active telephony task. */ const OutdialCallComponent: React.FunctionComponent = (props) => { - const {logger, startOutdial, getOutdialANIEntries} = props; + const {logger, startOutdial, getOutdialANIEntries, isTelephonyTaskActive} = props; // State Hooks const [destination, setDestination] = useState(''); const [isValidNumber, setIsValidNumber] = useState(''); - const [selectedANI, setSelectedANI] = useState(undefined); + const [selectedANI, setSelectedANI] = useState(undefined); const [outdialANIList, setOutdialANIList] = useState([]); + const [isSelectOpen, setIsSelectOpen] = useState(false); // Validate the input format using regex from agent desktop const regExForDnSpecialChars = useMemo( @@ -82,7 +89,7 @@ const OutdialCallComponent: React.FunctionComponent = helpTextType={isValidNumber ? 'error' : 'default'} placeholder={OutdialStrings.DN_PLACEHOLDER} value={destination} - onChange={(e: unknown) => { + onInput={(e: unknown) => { const inputValue = (e as React.ChangeEvent).target.value; setDestination(inputValue); validateOutboundNumber(inputValue); @@ -97,36 +104,53 @@ const OutdialCallComponent: React.FunctionComponent = ))} - +
+ + + { + const value = key as string; + // Set to undefined if key is 'none' or null + const newANI = !value || value === 'none' ? undefined : value; + setSelectedANI(newANI); + }} + onOpenChange={(isOpen: boolean) => setIsSelectOpen(isOpen)} + items={[ + {id: 'none', name: OutdialStrings.ANI_SELECT_PLACEHOLDER}, + ...outdialANIList.map((ani) => ({id: ani.number, name: ani.name})), + ]} + direction="bottom" + showBorder + > + {(item: {id: string; name: string}) => ( + +
{item.name}
+
+ )} +
+
+ + - - +
- name 1 - - - name 2 - - + + + +
+ - - +
- name 1 - - - name 2 - - + + + +
+ - - - name 1 - - +
- name 2 - - + + + +
+ - - - name 1 - - +
- name 2 - - + + + +
+ - +
+ +
+ + + +
+
+
- - +
- name 1 - - - name 2 - - + + + +
+
+ +
- - - name 1 - - +
- name 2 - - + + + +
+
- - +
- name 1 - - - name 2 - - + + + +
+ - - - name 1 - - +
- name 2 - - + + + +
+ { {name: 'name 1', number: '1'}, {name: 'name 2', number: '2'}, ]), + isTelephonyTaskActive: false, }; beforeEach(() => { // Create a custom event that mimics what the mdc-input component would fire - customEvent = new Event('change', {bubbles: true}); + customEvent = new Event('input', {bubbles: true}); }); afterEach(() => { @@ -57,10 +58,11 @@ describe('Outdial Call Component', () => { it('updates input value when clicking keypad buttons', async () => { const {container} = render(); - await screen.findByTestId('outdial-ani-option-1'); - fireEvent.click(await screen.findByText('1')); - fireEvent.click(await screen.findByText('2')); - fireEvent.click(await screen.findByText('3')); + const keypad = await screen.findByTestId('outdial-keypad-keys'); + const buttons = within(keypad).getAllByRole('button'); + fireEvent.click(buttons[0]); // '1' + fireEvent.click(buttons[1]); // '2' + fireEvent.click(buttons[2]); // '3' // Remove IDs to avoid snapshot issues with dynamic IDs container.querySelectorAll('[id^="mdc-input"]').forEach((el) => el.removeAttribute('id')); expect(container).toMatchSnapshot(); @@ -119,7 +121,14 @@ describe('Outdial Call Component', () => { }); it('has no ANI entry options when the entry list is empty', async () => { - const {container} = render(); + const {container} = render( + + ); const select = await screen.findByTestId('outdial-ani-option-select'); fireEvent.click(select); // Remove IDs to avoid snapshot issues with dynamic IDs diff --git a/packages/contact-center/cc-components/tests/components/task/OutdialCall/out-dial-call.tsx b/packages/contact-center/cc-components/tests/components/task/OutdialCall/out-dial-call.tsx index 5c54e0790..8087dc4c5 100644 --- a/packages/contact-center/cc-components/tests/components/task/OutdialCall/out-dial-call.tsx +++ b/packages/contact-center/cc-components/tests/components/task/OutdialCall/out-dial-call.tsx @@ -2,10 +2,10 @@ import React from 'react'; import {render, fireEvent, screen, waitFor, within} from '@testing-library/react'; import '@testing-library/jest-dom'; import OutdialCallComponent from '../../../../src/components/task/OutdialCall/outdial-call'; -import {KEY_LIST, OutdialStrings} from '../../../../src/components/task/OutdialCall/constants'; +import {KEY_LIST} from '../../../../src/components/task/OutdialCall/constants'; import store from '@webex/cc-store'; import {mockCC} from '@webex/test-fixtures'; -import {OutdialCallComponentProps} from 'packages/contact-center/cc-components/src/components/task/task.types'; +import {OutdialCallComponentProps} from '../../../../src/components/task/task.types'; describe('OutdialCallComponent', () => { let customEvent; @@ -15,7 +15,7 @@ describe('OutdialCallComponent', () => { beforeEach(() => { // Create a custom event that mimics what the mdc-input component would fire - customEvent = new Event('change', {bubbles: true}); + customEvent = new Event('input', {bubbles: true}); }); afterEach(() => { @@ -30,6 +30,7 @@ describe('OutdialCallComponent', () => { {name: 'name 1', number: '1'}, {name: 'name 2', number: '2'}, ]), + isTelephonyTaskActive: false, }; describe('renders the component correctly, should render:', () => { it('article container', async () => { @@ -69,11 +70,7 @@ describe('OutdialCallComponent', () => { it('outdial ani option select', async () => { render(); const outdialAniSelect = await screen.findByTestId('outdial-ani-option-select'); - expect(outdialAniSelect).toHaveClass('outdial-input'); - expect(outdialAniSelect).toHaveAttribute('help-text-type', 'default'); - expect(outdialAniSelect).toHaveAttribute('label', OutdialStrings.ANI_SELECT_LABEL); - expect(outdialAniSelect).toHaveAttribute('name', 'outdial-ani-option-select'); - expect(outdialAniSelect).toHaveTextContent(/name 1/); + expect(outdialAniSelect).toBeInTheDocument(); }); it('call button', async () => { @@ -107,10 +104,11 @@ describe('OutdialCallComponent', () => { it('updates input value when clicking keypad buttons', async () => { render(); - await screen.findByTestId('outdial-ani-option-1'); - fireEvent.click(await screen.findByText('1')); - fireEvent.click(await screen.findByText('2')); - fireEvent.click(await screen.findByText('3')); + const keypad = await screen.findByTestId('outdial-keypad-keys'); + const buttons = within(keypad).getAllByRole('button'); + fireEvent.click(buttons[0]); // '1' + fireEvent.click(buttons[1]); // '2' + fireEvent.click(buttons[2]); // '3' expect(await screen.findByTestId('outdial-number-input')).toHaveValue('123'); }); @@ -126,8 +124,8 @@ describe('OutdialCallComponent', () => { const callButton = await screen.findByTestId('outdial-call-button'); fireEvent.click(callButton); - waitFor(() => { - expect(props.getOutdialANIEntries).toHaveBeenCalledWith('123', undefined); + await waitFor(() => { + expect(props.startOutdial).toHaveBeenCalledWith('123', undefined); }); }); @@ -161,17 +159,31 @@ describe('OutdialCallComponent', () => { }); it('has no ANI entry options when the entry list is empty', async () => { - render(); + render( + + ); const select = await screen.findByTestId('outdial-ani-option-select'); fireEvent.click(select); - expect(await screen.queryByText('name 1')).not.toBeInTheDocument(); + + // Should still show the placeholder option + const placeholderOption = await screen.findByTestId('outdial-ani-option-none'); + expect(placeholderOption).toBeInTheDocument(); + + // But should not show 'name 1' or 'name 2' + expect(screen.queryByTestId('outdial-ani-option-1')).not.toBeInTheDocument(); + expect(screen.queryByTestId('outdial-ani-option-2')).not.toBeInTheDocument(); }); it('sets selected ani when an option is selected', async () => { render(); const select = await screen.findByTestId('outdial-ani-option-select'); fireEvent.click(select); - const option = await screen.findByText('name 1'); + const option = await screen.findByTestId('outdial-ani-option-1'); expect(option).toBeInTheDocument(); fireEvent.click(option); await waitFor(() => { @@ -184,4 +196,138 @@ describe('OutdialCallComponent', () => { const callButton = await screen.findByTestId('outdial-call-button'); expect(callButton).toBeDisabled(); }); + + it('disables call button when there is an active telephony task', async () => { + render(); + const input = await screen.findByTestId('outdial-number-input'); + Object.defineProperty(customEvent, 'target', { + writable: false, + value: {value: '123'}, + }); + fireEvent(input, customEvent); + + const callButton = await screen.findByTestId('outdial-call-button'); + expect(callButton).toBeDisabled(); + }); + + it('enables call button when there is no telephony task (digital task active)', async () => { + render(); + const input = await screen.findByTestId('outdial-number-input'); + Object.defineProperty(customEvent, 'target', { + writable: false, + value: {value: '123'}, + }); + fireEvent(input, customEvent); + + await waitFor(() => { + const callButton = screen.getByTestId('outdial-call-button'); + expect(callButton).not.toBeDisabled(); + }); + }); + + it('shows placeholder option to clear ANI selection', async () => { + render(); + const select = await screen.findByTestId('outdial-ani-option-select'); + expect(select).toBeInTheDocument(); + }); + + it('allows unselecting ANI by selecting placeholder option', async () => { + const mockStartOutdial = jest.fn(); + render(); + + // Enter a destination + const input = await screen.findByTestId('outdial-number-input'); + Object.defineProperty(customEvent, 'target', { + writable: false, + value: {value: '123'}, + }); + fireEvent(input, customEvent); + + // Initially, call should be without origin + const callButton = await screen.findByTestId('outdial-call-button'); + fireEvent.click(callButton); + + await waitFor(() => { + expect(mockStartOutdial).toHaveBeenCalledWith('123', undefined); + }); + + mockStartOutdial.mockClear(); + }); + + it('shows arrow-down icon by default', async () => { + render(); + const arrowIcon = await screen.findByTestId('select-arrow-icon'); + expect(arrowIcon).toBeInTheDocument(); + expect(arrowIcon).toHaveAttribute('name', 'arrow-down-bold'); + }); + + it('passes origin parameter when ANI is selected', async () => { + const mockStartOutdial = jest.fn(); + render(); + + // Enter a destination + const input = await screen.findByTestId('outdial-number-input'); + Object.defineProperty(customEvent, 'target', { + writable: false, + value: {value: '+14698041796'}, + }); + fireEvent(input, customEvent); + + // Wait for button to update + await waitFor(() => { + const callButton = screen.getByTestId('outdial-call-button'); + expect(callButton).not.toBeDisabled(); + }); + + // Select an ANI + const select = await screen.findByTestId('outdial-ani-option-select'); + fireEvent.click(select); + const aniOption = await screen.findByTestId('outdial-ani-option-1'); + fireEvent.click(aniOption); + + // Make the call + const callButton = await screen.findByTestId('outdial-call-button'); + fireEvent.click(callButton); + + await waitFor(() => { + expect(mockStartOutdial).toHaveBeenCalledWith('+14698041796', '1'); + }); + }); + + it('does not pass origin parameter when placeholder is selected', async () => { + const mockStartOutdial = jest.fn(); + render(); + + // Enter a destination + const input = await screen.findByTestId('outdial-number-input'); + Object.defineProperty(customEvent, 'target', { + writable: false, + value: {value: '+14698041796'}, + }); + fireEvent(input, customEvent); + + await waitFor(() => { + const callButton = screen.getByTestId('outdial-call-button'); + expect(callButton).not.toBeDisabled(); + }); + + // First select an ANI + const select = await screen.findByTestId('outdial-ani-option-select'); + fireEvent.click(select); + const aniOption = await screen.findByTestId('outdial-ani-option-1'); + fireEvent.click(aniOption); + + // Then select the placeholder to clear + fireEvent.click(select); + const placeholderOption = await screen.findByTestId('outdial-ani-option-none'); + fireEvent.click(placeholderOption); + + // Make the call + const callButton = await screen.findByTestId('outdial-call-button'); + fireEvent.click(callButton); + + await waitFor(() => { + expect(mockStartOutdial).toHaveBeenCalledWith('+14698041796', undefined); + }); + }); }); diff --git a/packages/contact-center/store/package.json b/packages/contact-center/store/package.json index d5a84950c..541d5bcf2 100644 --- a/packages/contact-center/store/package.json +++ b/packages/contact-center/store/package.json @@ -23,7 +23,7 @@ "deploy:npm": "yarn npm publish" }, "dependencies": { - "@webex/contact-center": "3.10.0-next.17", + "@webex/contact-center": "3.10.0-next.20", "mobx": "6.13.5", "typescript": "5.6.3" }, diff --git a/packages/contact-center/store/src/store.ts b/packages/contact-center/store/src/store.ts index 3b8fec3bd..ac7b31157 100644 --- a/packages/contact-center/store/src/store.ts +++ b/packages/contact-center/store/src/store.ts @@ -36,6 +36,7 @@ class Store implements IStore { currentState: string = ''; customState: ICustomState = null; isQueueConsultInProgress = false; + isDeclineButtonEnabled = false; currentConsultQueueId: string = ''; consultStartTimeStamp = undefined; lastStateChangeTimestamp?: number; diff --git a/packages/contact-center/store/src/store.types.ts b/packages/contact-center/store/src/store.types.ts index 3944ab439..db5972faf 100644 --- a/packages/contact-center/store/src/store.types.ts +++ b/packages/contact-center/store/src/store.types.ts @@ -116,6 +116,7 @@ interface IStore { currentTheme: string; customState: ICustomState; isQueueConsultInProgress: boolean; + isDeclineButtonEnabled: boolean; currentConsultQueueId: string; consultStartTimeStamp?: number; callControlAudio: MediaStream | null; @@ -151,6 +152,7 @@ interface IStoreWrapper extends IStore { setAgentProfile(profile: Profile): void; setTeamId(id: string): void; setIsMuted(value: boolean): void; + setIsDeclineButtonEnabled(value: boolean): void; setOnError(callback: (widgetName: string, error: Error) => void): void; } @@ -185,6 +187,7 @@ enum TASK_EVENTS { TASK_RECORDING_PAUSED = 'task:recordingPaused', TASK_RECORDING_RESUMED = 'task:recordingResumed', TASK_OFFER_CONSULT = 'task:offerConsult', + TASK_AUTO_ANSWERED = 'task:autoAnswered', TASK_CONFERENCE_ESTABLISHING = 'task:conferenceEstablishing', TASK_CONFERENCE_STARTED = 'task:conferenceStarted', TASK_CONFERENCE_FAILED = 'task:conferenceFailed', diff --git a/packages/contact-center/store/src/storeEventsWrapper.ts b/packages/contact-center/store/src/storeEventsWrapper.ts index 5dd7b94a5..2fba7ed50 100644 --- a/packages/contact-center/store/src/storeEventsWrapper.ts +++ b/packages/contact-center/store/src/storeEventsWrapper.ts @@ -132,6 +132,10 @@ class StoreWrapper implements IStoreWrapper { return this.store.isQueueConsultInProgress; } + get isDeclineButtonEnabled() { + return this.store.isDeclineButtonEnabled; + } + get currentConsultQueueId() { return this.store.currentConsultQueueId; } @@ -282,6 +286,12 @@ class StoreWrapper implements IStoreWrapper { }); }; + setIsDeclineButtonEnabled = (value: boolean): void => { + runInAction(() => { + this.store.isDeclineButtonEnabled = value; + }); + }; + setCurrentConsultQueueId = (queueId: string | null): void => { runInAction(() => { this.store.currentConsultQueueId = queueId; @@ -390,6 +400,7 @@ class StoreWrapper implements IStoreWrapper { taskToRemove.off(TASK_EVENTS.AGENT_WRAPPEDUP, this.refreshTaskList); taskToRemove.off(TASK_EVENTS.TASK_CONSULTING, this.handleConsulting); taskToRemove.off(TASK_EVENTS.TASK_OFFER_CONSULT, this.handleConsultOffer); + taskToRemove.off(TASK_EVENTS.TASK_AUTO_ANSWERED, this.handleAutoAnswer); taskToRemove.off(TASK_EVENTS.TASK_CONSULT_END, this.refreshTaskList); taskToRemove.off(TASK_EVENTS.TASK_CONSULT_ACCEPTED, this.handleConsultAccepted); taskToRemove.off(TASK_EVENTS.AGENT_CONSULT_CREATED, this.handleConsultCreated); @@ -437,6 +448,7 @@ class StoreWrapper implements IStoreWrapper { }; handleTaskEnd = () => { + this.setIsDeclineButtonEnabled(false); this.refreshTaskList(); }; @@ -480,6 +492,11 @@ class StoreWrapper implements IStoreWrapper { this.refreshTaskList(); }; + handleAutoAnswer = () => { + this.setIsDeclineButtonEnabled(true); + this.refreshTaskList(); + }; + handleConsultAccepted = (event) => { const task = event; runInAction(() => { @@ -541,6 +558,7 @@ class StoreWrapper implements IStoreWrapper { task.on(TASK_EVENTS.TASK_CONSULTING, this.handleConsulting); task.on(TASK_EVENTS.TASK_CONSULT_ACCEPTED, this.handleConsultAccepted); task.on(TASK_EVENTS.TASK_OFFER_CONSULT, this.handleConsultOffer); + task.on(TASK_EVENTS.TASK_AUTO_ANSWERED, this.handleAutoAnswer); task.on(TASK_EVENTS.TASK_CONSULT_END, this.refreshTaskList); task.on(TASK_EVENTS.TASK_HOLD, this.refreshTaskList); task.on(TASK_EVENTS.TASK_RESUME, this.refreshTaskList); diff --git a/packages/contact-center/store/tests/storeEventsWrapper.ts b/packages/contact-center/store/tests/storeEventsWrapper.ts index c2cacfc10..9bf0ab098 100644 --- a/packages/contact-center/store/tests/storeEventsWrapper.ts +++ b/packages/contact-center/store/tests/storeEventsWrapper.ts @@ -95,6 +95,7 @@ jest.mock('../src/store', () => ({ currentConsultQueueId: null, isEndConsultEnabled: true, allowConsultToQueue: false, + isDeclineButtonEnabled: false, setShowMultipleLoginAlert: jest.fn(), setCurrentState: jest.fn(), setLastStateChangeTimestamp: jest.fn(), @@ -250,6 +251,20 @@ describe('storeEventsWrapper', () => { expect(storeWrapper.allowConsultToQueue).toBe(storeWrapper['store'].allowConsultToQueue); }); + it('should proxy isDeclineButtonEnabled', () => { + expect(storeWrapper.isDeclineButtonEnabled).toBe(false); + }); + + it('should setIsDeclineButtonEnabled', () => { + expect(storeWrapper.setIsDeclineButtonEnabled).toBeInstanceOf(Function); + + storeWrapper.setIsDeclineButtonEnabled(true); + expect(storeWrapper['store'].isDeclineButtonEnabled).toBe(true); + + storeWrapper.setIsDeclineButtonEnabled(false); + expect(storeWrapper['store'].isDeclineButtonEnabled).toBe(false); + }); + it('should proxy consultStartTimeStamp', () => { expect(storeWrapper.consultStartTimeStamp).toBe(storeWrapper['store'].consultStartTimeStamp); }); @@ -558,6 +573,7 @@ describe('storeEventsWrapper', () => { expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_END, expect.any(Function)); expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_QUEUE_CANCELLED, expect.any(Function)); expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.AGENT_WRAPPEDUP, expect.any(Function)); + expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_AUTO_ANSWERED, expect.any(Function)); expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_HOLD, storeWrapper.refreshTaskList); expect(mockTask2.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_RESUME, storeWrapper.refreshTaskList); }); @@ -691,6 +707,7 @@ describe('storeEventsWrapper', () => { expect(mockTask.off).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, expect.any(Function)); expect(mockTask.off).toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.any(Function)); + expect(mockTask.off).toHaveBeenCalledWith(TASK_EVENTS.TASK_AUTO_ANSWERED, expect.any(Function)); expect(mockTask.off).toHaveBeenCalledWith(TASK_EVENTS.TASK_HOLD, storeWrapper.refreshTaskList); expect(mockTask.off).toHaveBeenCalledWith(TASK_EVENTS.TASK_RESUME, storeWrapper.refreshTaskList); @@ -1020,6 +1037,7 @@ describe('storeEventsWrapper', () => { expect(mockTask.off).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, expect.any(Function)); expect(mockTask.off).toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.any(Function)); + expect(mockTask.off).toHaveBeenCalledWith(TASK_EVENTS.TASK_AUTO_ANSWERED, expect.any(Function)); expect(refreshTaskListSpy).toHaveBeenCalledWith(); expect(setCurrentTaskSpy).toHaveBeenCalledWith(null); @@ -1537,6 +1555,54 @@ describe('storeEventsWrapper', () => { // Verify the TASK_CONSULT_QUEUE_CANCELLED handler was registered expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_QUEUE_CANCELLED, expect.any(Function)); }); + + it('should register TASK_AUTO_ANSWERED handler on incoming task', () => { + const mockTask: ITask = { + data: { + interactionId: 'interaction1', + interaction: { + state: 'connected', + }, + }, + on: jest.fn(), + off: jest.fn(), + } as unknown as ITask; + + storeWrapper['store'].taskList = {}; + storeWrapper.handleIncomingTask(mockTask); + + // Verify the TASK_AUTO_ANSWERED handler was registered + expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.TASK_AUTO_ANSWERED, expect.any(Function)); + }); + + it('should handle auto answer event and enable decline button', () => { + const autoAnswerTask: ITask = { + data: {interactionId: 'autoAnswerTest', interaction: {state: 'connected'}}, + on: jest.fn(), + off: jest.fn(), + } as unknown as ITask; + + const autoAnswerTaskOnSpy = jest.spyOn(autoAnswerTask, 'on'); + const setIsDeclineButtonEnabledSpy = jest.spyOn(storeWrapper, 'setIsDeclineButtonEnabled'); + const refreshTaskListSpy = jest.spyOn(storeWrapper, 'refreshTaskList'); + + storeWrapper['store'].cc.taskManager.getAllTasks = jest + .fn() + .mockReturnValue({[autoAnswerTask.data.interactionId]: autoAnswerTask}); + storeWrapper.refreshTaskList(); + storeWrapper.handleIncomingTask(autoAnswerTask); + + const autoAnswerCall = autoAnswerTaskOnSpy.mock.calls.find((call) => call[0] === TASK_EVENTS.TASK_AUTO_ANSWERED); + + expect(autoAnswerCall).toBeDefined(); + + const autoAnswerCallback = autoAnswerCall[1]; + + autoAnswerCallback(); + + expect(setIsDeclineButtonEnabledSpy).toHaveBeenCalledWith(true); + expect(refreshTaskListSpy).toHaveBeenCalled(); + }); }); describe('task:media conditionally attached based on deviceType', () => { diff --git a/packages/contact-center/task/src/helper.ts b/packages/contact-center/task/src/helper.ts index 17cbc6c32..a74a31e12 100644 --- a/packages/contact-center/task/src/helper.ts +++ b/packages/contact-center/task/src/helper.ts @@ -9,6 +9,7 @@ import store, { getConferenceParticipants, Participant, findMediaResourceId, + MEDIA_TYPE_TELEPHONY_LOWER, } from '@webex/cc-store'; import {findHoldTimestamp, getControlsVisibility} from './Utils/task-util'; import {OutdialAniEntriesResponse} from '@webex/contact-center/dist/types/services/config/types'; @@ -138,6 +139,7 @@ export const useTaskList = (props: UseTaskListProps) => { export const useIncomingTask = (props: UseTaskProps) => { const {onAccepted, onRejected, deviceType, incomingTask, logger} = props; const isBrowser = deviceType === 'BROWSER'; + const isDeclineButtonEnabled = store.isDeclineButtonEnabled; const taskAssignCallback = () => { try { @@ -265,6 +267,7 @@ export const useIncomingTask = (props: UseTaskProps) => { accept, reject, isBrowser, + isDeclineButtonEnabled, }; }; @@ -947,6 +950,29 @@ export const useCallControl = (props: useCallControlProps) => { export const useOutdialCall = (props: useOutdialCallProps) => { const {cc, logger} = props; + /** + * Check if there's an active telephony task in the task list. + * Returns true if any task in the task list is a telephony task. + * Digital tasks (email, chat) should not prevent outdial calls. + */ + const isTelephonyTaskActive = useMemo(() => { + try { + const taskList = store.taskList; + if (!taskList || Object.keys(taskList).length === 0) { + return false; + } + + // Check if any task in the list is a telephony task + return Object.values(taskList).some((task) => task?.data?.interaction?.mediaType === MEDIA_TYPE_TELEPHONY_LOWER); + } catch (error) { + logger?.error(`CC-Widgets: Task: Error checking telephony task - ${error.message}`, { + module: 'useOutdialCall', + method: 'isTelephonyTaskActive', + }); + return false; + } + }, [store.taskList, logger]); + const startOutdial = (destination: string, origin: string = undefined) => { try { // Perform validation on destination number. @@ -954,8 +980,12 @@ export const useOutdialCall = (props: useOutdialCallProps) => { alert('Destination number is required, it cannot be empty'); return; } + + // Only pass origin if it's defined and not empty + const outdialArgs = origin ? [destination, origin] : [destination]; + //@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762 - cc.startOutdial(destination, origin) + cc.startOutdial(...outdialArgs) .then((response) => { logger.info('Outdial call started', response); }) @@ -998,5 +1028,6 @@ export const useOutdialCall = (props: useOutdialCallProps) => { return { startOutdial, getOutdialANIEntries, + isTelephonyTaskActive, }; }; diff --git a/packages/contact-center/task/tests/IncomingTask/index.tsx b/packages/contact-center/task/tests/IncomingTask/index.tsx index 44f312907..693700327 100644 --- a/packages/contact-center/task/tests/IncomingTask/index.tsx +++ b/packages/contact-center/task/tests/IncomingTask/index.tsx @@ -35,6 +35,7 @@ describe('IncomingTask Component', () => { accept: jest.fn(), reject: jest.fn(), isBrowser: true, + isDeclineButtonEnabled: true, }); render(); diff --git a/packages/contact-center/task/tests/helper.ts b/packages/contact-center/task/tests/helper.ts index 8afa26fde..7567c0fd3 100644 --- a/packages/contact-center/task/tests/helper.ts +++ b/packages/contact-center/task/tests/helper.ts @@ -2,6 +2,7 @@ import {renderHook, act, waitFor} from '@testing-library/react'; import {useIncomingTask, useTaskList, useCallControl, useOutdialCall} from '../src/helper'; import * as taskUtils from '../src/Utils/task-util'; import {AddressBookEntriesResponse, EntryPointListResponse, TASK_EVENTS, IContactCenter} from '@webex/cc-store'; +import {ITask} from '@webex/contact-center'; import { mockAgents, mockCC, @@ -3411,7 +3412,7 @@ describe('useOutdialCall', () => { logger.info.mockRestore(); }); - it('should successfully start an outdial call', async () => { + it('should successfully start an outdial call without origin', async () => { const {result} = renderHook(() => useOutdialCall({ cc: mockOutdialCallProps, @@ -3423,7 +3424,24 @@ describe('useOutdialCall', () => { await result.current.startOutdial(destination); }); - expect(mockOutdialCallProps.startOutdial).toHaveBeenCalledWith(destination, undefined); + expect(mockOutdialCallProps.startOutdial).toHaveBeenCalledWith(destination); + expect(logger.info).toHaveBeenCalledWith('Outdial call started', 'Success'); + }); + + it('should successfully start an outdial call with origin', async () => { + const origin = '+16675260082'; + const {result} = renderHook(() => + useOutdialCall({ + cc: mockOutdialCallProps, + logger, + }) + ); + + await act(async () => { + await result.current.startOutdial(destination, origin); + }); + + expect(mockOutdialCallProps.startOutdial).toHaveBeenCalledWith(destination, origin); expect(logger.info).toHaveBeenCalledWith('Outdial call started', 'Success'); }); @@ -3460,7 +3478,7 @@ describe('useOutdialCall', () => { await result.current.startOutdial(destination); }); - expect(mockCCWithError.startOutdial).toHaveBeenCalledWith(destination, undefined); + expect(mockCCWithError.startOutdial).toHaveBeenCalledWith(destination); expect(logger.error).toHaveBeenCalledWith('Error: Outdial call failed', { module: 'widget-OutdialCall#helper.ts', method: 'startOutdial', @@ -3756,6 +3774,202 @@ describe('useOutdialCall', () => { ); }); }); + + describe('isTelephonyTaskActive', () => { + const originalTaskList = store.taskList; + + afterEach(() => { + // Reset store.taskList to original state + store.store.taskList = originalTaskList; + jest.clearAllMocks(); + }); + + it('should return false when task list is empty, null, or undefined', () => { + // Test empty object + jest.spyOn(store, 'taskList', 'get').mockReturnValue({}); + + const {result} = renderHook(() => + useOutdialCall({ + cc: mockCC, + logger, + }) + ); + + expect(result.current.isTelephonyTaskActive).toBe(false); + + // Test null/undefined + jest.spyOn(store, 'taskList', 'get').mockReturnValue(null); + + const hookResult = renderHook(() => + useOutdialCall({ + cc: mockCC, + logger, + }) + ); + + expect(hookResult.result.current.isTelephonyTaskActive).toBe(false); + }); + + it('should return true when there is a telephony task in the list', () => { + const telephonyTask = { + data: { + interactionId: 'telephony-task-1', + interaction: { + mediaType: 'telephony', + }, + }, + } as ITask; + + jest.spyOn(store, 'taskList', 'get').mockReturnValue({ + 'telephony-task-1': telephonyTask, + }); + + const {result} = renderHook(() => + useOutdialCall({ + cc: mockCC, + logger, + }) + ); + + expect(result.current.isTelephonyTaskActive).toBe(true); + }); + + it('should return false when there are only digital tasks', () => { + const chatTask = { + data: { + interactionId: 'chat-task-1', + interaction: { + mediaType: 'chat', + }, + }, + } as ITask; + + jest.spyOn(store, 'taskList', 'get').mockReturnValue({ + 'chat-task-1': chatTask, + }); + + const {result} = renderHook(() => + useOutdialCall({ + cc: mockCC, + logger, + }) + ); + + expect(result.current.isTelephonyTaskActive).toBe(false); + }); + + it('should return true when there is a mix of telephony and digital tasks', () => { + const telephonyTask = { + data: { + interactionId: 'telephony-task-1', + interaction: { + mediaType: 'telephony', + }, + }, + } as ITask; + + const chatTask = { + data: { + interactionId: 'chat-task-1', + interaction: { + mediaType: 'chat', + }, + }, + } as ITask; + + jest.spyOn(store, 'taskList', 'get').mockReturnValue({ + 'telephony-task-1': telephonyTask, + 'chat-task-1': chatTask, + }); + + const {result} = renderHook(() => + useOutdialCall({ + cc: mockCC, + logger, + }) + ); + + expect(result.current.isTelephonyTaskActive).toBe(true); + }); + + it('should handle errors gracefully and return false', () => { + // Create a task that will throw an error when accessed + const errorTask = { + get data() { + throw new Error('Error accessing task data'); + }, + } as unknown as ITask; + + jest.spyOn(store, 'taskList', 'get').mockReturnValue({ + 'error-task': errorTask, + }); + + const {result} = renderHook(() => + useOutdialCall({ + cc: mockCC, + logger, + }) + ); + + expect(result.current.isTelephonyTaskActive).toBe(false); + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining('CC-Widgets: Task: Error checking telephony task'), + { + module: 'useOutdialCall', + method: 'isTelephonyTaskActive', + } + ); + }); + + it('should update when taskList changes', () => { + const taskListSpy = jest.spyOn(store, 'taskList', 'get').mockReturnValue({}); + + const {result, rerender} = renderHook(() => + useOutdialCall({ + cc: mockCC, + logger, + }) + ); + + expect(result.current.isTelephonyTaskActive).toBe(false); + + // Add a telephony task + const telephonyTask = { + data: { + interactionId: 'telephony-task-1', + interaction: { + mediaType: 'telephony', + }, + }, + } as ITask; + + taskListSpy.mockReturnValue({ + 'telephony-task-1': telephonyTask, + }); + + rerender(); + + expect(result.current.isTelephonyTaskActive).toBe(true); + + // Remove telephony task and add digital task + const chatTask = { + data: { + interactionId: 'chat-task-1', + interaction: { + mediaType: 'chat', + }, + }, + } as ITask; + + taskListSpy.mockReturnValue({ + 'chat-task-1': chatTask, + }); + + rerender(); + + expect(result.current.isTelephonyTaskActive).toBe(false); + }); + }); }); describe('Task Hook Error Handling and Logging', () => { diff --git a/yarn.lock b/yarn.lock index 178d56b0b..bd38a42df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9211,13 +9211,13 @@ __metadata: languageName: node linkType: hard -"@webex/calling@npm:3.10.0-next.13": - version: 3.10.0-next.13 - resolution: "@webex/calling@npm:3.10.0-next.13" +"@webex/calling@npm:3.10.0-next.14": + version: 3.10.0-next.14 + resolution: "@webex/calling@npm:3.10.0-next.14" dependencies: "@types/platform": "npm:1.3.4" "@webex/internal-media-core": "npm:2.20.3" - "@webex/internal-plugin-metrics": "npm:3.10.0-next.3" + "@webex/internal-plugin-metrics": "npm:3.10.0-next.4" "@webex/media-helpers": "npm:3.10.0-next.2" async-mutex: "npm:0.4.0" buffer: "npm:6.0.3" @@ -9225,7 +9225,7 @@ __metadata: platform: "npm:1.3.6" uuid: "npm:8.3.2" xstate: "npm:4.30.6" - checksum: 10c0/40e0e02fb33e3e6b880f33f866abd595536bfd77298f0f8caff364e9f117cd3e03333336ad1bd65c6eaf12546359a1b7b927da14dcf786e34ec45b94629dc9a3 + checksum: 10c0/55d886132bb75241dbd3fbe13817dbbfd36a01494cee45c7f832b0093ab700c451fe58aa752ddad49262dc19fec4824125ba0b3246f89048c0e4c9f7a867f5de languageName: node linkType: hard @@ -9355,7 +9355,7 @@ __metadata: "@testing-library/react": "npm:16.0.1" "@types/jest": "npm:29.5.14" "@types/react-test-renderer": "npm:18" - "@webex/contact-center": "npm:3.10.0-next.17" + "@webex/contact-center": "npm:3.10.0-next.20" "@webex/test-fixtures": "workspace:*" babel-jest: "npm:29.7.0" babel-loader: "npm:9.2.1" @@ -9682,21 +9682,21 @@ __metadata: languageName: node linkType: hard -"@webex/contact-center@npm:3.10.0-next.17": - version: 3.10.0-next.17 - resolution: "@webex/contact-center@npm:3.10.0-next.17" +"@webex/contact-center@npm:3.10.0-next.20": + version: 3.10.0-next.20 + resolution: "@webex/contact-center@npm:3.10.0-next.20" dependencies: "@types/platform": "npm:1.3.4" - "@webex/calling": "npm:3.10.0-next.13" - "@webex/internal-plugin-mercury": "npm:3.10.0-next.3" - "@webex/internal-plugin-metrics": "npm:3.10.0-next.3" - "@webex/internal-plugin-support": "npm:3.10.0-next.3" - "@webex/plugin-authorization": "npm:3.10.0-next.3" - "@webex/plugin-logger": "npm:3.10.0-next.3" - "@webex/webex-core": "npm:3.10.0-next.3" + "@webex/calling": "npm:3.10.0-next.14" + "@webex/internal-plugin-mercury": "npm:3.10.0-next.4" + "@webex/internal-plugin-metrics": "npm:3.10.0-next.4" + "@webex/internal-plugin-support": "npm:3.10.0-next.4" + "@webex/plugin-authorization": "npm:3.10.0-next.4" + "@webex/plugin-logger": "npm:3.10.0-next.4" + "@webex/webex-core": "npm:3.10.0-next.4" jest-html-reporters: "npm:3.0.11" lodash: "npm:^4.17.21" - checksum: 10c0/6772b0913f7d0cea0be68f65995718e262a0d9ba0a61a4803656bb798d7235636b21598cb7d1e9e0197e7be0184973edeb9394f60809843d9ea3a8769bae3500 + checksum: 10c0/5ad164744adabbd3d98094834ec01056a170c580e6f5088755be24f4c4889ad715d15b3c40905806a5fd879015181f08738d8e1dbfb656b141ef946b2c1af7d0 languageName: node linkType: hard @@ -10024,21 +10024,21 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-conversation@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/internal-plugin-conversation@npm:3.10.0-next.3" +"@webex/internal-plugin-conversation@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/internal-plugin-conversation@npm:3.10.0-next.4" dependencies: "@webex/common": "npm:3.8.1-next.11" "@webex/helper-html": "npm:3.8.1-next.11" "@webex/helper-image": "npm:3.8.1-next.11" - "@webex/internal-plugin-encryption": "npm:3.10.0-next.3" - "@webex/internal-plugin-user": "npm:3.10.0-next.3" - "@webex/webex-core": "npm:3.10.0-next.3" + "@webex/internal-plugin-encryption": "npm:3.10.0-next.4" + "@webex/internal-plugin-user": "npm:3.10.0-next.4" + "@webex/webex-core": "npm:3.10.0-next.4" crypto-js: "npm:^4.1.1" lodash: "npm:^4.17.21" node-scr: "npm:^0.3.0" uuid: "npm:^3.3.2" - checksum: 10c0/f1ecd94b3c5222854c9f0e0c95050932ba7c0adab68019fed7b09da74ce1476cb6b679819f13ca9661128cf0dde3f312fd7c8802e31aa1df2239b3437c37b6c2 + checksum: 10c0/ba44ab1fa8c0cb141582f32ce312d47cc4f1038f4c2ee718444b6894741f6e1863aa587b3d90421c6863e82fdb00f6b70f15c2aefa8b68555561df570663a879 languageName: node linkType: hard @@ -10092,20 +10092,20 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-device@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/internal-plugin-device@npm:3.10.0-next.3" +"@webex/internal-plugin-device@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/internal-plugin-device@npm:3.10.0-next.4" dependencies: "@webex/common": "npm:3.8.1-next.11" "@webex/common-timers": "npm:3.8.1-next.11" "@webex/http-core": "npm:3.8.1-next.11" - "@webex/internal-plugin-metrics": "npm:3.10.0-next.3" - "@webex/webex-core": "npm:3.10.0-next.3" + "@webex/internal-plugin-metrics": "npm:3.10.0-next.4" + "@webex/webex-core": "npm:3.10.0-next.4" ampersand-collection: "npm:^2.0.2" ampersand-state: "npm:^5.0.3" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/7a3db54cbbf42e70c6bd5a842bed3b6230ee15772a57c104221b372f986488d0014300594e84cb35d19d390de01d014181152c5e9f45cea36c9a4dba9db25dd1 + checksum: 10c0/38ea181225bbd5a08bb4b477161d70344b101919f3cd263bb1b5a11ac35ff086aa00a228930af4bca1fd7d27454bb89d6853dd804aca0484660de20ef30810ca languageName: node linkType: hard @@ -10192,17 +10192,17 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-encryption@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/internal-plugin-encryption@npm:3.10.0-next.3" +"@webex/internal-plugin-encryption@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/internal-plugin-encryption@npm:3.10.0-next.4" dependencies: "@webex/common": "npm:3.8.1-next.11" "@webex/common-timers": "npm:3.8.1-next.11" "@webex/http-core": "npm:3.8.1-next.11" - "@webex/internal-plugin-device": "npm:3.10.0-next.3" - "@webex/internal-plugin-mercury": "npm:3.10.0-next.3" + "@webex/internal-plugin-device": "npm:3.10.0-next.4" + "@webex/internal-plugin-mercury": "npm:3.10.0-next.4" "@webex/test-helper-file": "npm:3.8.1-next.11" - "@webex/webex-core": "npm:3.10.0-next.3" + "@webex/webex-core": "npm:3.10.0-next.4" asn1js: "npm:^2.0.26" debug: "npm:^4.3.4" isomorphic-webcrypto: "npm:^2.3.8" @@ -10214,7 +10214,7 @@ __metadata: safe-buffer: "npm:^5.2.0" uuid: "npm:^3.3.2" valid-url: "npm:^1.0.9" - checksum: 10c0/7f58780d1fe678f1af0c370d10cce509db487b7369c0330c7bb39f1a2796439b8b20c05dbf23c1f1d16984649dfd277913868abd983d170f262423079f24490b + checksum: 10c0/bdf10d630bdc567a77b91648434b5ea9a640fb0e21378eaec884157e7ba0d4f6e7c2125c0b0ce106925e2dfd36d31e765c4e87cf53ae80bce789c70e5e6b46aa languageName: node linkType: hard @@ -10268,14 +10268,14 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-feature@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/internal-plugin-feature@npm:3.10.0-next.3" +"@webex/internal-plugin-feature@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/internal-plugin-feature@npm:3.10.0-next.4" dependencies: - "@webex/internal-plugin-device": "npm:3.10.0-next.3" - "@webex/webex-core": "npm:3.10.0-next.3" + "@webex/internal-plugin-device": "npm:3.10.0-next.4" + "@webex/webex-core": "npm:3.10.0-next.4" lodash: "npm:^4.17.21" - checksum: 10c0/97c2760bfffcc56dedc053ec951b3702d91cdc094c277e67e7ec8267c1335a88aee21ee9aa2c23920a9204bd6e6d24a5e689b0188d94e307a15b8e5b00915097 + checksum: 10c0/0e34a474b889185ebfdb842921af430017b059c1664282b30683f84ede77b9e5113057157259d6f907406182886e32422768a92c1d5672deb7a3336c8f136b85 languageName: node linkType: hard @@ -10440,27 +10440,27 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-mercury@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/internal-plugin-mercury@npm:3.10.0-next.3" +"@webex/internal-plugin-mercury@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/internal-plugin-mercury@npm:3.10.0-next.4" dependencies: "@webex/common": "npm:3.8.1-next.11" "@webex/common-timers": "npm:3.8.1-next.11" - "@webex/internal-plugin-device": "npm:3.10.0-next.3" - "@webex/internal-plugin-feature": "npm:3.10.0-next.3" - "@webex/internal-plugin-metrics": "npm:3.10.0-next.3" + "@webex/internal-plugin-device": "npm:3.10.0-next.4" + "@webex/internal-plugin-feature": "npm:3.10.0-next.4" + "@webex/internal-plugin-metrics": "npm:3.10.0-next.4" "@webex/test-helper-chai": "npm:3.8.1-next.11" "@webex/test-helper-mocha": "npm:3.8.1-next.11" "@webex/test-helper-mock-web-socket": "npm:3.8.1-next.11" "@webex/test-helper-mock-webex": "npm:3.8.1-next.11" "@webex/test-helper-refresh-callback": "npm:3.8.1-next.11" "@webex/test-helper-test-users": "npm:3.8.1-next.11" - "@webex/webex-core": "npm:3.10.0-next.3" + "@webex/webex-core": "npm:3.10.0-next.4" backoff: "npm:^2.5.0" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" ws: "npm:^8.17.1" - checksum: 10c0/c6c7220db23a3b7d7e70168d42aa561b927c8b2664952253d12e9592d18b477b09f7bfab516d2d8107393d6cb0db5c3fe096d286a41df6e2335770a281c0b0a3 + checksum: 10c0/0fbab2d050035649299bc92ec9859c3115031be839f44828fb1e7846bd5a2bc22e88b205bec2de7aee7ebdc5c57baa2f61d192fff03a135e655e07b5495fec70 languageName: node linkType: hard @@ -10514,20 +10514,20 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-metrics@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/internal-plugin-metrics@npm:3.10.0-next.3" +"@webex/internal-plugin-metrics@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/internal-plugin-metrics@npm:3.10.0-next.4" dependencies: "@webex/common": "npm:3.8.1-next.11" "@webex/common-timers": "npm:3.8.1-next.11" "@webex/event-dictionary-ts": "npm:^1.0.1930" "@webex/test-helper-chai": "npm:3.8.1-next.11" "@webex/test-helper-mock-webex": "npm:3.8.1-next.11" - "@webex/webex-core": "npm:3.10.0-next.3" + "@webex/webex-core": "npm:3.10.0-next.4" ip-anonymize: "npm:^0.1.0" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/3ccd5c18d245b953b39839154db791d309df7dd4662e3bf5f2f1366449fabbd5c5b73825f1696c53bbfd3306ec1d816a67672865baf39c42ddb1511ee2f43735 + checksum: 10c0/ea297016dd5f6a0d2af4f7c97689f9249ded94442d2d6534aed27c52e8dfe1f152b94f6f4c7caa441ad3a7d2d53a23bdc742484938aad62ca0a0c8b6ef2bfac4 languageName: node linkType: hard @@ -10626,18 +10626,18 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-search@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/internal-plugin-search@npm:3.10.0-next.3" +"@webex/internal-plugin-search@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/internal-plugin-search@npm:3.10.0-next.4" dependencies: "@webex/common": "npm:3.8.1-next.11" - "@webex/internal-plugin-conversation": "npm:3.10.0-next.3" - "@webex/internal-plugin-device": "npm:3.10.0-next.3" - "@webex/internal-plugin-encryption": "npm:3.10.0-next.3" - "@webex/webex-core": "npm:3.10.0-next.3" + "@webex/internal-plugin-conversation": "npm:3.10.0-next.4" + "@webex/internal-plugin-device": "npm:3.10.0-next.4" + "@webex/internal-plugin-encryption": "npm:3.10.0-next.4" + "@webex/webex-core": "npm:3.10.0-next.4" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/ba14771cca8bf226c1f18df2e92286f741f56987df09ea53baefade4ca26d47443980ab6e4386268908e7960239325dd86a1e7fce46ba27aebc050de92ae433a + checksum: 10c0/b757c2424f0e108ef71c951c159705fd443e1627a238f6dff150f9ad613a1fc951c3aedd6079f2c918dafaa976626894dc64170dca091032b29d9206ce01cfe9 languageName: node linkType: hard @@ -10690,20 +10690,20 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-support@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/internal-plugin-support@npm:3.10.0-next.3" +"@webex/internal-plugin-support@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/internal-plugin-support@npm:3.10.0-next.4" dependencies: - "@webex/internal-plugin-device": "npm:3.10.0-next.3" - "@webex/internal-plugin-search": "npm:3.10.0-next.3" + "@webex/internal-plugin-device": "npm:3.10.0-next.4" + "@webex/internal-plugin-search": "npm:3.10.0-next.4" "@webex/test-helper-chai": "npm:3.8.1-next.11" "@webex/test-helper-file": "npm:3.8.1-next.11" "@webex/test-helper-mock-webex": "npm:3.8.1-next.11" "@webex/test-helper-test-users": "npm:3.8.1-next.11" - "@webex/webex-core": "npm:3.10.0-next.3" + "@webex/webex-core": "npm:3.10.0-next.4" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/d41266a14e0549cfb96e6ff5f7c561b16901357b6ffac798b5c7ca585ac6b037907ddba5f4cebd488af3c4b508f09cfabb8190c2caec12d31ca14c307173a139 + checksum: 10c0/6adbcab1706cf557e47ff5449b4533a57d6c95bce2253f9d4c43c778eec6b66e0ca238d15c401542d238ce4ea783fc45c1ab06dedc6c6391df8d31898f4faaf2 languageName: node linkType: hard @@ -10756,19 +10756,19 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-user@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/internal-plugin-user@npm:3.10.0-next.3" +"@webex/internal-plugin-user@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/internal-plugin-user@npm:3.10.0-next.4" dependencies: "@webex/common": "npm:3.8.1-next.11" - "@webex/internal-plugin-device": "npm:3.10.0-next.3" + "@webex/internal-plugin-device": "npm:3.10.0-next.4" "@webex/test-helper-chai": "npm:3.8.1-next.11" "@webex/test-helper-mock-webex": "npm:3.8.1-next.11" "@webex/test-helper-test-users": "npm:3.8.1-next.11" - "@webex/webex-core": "npm:3.10.0-next.3" + "@webex/webex-core": "npm:3.10.0-next.4" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/354e86f55486a9913f15d3ed608be9bd5b7fae26bf4a86c012b1ea8ce3271239ba35050646b731ef80c3b7ca27db8185e8ba9191e67b46abf8b30ef95cb1a129 + checksum: 10c0/eaa1134d9840f1dbaa1bf1c2c656adca5103b2c3dcdfb01af0640276900e33287d863aa5f6e994ec34b3d06bf48201ea3ba99734f62444531ff85edeaab32f94 languageName: node linkType: hard @@ -10962,20 +10962,20 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-authorization-browser@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/plugin-authorization-browser@npm:3.10.0-next.3" +"@webex/plugin-authorization-browser@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/plugin-authorization-browser@npm:3.10.0-next.4" dependencies: "@webex/common": "npm:3.8.1-next.11" - "@webex/internal-plugin-device": "npm:3.10.0-next.3" - "@webex/plugin-authorization-node": "npm:3.10.0-next.3" - "@webex/storage-adapter-local-storage": "npm:3.10.0-next.3" + "@webex/internal-plugin-device": "npm:3.10.0-next.4" + "@webex/plugin-authorization-node": "npm:3.10.0-next.4" + "@webex/storage-adapter-local-storage": "npm:3.10.0-next.4" "@webex/storage-adapter-spec": "npm:3.8.1-next.11" - "@webex/webex-core": "npm:3.10.0-next.3" + "@webex/webex-core": "npm:3.10.0-next.4" jsonwebtoken: "npm:^9.0.2" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/2ccf3b376144acf8d995ca65714c263984d6e54edbcf3af628cf7b62a08b233fbb2b526131164dd95b33e5deb87144c6ad38a2512a76d1041b7428ef50505020 + checksum: 10c0/b6577459ee429e6ebf0a9d536caa2c3fa728f2cd0aec5c4e8c4dab3480bc3b6dccbb5cc879dda70363f836dfac5017ab493c5de3588b3ee0033634cab219b57c languageName: node linkType: hard @@ -11022,16 +11022,16 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-authorization-node@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/plugin-authorization-node@npm:3.10.0-next.3" +"@webex/plugin-authorization-node@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/plugin-authorization-node@npm:3.10.0-next.4" dependencies: "@webex/common": "npm:3.8.1-next.11" - "@webex/internal-plugin-device": "npm:3.10.0-next.3" - "@webex/webex-core": "npm:3.10.0-next.3" + "@webex/internal-plugin-device": "npm:3.10.0-next.4" + "@webex/webex-core": "npm:3.10.0-next.4" jsonwebtoken: "npm:^9.0.0" uuid: "npm:^3.3.2" - checksum: 10c0/ae421230f4bfa658cf2294bc469af45096d6aed1cbc6d38b04c65795e04ae2c20d58d2bf02633e49dd5937aad3ddd92fa7888b4afaaa78dcfd3a689bc788ea11 + checksum: 10c0/6dff780afbbdbc34dcb1f7200912b1e56097ecd626e303f748b2db18e18391ac70d7d4768475b1f11b5ad3a60d7c84bf5b900ab62c2a31990ba2c510f095826a languageName: node linkType: hard @@ -11068,13 +11068,13 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-authorization@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/plugin-authorization@npm:3.10.0-next.3" +"@webex/plugin-authorization@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/plugin-authorization@npm:3.10.0-next.4" dependencies: - "@webex/plugin-authorization-browser": "npm:3.10.0-next.3" - "@webex/plugin-authorization-node": "npm:3.10.0-next.3" - checksum: 10c0/e9bcab63585bf1ebbf1e4a90ca9e45dce833ea07b83e403ea3fe2c169d26d62e77c2ee281130f29fd6444259bea811b44db0b60c07c4572f441be0b243bdb3a3 + "@webex/plugin-authorization-browser": "npm:3.10.0-next.4" + "@webex/plugin-authorization-node": "npm:3.10.0-next.4" + checksum: 10c0/39a76857fe2697406ae05962f62feeca257d4bf75636c2288f82ceb7bb46fffbd82784e3a18b95d1554051ff015d6af44417c02d8bff58bda824271fc808024a languageName: node linkType: hard @@ -11180,17 +11180,17 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-logger@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/plugin-logger@npm:3.10.0-next.3" +"@webex/plugin-logger@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/plugin-logger@npm:3.10.0-next.4" dependencies: "@webex/common": "npm:3.8.1-next.11" "@webex/test-helper-chai": "npm:3.8.1-next.11" "@webex/test-helper-mocha": "npm:3.8.1-next.11" "@webex/test-helper-mock-webex": "npm:3.8.1-next.11" - "@webex/webex-core": "npm:3.10.0-next.3" + "@webex/webex-core": "npm:3.10.0-next.4" lodash: "npm:^4.17.21" - checksum: 10c0/77b6cfe67b043b8c63ab4a3eaf586cdb81be051747a3eb863e562cdf693fbe9b2f850005af97cd1ca171aa4d1379db212a90f00dbe3806447333c8eb50926e8e + checksum: 10c0/9b371defed53819b55d2b322f5f07f36bc6bc72ff0be8eb1f895946fb83db8c04fca5b3d86d839f48366dfebdd1ec61bf04e85e2185ebb64aafad2e85b78a67f languageName: node linkType: hard @@ -11665,14 +11665,14 @@ __metadata: languageName: node linkType: hard -"@webex/storage-adapter-local-storage@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/storage-adapter-local-storage@npm:3.10.0-next.3" +"@webex/storage-adapter-local-storage@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/storage-adapter-local-storage@npm:3.10.0-next.4" dependencies: "@webex/storage-adapter-spec": "npm:3.8.1-next.11" "@webex/test-helper-mocha": "npm:3.8.1-next.11" - "@webex/webex-core": "npm:3.10.0-next.3" - checksum: 10c0/7127c01fd12ade397771b5a3297f901276118e6f456f404b8a1ee60386bf7cc0fbe5236c3c6aa978bb0df7d38f9eedd5cc388a585aad469f0ab389152e23a636 + "@webex/webex-core": "npm:3.10.0-next.4" + checksum: 10c0/5e7e9083bf7523fd98167fa847f7ae56a532621927a0a7096d521efe8a085757bd3d4aed20c02e618820f2b77fd1febb5939f607385d70a12033a2865ae65c3b languageName: node linkType: hard @@ -12262,9 +12262,9 @@ __metadata: languageName: node linkType: hard -"@webex/webex-core@npm:3.10.0-next.3": - version: 3.10.0-next.3 - resolution: "@webex/webex-core@npm:3.10.0-next.3" +"@webex/webex-core@npm:3.10.0-next.4": + version: 3.10.0-next.4 + resolution: "@webex/webex-core@npm:3.10.0-next.4" dependencies: "@webex/common": "npm:3.8.1-next.11" "@webex/common-timers": "npm:3.8.1-next.11" @@ -12278,7 +12278,7 @@ __metadata: jsonwebtoken: "npm:^9.0.0" lodash: "npm:^4.17.21" uuid: "npm:^3.3.2" - checksum: 10c0/c69dcaa8eae54ee16ff89af81aede673dc60867c0588a49e1ba70bd20ac3a3b40716fcc0a0400b7333729226f9c34e19fcb9fd1bbeecb29bcbb8e2c36b084d6e + checksum: 10c0/db25bfc20088d0c6b9dd3c9d532182063e1a1a5f3125a2c564b06706bf6ddda3fb2d21a35a1cd4b22a9fe1fa1cc782396d646e99deba9e5768877abb2f2a9f2d languageName: node linkType: hard