@webex/plugin-meetings 3.4.0 → 3.5.0-next.10

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.
Files changed (76) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +1 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/interpretation/index.js +1 -1
  6. package/dist/interpretation/siLanguage.js +1 -1
  7. package/dist/media/index.js +6 -9
  8. package/dist/media/index.js.map +1 -1
  9. package/dist/meeting/in-meeting-actions.js +3 -1
  10. package/dist/meeting/in-meeting-actions.js.map +1 -1
  11. package/dist/meeting/index.js +154 -50
  12. package/dist/meeting/index.js.map +1 -1
  13. package/dist/meeting/util.js +1 -0
  14. package/dist/meeting/util.js.map +1 -1
  15. package/dist/meetings/index.js +11 -2
  16. package/dist/meetings/index.js.map +1 -1
  17. package/dist/reachability/index.js +175 -103
  18. package/dist/reachability/index.js.map +1 -1
  19. package/dist/reconnection-manager/index.js +1 -1
  20. package/dist/reconnection-manager/index.js.map +1 -1
  21. package/dist/types/constants.d.ts +2 -1
  22. package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
  23. package/dist/types/meeting/index.d.ts +22 -2
  24. package/dist/types/meetings/index.d.ts +4 -2
  25. package/dist/types/reachability/index.d.ts +14 -2
  26. package/dist/webinar/index.js +1 -1
  27. package/package.json +22 -22
  28. package/src/constants.ts +1 -0
  29. package/src/media/index.ts +6 -10
  30. package/src/meeting/in-meeting-actions.ts +3 -0
  31. package/src/meeting/index.ts +92 -10
  32. package/src/meeting/util.ts +2 -0
  33. package/src/meetings/index.ts +11 -4
  34. package/src/reachability/index.ts +49 -4
  35. package/src/reconnection-manager/index.ts +1 -1
  36. package/test/integration/spec/converged-space-meetings.js +1 -1
  37. package/test/unit/spec/breakouts/index.ts +1 -0
  38. package/test/unit/spec/interceptors/locusRetry.ts +11 -10
  39. package/test/unit/spec/media/MediaConnectionAwaiter.ts +1 -0
  40. package/test/unit/spec/media/index.ts +34 -7
  41. package/test/unit/spec/media/properties.ts +1 -1
  42. package/test/unit/spec/meeting/connectionStateHandler.ts +1 -0
  43. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  44. package/test/unit/spec/meeting/index.js +119 -12
  45. package/test/unit/spec/meeting/locusMediaRequest.ts +3 -2
  46. package/test/unit/spec/meeting/request.js +1 -0
  47. package/test/unit/spec/meeting/utils.js +4 -0
  48. package/test/unit/spec/meeting-info/meetinginfov2.js +10 -11
  49. package/test/unit/spec/meeting-info/request.js +1 -1
  50. package/test/unit/spec/meetings/index.js +40 -5
  51. package/test/unit/spec/members/request.js +2 -1
  52. package/test/unit/spec/multistream/mediaRequestManager.ts +1 -0
  53. package/test/unit/spec/multistream/receiveSlot.ts +1 -0
  54. package/test/unit/spec/multistream/receiveSlotManager.ts +1 -0
  55. package/test/unit/spec/multistream/remoteMedia.ts +1 -0
  56. package/test/unit/spec/multistream/remoteMediaGroup.ts +1 -0
  57. package/test/unit/spec/multistream/remoteMediaManager.ts +1 -0
  58. package/test/unit/spec/multistream/sendSlotManager.ts +1 -0
  59. package/test/unit/spec/personal-meeting-room/personal-meeting-room.js +0 -1
  60. package/test/unit/spec/reachability/index.ts +211 -13
  61. package/test/unit/spec/reachability/request.js +1 -0
  62. package/test/unit/spec/roap/request.ts +1 -0
  63. package/dist/networkQualityMonitor/index.js +0 -227
  64. package/dist/networkQualityMonitor/index.js.map +0 -1
  65. package/dist/rtcMetrics/constants.js +0 -11
  66. package/dist/rtcMetrics/constants.js.map +0 -1
  67. package/dist/rtcMetrics/index.js +0 -177
  68. package/dist/rtcMetrics/index.js.map +0 -1
  69. package/dist/types/networkQualityMonitor/index.d.ts +0 -70
  70. package/dist/types/rtcMetrics/constants.d.ts +0 -4
  71. package/dist/types/rtcMetrics/index.d.ts +0 -61
  72. package/src/networkQualityMonitor/index.ts +0 -211
  73. package/src/rtcMetrics/constants.ts +0 -3
  74. package/src/rtcMetrics/index.ts +0 -166
  75. package/test/unit/spec/networkQualityMonitor/index.js +0 -99
  76. package/test/unit/spec/rtcMetrics/index.ts +0 -123
