@webex/contact-center 0.0.0-next.1

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.
Files changed (177) hide show
  1. package/README.md +81 -0
  2. package/__mocks__/workerMock.js +15 -0
  3. package/babel.config.js +15 -0
  4. package/dist/cc.js +1416 -0
  5. package/dist/cc.js.map +1 -0
  6. package/dist/config.js +72 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/constants.js +58 -0
  9. package/dist/constants.js.map +1 -0
  10. package/dist/index.js +142 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/logger-proxy.js +115 -0
  13. package/dist/logger-proxy.js.map +1 -0
  14. package/dist/metrics/MetricsManager.js +474 -0
  15. package/dist/metrics/MetricsManager.js.map +1 -0
  16. package/dist/metrics/behavioral-events.js +322 -0
  17. package/dist/metrics/behavioral-events.js.map +1 -0
  18. package/dist/metrics/constants.js +134 -0
  19. package/dist/metrics/constants.js.map +1 -0
  20. package/dist/services/WebCallingService.js +323 -0
  21. package/dist/services/WebCallingService.js.map +1 -0
  22. package/dist/services/agent/index.js +177 -0
  23. package/dist/services/agent/index.js.map +1 -0
  24. package/dist/services/agent/types.js +137 -0
  25. package/dist/services/agent/types.js.map +1 -0
  26. package/dist/services/config/Util.js +203 -0
  27. package/dist/services/config/Util.js.map +1 -0
  28. package/dist/services/config/constants.js +221 -0
  29. package/dist/services/config/constants.js.map +1 -0
  30. package/dist/services/config/index.js +607 -0
  31. package/dist/services/config/index.js.map +1 -0
  32. package/dist/services/config/types.js +334 -0
  33. package/dist/services/config/types.js.map +1 -0
  34. package/dist/services/constants.js +117 -0
  35. package/dist/services/constants.js.map +1 -0
  36. package/dist/services/core/Err.js +43 -0
  37. package/dist/services/core/Err.js.map +1 -0
  38. package/dist/services/core/GlobalTypes.js +6 -0
  39. package/dist/services/core/GlobalTypes.js.map +1 -0
  40. package/dist/services/core/Utils.js +126 -0
  41. package/dist/services/core/Utils.js.map +1 -0
  42. package/dist/services/core/WebexRequest.js +96 -0
  43. package/dist/services/core/WebexRequest.js.map +1 -0
  44. package/dist/services/core/aqm-reqs.js +246 -0
  45. package/dist/services/core/aqm-reqs.js.map +1 -0
  46. package/dist/services/core/constants.js +109 -0
  47. package/dist/services/core/constants.js.map +1 -0
  48. package/dist/services/core/types.js +6 -0
  49. package/dist/services/core/types.js.map +1 -0
  50. package/dist/services/core/websocket/WebSocketManager.js +187 -0
  51. package/dist/services/core/websocket/WebSocketManager.js.map +1 -0
  52. package/dist/services/core/websocket/connection-service.js +111 -0
  53. package/dist/services/core/websocket/connection-service.js.map +1 -0
  54. package/dist/services/core/websocket/keepalive.worker.js +94 -0
  55. package/dist/services/core/websocket/keepalive.worker.js.map +1 -0
  56. package/dist/services/core/websocket/types.js +6 -0
  57. package/dist/services/core/websocket/types.js.map +1 -0
  58. package/dist/services/index.js +78 -0
  59. package/dist/services/index.js.map +1 -0
  60. package/dist/services/task/AutoWrapup.js +88 -0
  61. package/dist/services/task/AutoWrapup.js.map +1 -0
  62. package/dist/services/task/TaskManager.js +369 -0
  63. package/dist/services/task/TaskManager.js.map +1 -0
  64. package/dist/services/task/constants.js +58 -0
  65. package/dist/services/task/constants.js.map +1 -0
  66. package/dist/services/task/contact.js +464 -0
  67. package/dist/services/task/contact.js.map +1 -0
  68. package/dist/services/task/dialer.js +60 -0
  69. package/dist/services/task/dialer.js.map +1 -0
  70. package/dist/services/task/index.js +1188 -0
  71. package/dist/services/task/index.js.map +1 -0
  72. package/dist/services/task/types.js +214 -0
  73. package/dist/services/task/types.js.map +1 -0
  74. package/dist/types/cc.d.ts +676 -0
  75. package/dist/types/config.d.ts +66 -0
  76. package/dist/types/constants.d.ts +45 -0
  77. package/dist/types/index.d.ts +178 -0
  78. package/dist/types/logger-proxy.d.ts +71 -0
  79. package/dist/types/metrics/MetricsManager.d.ts +223 -0
  80. package/dist/types/metrics/behavioral-events.d.ts +29 -0
  81. package/dist/types/metrics/constants.d.ts +127 -0
  82. package/dist/types/services/WebCallingService.d.ts +1 -0
  83. package/dist/types/services/agent/index.d.ts +46 -0
  84. package/dist/types/services/agent/types.d.ts +413 -0
  85. package/dist/types/services/config/Util.d.ts +19 -0
  86. package/dist/types/services/config/constants.d.ts +203 -0
  87. package/dist/types/services/config/index.d.ts +171 -0
  88. package/dist/types/services/config/types.d.ts +1113 -0
  89. package/dist/types/services/constants.d.ts +97 -0
  90. package/dist/types/services/core/Err.d.ts +119 -0
  91. package/dist/types/services/core/GlobalTypes.d.ts +33 -0
  92. package/dist/types/services/core/Utils.d.ts +36 -0
  93. package/dist/types/services/core/WebexRequest.d.ts +22 -0
  94. package/dist/types/services/core/aqm-reqs.d.ts +16 -0
  95. package/dist/types/services/core/constants.d.ts +85 -0
  96. package/dist/types/services/core/types.d.ts +47 -0
  97. package/dist/types/services/core/websocket/WebSocketManager.d.ts +34 -0
  98. package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
  99. package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
  100. package/dist/types/services/core/websocket/types.d.ts +37 -0
  101. package/dist/types/services/index.d.ts +52 -0
  102. package/dist/types/services/task/AutoWrapup.d.ts +40 -0
  103. package/dist/types/services/task/TaskManager.d.ts +1 -0
  104. package/dist/types/services/task/constants.d.ts +46 -0
  105. package/dist/types/services/task/contact.d.ts +59 -0
  106. package/dist/types/services/task/dialer.d.ts +28 -0
  107. package/dist/types/services/task/index.d.ts +569 -0
  108. package/dist/types/services/task/types.d.ts +1041 -0
  109. package/dist/types/types.d.ts +452 -0
  110. package/dist/types/webex-config.d.ts +53 -0
  111. package/dist/types/webex.d.ts +7 -0
  112. package/dist/types.js +292 -0
  113. package/dist/types.js.map +1 -0
  114. package/dist/webex-config.js +60 -0
  115. package/dist/webex-config.js.map +1 -0
  116. package/dist/webex.js +99 -0
  117. package/dist/webex.js.map +1 -0
  118. package/jest.config.js +45 -0
  119. package/package.json +83 -0
  120. package/src/cc.ts +1618 -0
  121. package/src/config.ts +65 -0
  122. package/src/constants.ts +51 -0
  123. package/src/index.ts +220 -0
  124. package/src/logger-proxy.ts +110 -0
  125. package/src/metrics/MetricsManager.ts +512 -0
  126. package/src/metrics/behavioral-events.ts +332 -0
  127. package/src/metrics/constants.ts +135 -0
  128. package/src/services/WebCallingService.ts +351 -0
  129. package/src/services/agent/index.ts +149 -0
  130. package/src/services/agent/types.ts +440 -0
  131. package/src/services/config/Util.ts +261 -0
  132. package/src/services/config/constants.ts +249 -0
  133. package/src/services/config/index.ts +743 -0
  134. package/src/services/config/types.ts +1117 -0
  135. package/src/services/constants.ts +111 -0
  136. package/src/services/core/Err.ts +126 -0
  137. package/src/services/core/GlobalTypes.ts +34 -0
  138. package/src/services/core/Utils.ts +132 -0
  139. package/src/services/core/WebexRequest.ts +103 -0
  140. package/src/services/core/aqm-reqs.ts +272 -0
  141. package/src/services/core/constants.ts +106 -0
  142. package/src/services/core/types.ts +48 -0
  143. package/src/services/core/websocket/WebSocketManager.ts +196 -0
  144. package/src/services/core/websocket/connection-service.ts +142 -0
  145. package/src/services/core/websocket/keepalive.worker.js +88 -0
  146. package/src/services/core/websocket/types.ts +40 -0
  147. package/src/services/index.ts +71 -0
  148. package/src/services/task/AutoWrapup.ts +86 -0
  149. package/src/services/task/TaskManager.ts +420 -0
  150. package/src/services/task/constants.ts +52 -0
  151. package/src/services/task/contact.ts +429 -0
  152. package/src/services/task/dialer.ts +52 -0
  153. package/src/services/task/index.ts +1375 -0
  154. package/src/services/task/types.ts +1113 -0
  155. package/src/types.ts +639 -0
  156. package/src/webex-config.ts +54 -0
  157. package/src/webex.js +96 -0
  158. package/test/unit/spec/cc.ts +1985 -0
  159. package/test/unit/spec/metrics/MetricsManager.ts +491 -0
  160. package/test/unit/spec/metrics/behavioral-events.ts +102 -0
  161. package/test/unit/spec/services/WebCallingService.ts +416 -0
  162. package/test/unit/spec/services/agent/index.ts +65 -0
  163. package/test/unit/spec/services/config/index.ts +1035 -0
  164. package/test/unit/spec/services/core/Utils.ts +279 -0
  165. package/test/unit/spec/services/core/WebexRequest.ts +144 -0
  166. package/test/unit/spec/services/core/aqm-reqs.ts +570 -0
  167. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +378 -0
  168. package/test/unit/spec/services/core/websocket/connection-service.ts +178 -0
  169. package/test/unit/spec/services/task/TaskManager.ts +1351 -0
  170. package/test/unit/spec/services/task/contact.ts +204 -0
  171. package/test/unit/spec/services/task/dialer.ts +157 -0
  172. package/test/unit/spec/services/task/index.ts +1474 -0
  173. package/tsconfig.json +6 -0
  174. package/typedoc.json +37 -0
  175. package/typedoc.md +240 -0
  176. package/umd/contact-center.min.js +3 -0
  177. package/umd/contact-center.min.js.map +1 -0
