@webex/plugin-meetings 3.4.0 → 3.5.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.
Files changed (60) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/interpretation/index.js +1 -1
  4. package/dist/interpretation/siLanguage.js +1 -1
  5. package/dist/media/index.js +6 -9
  6. package/dist/media/index.js.map +1 -1
  7. package/dist/meeting/index.js +122 -49
  8. package/dist/meeting/index.js.map +1 -1
  9. package/dist/meeting/util.js +1 -0
  10. package/dist/meeting/util.js.map +1 -1
  11. package/dist/meetings/index.js +4 -2
  12. package/dist/meetings/index.js.map +1 -1
  13. package/dist/reachability/index.js +175 -103
  14. package/dist/reachability/index.js.map +1 -1
  15. package/dist/reconnection-manager/index.js +1 -1
  16. package/dist/reconnection-manager/index.js.map +1 -1
  17. package/dist/rtcMetrics/index.js +26 -6
  18. package/dist/rtcMetrics/index.js.map +1 -1
  19. package/dist/types/meeting/index.d.ts +11 -2
  20. package/dist/types/meetings/index.d.ts +2 -1
  21. package/dist/types/reachability/index.d.ts +14 -2
  22. package/dist/types/rtcMetrics/index.d.ts +11 -1
  23. package/dist/webinar/index.js +1 -1
  24. package/package.json +22 -22
  25. package/src/media/index.ts +5 -9
  26. package/src/meeting/index.ts +54 -10
  27. package/src/meeting/util.ts +2 -0
  28. package/src/meetings/index.ts +4 -3
  29. package/src/reachability/index.ts +49 -4
  30. package/src/reconnection-manager/index.ts +1 -1
  31. package/src/rtcMetrics/index.ts +25 -5
  32. package/test/integration/spec/converged-space-meetings.js +1 -1
  33. package/test/unit/spec/breakouts/index.ts +1 -0
  34. package/test/unit/spec/interceptors/locusRetry.ts +11 -10
  35. package/test/unit/spec/media/MediaConnectionAwaiter.ts +1 -0
  36. package/test/unit/spec/media/index.ts +34 -7
  37. package/test/unit/spec/media/properties.ts +1 -1
  38. package/test/unit/spec/meeting/connectionStateHandler.ts +1 -0
  39. package/test/unit/spec/meeting/index.js +77 -6
  40. package/test/unit/spec/meeting/locusMediaRequest.ts +3 -2
  41. package/test/unit/spec/meeting/request.js +1 -0
  42. package/test/unit/spec/meeting/utils.js +4 -0
  43. package/test/unit/spec/meeting-info/meetinginfov2.js +10 -11
  44. package/test/unit/spec/meeting-info/request.js +1 -1
  45. package/test/unit/spec/meetings/index.js +30 -3
  46. package/test/unit/spec/members/request.js +2 -1
  47. package/test/unit/spec/multistream/mediaRequestManager.ts +1 -0
  48. package/test/unit/spec/multistream/receiveSlot.ts +1 -0
  49. package/test/unit/spec/multistream/receiveSlotManager.ts +1 -0
  50. package/test/unit/spec/multistream/remoteMedia.ts +1 -0
  51. package/test/unit/spec/multistream/remoteMediaGroup.ts +1 -0
  52. package/test/unit/spec/multistream/remoteMediaManager.ts +1 -0
  53. package/test/unit/spec/multistream/sendSlotManager.ts +1 -0
  54. package/test/unit/spec/personal-meeting-room/personal-meeting-room.js +0 -1
  55. package/test/unit/spec/reachability/index.ts +211 -13
  56. package/test/unit/spec/reachability/request.js +1 -0
  57. package/test/unit/spec/roap/request.ts +1 -0
  58. package/test/unit/spec/rtcMetrics/index.ts +31 -0
  59. package/src/networkQualityMonitor/index.ts +0 -211
  60. package/test/unit/spec/networkQualityMonitor/index.js +0 -99
