@webex/contact-center 3.12.0-next.41 → 3.12.0-next.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cc.ts CHANGED
@@ -486,7 +486,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
486
486
  * ```
487
487
  */
488
488
  public async register(): Promise<Profile> {
489
- LoggerProxy.info('Starting CC SDK registration', {
489
+ LoggerProxy.log('Starting CC SDK registration', {
490
490
  module: CC_FILE,
491
491
  method: METHODS.REGISTER,
492
492
  });
@@ -731,7 +731,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
731
731
  * @private
732
732
  */
733
733
  private async connectWebsocket() {
734
- LoggerProxy.info('Connecting to websocket', {
734
+ LoggerProxy.log('Connecting to websocket', {
735
735
  module: CC_FILE,
736
736
  method: METHODS.CONNECT_WEBSOCKET,
737
737
  });
@@ -758,7 +758,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
758
758
  this.apiAIAssistant.setAIFeatureFlags(this.agentConfig.aiFeature);
759
759
 
760
760
  if (this.agentConfig.aiFeature?.realtimeTranscripts?.enable) {
761
- LoggerProxy.info('Connecting to RTD websocket', {
761
+ LoggerProxy.log('Connecting to RTD websocket', {
762
762
  module: CC_FILE,
763
763
  method: METHODS.CONNECT_WEBSOCKET,
764
764
  });
@@ -803,6 +803,11 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
803
803
 
804
804
  if (this.$config && this.$config.allowAutomatedRelogin) {
805
805
  await this.silentRelogin();
806
+ } else {
807
+ LoggerProxy.log('Skipping silent relogin: allowAutomatedRelogin is disabled', {
808
+ module: CC_FILE,
809
+ method: METHODS.CONNECT_WEBSOCKET,
810
+ });
806
811
  }
807
812
 
808
813
  return this.agentConfig;
@@ -845,26 +850,37 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
845
850
  * ```
846
851
  */
