@webex/plugin-meetings 3.11.0-next.4 → 3.11.0-next.41
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 +184 -0
- package/dist/aiEnableRequest/index.js.map +1 -0
- package/dist/aiEnableRequest/utils.js +36 -0
- package/dist/aiEnableRequest/utils.js.map +1 -0
- package/dist/annotation/index.js +3 -3
- package/dist/annotation/index.js.map +1 -1
- 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/constants.js +26 -6
- package/dist/constants.js.map +1 -1
- package/dist/hashTree/constants.js +3 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTree.js +18 -0
- package/dist/hashTree/hashTree.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +709 -380
- 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 +11 -2
- 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 +2 -2
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +5 -3
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +125 -68
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +1 -0
- package/dist/locus-info/selfUtils.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/in-meeting-actions.js +7 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +209 -90
- 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 +128 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +78 -36
- package/dist/meetings/index.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/util.js +10 -0
- package/dist/member/util.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/aiEnableRequest/index.d.ts +5 -0
- package/dist/types/aiEnableRequest/utils.d.ts +2 -0
- package/dist/types/config.d.ts +3 -0
- package/dist/types/constants.d.ts +21 -1
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTree.d.ts +7 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +99 -14
- package/dist/types/hashTree/types.d.ts +3 -0
- package/dist/types/hashTree/utils.d.ts +6 -0
- package/dist/types/index.d.ts +1 -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/in-meeting-actions.d.ts +6 -0
- package/dist/types/meeting/index.d.ts +24 -2
- 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 +31 -0
- package/dist/types/meetings/index.d.ts +4 -2
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/util.d.ts +5 -0
- 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/aiEnableRequest/README.md +84 -0
- package/src/aiEnableRequest/index.ts +170 -0
- package/src/aiEnableRequest/utils.ts +25 -0
- package/src/annotation/index.ts +7 -4
- package/src/config.ts +3 -0
- package/src/constants.ts +26 -1
- package/src/hashTree/constants.ts +1 -0
- package/src/hashTree/hashTree.ts +17 -0
- package/src/hashTree/hashTreeParser.ts +627 -249
- package/src/hashTree/types.ts +4 -0
- package/src/hashTree/utils.ts +9 -0
- package/src/index.ts +8 -1
- package/src/interceptors/constant.ts +6 -0
- package/src/interceptors/dataChannelAuthToken.ts +142 -0
- package/src/interceptors/index.ts +2 -1
- package/src/interpretation/index.ts +2 -2
- package/src/locus-info/controlsUtils.ts +11 -0
- package/src/locus-info/index.ts +146 -58
- package/src/locus-info/selfUtils.ts +1 -0
- 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/in-meeting-actions.ts +12 -0
- package/src/meeting/index.ts +127 -17
- package/src/meeting/request.ts +42 -0
- package/src/meeting/request.type.ts +6 -0
- package/src/meeting/util.ts +156 -1
- package/src/meetings/index.ts +94 -9
- package/src/member/index.ts +10 -0
- package/src/member/util.ts +12 -0
- 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/aiEnableRequest/index.ts +981 -0
- package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
- package/test/unit/spec/hashTree/hashTree.ts +66 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +1869 -189
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +141 -0
- package/test/unit/spec/locus-info/controlsUtils.js +29 -0
- package/test/unit/spec/locus-info/index.js +201 -45
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
- package/test/unit/spec/media/properties.ts +12 -3
- package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
- package/test/unit/spec/meeting/index.js +441 -75
- package/test/unit/spec/meeting/request.js +64 -0
- package/test/unit/spec/meeting/utils.js +433 -22
- package/test/unit/spec/meetings/index.js +550 -10
- package/test/unit/spec/member/index.js +28 -4
- package/test/unit/spec/member/util.js +65 -27
- 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
|
+
});
|
|
@@ -489,6 +489,35 @@ describe('plugin-meetings', () => {
|
|
|
489
489
|
assert.equal(updates.hasHesiodLLMIdChanged, true);
|
|
490
490
|
});
|
|
491
491
|
|
|
492
|
+
describe('hasAiSummaryNotificationChanged', () => {
|
|
493
|
+
it('returns false when aiSummaryNotification has not changed', () => {
|
|
494
|
+
const previous = {transcribe: {aiSummaryNotification: false}};
|
|
495
|
+
const current = {transcribe: {aiSummaryNotification: false}};
|
|
496
|
+
const {updates} = ControlsUtils.getControls(previous, current);
|
|
497
|
+
assert.equal(updates.hasAiSummaryNotificationChanged, false);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('returns true when aiSummaryNotification changes from false to true', () => {
|
|
501
|
+
const previous = {transcribe: {aiSummaryNotification: false}};
|
|
502
|
+
const current = {transcribe: {aiSummaryNotification: true}};
|
|
503
|
+
const {updates} = ControlsUtils.getControls(previous, current);
|
|
504
|
+
assert.equal(updates.hasAiSummaryNotificationChanged, true);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('returns true when aiSummaryNotification changes from undefined to true', () => {
|
|
508
|
+
const previous = {transcribe: undefined};
|
|
509
|
+
const current = {transcribe: {aiSummaryNotification: true}};
|
|
510
|
+
const {updates} = ControlsUtils.getControls(previous, current);
|
|
511
|
+
assert.equal(updates.hasAiSummaryNotificationChanged, true);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('parses aiSummaryNotification into the transcribe block', () => {
|
|
515
|
+
const controls = {transcribe: {transcribing: false, caption: false, aiSummaryNotification: true}};
|
|
516
|
+
const parsed = ControlsUtils.parse(controls);
|
|
517
|
+
assert.equal(parsed.transcribe.aiSummaryNotification, true);
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
492
521
|
describe('videoEnabled', () => {
|
|
493
522
|
const testVideoEnabled = (oldControls, newControls, updatedProperty) => {
|
|
494
523
|
const result = ControlsUtils.getControls(oldControls, newControls);
|
|
@@ -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(
|
|
@@ -1366,6 +1459,34 @@ describe('plugin-meetings', () => {
|
|
|
1366
1459
|
);
|
|
1367
1460
|
});
|
|
1368
1461
|
|
|
1462
|
+
it('should emit CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED when aiSummaryNotification changes', () => {
|
|
1463
|
+
locusInfo.emitScoped = sinon.stub();
|
|
1464
|
+
locusInfo.controls = {
|
|
1465
|
+
transcribe: {
|
|
1466
|
+
transcribing: false,
|
|
1467
|
+
caption: false,
|
|
1468
|
+
aiSummaryNotification: false,
|
|
1469
|
+
},
|
|
1470
|
+
};
|
|
1471
|
+
newControls.transcribe.transcribing = false;
|
|
1472
|
+
newControls.transcribe.caption = false;
|
|
1473
|
+
newControls.transcribe.aiSummaryNotification = true;
|
|
1474
|
+
|
|
1475
|
+
locusInfo.updateControls(newControls);
|
|
1476
|
+
|
|
1477
|
+
assert.calledWith(
|
|
1478
|
+
locusInfo.emitScoped,
|
|
1479
|
+
{
|
|
1480
|
+
file: 'locus-info',
|
|
1481
|
+
function: 'updateControls',
|
|
1482
|
+
},
|
|
1483
|
+
LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
|
|
1484
|
+
{
|
|
1485
|
+
aiSummaryNotification: true,
|
|
1486
|
+
}
|
|
1487
|
+
);
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1369
1490
|
it('should update the transcribe spoken language', () => {
|
|
1370
1491
|
locusInfo.emitScoped = sinon.stub();
|
|
1371
1492
|
locusInfo.controls = {
|
|
@@ -1631,7 +1752,6 @@ describe('plugin-meetings', () => {
|
|
|
1631
1752
|
);
|
|
1632
1753
|
});
|
|
1633
1754
|
|
|
1634
|
-
|
|
1635
1755
|
it('should call with participant display name', () => {
|
|
1636
1756
|
const failureParticipant = [
|
|
1637
1757
|
{
|
|
@@ -1656,7 +1776,7 @@ describe('plugin-meetings', () => {
|
|
|
1656
1776
|
displayName: 'Test User',
|
|
1657
1777
|
}
|
|
1658
1778
|
);
|
|
1659
|
-
})
|
|
1779
|
+
});
|
|
1660
1780
|
});
|
|
1661
1781
|
|
|
1662
1782
|
describe('#updateSelf', () => {
|
|
@@ -2457,8 +2577,8 @@ describe('plugin-meetings', () => {
|
|
|
2457
2577
|
{
|
|
2458
2578
|
isInitializing: !self,
|
|
2459
2579
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2580
|
+
);
|
|
2581
|
+
});
|
|
2462
2582
|
|
|
2463
2583
|
const checkMeetingInfoUpdatedCalled = (expected, payload) => {
|
|
2464
2584
|
const expectedArgs = [
|
|
@@ -2923,28 +3043,28 @@ describe('plugin-meetings', () => {
|
|
|
2923
3043
|
assert.isFunction(locusParser.onDeltaAction);
|
|
2924
3044
|
});
|
|
2925
3045
|
|
|
2926
|
-
it(
|
|
3046
|
+
it('#updateLocusInfo invokes updateLocusUrl before updateMeetingInfo', () => {
|
|
2927
3047
|
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(
|
|
3048
|
+
sinon.stub(locusInfo, 'updateControls');
|
|
3049
|
+
sinon.stub(locusInfo, 'updateConversationUrl');
|
|
3050
|
+
sinon.stub(locusInfo, 'updateCreated');
|
|
3051
|
+
sinon.stub(locusInfo, 'updateFullState');
|
|
3052
|
+
sinon.stub(locusInfo, 'updateHostInfo');
|
|
3053
|
+
sinon.stub(locusInfo, 'updateMeetingInfo').callsFake(() => {
|
|
3054
|
+
callOrder.push('updateMeetingInfo');
|
|
2935
3055
|
});
|
|
2936
|
-
sinon.stub(locusInfo,
|
|
2937
|
-
sinon.stub(locusInfo,
|
|
2938
|
-
sinon.stub(locusInfo,
|
|
2939
|
-
sinon.stub(locusInfo,
|
|
2940
|
-
callOrder.push(
|
|
3056
|
+
sinon.stub(locusInfo, 'updateMediaShares');
|
|
3057
|
+
sinon.stub(locusInfo, 'updateReplaces');
|
|
3058
|
+
sinon.stub(locusInfo, 'updateSelf');
|
|
3059
|
+
sinon.stub(locusInfo, 'updateLocusUrl').callsFake(() => {
|
|
3060
|
+
callOrder.push('updateLocusUrl');
|
|
2941
3061
|
});
|
|
2942
|
-
sinon.stub(locusInfo,
|
|
2943
|
-
sinon.stub(locusInfo,
|
|
2944
|
-
sinon.stub(locusInfo,
|
|
2945
|
-
sinon.stub(locusInfo,
|
|
2946
|
-
sinon.stub(locusInfo,
|
|
2947
|
-
sinon.stub(locusInfo,
|
|
3062
|
+
sinon.stub(locusInfo, 'updateAclUrl');
|
|
3063
|
+
sinon.stub(locusInfo, 'updateBasequence');
|
|
3064
|
+
sinon.stub(locusInfo, 'updateSequence');
|
|
3065
|
+
sinon.stub(locusInfo, 'updateEmbeddedApps');
|
|
3066
|
+
sinon.stub(locusInfo, 'updateLinks');
|
|
3067
|
+
sinon.stub(locusInfo, 'compareAndUpdate');
|
|
2948
3068
|
|
|
2949
3069
|
locusInfo.updateLocusInfo(locus);
|
|
2950
3070
|
|
|
@@ -3000,7 +3120,7 @@ describe('plugin-meetings', () => {
|
|
|
3000
3120
|
it('#updateLocusInfo puts the Locus DTO top level properties at the right place in LocusInfo class', () => {
|
|
3001
3121
|
// this test verifies that the top-level properties of Locus DTO are copied
|
|
3002
3122
|
// into LocusInfo class and set as top level properties too
|
|
3003
|
-
// this is important, because the code handling Locus
|
|
3123
|
+
// this is important, because the code handling Locus hash trees relies on it, see updateFromHashTree()
|
|
3004
3124
|
const info = {id: 'info id'};
|
|
3005
3125
|
const fullState = {id: 'fullState id'};
|
|
3006
3126
|
const links = {services: {id: 'service links'}, resources: {id: 'resource links'}};
|
|
@@ -3039,7 +3159,7 @@ describe('plugin-meetings', () => {
|
|
|
3039
3159
|
sandbox.stub(locusInfo, 'handleOneOnOneEvent');
|
|
3040
3160
|
sandbox.stub(locusParser, 'isNewFullLocus').returns(true);
|
|
3041
3161
|
|
|
3042
|
-
locusInfo.onFullLocus(fakeLocus, eventType);
|
|
3162
|
+
locusInfo.onFullLocus('test', fakeLocus, eventType);
|
|
3043
3163
|
|
|
3044
3164
|
assert.equal(fakeLocus, locusParser.workingCopy);
|
|
3045
3165
|
});
|
|
@@ -3060,7 +3180,7 @@ describe('plugin-meetings', () => {
|
|
|
3060
3180
|
|
|
3061
3181
|
sandbox.stub(locusParser, 'isNewFullLocus').returns(false);
|
|
3062
3182
|
|
|
3063
|
-
locusInfo.onFullLocus(fakeLocus, eventType);
|
|
3183
|
+
locusInfo.onFullLocus('test', fakeLocus, eventType);
|
|
3064
3184
|
|
|
3065
3185
|
spies.forEach((spy) => {
|
|
3066
3186
|
assert.notCalled(spy);
|
|
@@ -3210,7 +3330,11 @@ describe('plugin-meetings', () => {
|
|
|
3210
3330
|
}).then(() => {
|
|
3211
3331
|
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'oldLocusUrl'});
|
|
3212
3332
|
|
|
3213
|
-
assert.calledOnceWithExactly(
|
|
3333
|
+
assert.calledOnceWithExactly(
|
|
3334
|
+
meeting.locusInfo.onFullLocus,
|
|
3335
|
+
'classic Locus sync',
|
|
3336
|
+
fakeFullLocusDto
|
|
3337
|
+
);
|
|
3214
3338
|
assert.calledOnce(locusInfo.locusParser.resume);
|
|
3215
3339
|
});
|
|
3216
3340
|
});
|
|
@@ -3308,7 +3432,11 @@ describe('plugin-meetings', () => {
|
|
|
3308
3432
|
});
|
|
3309
3433
|
|
|
3310
3434
|
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
|
3311
|
-
assert.calledOnceWithExactly(
|
|
3435
|
+
assert.calledOnceWithExactly(
|
|
3436
|
+
meeting.locusInfo.onFullLocus,
|
|
3437
|
+
'classic Locus sync',
|
|
3438
|
+
fakeFullLocusDto
|
|
3439
|
+
);
|
|
3312
3440
|
assert.calledOnce(locusInfo.locusParser.resume);
|
|
3313
3441
|
});
|
|
3314
3442
|
});
|
|
@@ -3484,7 +3612,11 @@ describe('plugin-meetings', () => {
|
|
|
3484
3612
|
url: 'fake locus DELTA url',
|
|
3485
3613
|
});
|
|
3486
3614
|
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
|
3487
|
-
assert.calledOnceWithExactly(
|
|
3615
|
+
assert.calledOnceWithExactly(
|
|
3616
|
+
meeting.locusInfo.onFullLocus,
|
|
3617
|
+
'classic Locus sync',
|
|
3618
|
+
fakeFullLocusDto
|
|
3619
|
+
);
|
|
3488
3620
|
assert.calledOnce(locusInfo.locusParser.resume);
|
|
3489
3621
|
});
|
|
3490
3622
|
});
|
|
@@ -3856,7 +3988,7 @@ describe('plugin-meetings', () => {
|
|
|
3856
3988
|
|
|
3857
3989
|
describe('#updateLocusUrl', () => {
|
|
3858
3990
|
it('trigger LOCUS_INFO_UPDATE_URL event with isMainLocus is true as default', () => {
|
|
3859
|
-
const fakeUrl =
|
|
3991
|
+
const fakeUrl = 'https://fake.com/locus';
|
|
3860
3992
|
locusInfo.emitScoped = sinon.stub();
|
|
3861
3993
|
locusInfo.updateLocusUrl(fakeUrl);
|
|
3862
3994
|
|
|
@@ -3869,12 +4001,12 @@ describe('plugin-meetings', () => {
|
|
|
3869
4001
|
EVENTS.LOCUS_INFO_UPDATE_URL,
|
|
3870
4002
|
{
|
|
3871
4003
|
url: fakeUrl,
|
|
3872
|
-
isMainLocus: true
|
|
3873
|
-
}
|
|
4004
|
+
isMainLocus: true,
|
|
4005
|
+
}
|
|
3874
4006
|
);
|
|
3875
4007
|
});
|
|
3876
4008
|
it('trigger LOCUS_INFO_UPDATE_URL event with isMainLocus is false', () => {
|
|
3877
|
-
const fakeUrl =
|
|
4009
|
+
const fakeUrl = 'https://fake.com/locus';
|
|
3878
4010
|
locusInfo.emitScoped = sinon.stub();
|
|
3879
4011
|
locusInfo.updateLocusUrl(fakeUrl, false);
|
|
3880
4012
|
|
|
@@ -3887,8 +4019,8 @@ describe('plugin-meetings', () => {
|
|
|
3887
4019
|
EVENTS.LOCUS_INFO_UPDATE_URL,
|
|
3888
4020
|
{
|
|
3889
4021
|
url: fakeUrl,
|
|
3890
|
-
isMainLocus: false
|
|
3891
|
-
}
|
|
4022
|
+
isMainLocus: false,
|
|
4023
|
+
}
|
|
3892
4024
|
);
|
|
3893
4025
|
});
|
|
3894
4026
|
});
|
|
@@ -3940,8 +4072,8 @@ describe('plugin-meetings', () => {
|
|
|
3940
4072
|
|
|
3941
4073
|
sinon.stub(locusInfo, 'updateParticipants');
|
|
3942
4074
|
sinon.stub(locusInfo, 'isMeetingActive');
|
|
3943
|
-
|
|
3944
|
-
|
|
4075
|
+
sinon.stub(locusInfo, 'handleOneOnOneEvent');
|
|
4076
|
+
updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo');
|
|
3945
4077
|
syncRequestStub = sinon.stub().resolves({body: {}});
|
|
3946
4078
|
|
|
3947
4079
|
mockMeeting.locusInfo = locusInfo;
|
|
@@ -3950,7 +4082,7 @@ describe('plugin-meetings', () => {
|
|
|
3950
4082
|
getLocusDTO: syncRequestStub,
|
|
3951
4083
|
};
|
|
3952
4084
|
|
|
3953
|
-
locusInfo.onFullLocus({
|
|
4085
|
+
locusInfo.onFullLocus('test', {
|
|
3954
4086
|
sequence: {
|
|
3955
4087
|
rangeStart: 0,
|
|
3956
4088
|
rangeEnd: 0,
|
|
@@ -4213,6 +4345,30 @@ describe('plugin-meetings', () => {
|
|
|
4213
4345
|
|
|
4214
4346
|
assert.calledOnceWithExactly(mockHashTreeParser.handleMessage, fakeHashTreeMessage);
|
|
4215
4347
|
});
|
|
4348
|
+
|
|
4349
|
+
it('ignores hash tree event when hashTreeParser is not created yet', () => {
|
|
4350
|
+
const data = {
|
|
4351
|
+
eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
|
|
4352
|
+
stateElementsMessage: {
|
|
4353
|
+
locusStateElements: [],
|
|
4354
|
+
dataSets: [],
|
|
4355
|
+
},
|
|
4356
|
+
};
|
|
4357
|
+
|
|
4358
|
+
const loggerSpy = sinon.spy(LoggerProxy.logger, 'info');
|
|
4359
|
+
const getTheLocusToUpdateStub = sinon.stub(locusInfo, 'getTheLocusToUpdate');
|
|
4360
|
+
|
|
4361
|
+
// Ensure we're not using hash trees
|
|
4362
|
+
assert.isUndefined(locusInfo.hashTreeParser);
|
|
4363
|
+
|
|
4364
|
+
locusInfo.parse(mockMeeting, data);
|
|
4365
|
+
|
|
4366
|
+
assert.calledWith(
|
|
4367
|
+
loggerSpy,
|
|
4368
|
+
'Locus-info:index#parse --> received locus hash tree event before hashTreeParser is created'
|
|
4369
|
+
);
|
|
4370
|
+
assert.notCalled(getTheLocusToUpdateStub);
|
|
4371
|
+
});
|
|
4216
4372
|
});
|
|
4217
4373
|
});
|
|
4218
4374
|
});
|