@webex/plugin-meetings 3.9.0 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +8 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/controls-options-manager/index.js +22 -5
  6. package/dist/controls-options-manager/index.js.map +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/interceptors/index.js +7 -0
  10. package/dist/interceptors/index.js.map +1 -1
  11. package/dist/interceptors/locusRouteToken.js +116 -0
  12. package/dist/interceptors/locusRouteToken.js.map +1 -0
  13. package/dist/interpretation/index.js +1 -1
  14. package/dist/interpretation/siLanguage.js +1 -1
  15. package/dist/locus-info/controlsUtils.js +11 -2
  16. package/dist/locus-info/controlsUtils.js.map +1 -1
  17. package/dist/locus-info/index.js +56 -14
  18. package/dist/locus-info/index.js.map +1 -1
  19. package/dist/locus-info/parser.js +4 -1
  20. package/dist/locus-info/parser.js.map +1 -1
  21. package/dist/media/properties.js +53 -5
  22. package/dist/media/properties.js.map +1 -1
  23. package/dist/meeting/in-meeting-actions.js +8 -0
  24. package/dist/meeting/in-meeting-actions.js.map +1 -1
  25. package/dist/meeting/index.js +339 -185
  26. package/dist/meeting/index.js.map +1 -1
  27. package/dist/meeting/muteState.js +2 -5
  28. package/dist/meeting/muteState.js.map +1 -1
  29. package/dist/meeting/request.js +177 -14
  30. package/dist/meeting/request.js.map +1 -1
  31. package/dist/meeting/util.js +39 -11
  32. package/dist/meeting/util.js.map +1 -1
  33. package/dist/meeting-info/meeting-info-v2.js +29 -21
  34. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  35. package/dist/meetings/index.js +31 -25
  36. package/dist/meetings/index.js.map +1 -1
  37. package/dist/member/index.js +9 -0
  38. package/dist/member/index.js.map +1 -1
  39. package/dist/member/types.js.map +1 -1
  40. package/dist/member/util.js +10 -0
  41. package/dist/member/util.js.map +1 -1
  42. package/dist/members/collection.js +13 -0
  43. package/dist/members/collection.js.map +1 -1
  44. package/dist/members/index.js +42 -20
  45. package/dist/members/index.js.map +1 -1
  46. package/dist/members/util.js +7 -2
  47. package/dist/members/util.js.map +1 -1
  48. package/dist/metrics/constants.js +2 -1
  49. package/dist/metrics/constants.js.map +1 -1
  50. package/dist/reachability/index.js +3 -3
  51. package/dist/reachability/index.js.map +1 -1
  52. package/dist/types/constants.d.ts +7 -0
  53. package/dist/types/controls-options-manager/index.d.ts +9 -1
  54. package/dist/types/interceptors/index.d.ts +2 -1
  55. package/dist/types/interceptors/locusRouteToken.d.ts +38 -0
  56. package/dist/types/locus-info/index.d.ts +56 -2
  57. package/dist/types/media/properties.d.ts +21 -0
  58. package/dist/types/meeting/in-meeting-actions.d.ts +8 -0
  59. package/dist/types/meeting/index.d.ts +41 -1
  60. package/dist/types/meeting/request.d.ts +42 -0
  61. package/dist/types/meeting/util.d.ts +13 -3
  62. package/dist/types/meeting-info/meeting-info-v2.d.ts +6 -3
  63. package/dist/types/meetings/index.d.ts +3 -1
  64. package/dist/types/member/index.d.ts +1 -0
  65. package/dist/types/member/types.d.ts +1 -0
  66. package/dist/types/member/util.d.ts +5 -0
  67. package/dist/types/members/collection.d.ts +6 -0
  68. package/dist/types/members/index.d.ts +12 -2
  69. package/dist/types/members/util.d.ts +6 -3
  70. package/dist/types/metrics/constants.d.ts +1 -0
  71. package/dist/webinar/index.js +1 -1
  72. package/package.json +23 -23
  73. package/src/constants.ts +10 -0
  74. package/src/controls-options-manager/index.ts +26 -5
  75. package/src/index.ts +2 -1
  76. package/src/interceptors/index.ts +2 -1
  77. package/src/interceptors/locusRouteToken.ts +80 -0
  78. package/src/locus-info/controlsUtils.ts +18 -0
  79. package/src/locus-info/index.ts +99 -17
  80. package/src/locus-info/parser.ts +5 -1
  81. package/src/media/properties.ts +43 -0
  82. package/src/meeting/in-meeting-actions.ts +16 -0
  83. package/src/meeting/index.ts +204 -24
  84. package/src/meeting/muteState.ts +2 -6
  85. package/src/meeting/request.ts +141 -0
  86. package/src/meeting/util.ts +50 -20
  87. package/src/meeting-info/meeting-info-v2.ts +24 -5
  88. package/src/meetings/index.ts +9 -3
  89. package/src/member/index.ts +10 -0
  90. package/src/member/types.ts +1 -0
  91. package/src/member/util.ts +14 -0
  92. package/src/members/collection.ts +11 -0
  93. package/src/members/index.ts +38 -5
  94. package/src/members/util.ts +18 -2
  95. package/src/metrics/constants.ts +1 -0
  96. package/src/reachability/index.ts +3 -3
  97. package/test/unit/spec/common/browser-detection.js +0 -24
  98. package/test/unit/spec/controls-options-manager/index.js +47 -0
  99. package/test/unit/spec/fixture/locus.js +1 -0
  100. package/test/unit/spec/interceptors/locusRouteToken.ts +87 -0
  101. package/test/unit/spec/locus-info/index.js +91 -15
  102. package/test/unit/spec/locus-info/parser.js +3 -2
  103. package/test/unit/spec/media/properties.ts +137 -0
  104. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -0
  105. package/test/unit/spec/meeting/index.js +398 -30
  106. package/test/unit/spec/meeting/muteState.js +32 -6
  107. package/test/unit/spec/meeting/request.js +21 -0
  108. package/test/unit/spec/meeting/utils.js +49 -17
  109. package/test/unit/spec/meeting-info/meetinginfov2.js +8 -3
  110. package/test/unit/spec/meetings/index.js +10 -5
  111. package/test/unit/spec/member/util.js +24 -0
  112. package/test/unit/spec/members/collection.js +120 -0
  113. package/test/unit/spec/members/index.js +72 -3
  114. package/test/unit/spec/members/request.js +55 -0
  115. package/test/unit/spec/members/utils.js +116 -14
  116. package/test/unit/spec/reachability/index.ts +158 -3
  117. package/test/unit/spec/roap/turnDiscovery.ts +3 -3
