@webex/plugin-meetings 3.8.1-web-workers-keepalive.1 → 3.9.0-multipleLLM.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +26 -2
  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/locus-info/index.js +77 -95
  8. package/dist/locus-info/index.js.map +1 -1
  9. package/dist/locus-info/parser.js +4 -1
  10. package/dist/locus-info/parser.js.map +1 -1
  11. package/dist/media/properties.js +53 -5
  12. package/dist/media/properties.js.map +1 -1
  13. package/dist/meeting/brbState.js +14 -12
  14. package/dist/meeting/brbState.js.map +1 -1
  15. package/dist/meeting/in-meeting-actions.js +8 -0
  16. package/dist/meeting/in-meeting-actions.js.map +1 -1
  17. package/dist/meeting/index.js +443 -225
  18. package/dist/meeting/index.js.map +1 -1
  19. package/dist/meeting/muteState.js +2 -5
  20. package/dist/meeting/muteState.js.map +1 -1
  21. package/dist/meeting/request.js +44 -0
  22. package/dist/meeting/request.js.map +1 -1
  23. package/dist/meeting/request.type.js.map +1 -1
  24. package/dist/meeting/type.js +7 -0
  25. package/dist/meeting/type.js.map +1 -0
  26. package/dist/meeting/util.js +98 -13
  27. package/dist/meeting/util.js.map +1 -1
  28. package/dist/meeting-info/meeting-info-v2.js +29 -21
  29. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  30. package/dist/meetings/index.js +18 -10
  31. package/dist/meetings/index.js.map +1 -1
  32. package/dist/member/index.js.map +1 -1
  33. package/dist/member/types.js.map +1 -1
  34. package/dist/members/collection.js +13 -0
  35. package/dist/members/collection.js.map +1 -1
  36. package/dist/members/index.js +53 -29
  37. package/dist/members/index.js.map +1 -1
  38. package/dist/members/request.js +3 -3
  39. package/dist/members/request.js.map +1 -1
  40. package/dist/members/util.js +25 -8
  41. package/dist/members/util.js.map +1 -1
  42. package/dist/metrics/constants.js +2 -1
  43. package/dist/metrics/constants.js.map +1 -1
  44. package/dist/multistream/mediaRequestManager.js +1 -1
  45. package/dist/multistream/mediaRequestManager.js.map +1 -1
  46. package/dist/multistream/remoteMedia.js +34 -5
  47. package/dist/multistream/remoteMedia.js.map +1 -1
  48. package/dist/multistream/remoteMediaGroup.js +42 -2
  49. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  50. package/dist/multistream/sendSlotManager.js +32 -2
  51. package/dist/multistream/sendSlotManager.js.map +1 -1
  52. package/dist/reachability/index.js +3 -3
  53. package/dist/reachability/index.js.map +1 -1
  54. package/dist/types/constants.d.ts +24 -0
  55. package/dist/types/locus-info/index.d.ts +54 -10
  56. package/dist/types/media/properties.d.ts +21 -0
  57. package/dist/types/meeting/brbState.d.ts +0 -1
  58. package/dist/types/meeting/in-meeting-actions.d.ts +8 -0
  59. package/dist/types/meeting/index.d.ts +51 -20
  60. package/dist/types/meeting/request.d.ts +18 -1
  61. package/dist/types/meeting/request.type.d.ts +74 -0
  62. package/dist/types/meeting/type.d.ts +9 -0
  63. package/dist/types/meeting/util.d.ts +13 -3
  64. package/dist/types/meeting-info/meeting-info-v2.d.ts +6 -3
  65. package/dist/types/meetings/index.d.ts +3 -1
  66. package/dist/types/member/types.d.ts +1 -0
  67. package/dist/types/members/collection.d.ts +6 -0
  68. package/dist/types/members/index.d.ts +22 -9
  69. package/dist/types/members/request.d.ts +1 -1
  70. package/dist/types/members/util.d.ts +13 -6
  71. package/dist/types/metrics/constants.d.ts +1 -0
  72. package/dist/types/multistream/remoteMedia.d.ts +20 -1
  73. package/dist/types/multistream/remoteMediaGroup.d.ts +11 -0
  74. package/dist/types/multistream/sendSlotManager.d.ts +16 -0
  75. package/dist/webinar/index.js +1 -1
  76. package/package.json +23 -24
  77. package/src/constants.ts +25 -2
  78. package/src/locus-info/index.ts +133 -96
  79. package/src/locus-info/parser.ts +5 -1
  80. package/src/media/properties.ts +43 -0
  81. package/src/meeting/brbState.ts +9 -7
  82. package/src/meeting/in-meeting-actions.ts +17 -0
  83. package/src/meeting/index.ts +273 -42
  84. package/src/meeting/muteState.ts +2 -6
  85. package/src/meeting/request.ts +39 -0
  86. package/src/meeting/request.type.ts +64 -0
  87. package/src/meeting/type.ts +9 -0
  88. package/src/meeting/util.ts +114 -22
  89. package/src/meeting-info/meeting-info-v2.ts +24 -5
  90. package/src/meetings/index.ts +12 -5
  91. package/src/member/index.ts +1 -0
  92. package/src/member/types.ts +1 -0
  93. package/src/members/collection.ts +11 -0
  94. package/src/members/index.ts +51 -15
  95. package/src/members/request.ts +2 -2
  96. package/src/members/util.ts +34 -6
  97. package/src/metrics/constants.ts +1 -0
  98. package/src/multistream/mediaRequestManager.ts +7 -7
  99. package/src/multistream/remoteMedia.ts +34 -4
  100. package/src/multistream/remoteMediaGroup.ts +37 -2
  101. package/src/multistream/sendSlotManager.ts +34 -2
  102. package/src/reachability/index.ts +3 -3
  103. package/test/unit/spec/locus-info/index.js +229 -98
  104. package/test/unit/spec/locus-info/parser.js +3 -2
  105. package/test/unit/spec/media/properties.ts +137 -0
  106. package/test/unit/spec/meeting/brbState.ts +9 -9
  107. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -0
  108. package/test/unit/spec/meeting/index.js +1022 -93
  109. package/test/unit/spec/meeting/muteState.js +32 -6
  110. package/test/unit/spec/meeting/request.js +92 -0
  111. package/test/unit/spec/meeting/utils.js +167 -17
  112. package/test/unit/spec/meeting-info/meetinginfov2.js +8 -3
  113. package/test/unit/spec/meetings/index.js +12 -1
  114. package/test/unit/spec/members/collection.js +120 -0
  115. package/test/unit/spec/members/index.js +140 -12
  116. package/test/unit/spec/members/request.js +57 -2
  117. package/test/unit/spec/members/utils.js +139 -17
  118. package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
  119. package/test/unit/spec/multistream/remoteMedia.ts +66 -2
  120. package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
  121. package/test/unit/spec/reachability/index.ts +158 -1
