@webex/plugin-meetings 3.9.0 → 3.10.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 (117) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +8 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/controls-options-manager/index.js +22 -5
  6. package/dist/controls-options-manager/index.js.map +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/interceptors/index.js +7 -0
  10. package/dist/interceptors/index.js.map +1 -1
  11. package/dist/interceptors/locusRouteToken.js +116 -0
  12. package/dist/interceptors/locusRouteToken.js.map +1 -0
  13. package/dist/interpretation/index.js +1 -1
  14. package/dist/interpretation/siLanguage.js +1 -1
  15. package/dist/locus-info/controlsUtils.js +11 -2
  16. package/dist/locus-info/controlsUtils.js.map +1 -1
  17. package/dist/locus-info/index.js +56 -14
  18. package/dist/locus-info/index.js.map +1 -1
  19. package/dist/locus-info/parser.js +4 -1
  20. package/dist/locus-info/parser.js.map +1 -1
  21. package/dist/media/properties.js +53 -5
  22. package/dist/media/properties.js.map +1 -1
  23. package/dist/meeting/in-meeting-actions.js +8 -0
  24. package/dist/meeting/in-meeting-actions.js.map +1 -1
  25. package/dist/meeting/index.js +339 -185
  26. package/dist/meeting/index.js.map +1 -1
  27. package/dist/meeting/muteState.js +2 -5
  28. package/dist/meeting/muteState.js.map +1 -1
  29. package/dist/meeting/request.js +177 -14
  30. package/dist/meeting/request.js.map +1 -1
  31. package/dist/meeting/util.js +39 -11
  32. package/dist/meeting/util.js.map +1 -1
  33. package/dist/meeting-info/meeting-info-v2.js +29 -21
  34. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  35. package/dist/meetings/index.js +31 -25
  36. package/dist/meetings/index.js.map +1 -1
  37. package/dist/member/index.js +9 -0
  38. package/dist/member/index.js.map +1 -1
  39. package/dist/member/types.js.map +1 -1
  40. package/dist/member/util.js +10 -0
  41. package/dist/member/util.js.map +1 -1
  42. package/dist/members/collection.js +13 -0
  43. package/dist/members/collection.js.map +1 -1
  44. package/dist/members/index.js +42 -20
  45. package/dist/members/index.js.map +1 -1
  46. package/dist/members/util.js +7 -2
  47. package/dist/members/util.js.map +1 -1
  48. package/dist/metrics/constants.js +2 -1
  49. package/dist/metrics/constants.js.map +1 -1
  50. package/dist/reachability/index.js +3 -3
  51. package/dist/reachability/index.js.map +1 -1
  52. package/dist/types/constants.d.ts +7 -0
  53. package/dist/types/controls-options-manager/index.d.ts +9 -1
  54. package/dist/types/interceptors/index.d.ts +2 -1
  55. package/dist/types/interceptors/locusRouteToken.d.ts +38 -0
  56. package/dist/types/locus-info/index.d.ts +56 -2
  57. package/dist/types/media/properties.d.ts +21 -0
  58. package/dist/types/meeting/in-meeting-actions.d.ts +8 -0
  59. package/dist/types/meeting/index.d.ts +41 -1
  60. package/dist/types/meeting/request.d.ts +42 -0
  61. package/dist/types/meeting/util.d.ts +13 -3
  62. package/dist/types/meeting-info/meeting-info-v2.d.ts +6 -3
  63. package/dist/types/meetings/index.d.ts +3 -1
  64. package/dist/types/member/index.d.ts +1 -0
  65. package/dist/types/member/types.d.ts +1 -0
  66. package/dist/types/member/util.d.ts +5 -0
  67. package/dist/types/members/collection.d.ts +6 -0
  68. package/dist/types/members/index.d.ts +12 -2
  69. package/dist/types/members/util.d.ts +6 -3
  70. package/dist/types/metrics/constants.d.ts +1 -0
  71. package/dist/webinar/index.js +1 -1
  72. package/package.json +23 -23
  73. package/src/constants.ts +10 -0
  74. package/src/controls-options-manager/index.ts +26 -5
  75. package/src/index.ts +2 -1
  76. package/src/interceptors/index.ts +2 -1
  77. package/src/interceptors/locusRouteToken.ts +80 -0
  78. package/src/locus-info/controlsUtils.ts +18 -0
  79. package/src/locus-info/index.ts +99 -17
  80. package/src/locus-info/parser.ts +5 -1
  81. package/src/media/properties.ts +43 -0
  82. package/src/meeting/in-meeting-actions.ts +16 -0
  83. package/src/meeting/index.ts +204 -24
  84. package/src/meeting/muteState.ts +2 -6
  85. package/src/meeting/request.ts +141 -0
  86. package/src/meeting/util.ts +50 -20
  87. package/src/meeting-info/meeting-info-v2.ts +24 -5
  88. package/src/meetings/index.ts +9 -3
  89. package/src/member/index.ts +10 -0
  90. package/src/member/types.ts +1 -0
  91. package/src/member/util.ts +14 -0
  92. package/src/members/collection.ts +11 -0
  93. package/src/members/index.ts +38 -5
  94. package/src/members/util.ts +18 -2
  95. package/src/metrics/constants.ts +1 -0
  96. package/src/reachability/index.ts +3 -3
  97. package/test/unit/spec/common/browser-detection.js +0 -24
  98. package/test/unit/spec/controls-options-manager/index.js +47 -0
  99. package/test/unit/spec/fixture/locus.js +1 -0
  100. package/test/unit/spec/interceptors/locusRouteToken.ts +87 -0
  101. package/test/unit/spec/locus-info/index.js +91 -15
  102. package/test/unit/spec/locus-info/parser.js +3 -2
  103. package/test/unit/spec/media/properties.ts +137 -0
  104. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -0
  105. package/test/unit/spec/meeting/index.js +398 -30
  106. package/test/unit/spec/meeting/muteState.js +32 -6
  107. package/test/unit/spec/meeting/request.js +21 -0
  108. package/test/unit/spec/meeting/utils.js +49 -17
  109. package/test/unit/spec/meeting-info/meetinginfov2.js +8 -3
  110. package/test/unit/spec/meetings/index.js +10 -5
  111. package/test/unit/spec/member/util.js +24 -0
  112. package/test/unit/spec/members/collection.js +120 -0
  113. package/test/unit/spec/members/index.js +72 -3
  114. package/test/unit/spec/members/request.js +55 -0
  115. package/test/unit/spec/members/utils.js +116 -14
  116. package/test/unit/spec/reachability/index.ts +158 -3
  117. package/test/unit/spec/roap/turnDiscovery.ts +3 -3
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ import {registerPlugin} from '@webex/webex-core';
3
3
 
