@webex/internal-plugin-mercury 3.11.0 → 3.12.0-mobius-socket.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/README.md +54 -0
- package/dist/mercury.js +395 -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 +398 -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 +307 -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);
|
|
344
|
-
|
|
345
|
-
// Check appropriate backoff call based on connection type
|
|
346
|
-
if (isShutdownSwitchover && !this._shutdownSwitchoverBackoffCall) {
|
|
347
|
-
const msg = `${this.namespace}: prevent socket open when switchover backoff call no longer defined`;
|
|
348
|
-
const err = new Error(msg);
|
|
349
|
-
|
|
350
|
-
this.logger.info(msg);
|
|
489
|
+
this._attachSocketEventListeners(socket, sessionId);
|
|
351
490
|
|
|
352
|
-
|
|
353
|
-
|
|
491
|
+
const backoffCall = isShutdownSwitchover
|
|
492
|
+
? this._shutdownSwitchoverBackoffCalls.get(sessionId)
|
|
493
|
+
: this.backoffCalls.get(sessionId);
|
|
354
494
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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,34 +782,55 @@ 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
|
|
|
631
830
|
_getEventHandlers(eventType) {
|
|
831
|
+
if (!eventType) {
|
|
832
|
+
return [];
|
|
833
|
+
}
|
|
632
834
|
const [namespace, name] = eventType.split('.');
|
|
633
835
|
const handlers = [];
|
|
634
836
|
|
|
@@ -648,36 +850,40 @@ const Mercury = WebexPlugin.extend({
|
|
|
648
850
|
return handlers;
|
|
649
851
|
},
|
|
650
852
|
|
|
651
|
-
_onclose(event, sourceSocket) {
|
|
853
|
+
_onclose(sessionId, event, sourceSocket) {
|
|
652
854
|
// I don't see any way to avoid the complexity or statement count in here.
|
|
653
855
|
/* eslint complexity: [0] */
|
|
654
856
|
|
|
655
857
|
try {
|
|
656
|
-
const isActiveSocket = sourceSocket === this.socket;
|
|
657
858
|
const reason = event.reason && event.reason.toLowerCase();
|
|
658
|
-
|
|
859
|
+
const sessionSocket = this.sockets.get(sessionId);
|
|
659
860
|
let socketUrl;
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
// Old socket closed - get URL from the closed socket
|
|
861
|
+
event.sessionId = sessionId;
|
|
862
|
+
|
|
863
|
+
const isActiveSocket = sourceSocket === sessionSocket;
|
|
864
|
+
if (sourceSocket) {
|
|
665
865
|
socketUrl = sourceSocket.url;
|
|
666
866
|
}
|
|
867
|
+
this.sockets.delete(sessionId);
|
|
667
868
|
|
|
668
869
|
if (isActiveSocket) {
|
|
669
870
|
// Only tear down state if the currently active socket closed
|
|
670
|
-
if (
|
|
671
|
-
|
|
871
|
+
if (sessionSocket) {
|
|
872
|
+
sessionSocket.removeAllListeners();
|
|
873
|
+
if (sessionId === this.defaultSessionId) this.unset('socket');
|
|
874
|
+
this._emit(sessionId, 'offline', event);
|
|
875
|
+
}
|
|
876
|
+
// Update overall connected status
|
|
877
|
+
this.connecting = this.hasConnectingSockets();
|
|
878
|
+
this.connected = this.hasConnectedSockets();
|
|
879
|
+
|
|
880
|
+
if (!this.connected) {
|
|
881
|
+
this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(false);
|
|
672
882
|
}
|
|
673
|
-
this.unset('socket');
|
|
674
|
-
this.connected = false;
|
|
675
|
-
this._emit('offline', event);
|
|
676
|
-
this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(false);
|
|
677
883
|
} else {
|
|
678
884
|
// Old socket closed; do not flip connection state
|
|
679
885
|
this.logger.info(
|
|
680
|
-
`${this.namespace}: [shutdown] non-active socket closed, code=${event.code}`
|
|
886
|
+
`${this.namespace}: [shutdown] non-active socket closed, code=${event.code} for ${sessionId}`
|
|
681
887
|
);
|
|
682
888
|
// Clean up listeners from old socket now that it's closed
|
|
683
889
|
if (sourceSocket) {
|
|
@@ -689,14 +895,14 @@ const Mercury = WebexPlugin.extend({
|
|
|
689
895
|
case 1003:
|
|
690
896
|
// metric: disconnect
|
|
691
897
|
this.logger.info(
|
|
692
|
-
`${this.namespace}: Mercury service rejected last message; will not reconnect: ${event.reason}`
|
|
898
|
+
`${this.namespace}: Mercury service rejected last message for ${sessionId}; will not reconnect: ${event.reason}`
|
|
693
899
|
);
|
|
694
|
-
if (isActiveSocket) this._emit('offline.permanent', event);
|
|
900
|
+
if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
|
|
695
901
|
break;
|
|
696
902
|
case 4000:
|
|
697
903
|
// metric: disconnect
|
|
698
|
-
this.logger.info(`${this.namespace}: socket replaced; will not reconnect`);
|
|
699
|
-
if (isActiveSocket) this._emit('offline.replaced', event);
|
|
904
|
+
this.logger.info(`${this.namespace}: socket ${sessionId} replaced; will not reconnect`);
|
|
905
|
+
if (isActiveSocket) this._emit(sessionId, 'offline.replaced', event);
|
|
700
906
|
// If not active, nothing to do
|
|
701
907
|
break;
|
|
702
908
|
case 4001:
|
|
@@ -706,26 +912,28 @@ const Mercury = WebexPlugin.extend({
|
|
|
706
912
|
// to be replaced, but the switchover in _handleImminentShutdown failed.
|
|
707
913
|
// This is a permanent failure - do not reconnect.
|
|
708
914
|
this.logger.warn(
|
|
709
|
-
`${this.namespace}: active socket closed with 4001; shutdown switchover failed`
|
|
915
|
+
`${this.namespace}: active socket closed with 4001; shutdown switchover failed for ${sessionId}`
|
|
710
916
|
);
|
|
711
|
-
this._emit('offline.permanent', event);
|
|
917
|
+
this._emit(sessionId, 'offline.permanent', event);
|
|
712
918
|
} else {
|
|
713
919
|
// Expected: old socket closed after successful switchover
|
|
714
920
|
this.logger.info(
|
|
715
|
-
`${this.namespace}: old socket closed with 4001 (replaced during shutdown); no reconnect needed`
|
|
921
|
+
`${this.namespace}: old socket closed with 4001 (replaced during shutdown); no reconnect needed for ${sessionId}`
|
|
716
922
|
);
|
|
717
|
-
this._emit('offline.replaced', event);
|
|
923
|
+
this._emit(sessionId, 'offline.replaced', event);
|
|
718
924
|
}
|
|
719
925
|
break;
|
|
720
926
|
case 1001:
|
|
721
927
|
case 1005:
|
|
722
928
|
case 1006:
|
|
723
929
|
case 1011:
|
|
724
|
-
this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
|
|
930
|
+
this.logger.info(`${this.namespace}: socket ${sessionId} disconnected; reconnecting`);
|
|
725
931
|
if (isActiveSocket) {
|
|
726
|
-
this._emit('offline.transient', event);
|
|
727
|
-
this.logger.info(
|
|
728
|
-
|
|
932
|
+
this._emit(sessionId, 'offline.transient', event);
|
|
933
|
+
this.logger.info(
|
|
934
|
+
`${this.namespace}: [shutdown] reconnecting active socket to recover for ${sessionId}`
|
|
935
|
+
);
|
|
936
|
+
this._reconnect(socketUrl, sessionId);
|
|
729
937
|
}
|
|
730
938
|
// metric: disconnect
|
|
731
939
|
// if (code == 1011 && reason !== ping error) metric: unexpected disconnect
|
|
@@ -733,55 +941,71 @@ const Mercury = WebexPlugin.extend({
|
|
|
733
941
|
case 1000:
|
|
734
942
|
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
943
|
if (normalReconnectReasons.includes(reason)) {
|
|
736
|
-
this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
|
|
944
|
+
this.logger.info(`${this.namespace}: socket ${sessionId} disconnected; reconnecting`);
|
|
737
945
|
if (isActiveSocket) {
|
|
738
|
-
this._emit('offline.transient', event);
|
|
739
|
-
this.logger.info(
|
|
740
|
-
|
|
946
|
+
this._emit(sessionId, 'offline.transient', event);
|
|
947
|
+
this.logger.info(
|
|
948
|
+
`${this.namespace}: [shutdown] reconnecting due to normal close for ${sessionId}`
|
|
949
|
+
);
|
|
950
|
+
this._reconnect(socketUrl, sessionId);
|
|
741
951
|
}
|
|
742
952
|
// metric: disconnect
|
|
743
953
|
// if (reason === done forced) metric: force closure
|
|
744
954
|
} else {
|
|
745
955
|
this.logger.info(
|
|
746
|
-
`${this.namespace}: socket disconnected; will not reconnect: ${event.reason}`
|
|
956
|
+
`${this.namespace}: socket ${sessionId} disconnected; will not reconnect: ${event.reason}`
|
|
747
957
|
);
|
|
748
|
-
if (isActiveSocket) this._emit('offline.permanent', event);
|
|
958
|
+
if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
|
|
749
959
|
}
|
|
750
960
|
break;
|
|
751
961
|
default:
|
|
752
962
|
this.logger.info(
|
|
753
|
-
`${this.namespace}: socket disconnected unexpectedly; will not reconnect`
|
|
963
|
+
`${this.namespace}: socket ${sessionId} disconnected unexpectedly; will not reconnect`
|
|
754
964
|
);
|
|
755
965
|
// unexpected disconnect
|
|
756
|
-
if (isActiveSocket) this._emit('offline.permanent', event);
|
|
966
|
+
if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
|
|
757
967
|
}
|
|
758
968
|
} catch (error) {
|
|
759
|
-
this.logger.error(
|
|
969
|
+
this.logger.error(
|
|
970
|
+
`${this.namespace}: error occurred in close handler for ${sessionId}`,
|
|
971
|
+
error
|
|
972
|
+
);
|
|
760
973
|
}
|
|
761
974
|
},
|
|
762
975
|
|
|
763
|
-
_onmessage(event) {
|
|
764
|
-
this._setTimeOffset(event);
|
|
976
|
+
_onmessage(sessionId, event) {
|
|
977
|
+
this._setTimeOffset(sessionId, event);
|
|
765
978
|
const envelope = event.data;
|
|
766
979
|
|
|
767
980
|
if (process.env.ENABLE_MERCURY_LOGGING) {
|
|
768
|
-
this.logger.debug(`${this.namespace}: message envelope: `, envelope);
|
|
981
|
+
this.logger.debug(`${this.namespace}: message envelope from ${sessionId}: `, envelope);
|
|
769
982
|
}
|
|
770
983
|
|
|
984
|
+
envelope.sessionId = sessionId;
|
|
985
|
+
|
|
771
986
|
// Handle shutdown message shape: { type: 'shutdown' }
|
|
772
987
|
if (envelope && envelope.type === 'shutdown') {
|
|
773
|
-
this.logger.info(
|
|
774
|
-
|
|
988
|
+
this.logger.info(
|
|
989
|
+
`${this.namespace}: [shutdown] imminent shutdown message received for ${sessionId}`
|
|
990
|
+
);
|
|
991
|
+
this._emit(sessionId, 'event:mercury_shutdown_imminent', envelope);
|
|
775
992
|
|
|
776
|
-
this._handleImminentShutdown();
|
|
993
|
+
this._handleImminentShutdown(sessionId);
|
|
777
994
|
|
|
778
995
|
return Promise.resolve();
|
|
779
996
|
}
|
|
780
997
|
|
|
998
|
+
envelope.sessionId = sessionId;
|
|
781
999
|
const {data} = envelope;
|
|
782
1000
|
|
|
783
1001
|
this._applyOverrides(data);
|
|
784
1002
|
|
|
1003
|
+
if (!data || !data.eventType) {
|
|
1004
|
+
this._emit(sessionId, 'event', envelope);
|
|
1005
|
+
|
|
1006
|
+
return Promise.resolve();
|
|
1007
|
+
}
|
|
1008
|
+
|
|
785
1009
|
return this._getEventHandlers(data.eventType)
|
|
786
1010
|
.reduce(
|
|
787
1011
|
(promise, handler) =>
|
|
@@ -792,7 +1016,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
792
1016
|
resolve((this.webex[namespace] || this.webex.internal[namespace])[name](data))
|
|
793
1017
|
).catch((reason) =>
|
|
794
1018
|
this.logger.error(
|
|
795
|
-
`${this.namespace}: error occurred in autowired event handler for ${data.eventType}`,
|
|
1019
|
+
`${this.namespace}: error occurred in autowired event handler for ${data.eventType} from ${sessionId}`,
|
|
796
1020
|
reason
|
|
797
1021
|
)
|
|
798
1022
|
);
|
|
@@ -800,32 +1024,35 @@ const Mercury = WebexPlugin.extend({
|
|
|
800
1024
|
Promise.resolve()
|
|
801
1025
|
)
|
|
802
1026
|
.then(() => {
|
|
803
|
-
this._emit('event',
|
|
1027
|
+
this._emit(sessionId, 'event', envelope);
|
|
804
1028
|
const [namespace] = data.eventType.split('.');
|
|
805
1029
|
|
|
806
1030
|
if (namespace === data.eventType) {
|
|
807
|
-
this._emit(`event:${namespace}`, envelope);
|
|
1031
|
+
this._emit(sessionId, `event:${namespace}`, envelope);
|
|
808
1032
|
} else {
|
|
809
|
-
this._emit(`event:${namespace}`, envelope);
|
|
810
|
-
this._emit(`event:${data.eventType}`, envelope);
|
|
1033
|
+
this._emit(sessionId, `event:${namespace}`, envelope);
|
|
1034
|
+
this._emit(sessionId, `event:${data.eventType}`, envelope);
|
|
811
1035
|
}
|
|
812
1036
|
})
|
|
813
1037
|
.catch((reason) => {
|
|
814
|
-
this.logger.error(
|
|
1038
|
+
this.logger.error(
|
|
1039
|
+
`${this.namespace}: error occurred processing socket message from ${sessionId}`,
|
|
1040
|
+
reason
|
|
1041
|
+
);
|
|
815
1042
|
});
|
|
816
1043
|
},
|
|
817
1044
|
|
|
818
|
-
_setTimeOffset(event) {
|
|
1045
|
+
_setTimeOffset(sessionId, event) {
|
|
819
1046
|
const {wsWriteTimestamp} = event.data;
|
|
820
1047
|
if (typeof wsWriteTimestamp === 'number' && wsWriteTimestamp > 0) {
|
|
821
1048
|
this.mercuryTimeOffset = Date.now() - wsWriteTimestamp;
|
|
822
1049
|
}
|
|
823
1050
|
},
|
|
824
1051
|
|
|
825
|
-
_reconnect(webSocketUrl) {
|
|
826
|
-
this.logger.info(`${this.namespace}: reconnecting`);
|
|
1052
|
+
_reconnect(webSocketUrl, sessionId = this.defaultSessionId) {
|
|
1053
|
+
this.logger.info(`${this.namespace}: reconnecting ${sessionId}`);
|
|
827
1054
|
|
|
828
|
-
return this.connect(webSocketUrl);
|
|
1055
|
+
return this.connect(webSocketUrl, sessionId);
|
|
829
1056
|
},
|
|
830
1057
|
});
|
|
831
1058
|
|