@webex/internal-plugin-mercury 3.11.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/dist/mercury.js +388 -198
- package/dist/mercury.js.map +1 -1
- package/dist/socket/constants.js +16 -0
- package/dist/socket/constants.js.map +1 -0
- package/dist/socket/socket-base.js +36 -3
- package/dist/socket/socket-base.js.map +1 -1
- package/package.json +17 -17
- package/src/mercury.js +389 -171
- package/src/socket/constants.js +6 -0
- package/src/socket/socket-base.js +40 -3
- package/test/unit/spec/mercury-events.js +20 -2
- package/test/unit/spec/mercury.js +201 -139
- package/test/unit/spec/socket.js +61 -0
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,
|
|
@@ -99,50 +111,63 @@ const Mercury = WebexPlugin.extend({
|
|
|
99
111
|
/**
|
|
100
112
|
* Attach event listeners to a socket.
|
|
101
113
|
* @param {Socket} socket - The socket to attach listeners to
|
|
114
|
+
* @param {sessionId} sessionId - The socket related session ID
|
|
102
115
|
* @returns {void}
|
|
103
116
|
*/
|
|
104
|
-
_attachSocketEventListeners(socket) {
|
|
105
|
-
socket.on('close', (event) => this._onclose(event, socket));
|
|
106
|
-
socket.on('message', (...args) => this._onmessage(...args));
|
|
107
|
-
socket.on('pong', (...args) => this._setTimeOffset(...args));
|
|
108
|
-
socket.on('sequence-mismatch', (...args) =>
|
|
109
|
-
|
|
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
|
+
);
|
|
110
127
|
},
|
|
111
128
|
|
|
112
129
|
/**
|
|
113
130
|
* Handle imminent shutdown by establishing a new connection while keeping
|
|
114
131
|
* the current one alive (make-before-break).
|
|
115
132
|
* Idempotent: will no-op if already in progress.
|
|
133
|
+
* @param {string} sessionId - The session ID for which the shutdown is imminent
|
|
116
134
|
* @returns {void}
|
|
117
135
|
*/
|
|
118
|
-
_handleImminentShutdown() {
|
|
136
|
+
_handleImminentShutdown(sessionId) {
|
|
137
|
+
const oldSocket = this.sockets.get(sessionId);
|
|
138
|
+
|
|
119
139
|
try {
|
|
120
|
-
if
|
|
121
|
-
|
|
140
|
+
// Idempotent: if we already have a switchover backoff call for this session,
|
|
141
|
+
// a switchover is in progress – do nothing.
|
|
142
|
+
if (this._shutdownSwitchoverBackoffCalls.get(sessionId)) {
|
|
143
|
+
this.logger.info(
|
|
144
|
+
`${this.namespace}: [shutdown] switchover already in progress for ${sessionId}`
|
|
145
|
+
);
|
|
122
146
|
|
|
123
147
|
return;
|
|
124
148
|
}
|
|
125
|
-
|
|
149
|
+
|
|
126
150
|
this._shutdownSwitchoverId = `${Date.now()}`;
|
|
127
151
|
this.logger.info(
|
|
128
|
-
`${this.namespace}: [shutdown] switchover start, id=${this._shutdownSwitchoverId}`
|
|
152
|
+
`${this.namespace}: [shutdown] switchover start, id=${this._shutdownSwitchoverId} for ${sessionId}`
|
|
129
153
|
);
|
|
130
154
|
|
|
131
|
-
this._connectWithBackoff(undefined, {
|
|
155
|
+
this._connectWithBackoff(undefined, sessionId, {
|
|
132
156
|
isShutdownSwitchover: true,
|
|
133
157
|
attemptOptions: {
|
|
134
158
|
isShutdownSwitchover: true,
|
|
135
159
|
onSuccess: (newSocket, webSocketUrl) => {
|
|
136
160
|
this.logger.info(
|
|
137
|
-
`${this.namespace}: [shutdown] switchover connected, url: ${webSocketUrl}`
|
|
161
|
+
`${this.namespace}: [shutdown] switchover connected, url: ${webSocketUrl} for ${sessionId}`
|
|
138
162
|
);
|
|
139
163
|
|
|
140
|
-
const oldSocket = this.socket;
|
|
141
164
|
// Atomically switch active socket reference
|
|
142
|
-
this.socket =
|
|
143
|
-
this.connected =
|
|
165
|
+
this.socket = this.sockets.get(this.defaultSessionId);
|
|
166
|
+
this.connected = this.hasConnectedSockets(); // remain connected throughout
|
|
144
167
|
|
|
145
|
-
this._emit('event:mercury_shutdown_switchover_complete', {
|
|
168
|
+
this._emit(sessionId, 'event:mercury_shutdown_switchover_complete', {
|
|
169
|
+
url: webSocketUrl,
|
|
170
|
+
});
|
|
146
171
|
|
|
147
172
|
if (oldSocket) {
|
|
148
173
|
this.logger.info(
|
|
@@ -153,20 +178,25 @@ const Mercury = WebexPlugin.extend({
|
|
|
153
178
|
},
|
|
154
179
|
})
|
|
155
180
|
.then(() => {
|
|
156
|
-
this.logger.info(
|
|
181
|
+
this.logger.info(
|
|
182
|
+
`${this.namespace}: [shutdown] switchover completed successfully for ${sessionId}`
|
|
183
|
+
);
|
|
157
184
|
})
|
|
158
185
|
.catch((err) => {
|
|
159
186
|
this.logger.info(
|
|
160
|
-
`${this.namespace}: [shutdown] switchover exhausted retries; will fall back to normal reconnection`,
|
|
187
|
+
`${this.namespace}: [shutdown] switchover exhausted retries; will fall back to normal reconnection for ${sessionId}: `,
|
|
161
188
|
err
|
|
162
189
|
);
|
|
163
|
-
this._emit('event:mercury_shutdown_switchover_failed', {reason: err});
|
|
190
|
+
this._emit(sessionId, 'event:mercury_shutdown_switchover_failed', {reason: err});
|
|
164
191
|
// Old socket will eventually close with 4001, triggering normal reconnection
|
|
165
192
|
});
|
|
166
193
|
} catch (e) {
|
|
167
|
-
this.logger.error(
|
|
168
|
-
|
|
169
|
-
|
|
194
|
+
this.logger.error(
|
|
195
|
+
`${this.namespace}: [shutdown] error during switchover for ${sessionId}`,
|
|
196
|
+
e
|
|
197
|
+
);
|
|
198
|
+
this._shutdownSwitchoverBackoffCalls.delete(sessionId);
|
|
199
|
+
this._emit(sessionId, 'event:mercury_shutdown_switchover_failed', {reason: e});
|
|
170
200
|
}
|
|
171
201
|
},
|
|
172
202
|
|
|
@@ -178,29 +208,95 @@ const Mercury = WebexPlugin.extend({
|
|
|
178
208
|
return this.lastError;
|
|
179
209
|
},
|
|
180
210
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
211
|
+
/**
|
|
212
|
+
* Get all active socket connections
|
|
213
|
+
* @returns {Map} Map of sessionId to socket instances
|
|
214
|
+
*/
|
|
215
|
+
getSockets() {
|
|
216
|
+
return this.sockets;
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get a specific socket by connection ID
|
|
221
|
+
* @param {string} sessionId - The connection identifier
|
|
222
|
+
* @returns {Socket|undefined} The socket instance or undefined if not found
|
|
223
|
+
*/
|
|
224
|
+
getSocket(sessionId = this.defaultSessionId) {
|
|
225
|
+
return this.sockets.get(sessionId);
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Check if a socket is connected
|
|
230
|
+
* @param {string} [sessionId=this.defaultSessionId] - The session identifier
|
|
231
|
+
* @returns {boolean|undefined} True if the socket is connected
|
|
232
|
+
*/
|
|
233
|
+
hasConnectedSockets(sessionId = this.defaultSessionId) {
|
|
234
|
+
const socket = this.sockets.get(sessionId || this.defaultSessionId);
|
|
235
|
+
|
|
236
|
+
return socket?.connected;
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Check if any sockets are connecting
|
|
241
|
+
* @param {string} [sessionId=this.defaultSessionId] - The session identifier
|
|
242
|
+
* @returns {boolean|undefined} True if the socket is connecting
|
|
243
|
+
*/
|
|
244
|
+
hasConnectingSockets(sessionId = this.defaultSessionId) {
|
|
245
|
+
const socket = this.sockets.get(sessionId || this.defaultSessionId);
|
|
246
|
+
|
|
247
|
+
return socket?.connecting;
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Connect to Mercury for a specific session.
|
|
252
|
+
* @param {string} [webSocketUrl] - Optional websocket URL override. Falls back to the device websocket URL.
|
|
253
|
+
* @param {string} [sessionId=this.defaultSessionId] - The session identifier for this connection.
|
|
254
|
+
* @returns {Promise<void>} Resolves when connection flow completes for the session.
|
|
255
|
+
*/
|
|
256
|
+
connect(webSocketUrl, sessionId = this.defaultSessionId) {
|
|
257
|
+
if (!this._connectPromises) this._connectPromises = new Map();
|
|
258
|
+
|
|
259
|
+
// First check if there's already a connection promise for this session
|
|
260
|
+
if (this._connectPromises.has(sessionId)) {
|
|
261
|
+
this.logger.info(
|
|
262
|
+
`${this.namespace}: connection ${sessionId} already in progress, returning existing promise`
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
return this._connectPromises.get(sessionId);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const sessionSocket = this.sockets.get(sessionId);
|
|
269
|
+
if (sessionSocket?.connected || sessionSocket?.connecting) {
|
|
270
|
+
this.logger.info(
|
|
271
|
+
`${this.namespace}: connection ${sessionId} already connected, will not connect again`
|
|
272
|
+
);
|
|
185
273
|
|
|
186
274
|
return Promise.resolve();
|
|
187
275
|
}
|
|
188
276
|
|
|
189
277
|
this.connecting = true;
|
|
190
278
|
|
|
191
|
-
this.logger.info(`${this.namespace}: starting connection attempt`);
|
|
279
|
+
this.logger.info(`${this.namespace}: starting connection attempt for ${sessionId}`);
|
|
192
280
|
this.logger.info(
|
|
193
281
|
`${this.namespace}: debug_mercury_logging stack: `,
|
|
194
282
|
new Error('debug_mercury_logging').stack
|
|
195
283
|
);
|
|
196
284
|
|
|
197
|
-
|
|
285
|
+
const connectPromise = Promise.resolve(
|
|
198
286
|
this.webex.internal.device.registered || this.webex.internal.device.register()
|
|
199
|
-
)
|
|
200
|
-
|
|
287
|
+
)
|
|
288
|
+
.then(() => {
|
|
289
|
+
this.logger.info(`${this.namespace}: connecting ${sessionId}`);
|
|
201
290
|
|
|
202
|
-
|
|
203
|
-
|
|
291
|
+
return this._connectWithBackoff(webSocketUrl, sessionId);
|
|
292
|
+
})
|
|
293
|
+
.finally(() => {
|
|
294
|
+
this._connectPromises.delete(sessionId);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
this._connectPromises.set(sessionId, connectPromise);
|
|
298
|
+
|
|
299
|
+
return connectPromise;
|
|
204
300
|
},
|
|
205
301
|
|
|
206
302
|
logout() {
|
|
@@ -210,7 +306,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
210
306
|
new Error('debug_mercury_logging').stack
|
|
211
307
|
);
|
|
212
308
|
|
|
213
|
-
return this.
|
|
309
|
+
return this.disconnectAll(
|
|
214
310
|
this.config.beforeLogoutOptionsCloseReason &&
|
|
215
311
|
!normalReconnectReasons.includes(this.config.beforeLogoutOptionsCloseReason)
|
|
216
312
|
? {code: 3050, reason: this.config.beforeLogoutOptionsCloseReason}
|
|
@@ -218,26 +314,74 @@ const Mercury = WebexPlugin.extend({
|
|
|
218
314
|
);
|
|
219
315
|
},
|
|
220
316
|
|
|
221
|
-
|
|
222
|
-
|
|
317
|
+
/**
|
|
318
|
+
* Disconnect a Mercury socket for a specific session.
|
|
319
|
+
* @param {object} [options] - Optional websocket close options (for example: `{code, reason}`).
|
|
320
|
+
* @param {string} [sessionId=this.defaultSessionId] - The session identifier to disconnect.
|
|
321
|
+
* @returns {Promise<void>} Resolves after disconnect cleanup and close handling are initiated/completed.
|
|
322
|
+
*/
|
|
323
|
+
disconnect(options, sessionId = this.defaultSessionId) {
|
|
324
|
+
this.logger.info(
|
|
325
|
+
`${this.namespace}#disconnect: connecting state: ${this.connecting}, connected state: ${
|
|
326
|
+
this.connected
|
|
327
|
+
}, socket exists: ${!!this.socket}, options: ${JSON.stringify(options)}`
|
|
328
|
+
);
|
|
329
|
+
|
|
223
330
|
return new Promise((resolve) => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this.
|
|
331
|
+
const backoffCall = this.backoffCalls.get(sessionId);
|
|
332
|
+
if (backoffCall) {
|
|
333
|
+
this.logger.info(`${this.namespace}: aborting connection ${sessionId}`);
|
|
334
|
+
backoffCall.abort();
|
|
335
|
+
this.backoffCalls.delete(sessionId);
|
|
227
336
|
}
|
|
228
|
-
|
|
229
|
-
if (
|
|
230
|
-
this.logger.info(`${this.namespace}: aborting shutdown switchover`);
|
|
231
|
-
|
|
337
|
+
const shutdownSwitchoverBackoffCall = this._shutdownSwitchoverBackoffCalls.get(sessionId);
|
|
338
|
+
if (shutdownSwitchoverBackoffCall) {
|
|
339
|
+
this.logger.info(`${this.namespace}: aborting shutdown switchover connection ${sessionId}`);
|
|
340
|
+
shutdownSwitchoverBackoffCall.abort();
|
|
341
|
+
this._shutdownSwitchoverBackoffCalls.delete(sessionId);
|
|
232
342
|
}
|
|
233
|
-
|
|
234
|
-
if (this.
|
|
235
|
-
this.
|
|
236
|
-
this.once('offline', resolve);
|
|
237
|
-
resolve(this.socket.close(options || undefined));
|
|
343
|
+
// Clean up any pending connection promises
|
|
344
|
+
if (this._connectPromises) {
|
|
345
|
+
this._connectPromises.delete(sessionId);
|
|
238
346
|
}
|
|
239
347
|
|
|
348
|
+
const sessionSocket = this.sockets.get(sessionId);
|
|
349
|
+
const suffix = sessionId === this.defaultSessionId ? '' : `:${sessionId}`;
|
|
350
|
+
|
|
351
|
+
if (sessionSocket) {
|
|
352
|
+
sessionSocket.removeAllListeners('message');
|
|
353
|
+
sessionSocket.connecting = false;
|
|
354
|
+
sessionSocket.connected = false;
|
|
355
|
+
this.once(sessionId === this.defaultSessionId ? 'offline' : `offline${suffix}`, resolve);
|
|
356
|
+
resolve(sessionSocket.close(options || undefined));
|
|
357
|
+
}
|
|
240
358
|
resolve();
|
|
359
|
+
|
|
360
|
+
// Update overall connected status
|
|
361
|
+
this.connected = this.hasConnectedSockets();
|
|
362
|
+
});
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Disconnect all socket connections
|
|
367
|
+
* @param {object} options - Close options
|
|
368
|
+
* @returns {Promise} Promise that resolves when all connections are closed
|
|
369
|
+
*/
|
|
370
|
+
disconnectAll(options) {
|
|
371
|
+
const disconnectPromises = [];
|
|
372
|
+
|
|
373
|
+
for (const sessionId of this.sockets.keys()) {
|
|
374
|
+
disconnectPromises.push(this.disconnect(options, sessionId));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return Promise.all(disconnectPromises).then(() => {
|
|
378
|
+
this.connected = false;
|
|
379
|
+
this.sockets.clear();
|
|
380
|
+
this.backoffCalls.clear();
|
|
381
|
+
// Clear connection promises to prevent stale promises
|
|
382
|
+
if (this._connectPromises) {
|
|
383
|
+
this._connectPromises.clear();
|
|
384
|
+
}
|
|
241
385
|
});
|
|
242
386
|
},
|
|
243
387
|
|
|
@@ -329,34 +473,29 @@ const Mercury = WebexPlugin.extend({
|
|
|
329
473
|
}
|
|
330
474
|
|
|
331
475
|
webSocketUrl.query.clientTimestamp = Date.now();
|
|
476
|
+
delete webSocketUrl.search;
|
|
332
477
|
|
|
333
478
|
return url.format(webSocketUrl);
|
|
334
479
|
});
|
|
335
480
|
},
|
|
336
481
|
|
|
337
|
-
_attemptConnection(socketUrl, callback, options = {}) {
|
|
482
|
+
_attemptConnection(socketUrl, sessionId, callback, options = {}) {
|
|
338
483
|
const {isShutdownSwitchover = false, onSuccess = null} = options;
|
|
339
484
|
|
|
340
485
|
const socket = new Socket();
|
|
486
|
+
socket.connecting = true;
|
|
341
487
|
let newWSUrl;
|
|
342
488
|
|
|
343
|
-
this._attachSocketEventListeners(socket);
|
|
489
|
+
this._attachSocketEventListeners(socket, sessionId);
|
|
344
490
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const err = new Error(msg);
|
|
491
|
+
const backoffCall = isShutdownSwitchover
|
|
492
|
+
? this._shutdownSwitchoverBackoffCalls.get(sessionId)
|
|
493
|
+
: this.backoffCalls.get(sessionId);
|
|
349
494
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
return Promise.reject(err);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (!isShutdownSwitchover && !this.backoffCall) {
|
|
359
|
-
const msg = `${this.namespace}: prevent socket open when backoffCall no longer defined`;
|
|
495
|
+
// Check appropriate backoff call based on connection type
|
|
496
|
+
if (!backoffCall) {
|
|
497
|
+
const mode = isShutdownSwitchover ? 'switchover backoff call' : 'backoffCall';
|
|
498
|
+
const msg = `${this.namespace}: prevent socket open when ${mode} no longer defined for ${sessionId}`;
|
|
360
499
|
const err = new Error(msg);
|
|
361
500
|
|
|
362
501
|
this.logger.info(msg);
|
|
@@ -370,16 +509,17 @@ const Mercury = WebexPlugin.extend({
|
|
|
370
509
|
// For shutdown switchover, don't set socket yet (make-before-break)
|
|
371
510
|
// For normal connection, set socket before opening to allow disconnect() to close it
|
|
372
511
|
if (!isShutdownSwitchover) {
|
|
373
|
-
this.
|
|
512
|
+
this.sockets.set(sessionId, socket);
|
|
374
513
|
}
|
|
375
514
|
|
|
376
|
-
return this._prepareAndOpenSocket(socket, socketUrl, isShutdownSwitchover)
|
|
515
|
+
return this._prepareAndOpenSocket(socket, socketUrl, sessionId, isShutdownSwitchover)
|
|
377
516
|
.then((webSocketUrl) => {
|
|
378
517
|
newWSUrl = webSocketUrl;
|
|
518
|
+
|
|
379
519
|
this.logger.info(
|
|
380
520
|
`${this.namespace}: ${
|
|
381
521
|
isShutdownSwitchover ? '[shutdown] switchover' : ''
|
|
382
|
-
} connected to mercury, success, action: connected, url: ${newWSUrl}`
|
|
522
|
+
} connected to mercury, success, action: connected for ${sessionId}, url: ${newWSUrl}`
|
|
383
523
|
);
|
|
384
524
|
|
|
385
525
|
// Custom success handler for shutdown switchover
|
|
@@ -406,7 +546,10 @@ const Mercury = WebexPlugin.extend({
|
|
|
406
546
|
.catch((reason) => {
|
|
407
547
|
// For shutdown, simpler error handling - just callback for retry
|
|
408
548
|
if (isShutdownSwitchover) {
|
|
409
|
-
this.logger.info(
|
|
549
|
+
this.logger.info(
|
|
550
|
+
`${this.namespace}: [shutdown] switchover attempt failed for ${sessionId}`,
|
|
551
|
+
reason
|
|
552
|
+
);
|
|
410
553
|
|
|
411
554
|
return callback(reason);
|
|
412
555
|
}
|
|
@@ -414,30 +557,36 @@ const Mercury = WebexPlugin.extend({
|
|
|
414
557
|
// Normal connection error handling (existing complex logic)
|
|
415
558
|
this.lastError = reason; // remember the last error
|
|
416
559
|
|
|
560
|
+
const backoffCallNormal = this.backoffCalls.get(sessionId);
|
|
417
561
|
// Suppress connection errors that appear to be network related. This
|
|
418
562
|
// may end up suppressing metrics during outages, but we might not care
|
|
419
563
|
// (especially since many of our outages happen in a way that client
|
|
420
564
|
// metrics can't be trusted).
|
|
421
|
-
if (reason.code !== 1006 &&
|
|
422
|
-
this._emit('connection_failed', reason, {
|
|
565
|
+
if (reason.code !== 1006 && backoffCallNormal && backoffCallNormal?.getNumRetries() > 0) {
|
|
566
|
+
this._emit(sessionId, 'connection_failed', reason, {
|
|
567
|
+
sessionId,
|
|
568
|
+
retries: backoffCallNormal?.getNumRetries(),
|
|
569
|
+
});
|
|
423
570
|
}
|
|
424
571
|
this.logger.info(
|
|
425
|
-
`${this.namespace}: connection attempt failed`,
|
|
572
|
+
`${this.namespace}: connection attempt failed for ${sessionId}`,
|
|
426
573
|
reason,
|
|
427
|
-
|
|
574
|
+
backoffCallNormal?.getNumRetries() === 0 ? reason.stack : ''
|
|
428
575
|
);
|
|
429
576
|
// UnknownResponse is produced by IE for any 4XXX; treated it like a bad
|
|
430
577
|
// web socket url and let WDM handle the token checking
|
|
431
578
|
if (reason instanceof UnknownResponse) {
|
|
432
579
|
this.logger.info(
|
|
433
|
-
`${this.namespace}: received unknown response code, refreshing device registration`
|
|
580
|
+
`${this.namespace}: received unknown response code for ${sessionId}, refreshing device registration`
|
|
434
581
|
);
|
|
435
582
|
|
|
436
583
|
return this.webex.internal.device.refresh().then(() => callback(reason));
|
|
437
584
|
}
|
|
438
585
|
// NotAuthorized implies expired token
|
|
439
586
|
if (reason instanceof NotAuthorized) {
|
|
440
|
-
this.logger.info(
|
|
587
|
+
this.logger.info(
|
|
588
|
+
`${this.namespace}: received authorization error for ${sessionId}, reauthorizing`
|
|
589
|
+
);
|
|
441
590
|
|
|
442
591
|
return this.webex.credentials.refresh({force: true}).then(() => callback(reason));
|
|
443
592
|
}
|
|
@@ -450,8 +599,10 @@ const Mercury = WebexPlugin.extend({
|
|
|
450
599
|
// BadRequest implies current credentials are for a Service Account
|
|
451
600
|
// Forbidden implies current user is not entitle for Webex
|
|
452
601
|
if (reason instanceof BadRequest || reason instanceof Forbidden) {
|
|
453
|
-
this.logger.warn(
|
|
454
|
-
|
|
602
|
+
this.logger.warn(
|
|
603
|
+
`${this.namespace}: received unrecoverable response from mercury for ${sessionId}`
|
|
604
|
+
);
|
|
605
|
+
backoffCallNormal?.abort();
|
|
455
606
|
|
|
456
607
|
return callback(reason);
|
|
457
608
|
}
|
|
@@ -461,7 +612,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
461
612
|
.then((haMessagingEnabled) => {
|
|
462
613
|
if (haMessagingEnabled) {
|
|
463
614
|
this.logger.info(
|
|
464
|
-
`${this.namespace}: received a generic connection error, will try to connect to another datacenter. failed, action: 'failed', url: ${newWSUrl} error: ${reason.message}`
|
|
615
|
+
`${this.namespace}: received a generic connection error for ${sessionId}, will try to connect to another datacenter. failed, action: 'failed', url: ${newWSUrl} error: ${reason.message}`
|
|
465
616
|
);
|
|
466
617
|
|
|
467
618
|
return this.webex.internal.services.markFailedUrl(newWSUrl);
|
|
@@ -475,12 +626,15 @@ const Mercury = WebexPlugin.extend({
|
|
|
475
626
|
return callback(reason);
|
|
476
627
|
})
|
|
477
628
|
.catch((reason) => {
|
|
478
|
-
this.logger.error(
|
|
629
|
+
this.logger.error(
|
|
630
|
+
`${this.namespace}: failed to handle connection failure for ${sessionId}`,
|
|
631
|
+
reason
|
|
632
|
+
);
|
|
479
633
|
callback(reason);
|
|
480
634
|
});
|
|
481
635
|
},
|
|
482
636
|
|
|
483
|
-
_prepareAndOpenSocket(socket, socketUrl, isShutdownSwitchover = false) {
|
|
637
|
+
_prepareAndOpenSocket(socket, socketUrl, sessionId, isShutdownSwitchover = false) {
|
|
484
638
|
const logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
|
|
485
639
|
|
|
486
640
|
return Promise.all([this._prepareUrl(socketUrl), this.webex.credentials.getUserToken()]).then(
|
|
@@ -503,30 +657,32 @@ const Mercury = WebexPlugin.extend({
|
|
|
503
657
|
options = {...options, ...this.webex.config.defaultMercuryOptions};
|
|
504
658
|
}
|
|
505
659
|
|
|
506
|
-
|
|
660
|
+
// Set the socket before opening it. This allows a disconnect() to close
|
|
661
|
+
// the socket if it is in the process of being opened.
|
|
662
|
+
this.sockets.set(sessionId, socket);
|
|
663
|
+
this.socket = this.sockets.get(this.defaultSessionId);
|
|
664
|
+
|
|
665
|
+
this.logger.info(`${this.namespace} ${logPrefix} url for ${sessionId}: ${webSocketUrl}`);
|
|
507
666
|
|
|
508
667
|
return socket.open(webSocketUrl, options).then(() => webSocketUrl);
|
|
509
668
|
}
|
|
510
669
|
);
|
|
511
670
|
},
|
|
512
671
|
|
|
513
|
-
_connectWithBackoff(webSocketUrl, context = {}) {
|
|
672
|
+
_connectWithBackoff(webSocketUrl, sessionId, context = {}) {
|
|
514
673
|
const {isShutdownSwitchover = false, attemptOptions = {}} = context;
|
|
515
674
|
|
|
516
675
|
return new Promise((resolve, reject) => {
|
|
517
|
-
// eslint gets confused about whether
|
|
676
|
+
// eslint gets confused about whether call is actually used
|
|
518
677
|
// eslint-disable-next-line prefer-const
|
|
519
678
|
let call;
|
|
520
|
-
const onComplete = (err) => {
|
|
521
|
-
// Clear state flags based on connection type
|
|
679
|
+
const onComplete = (err, sid = sessionId) => {
|
|
522
680
|
if (isShutdownSwitchover) {
|
|
523
|
-
this.
|
|
524
|
-
this._shutdownSwitchoverBackoffCall = undefined;
|
|
681
|
+
this._shutdownSwitchoverBackoffCalls.delete(sid);
|
|
525
682
|
} else {
|
|
526
|
-
this.
|
|
527
|
-
this.backoffCall = undefined;
|
|
683
|
+
this.backoffCalls.delete(sid);
|
|
528
684
|
}
|
|
529
|
-
|
|
685
|
+
const sessionSocket = this.sockets.get(sid);
|
|
530
686
|
if (err) {
|
|
531
687
|
const msg = isShutdownSwitchover
|
|
532
688
|
? `[shutdown] switchover failed after ${call.getNumRetries()} retries`
|
|
@@ -535,29 +691,45 @@ const Mercury = WebexPlugin.extend({
|
|
|
535
691
|
this.logger.info(
|
|
536
692
|
`${this.namespace}: ${msg}; log statement about next retry was inaccurate; ${err}`
|
|
537
693
|
);
|
|
694
|
+
if (sessionSocket) {
|
|
695
|
+
sessionSocket.connecting = false;
|
|
696
|
+
sessionSocket.connected = false;
|
|
697
|
+
}
|
|
538
698
|
|
|
539
699
|
return reject(err);
|
|
540
700
|
}
|
|
541
701
|
|
|
702
|
+
// Update overall connected status
|
|
703
|
+
if (sessionSocket) {
|
|
704
|
+
sessionSocket.connecting = false;
|
|
705
|
+
sessionSocket.connected = true;
|
|
706
|
+
}
|
|
542
707
|
// Default success handling for normal connections
|
|
543
708
|
if (!isShutdownSwitchover) {
|
|
544
|
-
this.
|
|
709
|
+
this.connecting = this.hasConnectingSockets();
|
|
710
|
+
this.connected = this.hasConnectedSockets();
|
|
545
711
|
this.hasEverConnected = true;
|
|
546
|
-
this._emit('online');
|
|
547
|
-
this.
|
|
712
|
+
this._emit(sid, 'online');
|
|
713
|
+
if (this.connected) {
|
|
714
|
+
this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(true);
|
|
715
|
+
}
|
|
548
716
|
}
|
|
549
717
|
|
|
550
718
|
return resolve();
|
|
551
719
|
};
|
|
552
|
-
|
|
553
720
|
// eslint-disable-next-line prefer-reflect
|
|
554
|
-
call = backoff.call(
|
|
555
|
-
|
|
556
|
-
|
|
721
|
+
call = backoff.call(
|
|
722
|
+
(callback) => {
|
|
723
|
+
const attemptNum = call.getNumRetries();
|
|
724
|
+
const logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
|
|
557
725
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
726
|
+
this.logger.info(
|
|
727
|
+
`${this.namespace}: executing ${logPrefix} attempt ${attemptNum} for ${sessionId}`
|
|
728
|
+
);
|
|
729
|
+
this._attemptConnection(webSocketUrl, sessionId, callback, attemptOptions);
|
|
730
|
+
},
|
|
731
|
+
(err) => onComplete(err, sessionId)
|
|
732
|
+
);
|
|
561
733
|
|
|
562
734
|
call.setStrategy(
|
|
563
735
|
new backoff.ExponentialStrategy({
|
|
@@ -576,23 +748,32 @@ const Mercury = WebexPlugin.extend({
|
|
|
576
748
|
call.failAfter(this.config.maxRetries);
|
|
577
749
|
}
|
|
578
750
|
|
|
751
|
+
// Store the call BEFORE setting up event handlers to prevent race conditions
|
|
752
|
+
// Store backoff call reference BEFORE starting (so it's available in _attemptConnection)
|
|
753
|
+
if (isShutdownSwitchover) {
|
|
754
|
+
this._shutdownSwitchoverBackoffCalls.set(sessionId, call);
|
|
755
|
+
} else {
|
|
756
|
+
this.backoffCalls.set(sessionId, call);
|
|
757
|
+
}
|
|
758
|
+
|
|
579
759
|
call.on('abort', () => {
|
|
580
760
|
const msg = isShutdownSwitchover ? 'Shutdown Switchover' : 'Connection';
|
|
581
761
|
|
|
582
|
-
this.logger.info(`${this.namespace}: ${msg} aborted`);
|
|
583
|
-
reject(new Error(`Mercury ${msg} Aborted`));
|
|
762
|
+
this.logger.info(`${this.namespace}: ${msg} aborted for ${sessionId}`);
|
|
763
|
+
reject(new Error(`Mercury ${msg} Aborted for ${sessionId}`));
|
|
584
764
|
});
|
|
585
765
|
|
|
586
766
|
call.on('callback', (err) => {
|
|
587
767
|
if (err) {
|
|
588
768
|
const number = call.getNumRetries();
|
|
589
769
|
const delay = Math.min(call.strategy_.nextBackoffDelay_, this.config.backoffTimeMax);
|
|
770
|
+
|
|
590
771
|
const logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : '';
|
|
591
772
|
|
|
592
773
|
this.logger.info(
|
|
593
774
|
`${this.namespace}: ${logPrefix} failed to connect; attempting retry ${
|
|
594
775
|
number + 1
|
|
595
|
-
} in ${delay} ms`
|
|
776
|
+
} in ${delay} ms for ${sessionId}`
|
|
596
777
|
);
|
|
597
778
|
/* istanbul ignore if */
|
|
598
779
|
if (process.env.NODE_ENV === 'development') {
|
|
@@ -601,30 +782,48 @@ const Mercury = WebexPlugin.extend({
|
|
|
601
782
|
|
|
602
783
|
return;
|
|
603
784
|
}
|
|
604
|
-
this.logger.info(`${this.namespace}: connected`);
|
|
785
|
+
this.logger.info(`${this.namespace}: connected ${sessionId}`);
|
|
605
786
|
});
|
|
606
787
|
|
|
607
|
-
// Store backoff call reference BEFORE starting (so it's available in _attemptConnection)
|
|
608
|
-
if (isShutdownSwitchover) {
|
|
609
|
-
this._shutdownSwitchoverBackoffCall = call;
|
|
610
|
-
} else {
|
|
611
|
-
this.backoffCall = call;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
788
|
call.start();
|
|
615
789
|
});
|
|
616
790
|
},
|
|
617
791
|
|
|
618
792
|
_emit(...args) {
|
|
619
793
|
try {
|
|
620
|
-
|
|
794
|
+
if (!args || args.length === 0) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// New signature: _emit(sessionId, eventName, ...rest)
|
|
799
|
+
// Backwards compatibility: if the first arg isn't a known sessionId (or defaultSessionId),
|
|
800
|
+
// treat the call as the old signature and forward directly to trigger(...)
|
|
801
|
+
const [first, second, ...rest] = args;
|
|
802
|
+
|
|
803
|
+
if (typeof first === 'string' && typeof second === 'string') {
|
|
804
|
+
const sessionId = first;
|
|
805
|
+
const eventName = second;
|
|
806
|
+
const suffix = sessionId === this.defaultSessionId ? '' : `:${sessionId}`;
|
|
807
|
+
|
|
808
|
+
this.trigger(`${eventName}${suffix}`, ...rest);
|
|
809
|
+
} else {
|
|
810
|
+
// Old usage: _emit(eventName, ...args)
|
|
811
|
+
this.trigger(...args);
|
|
812
|
+
}
|
|
621
813
|
} catch (error) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
error
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
814
|
+
// Safely handle errors without causing additional issues during cleanup
|
|
815
|
+
try {
|
|
816
|
+
this.logger.error(
|
|
817
|
+
`${this.namespace}: error occurred in event handler:`,
|
|
818
|
+
error,
|
|
819
|
+
' with args: ',
|
|
820
|
+
args
|
|
821
|
+
);
|
|
822
|
+
} catch (logError) {
|
|
823
|
+
// If even logging fails, just ignore to prevent cascading errors during cleanup
|
|
824
|
+
// eslint-disable-next-line no-console
|
|
825
|
+
console.error('Mercury _emit error handling failed:', logError);
|
|
826
|
+
}
|
|
628
827
|
}
|
|
629
828
|
},
|
|
630
829
|
|
|
@@ -648,36 +847,40 @@ const Mercury = WebexPlugin.extend({
|
|
|
648
847
|
return handlers;
|
|
649
848
|
},
|
|
650
849
|
|
|
651
|
-
_onclose(event, sourceSocket) {
|
|
850
|
+
_onclose(sessionId, event, sourceSocket) {
|
|
652
851
|
// I don't see any way to avoid the complexity or statement count in here.
|
|
653
852
|
/* eslint complexity: [0] */
|
|
654
853
|
|
|
655
854
|
try {
|
|
656
|
-
const isActiveSocket = sourceSocket === this.socket;
|
|
657
855
|
const reason = event.reason && event.reason.toLowerCase();
|
|
658
|
-
|
|
856
|
+
const sessionSocket = this.sockets.get(sessionId);
|
|
659
857
|
let socketUrl;
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
// Old socket closed - get URL from the closed socket
|
|
858
|
+
event.sessionId = sessionId;
|
|
859
|
+
|
|
860
|
+
const isActiveSocket = sourceSocket === sessionSocket;
|
|
861
|
+
if (sourceSocket) {
|
|
665
862
|
socketUrl = sourceSocket.url;
|
|
666
863
|
}
|
|
864
|
+
this.sockets.delete(sessionId);
|
|
667
865
|
|
|
668
866
|
if (isActiveSocket) {
|
|
669
867
|
// Only tear down state if the currently active socket closed
|
|
670
|
-
if (
|
|
671
|
-
|
|
868
|
+
if (sessionSocket) {
|
|
869
|
+
sessionSocket.removeAllListeners();
|
|
870
|
+
if (sessionId === this.defaultSessionId) this.unset('socket');
|
|
871
|
+
this._emit(sessionId, 'offline', event);
|
|
872
|
+
}
|
|
873
|
+
// Update overall connected status
|
|
874
|
+
this.connecting = this.hasConnectingSockets();
|
|
875
|
+
this.connected = this.hasConnectedSockets();
|
|
876
|
+
|
|
877
|
+
if (!this.connected) {
|
|
878
|
+
this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(false);
|
|
672
879
|
}
|
|
673
|
-
this.unset('socket');
|
|
674
|
-
this.connected = false;
|
|
675
|
-
this._emit('offline', event);
|
|
676
|
-
this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(false);
|
|
677
880
|
} else {
|
|
678
881
|
// Old socket closed; do not flip connection state
|
|
679
882
|
this.logger.info(
|
|
680
|
-
`${this.namespace}: [shutdown] non-active socket closed, code=${event.code}`
|
|
883
|
+
`${this.namespace}: [shutdown] non-active socket closed, code=${event.code} for ${sessionId}`
|
|
681
884
|
);
|
|
682
885
|
// Clean up listeners from old socket now that it's closed
|
|
683
886
|
if (sourceSocket) {
|
|
@@ -689,14 +892,14 @@ const Mercury = WebexPlugin.extend({
|
|
|
689
892
|
case 1003:
|
|
690
893
|
// metric: disconnect
|
|
691
894
|
this.logger.info(
|
|
692
|
-
`${this.namespace}: Mercury service rejected last message; will not reconnect: ${event.reason}`
|
|
895
|
+
`${this.namespace}: Mercury service rejected last message for ${sessionId}; will not reconnect: ${event.reason}`
|
|
693
896
|
);
|
|
694
|
-
if (isActiveSocket) this._emit('offline.permanent', event);
|
|
897
|
+
if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
|
|
695
898
|
break;
|
|
696
899
|
case 4000:
|
|
697
900
|
// metric: disconnect
|
|
698
|
-
this.logger.info(`${this.namespace}: socket replaced; will not reconnect`);
|
|
699
|
-
if (isActiveSocket) this._emit('offline.replaced', event);
|
|
901
|
+
this.logger.info(`${this.namespace}: socket ${sessionId} replaced; will not reconnect`);
|
|
902
|
+
if (isActiveSocket) this._emit(sessionId, 'offline.replaced', event);
|
|
700
903
|
// If not active, nothing to do
|
|
701
904
|
break;
|
|
702
905
|
case 4001:
|
|
@@ -706,26 +909,28 @@ const Mercury = WebexPlugin.extend({
|
|
|
706
909
|
// to be replaced, but the switchover in _handleImminentShutdown failed.
|
|
707
910
|
// This is a permanent failure - do not reconnect.
|
|
708
911
|
this.logger.warn(
|
|
709
|
-
`${this.namespace}: active socket closed with 4001; shutdown switchover failed`
|
|
912
|
+
`${this.namespace}: active socket closed with 4001; shutdown switchover failed for ${sessionId}`
|
|
710
913
|
);
|
|
711
|
-
this._emit('offline.permanent', event);
|
|
914
|
+
this._emit(sessionId, 'offline.permanent', event);
|
|
712
915
|
} else {
|
|
713
916
|
// Expected: old socket closed after successful switchover
|
|
714
917
|
this.logger.info(
|
|
715
|
-
`${this.namespace}: old socket closed with 4001 (replaced during shutdown); no reconnect needed`
|
|
918
|
+
`${this.namespace}: old socket closed with 4001 (replaced during shutdown); no reconnect needed for ${sessionId}`
|
|
716
919
|
);
|
|
717
|
-
this._emit('offline.replaced', event);
|
|
920
|
+
this._emit(sessionId, 'offline.replaced', event);
|
|
718
921
|
}
|
|
719
922
|
break;
|
|
720
923
|
case 1001:
|
|
721
924
|
case 1005:
|
|
722
925
|
case 1006:
|
|
723
926
|
case 1011:
|
|
724
|
-
this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
|
|
927
|
+
this.logger.info(`${this.namespace}: socket ${sessionId} disconnected; reconnecting`);
|
|
725
928
|
if (isActiveSocket) {
|
|
726
|
-
this._emit('offline.transient', event);
|
|
727
|
-
this.logger.info(
|
|
728
|
-
|
|
929
|
+
this._emit(sessionId, 'offline.transient', event);
|
|
930
|
+
this.logger.info(
|
|
931
|
+
`${this.namespace}: [shutdown] reconnecting active socket to recover for ${sessionId}`
|
|
932
|
+
);
|
|
933
|
+
this._reconnect(socketUrl, sessionId);
|
|
729
934
|
}
|
|
730
935
|
// metric: disconnect
|
|
731
936
|
// if (code == 1011 && reason !== ping error) metric: unexpected disconnect
|
|
@@ -733,51 +938,61 @@ const Mercury = WebexPlugin.extend({
|
|
|
733
938
|
case 1000:
|
|
734
939
|
case 3050: // 3050 indicates logout form of closure, default to old behavior, use config reason defined by consumer to proceed with the permanent block
|
|
735
940
|
if (normalReconnectReasons.includes(reason)) {
|
|
736
|
-
this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
|
|
941
|
+
this.logger.info(`${this.namespace}: socket ${sessionId} disconnected; reconnecting`);
|
|
737
942
|
if (isActiveSocket) {
|
|
738
|
-
this._emit('offline.transient', event);
|
|
739
|
-
this.logger.info(
|
|
740
|
-
|
|
943
|
+
this._emit(sessionId, 'offline.transient', event);
|
|
944
|
+
this.logger.info(
|
|
945
|
+
`${this.namespace}: [shutdown] reconnecting due to normal close for ${sessionId}`
|
|
946
|
+
);
|
|
947
|
+
this._reconnect(socketUrl, sessionId);
|
|
741
948
|
}
|
|
742
949
|
// metric: disconnect
|
|
743
950
|
// if (reason === done forced) metric: force closure
|
|
744
951
|
} else {
|
|
745
952
|
this.logger.info(
|
|
746
|
-
`${this.namespace}: socket disconnected; will not reconnect: ${event.reason}`
|
|
953
|
+
`${this.namespace}: socket ${sessionId} disconnected; will not reconnect: ${event.reason}`
|
|
747
954
|
);
|
|
748
|
-
if (isActiveSocket) this._emit('offline.permanent', event);
|
|
955
|
+
if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
|
|
749
956
|
}
|
|
750
957
|
break;
|
|
751
958
|
default:
|
|
752
959
|
this.logger.info(
|
|
753
|
-
`${this.namespace}: socket disconnected unexpectedly; will not reconnect`
|
|
960
|
+
`${this.namespace}: socket ${sessionId} disconnected unexpectedly; will not reconnect`
|
|
754
961
|
);
|
|
755
962
|
// unexpected disconnect
|
|
756
|
-
if (isActiveSocket) this._emit('offline.permanent', event);
|
|
963
|
+
if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
|
|
757
964
|
}
|
|
758
965
|
} catch (error) {
|
|
759
|
-
this.logger.error(
|
|
966
|
+
this.logger.error(
|
|
967
|
+
`${this.namespace}: error occurred in close handler for ${sessionId}`,
|
|
968
|
+
error
|
|
969
|
+
);
|
|
760
970
|
}
|
|
761
971
|
},
|
|
762
972
|
|
|
763
|
-
_onmessage(event) {
|
|
764
|
-
this._setTimeOffset(event);
|
|
973
|
+
_onmessage(sessionId, event) {
|
|
974
|
+
this._setTimeOffset(sessionId, event);
|
|
765
975
|
const envelope = event.data;
|
|
766
976
|
|
|
767
977
|
if (process.env.ENABLE_MERCURY_LOGGING) {
|
|
768
|
-
this.logger.debug(`${this.namespace}: message envelope: `, envelope);
|
|
978
|
+
this.logger.debug(`${this.namespace}: message envelope from ${sessionId}: `, envelope);
|
|
769
979
|
}
|
|
770
980
|
|
|
981
|
+
envelope.sessionId = sessionId;
|
|
982
|
+
|
|
771
983
|
// Handle shutdown message shape: { type: 'shutdown' }
|
|
772
984
|
if (envelope && envelope.type === 'shutdown') {
|
|
773
|
-
this.logger.info(
|
|
774
|
-
|
|
985
|
+
this.logger.info(
|
|
986
|
+
`${this.namespace}: [shutdown] imminent shutdown message received for ${sessionId}`
|
|
987
|
+
);
|
|
988
|
+
this._emit(sessionId, 'event:mercury_shutdown_imminent', envelope);
|
|
775
989
|
|
|
776
|
-
this._handleImminentShutdown();
|
|
990
|
+
this._handleImminentShutdown(sessionId);
|
|
777
991
|
|
|
778
992
|
return Promise.resolve();
|
|
779
993
|
}
|
|
780
994
|
|
|
995
|
+
envelope.sessionId = sessionId;
|
|
781
996
|
const {data} = envelope;
|
|
782
997
|
|
|
783
998
|
this._applyOverrides(data);
|
|
@@ -792,7 +1007,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
792
1007
|
resolve((this.webex[namespace] || this.webex.internal[namespace])[name](data))
|
|
793
1008
|
).catch((reason) =>
|
|
794
1009
|
this.logger.error(
|
|
795
|
-
`${this.namespace}: error occurred in autowired event handler for ${data.eventType}`,
|
|
1010
|
+
`${this.namespace}: error occurred in autowired event handler for ${data.eventType} from ${sessionId}`,
|
|
796
1011
|
reason
|
|
797
1012
|
)
|
|
798
1013
|
);
|
|
@@ -800,32 +1015,35 @@ const Mercury = WebexPlugin.extend({
|
|
|
800
1015
|
Promise.resolve()
|
|
801
1016
|
)
|
|
802
1017
|
.then(() => {
|
|
803
|
-
this._emit('event',
|
|
1018
|
+
this._emit(sessionId, 'event', envelope);
|
|
804
1019
|
const [namespace] = data.eventType.split('.');
|
|
805
1020
|
|
|
806
1021
|
if (namespace === data.eventType) {
|
|
807
|
-
this._emit(`event:${namespace}`, envelope);
|
|
1022
|
+
this._emit(sessionId, `event:${namespace}`, envelope);
|
|
808
1023
|
} else {
|
|
809
|
-
this._emit(`event:${namespace}`, envelope);
|
|
810
|
-
this._emit(`event:${data.eventType}`, envelope);
|
|
1024
|
+
this._emit(sessionId, `event:${namespace}`, envelope);
|
|
1025
|
+
this._emit(sessionId, `event:${data.eventType}`, envelope);
|
|
811
1026
|
}
|
|
812
1027
|
})
|
|
813
1028
|
.catch((reason) => {
|
|
814
|
-
this.logger.error(
|
|
1029
|
+
this.logger.error(
|
|
1030
|
+
`${this.namespace}: error occurred processing socket message from ${sessionId}`,
|
|
1031
|
+
reason
|
|
1032
|
+
);
|
|
815
1033
|
});
|
|
816
1034
|
},
|
|
817
1035
|
|
|
818
|
-
_setTimeOffset(event) {
|
|
1036
|
+
_setTimeOffset(sessionId, event) {
|
|
819
1037
|
const {wsWriteTimestamp} = event.data;
|
|
820
1038
|
if (typeof wsWriteTimestamp === 'number' && wsWriteTimestamp > 0) {
|
|
821
1039
|
this.mercuryTimeOffset = Date.now() - wsWriteTimestamp;
|
|
822
1040
|
}
|
|
823
1041
|
},
|
|
824
1042
|
|
|
825
|
-
_reconnect(webSocketUrl) {
|
|
826
|
-
this.logger.info(`${this.namespace}: reconnecting`);
|
|
1043
|
+
_reconnect(webSocketUrl, sessionId = this.defaultSessionId) {
|
|
1044
|
+
this.logger.info(`${this.namespace}: reconnecting ${sessionId}`);
|
|
827
1045
|
|
|
828
|
-
return this.connect(webSocketUrl);
|
|
1046
|
+
return this.connect(webSocketUrl, sessionId);
|
|
829
1047
|
},
|
|
830
1048
|
});
|
|
831
1049
|
|