@webex/plugin-meetings 3.0.0-beta.43 → 3.0.0-beta.44

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 (43) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +10 -2
  3. package/dist/breakouts/index.js.map +1 -1
  4. package/dist/constants.js +6 -1
  5. package/dist/constants.js.map +1 -1
  6. package/dist/locus-info/controlsUtils.js +6 -2
  7. package/dist/locus-info/controlsUtils.js.map +1 -1
  8. package/dist/locus-info/index.js +35 -1
  9. package/dist/locus-info/index.js.map +1 -1
  10. package/dist/locus-info/selfUtils.js +28 -0
  11. package/dist/locus-info/selfUtils.js.map +1 -1
  12. package/dist/meeting/index.js +38 -3
  13. package/dist/meeting/index.js.map +1 -1
  14. package/dist/meeting/muteState.js +45 -20
  15. package/dist/meeting/muteState.js.map +1 -1
  16. package/dist/members/index.js +15 -3
  17. package/dist/members/index.js.map +1 -1
  18. package/dist/members/util.js +33 -12
  19. package/dist/members/util.js.map +1 -1
  20. package/dist/types/constants.d.ts +5 -0
  21. package/dist/types/locus-info/index.d.ts +7 -0
  22. package/dist/types/meeting/index.d.ts +12 -2
  23. package/dist/types/meeting/muteState.d.ts +16 -0
  24. package/dist/types/members/index.d.ts +7 -2
  25. package/package.json +18 -18
  26. package/src/breakouts/index.ts +6 -0
  27. package/src/constants.ts +5 -0
  28. package/src/locus-info/controlsUtils.ts +8 -0
  29. package/src/locus-info/index.ts +42 -1
  30. package/src/locus-info/selfUtils.ts +34 -0
  31. package/src/meeting/index.ts +45 -4
  32. package/src/meeting/muteState.ts +49 -30
  33. package/src/members/index.ts +12 -4
  34. package/src/members/util.ts +21 -8
  35. package/test/unit/spec/breakouts/index.ts +2 -2
  36. package/test/unit/spec/locus-info/controlsUtils.js +104 -46
  37. package/test/unit/spec/locus-info/index.js +131 -16
  38. package/test/unit/spec/locus-info/selfConstant.js +9 -5
  39. package/test/unit/spec/locus-info/selfUtils.js +39 -16
  40. package/test/unit/spec/meeting/index.js +208 -79
  41. package/test/unit/spec/meeting/muteState.js +72 -6
  42. package/test/unit/spec/members/index.js +75 -0
  43. package/test/unit/spec/members/utils.js +112 -0
