@webex/plugin-meetings 3.0.0-beta.115 → 3.0.0-beta.117

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 (53) hide show
  1. package/dist/breakouts/breakout.js +23 -6
  2. package/dist/breakouts/breakout.js.map +1 -1
  3. package/dist/breakouts/index.js +178 -139
  4. package/dist/breakouts/index.js.map +1 -1
  5. package/dist/constants.js +1 -0
  6. package/dist/constants.js.map +1 -1
  7. package/dist/locus-info/mediaSharesUtils.js +15 -1
  8. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  9. package/dist/meeting/index.js +73 -103
  10. package/dist/meeting/index.js.map +1 -1
  11. package/dist/meeting/locusMediaRequest.js +3 -0
  12. package/dist/meeting/locusMediaRequest.js.map +1 -1
  13. package/dist/meeting/muteState.js +1 -1
  14. package/dist/meeting/muteState.js.map +1 -1
  15. package/dist/meeting/request.js +27 -20
  16. package/dist/meeting/request.js.map +1 -1
  17. package/dist/meeting/util.js +463 -426
  18. package/dist/meeting/util.js.map +1 -1
  19. package/dist/members/index.js +4 -1
  20. package/dist/members/index.js.map +1 -1
  21. package/dist/members/request.js +75 -45
  22. package/dist/members/request.js.map +1 -1
  23. package/dist/members/util.js +308 -317
  24. package/dist/members/util.js.map +1 -1
  25. package/dist/types/constants.d.ts +1 -0
  26. package/dist/types/meeting/index.d.ts +20 -21
  27. package/dist/types/meeting/locusMediaRequest.d.ts +2 -0
  28. package/dist/types/meeting/request.d.ts +16 -8
  29. package/dist/types/meeting/util.d.ts +75 -1
  30. package/dist/types/members/request.d.ts +56 -11
  31. package/dist/types/members/util.d.ts +209 -1
  32. package/package.json +19 -19
  33. package/src/breakouts/breakout.ts +26 -4
  34. package/src/breakouts/index.ts +32 -17
  35. package/src/constants.ts +1 -0
  36. package/src/locus-info/mediaSharesUtils.ts +16 -0
  37. package/src/meeting/index.ts +20 -42
  38. package/src/meeting/locusMediaRequest.ts +6 -0
  39. package/src/meeting/muteState.ts +1 -1
  40. package/src/meeting/request.ts +26 -17
  41. package/src/meeting/util.ts +446 -410
  42. package/src/members/index.ts +7 -1
  43. package/src/members/request.ts +61 -21
  44. package/src/members/util.ts +316 -326
  45. package/test/unit/spec/breakouts/breakout.ts +26 -7
  46. package/test/unit/spec/breakouts/index.ts +48 -3
  47. package/test/unit/spec/meeting/index.js +53 -33
  48. package/test/unit/spec/meeting/locusMediaRequest.ts +25 -3
  49. package/test/unit/spec/meeting/muteState.js +5 -2
  50. package/test/unit/spec/meeting/request.js +215 -42
  51. package/test/unit/spec/meeting/utils.js +151 -7
  52. package/test/unit/spec/members/index.js +22 -1
  53. package/test/unit/spec/members/request.js +167 -35
@@ -1,10 +1,9 @@
1
- import {isEmpty} from 'lodash';
2
1
  import {LocalCameraTrack, LocalMicrophoneTrack} from '@webex/media-helpers';
3
2
 
3
+ import {cloneDeep} from 'lodash';
4
4
  import {MeetingNotActiveError, UserNotJoinedError} from '../common/errors/webex-errors';
5
5
  import Metrics from '../metrics';
6
6
  import {eventType, trigger} from '../metrics/config';
7
- import Media from '../media';
8
7
  import LoggerProxy from '../common/logs/logger-proxy';