@@ -1,7 +1,17 @@
1
1
  import { SendSlot, MediaType, LocalStream, MultistreamRoapMediaConnection, NamedMediaGroup, StreamState } from '@webex/internal-media-core';
2
+ /**
3
+ * This class is used to manage the sendSlots for the given media types.
4
+ */
2
5
  export default class SendSlotManager {
3
6
  private readonly slots;
4
7
  private readonly LoggerProxy;
8
+ private readonly sourceStateOverrides;
9
+ /**
10
+ * Constructor for SendSlotManager
11
+ *
12
+ * @param {any} LoggerProxy is used to log the messages
13
+ * @constructor
14
+ */
5
15
  constructor(LoggerProxy: any);
6
16
  /**
7
17
  * This method is used to create a sendSlot for the given mediaType and returns the created sendSlot
@@ -32,6 +42,12 @@ export default class SendSlotManager {
32
42
  * @returns {void}
33
43
  */
34
44
  setSourceStateOverride(mediaType: MediaType, state: StreamState | null): void;
45
+ /**
46
+ * Gets the source state override for the given media type.
47
+ * @param {MediaType} mediaType - The type of media to get the source state override for.
48
+ * @returns {StreamState | null} - The current source state override or null if not set.
49
+ */
50
+ private getSourceStateOverride;
35
51
  /**
36
52
  * This method publishes the given stream to the sendSlot for the given mediaType
37
53
  * @param {MediaType} mediaType MediaType of the sendSlot to which a stream needs to be published (AUDIO_MAIN/VIDEO_MAIN/AUDIO_SLIDES/VIDEO_SLIDES)
@@ -458,7 +458,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
458
458
  }, _callee7);
459
459
  }))();
460
460
  },
461
- version: "3.8.1-web-workers-keepalive.1"
461
+ version: "3.9.0-multipleLLM.1"
462
462
  });
463
463
  var _default = exports.default = Webinar;
464
464
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@webex/plugin-meetings",
3
3
  "description": "",
4
- "license": "Cisco EULA (https://www.cisco.com/c/en/us/products/end-user-license-agreement.html)",
4
+ "license": "Cisco's General Terms (https://www.cisco.com/site/us/en/about/legal/contract-experience/index.html)",
5
5
  "contributors": [
6
6
  "Adam Weeks <adweeks@cisco.com> (https://adamweeks.com/)",
7
7
  "Arun Ganeshan <arungane@cisco.com>",
@@ -43,13 +43,12 @@
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.8.1-web-workers-keepalive.1",
47
- "@webex/plugin-rooms": "3.8.1-web-workers-keepalive.1",
48
- "@webex/test-helper-chai": "3.8.1-web-workers-keepalive.1",
49
- "@webex/test-helper-mocha": "3.8.1-web-workers-keepalive.1",
50
- "@webex/test-helper-mock-webex": "3.8.1-web-workers-keepalive.1",
51
- "@webex/test-helper-retry": "3.8.1-web-workers-keepalive.1",
52
- "@webex/test-helper-test-users": "3.8.1-web-workers-keepalive.1",
46
+ "@webex/plugin-rooms": "3.9.0-multipleLLM.0",
47
+ "@webex/test-helper-chai": "3.9.0-multipleLLM.0",
48
+ "@webex/test-helper-mocha": "3.9.0-multipleLLM.0",
49
+ "@webex/test-helper-mock-webex": "3.9.0-multipleLLM.0",
50
+ "@webex/test-helper-retry": "3.9.0-multipleLLM.0",
51
+ "@webex/test-helper-test-users": "3.9.0-multipleLLM.0",
53
52
  "chai": "^4.3.4",
54
53
  "chai-as-promised": "^7.1.1",
55
54
  "eslint": "^8.24.0",
@@ -61,23 +60,23 @@
61
60
  "typescript": "^4.7.4"
62
61
  },
63
62
  "dependencies": {
64
- "@webex/common": "3.8.1-web-workers-keepalive.1",
65
- "@webex/event-dictionary-ts": "^1.0.1819",
66
- "@webex/internal-media-core": "2.18.3",
67
- "@webex/internal-plugin-conversation": "3.8.1-web-workers-keepalive.1",
68
- "@webex/internal-plugin-device": "3.8.1-web-workers-keepalive.1",
69
- "@webex/internal-plugin-llm": "3.8.1-web-workers-keepalive.1",
70
- "@webex/internal-plugin-mercury": "3.8.1-web-workers-keepalive.1",
71
- "@webex/internal-plugin-metrics": "3.8.1-web-workers-keepalive.1",
72
- "@webex/internal-plugin-support": "3.8.1-web-workers-keepalive.1",
73
- "@webex/internal-plugin-user": "3.8.1-web-workers-keepalive.1",
74
- "@webex/internal-plugin-voicea": "3.8.1-web-workers-keepalive.1",
75
- "@webex/media-helpers": "3.8.1-web-workers-keepalive.1",
76
- "@webex/plugin-people": "3.8.1-web-workers-keepalive.1",
77
- "@webex/plugin-rooms": "3.8.1-web-workers-keepalive.1",
63
+ "@webex/common": "3.9.0-multipleLLM.0",
64
+ "@webex/event-dictionary-ts": "^1.0.1930",
65
+ "@webex/internal-media-core": "2.19.0",
66
+ "@webex/internal-plugin-conversation": "3.9.0-multipleLLM.0",
67
+ "@webex/internal-plugin-device": "3.9.0-multipleLLM.0",
68
+ "@webex/internal-plugin-llm": "3.9.0-multipleLLM.0",
69
+ "@webex/internal-plugin-mercury": "3.9.0-multipleLLM.0",
70
+ "@webex/internal-plugin-metrics": "3.9.0-multipleLLM.0",
71
+ "@webex/internal-plugin-support": "3.9.0-multipleLLM.0",
72
+ "@webex/internal-plugin-user": "3.9.0-multipleLLM.0",
73
+ "@webex/internal-plugin-voicea": "3.9.0-multipleLLM.1",
74
+ "@webex/media-helpers": "3.9.0-multipleLLM.1",
75
+ "@webex/plugin-people": "3.9.0-multipleLLM.0",
76
+ "@webex/plugin-rooms": "3.9.0-multipleLLM.0",
78
77
  "@webex/ts-sdp": "^1.8.1",
79
78
  "@webex/web-capabilities": "^1.6.0",
80
- "@webex/webex-core": "3.8.1-web-workers-keepalive.1",
79
+ "@webex/webex-core": "3.9.0-multipleLLM.0",
81
80
  "ampersand-collection": "^2.0.2",
82
81
  "bowser": "^2.11.0",
83
82
  "btoa": "^1.2.1",
@@ -93,5 +92,5 @@
93
92
  "//": [
94
93
  "TODO: upgrade jwt-decode when moving to node 18"
95
94
  ],
96
- "version": "3.8.1-web-workers-keepalive.1"
95
+ "version": "3.9.0-multipleLLM.1"
97
96
  }
package/src/constants.ts CHANGED
@@ -217,6 +217,7 @@ export const DIALER_REGEX = {
217
217
  PHONE_NUMBER:
218
218
  /^(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?$/,
219
219
  E164_FORMAT: /^\+[1-9]\d{1,14}$/,
220
+ INTERNAL_NUMBER: /^\d{1,14}$/,
220
221
  };
221
222
 
222
223
  // eslint-disable-next-line max-len
@@ -262,7 +263,6 @@ export const RECORDING_STATE = {
262
263
  PAUSED: 'paused',
263
264
  RESUMED: 'resumed',
264
265
  };
265
-
266
266
  export const SHARE_STATUS = {
267
267
  NO_SHARE: 'no_share',
268
268
  REMOTE_SHARE_ACTIVE: 'remote_share_active',
@@ -347,6 +347,7 @@ export const EVENT_TRIGGERS = {
347
347
  MEETING_SELF_LEFT: 'meeting:self:left',
348
348
  NETWORK_QUALITY: 'network:quality',
349
349
  MEDIA_NEGOTIATED: 'media:negotiated',
350
+ MEDIA_INBOUND_AUDIO_ISSUE_DETECTED: 'media:inboundAudio:issueDetected',
350
351
  // the following events apply only to multistream media connections
351
352
  ACTIVE_SPEAKER_CHANGED: 'media:activeSpeakerChanged',
352
353
  REMOTE_VIDEO_SOURCE_COUNT_CHANGED: 'media:remoteVideoSourceCountChanged',
@@ -948,6 +949,9 @@ export const DISPLAY_HINTS = {
948
949
  PREMISE_RECORDING_CONTROL_PAUSE: 'PREMISE_RECORDING_CONTROL_PAUSE',
949
950
  PREMISE_RECORDING_CONTROL_STOP: 'PREMISE_RECORDING_CONTROL_STOP',
950
951
  PREMISE_RECORDING_CONTROL_RESUME: 'PREMISE_RECORDING_CONTROL_RESUME',
952
+ LOCAL_RECORDING_STATUS_STARTED: 'LOCAL_RECORDING_STATUS_STARTED',
953
+ LOCAL_RECORDING_STATUS_STOPPED: 'LOCAL_RECORDING_STATUS_STOPPED',
954
+ LOCAL_RECORDING_STATUS_PAUSED: 'LOCAL_RECORDING_STATUS_PAUSED',
951
955
  LOCK_CONTROL_UNLOCK: 'LOCK_CONTROL_UNLOCK',
952
956
  LOCK_CONTROL_LOCK: 'LOCK_CONTROL_LOCK',
953
957
  LOCK_STATUS_LOCKED: 'LOCK_STATUS_LOCKED',
@@ -963,6 +967,7 @@ export const DISPLAY_HINTS = {
963
967
  MANUAL_CAPTION_STOP: 'MANUAL_CAPTION_STOP',
964
968
  MANUAL_CAPTION_STATUS_ACTIVE: 'MANUAL_CAPTION_STATUS_ACTIVE',
965
969
  DISPLAY_REAL_TIME_TRANSLATION: 'DISPLAY_REAL_TIME_TRANSLATION',
970
+ SPOKEN_LANGUAGE_AUTO_DETECTION_ENABLED: 'SPOKEN_LANGUAGE_AUTO_DETECTION_ENABLED',
966
971
  ENABLE_CAPTION_PANEL: 'ENABLE_CAPTION_PANEL',
967
972
  DISPLAY_NON_ENGLISH_ASR: 'DISPLAY_NON_ENGLISH_ASR',
968
973
  TRANSCRIPTION_CONTROL_START: 'TRANSCRIPTION_CONTROL_START',
@@ -987,7 +992,6 @@ export const DISPLAY_HINTS = {
987
992
  CAN_RENAME_SELF_AND_OBSERVED: 'CAN_RENAME_SELF_AND_OBSERVED',
988
993
  CAN_RENAME_OTHERS: 'CAN_RENAME_OTHERS',
989
994
  MOVE_TO_LOBBY: 'MOVE_TO_LOBBY',
990
-
991
995
  // breakout session
992
996
  BREAKOUT_MANAGEMENT: 'BREAKOUT_MANAGEMENT',
993
997
  BROADCAST_MESSAGE_TO_BREAKOUT: 'BROADCAST_MESSAGE_TO_BREAKOUT',
@@ -1188,6 +1192,7 @@ export const QUALITY_LEVELS = {
1188
1192
  HIGH: 'HIGH',
1189
1193
  '360p': '360p',
1190
1194
  '480p': '480p',
1195
+ '540p': '540p',
1191
1196
  '720p': '720p',
1192
1197
  '1080p': '1080p',
1193
1198
  };
@@ -1217,6 +1222,18 @@ export const AVAILABLE_RESOLUTIONS = {
1217
1222
  },
1218
1223
  },
1219
1224
  },
1225
+ '540p': {
1226
+ video: {
1227
+ width: {
1228
+ max: 960,
1229
+ ideal: 960,
1230
+ },
1231
+ height: {
1232
+ max: 540,
1233
+ ideal: 540,
1234
+ },
1235
+ },
1236
+ },
1220
1237
  '720p': {
1221
1238
  video: {
1222
1239
  width: {
@@ -1357,3 +1374,9 @@ export const INITIAL_REGISTRATION_STATUS = {
1357
1374
  mercuryConnect: false,
1358
1375
  checkH264Support: false,
1359
1376
  };
1377
+
1378
+ export const STAGE_MANAGER_TYPE = {
1379
+ LOGO: 0b001,
1380
+ BACKGROUND: 0b010,
1381
+ NAME_LABEL: 0b100,
1382
+ };
@@ -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
@@ -48,7 +93,6 @@ export default class LocusInfo extends EventsScope {
48
93
  aclUrl: any;
49
94
  baseSequence: any;
50
95
  created: any;
51
- deltaParticipants: any;
52
96
  identities: any;
53
97
  membership: any;
54
98
  participants: any;
@@ -94,18 +138,26 @@ export default class LocusInfo extends EventsScope {
94
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.
95
139
  *
96
140
  * @param {Meeting} meeting
141
+ * @param {boolean} isLocusUrlChanged
142
+ * @param {Locus} locus
97
143
  * @returns {undefined}
98
144
  */
99
- private doLocusSync(meeting: any) {
100
- let isDelta;
145
+ private doLocusSync(meeting: any, isLocusUrlChanged: boolean, locus: any) {
101
146
  let url;
102
-
103
- if (this.locusParser.workingCopy.syncUrl) {
147
+ let isDelta = false;
148
+ let meetingDestroyed = false;
149
+
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(
@@ -134,32 +186,56 @@ export default class LocusInfo extends EventsScope {
134
186
 
135
187
  isDelta = false;
136
188
 
137
- return meeting.meetingRequest.getLocusDTO({url: meeting.locusUrl}).catch((err) => {
138
- LoggerProxy.logger.info(
139
- 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
140
- );
141
- this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
142
- throw err;
143
- });
189
+ // Locus sometimes returns 403, for example if meeting has ended, no point trying the fallback to full sync in that case
190
+ if (e.statusCode !== 403) {
191
+ return meeting.meetingRequest.getLocusDTO({url: meeting.locusUrl}).catch((err) => {
192
+ LoggerProxy.logger.info(
193
+ 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
194
+ );
195
+ this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
196
+ meetingDestroyed = true;
197
+ throw err;
198
+ });
199
+ }
200
+ LoggerProxy.logger.info(
201
+ 'Locus-info:index#doLocusSync --> got 403 from Locus, skipping fallback to full sync, destroying the meeting'
202
+ );
203
+ } else {
204
+ LoggerProxy.logger.info(
205
+ 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
206
+ );
144
207
  }
145
- LoggerProxy.logger.info(
146
- 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
147
- );
148
208
  this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
209
+ meetingDestroyed = true;
149
210
  throw e;
150
211
  })
151
212
  .then((res) => {
152
- if (isDelta) {
153
- if (!isEmpty(res.body)) {
154
- meeting.locusInfo.handleLocusDelta(res.body, meeting);
155
- } else {
213
+ if (isEmpty(res.body)) {
214
+ if (isDelta) {
156
215
  LoggerProxy.logger.info(
157
216
  'Locus-info:index#doLocusSync --> received empty body from syncUrl, so we already have latest Locus DTO'
158
217
  );
218
+ } else {
219
+ LoggerProxy.logger.info(
220
+ 'Locus-info:index#doLocusSync --> received empty body from full DTO sync request'
221
+ );
159
222
  }
160
- } else {
161
- meeting.locusInfo.onFullLocus(res.body);
223
+
224
+ return;
225
+ }
226
+
227
+ if (isDelta) {
228
+ if (res.body.baseSequence) {
229
+ meeting.locusInfo.handleLocusDelta(res.body, meeting);
230
+
231
+ return;
232
+ }
233
+ // in some cases Locus might return us full DTO even when we asked for a delta
234
+ LoggerProxy.logger.info(
235
+ 'Locus-info:index#doLocusSync --> got full DTO when we asked for delta'
236
+ );
162
237
  }
238
+ meeting.locusInfo.onFullLocus(res.body);
163
239
  })
164
240
  .catch((e) => {
165
241
  LoggerProxy.logger.info(
@@ -176,9 +252,11 @@ export default class LocusInfo extends EventsScope {
176
252
  });
177
253
  })
178
254
  .finally(() => {
179
- // Notify parser to resume processing delta events.
180
- // Any deltas in the queue that have now been superseded by this sync will simply be ignored
181
- this.locusParser.resume();
255
+ if (!meetingDestroyed) {
256
+ // Notify parser to resume processing delta events.
257
+ // Any deltas in the queue that have now been superseded by this sync will simply be ignored
258
+ this.locusParser.resume();
259
+ }
182
260
  });
183
261
  }
184
262
 
@@ -191,6 +269,7 @@ export default class LocusInfo extends EventsScope {
191
269
  */
192
270
  applyLocusDeltaData(action: string, locus: any, meeting: any) {
193
271
  const {DESYNC, USE_CURRENT, USE_INCOMING, WAIT, LOCUS_URL_CHANGED} = LocusDeltaParser.loci;
272
+ const isLocusUrlChanged = action === LOCUS_URL_CHANGED;
194
273
 
195
274
  switch (action) {
196
275
  case USE_INCOMING:
@@ -202,7 +281,7 @@ export default class LocusInfo extends EventsScope {
202
281
  break;
203
282
  case DESYNC:
204
283
  case LOCUS_URL_CHANGED:
205
- this.doLocusSync(meeting);
284
+ this.doLocusSync(meeting, isLocusUrlChanged, locus);
206
285
  break;
207
286
  default:
208
287
  LoggerProxy.logger.info(
@@ -257,21 +336,10 @@ export default class LocusInfo extends EventsScope {
257
336
  * @property {Object} person - Contains person data.
258
337
  */
259
338
 
260
- /**
261
- * Stored participant changes between the last event and the current event.
262
- * All previously stored events are overwritten between events.
263
- *
264
- * @instance
265
- * @type {Array<DeltaParticipant>}
266
- * @private
267
- * @member LocusInfo
268
- */
269
- this.deltaParticipants = [];
270
-
271
339
  this.updateLocusCache(locus);
272
340
  // above section only updates the locusInfo object
273
341
  // The below section makes sure it updates the locusInfo as well as updates the meeting object
274
- this.updateParticipants(locus.participants);
342
+ this.updateParticipants(locus.participants, []);
275
343
  // For 1:1 space meeting the conversation Url does not exist in locus.conversation
276
344
  this.updateConversationUrl(locus.conversationUrl, locus.info);
277
345
  this.updateControls(locus.controls, locus.self);
@@ -300,6 +368,16 @@ export default class LocusInfo extends EventsScope {
300
368
  this.emitChange = true;
301
369
  }
302
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
+
303
381
  /**
304
382
  * @param {Meeting} meeting
305
383
  * @param {Object} data
@@ -312,6 +390,8 @@ export default class LocusInfo extends EventsScope {
312
390
  const locus = this.getTheLocusToUpdate(data.locus);
313
391
  LoggerProxy.logger.info(`Locus-info:index#parse --> received locus data: ${eventType}`);
314
392
 
393
+ locus.jsSdkMeta = {removedParticipantIds: []};
394
+
315
395
  switch (eventType) {
316
396
  case LOCUSEVENT.PARTICIPANT_JOIN:
317
397
  case LOCUSEVENT.PARTICIPANT_LEFT:
@@ -373,12 +453,15 @@ export default class LocusInfo extends EventsScope {
373
453
  return;
374
454
  }
375
455
 
376
- this.updateParticipantDeltas(locus.participants);
377
456
  this.scheduledMeeting = locus.meeting || null;
378
457
  this.participants = locus.participants;
379
458
  const isReplaceMembers = ControlsUtils.isNeedReplaceMembers(this.controls, locus.controls);
380
459
  this.updateLocusInfo(locus);
381
- this.updateParticipants(locus.participants, isReplaceMembers);
460
+ this.updateParticipants(
461
+ locus.participants,
462
+ locus.jsSdkMeta?.removedParticipantIds,
463
+ isReplaceMembers
464
+ );
382
465
  this.isMeetingActive();
383
466
  this.handleOneOnOneEvent(eventType);
384
467
  this.updateEmbeddedApps(locus.embeddedApps);
@@ -440,7 +523,11 @@ export default class LocusInfo extends EventsScope {
440
523
  const isReplaceMembers = ControlsUtils.isNeedReplaceMembers(this.controls, locus.controls);
441
524
  this.mergeParticipants(this.participants, locus.participants);
442
525
  this.updateLocusInfo(locus);
443
- this.updateParticipants(locus.participants, isReplaceMembers);
526
+ this.updateParticipants(
527
+ locus.participants,
528
+ locus.jsSdkMeta?.removedParticipantIds,
529
+ isReplaceMembers
530
+ );
444
531
  this.isMeetingActive();
445
532
  }
446
533
 
@@ -462,12 +549,12 @@ export default class LocusInfo extends EventsScope {
462
549
  this.updateCreated(locus.created);
463
550
  this.updateFullState(locus.fullState);
464
551
  this.updateHostInfo(locus.host);
552
+ this.updateLocusUrl(locus.url);
465
553
  this.updateMeetingInfo(locus.info, locus.self);
466
554
  this.updateMediaShares(locus.mediaShares);
467
555
  this.updateParticipantsUrl(locus.participantsUrl);
468
556
  this.updateReplace(locus.replace);
469
557
  this.updateSelf(locus.self);
470
- this.updateLocusUrl(locus.url);
471
558
  this.updateAclUrl(locus.aclUrl);
472
559
  this.updateBasequence(locus.baseSequence);
473
560
  this.updateSequence(locus.sequence);
@@ -728,63 +815,15 @@ export default class LocusInfo extends EventsScope {
728
815
  }
729
816
  }
730
817
 
731
- /**
732
- * Update the deltaParticipants property of this object based on a list of
733
- * provided participants.
734
- *
735
- * @param {Array} [participants] - The participants to update against.
736
- * @returns {void}
737
- */
738
- updateParticipantDeltas(participants: Array<any> = []) {
739
- // Used to find a participant within a participants collection.
740
- const findParticipant = (participant, collection) =>
741
- collection.find((item) => item.person.id === participant.person.id);
742
-
743
- // Generates an object that indicates which state properties have changed.
744
- const generateDelta = (prevState: any = {}, newState: any = {}) => {
745
- // Setup deltas.
746
- const deltas = {
747
- audioStatus: prevState.audioStatus !== newState.audioStatus,
748
- videoSlidesStatus: prevState.videoSlidesStatus !== newState.videoSlidesStatus,
749
- videoStatus: prevState.videoStatus !== newState.videoStatus,
750
- };
751
-
752
- // Clean the object
753
- Object.keys(deltas).forEach((key) => {
754
- if (deltas[key] !== true) {
755
- delete deltas[key];
756
- }
757
- });
758
-
759
- return deltas;
760
- };
761
-
762
- this.deltaParticipants = participants.reduce((collection, participant) => {
763
- const existingParticipant = findParticipant(participant, this.participants || []) || {};
764
-
765
- const delta = generateDelta(existingParticipant.status, participant.status);
766
-
767
- const changed = Object.keys(delta).length > 0;
768
-
769
- if (changed) {
770
- collection.push({
771
- person: participant.person,
772
- delta,
773
- });
774
- }
775
-
776
- return collection;
777
- }, []);
778
- }
779
-
780
818
  /**
781
819
  * update meeting's members
782
820
  * @param {Object} participants new participants object
821
+ * @param {Array} removedParticipantIds list of removed participants
783
822
  * @param {Boolean} isReplace is replace the whole members
784
823
  * @returns {Array} updatedParticipants
785
824
  * @memberof LocusInfo
786
825
  */
787
- updateParticipants(participants: object, isReplace?: boolean) {
826
+ updateParticipants(participants: object, removedParticipantIds?: string[], isReplace?: boolean) {
788
827
  this.emitScoped(
789
828
  {
790
829
  file: 'locus-info',
@@ -793,6 +832,7 @@ export default class LocusInfo extends EventsScope {
793
832
  EVENTS.LOCUS_INFO_UPDATE_PARTICIPANTS,
794
833
  {
795
834
  participants,
835
+ removedParticipantIds,
796
836
  recordingId: this.parsedLocus.controls && this.parsedLocus.controls.record?.modifiedBy,
797
837
  selfIdentity: this.parsedLocus.self && this.parsedLocus.self.selfIdentity,
798
838
  selfId: this.parsedLocus.self && this.parsedLocus.self.selfId,
@@ -1289,10 +1329,7 @@ export default class LocusInfo extends EventsScope {
1289
1329
  */
1290
1330
  updateMeetingInfo(info: object, self?: object) {
1291
1331
  const roles = self ? SelfUtils.getRoles(self) : this.parsedLocus.self?.roles || [];
1292
- if (
1293
- (info && !isEqual(this.info, info)) ||
1294
- (roles.length && !isEqual(this.roles, roles) && info)
1295
- ) {
1332
+ if ((info && !isEqual(this.info, info)) || (!isEqual(this.roles, roles) && info)) {
1296
1333
  const isJoined = SelfUtils.isJoined(self || this.parsedLocus.self);
1297
1334
  const parsedInfo = InfoUtils.getInfos(this.parsedLocus.info, info, roles, isJoined);
1298
1335
 
@@ -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
  }