@webex/plugin-meetings 3.12.0-next.88 → 3.12.0-next.89
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.
- package/dist/aiEnableRequest/index.js +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +103 -66
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +229 -82
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meetings/index.js.map +1 -1
- package/dist/metrics/constants.js +1 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/types/meeting/index.d.ts +8 -0
- package/dist/types/meeting/locusMediaRequest.d.ts +12 -0
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +1 -1
- package/src/meeting/index.ts +33 -0
- package/src/meeting/locusMediaRequest.ts +106 -6
- package/src/meetings/index.ts +1 -0
- package/src/metrics/constants.ts +1 -0
- package/test/unit/spec/meeting/index.js +2 -0
- package/test/unit/spec/meeting/locusMediaRequest.ts +302 -4
|
@@ -13,11 +13,15 @@ import {
|
|
|
13
13
|
import testUtils from '../../../utils/testUtils';
|
|
14
14
|
import {Defer} from '@webex/common';
|
|
15
15
|
import {IP_VERSION} from '../../../../src/constants';
|
|
16
|
+
import Metrics from '../../../../src/metrics';
|
|
17
|
+
import BEHAVIORAL_METRICS from '../../../../src/metrics/constants';
|
|
16
18
|
|
|
17
19
|
describe('LocusMediaRequest.send()', () => {
|
|
18
20
|
let locusMediaRequest: LocusMediaRequest;
|
|
19
21
|
let webexRequestStub;
|
|
20
22
|
let mockWebex;
|
|
23
|
+
let sendBehavioralMetricStub;
|
|
24
|
+
const waitForSelfUrlChange = () => Promise.resolve();
|
|
21
25
|
|
|
22
26
|
const fakeLocusResponse = {
|
|
23
27
|
locus: {something: 'whatever'},
|
|
@@ -50,8 +54,16 @@ describe('LocusMediaRequest.send()', () => {
|
|
|
50
54
|
},
|
|
51
55
|
ipver: IP_VERSION.only_ipv4,
|
|
52
56
|
reachability: {
|
|
53
|
-
version:
|
|
54
|
-
result:
|
|
57
|
+
version: 1,
|
|
58
|
+
result: {
|
|
59
|
+
usedDiscoveryOptions: {
|
|
60
|
+
'early-call-min-clusters': 3,
|
|
61
|
+
},
|
|
62
|
+
metrics: {
|
|
63
|
+
'total-duration-ms': 10,
|
|
64
|
+
},
|
|
65
|
+
tests: {},
|
|
66
|
+
},
|
|
55
67
|
},
|
|
56
68
|
},
|
|
57
69
|
};
|
|
@@ -78,8 +90,16 @@ describe('LocusMediaRequest.send()', () => {
|
|
|
78
90
|
timeShot: '2023-05-23T08:03:49Z',
|
|
79
91
|
},
|
|
80
92
|
reachability: {
|
|
81
|
-
version:
|
|
82
|
-
result:
|
|
93
|
+
version: 1,
|
|
94
|
+
result: {
|
|
95
|
+
usedDiscoveryOptions: {
|
|
96
|
+
'early-call-min-clusters': 3,
|
|
97
|
+
},
|
|
98
|
+
metrics: {
|
|
99
|
+
'total-duration-ms': 10,
|
|
100
|
+
},
|
|
101
|
+
tests: {},
|
|
102
|
+
},
|
|
83
103
|
},
|
|
84
104
|
},
|
|
85
105
|
};
|
|
@@ -132,6 +152,7 @@ describe('LocusMediaRequest.send()', () => {
|
|
|
132
152
|
submitClientEvent: sinon.stub(),
|
|
133
153
|
},
|
|
134
154
|
};
|
|
155
|
+
sendBehavioralMetricStub = sinon.stub(Metrics, 'sendBehavioralMetric');
|
|
135
156
|
|
|
136
157
|
locusMediaRequest = new LocusMediaRequest(
|
|
137
158
|
{
|
|
@@ -143,6 +164,8 @@ describe('LocusMediaRequest.send()', () => {
|
|
|
143
164
|
correlationId: 'correlationId',
|
|
144
165
|
meetingId: 'meetingId',
|
|
145
166
|
preferTranscoding: true,
|
|
167
|
+
getCurrentSelfUrl: () => undefined,
|
|
168
|
+
waitForSelfUrlChange,
|
|
146
169
|
},
|
|
147
170
|
{
|
|
148
171
|
parent: mockWebex,
|
|
@@ -151,6 +174,10 @@ describe('LocusMediaRequest.send()', () => {
|
|
|
151
174
|
webexRequestStub = sinon.stub(locusMediaRequest, 'request').resolves(fakeLocusResponse);
|
|
152
175
|
});
|
|
153
176
|
|
|
177
|
+
afterEach(() => {
|
|
178
|
+
sendBehavioralMetricStub.restore();
|
|
179
|
+
});
|
|
180
|
+
|
|
154
181
|
const sendLocalMute = (muteOptions, overrides = {}) =>
|
|
155
182
|
locusMediaRequest.send({...exampleLocalMuteRequestBody, ...overrides, muteOptions});
|
|
156
183
|
|
|
@@ -167,6 +194,7 @@ describe('LocusMediaRequest.send()', () => {
|
|
|
167
194
|
|
|
168
195
|
webexRequestStub.resetHistory();
|
|
169
196
|
mockWebex.internal.newMetrics.submitClientEvent.resetHistory();
|
|
197
|
+
sendBehavioralMetricStub.resetHistory();
|
|
170
198
|
};
|
|
171
199
|
|
|
172
200
|
it('sends a roap message', async () => {
|
|
@@ -183,6 +211,276 @@ describe('LocusMediaRequest.send()', () => {
|
|
|
183
211
|
});
|
|
184
212
|
});
|
|
185
213
|
|
|
214
|
+
it('uses the latest resolved selfUrl when a queued roap message is sent', async () => {
|
|
215
|
+
let currentSelfUrl = 'oldSelfUrl';
|
|
216
|
+
locusMediaRequest = new LocusMediaRequest(
|
|
217
|
+
{
|
|
218
|
+
device: {
|
|
219
|
+
url: 'deviceUrl',
|
|
220
|
+
deviceType: 'deviceType',
|
|
221
|
+
regionCode: 'regionCode',
|
|
222
|
+
},
|
|
223
|
+
correlationId: 'correlationId',
|
|
224
|
+
meetingId: 'meetingId',
|
|
225
|
+
preferTranscoding: true,
|
|
226
|
+
getCurrentSelfUrl: () => currentSelfUrl,
|
|
227
|
+
waitForSelfUrlChange,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
parent: mockWebex,
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
webexRequestStub = sinon.stub(locusMediaRequest, 'request').resolves(fakeLocusResponse);
|
|
234
|
+
|
|
235
|
+
const request = cloneDeep(exampleRoapRequestBody);
|
|
236
|
+
|
|
237
|
+
request.selfUrl = 'oldSelfUrl';
|
|
238
|
+
currentSelfUrl = 'newSelfUrl';
|
|
239
|
+
|
|
240
|
+
const result = await locusMediaRequest.send(request);
|
|
241
|
+
|
|
242
|
+
assert.equal(result, fakeLocusResponse);
|
|
243
|
+
assert.calledOnce(webexRequestStub);
|
|
244
|
+
assert.equal(webexRequestStub.getCall(0).args[0].uri, 'newSelfUrl/media');
|
|
245
|
+
assert.calledOnceWithExactly(
|
|
246
|
+
sendBehavioralMetricStub,
|
|
247
|
+
BEHAVIORAL_METRICS.LOCUS_MEDIA_REQUEST_RETRY,
|
|
248
|
+
{
|
|
249
|
+
correlation_id: 'correlationId',
|
|
250
|
+
reason: 'selfUrlUpdatedBeforeMediaRequest',
|
|
251
|
+
}
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('retries a roap message with the latest resolved selfUrl after a conflict', async () => {
|
|
256
|
+
let currentSelfUrl = 'oldSelfUrl';
|
|
257
|
+
const conflictError = {statusCode: 409, message: 'conflict'};
|
|
258
|
+
|
|
259
|
+
locusMediaRequest = new LocusMediaRequest(
|
|
260
|
+
{
|
|
261
|
+
device: {
|
|
262
|
+
url: 'deviceUrl',
|
|
263
|
+
deviceType: 'deviceType',
|
|
264
|
+
regionCode: 'regionCode',
|
|
265
|
+
},
|
|
266
|
+
correlationId: 'correlationId',
|
|
267
|
+
meetingId: 'meetingId',
|
|
268
|
+
preferTranscoding: true,
|
|
269
|
+
getCurrentSelfUrl: () => currentSelfUrl,
|
|
270
|
+
waitForSelfUrlChange,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
parent: mockWebex,
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
webexRequestStub = sinon.stub(locusMediaRequest, 'request');
|
|
277
|
+
webexRequestStub.onFirstCall().callsFake(() => {
|
|
278
|
+
currentSelfUrl = 'newSelfUrl';
|
|
279
|
+
|
|
280
|
+
return Promise.reject(conflictError);
|
|
281
|
+
});
|
|
282
|
+
webexRequestStub.onSecondCall().resolves(fakeLocusResponse);
|
|
283
|
+
|
|
284
|
+
const request = cloneDeep(exampleRoapRequestBody);
|
|
285
|
+
|
|
286
|
+
request.selfUrl = 'oldSelfUrl';
|
|
287
|
+
request.roapMessage.messageType = 'ANSWER';
|
|
288
|
+
|
|
289
|
+
const result = await locusMediaRequest.send(request);
|
|
290
|
+
|
|
291
|
+
assert.equal(result, fakeLocusResponse);
|
|
292
|
+
assert.calledTwice(webexRequestStub);
|
|
293
|
+
assert.equal(webexRequestStub.getCall(0).args[0].uri, 'oldSelfUrl/media');
|
|
294
|
+
assert.equal(webexRequestStub.getCall(1).args[0].uri, 'newSelfUrl/media');
|
|
295
|
+
assert.calledWith(sendBehavioralMetricStub, BEHAVIORAL_METRICS.LOCUS_MEDIA_REQUEST_RETRY, {
|
|
296
|
+
correlation_id: 'correlationId',
|
|
297
|
+
reason: 'selfUrlUpdatedBeforeMediaRequest',
|
|
298
|
+
});
|
|
299
|
+
// The updated selfUrl is detected twice: once while deciding to retry and
|
|
300
|
+
// again when the request is re-resolved for the actual re-send.
|
|
301
|
+
assert.calledTwice(sendBehavioralMetricStub);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('does not retry a roap conflict when the resolved selfUrl has not changed', async () => {
|
|
305
|
+
locusMediaRequest = new LocusMediaRequest(
|
|
306
|
+
{
|
|
307
|
+
device: {
|
|
308
|
+
url: 'deviceUrl',
|
|
309
|
+
deviceType: 'deviceType',
|
|
310
|
+
regionCode: 'regionCode',
|
|
311
|
+
},
|
|
312
|
+
correlationId: 'correlationId',
|
|
313
|
+
meetingId: 'meetingId',
|
|
314
|
+
preferTranscoding: true,
|
|
315
|
+
getCurrentSelfUrl: () => 'sameSelfUrl',
|
|
316
|
+
waitForSelfUrlChange,
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
parent: mockWebex,
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
webexRequestStub = sinon.stub(locusMediaRequest, 'request').rejects({
|
|
323
|
+
statusCode: 409,
|
|
324
|
+
message: 'conflict',
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const request = cloneDeep(exampleRoapRequestBody);
|
|
328
|
+
|
|
329
|
+
request.selfUrl = 'sameSelfUrl';
|
|
330
|
+
request.roapMessage.messageType = 'ANSWER';
|
|
331
|
+
|
|
332
|
+
await assert.isRejected(locusMediaRequest.send(request));
|
|
333
|
+
|
|
334
|
+
assert.calledOnce(webexRequestStub);
|
|
335
|
+
assert.equal(webexRequestStub.getCall(0).args[0].uri, 'sameSelfUrl/media');
|
|
336
|
+
assert.calledWith(sendBehavioralMetricStub, BEHAVIORAL_METRICS.LOCUS_MEDIA_REQUEST_RETRY, {
|
|
337
|
+
correlation_id: 'correlationId',
|
|
338
|
+
reason: 'selfUrlNotChangedAfterWait',
|
|
339
|
+
retryAttempt: 0,
|
|
340
|
+
roapMessageType: 'ANSWER',
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('retries a roap conflict when selfUrl changes after waiting', async () => {
|
|
345
|
+
let currentSelfUrl = 'sameSelfUrl';
|
|
346
|
+
const waitForSelfUrlChangeAndUpdate = async () => {
|
|
347
|
+
currentSelfUrl = 'newSelfUrlAfterWait';
|
|
348
|
+
};
|
|
349
|
+
locusMediaRequest = new LocusMediaRequest(
|
|
350
|
+
{
|
|
351
|
+
device: {
|
|
352
|
+
url: 'deviceUrl',
|
|
353
|
+
deviceType: 'deviceType',
|
|
354
|
+
regionCode: 'regionCode',
|
|
355
|
+
},
|
|
356
|
+
correlationId: 'correlationId',
|
|
357
|
+
meetingId: 'meetingId',
|
|
358
|
+
preferTranscoding: true,
|
|
359
|
+
getCurrentSelfUrl: () => currentSelfUrl,
|
|
360
|
+
waitForSelfUrlChange: waitForSelfUrlChangeAndUpdate,
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
parent: mockWebex,
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
webexRequestStub = sinon.stub(locusMediaRequest, 'request');
|
|
367
|
+
webexRequestStub.onFirstCall().rejects({statusCode: 409, message: 'conflict'});
|
|
368
|
+
webexRequestStub.onSecondCall().resolves(fakeLocusResponse);
|
|
369
|
+
|
|
370
|
+
const request = cloneDeep(exampleRoapRequestBody);
|
|
371
|
+
|
|
372
|
+
request.selfUrl = 'sameSelfUrl';
|
|
373
|
+
request.roapMessage.messageType = 'ANSWER';
|
|
374
|
+
|
|
375
|
+
const result = await locusMediaRequest.send(request);
|
|
376
|
+
|
|
377
|
+
assert.equal(result, fakeLocusResponse);
|
|
378
|
+
assert.calledTwice(webexRequestStub);
|
|
379
|
+
assert.equal(webexRequestStub.getCall(0).args[0].uri, 'sameSelfUrl/media');
|
|
380
|
+
assert.equal(webexRequestStub.getCall(1).args[0].uri, 'newSelfUrlAfterWait/media');
|
|
381
|
+
assert.calledWith(sendBehavioralMetricStub, BEHAVIORAL_METRICS.LOCUS_MEDIA_REQUEST_RETRY, {
|
|
382
|
+
correlation_id: 'correlationId',
|
|
383
|
+
reason: 'selfUrlChangedAfterWait',
|
|
384
|
+
retryAttempt: 0,
|
|
385
|
+
roapMessageType: 'ANSWER',
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('retries a local mute request with the latest resolved selfUrl after a conflict', async () => {
|
|
390
|
+
let currentSelfUrl = 'oldSelfUrl';
|
|
391
|
+
|
|
392
|
+
locusMediaRequest = new LocusMediaRequest(
|
|
393
|
+
{
|
|
394
|
+
device: {
|
|
395
|
+
url: 'deviceUrl',
|
|
396
|
+
deviceType: 'deviceType',
|
|
397
|
+
regionCode: 'regionCode',
|
|
398
|
+
},
|
|
399
|
+
correlationId: 'correlationId',
|
|
400
|
+
meetingId: 'meetingId',
|
|
401
|
+
preferTranscoding: true,
|
|
402
|
+
getCurrentSelfUrl: () => currentSelfUrl,
|
|
403
|
+
waitForSelfUrlChange,
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
parent: mockWebex,
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
webexRequestStub = sinon.stub(locusMediaRequest, 'request').resolves(fakeLocusResponse);
|
|
410
|
+
|
|
411
|
+
// Create the confluence on this instance, otherwise the LocalMute request
|
|
412
|
+
// short-circuits (resolves without sending) while confluence is "not created".
|
|
413
|
+
await ensureConfluenceCreated();
|
|
414
|
+
|
|
415
|
+
webexRequestStub.reset();
|
|
416
|
+
webexRequestStub.onFirstCall().callsFake(() => {
|
|
417
|
+
currentSelfUrl = 'newSelfUrl';
|
|
418
|
+
|
|
419
|
+
return Promise.reject({statusCode: 409, message: 'conflict'});
|
|
420
|
+
});
|
|
421
|
+
webexRequestStub.onSecondCall().resolves(fakeLocusResponse);
|
|
422
|
+
|
|
423
|
+
const result = await locusMediaRequest.send({
|
|
424
|
+
...exampleLocalMuteRequestBody,
|
|
425
|
+
selfUrl: 'oldSelfUrl',
|
|
426
|
+
muteOptions: {audioMuted: false, videoMuted: true},
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
assert.equal(result, fakeLocusResponse);
|
|
430
|
+
assert.calledTwice(webexRequestStub);
|
|
431
|
+
assert.equal(webexRequestStub.getCall(0).args[0].uri, 'oldSelfUrl/media');
|
|
432
|
+
assert.equal(webexRequestStub.getCall(1).args[0].uri, 'newSelfUrl/media');
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('stops retrying roap conflicts after the selfUrl retry limit', async () => {
|
|
436
|
+
let currentSelfUrl = 'oldSelfUrl';
|
|
437
|
+
locusMediaRequest = new LocusMediaRequest(
|
|
438
|
+
{
|
|
439
|
+
device: {
|
|
440
|
+
url: 'deviceUrl',
|
|
441
|
+
deviceType: 'deviceType',
|
|
442
|
+
regionCode: 'regionCode',
|
|
443
|
+
},
|
|
444
|
+
correlationId: 'correlationId',
|
|
445
|
+
meetingId: 'meetingId',
|
|
446
|
+
preferTranscoding: true,
|
|
447
|
+
getCurrentSelfUrl: () => currentSelfUrl,
|
|
448
|
+
waitForSelfUrlChange,
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
parent: mockWebex,
|
|
452
|
+
}
|
|
453
|
+
);
|
|
454
|
+
webexRequestStub = sinon.stub(locusMediaRequest, 'request');
|
|
455
|
+
webexRequestStub.onFirstCall().callsFake(() => {
|
|
456
|
+
currentSelfUrl = 'secondSelfUrl';
|
|
457
|
+
|
|
458
|
+
return Promise.reject({statusCode: 409, message: 'conflict'});
|
|
459
|
+
});
|
|
460
|
+
webexRequestStub.onSecondCall().callsFake(() => {
|
|
461
|
+
currentSelfUrl = 'thirdSelfUrl';
|
|
462
|
+
|
|
463
|
+
return Promise.reject({statusCode: 409, message: 'conflict'});
|
|
464
|
+
});
|
|
465
|
+
webexRequestStub.onThirdCall().callsFake(() => {
|
|
466
|
+
currentSelfUrl = 'fourthSelfUrl';
|
|
467
|
+
|
|
468
|
+
return Promise.reject({statusCode: 409, message: 'conflict'});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
const request = cloneDeep(exampleRoapRequestBody);
|
|
472
|
+
|
|
473
|
+
request.selfUrl = 'oldSelfUrl';
|
|
474
|
+
request.roapMessage.messageType = 'ANSWER';
|
|
475
|
+
|
|
476
|
+
await assert.isRejected(locusMediaRequest.send(request));
|
|
477
|
+
|
|
478
|
+
assert.calledThrice(webexRequestStub);
|
|
479
|
+
assert.equal(webexRequestStub.getCall(0).args[0].uri, 'oldSelfUrl/media');
|
|
480
|
+
assert.equal(webexRequestStub.getCall(1).args[0].uri, 'secondSelfUrl/media');
|
|
481
|
+
assert.equal(webexRequestStub.getCall(2).args[0].uri, 'thirdSelfUrl/media');
|
|
482
|
+
});
|
|
483
|
+
|
|
186
484
|
it('sends correct metric event when roap message fails', async () => {
|
|
187
485
|
webexRequestStub.rejects({code: 300, message: 'fake error'});
|
|
188
486
|
await assert.isRejected(sendRoapMessage('OFFER'));
|