@@ -253,11 +253,12 @@ export default class Meetings extends WebexPlugin {
253
253
  getReachability(): Reachability;
254
254
  /**
255
255
  * initializes and starts gathering reachability for Meetings
256
+ * @param {string} trigger - explains the reason for starting reachability
256
257
  * @returns {Promise}
257
258
  * @public
258
259
  * @memberof Meetings
259
260
  */
260
- startReachability(): Promise<import("../reachability").ReachabilityResults>;
261
+ startReachability(trigger?: string): Promise<import("../reachability").ReachabilityResults>;
261
262
  /**
262
263
  * Get geoHint for info for meetings
263
264
  * @returns {Promise}
@@ -303,11 +304,12 @@ export default class Meetings extends WebexPlugin {
303
304
  * @param {CallStateForMetrics} callStateForMetrics - information about call state for metrics
304
305
  * @param {Object} [meetingInfo] - Pre-fetched complete meeting info
305
306
  * @param {String} [meetingLookupUrl] - meeting info prefetch url
307
+ * @param {string} sessionCorrelationId - the optional specified sessionCorrelationId (callStateForMetrics.sessionCorrelationId) can be provided instead
306
308
  * @returns {Promise<Meeting>} A new Meeting.
307
309
  * @public
308
310
  * @memberof Meetings
309
311
  */
310
- create(destination: string, type?: DESTINATION_TYPE, useRandomDelayForInfo?: boolean, infoExtraParams?: {}, correlationId?: string, failOnMissingMeetingInfo?: boolean, callStateForMetrics?: CallStateForMetrics, meetingInfo?: any, meetingLookupUrl?: any): any;
312
+ create(destination: string, type?: DESTINATION_TYPE, useRandomDelayForInfo?: boolean, infoExtraParams?: {}, correlationId?: string, failOnMissingMeetingInfo?: boolean, callStateForMetrics?: CallStateForMetrics, meetingInfo?: any, meetingLookupUrl?: any, sessionCorrelationId?: string): any;
311
313
  /**
312
314
  * Create meeting
313
315
  *
@@ -2,7 +2,7 @@
2
2
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
3
  */
4
4
  import { Defer } from '@webex/common';
5
- import ReachabilityRequest from './request';
5
+ import ReachabilityRequest, { ClusterList } from './request';
6
6
  import { ClusterReachability, ClusterReachabilityResult } from './clusterReachability';
7
7
  import EventsScope from '../common/events/events-scope';
8
8
  export type ReachabilityMetrics = {
@@ -73,19 +73,31 @@ export default class Reachability extends EventsScope {
73
73
  xtls: number;
74
74
  };
75
75
  };
76
+ protected lastTrigger?: string;
76
77
  /**
77
78
  * Creates an instance of Reachability.
78
79
  * @param {object} webex
79
80
  * @memberof Reachability
80
81
  */
81
82
  constructor(webex: object);
83
+ /**
84
+ * Fetches the list of media clusters from the backend
85
+ * @param {boolean} isRetry
86
+ * @private
87
+ * @returns {Promise<{clusters: ClusterList, joinCookie: any}>}
88
+ */
89
+ getClusters(isRetry?: boolean): Promise<{
90
+ clusters: ClusterList;
91
+ joinCookie: any;
92
+ }>;
82
93
  /**
83
94
  * Gets a list of media clusters from the backend and performs reachability checks on all the clusters
95
+ * @param {string} trigger - explains the reason for starting reachability
84
96
  * @returns {Promise<ReachabilityResults>} reachability results
85
97
  * @public
86
98
  * @memberof Reachability
87
99
  */
88
- gatherReachability(): Promise<ReachabilityResults>;
100
+ gatherReachability(trigger: string): Promise<ReachabilityResults>;
89
101
  /**
90
102
  * Returns statistics about last reachability results. The returned value is an object
91
103
  * with a flat list of properties so that it can be easily sent with metrics
@@ -62,7 +62,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
62
62
  updateCanManageWebcast: function updateCanManageWebcast(canManageWebcast) {
63
63
  this.set('canManageWebcast', canManageWebcast);
64
64
  },
65
- version: "3.4.0"
65
+ version: "3.5.0-next.10"
66
66
  });
67
67
  var _default = exports.default = Webinar;
68
68
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -43,13 +43,13 @@
43
43
  "@webex/eslint-config-legacy": "0.0.0",
44
44
  "@webex/jest-config-legacy": "0.0.0",
45
45
  "@webex/legacy-tools": "0.0.0",
46
- "@webex/plugin-meetings": "3.4.0",
47
- "@webex/plugin-rooms": "3.4.0",
48
- "@webex/test-helper-chai": "3.4.0",
49
- "@webex/test-helper-mocha": "3.4.0",
50
- "@webex/test-helper-mock-webex": "3.4.0",
51
- "@webex/test-helper-retry": "3.4.0",
52
- "@webex/test-helper-test-users": "3.4.0",
46
+ "@webex/plugin-meetings": "3.5.0-next.10",
47
+ "@webex/plugin-rooms": "3.5.0-next.7",
48
+ "@webex/test-helper-chai": "3.5.0-next.6",
49
+ "@webex/test-helper-mocha": "3.5.0-next.6",
50
+ "@webex/test-helper-mock-webex": "3.5.0-next.6",
51
+ "@webex/test-helper-retry": "3.5.0-next.6",
52
+ "@webex/test-helper-test-users": "3.5.0-next.6",
53
53
  "chai": "^4.3.4",
54
54
  "chai-as-promised": "^7.1.1",
55
55
  "eslint": "^8.24.0",
@@ -61,21 +61,21 @@
61
61
  "typescript": "^4.7.4"
62
62
  },
63
63
  "dependencies": {
64
- "@webex/common": "3.4.0",
65
- "@webex/internal-media-core": "2.10.0",
66
- "@webex/internal-plugin-conversation": "3.4.0",
67
- "@webex/internal-plugin-device": "3.4.0",
68
- "@webex/internal-plugin-llm": "3.4.0",
69
- "@webex/internal-plugin-mercury": "3.4.0",
70
- "@webex/internal-plugin-metrics": "3.4.0",
71
- "@webex/internal-plugin-support": "3.4.0",
72
- "@webex/internal-plugin-user": "3.4.0",
73
- "@webex/internal-plugin-voicea": "3.4.0",
74
- "@webex/media-helpers": "3.4.0",
75
- "@webex/plugin-people": "3.4.0",
76
- "@webex/plugin-rooms": "3.4.0",
64
+ "@webex/common": "3.5.0-next.6",
65
+ "@webex/internal-media-core": "2.11.3",
66
+ "@webex/internal-plugin-conversation": "3.5.0-next.7",
67
+ "@webex/internal-plugin-device": "3.5.0-next.6",
68
+ "@webex/internal-plugin-llm": "3.5.0-next.6",
69
+ "@webex/internal-plugin-mercury": "3.5.0-next.6",
70
+ "@webex/internal-plugin-metrics": "3.5.0-next.6",
71
+ "@webex/internal-plugin-support": "3.5.0-next.7",
72
+ "@webex/internal-plugin-user": "3.5.0-next.6",
73
+ "@webex/internal-plugin-voicea": "3.5.0-next.10",
74
+ "@webex/media-helpers": "3.5.0-next.7",
75
+ "@webex/plugin-people": "3.5.0-next.6",
76
+ "@webex/plugin-rooms": "3.5.0-next.7",
77
77
  "@webex/web-capabilities": "^1.4.0",
78
- "@webex/webex-core": "3.4.0",
78
+ "@webex/webex-core": "3.5.0-next.6",
79
79
  "ampersand-collection": "^2.0.2",
80
80
  "bowser": "^2.11.0",
81
81
  "btoa": "^1.2.1",
@@ -91,5 +91,5 @@
91
91
  "//": [
92
92
  "TODO: upgrade jwt-decode when moving to node 18"
93
93
  ],
94
- "version": "3.4.0"
94
+ "version": "3.5.0-next.10"
95
95
  }
package/src/constants.ts CHANGED
@@ -879,6 +879,7 @@ export enum SELF_POLICY {
879
879
  SUPPORT_HDV = 'supportHDV',
880
880
  SUPPORT_PARTICIPANT_LIST = 'supportParticipantList',
881
881
  SUPPORT_VOIP = 'supportVoIP',
882
+ SUPPORT_POLLING_AND_QA = 'supportPollingAndQA',
882
883
  }
883
884
 
884
885
  export const DISPLAY_HINTS = {
@@ -15,12 +15,12 @@ import {
15
15
  LocalSystemAudioStream,
16
16
  LocalMicrophoneStream,
17
17
  } from '@webex/media-helpers';
18
+ import {RtcMetrics} from '@webex/internal-plugin-metrics';
18
19
  import LoggerProxy from '../common/logs/logger-proxy';
19
20
  import {MEDIA_TRACK_CONSTRAINT} from '../constants';
20
21
  import Config from '../config';
21
22
  import StaticConfig from '../common/config';
22
23
  import BrowserDetection from '../common/browser-detection';
23
- import RtcMetrics from '../rtcMetrics';
24
24
 
25
25
  const {isBrowser} = BrowserDetection();
26
26
 
@@ -104,9 +104,7 @@ Media.getDirection = (forceSendRecv: boolean, receive: boolean, send: boolean) =
104
104
  *
105
105
  * @param {boolean} isMultistream
106
106
  * @param {string} debugId string useful for debugging (will appear in media connection logs)
107
- * @param {object} webex main `webex` object.
108
107
  * @param {string} meetingId id for the meeting using this connection
109
- * @param {string} correlationId id used in requests to correlate to this session
110
108
  * @param {Object} options
111
109
  * @param {Object} [options.mediaProperties] contains mediaDirection and local tracks:
112
110
  * audioTrack, videoTrack, shareVideoTrack, and shareAudioTrack
@@ -120,10 +118,9 @@ Media.getDirection = (forceSendRecv: boolean, receive: boolean, send: boolean) =
120
118
  Media.createMediaConnection = (
121
119
  isMultistream: boolean,
122
120
  debugId: string,
123
- webex: object,
124
121
  meetingId: string,
125
- correlationId: string,
126
122
  options: {
123
+ rtcMetrics?: RtcMetrics;
127
124
  mediaProperties: {
128
125
  mediaDirection?: {
129
126
  receiveAudio: boolean;
@@ -150,6 +147,7 @@ Media.createMediaConnection = (
150
147
  }
151
148
  ) => {
152
149
  const {
150
+ rtcMetrics,
153
151
  mediaProperties,
154
152
  remoteQualityLevel,
155
153
  enableRtx,
@@ -192,15 +190,13 @@ Media.createMediaConnection = (
192
190
  config.bundlePolicy = bundlePolicy;
193
191
  }
194
192
 
195
- const rtcMetrics = new RtcMetrics(webex, meetingId, correlationId);
196
-
197
193
  return new MultistreamRoapMediaConnection(
198
194
  config,
199
195
  meetingId,
200
196
  /* the rtc metrics objects callbacks */
201
- (data) => rtcMetrics.addMetrics(data),
202
- () => rtcMetrics.closeMetrics(),
203
- () => rtcMetrics.sendMetricsInQueue()
197
+ (data) => rtcMetrics?.addMetrics(data),
198
+ () => rtcMetrics?.closeMetrics(),
199
+ () => rtcMetrics?.sendMetricsInQueue()
204
200
  );
