@webex/plugin-meetings 3.12.0-next.9 → 3.12.0-next.91
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/AGENTS.md +9 -0
- package/dist/aiEnableRequest/index.js +15 -2
- package/dist/aiEnableRequest/index.js.map +1 -1
- package/dist/breakouts/breakout.js +8 -3
- package/dist/breakouts/breakout.js.map +1 -1
- package/dist/breakouts/index.js +26 -2
- package/dist/breakouts/index.js.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/constants.js +35 -9
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +11 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +67 -29
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/util.js +91 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/hashTree/constants.js +13 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +880 -382
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/utils.js +42 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/interceptors/dataChannelAuthToken.js +75 -15
- package/dist/interceptors/dataChannelAuthToken.js.map +1 -1
- package/dist/interceptors/locusRetry.js +23 -8
- package/dist/interceptors/locusRetry.js.map +1 -1
- package/dist/interpretation/index.js +10 -1
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/interpretation.types.js +7 -0
- package/dist/interpretation/interpretation.types.js.map +1 -0
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +4 -1
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +303 -88
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/infoUtils.js +36 -5
- package/dist/locus-info/infoUtils.js.map +1 -1
- package/dist/locus-info/types.js +19 -0
- package/dist/locus-info/types.js.map +1 -1
- package/dist/media/index.js +3 -1
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +1 -0
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +5 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +1233 -793
- 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/meeting/muteState.js +10 -1
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +5 -2
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +35 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +2 -2
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meetings/index.js +231 -118
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +6 -1
- package/dist/meetings/meetings.types.js.map +1 -1
- package/dist/meetings/request.js +39 -0
- package/dist/meetings/request.js.map +1 -1
- package/dist/meetings/util.js +79 -5
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +30 -0
- package/dist/member/util.js.map +1 -1
- package/dist/members/index.js +101 -43
- package/dist/members/index.js.map +1 -1
- package/dist/members/util.js +8 -1
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/constants.js +5 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/codec/constants.js +63 -0
- package/dist/multistream/codec/constants.js.map +1 -0
- package/dist/multistream/mediaRequestManager.js +62 -15
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/receiveSlot.js +9 -0
- package/dist/multistream/receiveSlot.js.map +1 -1
- package/dist/reachability/index.js +10 -13
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/reachability.types.js +2 -0
- package/dist/reachability/reachability.types.js.map +1 -1
- package/dist/reachability/util.js +45 -0
- package/dist/reachability/util.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/recording-controller/index.js +1 -3
- package/dist/recording-controller/index.js.map +1 -1
- package/dist/types/config.d.ts +2 -2
- package/dist/types/constants.d.ts +12 -1
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/controls-options-manager/index.d.ts +16 -0
- package/dist/types/hashTree/constants.d.ts +2 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +146 -17
- package/dist/types/hashTree/utils.d.ts +18 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/interceptors/locusRetry.d.ts +4 -4
- package/dist/types/interpretation/interpretation.types.d.ts +10 -0
- package/dist/types/locus-info/index.d.ts +50 -6
- package/dist/types/locus-info/infoUtils.d.ts +8 -0
- package/dist/types/locus-info/types.d.ts +21 -1
- package/dist/types/media/properties.d.ts +1 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
- package/dist/types/meeting/index.d.ts +94 -7
- package/dist/types/meeting/locusMediaRequest.d.ts +12 -0
- package/dist/types/meeting/request.d.ts +1 -0
- package/dist/types/meeting/util.d.ts +9 -0
- package/dist/types/meetings/index.d.ts +30 -18
- package/dist/types/meetings/meetings.types.d.ts +15 -0
- package/dist/types/meetings/request.d.ts +14 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +1 -0
- package/dist/types/member/util.d.ts +9 -0
- package/dist/types/members/index.d.ts +15 -2
- package/dist/types/members/util.d.ts +1 -5
- package/dist/types/metrics/constants.d.ts +4 -0
- package/dist/types/multistream/codec/constants.d.ts +7 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +22 -5
- package/dist/types/reachability/index.d.ts +1 -1
- package/dist/types/reachability/reachability.types.d.ts +7 -0
- package/dist/types/reachability/util.d.ts +17 -0
- package/dist/types/reactions/reactions.type.d.ts +3 -0
- package/dist/webinar/index.js +305 -159
- package/dist/webinar/index.js.map +1 -1
- package/package.json +22 -22
- package/src/aiEnableRequest/index.ts +16 -0
- package/src/breakouts/breakout.ts +3 -1
- package/src/breakouts/index.ts +31 -0
- package/src/config.ts +2 -2
- package/src/constants.ts +20 -3
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +87 -28
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +16 -0
- package/src/hashTree/hashTreeParser.ts +580 -196
- package/src/hashTree/utils.ts +36 -0
- package/src/index.ts +6 -0
- package/src/interceptors/dataChannelAuthToken.ts +88 -12
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/interpretation/index.ts +27 -9
- package/src/interpretation/interpretation.types.ts +11 -0
- package/src/locus-info/controlsUtils.ts +3 -1
- package/src/locus-info/index.ts +304 -100
- package/src/locus-info/infoUtils.ts +41 -6
- package/src/locus-info/types.ts +25 -1
- package/src/media/index.ts +3 -0
- package/src/media/properties.ts +1 -0
- package/src/meeting/in-meeting-actions.ts +8 -0
- package/src/meeting/index.ts +494 -67
- package/src/meeting/locusMediaRequest.ts +106 -6
- package/src/meeting/muteState.ts +10 -1
- package/src/meeting/request.ts +11 -0
- package/src/meeting/util.ts +47 -5
- package/src/meeting-info/meeting-info-v2.ts +4 -2
- package/src/meetings/index.ts +133 -78
- package/src/meetings/meetings.types.ts +19 -0
- package/src/meetings/request.ts +43 -0
- package/src/meetings/util.ts +97 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +29 -0
- package/src/members/index.ts +70 -7
- package/src/members/util.ts +12 -4
- package/src/metrics/constants.ts +4 -0
- package/src/multistream/codec/constants.ts +58 -0
- package/src/multistream/mediaRequestManager.ts +119 -28
- package/src/multistream/receiveSlot.ts +18 -0
- package/src/reachability/index.ts +19 -15
- package/src/reachability/reachability.types.ts +11 -0
- package/src/reachability/util.ts +36 -0
- package/src/reactions/reactions.type.ts +3 -0
- package/src/recording-controller/index.ts +1 -2
- package/src/webinar/index.ts +214 -36
- package/test/unit/spec/aiEnableRequest/index.ts +86 -0
- package/test/unit/spec/breakouts/breakout.ts +9 -3
- package/test/unit/spec/breakouts/index.ts +49 -0
- package/test/unit/spec/controls-options-manager/index.js +244 -29
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +1838 -180
- package/test/unit/spec/hashTree/utils.ts +125 -1
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +196 -0
- package/test/unit/spec/interceptors/locusRetry.ts +205 -4
- package/test/unit/spec/interpretation/index.ts +26 -4
- package/test/unit/spec/locus-info/controlsUtils.js +172 -57
- package/test/unit/spec/locus-info/index.js +499 -81
- package/test/unit/spec/locus-info/infoUtils.js +184 -1
- package/test/unit/spec/media/index.ts +31 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
- package/test/unit/spec/meeting/index.js +1615 -42
- package/test/unit/spec/meeting/locusMediaRequest.ts +302 -4
- package/test/unit/spec/meeting/muteState.js +81 -0
- package/test/unit/spec/meeting/request.js +12 -0
- package/test/unit/spec/meeting/utils.js +157 -1
- package/test/unit/spec/meeting-info/meetinginfov2.js +19 -10
- package/test/unit/spec/meetings/index.js +363 -36
- package/test/unit/spec/meetings/request.js +141 -0
- package/test/unit/spec/meetings/utils.js +189 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +89 -0
- package/test/unit/spec/members/index.js +216 -0
- package/test/unit/spec/members/request.js +37 -1
- package/test/unit/spec/members/utils.js +63 -0
- package/test/unit/spec/multistream/mediaRequestManager.ts +501 -37
- package/test/unit/spec/reachability/index.ts +59 -46
- package/test/unit/spec/reachability/util.ts +31 -0
- package/test/unit/spec/recording-controller/index.js +9 -8
- package/test/unit/spec/webinar/index.ts +329 -28
|
@@ -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'));
|
|
@@ -11,6 +11,7 @@ describe('plugin-meetings', () => {
|
|
|
11
11
|
let audio;
|
|
12
12
|
let video;
|
|
13
13
|
let originalRemoteUpdateAudioVideo;
|
|
14
|
+
let originalUpdateLocusFromApiResponse;
|
|
14
15
|
|
|
15
16
|
const fakeLocusResponse = {body: {locus: {info: 'this is a fake locus'}}};
|
|
16
17
|
|
|
@@ -45,6 +46,7 @@ describe('plugin-meetings', () => {
|
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
originalRemoteUpdateAudioVideo = MeetingUtil.remoteUpdateAudioVideo;
|
|
49
|
+
originalUpdateLocusFromApiResponse = MeetingUtil.updateLocusFromApiResponse;
|
|
48
50
|
|
|
49
51
|
MeetingUtil.remoteUpdateAudioVideo = sinon.stub().resolves(fakeLocusResponse);
|
|
50
52
|
MeetingUtil.updateLocusFromApiResponse = sinon.stub();
|
|
@@ -57,6 +59,7 @@ describe('plugin-meetings', () => {
|
|
|
57
59
|
|
|
58
60
|
afterEach(() => {
|
|
59
61
|
MeetingUtil.remoteUpdateAudioVideo = originalRemoteUpdateAudioVideo;
|
|
62
|
+
MeetingUtil.updateLocusFromApiResponse = originalUpdateLocusFromApiResponse;
|
|
60
63
|
});
|
|
61
64
|
|
|
62
65
|
describe('mute state library', () => {
|
|
@@ -109,6 +112,82 @@ describe('plugin-meetings', () => {
|
|
|
109
112
|
assert.isTrue(audio.isRemotelyMuted());
|
|
110
113
|
});
|
|
111
114
|
|
|
115
|
+
it('does not unmute the local stream when server clears remote mute while user is locally muted (breakout -> main regression)', async () => {
|
|
116
|
+
// Panelist is locally muted before joining a breakout.
|
|
117
|
+
meeting.mediaProperties.audioStream.userMuted = true;
|
|
118
|
+
audio.handleLocalStreamChange(meeting);
|
|
119
|
+
await testUtils.flushPromises();
|
|
120
|
+
|
|
121
|
+
// Server applied a remote mute at some point.
|
|
122
|
+
audio.handleServerRemoteMuteUpdate(meeting, true, true);
|
|
123
|
+
assert.isTrue(audio.isRemotelyMuted());
|
|
124
|
+
|
|
125
|
+
meeting.mediaProperties.audioStream.setServerMuted.resetHistory();
|
|
126
|
+
|
|
127
|
+
// Now the user returns from breakout to main, and Locus clears the remote mute.
|
|
128
|
+
audio.handleServerRemoteMuteUpdate(meeting, false, true);
|
|
129
|
+
|
|
130
|
+
// Stream must NOT be force-unmuted - user's local mute intent must be preserved.
|
|
131
|
+
// The stale remoteMute=false must not trigger setServerMuted again.
|
|
132
|
+
assert.notCalled(meeting.mediaProperties.audioStream.setServerMuted);
|
|
133
|
+
assert.isFalse(audio.isRemotelyMuted());
|
|
134
|
+
assert.isTrue(audio.isMuted());
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('does not touch the local stream when remote mute stays false (no transition)', async () => {
|
|
138
|
+
// No remote mute has been applied yet (initial state has server.remoteMute=false).
|
|
139
|
+
meeting.mediaProperties.audioStream.setServerMuted.resetHistory();
|
|
140
|
+
|
|
141
|
+
audio.handleServerRemoteMuteUpdate(meeting, false, true);
|
|
142
|
+
|
|
143
|
+
// setServerMuted must not be called - there is no remoteMute transition to act on.
|
|
144
|
+
assert.notCalled(meeting.mediaProperties.audioStream.setServerMuted);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('keeps isMuted() true when a stale remoteMute=false replays after remote mute', async () => {
|
|
148
|
+
// User was unmuted by host (userMuted=false locally).
|
|
149
|
+
meeting.mediaProperties.audioStream.userMuted = false;
|
|
150
|
+
audio.handleLocalStreamChange(meeting);
|
|
151
|
+
await testUtils.flushPromises();
|
|
152
|
+
|
|
153
|
+
// Host then hard-mutes the user.
|
|
154
|
+
audio.handleServerRemoteMuteUpdate(meeting, true, true);
|
|
155
|
+
await testUtils.flushPromises();
|
|
156
|
+
assert.isTrue(audio.isMuted());
|
|
157
|
+
|
|
158
|
+
meeting.mediaProperties.audioStream.setServerMuted.resetHistory();
|
|
159
|
+
|
|
160
|
+
// BO -> main replays a stale remoteMute=false from the locus cache.
|
|
161
|
+
audio.handleServerRemoteMuteUpdate(meeting, false, true);
|
|
162
|
+
|
|
163
|
+
// isMuted() must remain true (client.localMute is the safety net),
|
|
164
|
+
// and the stream must not be force-unmuted.
|
|
165
|
+
assert.isTrue(audio.isMuted());
|
|
166
|
+
assert.isFalse(audio.isRemotelyMuted());
|
|
167
|
+
assert.notCalled(meeting.mediaProperties.audioStream.setServerMuted);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('syncs client mute intent back to server on stale remoteMute=false', async () => {
|
|
171
|
+
// client wants mute (localMute=true) but server's
|
|
172
|
+
// local-mute is out of sync (=false) - e.g. an attendee promoted to panelist whose
|
|
173
|
+
// server-side mute state got mis-set during promotion. remoteMute is currently true.
|
|
174
|
+
audio.state.client.localMute = true;
|
|
175
|
+
audio.state.server.localMute = false;
|
|
176
|
+
audio.state.server.remoteMute = true;
|
|
177
|
+
|
|
178
|
+
MeetingUtil.remoteUpdateAudioVideo.resetHistory();
|
|
179
|
+
|
|
180
|
+
// Stale event after BO -> main clears remoteMute.
|
|
181
|
+
audio.handleServerRemoteMuteUpdate(meeting, false, true);
|
|
182
|
+
await testUtils.flushPromises();
|
|
183
|
+
|
|
184
|
+
// applyClientStateToServer must detect the mismatch and push local mute back
|
|
185
|
+
// to server, so server.controls.audio.muted stays true for other participants' tiles.
|
|
186
|
+
assert.calledOnce(MeetingUtil.remoteUpdateAudioVideo);
|
|
187
|
+
assert.calledWith(MeetingUtil.remoteUpdateAudioVideo, meeting, true, undefined);
|
|
188
|
+
assert.isTrue(audio.isMuted());
|
|
189
|
+
});
|
|
190
|
+
|
|
112
191
|
it('does local audio unmute if localAudioUnmuteRequired is received', async () => {
|
|
113
192
|
// first we need to have the local stream user muted
|
|
114
193
|
meeting.mediaProperties.audioStream.userMuted = true;
|
|
@@ -826,6 +905,8 @@ describe('plugin-meetings', () => {
|
|
|
826
905
|
it('does not do anything if current state is already the same', async () => {
|
|
827
906
|
// set it up so that we are remotely muted (so that a sync to server would do a remote unmute)
|
|
828
907
|
audio.handleServerRemoteMuteUpdate(meeting, true, true);
|
|
908
|
+
await testUtils.flushPromises();
|
|
909
|
+
resetStubHistory();
|
|
829
910
|
|
|
830
911
|
// audio is already enabled and we call to enable it again
|
|
831
912
|
audio.enable(meeting, true);
|
|
@@ -347,6 +347,18 @@ describe('plugin-meetings', () => {
|
|
|
347
347
|
'ANNOTATION_ON_SHARE_SUPPORTED',
|
|
348
348
|
]);
|
|
349
349
|
});
|
|
350
|
+
it('adds deviceCapabilities to request when simultaneous interpretation is enabled', async () => {
|
|
351
|
+
await meetingsRequest.joinMeeting({
|
|
352
|
+
enableSimultaneousInterpretation: true,
|
|
353
|
+
});
|
|
354
|
+
const requestParams = meetingsRequest.request.getCall(0).args[0];
|
|
355
|
+
assert.deepEqual(requestParams.body.deviceCapabilities, [
|
|
356
|
+
'HOST_CONTROL_SI_SUPPORTED',
|
|
357
|
+
'INTERPRETER_CONTROL_SI_SUPPORTED',
|
|
358
|
+
'SI_HANDOVER_SUPPORTED',
|
|
359
|
+
'SIGN_INTERPRETER_SUPPORTED',
|
|
360
|
+
]);
|
|
361
|
+
});
|
|
350
362
|
it('does not add deviceCapabilities to request when breakouts and live annotation are not supported', async () => {
|
|
351
363
|
await meetingsRequest.joinMeeting({});
|
|
352
364
|
|