@webex/internal-plugin-mercury 3.11.0 → 3.12.0-metrics-init-fix-2.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 +425 -201
- 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 +439 -175
- package/src/socket/constants.js +6 -0
- package/src/socket/socket-base.js +40 -3
- package/test/unit/spec/mercury-events.js +29 -3
- package/test/unit/spec/mercury.js +474 -153
- package/test/unit/spec/socket.js +61 -0
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
UnknownResponse,
|
|
18
18
|
// NotFound
|
|
19
19
|
} from '../errors';
|
|
20
|
+
import {SOCKET_READY_STATE} from './constants';
|
|
20
21
|
|
|
21
22
|
const sockets = new WeakMap();
|
|
22
23
|
|
|
@@ -33,6 +34,8 @@ export default class Socket extends EventEmitter {
|
|
|
33
34
|
this._domain = 'unknown-domain';
|
|
34
35
|
this.onmessage = this.onmessage.bind(this);
|
|
35
36
|
this.onclose = this.onclose.bind(this);
|
|
37
|
+
// Increase max listeners to avoid memory leak warning in tests
|
|
38
|
+
this.setMaxListeners(10);
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
/**
|
|
@@ -114,7 +117,10 @@ export default class Socket extends EventEmitter {
|
|
|
114
117
|
// logger is defined once open is called
|
|
115
118
|
this.logger.info(`socket,${this._domain}: closing`);
|
|
116
119
|
|
|
117
|
-
if (
|
|
120
|
+
if (
|
|
121
|
+
socket.readyState === SOCKET_READY_STATE.CLOSING ||
|
|
122
|
+
socket.readyState === SOCKET_READY_STATE.CLOSED
|
|
123
|
+
) {
|
|
118
124
|
this.logger.info(`socket,${this._domain}: already closed`);
|
|
119
125
|
resolve();
|
|
120
126
|
|
|
@@ -161,7 +167,27 @@ export default class Socket extends EventEmitter {
|
|
|
161
167
|
resolve(event);
|
|
162
168
|
};
|
|
163
169
|
|
|
164
|
-
socket
|
|
170
|
+
// If socket is still connecting, manually trigger close handler with desired code
|
|
171
|
+
// because calling close() on a CONNECTING socket may not preserve custom codes
|
|
172
|
+
if (socket.readyState === SOCKET_READY_STATE.CONNECTING) {
|
|
173
|
+
this.logger.info(
|
|
174
|
+
`socket,${this._domain}: socket still connecting, triggering close manually`
|
|
175
|
+
);
|
|
176
|
+
clearTimeout(closeTimer);
|
|
177
|
+
const closeEvent = {code: options.code, reason: options.reason};
|
|
178
|
+
this.onclose(closeEvent);
|
|
179
|
+
resolve(closeEvent);
|
|
180
|
+
try {
|
|
181
|
+
socket.close(options.code, options.reason);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
this.logger.info(
|
|
184
|
+
`socket,${this._domain}: error while closing CONNECTING socket, likely due to browser incompatibility with custom close codes`,
|
|
185
|
+
error
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
socket.close(options.code, options.reason);
|
|
190
|
+
}
|
|
165
191
|
});
|
|
166
192
|
}
|
|
167
193
|
|
|
@@ -328,7 +354,7 @@ export default class Socket extends EventEmitter {
|
|
|
328
354
|
*/
|
|
329
355
|
send(data) {
|
|
330
356
|
return new Promise((resolve, reject) => {
|
|
331
|
-
if (this.readyState !==
|
|
357
|
+
if (this.readyState !== SOCKET_READY_STATE.OPEN) {
|
|
332
358
|
return reject(new Error('INVALID_STATE_ERROR'));
|
|
333
359
|
}
|
|
334
360
|
|
|
@@ -358,9 +384,20 @@ export default class Socket extends EventEmitter {
|
|
|
358
384
|
return Promise.reject(new Error('`event.data.id` is required'));
|
|
359
385
|
}
|
|
360
386
|
|
|
387
|
+
// Don't try to acknowledge if socket is not in open state
|
|
388
|
+
if (this.readyState !== SOCKET_READY_STATE.OPEN) {
|
|
389
|
+
return Promise.resolve(); // Silently ignore acknowledgment for closed sockets
|
|
390
|
+
}
|
|
391
|
+
|
|
361
392
|
return this.send({
|
|
362
393
|
messageId: event.data.id,
|
|
363
394
|
type: 'ack',
|
|
395
|
+
}).catch((error) => {
|
|
396
|
+
// Gracefully handle send errors (like INVALID_STATE_ERROR) to prevent test issues
|
|
397
|
+
if (error.message === 'INVALID_STATE_ERROR') {
|
|
398
|
+
return Promise.resolve(); // Socket was closed, ignore the acknowledgment
|
|
399
|
+
}
|
|
400
|
+
throw error; // Re-throw other errors
|
|
364
401
|
});
|
|
365
402
|
}
|
|
366
403
|
|
|
@@ -38,14 +38,31 @@ describe('plugin-mercury', () => {
|
|
|
38
38
|
},
|
|
39
39
|
timestamp: Date.now(),
|
|
40
40
|
trackingId: `suffix_${uuid.v4()}_${Date.now()}`,
|
|
41
|
+
sessionId: 'mercury-default-session',
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
beforeEach(() => {
|
|
44
45
|
clock = FakeTimers.install({now: Date.now()});
|
|
45
46
|
});
|
|
46
47
|
|
|
47
|
-
afterEach(() => {
|
|
48
|
+
afterEach(async () => {
|
|
48
49
|
clock.uninstall();
|
|
50
|
+
// Clean up mercury socket and mockWebSocket
|
|
51
|
+
if (mercury && mercury.socket) {
|
|
52
|
+
try {
|
|
53
|
+
await mercury.socket.close();
|
|
54
|
+
} catch (e) {}
|
|
55
|
+
}
|
|
56
|
+
if (mockWebSocket && typeof mockWebSocket.close === 'function') {
|
|
57
|
+
mockWebSocket.close();
|
|
58
|
+
}
|
|
59
|
+
// Restore stubs
|
|
60
|
+
if (Socket.getWebSocketConstructor.restore) {
|
|
61
|
+
Socket.getWebSocketConstructor.restore();
|
|
62
|
+
}
|
|
63
|
+
if (socketOpenStub && socketOpenStub.restore) {
|
|
64
|
+
socketOpenStub.restore();
|
|
65
|
+
}
|
|
49
66
|
});
|
|
50
67
|
|
|
51
68
|
beforeEach(() => {
|
|
@@ -76,6 +93,7 @@ describe('plugin-mercury', () => {
|
|
|
76
93
|
});
|
|
77
94
|
|
|
78
95
|
mercury = webex.internal.mercury;
|
|
96
|
+
mercury.defaultSessionId = 'mercury-default-session';
|
|
79
97
|
});
|
|
80
98
|
|
|
81
99
|
afterEach(() => {
|
|
@@ -274,6 +292,8 @@ describe('plugin-mercury', () => {
|
|
|
274
292
|
mercury._reconnect.restore();
|
|
275
293
|
}
|
|
276
294
|
|
|
295
|
+
let expectedReconnectUrl;
|
|
296
|
+
|
|
277
297
|
sinon.spy(mercury, 'connect');
|
|
278
298
|
|
|
279
299
|
const offlineSpy = sinon.spy();
|
|
@@ -295,13 +315,19 @@ describe('plugin-mercury', () => {
|
|
|
295
315
|
// Make sure mercury.connect has a call count of zero
|
|
296
316
|
mercury.connect.resetHistory();
|
|
297
317
|
|
|
318
|
+
// Reconnection re-derives from the resolved URL
|
|
319
|
+
// captured in _prepareAndOpenSocket, not the socket's actual
|
|
320
|
+
// (possibly changed by interceptors) url.
|
|
321
|
+
expectedReconnectUrl =
|
|
322
|
+
mercury.sessionWebSocketUrls.get('mercury-default-session');
|
|
323
|
+
|
|
298
324
|
mockWebSocket.emit('close', {code, reason});
|
|
299
325
|
|
|
300
326
|
return promiseTick(1);
|
|
301
327
|
})
|
|
302
328
|
.then(() => {
|
|
303
329
|
assert.called(offlineSpy);
|
|
304
|
-
assert.calledWith(offlineSpy, {code, reason});
|
|
330
|
+
assert.calledWith(offlineSpy, {code, reason, sessionId: 'mercury-default-session'});
|
|
305
331
|
switch (action) {
|
|
306
332
|
case 'close':
|
|
307
333
|
assert.called(permanentSpy);
|
|
@@ -324,7 +350,7 @@ describe('plugin-mercury', () => {
|
|
|
324
350
|
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
325
351
|
if (action === 'reconnect') {
|
|
326
352
|
assert.called(mercury.connect);
|
|
327
|
-
assert.calledWith(mercury.connect,
|
|
353
|
+
assert.calledWith(mercury.connect, expectedReconnectUrl);
|
|
328
354
|
assert.isTrue(mercury.connecting, 'Mercury is connecting');
|
|
329
355
|
|
|
330
356
|
// Block until reconnect completes so logs don't overlap
|