From 4f9866e13b4f56425410b3c5a9ce35fdb7d3705f Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Fri, 8 Nov 2024 14:56:30 +0530 Subject: [PATCH 01/15] fix: removed unnecessary build command --- packages/@webex/plugin-cc/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@webex/plugin-cc/package.json b/packages/@webex/plugin-cc/package.json index 7e62f3bab77..f6daba0b555 100644 --- a/packages/@webex/plugin-cc/package.json +++ b/packages/@webex/plugin-cc/package.json @@ -13,7 +13,7 @@ }, "scripts": { "build:src": "webex-legacy-tools build -dest \"./dist\" -src \"./src\" -js -ts && yarn build", - "build": " yarn workspace @webex/calling run build:src && yarn run -T tsc --declaration true --declarationDir ./dist/types", + "build": "yarn run -T tsc --declaration true --declarationDir ./dist/types", "docs": "typedoc --emit none", "fix:lint": "eslint 'src/**/*.ts' --fix", "fix:prettier": "prettier \"src/**/*.ts\" --write", From caf10cf2d0960c32423da6dc38f4421d6b511726 Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Fri, 8 Nov 2024 16:29:58 +0530 Subject: [PATCH 02/15] fix: fixed test cases of agent/index.ts --- packages/@webex/plugin-cc/src/cc.ts | 2 +- .../@webex/plugin-cc/src/services/index.ts | 9 +--- .../test/unit/spec/services/agent/index.ts | 54 +++++++++++++++---- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/packages/@webex/plugin-cc/src/cc.ts b/packages/@webex/plugin-cc/src/cc.ts index 1d1ab844308..90110113786 100644 --- a/packages/@webex/plugin-cc/src/cc.ts +++ b/packages/@webex/plugin-cc/src/cc.ts @@ -132,7 +132,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter await loginResponse; return loginResponse; - } catch (error: any) { + } catch (error) { this.$webex.logger.log(`file: ${CC_FILE}: Station Login FAILED: ${error.id}`); throw new Error(error.details?.data?.reason ?? 'Error while performing station login'); } diff --git a/packages/@webex/plugin-cc/src/services/index.ts b/packages/@webex/plugin-cc/src/services/index.ts index 2f46f8c2e11..e1631d468e3 100644 --- a/packages/@webex/plugin-cc/src/services/index.ts +++ b/packages/@webex/plugin-cc/src/services/index.ts @@ -1,19 +1,13 @@ import routingAgent from './agent'; import {AqmReqs} from './core/aqm-reqs'; -export class Services { - // private readonly notifs: AqmNotifs; - +export default class Services { public readonly agent: ReturnType; - // readonly configs: ReturnType; private static instance: Services; constructor() { - // this.notifs = new AqmNotifs(); const aqmReq = new AqmReqs(); this.agent = routingAgent(aqmReq); - - // this.configs = aqmConfigs(httpRequest); } public static getInstance(): Services { @@ -24,4 +18,3 @@ export class Services { return this.instance; } } -export default Services; diff --git a/packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts b/packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts index 5e44ec0e4bc..a0459448b8c 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts @@ -1,31 +1,65 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import {AqmReqs} from '../../../../../src/services/core/aqm-reqs'; import routingAgent from '../../../../../src/services/agent'; +import {AqmReqs} from '../../../../../src/services/core/aqm-reqs'; +import * as Utils from '../../../../../src/services/core/Utils'; -jest.mock('../../../../../src/services/core/HttpRequest'); -jest.mock('../../../../../src/services/core/aqm-reqs'); +jest.mock('../../../../../src/services/core/Utils', () => ({ + createErrDetailsObject: jest.fn(), + getRoutingHost: jest.fn(), +})); -const fakeAqm = new AqmReqs(); -const agent = routingAgent(fakeAqm) as any; +jest.mock('../../../../../src/services/core/aqm-reqs', () => ({ + AqmReqs: jest.fn().mockImplementation(() => ({ + reqEmpty: jest.fn().mockImplementation((fn) => fn), + req: jest.fn().mockImplementation((fn) => fn), + evt: jest.fn().mockImplementation((fn) => fn), + })), +})); describe('AQM routing agent', () => { + let fakeAqm: jest.Mocked; + let agent: ReturnType; + + beforeEach(() => { + jest.clearAllMocks(); + + const getroutingSpy = jest + .spyOn(Utils, 'getRoutingHost') + .mockReturnValue('https://mock-routing-host.com'); + + fakeAqm = new AqmReqs() as jest.Mocked; + fakeAqm.reqEmpty = jest.fn().mockImplementation((fn) => fn); + fakeAqm.req = jest.fn().mockImplementation((fn) => fn); + fakeAqm.evt = jest.fn().mockImplementation((fn) => fn); + + agent = routingAgent(fakeAqm); + }); + it('logout', async () => { - const req = agent.logout({data: {logoutReason: 'User requested logout'}}).catch((e: any) => e); + const reqSpy = jest.spyOn(fakeAqm, 'reqEmpty'); + reqSpy.mockRejectedValue(new Error('dasd')); + const req = await agent.logout({data: {logoutReason: 'User requested logout'}}); expect(req).toBeDefined(); + expect(reqSpy).toHaveBeenCalled(); }); it('reload', async () => { - const req = agent.reload().catch((e: any) => e); + const reqSpy = jest.spyOn(fakeAqm, 'reqEmpty'); + const req = await agent.reload(); expect(req).toBeDefined(); + expect(reqSpy).toHaveBeenCalled(); }); it('stationLogin', async () => { - const req = agent.stationLogin({data: {} as any}).catch((e: any) => e); + const reqSpy = jest.spyOn(fakeAqm, 'req'); + const req = await agent.stationLogin({data: {} as any}); expect(req).toBeDefined(); + expect(reqSpy).toHaveBeenCalled(); }); it('stateChange', async () => { - const req = agent.stateChange({data: {} as any}).catch((e: any) => e); + const reqSpy = jest.spyOn(fakeAqm, 'evt'); + const req = await agent.stateChange({data: {} as any}); expect(req).toBeDefined(); + expect(reqSpy).toHaveBeenCalled(); }); }); From a665e1cbad3d1f4e95a07a9005fa987fa9bb26a0 Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Fri, 8 Nov 2024 16:53:53 +0530 Subject: [PATCH 03/15] fix: removed circular dependencies --- packages/@webex/plugin-cc/src/cc.ts | 3 +- .../plugin-cc/src/features/Agentconfig.ts | 5 +- .../@webex/plugin-cc/src/features/types.ts | 52 -------- .../plugin-cc/src/services/config/index.ts | 4 +- .../plugin-cc/src/services/config/types.ts | 62 +--------- packages/@webex/plugin-cc/src/types.ts | 112 +++++++++++++++++- 6 files changed, 118 insertions(+), 120 deletions(-) diff --git a/packages/@webex/plugin-cc/src/cc.ts b/packages/@webex/plugin-cc/src/cc.ts index 90110113786..e3c498ed2dd 100644 --- a/packages/@webex/plugin-cc/src/cc.ts +++ b/packages/@webex/plugin-cc/src/cc.ts @@ -1,6 +1,6 @@ import {WebexPlugin} from '@webex/webex-core'; import AgentConfig from './features/Agentconfig'; -import {IAgentProfile, StationLoginResponse} from './features/types'; +import {StationLoginResponse} from './features/types'; import { CCPluginConfig, IContactCenter, @@ -8,6 +8,7 @@ import { SubscribeRequest, LoginOption, WelcomeEvent, + IAgentProfile, } from './types'; import {READY, CC_FILE} from './constants'; import HttpRequest from './services/core/HttpRequest'; diff --git a/packages/@webex/plugin-cc/src/features/Agentconfig.ts b/packages/@webex/plugin-cc/src/features/Agentconfig.ts index 09589cad8b9..7f02b70c50d 100644 --- a/packages/@webex/plugin-cc/src/features/Agentconfig.ts +++ b/packages/@webex/plugin-cc/src/features/Agentconfig.ts @@ -1,7 +1,6 @@ -import {IAgentProfile, WORK_TYPE_CODE} from './types'; +import {WORK_TYPE_CODE} from './types'; import AgentConfigService from '../services/config'; -import {Team, AuxCode} from '../services/config/types'; -import {WebexSDK} from '../types'; +import {IAgentProfile, Team, AuxCode, WebexSDK} from '../types'; import {DEFAULT_ATTRIBUTES, DEFAULT_PAGE, DEFAULT_PAGE_SIZE} from './constants'; import HttpRequest from '../services/core/HttpRequest'; diff --git a/packages/@webex/plugin-cc/src/features/types.ts b/packages/@webex/plugin-cc/src/features/types.ts index 35e377bc996..d7db36fb58b 100644 --- a/packages/@webex/plugin-cc/src/features/types.ts +++ b/packages/@webex/plugin-cc/src/features/types.ts @@ -1,4 +1,3 @@ -import {AuxCode, Team} from '../services/config/types'; import * as Agent from '../services/agent/types'; import {WebexSDK} from '../types'; @@ -36,55 +35,4 @@ export type AgentConfigRequest = { orgId: string; }; -/** - * Represents the response from AgentConfig. - * - * @public - */ -export type IAgentProfile = { - /** - * The id of the agent. - */ - - agentId: string; - - /** - * The name of the agent. - */ - agentName: string; - - /** - * Identifier for a Desktop Profile. - */ - agentProfileId: string; - - /** - * The email address of the agent. - */ - - agentMailId: string; - - /** - * Represents list of teams of an agent. - */ - teams: Team[]; - - /** - * Represents the voice options of an agent. - */ - - loginVoiceOptions: string[]; - - /** - * Represents the Idle codes list that the agents can select in Agent Desktop.t. - */ - - idleCodes: AuxCode[]; - - /** - * Represents the wrap-up codes list that the agents can select when they wrap up a contact. - */ - wrapUpCodes: AuxCode[]; -}; - export type StationLoginResponse = Agent.StationLoginSuccess | Error; diff --git a/packages/@webex/plugin-cc/src/services/config/index.ts b/packages/@webex/plugin-cc/src/services/config/index.ts index 3fbb2d0ed97..7be743098e0 100644 --- a/packages/@webex/plugin-cc/src/services/config/index.ts +++ b/packages/@webex/plugin-cc/src/services/config/index.ts @@ -1,5 +1,5 @@ -import {WebexSDK, HTTP_METHODS} from '../../types'; -import {DesktopProfileResponse, ListAuxCodesResponse, Team, AgentResponse} from './types'; +import {WebexSDK, HTTP_METHODS, Team} from '../../types'; +import {DesktopProfileResponse, ListAuxCodesResponse, AgentResponse} from './types'; import HttpRequest from '../core/HttpRequest'; import {WCC_API_GATEWAY} from '../constants'; diff --git a/packages/@webex/plugin-cc/src/services/config/types.ts b/packages/@webex/plugin-cc/src/services/config/types.ts index c5855182641..96910e2faf2 100644 --- a/packages/@webex/plugin-cc/src/services/config/types.ts +++ b/packages/@webex/plugin-cc/src/services/config/types.ts @@ -1,4 +1,4 @@ -import {LoginOption} from '../../types'; +import {AuxCode, LoginOption} from '../../types'; type Enum> = T[keyof T]; @@ -138,66 +138,6 @@ export type SubscribeResponse = { message: string | null; }; -/** - * Represents the response from getListOfTeams method. - * - * @public - */ -export type Team = { - /** - * ID of the team. - */ - id: string; - - /** - * Name of the Team. - */ - name: string; -}; - -/** - * Represents AuxCode. - * @public - */ - -export type AuxCode = { - /** - * ID of the Auxiliary Code. - */ - id: string; - - /** - * Indicates whether the auxiliary code is active or not active. - */ - active: boolean; - - /** - * Indicates whether this is the default code (true) or not (false). - */ - defaultCode: boolean; - - /** - * Indicates whether this is the system default code (true) or not (false). - */ - isSystemCode: boolean; - - /** - * A short description indicating the context of the code. - */ - description: string; - - /** - * Name for the Auxiliary Code. - */ - name: string; - - /** - * Indicates the work type associated with this code.. - */ - - workTypeCode: string; -}; - /** * Represents the response from getListOfAuxCodes method. * diff --git a/packages/@webex/plugin-cc/src/types.ts b/packages/@webex/plugin-cc/src/types.ts index b34e94a3dd6..9097a357bac 100644 --- a/packages/@webex/plugin-cc/src/types.ts +++ b/packages/@webex/plugin-cc/src/types.ts @@ -1,5 +1,4 @@ import {CallingClientConfig} from '@webex/calling/dist/types/CallingClient/types'; -import {IAgentProfile} from './features/types'; type Enum> = T[keyof T]; @@ -153,4 +152,115 @@ export type SubscribeRequest = { allowMultiLogin: boolean; }; +/** + * Represents the response from getListOfTeams method. + * + * @public + */ +export type Team = { + /** + * ID of the team. + */ + id: string; + + /** + * Name of the Team. + */ + name: string; +}; + +/** + * Represents AuxCode. + * @public + */ + +export type AuxCode = { + /** + * ID of the Auxiliary Code. + */ + id: string; + + /** + * Indicates whether the auxiliary code is active or not active. + */ + active: boolean; + + /** + * Indicates whether this is the default code (true) or not (false). + */ + defaultCode: boolean; + + /** + * Indicates whether this is the system default code (true) or not (false). + */ + isSystemCode: boolean; + + /** + * A short description indicating the context of the code. + */ + description: string; + + /** + * Name for the Auxiliary Code. + */ + name: string; + + /** + * Indicates the work type associated with this code.. + */ + + workTypeCode: string; +}; + +/** + * Represents the response from AgentConfig. + * + * @public + */ +export type IAgentProfile = { + /** + * The id of the agent. + */ + + agentId: string; + + /** + * The name of the agent. + */ + agentName: string; + + /** + * Identifier for a Desktop Profile. + */ + agentProfileId: string; + + /** + * The email address of the agent. + */ + + agentMailId: string; + + /** + * Represents list of teams of an agent. + */ + teams: Team[]; + + /** + * Represents the voice options of an agent. + */ + + loginVoiceOptions: string[]; + + /** + * Represents the Idle codes list that the agents can select in Agent Desktop.t. + */ + + idleCodes: AuxCode[]; + + /** + * Represents the wrap-up codes list that the agents can select when they wrap up a contact. + */ + wrapUpCodes: AuxCode[]; +}; + export type EventResult = IAgentProfile; From 556873d458d1b99f37ecd3c510755f4c8545ac9d Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Fri, 8 Nov 2024 18:52:52 +0530 Subject: [PATCH 04/15] fix: refactored types to diff file --- .../plugin-cc/src/services/core/aqm-reqs.ts | 64 ++++--------------- .../plugin-cc/src/services/core/types.ts | 55 ++++++++++++++++ 2 files changed, 67 insertions(+), 52 deletions(-) create mode 100644 packages/@webex/plugin-cc/src/services/core/types.ts diff --git a/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts b/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts index 6daf1572e1f..b412316489a 100644 --- a/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts +++ b/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts @@ -1,10 +1,21 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import {Signal} from './Signal'; import {Msg} from './GlobalTypes'; import * as Err from './Err'; import {HTTP_METHODS, WebexRequestPayload} from '../../types'; import HttpRequest from './HttpRequest'; import LoggerProxy from '../../logger-proxy'; +import { + CbRes, + Conf, + ConfEmpty, + EvtConf, + EvtRes, + Pending, + Req, + Res, + ResEmpty, + Timeout, +} from './types'; export const TIMEOUT_REQ = 20000; const TIMEOUT_EVT = TIMEOUT_REQ; @@ -424,54 +435,3 @@ export class AqmReqs { } }; } - -type Pending = { - check: (msg: Msg) => boolean; - handle: (msg: Msg) => void; - alternateBind?: string; -}; - -type BindType = string | string[] | {[key: string]: BindType}; -interface Bind { - type: BindType; - data?: any; -} - -type Req = { - url: string; - host?: string; - method?: HTTP_METHODS; - err?: - | ((errObj: WebexRequestPayload) => Err.Details<'Service.reqs.generic.failure'>) - | Err.IdsMessage - | ((e: WebexRequestPayload) => Err.Message | Err.Details); - notifSuccess: {bind: Bind; msg: TRes}; - notifFail?: - | { - bind: Bind; - errMsg: TErr; - err: (e: TErr) => Err.Details; - } - | { - bind: Bind; - errId: Err.IdsDetails; - }; - data?: any; - headers?: Record; - timeout?: Timeout; - notifCancel?: {bind: Bind; msg: TRes}; -}; - -type Timeout = number | 'disabled'; - -type Conf = (p: TReq) => Req; -type ConfEmpty = () => Req; -export type Res = (p: TReq, cbRes?: CbRes) => Promise; -export type ResEmpty = (cbRes?: CbRes) => Promise; -type CbRes = (res: any) => void | TRes; - -// evt -type EvtConf = {bind: Bind; msg: T}; -type EvtRes = Signal.WithData & { - listenOnceAsync: (p?: {resolveIf?: (msg: T) => boolean; timeout?: Timeout}) => Promise; -}; diff --git a/packages/@webex/plugin-cc/src/services/core/types.ts b/packages/@webex/plugin-cc/src/services/core/types.ts new file mode 100644 index 00000000000..a23ed1ed362 --- /dev/null +++ b/packages/@webex/plugin-cc/src/services/core/types.ts @@ -0,0 +1,55 @@ +import {HTTP_METHODS, WebexRequestPayload} from '../../types'; +import * as Err from './Err'; +import {Msg} from './GlobalTypes'; +import {Signal} from './Signal'; + +export type Pending = { + check: (msg: Msg) => boolean; + handle: (msg: Msg) => void; + alternateBind?: string; +}; + +export type BindType = string | string[] | {[key: string]: BindType}; +interface Bind { + type: BindType; + data?: any; +} + +export type Timeout = number | 'disabled'; + +export type Req = { + url: string; + host?: string; + method?: HTTP_METHODS; + err?: + | ((errObj: WebexRequestPayload) => Err.Details<'Service.reqs.generic.failure'>) + | Err.IdsMessage + | ((e: WebexRequestPayload) => Err.Message | Err.Details); + notifSuccess: {bind: Bind; msg: TRes}; + notifFail?: + | { + bind: Bind; + errMsg: TErr; + err: (e: TErr) => Err.Details; + } + | { + bind: Bind; + errId: Err.IdsDetails; + }; + data?: any; + headers?: Record; + timeout?: Timeout; + notifCancel?: {bind: Bind; msg: TRes}; +}; + +export type Conf = (p: TReq) => Req; +export type ConfEmpty = () => Req; +export type Res = (p: TReq, cbRes?: CbRes) => Promise; +export type ResEmpty = (cbRes?: CbRes) => Promise; +export type CbRes = (res: any) => void | TRes; + +// evt +export type EvtConf = {bind: Bind; msg: T}; +export type EvtRes = Signal.WithData & { + listenOnceAsync: (p?: {resolveIf?: (msg: T) => boolean; timeout?: Timeout}) => Promise; +}; From d09356bb19860089e012f50e72114c621dad5b6d Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Fri, 8 Nov 2024 19:22:31 +0530 Subject: [PATCH 05/15] fix: callingClientConfig added to calling package exports --- packages/@webex/plugin-cc/src/WebCallingService.ts | 10 ++++++++-- packages/calling/src/index.ts | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/@webex/plugin-cc/src/WebCallingService.ts b/packages/@webex/plugin-cc/src/WebCallingService.ts index 98bcfb28d17..653d4218a51 100644 --- a/packages/@webex/plugin-cc/src/WebCallingService.ts +++ b/packages/@webex/plugin-cc/src/WebCallingService.ts @@ -1,5 +1,11 @@ -import {createClient, ICall, ICallingClient, ILine, LINE_EVENTS} from '@webex/calling'; -import {CallingClientConfig} from '@webex/calling/dist/types/CallingClient/types'; +import { + createClient, + ICall, + ICallingClient, + ILine, + LINE_EVENTS, + CallingClientConfig, +} from '@webex/calling'; import {WebexSDK} from './types'; import {TIMEOUT_DURATION} from './constants'; diff --git a/packages/calling/src/index.ts b/packages/calling/src/index.ts index cdb92097f7e..1673dfccb6f 100644 --- a/packages/calling/src/index.ts +++ b/packages/calling/src/index.ts @@ -52,3 +52,4 @@ export {CallError, LineError} from './Errors'; export {ICall, TransferType} from './CallingClient/calling/types'; export {LOGGER} from './Logger/types'; export {LocalMicrophoneStream} from '@webex/media-helpers'; +export {CallingClientConfig} from './CallingClient/types'; From da1aaf0d72cd554faa59403e5c821a280861609e Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Sun, 10 Nov 2024 20:10:26 +0530 Subject: [PATCH 06/15] fix: cleaned encryption and compression logic --- .../plugin-cc/src/services/core/aqm-reqs.ts | 123 ------------------ .../test/unit/spec/services/core/aqm-reqs.ts | 63 ++++++++- 2 files changed, 59 insertions(+), 127 deletions(-) diff --git a/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts b/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts index b412316489a..67fbda84785 100644 --- a/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts +++ b/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts @@ -19,8 +19,6 @@ import { export const TIMEOUT_REQ = 20000; const TIMEOUT_EVT = TIMEOUT_REQ; -const FC_DESKTOP_VIEW = 'FC-DESKTOP-VIEW'; -const fcDesktopView = 'fcDesktopView'; export class AqmReqs { private pendingRequests: Record = {}; @@ -261,70 +259,6 @@ export class AqmReqs { return true; } - private readonly identifyInteractionIsTaskObject = (event: any) => { - // This method will return the callProcessingDetails are present inside the interaction object or task object - return event?.data?.task?.callProcessingDetails ?? false; - }; - - private isFlowValuesEncrypted(event: any) { - let fcDesktopView1: any; - if (this.identifyInteractionIsTaskObject(event)) { - fcDesktopView1 = event?.data?.task?.callAssociatedData[FC_DESKTOP_VIEW]?.value; - } else { - fcDesktopView1 = event?.data?.interaction?.callAssociatedData[FC_DESKTOP_VIEW]?.value; - } - - return fcDesktopView1?.includes('pop-over') || fcDesktopView1?.includes('interaction-panel'); - } - - private isValidCADFlowValue(event: any) { - // event?.data?.interaction: CAD, CPD values are under the event?.data?.interaction for call events - // event?.data?.task :CAD, CPD values are under the event?.data?.task for monitoring call events - if (this.identifyInteractionIsTaskObject(event)) { - return ( - (event?.data?.task?.callAssociatedData[FC_DESKTOP_VIEW]?.value && - event?.data?.task?.callAssociatedData[FC_DESKTOP_VIEW]?.value !== '') ?? - false - ); - } - // Interaction details are present like event?.data?.interaction - - return ( - (event?.data?.interaction?.callAssociatedData[FC_DESKTOP_VIEW]?.value && - event?.data?.interaction?.callAssociatedData[FC_DESKTOP_VIEW]?.value !== '') ?? - false - ); - } - - private isValidCPDFlowValue(event: any) { - const isDesktopCpdViewEnabled = false; // TODO: revisit this to get the feature flag value if needed by desktop client - // event?.data?.interaction: CAD, CPD values are under the event?.data?.interaction for call events - // event?.data?.task :CAD, CPD values are under the event?.data?.task for monitoring call events - // SERVICE.featureflag.isDesktopCpdViewEnabled() - if (isDesktopCpdViewEnabled) { - if (this.identifyInteractionIsTaskObject(event)) { - return ( - event?.data?.task?.callProcessingDetails[fcDesktopView] && - event?.data?.task?.callProcessingDetails[fcDesktopView] !== '' - ); - } - - return ( - event?.data?.interaction?.callProcessingDetails[fcDesktopView] && - event?.data?.interaction?.callProcessingDetails[fcDesktopView] !== '' - ); - } - - return false; - } - - private getDecompressedValue(encryptedValue: Buffer) { - return encryptedValue; - // TODO: Revisit this to get the decompressSync method from the compression library if needed by desktop client - // const decryptedValue: Uint8Array = decompressSync(encryptedValue); - // return strFromU8(decryptedValue); - } - // must be lambda private readonly onMessage = (event: any) => { // const event = JSON.parse(msg); @@ -340,63 +274,6 @@ export class AqmReqs { return; } - if (this.isValidCADFlowValue(event) && !this.isFlowValuesEncrypted(event)) { - const isTaskObject = this.identifyInteractionIsTaskObject(event); - try { - const targetObject = isTaskObject ? event?.data?.task : event?.data?.interaction; - const encryptedFcValue = targetObject?.callAssociatedData[FC_DESKTOP_VIEW]?.value; - const encryptedValue = Buffer.from(encryptedFcValue, 'base64'); - // Update the decrypted value to same object - targetObject.callAssociatedData[FC_DESKTOP_VIEW].value = - this.getDecompressedValue(encryptedValue); - - const interactionId = targetObject?.interactionId; - LoggerProxy.logger.info( - `${FC_DESKTOP_VIEW} values decrypted successfully for Interaction ID: ${ - interactionId || '' - }` - ); - } catch { - const targetObject = isTaskObject ? event?.data?.task : event?.data?.interaction; - const interactionId = targetObject?.interactionId; - const fcValue = targetObject?.callAssociatedData[FC_DESKTOP_VIEW]?.value || ''; - - LoggerProxy.logger.error( - `Error on decrypting ${FC_DESKTOP_VIEW} value for Interaction Id: ${interactionId}${fcValue}` || - '' - ); - } - } else if (this.isValidCPDFlowValue(event)) { - const isTaskObject = this.identifyInteractionIsTaskObject(event); - try { - // When WXCC_DESKTOP_VIEW_IN_CPD FF is enabled, then values will be fcDesktopView values are always compressed. - const targetObject = isTaskObject ? event?.data?.task : event?.data?.interaction; - const encryptedFcValue = targetObject?.callProcessingDetails[fcDesktopView]; - const encryptedValue = Buffer.from(encryptedFcValue, 'base64'); - - // Update the decrypted value to same object - targetObject.callProcessingDetails[fcDesktopView] = - this.getDecompressedValue(encryptedValue); - - const interactionId = targetObject?.interactionId; - LoggerProxy.logger.info( - `${fcDesktopView} values decrypted successfully for Interaction ID: ${ - interactionId || '' - }` - ); - } catch { - const targetObject = isTaskObject ? event?.data?.task : event?.data?.interaction; - const interactionId = targetObject?.interactionId; - const fcValue = targetObject?.callProcessingDetails[fcDesktopView] || ''; - - LoggerProxy.logger.error( - `Error on decrypting ${fcDesktopView} value for Interaction Id: ${ - interactionId || '' - }${fcValue}` || '' - ); - } - } - let isHandled = false; const kReq = Object.keys(this.pendingRequests); diff --git a/packages/@webex/plugin-cc/test/unit/spec/services/core/aqm-reqs.ts b/packages/@webex/plugin-cc/test/unit/spec/services/core/aqm-reqs.ts index 593f9cd1159..1e4517fe205 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/services/core/aqm-reqs.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/services/core/aqm-reqs.ts @@ -1,11 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import * as config from '../../../../../src/services/core/config'; import {AqmReqs} from '../../../../../src/services/core/aqm-reqs'; -import {HTTP_METHODS} from '../../../../../src/types'; import HttpRequest from '../../../../../src/services/core/HttpRequest'; import LoggerProxy from '../../../../../src/logger-proxy'; -import * as Err from '../../../../../src/services/core/Err'; -import {Msg} from '../../../../../src/services/core/GlobalTypes'; jest.mock('../../../../../src/services/core/HttpRequest'); jest.mock('../../../../../src/logger-proxy', () => ({ @@ -14,6 +10,7 @@ jest.mock('../../../../../src/logger-proxy', () => ({ logger: { log: jest.fn(), error: jest.fn(), + info: jest.fn(), }, initialize: jest.fn(), }, @@ -323,4 +320,62 @@ describe('AqmReqs', () => { expect(p).toBeDefined(); } catch (e) {} }); + + it('should handle onMessage with Welcome event', () => { + const mockWebSocket = { + on: jest.fn(), + }; + + httpRequestInstance.getWebSocket = jest.fn().mockReturnValue(mockWebSocket); + + const aqm = new AqmReqs(); + + const event = { + type: 'Welcome', + }; + + aqm['onMessage'](event); + + expect(LoggerProxy.logger.info).toHaveBeenCalledWith( + 'Welcome message from Notifs Websocket[object Object]' + ); + }); + + it('should handle onMessage with Keepalive event', () => { + const mockWebSocket = { + on: jest.fn(), + }; + + httpRequestInstance.getWebSocket = jest.fn().mockReturnValue(mockWebSocket); + + const aqm = new AqmReqs(); + + const event = { + keepalive: true, + }; + + aqm['onMessage'](event); + + expect(LoggerProxy.logger.info).toHaveBeenCalledWith('Keepalive from notifs[object Object]'); + }); + + it('should handle onMessage with missing event handler', () => { + const mockWebSocket = { + on: jest.fn(), + }; + + httpRequestInstance.getWebSocket = jest.fn().mockReturnValue(mockWebSocket); + + const aqm = new AqmReqs(); + + const event = { + type: 'UnknownEvent', + }; + + aqm['onMessage'](event); + + expect(LoggerProxy.logger.info).toHaveBeenCalledWith( + 'event=missingEventHandler | [AqmReqs] missing routing message handler[object Object]' + ); + }); }); From 8acb6e3911106f702ffcb423a22815dbe1adcb1b Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Sun, 10 Nov 2024 20:35:25 +0530 Subject: [PATCH 07/15] fix: cleaned signal class and used event emitter and removed unused code --- .../@webex/plugin-cc/src/services/core/Err.ts | 1 - .../src/services/core/HttpRequest.ts | 57 ------- .../plugin-cc/src/services/core/Signal.ts | 153 ------------------ .../plugin-cc/src/services/core/aqm-reqs.ts | 21 +-- .../plugin-cc/src/services/core/types.ts | 4 +- .../unit/spec/services/core/HttpRequest.ts | 114 +------------ 6 files changed, 15 insertions(+), 335 deletions(-) delete mode 100644 packages/@webex/plugin-cc/src/services/core/Signal.ts diff --git a/packages/@webex/plugin-cc/src/services/core/Err.ts b/packages/@webex/plugin-cc/src/services/core/Err.ts index bc73ac3dc56..b33a5e27b31 100644 --- a/packages/@webex/plugin-cc/src/services/core/Err.ts +++ b/packages/@webex/plugin-cc/src/services/core/Err.ts @@ -1,7 +1,6 @@ import {WebexRequestPayload} from '../../types'; import {Failure} from './GlobalTypes'; -/* eslint-disable @typescript-eslint/no-namespace */ export type ErrDetails = {status: number; type: string; trackingId: string}; export type AgentErrorIds = diff --git a/packages/@webex/plugin-cc/src/services/core/HttpRequest.ts b/packages/@webex/plugin-cc/src/services/core/HttpRequest.ts index a9e9ce50e62..9cf375f4837 100644 --- a/packages/@webex/plugin-cc/src/services/core/HttpRequest.ts +++ b/packages/@webex/plugin-cc/src/services/core/HttpRequest.ts @@ -85,63 +85,6 @@ class HttpRequest { }); } - /* This sends a request and waits for a websocket event - * It sends the request and then listens for the event type specified in the options - * If the event type is received, it resolves the promise - * If the event type is not received, it rejects the promise - */ - public async sendRequestWithEvent(options: { - service: string; - resource: string; - method: HTTP_METHODS; - payload: object; - eventType: string; - success: string[]; - failure: string[]; - }): Promise { - try { - const {service, resource, method, payload, eventType, success, failure} = options; - - // Send the service request - const response = await this.webex.request({ - service, - resource, - method, - body: payload, - }); - this.webex.logger.log(`Service request sent successfully: ${response}`); - - // Listen for the event - return new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => { - this.webex.logger.error('Timeout waiting for event'); - this.eventHandlers.delete(eventType); - reject(new Error('Timeout waiting for event')); - }, WEBSOCKET_EVENT_TIMEOUT); - - // Store the event handler - this.eventHandlers.set(eventType, (data: any) => { - clearTimeout(timeoutId); - this.eventHandlers.delete(eventType); - if (success.includes(data.type)) { - resolve(data); - } else if (failure.includes(data.type)) { - const error = new Error(); - error.name = data.type; - error.message = data.reason; - reject(error); - } else { - // If event type is neither in success nor failure, handle it accordingly - reject(new Error(`Unexpected event type received: ${data.type}`)); - } - }); - }); - } catch (error) { - this.webex.logger.error(`Error sending service request: ${error}`); - throw error; - } - } - public async request(options: { service: string; resource: string; diff --git a/packages/@webex/plugin-cc/src/services/core/Signal.ts b/packages/@webex/plugin-cc/src/services/core/Signal.ts deleted file mode 100644 index 76bfe96d046..00000000000 --- a/packages/@webex/plugin-cc/src/services/core/Signal.ts +++ /dev/null @@ -1,153 +0,0 @@ -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace Signal { - class WithDataClass implements WithData { - private listeners: Listener[] = []; - private listenersOnce: Listener[] = []; - - listen = (listener: Listener) => { - this.listeners.push(listener); - - return {stopListen: () => this.stopListen(listener)}; - }; - - listenOnce = (listener: Listener) => { - this.listenersOnce.push(listener); - - return {stopListenOnce: () => this.stopListenOnce(listener)}; - }; - - stopListen = (listener: Listener) => { - const index = this.listeners.indexOf(listener, 0); - if (index > -1) { - this.listeners.splice(index, 1); - - return true; - } - - return false; - }; - - stopListenOnce = (listener: Listener) => { - const index = this.listenersOnce.indexOf(listener, 0); - if (index > -1) { - this.listenersOnce.splice(index, 1); - - return true; - } - - return false; - }; - - // concealed - stopListenAll = () => { - this.listeners = []; - this.listenersOnce = []; - }; - - // concealed - send = (data: T) => { - this.listeners.forEach((listener) => listener(data)); - this.listenersOnce.forEach((listener) => listener(data)); - this.listenersOnce = []; - }; - } - - class EmptyClass implements Empty { - private listeners: ListenerEmpty[] = []; - private listenersOnce: ListenerEmpty[] = []; - - listen = (listener: ListenerEmpty) => { - this.listeners.push(listener); - - return {stopListen: () => this.stopListen(listener)}; - }; - - listenOnce = (listener: ListenerEmpty) => { - this.listenersOnce.push(listener); - - return {stopListenOnce: () => this.stopListenOnce(listener)}; - }; - - stopListen = (listener: ListenerEmpty) => { - const index = this.listeners.indexOf(listener, 0); - if (index > -1) { - this.listeners.splice(index, 1); - - return true; - } - - return false; - }; - - stopListenOnce = (listener: ListenerEmpty) => { - const index = this.listenersOnce.indexOf(listener, 0); - if (index > -1) { - this.listenersOnce.splice(index, 1); - - return true; - } - - return false; - }; - - // concealed - stopListenAll = () => { - this.listeners = []; - this.listenersOnce = []; - }; - - // concealed - send = () => { - this.listeners.forEach((listener) => listener()); - this.listenersOnce.forEach((listener) => listener()); - this.listenersOnce = []; - }; - } - - type Listener = (data: T) => void; - type ListenerEmpty = () => void; - - export type Send = (data: T) => void; - export type SendEmpty = () => void; - - export interface WithData { - listen: (listener: Listener) => {stopListen: () => boolean}; - listenOnce: (listener: Listener) => {stopListenOnce: () => boolean}; - stopListen: (listener: Listener) => boolean; - stopListenOnce: (listener: Listener) => boolean; - } - - export interface Empty { - listen: (listener: ListenerEmpty) => {stopListen: () => boolean}; - listenOnce: (listener: ListenerEmpty) => {stopListenOnce: () => boolean}; - stopListen: (listener: ListenerEmpty) => boolean; - stopListenOnce: (listener: ListenerEmpty) => boolean; - } - - type Return = {signal: WithData; send: (data: T) => void; stopListenAll: () => void}; - type ReturnEmpty = {signal: Empty; send: () => void; stopListenAll: () => void}; - - class Create { - withData(): Return { - const signal = new WithDataClass(); - - return { - signal, - send: signal.send, - stopListenAll: signal.stopListenAll, - }; - } - - empty(): ReturnEmpty { - const signal = new EmptyClass(); - - return { - signal, - send: signal.send, - stopListenAll: signal.stopListenAll, - }; - } - } - - export const create = new Create(); -} diff --git a/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts b/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts index 67fbda84785..f6f633c7a32 100644 --- a/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts +++ b/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts @@ -1,4 +1,4 @@ -import {Signal} from './Signal'; +import {EventEmitter} from 'events'; import {Msg} from './GlobalTypes'; import * as Err from './Err'; import {HTTP_METHODS, WebexRequestPayload} from '../../types'; @@ -45,27 +45,30 @@ export class AqmReqs { } evt(p: EvtConf): EvtRes { - const {send, signal} = Signal.create.withData(); + const eventEmitter = new EventEmitter(); const k = this.bindPrint(p.bind); if (this.pendingEvents[k]) { throw new Err.Details('Service.aqm.reqs.PendingEvent', {key: k}); } + this.pendingEvents[k] = { check: (msg: Msg) => this.bindCheck(p.bind, msg), - handle: (msg: any) => send(msg), + handle: (msg: any) => eventEmitter.emit('data', msg), }; // add listenOnceAsync - const evt: EvtRes = signal as any; + const evt: EvtRes = eventEmitter as any; evt.listenOnceAsync = (promise?: {resolveIf?: (msg: T) => boolean; timeout?: Timeout}) => { return new Promise((resolve, reject) => { - const {stopListen} = signal.listen((msg) => { + const listener = (msg: T) => { if (promise?.resolveIf ? promise.resolveIf(msg) : true) { - stopListen(); + eventEmitter.removeListener('data', listener); resolve(msg); } - }); + }; + + eventEmitter.on('data', listener); if (promise?.timeout === 'disabled') { return; @@ -74,8 +77,8 @@ export class AqmReqs { const ms = promise && promise.timeout && promise.timeout > 0 ? promise.timeout : TIMEOUT_EVT; setTimeout(() => { - const isStopped = stopListen(); - if (isStopped) { + const removed = eventEmitter.removeListener('data', listener); + if (removed) { reject(new Err.Details('Service.aqm.reqs.TimeoutEvent', {key: k})); } }, ms); diff --git a/packages/@webex/plugin-cc/src/services/core/types.ts b/packages/@webex/plugin-cc/src/services/core/types.ts index a23ed1ed362..14ccaa4f514 100644 --- a/packages/@webex/plugin-cc/src/services/core/types.ts +++ b/packages/@webex/plugin-cc/src/services/core/types.ts @@ -1,7 +1,7 @@ +import {EventEmitter} from 'events'; import {HTTP_METHODS, WebexRequestPayload} from '../../types'; import * as Err from './Err'; import {Msg} from './GlobalTypes'; -import {Signal} from './Signal'; export type Pending = { check: (msg: Msg) => boolean; @@ -50,6 +50,6 @@ export type CbRes = (res: any) => void | TRes; // evt export type EvtConf = {bind: Bind; msg: T}; -export type EvtRes = Signal.WithData & { +export type EvtRes = EventEmitter & { listenOnceAsync: (p?: {resolveIf?: (msg: T) => boolean; timeout?: Timeout}) => Promise; }; diff --git a/packages/@webex/plugin-cc/test/unit/spec/services/core/HttpRequest.ts b/packages/@webex/plugin-cc/test/unit/spec/services/core/HttpRequest.ts index 8e65060f271..48b2cb72ab2 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/services/core/HttpRequest.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/services/core/HttpRequest.ts @@ -1,10 +1,7 @@ import HttpRequest from '../../../../../src/services/core/HttpRequest'; import {CC_EVENTS, SubscribeResponse} from '../../../../../src/services/config/types'; import {WEBSOCKET_EVENT_TIMEOUT} from '../../../../../src/services/constants'; -import MockWebex from '@webex/test-helper-mock-webex'; -import WebSocket from '../../../../../src/services/core/WebSocket'; -import {EventEmitter} from 'events'; -import {HTTP_METHODS, WebexSDK} from '../../../../../src/types'; +import {WebexSDK} from '../../../../../src/types'; jest.mock('../../../../../src/services/core/WebSocket'); @@ -75,113 +72,4 @@ describe('HttpRequest', () => { WEBSOCKET_EVENT_TIMEOUT + 1000 ); // Increase timeout for this test }); - - describe('sendRequestWithEvent', () => { - it('should resolve the promise when the specified event type is received', async () => { - const mockResponse = { - status: 200, - body: {}, - }; - const mockEvent = { - type: 'SUCCESS_EVENT', - data: {message: 'Success'}, - }; - - mockRequest.mockResolvedValueOnce(mockResponse); - - setTimeout(() => { - httpRequest.eventHandlers.get('EVENT_TYPE')(mockEvent); - }, 100); - - const result = await httpRequest.sendRequestWithEvent({ - service: 'service', - resource: 'resource', - method: HTTP_METHODS.POST, - payload: {}, - eventType: 'EVENT_TYPE', - success: ['SUCCESS_EVENT'], - failure: ['FAILURE_EVENT'], - }); - expect(result).toEqual(mockEvent); - }); - - it('should reject the promise when a failure event type is received', async () => { - const mockResponse = { - status: 200, - body: {}, - }; - const mockEvent = { - type: 'FAILURE_EVENT', - data: {reason: 'Failed'}, - }; - - mockRequest.mockResolvedValueOnce(mockResponse); - - setTimeout(() => { - httpRequest.eventHandlers.get('EVENT_TYPE')(mockEvent); - }, 100); - - await expect( - httpRequest.sendRequestWithEvent({ - service: 'service', - resource: 'resource', - method: HTTP_METHODS.POST, - payload: {}, - eventType: 'EVENT_TYPE', - success: ['SUCCESS_EVENT'], - failure: ['FAILURE_EVENT'], - }) - ).rejects.toThrow('FAILURE_EVENT'); - }); - - it('should reject the promise when an unexpected event type is received', async () => { - const mockResponse = { - status: 200, - body: {}, - }; - const mockEvent = { - type: 'UNEXPECTED_EVENT', - data: {message: 'Unexpected'}, - }; - - mockRequest.mockResolvedValueOnce(mockResponse); - - setTimeout(() => { - httpRequest.eventHandlers.get('EVENT_TYPE')(mockEvent); - }, 100); - - await expect( - httpRequest.sendRequestWithEvent({ - service: 'service', - resource: 'resource', - method: HTTP_METHODS.POST, - payload: {}, - eventType: 'EVENT_TYPE', - success: ['SUCCESS_EVENT'], - failure: ['FAILURE_EVENT'], - }) - ).rejects.toThrow('Unexpected event type received: UNEXPECTED_EVENT'); - }); - - it('should log and throw an error if the service request fails', async () => { - const mockError = new Error('Request failed'); - mockRequest.mockRejectedValueOnce(mockError); - - await expect( - httpRequest.sendRequestWithEvent({ - service: 'service', - resource: 'resource', - method: HTTP_METHODS.POST, - payload: {}, - eventType: 'EVENT_TYPE', - success: ['SUCCESS_EVENT'], - failure: ['FAILURE_EVENT'], - }) - ).rejects.toThrow('Request failed'); - - expect(mockWebex.logger.error).toHaveBeenCalledWith( - 'Error sending service request: Error: Request failed' - ); - }); - }); }); From bf907ea008e1f0198e481a0f0904d484c6e56cb3 Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Sun, 10 Nov 2024 20:37:55 +0530 Subject: [PATCH 08/15] fix: refactored code --- .../@webex/plugin-cc/src/services/core/Err.ts | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/packages/@webex/plugin-cc/src/services/core/Err.ts b/packages/@webex/plugin-cc/src/services/core/Err.ts index b33a5e27b31..f38a400c4c2 100644 --- a/packages/@webex/plugin-cc/src/services/core/Err.ts +++ b/packages/@webex/plugin-cc/src/services/core/Err.ts @@ -25,6 +25,34 @@ export interface Ids { 'Service.aqm.reqs': ReqError; } +export type IdsGlobal = + | 'system' // to handle errors that was not created by 'new Err.WithId()' + | 'handle' + | 'fallback'; + +export type IdsSub = Ids[keyof Ids]; + +export type IdsMessage = IdsGlobal | keyof Ids | Exclude; + +export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( + k: infer I +) => void + ? I + : never; + +export type FlattenUnion = { + [K in keyof UnionToIntersection]: K extends keyof T + ? T[K] extends any[] + ? T[K] + : T[K] extends object + ? FlattenUnion + : T[K] + : UnionToIntersection[K]; +}; +export type IdsDetailsType = FlattenUnion>; + +export type IdsDetails = keyof IdsDetailsType; + export type Id = IdsMessage | IdsDetails; export class Message extends Error { @@ -68,31 +96,3 @@ export class Details extends Error { // Marker to distinct Err class from other errors private isErr = 'yes'; } - -// -------------------- - -export type IdsGlobal = - | 'system' // to handle errors that was not created by 'new Err.WithId()' - | 'handle' - | 'fallback'; - -export type IdsSub = Ids[keyof Ids]; - -export type IdsMessage = IdsGlobal | keyof Ids | Exclude; -export type IdsDetails = keyof IdsDetailsType; -export type IdsDetailsType = FlattenUnion>; - -export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( - k: infer I -) => void - ? I - : never; -export type FlattenUnion = { - [K in keyof UnionToIntersection]: K extends keyof T - ? T[K] extends any[] - ? T[K] - : T[K] extends object - ? FlattenUnion - : T[K] - : UnionToIntersection[K]; -}; From 9ccd6901489b1973f404e02877b68c27e4edeb56 Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Mon, 11 Nov 2024 11:03:57 +0530 Subject: [PATCH 09/15] feat: added agent logout functionality --- docs/samples/contact-center/app.js | 16 +++++++ docs/samples/contact-center/index.html | 1 + .../@webex/plugin-cc/src/WebCallingService.ts | 2 +- packages/@webex/plugin-cc/src/cc.ts | 35 +++++++++++++-- .../@webex/plugin-cc/src/features/types.ts | 3 -- .../plugin-cc/src/services/config/types.ts | 43 +------------------ packages/@webex/plugin-cc/src/types.ts | 29 +++++++++++++ .../@webex/plugin-cc/test/unit/spec/cc.ts | 35 ++++++++++++--- 8 files changed, 109 insertions(+), 55 deletions(-) diff --git a/docs/samples/contact-center/app.js b/docs/samples/contact-center/app.js index de62381ffe2..952f6df61c8 100644 --- a/docs/samples/contact-center/app.js +++ b/docs/samples/contact-center/app.js @@ -27,6 +27,7 @@ const agentLogin = document.querySelector('#AgentLogin'); const agentLoginButton = document.querySelector('#loginAgent'); const dialNumber = document.querySelector('#dialNumber'); const registerStatus = document.querySelector('#ws-connection-status'); +const logoutAgentElm = document.querySelector('#logoutAgent'); // Store and Grab `access-token` from sessionStorage if (sessionStorage.getItem('date') > new Date().getTime()) { @@ -151,12 +152,27 @@ async function handleAgentLogin(e) { function doAgentLogin() { webex.cc.stationLogin({teamId: teamsDropdown.value, loginOption: agentDeviceType, dialNumber: dialNumber.value}).then((response) => { console.log('Agent Logged in successfully', response); + logoutAgentElm.classList.remove('hidden'); } ).catch((error) => { console.log('Agent Login failed', error); }); } + +function logoutAgent() { + webex.cc.stationLogout({logoutReason: 'logout'}).then((response) => { + console.log('Agent logged out successfully', response); + + setTimeout(() => { + logoutAgentElm.classList.add('hidden'); + }, 1000); + } + ).catch((error) => { + console.log('Agent logout failed', error); + }); +} + const allCollapsibleElements = document.querySelectorAll('.collapsible'); allCollapsibleElements.forEach((el) => { el.addEventListener('click', (event) => { diff --git a/docs/samples/contact-center/index.html b/docs/samples/contact-center/index.html index f6a2cdbf6e1..cbada7557d4 100644 --- a/docs/samples/contact-center/index.html +++ b/docs/samples/contact-center/index.html @@ -112,6 +112,7 @@

+
Agent status diff --git a/packages/@webex/plugin-cc/src/WebCallingService.ts b/packages/@webex/plugin-cc/src/WebCallingService.ts index 653d4218a51..c00e1c227b1 100644 --- a/packages/@webex/plugin-cc/src/WebCallingService.ts +++ b/packages/@webex/plugin-cc/src/WebCallingService.ts @@ -58,6 +58,6 @@ export default class WebCallingService { } public async deregisterWebCallingLine() { - this.line.deregister(); + this.line?.deregister(); } } diff --git a/packages/@webex/plugin-cc/src/cc.ts b/packages/@webex/plugin-cc/src/cc.ts index e3c498ed2dd..84079ab8fa7 100644 --- a/packages/@webex/plugin-cc/src/cc.ts +++ b/packages/@webex/plugin-cc/src/cc.ts @@ -1,6 +1,5 @@ import {WebexPlugin} from '@webex/webex-core'; import AgentConfig from './features/Agentconfig'; -import {StationLoginResponse} from './features/types'; import { CCPluginConfig, IContactCenter, @@ -9,14 +8,17 @@ import { LoginOption, WelcomeEvent, IAgentProfile, + AgentLogin, + StationLoginResponse, + StationLogoutResponse, } from './types'; import {READY, CC_FILE} from './constants'; import HttpRequest from './services/core/HttpRequest'; import WebCallingService from './WebCallingService'; -import {AgentLogin} from './services/config/types'; import {AGENT, WEB_RTC_PREFIX} from './services/constants'; import Services from './services'; import LoggerProxy from './logger-proxy'; +import * as Agent from './services/agent/types'; export default class ContactCenter extends WebexPlugin implements IContactCenter { namespace = 'cc'; @@ -106,7 +108,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter /** * This is used for agent login. * @param data - * @returns Promise + * @returns Promise * @throws Error */ public async stationLogin(data: AgentLogin): Promise { @@ -134,11 +136,36 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter return loginResponse; } catch (error) { - this.$webex.logger.log(`file: ${CC_FILE}: Station Login FAILED: ${error.id}`); + this.$webex.logger.log(`file: ${CC_FILE}: Station Login failed: ${error}`); throw new Error(error.details?.data?.reason ?? 'Error while performing station login'); } } + public async stationLogout(data: Agent.Logout): Promise { + try { + const logoutResponse = this.services.agent.logout({ + data, + }); + + await logoutResponse; + + if (this.webCallingService) { + this.webCallingService.deregisterWebCallingLine(); + } + + return logoutResponse; + } catch (error) { + this.$webex.logger.error(`file: ${CC_FILE}: Station Logout failed: ${error}`); + throw new Error(error.details?.data?.reason ?? 'Error while performing station logout'); + } + } + + /** + * This is used for agent logout. + * @param data + * @returns Promise + * @throws Error + */ private getDeviceId(loginOption: string, dialNumber: string): string { if (loginOption === LoginOption.EXTENSION || loginOption === LoginOption.AGENT_DN) { return dialNumber; diff --git a/packages/@webex/plugin-cc/src/features/types.ts b/packages/@webex/plugin-cc/src/features/types.ts index d7db36fb58b..40fab576405 100644 --- a/packages/@webex/plugin-cc/src/features/types.ts +++ b/packages/@webex/plugin-cc/src/features/types.ts @@ -1,4 +1,3 @@ -import * as Agent from '../services/agent/types'; import {WebexSDK} from '../types'; type Enum> = T[keyof T]; @@ -34,5 +33,3 @@ export type AgentConfigRequest = { */ orgId: string; }; - -export type StationLoginResponse = Agent.StationLoginSuccess | Error; diff --git a/packages/@webex/plugin-cc/src/services/config/types.ts b/packages/@webex/plugin-cc/src/services/config/types.ts index 96910e2faf2..f8b05f96d81 100644 --- a/packages/@webex/plugin-cc/src/services/config/types.ts +++ b/packages/@webex/plugin-cc/src/services/config/types.ts @@ -1,4 +1,4 @@ -import {AuxCode, LoginOption} from '../../types'; +import {AuxCode} from '../../types'; type Enum> = T[keyof T]; @@ -88,47 +88,6 @@ export type DesktopProfileResponse = { idleCodes: string[]; }; -/** - * Represents the request to a AgentLogin - * - * @public - */ -export type AgentLogin = { - /** - * A dialNumber field contains the number to dial such as a route point or extension. - */ - - dialNumber?: string; - - /** - * The unique ID representing a team of users. - */ - - teamId: string; - - /** - * The loginOption field contains the type of login. - */ - - loginOption: LoginOption; -}; - -export type UserStationLogin = { - dialNumber?: string | null; - dn?: string | null; - teamId: string | null; - teamName?: string | null; - roles?: Array; - siteId?: string; - usesOtherDN?: boolean; - skillProfileId?: string; - auxCodeId?: string; - isExtension?: boolean; - deviceType?: LoginOption; - deviceId: string | null; - isEmergencyModalAlreadyDisplayed?: boolean; -}; - export type SubscribeResponse = { statusCode: number; body: { diff --git a/packages/@webex/plugin-cc/src/types.ts b/packages/@webex/plugin-cc/src/types.ts index 9097a357bac..c7d31d40c50 100644 --- a/packages/@webex/plugin-cc/src/types.ts +++ b/packages/@webex/plugin-cc/src/types.ts @@ -1,4 +1,5 @@ import {CallingClientConfig} from '@webex/calling/dist/types/CallingClient/types'; +import * as Agent from './services/agent/types'; type Enum> = T[keyof T]; @@ -264,3 +265,31 @@ export type IAgentProfile = { }; export type EventResult = IAgentProfile; + +/** + * Represents the request to a AgentLogin + * + * @public + */ +export type AgentLogin = { + /** + * A dialNumber field contains the number to dial such as a route point or extension. + */ + + dialNumber?: string; + + /** + * The unique ID representing a team of users. + */ + + teamId: string; + + /** + * The loginOption field contains the type of login. + */ + + loginOption: LoginOption; +}; + +export type StationLoginResponse = Agent.StationLoginSuccess | Error; +export type StationLogoutResponse = Agent.LogoutSuccess | Error; diff --git a/packages/@webex/plugin-cc/test/unit/spec/cc.ts b/packages/@webex/plugin-cc/test/unit/spec/cc.ts index eaeb155feed..39b830c6a59 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/cc.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/cc.ts @@ -1,15 +1,12 @@ import 'jsdom-global/register'; -import {LoginOption, WebexSDK} from '../../../src/types'; -import HttpRequest from '../../../src/services/core/HttpRequest'; -import WebCallingService from '../../../src/WebCallingService'; +import {LoginOption, StationLogoutResponse, WebexSDK} from '../../../src/types'; import ContactCenter from '../../../src/cc'; import MockWebex from '@webex/test-helper-mock-webex'; import {StationLoginSuccess} from '../../../src/services/agent/types'; -import {IAgentProfile} from '../../../src/features/types'; +import {IAgentProfile} from '../../../src/types'; import {AGENT, WEB_RTC_PREFIX} from '../../../src/services/constants'; import Services from '../../../src/services'; import config from '../../../src/config'; -import {web} from 'webpack'; jest.mock('../../../src/logger-proxy', () => ({ __esModule: true, @@ -61,6 +58,7 @@ describe('webex.cc', () => { const mockServicesInstance = { agent: { stationLogin: jest.fn(), + logout: jest.fn(), }, }; (Services.getInstance as jest.Mock).mockReturnValue(mockServicesInstance); @@ -230,4 +228,31 @@ describe('webex.cc', () => { ); }); }); + + describe('stationLogout', () => { + it('should logout successfully', async () => { + const data = {logoutReason: 'Logout reason'}; + const response = {}; + + const stationLogoutMock = jest + .spyOn(webex.cc.services.agent, 'logout') + .mockResolvedValue({} as StationLogoutResponse); + + const result = await webex.cc.stationLogout(data); + + expect(stationLogoutMock).toHaveBeenCalledWith({data: data}); + expect(result).toEqual(response); + }); + + it('should handle error during stationLogout', async () => { + const data = {logoutReason: 'Logout reason'}; + const error = new Error('Error while performing station logout'); + + jest.spyOn(webex.cc.services.agent, 'logout').mockRejectedValue(error); + + await expect(webex.cc.stationLogout(data)).rejects.toThrow(error); + + expect(webex.logger.error).toHaveBeenCalledWith(`file: cc: Station Logout failed: ${error}`); + }); + }); }); From 6a752615bd25708a6350ec3346ca7832a000b2a7 Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Mon, 11 Nov 2024 11:23:24 +0530 Subject: [PATCH 10/15] feat: agent relogin functionality added --- docs/samples/contact-center/app.js | 10 +++++++ packages/@webex/plugin-cc/src/cc.ts | 23 +++++++++++----- packages/@webex/plugin-cc/src/types.ts | 1 + .../@webex/plugin-cc/test/unit/spec/cc.ts | 26 +++++++++++++++++++ 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/docs/samples/contact-center/app.js b/docs/samples/contact-center/app.js index 952f6df61c8..bc88c68364b 100644 --- a/docs/samples/contact-center/app.js +++ b/docs/samples/contact-center/app.js @@ -153,6 +153,16 @@ function doAgentLogin() { webex.cc.stationLogin({teamId: teamsDropdown.value, loginOption: agentDeviceType, dialNumber: dialNumber.value}).then((response) => { console.log('Agent Logged in successfully', response); logoutAgentElm.classList.remove('hidden'); + // Re-Login Agent after 5 seconds for testing purpose + setTimeout(async () => { + try { + const response = await webex.cc.stationReLogin(); + + console.log('Agent Re-Login successful', response); + } catch (error) { + console.log('Agent Re-Login failed', error); + } + }, 5000); } ).catch((error) => { console.log('Agent Login failed', error); diff --git a/packages/@webex/plugin-cc/src/cc.ts b/packages/@webex/plugin-cc/src/cc.ts index 84079ab8fa7..9914f18be79 100644 --- a/packages/@webex/plugin-cc/src/cc.ts +++ b/packages/@webex/plugin-cc/src/cc.ts @@ -11,6 +11,7 @@ import { AgentLogin, StationLoginResponse, StationLogoutResponse, + StationReLoginResponse, } from './types'; import {READY, CC_FILE} from './constants'; import HttpRequest from './services/core/HttpRequest'; @@ -141,6 +142,11 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter } } + /** This is used for agent logout. + * @param data + * @returns Promise + * @throws Error + */ public async stationLogout(data: Agent.Logout): Promise { try { const logoutResponse = this.services.agent.logout({ @@ -160,12 +166,17 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter } } - /** - * This is used for agent logout. - * @param data - * @returns Promise - * @throws Error - */ + public async stationReLogin(): Promise { + try { + const reLoginResponse = await this.services.agent.reload(); + + return reLoginResponse; + } catch (error) { + this.$webex.logger.error(`file: ${CC_FILE}: Station ReLogin failed: ${error}`); + throw new Error(error.details?.data?.reason ?? 'Error while performing station relogin'); + } + } + private getDeviceId(loginOption: string, dialNumber: string): string { if (loginOption === LoginOption.EXTENSION || loginOption === LoginOption.AGENT_DN) { return dialNumber; diff --git a/packages/@webex/plugin-cc/src/types.ts b/packages/@webex/plugin-cc/src/types.ts index c7d31d40c50..2d3ebf1d4cd 100644 --- a/packages/@webex/plugin-cc/src/types.ts +++ b/packages/@webex/plugin-cc/src/types.ts @@ -293,3 +293,4 @@ export type AgentLogin = { export type StationLoginResponse = Agent.StationLoginSuccess | Error; export type StationLogoutResponse = Agent.LogoutSuccess | Error; +export type StationReLoginResponse = Agent.ReloginSuccess | Error; diff --git a/packages/@webex/plugin-cc/test/unit/spec/cc.ts b/packages/@webex/plugin-cc/test/unit/spec/cc.ts index 39b830c6a59..f8604ad2ff2 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/cc.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/cc.ts @@ -59,6 +59,7 @@ describe('webex.cc', () => { agent: { stationLogin: jest.fn(), logout: jest.fn(), + reload: jest.fn(), }, }; (Services.getInstance as jest.Mock).mockReturnValue(mockServicesInstance); @@ -255,4 +256,29 @@ describe('webex.cc', () => { expect(webex.logger.error).toHaveBeenCalledWith(`file: cc: Station Logout failed: ${error}`); }); }); + + describe('stationRelogin', () => { + it('should relogin successfully', async () => { + const response = {}; + + const stationLoginMock = jest + .spyOn(webex.cc.services.agent, 'reload') + .mockResolvedValue({} as StationLoginSuccess); + + const result = await webex.cc.stationReLogin(); + + expect(stationLoginMock).toHaveBeenCalled(); + expect(result).toEqual(response); + }); + + it('should handle error during relogin', async () => { + const error = new Error('Error while performing station relogin'); + + jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error); + + await expect(webex.cc.stationReLogin()).rejects.toThrow(error); + + expect(webex.logger.error).toHaveBeenCalledWith(`file: cc: Station ReLogin failed: ${error}`); + }); + }); }); From 230aa13f53bd0074a2af71eb596b99726816cc54 Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Mon, 11 Nov 2024 13:27:05 +0530 Subject: [PATCH 11/15] fix: reverted signal back as its needed for aqm notif --- .../plugin-cc/src/services/core/Signal.ts | 153 ++++++++++++++++++ .../plugin-cc/src/services/core/aqm-reqs.ts | 21 ++- .../plugin-cc/src/services/core/types.ts | 4 +- 3 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 packages/@webex/plugin-cc/src/services/core/Signal.ts diff --git a/packages/@webex/plugin-cc/src/services/core/Signal.ts b/packages/@webex/plugin-cc/src/services/core/Signal.ts new file mode 100644 index 00000000000..76bfe96d046 --- /dev/null +++ b/packages/@webex/plugin-cc/src/services/core/Signal.ts @@ -0,0 +1,153 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace Signal { + class WithDataClass implements WithData { + private listeners: Listener[] = []; + private listenersOnce: Listener[] = []; + + listen = (listener: Listener) => { + this.listeners.push(listener); + + return {stopListen: () => this.stopListen(listener)}; + }; + + listenOnce = (listener: Listener) => { + this.listenersOnce.push(listener); + + return {stopListenOnce: () => this.stopListenOnce(listener)}; + }; + + stopListen = (listener: Listener) => { + const index = this.listeners.indexOf(listener, 0); + if (index > -1) { + this.listeners.splice(index, 1); + + return true; + } + + return false; + }; + + stopListenOnce = (listener: Listener) => { + const index = this.listenersOnce.indexOf(listener, 0); + if (index > -1) { + this.listenersOnce.splice(index, 1); + + return true; + } + + return false; + }; + + // concealed + stopListenAll = () => { + this.listeners = []; + this.listenersOnce = []; + }; + + // concealed + send = (data: T) => { + this.listeners.forEach((listener) => listener(data)); + this.listenersOnce.forEach((listener) => listener(data)); + this.listenersOnce = []; + }; + } + + class EmptyClass implements Empty { + private listeners: ListenerEmpty[] = []; + private listenersOnce: ListenerEmpty[] = []; + + listen = (listener: ListenerEmpty) => { + this.listeners.push(listener); + + return {stopListen: () => this.stopListen(listener)}; + }; + + listenOnce = (listener: ListenerEmpty) => { + this.listenersOnce.push(listener); + + return {stopListenOnce: () => this.stopListenOnce(listener)}; + }; + + stopListen = (listener: ListenerEmpty) => { + const index = this.listeners.indexOf(listener, 0); + if (index > -1) { + this.listeners.splice(index, 1); + + return true; + } + + return false; + }; + + stopListenOnce = (listener: ListenerEmpty) => { + const index = this.listenersOnce.indexOf(listener, 0); + if (index > -1) { + this.listenersOnce.splice(index, 1); + + return true; + } + + return false; + }; + + // concealed + stopListenAll = () => { + this.listeners = []; + this.listenersOnce = []; + }; + + // concealed + send = () => { + this.listeners.forEach((listener) => listener()); + this.listenersOnce.forEach((listener) => listener()); + this.listenersOnce = []; + }; + } + + type Listener = (data: T) => void; + type ListenerEmpty = () => void; + + export type Send = (data: T) => void; + export type SendEmpty = () => void; + + export interface WithData { + listen: (listener: Listener) => {stopListen: () => boolean}; + listenOnce: (listener: Listener) => {stopListenOnce: () => boolean}; + stopListen: (listener: Listener) => boolean; + stopListenOnce: (listener: Listener) => boolean; + } + + export interface Empty { + listen: (listener: ListenerEmpty) => {stopListen: () => boolean}; + listenOnce: (listener: ListenerEmpty) => {stopListenOnce: () => boolean}; + stopListen: (listener: ListenerEmpty) => boolean; + stopListenOnce: (listener: ListenerEmpty) => boolean; + } + + type Return = {signal: WithData; send: (data: T) => void; stopListenAll: () => void}; + type ReturnEmpty = {signal: Empty; send: () => void; stopListenAll: () => void}; + + class Create { + withData(): Return { + const signal = new WithDataClass(); + + return { + signal, + send: signal.send, + stopListenAll: signal.stopListenAll, + }; + } + + empty(): ReturnEmpty { + const signal = new EmptyClass(); + + return { + signal, + send: signal.send, + stopListenAll: signal.stopListenAll, + }; + } + } + + export const create = new Create(); +} diff --git a/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts b/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts index f6f633c7a32..af754eb9225 100644 --- a/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts +++ b/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts @@ -1,4 +1,4 @@ -import {EventEmitter} from 'events'; +import {Signal} from './Signal'; // TODO: remove and use Event Emitters after Adhwaith PR is merged import {Msg} from './GlobalTypes'; import * as Err from './Err'; import {HTTP_METHODS, WebexRequestPayload} from '../../types'; @@ -45,30 +45,27 @@ export class AqmReqs { } evt(p: EvtConf): EvtRes { - const eventEmitter = new EventEmitter(); + const {send, signal} = Signal.create.withData(); const k = this.bindPrint(p.bind); if (this.pendingEvents[k]) { throw new Err.Details('Service.aqm.reqs.PendingEvent', {key: k}); } - this.pendingEvents[k] = { check: (msg: Msg) => this.bindCheck(p.bind, msg), - handle: (msg: any) => eventEmitter.emit('data', msg), + handle: (msg: any) => send(msg), }; // add listenOnceAsync - const evt: EvtRes = eventEmitter as any; + const evt: EvtRes = signal as any; evt.listenOnceAsync = (promise?: {resolveIf?: (msg: T) => boolean; timeout?: Timeout}) => { return new Promise((resolve, reject) => { - const listener = (msg: T) => { + const {stopListen} = signal.listen((msg) => { if (promise?.resolveIf ? promise.resolveIf(msg) : true) { - eventEmitter.removeListener('data', listener); + stopListen(); resolve(msg); } - }; - - eventEmitter.on('data', listener); + }); if (promise?.timeout === 'disabled') { return; @@ -77,8 +74,8 @@ export class AqmReqs { const ms = promise && promise.timeout && promise.timeout > 0 ? promise.timeout : TIMEOUT_EVT; setTimeout(() => { - const removed = eventEmitter.removeListener('data', listener); - if (removed) { + const isStopped = stopListen(); + if (isStopped) { reject(new Err.Details('Service.aqm.reqs.TimeoutEvent', {key: k})); } }, ms); diff --git a/packages/@webex/plugin-cc/src/services/core/types.ts b/packages/@webex/plugin-cc/src/services/core/types.ts index 14ccaa4f514..a23ed1ed362 100644 --- a/packages/@webex/plugin-cc/src/services/core/types.ts +++ b/packages/@webex/plugin-cc/src/services/core/types.ts @@ -1,7 +1,7 @@ -import {EventEmitter} from 'events'; import {HTTP_METHODS, WebexRequestPayload} from '../../types'; import * as Err from './Err'; import {Msg} from './GlobalTypes'; +import {Signal} from './Signal'; export type Pending = { check: (msg: Msg) => boolean; @@ -50,6 +50,6 @@ export type CbRes = (res: any) => void | TRes; // evt export type EvtConf = {bind: Bind; msg: T}; -export type EvtRes = EventEmitter & { +export type EvtRes = Signal.WithData & { listenOnceAsync: (p?: {resolveIf?: (msg: T) => boolean; timeout?: Timeout}) => Promise; }; From 0f2bef8c5734bddcf04ba87ff9b0069675e30828 Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Mon, 11 Nov 2024 15:50:47 +0530 Subject: [PATCH 12/15] fix: added a central method to handle errors --- packages/@webex/plugin-cc/src/cc.ts | 25 ++++++++--- .../@webex/plugin-cc/test/unit/spec/cc.ts | 45 +++++++++++++++---- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/packages/@webex/plugin-cc/src/cc.ts b/packages/@webex/plugin-cc/src/cc.ts index 9914f18be79..62e2f74a987 100644 --- a/packages/@webex/plugin-cc/src/cc.ts +++ b/packages/@webex/plugin-cc/src/cc.ts @@ -20,6 +20,7 @@ import {AGENT, WEB_RTC_PREFIX} from './services/constants'; import Services from './services'; import LoggerProxy from './logger-proxy'; import * as Agent from './services/agent/types'; +import {Failure} from './services/core/GlobalTypes'; export default class ContactCenter extends WebexPlugin implements IContactCenter { namespace = 'cc'; @@ -122,6 +123,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter isExtension: data.loginOption === LoginOption.EXTENSION, deviceId: this.getDeviceId(data.loginOption, data.dialNumber), roles: [AGENT], + // TODO: The public API should not have the following properties so filling them with empty values for now. If needed, we can add them in the future. teamName: '', siteId: '', usesOtherDN: false, @@ -137,8 +139,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter return loginResponse; } catch (error) { - this.$webex.logger.log(`file: ${CC_FILE}: Station Login failed: ${error}`); - throw new Error(error.details?.data?.reason ?? 'Error while performing station login'); + throw this.handleError(error, 'stationLogin'); } } @@ -161,19 +162,21 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter return logoutResponse; } catch (error) { - this.$webex.logger.error(`file: ${CC_FILE}: Station Logout failed: ${error}`); - throw new Error(error.details?.data?.reason ?? 'Error while performing station logout'); + throw this.handleError(error, 'stationLogout'); } } + /* This is used for agent relogin. + * @returns Promise + * @throws Error + */ public async stationReLogin(): Promise { try { const reLoginResponse = await this.services.agent.reload(); return reLoginResponse; } catch (error) { - this.$webex.logger.error(`file: ${CC_FILE}: Station ReLogin failed: ${error}`); - throw new Error(error.details?.data?.reason ?? 'Error while performing station relogin'); + throw this.handleError(error, 'stationReLogin'); } } @@ -184,4 +187,14 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter return WEB_RTC_PREFIX + this.agentConfig.agentId; } + + // New method to handle errors + private handleError(error: any, methodName: string): never { + // TODO: we can enhance this as we need in future + const failure = error.details as Failure; + this.$webex.logger.error( + `file: ${CC_FILE}: ${methodName} failed with trackingId: ${failure?.trackingId}` + ); + throw new Error(failure?.data?.reason ?? `Error while performing ${methodName}`); + } } diff --git a/packages/@webex/plugin-cc/test/unit/spec/cc.ts b/packages/@webex/plugin-cc/test/unit/spec/cc.ts index f8604ad2ff2..9396efb80a9 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/cc.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/cc.ts @@ -221,11 +221,20 @@ describe('webex.cc', () => { dialNumber: '1234567890', }; - const error = new Error('Error while performing station login'); + const error = { + details: { + trackingId: '1234', + data: { + reason: 'Error while performing station login', + }, + }, + }; jest.spyOn(webex.cc.services.agent, 'stationLogin').mockRejectedValue(error); - await expect(webex.cc.stationLogin(options)).rejects.toThrow( - 'Error while performing station login' + await expect(webex.cc.stationLogin(options)).rejects.toThrow(error.details.data.reason); + + expect(webex.logger.error).toHaveBeenCalledWith( + `file: cc: stationLogin failed with trackingId: ${error.details.trackingId}` ); }); }); @@ -247,13 +256,22 @@ describe('webex.cc', () => { it('should handle error during stationLogout', async () => { const data = {logoutReason: 'Logout reason'}; - const error = new Error('Error while performing station logout'); + const error = { + details: { + trackingId: '1234', + data: { + reason: 'Error while performing station logout', + }, + }, + }; jest.spyOn(webex.cc.services.agent, 'logout').mockRejectedValue(error); - await expect(webex.cc.stationLogout(data)).rejects.toThrow(error); + await expect(webex.cc.stationLogout(data)).rejects.toThrow(error.details.data.reason); - expect(webex.logger.error).toHaveBeenCalledWith(`file: cc: Station Logout failed: ${error}`); + expect(webex.logger.error).toHaveBeenCalledWith( + `file: cc: stationLogout failed with trackingId: ${error.details.trackingId}` + ); }); }); @@ -272,13 +290,22 @@ describe('webex.cc', () => { }); it('should handle error during relogin', async () => { - const error = new Error('Error while performing station relogin'); + const error = { + details: { + trackingId: '1234', + data: { + reason: 'Error while performing station relogin', + }, + }, + }; jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error); - await expect(webex.cc.stationReLogin()).rejects.toThrow(error); + await expect(webex.cc.stationReLogin()).rejects.toThrow(error.details.data.reason); - expect(webex.logger.error).toHaveBeenCalledWith(`file: cc: Station ReLogin failed: ${error}`); + expect(webex.logger.error).toHaveBeenCalledWith( + `file: cc: stationReLogin failed with trackingId: ${error.details.trackingId}` + ); }); }); }); From 85ce7be7181624bb2d5b1342367d67b825b1264f Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Mon, 11 Nov 2024 20:07:24 +0530 Subject: [PATCH 13/15] fix: addressed comments to refactor code --- docs/samples/contact-center/app.js | 4 +- docs/samples/contact-center/index.html | 4 +- packages/@webex/plugin-cc/src/cc.ts | 29 ++-- packages/@webex/plugin-cc/src/config.ts | 1 + .../src/{ => services}/WebCallingService.ts | 4 +- .../plugin-cc/src/services/agent/index.ts | 79 +-------- .../plugin-cc/src/services/agent/types.ts | 36 +---- .../@webex/plugin-cc/src/services/core/Err.ts | 6 +- .../plugin-cc/src/services/core/Signal.ts | 153 ------------------ .../plugin-cc/src/services/core/Utils.ts | 14 +- .../plugin-cc/src/services/core/aqm-reqs.ts | 99 ++---------- .../services/core/{config.ts => constants.ts} | 1 + .../src/services/core/service-utils.ts | 48 ------ .../plugin-cc/src/services/core/types.ts | 7 - .../@webex/plugin-cc/src/services/index.ts | 2 +- .../@webex/plugin-cc/test/unit/spec/cc.ts | 17 +- .../spec/{ => services}/WebCallingService.ts | 4 +- .../test/unit/spec/services/agent/index.ts | 3 +- 18 files changed, 65 insertions(+), 446 deletions(-) rename packages/@webex/plugin-cc/src/{ => services}/WebCallingService.ts (95%) delete mode 100644 packages/@webex/plugin-cc/src/services/core/Signal.ts rename packages/@webex/plugin-cc/src/services/core/{config.ts => constants.ts} (88%) delete mode 100644 packages/@webex/plugin-cc/src/services/core/service-utils.ts rename packages/@webex/plugin-cc/test/unit/spec/{ => services}/WebCallingService.ts (97%) diff --git a/docs/samples/contact-center/app.js b/docs/samples/contact-center/app.js index bc88c68364b..b80188142c4 100644 --- a/docs/samples/contact-center/app.js +++ b/docs/samples/contact-center/app.js @@ -99,7 +99,7 @@ function initWebex(e) { console.log('Authentication#initWebex() :: Webex Ready'); authStatusElm.innerText = 'Saved access token!'; - registerStatus.innerHTML = 'Not Registered'; + registerStatus.innerHTML = 'Not Subscribed'; registerBtn.disabled = false; }); @@ -111,7 +111,7 @@ credentialsFormElm.addEventListener('submit', initWebex); function register() { webex.cc.register(true).then((agentProfile) => { - registerStatus.innerHTML = 'Registered'; + registerStatus.innerHTML = 'Subscribed'; console.log('Event subscription successful: ', agentProfile); teamsDropdown.innerHTML = ''; // Clear previously selected option on teamsDropdown const listTeams = agentProfile.teams; diff --git a/docs/samples/contact-center/index.html b/docs/samples/contact-center/index.html index cbada7557d4..ceea4239d00 100644 --- a/docs/samples/contact-center/index.html +++ b/docs/samples/contact-center/index.html @@ -76,7 +76,7 @@

-

Not Registered

+

Not Subscribed

@@ -88,7 +88,7 @@

- Agent Desktop Using Webex CC SDK + Agent Station Login/Logout

diff --git a/packages/@webex/plugin-cc/src/cc.ts b/packages/@webex/plugin-cc/src/cc.ts index 62e2f74a987..bfaaef0fd4f 100644 --- a/packages/@webex/plugin-cc/src/cc.ts +++ b/packages/@webex/plugin-cc/src/cc.ts @@ -4,7 +4,6 @@ import { CCPluginConfig, IContactCenter, WebexSDK, - SubscribeRequest, LoginOption, WelcomeEvent, IAgentProfile, @@ -15,12 +14,12 @@ import { } from './types'; import {READY, CC_FILE} from './constants'; import HttpRequest from './services/core/HttpRequest'; -import WebCallingService from './WebCallingService'; +import WebCallingService from './services/WebCallingService'; import {AGENT, WEB_RTC_PREFIX} from './services/constants'; import Services from './services'; import LoggerProxy from './logger-proxy'; -import * as Agent from './services/agent/types'; -import {Failure} from './services/core/GlobalTypes'; +import {Logout} from './services/agent/types'; +import {getErrorDetails} from './services/core/Utils'; export default class ContactCenter extends WebexPlugin implements IContactCenter { namespace = 'cc'; @@ -66,7 +65,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter } catch (error) { this.$webex.logger.error(`file: ${CC_FILE}: Error during register: ${error}`); - return Promise.reject(new Error('Error while performing register`', error)); + throw error; } } @@ -77,7 +76,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter * @private */ private async connectWebsocket() { - const connectionConfig: SubscribeRequest = { + const connectionConfig = { force: this.$config?.force ?? true, isKeepAliveEnabled: this.$config?.isKeepAliveEnabled ?? false, clientType: this.$config?.clientType ?? 'WebexCCSDK', @@ -139,7 +138,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter return loginResponse; } catch (error) { - throw this.handleError(error, 'stationLogin'); + throw getErrorDetails(error, 'stationLogin'); } } @@ -148,7 +147,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter * @returns Promise * @throws Error */ - public async stationLogout(data: Agent.Logout): Promise { + public async stationLogout(data: Logout): Promise { try { const logoutResponse = this.services.agent.logout({ data, @@ -162,7 +161,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter return logoutResponse; } catch (error) { - throw this.handleError(error, 'stationLogout'); + throw getErrorDetails(error, 'stationLogout'); } } @@ -176,7 +175,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter return reLoginResponse; } catch (error) { - throw this.handleError(error, 'stationReLogin'); + throw getErrorDetails(error, 'stationReLogin'); } } @@ -187,14 +186,4 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter return WEB_RTC_PREFIX + this.agentConfig.agentId; } - - // New method to handle errors - private handleError(error: any, methodName: string): never { - // TODO: we can enhance this as we need in future - const failure = error.details as Failure; - this.$webex.logger.error( - `file: ${CC_FILE}: ${methodName} failed with trackingId: ${failure?.trackingId}` - ); - throw new Error(failure?.data?.reason ?? `Error while performing ${methodName}`); - } } diff --git a/packages/@webex/plugin-cc/src/config.ts b/packages/@webex/plugin-cc/src/config.ts index 8eedc39a1cd..caaba9c18a5 100644 --- a/packages/@webex/plugin-cc/src/config.ts +++ b/packages/@webex/plugin-cc/src/config.ts @@ -16,6 +16,7 @@ export default { }, serviceData: { indicator: 'contactcenter', + // TODO: This should be dynamic based on the environment domain: 'rtw.prod-us1.rtmsprod.net', }, }, diff --git a/packages/@webex/plugin-cc/src/WebCallingService.ts b/packages/@webex/plugin-cc/src/services/WebCallingService.ts similarity index 95% rename from packages/@webex/plugin-cc/src/WebCallingService.ts rename to packages/@webex/plugin-cc/src/services/WebCallingService.ts index c00e1c227b1..27154131ac7 100644 --- a/packages/@webex/plugin-cc/src/WebCallingService.ts +++ b/packages/@webex/plugin-cc/src/services/WebCallingService.ts @@ -6,8 +6,8 @@ import { LINE_EVENTS, CallingClientConfig, } from '@webex/calling'; -import {WebexSDK} from './types'; -import {TIMEOUT_DURATION} from './constants'; +import {WebexSDK} from '../types'; +import {TIMEOUT_DURATION} from '../constants'; export default class WebCallingService { private callingClient: ICallingClient; diff --git a/packages/@webex/plugin-cc/src/services/agent/index.ts b/packages/@webex/plugin-cc/src/services/agent/index.ts index 6add776584b..bdc34bf7fab 100644 --- a/packages/@webex/plugin-cc/src/services/agent/index.ts +++ b/packages/@webex/plugin-cc/src/services/agent/index.ts @@ -1,9 +1,9 @@ import * as Err from '../core/Err'; -import {Failure, Msg} from '../core/GlobalTypes'; -import {createErrDetailsObject as err, getRoutingHost} from '../core/Utils'; +import {createErrDetailsObject as err} from '../core/Utils'; import * as Agent from './types'; -import {AqmReqs} from '../core/aqm-reqs'; +import AqmReqs from '../core/aqm-reqs'; import {HTTP_METHODS} from '../../types'; +import {WCC_API_GATEWAY} from '../constants'; /* * routingAgent @@ -14,7 +14,7 @@ import {HTTP_METHODS} from '../../types'; export default function routingAgent(routing: AqmReqs) { return { reload: routing.reqEmpty(() => ({ - host: getRoutingHost(), + host: WCC_API_GATEWAY, url: '/v1/agents/reload', data: {}, err, @@ -35,7 +35,7 @@ export default function routingAgent(routing: AqmReqs) { })), logout: routing.req((p: {data: Agent.Logout}) => ({ url: '/v1/agents/logout', - host: getRoutingHost(), + host: WCC_API_GATEWAY, data: p.data, err, notifSuccess: { @@ -55,7 +55,7 @@ export default function routingAgent(routing: AqmReqs) { })), stationLogin: routing.req((p: {data: Agent.UserStationLogin}) => ({ url: '/v1/agents/login', - host: getRoutingHost(), + host: WCC_API_GATEWAY, data: p.data, err: /* istanbul ignore next */ (e: any) => new Err.Details('Service.aqm.agent.stationLogin', { @@ -80,7 +80,7 @@ export default function routingAgent(routing: AqmReqs) { })), stateChange: routing.req((p: {data: Agent.StateChange}) => ({ url: '/v1/agents/session/state', - host: getRoutingHost(), + host: WCC_API_GATEWAY, data: {...p.data, auxCodeId: p.data.auxCodeIdArray}, err, method: HTTP_METHODS.PUT, @@ -99,70 +99,5 @@ export default function routingAgent(routing: AqmReqs) { errId: 'Service.aqm.agent.stateChange', }, })), - eMockOutdialAniList: routing.evt({ - bind: { - type: 'mockOutdialAniList', - }, - msg: {} as Agent.OutdialAniListSuccess, - }), - - eAgentDNRegistered: routing.evt({ - bind: { - type: 'RoutingMessage', - data: {type: 'AgentDNRegistered'}, - }, - msg: {} as Agent.DNRegistered, - }), - - eAgentDNRegisterFailure: routing.evt({ - bind: { - type: 'RoutingMessage', - data: {type: 'AgentDNRegisterFailure'}, - }, - msg: {} as Failure, - }), - - eAgentMultiLogin: routing.evt({ - bind: { - type: 'AGENT_MULTI_LOGIN', - data: {type: 'AgentMultiLoginCloseSession'}, - }, - msg: {} as Msg<{ - agentId: string; - reason: string; - type: 'AgentMultiLoginCloseSession'; - agentSessionId: string; - }>, - }), - - // jsapi required events - eAgentReloginSuccess: routing.evt({ - bind: { - type: 'AgentReloginSuccess', - data: {type: 'AgentReloginSuccess'}, - }, - msg: {} as Agent.ReloginSuccess, - }), - eAgentStationLoginSuccess: routing.evt({ - bind: { - type: 'StationLogin', - data: {type: 'AgentStationLoginSuccess'}, - }, - msg: {} as Agent.StationLoginSuccess, - }), - eAgentStateChangeSuccess: routing.evt({ - bind: { - type: 'AgentStateChange', - data: {type: 'AgentStateChangeSuccess'}, - }, - msg: {} as Agent.StateChangeSuccess, - }), - eAgentLogoutSuccess: routing.evt({ - bind: { - type: 'Logout', - data: {type: 'AgentLogoutSuccess'}, - }, - msg: {} as Agent.LogoutSuccess, - }), }; } diff --git a/packages/@webex/plugin-cc/src/services/agent/types.ts b/packages/@webex/plugin-cc/src/services/agent/types.ts index a626743decf..f8941456559 100644 --- a/packages/@webex/plugin-cc/src/services/agent/types.ts +++ b/packages/@webex/plugin-cc/src/services/agent/types.ts @@ -89,22 +89,6 @@ export type DNRegistered = Msg<{ type: 'AgentDNRegistered'; }>; -export type OutdialAniListSuccess = Msg<{ - data: Record; -}>; - -export type OutdialAni = { - id: string; - name: string; -}; - -export type OutDialAniData = { - initialFetchCompleted: boolean; - data: OutdialAni[]; -}; - -// PAYLOAD - export type Logout = {logoutReason?: 'User requested logout' | 'Inactivity Logout'}; export type StateChange = { @@ -132,24 +116,6 @@ export type UserStationLogin = { isEmergencyModalAlreadyDisplayed?: boolean; }; -export type AddressBooks = { - totalRecords?: number; - totalPages?: number; - page?: number; - speedDials: Address[]; -}; - -export type Address = { - desc: string; - dn: string; - phoneBookName?: string; -}; - -export type AddressBooksData = { - initialFetchCompleted: boolean; - data: Address[]; - errorObj: any; -}; export type LoginOption = 'AGENT_DN' | 'EXTENSION' | 'BROWSER'; -export type DeviceType = null | LoginOption | string; // cleanup this while removing FF: wxcc_webrtc. +export type DeviceType = LoginOption | string; diff --git a/packages/@webex/plugin-cc/src/services/core/Err.ts b/packages/@webex/plugin-cc/src/services/core/Err.ts index f38a400c4c2..f194e4ad62f 100644 --- a/packages/@webex/plugin-cc/src/services/core/Err.ts +++ b/packages/@webex/plugin-cc/src/services/core/Err.ts @@ -9,15 +9,13 @@ export type AgentErrorIds = | {'Service.aqm.agent.stateChange': Failure} | {'Service.aqm.agent.reload': Failure} | {'Service.aqm.agent.logout': Failure} - | {'Service.aqm.agent.mockOutdialAniList': Failure} - | {'Service.reqs.generic.failure': {trackingId: string}} - | 'Service.aqm.agent.fetchAddressBooks'; + | {'Service.reqs.generic.failure': {trackingId: string}}; export type ReqError = | 'Service.aqm.reqs.GenericRequestError' | {'Service.aqm.reqs.Pending': {key: string; msg: string}} | {'Service.aqm.reqs.PendingEvent': {key: string}} - | {'Service.aqm.reqs.Timeout': {key: string; resAxios: WebexRequestPayload}} + | {'Service.aqm.reqs.Timeout': {key: string; response: WebexRequestPayload}} | {'Service.aqm.reqs.TimeoutEvent': {key: string}}; export interface Ids { diff --git a/packages/@webex/plugin-cc/src/services/core/Signal.ts b/packages/@webex/plugin-cc/src/services/core/Signal.ts deleted file mode 100644 index 76bfe96d046..00000000000 --- a/packages/@webex/plugin-cc/src/services/core/Signal.ts +++ /dev/null @@ -1,153 +0,0 @@ -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace Signal { - class WithDataClass implements WithData { - private listeners: Listener[] = []; - private listenersOnce: Listener[] = []; - - listen = (listener: Listener) => { - this.listeners.push(listener); - - return {stopListen: () => this.stopListen(listener)}; - }; - - listenOnce = (listener: Listener) => { - this.listenersOnce.push(listener); - - return {stopListenOnce: () => this.stopListenOnce(listener)}; - }; - - stopListen = (listener: Listener) => { - const index = this.listeners.indexOf(listener, 0); - if (index > -1) { - this.listeners.splice(index, 1); - - return true; - } - - return false; - }; - - stopListenOnce = (listener: Listener) => { - const index = this.listenersOnce.indexOf(listener, 0); - if (index > -1) { - this.listenersOnce.splice(index, 1); - - return true; - } - - return false; - }; - - // concealed - stopListenAll = () => { - this.listeners = []; - this.listenersOnce = []; - }; - - // concealed - send = (data: T) => { - this.listeners.forEach((listener) => listener(data)); - this.listenersOnce.forEach((listener) => listener(data)); - this.listenersOnce = []; - }; - } - - class EmptyClass implements Empty { - private listeners: ListenerEmpty[] = []; - private listenersOnce: ListenerEmpty[] = []; - - listen = (listener: ListenerEmpty) => { - this.listeners.push(listener); - - return {stopListen: () => this.stopListen(listener)}; - }; - - listenOnce = (listener: ListenerEmpty) => { - this.listenersOnce.push(listener); - - return {stopListenOnce: () => this.stopListenOnce(listener)}; - }; - - stopListen = (listener: ListenerEmpty) => { - const index = this.listeners.indexOf(listener, 0); - if (index > -1) { - this.listeners.splice(index, 1); - - return true; - } - - return false; - }; - - stopListenOnce = (listener: ListenerEmpty) => { - const index = this.listenersOnce.indexOf(listener, 0); - if (index > -1) { - this.listenersOnce.splice(index, 1); - - return true; - } - - return false; - }; - - // concealed - stopListenAll = () => { - this.listeners = []; - this.listenersOnce = []; - }; - - // concealed - send = () => { - this.listeners.forEach((listener) => listener()); - this.listenersOnce.forEach((listener) => listener()); - this.listenersOnce = []; - }; - } - - type Listener = (data: T) => void; - type ListenerEmpty = () => void; - - export type Send = (data: T) => void; - export type SendEmpty = () => void; - - export interface WithData { - listen: (listener: Listener) => {stopListen: () => boolean}; - listenOnce: (listener: Listener) => {stopListenOnce: () => boolean}; - stopListen: (listener: Listener) => boolean; - stopListenOnce: (listener: Listener) => boolean; - } - - export interface Empty { - listen: (listener: ListenerEmpty) => {stopListen: () => boolean}; - listenOnce: (listener: ListenerEmpty) => {stopListenOnce: () => boolean}; - stopListen: (listener: ListenerEmpty) => boolean; - stopListenOnce: (listener: ListenerEmpty) => boolean; - } - - type Return = {signal: WithData; send: (data: T) => void; stopListenAll: () => void}; - type ReturnEmpty = {signal: Empty; send: () => void; stopListenAll: () => void}; - - class Create { - withData(): Return { - const signal = new WithDataClass(); - - return { - signal, - send: signal.send, - stopListenAll: signal.stopListenAll, - }; - } - - empty(): ReturnEmpty { - const signal = new EmptyClass(); - - return { - signal, - send: signal.send, - stopListenAll: signal.stopListenAll, - }; - } - } - - export const create = new Create(); -} diff --git a/packages/@webex/plugin-cc/src/services/core/Utils.ts b/packages/@webex/plugin-cc/src/services/core/Utils.ts index 8f5a05b722e..3228b88df98 100644 --- a/packages/@webex/plugin-cc/src/services/core/Utils.ts +++ b/packages/@webex/plugin-cc/src/services/core/Utils.ts @@ -1,6 +1,7 @@ import * as Err from './Err'; import {WebexRequestPayload} from '../../types'; -import {WCC_API_GATEWAY} from '../constants'; +import {Failure} from './GlobalTypes'; +import LoggerProxy from '../../logger-proxy'; const getCommonErrorDetails = (errObj: WebexRequestPayload) => { return { @@ -9,12 +10,15 @@ const getCommonErrorDetails = (errObj: WebexRequestPayload) => { }; }; +export const getErrorDetails = (error: any, methodName: string) => { + const failure = error.details as Failure; + LoggerProxy.logger.error(`${methodName} failed with trackingId: ${failure?.trackingId}`); + + return new Error(failure?.data?.reason ?? `Error while performing ${methodName}`); +}; + export const createErrDetailsObject = (errObj: WebexRequestPayload) => { const details = getCommonErrorDetails(errObj); return new Err.Details('Service.reqs.generic.failure', details); }; - -export const getRoutingHost = () => { - return `${WCC_API_GATEWAY}`; -}; diff --git a/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts b/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts index af754eb9225..057f041ac80 100644 --- a/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts +++ b/packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts @@ -1,31 +1,13 @@ -import {Signal} from './Signal'; // TODO: remove and use Event Emitters after Adhwaith PR is merged import {Msg} from './GlobalTypes'; import * as Err from './Err'; import {HTTP_METHODS, WebexRequestPayload} from '../../types'; import HttpRequest from './HttpRequest'; import LoggerProxy from '../../logger-proxy'; -import { - CbRes, - Conf, - ConfEmpty, - EvtConf, - EvtRes, - Pending, - Req, - Res, - ResEmpty, - Timeout, -} from './types'; +import {CbRes, Conf, ConfEmpty, Pending, Req, Res, ResEmpty} from './types'; +import {TIMEOUT_REQ} from './constants'; -export const TIMEOUT_REQ = 20000; -const TIMEOUT_EVT = TIMEOUT_REQ; - -export class AqmReqs { +export default class AqmReqs { private pendingRequests: Record = {}; - private pendingEvents: Record = {}; - // pendingNotifCancelrequest is used for handling request cancel events based on another action. When a promise can be resolved using multiple events. - // example consult and cancelCtq(end in new API) when we cancel consult to ques we need to resolve both consul and cancel ctq promises - // #CX-14258 : Consult to DN failing if Consult to Queue is cancelled in the first try #2344 private pendingNotifCancelrequest: Record = {}; private httpRequest: HttpRequest; constructor() { @@ -44,47 +26,6 @@ export class AqmReqs { return (cbRes?: CbRes) => this.makeAPIRequest(c(), cbRes); } - evt(p: EvtConf): EvtRes { - const {send, signal} = Signal.create.withData(); - - const k = this.bindPrint(p.bind); - if (this.pendingEvents[k]) { - throw new Err.Details('Service.aqm.reqs.PendingEvent', {key: k}); - } - this.pendingEvents[k] = { - check: (msg: Msg) => this.bindCheck(p.bind, msg), - handle: (msg: any) => send(msg), - }; - - // add listenOnceAsync - const evt: EvtRes = signal as any; - evt.listenOnceAsync = (promise?: {resolveIf?: (msg: T) => boolean; timeout?: Timeout}) => { - return new Promise((resolve, reject) => { - const {stopListen} = signal.listen((msg) => { - if (promise?.resolveIf ? promise.resolveIf(msg) : true) { - stopListen(); - resolve(msg); - } - }); - - if (promise?.timeout === 'disabled') { - return; - } - - const ms = - promise && promise.timeout && promise.timeout > 0 ? promise.timeout : TIMEOUT_EVT; - setTimeout(() => { - const isStopped = stopListen(); - if (isStopped) { - reject(new Err.Details('Service.aqm.reqs.TimeoutEvent', {key: k})); - } - }, ms); - }); - }; - - return evt; - } - private async makeAPIRequest(c: Req, cbRes?: CbRes): Promise { return this.createPromise(c, cbRes); } @@ -162,7 +103,7 @@ export class AqmReqs { }, }; } - let resAxios: WebexRequestPayload | null = null; + let response: WebexRequestPayload | null = null; this.httpRequest .request({ service: c.host ?? '', @@ -173,21 +114,21 @@ export class AqmReqs { body: c.data, }) .then((res: any) => { - resAxios = res; + response = res; if (cbRes) { cbRes(res); } }) - .catch((axiosErr: WebexRequestPayload) => { + .catch((error: WebexRequestPayload) => { clear(); - if (axiosErr?.headers) { - axiosErr.headers.Authorization = '*'; + if (error?.headers) { + error.headers.Authorization = '*'; } - if (axiosErr?.headers) { - axiosErr.headers.Authorization = '*'; + if (error?.headers) { + error.headers.Authorization = '*'; } if (typeof c.err === 'function') { - reject(c.err(axiosErr)); + reject(c.err(error)); } else if (typeof c.err === 'string') { reject(new Err.Message(c.err)); } else { @@ -202,14 +143,14 @@ export class AqmReqs { return; } clear(); - if (resAxios?.headers) { - resAxios.headers.Authorization = '*'; + if (response?.headers) { + response.headers.Authorization = '*'; } - LoggerProxy.logger.error(`Routing request timeout${keySuccess}${resAxios!}${c.url}`); + LoggerProxy.logger.error(`Routing request timeout${keySuccess}${response!}${c.url}`); reject( new Err.Details('Service.aqm.reqs.Timeout', { key: keySuccess, - resAxios: resAxios!, + response: response!, }) ); }, @@ -295,15 +236,7 @@ export class AqmReqs { } } - const kEvt = Object.keys(this.pendingEvents); - for (const thisEvt of kEvt) { - const evt = this.pendingEvents[thisEvt]; - if (evt.check(event)) { - evt.handle(event); - isHandled = true; - break; - } - } + // TODO: add event emitter for unhandled events to replicate event.listen or .on if (!isHandled) { LoggerProxy.logger.info( diff --git a/packages/@webex/plugin-cc/src/services/core/config.ts b/packages/@webex/plugin-cc/src/services/core/constants.ts similarity index 88% rename from packages/@webex/plugin-cc/src/services/core/config.ts rename to packages/@webex/plugin-cc/src/services/core/constants.ts index 28e6bb2ad93..59dd0c0a434 100644 --- a/packages/@webex/plugin-cc/src/services/core/config.ts +++ b/packages/@webex/plugin-cc/src/services/core/constants.ts @@ -4,3 +4,4 @@ export const CLOSE_SOCKET_TIMEOUT_DURATION = 16000; export const PING_API_URL = '/health'; export const WELCOME_TIMEOUT = 30000; export const RTD_PING_EVENT = 'rtd-online-status'; +export const TIMEOUT_REQ = 20000; diff --git a/packages/@webex/plugin-cc/src/services/core/service-utils.ts b/packages/@webex/plugin-cc/src/services/core/service-utils.ts deleted file mode 100644 index fcc3897f638..00000000000 --- a/packages/@webex/plugin-cc/src/services/core/service-utils.ts +++ /dev/null @@ -1,48 +0,0 @@ -import * as Err from './Err'; -import {WebexRequestPayload} from '../../types'; - -const getCommonErrorDetails = (errObj: WebexRequestPayload) => { - return { - trackingId: errObj?.headers?.trackingid || errObj?.headers?.TrackingID, - msg: errObj?.body, - }; -}; - -export const createErrDetailsObject = (errObj: WebexRequestPayload) => { - const details = getCommonErrorDetails(errObj); - - return new Err.Details('Service.reqs.generic.failure', details); -}; - -export const handleExternalServiceErrorDetails = (errObj: WebexRequestPayload) => { - const details: {trackingId: string; status?: number} = getCommonErrorDetails(errObj); - - return new Err.Details('Service.reqs.externalService.generic.failure', { - ...details, - status: errObj?.statusCode ?? '', - }); -}; - -export const getCanaryFlagFromSessionStorage = (): boolean => { - const flag = sessionStorage.getItem('canary'); - - return flag === 'true'; -}; - -export const generateUUID = (): string => { - // let d = DateTime.utc().toMillis(); - let d = Date.now(); - - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { - const r = (d + Math.random() * 16) % 16 | 0; // eslint-disable-line no-bitwise - d = Math.floor(d / 16); - - return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); // eslint-disable-line no-bitwise - }); -}; - -export const RETRY_INTERVAL = 200; -export const sleep = (interval: number) => - new Promise((resolve) => { - setTimeout(() => resolve(true), interval); - }); diff --git a/packages/@webex/plugin-cc/src/services/core/types.ts b/packages/@webex/plugin-cc/src/services/core/types.ts index a23ed1ed362..81cfe7ac6e9 100644 --- a/packages/@webex/plugin-cc/src/services/core/types.ts +++ b/packages/@webex/plugin-cc/src/services/core/types.ts @@ -1,7 +1,6 @@ import {HTTP_METHODS, WebexRequestPayload} from '../../types'; import * as Err from './Err'; import {Msg} from './GlobalTypes'; -import {Signal} from './Signal'; export type Pending = { check: (msg: Msg) => boolean; @@ -47,9 +46,3 @@ export type ConfEmpty = () => Req; export type Res = (p: TReq, cbRes?: CbRes) => Promise; export type ResEmpty = (cbRes?: CbRes) => Promise; export type CbRes = (res: any) => void | TRes; - -// evt -export type EvtConf = {bind: Bind; msg: T}; -export type EvtRes = Signal.WithData & { - listenOnceAsync: (p?: {resolveIf?: (msg: T) => boolean; timeout?: Timeout}) => Promise; -}; diff --git a/packages/@webex/plugin-cc/src/services/index.ts b/packages/@webex/plugin-cc/src/services/index.ts index e1631d468e3..6462c0d221a 100644 --- a/packages/@webex/plugin-cc/src/services/index.ts +++ b/packages/@webex/plugin-cc/src/services/index.ts @@ -1,5 +1,5 @@ import routingAgent from './agent'; -import {AqmReqs} from './core/aqm-reqs'; +import AqmReqs from './core/aqm-reqs'; export default class Services { public readonly agent: ReturnType; diff --git a/packages/@webex/plugin-cc/test/unit/spec/cc.ts b/packages/@webex/plugin-cc/test/unit/spec/cc.ts index 9396efb80a9..ca74591e796 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/cc.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/cc.ts @@ -7,6 +7,7 @@ import {IAgentProfile} from '../../../src/types'; import {AGENT, WEB_RTC_PREFIX} from '../../../src/services/constants'; import Services from '../../../src/services'; import config from '../../../src/config'; +import LoggerProxy from '../../../src/logger-proxy'; jest.mock('../../../src/logger-proxy', () => ({ __esModule: true, @@ -21,7 +22,7 @@ jest.mock('../../../src/logger-proxy', () => ({ jest.mock('../../../src/services/config'); jest.mock('../../../src/services/core/HttpRequest'); -jest.mock('../../../src/WebCallingService'); +jest.mock('../../../src/services/WebCallingService'); jest.mock('../../../src/services'); // Mock AgentConfig @@ -133,7 +134,7 @@ describe('webex.cc', () => { }); it('should log error and reject if registration fails', async () => { - const mockError = new Error('Registration failed'); + const mockError = new Error('Error while performing register'); mockHttpRequest.subscribeNotifications.mockRejectedValue(mockError); await expect(webex.cc.register()).rejects.toThrow('Error while performing register'); @@ -233,8 +234,8 @@ describe('webex.cc', () => { await expect(webex.cc.stationLogin(options)).rejects.toThrow(error.details.data.reason); - expect(webex.logger.error).toHaveBeenCalledWith( - `file: cc: stationLogin failed with trackingId: ${error.details.trackingId}` + expect(LoggerProxy.logger.error).toHaveBeenCalledWith( + `stationLogin failed with trackingId: ${error.details.trackingId}` ); }); }); @@ -269,8 +270,8 @@ describe('webex.cc', () => { await expect(webex.cc.stationLogout(data)).rejects.toThrow(error.details.data.reason); - expect(webex.logger.error).toHaveBeenCalledWith( - `file: cc: stationLogout failed with trackingId: ${error.details.trackingId}` + expect(LoggerProxy.logger.error).toHaveBeenCalledWith( + `stationLogout failed with trackingId: ${error.details.trackingId}` ); }); }); @@ -303,8 +304,8 @@ describe('webex.cc', () => { await expect(webex.cc.stationReLogin()).rejects.toThrow(error.details.data.reason); - expect(webex.logger.error).toHaveBeenCalledWith( - `file: cc: stationReLogin failed with trackingId: ${error.details.trackingId}` + expect(LoggerProxy.logger.error).toHaveBeenCalledWith( + `stationReLogin failed with trackingId: ${error.details.trackingId}` ); }); }); diff --git a/packages/@webex/plugin-cc/test/unit/spec/WebCallingService.ts b/packages/@webex/plugin-cc/test/unit/spec/services/WebCallingService.ts similarity index 97% rename from packages/@webex/plugin-cc/test/unit/spec/WebCallingService.ts rename to packages/@webex/plugin-cc/test/unit/spec/services/WebCallingService.ts index cf47ada2f52..0fe0bc476ed 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/WebCallingService.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/services/WebCallingService.ts @@ -1,7 +1,7 @@ import 'jsdom-global/register'; -import WebCallingService from '../../../src/WebCallingService'; +import WebCallingService from '../../../../src/services/WebCallingService'; import {createClient, ICallingClient, ILine, LINE_EVENTS, ICall} from '@webex/calling'; -import {WebexSDK} from '../../../src/types'; +import {WebexSDK} from '../../../../src/types'; jest.mock('@webex/calling'); diff --git a/packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts b/packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts index a0459448b8c..06d09bbbcde 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts @@ -29,7 +29,6 @@ describe('AQM routing agent', () => { fakeAqm = new AqmReqs() as jest.Mocked; fakeAqm.reqEmpty = jest.fn().mockImplementation((fn) => fn); fakeAqm.req = jest.fn().mockImplementation((fn) => fn); - fakeAqm.evt = jest.fn().mockImplementation((fn) => fn); agent = routingAgent(fakeAqm); }); @@ -57,7 +56,7 @@ describe('AQM routing agent', () => { }); it('stateChange', async () => { - const reqSpy = jest.spyOn(fakeAqm, 'evt'); + const reqSpy = jest.spyOn(fakeAqm, 'req'); const req = await agent.stateChange({data: {} as any}); expect(req).toBeDefined(); expect(reqSpy).toHaveBeenCalled(); From bc4b9304e8f9e0c045a22c72c2bd2967351cbd1b Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Mon, 11 Nov 2024 20:13:31 +0530 Subject: [PATCH 14/15] fix: fixed test cases --- .../plugin-cc/test/unit/spec/services/agent/index.ts | 10 ++-------- .../plugin-cc/test/unit/spec/services/core/aqm-reqs.ts | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts b/packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts index 06d09bbbcde..522f0c04276 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts @@ -1,5 +1,5 @@ import routingAgent from '../../../../../src/services/agent'; -import {AqmReqs} from '../../../../../src/services/core/aqm-reqs'; +import AqmReqs from '../../../../../src/services/core/aqm-reqs'; import * as Utils from '../../../../../src/services/core/Utils'; jest.mock('../../../../../src/services/core/Utils', () => ({ @@ -7,13 +7,7 @@ jest.mock('../../../../../src/services/core/Utils', () => ({ getRoutingHost: jest.fn(), })); -jest.mock('../../../../../src/services/core/aqm-reqs', () => ({ - AqmReqs: jest.fn().mockImplementation(() => ({ - reqEmpty: jest.fn().mockImplementation((fn) => fn), - req: jest.fn().mockImplementation((fn) => fn), - evt: jest.fn().mockImplementation((fn) => fn), - })), -})); +jest.mock('../../../../../src/services/core/aqm-reqs'); describe('AQM routing agent', () => { let fakeAqm: jest.Mocked; diff --git a/packages/@webex/plugin-cc/test/unit/spec/services/core/aqm-reqs.ts b/packages/@webex/plugin-cc/test/unit/spec/services/core/aqm-reqs.ts index 1e4517fe205..321b46cc09c 100644 --- a/packages/@webex/plugin-cc/test/unit/spec/services/core/aqm-reqs.ts +++ b/packages/@webex/plugin-cc/test/unit/spec/services/core/aqm-reqs.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import {AqmReqs} from '../../../../../src/services/core/aqm-reqs'; +import AqmReqs from '../../../../../src/services/core/aqm-reqs'; import HttpRequest from '../../../../../src/services/core/HttpRequest'; import LoggerProxy from '../../../../../src/logger-proxy'; From 010d4d9276b5889d1476c1d38f783cafcaf88302 Mon Sep 17 00:00:00 2001 From: Ravi Chandra Sekhar Sarika Date: Mon, 11 Nov 2024 23:26:17 +0530 Subject: [PATCH 15/15] fix: addressed comments to use enums --- packages/@webex/plugin-cc/src/cc.ts | 8 ++--- packages/@webex/plugin-cc/src/constants.ts | 1 + .../plugin-cc/src/services/agent/index.ts | 33 ++++++++++--------- .../plugin-cc/src/services/config/types.ts | 23 ++++++++++--- .../src/services/core/HttpRequest.ts | 3 +- .../plugin-cc/src/services/core/types.ts | 4 +-- packages/@webex/plugin-cc/src/types.ts | 5 +++ 7 files changed, 50 insertions(+), 27 deletions(-) diff --git a/packages/@webex/plugin-cc/src/cc.ts b/packages/@webex/plugin-cc/src/cc.ts index bfaaef0fd4f..4db465d7baa 100644 --- a/packages/@webex/plugin-cc/src/cc.ts +++ b/packages/@webex/plugin-cc/src/cc.ts @@ -12,7 +12,7 @@ import { StationLogoutResponse, StationReLoginResponse, } from './types'; -import {READY, CC_FILE} from './constants'; +import {READY, CC_FILE, EMPTY_STRING} from './constants'; import HttpRequest from './services/core/HttpRequest'; import WebCallingService from './services/WebCallingService'; import {AGENT, WEB_RTC_PREFIX} from './services/constants'; @@ -123,10 +123,10 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter deviceId: this.getDeviceId(data.loginOption, data.dialNumber), roles: [AGENT], // TODO: The public API should not have the following properties so filling them with empty values for now. If needed, we can add them in the future. - teamName: '', - siteId: '', + teamName: EMPTY_STRING, + siteId: EMPTY_STRING, usesOtherDN: false, - auxCodeId: '', + auxCodeId: EMPTY_STRING, }, }); diff --git a/packages/@webex/plugin-cc/src/constants.ts b/packages/@webex/plugin-cc/src/constants.ts index 8e1eee65da0..9eb57f14eac 100644 --- a/packages/@webex/plugin-cc/src/constants.ts +++ b/packages/@webex/plugin-cc/src/constants.ts @@ -2,3 +2,4 @@ export const EVENT = 'event'; export const READY = 'ready'; export const CC_FILE = 'cc'; export const TIMEOUT_DURATION = 20000; // 20 seconds timeout duration for webrtc registration +export const EMPTY_STRING = ''; diff --git a/packages/@webex/plugin-cc/src/services/agent/index.ts b/packages/@webex/plugin-cc/src/services/agent/index.ts index bdc34bf7fab..b62cb249201 100644 --- a/packages/@webex/plugin-cc/src/services/agent/index.ts +++ b/packages/@webex/plugin-cc/src/services/agent/index.ts @@ -4,6 +4,7 @@ import * as Agent from './types'; import AqmReqs from '../core/aqm-reqs'; import {HTTP_METHODS} from '../../types'; import {WCC_API_GATEWAY} from '../constants'; +import {CC_EVENTS} from '../config/types'; /* * routingAgent @@ -20,15 +21,15 @@ export default function routingAgent(routing: AqmReqs) { err, notifSuccess: { bind: { - type: 'AgentReloginSuccess', - data: {type: 'AgentReloginSuccess'}, + type: CC_EVENTS.AGENT_RELOGIN_SUCCESS, + data: {type: CC_EVENTS.AGENT_RELOGIN_SUCCESS}, }, msg: {} as Agent.ReloginSuccess, }, notifFail: { bind: { - type: 'AgentReloginFailed', - data: {type: 'AgentReloginFailed'}, + type: CC_EVENTS.AGENT_RELOGIN_FAILED, + data: {type: CC_EVENTS.AGENT_RELOGIN_FAILED}, }, errId: 'Service.aqm.agent.reload', }, @@ -40,15 +41,15 @@ export default function routingAgent(routing: AqmReqs) { err, notifSuccess: { bind: { - type: 'Logout', - data: {type: 'AgentLogoutSuccess'}, + type: CC_EVENTS.AGENT_LOGOUT, + data: {type: CC_EVENTS.AGENT_LOGOUT_SUCCESS}, }, msg: {} as Agent.LogoutSuccess, }, notifFail: { bind: { - type: 'Logout', - data: {type: 'AgentLogoutFailed'}, + type: CC_EVENTS.AGENT_LOGOUT, + data: {type: CC_EVENTS.AGENT_LOGOUT_FAILED}, }, errId: 'Service.aqm.agent.logout', }, @@ -65,15 +66,15 @@ export default function routingAgent(routing: AqmReqs) { }), notifSuccess: { bind: { - type: 'StationLogin', - data: {type: 'AgentStationLoginSuccess'}, + type: CC_EVENTS.AGENT_STATION_LOGIN, + data: {type: CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS}, }, msg: {} as Agent.StationLoginSuccess, }, notifFail: { bind: { - type: 'StationLogin', - data: {type: 'AgentStationLoginFailed'}, + type: CC_EVENTS.AGENT_STATION_LOGIN, + data: {type: CC_EVENTS.AGENT_STATION_LOGIN_FAILED}, }, errId: 'Service.aqm.agent.stationLoginFailed', }, @@ -86,15 +87,15 @@ export default function routingAgent(routing: AqmReqs) { method: HTTP_METHODS.PUT, notifSuccess: { bind: { - type: 'AgentStateChange', - data: {type: 'AgentStateChangeSuccess'}, + type: CC_EVENTS.AGENT_STATE_CHANGE, + data: {type: CC_EVENTS.AGENT_STATE_CHANGE_SUCCESS}, }, msg: {} as Agent.StateChangeSuccess, }, notifFail: { bind: { - type: 'AgentStateChange', - data: {type: 'AgentStateChangeFailed'}, + type: CC_EVENTS.AGENT_STATE_CHANGE, + data: {type: CC_EVENTS.AGENT_STATE_CHANGE_FAILED}, }, errId: 'Service.aqm.agent.stateChange', }, diff --git a/packages/@webex/plugin-cc/src/services/config/types.ts b/packages/@webex/plugin-cc/src/services/config/types.ts index f8b05f96d81..0f84e22fee9 100644 --- a/packages/@webex/plugin-cc/src/services/config/types.ts +++ b/packages/@webex/plugin-cc/src/services/config/types.ts @@ -1,10 +1,22 @@ -import {AuxCode} from '../../types'; +import {AuxCode, WelcomeEvent} from '../../types'; +import * as Agent from '../agent/types'; type Enum> = T[keyof T]; // Define the CC_EVENTS object export const CC_EVENTS = { WELCOME: 'Welcome', + AGENT_RELOGIN_SUCCESS: 'AgentReloginSuccess', + AGENT_RELOGIN_FAILED: 'AgentReloginFailed', + AGENT_LOGOUT: 'Logout', + AGENT_LOGOUT_SUCCESS: 'AgentLogoutSuccess', + AGENT_LOGOUT_FAILED: 'AgentLogoutFailed', + AGENT_STATION_LOGIN: 'StationLogin', + AGENT_STATION_LOGIN_SUCCESS: 'AgentStationLoginSuccess', + AGENT_STATION_LOGIN_FAILED: 'AgentStationLoginFailed', + AGENT_STATE_CHANGE: 'AgentStateChange', + AGENT_STATE_CHANGE_SUCCESS: 'AgentStateChangeSuccess', + AGENT_STATE_CHANGE_FAILED: 'AgentStateChangeFailed', } as const; // Derive the type using the utility type @@ -12,9 +24,12 @@ export type CC_EVENTS = Enum; export type WebSocketEvent = { type: CC_EVENTS; - data: { - agentId: string; - }; + data: + | WelcomeEvent + | Agent.StationLoginSuccess + | Agent.LogoutSuccess + | Agent.ReloginSuccess + | Agent.StateChangeSuccess; }; /** diff --git a/packages/@webex/plugin-cc/src/services/core/HttpRequest.ts b/packages/@webex/plugin-cc/src/services/core/HttpRequest.ts index 9cf375f4837..b11beed127e 100644 --- a/packages/@webex/plugin-cc/src/services/core/HttpRequest.ts +++ b/packages/@webex/plugin-cc/src/services/core/HttpRequest.ts @@ -6,6 +6,7 @@ import { IHttpResponse, WelcomeResponse, WelcomeEvent, + RequestBody, } from '../../types'; import IWebSocket from '../WebSocket/types'; import WebSocket from '../WebSocket'; @@ -89,7 +90,7 @@ class HttpRequest { service: string; resource: string; method: HTTP_METHODS; - body?: object; + body?: RequestBody; }): Promise { const {service, resource, method, body} = options; diff --git a/packages/@webex/plugin-cc/src/services/core/types.ts b/packages/@webex/plugin-cc/src/services/core/types.ts index 81cfe7ac6e9..59b83a91992 100644 --- a/packages/@webex/plugin-cc/src/services/core/types.ts +++ b/packages/@webex/plugin-cc/src/services/core/types.ts @@ -1,4 +1,4 @@ -import {HTTP_METHODS, WebexRequestPayload} from '../../types'; +import {HTTP_METHODS, RequestBody, WebexRequestPayload} from '../../types'; import * as Err from './Err'; import {Msg} from './GlobalTypes'; @@ -35,7 +35,7 @@ export type Req = { bind: Bind; errId: Err.IdsDetails; }; - data?: any; + data?: RequestBody; headers?: Record; timeout?: Timeout; notifCancel?: {bind: Bind; msg: TRes}; diff --git a/packages/@webex/plugin-cc/src/types.ts b/packages/@webex/plugin-cc/src/types.ts index 2d3ebf1d4cd..36e2ac9c859 100644 --- a/packages/@webex/plugin-cc/src/types.ts +++ b/packages/@webex/plugin-cc/src/types.ts @@ -290,6 +290,11 @@ export type AgentLogin = { loginOption: LoginOption; }; +export type RequestBody = + | SubscribeRequest + | Agent.Logout + | Agent.UserStationLogin + | Agent.StateChange; export type StationLoginResponse = Agent.StationLoginSuccess | Error; export type StationLogoutResponse = Agent.LogoutSuccess | Error;