@@ -371,6 +371,7 @@ export default class MeetingInfoV2 {
371
371
  * @param {String} conversationUrl conversationUrl to start adhoc meeting on
372
372
  * @param {String} installedOrgID org ID of user's machine
373
373
  * @param {Boolean} enableStaticMeetingLink whether or not to enable static meeting link
374
+ * @param {String} classificationId need it to start adhoc meeting if space support classification
374
375
  * @returns {Promise} returns a meeting info object
375
376
  * @public
376
377
  * @memberof MeetingInfo
@@ -379,7 +380,8 @@ export default class MeetingInfoV2 {
379
380
  conversationUrl: string,
380
381
  installedOrgID?: string,
381
382
  // setting this to true enables static meeting link
382
- enableStaticMeetingLink = false
383
+ enableStaticMeetingLink = false,
384
+ classificationId = undefined
383
385
  ) {
384
386
  const getInvitees = (particpants = []) => {
385
387
  const invitees = [];
@@ -407,6 +409,7 @@ export default class MeetingInfoV2 {
407
409
  invitees: getInvitees(conversation.participants?.items),
408
410
  installedOrgID,
409
411
  schedule: enableStaticMeetingLink,
412
+ classificationId,
410
413
  };
411
414
 
412
415
  if (installedOrgID) {
@@ -429,16 +432,26 @@ export default class MeetingInfoV2 {
429
432
  * Creates adhoc space meetings for a space by fetching the conversation infomation
430
433
  * @param {String} conversationUrl conversationUrl to start adhoc meeting on
431
434
  * @param {String} installedOrgID org ID of user's machine
435
+ * @param {String} classificationId if space is support classification, it needs provide it during start instant meeting
432
436
  * @returns {Promise} returns a meeting info object
433
437
  * @public
434
438
  * @memberof MeetingInfo
435
439
  */
436
- async createAdhocSpaceMeeting(conversationUrl: string, installedOrgID?: string) {
440
+ async createAdhocSpaceMeeting(
441
+ conversationUrl: string,
442
+ installedOrgID?: string,
443
+ classificationId?: string
444
+ ) {
437
445
  if (!this.webex.meetings.preferredWebexSite) {
438
446
  throw Error('No preferred webex site found');
439
447
  }
440
448
 
441
- return this.createAdhocSpaceMeetingOrEnableStaticMeetingLink(conversationUrl, installedOrgID)
449
+ return this.createAdhocSpaceMeetingOrEnableStaticMeetingLink(
450
+ conversationUrl,
451
+ installedOrgID,
452
+ false,
453
+ classificationId
454
+ )
442
455
  .then((requestResult) => {
443
456
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADHOC_MEETING_SUCCESS);
444
457
 
@@ -618,6 +631,7 @@ export default class MeetingInfoV2 {
618
631
  * @param {Object} options
619
632
  * @param {String} registrationId
620
633
  * @param {String} fullSiteUrl
634
+ * @param {String} classificationId
621
635
  * @returns {Promise} returns a meeting info object
622
636
  * @public
623
637
  * @memberof MeetingInfo
@@ -635,7 +649,8 @@ export default class MeetingInfoV2 {
635
649
  extraParams: object = {},
636
650
  options: {meetingId?: string; sendCAevents?: boolean} = {},
637
651
  registrationId: string = null,
638
- fullSiteUrl: string = null
652
+ fullSiteUrl: string = null,
653
+ classificationId: string = null
639
654
  ) {
640
655
  const {meetingId, sendCAevents} = options;
641
656
 
@@ -650,7 +665,11 @@ export default class MeetingInfoV2 {
650
665
  this.webex.config.meetings.experimental.enableAdhocMeetings &&
651
666
  this.webex.meetings.preferredWebexSite
652
667
  ) {
653
- return this.createAdhocSpaceMeeting(destinationType.destination, installedOrgID);
668
+ return this.createAdhocSpaceMeeting(
669
+ destinationType.destination,
670
+ installedOrgID,
671
+ classificationId
672
+ );
654
673
  }
655
674
 
656
675
  const body = await MeetingInfoUtil.getRequestBody({
@@ -1331,6 +1331,7 @@ export default class Meetings extends WebexPlugin {
1331
1331
  * @param {Object} [meetingInfo] - Pre-fetched complete meeting info
1332
1332
  * @param {String} [meetingLookupUrl] - meeting info prefetch url
1333
1333
  * @param {string} sessionCorrelationId - the optional specified sessionCorrelationId (callStateForMetrics.sessionCorrelationId) can be provided instead
1334
+ * @param {String} classificationId - If space support classification, it will provide it while start instant meeting
1334
1335
  * @returns {Promise<Meeting>} A new Meeting.
1335
1336
  * @public
1336
1337
  * @memberof Meetings
@@ -1345,7 +1346,8 @@ export default class Meetings extends WebexPlugin {
1345
1346
  callStateForMetrics: CallStateForMetrics = undefined,
1346
1347
  meetingInfo = undefined,
1347
1348
  meetingLookupUrl = undefined,
1348
- sessionCorrelationId: string = undefined
1349
+ sessionCorrelationId: string = undefined,
1350
+ classificationId: string = undefined
1349
1351
  ) {
1350
1352
  // Validate meeting information based on the provided destination and
1351
1353
  // type. This must be performed prior to determining if the meeting is
@@ -1415,7 +1417,8 @@ export default class Meetings extends WebexPlugin {
1415
1417
  callStateForMetrics,
1416
1418
  failOnMissingMeetingInfo,
1417
1419
  meetingInfo,
1418
- meetingLookupUrl
1420
+ meetingLookupUrl,
1421
+ classificationId
1419
1422
  ).then((createdMeeting: any) => {
1420
1423
  // If the meeting was successfully created.
1421
1424
  if (createdMeeting && createdMeeting.on) {
@@ -1529,6 +1532,7 @@ export default class Meetings extends WebexPlugin {
1529
1532
  * @param {Boolean} failOnMissingMeetingInfo - whether to throw an error if meeting info fails to fetch (for calls that are not 1:1 or content share)
1530
1533
  * @param {Object} [meetingInfo] - Pre-fetched complete meeting info
1531
1534
  * @param {String} [meetingLookupUrl] - meeting info prefetch url
1535
+ * @param {String} classificationId see create()
1532
1536
  * @returns {Promise} a new meeting instance complete with meeting info and destination
1533
1537
  * @private
1534
1538
  * @memberof Meetings
@@ -1541,7 +1545,8 @@ export default class Meetings extends WebexPlugin {
1541
1545
  callStateForMetrics: CallStateForMetrics = undefined,
1542
1546
  failOnMissingMeetingInfo = false,
1543
1547
  meetingInfo = undefined,
1544
- meetingLookupUrl = undefined
1548
+ meetingLookupUrl = undefined,
1549
+ classificationId = undefined
1545
1550
  ) {
1546
1551
  const meeting = new Meeting(
1547
1552
  {
@@ -1589,6 +1594,7 @@ export default class Meetings extends WebexPlugin {
1589
1594
  // @ts-ignore
1590
1595
  const {enableUnifiedMeetings} = this.config.experimental;
1591
1596
  const meetingInfoOptions = {
1597
+ classificationId,
1592
1598
  extraParams: infoExtraParams,
1593
1599
  sendCAevents: !!callStateForMetrics?.correlationId, // if client sends correlation id as argument of public create(), then it means that this meeting creation is part of a pre-join intent from user
1594
1600
  };
@@ -42,6 +42,7 @@ export default class Member {
42
42
  status: any;
43
43
  supportsBreakouts: boolean;
44
44
  supportsInterpretation: boolean;
45
+ supportsSingleUserAutoEndMeeting: boolean;
45
46
  supportLiveAnnotation: boolean;
46
47
  type: any;
47
48
  namespace = MEETINGS;
@@ -130,6 +131,13 @@ export default class Member {
130
131
  * @memberof Member
131
132
  */
132
133
  this.supportsBreakouts = null;
134
+ /**
135
+ * @instance
136
+ * @type {Boolean}
137
+ * @public
138
+ * @memberof Member
139
+ */
140
+ this.supportsSingleUserAutoEndMeeting = null;
133
141
  /**
134
142
  * @instance
135
143
  * @type {Boolean}
@@ -339,6 +347,8 @@ export default class Member {
339
347
  this.isVideoMuted = MemberUtil.isVideoMuted(participant);
340
348
  this.isHandRaised = MemberUtil.isHandRaised(participant);
341
349
  this.supportsBreakouts = MemberUtil.isBreakoutsSupported(participant);
350
+ this.supportsSingleUserAutoEndMeeting =
351
+ MemberUtil.isSupportsSingleUserAutoEndMeeting(participant);
342
352
  this.supportsInterpretation = MemberUtil.isInterpretationSupported(participant);
343
353
  this.supportLiveAnnotation = MemberUtil.isLiveAnnotationSupported(participant);
344
354
  this.isGuest = MemberUtil.isGuest(participant);
@@ -109,4 +109,5 @@ export interface Participant {
109
109
  status: ParticipantMediaStatus;
110
110
  type: string;
111
111
  url: ParticipantUrl;
112
+ isRemoved: boolean; // JS-SDK internal field to indicate in updates when the participant is removed
112
113
  }
@@ -207,6 +207,20 @@ const MemberUtil = {
207
207
  return !participant.doesNotSupportBreakouts;
208
208
  },
209
209
 
210
+ /**
211
+ * @param {Object} participant - The locus participant object.
212
+ * @returns {Boolean}
213
+ */
214
+ isSupportsSingleUserAutoEndMeeting: (participant) => {
215
+ if (!participant) {
216
+ throw new ParameterError(
217
+ 'Single user auto end meeting support could not be processed, participant is undefined.'
218
+ );
219
+ }
220
+
221
+ return !participant.doesNotSupportSingleUserAutoEndMeeting;
222
+ },
223
+
210
224
  /**
211
225
  * @param {Object} participant - The locus participant object.
212
226
  * @returns {Boolean}
@@ -39,6 +39,17 @@ export default class MembersCollection {
39
39
  return this.members;
40
40
  }
41
41
 
42
+ /**
43
+ * Removes a member from the collection
44
+ * @param {String} id
45
+ * @returns {void}
46
+ */
47
+ remove(id: string) {
48
+ if (this.members[id]) {
49
+ delete this.members[id];
50
+ }
51
+ }
52
+
42
53
  /**
43
54
  * @returns {void}
44
55
  * reset members
@@ -74,7 +74,11 @@ import {Invitee} from '../meeting/type';
74
74
  * @memberof Members
75
75
  */
76
76
 
77
- type UpdatedMembers = {added: Array<Member>; updated: Array<Member>};
77
+ type UpdatedMembers = {
78
+ added: Array<Member>;
79
+ updated: Array<Member>;
80
+ removedIds?: Array<string>; // removed member ids
81
+ };
78
82
  /**
79
83
  * @class Members
80
84
  */
@@ -384,12 +388,18 @@ export default class Members extends StatelessWebexPlugin {
384
388
  * when new participant updates come in, both delta and full participants, update them in members collection
385
389
  * delta object in the event will have {updated, added} and full will be the full membersCollection
386
390
  * @param {Object} payload
387
- * @param {Object} payload.participants
391
+ * @param {Object} payload.participants new/updated participants
392
+ * @param {Boolean} payload.isReplace whether to replace the whole members collection
393
+ * @param {Object} payload.removedParticipantIds ids of the removed participants
388
394
  * @returns {undefined}
389
395
  * @private
390
396
  * @memberof Members
391
397
  */
392
- locusParticipantsUpdate(payload: {participants: object; isReplace?: boolean}) {
398
+ locusParticipantsUpdate(payload: {
399
+ participants: object;
400
+ isReplace?: boolean;
401
+ removedParticipantIds?: Array<string>;
402
+ }) {
393
403
  if (payload) {
394
404
  if (payload.isReplace) {
395
405
  this.clearMembers();
@@ -547,10 +557,22 @@ export default class Members extends StatelessWebexPlugin {
547
557
  private handleMembersUpdate(membersUpdate: UpdatedMembers) {
548
558
  this.constructMembers(membersUpdate.updated, true);
549
559
  this.constructMembers(membersUpdate.added, false);
560
+ this.removeMembers(membersUpdate.removedIds);
550
561
 
551
562
  return this.membersCollection.getAll();
552
563
  }
553
564
 
565
+ /**
566
+ * removes members from the collection
567
+ * @param {Array<string>} removedMembers removed members ids
568
+ * @returns {void}
569
+ */
570
+ private removeMembers(removedMembers: Array<string>) {
571
+ removedMembers.forEach((memberId) => {
572
+ this.membersCollection.remove(memberId);
573
+ });
574
+ }
575
+
554
576
  /**
555
577
  * set members to the member collection from each updated/added lists as passed in
556
578
  * @param {Array} list
@@ -600,6 +622,10 @@ export default class Members extends StatelessWebexPlugin {
600
622
  }
601
623
  const memberUpdate = this.update(payload.participants);
602
624
 
625
+ // this code depends on memberIds being the same as participantIds
626
+ // if MemberUtil.extractId() ever changes, this will need to be updated
627
+ memberUpdate.removedIds = payload.removedParticipantIds || [];
628
+
603
629
  return memberUpdate;
604
630
  }
605
631
 
@@ -1181,11 +1207,17 @@ export default class Members extends StatelessWebexPlugin {
1181
1207
  * @param {string} memberId - id of the participant who is receiving request
1182
1208
  * @param {string} requestingParticipantId - id of the participant who is sending request (optional)
1183
1209
  * @param {string} [alias] - alias name
1210
+ * @param {string} [suffix] - name suffix (optional)
1184
1211
  * @returns {Promise}
1185
1212
  * @public
1186
1213
  * @memberof Members
1187
1214
  */
1188
- public editDisplayName(memberId: string, requestingParticipantId: string, alias: string) {
1215
+ public editDisplayName(
1216
+ memberId: string,
1217
+ requestingParticipantId: string,
1218
+ alias: string,
1219
+ suffix?: string
1220
+ ) {
1189
1221
  if (!this.locusUrl) {
1190
1222
  return Promise.reject(
1191
1223
  new ParameterError(
@@ -1205,7 +1237,8 @@ export default class Members extends StatelessWebexPlugin {
1205
1237
  memberId,
1206
1238
  requestingParticipantId,
1207
1239
  alias,
1208
- locusUrl
1240
+ locusUrl,
1241
+ suffix
1209
1242
  );
1210
1243
 
1211
1244
  return this.membersRequest.editDisplayNameMember(options);
@@ -190,13 +190,21 @@ const MembersUtil = {
190
190
  * @param {String} requestingParticipantId id of the participant who is sending request (optional)
191
191
  * @param {String} alias alias name
192
192
  * @param {String} locusUrl url
193
+ * @param {String} suffix optional suffix
193
194
  * @returns {Object} consists of {memberID: string, requestingParticipantId: string, alias: string, locusUrl: string}
194
195
  */
195
- generateEditDisplayNameMemberOptions: (memberId, requestingParticipantId, alias, locusUrl) => ({
196
+ generateEditDisplayNameMemberOptions: (
196
197
  memberId,
197
198
  requestingParticipantId,
198
199
  alias,
199
200
  locusUrl,
201
+ suffix
202
+ ) => ({
203
+ memberId,
204
+ requestingParticipantId,
205
+ alias,
206
+ locusUrl,
207
+ suffix,
200
208
  }),
201
209
 
202
210
  getMuteMemberRequestParams: (options) => {
@@ -301,10 +309,18 @@ const MembersUtil = {
301
309
  * @returns {Object} request parameters (method, uri, body) needed to make a editDisplayName request
302
310
  */
303
311
  editDisplayNameMemberRequestParams: (options) => {
304
- const body = {
312
+ const body: {
313
+ aliasValue: string;
314
+ requestingParticipantId: string;
315
+ suffixValue?: string;
316
+ } = {
305
317
  aliasValue: options.alias,
306
318
  requestingParticipantId: options.requestingParticipantId,
307
319
  };
320
+
321
+ if (options.suffix !== undefined) {
322
+ body.suffixValue = options.suffix;
323
+ }
308
324
  const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${ALIAS}`;
309
325
 
310
326
  return {
@@ -86,6 +86,7 @@ const BEHAVIORAL_METRICS = {
86
86
  VERIFY_REGISTRATION_ID_SUCCESS: 'js_sdk_verify_registrationId_success',
87
87
  VERIFY_REGISTRATION_ID_ERROR: 'js_sdk_verify_registrationId_error',
88
88
  JOIN_FORBIDDEN_ERROR: 'js_sdk_join_forbidden_error',
89
+ MEDIA_ISSUE_DETECTED: 'js_sdk_media_issue_detected',
89
90
  };
90
91
 
91
92
  export {BEHAVIORAL_METRICS as default};
@@ -923,10 +923,10 @@ export default class Reachability extends EventsScope {
923
923
 
924
924
  // update expected results counters to include this cluster
925
925
  this.expectedResultsCount[cluster.isVideoMesh ? 'videoMesh' : 'public'].udp +=
926
- cluster.udp.length;
926
+ cluster.udp.length > 0 ? 1 : 0;
927
927
  if (!cluster.isVideoMesh) {
928
- this.expectedResultsCount.public.tcp += cluster.tcp.length;
929
- this.expectedResultsCount.public.xtls += cluster.xtls.length;
928
+ this.expectedResultsCount.public.tcp += cluster.tcp.length > 0 ? 1 : 0;
929
+ this.expectedResultsCount.public.xtls += cluster.xtls.length > 0 ? 1 : 0;
930
930
  }
931
931
  });
932
932
 
@@ -18,16 +18,6 @@ const USER_AGENT_SAFARI_MAC =
18
18
  const USER_AGENT_FIREFOX_MAC =
19
19
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) ' + 'Gecko/20100101 Firefox/87.0';
20
20
 
21
- const mockDetectionObject = {
22
- /* eslint-disable global-require */
23
- getOSName: () => require('os').platform(),
24
- getOSVersion: () => require('os').release(),
25
- /* eslint-enable global-require */
26
- getBrowserName: () => '',
27
- getBrowserVersion: () => '',
28
- isBrowser: () => false,
29
- };
30
-
31
21
  describe('common/browser-detection', () => {
32
22
  it('returns the correct browser name.', () => {
33
23
  assert.equal(
@@ -102,18 +92,4 @@ describe('common/browser-detection', () => {
102
92
  'This browser is NOT Firefox'
103
93
  );
104
94
  });
105
-
106
- it('returns the mock object when there is no userAgent', () => {
107
- Object.defineProperty(global.window.navigator, 'userAgent', {
108
- get: () => undefined,
109
- configurable: true,
110
- });
111
-
112
- const {getBrowserName, getBrowserVersion, getOSName, getOSVersion} = BrowserDetection(null);
113
-
114
- assert.equal(getBrowserName(), mockDetectionObject.getBrowserName());
115
- assert.equal(getBrowserVersion(), mockDetectionObject.getBrowserVersion());
116
- assert.equal(getOSName(), mockDetectionObject.getOSName());
117
- assert.equal(getOSVersion(), mockDetectionObject.getOSVersion());
118
- });
119
95
  });
@@ -133,6 +133,7 @@ describe('plugin-meetings', () => {
133
133
 
134
134
  manager.set({
135
135
  locusUrl: 'test/id',
136
+ mainLocusUrl: '',
136
137
  displayHints: [],
137
138
  });
138
139
  });
@@ -201,6 +202,38 @@ describe('plugin-meetings', () => {
201
202
  Util.canUpdate = restorable;
202
203
  });
203
204
  });
205
+
206
+ it('should call request with mainLocusUrl and locusUrl as authorizingLocusUrl if mainLocusUrl is exist and not same with locusUrl', () => {
207
+ const restorable = Util.canUpdate;
208
+ Util.canUpdate = sinon.stub().returns(true);
209
+ manager.mainLocusUrl = 'test/main';
210
+
211
+ const audio = {scope: 'audio', properties: {a: 1, b: 2}};
212
+ const reactions = {scope: 'reactions', properties: {c: 3, d: 4}};
213
+
214
+ return manager.update(audio, reactions)
215
+ .then(() => {
216
+ assert.calledWith(request.request, {
217
+ uri: 'test/main/controls',
218
+ body: {
219
+ audio: audio.properties,
220
+ authorizingLocusUrl: 'test/id'
221
+ },
222
+ method: HTTP_VERBS.PATCH,
223
+ });
224
+
225
+ assert.calledWith(request.request, {
226
+ uri: 'test/main/controls',
227
+ body: {
228
+ reactions: reactions.properties,
229
+ authorizingLocusUrl: 'test/id'
230
+ },
231
+ method: HTTP_VERBS.PATCH,
232
+ });
233
+
234
+ Util.canUpdate = restorable;
235
+ });
236
+ });
204
237
  });
205
238
 
206
239
  describe('Mute/Unmute All', () => {
@@ -214,6 +247,7 @@ describe('plugin-meetings', () => {
214
247
 
215
248
  manager.set({
216
249
  locusUrl: 'test/id',
250
+ mainLocusUrl: '',
217
251
  displayHints: [],
218
252
  })
219
253
  });
@@ -305,6 +339,19 @@ describe('plugin-meetings', () => {
305
339
 
306
340
  assert.deepEqual(result, request.request.firstCall.returnValue);
307
341
  });
342
+
343
+ it('request with mainLocusUrl and make locusUrl as authorizingLocusUrl if mainLocusUrl is exist and not same with locusUrl', () => {
344
+ manager.setDisplayHints(['MUTE_ALL', 'DISABLE_HARD_MUTE', 'DISABLE_MUTE_ON_ENTRY']);
345
+ manager.mainLocusUrl = `test/main`;
346
+
347
+ const result = manager.setMuteAll(true, true, true, ['attendee']);
348
+
349
+ assert.calledWith(request.request, { uri: 'test/main/controls',
350
+ body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['attendee'] }, authorizingLocusUrl: 'test/id' },
351
+ method: HTTP_VERBS.PATCH});
352
+
353
+ assert.deepEqual(result, request.request.firstCall.returnValue);
354
+ });
308
355
  });
309
356
  });
310
357
  });
@@ -36,6 +36,7 @@ export default {
36
36
  'LOCK_STATUS_UNLOCKED',
37
37
  'ROSTER_IN_MEETING',
38
38
  'SHARE_WHITEBOARD',
39
+ 'SHOW_AUTO_END_MEETING_WARNING',
39
40
  'SHARE_WHITEBOARD_POLICY',
40
41
  'WEBEX_ASSISTANT_STATUS_INACTIVE',
41
42
  'CAPTION_START',
@@ -0,0 +1,87 @@
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ /* eslint-disable camelcase */
6
+ import 'jsdom-global/register';
7
+ import {assert} from '@webex/test-helper-chai';
8
+ import MockWebex from '@webex/test-helper-mock-webex';
9
+ import {LocusRouteTokenInterceptor} from '@webex/plugin-meetings/src/interceptors';
10
+ import Meetings from '@webex/plugin-meetings';
11
+
12
+ const X_CISCO_PART_ROUTE_TOKEN = 'X-Cisco-Part-Route-Token';
13
+
14
+ describe('LocusRouteTokenInterceptor', () => {
15
+ let interceptor, webex;
16
+ const TEST_LOCUS_ID = '0f1eba56-91e2-2a11-9b2b-1e2da077f066';
17
+ beforeEach(() => {
18
+ webex = new MockWebex({
19
+ children: {
20
+ meetings: Meetings,
21
+ },
22
+ });
23
+ interceptor = Reflect.apply(LocusRouteTokenInterceptor.create, webex, []);
24
+ });
25
+
26
+ it('getLocusIdByRequestUrl should return locusId from url', () => {
27
+ const url = `https://locus-test.webex.com/locus/api/v1/loci/${TEST_LOCUS_ID}/foo`;
28
+ assert.equal(interceptor.getLocusIdByRequestUrl(url), TEST_LOCUS_ID);
29
+ });
30
+
31
+ it('getLocusIdByRequestUrl should return undefined when no locusId in url', () => {
32
+ const url = 'https://locus-test.webex.com/locus/api/v1/foo';
33
+ assert.isUndefined(interceptor.getLocusIdByRequestUrl(url));
34
+ });
35
+
36
+ it('getLocusIdByRequestUrl should return undefined when url is undefined', () => {
37
+ assert.isUndefined(interceptor.getLocusIdByRequestUrl(undefined));
38
+ });
39
+
40
+ it('onResponse should store route token when header exists', async () => {
41
+ const response = {
42
+ headers: {
43
+ [X_CISCO_PART_ROUTE_TOKEN]: 'test-token',
44
+ },
45
+ };
46
+
47
+ const result = await interceptor.onResponse(
48
+ {
49
+ uri: `https://locus-test.webex.com/locus/api/v1/loci/${TEST_LOCUS_ID}/foo`,
50
+ },
51
+ response
52
+ );
53
+ assert.equal(result, response);
54
+ assert.equal(interceptor.getToken(TEST_LOCUS_ID), 'test-token');
55
+ });
56
+
57
+ it('onResponse should not store token when header missing', async () => {
58
+ interceptor.updateToken(TEST_LOCUS_ID);
59
+ const response = {headers: {}};
60
+
61
+ await interceptor.onResponse({}, response);
62
+ assert.isUndefined(interceptor.getToken(TEST_LOCUS_ID));
63
+ });
64
+
65
+ it('onRequest should attach token to headers when token exists', async () => {
66
+ interceptor.updateToken(TEST_LOCUS_ID, 'abc123');
67
+
68
+ const options = {
69
+ headers: {},
70
+ uri: `https://locus-test.webex.com/locus/api/v1/loci/${TEST_LOCUS_ID}/foo`,
71
+ };
72
+ const result = await interceptor.onRequest(options);
73
+ assert.equal(result.headers[X_CISCO_PART_ROUTE_TOKEN], 'abc123');
74
+ });
75
+
76
+ it('onRequest should not attach token if none is stored', async () => {
77
+ interceptor.updateToken(TEST_LOCUS_ID);
78
+ const options = {headers: {}};
79
+ const result = await interceptor.onRequest(options);
80
+ assert.isUndefined(result.headers[X_CISCO_PART_ROUTE_TOKEN]);
81
+ });
82
+
83
+ it('updateToken & getToken should work as pair', () => {
84
+ interceptor.updateToken(TEST_LOCUS_ID, 'abc456');
85
+ assert.equal(interceptor.getToken(TEST_LOCUS_ID), 'abc456');
86
+ });
87
+ });