@webex/plugin-meetings 3.11.0-next.3 → 3.11.0-next.30
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/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/hashTree/hashTree.js +18 -0
- package/dist/hashTree/hashTree.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +603 -266
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/types.js +4 -2
- package/dist/hashTree/types.js.map +1 -1
- package/dist/hashTree/utils.js +10 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/interceptors/constant.js +12 -0
- package/dist/interceptors/constant.js.map +1 -0
- package/dist/interceptors/dataChannelAuthToken.js +233 -0
- package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
- package/dist/interceptors/index.js +7 -0
- package/dist/interceptors/index.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +80 -44
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/types.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +57 -1
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/properties.js +4 -2
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/index.js +134 -40
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +50 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +108 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +76 -34
- package/dist/meetings/index.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +1 -1
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +11 -0
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/types/config.d.ts +3 -0
- package/dist/types/hashTree/hashTree.d.ts +7 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +83 -12
- package/dist/types/hashTree/types.d.ts +3 -0
- package/dist/types/hashTree/utils.d.ts +6 -0
- package/dist/types/interceptors/constant.d.ts +5 -0
- package/dist/types/interceptors/dataChannelAuthToken.d.ts +35 -0
- package/dist/types/interceptors/index.d.ts +2 -1
- package/dist/types/locus-info/index.d.ts +9 -2
- package/dist/types/locus-info/types.d.ts +1 -0
- package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
- package/dist/types/media/properties.d.ts +2 -1
- package/dist/types/meeting/index.d.ts +27 -5
- package/dist/types/meeting/request.d.ts +16 -1
- package/dist/types/meeting/request.type.d.ts +5 -0
- package/dist/types/meeting/util.d.ts +28 -0
- package/dist/types/meetings/index.d.ts +3 -1
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/reactions/reactions.type.d.ts +1 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +22 -22
- package/src/config.ts +3 -0
- package/src/hashTree/hashTree.ts +17 -0
- package/src/hashTree/hashTreeParser.ts +525 -188
- package/src/hashTree/types.ts +4 -0
- package/src/hashTree/utils.ts +9 -0
- package/src/index.ts +6 -1
- package/src/interceptors/constant.ts +6 -0
- package/src/interceptors/dataChannelAuthToken.ts +142 -0
- package/src/interceptors/index.ts +2 -1
- package/src/locus-info/index.ts +110 -35
- package/src/locus-info/types.ts +1 -0
- package/src/media/MediaConnectionAwaiter.ts +41 -1
- package/src/media/properties.ts +3 -1
- package/src/meeting/index.ts +101 -22
- package/src/meeting/request.ts +42 -0
- package/src/meeting/request.type.ts +6 -0
- package/src/meeting/util.ts +132 -1
- package/src/meetings/index.ts +88 -7
- package/src/metrics/constants.ts +1 -0
- package/src/multistream/mediaRequestManager.ts +1 -1
- package/src/multistream/remoteMediaManager.ts +13 -0
- package/src/reactions/reactions.type.ts +1 -0
- package/test/unit/spec/hashTree/hashTree.ts +66 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +1594 -162
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +141 -0
- package/test/unit/spec/locus-info/index.js +173 -45
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
- package/test/unit/spec/media/properties.ts +12 -3
- package/test/unit/spec/meeting/index.js +414 -62
- package/test/unit/spec/meeting/request.js +64 -0
- package/test/unit/spec/meeting/utils.js +294 -22
- package/test/unit/spec/meetings/index.js +550 -10
- package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import 'jsdom-global/register';
|
|
2
|
+
import {assert, expect} from '@webex/test-helper-chai';
|
|
3
|
+
import sinon from 'sinon';
|
|
4
|
+
import MockWebex from '@webex/test-helper-mock-webex';
|
|
5
|
+
import {WebexHttpError} from '@webex/webex-core';
|
|
6
|
+
import DataChannelAuthTokenInterceptor from '@webex/plugin-meetings/src/interceptors/dataChannelAuthToken';
|
|
7
|
+
import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
|
|
8
|
+
import {DATA_CHANNEL_AUTH_HEADER, MAX_RETRY} from '@webex/plugin-meetings/src/interceptors/constant';
|
|
9
|
+
|
|
10
|
+
describe('plugin-meetings', () => {
|
|
11
|
+
describe('Interceptors', () => {
|
|
12
|
+
describe('DataChannelAuthTokenInterceptor', () => {
|
|
13
|
+
let interceptor, webex, clock;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
clock = sinon.useFakeTimers();
|
|
17
|
+
|
|
18
|
+
webex = new MockWebex({children: {}});
|
|
19
|
+
webex.request = sinon.stub().resolves({});
|
|
20
|
+
|
|
21
|
+
interceptor = Reflect.apply(DataChannelAuthTokenInterceptor.create, webex, []);
|
|
22
|
+
|
|
23
|
+
interceptor._refreshDataChannelToken = sinon.stub();
|
|
24
|
+
interceptor._isDataChannelTokenEnabled = sinon.stub().resolves(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
clock.restore();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const makeReason = (statusCode) =>
|
|
32
|
+
new WebexHttpError({
|
|
33
|
+
statusCode,
|
|
34
|
+
options: {headers: {}, uri: 'https://example.com'},
|
|
35
|
+
body: {},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('#onResponseError', () => {
|
|
39
|
+
it('rejects when no Data-Channel-Auth-Token header exists', async () => {
|
|
40
|
+
const options = {headers: {}};
|
|
41
|
+
const reason = makeReason(401);
|
|
42
|
+
|
|
43
|
+
await assert.isRejected(interceptor.onResponseError(options, reason), reason);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('rejects when statusCode is not 401/403', async () => {
|
|
47
|
+
const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'abc'}};
|
|
48
|
+
const reason = makeReason(500);
|
|
49
|
+
|
|
50
|
+
await assert.isRejected(interceptor.onResponseError(options, reason), reason);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('rejects when retry count exceeds MAX_RETRY', async () => {
|
|
54
|
+
const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'abc'}};
|
|
55
|
+
const reason = makeReason(401);
|
|
56
|
+
|
|
57
|
+
for (let i = 0; i < MAX_RETRY; i++) {
|
|
58
|
+
interceptor.onResponseError(options, reason).catch(() => {});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await assert.isRejected(interceptor.onResponseError(options, reason), reason);
|
|
62
|
+
|
|
63
|
+
sinon.assert.calledOnce(LoggerProxy.logger.error);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('calls refreshTokenAndRetryWithDelay when eligible', async () => {
|
|
67
|
+
const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'abc'}};
|
|
68
|
+
const reason = makeReason(401);
|
|
69
|
+
|
|
70
|
+
interceptor._isDataChannelTokenEnabled.resolves(true);
|
|
71
|
+
|
|
72
|
+
const stub = sinon.stub(interceptor, 'refreshTokenAndRetryWithDelay').resolves('ok');
|
|
73
|
+
|
|
74
|
+
await interceptor.onResponseError(options, reason);
|
|
75
|
+
|
|
76
|
+
sinon.assert.calledOnceWithExactly(stub, options);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('rejects when isDataChannelTokenEnabled is false', async () => {
|
|
80
|
+
const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'abc'}};
|
|
81
|
+
const reason = makeReason(401);
|
|
82
|
+
|
|
83
|
+
interceptor._isDataChannelTokenEnabled.resolves(false);
|
|
84
|
+
|
|
85
|
+
await assert.isRejected(interceptor.onResponseError(options, reason), reason);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('#refreshTokenAndRetryWithDelay', () => {
|
|
90
|
+
const options = {
|
|
91
|
+
headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'},
|
|
92
|
+
method: 'GET',
|
|
93
|
+
uri: 'https://example.com',
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
it('refreshes token and retries request successfully', async () => {
|
|
97
|
+
interceptor._refreshDataChannelToken.resolves('new-token');
|
|
98
|
+
webex.request.resolves('mock-response');
|
|
99
|
+
|
|
100
|
+
const promise = interceptor.refreshTokenAndRetryWithDelay(options);
|
|
101
|
+
|
|
102
|
+
clock.tick(2000);
|
|
103
|
+
|
|
104
|
+
const result = await promise;
|
|
105
|
+
|
|
106
|
+
expect(interceptor._refreshDataChannelToken.calledOnce).to.be.true;
|
|
107
|
+
expect(options.headers[DATA_CHANNEL_AUTH_HEADER]).to.equal('new-token');
|
|
108
|
+
expect(webex.request.calledOnceWith(options)).to.be.true;
|
|
109
|
+
expect(result).to.equal('mock-response');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('rejects when refreshDataChannelToken fails', async () => {
|
|
113
|
+
interceptor._refreshDataChannelToken.rejects(new Error('refresh failed'));
|
|
114
|
+
|
|
115
|
+
const promise = interceptor.refreshTokenAndRetryWithDelay(options);
|
|
116
|
+
|
|
117
|
+
clock.tick(2000);
|
|
118
|
+
|
|
119
|
+
await assert.isRejected(
|
|
120
|
+
promise,
|
|
121
|
+
/DataChannel token refresh failed: refresh failed/
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('rejects when retry request fails', async () => {
|
|
126
|
+
interceptor._refreshDataChannelToken.resolves('new-token');
|
|
127
|
+
webex.request.rejects(new Error('request failed'));
|
|
128
|
+
|
|
129
|
+
const promise = interceptor.refreshTokenAndRetryWithDelay(options);
|
|
130
|
+
|
|
131
|
+
clock.tick(2000);
|
|
132
|
+
|
|
133
|
+
await assert.isRejected(
|
|
134
|
+
promise,
|
|
135
|
+
/DataChannel token refresh failed: request failed/
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -29,7 +29,8 @@ import {
|
|
|
29
29
|
} from '../../../../src/constants';
|
|
30
30
|
|
|
31
31
|
import {self, selfWithInactivity} from './selfConstant';
|
|
32
|
-
import {
|
|
32
|
+
import {MEETING_REMOVED_REASON} from '@webex/plugin-meetings/src/constants';
|
|
33
|
+
import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
|
|
33
34
|
|
|
34
35
|
describe('plugin-meetings', () => {
|
|
35
36
|
describe('LocusInfo index', () => {
|
|
@@ -106,7 +107,7 @@ describe('plugin-meetings', () => {
|
|
|
106
107
|
const createHashTreeMessage = (visibleDataSets) => ({
|
|
107
108
|
locusStateElements: [
|
|
108
109
|
{
|
|
109
|
-
htMeta: {elementId: {type: '
|
|
110
|
+
htMeta: {elementId: {type: 'metadata'}},
|
|
110
111
|
data: {visibleDataSets},
|
|
111
112
|
},
|
|
112
113
|
],
|
|
@@ -136,9 +137,13 @@ describe('plugin-meetings', () => {
|
|
|
136
137
|
HashTreeParserStub,
|
|
137
138
|
sinon.match({
|
|
138
139
|
initialLocus: {
|
|
139
|
-
locus:
|
|
140
|
+
locus: null,
|
|
140
141
|
dataSets: [],
|
|
141
142
|
},
|
|
143
|
+
metadata: {
|
|
144
|
+
htMeta: hashTreeMessage.locusStateElements[0].htMeta,
|
|
145
|
+
visibleDataSets,
|
|
146
|
+
},
|
|
142
147
|
webexRequest: sinon.match.func,
|
|
143
148
|
locusInfoUpdateCallback: sinon.match.func,
|
|
144
149
|
debugId: sinon.match.string,
|
|
@@ -169,11 +174,16 @@ describe('plugin-meetings', () => {
|
|
|
169
174
|
const visibleDataSets = ['dataset1', 'dataset2'];
|
|
170
175
|
const locus = createLocusWithVisibleDataSets(visibleDataSets);
|
|
171
176
|
const dataSets = [{name: 'dataset1', url: 'http://dataset-url.com'}];
|
|
177
|
+
const metadata = {
|
|
178
|
+
htMeta: {elementId: {type: 'metadata'}},
|
|
179
|
+
visibleDataSets,
|
|
180
|
+
};
|
|
172
181
|
|
|
173
182
|
await locusInfo.initialSetup({
|
|
174
183
|
trigger: 'join-response',
|
|
175
184
|
locus,
|
|
176
185
|
dataSets,
|
|
186
|
+
metadata,
|
|
177
187
|
});
|
|
178
188
|
|
|
179
189
|
assert.calledOnceWithExactly(
|
|
@@ -183,6 +193,7 @@ describe('plugin-meetings', () => {
|
|
|
183
193
|
locus,
|
|
184
194
|
dataSets,
|
|
185
195
|
},
|
|
196
|
+
metadata,
|
|
186
197
|
webexRequest: sinon.match.func,
|
|
187
198
|
locusInfoUpdateCallback: sinon.match.func,
|
|
188
199
|
debugId: sinon.match.string,
|
|
@@ -220,12 +231,13 @@ describe('plugin-meetings', () => {
|
|
|
220
231
|
HashTreeParserStub,
|
|
221
232
|
sinon.match({
|
|
222
233
|
initialLocus: {
|
|
223
|
-
locus:
|
|
234
|
+
locus: null,
|
|
224
235
|
dataSets: [],
|
|
225
236
|
},
|
|
226
237
|
webexRequest: sinon.match.func,
|
|
227
238
|
locusInfoUpdateCallback: sinon.match.func,
|
|
228
239
|
debugId: sinon.match.string,
|
|
240
|
+
metadata: null,
|
|
229
241
|
})
|
|
230
242
|
);
|
|
231
243
|
assert.calledOnceWithExactly(mockHashTreeParser.initializeFromGetLociResponse, locus);
|
|
@@ -249,6 +261,30 @@ describe('plugin-meetings', () => {
|
|
|
249
261
|
assert.isTrue(locusInfo.emitChange);
|
|
250
262
|
});
|
|
251
263
|
|
|
264
|
+
it('throws if called with "locus-message" and Metadata object without visibleDataSets', async () => {
|
|
265
|
+
const hashTreeMessage = {
|
|
266
|
+
locusStateElements: [
|
|
267
|
+
{
|
|
268
|
+
htMeta: {elementId: {type: 'Metadata'}},
|
|
269
|
+
data: {},
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
dataSets: [{name: 'dataset1', url: 'test-url'}],
|
|
273
|
+
};
|
|
274
|
+
try {
|
|
275
|
+
await locusInfo.initialSetup({
|
|
276
|
+
trigger: 'locus-message',
|
|
277
|
+
hashTreeMessage,
|
|
278
|
+
});
|
|
279
|
+
assert.fail('should have thrown an error');
|
|
280
|
+
} catch (error) {
|
|
281
|
+
assert.equal(
|
|
282
|
+
error.message,
|
|
283
|
+
'Metadata object with visibleDataSets is missing in the message'
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
252
288
|
describe('should setup correct locusInfoUpdateCallback when creating HashTreeParser', () => {
|
|
253
289
|
const OBJECTS_UPDATED = HashTreeParserModule.LocusInfoUpdateType.OBJECTS_UPDATED;
|
|
254
290
|
const MEETING_ENDED = HashTreeParserModule.LocusInfoUpdateType.MEETING_ENDED;
|
|
@@ -265,8 +301,8 @@ describe('plugin-meetings', () => {
|
|
|
265
301
|
hashTreeMessage: {
|
|
266
302
|
locusStateElements: [
|
|
267
303
|
{
|
|
268
|
-
htMeta: {elementId: {type: '
|
|
269
|
-
data: {visibleDataSets: ['dataset1']},
|
|
304
|
+
htMeta: {elementId: {type: 'Metadata'}},
|
|
305
|
+
data: {visibleDataSets: [{name: 'dataset1', url: 'test-url'}]},
|
|
270
306
|
},
|
|
271
307
|
],
|
|
272
308
|
dataSets: [{name: 'dataset1', url: 'test-url'}],
|
|
@@ -293,6 +329,16 @@ describe('plugin-meetings', () => {
|
|
|
293
329
|
htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 1}},
|
|
294
330
|
},
|
|
295
331
|
];
|
|
332
|
+
locusInfo.embeddedApps = [
|
|
333
|
+
{
|
|
334
|
+
id: 'fake-embedded-app-1',
|
|
335
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-1', version: 1}},
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
id: 'fake-embedded-app-2',
|
|
339
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-2', version: 1}},
|
|
340
|
+
},
|
|
341
|
+
];
|
|
296
342
|
locusInfo.meetings = {id: 'fake-meetings'};
|
|
297
343
|
locusInfo.participants = [
|
|
298
344
|
{id: 'fake-participant-1', name: 'Participant One'},
|
|
@@ -328,6 +374,16 @@ describe('plugin-meetings', () => {
|
|
|
328
374
|
htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 1}},
|
|
329
375
|
},
|
|
330
376
|
],
|
|
377
|
+
embeddedApps: [
|
|
378
|
+
{
|
|
379
|
+
id: 'fake-embedded-app-1',
|
|
380
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-1', version: 1}},
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
id: 'fake-embedded-app-2',
|
|
384
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-2', version: 1}},
|
|
385
|
+
},
|
|
386
|
+
],
|
|
331
387
|
meetings: {id: 'fake-meetings'},
|
|
332
388
|
jsSdkMeta: {removedParticipantIds: []},
|
|
333
389
|
participants: [], // empty means there were no participant updates
|
|
@@ -504,6 +560,7 @@ describe('plugin-meetings', () => {
|
|
|
504
560
|
self: {id: 'fake-self'},
|
|
505
561
|
links: {id: 'fake-links'},
|
|
506
562
|
mediaShares: expectedLocusInfo.mediaShares,
|
|
563
|
+
embeddedApps: expectedLocusInfo.embeddedApps,
|
|
507
564
|
// and now the new fields
|
|
508
565
|
...newLocus,
|
|
509
566
|
htMeta: newLocusHtMeta,
|
|
@@ -536,6 +593,7 @@ describe('plugin-meetings', () => {
|
|
|
536
593
|
self: 'new-self',
|
|
537
594
|
participants: 'new-participants',
|
|
538
595
|
mediaShares: 'new-mediaShares',
|
|
596
|
+
embeddedApps: 'new-embeddedApps',
|
|
539
597
|
},
|
|
540
598
|
},
|
|
541
599
|
],
|
|
@@ -551,6 +609,7 @@ describe('plugin-meetings', () => {
|
|
|
551
609
|
self: {id: 'fake-self'},
|
|
552
610
|
links: {id: 'fake-links'},
|
|
553
611
|
mediaShares: expectedLocusInfo.mediaShares,
|
|
612
|
+
embeddedApps: expectedLocusInfo.embeddedApps,
|
|
554
613
|
participants: [], // empty means there were no participant updates
|
|
555
614
|
jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
|
|
556
615
|
...newLocus,
|
|
@@ -586,6 +645,7 @@ describe('plugin-meetings', () => {
|
|
|
586
645
|
self: {id: 'fake-self'},
|
|
587
646
|
links: {id: 'fake-links'},
|
|
588
647
|
mediaShares: expectedLocusInfo.mediaShares,
|
|
648
|
+
embeddedApps: expectedLocusInfo.embeddedApps,
|
|
589
649
|
// and now the new fields
|
|
590
650
|
...newLocus,
|
|
591
651
|
htMeta: newLocusHtMeta,
|
|
@@ -725,6 +785,39 @@ describe('plugin-meetings', () => {
|
|
|
725
785
|
});
|
|
726
786
|
});
|
|
727
787
|
|
|
788
|
+
it('should process locus update correctly when called with updated EMBEDDEDAPP objects', () => {
|
|
789
|
+
const newEmbeddedApp = {
|
|
790
|
+
id: 'new-embedded-app-3',
|
|
791
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-3', version: 100}},
|
|
792
|
+
};
|
|
793
|
+
const updatedEmbeddedApp2 = {
|
|
794
|
+
id: 'fake-embedded-app-2',
|
|
795
|
+
someNewProp: 'newValue',
|
|
796
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-2', version: 100}},
|
|
797
|
+
};
|
|
798
|
+
// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
|
|
799
|
+
// with 1 embedded app added, 1 updated, and 1 removed
|
|
800
|
+
locusInfoUpdateCallback(OBJECTS_UPDATED, {
|
|
801
|
+
updatedObjects: [
|
|
802
|
+
{htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-1'}}, data: null},
|
|
803
|
+
{
|
|
804
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-2'}},
|
|
805
|
+
data: updatedEmbeddedApp2,
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-3'}},
|
|
809
|
+
data: newEmbeddedApp,
|
|
810
|
+
},
|
|
811
|
+
],
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
// check onDeltaLocus() was called with correctly updated locus info
|
|
815
|
+
assert.calledOnceWithExactly(onDeltaLocusStub, {
|
|
816
|
+
...expectedLocusInfo,
|
|
817
|
+
embeddedApps: [updatedEmbeddedApp2, newEmbeddedApp],
|
|
818
|
+
});
|
|
819
|
+
});
|
|
820
|
+
|
|
728
821
|
it('should process locus update correctly when called with a combination of various updated objects', () => {
|
|
729
822
|
const newSelf = {
|
|
730
823
|
id: 'new-self',
|
|
@@ -1076,7 +1169,7 @@ describe('plugin-meetings', () => {
|
|
|
1076
1169
|
it('should trigger the CONTROLS_POLLING_QA_CHANGED event when necessary', () => {
|
|
1077
1170
|
locusInfo.controls = {};
|
|
1078
1171
|
locusInfo.emitScoped = sinon.stub();
|
|
1079
|
-
newControls.pollingQAControl = {
|
|
1172
|
+
newControls.pollingQAControl = {enabled: true};
|
|
1080
1173
|
locusInfo.updateControls(newControls);
|
|
1081
1174
|
|
|
1082
1175
|
assert.calledWith(
|
|
@@ -1631,7 +1724,6 @@ describe('plugin-meetings', () => {
|
|
|
1631
1724
|
);
|
|
1632
1725
|
});
|
|
1633
1726
|
|
|
1634
|
-
|
|
1635
1727
|
it('should call with participant display name', () => {
|
|
1636
1728
|
const failureParticipant = [
|
|
1637
1729
|
{
|
|
@@ -1656,7 +1748,7 @@ describe('plugin-meetings', () => {
|
|
|
1656
1748
|
displayName: 'Test User',
|
|
1657
1749
|
}
|
|
1658
1750
|
);
|
|
1659
|
-
})
|
|
1751
|
+
});
|
|
1660
1752
|
});
|
|
1661
1753
|
|
|
1662
1754
|
describe('#updateSelf', () => {
|
|
@@ -2457,8 +2549,8 @@ describe('plugin-meetings', () => {
|
|
|
2457
2549
|
{
|
|
2458
2550
|
isInitializing: !self,
|
|
2459
2551
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2552
|
+
);
|
|
2553
|
+
});
|
|
2462
2554
|
|
|
2463
2555
|
const checkMeetingInfoUpdatedCalled = (expected, payload) => {
|
|
2464
2556
|
const expectedArgs = [
|
|
@@ -2923,28 +3015,28 @@ describe('plugin-meetings', () => {
|
|
|
2923
3015
|
assert.isFunction(locusParser.onDeltaAction);
|
|
2924
3016
|
});
|
|
2925
3017
|
|
|
2926
|
-
it(
|
|
3018
|
+
it('#updateLocusInfo invokes updateLocusUrl before updateMeetingInfo', () => {
|
|
2927
3019
|
const callOrder = [];
|
|
2928
|
-
sinon.stub(locusInfo,
|
|
2929
|
-
sinon.stub(locusInfo,
|
|
2930
|
-
sinon.stub(locusInfo,
|
|
2931
|
-
sinon.stub(locusInfo,
|
|
2932
|
-
sinon.stub(locusInfo,
|
|
2933
|
-
sinon.stub(locusInfo,
|
|
2934
|
-
callOrder.push(
|
|
3020
|
+
sinon.stub(locusInfo, 'updateControls');
|
|
3021
|
+
sinon.stub(locusInfo, 'updateConversationUrl');
|
|
3022
|
+
sinon.stub(locusInfo, 'updateCreated');
|
|
3023
|
+
sinon.stub(locusInfo, 'updateFullState');
|
|
3024
|
+
sinon.stub(locusInfo, 'updateHostInfo');
|
|
3025
|
+
sinon.stub(locusInfo, 'updateMeetingInfo').callsFake(() => {
|
|
3026
|
+
callOrder.push('updateMeetingInfo');
|
|
2935
3027
|
});
|
|
2936
|
-
sinon.stub(locusInfo,
|
|
2937
|
-
sinon.stub(locusInfo,
|
|
2938
|
-
sinon.stub(locusInfo,
|
|
2939
|
-
sinon.stub(locusInfo,
|
|
2940
|
-
callOrder.push(
|
|
3028
|
+
sinon.stub(locusInfo, 'updateMediaShares');
|
|
3029
|
+
sinon.stub(locusInfo, 'updateReplaces');
|
|
3030
|
+
sinon.stub(locusInfo, 'updateSelf');
|
|
3031
|
+
sinon.stub(locusInfo, 'updateLocusUrl').callsFake(() => {
|
|
3032
|
+
callOrder.push('updateLocusUrl');
|
|
2941
3033
|
});
|
|
2942
|
-
sinon.stub(locusInfo,
|
|
2943
|
-
sinon.stub(locusInfo,
|
|
2944
|
-
sinon.stub(locusInfo,
|
|
2945
|
-
sinon.stub(locusInfo,
|
|
2946
|
-
sinon.stub(locusInfo,
|
|
2947
|
-
sinon.stub(locusInfo,
|
|
3034
|
+
sinon.stub(locusInfo, 'updateAclUrl');
|
|
3035
|
+
sinon.stub(locusInfo, 'updateBasequence');
|
|
3036
|
+
sinon.stub(locusInfo, 'updateSequence');
|
|
3037
|
+
sinon.stub(locusInfo, 'updateEmbeddedApps');
|
|
3038
|
+
sinon.stub(locusInfo, 'updateLinks');
|
|
3039
|
+
sinon.stub(locusInfo, 'compareAndUpdate');
|
|
2948
3040
|
|
|
2949
3041
|
locusInfo.updateLocusInfo(locus);
|
|
2950
3042
|
|
|
@@ -3000,7 +3092,7 @@ describe('plugin-meetings', () => {
|
|
|
3000
3092
|
it('#updateLocusInfo puts the Locus DTO top level properties at the right place in LocusInfo class', () => {
|
|
3001
3093
|
// this test verifies that the top-level properties of Locus DTO are copied
|
|
3002
3094
|
// into LocusInfo class and set as top level properties too
|
|
3003
|
-
// this is important, because the code handling Locus
|
|
3095
|
+
// this is important, because the code handling Locus hash trees relies on it, see updateFromHashTree()
|
|
3004
3096
|
const info = {id: 'info id'};
|
|
3005
3097
|
const fullState = {id: 'fullState id'};
|
|
3006
3098
|
const links = {services: {id: 'service links'}, resources: {id: 'resource links'}};
|
|
@@ -3039,7 +3131,7 @@ describe('plugin-meetings', () => {
|
|
|
3039
3131
|
sandbox.stub(locusInfo, 'handleOneOnOneEvent');
|
|
3040
3132
|
sandbox.stub(locusParser, 'isNewFullLocus').returns(true);
|
|
3041
3133
|
|
|
3042
|
-
locusInfo.onFullLocus(fakeLocus, eventType);
|
|
3134
|
+
locusInfo.onFullLocus('test', fakeLocus, eventType);
|
|
3043
3135
|
|
|
3044
3136
|
assert.equal(fakeLocus, locusParser.workingCopy);
|
|
3045
3137
|
});
|
|
@@ -3060,7 +3152,7 @@ describe('plugin-meetings', () => {
|
|
|
3060
3152
|
|
|
3061
3153
|
sandbox.stub(locusParser, 'isNewFullLocus').returns(false);
|
|
3062
3154
|
|
|
3063
|
-
locusInfo.onFullLocus(fakeLocus, eventType);
|
|
3155
|
+
locusInfo.onFullLocus('test', fakeLocus, eventType);
|
|
3064
3156
|
|
|
3065
3157
|
spies.forEach((spy) => {
|
|
3066
3158
|
assert.notCalled(spy);
|
|
@@ -3210,7 +3302,11 @@ describe('plugin-meetings', () => {
|
|
|
3210
3302
|
}).then(() => {
|
|
3211
3303
|
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'oldLocusUrl'});
|
|
3212
3304
|
|
|
3213
|
-
assert.calledOnceWithExactly(
|
|
3305
|
+
assert.calledOnceWithExactly(
|
|
3306
|
+
meeting.locusInfo.onFullLocus,
|
|
3307
|
+
'classic Locus sync',
|
|
3308
|
+
fakeFullLocusDto
|
|
3309
|
+
);
|
|
3214
3310
|
assert.calledOnce(locusInfo.locusParser.resume);
|
|
3215
3311
|
});
|
|
3216
3312
|
});
|
|
@@ -3308,7 +3404,11 @@ describe('plugin-meetings', () => {
|
|
|
3308
3404
|
});
|
|
3309
3405
|
|
|
3310
3406
|
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
|
3311
|
-
assert.calledOnceWithExactly(
|
|
3407
|
+
assert.calledOnceWithExactly(
|
|
3408
|
+
meeting.locusInfo.onFullLocus,
|
|
3409
|
+
'classic Locus sync',
|
|
3410
|
+
fakeFullLocusDto
|
|
3411
|
+
);
|
|
3312
3412
|
assert.calledOnce(locusInfo.locusParser.resume);
|
|
3313
3413
|
});
|
|
3314
3414
|
});
|
|
@@ -3484,7 +3584,11 @@ describe('plugin-meetings', () => {
|
|
|
3484
3584
|
url: 'fake locus DELTA url',
|
|
3485
3585
|
});
|
|
3486
3586
|
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
|
3487
|
-
assert.calledOnceWithExactly(
|
|
3587
|
+
assert.calledOnceWithExactly(
|
|
3588
|
+
meeting.locusInfo.onFullLocus,
|
|
3589
|
+
'classic Locus sync',
|
|
3590
|
+
fakeFullLocusDto
|
|
3591
|
+
);
|
|
3488
3592
|
assert.calledOnce(locusInfo.locusParser.resume);
|
|
3489
3593
|
});
|
|
3490
3594
|
});
|
|
@@ -3856,7 +3960,7 @@ describe('plugin-meetings', () => {
|
|
|
3856
3960
|
|
|
3857
3961
|
describe('#updateLocusUrl', () => {
|
|
3858
3962
|
it('trigger LOCUS_INFO_UPDATE_URL event with isMainLocus is true as default', () => {
|
|
3859
|
-
const fakeUrl =
|
|
3963
|
+
const fakeUrl = 'https://fake.com/locus';
|
|
3860
3964
|
locusInfo.emitScoped = sinon.stub();
|
|
3861
3965
|
locusInfo.updateLocusUrl(fakeUrl);
|
|
3862
3966
|
|
|
@@ -3869,12 +3973,12 @@ describe('plugin-meetings', () => {
|
|
|
3869
3973
|
EVENTS.LOCUS_INFO_UPDATE_URL,
|
|
3870
3974
|
{
|
|
3871
3975
|
url: fakeUrl,
|
|
3872
|
-
isMainLocus: true
|
|
3873
|
-
}
|
|
3976
|
+
isMainLocus: true,
|
|
3977
|
+
}
|
|
3874
3978
|
);
|
|
3875
3979
|
});
|
|
3876
3980
|
it('trigger LOCUS_INFO_UPDATE_URL event with isMainLocus is false', () => {
|
|
3877
|
-
const fakeUrl =
|
|
3981
|
+
const fakeUrl = 'https://fake.com/locus';
|
|
3878
3982
|
locusInfo.emitScoped = sinon.stub();
|
|
3879
3983
|
locusInfo.updateLocusUrl(fakeUrl, false);
|
|
3880
3984
|
|
|
@@ -3887,8 +3991,8 @@ describe('plugin-meetings', () => {
|
|
|
3887
3991
|
EVENTS.LOCUS_INFO_UPDATE_URL,
|
|
3888
3992
|
{
|
|
3889
3993
|
url: fakeUrl,
|
|
3890
|
-
isMainLocus: false
|
|
3891
|
-
}
|
|
3994
|
+
isMainLocus: false,
|
|
3995
|
+
}
|
|
3892
3996
|
);
|
|
3893
3997
|
});
|
|
3894
3998
|
});
|
|
@@ -3940,8 +4044,8 @@ describe('plugin-meetings', () => {
|
|
|
3940
4044
|
|
|
3941
4045
|
sinon.stub(locusInfo, 'updateParticipants');
|
|
3942
4046
|
sinon.stub(locusInfo, 'isMeetingActive');
|
|
3943
|
-
|
|
3944
|
-
|
|
4047
|
+
sinon.stub(locusInfo, 'handleOneOnOneEvent');
|
|
4048
|
+
updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo');
|
|
3945
4049
|
syncRequestStub = sinon.stub().resolves({body: {}});
|
|
3946
4050
|
|
|
3947
4051
|
mockMeeting.locusInfo = locusInfo;
|
|
@@ -3950,7 +4054,7 @@ describe('plugin-meetings', () => {
|
|
|
3950
4054
|
getLocusDTO: syncRequestStub,
|
|
3951
4055
|
};
|
|
3952
4056
|
|
|
3953
|
-
locusInfo.onFullLocus({
|
|
4057
|
+
locusInfo.onFullLocus('test', {
|
|
3954
4058
|
sequence: {
|
|
3955
4059
|
rangeStart: 0,
|
|
3956
4060
|
rangeEnd: 0,
|
|
@@ -4213,6 +4317,30 @@ describe('plugin-meetings', () => {
|
|
|
4213
4317
|
|
|
4214
4318
|
assert.calledOnceWithExactly(mockHashTreeParser.handleMessage, fakeHashTreeMessage);
|
|
4215
4319
|
});
|
|
4320
|
+
|
|
4321
|
+
it('ignores hash tree event when hashTreeParser is not created yet', () => {
|
|
4322
|
+
const data = {
|
|
4323
|
+
eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
|
|
4324
|
+
stateElementsMessage: {
|
|
4325
|
+
locusStateElements: [],
|
|
4326
|
+
dataSets: [],
|
|
4327
|
+
},
|
|
4328
|
+
};
|
|
4329
|
+
|
|
4330
|
+
const loggerSpy = sinon.spy(LoggerProxy.logger, 'info');
|
|
4331
|
+
const getTheLocusToUpdateStub = sinon.stub(locusInfo, 'getTheLocusToUpdate');
|
|
4332
|
+
|
|
4333
|
+
// Ensure we're not using hash trees
|
|
4334
|
+
assert.isUndefined(locusInfo.hashTreeParser);
|
|
4335
|
+
|
|
4336
|
+
locusInfo.parse(mockMeeting, data);
|
|
4337
|
+
|
|
4338
|
+
assert.calledWith(
|
|
4339
|
+
loggerSpy,
|
|
4340
|
+
'Locus-info:index#parse --> received locus hash tree event before hashTreeParser is created'
|
|
4341
|
+
);
|
|
4342
|
+
assert.notCalled(getTheLocusToUpdateStub);
|
|
4343
|
+
});
|
|
4216
4344
|
});
|
|
4217
4345
|
});
|
|
4218
4346
|
});
|
|
@@ -5,6 +5,8 @@ import {ConnectionState, MediaConnectionEventNames} from '@webex/internal-media-
|
|
|
5
5
|
import testUtils from '../../../utils/testUtils';
|
|
6
6
|
import {ICE_AND_DTLS_CONNECTION_TIMEOUT} from '@webex/plugin-meetings/src/constants';
|
|
7
7
|
import MediaConnectionAwaiter from '../../../../src/media/MediaConnectionAwaiter';
|
|
8
|
+
import Metrics from '../../../../src/metrics';
|
|
9
|
+
import BEHAVIORAL_METRICS from '../../../../src/metrics/constants';
|
|
8
10
|
|
|
9
11
|
describe('MediaConnectionAwaiter', () => {
|
|
10
12
|
let mediaConnectionAwaiter;
|
|
@@ -14,18 +16,34 @@ describe('MediaConnectionAwaiter', () => {
|
|
|
14
16
|
beforeEach(() => {
|
|
15
17
|
clock = sinon.useFakeTimers();
|
|
16
18
|
|
|
19
|
+
const mockTransportReport = {
|
|
20
|
+
type: 'transport',
|
|
21
|
+
dtlsState: 'connecting',
|
|
22
|
+
iceState: 'checking',
|
|
23
|
+
packetsSent: 10,
|
|
24
|
+
packetsReceived: 5,
|
|
25
|
+
};
|
|
26
|
+
|
|
17
27
|
mockMC = {
|
|
18
|
-
getStats: sinon.stub().resolves(
|
|
28
|
+
getStats: sinon.stub().resolves({
|
|
29
|
+
values: () => [mockTransportReport],
|
|
30
|
+
}),
|
|
19
31
|
on: sinon.stub(),
|
|
20
32
|
off: sinon.stub(),
|
|
21
33
|
getConnectionState: sinon.stub().returns(ConnectionState.New),
|
|
22
34
|
getIceGatheringState: sinon.stub().returns('new'),
|
|
23
35
|
getIceConnectionState: sinon.stub().returns('new'),
|
|
24
36
|
getPeerConnectionState: sinon.stub().returns('new'),
|
|
37
|
+
multistreamConnection: {
|
|
38
|
+
dataChannel: {
|
|
39
|
+
readyState: 'open',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
25
42
|
};
|
|
26
43
|
|
|
27
44
|
mediaConnectionAwaiter = new MediaConnectionAwaiter({
|
|
28
45
|
webrtcMediaConnection: mockMC,
|
|
46
|
+
correlationId: 'test-correlation-id',
|
|
29
47
|
});
|
|
30
48
|
});
|
|
31
49
|
|
|
@@ -44,6 +62,8 @@ describe('MediaConnectionAwaiter', () => {
|
|
|
44
62
|
});
|
|
45
63
|
|
|
46
64
|
it('rejects after timeout if ice state is not connected', async () => {
|
|
65
|
+
const sendMetricSpy = sinon.spy(Metrics, 'sendBehavioralMetric');
|
|
66
|
+
|
|
47
67
|
mockMC.getConnectionState.returns(ConnectionState.Connecting);
|
|
48
68
|
mockMC.getIceGatheringState.returns('gathering');
|
|
49
69
|
|
|
@@ -83,6 +103,18 @@ describe('MediaConnectionAwaiter', () => {
|
|
|
83
103
|
assert.equal(promiseRejected, true);
|
|
84
104
|
|
|
85
105
|
assert.calledThrice(mockMC.off);
|
|
106
|
+
|
|
107
|
+
assert.calledOnceWithExactly(sendMetricSpy, BEHAVIORAL_METRICS.MEDIA_STILL_NOT_CONNECTED, {
|
|
108
|
+
correlation_id: 'test-correlation-id',
|
|
109
|
+
numTransports: 1,
|
|
110
|
+
dtlsState: 'connecting',
|
|
111
|
+
iceState: 'checking',
|
|
112
|
+
packetsSent: 10,
|
|
113
|
+
packetsReceived: 5,
|
|
114
|
+
dataChannelState: 'open',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
sendMetricSpy.restore();
|
|
86
118
|
});
|
|
87
119
|
|
|
88
120
|
it('rejects immediately if ice state is FAILED', async () => {
|
|
@@ -351,6 +383,8 @@ describe('MediaConnectionAwaiter', () => {
|
|
|
351
383
|
});
|
|
352
384
|
|
|
353
385
|
it(`reject with restart timer once if gathering state is not complete`, async () => {
|
|
386
|
+
const sendMetricSpy = sinon.spy(Metrics, 'sendBehavioralMetric');
|
|
387
|
+
|
|
354
388
|
mockMC.getConnectionState.returns(ConnectionState.Connecting);
|
|
355
389
|
mockMC.getIceGatheringState.returns('new');
|
|
356
390
|
|
|
@@ -390,6 +424,12 @@ describe('MediaConnectionAwaiter', () => {
|
|
|
390
424
|
|
|
391
425
|
assert.calledOnce(clearTimeoutSpy);
|
|
392
426
|
assert.calledTwice(setTimeoutSpy);
|
|
427
|
+
|
|
428
|
+
// verify sendMetric was called twice (once for each timeout)
|
|
429
|
+
assert.calledTwice(sendMetricSpy);
|
|
430
|
+
assert.calledWith(sendMetricSpy, BEHAVIORAL_METRICS.MEDIA_STILL_NOT_CONNECTED);
|
|
431
|
+
|
|
432
|
+
sendMetricSpy.restore();
|
|
393
433
|
});
|
|
394
434
|
|
|
395
435
|
it(`resolves gathering and connection state complete right after`, async () => {
|