@webex/plugin-meetings 1.149.2 → 1.151.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.
@@ -126,6 +126,12 @@ export const eventType = {
126
126
  // Fired when the client changes its local UI/layout to a content sharing view,
127
127
  // because it is expecting to display share media.
128
128
  SHARE_LAYOUT_DISPLAYED: 'client.share.layout.displayed',
129
+ // Fired when the user of the client starts a whiteboard share (e.g. click 'Share Live' button).
130
+ WHITEBOARD_SHARE_INITIATED: 'client.whiteboard.share.initiated',
131
+ // Fired when the meeting floor is released for whiteboard share
132
+ WHITEBOARD_SHARE_STOPPED: 'client.whiteboard.share.stopped',
133
+ // When the client receives a successful response from locus indicating that it has the floor for whiteboard sharing.
134
+ WHITEBOARD_SHARE_FLOOR_GRANTED: 'client.whiteboard.share.floor-granted',
129
135
  MUTED: 'client.muted',
130
136
  UNMUTED: 'client.unmuted',
131
137
  LEAVE: 'client.call.leave',
@@ -6,10 +6,10 @@ import util from 'util';
6
6
  import {includes} from 'lodash';
7
7
  import uuid from 'uuid';
8
8
  import window from 'global/window';
9
+ import anonymize from 'ip-anonymize';
9
10
 
10
11
  import LoggerProxy from '../common/logs/logger-proxy';
11
12
  import {MEETING_ERRORS} from '../constants';
12
- import StaticConfig from '../common/config';
13
13
  import BrowserDetection from '../common/browser-detection';
14
14
 
15
15
  import {
@@ -34,21 +34,19 @@ const {
34
34
  } = BrowserDetection();
35
35
 
36
36
  // Apply a CIDR /28 format to the IP address
37
- const getLocalNetworkPrefix = (localIp) => {
38
- if (!localIp) {
39
- return undefined;
40
- }
41
- const parts = localIp.split('.');
42
-
43
- // eslint-disable-next-line no-bitwise
44
- parts[3] &= 240;
45
-
46
- return parts.join('.');
47
- };
37
+ const anonymizeIPAddress = (localIp) => anonymize(localIp);
48
38
 
49
39
  const triggerTimers = ({event, meeting, data}) => {
50
40
  switch (event) {
41
+ case eventType.CALL_INITIATED:
42
+ meeting.setStartCallInitiateJoinReq();
43
+ break;
44
+ case eventType.LOCUS_JOIN_REQUEST:
45
+ meeting.setEndCallInitiateJoinReq();
46
+ meeting.setStartJoinReqResp();
47
+ break;
51
48
  case eventType.LOCUS_JOIN_RESPONSE:
49
+ meeting.setEndJoinReqResp();
52
50
  meeting.setStartSetupDelay(mediaType.AUDIO);
53
51
  meeting.setStartSetupDelay(mediaType.VIDEO);
54
52
  meeting.setStartSendingMediaDelay(mediaType.AUDIO);
@@ -60,6 +58,12 @@ const triggerTimers = ({event, meeting, data}) => {
60
58
  case eventType.SENDING_MEDIA_START:
61
59
  meeting.setEndSendingMediaDelay(data.mediaType);
62
60
  break;
61
+ case eventType.LOCAL_SDP_GENERATED:
62
+ meeting.setStartLocalSDPGenRemoteSDPRecvDelay();
63
+ break;
64
+ case eventType.REMOTE_SDP_RECEIVED:
65
+ meeting.setEndLocalSDPGenRemoteSDPRecvDelay();
66
+ break;
63
67
  default:
64
68
  break;
65
69
  }
@@ -144,6 +148,7 @@ class Metrics {
144
148
 
145
149
  if (!meeting && meetingId) {
146
150
  meeting = this.meetingCollection.get(meetingId);
151
+ options.meeting = meeting;
147
152
  }
148
153
 
149
154
  if (meeting) {
@@ -185,7 +190,7 @@ class Metrics {
185
190
  clientInfo: {
186
191
  clientType: options.clientType,
187
192
  clientVersion: `${CLIENT_NAME}/${this.webex.version}`,
188
- localNetworkPrefix: getLocalNetworkPrefix(this.webex.meetings.geoHintInfo?.clientAddress),
193
+ localNetworkPrefix: anonymizeIPAddress(this.webex.meetings.geoHintInfo?.clientAddress),
189
194
  osVersion: getOSVersion() || 'unknown',
190
195
  subClientType: options.subClientType,
191
196
  os: this.getOsName(),
@@ -228,6 +233,9 @@ class Metrics {
228
233
  if (options.recoveredBy) {
229
234
  payload.event.recoveredBy = options.recoveredBy;
230
235
  }
236
+ if (options.joinTimes) {
237
+ payload.event.joinTimes = options.joinTimes;
238
+ }
231
239
  }
232
240
 
233
241
  return payload;
@@ -259,7 +267,7 @@ class Metrics {
259
267
  * @memberof Metrics
260
268
  */
261
269
  initMediaPayload(eventType, identifiers, options = {}) {
262
- const {audioSetupDelay, videoSetupDelay} = options;
270
+ const {audioSetupDelay, videoSetupDelay, joinTimes} = options;
263
271
 
264
272
  const payload = {
265
273
  eventId: uuid.v4(),
@@ -273,7 +281,7 @@ class Metrics {
273
281
  clientInfo: {
274
282
  clientType: options.clientType, // TODO: Only clientType: 'TEAMS_CLIENT' is whitelisted
275
283
  clientVersion: `${CLIENT_NAME}/${this.webex.version}`,
276
- localNetworkPrefix: getLocalNetworkPrefix(this.webex.meetings.geoHintInfo?.clientAddress),
284
+ localNetworkPrefix: anonymizeIPAddress(this.webex.meetings.geoHintInfo?.clientAddress),
277
285
  os: this.getOsName(),
278
286
  osVersion: getOSVersion() || UNKNOWN,
279
287
  subClientType: options.subClientType,
@@ -290,7 +298,10 @@ class Metrics {
290
298
  canProceed: true,
291
299
  identifiers,
292
300
  intervals: [options.intervalData],
293
- eventData: {webClientDomain: window.location.hostname},
301
+ joinTimes,
302
+ eventData: {
303
+ webClientDomain: window.location.hostname
304
+ },
294
305
  sourceMetadata: {
295
306
  applicationSoftwareType: CLIENT_NAME,
296
307
  applicationSoftwareVersion: this.webex.version,
@@ -455,7 +466,7 @@ class Metrics {
455
466
  userAgentToString() {
456
467
  let userAgentOption;
457
468
  let browserInfo;
458
- const clientInfo = util.format('client=%s', `${StaticConfig.meetings.metrics.clientName}`);
469
+ const clientInfo = util.format('client=%s', `${this.webex.meetings?.metrics?.clientName}`);
459
470
 
460
471
  if (['chrome', 'firefox', 'msie', 'msedge', 'safari'].indexOf(getBrowserName().toLowerCase()) !== -1) {
461
472
  browserInfo = util.format('browser=%s', `${getBrowserName().toLowerCase()}/${getBrowserVersion().split('.')[0]}`);
@@ -288,7 +288,12 @@ pc.addStream = (peerConnection, stream) => {
288
288
  * @param {String} meetingId
289
289
  * @returns {undefined}
290
290
  */
291
- pc.setRemoteSessionDetails = (peerConnection, typeStr, remoteSdp, meetingId) => {
291
+ pc.setRemoteSessionDetails = (
292
+ peerConnection,
293
+ typeStr,
294
+ remoteSdp,
295
+ meetingId,
296
+ ) => {
292
297
  LoggerProxy.logger.log(`PeerConnectionManager:index#setRemoteSessionDetails --> Setting the remote description type: ${typeStr}State: ${peerConnection.signalingState}`);
293
298
  const sdp = remoteSdp;
294
299
 
@@ -314,7 +319,7 @@ pc.setRemoteSessionDetails = (peerConnection, typeStr, remoteSdp, meetingId) =>
314
319
  })
315
320
  )
316
321
  .then(() => {
317
- if (!peerConnection.remoteDescription) {
322
+ if (peerConnection.signalingState === SDP.STABLE) {
318
323
  Metrics.postEvent({
319
324
  event: eventType.REMOTE_SDP_RECEIVED,
320
325
  meetingId
@@ -13,7 +13,7 @@ const webexTestUsers = require('../../utils/webex-test-users');
13
13
 
14
14
  const {isBrowser} = BrowserDetection();
15
15
 
16
- let userSet, alice, bob, chris, enumerateSpy;
16
+ let userSet, alice, bob, chris, enumerateSpy, channelUrlA, channelUrlB;
17
17
 
18
18
  skipInNode(describe)('plugin-meetings', () => {
19
19
  describe('journey', () => {
@@ -32,6 +32,8 @@ skipInNode(describe)('plugin-meetings', () => {
32
32
  alice.webex.meetings.name = 'alice';
33
33
  bob.webex.meetings.name = 'bob';
34
34
  chris.webex.meetings.name = 'chris';
35
+ channelUrlA = 'https://board-a.wbx2.com/board/api/v1/channels/49cfb550-5517-11eb-a2af-1b9e4bc3da13';
36
+ channelUrlB = 'https://board-a.wbx2.com/board/api/v1/channels/977a7330-54f4-11eb-b1ef-91f5eefc7bf3';
35
37
  })
36
38
  .then(() => Promise.all([testUtils.syncAndEndMeeting(alice),
37
39
  testUtils.syncAndEndMeeting(bob)]))
@@ -162,6 +164,41 @@ skipInNode(describe)('plugin-meetings', () => {
162
164
  }));
163
165
  });
164
166
 
167
+ // Enabled when config.enableUnifiedMeetings = true
168
+ xdescribe('Conversation URL', () => {
169
+ describe('Successful 1:1 meeting', () => {
170
+ it('Fetch meeting information with a conversation URL for a 1:1 space', async () => {
171
+ assert.equal(Object.keys(bob.webex.meetings.getAllMeetings()), 0);
172
+ assert.equal(Object.keys(chris.webex.meetings.getAllMeetings()), 0);
173
+
174
+ const conversation = await chris.webex.internal.conversation.create({participants: [bob]});
175
+
176
+ await chris.webex.internal.conversation.post(conversation, {displayName: 'hello world how are you '});
177
+
178
+ await Promise.all([
179
+ testUtils.delayedPromise(chris.webex.meetings.create(conversation.url, 'CONVERSATION_URL')),
180
+ testUtils.waitForEvents([{scope: chris.webex.meetings, event: 'meeting:added', user: chris}])
181
+ ])
182
+ .then(function chrisJoinsMeeting() {
183
+ return Promise.all([
184
+ testUtils.delayedPromise(chris.meeting.join()),
185
+ testUtils.waitForEvents([{scope: bob.webex.meetings, event: 'meeting:added', user: bob},
186
+ {scope: chris.meeting, event: 'meeting:stateChange', user: chris}])
187
+ .then((response) => {
188
+ assert.equal(response[0].result.payload.currentState, 'ACTIVE');
189
+ })
190
+ ]);
191
+ });
192
+ });
193
+
194
+ it('Fetch meeting information with invalid conversation URL and throws error', () => {
195
+ chris.webex.meetings.meetingInfo.fetchMeetingInfo('http://some-invalid.com', 'CONVERSATION_URL').then((response) => {
196
+ assert(response.result === '404');
197
+ });
198
+ });
199
+ });
200
+ });
201
+
165
202
  describe('Successful 1:1 meeting (including Guest)', function () {
166
203
  before(() => {
167
204
  // Workaround since getDisplayMedia requires a user gesture to be activated, and this is a integration tests
@@ -482,7 +519,10 @@ skipInNode(describe)('plugin-meetings', () => {
482
519
  .then((response) => {
483
520
  assert.equal(response[0].result.memberId, alice.meeting.selfId);
484
521
  }),
485
- testUtils.waitForEvents([{scope: bob.meeting.members, event: 'members:update'}]),
522
+ testUtils.waitForEvents([{scope: bob.meeting.members, event: 'members:update'}])
523
+ .then((response) => {
524
+ console.log('SCREEN SHARE RESPONSE ', JSON.stringify(response, testUtils.getCircularReplacer()));
525
+ }),
486
526
  testUtils.waitForEvents([{scope: alice.meeting, event: 'media:ready'}])
487
527
  .then((response) => {
488
528
  console.log('MEDIA:READY event ', response[0].result);
@@ -512,7 +552,7 @@ skipInNode(describe)('plugin-meetings', () => {
512
552
  }),
513
553
  testUtils.waitForEvents([{scope: alice.meeting.members, event: 'members:update'}])
514
554
  .then((response) => {
515
- console.log('SCREEN SHARE RESPONSE ', JSON.stringify(response));
555
+ console.log('SCREEN SHARE RESPONSE ', JSON.stringify(response, testUtils.getCircularReplacer()));
516
556
  }),
517
557
  testUtils.waitForEvents([{scope: bob.meeting, event: 'media:ready'}])
518
558
  .then((response) => {
@@ -550,6 +590,143 @@ skipInNode(describe)('plugin-meetings', () => {
550
590
  assert.equal(alice.meeting.shareStatus, 'no_share');
551
591
  }));
552
592
 
593
+ it('alice shares whiteboard A', () => Promise.all([
594
+ testUtils.delayedPromise(alice.meeting.startWhiteboardShare(channelUrlA)),
595
+ testUtils.waitForEvents([
596
+ {scope: alice.meeting, event: 'meeting:startedSharingWhiteboard'}
597
+ ]),
598
+ testUtils.waitForEvents([{scope: bob.meeting, event: 'meeting:startedSharingWhiteboard'}])
599
+ .then((response) => {
600
+ const {memberId, resourceUrl} = response[0].result;
601
+
602
+ assert.equal(memberId, alice.meeting.selfId);
603
+ assert.equal(resourceUrl, channelUrlA);
604
+ }),
605
+ testUtils.waitForEvents([{scope: bob.meeting.members, event: 'members:update'}])
606
+ .then((response) => {
607
+ console.log('WHITEBOARD SHARE RESPONSE ', JSON.stringify(response, testUtils.getCircularReplacer()));
608
+ })
609
+ ])
610
+ .then(() => {
611
+ assert.equal(alice.meeting.isSharing, false);
612
+ assert.equal(alice.meeting.shareStatus, 'whiteboard_share_active');
613
+ assert.equal(bob.meeting.shareStatus, 'whiteboard_share_active');
614
+ }));
615
+
616
+ it('bob steals share from alice with whiteboard B', () => Promise.all([
617
+ testUtils.delayedPromise(bob.meeting.startWhiteboardShare(channelUrlB)),
618
+ testUtils.waitForEvents([
619
+ {scope: bob.meeting, event: 'meeting:startedSharingWhiteboard'}
620
+ ]),
621
+ testUtils.waitForEvents([{scope: alice.meeting, event: 'meeting:startedSharingWhiteboard'}])
622
+ .then((response) => {
623
+ const {memberId, resourceUrl} = response[0].result;
624
+
625
+ assert.equal(memberId, bob.meeting.selfId);
626
+ assert.equal(resourceUrl, channelUrlB);
627
+ }),
628
+ testUtils.waitForEvents([{scope: alice.meeting.members, event: 'members:update'}])
629
+ .then((response) => {
630
+ console.log('WHITEBOARD SHARE RESPONSE ', JSON.stringify(response, testUtils.getCircularReplacer()));
631
+ })
632
+ ])
633
+ .then(() => {
634
+ assert.equal(bob.meeting.isSharing, false);
635
+ assert.equal(alice.meeting.shareStatus, 'whiteboard_share_active');
636
+ assert.equal(bob.meeting.shareStatus, 'whiteboard_share_active');
637
+ }));
638
+
639
+ it('bob stops sharing ', () => Promise.all([
640
+ // Wait for peerConnection to stabalize
641
+ testUtils.waitUntil(20000),
642
+ testUtils.delayedPromise(bob.meeting.stopWhiteboardShare(channelUrlB)),
643
+ testUtils.waitForEvents([{scope: bob.meeting, event: 'meeting:stoppedSharingWhiteboard'}]),
644
+ testUtils.waitForEvents([{scope: alice.meeting, event: 'meeting:stoppedSharingWhiteboard'}])
645
+ ])
646
+ .then(() => {
647
+ assert.equal(bob.meeting.isSharing, false);
648
+ assert.equal(bob.meeting.shareStatus, 'no_share');
649
+ assert.equal(alice.meeting.shareStatus, 'no_share');
650
+ }));
651
+
652
+ it('alice shares whiteboard B', () => Promise.all([
653
+ testUtils.delayedPromise(alice.meeting.startWhiteboardShare(channelUrlB)),
654
+ testUtils.waitForEvents([
655
+ {scope: alice.meeting, event: 'meeting:startedSharingWhiteboard'}
656
+ ]),
657
+ testUtils.waitForEvents([{scope: bob.meeting, event: 'meeting:startedSharingWhiteboard'}])
658
+ .then((response) => {
659
+ const {memberId, resourceUrl} = response[0].result;
660
+
661
+ assert.equal(memberId, alice.meeting.selfId);
662
+ assert.equal(resourceUrl, channelUrlB);
663
+ }),
664
+ testUtils.waitForEvents([{scope: bob.meeting.members, event: 'members:update'}])
665
+ .then((response) => {
666
+ console.log('WHITEBOARD SHARE RESPONSE ', JSON.stringify(response, testUtils.getCircularReplacer()));
667
+ })
668
+ ])
669
+ .then(() => {
670
+ assert.equal(alice.meeting.isSharing, false);
671
+ assert.equal(alice.meeting.shareStatus, 'whiteboard_share_active');
672
+ assert.equal(bob.meeting.shareStatus, 'whiteboard_share_active');
673
+ }));
674
+
675
+ it('bob steals the share from alice with desktop share', () => Promise.all([
676
+ testUtils.delayedPromise(bob.meeting.shareScreen()),
677
+ testUtils.waitForEvents([{scope: alice.meeting, event: 'meeting:stoppedSharingWhiteboard'}]),
678
+ testUtils.waitForEvents([{scope: bob.meeting, event: 'meeting:startedSharingLocal'}]),
679
+ testUtils.waitForEvents([{scope: alice.meeting, event: 'meeting:startedSharingRemote'}])
680
+ .then((response) => {
681
+ assert.equal(response[0].result.memberId, bob.meeting.selfId);
682
+ }),
683
+ testUtils.waitForEvents([{scope: alice.meeting.members, event: 'members:update'}])
684
+ .then((response) => {
685
+ console.log('SCREEN SHARE RESPONSE ', JSON.stringify(response, testUtils.getCircularReplacer()));
686
+ }),
687
+ testUtils.waitForEvents([{scope: bob.meeting, event: 'media:ready'}])
688
+ .then((response) => {
689
+ console.log('MEDIA:READY event ', response[0].result);
690
+ assert.equal(response[0].result.type === 'localShare', true);
691
+ })
692
+ ])
693
+ .then(() => {
694
+ const heightResolution = DEFAULT_RESOLUTIONS.meetings.screenResolution.idealHeight;
695
+
696
+ // TODO: Re-eanable Safari when screensharing issues have been resolved
697
+ if (!isBrowser('safari')) {
698
+ assert.equal(bob.meeting.mediaProperties.shareTrack.getConstraints().height, heightResolution);
699
+ }
700
+ assert.equal(bob.meeting.isSharing, true);
701
+ assert.equal(bob.meeting.shareStatus, 'local_share_active');
702
+ assert.equal(alice.meeting.shareStatus, 'remote_share_active');
703
+
704
+ return testUtils.waitUntil(10000);
705
+ }));
706
+
707
+ it('bob shares whiteboard B', () => Promise.all([
708
+ testUtils.delayedPromise(bob.meeting.startWhiteboardShare(channelUrlB)),
709
+ testUtils.waitForEvents([
710
+ {scope: bob.meeting, event: 'meeting:startedSharingWhiteboard'}
711
+ ]),
712
+ testUtils.waitForEvents([{scope: alice.meeting, event: 'meeting:startedSharingWhiteboard'}])
713
+ .then((response) => {
714
+ const {memberId, resourceUrl} = response[0].result;
715
+
716
+ assert.equal(memberId, bob.meeting.selfId);
717
+ assert.equal(resourceUrl, channelUrlB);
718
+ }),
719
+ testUtils.waitForEvents([{scope: alice.meeting.members, event: 'members:update'}])
720
+ .then((response) => {
721
+ console.log('WHITEBOARD SHARE RESPONSE ', JSON.stringify(response, testUtils.getCircularReplacer()));
722
+ })
723
+ ])
724
+ .then(() => {
725
+ assert.equal(bob.meeting.isSharing, false);
726
+ assert.equal(alice.meeting.shareStatus, 'whiteboard_share_active');
727
+ assert.equal(bob.meeting.shareStatus, 'whiteboard_share_active');
728
+ }));
729
+
553
730
  it('alice adds chris as guest to 1:1 meeting', () => Promise.all([
554
731
  testUtils.delayedPromise(alice.meeting.invite({emailAddress: chris.emailAddress})),
555
732
  testUtils.waitForEvents([{scope: chris.webex.meetings, event: 'meeting:added', user: chris}]),
@@ -1,6 +1,8 @@
1
1
 
2
2
  import {assert} from '@webex/test-helper-chai';
3
3
  import {skipInNode, jenkinsOnly} from '@webex/test-helper-mocha';
4
+ import {patterns} from '@webex/common';
5
+ import MeetingInfoUtil from '@webex/plugin-meetings/src/meeting-info/utilv2';
4
6
 
5
7
  import CMR from '../../utils/cmr';
6
8
  import testUtils from '../../utils/testUtils';
@@ -63,18 +65,39 @@ skipInNode(describe)('plugin-meetings', () => {
63
65
  testUtils.delayedPromise(alice.meeting.join()),
64
66
  testUtils.waitForEvents([
65
67
  {scope: bob.webex.meetings, event: 'meeting:added', user: bob},
66
- {scope: chris.webex.meetings, event: 'meeting:added', user: chris}])
67
- ])));
68
+ {scope: chris.webex.meetings, event: 'meeting:added', user: chris}
69
+ ])
70
+ ]).then(() => {
71
+ const {meetingNumber} = bob.meeting.meetingInfo;
72
+
73
+ assert(meetingNumber === alice.meeting.meetingNumber, 'meetingNumber matches alice meeting number');
74
+ })));
75
+
76
+ xit('Should fetch user info using user hydra id with the new api', () => alice.webex.rooms.create({title: 'sample'})
77
+ .then((room) => MeetingInfoUtil.getDestinationType({
78
+ destination: room.creatorId,
79
+ webex: alice.webex
80
+ }))
81
+ .then((destinationType) => MeetingInfoUtil.getRequestBody(destinationType))
82
+ .then((res) => {
83
+ assert.exists(res.sipUrl, 'sipURL didnt exist');
84
+ assert.match(res.sipUrl, patterns.email); // assert sipURL is email
85
+ }));
68
86
 
69
87
  // Enable this test when we are going to enable the unified space meeeting .
70
88
  // We cannot change the config on load as the meetingInfo function loads dynamically
71
- xit('Should fetch meeting Info using the new api', async () => {
72
- alice.webex.meetings.config.experimental.enableUnifiedMeetings = true;
89
+ xit('Should fetch meeting info using space url with the new api', async () => {
73
90
  const res = await alice.webex.meetings.meetingInfo.fetchMeetingInfo(space.url, 'CONVERSATION_URL');
74
91
 
75
- assert.exists(res.meetingNumber);
92
+ assert.exists(res.body.meetingNumber);
93
+ });
94
+
95
+ xit('Bob fetches meeting info with SIP URI before joining', async () => {
96
+ const {sipUri} = alice.meeting;
97
+ const res = await bob.webex.meetings.meetingInfo.fetchMeetingInfo(sipUri, 'SIP_URI');
98
+ const {meetingNumber} = res.body;
76
99
 
77
- alice.webex.meetings.config.experimental.enableUnifiedMeetings = false;
100
+ assert(meetingNumber === alice.meeting.meetingNumber, 'meetingNumber matches alice meeting number');
78
101
  });
79
102
 
80
103
  it('Bob and chris joins space meeting', () => testUtils.waitForStateChange(alice.meeting, 'JOINED')