@webex/contact-center 3.11.0 → 3.12.0-mobius-socket.2
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/dist/cc.js +121 -28
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +5 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/metrics/behavioral-events.js +13 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +9 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +173 -0
- package/dist/services/ApiAiAssistant.js.map +1 -0
- package/dist/services/agent/types.js.map +1 -1
- package/dist/services/config/Util.js +6 -2
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/constants.js +12 -0
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/index.js +41 -2
- package/dist/services/config/index.js.map +1 -1
- package/dist/services/config/types.js +19 -1
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/constants.js +27 -1
- package/dist/services/constants.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/Utils.js +28 -6
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +92 -17
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +20 -5
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/core/websocket/connection-service.js +3 -1
- package/dist/services/core/websocket/connection-service.js.map +1 -1
- package/dist/services/index.js +6 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/task/TaskManager.js +117 -24
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +16 -3
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/constants.js +15 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/dialer.js +51 -0
- package/dist/services/task/dialer.js.map +1 -1
- package/dist/services/task/types.js +15 -0
- package/dist/services/task/types.js.map +1 -1
- package/dist/types/cc.d.ts +801 -0
- package/dist/types/config.d.ts +66 -0
- package/dist/types/constants.d.ts +50 -0
- package/dist/types/index.d.ts +184 -0
- package/dist/types/logger-proxy.d.ts +71 -0
- package/dist/types/metrics/MetricsManager.d.ts +223 -0
- package/dist/types/metrics/behavioral-events.d.ts +29 -0
- package/dist/types/metrics/constants.d.ts +161 -0
- package/dist/types/services/AddressBook.d.ts +74 -0
- package/dist/types/services/ApiAiAssistant.d.ts +31 -0
- package/dist/types/services/EntryPoint.d.ts +67 -0
- package/dist/types/services/Queue.d.ts +76 -0
- package/dist/types/services/WebCallingService.d.ts +1 -0
- package/dist/types/services/agent/index.d.ts +46 -0
- package/dist/types/services/agent/types.d.ts +413 -0
- package/dist/types/services/config/Util.d.ts +20 -0
- package/dist/types/services/config/constants.d.ts +249 -0
- package/dist/types/services/config/index.d.ts +177 -0
- package/dist/types/services/config/types.d.ts +1207 -0
- package/dist/types/services/constants.d.ts +110 -0
- package/dist/types/services/core/Err.d.ts +121 -0
- package/dist/types/services/core/GlobalTypes.d.ts +58 -0
- package/dist/types/services/core/Utils.d.ts +101 -0
- package/dist/types/services/core/WebexRequest.d.ts +22 -0
- package/dist/types/services/core/aqm-reqs.d.ts +65 -0
- package/dist/types/services/core/constants.d.ts +99 -0
- package/dist/types/services/core/types.d.ts +47 -0
- package/dist/types/services/core/websocket/WebSocketManager.d.ts +35 -0
- package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
- package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
- package/dist/types/services/core/websocket/types.d.ts +37 -0
- package/dist/types/services/index.d.ts +54 -0
- package/dist/types/services/task/AutoWrapup.d.ts +40 -0
- package/dist/types/services/task/TaskManager.d.ts +1 -0
- package/dist/types/services/task/TaskUtils.d.ts +92 -0
- package/dist/types/services/task/constants.d.ts +84 -0
- package/dist/types/services/task/contact.d.ts +69 -0
- package/dist/types/services/task/dialer.d.ts +43 -0
- package/dist/types/services/task/index.d.ts +650 -0
- package/dist/types/services/task/types.d.ts +1319 -0
- package/dist/types/types.d.ts +643 -0
- package/dist/types/utils/PageCache.d.ts +173 -0
- package/dist/types/webex-config.d.ts +53 -0
- package/dist/types/webex.d.ts +7 -0
- package/dist/types.js +14 -1
- package/dist/types.js.map +1 -1
- package/dist/webex.js +1 -1
- package/package.json +9 -9
- package/src/cc.ts +157 -30
- package/src/constants.ts +4 -0
- package/src/index.ts +1 -0
- package/src/metrics/behavioral-events.ts +14 -0
- package/src/metrics/constants.ts +11 -0
- package/src/services/ApiAiAssistant.ts +217 -0
- package/src/services/agent/types.ts +1 -1
- package/src/services/config/Util.ts +8 -0
- package/src/services/config/constants.ts +12 -0
- package/src/services/config/index.ts +45 -1
- package/src/services/config/types.ts +67 -0
- package/src/services/constants.ts +29 -0
- package/src/services/core/Err.ts +1 -0
- package/src/services/core/Utils.ts +32 -5
- package/src/services/core/aqm-reqs.ts +100 -22
- package/src/services/core/websocket/WebSocketManager.ts +21 -6
- package/src/services/core/websocket/connection-service.ts +5 -1
- package/src/services/index.ts +4 -0
- package/src/services/task/TaskManager.ts +174 -27
- package/src/services/task/TaskUtils.ts +12 -0
- package/src/services/task/constants.ts +16 -0
- package/src/services/task/dialer.ts +56 -1
- package/src/services/task/types.ts +24 -0
- package/src/types.ts +40 -1
- package/test/unit/spec/cc.ts +163 -23
- package/test/unit/spec/services/ApiAiAssistant.ts +115 -0
- package/test/unit/spec/services/config/index.ts +56 -0
- package/test/unit/spec/services/core/Utils.ts +63 -1
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +82 -12
- package/test/unit/spec/services/core/websocket/connection-service.ts +3 -1
- package/test/unit/spec/services/task/TaskManager.ts +1119 -251
- package/test/unit/spec/services/task/dialer.ts +198 -112
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
SiteInfo,
|
|
23
23
|
OutdialAniEntriesResponse,
|
|
24
24
|
OutdialAniParams,
|
|
25
|
+
AIFeatureFlagsResponse,
|
|
25
26
|
} from './types';
|
|
26
27
|
import WebexRequest from '../core/WebexRequest';
|
|
27
28
|
import {WCC_API_GATEWAY} from '../constants';
|
|
@@ -61,6 +62,7 @@ export default class AgentConfigService {
|
|
|
61
62
|
const orgSettingsPromise = this.getOrganizationSetting(orgId);
|
|
62
63
|
const tenantDataPromise = this.getTenantData(orgId);
|
|
63
64
|
const urlMappingPromise = this.getURLMapping(orgId);
|
|
65
|
+
const aiFeatureFlagsPromise = this.getAIFeatureFlags(orgId);
|
|
64
66
|
const auxCodesPromise = this.getAllAuxCodes(
|
|
65
67
|
orgId,
|
|
66
68
|
DEFAULT_PAGE_SIZE,
|
|
@@ -94,6 +96,7 @@ export default class AgentConfigService {
|
|
|
94
96
|
orgSettingsData,
|
|
95
97
|
tenantData,
|
|
96
98
|
urlMappingData,
|
|
99
|
+
aiFeatureFlagsData,
|
|
97
100
|
auxCodesData,
|
|
98
101
|
] = await Promise.all([
|
|
99
102
|
agentProfilePromise,
|
|
@@ -104,9 +107,9 @@ export default class AgentConfigService {
|
|
|
104
107
|
orgSettingsPromise,
|
|
105
108
|
tenantDataPromise,
|
|
106
109
|
urlMappingPromise,
|
|
110
|
+
aiFeatureFlagsPromise,
|
|
107
111
|
auxCodesPromise,
|
|
108
112
|
]);
|
|
109
|
-
|
|
110
113
|
const multimediaProfileId =
|
|
111
114
|
userConfigData.multimediaProfileId ||
|
|
112
115
|
userTeamData[0]?.multiMediaProfileId ||
|
|
@@ -128,6 +131,7 @@ export default class AgentConfigService {
|
|
|
128
131
|
dialPlanData: userDialPlanData,
|
|
129
132
|
urlMapping: urlMappingData,
|
|
130
133
|
multimediaProfileId,
|
|
134
|
+
aiFeatureFlags: aiFeatureFlagsData,
|
|
131
135
|
});
|
|
132
136
|
|
|
133
137
|
LoggerProxy.info('Parsing completed for agent-config', {
|
|
@@ -651,6 +655,46 @@ export default class AgentConfigService {
|
|
|
651
655
|
}
|
|
652
656
|
}
|
|
653
657
|
|
|
658
|
+
/**
|
|
659
|
+
* Fetches AI feature resources for the organization.
|
|
660
|
+
* @ignore
|
|
661
|
+
* @param {string} orgId - organization ID for which AI feature resources are to be fetched.
|
|
662
|
+
* @returns {Promise<AIFeatureFlagsResponse>} - AI feature resources response.
|
|
663
|
+
* @throws {Error} - Throws an error if the API call fails or if the response status is not 200.
|
|
664
|
+
* @private
|
|
665
|
+
*/
|
|
666
|
+
public async getAIFeatureFlags(orgId: string): Promise<AIFeatureFlagsResponse> {
|
|
667
|
+
LoggerProxy.info('Fetching AI feature resources', {
|
|
668
|
+
module: CONFIG_FILE_NAME,
|
|
669
|
+
method: METHODS.GET_AI_FEATURE_FLAGS,
|
|
670
|
+
});
|
|
671
|
+
try {
|
|
672
|
+
const resource = endPointMap.aiFeature(orgId);
|
|
673
|
+
const response = await this.webexReq.request({
|
|
674
|
+
service: WCC_API_GATEWAY,
|
|
675
|
+
resource,
|
|
676
|
+
method: HTTP_METHODS.GET,
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
if (response.statusCode !== 200) {
|
|
680
|
+
throw new Error(`API call failed with ${response.statusCode}`);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
LoggerProxy.log('getAIFeatureFlags api success.', {
|
|
684
|
+
module: CONFIG_FILE_NAME,
|
|
685
|
+
method: METHODS.GET_AI_FEATURE_FLAGS,
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
return Promise.resolve(response.body);
|
|
689
|
+
} catch (error) {
|
|
690
|
+
LoggerProxy.error(`getAIFeatureFlags API call failed with ${error}`, {
|
|
691
|
+
module: CONFIG_FILE_NAME,
|
|
692
|
+
method: METHODS.GET_AI_FEATURE_FLAGS,
|
|
693
|
+
});
|
|
694
|
+
throw error;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
654
698
|
/**
|
|
655
699
|
* Fetches the dial plan data for the given orgId.
|
|
656
700
|
* @ignore
|
|
@@ -111,6 +111,14 @@ export const CC_TASK_EVENTS = {
|
|
|
111
111
|
AGENT_CONTACT_UNASSIGNED: 'AgentContactUnassigned',
|
|
112
112
|
/** Event emitted when inviting agent fails */
|
|
113
113
|
AGENT_INVITE_FAILED: 'AgentInviteFailed',
|
|
114
|
+
/** Event emitted when a campaign preview contact is offered to the agent */
|
|
115
|
+
AGENT_OFFER_CAMPAIGN_RESERVATION: 'AgentOfferCampaignReservation',
|
|
116
|
+
/** Event emitted when campaign contact is updated */
|
|
117
|
+
CAMPAIGN_CONTACT_UPDATED: 'CampaignContactUpdated',
|
|
118
|
+
/** Event emitted when accepting a campaign preview contact fails */
|
|
119
|
+
CAMPAIGN_PREVIEW_ACCEPT_FAILED: 'CampaignPreviewAcceptFailed',
|
|
120
|
+
/** Event emitted when a real-time transcript chunk is received */
|
|
121
|
+
REAL_TIME_TRANSCRIPTION: 'REAL_TIME_TRANSCRIPTION',
|
|
114
122
|
} as const;
|
|
115
123
|
|
|
116
124
|
/**
|
|
@@ -580,6 +588,8 @@ export type OrgInfo = {
|
|
|
580
588
|
tenantId: string;
|
|
581
589
|
/** Organization timezone */
|
|
582
590
|
timezone: string;
|
|
591
|
+
/** Current environment (e.g., 'produs1', 'intgus1') */
|
|
592
|
+
environment: string;
|
|
583
593
|
};
|
|
584
594
|
|
|
585
595
|
/**
|
|
@@ -900,6 +910,59 @@ export type URLMappings = {
|
|
|
900
910
|
acqueonConsoleUrl: string;
|
|
901
911
|
};
|
|
902
912
|
|
|
913
|
+
/**
|
|
914
|
+
* AI feature resource row returned by /v2/ai-feature API.
|
|
915
|
+
* @public
|
|
916
|
+
*/
|
|
917
|
+
export type AIFeatureFlags = {
|
|
918
|
+
id: string;
|
|
919
|
+
realtimeTranscripts?: {
|
|
920
|
+
enable?: boolean;
|
|
921
|
+
agentInclusionType?: string;
|
|
922
|
+
};
|
|
923
|
+
suggestedResponses?: {
|
|
924
|
+
enable?: boolean;
|
|
925
|
+
};
|
|
926
|
+
generatedSummaries?: {
|
|
927
|
+
callDropSummariesEnabled?: boolean;
|
|
928
|
+
virtualAgentTransferSummariesEnabled?: boolean;
|
|
929
|
+
consultTransferSummariesEnabled?: boolean;
|
|
930
|
+
wrapUpSummariesEnabled?: boolean;
|
|
931
|
+
queuesInclusionType?: string;
|
|
932
|
+
};
|
|
933
|
+
agentWellbeing?: {
|
|
934
|
+
enable?: boolean;
|
|
935
|
+
agentInclusionType?: string;
|
|
936
|
+
wellnessBreakReminders?: string;
|
|
937
|
+
};
|
|
938
|
+
autoCSAT?: {
|
|
939
|
+
enable?: boolean;
|
|
940
|
+
queuesInclusionType?: string;
|
|
941
|
+
surveyDataSource?: string;
|
|
942
|
+
};
|
|
943
|
+
links?: string[];
|
|
944
|
+
createdTime?: number;
|
|
945
|
+
lastUpdatedTime?: number;
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Response type for list AI feature resources API.
|
|
950
|
+
* @public
|
|
951
|
+
*/
|
|
952
|
+
export type AIFeatureFlagsResponse = {
|
|
953
|
+
meta?: {
|
|
954
|
+
orgid?: string;
|
|
955
|
+
page?: number;
|
|
956
|
+
pageSize?: number;
|
|
957
|
+
totalPages?: number;
|
|
958
|
+
totalRecords?: number;
|
|
959
|
+
links?: {
|
|
960
|
+
self?: string;
|
|
961
|
+
};
|
|
962
|
+
};
|
|
963
|
+
data: AIFeatureFlags[];
|
|
964
|
+
};
|
|
965
|
+
|
|
903
966
|
/**
|
|
904
967
|
* Comprehensive agent profile configuration in the contact center system
|
|
905
968
|
* Contains all settings and capabilities for an agent
|
|
@@ -1031,6 +1094,8 @@ export type Profile = {
|
|
|
1031
1094
|
isAnalyzerEnabled?: boolean;
|
|
1032
1095
|
/** Tenant timezone */
|
|
1033
1096
|
tenantTimezone?: string;
|
|
1097
|
+
/** Current environment (e.g., 'produs1', 'intgus1') */
|
|
1098
|
+
environment?: string;
|
|
1034
1099
|
/** Available voice login options */
|
|
1035
1100
|
loginVoiceOptions?: LoginOption[];
|
|
1036
1101
|
/** Current login device type */
|
|
@@ -1055,6 +1120,8 @@ export type Profile = {
|
|
|
1055
1120
|
lastStateChangeTimestamp?: number;
|
|
1056
1121
|
/** Timestamp of last idle code change */
|
|
1057
1122
|
lastIdleCodeChangeTimestamp?: number;
|
|
1123
|
+
/** AI feature flags resolved from organization config */
|
|
1124
|
+
aiFeature?: AIFeatureFlags;
|
|
1058
1125
|
};
|
|
1059
1126
|
|
|
1060
1127
|
/**
|
|
@@ -59,6 +59,14 @@ export const AGENT = 'agent';
|
|
|
59
59
|
*/
|
|
60
60
|
export const SUBSCRIBE_API = 'v1/notification/subscribe';
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* API path for realtime transcription subscription.
|
|
64
|
+
* @type {string}
|
|
65
|
+
* @public
|
|
66
|
+
* @ignore
|
|
67
|
+
*/
|
|
68
|
+
export const RTD_SUBSCRIBE_API = 'v1/realtime/subscribe';
|
|
69
|
+
|
|
62
70
|
/**
|
|
63
71
|
* API path for agent login.
|
|
64
72
|
* @type {string}
|
|
@@ -109,3 +117,24 @@ export const METHODS = {
|
|
|
109
117
|
MAP_CALL_TO_TASK: 'mapCallToTask',
|
|
110
118
|
GET_TASK_ID_FOR_CALL: 'getTaskIdForCall',
|
|
111
119
|
};
|
|
120
|
+
|
|
121
|
+
export const AI_ASSISTANT_API_URLS = {
|
|
122
|
+
EVENT: '/event',
|
|
123
|
+
TRANSCRIPTS_LIST: '/transcripts/list',
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const AI_ASSISTANT_BASE_URL_TEMPLATE = 'https://api-ai-assistant.%s.ciscoccservice.com';
|
|
127
|
+
|
|
128
|
+
export const AI_ASSISTANT_ENV_MAP: Record<string, string> = {
|
|
129
|
+
'api.intgus1.ciscoccservice.com': 'intgus1',
|
|
130
|
+
'api.qaus1.ciscoccservice.com': 'qaus1',
|
|
131
|
+
'api.wxcc-us1.cisco.com': 'produs1',
|
|
132
|
+
'api.wxcc-eu1.cisco.com': 'prodeu1',
|
|
133
|
+
'api.wxcc-eu2.cisco.com': 'prodeu2',
|
|
134
|
+
'api.wxcc-anz1.cisco.com': 'prodanz1',
|
|
135
|
+
'api.wxcc-ca1.cisco.com': 'prodca1',
|
|
136
|
+
'api.wxcc-jp1.cisco.com': 'prodjp1',
|
|
137
|
+
'api.wxcc-sg1.cisco.com': 'prodsg1',
|
|
138
|
+
'api.wxcc-in1.cisco.com': 'prodin1',
|
|
139
|
+
'api.loadus1.cisco.com': 'loadus1',
|
|
140
|
+
};
|
package/src/services/core/Err.ts
CHANGED
|
@@ -38,6 +38,7 @@ export type TaskErrorIds =
|
|
|
38
38
|
| {'Service.aqm.task.pauseRecording': Failure}
|
|
39
39
|
| {'Service.aqm.task.resumeRecording': Failure}
|
|
40
40
|
| {'Service.aqm.dialer.startOutdial': Failure}
|
|
41
|
+
| {'Service.aqm.dialer.acceptPreviewContact': Failure}
|
|
41
42
|
| {'Service.reqs.generic.failure': {trackingId: string}};
|
|
42
43
|
|
|
43
44
|
export type ReqError =
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
Interaction,
|
|
11
11
|
} from '../task/types';
|
|
12
12
|
import {PARTICIPANT_TYPES, STATE_CONSULT} from './constants';
|
|
13
|
+
import {DialPlan} from '../config/types';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Extracts common error details from a Webex request payload.
|
|
@@ -48,11 +49,37 @@ const getAgentActionTypeFromTask = (taskData?: TaskData): 'DIAL_NUMBER' | '' =>
|
|
|
48
49
|
return isDialNumber || isEntryPointVariant ? 'DIAL_NUMBER' : '';
|
|
49
50
|
};
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const regexForDn = /1[0-9]{3}[2-9][0-9]{6}([,]{1,10}[0-9]+){0,1}/;
|
|
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}/;
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Validates a dial number against the provided dial plan regex patterns.
|
|
57
|
+
* 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.
|
|
59
|
+
*
|
|
60
|
+
* @param input - The dial number to validate
|
|
61
|
+
* @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
|
|
63
|
+
*/
|
|
64
|
+
export const isValidDialNumber = (
|
|
65
|
+
input: string,
|
|
66
|
+
dialPlanEntries: DialPlan['dialPlanEntity']
|
|
67
|
+
): boolean => {
|
|
68
|
+
if (!dialPlanEntries || dialPlanEntries.length === 0) {
|
|
69
|
+
LoggerProxy.info('No dial plan entries found. Falling back to US number validation.');
|
|
70
|
+
|
|
71
|
+
return FALLBACK_DIAL_NUMBER_REGEX.test(input);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return dialPlanEntries.some((entry) => {
|
|
75
|
+
try {
|
|
76
|
+
const regex = new RegExp(entry.regex);
|
|
77
|
+
|
|
78
|
+
return regex.test(input);
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
56
83
|
};
|
|
57
84
|
|
|
58
85
|
export const getStationLoginErrorData = (failure: Failure, loginOption: LoginOption) => {
|
|
@@ -74,7 +101,7 @@ export const getStationLoginErrorData = (failure: Failure, loginOption: LoginOpt
|
|
|
74
101
|
},
|
|
75
102
|
INVALID_DIAL_NUMBER: {
|
|
76
103
|
message:
|
|
77
|
-
'Enter a valid
|
|
104
|
+
'Enter a valid dial number. For help, reach out to your administrator or support team.',
|
|
78
105
|
fieldName: loginOption,
|
|
79
106
|
},
|
|
80
107
|
};
|
|
@@ -20,18 +20,40 @@ export default class AqmReqs {
|
|
|
20
20
|
this.webSocketManager.on('message', this.onMessage.bind(this));
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Creates a request function for an API call with parameters
|
|
25
|
+
* @param c - The configuration for the request
|
|
26
|
+
* @returns A function that makes the API request
|
|
27
|
+
*/
|
|
23
28
|
req<TRes, TErr, TReq>(c: Conf<TRes, TErr, TReq>): Res<TRes, TReq> {
|
|
24
29
|
return (p: TReq, cbRes?: CbRes<TRes>) => this.makeAPIRequest(c(p), cbRes);
|
|
25
30
|
}
|
|
26
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Creates a request function for an API call with no parameters
|
|
34
|
+
* @param c - The configuration for the request
|
|
35
|
+
* @returns A function that makes the API request
|
|
36
|
+
*/
|
|
27
37
|
reqEmpty<TRes, TErr>(c: ConfEmpty<TRes, TErr>): ResEmpty<TRes> {
|
|
28
38
|
return (cbRes?: CbRes<TRes>) => this.makeAPIRequest(c(), cbRes);
|
|
29
39
|
}
|
|
30
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Makes an API request
|
|
43
|
+
* @param c - The request configuration
|
|
44
|
+
* @param cbRes - The callback for the response
|
|
45
|
+
* @returns A promise that resolves with the response or rejects with an error
|
|
46
|
+
*/
|
|
31
47
|
private async makeAPIRequest<TRes, TErr>(c: Req<TRes, TErr>, cbRes?: CbRes<TRes>): Promise<TRes> {
|
|
32
48
|
return this.createPromise(c, cbRes);
|
|
33
49
|
}
|
|
34
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Creates a promise for an API request
|
|
53
|
+
* @param c - The request configuration
|
|
54
|
+
* @param cbRes - The callback for the response
|
|
55
|
+
* @returns A promise that resolves with the response or rejects with an error
|
|
56
|
+
*/
|
|
35
57
|
private createPromise<TRes, TErr>(c: Req<TRes, TErr>, cbRes?: CbRes<TRes>) {
|
|
36
58
|
return new Promise<TRes>((resolve, reject) => {
|
|
37
59
|
const keySuccess = this.bindPrint(c.notifSuccess.bind);
|
|
@@ -154,10 +176,13 @@ export default class AqmReqs {
|
|
|
154
176
|
if (response?.headers) {
|
|
155
177
|
response.headers.Authorization = '*';
|
|
156
178
|
}
|
|
157
|
-
LoggerProxy.error(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
179
|
+
LoggerProxy.error(
|
|
180
|
+
`Routing request timeout${keySuccess}${JSON.stringify(response)}${c.url}`,
|
|
181
|
+
{
|
|
182
|
+
module: AQM_REQS_FILE,
|
|
183
|
+
method: METHODS.CREATE_PROMISE,
|
|
184
|
+
}
|
|
185
|
+
);
|
|
161
186
|
reject(
|
|
162
187
|
new Err.Details('Service.aqm.reqs.Timeout', {
|
|
163
188
|
key: keySuccess,
|
|
@@ -171,39 +196,60 @@ export default class AqmReqs {
|
|
|
171
196
|
});
|
|
172
197
|
}
|
|
173
198
|
|
|
174
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Converts a bind object to a string representation
|
|
201
|
+
* @param bind - The bind object to convert
|
|
202
|
+
* @returns A string representation of the bind object
|
|
203
|
+
*/
|
|
204
|
+
private bindPrint(bind: any): string {
|
|
175
205
|
let result = '';
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
206
|
+
for (const key of Object.keys(bind).filter((prop) => prop !== '__typeMap')) {
|
|
207
|
+
const value = bind[key];
|
|
208
|
+
|
|
209
|
+
if (Array.isArray(value)) {
|
|
210
|
+
result += `${key}=[${value.join(',')}],`;
|
|
211
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
212
|
+
result += `${key}=(${this.bindPrint(value)}),`;
|
|
182
213
|
} else {
|
|
183
|
-
result += `${
|
|
214
|
+
result += `${key}=${value},`;
|
|
184
215
|
}
|
|
185
216
|
}
|
|
186
217
|
|
|
187
218
|
return result ? result.slice(0, -1) : result;
|
|
188
219
|
}
|
|
189
220
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Checks if a message matches a bind object
|
|
223
|
+
* @param bind - The bind object to check against
|
|
224
|
+
* @param msg - The message to check
|
|
225
|
+
* @returns True if the message matches the bind object, false otherwise
|
|
226
|
+
*/
|
|
227
|
+
private bindCheck(bind: any, msg: any): boolean {
|
|
228
|
+
// Handle type-dependent field matching if __typeMap is present
|
|
229
|
+
if (bind.__typeMap && typeof bind.__typeMap === 'object') {
|
|
230
|
+
if (!AqmReqs.typeMapCheck(bind.__typeMap, msg)) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
for (const key of Object.keys(bind).filter((prop) => prop !== '__typeMap')) {
|
|
236
|
+
const bindValue = bind[key];
|
|
237
|
+
const msgValue = msg[key];
|
|
238
|
+
|
|
239
|
+
if (Array.isArray(bindValue)) {
|
|
194
240
|
// Check if the message value matches any of the values in the array
|
|
195
|
-
if (!
|
|
241
|
+
if (!bindValue.includes(msgValue)) {
|
|
196
242
|
return false;
|
|
197
243
|
}
|
|
198
|
-
} else if (typeof
|
|
199
|
-
if (typeof
|
|
200
|
-
if (!this.bindCheck(
|
|
244
|
+
} else if (typeof bindValue === 'object' && bindValue !== null) {
|
|
245
|
+
if (typeof msgValue === 'object' && msgValue !== null) {
|
|
246
|
+
if (!this.bindCheck(bindValue, msgValue)) {
|
|
201
247
|
return false;
|
|
202
248
|
}
|
|
203
249
|
} else {
|
|
204
250
|
return false;
|
|
205
251
|
}
|
|
206
|
-
} else if (!
|
|
252
|
+
} else if (!msgValue || msgValue !== bindValue) {
|
|
207
253
|
return false;
|
|
208
254
|
}
|
|
209
255
|
}
|
|
@@ -211,7 +257,39 @@ export default class AqmReqs {
|
|
|
211
257
|
return true;
|
|
212
258
|
}
|
|
213
259
|
|
|
214
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Checks type-dependent field conditions defined in __typeMap.
|
|
262
|
+
* @param typeMap - The type map to check against
|
|
263
|
+
* @param msg - The message to check
|
|
264
|
+
* @returns True if the message matches the type map, false otherwise
|
|
265
|
+
* The typeMap has the shape:
|
|
266
|
+
* { typeField: "type", conditions: { EventA: { field: value }, EventB: { field: value } } }
|
|
267
|
+
* It reads msg[typeField] to determine which condition set to apply,
|
|
268
|
+
* then verifies all fields in that condition match the message.
|
|
269
|
+
*/
|
|
270
|
+
private static typeMapCheck(typeMap: any, msg: any): boolean {
|
|
271
|
+
const typeField = typeMap.typeField || 'type';
|
|
272
|
+
const msgType = msg[typeField];
|
|
273
|
+
|
|
274
|
+
if (typeMap.conditions && typeMap.conditions[msgType]) {
|
|
275
|
+
const condition = typeMap.conditions[msgType];
|
|
276
|
+
for (const field of Object.keys(condition)) {
|
|
277
|
+
if (!msg[field] || msg[field] !== condition[field]) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Handles incoming messages from the WebSocket (must be a lambda fn)
|
|
290
|
+
* @param msg - The message to handle
|
|
291
|
+
* @returns
|
|
292
|
+
*/
|
|
215
293
|
private readonly onMessage = (msg: any) => {
|
|
216
294
|
const event = JSON.parse(msg);
|
|
217
295
|
if (event.type === 'Welcome') {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import EventEmitter from 'events';
|
|
2
2
|
import {WebexSDK, SubscribeRequest, HTTP_METHODS} from '../../../types';
|
|
3
|
-
import {
|
|
3
|
+
import {WCC_API_GATEWAY} from '../../constants';
|
|
4
4
|
import {ConnectionLostDetails} from './types';
|
|
5
5
|
import {CC_EVENTS, SubscribeResponse, WelcomeResponse} from '../../config/types';
|
|
6
6
|
import LoggerProxy from '../../../logger-proxy';
|
|
@@ -44,10 +44,13 @@ export class WebSocketManager extends EventEmitter {
|
|
|
44
44
|
this.keepaliveWorker = new Worker(URL.createObjectURL(workerScriptBlob));
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
async initWebSocket(options: {
|
|
48
|
-
|
|
47
|
+
async initWebSocket(options: {
|
|
48
|
+
body: SubscribeRequest;
|
|
49
|
+
resource: string;
|
|
50
|
+
}): Promise<WelcomeResponse> {
|
|
51
|
+
const {body: connectionConfig, resource} = options;
|
|
49
52
|
try {
|
|
50
|
-
await this.register(connectionConfig);
|
|
53
|
+
await this.register(connectionConfig, resource);
|
|
51
54
|
} catch (error) {
|
|
52
55
|
LoggerProxy.error(`[WebSocketStatus] | Error in registering Websocket ${error}`, {
|
|
53
56
|
module: WEB_SOCKET_MANAGER_FILE,
|
|
@@ -84,13 +87,25 @@ export class WebSocketManager extends EventEmitter {
|
|
|
84
87
|
this.isConnectionLost = event.isConnectionLost;
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
private async register(connectionConfig: SubscribeRequest) {
|
|
90
|
+
private async register(connectionConfig: SubscribeRequest, resource: string) {
|
|
88
91
|
try {
|
|
92
|
+
// X-ORGANIZATION-ID header is only required for INT environments
|
|
93
|
+
const isIntEnv = this.webex.internal?.services?.isIntegrationEnvironment() || false;
|
|
94
|
+
const orgId = this.webex.credentials.getOrgId();
|
|
95
|
+
|
|
96
|
+
if (isIntEnv && orgId) {
|
|
97
|
+
LoggerProxy.log(`[WebSocketManager] Adding X-ORGANIZATION-ID header for INT environment`, {
|
|
98
|
+
module: WEB_SOCKET_MANAGER_FILE,
|
|
99
|
+
method: METHODS.REGISTER,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
89
103
|
const subscribeResponse: SubscribeResponse = await this.webex.request({
|
|
90
104
|
service: WCC_API_GATEWAY,
|
|
91
|
-
resource
|
|
105
|
+
resource,
|
|
92
106
|
method: HTTP_METHODS.POST,
|
|
93
107
|
body: connectionConfig,
|
|
108
|
+
headers: isIntEnv && orgId ? {'X-ORGANIZATION-ID': orgId} : undefined,
|
|
94
109
|
});
|
|
95
110
|
this.url = subscribeResponse.body.webSocketUrl;
|
|
96
111
|
} catch (e) {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '../constants';
|
|
11
11
|
import {CONNECTION_SERVICE_FILE} from '../../../constants';
|
|
12
12
|
import {SubscribeRequest} from '../../../types';
|
|
13
|
+
import {SUBSCRIBE_API} from '../../constants';
|
|
13
14
|
|
|
14
15
|
export class ConnectionService extends EventEmitter {
|
|
15
16
|
private connectionProp: ConnectionProp = {
|
|
@@ -124,7 +125,10 @@ export class ConnectionService extends EventEmitter {
|
|
|
124
125
|
});
|
|
125
126
|
const onlineStatus = navigator.onLine;
|
|
126
127
|
if (onlineStatus) {
|
|
127
|
-
await this.webSocketManager.initWebSocket({
|
|
128
|
+
await this.webSocketManager.initWebSocket({
|
|
129
|
+
body: this.subscribeRequest,
|
|
130
|
+
resource: SUBSCRIBE_API,
|
|
131
|
+
});
|
|
128
132
|
await this.clearTimerOnRestoreFailed();
|
|
129
133
|
this.isSocketReconnected = true;
|
|
130
134
|
} else {
|
package/src/services/index.ts
CHANGED
|
@@ -25,6 +25,8 @@ export default class Services {
|
|
|
25
25
|
public readonly dialer: ReturnType<typeof aqmDialer>;
|
|
26
26
|
/** WebSocket manager for handling real-time communications */
|
|
27
27
|
public readonly webSocketManager: WebSocketManager;
|
|
28
|
+
/** RTD WebSocket manager for handling realtime transcription */
|
|
29
|
+
public readonly rtdWebSocketManager: WebSocketManager;
|
|
28
30
|
/** Connection service for managing websocket connections */
|
|
29
31
|
public readonly connectionService: ConnectionService;
|
|
30
32
|
/** Singleton instance of the Services class */
|
|
@@ -39,6 +41,8 @@ export default class Services {
|
|
|
39
41
|
constructor(options: {webex: WebexSDK; connectionConfig: SubscribeRequest}) {
|
|
40
42
|
const {webex, connectionConfig} = options;
|
|
41
43
|
this.webSocketManager = new WebSocketManager({webex});
|
|
44
|
+
// TODO: Implement reconnection logic for this websocket in upcoming PR
|
|
45
|
+
this.rtdWebSocketManager = new WebSocketManager({webex});
|
|
42
46
|
const aqmReq = new AqmReqs(this.webSocketManager);
|
|
43
47
|
this.config = new AgentConfigService();
|
|
44
48
|
this.agent = routingAgent(aqmReq);
|