@webex/internal-plugin-mercury 3.11.0 → 3.12.0-auth-prejoin-fetch.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.
@@ -0,0 +1,6 @@
1
+ export const SOCKET_READY_STATE = Object.freeze({
2
+ CONNECTING: 0,
3
+ OPEN: 1,
4
+ CLOSING: 2,
5
+ CLOSED: 3,
6
+ });
@@ -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 (socket.readyState === 2 || socket.readyState === 3) {
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.close(options.code, options.reason);
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 !== 1) {
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(() => {
@@ -249,8 +267,14 @@ describe('plugin-mercury', () => {
249
267
  },
250
268
  {
251
269
  code: 4000,
270
+ reason: 'Replaced',
252
271
  action: 'replace',
253
272
  },
273
+ {
274
+ code: 4000,
275
+ reason: 'Unexpected close',
276
+ action: 'reconnect',
277
+ },
254
278
  {
255
279
  action: 'close',
256
280
  },
@@ -274,6 +298,8 @@ describe('plugin-mercury', () => {
274
298
  mercury._reconnect.restore();
275
299
  }
276
300
 
301
+ let expectedReconnectUrl;
302
+
277
303
  sinon.spy(mercury, 'connect');
278
304
 
279
305
  const offlineSpy = sinon.spy();
@@ -295,13 +321,19 @@ describe('plugin-mercury', () => {
295
321
  // Make sure mercury.connect has a call count of zero
296
322
  mercury.connect.resetHistory();
297
323
 
324
+ // Reconnection re-derives from the resolved URL
325
+ // captured in _prepareAndOpenSocket, not the socket's actual
326
+ // (possibly changed by interceptors) url.
327
+ expectedReconnectUrl =
328
+ mercury.sessionWebSocketUrls.get('mercury-default-session');
329
+
298
330
  mockWebSocket.emit('close', {code, reason});
299
331
 
300
332
  return promiseTick(1);
301
333
  })
302
334
  .then(() => {
303
335
  assert.called(offlineSpy);
304
- assert.calledWith(offlineSpy, {code, reason});
336
+ assert.calledWith(offlineSpy, {code, reason, sessionId: 'mercury-default-session'});
305
337
  switch (action) {
306
338
  case 'close':
307
339
  assert.called(permanentSpy);
@@ -324,7 +356,7 @@ describe('plugin-mercury', () => {
324
356
  assert.isFalse(mercury.connected, 'Mercury is not connected');
325
357
  if (action === 'reconnect') {
326
358
  assert.called(mercury.connect);
327
- assert.calledWith(mercury.connect, mockWebSocket.url);
359
+ assert.calledWith(mercury.connect, expectedReconnectUrl);
328
360
  assert.isTrue(mercury.connecting, 'Mercury is connecting');
329
361
 
330
362
  // Block until reconnect completes so logs don't overlap