@@ -1 +1 @@
1
- {"version":3,"names":["MembersUtil","generateAddMemberOptions","invitee","locusUrl","alertIfActive","generateAdmitMemberOptions","memberIds","getAddMemberBody","options","invitees","address","emailAddress","email","phoneNumber","getAdmitMemberRequestBody","admit","participantIds","getAdmitMemberRequestParams","format","body","uri","CONTROLS","method","HTTP_VERBS","PUT","getAddMemberRequestParams","requestParams","isInvalidInvitee","DIALER_REGEX","E164_FORMAT","test","VALID_EMAIL_ADDRESS","getRemoveMemberRequestParams","reason","PARTICIPANT","memberId","LEAVE","generateTransferHostMemberOptions","transfer","moderator","generateRemoveMemberOptions","removal","_FORCED_","generateMuteMemberOptions","status","muted","generateRaiseHandMemberOptions","raised","generateLowerAllHandsMemberOptions","requestingParticipantId","getMuteMemberRequestParams","audio","PATCH","getRaiseHandMemberRequestParams","hand","getLowerAllHandsMemberRequestParams","getTransferHostToMemberRequestParams","role","genderateSendDTMFOptions","url","tones","generateSendDTMFRequestParams","device","dtmf","correlationId","uuid","v4","direction","SEND_DTMF_ENDPOINT","POST","cancelPhoneInviteOptions","generateCancelInviteRequestParams","actionType","_REMOVE_"],"sources":["util.ts"],"sourcesContent":["import uuid from 'uuid';\n\nimport {\n HTTP_VERBS,\n CONTROLS,\n _FORCED_,\n LEAVE,\n PARTICIPANT,\n VALID_EMAIL_ADDRESS,\n DIALER_REGEX,\n SEND_DTMF_ENDPOINT,\n _REMOVE_,\n} from '../constants';\n\nconst MembersUtil: any = {};\n\n/**\n * @param {Object} invitee with emailAddress, email or phoneNumber\n * @param {String} locusUrl\n * @param {Boolean} alertIfActive\n * @returns {Object} the format object\n */\nMembersUtil.generateAddMemberOptions = (\n invitee: object,\n locusUrl: string,\n alertIfActive: boolean\n) => ({\n invitee,\n locusUrl,\n alertIfActive,\n});\n\n/**\n * @param {Array} memberIds\n * @param {String} locusUrl\n * @returns {Object} the format object\n */\nMembersUtil.generateAdmitMemberOptions = (memberIds: Array<any>, locusUrl: string) => ({\n locusUrl,\n memberIds,\n});\n\n/**\n * @param {Object} options with {invitee: {emailAddress, email, phoneNumber}, alertIfActive}\n * @returns {Object} with {invitees: [{address}], alertIfActive}\n */\nMembersUtil.getAddMemberBody = (options: any) => ({\n invitees: [\n {\n address: options.invitee.emailAddress || options.invitee.email || options.invitee.phoneNumber,\n },\n ],\n alertIfActive: options.alertIfActive,\n});\n\n/**\n * @param {Object} options with {memberIds}\n * @returns {Object} admit with {memberIds}\n */\nMembersUtil.getAdmitMemberRequestBody = (options: any) => ({\n admit: {participantIds: options.memberIds},\n});\n\n/**\n * @param {Object} format with {memberIds, locusUrl}\n * @returns {Object} the request parameters (method, uri, body) needed to make a admitMember request\n */\nMembersUtil.getAdmitMemberRequestParams = (format: any) => {\n const body = MembersUtil.getAdmitMemberRequestBody(format);\n const uri = `${format.locusUrl}/${CONTROLS}`;\n\n return {\n method: HTTP_VERBS.PUT,\n uri,\n body,\n };\n};\n\n/**\n * @param {Object} format with {invitee {emailAddress, email, phoneNumber}, locusUrl, alertIfActive}\n * @returns {Object} the request parameters (method, uri, body) needed to make a addMember request\n */\nMembersUtil.getAddMemberRequestParams = (format: any) => {\n const body = MembersUtil.getAddMemberBody(format);\n const requestParams = {\n method: HTTP_VERBS.PUT,\n uri: format.locusUrl,\n body,\n };\n\n return requestParams;\n};\n\nMembersUtil.isInvalidInvitee = (invitee) => {\n if (!(invitee && (invitee.email || invitee.emailAddress || invitee.phoneNumber))) {\n return true;\n }\n\n if (invitee.phoneNumber) {\n return !DIALER_REGEX.E164_FORMAT.test(invitee.phoneNumber);\n }\n\n return !VALID_EMAIL_ADDRESS.test(invitee.email || invitee.emailAddress);\n};\n\nMembersUtil.getRemoveMemberRequestParams = (options) => {\n const body = {\n reason: options.reason,\n };\n const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${LEAVE}`;\n\n return {\n method: HTTP_VERBS.PUT,\n uri,\n body,\n };\n};\n\nMembersUtil.generateTransferHostMemberOptions = (transfer, moderator, locusUrl) => ({\n moderator,\n locusUrl,\n memberId: transfer,\n});\n\nMembersUtil.generateRemoveMemberOptions = (removal, locusUrl) => ({\n reason: _FORCED_,\n memberId: removal,\n locusUrl,\n});\n\nMembersUtil.generateMuteMemberOptions = (memberId, status, locusUrl) => ({\n memberId,\n muted: status,\n locusUrl,\n});\n\nMembersUtil.generateRaiseHandMemberOptions = (memberId, status, locusUrl) => ({\n memberId,\n raised: status,\n locusUrl,\n});\n\nMembersUtil.generateLowerAllHandsMemberOptions = (requestingParticipantId, locusUrl) => ({\n requestingParticipantId,\n locusUrl,\n});\n\nMembersUtil.getMuteMemberRequestParams = (options) => {\n const body = {\n audio: {\n muted: options.muted,\n },\n };\n const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${CONTROLS}`;\n\n return {\n method: HTTP_VERBS.PATCH,\n uri,\n body,\n };\n};\n\nMembersUtil.getRaiseHandMemberRequestParams = (options) => {\n const body = {\n hand: {\n raised: options.raised,\n },\n };\n const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${CONTROLS}`;\n\n return {\n method: HTTP_VERBS.PATCH,\n uri,\n body,\n };\n};\n\nMembersUtil.getLowerAllHandsMemberRequestParams = (options) => {\n const body = {\n hand: {\n raised: false,\n },\n requestingParticipantId: options.requestingParticipantId,\n };\n const uri = `${options.locusUrl}/${CONTROLS}`;\n\n return {\n method: HTTP_VERBS.PATCH,\n uri,\n body,\n };\n};\n\nMembersUtil.getTransferHostToMemberRequestParams = (options) => {\n const body = {\n role: {\n moderator: options.moderator,\n },\n };\n const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${CONTROLS}`;\n\n return {\n method: HTTP_VERBS.PATCH,\n uri,\n body,\n };\n};\n\nMembersUtil.genderateSendDTMFOptions = (url, tones, memberId, locusUrl) => ({\n url,\n tones,\n memberId,\n locusUrl,\n});\n\nMembersUtil.generateSendDTMFRequestParams = ({url, tones, memberId, locusUrl}) => {\n const body = {\n device: {\n url,\n },\n memberId,\n dtmf: {\n correlationId: uuid.v4(),\n tones,\n direction: 'transmit',\n },\n };\n const uri = `${locusUrl}/${PARTICIPANT}/${memberId}/${SEND_DTMF_ENDPOINT}`;\n\n return {\n method: HTTP_VERBS.POST,\n uri,\n body,\n };\n};\n\nMembersUtil.cancelPhoneInviteOptions = (invitee, locusUrl) => ({\n invitee,\n locusUrl,\n});\n\nMembersUtil.generateCancelInviteRequestParams = (options) => {\n const body = {\n actionType: _REMOVE_,\n invitees: [\n {\n address: options.invitee.phoneNumber,\n },\n ],\n };\n const requestParams = {\n method: HTTP_VERBS.PUT,\n uri: options.locusUrl,\n body,\n };\n\n return requestParams;\n};\n\nexport default MembersUtil;\n"],"mappings":"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"}
1
+ {"version":3,"names":["MembersUtil","generateAddMemberOptions","invitee","locusUrl","alertIfActive","generateAdmitMemberOptions","memberIds","getAddMemberBody","options","invitees","address","emailAddress","email","phoneNumber","getAdmitMemberRequestBody","sessionLocusUrls","body","admit","participantIds","authorizingLocusUrl","getAdmitMemberRequestParams","format","baseUrl","mainLocusUrl","uri","CONTROLS","method","HTTP_VERBS","PUT","getAddMemberRequestParams","requestParams","isInvalidInvitee","DIALER_REGEX","E164_FORMAT","test","VALID_EMAIL_ADDRESS","getRemoveMemberRequestParams","reason","PARTICIPANT","memberId","LEAVE","generateTransferHostMemberOptions","transfer","moderator","generateRemoveMemberOptions","removal","_FORCED_","generateMuteMemberOptions","status","isAudio","muted","generateRaiseHandMemberOptions","raised","generateLowerAllHandsMemberOptions","requestingParticipantId","getMuteMemberRequestParams","property","PATCH","getRaiseHandMemberRequestParams","hand","getLowerAllHandsMemberRequestParams","getTransferHostToMemberRequestParams","role","genderateSendDTMFOptions","url","tones","generateSendDTMFRequestParams","device","dtmf","correlationId","uuid","v4","direction","SEND_DTMF_ENDPOINT","POST","cancelPhoneInviteOptions","generateCancelInviteRequestParams","actionType","_REMOVE_"],"sources":["util.ts"],"sourcesContent":["import uuid from 'uuid';\n\nimport {\n HTTP_VERBS,\n CONTROLS,\n _FORCED_,\n LEAVE,\n PARTICIPANT,\n VALID_EMAIL_ADDRESS,\n DIALER_REGEX,\n SEND_DTMF_ENDPOINT,\n _REMOVE_,\n} from '../constants';\n\nconst MembersUtil: any = {};\n\n/**\n * @param {Object} invitee with emailAddress, email or phoneNumber\n * @param {String} locusUrl\n * @param {Boolean} alertIfActive\n * @returns {Object} the format object\n */\nMembersUtil.generateAddMemberOptions = (\n invitee: object,\n locusUrl: string,\n alertIfActive: boolean\n) => ({\n invitee,\n locusUrl,\n alertIfActive,\n});\n\n/**\n * @param {Array} memberIds\n * @param {String} locusUrl\n * @returns {Object} the format object\n */\nMembersUtil.generateAdmitMemberOptions = (memberIds: Array<any>, locusUrl: string) => ({\n locusUrl,\n memberIds,\n});\n\n/**\n * @param {Object} options with {invitee: {emailAddress, email, phoneNumber}, alertIfActive}\n * @returns {Object} with {invitees: [{address}], alertIfActive}\n */\nMembersUtil.getAddMemberBody = (options: any) => ({\n invitees: [\n {\n address: options.invitee.emailAddress || options.invitee.email || options.invitee.phoneNumber,\n },\n ],\n alertIfActive: options.alertIfActive,\n});\n\n/**\n * @param {Object} options with {memberIds, authorizingLocusUrl}\n * @returns {Object} admit with {memberIds}\n */\nMembersUtil.getAdmitMemberRequestBody = (options: any) => {\n const {memberIds, sessionLocusUrls} = options;\n const body: any = {admit: {participantIds: memberIds}};\n if (sessionLocusUrls) {\n const {authorizingLocusUrl} = sessionLocusUrls;\n\n return {authorizingLocusUrl, ...body};\n }\n\n return body;\n};\n\n/**\n * @param {Object} format with {memberIds, locusUrl, sessionLocusUrls}\n * @returns {Object} the request parameters (method, uri, body) needed to make a admitMember request\n * if a host/cohost is in a breakout session, the locus url should be the main session locus url\n */\nMembersUtil.getAdmitMemberRequestParams = (format: any) => {\n const body = MembersUtil.getAdmitMemberRequestBody(format);\n const {locusUrl, sessionLocusUrls} = format;\n const baseUrl = sessionLocusUrls?.mainLocusUrl || locusUrl;\n const uri = `${baseUrl}/${CONTROLS}`;\n\n return {\n method: HTTP_VERBS.PUT,\n uri,\n body,\n };\n};\n\n/**\n * @param {Object} format with {invitee {emailAddress, email, phoneNumber}, locusUrl, alertIfActive}\n * @returns {Object} the request parameters (method, uri, body) needed to make a addMember request\n */\nMembersUtil.getAddMemberRequestParams = (format: any) => {\n const body = MembersUtil.getAddMemberBody(format);\n const requestParams = {\n method: HTTP_VERBS.PUT,\n uri: format.locusUrl,\n body,\n };\n\n return requestParams;\n};\n\nMembersUtil.isInvalidInvitee = (invitee) => {\n if (!(invitee && (invitee.email || invitee.emailAddress || invitee.phoneNumber))) {\n return true;\n }\n\n if (invitee.phoneNumber) {\n return !DIALER_REGEX.E164_FORMAT.test(invitee.phoneNumber);\n }\n\n return !VALID_EMAIL_ADDRESS.test(invitee.email || invitee.emailAddress);\n};\n\nMembersUtil.getRemoveMemberRequestParams = (options) => {\n const body = {\n reason: options.reason,\n };\n const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${LEAVE}`;\n\n return {\n method: HTTP_VERBS.PUT,\n uri,\n body,\n };\n};\n\nMembersUtil.generateTransferHostMemberOptions = (transfer, moderator, locusUrl) => ({\n moderator,\n locusUrl,\n memberId: transfer,\n});\n\nMembersUtil.generateRemoveMemberOptions = (removal, locusUrl) => ({\n reason: _FORCED_,\n memberId: removal,\n locusUrl,\n});\n\nMembersUtil.generateMuteMemberOptions = (memberId, status, locusUrl, isAudio) => ({\n memberId,\n muted: status,\n locusUrl,\n isAudio,\n});\n\nMembersUtil.generateRaiseHandMemberOptions = (memberId, status, locusUrl) => ({\n memberId,\n raised: status,\n locusUrl,\n});\n\nMembersUtil.generateLowerAllHandsMemberOptions = (requestingParticipantId, locusUrl) => ({\n requestingParticipantId,\n locusUrl,\n});\n\nMembersUtil.getMuteMemberRequestParams = (options) => {\n const property = options.isAudio === false ? 'video' : 'audio';\n const body = {\n [property]: {\n muted: options.muted,\n },\n };\n const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${CONTROLS}`;\n\n return {\n method: HTTP_VERBS.PATCH,\n uri,\n body,\n };\n};\n\nMembersUtil.getRaiseHandMemberRequestParams = (options) => {\n const body = {\n hand: {\n raised: options.raised,\n },\n };\n const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${CONTROLS}`;\n\n return {\n method: HTTP_VERBS.PATCH,\n uri,\n body,\n };\n};\n\nMembersUtil.getLowerAllHandsMemberRequestParams = (options) => {\n const body = {\n hand: {\n raised: false,\n },\n requestingParticipantId: options.requestingParticipantId,\n };\n const uri = `${options.locusUrl}/${CONTROLS}`;\n\n return {\n method: HTTP_VERBS.PATCH,\n uri,\n body,\n };\n};\n\nMembersUtil.getTransferHostToMemberRequestParams = (options) => {\n const body = {\n role: {\n moderator: options.moderator,\n },\n };\n const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${CONTROLS}`;\n\n return {\n method: HTTP_VERBS.PATCH,\n uri,\n body,\n };\n};\n\nMembersUtil.genderateSendDTMFOptions = (url, tones, memberId, locusUrl) => ({\n url,\n tones,\n memberId,\n locusUrl,\n});\n\nMembersUtil.generateSendDTMFRequestParams = ({url, tones, memberId, locusUrl}) => {\n const body = {\n device: {\n url,\n },\n memberId,\n dtmf: {\n correlationId: uuid.v4(),\n tones,\n direction: 'transmit',\n },\n };\n const uri = `${locusUrl}/${PARTICIPANT}/${memberId}/${SEND_DTMF_ENDPOINT}`;\n\n return {\n method: HTTP_VERBS.POST,\n uri,\n body,\n };\n};\n\nMembersUtil.cancelPhoneInviteOptions = (invitee, locusUrl) => ({\n invitee,\n locusUrl,\n});\n\nMembersUtil.generateCancelInviteRequestParams = (options) => {\n const body = {\n actionType: _REMOVE_,\n invitees: [\n {\n address: options.invitee.phoneNumber,\n },\n ],\n };\n const requestParams = {\n method: HTTP_VERBS.PUT,\n uri: options.locusUrl,\n body,\n };\n\n return requestParams;\n};\n\nexport default MembersUtil;\n"],"mappings":"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"}
@@ -196,6 +196,8 @@ export declare const EVENT_TRIGGERS: {
196
196
  MEETING_RINGING_STOP: string;
197
197
  MEETING_SELF_LOBBY_WAITING: string;
198
198
  MEETING_SELF_GUEST_ADMITTED: string;
199
+ MEETING_SELF_VIDEO_MUTED_BY_OTHERS: string;
200
+ MEETING_SELF_VIDEO_UNMUTED_BY_OTHERS: string;
199
201
  MEETING_SELF_MUTED_BY_OTHERS: string;
200
202
  MEETING_SELF_UNMUTED_BY_OTHERS: string;
201
203
  MEETING_SELF_REQUESTED_TO_UNMUTE: string;
@@ -423,6 +425,7 @@ export declare const BREAKOUTS: {
423
425
  ERROR_CODE: {
424
426
  EDIT_LOCK_TOKEN_MISMATCH: number;
425
427
  };
428
+ DEFAULT_DURATION: number;
426
429
  };
427
430
  export declare const LOCUSINFO: {
428
431
  EVENTS: {
@@ -432,8 +435,10 @@ export declare const LOCUSINFO: {
432
435
  CONTROLS_MEETING_BREAKOUT_UPDATED: string;
433
436
  CONTROLS_MEETING_CONTAINER_UPDATED: string;
434
437
  CONTROLS_ENTRY_EXIT_TONE_UPDATED: string;
438
+ CONTROLS_VIDEO_ENABLED_UPDATED: string;
435
439
  SELF_UNADMITTED_GUEST: string;
436
440
  SELF_ADMITTED_GUEST: string;
441
+ SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED: string;
437
442
  SELF_REMOTE_MUTE_STATUS_UPDATED: string;
438
443
  LOCAL_UNMUTE_REQUESTED: string;
439
444
  LOCAL_UNMUTE_REQUIRED: string;
@@ -34,6 +34,13 @@ export default class LocusInfo extends EventsScope {
34
34
  replace: any;
35
35
  url: any;
36
36
  services: any;
37
+ /**
38
+ * Constructor
39
+ * @param {boolean} updateMeeting true if the meeting should be updated
40
+ * @param {object} webex
41
+ * @param {string} meetingId
42
+ * @returns {undefined}
43
+ */
37
44
  constructor(updateMeeting: any, webex: any, meetingId: any);
38
45
  /**
39
46
  * Apply locus delta data to meeting
@@ -653,13 +653,23 @@ export default class Meeting extends StatelessWebexPlugin {
653
653
  phoneNumber: string;
654
654
  }): any;
655
655
  /**
656
- * Admit the guest(s) to the call once they are waiting
656
+ * Admit the guest(s) to the call once they are waiting.
657
+ * If the host/cohost is in a breakout session, the locus url
658
+ * of the session must be provided as the authorizingLocusUrl.
659
+ * Regardless of host/cohost location, the locus Id (lid) in
660
+ * the path should be the locus Id of the main, which means the
661
+ * locus url of the api call must be from the main session.
662
+ * If these loucs urls are not provided, the function will do the check.
657
663
  * @param {Array} memberIds
664
+ * @param {Object} sessionLocusUrls: {authorizingLocusUrl, mainLocusUrl}
658
665
  * @returns {Promise} see #members.admitMembers
659
666
  * @public
660
667
  * @memberof Meeting
661
668
  */
662
- admit(memberIds: Array<any>): any;
669
+ admit(memberIds: Array<any>, sessionLocusUrls?: {
670
+ authorizingLocusUrl: string;
671
+ mainLocusUrl: string;
672
+ }): any;
663
673
  /**
664
674
  * Remove the member from the meeting, boot them
665
675
  * @param {String} memberId
@@ -94,6 +94,22 @@ declare class MuteState {
94
94
  * @returns {Boolean}
95
95
  */
96
96
  isMuted(): any;
97
+ /**
98
+ * Returns true if the user is remotely muted
99
+ *
100
+ * @public
101
+ * @memberof MuteState
102
+ * @returns {Boolean}
103
+ */
104
+ isRemotelyMuted(): any;
105
+ /**
106
+ * Returns true if unmute is allowed
107
+ *
108
+ * @public
109
+ * @memberof MuteState
110
+ * @returns {Boolean}
111
+ */
112
+ isUnmuteAllowed(): any;
97
113
  /**
98
114
  * Returns true if the user is locally muted
99
115
  *
@@ -245,11 +245,15 @@ export default class Members extends StatelessWebexPlugin {
245
245
  /**
246
246
  * Admits waiting members (invited guests to meeting)
247
247
  * @param {Array} memberIds
248
+ * @param {Object} sessionLocusUrls: {authorizingLocusUrl, mainLocusUrl}
248
249
  * @returns {Promise}
249
250
  * @public
250
251
  * @memberof Members
251
252
  */
252
- admitMembers(memberIds: Array<any>): any;
253
+ admitMembers(memberIds: Array<any>, sessionLocusUrls?: {
254
+ authorizingLocusUrl: string;
255
+ mainLocusUrl: string;
256
+ }): any;
253
257
  /**
254
258
  * Removes a member from the meeting
255
259
  * @param {String} memberId
@@ -262,11 +266,12 @@ export default class Members extends StatelessWebexPlugin {
262
266
  * Audio mutes another member in a meeting
263
267
  * @param {String} memberId
264
268
  * @param {boolean} [mute] default true
269
+ * @param {boolean} [isAudio] default true
265
270
  * @returns {Promise}
266
271
  * @public
267
272
  * @memberof Members
268
273
  */
269
- muteMember(memberId: string, mute?: boolean): any;
274
+ muteMember(memberId: string, mute?: boolean, isAudio?: boolean): any;
270
275
  /**
271
276
  * Raise or lower the hand of a member in a meeting
272
277
  * @param {String} memberId
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webex/plugin-meetings",
3
- "version": "3.0.0-beta.43",
3
+ "version": "3.0.0-beta.44",
4
4
  "description": "",
5
5
  "license": "Cisco EULA (https://www.cisco.com/c/en/us/products/end-user-license-agreement.html)",
6
6
  "contributors": [
@@ -32,12 +32,12 @@
32
32
  "build": "yarn run -T tsc --declaration true --declarationDir ./dist/types"
33
33
  },
34
34
  "devDependencies": {
35
- "@webex/plugin-meetings": "3.0.0-beta.43",
36
- "@webex/test-helper-chai": "3.0.0-beta.43",
37
- "@webex/test-helper-mocha": "3.0.0-beta.43",
38
- "@webex/test-helper-mock-webex": "3.0.0-beta.43",
39
- "@webex/test-helper-retry": "3.0.0-beta.43",
40
- "@webex/test-helper-test-users": "3.0.0-beta.43",
35
+ "@webex/plugin-meetings": "3.0.0-beta.44",
36
+ "@webex/test-helper-chai": "3.0.0-beta.44",
37
+ "@webex/test-helper-mocha": "3.0.0-beta.44",
38
+ "@webex/test-helper-mock-webex": "3.0.0-beta.44",
39
+ "@webex/test-helper-retry": "3.0.0-beta.44",
40
+ "@webex/test-helper-test-users": "3.0.0-beta.44",
41
41
  "chai": "^4.3.4",
42
42
  "chai-as-promised": "^7.1.1",
43
43
  "jsdom-global": "3.0.2",
@@ -46,18 +46,18 @@
46
46
  "typescript": "^4.7.4"
47
47
  },
48
48
  "dependencies": {
49
- "@webex/common": "3.0.0-beta.43",
49
+ "@webex/common": "3.0.0-beta.44",
50
50
  "@webex/internal-media-core": "1.35.2",
51
- "@webex/internal-plugin-conversation": "3.0.0-beta.43",
52
- "@webex/internal-plugin-device": "3.0.0-beta.43",
53
- "@webex/internal-plugin-llm": "3.0.0-beta.43",
54
- "@webex/internal-plugin-mercury": "3.0.0-beta.43",
55
- "@webex/internal-plugin-metrics": "3.0.0-beta.43",
56
- "@webex/internal-plugin-support": "3.0.0-beta.43",
57
- "@webex/internal-plugin-user": "3.0.0-beta.43",
58
- "@webex/plugin-people": "3.0.0-beta.43",
59
- "@webex/plugin-rooms": "3.0.0-beta.43",
60
- "@webex/webex-core": "3.0.0-beta.43",
51
+ "@webex/internal-plugin-conversation": "3.0.0-beta.44",
52
+ "@webex/internal-plugin-device": "3.0.0-beta.44",
53
+ "@webex/internal-plugin-llm": "3.0.0-beta.44",
54
+ "@webex/internal-plugin-mercury": "3.0.0-beta.44",
55
+ "@webex/internal-plugin-metrics": "3.0.0-beta.44",
56
+ "@webex/internal-plugin-support": "3.0.0-beta.44",
57
+ "@webex/internal-plugin-user": "3.0.0-beta.44",
58
+ "@webex/plugin-people": "3.0.0-beta.44",
59
+ "@webex/plugin-rooms": "3.0.0-beta.44",
60
+ "@webex/webex-core": "3.0.0-beta.44",
61
61
  "ampersand-collection": "^2.0.2",
62
62
  "bowser": "^2.11.0",
63
63
  "btoa": "^1.2.1",
@@ -35,6 +35,7 @@ const Breakouts = WebexPlugin.extend({
35
35
  url: 'string', // appears from the moment you enable breakouts
36
36
  locusUrl: 'string', // the current locus url
37
37
  breakoutServiceUrl: 'string', // the current breakout resouce url
38
+ mainLocusUrl: 'string', // the locus url of the main session
38
39
  groups: 'array', // appears when create breakouts
39
40
  },
40
41
 
@@ -107,6 +108,10 @@ const Breakouts = WebexPlugin.extend({
107
108
  */
108
109
  locusUrlUpdate(locusUrl) {
109
110
  this.set('locusUrl', locusUrl);
111
+ const {isInMainSession, mainLocusUrl} = this;
112
+ if (isInMainSession || !mainLocusUrl) {
113
+ this.set('mainLocusUrl', locusUrl);
114
+ }
110
115
  },
111
116
 
112
117
  /**
@@ -438,6 +443,7 @@ const Breakouts = WebexPlugin.extend({
438
443
  action,
439
444
  allowBackToMain: false,
440
445
  allowToJoinLater: false,
446
+ duration: BREAKOUTS.DEFAULT_DURATION,
441
447
  ...params,
442
448
  };
443
449
 
package/src/constants.ts CHANGED
@@ -297,6 +297,8 @@ export const EVENT_TRIGGERS = {
297
297
  MEETING_RINGING_STOP: 'meeting:ringingStop',
298
298
  MEETING_SELF_LOBBY_WAITING: 'meeting:self:lobbyWaiting',
299
299
  MEETING_SELF_GUEST_ADMITTED: 'meeting:self:guestAdmitted',
300
+ MEETING_SELF_VIDEO_MUTED_BY_OTHERS: 'meeting:self:videoMutedByOthers',
301
+ MEETING_SELF_VIDEO_UNMUTED_BY_OTHERS: 'meeting:self:videoUnmutedByOthers',
300
302
  MEETING_SELF_MUTED_BY_OTHERS: 'meeting:self:mutedByOthers',
301
303
  MEETING_SELF_UNMUTED_BY_OTHERS: 'meeting:self:unmutedByOthers',
302
304
  MEETING_SELF_REQUESTED_TO_UNMUTE: 'meeting:self:requestedToUnmute',
@@ -554,6 +556,7 @@ export const BREAKOUTS = {
554
556
  ERROR_CODE: {
555
557
  EDIT_LOCK_TOKEN_MISMATCH: 201409024,
556
558
  },
559
+ DEFAULT_DURATION: 60000,
557
560
  };
558
561
 
559
562
  export const LOCUSINFO = {
@@ -564,8 +567,10 @@ export const LOCUSINFO = {
564
567
  CONTROLS_MEETING_BREAKOUT_UPDATED: 'CONTROLS_MEETING_BREAKOUT_UPDATED',
565
568
  CONTROLS_MEETING_CONTAINER_UPDATED: 'CONTROLS_MEETING_CONTAINER_UPDATED',
566
569
  CONTROLS_ENTRY_EXIT_TONE_UPDATED: 'CONTROLS_ENTRY_EXIT_TONE_UPDATED',
570
+ CONTROLS_VIDEO_ENABLED_UPDATED: 'CONTROLS_VIDEO_ENABLED_UPDATED',
567
571
  SELF_UNADMITTED_GUEST: 'SELF_UNADMITTED_GUEST',
568
572
  SELF_ADMITTED_GUEST: 'SELF_ADMITTED_GUEST',
573
+ SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED: 'SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED',
569
574
  SELF_REMOTE_MUTE_STATUS_UPDATED: 'SELF_REMOTE_MUTE_STATUS_UPDATED',
570
575
  LOCAL_UNMUTE_REQUESTED: 'LOCAL_UNMUTE_REQUESTED',
571
576
  LOCAL_UNMUTE_REQUIRED: 'LOCAL_UNMUTE_REQUIRED',
@@ -48,6 +48,10 @@ ControlsUtils.parse = (controls: any) => {
48
48
  : null;
49
49
  }
50
50
 
51
+ if (controls && controls.video) {
52
+ parsedControls.videoEnabled = controls.video.enabled;
53
+ }
54
+
51
55
  return parsedControls;
52
56
  };
53
57
 
@@ -94,6 +98,10 @@ ControlsUtils.getControls = (oldControls: any, newControls: any) => {
94
98
  ),
95
99
 
96
100
  hasBreakoutChanged: !isEqual(previous?.breakout, current?.breakout),
101
+
102
+ hasVideoEnabledChanged:
103
+ newControls.video?.enabled !== undefined &&
104
+ !isEqual(previous?.videoEnabled, current?.videoEnabled),
97
105
  },
98
106
  };
99
107
  };
@@ -64,6 +64,13 @@ export default class LocusInfo extends EventsScope {
64
64
  url: any;
65
65
  services: any;
66
66
 
67
+ /**
68
+ * Constructor
69
+ * @param {boolean} updateMeeting true if the meeting should be updated
70
+ * @param {object} webex
71
+ * @param {string} meetingId
72
+ * @returns {undefined}
73
+ */
67
74
  constructor(updateMeeting, webex, meetingId) {
68
75
  super();
69
76
  this.parsedLocus = {
@@ -685,6 +692,7 @@ export default class LocusInfo extends EventsScope {
685
692
  hasTranscribeChanged,
686
693
  hasEntryExitToneChanged,
687
694
  hasBreakoutChanged,
695
+ hasVideoEnabledChanged,
688
696
  },
689
697
  current,
690
698
  } = ControlsUtils.getControls(this.controls, controls);
@@ -766,6 +774,8 @@ export default class LocusInfo extends EventsScope {
766
774
  if (hasEntryExitToneChanged) {
767
775
  const {entryExitTone} = current;
768
776
 
777
+ this.updateMeeting({entryExitTone});
778
+
769
779
  this.emitScoped(
770
780
  {
771
781
  file: 'locus-info',
@@ -776,8 +786,26 @@ export default class LocusInfo extends EventsScope {
776
786
  entryExitTone,
777
787
  }
778
788
  );
789
+ }
779
790
 
780
- this.updateMeeting({entryExitTone});
791
+ // videoEnabled is handled differently than other controls,
792
+ // to fit with audio mute status logic
793
+ if (hasVideoEnabledChanged) {
794
+ const {videoEnabled} = current;
795
+
796
+ this.updateMeeting({unmuteVideoAllowed: videoEnabled});
797
+
798
+ this.emitScoped(
799
+ {
800
+ file: 'locus-info',
801
+ function: 'updateControls',
802
+ },
803
+ LOCUSINFO.EVENTS.CONTROLS_VIDEO_ENABLED_UPDATED,
804
+ {
805
+ // muted: not part of locus.controls
806
+ unmuteAllowed: videoEnabled,
807
+ }
808
+ );
781
809
  }
782
810
 
783
811
  this.controls = controls;
@@ -1120,6 +1148,19 @@ export default class LocusInfo extends EventsScope {
1120
1148
  self
1121
1149
  );
1122
1150
  }
1151
+ if (parsedSelves.updates.isVideoMutedByOthersChanged) {
1152
+ this.emitScoped(
1153
+ {
1154
+ file: 'locus-info',
1155
+ function: 'updateSelf',
1156
+ },
1157
+ LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED,
1158
+ {
1159
+ muted: parsedSelves.current.remoteVideoMuted,
1160
+ // unmuteAllowed: not part of .self
1161
+ }
1162
+ );
1163
+ }
1123
1164
  if (parsedSelves.updates.localAudioUnmuteRequiredByServer) {
1124
1165
  this.emitScoped(
1125
1166
  {
@@ -32,6 +32,7 @@ SelfUtils.parse = (self: any, deviceId: string) => {
32
32
  const pstnDevices = self.devices.filter((device) => PSTN_DEVICE_TYPE === device.deviceType);
33
33
 
34
34
  return {
35
+ remoteVideoMuted: SelfUtils.getRemoteVideoMuted(self),
35
36
  remoteMuted: SelfUtils.getRemoteMuted(self),
36
37
  unmuteAllowed: SelfUtils.getUnmuteAllowed(self),
37
38
  localAudioUnmuteRequested: SelfUtils.getLocalAudioUnmuteRequested(self),
@@ -93,6 +94,7 @@ SelfUtils.getSelves = (oldSelf, newSelf, deviceId) => {
93
94
 
94
95
  updates.isUserUnadmitted = SelfUtils.isUserUnadmitted(current);
95
96
  updates.isUserAdmitted = SelfUtils.isUserAdmitted(previous, current);
97
+ updates.isVideoMutedByOthersChanged = SelfUtils.videoMutedByOthersChanged(previous, current);
96
98
  updates.isMutedByOthersChanged = SelfUtils.mutedByOthersChanged(previous, current);
97
99
  updates.localAudioUnmuteRequestedByServer = SelfUtils.localAudioUnmuteRequestedByServer(
98
100
  previous,
@@ -236,6 +238,19 @@ SelfUtils.getSelfIdentity = (self: any) => {
236
238
  return self.person.id;
237
239
  };
238
240
 
241
+ /**
242
+ * get the "remote video mute" property from the self object
243
+ * @param {Object} self
244
+ * @returns {Boolean}
245
+ */
246
+ SelfUtils.getRemoteVideoMuted = (self: any) => {
247
+ if (!self || !self.controls || !self.controls.video) {
248
+ return null;
249
+ }
250
+
251
+ return self.controls.video.muted;
252
+ };
253
+
239
254
  /**
240
255
  * get the "remote mute" property from the self object
241
256
  * @param {Object} self
@@ -351,6 +366,25 @@ SelfUtils.isUserAdmitted = (oldSelf: object, changedSelf: object) => {
351
366
  return SelfUtils.isLocusUserUnadmitted(oldSelf) && SelfUtils.isLocusUserAdmitted(changedSelf);
352
367
  };
353
368
 
369
+ SelfUtils.videoMutedByOthersChanged = (oldSelf, changedSelf) => {
370
+ if (!changedSelf) {
371
+ throw new ParameterError(
372
+ 'New self must be defined to determine if self was video muted by others.'
373
+ );
374
+ }
375
+
376
+ if (!oldSelf || oldSelf.remoteVideoMuted === null) {
377
+ if (changedSelf.remoteVideoMuted) {
378
+ return true; // this happens when host disables "Allow start video"
379
+ }
380
+
381
+ // we don't want to be sending the 'meeting:self:videoUnmutedByOthers' notification on meeting join
382
+ return false;
383
+ }
384
+
385
+ return oldSelf.remoteVideoMuted !== changedSelf.remoteVideoMuted;
386
+ };
387
+
354
388
  SelfUtils.mutedByOthersChanged = (oldSelf, changedSelf) => {
355
389
  if (!changedSelf) {
356
390
  throw new ParameterError('New self must be defined to determine if self was muted by others.');
@@ -17,7 +17,6 @@ import {
17
17
 
18
18
  import {
19
19
  MeetingNotActiveError,
20
- createMeetingsError,
21
20
  UserInLobbyError,
22
21
  NoMediaEstablishedYetError,
23
22
  UserNotJoinedError,
@@ -2408,6 +2407,30 @@ export default class Meeting extends StatelessWebexPlugin {
2408
2407
  );
2409
2408
  }
2410
2409
  });
2410
+
2411
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED, (payload) => {
2412
+ if (payload) {
2413
+ if (this.video) {
2414
+ payload.muted = payload.muted ?? this.video.isRemotelyMuted();
2415
+ payload.unmuteAllowed = payload.unmuteAllowed ?? this.video.isUnmuteAllowed();
2416
+ this.video.handleServerRemoteMuteUpdate(payload.muted, payload.unmuteAllowed);
2417
+ }
2418
+ Trigger.trigger(
2419
+ this,
2420
+ {
2421
+ file: 'meeting/index',
2422
+ function: 'setUpLocusInfoSelfListener',
2423
+ },
2424
+ payload.muted
2425
+ ? EVENT_TRIGGERS.MEETING_SELF_VIDEO_MUTED_BY_OTHERS
2426
+ : EVENT_TRIGGERS.MEETING_SELF_VIDEO_UNMUTED_BY_OTHERS,
2427
+ {
2428
+ payload,
2429
+ }
2430
+ );
2431
+ }
2432
+ });
2433
+
2411
2434
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED, (payload) => {
2412
2435
  if (payload) {
2413
2436
  if (this.audio) {
@@ -2695,14 +2718,32 @@ export default class Meeting extends StatelessWebexPlugin {
2695
2718
  }
2696
2719
 
2697
2720
  /**
2698
- * Admit the guest(s) to the call once they are waiting
2721
+ * Admit the guest(s) to the call once they are waiting.
2722
+ * If the host/cohost is in a breakout session, the locus url
2723
+ * of the session must be provided as the authorizingLocusUrl.
2724
+ * Regardless of host/cohost location, the locus Id (lid) in
2725
+ * the path should be the locus Id of the main, which means the
2726
+ * locus url of the api call must be from the main session.
2727
+ * If these loucs urls are not provided, the function will do the check.
2699
2728
  * @param {Array} memberIds
2729
+ * @param {Object} sessionLocusUrls: {authorizingLocusUrl, mainLocusUrl}
2700
2730
  * @returns {Promise} see #members.admitMembers
2701
2731
  * @public
2702
2732
  * @memberof Meeting
2703
2733
  */
2704
- public admit(memberIds: Array<any>) {
2705
- return this.members.admitMembers(memberIds);
2734
+ public admit(
2735
+ memberIds: Array<any>,
2736
+ sessionLocusUrls?: {authorizingLocusUrl: string; mainLocusUrl: string}
2737
+ ) {
2738
+ let locusUrls = sessionLocusUrls;
2739
+ if (!locusUrls) {
2740
+ const {locusUrl, mainLocusUrl} = this.breakouts;
2741
+ if (locusUrl && mainLocusUrl) {
2742
+ locusUrls = {authorizingLocusUrl: locusUrl, mainLocusUrl};
2743
+ }
2744
+ }
2745
+
2746
+ return this.members.admitMembers(memberIds, locusUrls);
2706
2747
  }
2707
2748
 
2708
2749
  /**
@@ -52,9 +52,9 @@ class MuteState {
52
52
  },
53
53
  server: {
54
54
  localMute: false,
55
- // initial values available only for audio (REMOTE_MUTE_VIDEO_MISSING_IMPLEMENTATION)
56
- remoteMute: type === AUDIO ? meeting.remoteMuted : false,
57
- unmuteAllowed: type === AUDIO ? meeting.unmuteAllowed : true,
55
+ // because remoteVideoMuted and unmuteVideoAllowed are updated seperately, they might be undefined
56
+ remoteMute: type === AUDIO ? meeting.remoteMuted : meeting.remoteVideoMuted ?? false,
57
+ unmuteAllowed: type === AUDIO ? meeting.unmuteAllowed : meeting.unmuteVideoAllowed ?? true,
58
58
  },
59
59
  syncToServerInProgress: false,
60
60
  };
@@ -241,35 +241,28 @@ class MuteState {
241
241
  * @returns {Promise}
242
242
  */
243
243
  private sendRemoteMuteRequestToServer(meeting?: any) {
244
- if (this.type === AUDIO) {
245
- const remoteMute = this.state.client.localMute;
244
+ const remoteMute = this.state.client.localMute;
246
245
 
247
- LoggerProxy.logger.info(
248
- `Meeting:muteState#sendRemoteMuteRequestToServer --> ${this.type}: sending remote mute:${remoteMute} to server`
249
- );
246
+ LoggerProxy.logger.info(
247
+ `Meeting:muteState#sendRemoteMuteRequestToServer --> ${this.type}: sending remote mute:${remoteMute} to server`
248
+ );
250
249
 
251
- return meeting.members
252
- .muteMember(meeting.members.selfId, remoteMute)
253
- .then(() => {
254
- LoggerProxy.logger.info(
255
- `Meeting:muteState#sendRemoteMuteRequestToServer --> ${this.type}: remote mute:${remoteMute} applied to server`
256
- );
257
-
258
- this.state.server.remoteMute = remoteMute;
259
- })
260
- .catch((remoteUpdateError) => {
261
- LoggerProxy.logger.warn(
262
- `Meeting:muteState#sendRemoteMuteRequestToServer --> ${this.type}: failed to apply remote mute ${remoteMute} to server: ${remoteUpdateError}`
263
- );
264
-
265
- return Promise.reject(remoteUpdateError);
266
- });
267
- }
250
+ return meeting.members
251
+ .muteMember(meeting.members.selfId, remoteMute, this.type === AUDIO)
252
+ .then(() => {
253
+ LoggerProxy.logger.info(
254
+ `Meeting:muteState#sendRemoteMuteRequestToServer --> ${this.type}: remote mute:${remoteMute} applied to server`
255
+ );
268
256
 
269
- // for now we don't need to support remote muting of video (REMOTE_MUTE_VIDEO_MISSING_IMPLEMENTATION)
270
- this.state.server.remoteMute = this.state.client.localMute;
257
+ this.state.server.remoteMute = remoteMute;
258
+ })
259
+ .catch((remoteUpdateError) => {
260
+ LoggerProxy.logger.warn(
261
+ `Meeting:muteState#sendRemoteMuteRequestToServer --> ${this.type}: failed to apply remote mute ${remoteMute} to server: ${remoteUpdateError}`
262
+ );
271
263
 
272
- return Promise.resolve();
264
+ return Promise.reject(remoteUpdateError);
265
+ });
273
266
  }
274
267
 
275
268
  /**
@@ -285,8 +278,12 @@ class MuteState {
285
278
  LoggerProxy.logger.info(
286
279
  `Meeting:muteState#handleServerRemoteMuteUpdate --> ${this.type}: updating server remoteMute to (${muted})`
287
280
  );
288
- this.state.server.remoteMute = muted;
289
- this.state.server.unmuteAllowed = unmuteAllowed;
281
+ if (muted !== undefined) {
282
+ this.state.server.remoteMute = muted;
283
+ }
284
+ if (unmuteAllowed !== undefined) {
285
+ this.state.server.unmuteAllowed = unmuteAllowed;
286
+ }
290
287
  }
291
288
 
292
289
  /**
@@ -330,6 +327,28 @@ class MuteState {
330
327
  );
331
328
  }
332
329
 
330
+ /**
331
+ * Returns true if the user is remotely muted
332
+ *
333
+ * @public
334
+ * @memberof MuteState
335
+ * @returns {Boolean}
336
+ */
337
+ public isRemotelyMuted() {
338
+ return this.state.server.remoteMute;
339
+ }
340
+
341
+ /**
342
+ * Returns true if unmute is allowed
343
+ *
344
+ * @public
345
+ * @memberof MuteState
346
+ * @returns {Boolean}
347
+ */
348
+ public isUnmuteAllowed() {
349
+ return this.state.server.unmuteAllowed;
350
+ }
351
+
333
352
  /**
334
353
  * Returns true if the user is locally muted
335
354
  *