847
852
  public async stationLogin(data: AgentLogin): Promise<StationLoginResponse> {
848
- LoggerProxy.info('Starting agent station login', {
849
- module: CC_FILE,
850
- method: METHODS.STATION_LOGIN,
851
- });
853
+ const loggerContext = {module: CC_FILE, method: METHODS.STATION_LOGIN};
854
+
852
855
  try {
856
+ LoggerProxy.log(
857
+ `Starting agent station login | loginOption: ${data?.loginOption} teamId: ${data?.teamId}`,
858
+ loggerContext
859
+ );
853
860
  this.metricsManager.timeEvent([
854
861
  METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
855
862
  METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
856
863
  ]);
857
864
 
858
865
  const dialPlanEntries = this.agentConfig?.dialPlan?.dialPlanEntity ?? [];
859
- if (
860
- data.loginOption === LoginOption.AGENT_DN &&
861
- !isValidDialNumber(data.dialNumber, dialPlanEntries)
862
- ) {
863
- const error = new Error('INVALID_DIAL_NUMBER');
864
- // @ts-ignore - adding custom key to the error object
865
- error.details = {data: {reason: 'INVALID_DIAL_NUMBER'}} as Failure;
866
+ if (data.loginOption === LoginOption.AGENT_DN) {
867
+ LoggerProxy.log(
868
+ `Validating dial number | dialPlanEnabled: ${!!this.agentConfig
869
+ ?.dialPlan} | dialPlanEntryCount: ${dialPlanEntries.length}`,
870
+ loggerContext
871
+ );
872
+
873
+ if (!isValidDialNumber(data.dialNumber, dialPlanEntries)) {
874
+ LoggerProxy.log(
875
+ `Dial number validation failed | dialNumber: ${data.dialNumber} | dialPlanEntryCount: ${dialPlanEntries.length}`,
876
+ loggerContext
877
+ );
878
+ const error = new Error('INVALID_DIAL_NUMBER');
879
+ // @ts-ignore - adding custom key to the error object
880
+ error.details = {data: {reason: 'INVALID_DIAL_NUMBER'}} as Failure;
866
881
 
867
- throw error;
882
+ throw error;
883
+ }
868
884
  }
869
885
 
870
886
  const loginResponse = await this.services.agent.stationLogin({
@@ -1277,13 +1293,13 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1277
1293
  private async handleConnectionLost(msg: ConnectionLostDetails): Promise<void> {
1278
1294
  if (msg.isConnectionLost) {
1279
1295
  // TODO: Emit an event saying connection is lost
1280
- LoggerProxy.info('event=handleConnectionLost | Connection lost', {
1296
+ LoggerProxy.log('event=handleConnectionLost | Connection lost', {
1281
1297
  module: CC_FILE,
1282
1298
  method: METHODS.HANDLE_CONNECTION_LOST,
1283
1299
  });
1284
1300
  } else if (msg.isSocketReconnected) {
1285
1301
  // TODO: Emit an event saying connection is re-estabilished
1286
- LoggerProxy.info(
1302
+ LoggerProxy.log(
1287
1303
  'event=handleConnectionReconnect | Connection reconnected attempting to request silent relogin',
1288
1304
  {module: CC_FILE, method: METHODS.HANDLE_CONNECTION_LOST}
1289
1305
  );
@@ -1298,7 +1314,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1298
1314
  * @private
1299
1315
  */
1300
1316
  private async silentRelogin(): Promise<void> {
1301
- LoggerProxy.info('Starting silent relogin process', {
1317
+ LoggerProxy.log('Starting silent relogin process', {
1302
1318
  module: CC_FILE,
1303
1319
  method: METHODS.SILENT_RELOGIN,
1304
1320
  });
@@ -1320,7 +1336,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1320
1336
  await this.handleDeviceType(deviceType as LoginOption, dn);
1321
1337
 
1322
1338
  if (lastStateChangeReason === 'agent-wss-disconnect') {
1323
- LoggerProxy.info(
1339
+ LoggerProxy.log(
1324
1340
  'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1325
1341
  {module: CC_FILE, method: METHODS.SILENT_RELOGIN}
1326
1342
  );
@@ -189,7 +189,7 @@ function parseAgentConfigs(profileData: {
189
189
 
190
190
  const finalData = {
191
191
  teams: teamData,
192
- defaultDn: userData.defaultDialledNumber,
192
+ defaultDn: userData.deafultDialledNumber,
193
193
  forceDefaultDn: tenantData.forceDefaultDn,
194
194
  forceDefaultDnForAgent: getDefaultAgentDN(agentProfileData.agentDNValidation),
195
195
  regexUS: tenantData.dnDefaultRegex,
@@ -279,8 +279,9 @@ export type AgentResponse = {
279
279
 
280
280
  /**
281
281
  * The default dialed number of the agent.
282
+ * Note: The API returns this field as "deafultDialledNumber" (with typo).
282
283
  */
283
- defaultDialledNumber?: string;
284
+ deafultDialledNumber?: string;
284
285
  };
285
286
 
286
287
  /**
@@ -49,34 +49,69 @@ const getAgentActionTypeFromTask = (taskData?: TaskData): 'DIAL_NUMBER' | '' =>
49
49
  return isDialNumber || isEntryPointVariant ? 'DIAL_NUMBER' : '';
50
50
  };
51
51
 
52
- // Fallback regex for US/Canada dial numbers when no dial plan entries are configured
53
- export const FALLBACK_DIAL_NUMBER_REGEX = /1[0-9]{3}[2-9][0-9]{6}([,]{1,10}[0-9]+){0,1}/;
52
+ /**
53
+ * Strips characters defined in the dial plan entry from the input string.
54
+ *
55
+ * @param input - The dial number to sanitize
56
+ * @param strippedChars - String of characters to remove from the input
57
+ * @returns The sanitized input with specified characters removed
58
+ */
59
+ export const stripDialPlanChars = (input: string, strippedChars: string): string => {
60
+ if (!strippedChars) {
61
+ return input;
62
+ }
63
+
64
+ const charsToStrip = new Set(strippedChars.split(''));
65
+
66
+ return input
67
+ .split('')
68
+ .filter((c) => !charsToStrip.has(c))
69
+ .join('');
70
+ };
54
71
 
55
72
  /**
56
73
  * Validates a dial number against the provided dial plan regex patterns.
57
74
  * A number is valid if it matches at least one regex pattern in the dial plans.
58
- * Falls back to US/Canada regex validation if no dial plan entries are configured.
75
+ * Skips validation when no dial plan entries are configured, deferring to the server.
59
76
  *
60
77
  * @param input - The dial number to validate
61
78
  * @param dialPlanEntries - Array of dial plan entries containing regex patterns
62
- * @returns true if the input matches at least one dial plan regex pattern, false otherwise
79
+ * @returns true if the input matches at least one dial plan regex pattern or no entries are configured, false otherwise
63
80
  */
64
81
  export const isValidDialNumber = (
65
82
  input: string,
66
83
  dialPlanEntries: DialPlan['dialPlanEntity']
67
84
  ): boolean => {
85
+ if (!input) {
86
+ LoggerProxy.warn('Dial number is empty or undefined.', {
87
+ module: 'Utils',
88
+ method: 'isValidDialNumber',
89
+ });
90
+
91
+ return false;
92
+ }
93
+
68
94
  if (!dialPlanEntries || dialPlanEntries.length === 0) {
69
- LoggerProxy.info('No dial plan entries found. Falling back to US number validation.');
95
+ LoggerProxy.log(
96
+ 'No dial plan entries found. Skipping client-side validation, deferring to server.',
97
+ {module: 'Utils', method: 'isValidDialNumber'}
98
+ );
70
99
 
71
- return FALLBACK_DIAL_NUMBER_REGEX.test(input);
100
+ return true;
72
101
  }
73
102
 
74
103
  return dialPlanEntries.some((entry) => {
75
104
  try {
105
+ const sanitizedInput = stripDialPlanChars(input, entry.strippedChars);
76
106
  const regex = new RegExp(entry.regex);
77
107
 
78
- return regex.test(input);
79
- } catch {
108
+ return regex.test(sanitizedInput);
109
+ } catch (e) {
110
+ LoggerProxy.warn(`Failed to validate dial number against entry "${entry.name}": ${e}`, {
111
+ module: 'Utils',
112
+ method: 'isValidDialNumber',
113
+ });
114
+
80
115
  return false;
81
116
  }
82
117
  });
@@ -302,7 +302,7 @@ describe('webex.cc', () => {
302
302
  const result = await webex.cc.register();
303
303
 
304
304
  // Verify logging calls
305
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
305
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
306
306
  module: CC_FILE,
307
307
  method: 'register',
308
308
  });
@@ -415,7 +415,7 @@ describe('webex.cc', () => {
415
415
 
416
416
  await expect(webex.cc.register()).rejects.toThrow('Error while performing register');
417
417
 
418
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
418
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
419
419
  module: CC_FILE,
420
420
  method: 'register',
421
421
  });
@@ -814,10 +814,13 @@ describe('webex.cc', () => {
814
814
  const result = await webex.cc.stationLogin(options);
815
815
 
816
816
  // Verify logging calls
817
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
818
- module: CC_FILE,
819
- method: 'stationLogin',
820
- });
817
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
818
+ `Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
819
+ {
820
+ module: CC_FILE,
821
+ method: 'stationLogin',
822
+ }
823
+ );
821
824
  expect(LoggerProxy.log).toHaveBeenCalledWith(
822
825
  `Agent station login completed successfully agentId: ${mockData.data.agentId} loginOption: ${mockData.data.loginOption} teamId: ${mockData.data.teamId}`,
823
826
  {
@@ -868,10 +871,13 @@ describe('webex.cc', () => {
868
871
 
869
872
  await expect(webex.cc.stationLogin(options)).rejects.toThrow(error.details.data.reason);
870
873
 
871
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
872
- module: CC_FILE,
873
- method: 'stationLogin',
874
- });
874
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
875
+ `Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
876
+ {
877
+ module: CC_FILE,
878
+ method: 'stationLogin',
879
+ }
880
+ );
875
881
  expect(LoggerProxy.error).toHaveBeenCalledWith(
876
882
  `stationLogin failed with reason: ${error.details.data.reason}`,
877
883
  {module: CC_FILE, method: 'stationLogin', trackingId: error.details.trackingId}
@@ -1210,11 +1216,11 @@ describe('webex.cc', () => {
1210
1216
  const webSocketManagerOnSpy = jest.spyOn(webex.cc.services.webSocketManager, 'on');
1211
1217
  await webex.cc['silentRelogin']();
1212
1218
 
1213
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1219
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1214
1220
  module: CC_FILE,
1215
1221
  method: 'silentRelogin',
1216
1222
  });
1217
- expect(LoggerProxy.info).toHaveBeenCalledWith(
1223
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1218
1224
  'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1219
1225
  {module: CC_FILE, method: 'silentRelogin'}
1220
1226
  );
@@ -1259,7 +1265,7 @@ describe('webex.cc', () => {
1259
1265
 
1260
1266
  jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1261
1267
  await webex.cc['silentRelogin']();
1262
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1268
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1263
1269
  module: CC_FILE,
1264
1270
  method: 'silentRelogin',
1265
1271
  });
@@ -1274,7 +1280,7 @@ describe('webex.cc', () => {
1274
1280
  jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1275
1281
 
1276
1282
  await expect(webex.cc['silentRelogin']()).rejects.toThrow(error);
1277
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1283
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1278
1284
  module: CC_FILE,
1279
1285
  method: 'silentRelogin',
1280
1286
  });
@@ -1316,7 +1322,7 @@ describe('webex.cc', () => {
1316
1322
 
1317
1323
  await webex.cc['silentRelogin']();
1318
1324
 
1319
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1325
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1320
1326
  module: CC_FILE,
1321
1327
  method: 'silentRelogin',
1322
1328
  });
@@ -724,7 +724,7 @@ describe('AgentConfigService', () => {
724
724
  agentProfileId: 'profile123',
725
725
  siteId: 'site789',
726
726
  dbId: 'db123',
727
- defaultDialledNumber: '1234567890',
727
+ deafultDialledNumber: '1234567890',
728
728
  id: 'user001',
729
729
  teamIds: ['team1', 'team2'],
730
730
  };
@@ -864,7 +864,7 @@ describe('AgentConfigService', () => {
864
864
  skillProfileId: 'skillProfile456',
865
865
  siteId: 'site789',
866
866
  dbId: 'db123',
867
- defaultDialledNumber: '1234567890',
867
+ deafultDialledNumber: '1234567890',
868
868
  id: 'user001',
869
869
  teamIds: ['team1', 'team2'],
870
870
  };
@@ -1,5 +1,4 @@
1
1
  import * as Utils from '../../../../../src/services/core/Utils';
2
- import {FALLBACK_DIAL_NUMBER_REGEX} from '../../../../../src/services/core/Utils';
3
2
  import LoggerProxy from '../../../../../src/logger-proxy';
4
3
  import WebexRequest from '../../../../../src/services/core/WebexRequest';
5
4
  import {LoginOption, WebexRequestPayload} from '../../../../../src/types';
@@ -12,6 +11,7 @@ jest.mock('../../../../../src/logger-proxy', () => ({
12
11
  log: jest.fn(),
13
12
  error: jest.fn(),
14
13
  info: jest.fn(),
14
+ warn: jest.fn(),
15
15
  initialize: jest.fn(),
16
16
  },
17
17
  }));
@@ -569,7 +569,7 @@ describe('Utils', () => {
569
569
  const usOnlyEntry = {
570
570
  name: 'US',
571
571
  prefix: '1',
572
- regex: FALLBACK_DIAL_NUMBER_REGEX.source,
572
+ regex: '1[0-9]{3}[2-9][0-9]{6}([,]{1,10}[0-9]+){0,1}',
573
573
  strippedChars: '( )-',
574
574
  };
575
575
 
@@ -585,6 +585,11 @@ describe('Utils', () => {
585
585
  const result = Utils.isValidDialNumber('+442030484377', dialPlanEntries);
586
586
  expect(result).toBe(true);
587
587
  });
588
+
589
+ it('should return true for a European number', () => {
590
+ const result = Utils.isValidDialNumber('6955577166', dialPlanEntries);
591
+ expect(result).toBe(true);
592
+ });
588
593
  });
589
594
 
590
595
  describe('with US-only dial plan entry', () => {
@@ -606,17 +611,95 @@ describe('Utils', () => {
606
611
  });
607
612
  });
608
613
 
609
- describe('with empty dial plan entries (fallback to US regex)', () => {
610
- it('should return true for a valid US phone number', () => {
611
- const result = Utils.isValidDialNumber('12223334567', []);
614
+ describe('with empty dial plan entries (defers to server)', () => {
615
+ it('should return true for any dial number', () => {
616
+ expect(Utils.isValidDialNumber('12223334567', [])).toBe(true);
617
+ });
618
+
619
+ it('should return true for a UK phone number', () => {
620
+ expect(Utils.isValidDialNumber('+442030484377', [])).toBe(true);
621
+ });
622
+
623
+ it('should return true for a European number', () => {
624
+ expect(Utils.isValidDialNumber('6955577166', [])).toBe(true);
625
+ });
626
+ });
627
+
628
+ describe('strippedChars handling', () => {
629
+ it('should strip characters before regex matching', () => {
630
+ const strictEntry = {
631
+ name: 'Digits Only',
632
+ prefix: '',
633
+ regex: '^[0-9]{10,15}$',
634
+ strippedChars: '( )-+',
635
+ };
636
+ const result = Utils.isValidDialNumber('+44 (203) 048-4377', [strictEntry]);
612
637
  expect(result).toBe(true);
613
638
  });
614
639
 
615
- it('should return false for a UK phone number', () => {
616
- const result = Utils.isValidDialNumber('+442030484377', []);
640
+ it('should handle entries with no strippedChars', () => {
641
+ const noStripEntry = {
642
+ name: 'No Strip',
643
+ prefix: '',
644
+ regex: '^[0-9]+$',
645
+ strippedChars: '',
646
+ };
647
+ expect(Utils.isValidDialNumber('12345', [noStripEntry])).toBe(true);
648
+ expect(Utils.isValidDialNumber('+12345', [noStripEntry])).toBe(false);
649
+ });
650
+ });
651
+
652
+ describe('empty or undefined dial number', () => {
653
+ it('should return false and log warning for undefined dial number', () => {
654
+ const result = Utils.isValidDialNumber(undefined as any, [anyFormatEntry]);
655
+ expect(result).toBe(false);
656
+ expect(LoggerProxy.warn).toHaveBeenCalledWith(
657
+ 'Dial number is empty or undefined.',
658
+ expect.objectContaining({module: 'Utils', method: 'isValidDialNumber'})
659
+ );
660
+ });
661
+
662
+ it('should return false and log warning for empty string dial number', () => {
663
+ const result = Utils.isValidDialNumber('', [anyFormatEntry]);
664
+ expect(result).toBe(false);
665
+ expect(LoggerProxy.warn).toHaveBeenCalledWith(
666
+ 'Dial number is empty or undefined.',
667
+ expect.objectContaining({module: 'Utils', method: 'isValidDialNumber'})
668
+ );
669
+ });
670
+ });
671
+
672
+ describe('invalid regex handling', () => {
673
+ it('should return false and log warning for invalid regex pattern', () => {
674
+ const badEntry = {
675
+ name: 'Bad Regex',
676
+ prefix: '',
677
+ regex: '[invalid(',
678
+ strippedChars: '',
679
+ };
680
+ const result = Utils.isValidDialNumber('12345', [badEntry]);
617
681
  expect(result).toBe(false);
682
+ expect(LoggerProxy.warn).toHaveBeenCalledWith(
683
+ expect.stringContaining('Failed to validate dial number against entry "Bad Regex"'),
684
+ expect.objectContaining({module: 'Utils', method: 'isValidDialNumber'})
685
+ );
618
686
  });
619
687
  });
620
688
  });
621
689
 
690
+ describe('stripDialPlanChars', () => {
691
+ it('should remove specified characters from input', () => {
692
+ expect(Utils.stripDialPlanChars('+44 (203) 048-4377', '( )-+')).toBe('442030484377');
693
+ });
694
+
695
+ it('should return input unchanged when strippedChars is empty', () => {
696
+ expect(Utils.stripDialPlanChars('+442030484377', '')).toBe('+442030484377');
697
+ });
698
+
699
+ it('should return input unchanged when strippedChars is null/undefined', () => {
700
+ expect(Utils.stripDialPlanChars('12345', null as any)).toBe('12345');
701
+ expect(Utils.stripDialPlanChars('12345', undefined as any)).toBe('12345');
702
+ });
703
+ });
704
+
622
705
  });