diff --git a/packages/calling/src/CallHistory/CallHistory.test.ts b/packages/calling/src/CallHistory/CallHistory.test.ts index 5efa59d50b8..dadfbf0541a 100644 --- a/packages/calling/src/CallHistory/CallHistory.test.ts +++ b/packages/calling/src/CallHistory/CallHistory.test.ts @@ -3,7 +3,7 @@ /* eslint-disable @typescript-eslint/no-shadow */ import {LOGGER} from '../Logger/types'; import {getTestUtilsWebex} from '../common/testUtil'; -import {HTTP_METHODS, SORT, SORT_BY, WebexRequestPayload} from '../common/types'; +import {CALLING_BACKEND, HTTP_METHODS, SORT, SORT_BY, WebexRequestPayload} from '../common/types'; import {CallHistory, createCallHistoryClient} from './CallHistory'; import {ICallHistory} from './types'; import { @@ -16,6 +16,10 @@ import { janusSetReadStateUrl, ERROR_DETAILS_401, ERROR_DETAILS_400, + MOCK_LINES_API_CALL_RESPONSE, + MOCK_LINES_API_CALL_RESPONSE_WITH_NO_LINEDATA, + MOCK_CALL_HISTORY_WITH_UCM_LINE_NUMBER, + MOCK_CALL_HISTORY_WITHOUT_UCM_LINE_NUMBER, } from './callHistoryFixtures'; import { COMMON_EVENT_KEYS, @@ -247,4 +251,113 @@ describe('Call history tests', () => { ); }); }); + + describe('fetchUCMLinesData test', () => { + it('verify successful UCM lines API case', async () => { + const ucmLinesAPIPayload = (MOCK_LINES_API_CALL_RESPONSE); + + webex.request.mockResolvedValue(ucmLinesAPIPayload); + const response = await callHistory['fetchUCMLinesData'](); + + expect(response.statusCode).toBe(200); + expect(response.message).toBe('SUCCESS'); + }); + + it('verify bad request failed UCM lines API case', async () => { + const failurePayload = { + statusCode: 400, + }; + const ucmLinesAPIPayload = (failurePayload); + + webex.request.mockRejectedValue(ucmLinesAPIPayload); + const response = await callHistory['fetchUCMLinesData'](); + + expect(response).toStrictEqual(ERROR_DETAILS_400); + expect(response.data.error).toEqual(ERROR_DETAILS_400.data.error); + expect(response.statusCode).toBe(400); + expect(response.message).toBe('FAILURE'); + expect(serviceErrorCodeHandlerSpy).toHaveBeenCalledWith( + {statusCode: 400}, + {file: 'CallHistory', method: 'fetchLinesData'} + ); + }); + + it('should call fetchUCMLinesData when calling backend is UCM and userSessions contain valid cucmDN', async () => { + jest.spyOn(utils, 'getCallingBackEnd').mockReturnValue(CALLING_BACKEND.UCM); + // Since fetchUCMLinesData is a private method, TypeScript restricts direct access to it. + // To bypass this restriction, we are using 'as any' to access and invoke the method for testing purposes. + const fetchUCMLinesDataSpy = jest + .spyOn(callHistory as any, 'fetchUCMLinesData') + .mockResolvedValue(MOCK_LINES_API_CALL_RESPONSE); + + const mockCallHistoryPayload = ( + (MOCK_CALL_HISTORY_WITH_UCM_LINE_NUMBER) + ); + webex.request.mockResolvedValue(mockCallHistoryPayload); + + const response = await callHistory.getCallHistoryData(7, 10, SORT.DEFAULT, SORT_BY.DEFAULT); + + expect(fetchUCMLinesDataSpy).toHaveBeenCalledTimes(1); + + expect(response.statusCode).toBe(200); + expect( + response.data.userSessions && response.data.userSessions[0].self.ucmLineNumber + ).toEqual(1); + }); + + it('should fetchUCMLinesData but not assign ucmLineNumber when UCM backend has no line data', async () => { + jest.spyOn(utils, 'getCallingBackEnd').mockReturnValue(CALLING_BACKEND.UCM); + + // Since fetchUCMLinesData is a private method, TypeScript restricts direct access to it. + // To bypass this restriction, we are using 'as any' to access and invoke the method for testing purposes. + const fetchUCMLinesDataSpy = jest + .spyOn(callHistory as any, 'fetchUCMLinesData') + .mockResolvedValue(MOCK_LINES_API_CALL_RESPONSE_WITH_NO_LINEDATA); + + const mockCallHistoryPayload = ( + (MOCK_CALL_HISTORY_WITHOUT_UCM_LINE_NUMBER) + ); + webex.request.mockResolvedValue(mockCallHistoryPayload); + + const response = await callHistory.getCallHistoryData(7, 10, SORT.DEFAULT, SORT_BY.DEFAULT); + + expect(fetchUCMLinesDataSpy).toHaveBeenCalledTimes(1); + + expect(response.statusCode).toBe(200); + expect(response.data.userSessions && response.data.userSessions[0].self.cucmDN).toBeDefined(); + expect( + response.data.userSessions && response.data.userSessions[0].self.ucmLineNumber + ).toEqual(undefined); + }); + + it('should not call fetchUCMLinesData when calling backend is UCM but no valid cucmDN is present', async () => { + jest.spyOn(utils, 'getCallingBackEnd').mockReturnValue(CALLING_BACKEND.UCM); + // Since fetchUCMLinesData is a private method, TypeScript restricts direct access to it. + // To bypass this restriction, we are using 'as any' to access and invoke the method for testing purposes. + const fetchUCMLinesDataSpy = jest + .spyOn(callHistory as any, 'fetchUCMLinesData') + .mockResolvedValue({}); + + const callHistoryPayload = (mockCallHistoryBody); + webex.request.mockResolvedValue(callHistoryPayload); + + await callHistory.getCallHistoryData(7, 10, SORT.DEFAULT, SORT_BY.DEFAULT); + + expect(fetchUCMLinesDataSpy).not.toHaveBeenCalled(); + }); + + it('should not call fetchUCMLinesData when calling backend is not UCM', async () => { + jest.spyOn(utils, 'getCallingBackEnd').mockReturnValue(CALLING_BACKEND.WXC); + // Since fetchUCMLinesData is a private method, TypeScript restricts direct access to it. + // To bypass this restriction, we are using 'as any' to access and invoke the method for testing purposes. + const fetchUCMLinesDataSpy = jest + .spyOn(callHistory as any, 'fetchUCMLinesData') + .mockResolvedValue({}); + + const callHistoryPayload = (mockCallHistoryBody); + webex.request.mockResolvedValue(callHistoryPayload); + await callHistory.getCallHistoryData(7, 10, SORT.DEFAULT, SORT_BY.DEFAULT); + expect(fetchUCMLinesDataSpy).not.toHaveBeenCalled(); // Check that fetchUCMLinesData was not called + }); + }); }); diff --git a/packages/calling/src/CallHistory/CallHistory.ts b/packages/calling/src/CallHistory/CallHistory.ts index 4afab6d6722..dc9f6fa551b 100644 --- a/packages/calling/src/CallHistory/CallHistory.ts +++ b/packages/calling/src/CallHistory/CallHistory.ts @@ -2,15 +2,23 @@ /* eslint-disable no-underscore-dangle */ import SDKConnector from '../SDKConnector'; import {ISDKConnector, WebexSDK} from '../SDKConnector/types'; -import {ALLOWED_SERVICES, HTTP_METHODS, WebexRequestPayload, SORT, SORT_BY} from '../common/types'; +import { + ALLOWED_SERVICES, + HTTP_METHODS, + WebexRequestPayload, + SORT, + SORT_BY, + CALLING_BACKEND, +} from '../common/types'; import { ICallHistory, JanusResponseEvent, LoggerInterface, UpdateMissedCallsResponse, + UCMLinesResponse, } from './types'; import log from '../Logger'; -import {serviceErrorCodeHandler} from '../common/Utils'; +import {serviceErrorCodeHandler, getVgActionEndpoint, getCallingBackEnd} from '../common/Utils'; import { APPLICATION_JSON, CALL_HISTORY_FILE, @@ -21,6 +29,12 @@ import { NUMBER_OF_DAYS, UPDATE_MISSED_CALLS_ENDPOINT, SET_READ_STATE_SUCCESS_MESSAGE, + VERSION_1, + UNIFIED_COMMUNICATIONS, + CONFIG, + PEOPLE, + LINES, + ORG_ID, } from './constants'; import {STATUS_CODE, SUCCESS_MESSAGE, USER_SESSIONS} from '../common/constants'; import { @@ -32,6 +46,7 @@ import { EndTimeSessionId, CallSessionViewedEvent, SanitizedEndTimeAndSessionId, + UCMLinesApiResponse, } from '../Events/types'; import {Eventing} from '../Events/impl'; /** @@ -128,6 +143,43 @@ export class CallHistory extends Eventing implements ICal ); } } + // Check the calling backend + const callingBackend = getCallingBackEnd(this.webex); + if (callingBackend === CALLING_BACKEND.UCM) { + // Check if userSessions exist and the length is greater than 0 + if (this.userSessions[USER_SESSIONS] && this.userSessions[USER_SESSIONS].length > 0) { + // Check if cucmDN exists and is valid in any of the userSessions + const hasCucmDN = this.userSessions[USER_SESSIONS].some( + (session: UserSession) => session.self.cucmDN && session.self.cucmDN.length > 0 + ); + // If any user session has cucmDN, proceed to fetch line data + if (hasCucmDN) { + // Fetch the Lines data + const ucmLinesResponse = await this.fetchUCMLinesData(); + + // Check if the Lines API response was successful + if (ucmLinesResponse.statusCode === 200 && ucmLinesResponse.data.lines?.devices) { + const ucmLinesData = ucmLinesResponse.data.lines.devices; + + // Iterate over user sessions and match with Lines data + this.userSessions[USER_SESSIONS].forEach((session: UserSession) => { + const cucmDN = session.self.cucmDN; + + if (cucmDN) { + ucmLinesData.forEach((device) => { + device.lines.forEach((line) => { + if (line.dnorpattern === cucmDN) { + session.self.ucmLineNumber = line.index; // Assign the ucmLineNumber + } + }); + }); + } + }); + } + } + } + } + const responseDetails = { statusCode: this.userSessions[STATUS_CODE], data: { @@ -202,6 +254,45 @@ export class CallHistory extends Eventing implements ICal } } + /** + * Function to display the UCM Lines API response. + * @returns {Promise} Resolves to an object of type {@link UCMLinesResponse}.Response details with success or error status. + */ + private async fetchUCMLinesData(): Promise { + const loggerContext = { + file: CALL_HISTORY_FILE, + method: 'fetchLinesData', + }; + const vgEndpoint = getVgActionEndpoint(this.webex, CALLING_BACKEND.UCM); + const userId = this.webex.internal.device.userId; + const orgId = this.webex.internal.device.orgId; + const linesURIForUCM = `${vgEndpoint}/${VERSION_1}/${UNIFIED_COMMUNICATIONS}/${CONFIG}/${PEOPLE}/${userId}/${LINES}?${ORG_ID}=${orgId}`; + + try { + const response = await this.webex.request({ + uri: `${linesURIForUCM}`, + method: HTTP_METHODS.GET, + }); + + const ucmLineDetails: UCMLinesResponse = { + statusCode: Number(response.statusCode), + data: { + lines: response.body as UCMLinesApiResponse, + }, + message: SUCCESS_MESSAGE, + }; + + log.info(`Line details fetched successfully`, loggerContext); + + return ucmLineDetails; + } catch (err: unknown) { + const errorInfo = err as WebexRequestPayload; + const errorStatus = serviceErrorCodeHandler(errorInfo, loggerContext); + + return errorStatus; + } + } + handleSessionEvents = async (event?: CallSessionEvent) => { if (event && event.data.userSessions.userSessions) { this.emit(COMMON_EVENT_KEYS.CALL_HISTORY_USER_SESSION_INFO, event as CallSessionEvent); diff --git a/packages/calling/src/CallHistory/callHistoryFixtures.ts b/packages/calling/src/CallHistory/callHistoryFixtures.ts index ef30a1e8219..b7901a6dcc6 100644 --- a/packages/calling/src/CallHistory/callHistoryFixtures.ts +++ b/packages/calling/src/CallHistory/callHistoryFixtures.ts @@ -5,7 +5,7 @@ import { SessionType, CallSessionViewedEvent, } from '../Events/types'; -import {UpdateMissedCallsResponse} from './types'; +import {UCMLinesResponse, UpdateMissedCallsResponse} from './types'; export const sortedCallHistory = { body: { @@ -287,6 +287,200 @@ export const mockCallHistoryBody = { }, }; +/** + * MOCK_CALL_HISTORY_WITH_UCM_LINE_NUMBER simulates a call history response where the session contains + * both cucmDN and ucmLineNumber data. This implies that the cucmDN was successfully matched with the UCM lines data. + */ +export const MOCK_CALL_HISTORY_WITH_UCM_LINE_NUMBER = { + body: { + statusCode: 200, + userSessions: [ + { + id: '123456', + durationSecs: 438, + self: { + id: 'fd2e1234', + name: 'Mark', + cucmDN: '1001', + ucmLineNumber: 1, + incomingCallProtocols: [], + callbackInfo: { + callbackAddress: 'test@cisco.com', + callbackType: 'EMAIL', + }, + lookUpInfo: { + lookupLink: 'https://conv-a.wbx2.com/conversation/api/v1/conversations/98765', + type: 'CONVERSATION', + }, + }, + url: 'https://janus-a.wbx2.com/janus/api/v1/history/userSessions/654321', + sessionId: '123456', + sessionType: 'SPARK', + startTime: '2022-08-22T10:45:21.565Z', + endTime: '2022-08-22T10:53:01.624Z', + direction: 'OUTGOING', + disposition: 'INITIATED', + other: { + id: '100001', + name: 'test', + isPrivate: false, + callbackAddress: '89998888', + }, + durationSeconds: 438, + joinedDurationSeconds: 457, + participantCount: 2, + links: { + locusUrl: 'https://locus-a.wbx2.com/locus/api/v1/loci/786765', + conversationUrl: 'https://conv-a.wbx2.com/conversation/api/v1/conversations/55443322', + callbackAddress: '01010101', + }, + isDeleted: false, + isPMR: false, + correlationIds: ['008899'], + }, + { + id: '20191817', + durationSecs: 438, + self: { + id: '12131415', + name: 'Mark', + cucmDN: '1002', + ucmLineNumber: 2, + incomingCallProtocols: [], + callbackInfo: { + callbackAddress: 'test@cisco.com', + callbackType: 'EMAIL', + }, + lookUpInfo: { + lookupLink: 'https://conv-a.wbx2.com/conversation/api/v1/conversations/21314151', + type: 'CONVERSATION', + }, + }, + url: 'https://janus-a.wbx2.com/janus/api/v1/history/userSessions/100101102', + sessionId: '20191817', + sessionType: 'SPARK', + startTime: '2022-08-30T10:45:21.565Z', + endTime: '2022-08-30T10:53:01.624Z', + direction: 'OUTGOING', + disposition: 'INITIATED', + other: { + id: '301302303', + name: 'test', + isPrivate: false, + callbackAddress: '401402403', + }, + durationSeconds: 438, + joinedDurationSeconds: 457, + participantCount: 2, + links: { + locusUrl: 'https://locus-a.wbx2.com/locus/api/v1/loci/501502503', + conversationUrl: 'https://conv-a.wbx2.com/conversation/api/v1/conversations/601602603', + callbackAddress: '801802803', + }, + isDeleted: false, + isPMR: false, + correlationIds: ['901902903'], + }, + ], + }, +}; + +/** + * MOCK_CALL_HISTORY_WITHOUT_UCM_LINE_NUMBER simulates a call history response where the session contains + * cucmDN, but no ucmLineNumber is present. This implies that the cucmDN was not matched with any UCM lines data. + */ +export const MOCK_CALL_HISTORY_WITHOUT_UCM_LINE_NUMBER = { + body: { + statusCode: 200, + userSessions: [ + { + id: '123456', + durationSecs: 438, + self: { + id: 'fd2e1234', + name: 'Mark', + cucmDN: '1001', + incomingCallProtocols: [], + callbackInfo: { + callbackAddress: 'test@cisco.com', + callbackType: 'EMAIL', + }, + lookUpInfo: { + lookupLink: 'https://conv-a.wbx2.com/conversation/api/v1/conversations/98765', + type: 'CONVERSATION', + }, + }, + url: 'https://janus-a.wbx2.com/janus/api/v1/history/userSessions/654321', + sessionId: '123456', + sessionType: 'SPARK', + startTime: '2022-08-22T10:45:21.565Z', + endTime: '2022-08-22T10:53:01.624Z', + direction: 'OUTGOING', + disposition: 'INITIATED', + other: { + id: '100001', + name: 'test', + isPrivate: false, + callbackAddress: '89998888', + }, + durationSeconds: 438, + joinedDurationSeconds: 457, + participantCount: 2, + links: { + locusUrl: 'https://locus-a.wbx2.com/locus/api/v1/loci/786765', + conversationUrl: 'https://conv-a.wbx2.com/conversation/api/v1/conversations/55443322', + callbackAddress: '01010101', + }, + isDeleted: false, + isPMR: false, + correlationIds: ['008899'], + }, + { + id: '20191817', + durationSecs: 438, + self: { + id: '12131415', + name: 'Mark', + cucmDN: '1002', + incomingCallProtocols: [], + callbackInfo: { + callbackAddress: 'test@cisco.com', + callbackType: 'EMAIL', + }, + lookUpInfo: { + lookupLink: 'https://conv-a.wbx2.com/conversation/api/v1/conversations/21314151', + type: 'CONVERSATION', + }, + }, + url: 'https://janus-a.wbx2.com/janus/api/v1/history/userSessions/100101102', + sessionId: '20191817', + sessionType: 'SPARK', + startTime: '2022-08-30T10:45:21.565Z', + endTime: '2022-08-30T10:53:01.624Z', + direction: 'OUTGOING', + disposition: 'INITIATED', + other: { + id: '301302303', + name: 'test', + isPrivate: false, + callbackAddress: '401402403', + }, + durationSeconds: 438, + joinedDurationSeconds: 457, + participantCount: 2, + links: { + locusUrl: 'https://locus-a.wbx2.com/locus/api/v1/loci/501502503', + conversationUrl: 'https://conv-a.wbx2.com/conversation/api/v1/conversations/601602603', + callbackAddress: '801802803', + }, + isDeleted: false, + isPMR: false, + correlationIds: ['901902903'], + }, + ], + }, +}; + const WEBEX_CALL_SESSION = { id: 'd74d19cc-6aa7-f341-6012-aec433cc6f8d', durationSecs: 438, @@ -439,3 +633,43 @@ export const ERROR_DETAILS_400 = { }, message: 'FAILURE', }; + +/* + * MOCK_LINES_API_CALL_RESPONSE simulates a successful response from the UCM lines API. + */ +export const MOCK_LINES_API_CALL_RESPONSE: UCMLinesResponse = { + statusCode: 200, + data: { + lines: { + devices: [ + { + name: 'CSFheliosucm01', + model: 503, + lines: [ + { + dnorpattern: '+14928000001', + index: 1, + label: '', + }, + { + dnorpattern: '+14928000003', + index: 2, + label: '', + }, + ], + }, + ], + }, + }, + message: 'SUCCESS', +}; + +/** + * MOCK_LINES_API_CALL_RESPONSE_WITH_NO_LINEDATA simulates a successful UCM lines API response + * where no line data is present. The `lines` field is empty, indicating no devices or lines available. + */ +export const MOCK_LINES_API_CALL_RESPONSE_WITH_NO_LINEDATA: UCMLinesResponse = { + statusCode: 200, + data: {}, + message: 'SUCCESS', +}; diff --git a/packages/calling/src/CallHistory/constants.ts b/packages/calling/src/CallHistory/constants.ts index ac222e6edc3..f22c1d04e86 100644 --- a/packages/calling/src/CallHistory/constants.ts +++ b/packages/calling/src/CallHistory/constants.ts @@ -1,13 +1,19 @@ export const APPLICATION_JSON = 'application/json'; export const CALL_HISTORY_FILE = 'CallHistory'; export const CONTENT_TYPE = 'Content-Type'; +export const CONFIG = 'config'; export const FROM_DATE = '?from'; export const HISTORY = 'history'; export const LIMIT = 50; +export const LINES = 'lines'; export const NUMBER_OF_DAYS = 10; +export const ORG_ID = 'orgId'; +export const PEOPLE = 'people'; export const RESPONSE_MESSAGE = 'responseMessage'; -export const UPDATE_MISSED_CALLS_ENDPOINT = 'setReadState'; export const SET_READ_STATE_SUCCESS_MESSAGE = 'Missed calls are read by the user.'; export const SUCCESS_MESSAGE = 'SUCCESS'; export const STATUS_CODE = 'statusCode'; export const USER_SESSIONS = 'userSessions'; +export const UPDATE_MISSED_CALLS_ENDPOINT = 'setReadState'; +export const UNIFIED_COMMUNICATIONS = 'uc'; +export const VERSION_1 = 'v1'; diff --git a/packages/calling/src/CallHistory/types.ts b/packages/calling/src/CallHistory/types.ts index ca0ca3f333e..60be740f665 100644 --- a/packages/calling/src/CallHistory/types.ts +++ b/packages/calling/src/CallHistory/types.ts @@ -1,5 +1,10 @@ import {Eventing} from '../Events/impl'; -import {CallHistoryEventTypes, EndTimeSessionId, UserSession} from '../Events/types'; +import { + CallHistoryEventTypes, + EndTimeSessionId, + UserSession, + UCMLinesApiResponse, +} from '../Events/types'; import {LOGGER} from '../Logger/types'; import {SORT, SORT_BY} from '../common/types'; @@ -25,6 +30,15 @@ export type UpdateMissedCallsResponse = { message: string | null; }; +export type UCMLinesResponse = { + statusCode: number; + data: { + lines?: UCMLinesApiResponse; + error?: string; + }; + message: string | null; +}; + /** * Interface for CallHistory Client. * This encompasses a set of APIs designed to facilitate the retrieval of recent Call History Record. diff --git a/packages/calling/src/Events/types.ts b/packages/calling/src/Events/types.ts index 17c8c51bf5d..c0ed077a021 100644 --- a/packages/calling/src/Events/types.ts +++ b/packages/calling/src/Events/types.ts @@ -79,6 +79,8 @@ export type CallRecordSelf = { id: string; name?: string; phoneNumber?: string; + cucmDN?: string; + ucmLineNumber?: number; }; export type CallRecordListOther = { @@ -242,32 +244,38 @@ enum CALL_STATE { REMOTE_HELD = 'remoteheld', CONNECTED = 'connected', } + type eventType = string; + type callProgressData = { alerting: boolean; inbandROAP: boolean; }; + export type CallerIdInfo = { 'x-broadworks-remote-party-info'?: string; 'p-asserted-identity'?: string; from?: string; }; + type callId = string; type deviceId = string; type correlationId = string; type callUrl = string; type causecode = number; type cause = string; + type eventData = { callerId: CallerIdInfo; callState: CALL_STATE; }; + type midCallServiceData = { eventType: eventType; eventData: eventData; }; -type midCallService = Array; +type midCallService = Array; interface BaseMessage { eventType: eventType; correlationId: correlationId; @@ -275,19 +283,18 @@ interface BaseMessage { callId: callId; callUrl: callUrl; } - export interface CallSetupMessage extends BaseMessage { callerId: CallerIdInfo; trackingId: string; alertType: string; } - interface CallProgressMessage extends BaseMessage { callProgressData: callProgressData; callerId: CallerIdInfo; } export const WEBSOCKET_SCOPE = 'mobius'; + export enum WEBSOCKET_KEYS { CALL_PROGRESS = 'callprogress', CALL_CONNECTED = 'callconnected', @@ -368,7 +375,24 @@ export type EndTimeSessionId = { endTime: string; sessionId: string; }; + export type SanitizedEndTimeAndSessionId = { endTime: number; sessionId: string; }; + +export type UCMLine = { + dnorpattern: string; + index: number; + label: string | null; +}; + +export type UCMDevice = { + name: string; + model: number; + lines: UCMLine[]; +}; + +export type UCMLinesApiResponse = { + devices: UCMDevice[]; +}; diff --git a/packages/calling/src/common/Utils.ts b/packages/calling/src/common/Utils.ts index 5c5ee2434ff..4c2d8441dfe 100644 --- a/packages/calling/src/common/Utils.ts +++ b/packages/calling/src/common/Utils.ts @@ -85,7 +85,11 @@ import { URL_ENDPOINT, UTILS_FILE, } from '../CallingClient/constants'; -import {JanusResponseEvent, UpdateMissedCallsResponse} from '../CallHistory/types'; +import { + JanusResponseEvent, + UCMLinesResponse, + UpdateMissedCallsResponse, +} from '../CallHistory/types'; import { VoicemailResponseEvent, MessageInfo, @@ -691,6 +695,7 @@ export async function serviceErrorCodeHandler( | CallSettingResponse | ContactResponse | UpdateMissedCallsResponse + | UCMLinesResponse > { const errorCode = Number(err.statusCode); const failureMessage = 'FAILURE';