Skip to content

Commit b814079

Browse files
authored
feat(calling): auto-fetch RTMS domain from service catalog (#4923)
1 parent 79e0427 commit b814079

4 files changed

Lines changed: 99 additions & 41 deletions

File tree

packages/calling/src/CallingClient/CallingClient.test.ts

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
/* eslint-disable dot-notation */
1818
import {CALLING_CLIENT_EVENT_KEYS, CallSessionEvent, MOBIUS_EVENT_KEYS} from '../Events/types';
1919
import log from '../Logger';
20-
import {createClient} from './CallingClient';
20+
import {CallingClient, createClient} from './CallingClient';
2121
import {ICallingClient} from './types';
2222
import * as utils from '../common/Utils';
2323
import {getCallManager} from './calling/callManager';
@@ -212,43 +212,48 @@ describe('CallingClient Tests', () => {
212212
}).not.toThrow(Error);
213213
});
214214

215-
/**
216-
* Input sdk config to callingClient with serviceData carrying valid value for indicator
217-
* 'contactcenter', but an empty string for domain field in it.
218-
*
219-
* It should throw error and abort execution as domain value is invalid.
220-
*
221-
* DOMAIN field for service type 'contactcenter' must carry a non-empty valid domain type string.
222-
*/
223-
it('ContactCenter: verify empty invalid service domain', async () => {
224-
const serviceDataObj = {indicator: ServiceIndicator.CONTACT_CENTER, domain: ''};
215+
it('ContactCenter: uses config domain and does not fetch RTMS domain from catalog', async () => {
216+
const serviceDataObj = {
217+
indicator: ServiceIndicator.CONTACT_CENTER,
218+
domain: 'test.example.com',
219+
};
225220

226-
try {
227-
callingClient = await createClient(webex, {serviceData: serviceDataObj});
228-
} catch (e) {
229-
expect(e.message).toEqual('Invalid service domain.');
230-
}
231-
expect.assertions(1);
221+
webex.internal.services.get = jest.fn();
222+
callingClient = await createClient(webex, {serviceData: serviceDataObj});
223+
expect(callingClient).toBeTruthy();
224+
expect(webex.internal.services.get).not.toHaveBeenCalled();
232225
});
233226

234227
/**
235228
* Input sdk config to callingClient with serviceData carrying valid value for indicator
236229
* 'contactcenter' , and a valid domain type string for domain field in it.
237-
*
238-
* Execution should proceed properly and createRegistration should be called with same serviceData.
239-
*
240-
* DOMAIN field for service type 'contactcenter' must carry a non-empty valid domain type string.
241230
*/
242-
it('ContactCenter: verify valid service domain', async () => {
243-
const serviceDataObj = {
244-
indicator: ServiceIndicator.CONTACT_CENTER,
245-
domain: 'test.example.com',
246-
};
231+
it('ContactCenter: fetches RTMS domain from catalog when config domain is empty', async () => {
232+
const serviceDataObj = {indicator: ServiceIndicator.CONTACT_CENTER, domain: ''};
247233

248-
expect(async () => {
249-
callingClient = await createClient(webex, {serviceData: serviceDataObj});
250-
expect(callingClient).toBeTruthy();
251-
}).not.toThrow(Error);
234+
webex.internal.services.get = jest
235+
.fn()
236+
.mockReturnValue('https://cc-rtms.example.com/calling/web/rtms');
237+
238+
callingClient = await createClient(webex, {serviceData: serviceDataObj});
239+
expect(callingClient).toBeTruthy();
240+
expect(webex.internal.services.get).toHaveBeenCalledWith('wcc-calling-rtms-domain');
241+
});
242+
243+
it('ContactCenter: init fails when config domain is empty and catalog fetch fails', async () => {
244+
const serviceDataObj = {indicator: ServiceIndicator.CONTACT_CENTER, domain: ''};
245+
const createLineSpy = jest.spyOn(CallingClient.prototype as any, 'createLine');
246+
247+
webex.internal.services.get = jest.fn(() => {
248+
throw new Error('catalog unavailable');
249+
});
250+
251+
await expect(createClient(webex, {serviceData: serviceDataObj})).rejects.toThrow(
252+
'Invalid service domain.'
253+
);
254+
expect(createLineSpy).not.toHaveBeenCalled();
255+
256+
createLineSpy.mockRestore();
252257
});
253258

254259
it('Get current log level', async () => {

packages/calling/src/CallingClient/CallingClient.ts

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {METHOD_START_MESSAGE} from '../common/constants';
77
import {
88
filterMobiusUris,
99
handleCallingClientErrors,
10+
isValidServiceDomain,
1011
uploadLogs,
1112
validateServiceData,
1213
} from '../common/Utils';
@@ -27,6 +28,7 @@ import {
2728
ALLOWED_SERVICES,
2829
HTTP_METHODS,
2930
MobiusServers,
31+
ServiceData,
3032
WebexRequestPayload,
3133
RegistrationStatus,
3234
UploadLogsResponse,
@@ -50,6 +52,7 @@ import {
5052
METHODS,
5153
NETWORK_FLAP_TIMEOUT,
5254
DEVICES_ENDPOINT_RESOURCE,
55+
WCC_CALLING_RTMS_DOMAIN,
5356
} from './constants';
5457
import Line from './line';
5558
import {ILine} from './line/types';
@@ -88,6 +91,8 @@ export class CallingClient extends Eventing<CallingClientEventTypes> implements
8891

8992
private sdkConfig?: CallingClientConfig;
9093

94+
private serviceData: ServiceData;
95+
9196
private primaryMobiusUris: string[];
9297

9398
private backupMobiusUris: string[];
@@ -128,16 +133,16 @@ export class CallingClient extends Eventing<CallingClientEventTypes> implements
128133
this.webex = this.sdkConnector.getWebex();
129134

130135
this.sdkConfig = config;
131-
const serviceData = this.sdkConfig?.serviceData?.indicator
136+
this.serviceData = this.sdkConfig?.serviceData?.indicator
132137
? this.sdkConfig.serviceData
133138
: {indicator: ServiceIndicator.CALLING, domain: ''};
134139

135140
const logLevel = this.sdkConfig?.logger?.level ? this.sdkConfig.logger.level : LOGGER.ERROR;
136141
log.setLogger(logLevel, CALLING_CLIENT_FILE);
137-
validateServiceData(serviceData);
142+
validateServiceData(this.serviceData);
138143

139-
this.callManager = getCallManager(this.webex, serviceData.indicator);
140-
this.metricManager = getMetricManager(this.webex, serviceData.indicator);
144+
this.callManager = getCallManager(this.webex, this.serviceData.indicator);
145+
this.metricManager = getMetricManager(this.webex, this.serviceData.indicator);
141146

142147
this.mediaEngine = Media;
143148

@@ -202,11 +207,60 @@ export class CallingClient extends Eventing<CallingClientEventTypes> implements
202207
}
203208

204209
await this.getMobiusServers();
210+
211+
// Auto-fetch RTMS domain from service catalog for contact-center flows
212+
if (
213+
this.serviceData.indicator === ServiceIndicator.CONTACT_CENTER &&
214+
!this.serviceData.domain
215+
) {
216+
const rtmsDomain = this.getRTMSDomain();
217+
218+
this.serviceData.domain = rtmsDomain;
219+
if (this.sdkConfig?.serviceData) {
220+
this.sdkConfig.serviceData.domain = this.serviceData.domain;
221+
}
222+
}
223+
224+
if (!isValidServiceDomain(this.serviceData)) {
225+
throw new Error('Invalid service domain.');
226+
}
227+
205228
await this.createLine();
206229

207230
this.setupNetworkEventListeners();
208231
}
209232

233+
/**
234+
* Retrieves the RTMS domain from the service catalog for contact-center flows.
235+
*
236+
* @returns The RTMS domain from catalog when available.
237+
*/
238+
private getRTMSDomain(): string {
239+
log.info('Fetching RTMS domain from service catalog', {
240+
file: CALLING_CLIENT_FILE,
241+
method: METHODS.GET_RTMS_DOMAIN,
242+
});
243+
244+
try {
245+
const rtmsURL = this.webex.internal.services.get(WCC_CALLING_RTMS_DOMAIN);
246+
const url = new URL(rtmsURL);
247+
248+
log.info(`RTMS domain resolved from catalog: ${url.hostname}`, {
249+
file: CALLING_CLIENT_FILE,
250+
method: METHODS.GET_RTMS_DOMAIN,
251+
});
252+
253+
return url.hostname;
254+
} catch (error) {
255+
log.warn(`Failed to fetch RTMS domain from service catalog: ${error}`, {
256+
file: CALLING_CLIENT_FILE,
257+
method: METHODS.GET_RTMS_DOMAIN,
258+
});
259+
260+
return '';
261+
}
262+
}
263+
210264
/**
211265
* Ping a reliable external endpoint with a short timeout to infer connectivity.
212266
*/
@@ -706,7 +760,7 @@ export class CallingClient extends Eventing<CallingClientEventTypes> implements
706760
this.primaryMobiusUris,
707761
this.backupMobiusUris,
708762
this.getLoggingLevel(),
709-
this.sdkConfig?.serviceData,
763+
this.serviceData,
710764
this.sdkConfig?.jwe
711765
);
712766

packages/calling/src/CallingClient/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ export const MOBIUS_EU_INT = 'mobius-eu-central-1.int.infra.webex.com';
127127
export const FAILOVER_CACHE_PREFIX = 'wxc-failover-state';
128128
export const ACTIVE_MOBIUS_STORAGE_KEY = 'wxc-active-mobius';
129129
export const ICE_CANDIDATES_TIMEOUT = 3000;
130+
export const WCC_CALLING_RTMS_DOMAIN = 'wcc-calling-rtms-domain';
131+
130132
// Define constants for method names
131133
export const METHODS = {
132134
CONSTRUCTOR: 'constructor',
@@ -231,4 +233,5 @@ export const METHODS = {
231233
UPLOAD_LOGS: 'uploadLogs',
232234
GET_SDK_CONNECTOR: 'getSDKConnector',
233235
GET_CONNECTED_CALL: 'getConnectedCall',
236+
GET_RTMS_DOMAIN: 'getRTMSDomain',
234237
};

packages/calling/src/common/Utils.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,7 +1579,7 @@ function isValidServiceIndicator(indicator: ServiceIndicator): boolean {
15791579
* @param serviceData - .
15801580
* @returns True if validation is successful else false.
15811581
*/
1582-
function isValidServiceDomain(serviceData: ServiceData): boolean {
1582+
export function isValidServiceDomain(serviceData: ServiceData): boolean {
15831583
const regexp = /^[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,6}$/i;
15841584
const {domain} = serviceData;
15851585

@@ -1594,7 +1594,7 @@ function isValidServiceDomain(serviceData: ServiceData): boolean {
15941594
}
15951595

15961596
/**
1597-
* Validates service data object(indicator & domain) and throws
1597+
* Validates service data object(indicator) and throws
15981598
* exception with a message indicating the reason for validation
15991599
* failure.
16001600
*
@@ -1607,10 +1607,6 @@ export function validateServiceData(serviceData: ServiceData) {
16071607
if (!isValidServiceIndicator(serviceData.indicator)) {
16081608
throw new Error(`Invalid service indicator, Allowed values are: ${formattedValues}`);
16091609
}
1610-
1611-
if (!isValidServiceDomain(serviceData)) {
1612-
throw new Error('Invalid service domain.');
1613-
}
16141610
}
16151611

16161612
/**

0 commit comments

Comments
 (0)