@webex/internal-plugin-mercury 3.9.0-multi-llms.3 → 3.9.0-multi-llms.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mercury.js +321 -131
- package/dist/mercury.js.map +1 -1
- package/dist/socket/socket-base.js +15 -0
- package/dist/socket/socket-base.js.map +1 -1
- package/package.json +17 -17
- package/src/mercury.js +279 -105
- package/src/socket/socket-base.js +13 -0
- package/test/unit/spec/mercury-events.js +20 -2
- package/test/unit/spec/mercury.js +53 -22
package/src/mercury.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import url from 'url';
|
|
7
7
|
|
|
8
8
|
import {WebexPlugin} from '@webex/webex-core';
|
|
9
|
-
import {deprecated
|
|
9
|
+
import {deprecated} from '@webex/common';
|
|
10
10
|
import {camelCase, get, set} from 'lodash';
|
|
11
11
|
import backoff from 'backoff';
|
|
12
12
|
|
|
@@ -25,6 +25,7 @@ const normalReconnectReasons = ['idle', 'done (forced)', 'pong not received', 'p
|
|
|
25
25
|
const Mercury = WebexPlugin.extend({
|
|
26
26
|
namespace: 'Mercury',
|
|
27
27
|
lastError: undefined,
|
|
28
|
+
defaultSessionId: 'mercury-default-session',
|
|
28
29
|
|
|
29
30
|
session: {
|
|
30
31
|
connected: {
|
|
@@ -39,7 +40,14 @@ const Mercury = WebexPlugin.extend({
|
|
|
39
40
|
default: false,
|
|
40
41
|
type: 'boolean',
|
|
41
42
|
},
|
|
42
|
-
|
|
43
|
+
sockets: {
|
|
44
|
+
default: () => new Map(),
|
|
45
|
+
type: 'object',
|
|
46
|
+
},
|
|
47
|
+
backoffCalls: {
|
|
48
|
+
default: () => new Map(),
|
|
49
|
+
type: 'object',
|
|
50
|
+
},
|
|
43
51
|
localClusterServiceUrls: 'object',
|
|
44
52
|
mercuryTimeOffset: {
|
|
45
53
|
default: undefined,
|
|
@@ -104,29 +112,96 @@ const Mercury = WebexPlugin.extend({
|
|
|
104
112
|
return this.lastError;
|
|
105
113
|
},
|
|
106
114
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Get all active socket connections
|
|
117
|
+
* @returns {Map} Map of sessionId to socket instances
|
|
118
|
+
*/
|
|
119
|
+
getSockets() {
|
|
120
|
+
return this.sockets;
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get a specific socket by connection ID
|
|
125
|
+
* @param {string} sessionId - The connection identifier
|
|
126
|
+
* @returns {Socket|undefined} The socket instance or undefined if not found
|
|
127
|
+
*/
|
|
128
|
+
getSocket(sessionId = this.defaultSessionId) {
|
|
129
|
+
return this.sockets.get(sessionId);
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if any sockets are connected
|
|
134
|
+
* @returns {boolean} True if at least one socket is connected
|
|
135
|
+
*/
|
|
136
|
+
hasConnectedSockets() {
|
|
137
|
+
for (const socket of this.sockets.values()) {
|
|
138
|
+
if (socket && socket.connected) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return false;
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if any sockets are connecting
|
|
148
|
+
* @returns {boolean} True if at least one socket is connected
|
|
149
|
+
*/
|
|
150
|
+
hasConnectingSockets() {
|
|
151
|
+
for (const socket of this.sockets.values()) {
|
|
152
|
+
if (socket && socket.connecting) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return false;
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// @oneFlight
|
|
161
|
+
connect(webSocketUrl, sessionId = this.defaultSessionId) {
|
|
162
|
+
if (!this._connectPromises) this._connectPromises = new Map();
|
|
163
|
+
|
|
164
|
+
// First check if there's already a connection promise for this session
|
|
165
|
+
if (this._connectPromises.has(sessionId)) {
|
|
166
|
+
this.logger.info(
|
|
167
|
+
`${this.namespace}: connection ${sessionId} already in progress, returning existing promise`
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
return this._connectPromises.get(sessionId);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const sessionSocket = this.sockets.get(sessionId);
|
|
174
|
+
if (sessionSocket?.connected || sessionSocket?.connecting) {
|
|
175
|
+
this.logger.info(
|
|
176
|
+
`${this.namespace}: connection ${sessionId} already connected, will not connect again`
|
|
177
|
+
);
|
|
111
178
|
|
|
112
179
|
return Promise.resolve();
|
|
113
180
|
}
|
|
114
181
|
|
|
115
182
|
this.connecting = true;
|
|
116
183
|
|
|
117
|
-
this.logger.info(`${this.namespace}: starting connection attempt`);
|
|
184
|
+
this.logger.info(`${this.namespace}: starting connection attempt for ${sessionId}`);
|
|
118
185
|
this.logger.info(
|
|
119
186
|
`${this.namespace}: debug_mercury_logging stack: `,
|
|
120
187
|
new Error('debug_mercury_logging').stack
|
|
121
188
|
);
|
|
122
189
|
|
|
123
|
-
|
|
190
|
+
const connectPromise = Promise.resolve(
|
|
124
191
|
this.webex.internal.device.registered || this.webex.internal.device.register()
|
|
125
|
-
)
|
|
126
|
-
|
|
192
|
+
)
|
|
193
|
+
.then(() => {
|
|
194
|
+
this.logger.info(`${this.namespace}: connecting ${sessionId}`);
|
|
127
195
|
|
|
128
|
-
|
|
129
|
-
|
|
196
|
+
return this._connectWithBackoff(webSocketUrl, sessionId);
|
|
197
|
+
})
|
|
198
|
+
.finally(() => {
|
|
199
|
+
this._connectPromises.delete(sessionId);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
this._connectPromises.set(sessionId, connectPromise);
|
|
203
|
+
|
|
204
|
+
return connectPromise;
|
|
130
205
|
},
|
|
131
206
|
|
|
132
207
|
logout() {
|
|
@@ -136,7 +211,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
136
211
|
new Error('debug_mercury_logging').stack
|
|
137
212
|
);
|
|
138
213
|
|
|
139
|
-
return this.
|
|
214
|
+
return this.disconnectAll(
|
|
140
215
|
this.config.beforeLogoutOptionsCloseReason &&
|
|
141
216
|
!normalReconnectReasons.includes(this.config.beforeLogoutOptionsCloseReason)
|
|
142
217
|
? {code: 3050, reason: this.config.beforeLogoutOptionsCloseReason}
|
|
@@ -144,21 +219,58 @@ const Mercury = WebexPlugin.extend({
|
|
|
144
219
|
);
|
|
145
220
|
},
|
|
146
221
|
|
|
147
|
-
@oneFlight
|
|
148
|
-
disconnect(options) {
|
|
222
|
+
// @oneFlight
|
|
223
|
+
disconnect(options, sessionId = this.defaultSessionId) {
|
|
149
224
|
return new Promise((resolve) => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
this.
|
|
225
|
+
const backoffCall = this.backoffCalls.get(sessionId);
|
|
226
|
+
if (backoffCall) {
|
|
227
|
+
this.logger.info(`${this.namespace}: aborting connection ${sessionId}`);
|
|
228
|
+
backoffCall.abort();
|
|
229
|
+
this.backoffCalls.delete(sessionId);
|
|
153
230
|
}
|
|
154
231
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
this.
|
|
158
|
-
resolve(this.socket.close(options || undefined));
|
|
232
|
+
// Clean up any pending connection promises
|
|
233
|
+
if (this._connectPromises) {
|
|
234
|
+
this._connectPromises.delete(sessionId);
|
|
159
235
|
}
|
|
160
236
|
|
|
237
|
+
const sessionSocket = this.sockets.get(sessionId);
|
|
238
|
+
const suffix = sessionId === this.defaultSessionId ? '' : `:${sessionId}`;
|
|
239
|
+
|
|
240
|
+
if (sessionSocket) {
|
|
241
|
+
sessionSocket.removeAllListeners('message');
|
|
242
|
+
sessionSocket.connecting = false;
|
|
243
|
+
sessionSocket.connected = false;
|
|
244
|
+
this.once(sessionId === this.defaultSessionId ? 'offline' : `offline${suffix}`, resolve);
|
|
245
|
+
resolve(sessionSocket.close(options || undefined));
|
|
246
|
+
}
|
|
161
247
|
resolve();
|
|
248
|
+
|
|
249
|
+
// Update overall connected status
|
|
250
|
+
this.connected = this.hasConnectedSockets();
|
|
251
|
+
});
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Disconnect all socket connections
|
|
256
|
+
* @param {object} options - Close options
|
|
257
|
+
* @returns {Promise} Promise that resolves when all connections are closed
|
|
258
|
+
*/
|
|
259
|
+
disconnectAll(options) {
|
|
260
|
+
const disconnectPromises = [];
|
|
261
|
+
|
|
262
|
+
for (const sessionId of this.sockets.keys()) {
|
|
263
|
+
disconnectPromises.push(this.disconnect(options, sessionId));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return Promise.all(disconnectPromises).then(() => {
|
|
267
|
+
this.connected = false;
|
|
268
|
+
this.sockets.clear();
|
|
269
|
+
this.backoffCalls.clear();
|
|
270
|
+
// Clear connection promises to prevent stale promises
|
|
271
|
+
if (this._connectPromises) {
|
|
272
|
+
this._connectPromises.clear();
|
|
273
|
+
}
|
|
162
274
|
});
|
|
163
275
|
},
|
|
164
276
|
|
|
@@ -233,20 +345,23 @@ const Mercury = WebexPlugin.extend({
|
|
|
233
345
|
});
|
|
234
346
|
},
|
|
235
347
|
|
|
236
|
-
_attemptConnection(socketUrl, callback) {
|
|
348
|
+
_attemptConnection(socketUrl, sessionId, callback) {
|
|
237
349
|
const socket = new Socket();
|
|
350
|
+
socket.connecting = true;
|
|
238
351
|
let attemptWSUrl;
|
|
352
|
+
const suffix = sessionId === this.defaultSessionId ? '' : `:${sessionId}`;
|
|
239
353
|
|
|
240
|
-
socket.on('close', (...args) => this._onclose(...args));
|
|
241
|
-
socket.on('message', (...args) => this._onmessage(...args));
|
|
242
|
-
socket.on('pong', (...args) => this._setTimeOffset(...args));
|
|
243
|
-
socket.on('sequence-mismatch', (...args) => this._emit(
|
|
244
|
-
socket.on('ping-pong-latency', (...args) => this._emit(
|
|
354
|
+
socket.on('close', (...args) => this._onclose(sessionId, ...args));
|
|
355
|
+
socket.on('message', (...args) => this._onmessage(sessionId, ...args));
|
|
356
|
+
socket.on('pong', (...args) => this._setTimeOffset(sessionId, ...args));
|
|
357
|
+
socket.on('sequence-mismatch', (...args) => this._emit(`sequence-mismatch${suffix}`, ...args));
|
|
358
|
+
socket.on('ping-pong-latency', (...args) => this._emit(`ping-pong-latency${suffix}`, ...args));
|
|
245
359
|
|
|
246
360
|
Promise.all([this._prepareUrl(socketUrl), this.webex.credentials.getUserToken()])
|
|
247
361
|
.then(([webSocketUrl, token]) => {
|
|
248
|
-
|
|
249
|
-
|
|
362
|
+
const backoffCall = this.backoffCalls.get(sessionId);
|
|
363
|
+
if (!backoffCall) {
|
|
364
|
+
const msg = `${this.namespace}: prevent socket open when backoffCall no longer defined for ${sessionId}`;
|
|
250
365
|
|
|
251
366
|
this.logger.info(msg);
|
|
252
367
|
|
|
@@ -260,27 +375,28 @@ const Mercury = WebexPlugin.extend({
|
|
|
260
375
|
pingInterval: this.config.pingInterval,
|
|
261
376
|
pongTimeout: this.config.pongTimeout,
|
|
262
377
|
token: token.toString(),
|
|
263
|
-
trackingId: `${this.webex.sessionId}_${Date.now()}`,
|
|
378
|
+
trackingId: `${this.webex.sessionId}_${sessionId}_${Date.now()}`,
|
|
264
379
|
logger: this.logger,
|
|
265
380
|
};
|
|
266
381
|
|
|
267
382
|
// if the consumer has supplied request options use them
|
|
268
383
|
if (this.webex.config.defaultMercuryOptions) {
|
|
269
|
-
this.logger.info(`${this.namespace}: setting custom options`);
|
|
384
|
+
this.logger.info(`${this.namespace}: setting custom options for ${sessionId}`);
|
|
270
385
|
options = {...options, ...this.webex.config.defaultMercuryOptions};
|
|
271
386
|
}
|
|
272
387
|
|
|
273
388
|
// Set the socket before opening it. This allows a disconnect() to close
|
|
274
389
|
// the socket if it is in the process of being opened.
|
|
275
|
-
this.
|
|
390
|
+
this.sockets.set(sessionId, socket);
|
|
391
|
+
this.socket = this.sockets.get(this.defaultSessionId) || socket;
|
|
276
392
|
|
|
277
|
-
this.logger.info(`${this.namespace} connection url: ${webSocketUrl}`);
|
|
393
|
+
this.logger.info(`${this.namespace} connection url for ${sessionId}: ${webSocketUrl}`);
|
|
278
394
|
|
|
279
395
|
return socket.open(webSocketUrl, options);
|
|
280
396
|
})
|
|
281
397
|
.then(() => {
|
|
282
398
|
this.logger.info(
|
|
283
|
-
`${this.namespace}: connected to mercury, success, action: connected, url: ${attemptWSUrl}`
|
|
399
|
+
`${this.namespace}: connected to mercury, success, action: connected, sessionId: ${sessionId}, url: ${attemptWSUrl}`
|
|
284
400
|
);
|
|
285
401
|
callback();
|
|
286
402
|
|
|
@@ -297,30 +413,36 @@ const Mercury = WebexPlugin.extend({
|
|
|
297
413
|
.catch((reason) => {
|
|
298
414
|
this.lastError = reason; // remember the last error
|
|
299
415
|
|
|
416
|
+
const backoffCall = this.backoffCalls.get(sessionId);
|
|
300
417
|
// Suppress connection errors that appear to be network related. This
|
|
301
418
|
// may end up suppressing metrics during outages, but we might not care
|
|
302
419
|
// (especially since many of our outages happen in a way that client
|
|
303
420
|
// metrics can't be trusted).
|
|
304
|
-
if (reason.code !== 1006 &&
|
|
305
|
-
this._emit(
|
|
421
|
+
if (reason.code !== 1006 && backoffCall && backoffCall?.getNumRetries() > 0) {
|
|
422
|
+
this._emit(`connection_failed${suffix}`, reason, {
|
|
423
|
+
sessionId,
|
|
424
|
+
retries: backoffCall?.getNumRetries(),
|
|
425
|
+
});
|
|
306
426
|
}
|
|
307
427
|
this.logger.info(
|
|
308
|
-
`${this.namespace}: connection attempt failed`,
|
|
428
|
+
`${this.namespace}: connection attempt failed for ${sessionId}`,
|
|
309
429
|
reason,
|
|
310
|
-
|
|
430
|
+
backoffCall?.getNumRetries() === 0 ? reason.stack : ''
|
|
311
431
|
);
|
|
312
432
|
// UnknownResponse is produced by IE for any 4XXX; treated it like a bad
|
|
313
433
|
// web socket url and let WDM handle the token checking
|
|
314
434
|
if (reason instanceof UnknownResponse) {
|
|
315
435
|
this.logger.info(
|
|
316
|
-
`${this.namespace}: received unknown response code, refreshing device registration`
|
|
436
|
+
`${this.namespace}: received unknown response code for ${sessionId}, refreshing device registration`
|
|
317
437
|
);
|
|
318
438
|
|
|
319
439
|
return this.webex.internal.device.refresh().then(() => callback(reason));
|
|
320
440
|
}
|
|
321
441
|
// NotAuthorized implies expired token
|
|
322
442
|
if (reason instanceof NotAuthorized) {
|
|
323
|
-
this.logger.info(
|
|
443
|
+
this.logger.info(
|
|
444
|
+
`${this.namespace}: received authorization error for ${sessionId}, reauthorizing`
|
|
445
|
+
);
|
|
324
446
|
|
|
325
447
|
return this.webex.credentials.refresh({force: true}).then(() => callback(reason));
|
|
326
448
|
}
|
|
@@ -333,8 +455,10 @@ const Mercury = WebexPlugin.extend({
|
|
|
333
455
|
// BadRequest implies current credentials are for a Service Account
|
|
334
456
|
// Forbidden implies current user is not entitle for Webex
|
|
335
457
|
if (reason instanceof BadRequest || reason instanceof Forbidden) {
|
|
336
|
-
this.logger.warn(
|
|
337
|
-
|
|
458
|
+
this.logger.warn(
|
|
459
|
+
`${this.namespace}: received unrecoverable response from mercury for ${sessionId}`
|
|
460
|
+
);
|
|
461
|
+
backoffCall?.abort();
|
|
338
462
|
|
|
339
463
|
return callback(reason);
|
|
340
464
|
}
|
|
@@ -344,7 +468,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
344
468
|
.then((haMessagingEnabled) => {
|
|
345
469
|
if (haMessagingEnabled) {
|
|
346
470
|
this.logger.info(
|
|
347
|
-
`${this.namespace}: received a generic connection error, will try to connect to another datacenter. failed, action: 'failed', url: ${attemptWSUrl} error: ${reason.message}`
|
|
471
|
+
`${this.namespace}: received a generic connection error for ${sessionId}, will try to connect to another datacenter. failed, action: 'failed', url: ${attemptWSUrl} error: ${reason.message}`
|
|
348
472
|
);
|
|
349
473
|
|
|
350
474
|
return this.webex.internal.services.markFailedUrl(attemptWSUrl);
|
|
@@ -358,42 +482,59 @@ const Mercury = WebexPlugin.extend({
|
|
|
358
482
|
return callback(reason);
|
|
359
483
|
})
|
|
360
484
|
.catch((reason) => {
|
|
361
|
-
this.logger.error(
|
|
485
|
+
this.logger.error(
|
|
486
|
+
`${this.namespace}: failed to handle connection failure for ${sessionId}`,
|
|
487
|
+
reason
|
|
488
|
+
);
|
|
362
489
|
callback(reason);
|
|
363
490
|
});
|
|
364
491
|
},
|
|
365
492
|
|
|
366
|
-
_connectWithBackoff(webSocketUrl) {
|
|
493
|
+
_connectWithBackoff(webSocketUrl, sessionId) {
|
|
367
494
|
return new Promise((resolve, reject) => {
|
|
368
495
|
// eslint gets confused about whether or not call is actually used
|
|
369
496
|
// eslint-disable-next-line prefer-const
|
|
370
497
|
let call;
|
|
371
|
-
const onComplete = (err) => {
|
|
372
|
-
this.
|
|
373
|
-
|
|
374
|
-
this.backoffCall = undefined;
|
|
498
|
+
const onComplete = (err, sid = sessionId) => {
|
|
499
|
+
this.backoffCalls.delete(sid);
|
|
375
500
|
if (err) {
|
|
376
501
|
this.logger.info(
|
|
377
502
|
`${
|
|
378
503
|
this.namespace
|
|
379
|
-
}: failed to connect after ${call.getNumRetries()} retries; log statement about next retry was inaccurate; ${err}`
|
|
504
|
+
}: failed to connect ${sid} after ${call.getNumRetries()} retries; log statement about next retry was inaccurate; ${err}`
|
|
380
505
|
);
|
|
381
506
|
|
|
382
507
|
return reject(err);
|
|
383
508
|
}
|
|
384
|
-
|
|
509
|
+
// Update overall connected status
|
|
510
|
+
const sessionSocket = this.sockets.get(sid);
|
|
511
|
+
if (sessionSocket) {
|
|
512
|
+
sessionSocket.connecting = false;
|
|
513
|
+
sessionSocket.connected = true;
|
|
514
|
+
}
|
|
515
|
+
// @ts-ignore
|
|
516
|
+
this.connecting = this.hasConnectingSockets();
|
|
517
|
+
this.connected = this.hasConnectedSockets();
|
|
385
518
|
this.hasEverConnected = true;
|
|
386
|
-
this.
|
|
519
|
+
const suffix = sid === this.defaultSessionId ? '' : `:${sid}`;
|
|
520
|
+
this._emit(`online${suffix}`, {sessionId: sid});
|
|
387
521
|
this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(true);
|
|
388
522
|
|
|
389
523
|
return resolve();
|
|
390
524
|
};
|
|
391
525
|
|
|
392
526
|
// eslint-disable-next-line prefer-reflect
|
|
393
|
-
call = backoff.call(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
527
|
+
call = backoff.call(
|
|
528
|
+
(callback) => {
|
|
529
|
+
this.logger.info(
|
|
530
|
+
`${
|
|
531
|
+
this.namespace
|
|
532
|
+
}: executing connection attempt ${call.getNumRetries()} for ${sessionId}`
|
|
533
|
+
);
|
|
534
|
+
this._attemptConnection(webSocketUrl, sessionId, callback);
|
|
535
|
+
},
|
|
536
|
+
(err) => onComplete(err, sessionId)
|
|
537
|
+
);
|
|
397
538
|
|
|
398
539
|
call.setStrategy(
|
|
399
540
|
new backoff.ExponentialStrategy({
|
|
@@ -408,9 +549,12 @@ const Mercury = WebexPlugin.extend({
|
|
|
408
549
|
call.failAfter(this.config.maxRetries);
|
|
409
550
|
}
|
|
410
551
|
|
|
552
|
+
// Store the call BEFORE setting up event handlers to prevent race conditions
|
|
553
|
+
this.backoffCalls.set(sessionId, call);
|
|
554
|
+
|
|
411
555
|
call.on('abort', () => {
|
|
412
|
-
this.logger.info(`${this.namespace}: connection aborted`);
|
|
413
|
-
reject(new Error(
|
|
556
|
+
this.logger.info(`${this.namespace}: connection aborted for ${sessionId}`);
|
|
557
|
+
reject(new Error(`Mercury Connection Aborted for ${sessionId}`));
|
|
414
558
|
});
|
|
415
559
|
|
|
416
560
|
call.on('callback', (err) => {
|
|
@@ -419,7 +563,9 @@ const Mercury = WebexPlugin.extend({
|
|
|
419
563
|
const delay = Math.min(call.strategy_.nextBackoffDelay_, this.config.backoffTimeMax);
|
|
420
564
|
|
|
421
565
|
this.logger.info(
|
|
422
|
-
`${this.namespace}: failed to connect; attempting retry ${
|
|
566
|
+
`${this.namespace}: failed to connect ${sessionId}; attempting retry ${
|
|
567
|
+
number + 1
|
|
568
|
+
} in ${delay} ms`
|
|
423
569
|
);
|
|
424
570
|
/* istanbul ignore if */
|
|
425
571
|
if (process.env.NODE_ENV === 'development') {
|
|
@@ -428,25 +574,32 @@ const Mercury = WebexPlugin.extend({
|
|
|
428
574
|
|
|
429
575
|
return;
|
|
430
576
|
}
|
|
431
|
-
this.logger.info(`${this.namespace}: connected`);
|
|
577
|
+
this.logger.info(`${this.namespace}: connected ${sessionId}`);
|
|
432
578
|
});
|
|
433
579
|
|
|
434
580
|
call.start();
|
|
435
|
-
|
|
436
|
-
this.backoffCall = call;
|
|
437
581
|
});
|
|
438
582
|
},
|
|
439
583
|
|
|
440
584
|
_emit(...args) {
|
|
441
585
|
try {
|
|
442
|
-
|
|
586
|
+
// Validate args before processing
|
|
587
|
+
if (args && args.length > 0) {
|
|
588
|
+
this.trigger(...args);
|
|
589
|
+
}
|
|
443
590
|
} catch (error) {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
error
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
591
|
+
// Safely handle errors without causing additional issues during cleanup
|
|
592
|
+
try {
|
|
593
|
+
this.logger.error(
|
|
594
|
+
`${this.namespace}: error occurred in event handler:`,
|
|
595
|
+
error,
|
|
596
|
+
' with args: ',
|
|
597
|
+
args
|
|
598
|
+
);
|
|
599
|
+
} catch (logError) {
|
|
600
|
+
// If even logging fails, just ignore to prevent cascading errors during cleanup
|
|
601
|
+
console.error('Mercury _emit error handling failed:', logError);
|
|
602
|
+
}
|
|
450
603
|
}
|
|
451
604
|
},
|
|
452
605
|
|
|
@@ -470,78 +623,94 @@ const Mercury = WebexPlugin.extend({
|
|
|
470
623
|
return handlers;
|
|
471
624
|
},
|
|
472
625
|
|
|
473
|
-
_onclose(event) {
|
|
626
|
+
_onclose(sessionId, event) {
|
|
474
627
|
// I don't see any way to avoid the complexity or statement count in here.
|
|
475
628
|
/* eslint complexity: [0] */
|
|
476
629
|
|
|
477
630
|
try {
|
|
478
631
|
const reason = event.reason && event.reason.toLowerCase();
|
|
479
|
-
|
|
632
|
+
let sessionSocket = this.sockets.get(sessionId);
|
|
633
|
+
const socketUrl = sessionSocket?.url;
|
|
634
|
+
const suffix = sessionId === this.defaultSessionId ? '' : `:${sessionId}`;
|
|
635
|
+
event.sessionId = sessionId;
|
|
636
|
+
this.sockets.delete(sessionId);
|
|
637
|
+
|
|
638
|
+
if (sessionSocket) {
|
|
639
|
+
sessionSocket.removeAllListeners();
|
|
640
|
+
sessionSocket = null;
|
|
641
|
+
this._emit(`offline${suffix}`, event);
|
|
642
|
+
}
|
|
480
643
|
|
|
481
|
-
|
|
482
|
-
this.
|
|
483
|
-
this.connected =
|
|
484
|
-
|
|
485
|
-
this.
|
|
644
|
+
// Update overall connected status
|
|
645
|
+
this.connecting = this.hasConnectingSockets();
|
|
646
|
+
this.connected = this.hasConnectedSockets();
|
|
647
|
+
|
|
648
|
+
if (!this.connected) {
|
|
649
|
+
this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(false);
|
|
650
|
+
}
|
|
486
651
|
|
|
487
652
|
switch (event.code) {
|
|
488
653
|
case 1003:
|
|
489
654
|
// metric: disconnect
|
|
490
655
|
this.logger.info(
|
|
491
|
-
`${this.namespace}: Mercury service rejected last message; will not reconnect: ${event.reason}`
|
|
656
|
+
`${this.namespace}: Mercury service rejected last message for ${sessionId}; will not reconnect: ${event.reason}`
|
|
492
657
|
);
|
|
493
|
-
this._emit(
|
|
658
|
+
this._emit(`offline.permanent${suffix}`, event);
|
|
494
659
|
break;
|
|
495
660
|
case 4000:
|
|
496
661
|
// metric: disconnect
|
|
497
|
-
this.logger.info(`${this.namespace}: socket replaced; will not reconnect`);
|
|
498
|
-
this._emit(
|
|
662
|
+
this.logger.info(`${this.namespace}: socket ${sessionId} replaced; will not reconnect`);
|
|
663
|
+
this._emit(`offline.replaced${suffix}`, event);
|
|
499
664
|
break;
|
|
500
665
|
case 1001:
|
|
501
666
|
case 1005:
|
|
502
667
|
case 1006:
|
|
503
668
|
case 1011:
|
|
504
|
-
this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
|
|
505
|
-
this._emit(
|
|
506
|
-
this._reconnect(socketUrl);
|
|
669
|
+
this.logger.info(`${this.namespace}: socket ${sessionId} disconnected; reconnecting`);
|
|
670
|
+
this._emit(`offline.transient${suffix}`, event);
|
|
671
|
+
this._reconnect(socketUrl, sessionId);
|
|
507
672
|
// metric: disconnect
|
|
508
673
|
// if (code == 1011 && reason !== ping error) metric: unexpected disconnect
|
|
509
674
|
break;
|
|
510
675
|
case 1000:
|
|
511
676
|
case 3050: // 3050 indicates logout form of closure, default to old behavior, use config reason defined by consumer to proceed with the permanent block
|
|
512
677
|
if (normalReconnectReasons.includes(reason)) {
|
|
513
|
-
this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
|
|
514
|
-
this._emit(
|
|
515
|
-
this._reconnect(socketUrl);
|
|
678
|
+
this.logger.info(`${this.namespace}: socket ${sessionId} disconnected; reconnecting`);
|
|
679
|
+
this._emit(`offline.transient${suffix}`, event);
|
|
680
|
+
this._reconnect(socketUrl, sessionId);
|
|
516
681
|
// metric: disconnect
|
|
517
682
|
// if (reason === done forced) metric: force closure
|
|
518
683
|
} else {
|
|
519
684
|
this.logger.info(
|
|
520
|
-
`${this.namespace}: socket disconnected; will not reconnect: ${event.reason}`
|
|
685
|
+
`${this.namespace}: socket ${sessionId} disconnected; will not reconnect: ${event.reason}`
|
|
521
686
|
);
|
|
522
|
-
this._emit(
|
|
687
|
+
this._emit(`offline.permanent${suffix}`, event);
|
|
523
688
|
}
|
|
524
689
|
break;
|
|
525
690
|
default:
|
|
526
691
|
this.logger.info(
|
|
527
|
-
`${this.namespace}: socket disconnected unexpectedly; will not reconnect`
|
|
692
|
+
`${this.namespace}: socket ${sessionId} disconnected unexpectedly; will not reconnect`
|
|
528
693
|
);
|
|
529
694
|
// unexpected disconnect
|
|
530
|
-
this._emit(
|
|
695
|
+
this._emit(`offline.permanent${suffix}`, event);
|
|
531
696
|
}
|
|
532
697
|
} catch (error) {
|
|
533
|
-
this.logger.error(
|
|
698
|
+
this.logger.error(
|
|
699
|
+
`${this.namespace}: error occurred in close handler for ${sessionId}`,
|
|
700
|
+
error
|
|
701
|
+
);
|
|
534
702
|
}
|
|
535
703
|
},
|
|
536
704
|
|
|
537
|
-
_onmessage(event) {
|
|
538
|
-
this._setTimeOffset(event);
|
|
705
|
+
_onmessage(sessionId, event) {
|
|
706
|
+
this._setTimeOffset(sessionId, event);
|
|
539
707
|
const envelope = event.data;
|
|
540
708
|
|
|
541
709
|
if (process.env.ENABLE_MERCURY_LOGGING) {
|
|
542
|
-
this.logger.debug(`${this.namespace}: message envelope: `, envelope);
|
|
710
|
+
this.logger.debug(`${this.namespace}: message envelope from ${sessionId}: `, envelope);
|
|
543
711
|
}
|
|
544
712
|
|
|
713
|
+
envelope.sessionId = sessionId;
|
|
545
714
|
const {data} = envelope;
|
|
546
715
|
|
|
547
716
|
this._applyOverrides(data);
|
|
@@ -556,7 +725,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
556
725
|
resolve((this.webex[namespace] || this.webex.internal[namespace])[name](data))
|
|
557
726
|
).catch((reason) =>
|
|
558
727
|
this.logger.error(
|
|
559
|
-
`${this.namespace}: error occurred in autowired event handler for ${data.eventType}`,
|
|
728
|
+
`${this.namespace}: error occurred in autowired event handler for ${data.eventType} from ${sessionId}`,
|
|
560
729
|
reason
|
|
561
730
|
)
|
|
562
731
|
);
|
|
@@ -564,32 +733,37 @@ const Mercury = WebexPlugin.extend({
|
|
|
564
733
|
Promise.resolve()
|
|
565
734
|
)
|
|
566
735
|
.then(() => {
|
|
567
|
-
this.
|
|
736
|
+
const suffix = sessionId === this.defaultSessionId ? '' : `:${sessionId}`;
|
|
737
|
+
|
|
738
|
+
this._emit(`event${suffix}`, envelope);
|
|
568
739
|
const [namespace] = data.eventType.split('.');
|
|
569
740
|
|
|
570
741
|
if (namespace === data.eventType) {
|
|
571
|
-
this._emit(`event:${namespace}`, envelope);
|
|
742
|
+
this._emit(`event:${namespace}${suffix}`, envelope);
|
|
572
743
|
} else {
|
|
573
|
-
this._emit(`event:${namespace}`, envelope);
|
|
574
|
-
this._emit(`event:${data.eventType}`, envelope);
|
|
744
|
+
this._emit(`event:${namespace}${suffix}`, envelope);
|
|
745
|
+
this._emit(`event:${data.eventType}${suffix}`, envelope);
|
|
575
746
|
}
|
|
576
747
|
})
|
|
577
748
|
.catch((reason) => {
|
|
578
|
-
this.logger.error(
|
|
749
|
+
this.logger.error(
|
|
750
|
+
`${this.namespace}: error occurred processing socket message from ${sessionId}`,
|
|
751
|
+
reason
|
|
752
|
+
);
|
|
579
753
|
});
|
|
580
754
|
},
|
|
581
755
|
|
|
582
|
-
_setTimeOffset(event) {
|
|
756
|
+
_setTimeOffset(sessionId, event) {
|
|
583
757
|
const {wsWriteTimestamp} = event.data;
|
|
584
758
|
if (typeof wsWriteTimestamp === 'number' && wsWriteTimestamp > 0) {
|
|
585
759
|
this.mercuryTimeOffset = Date.now() - wsWriteTimestamp;
|
|
586
760
|
}
|
|
587
761
|
},
|
|
588
762
|
|
|
589
|
-
_reconnect(webSocketUrl) {
|
|
590
|
-
this.logger.info(`${this.namespace}: reconnecting`);
|
|
763
|
+
_reconnect(webSocketUrl, sessionId = this.defaultSessionId) {
|
|
764
|
+
this.logger.info(`${this.namespace}: reconnecting ${sessionId}`);
|
|
591
765
|
|
|
592
|
-
return this.connect(webSocketUrl);
|
|
766
|
+
return this.connect(webSocketUrl, sessionId);
|
|
593
767
|
},
|
|
594
768
|
});
|
|
595
769
|
|