4
4
  import Meetings from './meetings';
5
5
  import config from './config';
6
- import {LocusRetryStatusInterceptor} from './interceptors';
6
+ import {LocusRetryStatusInterceptor, LocusRouteTokenInterceptor} from './interceptors';
7
7
  import CaptchaError from './common/errors/captcha-error';
8
8
  import IntentToJoinError from './common/errors/intent-to-join';
9
9
  import PasswordError from './common/errors/password-error';
@@ -23,6 +23,7 @@ registerPlugin('meetings', Meetings, {
23
23
  config,
24
24
  interceptors: {
25
25
  LocusRetryStatusInterceptor: LocusRetryStatusInterceptor.create,
26
+ LocusRouteTokenInterceptor: LocusRouteTokenInterceptor.create,
26
27
  },
27
28
  });
28
29
 
@@ -1,3 +1,4 @@
1
1
  import LocusRetryStatusInterceptor from './locusRetry';
2
+ import LocusRouteTokenInterceptor from './locusRouteToken';
2
3
 
3
- export {LocusRetryStatusInterceptor};
4
+ export {LocusRetryStatusInterceptor, LocusRouteTokenInterceptor};
@@ -0,0 +1,80 @@
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import {Interceptor} from '@webex/http-core';
6
+ import {has} from 'lodash';
7
+
8
+ const LOCUS_ID_REGEX = /\/locus\/api\/v1\/loci\/([a-f0-9-]{36})/i;
9
+ const X_CISCO_PART_ROUTE_TOKEN = 'X-Cisco-Part-Route-Token';
10
+ const ROUTE_TOKEN = {};
11
+
12
+ /**
13
+ * @class LocusRouteTokenInterceptor
14
+ */
15
+ export default class LocusRouteTokenInterceptor extends Interceptor {
16
+ /**
17
+ * @returns {LocusRouteTokenInterceptor}
18
+ */
19
+ static create() {
20
+ // @ts-ignore
21
+ return new LocusRouteTokenInterceptor({webex: this});
22
+ }
23
+
24
+ getLocusIdByRequestUrl(url: string) {
25
+ return url?.match(LOCUS_ID_REGEX)?.[1];
26
+ }
27
+
28
+ /**
29
+ * @param {Object} options
30
+ * @param {HttpResponse} response
31
+ * @returns {Promise<HttpResponse>}
32
+ */
33
+ onResponse(options, response) {
34
+ const locusId = this.getLocusIdByRequestUrl(options.uri);
35
+ if (locusId) {
36
+ const hasRouteToken = has(response.headers, X_CISCO_PART_ROUTE_TOKEN);
37
+ const token = response.headers[X_CISCO_PART_ROUTE_TOKEN];
38
+ if (hasRouteToken) {
39
+ this.updateToken(locusId, token);
40
+ }
41
+ }
42
+
43
+ return Promise.resolve(response);
44
+ }
45
+
46
+ /**
47
+ * @param {Object} options
48
+ * @returns {Promise<Object>} options
49
+ */
50
+ onRequest(options) {
51
+ const locusId = this.getLocusIdByRequestUrl(options.uri);
52
+ if (locusId) {
53
+ const token = this.getToken(locusId);
54
+ if (token) {
55
+ options.headers[X_CISCO_PART_ROUTE_TOKEN] = token;
56
+ }
57
+ }
58
+
59
+ return Promise.resolve(options);
60
+ }
61
+
62
+ /**
63
+ * Update the meeting route token
64
+ * @param {string} locusId
65
+ * @param {string} token
66
+ * @returns {void}
67
+ */
68
+ updateToken(locusId, token) {
69
+ ROUTE_TOKEN[locusId] = token;
70
+ }
71
+
72
+ /**
73
+ * Get the meeting route token
74
+ * @param {string} locusId
75
+ * @returns {string|undefined}
76
+ */
77
+ getToken(locusId) {
78
+ return ROUTE_TOKEN[locusId];
79
+ }
80
+ }
@@ -130,6 +130,15 @@ ControlsUtils.parse = (controls: any) => {
130
130
  };
