@webex/internal-plugin-mercury 2.59.1 → 2.59.3-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/.eslintrc.js +6 -6
- package/README.md +64 -64
- package/babel.config.js +3 -3
- package/dist/config.js +18 -18
- package/dist/config.js.map +1 -1
- package/dist/errors.js +13 -13
- package/dist/errors.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mercury.js +1 -1
- package/dist/mercury.js.map +1 -1
- package/dist/socket/index.js.map +1 -1
- package/dist/socket/socket-base.js +76 -76
- package/dist/socket/socket-base.js.map +1 -1
- package/dist/socket/socket.js +2 -2
- package/dist/socket/socket.js.map +1 -1
- package/dist/socket/socket.shim.js +2 -2
- package/dist/socket/socket.shim.js.map +1 -1
- package/jest.config.js +3 -3
- package/package.json +24 -23
- package/process +1 -1
- package/src/config.js +34 -34
- package/src/errors.js +66 -66
- package/src/index.js +32 -32
- package/src/mercury.js +498 -498
- package/src/socket/index.js +5 -5
- package/src/socket/socket-base.js +481 -481
- package/src/socket/socket.js +13 -13
- package/src/socket/socket.shim.js +31 -31
- package/test/integration/spec/mercury.js +117 -117
- package/test/integration/spec/sharable-mercury.js +59 -59
- package/test/integration/spec/webex.js +43 -43
- package/test/unit/lib/promise-tick.js +19 -19
- package/test/unit/spec/mercury-events.js +473 -473
- package/test/unit/spec/mercury.js +709 -709
- package/test/unit/spec/socket.js +812 -812
|
@@ -1,709 +1,709 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {assert} from '@webex/test-helper-chai';
|
|
6
|
-
import Mercury, {
|
|
7
|
-
BadRequest,
|
|
8
|
-
NotAuthorized,
|
|
9
|
-
Forbidden,
|
|
10
|
-
UnknownResponse,
|
|
11
|
-
// NotFound,
|
|
12
|
-
config as mercuryConfig,
|
|
13
|
-
ConnectionError,
|
|
14
|
-
Socket,
|
|
15
|
-
} from '@webex/internal-plugin-mercury';
|
|
16
|
-
import sinon from 'sinon';
|
|
17
|
-
import MockWebex from '@webex/test-helper-mock-webex';
|
|
18
|
-
import MockWebSocket from '@webex/test-helper-mock-web-socket';
|
|
19
|
-
import uuid from 'uuid';
|
|
20
|
-
import FakeTimers from '@sinonjs/fake-timers';
|
|
21
|
-
import {skipInBrowser} from '@webex/test-helper-mocha';
|
|
22
|
-
|
|
23
|
-
import promiseTick from '../lib/promise-tick';
|
|
24
|
-
|
|
25
|
-
describe('plugin-mercury', () => {
|
|
26
|
-
describe('Mercury', () => {
|
|
27
|
-
let clock, mercury, mockWebSocket, socketOpenStub, webex;
|
|
28
|
-
|
|
29
|
-
const statusStartTypingMessage = JSON.stringify({
|
|
30
|
-
id: uuid.v4(),
|
|
31
|
-
data: {
|
|
32
|
-
eventType: 'status.start_typing',
|
|
33
|
-
actor: {
|
|
34
|
-
id: 'actorId',
|
|
35
|
-
},
|
|
36
|
-
conversationId: uuid.v4(),
|
|
37
|
-
},
|
|
38
|
-
timestamp: Date.now(),
|
|
39
|
-
trackingId: `suffix_${uuid.v4()}_${Date.now()}`,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
beforeEach(() => {
|
|
43
|
-
clock = FakeTimers.install({now: Date.now()});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
afterEach(() => {
|
|
47
|
-
clock.uninstall();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
webex = new MockWebex({
|
|
52
|
-
children: {
|
|
53
|
-
mercury: Mercury,
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
webex.credentials = {
|
|
57
|
-
refresh: sinon.stub().returns(Promise.resolve()),
|
|
58
|
-
getUserToken: sinon.stub().returns(
|
|
59
|
-
Promise.resolve({
|
|
60
|
-
toString() {
|
|
61
|
-
return 'Bearer FAKE';
|
|
62
|
-
},
|
|
63
|
-
})
|
|
64
|
-
),
|
|
65
|
-
};
|
|
66
|
-
webex.internal.device = {
|
|
67
|
-
register: sinon.stub().returns(Promise.resolve()),
|
|
68
|
-
refresh: sinon.stub().returns(Promise.resolve()),
|
|
69
|
-
webSocketUrl: 'ws://example.com',
|
|
70
|
-
getWebSocketUrl: sinon.stub().returns(Promise.resolve('ws://example-2.com')),
|
|
71
|
-
useServiceCatalogUrl: sinon
|
|
72
|
-
.stub()
|
|
73
|
-
.returns(Promise.resolve('https://service-catalog-url.com')),
|
|
74
|
-
};
|
|
75
|
-
webex.internal.services = {
|
|
76
|
-
convertUrlToPriorityHostUrl: sinon.stub().returns(Promise.resolve('ws://example-2.com')),
|
|
77
|
-
markFailedUrl: sinon.stub().returns(Promise.resolve()),
|
|
78
|
-
};
|
|
79
|
-
webex.internal.metrics.submitClientMetrics = sinon.stub();
|
|
80
|
-
webex.trackingId = 'fakeTrackingId';
|
|
81
|
-
webex.config.mercury = mercuryConfig.mercury;
|
|
82
|
-
|
|
83
|
-
webex.logger = console;
|
|
84
|
-
|
|
85
|
-
mockWebSocket = new MockWebSocket();
|
|
86
|
-
sinon.stub(Socket, 'getWebSocketConstructor').returns(() => mockWebSocket);
|
|
87
|
-
|
|
88
|
-
const origOpen = Socket.prototype.open;
|
|
89
|
-
|
|
90
|
-
socketOpenStub = sinon.stub(Socket.prototype, 'open').callsFake(function (...args) {
|
|
91
|
-
const promise = Reflect.apply(origOpen, this, args);
|
|
92
|
-
|
|
93
|
-
process.nextTick(() => mockWebSocket.open());
|
|
94
|
-
|
|
95
|
-
return promise;
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
mercury = webex.internal.mercury;
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
afterEach(() => {
|
|
102
|
-
if (socketOpenStub) {
|
|
103
|
-
socketOpenStub.restore();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (Socket.getWebSocketConstructor.restore) {
|
|
107
|
-
Socket.getWebSocketConstructor.restore();
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe('#listen()', () => {
|
|
112
|
-
it('proxies to #connect()', () => {
|
|
113
|
-
sinon.stub(mercury, 'connect');
|
|
114
|
-
mercury.listen();
|
|
115
|
-
assert.called(mercury.connect);
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
describe('#stopListening()', () => {
|
|
120
|
-
it('proxies to #disconnect()', () => {
|
|
121
|
-
sinon.stub(mercury, 'connect');
|
|
122
|
-
mercury.listen();
|
|
123
|
-
assert.called(mercury.connect);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
describe('#connect()', () => {
|
|
128
|
-
it('lazily registers the device', () => {
|
|
129
|
-
webex.internal.device.registered = false;
|
|
130
|
-
assert.notCalled(webex.internal.device.register);
|
|
131
|
-
const promise = mercury.connect();
|
|
132
|
-
|
|
133
|
-
mockWebSocket.open();
|
|
134
|
-
|
|
135
|
-
return promise.then(() => {
|
|
136
|
-
assert.calledOnce(webex.internal.device.register);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('connects to Mercury using default url', () => {
|
|
141
|
-
const promise = mercury.connect();
|
|
142
|
-
|
|
143
|
-
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
144
|
-
assert.isTrue(mercury.connecting, 'Mercury is connecting');
|
|
145
|
-
mockWebSocket.open();
|
|
146
|
-
|
|
147
|
-
return promise.then(() => {
|
|
148
|
-
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
149
|
-
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
150
|
-
assert.calledWith(socketOpenStub, sinon.match(/ws:\/\/example.com/), sinon.match.any);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe('when `maxRetries` is set', () => {
|
|
155
|
-
// skipping due to apparent bug with lolex in all browsers but Chrome.
|
|
156
|
-
skipInBrowser(it)('fails after `maxRetries` attempts', () => {
|
|
157
|
-
mercury.config.maxRetries = 2;
|
|
158
|
-
socketOpenStub.restore();
|
|
159
|
-
socketOpenStub = sinon.stub(Socket.prototype, 'open');
|
|
160
|
-
socketOpenStub.returns(Promise.reject(new ConnectionError()));
|
|
161
|
-
assert.notCalled(Socket.prototype.open);
|
|
162
|
-
|
|
163
|
-
const promise = mercury.connect();
|
|
164
|
-
|
|
165
|
-
return promiseTick(5)
|
|
166
|
-
.then(() => {
|
|
167
|
-
assert.calledOnce(Socket.prototype.open);
|
|
168
|
-
|
|
169
|
-
return promiseTick(5);
|
|
170
|
-
})
|
|
171
|
-
.then(() => {
|
|
172
|
-
clock.tick(mercury.config.backoffTimeReset);
|
|
173
|
-
|
|
174
|
-
return promiseTick(5);
|
|
175
|
-
})
|
|
176
|
-
.then(() => {
|
|
177
|
-
assert.calledTwice(Socket.prototype.open);
|
|
178
|
-
clock.tick(2 * mercury.config.backoffTimeReset);
|
|
179
|
-
|
|
180
|
-
return promiseTick(5);
|
|
181
|
-
})
|
|
182
|
-
.then(() => {
|
|
183
|
-
assert.calledThrice(Socket.prototype.open);
|
|
184
|
-
clock.tick(5 * mercury.config.backoffTimeReset);
|
|
185
|
-
|
|
186
|
-
return assert.isRejected(promise);
|
|
187
|
-
})
|
|
188
|
-
.then(() => {
|
|
189
|
-
assert.calledThrice(Socket.prototype.open);
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('can safely be called multiple times', () => {
|
|
195
|
-
const promise = Promise.all([
|
|
196
|
-
mercury.connect(),
|
|
197
|
-
mercury.connect(),
|
|
198
|
-
mercury.connect(),
|
|
199
|
-
mercury.connect(),
|
|
200
|
-
]);
|
|
201
|
-
|
|
202
|
-
mockWebSocket.open();
|
|
203
|
-
|
|
204
|
-
return promise.then(() => {
|
|
205
|
-
assert.calledOnce(Socket.prototype.open);
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// skipping due to apparent bug with lolex in all browsers but Chrome.
|
|
210
|
-
skipInBrowser(describe)('when the connection fails', () => {
|
|
211
|
-
it('backs off exponentially', () => {
|
|
212
|
-
socketOpenStub.restore();
|
|
213
|
-
socketOpenStub = sinon.stub(Socket.prototype, 'open');
|
|
214
|
-
socketOpenStub.returns(Promise.reject(new ConnectionError({code: 4001})));
|
|
215
|
-
// Note: onCall is zero-based
|
|
216
|
-
socketOpenStub.onCall(2).returns(Promise.resolve(new MockWebSocket()));
|
|
217
|
-
assert.notCalled(Socket.prototype.open);
|
|
218
|
-
|
|
219
|
-
const promise = mercury.connect();
|
|
220
|
-
|
|
221
|
-
return promiseTick(5)
|
|
222
|
-
.then(() => {
|
|
223
|
-
assert.calledOnce(Socket.prototype.open);
|
|
224
|
-
|
|
225
|
-
// I'm not sure why, but it's important the clock doesn't advance
|
|
226
|
-
// until a tick happens
|
|
227
|
-
return promiseTick(5);
|
|
228
|
-
})
|
|
229
|
-
.then(() => {
|
|
230
|
-
clock.tick(mercury.config.backoffTimeReset);
|
|
231
|
-
|
|
232
|
-
return promiseTick(5);
|
|
233
|
-
})
|
|
234
|
-
.then(() => {
|
|
235
|
-
assert.calledTwice(Socket.prototype.open);
|
|
236
|
-
clock.tick(2 * mercury.config.backoffTimeReset);
|
|
237
|
-
|
|
238
|
-
return promiseTick(5);
|
|
239
|
-
})
|
|
240
|
-
.then(() => {
|
|
241
|
-
assert.calledThrice(Socket.prototype.open);
|
|
242
|
-
clock.tick(5 * mercury.config.backoffTimeReset);
|
|
243
|
-
|
|
244
|
-
return promise;
|
|
245
|
-
})
|
|
246
|
-
.then(() => {
|
|
247
|
-
assert.calledThrice(Socket.prototype.open);
|
|
248
|
-
clock.tick(8 * mercury.config.backoffTimeReset);
|
|
249
|
-
|
|
250
|
-
return promiseTick(5);
|
|
251
|
-
})
|
|
252
|
-
.then(() => {
|
|
253
|
-
assert.calledThrice(Socket.prototype.open);
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
describe('with `BadRequest`', () => {
|
|
258
|
-
it('fails permanently', () => {
|
|
259
|
-
clock.uninstall();
|
|
260
|
-
socketOpenStub.restore();
|
|
261
|
-
socketOpenStub = sinon
|
|
262
|
-
.stub(Socket.prototype, 'open')
|
|
263
|
-
.returns(Promise.reject(new BadRequest({code: 4400})));
|
|
264
|
-
|
|
265
|
-
return assert.isRejected(mercury.connect());
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
describe('with `UnknownResponse`', () => {
|
|
270
|
-
it('triggers a device refresh', () => {
|
|
271
|
-
socketOpenStub.restore();
|
|
272
|
-
socketOpenStub = sinon.stub(Socket.prototype, 'open').returns(Promise.resolve());
|
|
273
|
-
socketOpenStub.onCall(0).returns(Promise.reject(new UnknownResponse({code: 4444})));
|
|
274
|
-
assert.notCalled(webex.credentials.refresh);
|
|
275
|
-
assert.notCalled(webex.internal.device.refresh);
|
|
276
|
-
const promise = mercury.connect();
|
|
277
|
-
|
|
278
|
-
return promiseTick(7).then(() => {
|
|
279
|
-
assert.notCalled(webex.credentials.refresh);
|
|
280
|
-
assert.called(webex.internal.device.refresh);
|
|
281
|
-
clock.tick(1000);
|
|
282
|
-
|
|
283
|
-
return promise;
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
describe('with `NotAuthorized`', () => {
|
|
289
|
-
it('triggers a token refresh', () => {
|
|
290
|
-
socketOpenStub.restore();
|
|
291
|
-
socketOpenStub = sinon.stub(Socket.prototype, 'open').returns(Promise.resolve());
|
|
292
|
-
socketOpenStub.onCall(0).returns(Promise.reject(new NotAuthorized({code: 4401})));
|
|
293
|
-
assert.notCalled(webex.credentials.refresh);
|
|
294
|
-
assert.notCalled(webex.internal.device.refresh);
|
|
295
|
-
const promise = mercury.connect();
|
|
296
|
-
|
|
297
|
-
return promiseTick(7).then(() => {
|
|
298
|
-
assert.called(webex.credentials.refresh);
|
|
299
|
-
assert.notCalled(webex.internal.device.refresh);
|
|
300
|
-
clock.tick(1000);
|
|
301
|
-
|
|
302
|
-
return promise;
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
describe('with `Forbidden`', () => {
|
|
308
|
-
it('fails permanently', () => {
|
|
309
|
-
clock.uninstall();
|
|
310
|
-
socketOpenStub.restore();
|
|
311
|
-
socketOpenStub = sinon
|
|
312
|
-
.stub(Socket.prototype, 'open')
|
|
313
|
-
.returns(Promise.reject(new Forbidden({code: 4403})));
|
|
314
|
-
|
|
315
|
-
return assert.isRejected(mercury.connect());
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
// describe(`with \`NotFound\``, () => {
|
|
320
|
-
// it(`triggers a device refresh`, () => {
|
|
321
|
-
// socketOpenStub.restore();
|
|
322
|
-
// socketOpenStub = sinon.stub(Socket.prototype, `open`).returns(Promise.resolve());
|
|
323
|
-
// socketOpenStub.onCall(0).returns(Promise.reject(new NotFound({code: 4404})));
|
|
324
|
-
// assert.notCalled(webex.credentials.refresh);
|
|
325
|
-
// assert.notCalled(webex.internal.device.refresh);
|
|
326
|
-
// const promise = mercury.connect();
|
|
327
|
-
// return promiseTick(6)
|
|
328
|
-
// .then(() => {
|
|
329
|
-
// assert.notCalled(webex.credentials.refresh);
|
|
330
|
-
// assert.called(webex.internal.device.refresh);
|
|
331
|
-
// clock.tick(1000);
|
|
332
|
-
// return assert.isFulfilled(promise);
|
|
333
|
-
// });
|
|
334
|
-
// });
|
|
335
|
-
// });
|
|
336
|
-
|
|
337
|
-
describe('when web-high-availability feature is enabled', () => {
|
|
338
|
-
it('marks current socket url as failed and get new one on Connection Error', () => {
|
|
339
|
-
webex.internal.feature.getFeature.returns(Promise.resolve(true));
|
|
340
|
-
socketOpenStub.restore();
|
|
341
|
-
socketOpenStub = sinon.stub(Socket.prototype, 'open').returns(Promise.resolve());
|
|
342
|
-
socketOpenStub.onCall(0).returns(Promise.reject(new ConnectionError({code: 4001})));
|
|
343
|
-
const promise = mercury.connect();
|
|
344
|
-
|
|
345
|
-
return promiseTick(7).then(() => {
|
|
346
|
-
assert.calledOnce(webex.internal.services.markFailedUrl);
|
|
347
|
-
clock.tick(1000);
|
|
348
|
-
|
|
349
|
-
return promise;
|
|
350
|
-
});
|
|
351
|
-
});
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
describe('when connected', () => {
|
|
356
|
-
it('resolves immediately', () =>
|
|
357
|
-
mercury.connect().then(() => {
|
|
358
|
-
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
359
|
-
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
360
|
-
const promise = mercury.connect();
|
|
361
|
-
|
|
362
|
-
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
363
|
-
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
364
|
-
|
|
365
|
-
return promise;
|
|
366
|
-
}));
|
|
367
|
-
|
|
368
|
-
// skipping due to apparent bug with lolex in all browsers but Chrome.
|
|
369
|
-
skipInBrowser(it)('does not continue attempting to connect', () => {
|
|
370
|
-
mercury.connect();
|
|
371
|
-
|
|
372
|
-
return promiseTick(2)
|
|
373
|
-
.then(() => {
|
|
374
|
-
clock.tick(6 * webex.internal.mercury.config.backoffTimeReset);
|
|
375
|
-
|
|
376
|
-
return promiseTick(2);
|
|
377
|
-
})
|
|
378
|
-
.then(() => {
|
|
379
|
-
assert.calledOnce(Socket.prototype.open);
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
describe.skip('when webSocketUrl is provided', () => {
|
|
385
|
-
it('connects to Mercury with provided url', () => {
|
|
386
|
-
const webSocketUrl = 'ws://providedurl.com';
|
|
387
|
-
const promise = mercury.connect(webSocketUrl);
|
|
388
|
-
|
|
389
|
-
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
390
|
-
assert.isTrue(mercury.connecting, 'Mercury is connecting');
|
|
391
|
-
mockWebSocket.open();
|
|
392
|
-
|
|
393
|
-
return promise.then(() => {
|
|
394
|
-
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
395
|
-
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
396
|
-
assert.calledWith(
|
|
397
|
-
Socket.prototype.open,
|
|
398
|
-
sinon.match(/ws:\/\/providedurl.com/),
|
|
399
|
-
sinon.match.any
|
|
400
|
-
);
|
|
401
|
-
});
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
describe.skip('Websocket proxy agent', () => {
|
|
407
|
-
afterEach(() => {
|
|
408
|
-
delete webex.config.defaultMercuryOptions;
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
it('connects to Mercury using proxy agent', () => {
|
|
412
|
-
const testProxyUrl = 'http://proxyurl.com:80';
|
|
413
|
-
|
|
414
|
-
webex.config.defaultMercuryOptions = {agent: {proxy: {href: testProxyUrl}}};
|
|
415
|
-
const promise = mercury.connect();
|
|
416
|
-
|
|
417
|
-
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
418
|
-
assert.isTrue(mercury.connecting, 'Mercury is connecting');
|
|
419
|
-
mockWebSocket.open();
|
|
420
|
-
|
|
421
|
-
return promise.then(() => {
|
|
422
|
-
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
423
|
-
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
424
|
-
assert.calledWith(
|
|
425
|
-
socketOpenStub,
|
|
426
|
-
sinon.match(/ws:\/\/example.com/),
|
|
427
|
-
sinon.match.has(
|
|
428
|
-
'agent',
|
|
429
|
-
sinon.match.has('proxy', sinon.match.has('href', testProxyUrl))
|
|
430
|
-
)
|
|
431
|
-
);
|
|
432
|
-
});
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
it('connects to Mercury without proxy agent', () => {
|
|
436
|
-
const promise = mercury.connect();
|
|
437
|
-
|
|
438
|
-
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
439
|
-
assert.isTrue(mercury.connecting, 'Mercury is connecting');
|
|
440
|
-
mockWebSocket.open();
|
|
441
|
-
|
|
442
|
-
return promise.then(() => {
|
|
443
|
-
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
444
|
-
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
445
|
-
assert.calledWith(
|
|
446
|
-
socketOpenStub,
|
|
447
|
-
sinon.match(/ws:\/\/example.com/),
|
|
448
|
-
sinon.match({agent: undefined})
|
|
449
|
-
);
|
|
450
|
-
});
|
|
451
|
-
});
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
describe.skip('#disconnect()', () => {
|
|
455
|
-
it('disconnects the WebSocket', () => mercury.connect()
|
|
456
|
-
.then(() => {
|
|
457
|
-
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
458
|
-
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
459
|
-
const promise = mercury.disconnect();
|
|
460
|
-
|
|
461
|
-
mockWebSocket.emit('close', {
|
|
462
|
-
code: 1000,
|
|
463
|
-
reason: 'Done',
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
return promise;
|
|
467
|
-
})
|
|
468
|
-
.then(() => {
|
|
469
|
-
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
470
|
-
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
471
|
-
assert.isUndefined(mercury.mockWebSocket, 'Mercury does not have a mockWebSocket');
|
|
472
|
-
}));
|
|
473
|
-
|
|
474
|
-
it('stops emitting message events', () => {
|
|
475
|
-
const spy = sinon.spy();
|
|
476
|
-
|
|
477
|
-
mercury.on('event:status.start_typing', spy);
|
|
478
|
-
|
|
479
|
-
return mercury
|
|
480
|
-
.connect()
|
|
481
|
-
.then(() => {
|
|
482
|
-
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
483
|
-
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
484
|
-
|
|
485
|
-
assert.notCalled(spy);
|
|
486
|
-
mockWebSocket.readyState = 1;
|
|
487
|
-
mockWebSocket.emit('open');
|
|
488
|
-
mockWebSocket.emit('message', {data: statusStartTypingMessage});
|
|
489
|
-
})
|
|
490
|
-
.then(() => {
|
|
491
|
-
assert.calledOnce(spy);
|
|
492
|
-
|
|
493
|
-
const promise = mercury.disconnect();
|
|
494
|
-
|
|
495
|
-
mockWebSocket.readyState = 1;
|
|
496
|
-
mockWebSocket.emit('open');
|
|
497
|
-
mockWebSocket.emit('message', {data: statusStartTypingMessage});
|
|
498
|
-
mockWebSocket.emit('close', {
|
|
499
|
-
code: 1000,
|
|
500
|
-
reason: 'Done',
|
|
501
|
-
});
|
|
502
|
-
mockWebSocket.emit('message', {data: statusStartTypingMessage});
|
|
503
|
-
|
|
504
|
-
return promise;
|
|
505
|
-
})
|
|
506
|
-
|
|
507
|
-
.then(() => {
|
|
508
|
-
mockWebSocket.readyState = 1;
|
|
509
|
-
mockWebSocket.emit('open');
|
|
510
|
-
mockWebSocket.emit('message', {data: statusStartTypingMessage});
|
|
511
|
-
assert.calledOnce(spy);
|
|
512
|
-
});
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
describe('when there is a connection attempt inflight', () => {
|
|
516
|
-
it('stops the attempt when disconnect called', () => {
|
|
517
|
-
socketOpenStub.restore();
|
|
518
|
-
socketOpenStub = sinon.stub(Socket.prototype, 'open');
|
|
519
|
-
socketOpenStub.onCall(0).returns(
|
|
520
|
-
// Delay the opening of the socket so that disconnect is called while open
|
|
521
|
-
// is in progress
|
|
522
|
-
promiseTick(2 * webex.internal.mercury.config.backoffTimeReset)
|
|
523
|
-
// Pretend the socket opened successfully. Failing should be fine too but
|
|
524
|
-
// it generates more console output.
|
|
525
|
-
.then(() => Promise.resolve())
|
|
526
|
-
);
|
|
527
|
-
const promise = mercury.connect();
|
|
528
|
-
|
|
529
|
-
// Wait for the connect call to setup
|
|
530
|
-
return promiseTick(webex.internal.mercury.config.backoffTimeReset).then(() => {
|
|
531
|
-
// By this time backoffCall and mercury socket should be defined by the
|
|
532
|
-
// 'connect' call
|
|
533
|
-
assert.isDefined(mercury.backoffCall, 'Mercury backoffCall is not defined');
|
|
534
|
-
assert.isDefined(mercury.socket, 'Mercury socket is not defined');
|
|
535
|
-
// Calling disconnect will abort the backoffCall, close the socket, and
|
|
536
|
-
// reject the connect
|
|
537
|
-
mercury.disconnect();
|
|
538
|
-
assert.isUndefined(mercury.backoffCall, 'Mercury backoffCall is still defined');
|
|
539
|
-
// The socket will never be unset (which seems bad)
|
|
540
|
-
assert.isDefined(mercury.socket, 'Mercury socket is not defined');
|
|
541
|
-
|
|
542
|
-
return assert.isRejected(promise);
|
|
543
|
-
});
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
it('stops the attempt when backoffCall is undefined', () => {
|
|
547
|
-
socketOpenStub.restore();
|
|
548
|
-
socketOpenStub = sinon.stub(Socket.prototype, 'open');
|
|
549
|
-
socketOpenStub.returns(Promise.resolve());
|
|
550
|
-
|
|
551
|
-
let reason;
|
|
552
|
-
|
|
553
|
-
mercury.backoffCall = undefined;
|
|
554
|
-
mercury._attemptConnection('ws://example.com', (_reason) => {
|
|
555
|
-
reason = _reason;
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
return promiseTick(webex.internal.mercury.config.backoffTimeReset).then(() => {
|
|
559
|
-
assert.equal(
|
|
560
|
-
reason.message,
|
|
561
|
-
'mercury: prevent socket open when backoffCall no longer defined'
|
|
562
|
-
);
|
|
563
|
-
});
|
|
564
|
-
});
|
|
565
|
-
});
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
describe('#_emit()', () => {
|
|
569
|
-
it('emits Error-safe events', () => {
|
|
570
|
-
mercury.on('break', () => {
|
|
571
|
-
throw new Error();
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
return Promise.resolve(mercury._emit('break'));
|
|
575
|
-
});
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
describe('#_applyOverrides()', () => {
|
|
579
|
-
const lastSeenActivityDate = 'Some date';
|
|
580
|
-
const lastReadableActivityDate = 'Some other date';
|
|
581
|
-
|
|
582
|
-
it('merges a single header field with data', () => {
|
|
583
|
-
const envelope = {
|
|
584
|
-
headers: {
|
|
585
|
-
'data.activity.target.lastSeenActivityDate': lastSeenActivityDate,
|
|
586
|
-
},
|
|
587
|
-
data: {
|
|
588
|
-
activity: {},
|
|
589
|
-
},
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
mercury._applyOverrides(envelope);
|
|
593
|
-
|
|
594
|
-
assert.equal(envelope.data.activity.target.lastSeenActivityDate, lastSeenActivityDate);
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
it('merges a multiple header fields with data', () => {
|
|
598
|
-
const envelope = {
|
|
599
|
-
headers: {
|
|
600
|
-
'data.activity.target.lastSeenActivityDate': lastSeenActivityDate,
|
|
601
|
-
'data.activity.target.lastReadableActivityDate': lastReadableActivityDate,
|
|
602
|
-
},
|
|
603
|
-
data: {
|
|
604
|
-
activity: {},
|
|
605
|
-
},
|
|
606
|
-
};
|
|
607
|
-
|
|
608
|
-
mercury._applyOverrides(envelope);
|
|
609
|
-
|
|
610
|
-
assert.equal(envelope.data.activity.target.lastSeenActivityDate, lastSeenActivityDate);
|
|
611
|
-
assert.equal(
|
|
612
|
-
envelope.data.activity.target.lastReadableActivityDate,
|
|
613
|
-
lastReadableActivityDate
|
|
614
|
-
);
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
it('merges headers when Mercury messages arrive', () => {
|
|
618
|
-
const envelope = {
|
|
619
|
-
headers: {
|
|
620
|
-
'data.activity.target.lastSeenActivityDate': lastSeenActivityDate,
|
|
621
|
-
},
|
|
622
|
-
data: {
|
|
623
|
-
activity: {},
|
|
624
|
-
},
|
|
625
|
-
};
|
|
626
|
-
|
|
627
|
-
mercury._applyOverrides(envelope);
|
|
628
|
-
|
|
629
|
-
assert.equal(envelope.data.activity.target.lastSeenActivityDate, lastSeenActivityDate);
|
|
630
|
-
});
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
describe('#_prepareUrl()', () => {
|
|
634
|
-
beforeEach(() => {
|
|
635
|
-
webex.internal.device.webSocketUrl = 'ws://example.com';
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
it('uses device default webSocketUrl', () =>
|
|
639
|
-
webex.internal.mercury._prepareUrl().then((wsUrl) => assert.match(wsUrl, /example.com/)));
|
|
640
|
-
it('uses provided webSocketUrl', () =>
|
|
641
|
-
webex.internal.mercury
|
|
642
|
-
._prepareUrl('ws://provided.com')
|
|
643
|
-
.then((wsUrl) => assert.match(wsUrl, /provided.com/)));
|
|
644
|
-
it('requests text-mode WebSockets', () =>
|
|
645
|
-
webex.internal.mercury
|
|
646
|
-
._prepareUrl()
|
|
647
|
-
.then((wsUrl) => assert.match(wsUrl, /outboundWireFormat=text/)));
|
|
648
|
-
|
|
649
|
-
it('requests the buffer state message', () =>
|
|
650
|
-
webex.internal.mercury
|
|
651
|
-
._prepareUrl()
|
|
652
|
-
.then((wsUrl) => assert.match(wsUrl, /bufferStates=true/)));
|
|
653
|
-
|
|
654
|
-
it('does not add conditional properties', () =>
|
|
655
|
-
webex.internal.mercury._prepareUrl().then((wsUrl) => {
|
|
656
|
-
assert.notMatch(wsUrl, /mercuryRegistrationStatus/);
|
|
657
|
-
assert.notMatch(wsUrl, /mercuryRegistrationStatus/);
|
|
658
|
-
assert.notMatch(wsUrl, /isRegistrationRefreshEnabled/);
|
|
659
|
-
assert.notMatch(wsUrl, /multipleConnections/);
|
|
660
|
-
}));
|
|
661
|
-
|
|
662
|
-
describe('when web-high-availability is enabled', () => {
|
|
663
|
-
it('uses webSocketUrl provided by device', () => {
|
|
664
|
-
webex.internal.device.useServiceCatalogUrl = sinon
|
|
665
|
-
.stub()
|
|
666
|
-
.returns(Promise.resolve('ws://example-2.com'));
|
|
667
|
-
webex.internal.feature.getFeature.onCall(0).returns(Promise.resolve(true));
|
|
668
|
-
|
|
669
|
-
return webex.internal.mercury
|
|
670
|
-
._prepareUrl()
|
|
671
|
-
.then((wsUrl) => assert.match(wsUrl, /example-2.com/));
|
|
672
|
-
});
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
describe("when 'web-shared-socket' is enabled", () => {
|
|
676
|
-
beforeEach(() => {
|
|
677
|
-
webex.internal.feature.getFeature.returns(Promise.resolve(true));
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
it('requests shared socket support', () =>
|
|
681
|
-
webex.internal.mercury
|
|
682
|
-
._prepareUrl()
|
|
683
|
-
.then((wsUrl) => assert.match(wsUrl, /isRegistrationRefreshEnabled=true/)));
|
|
684
|
-
|
|
685
|
-
it('requests the registration banner', () =>
|
|
686
|
-
webex.internal.mercury
|
|
687
|
-
._prepareUrl()
|
|
688
|
-
.then((wsUrl) => assert.match(wsUrl, /mercuryRegistrationStatus=true/)));
|
|
689
|
-
|
|
690
|
-
it('does not request the buffer state message', () =>
|
|
691
|
-
webex.internal.mercury._prepareUrl().then((wsUrl) => {
|
|
692
|
-
assert.match(wsUrl, /mercuryRegistrationStatus=true/);
|
|
693
|
-
assert.notMatch(wsUrl, /bufferStates/);
|
|
694
|
-
}));
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
describe('when using an ephemeral device', () => {
|
|
698
|
-
beforeEach(() => {
|
|
699
|
-
webex.config.device.ephemeral = true;
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
it('indicates multiple connections may be coming from this user', () =>
|
|
703
|
-
webex.internal.mercury
|
|
704
|
-
._prepareUrl()
|
|
705
|
-
.then((wsUrl) => assert.match(wsUrl, /multipleConnections/)));
|
|
706
|
-
});
|
|
707
|
-
});
|
|
708
|
-
});
|
|
709
|
-
});
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {assert} from '@webex/test-helper-chai';
|
|
6
|
+
import Mercury, {
|
|
7
|
+
BadRequest,
|
|
8
|
+
NotAuthorized,
|
|
9
|
+
Forbidden,
|
|
10
|
+
UnknownResponse,
|
|
11
|
+
// NotFound,
|
|
12
|
+
config as mercuryConfig,
|
|
13
|
+
ConnectionError,
|
|
14
|
+
Socket,
|
|
15
|
+
} from '@webex/internal-plugin-mercury';
|
|
16
|
+
import sinon from 'sinon';
|
|
17
|
+
import MockWebex from '@webex/test-helper-mock-webex';
|
|
18
|
+
import MockWebSocket from '@webex/test-helper-mock-web-socket';
|
|
19
|
+
import uuid from 'uuid';
|
|
20
|
+
import FakeTimers from '@sinonjs/fake-timers';
|
|
21
|
+
import {skipInBrowser} from '@webex/test-helper-mocha';
|
|
22
|
+
|
|
23
|
+
import promiseTick from '../lib/promise-tick';
|
|
24
|
+
|
|
25
|
+
describe('plugin-mercury', () => {
|
|
26
|
+
describe('Mercury', () => {
|
|
27
|
+
let clock, mercury, mockWebSocket, socketOpenStub, webex;
|
|
28
|
+
|
|
29
|
+
const statusStartTypingMessage = JSON.stringify({
|
|
30
|
+
id: uuid.v4(),
|
|
31
|
+
data: {
|
|
32
|
+
eventType: 'status.start_typing',
|
|
33
|
+
actor: {
|
|
34
|
+
id: 'actorId',
|
|
35
|
+
},
|
|
36
|
+
conversationId: uuid.v4(),
|
|
37
|
+
},
|
|
38
|
+
timestamp: Date.now(),
|
|
39
|
+
trackingId: `suffix_${uuid.v4()}_${Date.now()}`,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
clock = FakeTimers.install({now: Date.now()});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
clock.uninstall();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
webex = new MockWebex({
|
|
52
|
+
children: {
|
|
53
|
+
mercury: Mercury,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
webex.credentials = {
|
|
57
|
+
refresh: sinon.stub().returns(Promise.resolve()),
|
|
58
|
+
getUserToken: sinon.stub().returns(
|
|
59
|
+
Promise.resolve({
|
|
60
|
+
toString() {
|
|
61
|
+
return 'Bearer FAKE';
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
),
|
|
65
|
+
};
|
|
66
|
+
webex.internal.device = {
|
|
67
|
+
register: sinon.stub().returns(Promise.resolve()),
|
|
68
|
+
refresh: sinon.stub().returns(Promise.resolve()),
|
|
69
|
+
webSocketUrl: 'ws://example.com',
|
|
70
|
+
getWebSocketUrl: sinon.stub().returns(Promise.resolve('ws://example-2.com')),
|
|
71
|
+
useServiceCatalogUrl: sinon
|
|
72
|
+
.stub()
|
|
73
|
+
.returns(Promise.resolve('https://service-catalog-url.com')),
|
|
74
|
+
};
|
|
75
|
+
webex.internal.services = {
|
|
76
|
+
convertUrlToPriorityHostUrl: sinon.stub().returns(Promise.resolve('ws://example-2.com')),
|
|
77
|
+
markFailedUrl: sinon.stub().returns(Promise.resolve()),
|
|
78
|
+
};
|
|
79
|
+
webex.internal.metrics.submitClientMetrics = sinon.stub();
|
|
80
|
+
webex.trackingId = 'fakeTrackingId';
|
|
81
|
+
webex.config.mercury = mercuryConfig.mercury;
|
|
82
|
+
|
|
83
|
+
webex.logger = console;
|
|
84
|
+
|
|
85
|
+
mockWebSocket = new MockWebSocket();
|
|
86
|
+
sinon.stub(Socket, 'getWebSocketConstructor').returns(() => mockWebSocket);
|
|
87
|
+
|
|
88
|
+
const origOpen = Socket.prototype.open;
|
|
89
|
+
|
|
90
|
+
socketOpenStub = sinon.stub(Socket.prototype, 'open').callsFake(function (...args) {
|
|
91
|
+
const promise = Reflect.apply(origOpen, this, args);
|
|
92
|
+
|
|
93
|
+
process.nextTick(() => mockWebSocket.open());
|
|
94
|
+
|
|
95
|
+
return promise;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
mercury = webex.internal.mercury;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
afterEach(() => {
|
|
102
|
+
if (socketOpenStub) {
|
|
103
|
+
socketOpenStub.restore();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (Socket.getWebSocketConstructor.restore) {
|
|
107
|
+
Socket.getWebSocketConstructor.restore();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('#listen()', () => {
|
|
112
|
+
it('proxies to #connect()', () => {
|
|
113
|
+
sinon.stub(mercury, 'connect');
|
|
114
|
+
mercury.listen();
|
|
115
|
+
assert.called(mercury.connect);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('#stopListening()', () => {
|
|
120
|
+
it('proxies to #disconnect()', () => {
|
|
121
|
+
sinon.stub(mercury, 'connect');
|
|
122
|
+
mercury.listen();
|
|
123
|
+
assert.called(mercury.connect);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('#connect()', () => {
|
|
128
|
+
it('lazily registers the device', () => {
|
|
129
|
+
webex.internal.device.registered = false;
|
|
130
|
+
assert.notCalled(webex.internal.device.register);
|
|
131
|
+
const promise = mercury.connect();
|
|
132
|
+
|
|
133
|
+
mockWebSocket.open();
|
|
134
|
+
|
|
135
|
+
return promise.then(() => {
|
|
136
|
+
assert.calledOnce(webex.internal.device.register);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('connects to Mercury using default url', () => {
|
|
141
|
+
const promise = mercury.connect();
|
|
142
|
+
|
|
143
|
+
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
144
|
+
assert.isTrue(mercury.connecting, 'Mercury is connecting');
|
|
145
|
+
mockWebSocket.open();
|
|
146
|
+
|
|
147
|
+
return promise.then(() => {
|
|
148
|
+
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
149
|
+
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
150
|
+
assert.calledWith(socketOpenStub, sinon.match(/ws:\/\/example.com/), sinon.match.any);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('when `maxRetries` is set', () => {
|
|
155
|
+
// skipping due to apparent bug with lolex in all browsers but Chrome.
|
|
156
|
+
skipInBrowser(it)('fails after `maxRetries` attempts', () => {
|
|
157
|
+
mercury.config.maxRetries = 2;
|
|
158
|
+
socketOpenStub.restore();
|
|
159
|
+
socketOpenStub = sinon.stub(Socket.prototype, 'open');
|
|
160
|
+
socketOpenStub.returns(Promise.reject(new ConnectionError()));
|
|
161
|
+
assert.notCalled(Socket.prototype.open);
|
|
162
|
+
|
|
163
|
+
const promise = mercury.connect();
|
|
164
|
+
|
|
165
|
+
return promiseTick(5)
|
|
166
|
+
.then(() => {
|
|
167
|
+
assert.calledOnce(Socket.prototype.open);
|
|
168
|
+
|
|
169
|
+
return promiseTick(5);
|
|
170
|
+
})
|
|
171
|
+
.then(() => {
|
|
172
|
+
clock.tick(mercury.config.backoffTimeReset);
|
|
173
|
+
|
|
174
|
+
return promiseTick(5);
|
|
175
|
+
})
|
|
176
|
+
.then(() => {
|
|
177
|
+
assert.calledTwice(Socket.prototype.open);
|
|
178
|
+
clock.tick(2 * mercury.config.backoffTimeReset);
|
|
179
|
+
|
|
180
|
+
return promiseTick(5);
|
|
181
|
+
})
|
|
182
|
+
.then(() => {
|
|
183
|
+
assert.calledThrice(Socket.prototype.open);
|
|
184
|
+
clock.tick(5 * mercury.config.backoffTimeReset);
|
|
185
|
+
|
|
186
|
+
return assert.isRejected(promise);
|
|
187
|
+
})
|
|
188
|
+
.then(() => {
|
|
189
|
+
assert.calledThrice(Socket.prototype.open);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('can safely be called multiple times', () => {
|
|
195
|
+
const promise = Promise.all([
|
|
196
|
+
mercury.connect(),
|
|
197
|
+
mercury.connect(),
|
|
198
|
+
mercury.connect(),
|
|
199
|
+
mercury.connect(),
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
mockWebSocket.open();
|
|
203
|
+
|
|
204
|
+
return promise.then(() => {
|
|
205
|
+
assert.calledOnce(Socket.prototype.open);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// skipping due to apparent bug with lolex in all browsers but Chrome.
|
|
210
|
+
skipInBrowser(describe)('when the connection fails', () => {
|
|
211
|
+
it('backs off exponentially', () => {
|
|
212
|
+
socketOpenStub.restore();
|
|
213
|
+
socketOpenStub = sinon.stub(Socket.prototype, 'open');
|
|
214
|
+
socketOpenStub.returns(Promise.reject(new ConnectionError({code: 4001})));
|
|
215
|
+
// Note: onCall is zero-based
|
|
216
|
+
socketOpenStub.onCall(2).returns(Promise.resolve(new MockWebSocket()));
|
|
217
|
+
assert.notCalled(Socket.prototype.open);
|
|
218
|
+
|
|
219
|
+
const promise = mercury.connect();
|
|
220
|
+
|
|
221
|
+
return promiseTick(5)
|
|
222
|
+
.then(() => {
|
|
223
|
+
assert.calledOnce(Socket.prototype.open);
|
|
224
|
+
|
|
225
|
+
// I'm not sure why, but it's important the clock doesn't advance
|
|
226
|
+
// until a tick happens
|
|
227
|
+
return promiseTick(5);
|
|
228
|
+
})
|
|
229
|
+
.then(() => {
|
|
230
|
+
clock.tick(mercury.config.backoffTimeReset);
|
|
231
|
+
|
|
232
|
+
return promiseTick(5);
|
|
233
|
+
})
|
|
234
|
+
.then(() => {
|
|
235
|
+
assert.calledTwice(Socket.prototype.open);
|
|
236
|
+
clock.tick(2 * mercury.config.backoffTimeReset);
|
|
237
|
+
|
|
238
|
+
return promiseTick(5);
|
|
239
|
+
})
|
|
240
|
+
.then(() => {
|
|
241
|
+
assert.calledThrice(Socket.prototype.open);
|
|
242
|
+
clock.tick(5 * mercury.config.backoffTimeReset);
|
|
243
|
+
|
|
244
|
+
return promise;
|
|
245
|
+
})
|
|
246
|
+
.then(() => {
|
|
247
|
+
assert.calledThrice(Socket.prototype.open);
|
|
248
|
+
clock.tick(8 * mercury.config.backoffTimeReset);
|
|
249
|
+
|
|
250
|
+
return promiseTick(5);
|
|
251
|
+
})
|
|
252
|
+
.then(() => {
|
|
253
|
+
assert.calledThrice(Socket.prototype.open);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('with `BadRequest`', () => {
|
|
258
|
+
it('fails permanently', () => {
|
|
259
|
+
clock.uninstall();
|
|
260
|
+
socketOpenStub.restore();
|
|
261
|
+
socketOpenStub = sinon
|
|
262
|
+
.stub(Socket.prototype, 'open')
|
|
263
|
+
.returns(Promise.reject(new BadRequest({code: 4400})));
|
|
264
|
+
|
|
265
|
+
return assert.isRejected(mercury.connect());
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe('with `UnknownResponse`', () => {
|
|
270
|
+
it('triggers a device refresh', () => {
|
|
271
|
+
socketOpenStub.restore();
|
|
272
|
+
socketOpenStub = sinon.stub(Socket.prototype, 'open').returns(Promise.resolve());
|
|
273
|
+
socketOpenStub.onCall(0).returns(Promise.reject(new UnknownResponse({code: 4444})));
|
|
274
|
+
assert.notCalled(webex.credentials.refresh);
|
|
275
|
+
assert.notCalled(webex.internal.device.refresh);
|
|
276
|
+
const promise = mercury.connect();
|
|
277
|
+
|
|
278
|
+
return promiseTick(7).then(() => {
|
|
279
|
+
assert.notCalled(webex.credentials.refresh);
|
|
280
|
+
assert.called(webex.internal.device.refresh);
|
|
281
|
+
clock.tick(1000);
|
|
282
|
+
|
|
283
|
+
return promise;
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('with `NotAuthorized`', () => {
|
|
289
|
+
it('triggers a token refresh', () => {
|
|
290
|
+
socketOpenStub.restore();
|
|
291
|
+
socketOpenStub = sinon.stub(Socket.prototype, 'open').returns(Promise.resolve());
|
|
292
|
+
socketOpenStub.onCall(0).returns(Promise.reject(new NotAuthorized({code: 4401})));
|
|
293
|
+
assert.notCalled(webex.credentials.refresh);
|
|
294
|
+
assert.notCalled(webex.internal.device.refresh);
|
|
295
|
+
const promise = mercury.connect();
|
|
296
|
+
|
|
297
|
+
return promiseTick(7).then(() => {
|
|
298
|
+
assert.called(webex.credentials.refresh);
|
|
299
|
+
assert.notCalled(webex.internal.device.refresh);
|
|
300
|
+
clock.tick(1000);
|
|
301
|
+
|
|
302
|
+
return promise;
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe('with `Forbidden`', () => {
|
|
308
|
+
it('fails permanently', () => {
|
|
309
|
+
clock.uninstall();
|
|
310
|
+
socketOpenStub.restore();
|
|
311
|
+
socketOpenStub = sinon
|
|
312
|
+
.stub(Socket.prototype, 'open')
|
|
313
|
+
.returns(Promise.reject(new Forbidden({code: 4403})));
|
|
314
|
+
|
|
315
|
+
return assert.isRejected(mercury.connect());
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// describe(`with \`NotFound\``, () => {
|
|
320
|
+
// it(`triggers a device refresh`, () => {
|
|
321
|
+
// socketOpenStub.restore();
|
|
322
|
+
// socketOpenStub = sinon.stub(Socket.prototype, `open`).returns(Promise.resolve());
|
|
323
|
+
// socketOpenStub.onCall(0).returns(Promise.reject(new NotFound({code: 4404})));
|
|
324
|
+
// assert.notCalled(webex.credentials.refresh);
|
|
325
|
+
// assert.notCalled(webex.internal.device.refresh);
|
|
326
|
+
// const promise = mercury.connect();
|
|
327
|
+
// return promiseTick(6)
|
|
328
|
+
// .then(() => {
|
|
329
|
+
// assert.notCalled(webex.credentials.refresh);
|
|
330
|
+
// assert.called(webex.internal.device.refresh);
|
|
331
|
+
// clock.tick(1000);
|
|
332
|
+
// return assert.isFulfilled(promise);
|
|
333
|
+
// });
|
|
334
|
+
// });
|
|
335
|
+
// });
|
|
336
|
+
|
|
337
|
+
describe('when web-high-availability feature is enabled', () => {
|
|
338
|
+
it('marks current socket url as failed and get new one on Connection Error', () => {
|
|
339
|
+
webex.internal.feature.getFeature.returns(Promise.resolve(true));
|
|
340
|
+
socketOpenStub.restore();
|
|
341
|
+
socketOpenStub = sinon.stub(Socket.prototype, 'open').returns(Promise.resolve());
|
|
342
|
+
socketOpenStub.onCall(0).returns(Promise.reject(new ConnectionError({code: 4001})));
|
|
343
|
+
const promise = mercury.connect();
|
|
344
|
+
|
|
345
|
+
return promiseTick(7).then(() => {
|
|
346
|
+
assert.calledOnce(webex.internal.services.markFailedUrl);
|
|
347
|
+
clock.tick(1000);
|
|
348
|
+
|
|
349
|
+
return promise;
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
describe('when connected', () => {
|
|
356
|
+
it('resolves immediately', () =>
|
|
357
|
+
mercury.connect().then(() => {
|
|
358
|
+
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
359
|
+
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
360
|
+
const promise = mercury.connect();
|
|
361
|
+
|
|
362
|
+
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
363
|
+
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
364
|
+
|
|
365
|
+
return promise;
|
|
366
|
+
}));
|
|
367
|
+
|
|
368
|
+
// skipping due to apparent bug with lolex in all browsers but Chrome.
|
|
369
|
+
skipInBrowser(it)('does not continue attempting to connect', () => {
|
|
370
|
+
mercury.connect();
|
|
371
|
+
|
|
372
|
+
return promiseTick(2)
|
|
373
|
+
.then(() => {
|
|
374
|
+
clock.tick(6 * webex.internal.mercury.config.backoffTimeReset);
|
|
375
|
+
|
|
376
|
+
return promiseTick(2);
|
|
377
|
+
})
|
|
378
|
+
.then(() => {
|
|
379
|
+
assert.calledOnce(Socket.prototype.open);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
describe.skip('when webSocketUrl is provided', () => {
|
|
385
|
+
it('connects to Mercury with provided url', () => {
|
|
386
|
+
const webSocketUrl = 'ws://providedurl.com';
|
|
387
|
+
const promise = mercury.connect(webSocketUrl);
|
|
388
|
+
|
|
389
|
+
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
390
|
+
assert.isTrue(mercury.connecting, 'Mercury is connecting');
|
|
391
|
+
mockWebSocket.open();
|
|
392
|
+
|
|
393
|
+
return promise.then(() => {
|
|
394
|
+
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
395
|
+
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
396
|
+
assert.calledWith(
|
|
397
|
+
Socket.prototype.open,
|
|
398
|
+
sinon.match(/ws:\/\/providedurl.com/),
|
|
399
|
+
sinon.match.any
|
|
400
|
+
);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
describe.skip('Websocket proxy agent', () => {
|
|
407
|
+
afterEach(() => {
|
|
408
|
+
delete webex.config.defaultMercuryOptions;
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('connects to Mercury using proxy agent', () => {
|
|
412
|
+
const testProxyUrl = 'http://proxyurl.com:80';
|
|
413
|
+
|
|
414
|
+
webex.config.defaultMercuryOptions = {agent: {proxy: {href: testProxyUrl}}};
|
|
415
|
+
const promise = mercury.connect();
|
|
416
|
+
|
|
417
|
+
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
418
|
+
assert.isTrue(mercury.connecting, 'Mercury is connecting');
|
|
419
|
+
mockWebSocket.open();
|
|
420
|
+
|
|
421
|
+
return promise.then(() => {
|
|
422
|
+
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
423
|
+
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
424
|
+
assert.calledWith(
|
|
425
|
+
socketOpenStub,
|
|
426
|
+
sinon.match(/ws:\/\/example.com/),
|
|
427
|
+
sinon.match.has(
|
|
428
|
+
'agent',
|
|
429
|
+
sinon.match.has('proxy', sinon.match.has('href', testProxyUrl))
|
|
430
|
+
)
|
|
431
|
+
);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('connects to Mercury without proxy agent', () => {
|
|
436
|
+
const promise = mercury.connect();
|
|
437
|
+
|
|
438
|
+
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
439
|
+
assert.isTrue(mercury.connecting, 'Mercury is connecting');
|
|
440
|
+
mockWebSocket.open();
|
|
441
|
+
|
|
442
|
+
return promise.then(() => {
|
|
443
|
+
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
444
|
+
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
445
|
+
assert.calledWith(
|
|
446
|
+
socketOpenStub,
|
|
447
|
+
sinon.match(/ws:\/\/example.com/),
|
|
448
|
+
sinon.match({agent: undefined})
|
|
449
|
+
);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
describe.skip('#disconnect()', () => {
|
|
455
|
+
it('disconnects the WebSocket', () => mercury.connect()
|
|
456
|
+
.then(() => {
|
|
457
|
+
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
458
|
+
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
459
|
+
const promise = mercury.disconnect();
|
|
460
|
+
|
|
461
|
+
mockWebSocket.emit('close', {
|
|
462
|
+
code: 1000,
|
|
463
|
+
reason: 'Done',
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
return promise;
|
|
467
|
+
})
|
|
468
|
+
.then(() => {
|
|
469
|
+
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
470
|
+
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
471
|
+
assert.isUndefined(mercury.mockWebSocket, 'Mercury does not have a mockWebSocket');
|
|
472
|
+
}));
|
|
473
|
+
|
|
474
|
+
it('stops emitting message events', () => {
|
|
475
|
+
const spy = sinon.spy();
|
|
476
|
+
|
|
477
|
+
mercury.on('event:status.start_typing', spy);
|
|
478
|
+
|
|
479
|
+
return mercury
|
|
480
|
+
.connect()
|
|
481
|
+
.then(() => {
|
|
482
|
+
assert.isTrue(mercury.connected, 'Mercury is connected');
|
|
483
|
+
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
484
|
+
|
|
485
|
+
assert.notCalled(spy);
|
|
486
|
+
mockWebSocket.readyState = 1;
|
|
487
|
+
mockWebSocket.emit('open');
|
|
488
|
+
mockWebSocket.emit('message', {data: statusStartTypingMessage});
|
|
489
|
+
})
|
|
490
|
+
.then(() => {
|
|
491
|
+
assert.calledOnce(spy);
|
|
492
|
+
|
|
493
|
+
const promise = mercury.disconnect();
|
|
494
|
+
|
|
495
|
+
mockWebSocket.readyState = 1;
|
|
496
|
+
mockWebSocket.emit('open');
|
|
497
|
+
mockWebSocket.emit('message', {data: statusStartTypingMessage});
|
|
498
|
+
mockWebSocket.emit('close', {
|
|
499
|
+
code: 1000,
|
|
500
|
+
reason: 'Done',
|
|
501
|
+
});
|
|
502
|
+
mockWebSocket.emit('message', {data: statusStartTypingMessage});
|
|
503
|
+
|
|
504
|
+
return promise;
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
.then(() => {
|
|
508
|
+
mockWebSocket.readyState = 1;
|
|
509
|
+
mockWebSocket.emit('open');
|
|
510
|
+
mockWebSocket.emit('message', {data: statusStartTypingMessage});
|
|
511
|
+
assert.calledOnce(spy);
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
describe('when there is a connection attempt inflight', () => {
|
|
516
|
+
it('stops the attempt when disconnect called', () => {
|
|
517
|
+
socketOpenStub.restore();
|
|
518
|
+
socketOpenStub = sinon.stub(Socket.prototype, 'open');
|
|
519
|
+
socketOpenStub.onCall(0).returns(
|
|
520
|
+
// Delay the opening of the socket so that disconnect is called while open
|
|
521
|
+
// is in progress
|
|
522
|
+
promiseTick(2 * webex.internal.mercury.config.backoffTimeReset)
|
|
523
|
+
// Pretend the socket opened successfully. Failing should be fine too but
|
|
524
|
+
// it generates more console output.
|
|
525
|
+
.then(() => Promise.resolve())
|
|
526
|
+
);
|
|
527
|
+
const promise = mercury.connect();
|
|
528
|
+
|
|
529
|
+
// Wait for the connect call to setup
|
|
530
|
+
return promiseTick(webex.internal.mercury.config.backoffTimeReset).then(() => {
|
|
531
|
+
// By this time backoffCall and mercury socket should be defined by the
|
|
532
|
+
// 'connect' call
|
|
533
|
+
assert.isDefined(mercury.backoffCall, 'Mercury backoffCall is not defined');
|
|
534
|
+
assert.isDefined(mercury.socket, 'Mercury socket is not defined');
|
|
535
|
+
// Calling disconnect will abort the backoffCall, close the socket, and
|
|
536
|
+
// reject the connect
|
|
537
|
+
mercury.disconnect();
|
|
538
|
+
assert.isUndefined(mercury.backoffCall, 'Mercury backoffCall is still defined');
|
|
539
|
+
// The socket will never be unset (which seems bad)
|
|
540
|
+
assert.isDefined(mercury.socket, 'Mercury socket is not defined');
|
|
541
|
+
|
|
542
|
+
return assert.isRejected(promise);
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('stops the attempt when backoffCall is undefined', () => {
|
|
547
|
+
socketOpenStub.restore();
|
|
548
|
+
socketOpenStub = sinon.stub(Socket.prototype, 'open');
|
|
549
|
+
socketOpenStub.returns(Promise.resolve());
|
|
550
|
+
|
|
551
|
+
let reason;
|
|
552
|
+
|
|
553
|
+
mercury.backoffCall = undefined;
|
|
554
|
+
mercury._attemptConnection('ws://example.com', (_reason) => {
|
|
555
|
+
reason = _reason;
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
return promiseTick(webex.internal.mercury.config.backoffTimeReset).then(() => {
|
|
559
|
+
assert.equal(
|
|
560
|
+
reason.message,
|
|
561
|
+
'mercury: prevent socket open when backoffCall no longer defined'
|
|
562
|
+
);
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
describe('#_emit()', () => {
|
|
569
|
+
it('emits Error-safe events', () => {
|
|
570
|
+
mercury.on('break', () => {
|
|
571
|
+
throw new Error();
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
return Promise.resolve(mercury._emit('break'));
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
describe('#_applyOverrides()', () => {
|
|
579
|
+
const lastSeenActivityDate = 'Some date';
|
|
580
|
+
const lastReadableActivityDate = 'Some other date';
|
|
581
|
+
|
|
582
|
+
it('merges a single header field with data', () => {
|
|
583
|
+
const envelope = {
|
|
584
|
+
headers: {
|
|
585
|
+
'data.activity.target.lastSeenActivityDate': lastSeenActivityDate,
|
|
586
|
+
},
|
|
587
|
+
data: {
|
|
588
|
+
activity: {},
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
mercury._applyOverrides(envelope);
|
|
593
|
+
|
|
594
|
+
assert.equal(envelope.data.activity.target.lastSeenActivityDate, lastSeenActivityDate);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('merges a multiple header fields with data', () => {
|
|
598
|
+
const envelope = {
|
|
599
|
+
headers: {
|
|
600
|
+
'data.activity.target.lastSeenActivityDate': lastSeenActivityDate,
|
|
601
|
+
'data.activity.target.lastReadableActivityDate': lastReadableActivityDate,
|
|
602
|
+
},
|
|
603
|
+
data: {
|
|
604
|
+
activity: {},
|
|
605
|
+
},
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
mercury._applyOverrides(envelope);
|
|
609
|
+
|
|
610
|
+
assert.equal(envelope.data.activity.target.lastSeenActivityDate, lastSeenActivityDate);
|
|
611
|
+
assert.equal(
|
|
612
|
+
envelope.data.activity.target.lastReadableActivityDate,
|
|
613
|
+
lastReadableActivityDate
|
|
614
|
+
);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it('merges headers when Mercury messages arrive', () => {
|
|
618
|
+
const envelope = {
|
|
619
|
+
headers: {
|
|
620
|
+
'data.activity.target.lastSeenActivityDate': lastSeenActivityDate,
|
|
621
|
+
},
|
|
622
|
+
data: {
|
|
623
|
+
activity: {},
|
|
624
|
+
},
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
mercury._applyOverrides(envelope);
|
|
628
|
+
|
|
629
|
+
assert.equal(envelope.data.activity.target.lastSeenActivityDate, lastSeenActivityDate);
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
describe('#_prepareUrl()', () => {
|
|
634
|
+
beforeEach(() => {
|
|
635
|
+
webex.internal.device.webSocketUrl = 'ws://example.com';
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
it('uses device default webSocketUrl', () =>
|
|
639
|
+
webex.internal.mercury._prepareUrl().then((wsUrl) => assert.match(wsUrl, /example.com/)));
|
|
640
|
+
it('uses provided webSocketUrl', () =>
|
|
641
|
+
webex.internal.mercury
|
|
642
|
+
._prepareUrl('ws://provided.com')
|
|
643
|
+
.then((wsUrl) => assert.match(wsUrl, /provided.com/)));
|
|
644
|
+
it('requests text-mode WebSockets', () =>
|
|
645
|
+
webex.internal.mercury
|
|
646
|
+
._prepareUrl()
|
|
647
|
+
.then((wsUrl) => assert.match(wsUrl, /outboundWireFormat=text/)));
|
|
648
|
+
|
|
649
|
+
it('requests the buffer state message', () =>
|
|
650
|
+
webex.internal.mercury
|
|
651
|
+
._prepareUrl()
|
|
652
|
+
.then((wsUrl) => assert.match(wsUrl, /bufferStates=true/)));
|
|
653
|
+
|
|
654
|
+
it('does not add conditional properties', () =>
|
|
655
|
+
webex.internal.mercury._prepareUrl().then((wsUrl) => {
|
|
656
|
+
assert.notMatch(wsUrl, /mercuryRegistrationStatus/);
|
|
657
|
+
assert.notMatch(wsUrl, /mercuryRegistrationStatus/);
|
|
658
|
+
assert.notMatch(wsUrl, /isRegistrationRefreshEnabled/);
|
|
659
|
+
assert.notMatch(wsUrl, /multipleConnections/);
|
|
660
|
+
}));
|
|
661
|
+
|
|
662
|
+
describe('when web-high-availability is enabled', () => {
|
|
663
|
+
it('uses webSocketUrl provided by device', () => {
|
|
664
|
+
webex.internal.device.useServiceCatalogUrl = sinon
|
|
665
|
+
.stub()
|
|
666
|
+
.returns(Promise.resolve('ws://example-2.com'));
|
|
667
|
+
webex.internal.feature.getFeature.onCall(0).returns(Promise.resolve(true));
|
|
668
|
+
|
|
669
|
+
return webex.internal.mercury
|
|
670
|
+
._prepareUrl()
|
|
671
|
+
.then((wsUrl) => assert.match(wsUrl, /example-2.com/));
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
describe("when 'web-shared-socket' is enabled", () => {
|
|
676
|
+
beforeEach(() => {
|
|
677
|
+
webex.internal.feature.getFeature.returns(Promise.resolve(true));
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
it('requests shared socket support', () =>
|
|
681
|
+
webex.internal.mercury
|
|
682
|
+
._prepareUrl()
|
|
683
|
+
.then((wsUrl) => assert.match(wsUrl, /isRegistrationRefreshEnabled=true/)));
|
|
684
|
+
|
|
685
|
+
it('requests the registration banner', () =>
|
|
686
|
+
webex.internal.mercury
|
|
687
|
+
._prepareUrl()
|
|
688
|
+
.then((wsUrl) => assert.match(wsUrl, /mercuryRegistrationStatus=true/)));
|
|
689
|
+
|
|
690
|
+
it('does not request the buffer state message', () =>
|
|
691
|
+
webex.internal.mercury._prepareUrl().then((wsUrl) => {
|
|
692
|
+
assert.match(wsUrl, /mercuryRegistrationStatus=true/);
|
|
693
|
+
assert.notMatch(wsUrl, /bufferStates/);
|
|
694
|
+
}));
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
describe('when using an ephemeral device', () => {
|
|
698
|
+
beforeEach(() => {
|
|
699
|
+
webex.config.device.ephemeral = true;
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('indicates multiple connections may be coming from this user', () =>
|
|
703
|
+
webex.internal.mercury
|
|
704
|
+
._prepareUrl()
|
|
705
|
+
.then((wsUrl) => assert.match(wsUrl, /multipleConnections/)));
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
});
|