@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.
- package/README.md +81 -0
- package/__mocks__/workerMock.js +15 -0
- package/babel.config.js +15 -0
- package/dist/cc.js +1416 -0
- package/dist/cc.js.map +1 -0
- package/dist/config.js +72 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.js +58 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.js +142 -0
- package/dist/index.js.map +1 -0
- package/dist/logger-proxy.js +115 -0
- package/dist/logger-proxy.js.map +1 -0
- package/dist/metrics/MetricsManager.js +474 -0
- package/dist/metrics/MetricsManager.js.map +1 -0
- package/dist/metrics/behavioral-events.js +322 -0
- package/dist/metrics/behavioral-events.js.map +1 -0
- package/dist/metrics/constants.js +134 -0
- package/dist/metrics/constants.js.map +1 -0
- package/dist/services/WebCallingService.js +323 -0
- package/dist/services/WebCallingService.js.map +1 -0
- package/dist/services/agent/index.js +177 -0
- package/dist/services/agent/index.js.map +1 -0
- package/dist/services/agent/types.js +137 -0
- package/dist/services/agent/types.js.map +1 -0
- package/dist/services/config/Util.js +203 -0
- package/dist/services/config/Util.js.map +1 -0
- package/dist/services/config/constants.js +221 -0
- package/dist/services/config/constants.js.map +1 -0
- package/dist/services/config/index.js +607 -0
- package/dist/services/config/index.js.map +1 -0
- package/dist/services/config/types.js +334 -0
- package/dist/services/config/types.js.map +1 -0
- package/dist/services/constants.js +117 -0
- package/dist/services/constants.js.map +1 -0
- package/dist/services/core/Err.js +43 -0
- package/dist/services/core/Err.js.map +1 -0
- package/dist/services/core/GlobalTypes.js +6 -0
- package/dist/services/core/GlobalTypes.js.map +1 -0
- package/dist/services/core/Utils.js +126 -0
- package/dist/services/core/Utils.js.map +1 -0
- package/dist/services/core/WebexRequest.js +96 -0
- package/dist/services/core/WebexRequest.js.map +1 -0
- package/dist/services/core/aqm-reqs.js +246 -0
- package/dist/services/core/aqm-reqs.js.map +1 -0
- package/dist/services/core/constants.js +109 -0
- package/dist/services/core/constants.js.map +1 -0
- package/dist/services/core/types.js +6 -0
- package/dist/services/core/types.js.map +1 -0
- package/dist/services/core/websocket/WebSocketManager.js +187 -0
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -0
- package/dist/services/core/websocket/connection-service.js +111 -0
- package/dist/services/core/websocket/connection-service.js.map +1 -0
- package/dist/services/core/websocket/keepalive.worker.js +94 -0
- package/dist/services/core/websocket/keepalive.worker.js.map +1 -0
- package/dist/services/core/websocket/types.js +6 -0
- package/dist/services/core/websocket/types.js.map +1 -0
- package/dist/services/index.js +78 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/task/AutoWrapup.js +88 -0
- package/dist/services/task/AutoWrapup.js.map +1 -0
- package/dist/services/task/TaskManager.js +369 -0
- package/dist/services/task/TaskManager.js.map +1 -0
- package/dist/services/task/constants.js +58 -0
- package/dist/services/task/constants.js.map +1 -0
- package/dist/services/task/contact.js +464 -0
- package/dist/services/task/contact.js.map +1 -0
- package/dist/services/task/dialer.js +60 -0
- package/dist/services/task/dialer.js.map +1 -0
- package/dist/services/task/index.js +1188 -0
- package/dist/services/task/index.js.map +1 -0
- package/dist/services/task/types.js +214 -0
- package/dist/services/task/types.js.map +1 -0
- package/dist/types/cc.d.ts +676 -0
- package/dist/types/config.d.ts +66 -0
- package/dist/types/constants.d.ts +45 -0
- package/dist/types/index.d.ts +178 -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 +127 -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 +19 -0
- package/dist/types/services/config/constants.d.ts +203 -0
- package/dist/types/services/config/index.d.ts +171 -0
- package/dist/types/services/config/types.d.ts +1113 -0
- package/dist/types/services/constants.d.ts +97 -0
- package/dist/types/services/core/Err.d.ts +119 -0
- package/dist/types/services/core/GlobalTypes.d.ts +33 -0
- package/dist/types/services/core/Utils.d.ts +36 -0
- package/dist/types/services/core/WebexRequest.d.ts +22 -0
- package/dist/types/services/core/aqm-reqs.d.ts +16 -0
- package/dist/types/services/core/constants.d.ts +85 -0
- package/dist/types/services/core/types.d.ts +47 -0
- package/dist/types/services/core/websocket/WebSocketManager.d.ts +34 -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 +52 -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/constants.d.ts +46 -0
- package/dist/types/services/task/contact.d.ts +59 -0
- package/dist/types/services/task/dialer.d.ts +28 -0
- package/dist/types/services/task/index.d.ts +569 -0
- package/dist/types/services/task/types.d.ts +1041 -0
- package/dist/types/types.d.ts +452 -0
- package/dist/types/webex-config.d.ts +53 -0
- package/dist/types/webex.d.ts +7 -0
- package/dist/types.js +292 -0
- package/dist/types.js.map +1 -0
- package/dist/webex-config.js +60 -0
- package/dist/webex-config.js.map +1 -0
- package/dist/webex.js +99 -0
- package/dist/webex.js.map +1 -0
- package/jest.config.js +45 -0
- package/package.json +83 -0
- package/src/cc.ts +1618 -0
- package/src/config.ts +65 -0
- package/src/constants.ts +51 -0
- package/src/index.ts +220 -0
- package/src/logger-proxy.ts +110 -0
- package/src/metrics/MetricsManager.ts +512 -0
- package/src/metrics/behavioral-events.ts +332 -0
- package/src/metrics/constants.ts +135 -0
- package/src/services/WebCallingService.ts +351 -0
- package/src/services/agent/index.ts +149 -0
- package/src/services/agent/types.ts +440 -0
- package/src/services/config/Util.ts +261 -0
- package/src/services/config/constants.ts +249 -0
- package/src/services/config/index.ts +743 -0
- package/src/services/config/types.ts +1117 -0
- package/src/services/constants.ts +111 -0
- package/src/services/core/Err.ts +126 -0
- package/src/services/core/GlobalTypes.ts +34 -0
- package/src/services/core/Utils.ts +132 -0
- package/src/services/core/WebexRequest.ts +103 -0
- package/src/services/core/aqm-reqs.ts +272 -0
- package/src/services/core/constants.ts +106 -0
- package/src/services/core/types.ts +48 -0
- package/src/services/core/websocket/WebSocketManager.ts +196 -0
- package/src/services/core/websocket/connection-service.ts +142 -0
- package/src/services/core/websocket/keepalive.worker.js +88 -0
- package/src/services/core/websocket/types.ts +40 -0
- package/src/services/index.ts +71 -0
- package/src/services/task/AutoWrapup.ts +86 -0
- package/src/services/task/TaskManager.ts +420 -0
- package/src/services/task/constants.ts +52 -0
- package/src/services/task/contact.ts +429 -0
- package/src/services/task/dialer.ts +52 -0
- package/src/services/task/index.ts +1375 -0
- package/src/services/task/types.ts +1113 -0
- package/src/types.ts +639 -0
- package/src/webex-config.ts +54 -0
- package/src/webex.js +96 -0
- package/test/unit/spec/cc.ts +1985 -0
- package/test/unit/spec/metrics/MetricsManager.ts +491 -0
- package/test/unit/spec/metrics/behavioral-events.ts +102 -0
- package/test/unit/spec/services/WebCallingService.ts +416 -0
- package/test/unit/spec/services/agent/index.ts +65 -0
- package/test/unit/spec/services/config/index.ts +1035 -0
- package/test/unit/spec/services/core/Utils.ts +279 -0
- package/test/unit/spec/services/core/WebexRequest.ts +144 -0
- package/test/unit/spec/services/core/aqm-reqs.ts +570 -0
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +378 -0
- package/test/unit/spec/services/core/websocket/connection-service.ts +178 -0
- package/test/unit/spec/services/task/TaskManager.ts +1351 -0
- package/test/unit/spec/services/task/contact.ts +204 -0
- package/test/unit/spec/services/task/dialer.ts +157 -0
- package/test/unit/spec/services/task/index.ts +1474 -0
- package/tsconfig.json +6 -0
- package/typedoc.json +37 -0
- package/typedoc.md +240 -0
- package/umd/contact-center.min.js +3 -0
- package/umd/contact-center.min.js.map +1 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import {WebSocketManager} from '../../../../../../src/services/core/websocket/WebSocketManager';
|
|
3
|
+
import {WebexSDK, SubscribeRequest} from '../../../../../../src/types';
|
|
4
|
+
import {SUBSCRIBE_API, WCC_API_GATEWAY} from '../../../../../../src/services/constants';
|
|
5
|
+
import {WEB_SOCKET_MANAGER_FILE} from '../../../../../../src/constants';
|
|
6
|
+
import LoggerProxy from '../../../../../../src/logger-proxy';
|
|
7
|
+
|
|
8
|
+
jest.mock('../../../../../../src/services/core/WebexRequest');
|
|
9
|
+
jest.mock('../../../../../../src/logger-proxy', () => ({
|
|
10
|
+
__esModule: true,
|
|
11
|
+
default: {
|
|
12
|
+
log: jest.fn(),
|
|
13
|
+
error: jest.fn(),
|
|
14
|
+
info: jest.fn(),
|
|
15
|
+
initialize: jest.fn(),
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
class MockWebSocket {
|
|
20
|
+
static inst: MockWebSocket;
|
|
21
|
+
onopen: () => void = () => { };
|
|
22
|
+
onerror: (event: any) => void = () => { };
|
|
23
|
+
onclose: (event: any) => void = () => { };
|
|
24
|
+
onmessage: (msg: any) => void = () => { };
|
|
25
|
+
close = jest.fn();
|
|
26
|
+
send = jest.fn();
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
MockWebSocket.inst = this;
|
|
30
|
+
setTimeout(() => {
|
|
31
|
+
this.onopen();
|
|
32
|
+
}, 10);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Mock CustomEvent class
|
|
37
|
+
class MockCustomEvent<T> extends Event {
|
|
38
|
+
detail: T;
|
|
39
|
+
|
|
40
|
+
constructor(event: string, params: { detail: T }) {
|
|
41
|
+
super(event);
|
|
42
|
+
this.detail = params.detail;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
global.CustomEvent = MockCustomEvent as any;
|
|
47
|
+
|
|
48
|
+
// Mock MessageEvent class
|
|
49
|
+
class MockMessageEvent extends Event {
|
|
50
|
+
data: any;
|
|
51
|
+
|
|
52
|
+
constructor(type: string, eventInitDict: { data: any }) {
|
|
53
|
+
super(type);
|
|
54
|
+
this.data = eventInitDict.data;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
global.MessageEvent = MockMessageEvent as any;
|
|
59
|
+
|
|
60
|
+
describe('WebSocketManager', () => {
|
|
61
|
+
let webSocketManager: WebSocketManager;
|
|
62
|
+
let mockWebex: WebexSDK;
|
|
63
|
+
let mockWorker: any;
|
|
64
|
+
|
|
65
|
+
const fakeSubscribeRequest: SubscribeRequest = {
|
|
66
|
+
force: true,
|
|
67
|
+
isKeepAliveEnabled: false,
|
|
68
|
+
clientType: 'WebexCCSDK',
|
|
69
|
+
allowMultiLogin: true,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
jest.clearAllMocks();
|
|
74
|
+
|
|
75
|
+
mockWebex = {
|
|
76
|
+
request: jest.fn(),
|
|
77
|
+
} as unknown as WebexSDK;
|
|
78
|
+
|
|
79
|
+
mockWorker = {
|
|
80
|
+
postMessage: jest.fn(),
|
|
81
|
+
onmessage: jest.fn(),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
global.Worker = jest.fn(() => mockWorker) as any;
|
|
85
|
+
global.WebSocket = MockWebSocket as any;
|
|
86
|
+
|
|
87
|
+
global.Blob = function (content: any[], options: any) {
|
|
88
|
+
return { content, options };
|
|
89
|
+
} as any;
|
|
90
|
+
|
|
91
|
+
global.URL.createObjectURL = function (blob: Blob) {
|
|
92
|
+
return 'blob:http://localhost:3000/12345';
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
webSocketManager = new WebSocketManager({ webex: mockWebex });
|
|
96
|
+
|
|
97
|
+
setTimeout(() => {
|
|
98
|
+
MockWebSocket.inst.onopen();
|
|
99
|
+
MockWebSocket.inst.onmessage({ data: JSON.stringify({ type: "Welcome" }) });
|
|
100
|
+
}, 1);
|
|
101
|
+
|
|
102
|
+
console.log = jest.fn();
|
|
103
|
+
console.error = jest.fn();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should initialize WebSocketManager', () => {
|
|
107
|
+
expect(webSocketManager).toBeDefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should register and connect to WebSocket', async () => {
|
|
111
|
+
const subscribeResponse = {
|
|
112
|
+
body: {
|
|
113
|
+
webSocketUrl: 'wss://fake-url',
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
(mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
|
|
118
|
+
|
|
119
|
+
await webSocketManager.initWebSocket({ body: fakeSubscribeRequest });
|
|
120
|
+
|
|
121
|
+
expect(mockWebex.request).toHaveBeenCalledWith({
|
|
122
|
+
service: WCC_API_GATEWAY,
|
|
123
|
+
resource: SUBSCRIBE_API,
|
|
124
|
+
method: 'POST',
|
|
125
|
+
body: fakeSubscribeRequest,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should close WebSocket connection', async () => {
|
|
130
|
+
const subscribeResponse = {
|
|
131
|
+
body: {
|
|
132
|
+
webSocketUrl: 'wss://fake-url',
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
(mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
|
|
137
|
+
|
|
138
|
+
await webSocketManager.initWebSocket({ body: fakeSubscribeRequest });
|
|
139
|
+
|
|
140
|
+
webSocketManager.close(true, 'Test reason');
|
|
141
|
+
|
|
142
|
+
expect(MockWebSocket.inst.close).toHaveBeenCalled();
|
|
143
|
+
expect(mockWorker.postMessage).toHaveBeenCalledWith({ type: 'terminate' });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should handle WebSocket keepalive messages', async () => {
|
|
147
|
+
const subscribeResponse = {
|
|
148
|
+
body: {
|
|
149
|
+
webSocketUrl: 'wss://fake-url',
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
(mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
|
|
154
|
+
|
|
155
|
+
await webSocketManager.initWebSocket({ body: fakeSubscribeRequest });
|
|
156
|
+
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
MockWebSocket.inst.onopen();
|
|
159
|
+
MockWebSocket.inst.onmessage({ data: JSON.stringify({ type: 'keepalive' }) });
|
|
160
|
+
mockWorker.onmessage({
|
|
161
|
+
data: {
|
|
162
|
+
type: 'keepalive'
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}, 1);
|
|
166
|
+
|
|
167
|
+
expect(MockWebSocket.inst.send).toHaveBeenCalledWith(JSON.stringify({ keepalive: 'true' }));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should handle WebSocket close due to network issue', async () => {
|
|
171
|
+
const subscribeResponse = {
|
|
172
|
+
body: {
|
|
173
|
+
webSocketUrl: 'wss://fake-url',
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
(mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
|
|
178
|
+
|
|
179
|
+
await webSocketManager.initWebSocket({ body: fakeSubscribeRequest });
|
|
180
|
+
|
|
181
|
+
// Mock navigator.onLine to simulate network issue
|
|
182
|
+
Object.defineProperty(global, 'navigator', {
|
|
183
|
+
value: {
|
|
184
|
+
onLine: false,
|
|
185
|
+
},
|
|
186
|
+
configurable: true,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Simulate the WebSocket close event
|
|
190
|
+
setTimeout(() => {
|
|
191
|
+
MockWebSocket.inst.onclose({
|
|
192
|
+
wasClean: false,
|
|
193
|
+
code: 1006,
|
|
194
|
+
reason: 'network issue',
|
|
195
|
+
target: MockWebSocket.inst,
|
|
196
|
+
});
|
|
197
|
+
}, 1);
|
|
198
|
+
|
|
199
|
+
// Wait for the close event to be handled
|
|
200
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
201
|
+
|
|
202
|
+
expect(mockWorker.postMessage).toHaveBeenCalledWith({ type: 'terminate' });
|
|
203
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith(
|
|
204
|
+
'[WebSocketStatus] | desktop online status is false',
|
|
205
|
+
{ module: WEB_SOCKET_MANAGER_FILE, method: 'webSocketOnCloseHandler' }
|
|
206
|
+
);
|
|
207
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
208
|
+
'[WebSocketStatus] | event=webSocketClose | WebSocket connection closed REASON: network issue',
|
|
209
|
+
{ module: WEB_SOCKET_MANAGER_FILE, method: 'webSocketOnCloseHandler' }
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// Restore navigator.onLine to true
|
|
213
|
+
Object.defineProperty(global, 'navigator', {
|
|
214
|
+
value: {
|
|
215
|
+
onLine: true,
|
|
216
|
+
},
|
|
217
|
+
configurable: true,
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should handle WebSocket error event', async () => {
|
|
222
|
+
const subscribeResponse = {
|
|
223
|
+
body: {
|
|
224
|
+
webSocketUrl: 'wss://fake-url',
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
(mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
|
|
229
|
+
|
|
230
|
+
await webSocketManager.initWebSocket({ body: fakeSubscribeRequest });
|
|
231
|
+
|
|
232
|
+
const errorEvent = new Event('error');
|
|
233
|
+
MockWebSocket.inst.onerror(errorEvent);
|
|
234
|
+
|
|
235
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
236
|
+
'[WebSocketStatus] | event=socketConnectionFailed | WebSocket connection failed [object Event]',
|
|
237
|
+
{ module: WEB_SOCKET_MANAGER_FILE, method: 'connect' }
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should handle WebSocket message event with AGENT_MULTI_LOGIN', async () => {
|
|
242
|
+
const subscribeResponse = {
|
|
243
|
+
body: {
|
|
244
|
+
webSocketUrl: 'wss://fake-url',
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
(mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
|
|
249
|
+
|
|
250
|
+
await webSocketManager.initWebSocket({ body: fakeSubscribeRequest });
|
|
251
|
+
|
|
252
|
+
const messageEvent = new MessageEvent('message', {
|
|
253
|
+
data: JSON.stringify({ type: 'AGENT_MULTI_LOGIN' }),
|
|
254
|
+
});
|
|
255
|
+
MockWebSocket.inst.onmessage(messageEvent);
|
|
256
|
+
|
|
257
|
+
expect(MockWebSocket.inst.close).toHaveBeenCalled();
|
|
258
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
259
|
+
'[WebSocketStatus] | event=agentMultiLogin | WebSocket connection closed by agent multiLogin',
|
|
260
|
+
{ module: WEB_SOCKET_MANAGER_FILE, method: 'connect' }
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should handle WebSocket message event with Welcome', async () => {
|
|
265
|
+
const subscribeResponse = {
|
|
266
|
+
body: {
|
|
267
|
+
webSocketUrl: 'wss://fake-url',
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
(mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
|
|
272
|
+
|
|
273
|
+
await webSocketManager.initWebSocket({ body: fakeSubscribeRequest });
|
|
274
|
+
|
|
275
|
+
const messageEvent = new MessageEvent('message', {
|
|
276
|
+
data: JSON.stringify({ type: 'Welcome', data: { someData: 'data' } }),
|
|
277
|
+
});
|
|
278
|
+
MockWebSocket.inst.onmessage(messageEvent);
|
|
279
|
+
|
|
280
|
+
expect(webSocketManager['isWelcomeReceived']).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should handle WebSocket close with forceCloseWebSocketOnTimeout', async () => {
|
|
284
|
+
const subscribeResponse = {
|
|
285
|
+
body: {
|
|
286
|
+
webSocketUrl: 'wss://fake-url',
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
(mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
|
|
291
|
+
|
|
292
|
+
await webSocketManager.initWebSocket({ body: fakeSubscribeRequest });
|
|
293
|
+
|
|
294
|
+
webSocketManager['forceCloseWebSocketOnTimeout'] = true;
|
|
295
|
+
|
|
296
|
+
// Simulate the WebSocket close event
|
|
297
|
+
setTimeout(() => {
|
|
298
|
+
MockWebSocket.inst.onclose({
|
|
299
|
+
wasClean: false,
|
|
300
|
+
code: 1006,
|
|
301
|
+
reason: 'timeout',
|
|
302
|
+
target: MockWebSocket.inst,
|
|
303
|
+
});
|
|
304
|
+
}, 1);
|
|
305
|
+
|
|
306
|
+
webSocketManager.shouldReconnect = true;
|
|
307
|
+
|
|
308
|
+
// Wait for the close event to be handled
|
|
309
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
310
|
+
|
|
311
|
+
expect(mockWorker.postMessage).toHaveBeenCalledWith({ type: 'terminate' });
|
|
312
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
313
|
+
'[WebSocketStatus] | event=webSocketClose | WebSocket connection closed REASON: WebSocket auto close timed out. Forcefully closed websocket.',
|
|
314
|
+
{ module: WEB_SOCKET_MANAGER_FILE, method: 'webSocketOnCloseHandler' }
|
|
315
|
+
);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('should handle WebSocket close without reconnect', async () => {
|
|
319
|
+
const subscribeResponse = {
|
|
320
|
+
body: {
|
|
321
|
+
webSocketUrl: 'wss://fake-url',
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
(mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
|
|
326
|
+
|
|
327
|
+
await webSocketManager.initWebSocket({ body: fakeSubscribeRequest });
|
|
328
|
+
webSocketManager.shouldReconnect = false;
|
|
329
|
+
// Simulate the WebSocket close event
|
|
330
|
+
setTimeout(() => {
|
|
331
|
+
MockWebSocket.inst.onclose({
|
|
332
|
+
wasClean: false,
|
|
333
|
+
code: 1006,
|
|
334
|
+
reason: 'no reconnect',
|
|
335
|
+
target: MockWebSocket.inst,
|
|
336
|
+
});
|
|
337
|
+
}, 1);
|
|
338
|
+
|
|
339
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
340
|
+
|
|
341
|
+
expect(mockWorker.postMessage).toHaveBeenCalledWith({ type: 'terminate' });
|
|
342
|
+
expect(LoggerProxy.error).not.toHaveBeenCalledWith(
|
|
343
|
+
'[WebSocketStatus] | event=webSocketClose | WebSocket connection closed REASON: no reconnect',
|
|
344
|
+
{ module: WEB_SOCKET_MANAGER_FILE, method: 'webSocketOnCloseHandler' }
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should handle WebSocket close with clean close', async () => {
|
|
349
|
+
const subscribeResponse = {
|
|
350
|
+
body: {
|
|
351
|
+
webSocketUrl: 'wss://fake-url',
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
(mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
|
|
356
|
+
|
|
357
|
+
await webSocketManager.initWebSocket({ body: fakeSubscribeRequest });
|
|
358
|
+
|
|
359
|
+
// Simulate the WebSocket close event
|
|
360
|
+
setTimeout(() => {
|
|
361
|
+
MockWebSocket.inst.onclose({
|
|
362
|
+
wasClean: true,
|
|
363
|
+
code: 1000,
|
|
364
|
+
reason: 'clean close',
|
|
365
|
+
target: MockWebSocket.inst,
|
|
366
|
+
});
|
|
367
|
+
}, 1);
|
|
368
|
+
|
|
369
|
+
// Wait for the close event to be handled
|
|
370
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
371
|
+
|
|
372
|
+
expect(mockWorker.postMessage).toHaveBeenCalledWith({ type: 'terminate' });
|
|
373
|
+
expect(LoggerProxy.error).not.toHaveBeenCalledWith(
|
|
374
|
+
'[WebSocketStatus] | event=webSocketClose | WebSocket connection closed REASON: clean close',
|
|
375
|
+
{ module: WEB_SOCKET_MANAGER_FILE, method: 'webSocketOnCloseHandler' }
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import {ConnectionService} from '../../../../../../src/services/core/websocket/connection-service';
|
|
2
|
+
import {WebSocketManager} from '../../../../../../src/services/core/websocket/WebSocketManager';
|
|
3
|
+
import {SubscribeRequest} from '../../../../../../src/types';
|
|
4
|
+
import LoggerProxy from '../../../../../../src/logger-proxy';
|
|
5
|
+
import {CONNECTIVITY_CHECK_INTERVAL} from '../../../../../../src/services/core/constants';
|
|
6
|
+
import { CONNECTION_SERVICE_FILE } from '../../../../../../src/constants';
|
|
7
|
+
|
|
8
|
+
jest.mock('../../../../../../src/services/core/websocket/WebSocketManager');
|
|
9
|
+
jest.mock('../../../../../../src/logger-proxy', () => ({
|
|
10
|
+
__esModule: true,
|
|
11
|
+
default: {
|
|
12
|
+
log: jest.fn(),
|
|
13
|
+
error: jest.fn(),
|
|
14
|
+
info: jest.fn(),
|
|
15
|
+
initialize: jest.fn(),
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
// Mock CustomEvent class
|
|
20
|
+
class MockCustomEvent<T> extends Event {
|
|
21
|
+
detail: T;
|
|
22
|
+
|
|
23
|
+
constructor(event: string, params: {detail: T}) {
|
|
24
|
+
super(event);
|
|
25
|
+
this.detail = params.detail;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
global.CustomEvent = MockCustomEvent as any;
|
|
30
|
+
|
|
31
|
+
describe('ConnectionService', () => {
|
|
32
|
+
let connectionService: ConnectionService;
|
|
33
|
+
let mockWebSocketManager: jest.Mocked<WebSocketManager>;
|
|
34
|
+
const mockSubscribeRequest: SubscribeRequest = {
|
|
35
|
+
force: true,
|
|
36
|
+
isKeepAliveEnabled: false,
|
|
37
|
+
clientType: 'WebexCCSDK',
|
|
38
|
+
allowMultiLogin: true,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
jest.clearAllMocks();
|
|
43
|
+
jest.useFakeTimers();
|
|
44
|
+
|
|
45
|
+
mockWebSocketManager = new WebSocketManager({
|
|
46
|
+
webex: {} as any,
|
|
47
|
+
}) as jest.Mocked<WebSocketManager>;
|
|
48
|
+
mockWebSocketManager.initWebSocket = jest.fn().mockResolvedValue({});
|
|
49
|
+
|
|
50
|
+
connectionService = new ConnectionService({
|
|
51
|
+
webSocketManager: mockWebSocketManager,
|
|
52
|
+
subscribeRequest: mockSubscribeRequest,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
jest.useRealTimers();
|
|
58
|
+
jest.clearAllTimers();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should initialize ConnectionService', () => {
|
|
62
|
+
expect(connectionService).toBeDefined();
|
|
63
|
+
expect(connectionService['subscribeRequest']).toEqual(mockSubscribeRequest);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should set connection properties', () => {
|
|
67
|
+
const newProps = {lostConnectionRecoveryTimeout: 30000};
|
|
68
|
+
connectionService.setConnectionProp(newProps);
|
|
69
|
+
expect(connectionService['connectionProp']).toEqual(newProps);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should handle ping message and update connection data', () => {
|
|
73
|
+
const pingMessage = JSON.stringify({keepalive: 'true'});
|
|
74
|
+
connectionService['onPing'](pingMessage);
|
|
75
|
+
expect(connectionService['isKeepAlive']).toBe(true);
|
|
76
|
+
expect(connectionService['isConnectionLost']).toBe(false);
|
|
77
|
+
expect(connectionService['isRestoreFailed']).toBe(false);
|
|
78
|
+
expect(connectionService['isSocketReconnected']).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should handle connection lost', () => {
|
|
82
|
+
connectionService['handleConnectionLost']();
|
|
83
|
+
expect(connectionService['isConnectionLost']).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should clear timer on restore failed', async () => {
|
|
87
|
+
connectionService['reconnectInterval'] = setInterval(() => {}, 1000);
|
|
88
|
+
jest.spyOn(global, 'clearInterval');
|
|
89
|
+
await connectionService['clearTimerOnRestoreFailed']();
|
|
90
|
+
expect(clearInterval).toHaveBeenCalledWith(connectionService['reconnectInterval']);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should handle restore failed', async () => {
|
|
94
|
+
await connectionService['handleRestoreFailed']();
|
|
95
|
+
expect(connectionService['isRestoreFailed']).toBe(true);
|
|
96
|
+
expect(connectionService['webSocketManager'].shouldReconnect).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should handle socket close when online', async () => {
|
|
100
|
+
Object.defineProperty(global, 'navigator', {
|
|
101
|
+
value: {
|
|
102
|
+
onLine: true,
|
|
103
|
+
},
|
|
104
|
+
configurable: true,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await connectionService['handleSocketClose']();
|
|
108
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith(
|
|
109
|
+
'event=socketConnectionRetry | Trying to reconnect to websocket',
|
|
110
|
+
{module: CONNECTION_SERVICE_FILE, method: 'handleSocketClose'}
|
|
111
|
+
);
|
|
112
|
+
expect(mockWebSocketManager.initWebSocket).toHaveBeenCalledWith({body: mockSubscribeRequest});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('ConnectionService onPing', () => {
|
|
116
|
+
it('should handle ping message without keepalive and not update connection data', () => {
|
|
117
|
+
const pingMessage = JSON.stringify({someOtherProperty: 'value'});
|
|
118
|
+
connectionService['onPing'](pingMessage);
|
|
119
|
+
expect(connectionService['isKeepAlive']).toBe(false);
|
|
120
|
+
expect(connectionService['isConnectionLost']).toBe(false);
|
|
121
|
+
expect(connectionService['isRestoreFailed']).toBe(false);
|
|
122
|
+
expect(connectionService['isSocketReconnected']).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should clear reconnectingTimer and restoreTimer on ping message', () => {
|
|
126
|
+
jest.useFakeTimers('modern');
|
|
127
|
+
const reconnectingTimer = setTimeout(() => {}, 1000);
|
|
128
|
+
const restoreTimer = setTimeout(() => {}, 1000);
|
|
129
|
+
connectionService['reconnectingTimer'] = reconnectingTimer;
|
|
130
|
+
connectionService['restoreTimer'] = restoreTimer;
|
|
131
|
+
const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout');
|
|
132
|
+
const pingMessage = JSON.stringify({keepalive: 'true'});
|
|
133
|
+
connectionService['onPing'](pingMessage);
|
|
134
|
+
|
|
135
|
+
expect(clearTimeoutSpy).toHaveBeenCalledTimes(2);
|
|
136
|
+
expect(clearTimeoutSpy).toHaveBeenCalledWith(reconnectingTimer);
|
|
137
|
+
expect(clearTimeoutSpy).toHaveBeenCalledWith(restoreTimer);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('ConnectionService Reconnect', () => {
|
|
142
|
+
it('should handle connection lost and set isConnectionLost to true', () => {
|
|
143
|
+
connectionService['handleConnectionLost']();
|
|
144
|
+
expect(connectionService['isConnectionLost']).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should handle restore failed and set isRestoreFailed to true', async () => {
|
|
148
|
+
await connectionService['handleRestoreFailed']();
|
|
149
|
+
expect(connectionService['isRestoreFailed']).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should handle restore failed and set shouldReconnect to false', async () => {
|
|
153
|
+
await connectionService['handleRestoreFailed']();
|
|
154
|
+
expect(connectionService['webSocketManager'].shouldReconnect).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should handle socket close and reconnect when online', async () => {
|
|
158
|
+
Object.defineProperty(global, 'navigator', {
|
|
159
|
+
value: {
|
|
160
|
+
onLine: true,
|
|
161
|
+
},
|
|
162
|
+
configurable: true,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
await connectionService['handleSocketClose']();
|
|
166
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith(
|
|
167
|
+
'event=socketConnectionRetry | Trying to reconnect to websocket',
|
|
168
|
+
{module: CONNECTION_SERVICE_FILE, method: 'handleSocketClose'}
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should handle onSocketClose and start reconnect interval', () => {
|
|
173
|
+
jest.spyOn(global, 'setInterval');
|
|
174
|
+
connectionService['onSocketClose']();
|
|
175
|
+
expect(setInterval).toHaveBeenCalledWith(expect.any(Function), CONNECTIVITY_CHECK_INTERVAL);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|