@@ -0,0 +1,272 @@
1
+ import {Msg} from './GlobalTypes';
2
+ import * as Err from './Err';
3
+ import {HTTP_METHODS, WebexRequestPayload} from '../../types';
4
+ import LoggerProxy from '../../logger-proxy';
5
+ import {CbRes, Conf, ConfEmpty, Pending, Req, Res, ResEmpty} from './types';
6
+ import {TIMEOUT_REQ, METHODS} from './constants';
7
+ import {AQM_REQS_FILE} from '../../constants';
8
+ import WebexRequest from './WebexRequest';
9
+ import {WebSocketManager} from './websocket/WebSocketManager';
10
+
11
+ export default class AqmReqs {
12
+ private pendingRequests: Record<string, Pending> = {};
13
+ private pendingNotifCancelrequest: Record<string, Pending> = {};
14
+ private webexRequest: WebexRequest;
15
+ private webSocketManager: WebSocketManager;
16
+
17
+ constructor(webSocketManager: WebSocketManager) {
18
+ this.webexRequest = WebexRequest.getInstance();
19
+ this.webSocketManager = webSocketManager;
20
+ this.webSocketManager.on('message', this.onMessage.bind(this));
21
+ }
22
+
23
+ req<TRes, TErr, TReq>(c: Conf<TRes, TErr, TReq>): Res<TRes, TReq> {
24
+ return (p: TReq, cbRes?: CbRes<TRes>) => this.makeAPIRequest(c(p), cbRes);
25
+ }
26
+
27
+ reqEmpty<TRes, TErr>(c: ConfEmpty<TRes, TErr>): ResEmpty<TRes> {
28
+ return (cbRes?: CbRes<TRes>) => this.makeAPIRequest(c(), cbRes);
29
+ }
30
+
31
+ private async makeAPIRequest<TRes, TErr>(c: Req<TRes, TErr>, cbRes?: CbRes<TRes>): Promise<TRes> {
32
+ return this.createPromise(c, cbRes);
33
+ }
34
+
35
+ private createPromise<TRes, TErr>(c: Req<TRes, TErr>, cbRes?: CbRes<TRes>) {
36
+ return new Promise<TRes>((resolve, reject) => {
37
+ const keySuccess = this.bindPrint(c.notifSuccess.bind);
38
+ const keyFail = c.notifFail ? this.bindPrint(c.notifFail.bind) : null;
39
+ const keyCancel = c.notifCancel?.bind ? this.bindPrint(c.notifCancel.bind) : null;
40
+ let k = '';
41
+ if (this.pendingRequests[keySuccess]) {
42
+ k = keySuccess;
43
+ }
44
+ if (keyFail && this.pendingRequests[keyFail]) {
45
+ k += keyFail;
46
+ }
47
+ if (k && c.timeout !== 'disabled') {
48
+ reject(
49
+ new Err.Details('Service.aqm.reqs.Pending', {
50
+ key: k,
51
+ msg: 'The request has been already created, multiple requests are not allowed.',
52
+ })
53
+ );
54
+
55
+ return;
56
+ }
57
+
58
+ let isClear = false;
59
+ const clear = () => {
60
+ delete this.pendingRequests[keySuccess];
61
+ if (keyFail) {
62
+ delete this.pendingRequests[keyFail];
63
+ }
64
+ if (keyCancel) {
65
+ delete this.pendingNotifCancelrequest[keyCancel];
66
+ }
67
+ isClear = true;
68
+ };
69
+
70
+ this.pendingRequests[keySuccess] = {
71
+ check: (msg: Msg) => this.bindCheck(c.notifSuccess.bind, msg),
72
+ handle: (msg: Msg) => {
73
+ clear();
74
+ resolve(msg as any);
75
+ },
76
+ };
77
+ if (keyCancel) {
78
+ this.pendingRequests[keySuccess].alternateBind = keyCancel;
79
+ this.pendingNotifCancelrequest[keyCancel] = {
80
+ check: (msg: Msg) => this.bindCheck(c.notifCancel?.bind, msg),
81
+ handle: (msg: Msg) => {
82
+ const alternateBindKey = this.pendingNotifCancelrequest[keyCancel].alternateBind;
83
+ if (alternateBindKey) {
84
+ this.pendingRequests[alternateBindKey].handle(msg);
85
+ }
86
+ },
87
+ alternateBind: keySuccess,
88
+ };
89
+ }
90
+
91
+ if (keyFail) {
92
+ this.pendingRequests[keyFail] = {
93
+ check: (msg: Msg) => this.bindCheck(c.notifFail!.bind, msg),
94
+ handle: (msg: Msg) => {
95
+ clear();
96
+ const notifFail = c.notifFail!;
97
+ if ('errId' in notifFail) {
98
+ LoggerProxy.log(`Routing request failed: ${JSON.stringify(msg)}`, {
99
+ module: AQM_REQS_FILE,
100
+ method: METHODS.CREATE_PROMISE,
101
+ });
102
+ const eerr = new Err.Details(notifFail.errId, msg as any);
103
+ LoggerProxy.log(`Routing request failed: ${eerr}`, {
104
+ module: AQM_REQS_FILE,
105
+ method: METHODS.CREATE_PROMISE,
106
+ });
107
+ reject(eerr);
108
+ } else {
109
+ reject(notifFail.err(msg as any));
110
+ }
111
+ },
112
+ };
113
+ }
114
+ let response: WebexRequestPayload | null = null;
115
+ this.webexRequest
116
+ .request({
117
+ service: c.host ?? '',
118
+ resource: c.url,
119
+ // eslint-disable-next-line no-nested-ternary
120
+ method: c.method ? c.method : c.data ? HTTP_METHODS.POST : HTTP_METHODS.GET,
121
+
122
+ body: c.data,
123
+ })
124
+ .then((res: any) => {
125
+ response = res;
126
+ if (cbRes) {
127
+ cbRes(res);
128
+ }
129
+ })
130
+ .catch((error: WebexRequestPayload) => {
131
+ clear();
132
+ if (error?.headers) {
133
+ error.headers.Authorization = '*';
134
+ }
135
+ if (error?.headers) {
136
+ error.headers.Authorization = '*';
137
+ }
138
+ if (typeof c.err === 'function') {
139
+ reject(c.err(error));
140
+ } else if (typeof c.err === 'string') {
141
+ reject(new Err.Message(c.err));
142
+ } else {
143
+ reject(new Err.Message('Service.aqm.reqs.GenericRequestError'));
144
+ }
145
+ });
146
+
147
+ if (c.timeout !== 'disabled') {
148
+ window.setTimeout(
149
+ () => {
150
+ if (isClear) {
151
+ return;
152
+ }
153
+ clear();
154
+ if (response?.headers) {
155
+ response.headers.Authorization = '*';
156
+ }
157
+ LoggerProxy.error(`Routing request timeout${keySuccess}${response!}${c.url}`, {
158
+ module: AQM_REQS_FILE,
159
+ method: METHODS.CREATE_PROMISE,
160
+ });
161
+ reject(
162
+ new Err.Details('Service.aqm.reqs.Timeout', {
163
+ key: keySuccess,
164
+ response: response!,
165
+ })
166
+ );
167
+ },
168
+ c.timeout && c.timeout > 0 ? c.timeout : TIMEOUT_REQ
169
+ );
170
+ }
171
+ });
172
+ }
173
+
174
+ private bindPrint(bind: any) {
175
+ let result = '';
176
+ // eslint-disable-next-line no-restricted-syntax
177
+ for (const k in bind) {
178
+ if (Array.isArray(bind[k])) {
179
+ result += `${k}=[${bind[k].join(',')}],`;
180
+ } else if (typeof bind[k] === 'object' && bind[k] !== null) {
181
+ result += `${k}=(${this.bindPrint(bind[k])}),`;
182
+ } else {
183
+ result += `${k}=${bind[k]},`;
184
+ }
185
+ }
186
+
187
+ return result ? result.slice(0, -1) : result;
188
+ }
189
+
190
+ private bindCheck(bind: any, msg: any) {
191
+ // eslint-disable-next-line no-restricted-syntax
192
+ for (const k in bind) {
193
+ if (Array.isArray(bind[k])) {
194
+ // Check if the message value matches any of the values in the array
195
+ if (!bind[k].includes(msg[k])) {
196
+ return false;
197
+ }
198
+ } else if (typeof bind[k] === 'object' && bind[k] !== null) {
199
+ if (typeof msg[k] === 'object' && msg[k] !== null) {
200
+ if (!this.bindCheck(bind[k], msg[k])) {
201
+ return false;
202
+ }
203
+ } else {
204
+ return false;
205
+ }
206
+ } else if (!msg[k] || msg[k] !== bind[k]) {
207
+ return false;
208
+ }
209
+ }
210
+
211
+ return true;
212
+ }
213
+
214
+ // must be lambda
215
+ private readonly onMessage = (msg: any) => {
216
+ const event = JSON.parse(msg);
217
+ if (event.type === 'Welcome') {
218
+ LoggerProxy.info(`Welcome message from Notifs Websocket`, {
219
+ module: AQM_REQS_FILE,
220
+ method: METHODS.ON_MESSAGE,
221
+ });
222
+
223
+ return;
224
+ }
225
+
226
+ if (event.keepalive === 'true') {
227
+ LoggerProxy.info(`Keepalive from web socket`, {
228
+ module: AQM_REQS_FILE,
229
+ method: METHODS.ON_MESSAGE,
230
+ });
231
+
232
+ return;
233
+ }
234
+
235
+ if (event.type === 'AgentReloginFailed') {
236
+ LoggerProxy.info('Silently handling the agent relogin fail', {
237
+ module: AQM_REQS_FILE,
238
+ method: METHODS.ON_MESSAGE,
239
+ });
240
+ }
241
+
242
+ let isHandled = false;
243
+
244
+ const kReq = Object.keys(this.pendingRequests);
245
+ for (const thisReq of kReq) {
246
+ const req = this.pendingRequests[thisReq];
247
+ if (req.check(event)) {
248
+ req.handle(event);
249
+ isHandled = true;
250
+ break;
251
+ }
252
+ }
253
+ // pendingNotifCancelrequest stores the secondary bind key, checks for the secondary bind key and handles the event
254
+ const kReqAlt = Object.keys(this.pendingNotifCancelrequest);
255
+ for (const thisReq of kReqAlt) {
256
+ const req = this.pendingNotifCancelrequest[thisReq];
257
+ if (req.check(event)) {
258
+ req.handle(event);
259
+ isHandled = true;
260
+ }
261
+ }
262
+
263
+ // TODO: add event emitter for unhandled events to replicate event.listen or .on
264
+
265
+ if (!isHandled) {
266
+ LoggerProxy.info(`event=missingEventHandler | [AqmReqs] missing routing message handler`, {
267
+ module: AQM_REQS_FILE,
268
+ method: METHODS.ON_MESSAGE,
269
+ });
270
+ }
271
+ };
272
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Interval in milliseconds for sending keepalive pings to the worker.
3
+ * @ignore
4
+ */
5
+ export const KEEPALIVE_WORKER_INTERVAL = 4000;
6
+
7
+ /**
8
+ * Delay in milliseconds before resolving notification handlers.
9
+ * @ignore
10
+ */
11
+ export const NOTIFS_RESOLVE_DELAY = 1200;
12
+
13
+ /**
14
+ * Timeout duration in milliseconds before forcefully closing a WebSocket connection.
15
+ * @ignore
16
+ */
17
+ export const CLOSE_SOCKET_TIMEOUT_DURATION = 16000;
18
+
19
+ /**
20
+ * API endpoint used for connectivity or health checks.
21
+ * @ignore
22
+ */
23
+ export const PING_API_URL = '/health';
24
+
25
+ /**
26
+ * Timeout in milliseconds to wait for a welcome message after socket connection.
27
+ * @ignore
28
+ */
29
+ export const WELCOME_TIMEOUT = 30000;
30
+
31
+ /**
32
+ * Event name used for real-time device (RTD) ping status.
33
+ * @ignore
34
+ */
35
+ export const RTD_PING_EVENT = 'rtd-online-status';
36
+
37
+ /**
38
+ * Timeout in milliseconds for individual HTTP requests.
39
+ * @ignore
40
+ */
41
+ export const TIMEOUT_REQ = 20000;
42
+
43
+ /**
44
+ * Duration in milliseconds to wait before attempting lost connection recovery.
45
+ * @ignore
46
+ */
47
+ export const LOST_CONNECTION_RECOVERY_TIMEOUT = 50000;
48
+
49
+ /**
50
+ * Duration in milliseconds after which a WebSocket disconnect is considered allowed or expected.
51
+ * @ignore
52
+ */
53
+ export const WS_DISCONNECT_ALLOWED = 8000;
54
+
55
+ /**
56
+ * Interval in milliseconds to check for connectivity status.
57
+ * @ignore
58
+ */
59
+ export const CONNECTIVITY_CHECK_INTERVAL = 5000;
60
+
61
+ /**
62
+ * Timeout in milliseconds for cleanly closing the WebSocket.
63
+ * @ignore
64
+ */
65
+ export const CLOSE_SOCKET_TIMEOUT = 16000;
66
+
67
+ // Method names for core services
68
+ export const METHODS = {
69
+ // WebexRequest methods
70
+ REQUEST: 'request',
71
+ UPLOAD_LOGS: 'uploadLogs',
72
+
73
+ // Utils methods
74
+ GET_ERROR_DETAILS: 'getErrorDetails',
75
+ GET_COMMON_ERROR_DETAILS: 'getCommonErrorDetails',
76
+ CREATE_ERR_DETAILS_OBJECT: 'createErrDetailsObject',
77
+
78
+ // AqmReqs methods
79
+ REQ: 'req',
80
+ REQ_EMPTY: 'reqEmpty',
81
+ MAKE_API_REQUEST: 'makeAPIRequest',
82
+ CREATE_PROMISE: 'createPromise',
83
+ BIND_PRINT: 'bindPrint',
84
+ BIND_CHECK: 'bindCheck',
85
+ ON_MESSAGE: 'onMessage',
86
+
87
+ // WebSocketManager methods
88
+ INIT_WEB_SOCKET: 'initWebSocket',
89
+ CLOSE: 'close',
90
+ HANDLE_CONNECTION_LOST: 'handleConnectionLost',
91
+ REGISTER: 'register',
92
+ CONNECT: 'connect',
93
+ WEB_SOCKET_ON_CLOSE_HANDLER: 'webSocketOnCloseHandler',
94
+
95
+ // ConnectionService methods
96
+ SETUP_EVENT_LISTENERS: 'setupEventListeners',
97
+ DISPATCH_CONNECTION_EVENT: 'dispatchConnectionEvent',
98
+ CS_HANDLE_CONNECTION_LOST: 'handleConnectionLost',
99
+ CLEAR_TIMER_ON_RESTORE_FAILED: 'clearTimerOnRestoreFailed',
100
+ HANDLE_RESTORE_FAILED: 'handleRestoreFailed',
101
+ UPDATE_CONNECTION_DATA: 'updateConnectionData',
102
+ SET_CONNECTION_PROP: 'setConnectionProp',
103
+ ON_PING: 'onPing',
104
+ HANDLE_SOCKET_CLOSE: 'handleSocketClose',
105
+ ON_SOCKET_CLOSE: 'onSocketClose',
106
+ };
@@ -0,0 +1,48 @@
1
+ import {HTTP_METHODS, RequestBody, WebexRequestPayload} from '../../types';
2
+ import * as Err from './Err';
3
+ import {Msg} from './GlobalTypes';
4
+
5
+ export type Pending = {
6
+ check: (msg: Msg) => boolean;
7
+ handle: (msg: Msg) => void;
8
+ alternateBind?: string;
9
+ };
10
+
11
+ export type BindType = string | string[] | {[key: string]: BindType};
12
+ interface Bind {
13
+ type: BindType;
14
+ data?: any;
15
+ }
16
+
17
+ export type Timeout = number | 'disabled';
18
+
19
+ export type Req<TRes, TErr> = {
20
+ url: string;
21
+ host?: string;
22
+ method?: HTTP_METHODS;
23
+ err?:
24
+ | ((errObj: WebexRequestPayload) => Err.Details<'Service.reqs.generic.failure'>)
25
+ | Err.IdsMessage
26
+ | ((e: WebexRequestPayload) => Err.Message | Err.Details<Err.IdsDetails>);
27
+ notifSuccess: {bind: Bind; msg: TRes};
28
+ notifFail?:
29
+ | {
30
+ bind: Bind;
31
+ errMsg: TErr;
32
+ err: (e: TErr) => Err.Details<Err.IdsDetails>;
33
+ }
34
+ | {
35
+ bind: Bind;
36
+ errId: Err.IdsDetails;
37
+ };
38
+ data?: RequestBody;
39
+ headers?: Record<string, string>;
40
+ timeout?: Timeout;
41
+ notifCancel?: {bind: Bind; msg: TRes};
42
+ };
43
+
44
+ export type Conf<TRes, TErr, TReq> = (p: TReq) => Req<TRes, TErr>;
45
+ export type ConfEmpty<TRes, TErr> = () => Req<TRes, TErr>;
46
+ export type Res<TRes, TReq> = (p: TReq, cbRes?: CbRes<TRes>) => Promise<TRes>;
47
+ export type ResEmpty<TRes> = (cbRes?: CbRes<TRes>) => Promise<TRes>;
48
+ export type CbRes<TRes> = (res: any) => void | TRes;
@@ -0,0 +1,196 @@
1
+ import EventEmitter from 'events';
2
+ import {WebexSDK, SubscribeRequest, HTTP_METHODS} from '../../../types';
3
+ import {SUBSCRIBE_API, WCC_API_GATEWAY} from '../../constants';
4
+ import {ConnectionLostDetails} from './types';
5
+ import {CC_EVENTS, SubscribeResponse, WelcomeResponse} from '../../config/types';
6
+ import LoggerProxy from '../../../logger-proxy';
7
+ import workerScript from './keepalive.worker';
8
+ import {KEEPALIVE_WORKER_INTERVAL, CLOSE_SOCKET_TIMEOUT, METHODS} from '../constants';
9
+ import {WEB_SOCKET_MANAGER_FILE} from '../../../constants';
10
+
11
+ /**
12
+ * WebSocketManager handles the WebSocket connection for Contact Center operations.
13
+ * It manages the connection lifecycle, including registration, reconnection, and message handling.
14
+ * It also utilizes a Web Worker to manage keepalive messages and socket closure.
15
+ * @ignore
16
+ */
17
+ export class WebSocketManager extends EventEmitter {
18
+ private websocket: WebSocket;
19
+ shouldReconnect: boolean;
20
+ isSocketClosed: boolean;
21
+ private isWelcomeReceived: boolean;
22
+ private url: string | null = null;
23
+ private forceCloseWebSocketOnTimeout: boolean;
24
+ private isConnectionLost: boolean;
25
+ private webex: WebexSDK;
26
+ private welcomePromiseResolve:
27
+ | ((value: WelcomeResponse | PromiseLike<WelcomeResponse>) => void)
28
+ | null = null;
29
+
30
+ private keepaliveWorker: Worker;
31
+
32
+ constructor(options: {webex: WebexSDK}) {
33
+ super();
34
+ const {webex} = options;
35
+ this.webex = webex;
36
+ this.shouldReconnect = true;
37
+ this.websocket = {} as WebSocket;
38
+ this.isSocketClosed = false;
39
+ this.isWelcomeReceived = false;
40
+ this.forceCloseWebSocketOnTimeout = false;
41
+ this.isConnectionLost = false;
42
+
43
+ const workerScriptBlob = new Blob([workerScript], {type: 'application/javascript'});
44
+ this.keepaliveWorker = new Worker(URL.createObjectURL(workerScriptBlob));
45
+ }
46
+
47
+ async initWebSocket(options: {body: SubscribeRequest}): Promise<WelcomeResponse> {
48
+ const connectionConfig = options.body;
49
+ await this.register(connectionConfig);
50
+
51
+ return new Promise((resolve, reject) => {
52
+ this.welcomePromiseResolve = resolve;
53
+ this.connect().catch((error) => {
54
+ LoggerProxy.error(`[WebSocketStatus] | Error in connecting Websocket ${error}`, {
55
+ module: WEB_SOCKET_MANAGER_FILE,
56
+ method: METHODS.INIT_WEB_SOCKET,
57
+ });
58
+ reject(error);
59
+ });
60
+ });
61
+ }
62
+
63
+ close(shouldReconnect: boolean, reason = 'Unknown') {
64
+ if (!this.isSocketClosed && this.shouldReconnect) {
65
+ this.shouldReconnect = shouldReconnect;
66
+ this.websocket.close();
67
+ this.keepaliveWorker.postMessage({type: 'terminate'});
68
+ LoggerProxy.log(
69
+ `[WebSocketStatus] | event=webSocketClose | WebSocket connection closed manually REASON: ${reason}`,
70
+ {module: WEB_SOCKET_MANAGER_FILE, method: METHODS.CLOSE}
71
+ );
72
+ }
73
+ }
74
+
75
+ handleConnectionLost(event: ConnectionLostDetails) {
76
+ this.isConnectionLost = event.isConnectionLost;
77
+ }
78
+
79
+ private async register(connectionConfig: SubscribeRequest) {
80
+ try {
81
+ const subscribeResponse: SubscribeResponse = await this.webex.request({
82
+ service: WCC_API_GATEWAY,
83
+ resource: SUBSCRIBE_API,
84
+ method: HTTP_METHODS.POST,
85
+ body: connectionConfig,
86
+ });
87
+ this.url = subscribeResponse.body.webSocketUrl;
88
+ } catch (e) {
89
+ LoggerProxy.error(
90
+ `Register API Failed, Request to RoutingNotifs websocket registration API failed ${e}`,
91
+ {module: WEB_SOCKET_MANAGER_FILE, method: METHODS.REGISTER}
92
+ );
93
+ }
94
+ }
95
+
96
+ private async connect() {
97
+ if (!this.url) {
98
+ return undefined;
99
+ }
100
+ LoggerProxy.log(
101
+ `[WebSocketStatus] | event=webSocketConnecting | Connecting to WebSocket: ${this.url}`,
102
+ {module: WEB_SOCKET_MANAGER_FILE, method: METHODS.CONNECT}
103
+ );
104
+ this.websocket = new WebSocket(this.url);
105
+
106
+ return new Promise((resolve, reject) => {
107
+ this.websocket.onopen = () => {
108
+ this.isSocketClosed = false;
109
+ this.shouldReconnect = true;
110
+
111
+ this.websocket.send(JSON.stringify({keepalive: 'true'}));
112
+ this.keepaliveWorker.onmessage = (keepAliveEvent: {data: any}) => {
113
+ if (keepAliveEvent?.data?.type === 'keepalive') {
114
+ this.websocket.send(JSON.stringify({keepalive: 'true'}));
115
+ }
116
+
117
+ if (keepAliveEvent?.data?.type === 'closeSocket' && this.isConnectionLost) {
118
+ this.forceCloseWebSocketOnTimeout = true;
119
+ this.close(true, 'WebSocket did not auto close within 16 secs');
120
+ LoggerProxy.error(
121
+ '[webSocketTimeout] | event=webSocketTimeout | WebSocket connection closed forcefully',
122
+ {module: WEB_SOCKET_MANAGER_FILE, method: METHODS.CONNECT}
123
+ );
124
+ }
125
+ };
126
+
127
+ this.keepaliveWorker.postMessage({
128
+ type: 'start',
129
+ intervalDuration: KEEPALIVE_WORKER_INTERVAL, // Keepalive interval
130
+ isSocketClosed: this.isSocketClosed,
131
+ closeSocketTimeout: CLOSE_SOCKET_TIMEOUT, // Close socket timeout
132
+ });
133
+ };
134
+
135
+ this.websocket.onerror = (event: any) => {
136
+ LoggerProxy.error(
137
+ `[WebSocketStatus] | event=socketConnectionFailed | WebSocket connection failed ${event}`,
138
+ {module: WEB_SOCKET_MANAGER_FILE, method: METHODS.CONNECT}
139
+ );
140
+ reject();
141
+ };
142
+
143
+ this.websocket.onclose = async (event: any) => {
144
+ this.webSocketOnCloseHandler(event);
145
+ };
146
+
147
+ this.websocket.onmessage = (e: MessageEvent) => {
148
+ this.emit('message', e.data);
149
+ const eventData = JSON.parse(e.data);
150
+
151
+ if (eventData.type === CC_EVENTS.WELCOME) {
152
+ this.isWelcomeReceived = true;
153
+ if (this.welcomePromiseResolve) {
154
+ this.welcomePromiseResolve(eventData.data as WelcomeResponse);
155
+ this.welcomePromiseResolve = null;
156
+ }
157
+ }
158
+
159
+ if (eventData.type === 'AGENT_MULTI_LOGIN') {
160
+ this.close(false, 'multiLogin');
161
+ LoggerProxy.error(
162
+ '[WebSocketStatus] | event=agentMultiLogin | WebSocket connection closed by agent multiLogin',
163
+ {module: WEB_SOCKET_MANAGER_FILE, method: METHODS.CONNECT}
164
+ );
165
+ }
166
+ };
167
+ });
168
+ }
169
+
170
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
171
+ private async webSocketOnCloseHandler(event: any) {
172
+ this.isSocketClosed = true;
173
+ this.keepaliveWorker.postMessage({type: 'terminate'});
174
+ if (this.shouldReconnect) {
175
+ this.emit('socketClose');
176
+ let issueReason;
177
+ if (this.forceCloseWebSocketOnTimeout) {
178
+ issueReason = 'WebSocket auto close timed out. Forcefully closed websocket.';
179
+ } else {
180
+ const onlineStatus = navigator.onLine;
181
+ LoggerProxy.info(`[WebSocketStatus] | desktop online status is ${onlineStatus}`, {
182
+ module: WEB_SOCKET_MANAGER_FILE,
183
+ method: METHODS.WEB_SOCKET_ON_CLOSE_HANDLER,
184
+ });
185
+ issueReason = !onlineStatus
186
+ ? 'network issue'
187
+ : 'missing keepalive from either desktop or notif service';
188
+ }
189
+ LoggerProxy.error(
190
+ `[WebSocketStatus] | event=webSocketClose | WebSocket connection closed REASON: ${issueReason}`,
191
+ {module: WEB_SOCKET_MANAGER_FILE, method: METHODS.WEB_SOCKET_ON_CLOSE_HANDLER}
192
+ );
193
+ this.forceCloseWebSocketOnTimeout = false;
194
+ }
195
+ }
196
+ }