9
8
  import {
10
9
  INTENT_TO_JOIN,
@@ -22,499 +21,536 @@ import PermissionError from '../common/errors/permission';
22
21
  import PasswordError from '../common/errors/password-error';
23
22
  import CaptchaError from '../common/errors/captcha-error';
24
23
 
25
- const MeetingUtil: any = {};
24
+ const MeetingUtil = {
25
+ parseLocusJoin: (response) => {
26
+ const parsed: any = {};
27
+
28
+ // First todo: add check for existance
29
+ parsed.locus = response.body.locus;
30
+ parsed.mediaConnections = response.body.mediaConnections;
31
+ parsed.locusUrl = parsed.locus.url;
32
+ parsed.locusId = parsed.locus.url.split('/').pop();
33
+ parsed.selfId = parsed.locus.self.id;
34
+
35
+ // we need mediaId before making roap calls
36
+ parsed.mediaConnections.forEach((mediaConnection) => {
37
+ if (mediaConnection.mediaId) {
38
+ parsed.mediaId = mediaConnection.mediaId;
39
+ }
40
+ });
26
41
 
27
- MeetingUtil.parseLocusJoin = (response) => {
28
- const parsed: any = {};
42
+ return parsed;
43
+ },
29
44
 
30
- // First todo: add check for existance
31
- parsed.locus = response.body.locus;
32
- parsed.mediaConnections = response.body.mediaConnections;
33
- parsed.locusUrl = parsed.locus.url;
34
- parsed.locusId = parsed.locus.url.split('/').pop();
35
- parsed.selfId = parsed.locus.self.id;
45
+ remoteUpdateAudioVideo: (meeting, audioMuted?: boolean, videoMuted?: boolean) => {
46
+ if (!meeting) {
47
+ return Promise.reject(new ParameterError('You need a meeting object.'));
48
+ }
36
49
 
37
- // we need mediaId before making roap calls
38
- parsed.mediaConnections.forEach((mediaConnection) => {
39
- if (mediaConnection.mediaId) {
40
- parsed.mediaId = mediaConnection.mediaId;
50
+ if (!meeting.locusMediaRequest) {
51
+ return Promise.reject(
52
+ new ParameterError(
53
+ 'You need a meeting with a media connection, call Meeting.addMedia() first.'
54
+ )
55
+ );
41
56
  }
42
- });
43
57
 
44
- return parsed;
45
- };
58
+ Metrics.postEvent({event: eventType.MEDIA_REQUEST, meeting});
59
+
60
+ return meeting.locusMediaRequest
61
+ .send({
62
+ type: 'LocalMute',
63
+ selfUrl: meeting.selfUrl,
64
+ mediaId: meeting.mediaId,
65
+ sequence: meeting.locusInfo.sequence,
66
+ muteOptions: {
67
+ audioMuted,
68
+ videoMuted,
69
+ },
70
+ })
71
+ .then((response) => {
72
+ Metrics.postEvent({event: eventType.MEDIA_RESPONSE, meeting});
46
73
 
47
- MeetingUtil.remoteUpdateAudioVideo = (meeting, audioMuted?: boolean, videoMuted?: boolean) => {
48
- if (!meeting) {
49
- return Promise.reject(new ParameterError('You need a meeting object.'));
50
- }
51
-
52
- if (!meeting.locusMediaRequest) {
53
- return Promise.reject(
54
- new ParameterError(
55
- 'You need a meeting with a media connection, call Meeting.addMedia() first.'
56
- )
57
- );
58
- }
59
-
60
- Metrics.postEvent({event: eventType.MEDIA_REQUEST, meeting});
61
-
62
- return meeting.locusMediaRequest
63
- .send({
64
- type: 'LocalMute',
65
- selfUrl: meeting.selfUrl,
66
- mediaId: meeting.mediaId,
67
- muteOptions: {
68
- audioMuted,
69
- videoMuted,
70
- },
71
- })
72
- .then((response) => {
73
- Metrics.postEvent({event: eventType.MEDIA_RESPONSE, meeting});
74
-
75
- return response?.body?.locus;
76
- });
77
- };
74
+ return response?.body?.locus;
75
+ });
76
+ },
78
77
 
79
- MeetingUtil.hasOwner = (info) => info && info.owner;
78
+ hasOwner: (info) => info && info.owner,
80
79
 
81
- MeetingUtil.isOwnerSelf = (owner, selfId) => owner === selfId;
80
+ isOwnerSelf: (owner, selfId) => owner === selfId,
82
81
 
83
- MeetingUtil.isPinOrGuest = (err) =>
84
- err?.body?.errorCode && INTENT_TO_JOIN.includes(err.body.errorCode);
82
+ isPinOrGuest: (err) => err?.body?.errorCode && INTENT_TO_JOIN.includes(err.body.errorCode),
85
83
 
86
- MeetingUtil.joinMeeting = (meeting, options) => {
87
- if (!meeting) {
88
- return Promise.reject(new ParameterError('You need a meeting object.'));
89
- }
84
+ joinMeeting: (meeting, options) => {
85
+ if (!meeting) {
86
+ return Promise.reject(new ParameterError('You need a meeting object.'));
87
+ }
90
88
 
91
- Metrics.postEvent({event: eventType.LOCUS_JOIN_REQUEST, meeting});
89
+ Metrics.postEvent({event: eventType.LOCUS_JOIN_REQUEST, meeting});
90
+
91
+ // eslint-disable-next-line no-warning-comments
92
+ // TODO: check if the meeting is in JOINING state
93
+ // if Joining state termintate the request as user might click multiple times
94
+ return meeting.meetingRequest
95
+ .joinMeeting({
96
+ inviteeAddress: meeting.meetingJoinUrl || meeting.sipUri,
97
+ meetingNumber: meeting.meetingNumber,
98
+ deviceUrl: meeting.deviceUrl,
99
+ locusUrl: meeting.locusUrl,
100
+ correlationId: meeting.correlationId,
101
+ roapMessage: options.roapMessage,
102
+ permissionToken: meeting.permissionToken,
103
+ resourceId: options.resourceId || null,
104
+ moderator: options.moderator,
105
+ pin: options.pin,
106
+ moveToResource: options.moveToResource,
107
+ preferTranscoding: !meeting.isMultistream,
108
+ asResourceOccupant: options.asResourceOccupant,
109
+ breakoutsSupported: options.breakoutsSupported,
110
+ locale: options.locale,
111
+ deviceCapabilities: options.deviceCapabilities,
112
+ })
113
+ .then((res) => {
114
+ Metrics.postEvent({
115
+ event: eventType.LOCUS_JOIN_RESPONSE,
116
+ meeting,
117
+ data: {
118
+ trigger: trigger.LOCI_UPDATE,
119
+ locus: res.body.locus,
120
+ mediaConnections: res.body.mediaConnections,
121
+ trackingId: res.headers.trackingid,
122
+ },
123
+ });
92
124
 
93
- // eslint-disable-next-line no-warning-comments
94
- // TODO: check if the meeting is in JOINING state
95
- // if Joining state termintate the request as user might click multiple times
96
- return meeting.meetingRequest
97
- .joinMeeting({
98
- inviteeAddress: meeting.meetingJoinUrl || meeting.sipUri,
99
- meetingNumber: meeting.meetingNumber,
100
- deviceUrl: meeting.deviceUrl,
101
- locusUrl: meeting.locusUrl,
102
- correlationId: meeting.correlationId,
103
- roapMessage: options.roapMessage,
104
- permissionToken: meeting.permissionToken,
105
- resourceId: options.resourceId || null,
106
- moderator: options.moderator,
107
- pin: options.pin,
108
- moveToResource: options.moveToResource,
109
- preferTranscoding: !meeting.isMultistream,
110
- asResourceOccupant: options.asResourceOccupant,
111
- breakoutsSupported: options.breakoutsSupported,
112
- locale: options.locale,
113
- deviceCapabilities: options.deviceCapabilities,
114
- })
115
- .then((res) => {
116
- Metrics.postEvent({
117
- event: eventType.LOCUS_JOIN_RESPONSE,
118
- meeting,
119
- data: {
120
- trigger: trigger.LOCI_UPDATE,
121
- locus: res.body.locus,
122
- mediaConnections: res.body.mediaConnections,
123
- trackingId: res.headers.trackingid,
124
- },
125
+ return MeetingUtil.parseLocusJoin(res);
125
126
  });
127
+ },
128
+
129
+ cleanUp: (meeting) => {
130
+ meeting.breakouts.cleanUp();
131
+
132
+ // make sure we send last metrics before we close the peerconnection
133
+ const stopStatsAnalyzer = meeting.statsAnalyzer
134
+ ? meeting.statsAnalyzer.stopAnalyzer()
135
+ : Promise.resolve();
136
+
137
+ return stopStatsAnalyzer
138
+ .then(() => meeting.closeLocalStream())
139
+ .then(() => meeting.closeLocalShare())
140
+ .then(() => meeting.closeRemoteTracks())
141
+ .then(() => meeting.closePeerConnections())
142
+ .then(() => {
143
+ meeting.unsetLocalVideoTrack();
144
+ meeting.unsetLocalShareTrack();
145
+ meeting.unsetRemoteTracks();
146
+ meeting.unsetPeerConnections();
147
+ meeting.reconnectionManager.cleanUp();
148
+ })
149
+ .then(() => meeting.stopKeepAlive())
150
+ .then(() => meeting.updateLLMConnection());
151
+ },
152
+
153
+ disconnectPhoneAudio: (meeting, phoneUrl) => {
154
+ if (meeting.meetingState === FULL_STATE.INACTIVE) {
155
+ return Promise.reject(new MeetingNotActiveError());
156
+ }
126
157
 
127
- return MeetingUtil.parseLocusJoin(res);
128
- });
129
- };
130
-
131
- MeetingUtil.cleanUp = (meeting) => {
132
- meeting.breakouts.cleanUp();
133
-
134
- // make sure we send last metrics before we close the peerconnection
135
- const stopStatsAnalyzer = meeting.statsAnalyzer
136
- ? meeting.statsAnalyzer.stopAnalyzer()
137
- : Promise.resolve();
138
-
139
- return stopStatsAnalyzer
140
- .then(() => meeting.closeLocalStream())
141
- .then(() => meeting.closeLocalShare())
142
- .then(() => meeting.closeRemoteTracks())
143
- .then(() => meeting.closePeerConnections())
144
- .then(() => {
145
- meeting.unsetLocalVideoTrack();
146
- meeting.unsetLocalShareTrack();
147
- meeting.unsetRemoteTracks();
148
- meeting.unsetPeerConnections();
149
- meeting.reconnectionManager.cleanUp();
150
- })
151
- .then(() => meeting.stopKeepAlive())
152
- .then(() => meeting.updateLLMConnection());
153
- };
158
+ const options = {
159
+ locusUrl: meeting.locusUrl,
160
+ selfId: meeting.selfId,
161
+ correlationId: meeting.correlationId,
162
+ phoneUrl,
163
+ };
154
164
 
155
- MeetingUtil.disconnectPhoneAudio = (meeting, phoneUrl) => {
156
- if (meeting.meetingState === FULL_STATE.INACTIVE) {
157
- return Promise.reject(new MeetingNotActiveError());
158
- }
159
-
160
- const options = {
161
- locusUrl: meeting.locusUrl,
162
- selfId: meeting.selfId,
163
- correlationId: meeting.correlationId,
164
- phoneUrl,
165
- };
166
-
167
- return meeting.meetingRequest
168
- .disconnectPhoneAudio(options)
169
- .then((response) => {
170
- if (response?.body?.locus) {
171
- meeting.locusInfo.onFullLocus(response.body.locus);
172
- }
173
- })
174
- .catch((err) => {
165
+ return meeting.meetingRequest.disconnectPhoneAudio(options).catch((err) => {
175
166
  LoggerProxy.logger.error(
176
167
  `Meeting:util#disconnectPhoneAudio --> An error occured while disconnecting phone audio in meeting ${meeting.id}, error: ${err}`
177
168
  );
178
169
 
179
170
  return Promise.reject(err);
180
171
  });
181
- };
172
+ },
173
+
174
+ // by default will leave on meeting's resourceId
175
+ // if you explicity want it not to leave on resource id, pass
176
+ // {resourceId: null}
177
+ // TODO: chris, you can modify this however you want
178
+ leaveMeeting: (meeting, options: any = {}) => {
179
+ if (meeting.meetingState === FULL_STATE.INACTIVE) {
180
+ // TODO: clean up if the meeting is already inactive
181
+ return Promise.reject(new MeetingNotActiveError());
182
+ }
182
183
 
183
- // by default will leave on meeting's resourceId
184
- // if you explicity want it not to leave on resource id, pass
185
- // {resourceId: null}
186
- // TODO: chris, you can modify this however you want
187
- MeetingUtil.leaveMeeting = (meeting, options: any = {}) => {
188
- if (meeting.meetingState === FULL_STATE.INACTIVE) {
189
- // TODO: clean up if the meeting is already inactive
190
- return Promise.reject(new MeetingNotActiveError());
191
- }
192
-
193
- if (MeetingUtil.isUserInLeftState(meeting.locusInfo)) {
194
- return Promise.reject(new UserNotJoinedError());
195
- }
196
-
197
- const defaultOptions = {
198
- locusUrl: meeting.locusUrl,
199
- selfId: meeting.selfId,
200
- correlationId: meeting.correlationId,
201
- resourceId: meeting.resourceId,
202
- deviceUrl: meeting.deviceUrl,
203
- };
204
-
205
- const leaveOptions = {...defaultOptions, ...options};
206
-
207
- return meeting.meetingRequest
208
- .leaveMeeting(leaveOptions)
209
- .then((response) => {
210
- if (response && response.body && response.body.locus) {
211
- // && !options.moveMeeting) {
212
- meeting.locusInfo.onFullLocus(response.body.locus);
213
- }
184
+ if (MeetingUtil.isUserInLeftState(meeting.locusInfo)) {
185
+ return Promise.reject(new UserNotJoinedError());
186
+ }
214
187
 
215
- return Promise.resolve();
216
- })
217
- .then(() => {
218
- if (options.moveMeeting) {
219
- return Promise.resolve();
220
- }
188
+ const defaultOptions = {
189
+ locusUrl: meeting.locusUrl,
190
+ selfId: meeting.selfId,
191
+ correlationId: meeting.correlationId,
192
+ resourceId: meeting.resourceId,
193
+ deviceUrl: meeting.deviceUrl,
194
+ };
195
+
196
+ const leaveOptions = {...defaultOptions, ...options};
197
+
198
+ return meeting.meetingRequest
199
+ .leaveMeeting(leaveOptions)
200
+ .then(() => {
201
+ if (options.moveMeeting) {
202
+ return Promise.resolve();
203
+ }
204
+
205
+ return MeetingUtil.cleanUp(meeting);
206
+ })
207
+ .catch((err) => {
208
+ // TODO: If the meeting state comes as LEFT or INACTIVE as response then
209
+ // 1) on leave clean up the meeting or simply do a sync on the meeting
210
+ // 2) If the error says meeting is inactive then destroy the meeting object
211
+ LoggerProxy.logger.error(
212
+ `Meeting:util#leaveMeeting --> An error occured while trying to leave meeting with an id of ${meeting.id}, error: ${err}`
213
+ );
214
+
215
+ return Promise.reject(err);
216
+ });
217
+ },
218
+ declineMeeting: (meeting, reason) =>
219
+ meeting.meetingRequest.declineMeeting({
220
+ locusUrl: meeting.locusUrl,
221
+ deviceUrl: meeting.deviceUrl,
222
+ reason,
223
+ }),
221
224
 
222
- return MeetingUtil.cleanUp(meeting);
223
- })
224
- .catch((err) => {
225
- // TODO: If the meeting state comes as LEFT or INACTIVE as response then
226
- // 1) on leave clean up the meeting or simply do a sync on the meeting
227
- // 2) If the error says meeting is inactive then destroy the meeting object
228
- LoggerProxy.logger.error(
229
- `Meeting:util#leaveMeeting --> An error occured while trying to leave meeting with an id of ${meeting.id}, error: ${err}`
230
- );
225
+ isUserInLeftState: (locusInfo) => locusInfo.parsedLocus?.self?.state === _LEFT_,
231
226
 
232
- return Promise.reject(err);
233
- });
234
- };
235
- MeetingUtil.declineMeeting = (meeting, reason) =>
236
- meeting.meetingRequest.declineMeeting({
237
- locusUrl: meeting.locusUrl,
238
- deviceUrl: meeting.deviceUrl,
239
- reason,
240
- });
227
+ isUserInIdleState: (locusInfo) => locusInfo.parsedLocus?.self?.state === _IDLE_,
241
228
 
242
- MeetingUtil.isUserInLeftState = (locusInfo) => locusInfo.parsedLocus?.self?.state === _LEFT_;
229
+ isUserInJoinedState: (locusInfo) => locusInfo.parsedLocus?.self?.state === _JOINED_,
243
230
 
244
- MeetingUtil.isUserInIdleState = (locusInfo) => locusInfo.parsedLocus?.self?.state === _IDLE_;
231
+ isMediaEstablished: (currentMediaStatus) =>
232
+ currentMediaStatus &&
233
+ (currentMediaStatus.audio || currentMediaStatus.video || currentMediaStatus.share),
245
234
 
246
- MeetingUtil.isUserInJoinedState = (locusInfo) => locusInfo.parsedLocus?.self?.state === _JOINED_;
235
+ joinMeetingOptions: (meeting, options: any = {}) => {
236
+ meeting.resourceId = meeting.resourceId || options.resourceId;
247
237
 
248
- MeetingUtil.isMediaEstablished = (currentMediaStatus) =>
249
- currentMediaStatus &&
250
- (currentMediaStatus.audio || currentMediaStatus.video || currentMediaStatus.share);
238
+ if (meeting.requiredCaptcha) {
239
+ return Promise.reject(new CaptchaError());
240
+ }
241
+ if (meeting.passwordStatus === PASSWORD_STATUS.REQUIRED) {
242
+ return Promise.reject(new PasswordError());
243
+ }
251
244
 
252
- MeetingUtil.joinMeetingOptions = (meeting, options: any = {}) => {
253
- meeting.resourceId = meeting.resourceId || options.resourceId;
245
+ if (options.pin) {
246
+ Metrics.postEvent({
247
+ event: eventType.PIN_COLLECTED,
248
+ meeting,
249
+ });
250
+ }
254
251
 
255
- if (meeting.requiredCaptcha) {
256
- return Promise.reject(new CaptchaError());
257
- }
258
- if (meeting.passwordStatus === PASSWORD_STATUS.REQUIRED) {
259
- return Promise.reject(new PasswordError());
260
- }
252
+ // normal join meeting, scenario A, D
253
+ return MeetingUtil.joinMeeting(meeting, options)
254
+ .then((response) => {
255
+ meeting.setLocus(response);
256
+
257
+ return Promise.resolve(response);
258
+ })
259
+ .catch((err) => {
260
+ // joining a claimed PMR that is not my own, scenario B
261
+ if (MeetingUtil.isPinOrGuest(err)) {
262
+ Metrics.postEvent({
263
+ event: eventType.PIN_PROMPT,
264
+ meeting,
265
+ });
266
+
267
+ // request host pin or non host for unclaimed PMR, start of Scenario C
268
+ // see https://sqbu-github.cisco.com/WebExSquared/locus/wiki/Locus-Lobby-and--IVR-Feature
269
+ return Promise.reject(new IntentToJoinError('Error Joining Meeting', err));
270
+ }
271
+ LoggerProxy.logger.error(
272
+ 'Meeting:util#joinMeetingOptions --> Error joining the call, ',
273
+ err
274
+ );
275
+
276
+ return Promise.reject(new JoinMeetingError(options, 'Error Joining Meeting', err));
277
+ });
278
+ },
261
279
 
262
- if (options.pin) {
263
- Metrics.postEvent({
264
- event: eventType.PIN_COLLECTED,
265
- meeting,
266
- });
267
- }
268
-
269
- // normal join meeting, scenario A, D
270
- return MeetingUtil.joinMeeting(meeting, options)
271
- .then((response) => {
272
- meeting.setLocus(response);
273
-
274
- return Promise.resolve(response);
275
- })
276
- .catch((err) => {
277
- // joining a claimed PMR that is not my own, scenario B
278
- if (MeetingUtil.isPinOrGuest(err)) {
279
- Metrics.postEvent({
280
- event: eventType.PIN_PROMPT,
281
- meeting,
282
- });
280
+ validateOptions: (options) => {
281
+ const {sendVideo, sendAudio, sendShare, localStream, localShare} = options;
283
282
 
284
- // request host pin or non host for unclaimed PMR, start of Scenario C
285
- // see https://sqbu-github.cisco.com/WebExSquared/locus/wiki/Locus-Lobby-and--IVR-Feature
286
- return Promise.reject(new IntentToJoinError('Error Joining Meeting', err));
287
- }
288
- LoggerProxy.logger.error('Meeting:util#joinMeetingOptions --> Error joining the call, ', err);
283
+ if (sendVideo && !MeetingUtil.getTrack(localStream).videoTrack) {
284
+ return Promise.reject(new ParameterError('please pass valid video streams'));
285
+ }
289
286
 
290
- return Promise.reject(new JoinMeetingError(options, 'Error Joining Meeting', err));
291
- });
292
- };
287
+ if (sendAudio && !MeetingUtil.getTrack(localStream).audioTrack) {
288
+ return Promise.reject(new ParameterError('please pass valid audio streams'));
289
+ }
293
290
 
294
- MeetingUtil.validateOptions = (options) => {
295
- const {sendVideo, sendAudio, sendShare, localStream, localShare} = options;
291
+ if (sendShare && !MeetingUtil.getTrack(localShare).videoTrack) {
292
+ return Promise.reject(new ParameterError('please pass valid share streams'));
293
+ }
296
294
 
297
- if (sendVideo && !MeetingUtil.getTrack(localStream).videoTrack) {
298
- return Promise.reject(new ParameterError('please pass valid video streams'));
299
- }
295
+ return Promise.resolve();
296
+ },
300
297
 
301
- if (sendAudio && !MeetingUtil.getTrack(localStream).audioTrack) {
302
- return Promise.reject(new ParameterError('please pass valid audio streams'));
303
- }
298
+ getTrack: (stream) => {
299
+ let audioTrack = null;
300
+ let videoTrack = null;
301
+ let audioTracks = null;
302
+ let videoTracks = null;
304
303
 
305
- if (sendShare && !MeetingUtil.getTrack(localShare).videoTrack) {
306
- return Promise.reject(new ParameterError('please pass valid share streams'));
307
- }
304
+ if (!stream) {
305
+ return {audioTrack: null, videoTrack: null};
306
+ }
307
+ if (stream.getAudioTracks) {
308
+ audioTracks = stream.getAudioTracks();
309
+ }
310
+ if (stream.getVideoTracks) {
311
+ videoTracks = stream.getVideoTracks();
312
+ }
308
313
 
309
- return Promise.resolve();
310
- };
314
+ if (audioTracks && audioTracks.length > 0) {
315
+ [audioTrack] = audioTracks;
316
+ }
311
317
 
312
- MeetingUtil.getTrack = (stream) => {
313
- let audioTrack = null;
314
- let videoTrack = null;
315
- let audioTracks = null;
316
- let videoTracks = null;
317
-
318
- if (!stream) {
319
- return {audioTrack: null, videoTrack: null};
320
- }
321
- if (stream.getAudioTracks) {
322
- audioTracks = stream.getAudioTracks();
323
- }
324
- if (stream.getVideoTracks) {
325
- videoTracks = stream.getVideoTracks();
326
- }
327
-
328
- if (audioTracks && audioTracks.length > 0) {
329
- [audioTrack] = audioTracks;
330
- }
331
-
332
- if (videoTracks && videoTracks.length > 0) {
333
- [videoTrack] = videoTracks;
334
- }
335
-
336
- return {audioTrack, videoTrack};
337
- };
318
+ if (videoTracks && videoTracks.length > 0) {
319
+ [videoTrack] = videoTracks;
320
+ }
338
321
 
339
- MeetingUtil.getModeratorFromLocusInfo = (locusInfo) =>
340
- locusInfo &&
341
- locusInfo.parsedLocus &&
342
- locusInfo.parsedLocus.info &&
343
- locusInfo.parsedLocus.info &&
344
- locusInfo.parsedLocus.info.moderator;
322
+ return {audioTrack, videoTrack};
323
+ },
345
324
 
346
- MeetingUtil.getPolicyFromLocusInfo = (locusInfo) =>
347
- locusInfo &&
348
- locusInfo.parsedLocus &&
349
- locusInfo.parsedLocus.info &&
350
- locusInfo.parsedLocus.info &&
351
- locusInfo.parsedLocus.info.policy;
325
+ getModeratorFromLocusInfo: (locusInfo) =>
326
+ locusInfo &&
327
+ locusInfo.parsedLocus &&
328
+ locusInfo.parsedLocus.info &&
329
+ locusInfo.parsedLocus.info &&
330
+ locusInfo.parsedLocus.info.moderator,
352
331
 
353
- MeetingUtil.getUserDisplayHintsFromLocusInfo = (locusInfo) =>
354
- locusInfo?.parsedLocus?.info?.userDisplayHints || [];
332
+ getPolicyFromLocusInfo: (locusInfo) =>
333
+ locusInfo &&
334
+ locusInfo.parsedLocus &&
335
+ locusInfo.parsedLocus.info &&
336
+ locusInfo.parsedLocus.info &&
337
+ locusInfo.parsedLocus.info.policy,
355
338
 
356
- MeetingUtil.canInviteNewParticipants = (displayHints) =>
357
- displayHints.includes(DISPLAY_HINTS.ADD_GUEST);
339
+ getUserDisplayHintsFromLocusInfo: (locusInfo) =>
340
+ locusInfo?.parsedLocus?.info?.userDisplayHints || [],
358
341
 
359
- MeetingUtil.canAdmitParticipant = (displayHints) =>
360
- displayHints.includes(DISPLAY_HINTS.ROSTER_WAITING_TO_JOIN);
342
+ canInviteNewParticipants: (displayHints) => displayHints.includes(DISPLAY_HINTS.ADD_GUEST),
361
343
 
362
- MeetingUtil.canUserLock = (displayHints) =>
363
- displayHints.includes(DISPLAY_HINTS.LOCK_CONTROL_LOCK) &&
364
- displayHints.includes(DISPLAY_HINTS.LOCK_STATUS_UNLOCKED);
344
+ canAdmitParticipant: (displayHints) =>
345
+ displayHints.includes(DISPLAY_HINTS.ROSTER_WAITING_TO_JOIN),
365
346
 
366
- MeetingUtil.canUserUnlock = (displayHints) =>
367
- displayHints.includes(DISPLAY_HINTS.LOCK_CONTROL_UNLOCK) &&
368
- displayHints.includes(DISPLAY_HINTS.LOCK_STATUS_LOCKED);
347
+ canUserLock: (displayHints) =>
348
+ displayHints.includes(DISPLAY_HINTS.LOCK_CONTROL_LOCK) &&
349
+ displayHints.includes(DISPLAY_HINTS.LOCK_STATUS_UNLOCKED),
369
350
 
370
- MeetingUtil.canUserRaiseHand = (displayHints) => displayHints.includes(DISPLAY_HINTS.RAISE_HAND);
351
+ canUserUnlock: (displayHints) =>
352
+ displayHints.includes(DISPLAY_HINTS.LOCK_CONTROL_UNLOCK) &&
353
+ displayHints.includes(DISPLAY_HINTS.LOCK_STATUS_LOCKED),
371
354
 
372
- MeetingUtil.canUserLowerAllHands = (displayHints) =>
373
- displayHints.includes(DISPLAY_HINTS.LOWER_ALL_HANDS);
355
+ canUserRaiseHand: (displayHints) => displayHints.includes(DISPLAY_HINTS.RAISE_HAND),
374
356
 
375
- MeetingUtil.canUserLowerSomeoneElsesHand = (displayHints) =>
376
- displayHints.includes(DISPLAY_HINTS.LOWER_SOMEONE_ELSES_HAND);
357
+ canUserLowerAllHands: (displayHints) => displayHints.includes(DISPLAY_HINTS.LOWER_ALL_HANDS),
377
358
 
378
- MeetingUtil.bothLeaveAndEndMeetingAvailable = (displayHints) =>
379
- displayHints.includes(DISPLAY_HINTS.LEAVE_TRANSFER_HOST_END_MEETING) ||
380
- displayHints.includes(DISPLAY_HINTS.LEAVE_END_MEETING);
359
+ canUserLowerSomeoneElsesHand: (displayHints) =>
360
+ displayHints.includes(DISPLAY_HINTS.LOWER_SOMEONE_ELSES_HAND),
381
361
 
382
- MeetingUtil.canManageBreakout = (displayHints) =>
383
- displayHints.includes(DISPLAY_HINTS.BREAKOUT_MANAGEMENT);
362
+ bothLeaveAndEndMeetingAvailable: (displayHints) =>
363
+ displayHints.includes(DISPLAY_HINTS.LEAVE_TRANSFER_HOST_END_MEETING) ||
364
+ displayHints.includes(DISPLAY_HINTS.LEAVE_END_MEETING),
384
365
 
385
- MeetingUtil.isSuppressBreakoutSupport = (displayHints) =>
386
- displayHints.includes(DISPLAY_HINTS.UCF_SUPPRESS_BREAKOUTS_SUPPORT);
366
+ canManageBreakout: (displayHints) => displayHints.includes(DISPLAY_HINTS.BREAKOUT_MANAGEMENT),
387
367
 
388
- MeetingUtil.canAdmitLobbyToBreakout = (displayHints) =>
389
- !displayHints.includes(DISPLAY_HINTS.DISABLE_LOBBY_TO_BREAKOUT);
368
+ isSuppressBreakoutSupport: (displayHints) =>
369
+ displayHints.includes(DISPLAY_HINTS.UCF_SUPPRESS_BREAKOUTS_SUPPORT),
390
370
 
391
- MeetingUtil.isBreakoutPreassignmentsEnabled = (displayHints) =>
392
- !displayHints.includes(DISPLAY_HINTS.DISABLE_BREAKOUT_PREASSIGNMENTS);
371
+ canAdmitLobbyToBreakout: (displayHints) =>
372
+ !displayHints.includes(DISPLAY_HINTS.DISABLE_LOBBY_TO_BREAKOUT),
393
373
 
394
- MeetingUtil.canUserAskForHelp = (displayHints) =>
395
- !displayHints.includes(DISPLAY_HINTS.DISABLE_ASK_FOR_HELP);
374
+ isBreakoutPreassignmentsEnabled: (displayHints) =>
375
+ !displayHints.includes(DISPLAY_HINTS.DISABLE_BREAKOUT_PREASSIGNMENTS),
396
376
 
397
- MeetingUtil.lockMeeting = (actions, request, locusUrl) => {
398
- if (actions && actions.canLock) {
399
- return request.lockMeeting({locusUrl, lock: true});
400
- }
377
+ canUserAskForHelp: (displayHints) => !displayHints.includes(DISPLAY_HINTS.DISABLE_ASK_FOR_HELP),
401
378
 
402
- return Promise.reject(new PermissionError('Lock not allowed, due to joined property.'));
403
- };
379
+ lockMeeting: (actions, request, locusUrl) => {
380
+ if (actions && actions.canLock) {
381
+ return request.lockMeeting({locusUrl, lock: true});
382
+ }
404
383
 
405
- MeetingUtil.unlockMeeting = (actions, request, locusUrl) => {
406
- if (actions && actions.canUnlock) {
407
- return request.lockMeeting({locusUrl, lock: false});
408
- }
384
+ return Promise.reject(new PermissionError('Lock not allowed, due to joined property.'));
385
+ },
409
386
 
410
- return Promise.reject(new PermissionError('Unlock not allowed, due to joined property.'));
411
- };
387
+ unlockMeeting: (actions, request, locusUrl) => {
388
+ if (actions && actions.canUnlock) {
389
+ return request.lockMeeting({locusUrl, lock: false});
390
+ }
412
391
 
413
- MeetingUtil.handleAudioLogging = (audioTrack: LocalMicrophoneTrack | null) => {
414
- const LOG_HEADER = 'MeetingUtil#handleAudioLogging -->';
392
+ return Promise.reject(new PermissionError('Unlock not allowed, due to joined property.'));
393
+ },
415
394
 
416
- if (audioTrack) {
417
- const settings = audioTrack.underlyingTrack.getSettings();
418
- const {deviceId} = settings;
395
+ handleAudioLogging: (audioTrack: LocalMicrophoneTrack | null) => {
396
+ const LOG_HEADER = 'MeetingUtil#handleAudioLogging -->';
419
397
 
420
- LoggerProxy.logger.log(LOG_HEADER, `deviceId = ${deviceId}`);
421
- LoggerProxy.logger.log(LOG_HEADER, 'settings =', JSON.stringify(settings));
422
- }
423
- };
398
+ if (audioTrack) {
399
+ const settings = audioTrack.underlyingTrack.getSettings();
400
+ const {deviceId} = settings;
424
401
 
425
- MeetingUtil.handleVideoLogging = (videoTrack: LocalCameraTrack | null) => {
426
- const LOG_HEADER = 'MeetingUtil#handleVideoLogging -->';
402
+ LoggerProxy.logger.log(LOG_HEADER, `deviceId = ${deviceId}`);
403
+ LoggerProxy.logger.log(LOG_HEADER, 'settings =', JSON.stringify(settings));
404
+ }
405
+ },
427
406
 
428
- if (videoTrack) {
429
- const settings = videoTrack.underlyingTrack.getSettings();
430
- const {deviceId} = settings;
407
+ handleVideoLogging: (videoTrack: LocalCameraTrack | null) => {
408
+ const LOG_HEADER = 'MeetingUtil#handleVideoLogging -->';
431
409
 
432
- LoggerProxy.logger.log(LOG_HEADER, `deviceId = ${deviceId}`);
433
- LoggerProxy.logger.log(LOG_HEADER, 'settings =', JSON.stringify(settings));
434
- }
435
- };
410
+ if (videoTrack) {
411
+ const settings = videoTrack.underlyingTrack.getSettings();
412
+ const {deviceId} = settings;
436
413
 
437
- MeetingUtil.handleDeviceLogging = (devices = []) => {
438
- const LOG_HEADER = 'MeetingUtil#handleDeviceLogging -->';
414
+ LoggerProxy.logger.log(LOG_HEADER, `deviceId = ${deviceId}`);
415
+ LoggerProxy.logger.log(LOG_HEADER, 'settings =', JSON.stringify(settings));
416
+ }
417
+ },
439
418
 
440
- devices.forEach((device) => {
441
- LoggerProxy.logger.log(LOG_HEADER, `deviceId = ${device.deviceId}`);
442
- LoggerProxy.logger.log(LOG_HEADER, 'settings', JSON.stringify(device));
443
- });
444
- };
419
+ handleDeviceLogging: (devices = []) => {
420
+ const LOG_HEADER = 'MeetingUtil#handleDeviceLogging -->';
445
421
 
446
- MeetingUtil.endMeetingForAll = (meeting) => {
447
- if (meeting.meetingState === FULL_STATE.INACTIVE) {
448
- return Promise.reject(new MeetingNotActiveError());
449
- }
422
+ devices.forEach((device) => {
423
+ LoggerProxy.logger.log(LOG_HEADER, `deviceId = ${device.deviceId}`);
424
+ LoggerProxy.logger.log(LOG_HEADER, 'settings', JSON.stringify(device));
425
+ });
426
+ },
450
427
 
451
- const endOptions = {
452
- locusUrl: meeting.locusUrl,
453
- };
428
+ endMeetingForAll: (meeting) => {
429
+ if (meeting.meetingState === FULL_STATE.INACTIVE) {
430
+ return Promise.reject(new MeetingNotActiveError());
431
+ }
454
432
 
455
- return meeting.meetingRequest
456
- .endMeetingForAll(endOptions)
457
- .then((response) => {
458
- if (response && response.body && response.body.locus) {
459
- meeting.locusInfo.onFullLocus(response.body.locus);
460
- }
433
+ const endOptions = {
434
+ locusUrl: meeting.locusUrl,
435
+ };
461
436
 
462
- return Promise.resolve();
463
- })
464
- .then(() => MeetingUtil.cleanUp(meeting))
465
- .catch((err) => {
466
- LoggerProxy.logger.error(
467
- `Meeting:util#endMeetingForAll An error occured while trying to end meeting for all with an id of ${meeting.id}, error: ${err}`
468
- );
437
+ return meeting.meetingRequest
438
+ .endMeetingForAll(endOptions)
439
+ .then(() => MeetingUtil.cleanUp(meeting))
440
+ .catch((err) => {
441
+ LoggerProxy.logger.error(
442
+ `Meeting:util#endMeetingForAll An error occured while trying to end meeting for all with an id of ${meeting.id}, error: ${err}`
443
+ );
469
444
 
470
- return Promise.reject(err);
471
- });
472
- };
445
+ return Promise.reject(err);
446
+ });
447
+ },
473
448
 
474
- MeetingUtil.canEnableClosedCaption = (displayHints) =>
475
- displayHints.includes(DISPLAY_HINTS.CAPTION_START);
449
+ canEnableClosedCaption: (displayHints) => displayHints.includes(DISPLAY_HINTS.CAPTION_START),
476
450
 
477
- MeetingUtil.isSaveTranscriptsEnabled = (displayHints) =>
478
- displayHints.includes(DISPLAY_HINTS.SAVE_TRANSCRIPTS_ENABLED);
451
+ isSaveTranscriptsEnabled: (displayHints) =>
452
+ displayHints.includes(DISPLAY_HINTS.SAVE_TRANSCRIPTS_ENABLED),
479
453
 
480
- MeetingUtil.canStartTranscribing = (displayHints) =>
481
- displayHints.includes(DISPLAY_HINTS.TRANSCRIPTION_CONTROL_START);
454
+ canStartTranscribing: (displayHints) =>
455
+ displayHints.includes(DISPLAY_HINTS.TRANSCRIPTION_CONTROL_START),
482
456
 
483
- MeetingUtil.canStopTranscribing = (displayHints) =>
484
- displayHints.includes(DISPLAY_HINTS.TRANSCRIPTION_CONTROL_STOP);
457
+ canStopTranscribing: (displayHints) =>
458
+ displayHints.includes(DISPLAY_HINTS.TRANSCRIPTION_CONTROL_STOP),
485
459
 
486
- MeetingUtil.isClosedCaptionActive = (displayHints) =>
487
- displayHints.includes(DISPLAY_HINTS.CAPTION_STATUS_ACTIVE);
460
+ isClosedCaptionActive: (displayHints) =>
461
+ displayHints.includes(DISPLAY_HINTS.CAPTION_STATUS_ACTIVE),
488
462
 
489
- MeetingUtil.isWebexAssistantActive = (displayHints) =>
490
- displayHints.includes(DISPLAY_HINTS.WEBEX_ASSISTANT_STATUS_ACTIVE);
463
+ isWebexAssistantActive: (displayHints) =>
464
+ displayHints.includes(DISPLAY_HINTS.WEBEX_ASSISTANT_STATUS_ACTIVE),
491
465
 
492
- MeetingUtil.canViewCaptionPanel = (displayHints) =>
493
- displayHints.includes(DISPLAY_HINTS.ENABLE_CAPTION_PANEL);
466
+ canViewCaptionPanel: (displayHints) => displayHints.includes(DISPLAY_HINTS.ENABLE_CAPTION_PANEL),
494
467
 
495
- MeetingUtil.isRealTimeTranslationEnabled = (displayHints) =>
496
- displayHints.includes(DISPLAY_HINTS.DISPLAY_REAL_TIME_TRANSLATION);
468
+ isRealTimeTranslationEnabled: (displayHints) =>
469
+ displayHints.includes(DISPLAY_HINTS.DISPLAY_REAL_TIME_TRANSLATION),
497
470
 
498
- MeetingUtil.canSelectSpokenLanguages = (displayHints) =>
499
- displayHints.includes(DISPLAY_HINTS.DISPLAY_NON_ENGLISH_ASR);
471
+ canSelectSpokenLanguages: (displayHints) =>
472
+ displayHints.includes(DISPLAY_HINTS.DISPLAY_NON_ENGLISH_ASR),
500
473
 
501
- MeetingUtil.waitingForOthersToJoin = (displayHints) =>
502
- displayHints.includes(DISPLAY_HINTS.WAITING_FOR_OTHERS);
474
+ waitingForOthersToJoin: (displayHints) => displayHints.includes(DISPLAY_HINTS.WAITING_FOR_OTHERS),
503
475
 
504
- MeetingUtil.canSendReactions = (originalValue, displayHints) => {
505
- if (displayHints.includes(DISPLAY_HINTS.REACTIONS_ACTIVE)) {
506
- return true;
507
- }
508
- if (displayHints.includes(DISPLAY_HINTS.REACTIONS_INACTIVE)) {
509
- return false;
510
- }
476
+ canSendReactions: (originalValue, displayHints) => {
477
+ if (displayHints.includes(DISPLAY_HINTS.REACTIONS_ACTIVE)) {
478
+ return true;
479
+ }
480
+ if (displayHints.includes(DISPLAY_HINTS.REACTIONS_INACTIVE)) {
481
+ return false;
482
+ }
511
483
 
512
- return originalValue;
513
- };
514
- MeetingUtil.canUserRenameSelfAndObserved = (displayHints) =>
515
- displayHints.includes(DISPLAY_HINTS.CAN_RENAME_SELF_AND_OBSERVED);
484
+ return originalValue;
485
+ },
486
+ canUserRenameSelfAndObserved: (displayHints) =>
487
+ displayHints.includes(DISPLAY_HINTS.CAN_RENAME_SELF_AND_OBSERVED),
488
+
489
+ canUserRenameOthers: (displayHints) => displayHints.includes(DISPLAY_HINTS.CAN_RENAME_OTHERS),
516
490
 
517
- MeetingUtil.canUserRenameOthers = (displayHints) =>
518
- displayHints.includes(DISPLAY_HINTS.CAN_RENAME_OTHERS);
491
+ /**
492
+ * Adds the current locus sequence information to a request body
493
+ * @param {Object} meeting The meeting object
494
+ * @param {Object} requestBody The body of a request to locus
495
+ * @returns {void}
496
+ */
497
+ addSequence: (meeting, requestBody) => {
498
+ const sequence = meeting?.locusInfo?.sequence;
499
+
500
+ if (!sequence) {
501
+ return;
502
+ }
503
+
504
+ requestBody.sequence = sequence;
505
+ },
506
+
507
+ /**
508
+ * Updates the locus info for the meeting with the delta locus
509
+ * returned from requests that include the sequence information
510
+ * Returns the original response object
511
+ * @param {Object} meeting The meeting object
512
+ * @param {Object} response The response of the http request
513
+ * @returns {Object}
514
+ */
515
+ updateLocusWithDelta: (meeting, response) => {
516
+ if (!meeting) {
517
+ return response;
518
+ }
519
+
520
+ const locus = response?.body?.locus;
521
+
522
+ if (locus) {
523
+ meeting.locusInfo.onDeltaLocus(locus);
524
+ }
525
+
526
+ return response;
527
+ },
528
+
529
+ generateLocusDeltaRequest: (originalMeeting) => {
530
+ const meetingRef = new WeakRef(originalMeeting);
531
+
532
+ const locusDeltaRequest = (originalOptions) => {
533
+ const meeting = meetingRef.deref();
534
+
535
+ if (!meeting) {
536
+ return Promise.resolve();
537
+ }
538
+
539
+ const options = cloneDeep(originalOptions);
540
+
541
+ if (!options.body) {
542
+ options.body = {};
543
+ }
544
+
545
+ MeetingUtil.addSequence(meeting, options.body);
546
+
547
+ return meeting
548
+ .request(options)
549
+ .then((response) => MeetingUtil.updateLocusWithDelta(meeting, response));
550
+ };
551
+
552
+ return locusDeltaRequest;
553
+ },
554
+ };
519
555
 
520
556
  export default MeetingUtil;