205
201
  }
206
202
 
@@ -82,6 +82,7 @@ interface IInMeetingActions {
82
82
  supportHDV?: boolean;
83
83
  canShareWhiteBoard?: boolean;
84
84
  enforceVirtualBackground?: boolean;
85
+ canPollingAndQA?: boolean;
85
86
  }
86
87
 
87
88
  /**
@@ -236,6 +237,7 @@ export default class InMeetingActions implements IInMeetingActions {
236
237
 
237
238
  canShareWhiteBoard = null;
238
239
 
240
+ canPollingAndQA = null;
239
241
  /**
240
242
  * Returns all meeting action options
241
243
  * @returns {Object}
@@ -314,6 +316,7 @@ export default class InMeetingActions implements IInMeetingActions {
314
316
  supportHQV: this.supportHQV,
315
317
  supportHDV: this.supportHDV,
316
318
  canShareWhiteBoard: this.canShareWhiteBoard,
319
+ canPollingAndQA: this.canPollingAndQA,
317
320
  });
318
321
 
319
322
  /**
@@ -10,6 +10,7 @@ import {
10
10
  ClientEventLeaveReason,
11
11
  CallDiagnosticUtils,
12
12
  CALL_DIAGNOSTIC_CONFIG,
13
+ RtcMetrics,
13
14
  } from '@webex/internal-plugin-metrics';
14
15
  import {ClientEvent as RawClientEvent} from '@webex/event-dictionary-ts';
15
16
 
@@ -24,6 +25,8 @@ import {
24
25
  RoapMessage,
25
26
  StatsAnalyzer,
26
27
  StatsAnalyzerEventNames,
28
+ NetworkQualityEventNames,
29
+ NetworkQualityMonitor,
27
30
  } from '@webex/internal-media-core';
28
31
 
29
32
  import {
@@ -54,7 +57,6 @@ import {
54
57
  AddMediaFailed,
55
58
  } from '../common/errors/webex-errors';
56
59
 
57
- import NetworkQualityMonitor from '../networkQualityMonitor';
58
60
  import LoggerProxy from '../common/logs/logger-proxy';
59
61
  import EventsUtil from '../common/events/util';
60
62
  import Trigger from '../common/events/trigger-proxy';
@@ -225,6 +227,7 @@ export type AddMediaOptions = {
225
227
 
226
228
  export type CallStateForMetrics = {
227
229
  correlationId?: string;
230
+ sessionCorrelationId?: string;
228
231
  joinTrigger?: string;
229
232
  loginType?: string;
230
233
  };
@@ -536,6 +539,7 @@ export default class Meeting extends StatelessWebexPlugin {
536
539
  id: string;
537
540
  isMultistream: boolean;
538
541
  locusUrl: string;
542
+ #isoLocalClientMeetingJoinTime?: string;
539
543
  mediaConnections: any[];
540
544
  mediaId?: string;
541
545
  meetingFiniteStateMachine: any;
@@ -695,6 +699,7 @@ export default class Meeting extends StatelessWebexPlugin {
695
699
  private connectionStateHandler?: ConnectionStateHandler;
696
700
  private iceCandidateErrors: Map<string, number>;
697
701
  private iceCandidatesCount: number;
702
+ private rtcMetrics?: RtcMetrics;
698
703
 
699
704
  /**
700
705
  * @param {Object} attrs
@@ -738,12 +743,29 @@ export default class Meeting extends StatelessWebexPlugin {
738
743
  */
