@webex/plugin-meetings 1.149.0 → 1.150.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1310,7 +1310,7 @@ export default class Meeting extends StatelessWebexPlugin {
1310
1310
 
1311
1311
  /**
1312
1312
  * Set up the locus info media shares listener
1313
- * update content sharing id value for members, and updates the member
1313
+ * update content and whiteboard sharing id value for members, and updates the member
1314
1314
  * notifies consumer with members:content:update {activeContentSharingId, endedContentSharingId}
1315
1315
  * @returns {undefined}
1316
1316
  * @private
@@ -1319,11 +1319,16 @@ export default class Meeting extends StatelessWebexPlugin {
1319
1319
  setUpLocusMediaSharesListener() {
1320
1320
  // Will get triggered on local and remote share
1321
1321
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES, (payload) => {
1322
- const {contentId, disposition} = payload.current;
1322
+ const {content: contentShare, whiteboard: whiteboardShare} = payload.current;
1323
+ const previousContentShare = payload.previous?.content;
1324
+ const previousWhiteboardShare = payload.previous?.whiteboard;
1323
1325
 
1324
1326
  if (
1325
- contentId === payload.previous?.contentId &&
1326
- disposition === payload.previous?.disposition
1327
+ (contentShare.beneficiaryId === previousContentShare?.beneficiaryId &&
1328
+ contentShare.disposition === previousContentShare?.disposition) &&
1329
+ (whiteboardShare.beneficiaryId === previousWhiteboardShare?.beneficiaryId &&
1330
+ whiteboardShare.disposition === previousWhiteboardShare?.disposition &&
1331
+ whiteboardShare.resourceUrl === previousWhiteboardShare?.resourceUrl)
1327
1332
  ) {
1328
1333
  // nothing changed, so ignore
1329
1334
  // (this happens when we steal presentation from remote)
@@ -1334,15 +1339,16 @@ export default class Meeting extends StatelessWebexPlugin {
1334
1339
 
1335
1340
  // REMOTE - check if remote started sharing
1336
1341
  if (
1337
- this.selfId !== contentId &&
1338
- disposition === FLOOR_ACTION.GRANTED
1342
+ this.selfId !== contentShare.beneficiaryId &&
1343
+ contentShare.disposition === FLOOR_ACTION.GRANTED
1339
1344
  ) {
1345
+ // CONTENT - sharing content remote
1340
1346
  newShareStatus = SHARE_STATUS.REMOTE_SHARE_ACTIVE;
1341
1347
  }
1342
- // LOCAL - check if we started sharing
1348
+ // LOCAL - check if we started sharing content
1343
1349
  else if (
1344
- this.selfId === contentId &&
1345
- disposition === FLOOR_ACTION.GRANTED
1350
+ this.selfId === contentShare.beneficiaryId &&
1351
+ contentShare.disposition === FLOOR_ACTION.GRANTED
1346
1352
  ) {
1347
1353
  if (this.mediaProperties.shareTrack?.readyState === 'ended') {
1348
1354
  this.stopShare({
@@ -1353,13 +1359,23 @@ export default class Meeting extends StatelessWebexPlugin {
1353
1359
  });
1354
1360
  }
1355
1361
  else {
1362
+ // CONTENT - sharing content local
1356
1363
  newShareStatus = SHARE_STATUS.LOCAL_SHARE_ACTIVE;
1357
1364
  }
1358
1365
  }
1359
- // or if sharing has been stopped
1366
+ // If we did not hit the cases above, no one is sharng content, so we check if we are sharing whiteboard
1367
+ // There is no concept of local/remote share for whiteboard
1368
+ // It does not matter who requested to share the whiteboard, everyone gets the same view
1369
+ else if (whiteboardShare.disposition === FLOOR_ACTION.GRANTED) {
1370
+ // WHITEBOARD - sharing whiteboard
1371
+ newShareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
1372
+ }
1373
+ // or if content share is either released or null and whiteboard share is either released or null, no one is sharing
1360
1374
  else if (
1361
- payload.previous &&
1362
- disposition === FLOOR_ACTION.RELEASED
1375
+ (previousContentShare &&
1376
+ (contentShare.disposition === FLOOR_ACTION.RELEASED) || (contentShare.disposition === null)) &&
1377
+ (previousWhiteboardShare &&
1378
+ (whiteboardShare.disposition === FLOOR_ACTION.RELEASED) || (whiteboardShare.disposition === null))
1363
1379
  ) {
1364
1380
  newShareStatus = SHARE_STATUS.NO_SHARE;
1365
1381
  }
@@ -1397,6 +1413,17 @@ export default class Meeting extends StatelessWebexPlugin {
1397
1413
  );
1398
1414
  break;
1399
1415
 
1416
+ case SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE:
1417
+ Trigger.trigger(
1418
+ this,
1419
+ {
1420
+ file: 'meeting/index',
1421
+ function: 'stopWhiteboardShare'
1422
+ },
1423
+ EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD
1424
+ );
1425
+ break;
1426
+
1400
1427
  case SHARE_STATUS.NO_SHARE:
1401
1428
  // nothing to do
1402
1429
  break;
@@ -1417,13 +1444,16 @@ export default class Meeting extends StatelessWebexPlugin {
1417
1444
  },
1418
1445
  EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
1419
1446
  {
1420
- memberId: contentId
1447
+ memberId: contentShare.beneficiaryId
1421
1448
  }
1422
1449
  );
1423
1450
  };
1424
1451
 
1425
1452
  // if a remote participant is stealing the presentation from us
1426
- if (this.mediaProperties.mediaDirection?.sendShare) {
1453
+ if (!this.mediaProperties.mediaDirection?.sendShare || oldShareStatus === SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE) {
1454
+ sendStartedSharingRemote();
1455
+ }
1456
+ else {
1427
1457
  this.updateShare({
1428
1458
  sendShare: false,
1429
1459
  receiveShare: this.mediaProperties.mediaDirection.receiveShare
@@ -1432,9 +1462,6 @@ export default class Meeting extends StatelessWebexPlugin {
1432
1462
  sendStartedSharingRemote();
1433
1463
  });
1434
1464
  }
1435
- else {
1436
- sendStartedSharingRemote();
1437
- }
1438
1465
  break;
1439
1466
  }
1440
1467
 
@@ -1450,6 +1477,22 @@ export default class Meeting extends StatelessWebexPlugin {
1450
1477
  Metrics.postEvent({event: eventType.LOCAL_SHARE_FLOOR_GRANTED, meeting: this});
1451
1478
  break;
1452
1479
 
1480
+ case SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE:
1481
+ Trigger.trigger(
1482
+ this,
1483
+ {
1484
+ file: 'meeting/index',
1485
+ function: 'startWhiteboardShare'
1486
+ },
1487
+ EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
1488
+ {
1489
+ resourceUrl: whiteboardShare.resourceUrl,
1490
+ memberId: whiteboardShare.beneficiaryId
1491
+ }
1492
+ );
1493
+ Metrics.postEvent({event: eventType.WHITEBOARD_SHARE_FLOOR_GRANTED, meeting: this});
1494
+ break;
1495
+
1453
1496
  case SHARE_STATUS.NO_SHARE:
1454
1497
  // nothing to do
1455
1498
  break;
@@ -1471,9 +1514,27 @@ export default class Meeting extends StatelessWebexPlugin {
1471
1514
  },
1472
1515
  EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
1473
1516
  {
1474
- memberId: contentId
1517
+ memberId: contentShare.beneficiaryId
1518
+ }
1519
+ );
1520
+ this.members.locusMediaSharesUpdate(payload);
1521
+ }
1522
+ else if (newShareStatus === SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE) {
1523
+ // if we got here, then some remote participant has stolen
1524
+ // the presentation from another remote participant
1525
+ Trigger.trigger(
1526
+ this,
1527
+ {
1528
+ file: 'meeting/index',
1529
+ function: 'startWhiteboardShare'
1530
+ },
1531
+ EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
1532
+ {
1533
+ resourceUrl: whiteboardShare.resourceUrl,
1534
+ memberId: whiteboardShare.beneficiaryId
1475
1535
  }
1476
1536
  );
1537
+ Metrics.postEvent({event: eventType.WHITEBOARD_SHARE_FLOOR_GRANTED, meeting: this});
1477
1538
  this.members.locusMediaSharesUpdate(payload);
1478
1539
  }
1479
1540
  });
@@ -4491,6 +4552,105 @@ export default class Meeting extends StatelessWebexPlugin {
4491
4552
  });
4492
4553
  }
4493
4554
 
4555
+ /**
4556
+ * Start sharing whiteboard given channelUrl
4557
+ * @param {string} channelUrl whiteboard url
4558
+ * @param {String} resourceToken token created by authorize media injector
4559
+ * @returns {Promise}
4560
+ * @public
4561
+ * @memberof Meeting
4562
+ */
4563
+ startWhiteboardShare(channelUrl, resourceToken) {
4564
+ const whiteboard = this.locusInfo.mediaShares.find((element) => element.name === 'whiteboard');
4565
+
4566
+ if (!channelUrl) {
4567
+ return Promise.reject(new ParameterError('Cannot share without channelUrl.'));
4568
+ }
4569
+
4570
+ if (whiteboard) {
4571
+ Metrics.postEvent({event: eventType.WHITEBOARD_SHARE_INITIATED, meeting: this});
4572
+
4573
+ const body = {
4574
+ disposition: FLOOR_ACTION.GRANTED,
4575
+ personUrl: this.locusInfo.self.url,
4576
+ deviceUrl: this.deviceUrl,
4577
+ uri: whiteboard.url,
4578
+ resourceUrl: channelUrl
4579
+ };
4580
+
4581
+ if (resourceToken) {
4582
+ body.resourceToken = resourceToken;
4583
+ }
4584
+
4585
+ return this.meetingRequest.changeMeetingFloor(body)
4586
+ .then(() => {
4587
+ this.isSharing = false;
4588
+
4589
+ return Promise.resolve();
4590
+ })
4591
+ .catch((error) => {
4592
+ LoggerProxy.logger.error('Meeting:index#startWhiteboardShare --> Error ', error);
4593
+
4594
+ Metrics.sendOperationalMetric(
4595
+ METRICS_OPERATIONAL_MEASURES.MEETING_START_WHITEBOARD_SHARE_FAILURE,
4596
+ {
4597
+ correlation_id: this.correlationId,
4598
+ locus_id: this.locusUrl.split('/').pop(),
4599
+ reason: error.message,
4600
+ stack: error.stack,
4601
+ board: {channelUrl}
4602
+ }
4603
+ );
4604
+
4605
+ return Promise.reject(error);
4606
+ });
4607
+ }
4608
+
4609
+ return Promise.reject(new ParameterError('Cannot share without whiteboard.'));
4610
+ }
4611
+
4612
+ /**
4613
+ * Stop sharing whiteboard given channelUrl
4614
+ * @param {string} channelUrl whiteboard url
4615
+ * @returns {Promise}
4616
+ * @public
4617
+ * @memberof Meeting
4618
+ */
4619
+ stopWhiteboardShare(channelUrl) {
4620
+ const whiteboard = this.locusInfo.mediaShares.find((element) => element.name === 'whiteboard');
4621
+
4622
+ if (whiteboard) {
4623
+ Metrics.postEvent({event: eventType.WHITEBOARD_SHARE_STOPPED, meeting: this});
4624
+
4625
+ return this.meetingRequest.changeMeetingFloor({
4626
+ disposition: FLOOR_ACTION.RELEASED,
4627
+ personUrl: this.locusInfo.self.url,
4628
+ deviceUrl: this.deviceUrl,
4629
+ uri: whiteboard.url
4630
+ })
4631
+ .catch((error) => {
4632
+ LoggerProxy.logger.error('Meeting:index#stopWhiteboardShare --> Error ', error);
4633
+
4634
+ Metrics.sendOperationalMetric(
4635
+ METRICS_OPERATIONAL_MEASURES.STOP_WHITEBOARD_SHARE_FAILURE,
4636
+ {
4637
+ correlation_id: this.correlationId,
4638
+ locus_id: this.locusUrl.split('/').pop(),
4639
+ reason: error.message,
4640
+ stack: error.stack,
4641
+ board: {channelUrl}
4642
+ }
4643
+ );
4644
+
4645
+ return Promise.reject(error);
4646
+ })
4647
+ .finally(() => {
4648
+ });
4649
+ }
4650
+
4651
+ return Promise.reject(new ParameterError('Cannot stop share without whiteboard.'));
4652
+ }
4653
+
4494
4654
  /**
4495
4655
  * Start sharing content with server
4496
4656
  * @returns {Promise} see #meetingRequest.changeMeetingFloor
@@ -4564,7 +4724,7 @@ export default class Meeting extends StatelessWebexPlugin {
4564
4724
  Metrics.postEvent({event: eventType.SHARE_STOPPED, meeting: this});
4565
4725
  Media.stopTracks(this.mediaProperties.shareTrack);
4566
4726
 
4567
- if (this.contentId !== this.selfId) {
4727
+ if (content.floor.beneficiary.id !== this.selfId) {
4568
4728
  // remote participant started sharing and caused our sharing to stop, we don't want to send any floor action request in that case
4569
4729
  this.isSharing = false;
4570
4730
 
@@ -1,9 +1,7 @@
1
1
  import uuid from 'uuid';
2
2
  import {debounce} from 'lodash';
3
3
  import {StatelessWebexPlugin} from '@webex/webex-core';
4
- import {
5
- deviceType
6
- } from '@webex/common';
4
+ import {deviceType} from '@webex/common';
7
5
 
8
6
  import LoggerProxy from '../common/logs/logger-proxy';
9
7
  import {
@@ -51,7 +49,19 @@ export default class MeetingRequest extends StatelessWebexPlugin {
51
49
  */
52
50
  async joinMeeting(options) {
53
51
  const {
54
- asResourceOccupant, sipUri, meetingNumber, deviceUrl, locusUrl, resourceId, correlationId, ensureConversation, moderator, pin, moveToResource, roapMessage, preferTranscoding
52
+ asResourceOccupant,
53
+ sipUri,
54
+ meetingNumber,
55
+ deviceUrl,
56
+ locusUrl,
57
+ resourceId,
58
+ correlationId,
59
+ ensureConversation,
60
+ moderator,
61
+ pin,
62
+ moveToResource,
63
+ roapMessage,
64
+ preferTranscoding
55
65
  } = options;
56
66
 
57
67
  LoggerProxy.logger.info(
@@ -95,29 +105,16 @@ export default class MeetingRequest extends StatelessWebexPlugin {
95
105
  if (locusUrl) {
96
106
  url = `${locusUrl}/${PARTICIPANT}`;
97
107
  }
98
- else if (meetingNumber) {
108
+ else if (sipUri || meetingNumber) {
99
109
  try {
100
110
  await this.webex.internal.services.waitForCatalog('postauth');
101
111
  url = `${this.webex.internal.services.get('locus')}/${LOCI}/${CALL}`;
102
112
  body.invitee = {
103
- address: `wbxmn:${meetingNumber}`
113
+ address: sipUri || `wbxmn:${meetingNumber}`
104
114
  };
105
115
  }
106
116
  catch (e) {
107
- LoggerProxy.logger.error(`Meeting:request#joinMeeting webex meeting id--> ${e}`);
108
- throw (e);
109
- }
110
- }
111
- else if (sipUri) {
112
- try {
113
- await this.webex.internal.services.waitForCatalog('postauth');
114
- url = `${this.webex.internal.services.get('locus')}/${LOCI}/${CALL}`;
115
- body.invitee = {
116
- address: sipUri
117
- };
118
- }
119
- catch (e) {
120
- LoggerProxy.logger.error(`Meeting:request#joinMeeting sipUrl --> ${e}`);
117
+ LoggerProxy.logger.error(`Meeting:request#joinMeeting ${sipUri ? 'sipUri' : 'meetingNumber'} --> ${e}`);
121
118
  throw (e);
122
119
  }
123
120
  }
@@ -154,7 +151,10 @@ export default class MeetingRequest extends StatelessWebexPlugin {
154
151
  * @private
155
152
  */
156
153
  dialIn({
157
- locusUrl, dialInUrl, clientUrl, correlationId
154
+ locusUrl,
155
+ dialInUrl,
156
+ clientUrl,
157
+ correlationId
158
158
  }) {
159
159
  LoggerProxy.logger.info(
160
160
  'Meeting:request#dialIn --> Provisioning a dial in device',
@@ -195,7 +195,11 @@ export default class MeetingRequest extends StatelessWebexPlugin {
195
195
  * @private
196
196
  */
197
197
  dialOut({
198
- locusUrl, dialOutUrl, phoneNumber, clientUrl, correlationId
198
+ locusUrl,
199
+ dialOutUrl,
200
+ phoneNumber,
201
+ clientUrl,
202
+ correlationId
199
203
  }) {
200
204
  LoggerProxy.logger.info(
201
205
  'Meeting:request#dialOut --> Provisioning a dial out device',
@@ -294,7 +298,10 @@ export default class MeetingRequest extends StatelessWebexPlugin {
294
298
  * @private
295
299
  */
296
300
  disconnectPhoneAudio({
297
- locusUrl, phoneUrl, correlationId, selfId
301
+ locusUrl,
302
+ phoneUrl,
303
+ correlationId,
304
+ selfId
298
305
  }) {
299
306
  LoggerProxy.logger.info(
300
307
  `Meeting:request#disconnectPhoneAudio --> request phone ${phoneUrl} to leave`,
@@ -334,7 +341,11 @@ export default class MeetingRequest extends StatelessWebexPlugin {
334
341
  * @returns {Promise}
335
342
  */
336
343
  leaveMeeting({
337
- locusUrl, selfId, deviceUrl: url, resourceId, correlationId
344
+ locusUrl,
345
+ selfId,
346
+ deviceUrl: url,
347
+ resourceId,
348
+ correlationId
338
349
  }) {
339
350
  LoggerProxy.logger.info(
340
351
  'Meeting:request#leaveMeeting --> Leaving a meeting',
@@ -512,13 +523,19 @@ export default class MeetingRequest extends StatelessWebexPlugin {
512
523
  };
513
524
  }
514
525
 
526
+ const body = {
527
+ floor: floorReq,
528
+ resourceUrl: options.resourceUrl
529
+ };
530
+
531
+ if (options?.resourceToken) {
532
+ body.resourceToken = options?.resourceToken;
533
+ }
534
+
515
535
  return this.request({
516
536
  uri: options.uri,
517
537
  method: HTTP_VERBS.PUT,
518
- body: {
519
- floor: floorReq,
520
- resourceUrl: options.resourceUrl
521
- }
538
+ body
522
539
  });
523
540
  }
524
541
 
@@ -559,7 +576,11 @@ export default class MeetingRequest extends StatelessWebexPlugin {
559
576
  * @returns {Promise}
560
577
  */
561
578
  changeVideoLayout({
562
- locusUrl, deviceUrl, layoutType, main, content
579
+ locusUrl,
580
+ deviceUrl,
581
+ layoutType,
582
+ main,
583
+ content
563
584
  }) {
564
585
  // send main/content renderInfo only if both width and height are specified
565
586
  if (main && (!main.width || !main.height)) {
@@ -108,7 +108,10 @@ export default class MeetingInfo {
108
108
  * @memberof MeetingInfo
109
109
  */
110
110
  fetchMeetingInfo(destination, type = null) {
111
- return this.fetchInfoOptions(destination, type).then((options) =>
111
+ return this.fetchInfoOptions(
112
+ MeetingInfoUtil.extractDestination(destination, type),
113
+ type
114
+ ).then((options) =>
112
115
  // fetch meeting info
113
116
  this.requestFetchInfo(options).catch((error) => {
114
117
  // if it failed the first time as meeting link
@@ -35,6 +35,19 @@ import {
35
35
 
36
36
  const MeetingInfoUtil = {};
37
37
 
38
+ MeetingInfoUtil.extractDestination = (destination, type) => {
39
+ let dest = destination;
40
+
41
+ if (type === _LOCUS_ID_) {
42
+ if (!(destination && destination.url)) {
43
+ throw new ParameterError('You cannot create a meeting by locus without a locus.url defined');
44
+ }
45
+ dest = destination.url;
46
+ }
47
+
48
+ return dest;
49
+ };
50
+
38
51
  MeetingInfoUtil.getParsedUrl = (link) => {
39
52
  try {
40
53
  let parsedUrl = url.parse(link);
@@ -116,15 +116,7 @@ export default class Meetings extends WebexPlugin {
116
116
  */
117
117
  constructor(...args) {
118
118
  super(...args);
119
- /**
120
- * The MeetingInfo object to interact with server
121
- * @instance
122
- * @type {Object}
123
- * @private
124
- * @memberof Meetings
125
- */
126
119
 
127
- this.meetingInfo = null;
128
120
  /**
129
121
  * The Meetings request to interact with server
130
122
  * @instance
@@ -381,6 +373,15 @@ export default class Meetings extends WebexPlugin {
381
373
  LoggerConfig.set(this.config.logging);
382
374
  LoggerProxy.set(this.webex.logger);
383
375
 
376
+ /**
377
+ * The MeetingInfo object to interact with server
378
+ * @instance
379
+ * @type {Object}
380
+ * @private
381
+ * @memberof Meetings
382
+ */
383
+ this.meetingInfo = this.config.experimental.enableUnifiedMeetings ? new MeetingInfoV2(this.webex) : new MeetingInfo(this.webex);
384
+
384
385
  Trigger.trigger(
385
386
  this,
386
387
  {
@@ -407,8 +408,6 @@ export default class Meetings extends WebexPlugin {
407
408
  return Promise.reject(new Error('SDK cannot authorize'));
408
409
  }
409
410
 
410
- this.meetingInfo = this.config.experimental.enableUnifiedMeetings ? new MeetingInfoV2(this.webex) : new MeetingInfo(this.webex);
411
-
412
411
 
413
412
  if (this.registered) {
414
413
  LoggerProxy.logger.info('Meetings:index#register --> INFO, Meetings plugin already registered');
@@ -725,7 +724,7 @@ export default class Meetings extends WebexPlugin {
725
724
  this.meetingCollection.set(meeting);
726
725
 
727
726
  try {
728
- const info = await this.meetingInfo.fetchMeetingInfo(MeetingsUtil.extractDestination(destination, type), type);
727
+ const info = await this.meetingInfo.fetchMeetingInfo(destination, type);
729
728
 
730
729
  meeting.parseMeetingInfo(info);
731
730
  meeting.meetingInfo = info ? info.body : null;
@@ -8,7 +8,6 @@ import {
8
8
  CORRELATION_ID,
9
9
  EVENT_TRIGGERS
10
10
  } from '../constants';
11
- import ParameterError from '../common/errors/parameter';
12
11
  import LoggerProxy from '../common/logs/logger-proxy';
13
12
  import Trigger from '../common/events/trigger-proxy';
14
13
 
@@ -32,19 +31,6 @@ import Trigger from '../common/events/trigger-proxy';
32
31
 
33
32
  const MeetingsUtil = {};
34
33
 
35
- MeetingsUtil.extractDestination = (destination, type) => {
36
- let dest = destination;
37
-
38
- if (type === _LOCUS_ID_) {
39
- if (!(destination && destination.url)) {
40
- throw new ParameterError('You cannot create a meeting by locus without a locus.url defined');
41
- }
42
- dest = destination.url;
43
- }
44
-
45
- return dest;
46
- };
47
-
48
34
  MeetingsUtil.getMeetingAddedType = (type) => (type === _LOCUS_ID_ ? _INCOMING_ : _CREATED_);
49
35
 
50
36
  MeetingsUtil.handleRoapMercury = (envelope, meetingCollection) => {