@webex/internal-plugin-mercury 3.9.0 → 3.10.0-multi-llms.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/dist/mercury.js +571 -164
- 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 +18 -18
- package/src/mercury.js +589 -137
- package/src/socket/socket-base.js +13 -0
- package/test/unit/spec/mercury-events.js +20 -2
- package/test/unit/spec/mercury.js +737 -23
- package/test/unit/spec/socket.js +6 -6
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,18 @@ 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
|
+
},
|
|
51
|
+
_shutdownSwitchoverBackoffCalls: {
|
|
52
|
+
default: () => new Map(),
|
|
53
|
+
type: 'object',
|
|
54
|
+
},
|
|
43
55
|
localClusterServiceUrls: 'object',
|
|
44
56
|
mercuryTimeOffset: {
|
|
45
57
|
default: undefined,
|
|
@@ -68,6 +80,121 @@ const Mercury = WebexPlugin.extend({
|
|
|
68
80
|
this.webex.internal.feature.updateFeature(envelope.data.featureToggle);
|
|
69
81
|
}
|
|
70
82
|
});
|
|
83
|
+
/*
|
|
84
|
+
* When Cluster Migrations, notify clients using ActiveClusterStatusEvent via mercury
|
|
85
|
+
* https://wwwin-github.cisco.com/pages/Webex/crr-docs/techdocs/rr-002.html#wip-notifying-clients-of-cluster-migrations
|
|
86
|
+
* */
|
|
87
|
+
this.on('event:ActiveClusterStatusEvent', (envelope) => {
|
|
88
|
+
if (
|
|
89
|
+
typeof this.webex.internal.services?.switchActiveClusterIds === 'function' &&
|
|
90
|
+
envelope &&
|
|
91
|
+
envelope.data
|
|
92
|
+
) {
|
|
93
|
+
this.webex.internal.services.switchActiveClusterIds(envelope.data?.activeClusters);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
/*
|
|
97
|
+
* Using cache-invalidation via mercury to instead the method of polling via the new /timestamp endpoint from u2c
|
|
98
|
+
* https://wwwin-github.cisco.com/pages/Webex/crr-docs/techdocs/rr-005.html#websocket-notifications
|
|
99
|
+
* */
|
|
100
|
+
this.on('event:u2c.cache-invalidation', (envelope) => {
|
|
101
|
+
if (
|
|
102
|
+
typeof this.webex.internal.services?.invalidateCache === 'function' &&
|
|
103
|
+
envelope &&
|
|
104
|
+
envelope.data
|
|
105
|
+
) {
|
|
106
|
+
this.webex.internal.services.invalidateCache(envelope.data?.timestamp);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Attach event listeners to a socket.
|
|
113
|
+
* @param {Socket} socket - The socket to attach listeners to
|
|
114
|
+
* @param {sessionId} sessionId - The socket related session ID
|
|
115
|
+
* @returns {void}
|
|
116
|
+
*/
|
|
117
|
+
_attachSocketEventListeners(socket, sessionId) {
|
|
118
|
+
socket.on('close', (event) => this._onclose(sessionId, event, socket));
|
|
119
|
+
socket.on('message', (...args) => this._onmessage(sessionId, ...args));
|
|
120
|
+
socket.on('pong', (...args) => this._setTimeOffset(sessionId, ...args));
|
|
121
|
+
socket.on('sequence-mismatch', (...args) =>
|
|
122
|
+
this._emit(sessionId, 'sequence-mismatch', ...args)
|
|
123
|
+
);
|
|
124
|
+
socket.on('ping-pong-latency', (...args) =>
|
|
125
|
+
this._emit(sessionId, 'ping-pong-latency', ...args)
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Handle imminent shutdown by establishing a new connection while keeping
|
|
131
|
+
* the current one alive (make-before-break).
|
|
132
|
+
* Idempotent: will no-op if already in progress.
|
|
133
|
+
* @param {string} sessionId - The session ID for which the shutdown is imminent
|
|
134
|
+
* @returns {void}
|
|
135
|
+
*/
|
|
136
|
+
_handleImminentShutdown(sessionId) {
|
|
137
|
+
const oldSocket = this.sockets.get(sessionId);
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
if (this._shutdownSwitchoverBackoffCalls.get(sessionId)) {
|
|
141
|
+
this.logger.info(
|
|
142
|
+
`${this.namespace}: [shutdown] switchover already in progress for ${sessionId}`
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this._shutdownSwitchoverId = `${Date.now()}`;
|
|
148
|
+
this.logger.info(
|
|
149
|
+
`${this.namespace}: [shutdown] switchover start, id=${this._shutdownSwitchoverId} for ${sessionId}`
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
this._connectWithBackoff(undefined, sessionId, {
|
|
153
|
+
isShutdownSwitchover: true,
|
|
154
|
+
attemptOptions: {
|
|
155
|
+
isShutdownSwitchover: true,
|
|
156
|
+
onSuccess: (newSocket, webSocketUrl) => {
|
|
157
|
+
this.logger.info(
|
|
158
|
+
`${this.namespace}: [shutdown] switchover connected, url: ${webSocketUrl} for ${sessionId}`
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Atomically switch active socket reference
|
|
162
|
+
this.socket = this.sockets.get(this.defaultSessionId) || newSocket;
|
|
163
|
+
this.connected = this.hasConnectedSockets(); // remain connected throughout
|
|
164
|
+
|
|
165
|
+
this._emit(sessionId, 'event:mercury_shutdown_switchover_complete', {
|
|
166
|
+
url: webSocketUrl,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
if (oldSocket) {
|
|
170
|
+
this.logger.info(
|
|
171
|
+
`${this.namespace}: [shutdown] old socket retained; server will close with 4001`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
.then(() => {
|
|
178
|
+
this.logger.info(
|
|
179
|
+
`${this.namespace}: [shutdown] switchover completed successfully for ${sessionId}`
|
|
180
|
+
);
|
|
181
|
+
})
|
|
182
|
+
.catch((err) => {
|
|
183
|
+
this.logger.info(
|
|
184
|
+
`${this.namespace}: [shutdown] switchover exhausted retries; will fall back to normal reconnection for ${sessionId}: `,
|
|
185
|
+
err
|
|
186
|
+
);
|
|
187
|
+
this._emit(sessionId, 'event:mercury_shutdown_switchover_failed', {reason: err});
|
|
188
|
+
// Old socket will eventually close with 4001, triggering normal reconnection
|
|
189
|
+
});
|
|
190
|
+
} catch (e) {
|
|
191
|
+
this.logger.error(
|
|
192
|
+
`${this.namespace}: [shutdown] error during switchover for ${sessionId}`,
|
|
193
|
+
e
|
|
194
|
+
);
|
|
195
|
+
this._shutdownSwitchoverBackoffCalls.delete(sessionId);
|
|
196
|
+
this._emit(sessionId, 'event:mercury_shutdown_switchover_failed', {reason: e});
|
|
197
|
+
}
|
|
71
198
|
},
|
|
72
199
|
|
|
73
200
|
/**
|
|
@@ -78,29 +205,96 @@ const Mercury = WebexPlugin.extend({
|
|
|
78
205
|
return this.lastError;
|
|
79
206
|
},
|
|
80
207
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Get all active socket connections
|
|
210
|
+
* @returns {Map} Map of sessionId to socket instances
|
|
211
|
+
*/
|
|
212
|
+
getSockets() {
|
|
213
|
+
return this.sockets;
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get a specific socket by connection ID
|
|
218
|
+
* @param {string} sessionId - The connection identifier
|
|
219
|
+
* @returns {Socket|undefined} The socket instance or undefined if not found
|
|
220
|
+
*/
|
|
221
|
+
getSocket(sessionId = this.defaultSessionId) {
|
|
222
|
+
return this.sockets.get(sessionId);
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Check if any sockets are connected
|
|
227
|
+
* @returns {boolean} True if at least one socket is connected
|
|
228
|
+
*/
|
|
229
|
+
hasConnectedSockets() {
|
|
230
|
+
for (const socket of this.sockets.values()) {
|
|
231
|
+
if (socket && socket.connected) {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return false;
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Check if any sockets are connecting
|
|
241
|
+
* @returns {boolean} True if at least one socket is connected
|
|
242
|
+
*/
|
|
243
|
+
hasConnectingSockets() {
|
|
244
|
+
for (const socket of this.sockets.values()) {
|
|
245
|
+
if (socket && socket.connecting) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return false;
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
// @oneFlight
|
|
254
|
+
connect(webSocketUrl, sessionId = this.defaultSessionId) {
|
|
255
|
+
if (!this._connectPromises) this._connectPromises = new Map();
|
|
256
|
+
|
|
257
|
+
// First check if there's already a connection promise for this session
|
|
258
|
+
if (this._connectPromises.has(sessionId)) {
|
|
259
|
+
this.logger.info(
|
|
260
|
+
`${this.namespace}: connection ${sessionId} already in progress, returning existing promise`
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
return this._connectPromises.get(sessionId);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const sessionSocket = this.sockets.get(sessionId);
|
|
267
|
+
if (sessionSocket?.connected || sessionSocket?.connecting) {
|
|
268
|
+
this.logger.info(
|
|
269
|
+
`${this.namespace}: connection ${sessionId} already connected, will not connect again`
|
|
270
|
+
);
|
|
85
271
|
|
|
86
272
|
return Promise.resolve();
|
|
87
273
|
}
|
|
88
274
|
|
|
89
275
|
this.connecting = true;
|
|
90
276
|
|
|
91
|
-
this.logger.info(`${this.namespace}: starting connection attempt`);
|
|
277
|
+
this.logger.info(`${this.namespace}: starting connection attempt for ${sessionId}`);
|
|
92
278
|
this.logger.info(
|
|
93
279
|
`${this.namespace}: debug_mercury_logging stack: `,
|
|
94
280
|
new Error('debug_mercury_logging').stack
|
|
95
281
|
);
|
|
96
282
|
|
|
97
|
-
|
|
283
|
+
const connectPromise = Promise.resolve(
|
|
98
284
|
this.webex.internal.device.registered || this.webex.internal.device.register()
|
|
99
|
-
)
|
|
100
|
-
|
|
285
|
+
)
|
|
286
|
+
.then(() => {
|
|
287
|
+
this.logger.info(`${this.namespace}: connecting ${sessionId}`);
|
|
101
288
|
|
|
102
|
-
|
|
103
|
-
|
|
289
|
+
return this._connectWithBackoff(webSocketUrl, sessionId);
|
|
290
|
+
})
|
|
291
|
+
.finally(() => {
|
|
292
|
+
this._connectPromises.delete(sessionId);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
this._connectPromises.set(sessionId, connectPromise);
|
|
296
|
+
|
|
297
|
+
return connectPromise;
|
|
104
298
|
},
|
|
105
299
|
|
|
106
300
|
logout() {
|
|
@@ -110,7 +304,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
110
304
|
new Error('debug_mercury_logging').stack
|
|
111
305
|
);
|
|
112
306
|
|
|
113
|
-
return this.
|
|
307
|
+
return this.disconnectAll(
|
|
114
308
|
this.config.beforeLogoutOptionsCloseReason &&
|
|
115
309
|
!normalReconnectReasons.includes(this.config.beforeLogoutOptionsCloseReason)
|
|
116
310
|
? {code: 3050, reason: this.config.beforeLogoutOptionsCloseReason}
|
|
@@ -118,21 +312,63 @@ const Mercury = WebexPlugin.extend({
|
|
|
118
312
|
);
|
|
119
313
|
},
|
|
120
314
|
|
|
121
|
-
@oneFlight
|
|
122
|
-
disconnect(options) {
|
|
315
|
+
// @oneFlight
|
|
316
|
+
disconnect(options, sessionId = this.defaultSessionId) {
|
|
123
317
|
return new Promise((resolve) => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.
|
|
318
|
+
const backoffCall = this.backoffCalls.get(sessionId);
|
|
319
|
+
if (backoffCall) {
|
|
320
|
+
this.logger.info(`${this.namespace}: aborting connection ${sessionId}`);
|
|
321
|
+
backoffCall.abort();
|
|
322
|
+
this.backoffCalls.delete(sessionId);
|
|
127
323
|
}
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
this.
|
|
131
|
-
|
|
132
|
-
|
|
324
|
+
const shutdownSwitchoverBackoffCalls = this._shutdownSwitchoverBackoffCalls.get(sessionId);
|
|
325
|
+
if (shutdownSwitchoverBackoffCalls) {
|
|
326
|
+
this.logger.info(`${this.namespace}: aborting shutdown switchover connection ${sessionId}`);
|
|
327
|
+
shutdownSwitchoverBackoffCalls.abort();
|
|
328
|
+
this._shutdownSwitchoverBackoffCalls.delete(sessionId);
|
|
133
329
|
}
|
|
330
|
+
// Clean up any pending connection promises
|
|
331
|
+
if (this._connectPromises) {
|
|
332
|
+
this._connectPromises.delete(sessionId);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const sessionSocket = this.sockets.get(sessionId);
|
|
336
|
+
const suffix = sessionId === this.defaultSessionId ? '' : `:${sessionId}`;
|
|
134
337
|
|
|
338
|
+
if (sessionSocket) {
|
|
339
|
+
sessionSocket.removeAllListeners('message');
|
|
340
|
+
sessionSocket.connecting = false;
|
|
341
|
+
sessionSocket.connected = false;
|
|
342
|
+
this.once(sessionId === this.defaultSessionId ? 'offline' : `offline${suffix}`, resolve);
|
|
343
|
+
resolve(sessionSocket.close(options || undefined));
|
|
344
|
+
}
|
|
135
345
|
resolve();
|
|
346
|
+
|
|
347
|
+
// Update overall connected status
|
|
348
|
+
this.connected = this.hasConnectedSockets();
|
|
349
|
+
});
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Disconnect all socket connections
|
|
354
|
+
* @param {object} options - Close options
|
|
355
|
+
* @returns {Promise} Promise that resolves when all connections are closed
|
|
356
|
+
*/
|
|
357
|
+
disconnectAll(options) {
|
|
358
|
+
const disconnectPromises = [];
|
|
359
|
+
|
|
360
|
+
for (const sessionId of this.sockets.keys()) {
|
|
361
|
+
disconnectPromises.push(this.disconnect(options, sessionId));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return Promise.all(disconnectPromises).then(() => {
|
|
365
|
+
this.connected = false;
|
|
366
|
+
this.sockets.clear();
|
|
367
|
+
this.backoffCalls.clear();
|
|
368
|
+
// Clear connection promises to prevent stale promises
|
|
369
|
+
if (this._connectPromises) {
|
|
370
|
+
this._connectPromises.clear();
|
|
371
|
+
}
|
|
136
372
|
});
|
|
137
373
|
},
|
|
138
374
|
|
|
@@ -207,55 +443,69 @@ const Mercury = WebexPlugin.extend({
|
|
|
207
443
|
});
|
|
208
444
|
},
|
|
209
445
|
|
|
210
|
-
_attemptConnection(socketUrl, callback) {
|
|
446
|
+
_attemptConnection(socketUrl, sessionId, callback, options = {}) {
|
|
447
|
+
const {isShutdownSwitchover = false, onSuccess = null} = options;
|
|
448
|
+
|
|
211
449
|
const socket = new Socket();
|
|
212
|
-
|
|
450
|
+
socket.connecting = true;
|
|
451
|
+
let newWSUrl;
|
|
213
452
|
|
|
214
|
-
|
|
215
|
-
socket.on('message', (...args) => this._onmessage(...args));
|
|
216
|
-
socket.on('pong', (...args) => this._setTimeOffset(...args));
|
|
217
|
-
socket.on('sequence-mismatch', (...args) => this._emit('sequence-mismatch', ...args));
|
|
218
|
-
socket.on('ping-pong-latency', (...args) => this._emit('ping-pong-latency', ...args));
|
|
453
|
+
this._attachSocketEventListeners(socket, sessionId);
|
|
219
454
|
|
|
220
|
-
|
|
221
|
-
.
|
|
222
|
-
|
|
223
|
-
const msg = `${this.namespace}: prevent socket open when backoffCall no longer defined`;
|
|
455
|
+
const backoffCall = isShutdownSwitchover
|
|
456
|
+
? this._shutdownSwitchoverBackoffCalls.get(sessionId)
|
|
457
|
+
: this.backoffCalls.get(sessionId);
|
|
224
458
|
|
|
225
|
-
|
|
459
|
+
// Check appropriate backoff call based on connection type
|
|
460
|
+
if (isShutdownSwitchover && !backoffCall) {
|
|
461
|
+
const msg = `${this.namespace}: prevent socket open when switchover backoff call no longer defined for ${sessionId}`;
|
|
462
|
+
const err = new Error(msg);
|
|
226
463
|
|
|
227
|
-
|
|
228
|
-
}
|
|
464
|
+
this.logger.info(msg);
|
|
229
465
|
|
|
230
|
-
|
|
466
|
+
// Call the callback with the error before rejecting
|
|
467
|
+
callback(err);
|
|
231
468
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
pingInterval: this.config.pingInterval,
|
|
235
|
-
pongTimeout: this.config.pongTimeout,
|
|
236
|
-
token: token.toString(),
|
|
237
|
-
trackingId: `${this.webex.sessionId}_${Date.now()}`,
|
|
238
|
-
logger: this.logger,
|
|
239
|
-
};
|
|
469
|
+
return Promise.reject(err);
|
|
470
|
+
}
|
|
240
471
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
options = {...options, ...this.webex.config.defaultMercuryOptions};
|
|
245
|
-
}
|
|
472
|
+
if (!isShutdownSwitchover && !backoffCall) {
|
|
473
|
+
const msg = `${this.namespace}: prevent socket open when backoffCall no longer defined for ${sessionId}`;
|
|
474
|
+
const err = new Error(msg);
|
|
246
475
|
|
|
247
|
-
|
|
248
|
-
// the socket if it is in the process of being opened.
|
|
249
|
-
this.socket = socket;
|
|
476
|
+
this.logger.info(msg);
|
|
250
477
|
|
|
251
|
-
|
|
478
|
+
// Call the callback with the error before rejecting
|
|
479
|
+
callback(err);
|
|
480
|
+
|
|
481
|
+
return Promise.reject(err);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// For shutdown switchover, don't set socket yet (make-before-break)
|
|
485
|
+
// For normal connection, set socket before opening to allow disconnect() to close it
|
|
486
|
+
if (!isShutdownSwitchover) {
|
|
487
|
+
this.sockets.set(sessionId, socket);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return this._prepareAndOpenSocket(socket, socketUrl, sessionId, isShutdownSwitchover)
|
|
491
|
+
.then((webSocketUrl) => {
|
|
492
|
+
newWSUrl = webSocketUrl;
|
|
252
493
|
|
|
253
|
-
return socket.open(webSocketUrl, options);
|
|
254
|
-
})
|
|
255
|
-
.then(() => {
|
|
256
494
|
this.logger.info(
|
|
257
|
-
`${this.namespace}:
|
|
495
|
+
`${this.namespace}: ${
|
|
496
|
+
isShutdownSwitchover ? '[shutdown] switchover' : ''
|
|
497
|
+
} connected to mercury, success, action: connected for ${sessionId}, url: ${newWSUrl}`
|
|
258
498
|
);
|
|
499
|
+
|
|
500
|
+
// Custom success handler for shutdown switchover
|
|
501
|
+
if (onSuccess) {
|
|
502
|
+
onSuccess(socket, webSocketUrl);
|
|
503
|
+
callback();
|
|
504
|
+
|
|
505
|
+
return Promise.resolve();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Default behavior for normal connection
|
|
259
509
|
callback();
|
|
260
510
|
|
|
261
511
|
return this.webex.internal.feature
|
|
@@ -269,32 +519,49 @@ const Mercury = WebexPlugin.extend({
|
|
|
269
519
|
});
|
|
270
520
|
})
|
|
271
521
|
.catch((reason) => {
|
|
522
|
+
// For shutdown, simpler error handling - just callback for retry
|
|
523
|
+
if (isShutdownSwitchover) {
|
|
524
|
+
this.logger.info(
|
|
525
|
+
`${this.namespace}: [shutdown] switchover attempt failed for ${sessionId}`,
|
|
526
|
+
reason
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
return callback(reason);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Normal connection error handling (existing complex logic)
|
|
272
533
|
this.lastError = reason; // remember the last error
|
|
273
534
|
|
|
535
|
+
const backoffCall = this.backoffCalls.get(sessionId);
|
|
274
536
|
// Suppress connection errors that appear to be network related. This
|
|
275
537
|
// may end up suppressing metrics during outages, but we might not care
|
|
276
538
|
// (especially since many of our outages happen in a way that client
|
|
277
539
|
// metrics can't be trusted).
|
|
278
|
-
if (reason.code !== 1006 &&
|
|
279
|
-
this._emit('connection_failed', reason, {
|
|
540
|
+
if (reason.code !== 1006 && backoffCall && backoffCall?.getNumRetries() > 0) {
|
|
541
|
+
this._emit(sessionId, 'connection_failed', reason, {
|
|
542
|
+
sessionId,
|
|
543
|
+
retries: backoffCall?.getNumRetries(),
|
|
544
|
+
});
|
|
280
545
|
}
|
|
281
546
|
this.logger.info(
|
|
282
|
-
`${this.namespace}: connection attempt failed`,
|
|
547
|
+
`${this.namespace}: connection attempt failed for ${sessionId}`,
|
|
283
548
|
reason,
|
|
284
|
-
|
|
549
|
+
backoffCall?.getNumRetries() === 0 ? reason.stack : ''
|
|
285
550
|
);
|
|
286
551
|
// UnknownResponse is produced by IE for any 4XXX; treated it like a bad
|
|
287
552
|
// web socket url and let WDM handle the token checking
|
|
288
553
|
if (reason instanceof UnknownResponse) {
|
|
289
554
|
this.logger.info(
|
|
290
|
-
`${this.namespace}: received unknown response code, refreshing device registration`
|
|
555
|
+
`${this.namespace}: received unknown response code for ${sessionId}, refreshing device registration`
|
|
291
556
|
);
|
|
292
557
|
|
|
293
558
|
return this.webex.internal.device.refresh().then(() => callback(reason));
|
|
294
559
|
}
|
|
295
560
|
// NotAuthorized implies expired token
|
|
296
561
|
if (reason instanceof NotAuthorized) {
|
|
297
|
-
this.logger.info(
|
|
562
|
+
this.logger.info(
|
|
563
|
+
`${this.namespace}: received authorization error for ${sessionId}, reauthorizing`
|
|
564
|
+
);
|
|
298
565
|
|
|
299
566
|
return this.webex.credentials.refresh({force: true}).then(() => callback(reason));
|
|
300
567
|
}
|
|
@@ -307,8 +574,10 @@ const Mercury = WebexPlugin.extend({
|
|
|
307
574
|
// BadRequest implies current credentials are for a Service Account
|
|
308
575
|
// Forbidden implies current user is not entitle for Webex
|
|
309
576
|
if (reason instanceof BadRequest || reason instanceof Forbidden) {
|
|
310
|
-
this.logger.warn(
|
|
311
|
-
|
|
577
|
+
this.logger.warn(
|
|
578
|
+
`${this.namespace}: received unrecoverable response from mercury for ${sessionId}`
|
|
579
|
+
);
|
|
580
|
+
backoffCall?.abort();
|
|
312
581
|
|
|
313
582
|
return callback(reason);
|
|
314
583
|
}
|
|
@@ -318,10 +587,10 @@ const Mercury = WebexPlugin.extend({
|
|
|
318
587
|
.then((haMessagingEnabled) => {
|
|
319
588
|
if (haMessagingEnabled) {
|
|
320
589
|
this.logger.info(
|
|
321
|
-
`${this.namespace}: received a generic connection error, will try to connect to another datacenter. failed, action: 'failed', url: ${
|
|
590
|
+
`${this.namespace}: received a generic connection error for ${sessionId}, will try to connect to another datacenter. failed, action: 'failed', url: ${newWSUrl} error: ${reason.message}`
|
|
322
591
|
);
|
|
323
592
|
|
|
324
|
-
return this.webex.internal.services.markFailedUrl(
|
|
593
|
+
return this.webex.internal.services.markFailedUrl(newWSUrl);
|
|
325
594
|
}
|
|
326
595
|
|
|
327
596
|
return null;
|
|
@@ -332,42 +601,103 @@ const Mercury = WebexPlugin.extend({
|
|
|
332
601
|
return callback(reason);
|
|
333
602
|
})
|
|
334
603
|
.catch((reason) => {
|
|
335
|
-
this.logger.error(
|
|
604
|
+
this.logger.error(
|
|
605
|
+
`${this.namespace}: failed to handle connection failure for ${sessionId}`,
|
|
606
|
+
reason
|
|
607
|
+
);
|
|
336
608
|
callback(reason);
|
|
337
609
|
});
|
|
338
610
|
},
|
|
339
611
|
|
|
340
|
-
|
|
612
|
+
_prepareAndOpenSocket(socket, socketUrl, sessionId, isShutdownSwitchover = false) {
|
|
613
|
+
const logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
|
|
614
|
+
|
|
615
|
+
return Promise.all([this._prepareUrl(socketUrl), this.webex.credentials.getUserToken()]).then(
|
|
616
|
+
([webSocketUrl, token]) => {
|
|
617
|
+
let options = {
|
|
618
|
+
forceCloseDelay: this.config.forceCloseDelay,
|
|
619
|
+
pingInterval: this.config.pingInterval,
|
|
620
|
+
pongTimeout: this.config.pongTimeout,
|
|
621
|
+
token: token.toString(),
|
|
622
|
+
trackingId: `${this.webex.sessionId}_${Date.now()}`,
|
|
623
|
+
logger: this.logger,
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
if (this.webex.config.defaultMercuryOptions) {
|
|
627
|
+
const customOptionsMsg = isShutdownSwitchover
|
|
628
|
+
? 'setting custom options for switchover'
|
|
629
|
+
: 'setting custom options';
|
|
630
|
+
|
|
631
|
+
this.logger.info(`${this.namespace}: ${customOptionsMsg}`);
|
|
632
|
+
options = {...options, ...this.webex.config.defaultMercuryOptions};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Set the socket before opening it. This allows a disconnect() to close
|
|
636
|
+
// the socket if it is in the process of being opened.
|
|
637
|
+
this.sockets.set(sessionId, socket);
|
|
638
|
+
this.socket = this.sockets.get(this.defaultSessionId) || socket;
|
|
639
|
+
|
|
640
|
+
this.logger.info(`${this.namespace} ${logPrefix} url for ${sessionId}: ${webSocketUrl}`);
|
|
641
|
+
|
|
642
|
+
return socket.open(webSocketUrl, options).then(() => webSocketUrl);
|
|
643
|
+
}
|
|
644
|
+
);
|
|
645
|
+
},
|
|
646
|
+
|
|
647
|
+
_connectWithBackoff(webSocketUrl, sessionId, context = {}) {
|
|
648
|
+
const {isShutdownSwitchover = false, attemptOptions = {}} = context;
|
|
649
|
+
|
|
341
650
|
return new Promise((resolve, reject) => {
|
|
342
|
-
// eslint gets confused about whether
|
|
651
|
+
// eslint gets confused about whether call is actually used
|
|
343
652
|
// eslint-disable-next-line prefer-const
|
|
344
653
|
let call;
|
|
345
|
-
const onComplete = (err) => {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
654
|
+
const onComplete = (err, sid = sessionId) => {
|
|
655
|
+
if (isShutdownSwitchover) {
|
|
656
|
+
this._shutdownSwitchoverBackoffCalls.delete(sid);
|
|
657
|
+
} else {
|
|
658
|
+
this.backoffCalls.delete(sid);
|
|
659
|
+
}
|
|
349
660
|
if (err) {
|
|
661
|
+
const msg = isShutdownSwitchover
|
|
662
|
+
? `[shutdown] switchover failed after ${call.getNumRetries()} retries`
|
|
663
|
+
: `failed to connect after ${call.getNumRetries()} retries`;
|
|
664
|
+
|
|
350
665
|
this.logger.info(
|
|
351
|
-
`${
|
|
352
|
-
this.namespace
|
|
353
|
-
}: failed to connect after ${call.getNumRetries()} retries; log statement about next retry was inaccurate; ${err}`
|
|
666
|
+
`${this.namespace}: ${msg}; log statement about next retry was inaccurate; ${err}`
|
|
354
667
|
);
|
|
355
668
|
|
|
356
669
|
return reject(err);
|
|
357
670
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
671
|
+
// Update overall connected status
|
|
672
|
+
const sessionSocket = this.sockets.get(sid);
|
|
673
|
+
if (sessionSocket) {
|
|
674
|
+
sessionSocket.connecting = false;
|
|
675
|
+
sessionSocket.connected = true;
|
|
676
|
+
}
|
|
677
|
+
// Default success handling for normal connections
|
|
678
|
+
if (!isShutdownSwitchover) {
|
|
679
|
+
this.connecting = this.hasConnectingSockets();
|
|
680
|
+
this.connected = this.hasConnectedSockets();
|
|
681
|
+
this.hasEverConnected = true;
|
|
682
|
+
this._emit(sid, 'online', {sessionId: sid});
|
|
683
|
+
this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(true);
|
|
684
|
+
}
|
|
362
685
|
|
|
363
686
|
return resolve();
|
|
364
687
|
};
|
|
365
|
-
|
|
366
688
|
// eslint-disable-next-line prefer-reflect
|
|
367
|
-
call = backoff.call(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
689
|
+
call = backoff.call(
|
|
690
|
+
(callback) => {
|
|
691
|
+
const attemptNum = call.getNumRetries();
|
|
692
|
+
const logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
|
|
693
|
+
|
|
694
|
+
this.logger.info(
|
|
695
|
+
`${this.namespace}: executing ${logPrefix} attempt ${attemptNum} for ${sessionId}`
|
|
696
|
+
);
|
|
697
|
+
this._attemptConnection(webSocketUrl, sessionId, callback, attemptOptions);
|
|
698
|
+
},
|
|
699
|
+
(err) => onComplete(err, sessionId)
|
|
700
|
+
);
|
|
371
701
|
|
|
372
702
|
call.setStrategy(
|
|
373
703
|
new backoff.ExponentialStrategy({
|
|
@@ -376,15 +706,29 @@ const Mercury = WebexPlugin.extend({
|
|
|
376
706
|
})
|
|
377
707
|
);
|
|
378
708
|
|
|
379
|
-
if (
|
|
709
|
+
if (
|
|
710
|
+
this.config.initialConnectionMaxRetries &&
|
|
711
|
+
!this.hasEverConnected &&
|
|
712
|
+
!isShutdownSwitchover
|
|
713
|
+
) {
|
|
380
714
|
call.failAfter(this.config.initialConnectionMaxRetries);
|
|
381
715
|
} else if (this.config.maxRetries) {
|
|
382
716
|
call.failAfter(this.config.maxRetries);
|
|
383
717
|
}
|
|
384
718
|
|
|
719
|
+
// Store the call BEFORE setting up event handlers to prevent race conditions
|
|
720
|
+
// Store backoff call reference BEFORE starting (so it's available in _attemptConnection)
|
|
721
|
+
if (isShutdownSwitchover) {
|
|
722
|
+
this._shutdownSwitchoverBackoffCalls.set(sessionId, call);
|
|
723
|
+
} else {
|
|
724
|
+
this.backoffCalls.set(sessionId, call);
|
|
725
|
+
}
|
|
726
|
+
|
|
385
727
|
call.on('abort', () => {
|
|
386
|
-
|
|
387
|
-
|
|
728
|
+
const msg = isShutdownSwitchover ? 'Shutdown Switchover' : 'Connection';
|
|
729
|
+
|
|
730
|
+
this.logger.info(`${this.namespace}: ${msg} aborted for ${sessionId}`);
|
|
731
|
+
reject(new Error(`Mercury ${msg} Aborted for ${sessionId}`));
|
|
388
732
|
});
|
|
389
733
|
|
|
390
734
|
call.on('callback', (err) => {
|
|
@@ -392,8 +736,12 @@ const Mercury = WebexPlugin.extend({
|
|
|
392
736
|
const number = call.getNumRetries();
|
|
393
737
|
const delay = Math.min(call.strategy_.nextBackoffDelay_, this.config.backoffTimeMax);
|
|
394
738
|
|
|
739
|
+
const logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : '';
|
|
740
|
+
|
|
395
741
|
this.logger.info(
|
|
396
|
-
`${this.namespace}: failed to connect; attempting retry ${
|
|
742
|
+
`${this.namespace}: ${logPrefix} failed to connect; attempting retry ${
|
|
743
|
+
number + 1
|
|
744
|
+
} in ${delay} ms for ${sessionId}`
|
|
397
745
|
);
|
|
398
746
|
/* istanbul ignore if */
|
|
399
747
|
if (process.env.NODE_ENV === 'development') {
|
|
@@ -402,25 +750,52 @@ const Mercury = WebexPlugin.extend({
|
|
|
402
750
|
|
|
403
751
|
return;
|
|
404
752
|
}
|
|
405
|
-
this.logger.info(`${this.namespace}: connected`);
|
|
753
|
+
this.logger.info(`${this.namespace}: connected ${sessionId}`);
|
|
406
754
|
});
|
|
407
755
|
|
|
408
756
|
call.start();
|
|
409
|
-
|
|
410
|
-
this.backoffCall = call;
|
|
411
757
|
});
|
|
412
758
|
},
|
|
413
759
|
|
|
414
760
|
_emit(...args) {
|
|
415
761
|
try {
|
|
416
|
-
|
|
762
|
+
if (!args || args.length === 0) {
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// New signature: _emit(sessionId, eventName, ...rest)
|
|
767
|
+
// Backwards compatibility: if the first arg isn't a known sessionId (or defaultSessionId),
|
|
768
|
+
// treat the call as the old signature and forward directly to trigger(...)
|
|
769
|
+
const [first, second, ...rest] = args;
|
|
770
|
+
|
|
771
|
+
if (
|
|
772
|
+
typeof first === 'string' &&
|
|
773
|
+
(this.sockets.has(first) || first === this.defaultSessionId) &&
|
|
774
|
+
typeof second === 'string'
|
|
775
|
+
) {
|
|
776
|
+
const sessionId = first;
|
|
777
|
+
const eventName = second;
|
|
778
|
+
const suffix = sessionId === this.defaultSessionId ? '' : `:${sessionId}`;
|
|
779
|
+
|
|
780
|
+
this.trigger(`${eventName}${suffix}`, ...rest);
|
|
781
|
+
} else {
|
|
782
|
+
// Old usage: _emit(eventName, ...args)
|
|
783
|
+
this.trigger(...args);
|
|
784
|
+
}
|
|
417
785
|
} catch (error) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
error
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
786
|
+
// Safely handle errors without causing additional issues during cleanup
|
|
787
|
+
try {
|
|
788
|
+
this.logger.error(
|
|
789
|
+
`${this.namespace}: error occurred in event handler:`,
|
|
790
|
+
error,
|
|
791
|
+
' with args: ',
|
|
792
|
+
args
|
|
793
|
+
);
|
|
794
|
+
} catch (logError) {
|
|
795
|
+
// If even logging fails, just ignore to prevent cascading errors during cleanup
|
|
796
|
+
// eslint-disable-next-line no-console
|
|
797
|
+
console.error('Mercury _emit error handling failed:', logError);
|
|
798
|
+
}
|
|
424
799
|
}
|
|
425
800
|
},
|
|
426
801
|
|
|
@@ -444,78 +819,152 @@ const Mercury = WebexPlugin.extend({
|
|
|
444
819
|
return handlers;
|
|
445
820
|
},
|
|
446
821
|
|
|
447
|
-
_onclose(event) {
|
|
822
|
+
_onclose(sessionId, event, sourceSocket) {
|
|
448
823
|
// I don't see any way to avoid the complexity or statement count in here.
|
|
449
824
|
/* eslint complexity: [0] */
|
|
450
825
|
|
|
451
826
|
try {
|
|
452
827
|
const reason = event.reason && event.reason.toLowerCase();
|
|
453
|
-
|
|
828
|
+
let sessionSocket = this.sockets.get(sessionId);
|
|
829
|
+
let socketUrl;
|
|
830
|
+
event.sessionId = sessionId;
|
|
454
831
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
this.
|
|
832
|
+
const isActiveSocket = sourceSocket === sessionSocket;
|
|
833
|
+
if (sourceSocket) {
|
|
834
|
+
socketUrl = sourceSocket.url;
|
|
835
|
+
}
|
|
836
|
+
this.sockets.delete(sessionId);
|
|
837
|
+
|
|
838
|
+
if (isActiveSocket) {
|
|
839
|
+
// Only tear down state if the currently active socket closed
|
|
840
|
+
if (sessionSocket) {
|
|
841
|
+
sessionSocket.removeAllListeners();
|
|
842
|
+
sessionSocket = null;
|
|
843
|
+
this._emit(sessionId, 'offline', event);
|
|
844
|
+
}
|
|
845
|
+
// Update overall connected status
|
|
846
|
+
this.connecting = this.hasConnectingSockets();
|
|
847
|
+
this.connected = this.hasConnectedSockets();
|
|
848
|
+
|
|
849
|
+
if (!this.connected) {
|
|
850
|
+
this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(false);
|
|
851
|
+
}
|
|
852
|
+
} else {
|
|
853
|
+
// Old socket closed; do not flip connection state
|
|
854
|
+
this.logger.info(
|
|
855
|
+
`${this.namespace}: [shutdown] non-active socket closed, code=${event.code} for ${sessionId}`
|
|
856
|
+
);
|
|
857
|
+
// Clean up listeners from old socket now that it's closed
|
|
858
|
+
if (sourceSocket) {
|
|
859
|
+
sourceSocket.removeAllListeners();
|
|
860
|
+
}
|
|
861
|
+
}
|
|
460
862
|
|
|
461
863
|
switch (event.code) {
|
|
462
864
|
case 1003:
|
|
463
865
|
// metric: disconnect
|
|
464
866
|
this.logger.info(
|
|
465
|
-
`${this.namespace}: Mercury service rejected last message; will not reconnect: ${event.reason}`
|
|
867
|
+
`${this.namespace}: Mercury service rejected last message for ${sessionId}; will not reconnect: ${event.reason}`
|
|
466
868
|
);
|
|
467
|
-
this._emit('offline.permanent', event);
|
|
869
|
+
if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
|
|
468
870
|
break;
|
|
469
871
|
case 4000:
|
|
470
872
|
// metric: disconnect
|
|
471
|
-
this.logger.info(`${this.namespace}: socket replaced; will not reconnect`);
|
|
472
|
-
this._emit('offline.replaced', event);
|
|
873
|
+
this.logger.info(`${this.namespace}: socket ${sessionId} replaced; will not reconnect`);
|
|
874
|
+
if (isActiveSocket) this._emit(sessionId, 'offline.replaced', event);
|
|
875
|
+
// If not active, nothing to do
|
|
876
|
+
break;
|
|
877
|
+
case 4001:
|
|
878
|
+
// replaced during shutdown
|
|
879
|
+
if (isActiveSocket) {
|
|
880
|
+
// Server closed active socket with 4001, meaning it expected this connection
|
|
881
|
+
// to be replaced, but the switchover in _handleImminentShutdown failed.
|
|
882
|
+
// This is a permanent failure - do not reconnect.
|
|
883
|
+
this.logger.warn(
|
|
884
|
+
`${this.namespace}: active socket closed with 4001; shutdown switchover failed for ${sessionId}`
|
|
885
|
+
);
|
|
886
|
+
this._emit(sessionId, 'offline.permanent', event);
|
|
887
|
+
} else {
|
|
888
|
+
// Expected: old socket closed after successful switchover
|
|
889
|
+
this.logger.info(
|
|
890
|
+
`${this.namespace}: old socket closed with 4001 (replaced during shutdown); no reconnect needed for ${sessionId}`
|
|
891
|
+
);
|
|
892
|
+
this._emit(sessionId, 'offline.replaced', event);
|
|
893
|
+
}
|
|
473
894
|
break;
|
|
474
895
|
case 1001:
|
|
475
896
|
case 1005:
|
|
476
897
|
case 1006:
|
|
477
898
|
case 1011:
|
|
478
|
-
this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
|
|
479
|
-
|
|
480
|
-
|
|
899
|
+
this.logger.info(`${this.namespace}: socket ${sessionId} disconnected; reconnecting`);
|
|
900
|
+
if (isActiveSocket) {
|
|
901
|
+
this._emit(sessionId, 'offline.transient', event);
|
|
902
|
+
this.logger.info(
|
|
903
|
+
`${this.namespace}: [shutdown] reconnecting active socket to recover for ${sessionId}`
|
|
904
|
+
);
|
|
905
|
+
this._reconnect(socketUrl, sessionId);
|
|
906
|
+
}
|
|
481
907
|
// metric: disconnect
|
|
482
908
|
// if (code == 1011 && reason !== ping error) metric: unexpected disconnect
|
|
483
909
|
break;
|
|
484
910
|
case 1000:
|
|
485
911
|
case 3050: // 3050 indicates logout form of closure, default to old behavior, use config reason defined by consumer to proceed with the permanent block
|
|
486
912
|
if (normalReconnectReasons.includes(reason)) {
|
|
487
|
-
this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
|
|
488
|
-
|
|
489
|
-
|
|
913
|
+
this.logger.info(`${this.namespace}: socket ${sessionId} disconnected; reconnecting`);
|
|
914
|
+
if (isActiveSocket) {
|
|
915
|
+
this._emit(sessionId, 'offline.transient', event);
|
|
916
|
+
this.logger.info(
|
|
917
|
+
`${this.namespace}: [shutdown] reconnecting due to normal close for ${sessionId}`
|
|
918
|
+
);
|
|
919
|
+
this._reconnect(socketUrl, sessionId);
|
|
920
|
+
}
|
|
490
921
|
// metric: disconnect
|
|
491
922
|
// if (reason === done forced) metric: force closure
|
|
492
923
|
} else {
|
|
493
924
|
this.logger.info(
|
|
494
|
-
`${this.namespace}: socket disconnected; will not reconnect: ${event.reason}`
|
|
925
|
+
`${this.namespace}: socket ${sessionId} disconnected; will not reconnect: ${event.reason}`
|
|
495
926
|
);
|
|
496
|
-
this._emit('offline.permanent', event);
|
|
927
|
+
if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
|
|
497
928
|
}
|
|
498
929
|
break;
|
|
499
930
|
default:
|
|
500
931
|
this.logger.info(
|
|
501
|
-
`${this.namespace}: socket disconnected unexpectedly; will not reconnect`
|
|
932
|
+
`${this.namespace}: socket ${sessionId} disconnected unexpectedly; will not reconnect`
|
|
502
933
|
);
|
|
503
934
|
// unexpected disconnect
|
|
504
|
-
this._emit('offline.permanent', event);
|
|
935
|
+
if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
|
|
505
936
|
}
|
|
506
937
|
} catch (error) {
|
|
507
|
-
this.logger.error(
|
|
938
|
+
this.logger.error(
|
|
939
|
+
`${this.namespace}: error occurred in close handler for ${sessionId}`,
|
|
940
|
+
error
|
|
941
|
+
);
|
|
508
942
|
}
|
|
509
943
|
},
|
|
510
944
|
|
|
511
|
-
_onmessage(event) {
|
|
512
|
-
this._setTimeOffset(event);
|
|
945
|
+
_onmessage(sessionId, event) {
|
|
946
|
+
this._setTimeOffset(sessionId, event);
|
|
513
947
|
const envelope = event.data;
|
|
514
948
|
|
|
515
949
|
if (process.env.ENABLE_MERCURY_LOGGING) {
|
|
516
|
-
this.logger.debug(`${this.namespace}: message envelope: `, envelope);
|
|
950
|
+
this.logger.debug(`${this.namespace}: message envelope from ${sessionId}: `, envelope);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
envelope.sessionId = sessionId;
|
|
954
|
+
|
|
955
|
+
// Handle shutdown message shape: { type: 'shutdown' }
|
|
956
|
+
if (envelope && envelope.type === 'shutdown') {
|
|
957
|
+
this.logger.info(
|
|
958
|
+
`${this.namespace}: [shutdown] imminent shutdown message received for ${sessionId}`
|
|
959
|
+
);
|
|
960
|
+
this._emit(sessionId, 'event:mercury_shutdown_imminent', envelope);
|
|
961
|
+
|
|
962
|
+
this._handleImminentShutdown(sessionId);
|
|
963
|
+
|
|
964
|
+
return Promise.resolve();
|
|
517
965
|
}
|
|
518
966
|
|
|
967
|
+
envelope.sessionId = sessionId;
|
|
519
968
|
const {data} = envelope;
|
|
520
969
|
|
|
521
970
|
this._applyOverrides(data);
|
|
@@ -530,7 +979,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
530
979
|
resolve((this.webex[namespace] || this.webex.internal[namespace])[name](data))
|
|
531
980
|
).catch((reason) =>
|
|
532
981
|
this.logger.error(
|
|
533
|
-
`${this.namespace}: error occurred in autowired event handler for ${data.eventType}`,
|
|
982
|
+
`${this.namespace}: error occurred in autowired event handler for ${data.eventType} from ${sessionId}`,
|
|
534
983
|
reason
|
|
535
984
|
)
|
|
536
985
|
);
|
|
@@ -538,32 +987,35 @@ const Mercury = WebexPlugin.extend({
|
|
|
538
987
|
Promise.resolve()
|
|
539
988
|
)
|
|
540
989
|
.then(() => {
|
|
541
|
-
this._emit('event',
|
|
990
|
+
this._emit(sessionId, 'event', envelope);
|
|
542
991
|
const [namespace] = data.eventType.split('.');
|
|
543
992
|
|
|
544
993
|
if (namespace === data.eventType) {
|
|
545
|
-
this._emit(`event:${namespace}`, envelope);
|
|
994
|
+
this._emit(sessionId, `event:${namespace}`, envelope);
|
|
546
995
|
} else {
|
|
547
|
-
this._emit(`event:${namespace}`, envelope);
|
|
548
|
-
this._emit(`event:${data.eventType}`, envelope);
|
|
996
|
+
this._emit(sessionId, `event:${namespace}`, envelope);
|
|
997
|
+
this._emit(sessionId, `event:${data.eventType}`, envelope);
|
|
549
998
|
}
|
|
550
999
|
})
|
|
551
1000
|
.catch((reason) => {
|
|
552
|
-
this.logger.error(
|
|
1001
|
+
this.logger.error(
|
|
1002
|
+
`${this.namespace}: error occurred processing socket message from ${sessionId}`,
|
|
1003
|
+
reason
|
|
1004
|
+
);
|
|
553
1005
|
});
|
|
554
1006
|
},
|
|
555
1007
|
|
|
556
|
-
_setTimeOffset(event) {
|
|
1008
|
+
_setTimeOffset(sessionId, event) {
|
|
557
1009
|
const {wsWriteTimestamp} = event.data;
|
|
558
1010
|
if (typeof wsWriteTimestamp === 'number' && wsWriteTimestamp > 0) {
|
|
559
1011
|
this.mercuryTimeOffset = Date.now() - wsWriteTimestamp;
|
|
560
1012
|
}
|
|
561
1013
|
},
|
|
562
1014
|
|
|
563
|
-
_reconnect(webSocketUrl) {
|
|
564
|
-
this.logger.info(`${this.namespace}: reconnecting`);
|
|
1015
|
+
_reconnect(webSocketUrl, sessionId = this.defaultSessionId) {
|
|
1016
|
+
this.logger.info(`${this.namespace}: reconnecting ${sessionId}`);
|
|
565
1017
|
|
|
566
|
-
return this.connect(webSocketUrl);
|
|
1018
|
+
return this.connect(webSocketUrl, sessionId);
|
|
567
1019
|
},
|
|
568
1020
|
});
|
|
569
1021
|
|