739
744
  this.callStateForMetrics = attrs.callStateForMetrics || {};
740
745
  const correlationId = attrs.correlationId || attrs.callStateForMetrics?.correlationId;
746
+ const sessionCorrelationId =
747
+ attrs.sessionCorrelationId || attrs.callStateForMetrics?.sessionCorrelationId;
748
+ if (sessionCorrelationId) {
749
+ LoggerProxy.logger.log(
750
+ `Meetings:index#constructor --> Initializing the meeting object with session correlation id from app ${correlationId}`
751
+ );
752
+ this.callStateForMetrics.sessionCorrelationId = sessionCorrelationId;
753
+ } else {
754
+ LoggerProxy.logger.log(
755
+ `Meetings:index#constructor --> No session correlation id supplied. None will be generated and this field will remain blank`
756
+ );
757
+ // TODO: supply a session from the meetings instance
758
+ this.callStateForMetrics.sessionCorrelationId = '';
759
+ }
741
760
  if (correlationId) {
742
761
  LoggerProxy.logger.log(
743
762
  `Meetings:index#constructor --> Initializing the meeting object with correlation id from app ${correlationId}`
744
763
  );
745
764
  this.callStateForMetrics.correlationId = correlationId;
746
765
  } else {
766
+ LoggerProxy.logger.log(
767
+ `Meetings:index#constructor --> Initializing the meeting object with generated correlation id from sdk ${this.id}`
768
+ );
747
769
  this.callStateForMetrics.correlationId = this.id;
748
770
  }