131
131
  }
132
132
 
133
+ if (controls?.autoEndMeetingWarning) {
134
+ parsedControls.autoEndMeetingWarning = {
135
+ enabled: controls.autoEndMeetingWarning.enabled,
136
+ extensionDurationMinutes: controls.autoEndMeetingWarning.extensionDurationMinutes,
137
+ countdownDurationMinutes: controls.autoEndMeetingWarning.countdownDurationMinutes,
138
+ countdownStartedAt: controls.autoEndMeetingWarning.countdownStartedAt,
139
+ };
140
+ }
141
+
133
142
  return parsedControls;
134
143
  };
135
144
 
@@ -244,6 +253,15 @@ ControlsUtils.getControls = (oldControls: any, newControls: any) => {
244
253
 
245
254
  hasPollingQAControlChanged:
246
255
  current?.pollingQAControl?.enabled !== previous?.pollingQAControl?.enabled,
256
+
257
+ hasAutoEndMeetingChanged:
258
+ current?.autoEndMeetingWarning?.enabled !== previous?.autoEndMeetingWarning?.enabled ||
259
+ current?.autoEndMeetingWarning?.extensionDurationMinutes !==
260
+ previous?.autoEndMeetingWarning?.extensionDurationMinutes ||
261
+ current?.autoEndMeetingWarning?.countdownDurationMinutes !==
262
+ previous?.autoEndMeetingWarning?.countdownDurationMinutes ||
263
+ current?.autoEndMeetingWarning?.countdownStartedAt !==
264
+ previous?.autoEndMeetingWarning?.countdownStartedAt,
247
265
  },
