@webex/internal-plugin-mercury 3.0.0-beta.40 → 3.0.0-beta.400
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 +14 -1
- package/dist/mercury.js +54 -53
- package/dist/mercury.js.map +1 -1
- package/dist/socket/socket-base.js +43 -27
- package/dist/socket/socket-base.js.map +1 -1
- package/package.json +14 -14
- package/src/mercury.js +51 -52
- package/src/socket/socket-base.js +61 -28
- package/test/integration/spec/webex.js +3 -2
- package/test/unit/spec/mercury.js +59 -8
- package/test/unit/spec/socket.js +17 -0
package/src/mercury.js
CHANGED
|
@@ -33,6 +33,10 @@ const Mercury = WebexPlugin.extend({
|
|
|
33
33
|
default: false,
|
|
34
34
|
type: 'boolean',
|
|
35
35
|
},
|
|
36
|
+
hasEverConnected: {
|
|
37
|
+
default: false,
|
|
38
|
+
type: 'boolean',
|
|
39
|
+
},
|
|
36
40
|
socket: 'object',
|
|
37
41
|
localClusterServiceUrls: 'object',
|
|
38
42
|
},
|
|
@@ -49,7 +53,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
49
53
|
@oneFlight
|
|
50
54
|
connect(webSocketUrl) {
|
|
51
55
|
if (this.connected) {
|
|
52
|
-
this.logger.info(
|
|
56
|
+
this.logger.info(`${this.namespace}: already connected, will not connect again`);
|
|
53
57
|
|
|
54
58
|
return Promise.resolve();
|
|
55
59
|
}
|
|
@@ -59,7 +63,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
59
63
|
return Promise.resolve(
|
|
60
64
|
this.webex.internal.device.registered || this.webex.internal.device.register()
|
|
61
65
|
).then(() => {
|
|
62
|
-
this.logger.info(
|
|
66
|
+
this.logger.info(`${this.namespace}: connecting`);
|
|
63
67
|
|
|
64
68
|
return this._connectWithBackoff(webSocketUrl);
|
|
65
69
|
});
|
|
@@ -69,16 +73,14 @@ const Mercury = WebexPlugin.extend({
|
|
|
69
73
|
disconnect() {
|
|
70
74
|
return new Promise((resolve) => {
|
|
71
75
|
if (this.backoffCall) {
|
|
72
|
-
this.logger.info(
|
|
76
|
+
this.logger.info(`${this.namespace}: aborting connection`);
|
|
73
77
|
this.backoffCall.abort();
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
if (this.socket) {
|
|
77
81
|
this.socket.removeAllListeners('message');
|
|
78
82
|
this.once('offline', resolve);
|
|
79
|
-
this.socket.close();
|
|
80
|
-
|
|
81
|
-
return;
|
|
83
|
+
resolve(this.socket.close());
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
resolve();
|
|
@@ -161,11 +163,12 @@ const Mercury = WebexPlugin.extend({
|
|
|
161
163
|
socket.on('close', (...args) => this._onclose(...args));
|
|
162
164
|
socket.on('message', (...args) => this._onmessage(...args));
|
|
163
165
|
socket.on('sequence-mismatch', (...args) => this._emit('sequence-mismatch', ...args));
|
|
166
|
+
socket.on('ping-pong-latency', (...args) => this._emit('ping-pong-latency', ...args));
|
|
164
167
|
|
|
165
168
|
Promise.all([this._prepareUrl(socketUrl), this.webex.credentials.getUserToken()])
|
|
166
169
|
.then(([webSocketUrl, token]) => {
|
|
167
170
|
if (!this.backoffCall) {
|
|
168
|
-
const msg =
|
|
171
|
+
const msg = `${this.namespace}: prevent socket open when backoffCall no longer defined`;
|
|
169
172
|
|
|
170
173
|
this.logger.info(msg);
|
|
171
174
|
|
|
@@ -185,7 +188,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
185
188
|
|
|
186
189
|
// if the consumer has supplied request options use them
|
|
187
190
|
if (this.webex.config.defaultMercuryOptions) {
|
|
188
|
-
this.logger.info(
|
|
191
|
+
this.logger.info(`${this.namespace}: setting custom options`);
|
|
189
192
|
options = {...options, ...this.webex.config.defaultMercuryOptions};
|
|
190
193
|
}
|
|
191
194
|
|
|
@@ -193,18 +196,14 @@ const Mercury = WebexPlugin.extend({
|
|
|
193
196
|
// the socket if it is in the process of being opened.
|
|
194
197
|
this.socket = socket;
|
|
195
198
|
|
|
199
|
+
this.logger.info(`${this.namespace} connection url: ${webSocketUrl}`);
|
|
200
|
+
|
|
196
201
|
return socket.open(webSocketUrl, options);
|
|
197
202
|
})
|
|
198
203
|
.then(() => {
|
|
199
|
-
this.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
},
|
|
203
|
-
tags: {
|
|
204
|
-
action: 'connected',
|
|
205
|
-
url: attemptWSUrl,
|
|
206
|
-
},
|
|
207
|
-
});
|
|
204
|
+
this.logger.info(
|
|
205
|
+
`${this.namespace}: connected to mercury, success, action: connected, url: ${attemptWSUrl}`
|
|
206
|
+
);
|
|
208
207
|
callback();
|
|
209
208
|
|
|
210
209
|
return this.webex.internal.feature
|
|
@@ -225,19 +224,19 @@ const Mercury = WebexPlugin.extend({
|
|
|
225
224
|
if (reason.code !== 1006 && this.backoffCall && this.backoffCall.getNumRetries() > 0) {
|
|
226
225
|
this._emit('connection_failed', reason, {retries: this.backoffCall.getNumRetries()});
|
|
227
226
|
}
|
|
228
|
-
this.logger.info(
|
|
227
|
+
this.logger.info(`${this.namespace}: connection attempt failed`, reason);
|
|
229
228
|
// UnknownResponse is produced by IE for any 4XXX; treated it like a bad
|
|
230
229
|
// web socket url and let WDM handle the token checking
|
|
231
230
|
if (reason instanceof UnknownResponse) {
|
|
232
231
|
this.logger.info(
|
|
233
|
-
|
|
232
|
+
`${this.namespace}: received unknown response code, refreshing device registration`
|
|
234
233
|
);
|
|
235
234
|
|
|
236
235
|
return this.webex.internal.device.refresh().then(() => callback(reason));
|
|
237
236
|
}
|
|
238
237
|
// NotAuthorized implies expired token
|
|
239
238
|
if (reason instanceof NotAuthorized) {
|
|
240
|
-
this.logger.info(
|
|
239
|
+
this.logger.info(`${this.namespace}: received authorization error, reauthorizing`);
|
|
241
240
|
|
|
242
241
|
return this.webex.credentials.refresh({force: true}).then(() => callback(reason));
|
|
243
242
|
}
|
|
@@ -250,7 +249,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
250
249
|
// BadRequest implies current credentials are for a Service Account
|
|
251
250
|
// Forbidden implies current user is not entitle for Webex
|
|
252
251
|
if (reason instanceof BadRequest || reason instanceof Forbidden) {
|
|
253
|
-
this.logger.warn(
|
|
252
|
+
this.logger.warn(`${this.namespace}: received unrecoverable response from mercury`);
|
|
254
253
|
this.backoffCall.abort();
|
|
255
254
|
|
|
256
255
|
return callback(reason);
|
|
@@ -261,18 +260,8 @@ const Mercury = WebexPlugin.extend({
|
|
|
261
260
|
.then((haMessagingEnabled) => {
|
|
262
261
|
if (haMessagingEnabled) {
|
|
263
262
|
this.logger.info(
|
|
264
|
-
|
|
263
|
+
`${this.namespace}: received a generic connection error, will try to connect to another datacenter. failed, action: 'failed', url: ${attemptWSUrl} error: ${reason.message}`
|
|
265
264
|
);
|
|
266
|
-
this.webex.internal.metrics.submitClientMetrics('web-ha-mercury', {
|
|
267
|
-
fields: {
|
|
268
|
-
success: false,
|
|
269
|
-
},
|
|
270
|
-
tags: {
|
|
271
|
-
action: 'failed',
|
|
272
|
-
error: reason.message,
|
|
273
|
-
url: attemptWSUrl,
|
|
274
|
-
},
|
|
275
|
-
});
|
|
276
265
|
|
|
277
266
|
return this.webex.internal.services.markFailedUrl(attemptWSUrl);
|
|
278
267
|
}
|
|
@@ -285,7 +274,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
285
274
|
return callback(reason);
|
|
286
275
|
})
|
|
287
276
|
.catch((reason) => {
|
|
288
|
-
this.logger.error(
|
|
277
|
+
this.logger.error(`${this.namespace}: failed to handle connection failure`, reason);
|
|
289
278
|
callback(reason);
|
|
290
279
|
});
|
|
291
280
|
},
|
|
@@ -301,12 +290,15 @@ const Mercury = WebexPlugin.extend({
|
|
|
301
290
|
this.backoffCall = undefined;
|
|
302
291
|
if (err) {
|
|
303
292
|
this.logger.info(
|
|
304
|
-
|
|
293
|
+
`${
|
|
294
|
+
this.namespace
|
|
295
|
+
}: failed to connect after ${call.getNumRetries()} retries; log statement about next retry was inaccurate; ${err}`
|
|
305
296
|
);
|
|
306
297
|
|
|
307
298
|
return reject(err);
|
|
308
299
|
}
|
|
309
300
|
this.connected = true;
|
|
301
|
+
this.hasEverConnected = true;
|
|
310
302
|
this._emit('online');
|
|
311
303
|
|
|
312
304
|
return resolve();
|
|
@@ -314,7 +306,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
314
306
|
|
|
315
307
|
// eslint-disable-next-line prefer-reflect
|
|
316
308
|
call = backoff.call((callback) => {
|
|
317
|
-
this.logger.info(
|
|
309
|
+
this.logger.info(`${this.namespace}: executing connection attempt ${call.getNumRetries()}`);
|
|
318
310
|
this._attemptConnection(webSocketUrl, callback);
|
|
319
311
|
}, onComplete);
|
|
320
312
|
|
|
@@ -325,12 +317,14 @@ const Mercury = WebexPlugin.extend({
|
|
|
325
317
|
})
|
|
326
318
|
);
|
|
327
319
|
|
|
328
|
-
if (this.config.
|
|
320
|
+
if (this.config.initialConnectionMaxRetries && !this.hasEverConnected) {
|
|
321
|
+
call.failAfter(this.config.initialConnectionMaxRetries);
|
|
322
|
+
} else if (this.config.maxRetries) {
|
|
329
323
|
call.failAfter(this.config.maxRetries);
|
|
330
324
|
}
|
|
331
325
|
|
|
332
326
|
call.on('abort', () => {
|
|
333
|
-
this.logger.info(
|
|
327
|
+
this.logger.info(`${this.namespace}: connection aborted`);
|
|
334
328
|
reject(new Error('Mercury Connection Aborted'));
|
|
335
329
|
});
|
|
336
330
|
|
|
@@ -340,16 +334,16 @@ const Mercury = WebexPlugin.extend({
|
|
|
340
334
|
const delay = Math.min(call.strategy_.nextBackoffDelay_, this.config.backoffTimeMax);
|
|
341
335
|
|
|
342
336
|
this.logger.info(
|
|
343
|
-
|
|
337
|
+
`${this.namespace}: failed to connect; attempting retry ${number + 1} in ${delay} ms`
|
|
344
338
|
);
|
|
345
339
|
/* istanbul ignore if */
|
|
346
340
|
if (process.env.NODE_ENV === 'development') {
|
|
347
|
-
this.logger.debug(
|
|
341
|
+
this.logger.debug(`${this.namespace}: `, err, err.stack);
|
|
348
342
|
}
|
|
349
343
|
|
|
350
344
|
return;
|
|
351
345
|
}
|
|
352
|
-
this.logger.info(
|
|
346
|
+
this.logger.info(`${this.namespace}: connected`);
|
|
353
347
|
});
|
|
354
348
|
|
|
355
349
|
call.start();
|
|
@@ -362,7 +356,10 @@ const Mercury = WebexPlugin.extend({
|
|
|
362
356
|
try {
|
|
363
357
|
this.trigger(...args);
|
|
364
358
|
} catch (error) {
|
|
365
|
-
this.logger.error(
|
|
359
|
+
this.logger.error(`${this.namespace}: error occurred in event handler`, {
|
|
360
|
+
error,
|
|
361
|
+
arguments: args,
|
|
362
|
+
});
|
|
366
363
|
}
|
|
367
364
|
},
|
|
368
365
|
|
|
@@ -403,20 +400,20 @@ const Mercury = WebexPlugin.extend({
|
|
|
403
400
|
case 1003:
|
|
404
401
|
// metric: disconnect
|
|
405
402
|
this.logger.info(
|
|
406
|
-
|
|
403
|
+
`${this.namespace}: Mercury service rejected last message; will not reconnect: ${event.reason}`
|
|
407
404
|
);
|
|
408
405
|
this._emit('offline.permanent', event);
|
|
409
406
|
break;
|
|
410
407
|
case 4000:
|
|
411
408
|
// metric: disconnect
|
|
412
|
-
this.logger.info(
|
|
409
|
+
this.logger.info(`${this.namespace}: socket replaced; will not reconnect`);
|
|
413
410
|
this._emit('offline.replaced', event);
|
|
414
411
|
break;
|
|
415
412
|
case 1001:
|
|
416
413
|
case 1005:
|
|
417
414
|
case 1006:
|
|
418
415
|
case 1011:
|
|
419
|
-
this.logger.info(
|
|
416
|
+
this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
|
|
420
417
|
this._emit('offline.transient', event);
|
|
421
418
|
this._reconnect(socketUrl);
|
|
422
419
|
// metric: disconnect
|
|
@@ -424,23 +421,25 @@ const Mercury = WebexPlugin.extend({
|
|
|
424
421
|
break;
|
|
425
422
|
case 1000:
|
|
426
423
|
if (normalReconnectReasons.includes(reason)) {
|
|
427
|
-
this.logger.info(
|
|
424
|
+
this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
|
|
428
425
|
this._emit('offline.transient', event);
|
|
429
426
|
this._reconnect(socketUrl);
|
|
430
427
|
// metric: disconnect
|
|
431
428
|
// if (reason === done forced) metric: force closure
|
|
432
429
|
} else {
|
|
433
|
-
this.logger.info(
|
|
430
|
+
this.logger.info(`${this.namespace}: socket disconnected; will not reconnect`);
|
|
434
431
|
this._emit('offline.permanent', event);
|
|
435
432
|
}
|
|
436
433
|
break;
|
|
437
434
|
default:
|
|
438
|
-
this.logger.info(
|
|
435
|
+
this.logger.info(
|
|
436
|
+
`${this.namespace}: socket disconnected unexpectedly; will not reconnect`
|
|
437
|
+
);
|
|
439
438
|
// unexpected disconnect
|
|
440
439
|
this._emit('offline.permanent', event);
|
|
441
440
|
}
|
|
442
441
|
} catch (error) {
|
|
443
|
-
this.logger.error(
|
|
442
|
+
this.logger.error(`${this.namespace}: error occurred in close handler`, error);
|
|
444
443
|
}
|
|
445
444
|
},
|
|
446
445
|
|
|
@@ -448,7 +447,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
448
447
|
const envelope = event.data;
|
|
449
448
|
|
|
450
449
|
if (process.env.ENABLE_MERCURY_LOGGING) {
|
|
451
|
-
this.logger.debug(
|
|
450
|
+
this.logger.debug(`${this.namespace}: message envelope: `, envelope);
|
|
452
451
|
}
|
|
453
452
|
|
|
454
453
|
const {data} = envelope;
|
|
@@ -465,7 +464,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
465
464
|
resolve((this.webex[namespace] || this.webex.internal[namespace])[name](data))
|
|
466
465
|
).catch((reason) =>
|
|
467
466
|
this.logger.error(
|
|
468
|
-
|
|
467
|
+
`${this.namespace}: error occurred in autowired event handler for ${data.eventType}`,
|
|
469
468
|
reason
|
|
470
469
|
)
|
|
471
470
|
);
|
|
@@ -484,12 +483,12 @@ const Mercury = WebexPlugin.extend({
|
|
|
484
483
|
}
|
|
485
484
|
})
|
|
486
485
|
.catch((reason) => {
|
|
487
|
-
this.logger.error(
|
|
486
|
+
this.logger.error(`${this.namespace}: error occurred processing socket message`, reason);
|
|
488
487
|
});
|
|
489
488
|
},
|
|
490
489
|
|
|
491
490
|
_reconnect(webSocketUrl) {
|
|
492
|
-
this.logger.info(
|
|
491
|
+
this.logger.info(`${this.namespace}: reconnecting`);
|
|
493
492
|
|
|
494
493
|
return this.connect(webSocketUrl);
|
|
495
494
|
},
|
|
@@ -30,6 +30,7 @@ export default class Socket extends EventEmitter {
|
|
|
30
30
|
*/
|
|
31
31
|
constructor() {
|
|
32
32
|
super();
|
|
33
|
+
this._domain = 'unknown-domain';
|
|
33
34
|
this.onmessage = this.onmessage.bind(this);
|
|
34
35
|
this.onclose = this.onclose.bind(this);
|
|
35
36
|
}
|
|
@@ -111,10 +112,10 @@ export default class Socket extends EventEmitter {
|
|
|
111
112
|
return;
|
|
112
113
|
}
|
|
113
114
|
// logger is defined once open is called
|
|
114
|
-
this.logger.info(
|
|
115
|
+
this.logger.info(`socket,${this._domain}: closing`);
|
|
115
116
|
|
|
116
117
|
if (socket.readyState === 2 || socket.readyState === 3) {
|
|
117
|
-
this.logger.info(
|
|
118
|
+
this.logger.info(`socket,${this._domain}: already closed`);
|
|
118
119
|
resolve();
|
|
119
120
|
|
|
120
121
|
return;
|
|
@@ -134,7 +135,7 @@ export default class Socket extends EventEmitter {
|
|
|
134
135
|
|
|
135
136
|
const closeTimer = safeSetTimeout(() => {
|
|
136
137
|
try {
|
|
137
|
-
this.logger.info(
|
|
138
|
+
this.logger.info(`socket,${this._domain}: no close event received, forcing closure`);
|
|
138
139
|
resolve(
|
|
139
140
|
this.onclose({
|
|
140
141
|
code: 1000,
|
|
@@ -142,12 +143,12 @@ export default class Socket extends EventEmitter {
|
|
|
142
143
|
})
|
|
143
144
|
);
|
|
144
145
|
} catch (error) {
|
|
145
|
-
this.logger.warn(
|
|
146
|
+
this.logger.warn(`socket,${this._domain}: force-close failed`, error);
|
|
146
147
|
}
|
|
147
148
|
}, this.forceCloseDelay);
|
|
148
149
|
|
|
149
150
|
socket.onclose = (event) => {
|
|
150
|
-
this.logger.info(
|
|
151
|
+
this.logger.info(`socket,${this._domain}: close event fired`, event.code, event.reason);
|
|
151
152
|
clearTimeout(closeTimer);
|
|
152
153
|
this.onclose(event);
|
|
153
154
|
resolve(event);
|
|
@@ -171,6 +172,12 @@ export default class Socket extends EventEmitter {
|
|
|
171
172
|
* @returns {Promise}
|
|
172
173
|
*/
|
|
173
174
|
open(url, options) {
|
|
175
|
+
try {
|
|
176
|
+
this._domain = new URL(url).hostname;
|
|
177
|
+
} catch {
|
|
178
|
+
this._domain = url;
|
|
179
|
+
}
|
|
180
|
+
|
|
174
181
|
return new Promise((resolve, reject) => {
|
|
175
182
|
/* eslint complexity: [0] */
|
|
176
183
|
if (!url) {
|
|
@@ -201,7 +208,7 @@ export default class Socket extends EventEmitter {
|
|
|
201
208
|
|
|
202
209
|
const WebSocket = Socket.getWebSocketConstructor();
|
|
203
210
|
|
|
204
|
-
this.logger.info(
|
|
211
|
+
this.logger.info(`socket,${this._domain}: creating WebSocket`);
|
|
205
212
|
const socket = new WebSocket(url, [], options);
|
|
206
213
|
|
|
207
214
|
socket.binaryType = 'arraybuffer';
|
|
@@ -209,7 +216,7 @@ export default class Socket extends EventEmitter {
|
|
|
209
216
|
|
|
210
217
|
socket.onclose = (event) => {
|
|
211
218
|
event = this._fixCloseCode(event);
|
|
212
|
-
this.logger.info(
|
|
219
|
+
this.logger.info(`socket,${this._domain}: closed before open`, event.code, event.reason);
|
|
213
220
|
switch (event.code) {
|
|
214
221
|
case 1005:
|
|
215
222
|
// IE 11 doesn't seem to allow 4XXX codes, so if we get a 1005, assume
|
|
@@ -231,10 +238,10 @@ export default class Socket extends EventEmitter {
|
|
|
231
238
|
};
|
|
232
239
|
|
|
233
240
|
socket.onopen = () => {
|
|
234
|
-
this.logger.info(
|
|
241
|
+
this.logger.info(`socket,${this._domain}: connected`);
|
|
235
242
|
this._authorize()
|
|
236
243
|
.then(() => {
|
|
237
|
-
this.logger.info(
|
|
244
|
+
this.logger.info(`socket,${this._domain}: authorized`);
|
|
238
245
|
socket.onclose = this.onclose;
|
|
239
246
|
resolve();
|
|
240
247
|
})
|
|
@@ -242,11 +249,11 @@ export default class Socket extends EventEmitter {
|
|
|
242
249
|
};
|
|
243
250
|
|
|
244
251
|
socket.onerror = (event) => {
|
|
245
|
-
this.logger.warn(
|
|
252
|
+
this.logger.warn(`socket,${this._domain}: error event fired`, event);
|
|
246
253
|
};
|
|
247
254
|
|
|
248
255
|
sockets.set(this, socket);
|
|
249
|
-
this.logger.info(
|
|
256
|
+
this.logger.info(`socket,${this._domain}: waiting for server`);
|
|
250
257
|
});
|
|
251
258
|
}
|
|
252
259
|
|
|
@@ -256,7 +263,7 @@ export default class Socket extends EventEmitter {
|
|
|
256
263
|
* @returns {undefined}
|
|
257
264
|
*/
|
|
258
265
|
onclose(event) {
|
|
259
|
-
this.logger.info(
|
|
266
|
+
this.logger.info(`socket,${this._domain}: closed`, event.code, event.reason);
|
|
260
267
|
clearTimeout(this.pongTimer);
|
|
261
268
|
clearTimeout(this.pingTimer);
|
|
262
269
|
|
|
@@ -278,10 +285,10 @@ export default class Socket extends EventEmitter {
|
|
|
278
285
|
const data = JSON.parse(event.data);
|
|
279
286
|
const sequenceNumber = parseInt(data.sequenceNumber, 10);
|
|
280
287
|
|
|
281
|
-
this.logger.debug(
|
|
288
|
+
this.logger.debug(`socket,${this._domain}: sequence number: `, sequenceNumber);
|
|
282
289
|
if (this.expectedSequenceNumber && sequenceNumber !== this.expectedSequenceNumber) {
|
|
283
290
|
this.logger.debug(
|
|
284
|
-
`socket: sequence number mismatch indicates lost mercury message. expected: ${this.expectedSequenceNumber}, actual: ${sequenceNumber}`
|
|
291
|
+
`socket,${this._domain}: sequence number mismatch indicates lost mercury message. expected: ${this.expectedSequenceNumber}, actual: ${sequenceNumber}`
|
|
285
292
|
);
|
|
286
293
|
this.emit('sequence-mismatch', sequenceNumber, this.expectedSequenceNumber);
|
|
287
294
|
}
|
|
@@ -303,7 +310,7 @@ export default class Socket extends EventEmitter {
|
|
|
303
310
|
// message from Mercury. At this time, the only action we have is to
|
|
304
311
|
// ignore it and move on.
|
|
305
312
|
/* istanbul ignore next */
|
|
306
|
-
this.logger.warn(
|
|
313
|
+
this.logger.warn(`socket,${this._domain}: error while receiving WebSocket message`, error);
|
|
307
314
|
}
|
|
308
315
|
}
|
|
309
316
|
|
|
@@ -357,7 +364,7 @@ export default class Socket extends EventEmitter {
|
|
|
357
364
|
*/
|
|
358
365
|
_authorize() {
|
|
359
366
|
return new Promise((resolve) => {
|
|
360
|
-
this.logger.info(
|
|
367
|
+
this.logger.info(`socket,${this._domain}: authorizing`);
|
|
361
368
|
this.send({
|
|
362
369
|
id: uuid.v4(),
|
|
363
370
|
type: 'authorization',
|
|
@@ -395,12 +402,18 @@ export default class Socket extends EventEmitter {
|
|
|
395
402
|
if (event.code === 1005 && event.reason) {
|
|
396
403
|
switch (event.reason.toLowerCase()) {
|
|
397
404
|
case 'replaced':
|
|
398
|
-
this.logger.info(
|
|
405
|
+
this.logger.info(
|
|
406
|
+
`socket,${this._domain}: fixing CloseEvent code for reason: `,
|
|
407
|
+
event.reason
|
|
408
|
+
);
|
|
399
409
|
event.code = 4000;
|
|
400
410
|
break;
|
|
401
411
|
case 'authentication failed':
|
|
402
412
|
case 'authentication did not happen within the timeout window of 30000 seconds.':
|
|
403
|
-
this.logger.info(
|
|
413
|
+
this.logger.info(
|
|
414
|
+
`socket,${this._domain}: fixing CloseEvent code for reason: `,
|
|
415
|
+
event.reason
|
|
416
|
+
);
|
|
404
417
|
event.code = 1008;
|
|
405
418
|
break;
|
|
406
419
|
default:
|
|
@@ -420,10 +433,12 @@ export default class Socket extends EventEmitter {
|
|
|
420
433
|
_ping(id) {
|
|
421
434
|
const confirmPongId = (event) => {
|
|
422
435
|
try {
|
|
423
|
-
this.logger.debug(
|
|
436
|
+
this.logger.debug(`socket,${this._domain}: pong`, event.data.id);
|
|
424
437
|
if (event.data && event.data.id !== id) {
|
|
425
|
-
this.logger.info(
|
|
426
|
-
|
|
438
|
+
this.logger.info(
|
|
439
|
+
`socket,${this._domain}: received pong for wrong ping id, closing socket`
|
|
440
|
+
);
|
|
441
|
+
this.logger.debug(`socket,${this._domain}: expected`, id, 'received', event.data.id);
|
|
427
442
|
this.close({
|
|
428
443
|
code: 1000,
|
|
429
444
|
reason: 'Pong mismatch',
|
|
@@ -433,24 +448,29 @@ export default class Socket extends EventEmitter {
|
|
|
433
448
|
// This try/catch block was added as a debugging step; to the best of my
|
|
434
449
|
// knowledge, the above can never throw.
|
|
435
450
|
/* istanbul ignore next */
|
|
436
|
-
this.logger.error(
|
|
451
|
+
this.logger.error(`socket,${this._domain}: error occurred in confirmPongId`, error);
|
|
437
452
|
}
|
|
438
453
|
};
|
|
439
454
|
|
|
440
455
|
const onPongNotReceived = () => {
|
|
441
456
|
try {
|
|
442
|
-
this.logger.info(
|
|
457
|
+
this.logger.info(
|
|
458
|
+
`socket,${this._domain}: pong not receive in expected period, closing socket`
|
|
459
|
+
);
|
|
443
460
|
this.close({
|
|
444
461
|
code: 1000,
|
|
445
462
|
reason: 'Pong not received',
|
|
446
463
|
}).catch((reason) => {
|
|
447
|
-
this.logger.warn(
|
|
464
|
+
this.logger.warn(
|
|
465
|
+
`socket,${this._domain}: failed to close socket after missed pong`,
|
|
466
|
+
reason
|
|
467
|
+
);
|
|
448
468
|
});
|
|
449
469
|
} catch (error) {
|
|
450
470
|
// This try/catch block was added as a debugging step; to the best of my
|
|
451
471
|
// knowledge, the above can never throw.
|
|
452
472
|
/* istanbul ignore next */
|
|
453
|
-
this.logger.error(
|
|
473
|
+
this.logger.error(`socket,${this._domain}: error occurred in onPongNotReceived`, error);
|
|
454
474
|
}
|
|
455
475
|
};
|
|
456
476
|
|
|
@@ -462,16 +482,29 @@ export default class Socket extends EventEmitter {
|
|
|
462
482
|
// This try/catch block was added as a debugging step; to the best of my
|
|
463
483
|
// knowledge, the above can never throw.
|
|
464
484
|
/* istanbul ignore next */
|
|
465
|
-
this.logger.error(
|
|
485
|
+
this.logger.error(
|
|
486
|
+
`socket,${this._domain}: error occurred in scheduleNextPingAndCancelPongTimer`,
|
|
487
|
+
error
|
|
488
|
+
);
|
|
466
489
|
}
|
|
467
490
|
};
|
|
468
491
|
|
|
492
|
+
const calculateLatency = (pingTimestamp) => {
|
|
493
|
+
const now = performance.now();
|
|
494
|
+
const latency = now - pingTimestamp;
|
|
495
|
+
|
|
496
|
+
this.logger.debug(`socket,${this._domain}: latency: `, latency);
|
|
497
|
+
this.emit('ping-pong-latency', latency);
|
|
498
|
+
};
|
|
499
|
+
|
|
469
500
|
id = id || uuid.v4();
|
|
501
|
+
const pingTimestamp = performance.now();
|
|
502
|
+
|
|
470
503
|
this.pongTimer = safeSetTimeout(onPongNotReceived, this.pongTimeout);
|
|
471
504
|
this.once('pong', scheduleNextPingAndCancelPongTimer);
|
|
472
505
|
this.once('pong', confirmPongId);
|
|
473
|
-
|
|
474
|
-
this.logger.debug(`socket: ping ${id}`);
|
|
506
|
+
this.once('pong', () => calculateLatency(pingTimestamp));
|
|
507
|
+
this.logger.debug(`socket,${this._domain}: ping ${id}`);
|
|
475
508
|
|
|
476
509
|
return this.send({
|
|
477
510
|
id,
|
|
@@ -32,12 +32,13 @@ describe('plugin-mercury', function () {
|
|
|
32
32
|
);
|
|
33
33
|
|
|
34
34
|
describe('onBeforeLogout()', () => {
|
|
35
|
-
it('disconnects the web socket', () =>
|
|
35
|
+
it('disconnects the web socket', () => {
|
|
36
36
|
webex.logout({noRedirect: true}).then(() => {
|
|
37
37
|
assert.called(webex.internal.mercury.disconnect);
|
|
38
38
|
assert.isFalse(webex.internal.mercury.connected);
|
|
39
39
|
assert.called(webex.internal.device.unregister);
|
|
40
40
|
assert.isFalse(webex.internal.device.registered);
|
|
41
|
-
})
|
|
41
|
+
});
|
|
42
|
+
});
|
|
42
43
|
});
|
|
43
44
|
});
|
|
@@ -152,9 +152,8 @@ describe('plugin-mercury', () => {
|
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
describe('when `maxRetries` is set', () => {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
mercury.config.maxRetries = 2;
|
|
155
|
+
|
|
156
|
+
const check = () => {
|
|
158
157
|
socketOpenStub.restore();
|
|
159
158
|
socketOpenStub = sinon.stub(Socket.prototype, 'open');
|
|
160
159
|
socketOpenStub.returns(Promise.reject(new ConnectionError()));
|
|
@@ -182,12 +181,42 @@ describe('plugin-mercury', () => {
|
|
|
182
181
|
.then(() => {
|
|
183
182
|
assert.calledThrice(Socket.prototype.open);
|
|
184
183
|
clock.tick(5 * mercury.config.backoffTimeReset);
|
|
185
|
-
|
|
186
184
|
return assert.isRejected(promise);
|
|
187
185
|
})
|
|
188
186
|
.then(() => {
|
|
189
187
|
assert.calledThrice(Socket.prototype.open);
|
|
190
188
|
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// skipping due to apparent bug with lolex in all browsers but Chrome.
|
|
192
|
+
// if initial retries is zero and mercury has never connected max retries is used
|
|
193
|
+
skipInBrowser(it)('fails after `maxRetries` attempts', () => {
|
|
194
|
+
mercury.config.maxRetries = 2;
|
|
195
|
+
mercury.config.initialConnectionMaxRetries = 0;
|
|
196
|
+
|
|
197
|
+
return check();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// initial retries is non-zero so takes precedence over maxRetries when mercury has never connected
|
|
201
|
+
skipInBrowser(it)('fails after `initialConnectionMaxRetries` attempts', () => {
|
|
202
|
+
mercury.config.maxRetries = 0;
|
|
203
|
+
mercury.config.initialConnectionMaxRetries = 2;
|
|
204
|
+
return check();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// initial retries is non-zero so takes precedence over maxRetries when mercury has never connected
|
|
208
|
+
skipInBrowser(it)('fails after `initialConnectionMaxRetries` attempts', () => {
|
|
209
|
+
mercury.config.initialConnectionMaxRetries = 2;
|
|
210
|
+
mercury.config.maxRetries = 5;
|
|
211
|
+
return check();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// when mercury has connected maxRetries is used and the initialConnectionMaxRetries is ignored
|
|
215
|
+
skipInBrowser(it)('fails after `initialConnectionMaxRetries` attempts', () => {
|
|
216
|
+
mercury.config.initialConnectionMaxRetries = 5;
|
|
217
|
+
mercury.config.maxRetries = 2;
|
|
218
|
+
mercury.hasEverConnected = true;
|
|
219
|
+
return check();
|
|
191
220
|
});
|
|
192
221
|
});
|
|
193
222
|
|
|
@@ -560,7 +589,7 @@ describe('plugin-mercury', () => {
|
|
|
560
589
|
return promiseTick(webex.internal.mercury.config.backoffTimeReset).then(() => {
|
|
561
590
|
assert.equal(
|
|
562
591
|
reason.message,
|
|
563
|
-
'
|
|
592
|
+
'Mercury: prevent socket open when backoffCall no longer defined'
|
|
564
593
|
);
|
|
565
594
|
});
|
|
566
595
|
});
|
|
@@ -568,12 +597,21 @@ describe('plugin-mercury', () => {
|
|
|
568
597
|
});
|
|
569
598
|
|
|
570
599
|
describe('#_emit()', () => {
|
|
571
|
-
it('emits Error-safe events', () => {
|
|
600
|
+
it('emits Error-safe events and log the error with the call parameters', () => {
|
|
601
|
+
const error = 'error';
|
|
602
|
+
const event = {data: 'some data'};
|
|
572
603
|
mercury.on('break', () => {
|
|
573
|
-
throw
|
|
604
|
+
throw error;
|
|
574
605
|
});
|
|
606
|
+
sinon.stub(mercury.logger, 'error');
|
|
575
607
|
|
|
576
|
-
return Promise.resolve(mercury._emit('break'))
|
|
608
|
+
return Promise.resolve(mercury._emit('break', event)).then((res) => {
|
|
609
|
+
assert.calledWith(mercury.logger.error, 'Mercury: error occurred in event handler', {
|
|
610
|
+
error,
|
|
611
|
+
arguments: ['break', event],
|
|
612
|
+
});
|
|
613
|
+
return res;
|
|
614
|
+
});
|
|
577
615
|
});
|
|
578
616
|
});
|
|
579
617
|
|
|
@@ -707,5 +745,18 @@ describe('plugin-mercury', () => {
|
|
|
707
745
|
.then((wsUrl) => assert.match(wsUrl, /multipleConnections/)));
|
|
708
746
|
});
|
|
709
747
|
});
|
|
748
|
+
|
|
749
|
+
describe('ping pong latency event is forwarded', () => {
|
|
750
|
+
it('should forward ping pong latency event', () => {
|
|
751
|
+
const spy = sinon.spy();
|
|
752
|
+
|
|
753
|
+
mercury.on('ping-pong-latency', spy);
|
|
754
|
+
|
|
755
|
+
return mercury.connect().then(() => {
|
|
756
|
+
assert.calledWith(spy, 0);
|
|
757
|
+
assert.calledOnce(spy);
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
});
|
|
710
761
|
});
|
|
711
762
|
});
|