749
771
  /**
@@ -1518,6 +1540,17 @@ export default class Meeting extends StatelessWebexPlugin {
1518
1540
  * @memberof Meeting
1519
1541
  */
1520
1542
  this.iceCandidatesCount = 0;
1543
+
1544
+ /**
1545
+ * Start time of meeting as an ISO string
1546
+ * based on browser time, so can only be used to compute durations client side
1547
+ * undefined if meeting has not been joined, set once on meeting join, and not updated again
1548
+ * @instance
1549
+ * @type {string}
1550
+ * @private
1551
+ * @memberof Meeting
1552
+ */
1553
+ this.#isoLocalClientMeetingJoinTime = undefined;
1521
1554
  }
1522
1555
 
1523
1556
  /**
@@ -1566,6 +1599,31 @@ export default class Meeting extends StatelessWebexPlugin {
1566
1599
  this.callStateForMetrics.correlationId = correlationId;
1567
1600
  }
1568
1601
 
1602
+ /**
1603
+ * Getter - Returns callStateForMetrics.sessionCorrelationId
1604
+ * @returns {string}
1605
+ */
1606
+ get sessionCorrelationId() {
1607
+ return this.callStateForMetrics.sessionCorrelationId;
1608
+ }
1609
+
1610
+ /**
1611
+ * Setter - sets callStateForMetrics.sessionCorrelationId
1612
+ * @param {string} sessionCorrelationId
1613
+ */
1614
+ set sessionCorrelationId(sessionCorrelationId: string) {
1615
+ this.callStateForMetrics.sessionCorrelationId = sessionCorrelationId;
1616
+ }
1617
+
1618
+ /**
1619
+ * Getter - Returns isoLocalClientMeetingJoinTime
1620
+ * This will be set once on meeting join, and not updated again
1621
+ * @returns {string | undefined}
1622
+ */
1623
+ get isoLocalClientMeetingJoinTime(): string | undefined {
1624
+ return this.#isoLocalClientMeetingJoinTime;
1625
+ }
1626
+
1569
1627
  /**
1570
1628
  * Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
1571
1629
  * @param {any} info
@@ -3155,6 +3213,7 @@ export default class Meeting extends StatelessWebexPlugin {
3155
3213
  options: {meetingId: this.id},
3156
3214
  });
3157
3215
  }
3216
+ this.rtcMetrics?.sendNextMetrics();
3158
3217
  this.updateLLMConnection();
3159
3218
  });
3160
3219
 
@@ -3767,6 +3826,10 @@ export default class Meeting extends StatelessWebexPlugin {
3767
3826
  requiredPolicies: [SELF_POLICY.SUPPORT_CHAT],
3768
3827
  policies: this.selfUserPolicies,
3769
3828
  }),
3829
+ canPollingAndQA: ControlsOptionsUtil.hasPolicies({
3830
+ requiredPolicies: [SELF_POLICY.SUPPORT_POLLING_AND_QA],
3831
+ policies: this.selfUserPolicies,
3832
+ }),
3770
3833
  canShareApplication:
3771
3834
  (ControlsOptionsUtil.hasHints({
3772
3835
  requiredHints: [DISPLAY_HINTS.SHARE_APPLICATION],
@@ -5228,6 +5291,11 @@ export default class Meeting extends StatelessWebexPlugin {
5228
5291
  this.meetingFiniteStateMachine.join();
5229
5292
  this.setupLocusMediaRequest();
5230
5293
 
5294
+ // @ts-ignore
5295
+ this.webex.internal.device.meetingStarted();
5296
+
5297
+ this.#isoLocalClientMeetingJoinTime = new Date().toISOString();
5298
+
5231
5299
  LoggerProxy.logger.log('Meeting:index#join --> Success');
5232
5300
 
5233
5301
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_SUCCESS, {
@@ -6306,14 +6374,17 @@ export default class Meeting extends StatelessWebexPlugin {
6306
6374
  * @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
6307
6375
  */