248
266
  };
249
267
  };
@@ -31,6 +31,51 @@ import LocusDeltaParser from './parser';
31
31
  import Metrics from '../metrics';
32
32
  import BEHAVIORAL_METRICS from '../metrics/constants';
33
33
 
34
+ export type LocusDTO = {
35
+ controls?: any;
36
+ fullState?: {
37
+ active: boolean;
38
+ count: number;
39
+ lastActive: string;
40
+ locked: boolean;
41
+ sessionId: string;
42
+ seessionIds: string[];
43
+ startTime: number;
44
+ state: string;
45
+ type: string;
46
+ };
47
+ host?: {
48
+ id: string;
49
+ incomingCallProtocols: any[];
50
+ isExternal: boolean;
51
+ name: string;
52
+ orgId: string;
53
+ };
54
+ info?: any;
55
+ links?: any;
56
+ mediaShares?: any[];
57
+ meetings?: any[];
58
+ participants: any[];
59
+ replaces?: any[];
60
+ self?: any;
61
+ sequence?: {
62
+ dirtyParticipants: number;
63
+ entries: number[];
64
+ rangeEnd: number;
65
+ rangeStart: number;
66
+ sequenceHash: number;
67
+ sessionToken: string;
68
+ since: string;
69
+ totalParticipants: number;
70
+ };
71
+ syncUrl?: string;
72
+ url?: string;
73
+ };
74
+
75
+ export type LocusApiResponseBody = {
76
+ locus: LocusDTO; // this LocusDTO here might not be the full one (for example it won't have all the participants, but it should have self)
77
+ };
78
+
34
79
  /**
35
80
  * @description LocusInfo extends ChildEmitter to convert locusInfo info a private emitter to parent object
36
81
  * @export
@@ -93,19 +138,26 @@ export default class LocusInfo extends EventsScope {
93
138
  * Does a Locus sync. It tries to get the latest delta DTO or if it can't, it falls back to getting the full Locus DTO.
94
139
  *
95
140
  * @param {Meeting} meeting
141
+ * @param {boolean} isLocusUrlChanged
142
+ * @param {Locus} locus
96
143
  * @returns {undefined}
97
144
  */
