@webex/internal-plugin-mercury 3.9.0-multi-llms.4 → 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/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, oneFlight} from '@webex/common';
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
- socket: 'object',
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
- @oneFlight
108
- connect(webSocketUrl) {
109
- if (this.connected) {
110
- this.logger.info(`${this.namespace}: already connected, will not connect again`);
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
- return Promise.resolve(
190
+ const connectPromise = Promise.resolve(
124
191
  this.webex.internal.device.registered || this.webex.internal.device.register()
125
- ).then(() => {
126
- this.logger.info(`${this.namespace}: connecting`);
192
+ )
193
+ .then(() => {
194
+ this.logger.info(`${this.namespace}: connecting ${sessionId}`);
127
195
 
128
- return this._connectWithBackoff(webSocketUrl);
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.disconnect(
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
- if (this.backoffCall) {
151
- this.logger.info(`${this.namespace}: aborting connection`);
152
- this.backoffCall.abort();
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
- if (this.socket) {
156
- this.socket.removeAllListeners('message');
157
- this.once('offline', resolve);
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('sequence-mismatch', ...args));
244
- socket.on('ping-pong-latency', (...args) => this._emit('ping-pong-latency', ...args));
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
- if (!this.backoffCall) {
249
- const msg = `${this.namespace}: prevent socket open when backoffCall no longer defined`;
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.socket = socket;
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 && this.backoffCall && this.backoffCall?.getNumRetries() > 0) {
305
- this._emit('connection_failed', reason, {retries: this.backoffCall?.getNumRetries()});
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
- this.backoffCall?.getNumRetries() === 0 ? reason.stack : ''
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(`${this.namespace}: received authorization error, reauthorizing`);
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(`${this.namespace}: received unrecoverable response from mercury`);
337
- this.backoffCall.abort();
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(`${this.namespace}: failed to handle connection failure`, reason);
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.connecting = false;
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
- this.connected = true;
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._emit('online');
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((callback) => {
394
- this.logger.info(`${this.namespace}: executing connection attempt ${call.getNumRetries()}`);
395
- this._attemptConnection(webSocketUrl, callback);
396
- }, onComplete);
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('Mercury Connection Aborted'));
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 ${number + 1} in ${delay} ms`
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
- this.trigger(...args);
586
+ // Validate args before processing
587
+ if (args && args.length > 0) {
588
+ this.trigger(...args);
589
+ }
443
590
  } catch (error) {
444
- this.logger.error(
445
- `${this.namespace}: error occurred in event handler:`,
446
- error,
447
- ' with args: ',
448
- args
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
- const socketUrl = this.socket.url;
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
- this.socket.removeAllListeners();
482
- this.unset('socket');
483
- this.connected = false;
484
- this._emit('offline', event);
485
- this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(false);
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('offline.permanent', event);
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('offline.replaced', event);
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('offline.transient', event);
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('offline.transient', event);
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('offline.permanent', event);
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('offline.permanent', event);
695
+ this._emit(`offline.permanent${suffix}`, event);
531
696
  }
532
697
  } catch (error) {
533
- this.logger.error(`${this.namespace}: error occurred in close handler`, 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._emit('event', event.data);
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(`${this.namespace}: error occurred processing socket message`, reason);
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