6308
6376
  private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
6377
+ this.rtcMetrics = this.isMultistream
6378
+ ? // @ts-ignore
6379
+ new RtcMetrics(this.webex, this.id, this.correlationId)
6380
+ : undefined;
6381
+
6309
6382
  const mc = Media.createMediaConnection(
6310
6383
  this.isMultistream,
6311
6384
  this.getMediaConnectionDebugId(),
6312
- // @ts-ignore
6313
- this.webex,
6314
6385
  this.id,
6315
- this.correlationId,
6316
6386
  {
6387
+ rtcMetrics: this.rtcMetrics,
6317
6388
  mediaProperties: this.mediaProperties,
6318
6389
  remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
6319
6390
  // @ts-ignore - config coming from registerPlugin
@@ -6500,7 +6571,7 @@ export default class Meeting extends StatelessWebexPlugin {
6500
6571
  });
6501
6572
  this.setupStatsAnalyzerEventHandlers();
6502
6573
  this.networkQualityMonitor.on(
6503
- EVENT_TRIGGERS.NETWORK_QUALITY,
6574
+ NetworkQualityEventNames.NETWORK_QUALITY,
6504
6575
  this.sendNetworkQualityEvent.bind(this)
6505
6576
  );
6506
6577
  }
@@ -6511,12 +6582,21 @@ export default class Meeting extends StatelessWebexPlugin {
6511
6582
  *
6512
6583
  * @private
6513
6584
  * @static
6585
+ * @param {boolean} isAudioEnabled
6586
+ * @param {boolean} isVideoEnabled
6514
6587
  * @returns {Promise<void>}
6515
6588
  */
6516
- private static async handleDeviceLogging(): Promise<void> {
6517
- try {
6518
- const devices = await getDevices();
6519
6589
 
6590
+ private static async handleDeviceLogging(isAudioEnabled, isVideoEnabled): Promise<void> {
6591
+ try {
6592
+ let devices = [];
6593
+ if (isVideoEnabled && isAudioEnabled) {
6594
+ devices = await getDevices();
6595
+ } else if (isVideoEnabled) {
6596
+ devices = await getDevices(Media.DeviceKind.VIDEO_INPUT);
6597
+ } else if (isAudioEnabled) {
6598
+ devices = await getDevices(Media.DeviceKind.AUDIO_INPUT);
6599
+ }
6520
6600
  MeetingUtil.handleDeviceLogging(devices);
6521
6601
  } catch {
6522
6602
  // getDevices may fail if we don't have browser permissions, that's ok, we still can have a media connection
@@ -7009,7 +7089,7 @@ export default class Meeting extends StatelessWebexPlugin {
7009
7089
  );
7010
7090
 
7011
7091
  if (audioEnabled || videoEnabled) {
7012
- await Meeting.handleDeviceLogging();
7092
+ await Meeting.handleDeviceLogging(audioEnabled, videoEnabled);
7013
7093
  } else {
7014
7094
  LoggerProxy.logger.info(`${LOG_HEADER} device logging not required`);
7015
7095
  }
@@ -7022,6 +7102,7 @@ export default class Meeting extends StatelessWebexPlugin {
7022
7102
  await this.mediaProperties.getCurrentConnectionInfo();
7023
7103
  // @ts-ignore
7024
7104
  const reachabilityStats = await this.webex.meetings.reachability.getReachabilityMetrics();
7105
+ const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
7025
7106
 
7026
7107
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
7027
7108
  correlation_id: this.correlationId,
@@ -7033,6 +7114,7 @@ export default class Meeting extends StatelessWebexPlugin {
7033
7114
  retriedWithTurnServer: this.addMediaData.retriedWithTurnServer,
7034
7115
  isJoinWithMediaRetry: this.joinWithMediaRetryInfo.isRetry,
7035
7116
  ...reachabilityStats,
7117
+ ...iceCandidateErrors,
7036
7118
  iceCandidatesCount: this.iceCandidatesCount,
7037
7119
  });
7038
7120
  // @ts-ignore
@@ -8191,7 +8273,7 @@ export default class Meeting extends StatelessWebexPlugin {
8191
8273
  * @private
8192
8274
  * @memberof Meeting
8193
8275
  */
8194
- private sendNetworkQualityEvent(res: any) {
8276
+ private sendNetworkQualityEvent(res: {networkQualityScore: number; mediaType: string}) {
8195
8277
  Trigger.trigger(
8196
8278
  this,
8197
8279
  {
@@ -170,6 +170,8 @@ const MeetingUtil = {
170
170
  },
171
171
 
172
172
  cleanUp: (meeting) => {
173
+ meeting.getWebexObject().internal.device.meetingEnded();
174
+
173
175
  meeting.breakouts.cleanUp();
174
176
  meeting.simultaneousInterpretation.cleanUp();
175
177
  meeting.locusMediaRequest = undefined;
@@ -765,7 +765,7 @@ export default class Meetings extends WebexPlugin {
765
765
  return Promise.all([
766
766
  this.fetchUserPreferredWebexSite(),
767
767
  this.getGeoHint(),
768
- this.startReachability().catch((error) => {
768
+ this.startReachability('registration').catch((error) => {
769
769
  LoggerProxy.logger.error(`Meetings:index#register --> GDM error, ${error.message}`);
770
770
  }),
771
771
  // @ts-ignore
@@ -967,12 +967,13 @@ export default class Meetings extends WebexPlugin {
967
967
 
968
968
  /**
969
969
  * initializes and starts gathering reachability for Meetings
970
+ * @param {string} trigger - explains the reason for starting reachability
970
971
  * @returns {Promise}
971
972
  * @public
972
973
  * @memberof Meetings
973
974
  */
974
- startReachability() {
975
- return this.getReachability().gatherReachability();
975
+ startReachability(trigger = 'client') {
976
+ return this.getReachability().gatherReachability(trigger);
976
977
  }
977
978
 
978
979
  /**
@@ -1080,6 +1081,7 @@ export default class Meetings extends WebexPlugin {
1080
1081
  * @param {CallStateForMetrics} callStateForMetrics - information about call state for metrics
1081
1082
  * @param {Object} [meetingInfo] - Pre-fetched complete meeting info
1082
1083
  * @param {String} [meetingLookupUrl] - meeting info prefetch url
1084
+ * @param {string} sessionCorrelationId - the optional specified sessionCorrelationId (callStateForMetrics.sessionCorrelationId) can be provided instead
1083
1085
  * @returns {Promise<Meeting>} A new Meeting.
1084
1086
  * @public
1085
1087
  * @memberof Meetings
@@ -1093,7 +1095,8 @@ export default class Meetings extends WebexPlugin {
1093
1095
  failOnMissingMeetingInfo = false,
1094
1096
  callStateForMetrics: CallStateForMetrics = undefined,
1095
1097
  meetingInfo = undefined,
1096
- meetingLookupUrl = undefined
1098
+ meetingLookupUrl = undefined,
1099
+ sessionCorrelationId: string = undefined
1097
1100
  ) {
1098
1101
  // Validate meeting information based on the provided destination and
1099
1102
  // type. This must be performed prior to determining if the meeting is
@@ -1104,6 +1107,10 @@ export default class Meetings extends WebexPlugin {
1104
1107
  callStateForMetrics = {...(callStateForMetrics || {}), correlationId};
1105
1108
  }
1106
1109
 
1110
+ if (sessionCorrelationId) {
1111
+ callStateForMetrics = {...(callStateForMetrics || {}), sessionCorrelationId};
1112
+ }
1113
+
1107
1114
  return (
1108
1115
  this.meetingInfo
1109
1116
  .fetchInfoOptions(destination, type)
@@ -93,6 +93,8 @@ export default class Reachability extends EventsScope {
93
93
  expectedResultsCount = {videoMesh: {udp: 0}, public: {udp: 0, tcp: 0, xtls: 0}};
94
94
  resultsCount = {videoMesh: {udp: 0}, public: {udp: 0, tcp: 0, xtls: 0}};
95
95
 
96
+ protected lastTrigger?: string;
97
+
96
98
  /**
97
99
  * Creates an instance of Reachability.
98
100
  * @param {object} webex
@@ -114,18 +116,50 @@ export default class Reachability extends EventsScope {
114
116
  this.clusterReachability = {};
115
117
  }
116
118
 
119
+ /**
120
+ * Fetches the list of media clusters from the backend
121
+ * @param {boolean} isRetry
122
+ * @private
123
+ * @returns {Promise<{clusters: ClusterList, joinCookie: any}>}
124
+ */
125
+ async getClusters(isRetry = false): Promise<{clusters: ClusterList; joinCookie: any}> {
126
+ try {
127
+ const {clusters, joinCookie} = await this.reachabilityRequest.getClusters(
128
+ MeetingUtil.getIpVersion(this.webex)
129
+ );
130
+
131
+ return {clusters, joinCookie};
132
+ } catch (error) {
133
+ if (isRetry) {
134
+ throw error;
135
+ }
136
+
137
+ LoggerProxy.logger.error(
138
+ `Reachability:index#getClusters --> Failed with error: ${error}, retrying...`
139
+ );
140
+
141
+ return this.getClusters(true);
142
+ }
143
+ }
144
+
117
145
  /**
118
146
  * Gets a list of media clusters from the backend and performs reachability checks on all the clusters
147
+ * @param {string} trigger - explains the reason for starting reachability
119
148
  * @returns {Promise<ReachabilityResults>} reachability results
120
149
  * @public
121
150
  * @memberof Reachability
122
151
  */
123
- public async gatherReachability(): Promise<ReachabilityResults> {
152
+ public async gatherReachability(trigger: string): Promise<ReachabilityResults> {
124
153
  // Fetch clusters and measure latency
125
154
  try {
126
- const {clusters, joinCookie} = await this.reachabilityRequest.getClusters(
127
- MeetingUtil.getIpVersion(this.webex)
128
- );
155
+ this.lastTrigger = trigger;
156
+
157
+ // kick off ip version detection. For now we don't await it, as we're doing it
158
+ // to gather the timings and send them with our reachability metrics
159
+ // @ts-ignore
160
+ this.webex.internal.device.ipNetworkDetector.detect();
161
+
162
+ const {clusters, joinCookie} = await this.getClusters();
129
163
 
130
164
  // @ts-ignore
131
165
  await this.webex.boundedStorage.put(
@@ -513,6 +547,17 @@ export default class Reachability extends EventsScope {
513
547
  tcp: this.getStatistics(results, 'tcp', false),
514
548
  xtls: this.getStatistics(results, 'xtls', false),
515
549
  },
550
+ ipver: {
551
+ // @ts-ignore
552
+ firstIpV4: this.webex.internal.device.ipNetworkDetector.firstIpV4,
553
+ // @ts-ignore
554
+ firstIpV6: this.webex.internal.device.ipNetworkDetector.firstIpV6,
555
+ // @ts-ignore
556
+ firstMdns: this.webex.internal.device.ipNetworkDetector.firstMdns,
557
+ // @ts-ignore
558
+ totalTime: this.webex.internal.device.ipNetworkDetector.totalTime,
559
+ },
560
+ trigger: this.lastTrigger,
516
561
  };
517
562
  Metrics.sendBehavioralMetric(
518
563
  BEHAVIORAL_METRICS.REACHABILITY_COMPLETED,
@@ -342,7 +342,7 @@ export default class ReconnectionManager {
342
342
  }
343
343
 
344
344
  try {
345
- await this.webex.meetings.startReachability();
345
+ await this.webex.meetings.startReachability('reconnection');
346
346
  } catch (err) {
347
347
  LoggerProxy.logger.info(
348
348
  'ReconnectionManager:index#reconnect --> Reachability failed, continuing with reconnection attempt, err: ',
@@ -1,5 +1,5 @@
1
- import { config } from 'dotenv';
2
1
  import 'jsdom-global/register';
2
+ import {config} from 'dotenv';
3
3
  import {assert} from '@webex/test-helper-chai';
4
4
  import {skipInNode} from '@webex/test-helper-mocha';
5
5
  import BrowserDetection from '@webex/plugin-meetings/dist/common/browser-detection';
@@ -1,3 +1,4 @@
1
+ import 'jsdom-global/register';
1
2
  import {assert, expect} from '@webex/test-helper-chai';
2
3
  import Breakouts from '@webex/plugin-meetings/src/breakouts';
3
4
  import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';