98
- private doLocusSync(meeting: any) {
99
- let isDelta;
145
+ private doLocusSync(meeting: any, isLocusUrlChanged: boolean, locus: any) {
100
146
  let url;
147
+ let isDelta = false;
101
148
  let meetingDestroyed = false;
102
149
 
103
- if (this.locusParser.workingCopy.syncUrl) {
150
+ if (isLocusUrlChanged) {
151
+ // for the locus url changed case from breakout to main session, we should always do a full sync, in this case, the url from locus is always on main session,
152
+ // so use the main session locus url to get the full locus(full participants list in the response).
153
+ // for the locus url changed case from main session to breakout, we don't need to care about it here,
154
+ // because it is a USE_INCOMING case, it will not be executed here.
155
+ url = locus.url;
156
+ } else if (this.locusParser.workingCopy?.syncUrl) {
104
157
  url = this.locusParser.workingCopy.syncUrl;
105
158
  isDelta = true;
106
159
  } else {
107
160
  url = meeting.locusUrl;
108
- isDelta = false;
109
161
  }
110
162
 
111
163
  LoggerProxy.logger.info(
@@ -217,6 +269,7 @@ export default class LocusInfo extends EventsScope {
217
269
  */
218
270
  applyLocusDeltaData(action: string, locus: any, meeting: any) {
219
271
  const {DESYNC, USE_CURRENT, USE_INCOMING, WAIT, LOCUS_URL_CHANGED} = LocusDeltaParser.loci;
272
+ const isLocusUrlChanged = action === LOCUS_URL_CHANGED;
220
273
 
221
274
  switch (action) {
222
275
  case USE_INCOMING:
@@ -228,7 +281,7 @@ export default class LocusInfo extends EventsScope {
228
281
  break;
229
282
  case DESYNC:
230
283
  case LOCUS_URL_CHANGED:
231
- this.doLocusSync(meeting);
284
+ this.doLocusSync(meeting, isLocusUrlChanged, locus);
232
285
  break;
233
286
  default:
234
287
  LoggerProxy.logger.info(
@@ -286,11 +339,11 @@ export default class LocusInfo extends EventsScope {
286
339
  this.updateLocusCache(locus);
287
340
  // above section only updates the locusInfo object
288
341
  // The below section makes sure it updates the locusInfo as well as updates the meeting object
289
- this.updateParticipants(locus.participants);
342
+ this.updateParticipants(locus.participants, []);
290
343
  // For 1:1 space meeting the conversation Url does not exist in locus.conversation
291
344
  this.updateConversationUrl(locus.conversationUrl, locus.info);
292
345
  this.updateControls(locus.controls, locus.self);
293
- this.updateLocusUrl(locus.url);
346
+ this.updateLocusUrl(locus.url, ControlsUtils.isMainSessionDTO(locus));
294
347
  this.updateFullState(locus.fullState);
295
348
  this.updateMeetingInfo(locus.info);
296
349
  this.updateEmbeddedApps(locus.embeddedApps);
@@ -315,6 +368,16 @@ export default class LocusInfo extends EventsScope {
315
368
  this.emitChange = true;
316
369
  }
317
370
 
371
+ /**
372
+ * Handles HTTP response from Locus API call.
373
+ * @param {Meeting} meeting meeting object
374
+ * @param {LocusApiResponseBody} responseBody body of the http response from Locus API call
375
+ * @returns {void}
376
+ */
377
+ handleLocusAPIResponse(meeting, responseBody: LocusApiResponseBody): void {
378
+ this.handleLocusDelta(responseBody.locus, meeting);
379
+ }
380
+
318
381
  /**
319
382
  * @param {Meeting} meeting
320
383
  * @param {Object} data
@@ -327,6 +390,8 @@ export default class LocusInfo extends EventsScope {
327
390
  const locus = this.getTheLocusToUpdate(data.locus);
328
391
  LoggerProxy.logger.info(`Locus-info:index#parse --> received locus data: ${eventType}`);
329
392
 
393
+ locus.jsSdkMeta = {removedParticipantIds: []};
394
+
330
395
  switch (eventType) {
331
396
  case LOCUSEVENT.PARTICIPANT_JOIN:
332
397
  case LOCUSEVENT.PARTICIPANT_LEFT:
@@ -392,7 +457,11 @@ export default class LocusInfo extends EventsScope {
392
457
  this.participants = locus.participants;
393
458
  const isReplaceMembers = ControlsUtils.isNeedReplaceMembers(this.controls, locus.controls);
394
459
  this.updateLocusInfo(locus);
395
- this.updateParticipants(locus.participants, isReplaceMembers);
460
+ this.updateParticipants(
461
+ locus.participants,
462
+ locus.jsSdkMeta?.removedParticipantIds,
463
+ isReplaceMembers
464
+ );
396
465
  this.isMeetingActive();
397
466
  this.handleOneOnOneEvent(eventType);
398
467
  this.updateEmbeddedApps(locus.embeddedApps);
@@ -454,7 +523,11 @@ export default class LocusInfo extends EventsScope {
454
523
  const isReplaceMembers = ControlsUtils.isNeedReplaceMembers(this.controls, locus.controls);
455
524
  this.mergeParticipants(this.participants, locus.participants);
456
525
  this.updateLocusInfo(locus);
457
- this.updateParticipants(locus.participants, isReplaceMembers);
526
+ this.updateParticipants(
527
+ locus.participants,
528
+ locus.jsSdkMeta?.removedParticipantIds,
529
+ isReplaceMembers
530
+ );
458
531
  this.isMeetingActive();
459
532
  }
460
533
 
@@ -476,7 +549,7 @@ export default class LocusInfo extends EventsScope {
476
549
  this.updateCreated(locus.created);
477
550
  this.updateFullState(locus.fullState);
478
551
  this.updateHostInfo(locus.host);
479
- this.updateLocusUrl(locus.url);
552
+ this.updateLocusUrl(locus.url, ControlsUtils.isMainSessionDTO(locus));
480
553
  this.updateMeetingInfo(locus.info, locus.self);
481
554
  this.updateMediaShares(locus.mediaShares);
482
555
  this.updateParticipantsUrl(locus.participantsUrl);
@@ -745,11 +818,12 @@ export default class LocusInfo extends EventsScope {
745
818
  /**
746
819
  * update meeting's members
747
820
  * @param {Object} participants new participants object
821
+ * @param {Array} removedParticipantIds list of removed participants
748
822
  * @param {Boolean} isReplace is replace the whole members
749
823
  * @returns {Array} updatedParticipants
750
824
  * @memberof LocusInfo
751
825
  */
752
- updateParticipants(participants: object, isReplace?: boolean) {
826
+ updateParticipants(participants: object, removedParticipantIds?: string[], isReplace?: boolean) {
753
827
  this.emitScoped(
754
828
  {
755
829
  file: 'locus-info',
@@ -758,6 +832,7 @@ export default class LocusInfo extends EventsScope {
758
832
  EVENTS.LOCUS_INFO_UPDATE_PARTICIPANTS,
759
833
  {
760
834
  participants,
835
+ removedParticipantIds,
761
836
  recordingId: this.parsedLocus.controls && this.parsedLocus.controls.record?.modifiedBy,
762
837
  selfIdentity: this.parsedLocus.self && this.parsedLocus.self.selfIdentity,
763
838
  selfId: this.parsedLocus.self && this.parsedLocus.self.selfId,
@@ -820,6 +895,7 @@ export default class LocusInfo extends EventsScope {
820
895
  hasAnnotationControlChanged,
821
896
  hasRemoteDesktopControlChanged,
822
897
  hasPollingQAControlChanged,
898
+ hasAutoEndMeetingChanged,
823
899
  },
824
900
  current,
825
901
  } = ControlsUtils.getControls(this.controls, controls);
@@ -1094,6 +1170,14 @@ export default class LocusInfo extends EventsScope {
1094
1170
  );
1095
1171
  }
1096
1172
 
1173
+ if (hasAutoEndMeetingChanged) {
1174
+ this.emitScoped(
1175
+ {file: 'locus-info', function: 'updateControls'},
1176
+ LOCUSINFO.EVENTS.CONTROLS_AUTO_END_MEETING_WARNING_CHANGED,
1177
+ {state: current.autoEndMeetingWarning}
1178
+ );
1179
+ }
1180
+
1097
1181
  this.controls = controls;
1098
1182
  }
1099
1183
  }
@@ -1254,10 +1338,7 @@ export default class LocusInfo extends EventsScope {
1254
1338
  */
1255
1339
  updateMeetingInfo(info: object, self?: object) {
1256
1340
  const roles = self ? SelfUtils.getRoles(self) : this.parsedLocus.self?.roles || [];
1257
- if (
1258
- (info && !isEqual(this.info, info)) ||
1259
- (roles.length && !isEqual(this.roles, roles) && info)
1260
- ) {
1341
+ if ((info && !isEqual(this.info, info)) || (!isEqual(this.roles, roles) && info)) {
1261
1342
  const isJoined = SelfUtils.isJoined(self || this.parsedLocus.self);
1262
1343
  const parsedInfo = InfoUtils.getInfos(this.parsedLocus.info, info, roles, isJoined);
1263
1344
 
@@ -1660,10 +1741,11 @@ export default class LocusInfo extends EventsScope {
1660
1741
  /**
1661
1742
  * handles when the locus.url is updated
1662
1743
  * @param {String} url
1744
+ * @param {Boolean} isMainLocus
1663
1745
  * @returns {undefined}
1664
1746
  * emits internal event locus_info_update_url
1665
1747
  */
1666
- updateLocusUrl(url: string) {
1748
+ updateLocusUrl(url: string, isMainLocus = true) {
1667
1749
  if (url && this.url !== url) {
1668
1750
  this.url = url;
1669
1751
  this.updateMeeting({locusUrl: url});
@@ -1673,7 +1755,7 @@ export default class LocusInfo extends EventsScope {
1673
1755
  function: 'updateLocusUrl',
1674
1756
  },
1675
1757
  EVENTS.LOCUS_INFO_UPDATE_URL,
1676
- url
1758
+ {url, isMainLocus}
1677
1759
  );
1678
1760
  }
1679
1761
  }
@@ -728,13 +728,17 @@ export default class Parser {
728
728
  break;
729
729
 
730
730
  case USE_INCOMING:
731
- case LOCUS_URL_CHANGED:
732
731
  // update working copy for future comparisons.
733
732
  // Note: The working copy of parser gets updated in .onFullLocus()
734
733
  // and here when USE_INCOMING or LOCUS_URL_CHANGED locus.
735
734
  this.workingCopy = newLoci;
736
735
  break;
737
736
 
737
+ case LOCUS_URL_CHANGED:
738
+ // clear the working copy completely, do a full locus sync
739
+ this.workingCopy = null;
740
+ break;
741
+
738
742
  case WAIT:
739
743
  // we've taken newLoci from the front of the queue, so put it back there as we have to wait
740
744
  // for the one that should be in front of it, before we can process it
@@ -9,9 +9,12 @@ import {
9
9
 
10
10
  import {parse} from '@webex/ts-sdp';
11
11
  import {ClientEvent} from '@webex/internal-plugin-metrics';
12
+ import {throttle} from 'lodash';
13
+ import Metrics from '../metrics';
12
14
  import {MEETINGS, QUALITY_LEVELS} from '../constants';
13
15
  import LoggerProxy from '../common/logs/logger-proxy';
14
16
  import MediaConnectionAwaiter from './MediaConnectionAwaiter';
17
+ import BEHAVIORAL_METRICS from '../metrics/constants';
15
18
 
16
19
  export type MediaDirection = {
17
20
  sendAudio: boolean;
@@ -41,6 +44,8 @@ export default class MediaProperties {
41
44
  videoDeviceId: any;
42
45
  videoStream?: LocalCameraStream;
43
46
  namespace = MEETINGS;
47
+ mediaIssueCounters: {[key: string]: number} = {};
48
+ throttledSendMediaIssueMetric: ReturnType<typeof throttle>;
44
49
 
45
50
  /**
46
51
  * @param {Object} [options] -- to auto construct
@@ -66,6 +71,15 @@ export default class MediaProperties {
66
71
  this.remoteQualityLevel = QUALITY_LEVELS.HIGH;
67
72
  this.mediaSettings = {};
68
73
  this.videoDeviceId = null;
74
+
75
+ this.throttledSendMediaIssueMetric = throttle((eventPayload) => {
76
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED, {
77
+ ...eventPayload,
78
+ });
79
+ Object.keys(this.mediaIssueCounters).forEach((key) => {
80
+ this.mediaIssueCounters[key] = 0;
81
+ });
82
+ }, 1000 * 60 * 5); // at most once every 5 minutes
69
83
  }
70
84
 
71
85
  /**
@@ -139,8 +153,14 @@ export default class MediaProperties {
139
153
  this.videoDeviceId = deviceId;
140
154
  }
141
155
 
156
+ /**
157
+ * Clears the webrtcMediaConnection. This method should be called after
158
+ * peer connection is closed and no longer needed.
159
+ * @returns {void}
160
+ */
142
161
  unsetPeerConnection() {
143
162
  this.webrtcMediaConnection = null;
163
+ this.throttledSendMediaIssueMetric.flush();
144
164
  }
145
165
 
146
166
  /**
@@ -424,4 +444,27 @@ export default class MediaProperties {
424
444
  };
425
445
  }
426
446
  }
447
+
448
+ /**
449
+ * Sends a metric about a media issue. Metrics are throttled so that we don't
450
+ * send too many of them, but include a count so that we know how many issues
451
+ * were detected.
452
+ *
453
+ * @param {string} issueType
454
+ * @param {string} issueSubType
455
+ * @param {string} correlationId
456
+ * @returns {void}
457
+ */
458
+ public sendMediaIssueMetric(issueType: string, issueSubType: string, correlationId) {
459
+ const key = `${issueType}_${issueSubType}`;
460
+
461
+ const count = (this.mediaIssueCounters[key] || 0) + 1;
462
+
463
+ this.mediaIssueCounters[key] = count;
464
+
465
+ this.throttledSendMediaIssueMetric({
466
+ correlationId,
467
+ ...this.mediaIssueCounters,
468
+ });
469
+ }
427
470
  }
@@ -41,9 +41,12 @@ interface IInMeetingActions {
41
41
  isLocalRecordingStarted?: boolean;
42
42
  isLocalRecordingStopped?: boolean;
43
43
  isLocalRecordingPaused?: boolean;
44
+ isLocalStreamingStarted?: boolean;
45
+ isLocalStreamingStopped?: boolean;
44
46
 
45
47
  isManualCaptionActive?: boolean;
46
48
  isSaveTranscriptsEnabled?: boolean;
49
+ isSpokenLanguageAutoDetectionEnabled?: boolean;
47
50
  isWebexAssistantActive?: boolean;
48
51
  canViewCaptionPanel?: boolean;
49
52
  isRealTimeTranslationEnabled?: boolean;
@@ -91,6 +94,7 @@ interface IInMeetingActions {
91
94
  canDoVideo?: boolean;
92
95
  canAnnotate?: boolean;
93
96
  canUseVoip?: boolean;
97
+ showAutoEndMeetingWarning?: boolean;
94
98
  supportHQV?: boolean;
95
99
  supportHDV?: boolean;
96
100
  canShareWhiteBoard?: boolean;
@@ -185,8 +189,14 @@ export default class InMeetingActions implements IInMeetingActions {
185
189
 
186
190
  isManualCaptionActive = null;
187
191
 
192
+ isLocalStreamingStarted = null;
193
+
194
+ isLocalStreamingStopped = null;
195
+
188
196
  isSaveTranscriptsEnabled = null;
189
197
 
198
+ isSpokenLanguageAutoDetectionEnabled = null;
199
+
190
200
  isWebexAssistantActive = null;
191
201
 
192
202
  canViewCaptionPanel = null;
@@ -281,6 +291,8 @@ export default class InMeetingActions implements IInMeetingActions {
281
291
 
282
292
  canUseVoip = null;
283
293
 
294
+ showAutoEndMeetingWarning = null;
295
+
284
296
  supportHQV = null;
285
297
 
286
298
  enforceVirtualBackground = null;
@@ -360,9 +372,12 @@ export default class InMeetingActions implements IInMeetingActions {
360
372
  isLocalRecordingStarted: this.isLocalRecordingStarted,
361
373
  isLocalRecordingStopped: this.isLocalRecordingStopped,
362
374
  isLocalRecordingPaused: this.isLocalRecordingPaused,
375
+ isLocalStreamingStarted: this.isLocalStreamingStarted,
376
+ isLocalStreamingStopped: this.isLocalStreamingStopped,
363
377
  canStopManualCaption: this.canStopManualCaption,
364
378
  isManualCaptionActive: this.isManualCaptionActive,
365
379
  isSaveTranscriptsEnabled: this.isSaveTranscriptsEnabled,
380
+ isSpokenLanguageAutoDetectionEnabled: this.isSpokenLanguageAutoDetectionEnabled,
366
381
  isWebexAssistantActive: this.isWebexAssistantActive,
367
382
  canViewCaptionPanel: this.canViewCaptionPanel,
368
383
  isRealTimeTranslationEnabled: this.isRealTimeTranslationEnabled,
@@ -401,6 +416,7 @@ export default class InMeetingActions implements IInMeetingActions {
401
416
  canShareFile: this.canShareFile,
402
417
  canShareApplication: this.canShareApplication,
403
418
  canShareCamera: this.canShareCamera,
419
+ showAutoEndMeetingWarning: this.showAutoEndMeetingWarning,
404
420
  canShareDesktop: this.canShareDesktop,
405
421
  canShareContent: this.canShareContent,
406
422
  canTransferFile: this.canTransferFile,