@webex/plugin-meetings 1.158.0 → 1.159.2

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.
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import '@webex/internal-plugin-mercury';
6
+ import '@webex/internal-plugin-conversation';
6
7
  import {WebexPlugin} from '@webex/webex-core';
7
8
 
8
9
  import 'webrtc-adapter';
@@ -163,6 +164,15 @@ export default class Meetings extends WebexPlugin {
163
164
  */
164
165
  this.registered = false;
165
166
 
167
+ /**
168
+ * This values indicates the preferred webex site the user will start there meeting, getsits value from {@link Meetings#register}
169
+ * @instance
170
+ * @type {String}
171
+ * @private
172
+ * @memberof Meetings
173
+ */
174
+ this.preferredWebexSite = '';
175
+
166
176
  /**
167
177
  * The public interface for the internal Media util files. These are helpful to expose outside the context
168
178
  * of a meeting so that a user can access media without creating a meeting instance.
@@ -407,6 +417,22 @@ export default class Meetings extends WebexPlugin {
407
417
  }
408
418
  }
409
419
 
420
+ /**
421
+ * API to toggle starting adhoc meeting
422
+ * @param {Boolean} changeState
423
+ * @private
424
+ * @memberof Meetings
425
+ * @returns {undefined}
426
+ */
427
+ _toggleAdhocMeetings(changeState) {
428
+ if (typeof changeState !== 'boolean') {
429
+ return;
430
+ }
431
+ if (this.config?.experimental?.enableAdhocMeetings !== changeState) {
432
+ this.config.experimental.enableAdhocMeetings = changeState;
433
+ }
434
+ }
435
+
410
436
  /**
411
437
  * Explicitly sets up the meetings plugin by registering
412
438
  * the device, connecting to mercury, and listening for locus events.
@@ -430,6 +456,7 @@ export default class Meetings extends WebexPlugin {
430
456
  }
431
457
 
432
458
  return Promise.all([
459
+ this.fetchUserPreferredWebexSite(),
433
460
  this.getGeoHint(),
434
461
  this.startReachability().catch((error) => {
435
462
  LoggerProxy.logger.error(`Meetings:index#register --> GDM error, ${error.message}`);
@@ -604,12 +631,29 @@ export default class Meetings extends WebexPlugin {
604
631
  });
605
632
  }
606
633
 
634
+ /**
635
+ * Fetch user preferred webex site information
636
+ * This also has other infomation about the user
637
+ * @returns {Promise}
638
+ * @private
639
+ * @memberof Meetings
640
+ */
641
+ fetchUserPreferredWebexSite() {
642
+ return this.request.fetchLoginUserInformation().then((res) => {
643
+ if (res && res.userPreferences) {
644
+ this.preferredWebexSite = MeetingsUtil.parseUserPreferences(res?.userPreferences);
645
+ }
646
+ });
647
+ }
648
+
649
+
607
650
  /**
608
651
  * gets the personal meeting room instance, for saved PMR values for this user
609
652
  * @returns {PersonalMeetingRoom}
610
653
  * @public
611
654
  * @memberof Meetings
612
655
  */
656
+
613
657
  getPersonalMeetingRoom() {
614
658
  return this.personalMeetingRoom;
615
659
  }
@@ -740,7 +784,9 @@ export default class Meetings extends WebexPlugin {
740
784
  orgId: this.webex.internal.device.orgId,
741
785
  roapSeq: 0,
742
786
  locus: type === _LOCUS_ID_ ? destination : null, // pass the locus object if present
743
- meetingInfoProvider: this.meetingInfo
787
+ meetingInfoProvider: this.meetingInfo,
788
+ destination,
789
+ destinationType: type,
744
790
  },
745
791
  {
746
792
  parent: this.webex
@@ -750,7 +796,7 @@ export default class Meetings extends WebexPlugin {
750
796
  this.meetingCollection.set(meeting);
751
797
 
752
798
  try {
753
- await meeting.fetchMeetingInfo({destination, type});
799
+ await meeting.fetchMeetingInfo({});
754
800
  }
755
801
  catch (err) {
756
802
  if (!(err instanceof CaptchaError) && !(err instanceof PasswordError)) {
@@ -759,8 +805,6 @@ export default class Meetings extends WebexPlugin {
759
805
  LoggerProxy.logger.info('Meetings:index#createMeeting --> Info assuming this destination is a 1:1 or wireless share');
760
806
  }
761
807
  LoggerProxy.logger.debug(`Meetings:index#createMeeting --> Debug ${err} fetching /meetingInfo for creation.`);
762
- // We need to save this info for future reference
763
- meeting.destination = destination;
764
808
  }
765
809
  finally {
766
810
  // For type LOCUS_ID we need to parse the locus object to get the information
@@ -12,9 +12,9 @@ import {
12
12
  */
13
13
  export default class MeetingRequest extends StatelessWebexPlugin {
14
14
  /**
15
- * get all the active meetings for the user
16
- * @returns {Array} return locus array
17
- */
15
+ * get all the active meetings for the user
16
+ * @returns {Array} return locus array
17
+ */
18
18
  getActiveMeetings() {
19
19
  return this.request({
20
20
  api: API.LOCUS,
@@ -27,13 +27,22 @@ export default class MeetingRequest extends StatelessWebexPlugin {
27
27
  }
28
28
 
29
29
  /**
30
- * fetch geoHit for the user
31
- * @returns {Promise<object>} geoHintInfo
32
- */
30
+ * fetch geoHit for the user
31
+ * @returns {Promise<object>} geoHintInfo
32
+ */
33
33
  fetchGeoHint() {
34
34
  return this.webex.internal.services.fetchClientRegionInfo();
35
35
  }
36
36
 
37
+ /**
38
+ * fetch login user information
39
+ * @returns {Promise<object>} loginUserInformation
40
+ */
41
+ fetchLoginUserInformation() {
42
+ return this.webex.internal.services.fetchLoginUserInformation();
43
+ }
44
+
45
+
37
46
  // locus federation, determines and populate locus if the responseBody has remote URLs to fetch locus details
38
47
 
39
48
  /**
@@ -64,6 +64,25 @@ MeetingsUtil.checkForCorrelationId = (deviceUrl, locus) => {
64
64
  return false;
65
65
  };
66
66
 
67
+ MeetingsUtil.parseUserPreferences = (userPreferences) => {
68
+ let webexSite = null;
69
+
70
+ userPreferences.find((item) => {
71
+ // eslint-disable-next-line no-useless-escape
72
+ const regex = /"preferredWebExSite\":\"(\S+)\"/;
73
+ const preferredSite = item.match(regex);
74
+
75
+ if (preferredSite) {
76
+ webexSite = preferredSite[1];
77
+
78
+ return true;
79
+ }
80
+
81
+ return false;
82
+ });
83
+
84
+ return webexSite;
85
+ };
67
86
 
68
87
  /**
69
88
  * Will check to see if the H.264 media codec is supported.
@@ -36,7 +36,8 @@ import {
36
36
  PASSWORD_STATUS,
37
37
  EVENTS,
38
38
  EVENT_TRIGGERS,
39
- _SIP_URI_
39
+ _SIP_URI_,
40
+ _MEETING_ID_,
40
41
  } from '@webex/plugin-meetings/src/constants';
41
42
  import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
42
43
 
@@ -126,6 +127,7 @@ describe('plugin-meetings', () => {
126
127
  let test2;
127
128
  let test3;
128
129
  let test4;
130
+ let testDestination;
129
131
 
130
132
  beforeEach(() => {
131
133
  webex = new MockWebex({
@@ -175,13 +177,16 @@ describe('plugin-meetings', () => {
175
177
  test2 = `test2-${uuid.v4()}`;
176
178
  test3 = `test3-${uuid.v4()}`;
177
179
  test4 = `test4-${uuid.v4()}`;
180
+ testDestination = `testDestination-${uuid.v4()}`;
178
181
 
179
182
  meeting = new Meeting(
180
183
  {
181
184
  userId: uuid1,
182
185
  resource: uuid2,
183
186
  deviceUrl: uuid3,
184
- locus: {url: url1}
187
+ locus: {url: url1},
188
+ destination: testDestination,
189
+ destinationType: _MEETING_ID_,
185
190
  },
186
191
  {
187
192
  parent: webex
@@ -227,6 +232,8 @@ describe('plugin-meetings', () => {
227
232
  assert.equal(meeting.passwordStatus, PASSWORD_STATUS.UNKNOWN);
228
233
  assert.equal(meeting.requiredCaptcha, null);
229
234
  assert.equal(meeting.meetingInfoFailureReason, undefined);
235
+ assert.equal(meeting.destination, testDestination);
236
+ assert.equal(meeting.destinationType, _MEETING_ID_);
230
237
  });
231
238
  });
232
239
  describe('#invite', () => {
@@ -2141,8 +2148,11 @@ describe('plugin-meetings', () => {
2141
2148
  it('calls meetingInfoProvider with all the right parameters and parses the result', async () => {
2142
2149
  meeting.attrs.meetingInfoProvider = {fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO})};
2143
2150
  meeting.requiredCaptcha = FAKE_SDK_CAPTCHA_INFO;
2151
+ meeting.destination = FAKE_DESTINATION;
2152
+ meeting.destinationType = FAKE_TYPE;
2153
+
2144
2154
  await meeting.fetchMeetingInfo({
2145
- destination: FAKE_DESTINATION, type: FAKE_TYPE, password: FAKE_PASSWORD, captchaCode: FAKE_CAPTCHA_CODE
2155
+ password: FAKE_PASSWORD, captchaCode: FAKE_CAPTCHA_CODE
2146
2156
  });
2147
2157
 
2148
2158
  assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, FAKE_PASSWORD, {code: FAKE_CAPTCHA_CODE, id: FAKE_CAPTCHA_ID});
@@ -2156,9 +2166,11 @@ describe('plugin-meetings', () => {
2156
2166
  it('fails if captchaCode is provided when captcha not needed', async () => {
2157
2167
  meeting.attrs.meetingInfoProvider = {fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO})};
2158
2168
  meeting.requiredCaptcha = null;
2169
+ meeting.destination = FAKE_DESTINATION;
2170
+ meeting.destinationType = FAKE_TYPE;
2159
2171
 
2160
2172
  await assert.isRejected(meeting.fetchMeetingInfo({
2161
- destination: FAKE_DESTINATION, type: FAKE_TYPE, captchaCode: FAKE_CAPTCHA_CODE
2173
+ captchaCode: FAKE_CAPTCHA_CODE
2162
2174
  }), Error, 'fetchMeetingInfo() called with captchaCode when captcha was not required');
2163
2175
 
2164
2176
  assert.notCalled(meeting.attrs.meetingInfoProvider.fetchMeetingInfo);
@@ -2167,22 +2179,24 @@ describe('plugin-meetings', () => {
2167
2179
  it('fails if password is provided when not required', async () => {
2168
2180
  meeting.attrs.meetingInfoProvider = {fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO})};
2169
2181
  meeting.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
2182
+ meeting.destination = FAKE_DESTINATION;
2183
+ meeting.destinationType = FAKE_TYPE;
2170
2184
 
2171
2185
  await assert.isRejected(meeting.fetchMeetingInfo({
2172
- destination: FAKE_DESTINATION, type: FAKE_TYPE, password: FAKE_PASSWORD
2186
+ password: FAKE_PASSWORD
2173
2187
  }), Error, 'fetchMeetingInfo() called with password when password was not required');
2174
2188
 
2175
2189
  assert.notCalled(meeting.attrs.meetingInfoProvider.fetchMeetingInfo);
2176
2190
  });
2177
2191
 
2178
2192
  it('handles meetingInfoProvider requiring password', async () => {
2193
+ meeting.destination = FAKE_DESTINATION;
2194
+ meeting.destinationType = FAKE_TYPE;
2179
2195
  meeting.attrs.meetingInfoProvider = {
2180
2196
  fetchMeetingInfo: sinon.stub().throws(new MeetingInfoV2PasswordError(403004, FAKE_MEETING_INFO))
2181
2197
  };
2182
2198
 
2183
- await assert.isRejected(meeting.fetchMeetingInfo({
2184
- destination: FAKE_DESTINATION, type: FAKE_TYPE
2185
- }), PasswordError);
2199
+ await assert.isRejected(meeting.fetchMeetingInfo({}), PasswordError);
2186
2200
 
2187
2201
  assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, null, null);
2188
2202
 
@@ -2193,13 +2207,15 @@ describe('plugin-meetings', () => {
2193
2207
  });
2194
2208
 
2195
2209
  it('handles meetingInfoProvider requiring captcha because of wrong password', async () => {
2210
+ meeting.destination = FAKE_DESTINATION;
2211
+ meeting.destinationType = FAKE_TYPE;
2196
2212
  meeting.attrs.meetingInfoProvider = {
2197
2213
  fetchMeetingInfo: sinon.stub().throws(new MeetingInfoV2CaptchaError(423005, FAKE_SDK_CAPTCHA_INFO))
2198
2214
  };
2199
2215
  meeting.requiredCaptcha = null;
2200
2216
 
2201
2217
  await assert.isRejected(meeting.fetchMeetingInfo({
2202
- destination: FAKE_DESTINATION, type: FAKE_TYPE, password: 'aaa'
2218
+ password: 'aaa'
2203
2219
  }), CaptchaError);
2204
2220
 
2205
2221
  assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', null);
@@ -2216,13 +2232,15 @@ describe('plugin-meetings', () => {
2216
2232
  });
2217
2233
 
2218
2234
  it('handles meetingInfoProvider requiring captcha because of wrong captcha', async () => {
2235
+ meeting.destination = FAKE_DESTINATION;
2236
+ meeting.destinationType = FAKE_TYPE;
2219
2237
  meeting.attrs.meetingInfoProvider = {
2220
2238
  fetchMeetingInfo: sinon.stub().throws(new MeetingInfoV2CaptchaError(423005, FAKE_SDK_CAPTCHA_INFO))
2221
2239
  };
2222
2240
  meeting.requiredCaptcha = FAKE_SDK_CAPTCHA_INFO;
2223
2241
 
2224
2242
  await assert.isRejected(meeting.fetchMeetingInfo({
2225
- destination: FAKE_DESTINATION, type: FAKE_TYPE, password: 'aaa', captchaCode: 'bbb'
2243
+ password: 'aaa', captchaCode: 'bbb'
2226
2244
  }), CaptchaError);
2227
2245
 
2228
2246
  assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', {code: 'bbb', id: FAKE_CAPTCHA_ID});
@@ -2234,6 +2252,8 @@ describe('plugin-meetings', () => {
2234
2252
  });
2235
2253
 
2236
2254
  it('handles successful response when good password is passed', async () => {
2255
+ meeting.destination = FAKE_DESTINATION;
2256
+ meeting.destinationType = FAKE_TYPE;
2237
2257
  meeting.attrs.meetingInfoProvider = {
2238
2258
  fetchMeetingInfo: sinon.stub().resolves(
2239
2259
  {
@@ -2245,7 +2265,7 @@ describe('plugin-meetings', () => {
2245
2265
  meeting.passwordStatus = PASSWORD_STATUS.REQUIRED;
2246
2266
 
2247
2267
  await meeting.fetchMeetingInfo({
2248
- destination: FAKE_DESTINATION, type: FAKE_TYPE, password: 'aaa'
2268
+ password: 'aaa'
2249
2269
  });
2250
2270
 
2251
2271
  assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', null);
@@ -2257,6 +2277,8 @@ describe('plugin-meetings', () => {
2257
2277
  });
2258
2278
 
2259
2279
  it('refreshes captcha when captcha was required and we received 403 error code', async () => {
2280
+ meeting.destination = FAKE_DESTINATION;
2281
+ meeting.destinationType = FAKE_TYPE;
2260
2282
  const refreshedCaptcha = {
2261
2283
  captchaID: FAKE_WBXAPPAPI_CAPTCHA_INFO.captchaID,
2262
2284
  verificationImageURL: FAKE_WBXAPPAPI_CAPTCHA_INFO.verificationImageURL,
@@ -2273,9 +2295,11 @@ describe('plugin-meetings', () => {
2273
2295
  ));
2274
2296
  meeting.passwordStatus = PASSWORD_STATUS.REQUIRED;
2275
2297
  meeting.requiredCaptcha = FAKE_SDK_CAPTCHA_INFO;
2298
+ meeting.destination = FAKE_DESTINATION;
2299
+ meeting.destinationType = FAKE_TYPE;
2276
2300
 
2277
2301
  await assert.isRejected(meeting.fetchMeetingInfo({
2278
- destination: FAKE_DESTINATION, type: FAKE_TYPE, password: 'aaa', captchaCode: 'bbb'
2302
+ password: 'aaa', captchaCode: 'bbb'
2279
2303
  }));
2280
2304
 
2281
2305
  assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', {code: 'bbb', id: FAKE_CAPTCHA_ID});
@@ -2319,7 +2343,7 @@ describe('plugin-meetings', () => {
2319
2343
  };
2320
2344
 
2321
2345
  await assert.isRejected(meeting.fetchMeetingInfo({
2322
- destination: 'something@somecompany.com', type: _SIP_URI_, password: ''
2346
+ password: ''
2323
2347
  }), CaptchaError);
2324
2348
 
2325
2349
  assert.deepEqual(meeting.requiredCaptcha, FAKE_SDK_CAPTCHA_INFO);
@@ -2352,6 +2376,10 @@ describe('plugin-meetings', () => {
2352
2376
  assert.equal(result.isPasswordValid, true);
2353
2377
  assert.equal(result.requiredCaptcha, null);
2354
2378
  assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.NONE);
2379
+ assert.calledWith(meeting.fetchMeetingInfo, {
2380
+ password: 'password',
2381
+ captchaCode: 'captcha id',
2382
+ });
2355
2383
  });
2356
2384
  it('handles PasswordError returned by fetchMeetingInfo', async () => {
2357
2385
  meeting.fetchMeetingInfo = sinon.stub().callsFake(() => {
@@ -10,12 +10,34 @@ import Mercury from '@webex/internal-plugin-mercury';
10
10
  import Meetings from '@webex/plugin-meetings/src/meetings';
11
11
  import {
12
12
  _MEETING_ID_,
13
- _PERSONAL_ROOM_
13
+ _PERSONAL_ROOM_,
14
+ _CONVERSATION_URL_
14
15
  } from '@webex/plugin-meetings/src/constants';
15
- import MeetingInfo, {MeetingInfoV2PasswordError, MeetingInfoV2CaptchaError} from '@webex/plugin-meetings/src/meeting-info/meeting-info-v2';
16
+ import MeetingInfo, {MeetingInfoV2PasswordError, MeetingInfoV2CaptchaError, MeetingInfoV2AdhocMeetingError} from '@webex/plugin-meetings/src/meeting-info/meeting-info-v2';
16
17
  import MeetingInfoUtil from '@webex/plugin-meetings/src/meeting-info/utilv2';
17
18
 
18
19
  describe('plugin-meetings', () => {
20
+ const conversation = {
21
+ displayName: 'displayName',
22
+ url: 'conversationUrl',
23
+ encryptionKeyUrl: 'encryptionKeyUrl',
24
+ kmsResourceObjectUrl: 'kmsResourceObjectUrl',
25
+ participants: {
26
+ items: [
27
+ {
28
+ id: '344ea183-9d5d-4e77-aed',
29
+ emailAddress: 'testUser1@cisco.com',
30
+ entryUUID: '344ea183-9d5d-4e77-'
31
+
32
+ },
33
+ {
34
+ id: '40b446fe-175c-4628-8a9d',
35
+ emailAddress: 'testUser2@cisco.com',
36
+ entryUUID: '40b446fe-175c-4628'
37
+ }
38
+ ]
39
+ }
40
+ };
19
41
  let webex;
20
42
  let meetingInfo = null;
21
43
 
@@ -29,6 +51,9 @@ describe('plugin-meetings', () => {
29
51
  }
30
52
  });
31
53
 
54
+ webex.meetings.preferredWebexSite = 'go.webex.com';
55
+ webex.config.meetings = {experimental: {enableUnifiedMeetings: true, enableAdhocMeetings: true}};
56
+
32
57
  Object.assign(webex.internal, {
33
58
  device: {
34
59
  deviceType: 'FAKE_DEVICE',
@@ -41,6 +66,9 @@ describe('plugin-meetings', () => {
41
66
  disconnect: sinon.stub().returns(Promise.resolve()),
42
67
  on: () => {},
43
68
  off: () => {}
69
+ },
70
+ conversation: {
71
+ get: sinon.stub().returns(Promise.resolve(conversation))
44
72
  }
45
73
  });
46
74
 
@@ -98,6 +126,40 @@ describe('plugin-meetings', () => {
98
126
  });
99
127
  });
100
128
 
129
+ it('create adhoc meeting when conversationUrl passed with enableAdhocMeetings toggle', async () => {
130
+ sinon.stub(meetingInfo, 'createAdhocSpaceMeeting').returns(Promise.resolve());
131
+ await meetingInfo.fetchMeetingInfo('conversationUrl', _CONVERSATION_URL_);
132
+
133
+ assert.calledWith(meetingInfo.createAdhocSpaceMeeting, 'conversationUrl');
134
+ assert.notCalled(webex.request);
135
+ meetingInfo.createAdhocSpaceMeeting.restore();
136
+ });
137
+
138
+ it('should not call createAdhocSpaceMeeting if enableAdhocMeetings toggle is off', async () => {
139
+ webex.config.meetings.experimental.enableAdhocMeetings = false;
140
+ sinon.stub(meetingInfo, 'createAdhocSpaceMeeting').returns(Promise.resolve());
141
+
142
+ await meetingInfo.fetchMeetingInfo('conversationUrl', _CONVERSATION_URL_);
143
+
144
+ assert.notCalled(meetingInfo.createAdhocSpaceMeeting);
145
+ assert.called(webex.request);
146
+ meetingInfo.createAdhocSpaceMeeting.restore();
147
+ });
148
+
149
+ it('should throw an error MeetingInfoV2AdhocMeetingError if not able to start adhoc meeting for a conversation', async () => {
150
+ webex.config.meetings.experimental.enableAdhocMeetings = true;
151
+
152
+ webex.request = sinon.stub().rejects({statusCode: 403, body: {code: 400000, message: 'Input is invalid'}});
153
+ try {
154
+ await meetingInfo.createAdhocSpaceMeeting('conversationUrl');
155
+ }
156
+ catch (err) {
157
+ assert.instanceOf(err, MeetingInfoV2AdhocMeetingError);
158
+ assert.deepEqual(err.message, 'Input is invalid, code=400000');
159
+ assert.equal(err.wbxAppApiCode, 400000);
160
+ }
161
+ });
162
+
101
163
  it('should throw MeetingInfoV2PasswordError for 403 response', async () => {
102
164
  const FAKE_MEETING_INFO = {blablabla: 'some_fake_meeting_info'};
103
165
 
@@ -154,5 +216,40 @@ describe('plugin-meetings', () => {
154
216
  });
155
217
  });
156
218
  });
219
+
220
+
221
+ describe('createAdhocSpaceMeeting', () => {
222
+ it('Make a request to /instantSpace when conversationUrl', async () => {
223
+ const conversationUrl = 'https://conversationUrl/xxx';
224
+ const invitee = [];
225
+
226
+ invitee.push({
227
+ email: conversation.participants.items[0].emailAddress,
228
+ ciUserUuid: conversation.participants.items[0].entryUUID
229
+ });
230
+
231
+ invitee.push({
232
+ email: conversation.participants.items[1].emailAddress,
233
+ ciUserUuid: conversation.participants.items[1].entryUUID
234
+ });
235
+
236
+ await meetingInfo.createAdhocSpaceMeeting(conversationUrl);
237
+
238
+ assert.calledWith(webex.internal.conversation.get, {url: conversationUrl},
239
+ {includeParticipants: true, disableTransform: true});
240
+
241
+ assert.calledWith(webex.request, {
242
+ method: 'POST',
243
+ uri: 'https://go.webex.com/wbxappapi/v2/meetings/spaceInstant',
244
+ body: {
245
+ title: conversation.displayName,
246
+ spaceUrl: conversation.url,
247
+ keyUrl: conversation.encryptionKeyUrl,
248
+ kroUrl: conversation.kmsResourceObjectUrl,
249
+ invitees: invitee
250
+ }
251
+ });
252
+ });
253
+ });
157
254
  });
158
255
  });