@@ -1 +1 @@
1
- {"version":3,"names":["_internalPluginMetrics","require","_uuid","_interopRequireDefault","_constants","parseJsonPayload","payload","JSON","parse","_","RtcMetrics","exports","default","webex","meetingId","correlationId","_classCallCheck2","_defineProperty2","intervalId","window","setInterval","sendMetricsInQueue","bind","setNewConnectionId","setTimeout","_createClass2","key","value","metricsQueue","length","sendMetrics","addMetrics","data","name","map","anonymizeIp","push","parsedPayload","e","console","error","closeMetrics","clearInterval","stats","type","ip","CallDiagnosticUtils","anonymizeIPAddress","undefined","address","relatedAddress","_stringify","connectionId","uuid","v4","request","method","service","resource","headers","appId","RTC_METRICS","APP_ID","body","metrics","version","userId","internal","device"],"sources":["index.ts"],"sourcesContent":["/* eslint-disable class-methods-use-this */\nimport {CallDiagnosticUtils} from '@webex/internal-plugin-metrics';\nimport uuid from 'uuid';\nimport RTC_METRICS from './constants';\n\nconst parseJsonPayload = (payload: any[]): any | null => {\n try {\n if (payload && payload[0]) {\n return JSON.parse(payload[0]);\n }\n\n return null;\n } catch (_) {\n return null;\n }\n};\n\n/**\n * Rtc Metrics\n */\nexport default class RtcMetrics {\n /**\n * Array of MetricData items to be sent to the metrics service.\n */\n metricsQueue = [];\n\n intervalId: number;\n\n webex: any;\n\n meetingId: string;\n\n correlationId: string;\n\n connectionId: string;\n\n /**\n * Initialize the interval.\n *\n * @param {object} webex - The main `webex` object.\n * @param {string} meetingId - The meeting id.\n * @param {string} correlationId - The correlation id.\n */\n constructor(webex, meetingId, correlationId) {\n // `window` is used to prevent typescript from returning a NodeJS.Timer.\n this.intervalId = window.setInterval(this.sendMetricsInQueue.bind(this), 30 * 1000);\n this.meetingId = meetingId;\n this.webex = webex;\n this.correlationId = correlationId;\n this.setNewConnectionId();\n // Send the first set of metrics at 5 seconds in the case of a user leaving the call shortly after joining.\n setTimeout(this.sendMetricsInQueue.bind(this), 5 * 1000);\n }\n\n /**\n * Check to see if the metrics queue has any items.\n *\n * @returns {void}\n */\n public sendMetricsInQueue() {\n if (this.metricsQueue.length) {\n this.sendMetrics();\n this.metricsQueue = [];\n }\n }\n\n /**\n * Add metrics items to the metrics queue.\n *\n * @param {object} data - An object with a payload array of metrics items.\n *\n * @returns {void}\n */\n addMetrics(data) {\n if (data.payload.length) {\n if (data.name === 'stats-report') {\n data.payload = data.payload.map(this.anonymizeIp);\n }\n\n this.metricsQueue.push(data);\n\n try {\n // If a connection fails, send the rest of the metrics in queue and get a new connection id.\n const parsedPayload = parseJsonPayload(data.payload);\n if (\n data.name === 'onconnectionstatechange' &&\n parsedPayload &&\n parsedPayload.value === 'failed'\n ) {\n this.sendMetricsInQueue();\n this.setNewConnectionId();\n }\n } catch (e) {\n console.error(e);\n }\n }\n }\n\n /**\n * Clear the metrics interval.\n *\n * @returns {void}\n */\n closeMetrics() {\n this.sendMetricsInQueue();\n clearInterval(this.intervalId);\n }\n\n /**\n * Anonymize IP addresses.\n *\n * @param {array} stats - An RTCStatsReport organized into an array of strings.\n * @returns {string}\n */\n anonymizeIp(stats: string): string {\n const data = JSON.parse(stats);\n // on local and remote candidates, anonymize the last 4 bits.\n if (data.type === 'local-candidate' || data.type === 'remote-candidate') {\n data.ip = CallDiagnosticUtils.anonymizeIPAddress(data.ip) || undefined;\n data.address = CallDiagnosticUtils.anonymizeIPAddress(data.address) || undefined;\n data.relatedAddress =\n CallDiagnosticUtils.anonymizeIPAddress(data.relatedAddress) || undefined;\n }\n\n return JSON.stringify(data);\n }\n\n /**\n * Set a new connection id.\n *\n * @returns {void}\n */\n private setNewConnectionId() {\n this.connectionId = uuid.v4();\n }\n\n /**\n * Send metrics to the metrics service.\n *\n * @returns {void}\n */\n private sendMetrics() {\n this.webex.request({\n method: 'POST',\n service: 'unifiedTelemetry',\n resource: 'metric/v2',\n headers: {\n type: 'webrtcMedia',\n appId: RTC_METRICS.APP_ID,\n },\n body: {\n metrics: [\n {\n type: 'webrtc',\n version: '1.1.0',\n userId: this.webex.internal.device.userId,\n meetingId: this.meetingId,\n correlationId: this.correlationId,\n connectionId: this.connectionId,\n data: this.metricsQueue,\n },\n ],\n },\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"names":["_internalPluginMetrics","require","_uuid","_interopRequireDefault","_constants","parseJsonPayload","payload","JSON","parse","_","RtcMetrics","exports","default","webex","meetingId","correlationId","_classCallCheck2","_defineProperty2","intervalId","window","setInterval","sendMetricsInQueue","bind","resetConnection","_createClass2","key","value","metricsQueue","length","sendMetrics","sendNextMetrics","shouldSendMetricsOnNextStatsReport","addMetrics","data","name","map","anonymizeIp","push","parsedPayload","e","console","error","closeMetrics","clearInterval","stats","type","ip","CallDiagnosticUtils","anonymizeIPAddress","undefined","address","relatedAddress","_stringify","connectionId","uuid","v4","request","method","service","resource","headers","appId","RTC_METRICS","APP_ID","body","metrics","version","userId","internal","device"],"sources":["index.ts"],"sourcesContent":["/* eslint-disable class-methods-use-this */\nimport {CallDiagnosticUtils} from '@webex/internal-plugin-metrics';\nimport uuid from 'uuid';\nimport RTC_METRICS from './constants';\n\nconst parseJsonPayload = (payload: any[]): any | null => {\n try {\n if (payload && payload[0]) {\n return JSON.parse(payload[0]);\n }\n\n return null;\n } catch (_) {\n return null;\n }\n};\n\n/**\n * Rtc Metrics\n */\nexport default class RtcMetrics {\n /**\n * Array of MetricData items to be sent to the metrics service.\n */\n metricsQueue = [];\n\n intervalId: number;\n\n webex: any;\n\n meetingId: string;\n\n correlationId: string;\n\n connectionId: string;\n\n shouldSendMetricsOnNextStatsReport: boolean;\n\n /**\n * Initialize the interval.\n *\n * @param {object} webex - The main `webex` object.\n * @param {string} meetingId - The meeting id.\n * @param {string} correlationId - The correlation id.\n */\n constructor(webex, meetingId, correlationId) {\n // `window` is used to prevent typescript from returning a NodeJS.Timer.\n this.intervalId = window.setInterval(this.sendMetricsInQueue.bind(this), 30 * 1000);\n this.meetingId = meetingId;\n this.webex = webex;\n this.correlationId = correlationId;\n this.resetConnection();\n }\n\n /**\n * Check to see if the metrics queue has any items.\n *\n * @returns {void}\n */\n public sendMetricsInQueue() {\n if (this.metricsQueue.length) {\n this.sendMetrics();\n this.metricsQueue = [];\n }\n }\n\n /**\n * Forces sending metrics when we get the next stats-report\n *\n * This is useful for cases when something important happens that affects the media connection,\n * for example when we move from lobby into the meeting.\n *\n * @returns {void}\n */\n public sendNextMetrics() {\n this.shouldSendMetricsOnNextStatsReport = true;\n }\n\n /**\n * Add metrics items to the metrics queue.\n *\n * @param {object} data - An object with a payload array of metrics items.\n *\n * @returns {void}\n */\n addMetrics(data) {\n if (data.payload.length) {\n if (data.name === 'stats-report') {\n data.payload = data.payload.map(this.anonymizeIp);\n }\n\n this.metricsQueue.push(data);\n\n if (this.shouldSendMetricsOnNextStatsReport && data.name === 'stats-report') {\n // this is the first useful set of data (WCME gives it to us after 5s), send it out immediately\n // in case the user is unhappy and closes the browser early\n this.sendMetricsInQueue();\n this.shouldSendMetricsOnNextStatsReport = false;\n }\n\n try {\n // If a connection fails, send the rest of the metrics in queue and get a new connection id.\n const parsedPayload = parseJsonPayload(data.payload);\n if (\n data.name === 'onconnectionstatechange' &&\n parsedPayload &&\n parsedPayload.value === 'failed'\n ) {\n this.sendMetricsInQueue();\n this.resetConnection();\n }\n } catch (e) {\n console.error(e);\n }\n }\n }\n\n /**\n * Clear the metrics interval.\n *\n * @returns {void}\n */\n closeMetrics() {\n this.sendMetricsInQueue();\n clearInterval(this.intervalId);\n }\n\n /**\n * Anonymize IP addresses.\n *\n * @param {array} stats - An RTCStatsReport organized into an array of strings.\n * @returns {string}\n */\n anonymizeIp(stats: string): string {\n const data = JSON.parse(stats);\n // on local and remote candidates, anonymize the last 4 bits.\n if (data.type === 'local-candidate' || data.type === 'remote-candidate') {\n data.ip = CallDiagnosticUtils.anonymizeIPAddress(data.ip) || undefined;\n data.address = CallDiagnosticUtils.anonymizeIPAddress(data.address) || undefined;\n data.relatedAddress =\n CallDiagnosticUtils.anonymizeIPAddress(data.relatedAddress) || undefined;\n }\n\n return JSON.stringify(data);\n }\n\n /**\n * Set a new connection id.\n *\n * @returns {void}\n */\n private resetConnection() {\n this.connectionId = uuid.v4();\n this.shouldSendMetricsOnNextStatsReport = true;\n }\n\n /**\n * Send metrics to the metrics service.\n *\n * @returns {void}\n */\n private sendMetrics() {\n this.webex.request({\n method: 'POST',\n service: 'unifiedTelemetry',\n resource: 'metric/v2',\n headers: {\n type: 'webrtcMedia',\n appId: RTC_METRICS.APP_ID,\n },\n body: {\n metrics: [\n {\n type: 'webrtc',\n version: '1.1.0',\n userId: this.webex.internal.device.userId,\n meetingId: this.meetingId,\n correlationId: this.correlationId,\n connectionId: this.connectionId,\n data: this.metricsQueue,\n },\n ],\n },\n });\n }\n}\n"],"mappings":"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"}
@@ -2,9 +2,8 @@
2
2
  import { StatelessWebexPlugin } from '@webex/webex-core';
3
3
  import { ClientEvent, ClientEventLeaveReason } from '@webex/internal-plugin-metrics';
4
4
  import { ClientEvent as RawClientEvent } from '@webex/event-dictionary-ts';
5
- import { MediaType, StatsAnalyzer } from '@webex/internal-media-core';
5
+ import { MediaType, StatsAnalyzer, NetworkQualityMonitor } from '@webex/internal-media-core';
6
6
  import { LocalStream, LocalCameraStream, LocalDisplayStream, LocalSystemAudioStream, LocalMicrophoneStream } from '@webex/media-helpers';
7
- import NetworkQualityMonitor from '../networkQualityMonitor';
8
7
  import Roap, { type TurnServerInfo, type TurnDiscoverySkipReason } from '../roap/index';
9
8
  import { type BundlePolicy } from '../media';
10
9
  import MediaProperties from '../media/properties';
@@ -334,6 +333,7 @@ type FetchMeetingInfoParams = {
334
333
  * @class Meeting
335
334
  */
336
335
  export default class Meeting extends StatelessWebexPlugin {
336
+ #private;
337
337
  attrs: any;
338
338
  audio: any;
339
339
  breakouts: any;
@@ -458,6 +458,7 @@ export default class Meeting extends StatelessWebexPlugin {
458
458
  private connectionStateHandler?;
459
459
  private iceCandidateErrors;
460
460
  private iceCandidatesCount;
461
+ private rtcMetrics?;
461
462
  /**
462
463
  * @param {Object} attrs
463
464
  * @param {Object} options
@@ -487,6 +488,12 @@ export default class Meeting extends StatelessWebexPlugin {
487
488
  * @param {string} correlationId
488
489
  */
489
490
  set correlationId(correlationId: string);
491
+ /**
492
+ * Getter - Returns isoLocalClientMeetingJoinTime
493
+ * This will be set once on meeting join, and not updated again
494
+ * @returns {string | undefined}
495
+ */
496
+ get isoLocalClientMeetingJoinTime(): string | undefined;
490
497
  /**
491
498
  * Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
492
499
  * @param {any} info
@@ -1295,6 +1302,8 @@ export default class Meeting extends StatelessWebexPlugin {
1295
1302
  *
1296
1303
  * @private
1297
1304
  * @static
1305
+ * @param {boolean} isAudioEnabled
1306
+ * @param {boolean} isVideoEnabled
1298
1307
  * @returns {Promise<void>}
1299
1308
  */
1300
1309
  private static handleDeviceLogging;
@@ -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}
@@ -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
@@ -11,6 +11,7 @@ export default class RtcMetrics {
11
11
  meetingId: string;
12
12
  correlationId: string;
13
13
  connectionId: string;
14
+ shouldSendMetricsOnNextStatsReport: boolean;
14
15
  /**
15
16
  * Initialize the interval.
16
17
  *
@@ -25,6 +26,15 @@ export default class RtcMetrics {
25
26
  * @returns {void}
26
27
  */
27
28
  sendMetricsInQueue(): void;
29
+ /**
30
+ * Forces sending metrics when we get the next stats-report
31
+ *
32
+ * This is useful for cases when something important happens that affects the media connection,
33
+ * for example when we move from lobby into the meeting.
34
+ *
35
+ * @returns {void}
36
+ */
37
+ sendNextMetrics(): void;
28
38
  /**
29
39
  * Add metrics items to the metrics queue.
30
40
  *
@@ -51,7 +61,7 @@ export default class RtcMetrics {
51
61
  *
52
62
  * @returns {void}
53
63
  */
54
- private setNewConnectionId;
64
+ private resetConnection;
55
65
  /**
56
66
  * Send metrics to the metrics service.
57
67
  *
@@ -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"
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",
47
+ "@webex/plugin-rooms": "3.5.0",
48
+ "@webex/test-helper-chai": "3.5.0",
49
+ "@webex/test-helper-mocha": "3.5.0",
50
+ "@webex/test-helper-mock-webex": "3.5.0",
51
+ "@webex/test-helper-retry": "3.5.0",
52
+ "@webex/test-helper-test-users": "3.5.0",
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",
65
+ "@webex/internal-media-core": "2.11.1",
66
+ "@webex/internal-plugin-conversation": "3.5.0",
67
+ "@webex/internal-plugin-device": "3.5.0",
68
+ "@webex/internal-plugin-llm": "3.5.0",
69
+ "@webex/internal-plugin-mercury": "3.5.0",
70
+ "@webex/internal-plugin-metrics": "3.5.0",
71
+ "@webex/internal-plugin-support": "3.5.0",
72
+ "@webex/internal-plugin-user": "3.5.0",
73
+ "@webex/internal-plugin-voicea": "3.5.0",
74
+ "@webex/media-helpers": "3.5.0",
75
+ "@webex/plugin-people": "3.5.0",
76
+ "@webex/plugin-rooms": "3.5.0",
77
77
  "@webex/web-capabilities": "^1.4.0",
78
- "@webex/webex-core": "3.4.0",
78
+ "@webex/webex-core": "3.5.0",
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"
95
95
  }
@@ -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
 
@@ -24,6 +24,8 @@ import {
24
24
  RoapMessage,
25
25
  StatsAnalyzer,
26
26
  StatsAnalyzerEventNames,
27
+ NetworkQualityEventNames,
28
+ NetworkQualityMonitor,
27
29
  } from '@webex/internal-media-core';
28
30
 
29
31
  import {
@@ -54,7 +56,6 @@ import {
54
56
  AddMediaFailed,
55
57
  } from '../common/errors/webex-errors';
56
58
 
57
- import NetworkQualityMonitor from '../networkQualityMonitor';
58
59
  import LoggerProxy from '../common/logs/logger-proxy';
59
60
  import EventsUtil from '../common/events/util';
60
61
  import Trigger from '../common/events/trigger-proxy';
@@ -154,6 +155,7 @@ import ControlsOptionsManager from '../controls-options-manager';
154
155
  import PermissionError from '../common/errors/permission';
155
156
  import {LocusMediaRequest} from './locusMediaRequest';
156
157
  import {ConnectionStateHandler, ConnectionStateEvent} from './connectionStateHandler';
158
+ import RtcMetrics from '../rtcMetrics';
157
159
 
158
160
  // default callback so we don't call an undefined function, but in practice it should never be used
159
161
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -536,6 +538,7 @@ export default class Meeting extends StatelessWebexPlugin {
536
538
  id: string;
537
539
  isMultistream: boolean;
538
540
  locusUrl: string;
541
+ #isoLocalClientMeetingJoinTime?: string;
539
542
  mediaConnections: any[];
540
543
  mediaId?: string;
541
544
  meetingFiniteStateMachine: any;
@@ -695,6 +698,7 @@ export default class Meeting extends StatelessWebexPlugin {
695
698
  private connectionStateHandler?: ConnectionStateHandler;
696
699
  private iceCandidateErrors: Map<string, number>;
697
700
  private iceCandidatesCount: number;
701
+ private rtcMetrics?: RtcMetrics;
698
702
 
699
703
  /**
700
704
  * @param {Object} attrs
@@ -1518,6 +1522,17 @@ export default class Meeting extends StatelessWebexPlugin {
1518
1522
  * @memberof Meeting
1519
1523
  */
1520
1524
  this.iceCandidatesCount = 0;
1525
+
1526
+ /**
1527
+ * Start time of meeting as an ISO string
1528
+ * based on browser time, so can only be used to compute durations client side
1529
+ * undefined if meeting has not been joined, set once on meeting join, and not updated again
1530
+ * @instance
1531
+ * @type {string}
1532
+ * @private
1533
+ * @memberof Meeting
1534
+ */
1535
+ this.#isoLocalClientMeetingJoinTime = undefined;
1521
1536
  }
1522
1537
 
1523
1538
  /**
@@ -1566,6 +1581,15 @@ export default class Meeting extends StatelessWebexPlugin {
1566
1581
  this.callStateForMetrics.correlationId = correlationId;
1567
1582
  }
1568
1583
 
1584
+ /**
1585
+ * Getter - Returns isoLocalClientMeetingJoinTime
1586
+ * This will be set once on meeting join, and not updated again
1587
+ * @returns {string | undefined}
1588
+ */
1589
+ get isoLocalClientMeetingJoinTime(): string | undefined {
1590
+ return this.#isoLocalClientMeetingJoinTime;
1591
+ }
1592
+
1569
1593
  /**
1570
1594
  * Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
1571
1595
  * @param {any} info
@@ -3155,6 +3179,7 @@ export default class Meeting extends StatelessWebexPlugin {
3155
3179
  options: {meetingId: this.id},
3156
3180
  });
3157
3181
  }
3182
+ this.rtcMetrics?.sendNextMetrics();
3158
3183
  this.updateLLMConnection();
3159
3184
  });
3160
3185
 
@@ -5228,6 +5253,11 @@ export default class Meeting extends StatelessWebexPlugin {
5228
5253
  this.meetingFiniteStateMachine.join();
5229
5254
  this.setupLocusMediaRequest();
5230
5255
 
5256
+ // @ts-ignore
5257
+ this.webex.internal.device.meetingStarted();
5258
+
5259
+ this.#isoLocalClientMeetingJoinTime = new Date().toISOString();
5260
+
5231
5261
  LoggerProxy.logger.log('Meeting:index#join --> Success');
5232
5262
 
5233
5263
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_SUCCESS, {
@@ -6306,14 +6336,17 @@ export default class Meeting extends StatelessWebexPlugin {
6306
6336
  * @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
6307
6337
  */
6308
6338
  private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
6339
+ this.rtcMetrics = this.isMultistream
6340
+ ? // @ts-ignore
6341
+ new RtcMetrics(this.webex, this.id, this.correlationId)
6342
+ : undefined;
6343
+
6309
6344
  const mc = Media.createMediaConnection(
6310
6345
  this.isMultistream,
6311
6346
  this.getMediaConnectionDebugId(),
6312
- // @ts-ignore
6313
- this.webex,
6314
6347
  this.id,
6315
- this.correlationId,
6316
6348
  {
6349
+ rtcMetrics: this.rtcMetrics,
6317
6350
  mediaProperties: this.mediaProperties,
6318
6351
  remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
6319
6352
  // @ts-ignore - config coming from registerPlugin
@@ -6500,7 +6533,7 @@ export default class Meeting extends StatelessWebexPlugin {
6500
6533
  });
6501
6534
  this.setupStatsAnalyzerEventHandlers();
6502
6535
  this.networkQualityMonitor.on(
6503
- EVENT_TRIGGERS.NETWORK_QUALITY,
6536
+ NetworkQualityEventNames.NETWORK_QUALITY,
6504
6537
  this.sendNetworkQualityEvent.bind(this)
6505
6538
  );
6506
6539
  }
@@ -6511,12 +6544,21 @@ export default class Meeting extends StatelessWebexPlugin {
6511
6544
  *
6512
6545
  * @private
6513
6546
  * @static
6547
+ * @param {boolean} isAudioEnabled
6548
+ * @param {boolean} isVideoEnabled
6514
6549
  * @returns {Promise<void>}
6515
6550
  */
6516
- private static async handleDeviceLogging(): Promise<void> {
6517
- try {
6518
- const devices = await getDevices();
6519
6551
 
6552
+ private static async handleDeviceLogging(isAudioEnabled, isVideoEnabled): Promise<void> {
6553
+ try {
6554
+ let devices = [];
6555
+ if (isVideoEnabled && isAudioEnabled) {
6556
+ devices = await getDevices();
6557
+ } else if (isVideoEnabled) {
6558
+ devices = await getDevices(Media.DeviceKind.VIDEO_INPUT);
6559
+ } else if (isAudioEnabled) {
6560
+ devices = await getDevices(Media.DeviceKind.AUDIO_INPUT);
6561
+ }
6520
6562
  MeetingUtil.handleDeviceLogging(devices);
6521
6563
  } catch {
6522
6564
  // getDevices may fail if we don't have browser permissions, that's ok, we still can have a media connection
@@ -7009,7 +7051,7 @@ export default class Meeting extends StatelessWebexPlugin {
7009
7051
  );
7010
7052
 
7011
7053
  if (audioEnabled || videoEnabled) {
7012
- await Meeting.handleDeviceLogging();
7054
+ await Meeting.handleDeviceLogging(audioEnabled, videoEnabled);
7013
7055
  } else {
7014
7056
  LoggerProxy.logger.info(`${LOG_HEADER} device logging not required`);
7015
7057
  }
@@ -7022,6 +7064,7 @@ export default class Meeting extends StatelessWebexPlugin {
7022
7064
  await this.mediaProperties.getCurrentConnectionInfo();
7023
7065
  // @ts-ignore
7024
7066
  const reachabilityStats = await this.webex.meetings.reachability.getReachabilityMetrics();
7067
+ const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
7025
7068
 
7026
7069
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
7027
7070
  correlation_id: this.correlationId,
@@ -7033,6 +7076,7 @@ export default class Meeting extends StatelessWebexPlugin {
7033
7076
  retriedWithTurnServer: this.addMediaData.retriedWithTurnServer,
7034
7077
  isJoinWithMediaRetry: this.joinWithMediaRetryInfo.isRetry,
7035
7078
  ...reachabilityStats,
7079
+ ...iceCandidateErrors,
7036
7080
  iceCandidatesCount: this.iceCandidatesCount,
7037
7081
  });
7038
7082
  // @ts-ignore
@@ -8191,7 +8235,7 @@ export default class Meeting extends StatelessWebexPlugin {
8191
8235
  * @private
8192
8236
  * @memberof Meeting
8193
8237
  */
8194
- private sendNetworkQualityEvent(res: any) {
8238
+ private sendNetworkQualityEvent(res: {networkQualityScore: number; mediaType: string}) {
8195
8239
  Trigger.trigger(
8196
8240
  this,
8197
8241
  {
@@ -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
  /**
@@ -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: ',