@webex/plugin-meetings 3.12.0-next.8 → 3.12.0-task-refactor.1
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/annotation/index.js +5 -14
- 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 +2 -8
- package/dist/config.js.map +1 -1
- package/dist/constants.js +6 -29
- package/dist/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +29 -1563
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/types.js +3 -13
- package/dist/hashTree/types.js.map +1 -1
- package/dist/index.js +2 -11
- package/dist/index.js.map +1 -1
- package/dist/interceptors/index.js +0 -7
- package/dist/interceptors/index.js.map +1 -1
- package/dist/interceptors/locusRouteToken.js +5 -27
- package/dist/interceptors/locusRouteToken.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 +3 -7
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +247 -642
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +0 -1
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/locus-info/types.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +1 -57
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/properties.js +2 -4
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +1 -7
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +1036 -1481
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +0 -50
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +3 -133
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +59 -142
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +7 -11
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +0 -10
- package/dist/member/index.js.map +1 -1
- package/dist/member/util.js +0 -10
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +1 -7
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +60 -9
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +0 -11
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +2 -116
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/reachability/clusterReachability.js +18 -171
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.js +11 -21
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/reachabilityPeerConnection.js +1 -1
- package/dist/reachability/reachabilityPeerConnection.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/reconnection-manager/index.js +1 -0
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/types/common/browser-detection.d.ts +0 -1
- package/dist/types/common/events/events-scope.d.ts +0 -1
- package/dist/types/common/events/events.d.ts +0 -1
- package/dist/types/config.d.ts +0 -5
- package/dist/types/constants.d.ts +1 -24
- package/dist/types/hashTree/hashTreeParser.d.ts +11 -260
- package/dist/types/hashTree/types.d.ts +0 -20
- package/dist/types/index.d.ts +0 -1
- package/dist/types/interceptors/index.d.ts +1 -2
- package/dist/types/interceptors/locusRouteToken.d.ts +0 -2
- package/dist/types/locus-info/index.d.ts +47 -68
- package/dist/types/locus-info/types.d.ts +12 -28
- package/dist/types/media/MediaConnectionAwaiter.d.ts +1 -10
- package/dist/types/media/properties.d.ts +1 -2
- package/dist/types/meeting/in-meeting-actions.d.ts +0 -6
- package/dist/types/meeting/index.d.ts +7 -86
- package/dist/types/meeting/request.d.ts +1 -16
- package/dist/types/meeting/request.type.d.ts +0 -5
- package/dist/types/meeting/util.d.ts +0 -31
- package/dist/types/meeting-info/util.d.ts +0 -1
- package/dist/types/meeting-info/utilv2.d.ts +0 -1
- package/dist/types/meetings/index.d.ts +2 -4
- package/dist/types/member/index.d.ts +0 -1
- package/dist/types/member/types.d.ts +4 -4
- package/dist/types/member/util.d.ts +0 -5
- package/dist/types/metrics/constants.d.ts +0 -6
- package/dist/types/multistream/mediaRequestManager.d.ts +23 -0
- package/dist/types/multistream/sendSlotManager.d.ts +1 -23
- package/dist/types/reachability/clusterReachability.d.ts +3 -30
- package/dist/types/reactions/reactions.type.d.ts +0 -1
- package/dist/types/recording-controller/util.d.ts +5 -5
- package/dist/types/roap/index.d.ts +1 -1
- package/dist/webinar/index.js +163 -438
- package/dist/webinar/index.js.map +1 -1
- package/package.json +24 -26
- package/src/annotation/index.ts +7 -27
- package/src/config.ts +0 -5
- package/src/constants.ts +1 -30
- package/src/hashTree/hashTreeParser.ts +25 -1523
- package/src/hashTree/types.ts +1 -24
- package/src/index.ts +1 -8
- package/src/interceptors/index.ts +1 -2
- package/src/interceptors/locusRouteToken.ts +5 -22
- package/src/interpretation/index.ts +2 -2
- package/src/locus-info/controlsUtils.ts +0 -17
- package/src/locus-info/index.ts +213 -707
- package/src/locus-info/selfUtils.ts +0 -1
- package/src/locus-info/types.ts +12 -27
- package/src/media/MediaConnectionAwaiter.ts +1 -41
- package/src/media/properties.ts +1 -3
- package/src/meeting/in-meeting-actions.ts +0 -12
- package/src/meeting/index.ts +84 -461
- package/src/meeting/request.ts +0 -42
- package/src/meeting/request.type.ts +0 -6
- package/src/meeting/util.ts +2 -160
- package/src/meetings/index.ts +60 -180
- package/src/meetings/util.ts +9 -10
- package/src/member/index.ts +0 -10
- package/src/member/util.ts +0 -12
- package/src/metrics/constants.ts +0 -7
- package/src/multistream/mediaRequestManager.ts +54 -4
- package/src/multistream/remoteMediaManager.ts +0 -13
- package/src/multistream/sendSlotManager.ts +3 -97
- package/src/reachability/clusterReachability.ts +27 -153
- package/src/reachability/index.ts +1 -15
- package/src/reachability/reachabilityPeerConnection.ts +1 -3
- package/src/reactions/reactions.type.ts +0 -1
- package/src/reconnection-manager/index.ts +1 -0
- package/src/webinar/index.ts +6 -265
- package/test/unit/spec/annotation/index.ts +7 -69
- package/test/unit/spec/interceptors/locusRouteToken.ts +0 -44
- package/test/unit/spec/locus-info/controlsUtils.js +1 -56
- package/test/unit/spec/locus-info/index.js +90 -1457
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +1 -41
- package/test/unit/spec/media/properties.ts +3 -12
- package/test/unit/spec/meeting/in-meeting-actions.ts +2 -8
- package/test/unit/spec/meeting/index.js +128 -981
- package/test/unit/spec/meeting/request.js +0 -70
- package/test/unit/spec/meeting/utils.js +26 -438
- package/test/unit/spec/meetings/index.js +33 -845
- package/test/unit/spec/meetings/utils.js +1 -51
- package/test/unit/spec/member/index.js +4 -28
- package/test/unit/spec/member/util.js +27 -65
- package/test/unit/spec/multistream/mediaRequestManager.ts +85 -2
- package/test/unit/spec/multistream/remoteMediaManager.ts +0 -30
- package/test/unit/spec/multistream/sendSlotManager.ts +36 -135
- package/test/unit/spec/reachability/clusterReachability.ts +1 -125
- package/test/unit/spec/reachability/index.ts +3 -26
- package/test/unit/spec/reconnection-manager/index.js +8 -4
- package/test/unit/spec/webinar/index.ts +37 -534
- package/dist/aiEnableRequest/index.js +0 -184
- package/dist/aiEnableRequest/index.js.map +0 -1
- package/dist/aiEnableRequest/utils.js +0 -36
- package/dist/aiEnableRequest/utils.js.map +0 -1
- package/dist/hashTree/constants.js +0 -22
- package/dist/hashTree/constants.js.map +0 -1
- package/dist/hashTree/hashTree.js +0 -533
- package/dist/hashTree/hashTree.js.map +0 -1
- package/dist/hashTree/utils.js +0 -69
- package/dist/hashTree/utils.js.map +0 -1
- package/dist/interceptors/constant.js +0 -12
- package/dist/interceptors/constant.js.map +0 -1
- package/dist/interceptors/dataChannelAuthToken.js +0 -290
- package/dist/interceptors/dataChannelAuthToken.js.map +0 -1
- package/dist/interceptors/utils.js +0 -27
- package/dist/interceptors/utils.js.map +0 -1
- package/dist/types/aiEnableRequest/index.d.ts +0 -5
- package/dist/types/aiEnableRequest/utils.d.ts +0 -2
- package/dist/types/hashTree/constants.d.ts +0 -9
- package/dist/types/hashTree/hashTree.d.ts +0 -136
- package/dist/types/hashTree/utils.d.ts +0 -22
- package/dist/types/interceptors/constant.d.ts +0 -5
- package/dist/types/interceptors/dataChannelAuthToken.d.ts +0 -43
- package/dist/types/interceptors/utils.d.ts +0 -1
- package/dist/types/webinar/utils.d.ts +0 -6
- package/dist/webinar/utils.js +0 -25
- package/dist/webinar/utils.js.map +0 -1
- package/src/aiEnableRequest/README.md +0 -84
- package/src/aiEnableRequest/index.ts +0 -170
- package/src/aiEnableRequest/utils.ts +0 -25
- package/src/hashTree/constants.ts +0 -10
- package/src/hashTree/hashTree.ts +0 -480
- package/src/hashTree/utils.ts +0 -62
- package/src/interceptors/constant.ts +0 -6
- package/src/interceptors/dataChannelAuthToken.ts +0 -170
- package/src/interceptors/utils.ts +0 -16
- package/src/webinar/utils.ts +0 -16
- package/test/unit/spec/aiEnableRequest/index.ts +0 -981
- package/test/unit/spec/aiEnableRequest/utils.ts +0 -130
- package/test/unit/spec/hashTree/hashTree.ts +0 -721
- package/test/unit/spec/hashTree/hashTreeParser.ts +0 -3670
- package/test/unit/spec/hashTree/utils.ts +0 -140
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +0 -210
- package/test/unit/spec/interceptors/utils.ts +0 -75
- package/test/unit/spec/webinar/utils.ts +0 -39
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {assert} from '@webex/test-helper-chai';
|
|
1
|
+
import {assert, expect} from '@webex/test-helper-chai';
|
|
2
2
|
import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
|
|
3
3
|
import Webinar from '@webex/plugin-meetings/src/webinar';
|
|
4
4
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
5
5
|
import uuid from 'uuid';
|
|
6
6
|
import sinon from 'sinon';
|
|
7
|
-
import {DataChannelTokenType} from '@webex/internal-plugin-llm';
|
|
8
|
-
import {LLM_PRACTICE_SESSION, SHARE_STATUS} from '@webex/plugin-meetings/src/constants';
|
|
9
7
|
|
|
10
8
|
describe('plugin-meetings', () => {
|
|
11
9
|
describe('Webinar', () => {
|
|
@@ -28,20 +26,7 @@ describe('plugin-meetings', () => {
|
|
|
28
26
|
webex.meetings = {};
|
|
29
27
|
webex.credentials.getUserToken = getUserTokenStub;
|
|
30
28
|
webex.meetings.getMeetingByType = sinon.stub();
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
webex.internal.llm = {
|
|
34
|
-
getDatachannelToken: sinon.stub().returns(undefined),
|
|
35
|
-
setDatachannelToken: sinon.stub(),
|
|
36
|
-
isDataChannelTokenEnabled: sinon.stub().resolves(false),
|
|
37
|
-
isConnected: sinon.stub().returns(false),
|
|
38
|
-
disconnectLLM: sinon.stub().resolves(),
|
|
39
|
-
off: sinon.stub(),
|
|
40
|
-
on: sinon.stub(),
|
|
41
|
-
getLocusUrl: sinon.stub().returns('old-locus-url'),
|
|
42
|
-
getDatachannelUrl: sinon.stub().returns('old-dc-url'),
|
|
43
|
-
registerAndConnect: sinon.stub().resolves('REGISTER_AND_CONNECT_RESULT'),
|
|
44
|
-
};
|
|
29
|
+
|
|
45
30
|
});
|
|
46
31
|
|
|
47
32
|
afterEach(() => {
|
|
@@ -162,399 +147,20 @@ describe('plugin-meetings', () => {
|
|
|
162
147
|
assert.equal(result.isPromoted, false, 'should not indicate promotion');
|
|
163
148
|
assert.equal(result.isDemoted, false, 'should not indicate demotion');
|
|
164
149
|
});
|
|
165
|
-
|
|
166
|
-
it('handles missing role payload safely', () => {
|
|
167
|
-
const updateStatusByRoleStub = sinon.stub(webinar, 'updateStatusByRole');
|
|
168
|
-
|
|
169
|
-
const result = webinar.updateRoleChanged(undefined);
|
|
170
|
-
|
|
171
|
-
assert.equal(webinar.selfIsPanelist, false);
|
|
172
|
-
assert.equal(webinar.selfIsAttendee, false);
|
|
173
|
-
assert.equal(webinar.canManageWebcast, false);
|
|
174
|
-
assert.deepEqual(result, {isPromoted: false, isDemoted: false});
|
|
175
|
-
assert.calledOnceWithExactly(updateStatusByRoleStub, {isPromoted: false, isDemoted: false});
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
describe('#cleanUp', () => {
|
|
180
|
-
it('delegates to cleanupPSDataChannel', () => {
|
|
181
|
-
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
182
|
-
|
|
183
|
-
webinar.cleanUp();
|
|
184
|
-
|
|
185
|
-
assert.calledOnceWithExactly(cleanupPSDataChannelStub);
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
describe('#cleanupPSDataChannel', () => {
|
|
190
|
-
let meeting;
|
|
191
|
-
|
|
192
|
-
beforeEach(() => {
|
|
193
|
-
meeting = {
|
|
194
|
-
processRelayEvent: sinon.stub(),
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
webex.meetings.getMeetingByType = sinon.stub().returns(meeting);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('disconnects the practice session channel and removes the relay listener', async () => {
|
|
201
|
-
await webinar.cleanupPSDataChannel();
|
|
202
|
-
|
|
203
|
-
assert.calledOnceWithExactly(
|
|
204
|
-
webex.internal.llm.disconnectLLM,
|
|
205
|
-
{code: 3050, reason: 'done (permanent)'},
|
|
206
|
-
LLM_PRACTICE_SESSION
|
|
207
|
-
);
|
|
208
|
-
assert.calledOnceWithExactly(
|
|
209
|
-
webex.internal.llm.off,
|
|
210
|
-
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
211
|
-
meeting.processRelayEvent
|
|
212
|
-
);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('removes a pending online listener if one exists', async () => {
|
|
216
|
-
const listener = sinon.stub();
|
|
217
|
-
webinar._pendingOnlineListener = listener;
|
|
218
|
-
|
|
219
|
-
await webinar.cleanupPSDataChannel();
|
|
220
|
-
|
|
221
|
-
assert.calledWith(webex.internal.llm.off, 'online', listener);
|
|
222
|
-
assert.isNull(webinar._pendingOnlineListener);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('skips online listener removal when none is pending', async () => {
|
|
226
|
-
webinar._pendingOnlineListener = null;
|
|
227
|
-
|
|
228
|
-
await webinar.cleanupPSDataChannel();
|
|
229
|
-
|
|
230
|
-
// 'off' should only be called for the relay event, not for 'online'
|
|
231
|
-
const onlineOffCalls = webex.internal.llm.off.args.filter(([event]) => event === 'online');
|
|
232
|
-
assert.equal(onlineOffCalls.length, 0);
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
describe('#updatePSDataChannel', () => {
|
|
237
|
-
let meeting;
|
|
238
|
-
let processRelayEvent;
|
|
239
|
-
|
|
240
|
-
beforeEach(() => {
|
|
241
|
-
processRelayEvent = sinon.stub();
|
|
242
|
-
meeting = {
|
|
243
|
-
isJoined: sinon.stub().returns(true),
|
|
244
|
-
processRelayEvent,
|
|
245
|
-
locusInfo: {
|
|
246
|
-
url: 'locus-url',
|
|
247
|
-
info: {practiceSessionDatachannelUrl: 'dc-url'},
|
|
248
|
-
},
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
webex.meetings.getMeetingByType = sinon.stub().returns(meeting);
|
|
252
|
-
|
|
253
|
-
// Default session is connected by default; practice session is not
|
|
254
|
-
webex.internal.llm.isConnected = sinon.stub().callsFake((sessionId) => {
|
|
255
|
-
return sessionId !== LLM_PRACTICE_SESSION;
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
// Token is pre-saved into LLM by saveDataChannelToken
|
|
259
|
-
webex.internal.llm.getDatachannelToken = sinon.stub().callsFake((tokenType) => {
|
|
260
|
-
if (tokenType === DataChannelTokenType.PracticeSession) return 'ps-token';
|
|
261
|
-
return undefined;
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// Ensure connect path is eligible
|
|
265
|
-
webinar.selfIsPanelist = true;
|
|
266
|
-
webinar.practiceSessionEnabled = true;
|
|
267
|
-
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
|
|
268
|
-
webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it('refreshes practice-session token before register when cached token is missing', async () => {
|
|
272
|
-
webex.internal.llm.isDataChannelTokenEnabled.resolves(true);
|
|
273
|
-
webex.internal.llm.getDatachannelToken = sinon.stub().callsFake((tokenType) => {
|
|
274
|
-
if (tokenType === DataChannelTokenType.PracticeSession) return undefined;
|
|
275
|
-
|
|
276
|
-
return undefined;
|
|
277
|
-
});
|
|
278
|
-
meeting.refreshDataChannelToken = sinon.stub().resolves({
|
|
279
|
-
body: {
|
|
280
|
-
datachannelToken: 'ps-token-from-refresh',
|
|
281
|
-
dataChannelTokenType: DataChannelTokenType.PracticeSession,
|
|
282
|
-
},
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
await webinar.updatePSDataChannel();
|
|
286
|
-
|
|
287
|
-
assert.calledOnceWithExactly(meeting.refreshDataChannelToken);
|
|
288
|
-
assert.calledWithExactly(
|
|
289
|
-
webex.internal.llm.setDatachannelToken,
|
|
290
|
-
'ps-token-from-refresh',
|
|
291
|
-
DataChannelTokenType.PracticeSession
|
|
292
|
-
);
|
|
293
|
-
assert.calledWith(
|
|
294
|
-
webex.internal.llm.registerAndConnect,
|
|
295
|
-
'locus-url',
|
|
296
|
-
'dc-url',
|
|
297
|
-
'ps-token-from-refresh',
|
|
298
|
-
LLM_PRACTICE_SESSION
|
|
299
|
-
);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it('does not reconnect if practice-session eligibility changes during async token refresh', async () => {
|
|
303
|
-
webex.internal.llm.isDataChannelTokenEnabled.resolves(true);
|
|
304
|
-
webex.internal.llm.getDatachannelToken = sinon.stub().returns(undefined);
|
|
305
|
-
|
|
306
|
-
let resolveRefresh;
|
|
307
|
-
meeting.refreshDataChannelToken = sinon.stub().returns(
|
|
308
|
-
new Promise((resolve) => {
|
|
309
|
-
resolveRefresh = resolve;
|
|
310
|
-
})
|
|
311
|
-
);
|
|
312
|
-
|
|
313
|
-
const updatePromise = webinar.updatePSDataChannel();
|
|
314
|
-
|
|
315
|
-
webinar.practiceSessionEnabled = false;
|
|
316
|
-
|
|
317
|
-
resolveRefresh({
|
|
318
|
-
body: {
|
|
319
|
-
datachannelToken: 'stale-ps-token',
|
|
320
|
-
dataChannelTokenType: DataChannelTokenType.PracticeSession,
|
|
321
|
-
},
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
const result = await updatePromise;
|
|
325
|
-
|
|
326
|
-
assert.isUndefined(result);
|
|
327
|
-
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
it('no-ops when practice session join eligibility is false', async () => {
|
|
331
|
-
webinar.practiceSessionEnabled = false;
|
|
332
|
-
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
333
|
-
|
|
334
|
-
const result = await webinar.updatePSDataChannel();
|
|
335
|
-
|
|
336
|
-
assert.isUndefined(result);
|
|
337
|
-
assert.calledOnceWithExactly(cleanupPSDataChannelStub);
|
|
338
|
-
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
it('no-ops when meeting is not joined', async () => {
|
|
342
|
-
meeting.isJoined.returns(false);
|
|
343
|
-
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
344
|
-
|
|
345
|
-
const result = await webinar.updatePSDataChannel();
|
|
346
|
-
|
|
347
|
-
assert.isUndefined(result);
|
|
348
|
-
assert.calledOnceWithExactly(cleanupPSDataChannelStub);
|
|
349
|
-
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it('no-ops when practiceSessionDatachannelUrl is missing', async () => {
|
|
353
|
-
meeting.locusInfo.info.practiceSessionDatachannelUrl = undefined;
|
|
354
|
-
|
|
355
|
-
const result = await webinar.updatePSDataChannel();
|
|
356
|
-
|
|
357
|
-
assert.isUndefined(result);
|
|
358
|
-
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it('no-ops when already connected to the same endpoints', async () => {
|
|
362
|
-
webex.internal.llm.isConnected.returns(true);
|
|
363
|
-
webex.internal.llm.getLocusUrl.returns('locus-url');
|
|
364
|
-
webex.internal.llm.getDatachannelUrl.returns('dc-url');
|
|
365
|
-
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
366
|
-
|
|
367
|
-
const result = await webinar.updatePSDataChannel();
|
|
368
|
-
|
|
369
|
-
assert.isUndefined(result);
|
|
370
|
-
assert.notCalled(cleanupPSDataChannelStub);
|
|
371
|
-
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
it('connects when eligible', async () => {
|
|
375
|
-
const result = await webinar.updatePSDataChannel();
|
|
376
|
-
|
|
377
|
-
assert.calledOnce(webex.internal.llm.registerAndConnect);
|
|
378
|
-
assert.calledWith(
|
|
379
|
-
webex.internal.llm.registerAndConnect,
|
|
380
|
-
'locus-url',
|
|
381
|
-
'dc-url',
|
|
382
|
-
'ps-token',
|
|
383
|
-
LLM_PRACTICE_SESSION
|
|
384
|
-
);
|
|
385
|
-
assert.calledOnceWithExactly(webex.internal.voicea.announce);
|
|
386
|
-
assert.equal(result, 'REGISTER_AND_CONNECT_RESULT');
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it('uses token from LLM', async () => {
|
|
390
|
-
webex.internal.llm.getDatachannelToken = sinon.stub().callsFake((tokenType) => {
|
|
391
|
-
if (tokenType === DataChannelTokenType.PracticeSession) return 'cached-token';
|
|
392
|
-
return undefined;
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
await webinar.updatePSDataChannel();
|
|
396
|
-
|
|
397
|
-
assert.calledWithExactly(
|
|
398
|
-
webex.internal.llm.getDatachannelToken,
|
|
399
|
-
DataChannelTokenType.PracticeSession
|
|
400
|
-
);
|
|
401
|
-
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
402
|
-
assert.calledWith(
|
|
403
|
-
webex.internal.llm.registerAndConnect,
|
|
404
|
-
'locus-url',
|
|
405
|
-
'dc-url',
|
|
406
|
-
'cached-token',
|
|
407
|
-
LLM_PRACTICE_SESSION
|
|
408
|
-
);
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
it('cleans up the existing practice session channel before reconnecting to new endpoints', async () => {
|
|
412
|
-
webex.internal.llm.isConnected.returns(true);
|
|
413
|
-
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
414
|
-
|
|
415
|
-
await webinar.updatePSDataChannel();
|
|
416
|
-
|
|
417
|
-
assert.calledOnceWithExactly(cleanupPSDataChannelStub);
|
|
418
|
-
assert.calledOnce(webex.internal.llm.registerAndConnect);
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
it('rebinds relay listener after successful connect', async () => {
|
|
422
|
-
await webinar.updatePSDataChannel();
|
|
423
|
-
|
|
424
|
-
assert.calledWith(
|
|
425
|
-
webex.internal.llm.off,
|
|
426
|
-
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
427
|
-
processRelayEvent
|
|
428
|
-
);
|
|
429
|
-
assert.calledWith(
|
|
430
|
-
webex.internal.llm.on,
|
|
431
|
-
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
432
|
-
processRelayEvent
|
|
433
|
-
);
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it('subscribes to transcription when caption intent is enabled', async () => {
|
|
437
|
-
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(true);
|
|
438
|
-
|
|
439
|
-
await webinar.updatePSDataChannel();
|
|
440
|
-
|
|
441
|
-
assert.calledOnceWithExactly(webex.internal.voicea.updateSubchannelSubscriptions, { subscribe: ['transcription'] });
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
it('does not subscribe to transcription when caption intent is disabled', async () => {
|
|
445
|
-
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
|
|
446
|
-
|
|
447
|
-
await webinar.updatePSDataChannel();
|
|
448
|
-
|
|
449
|
-
assert.notCalled(webex.internal.voicea.updateSubchannelSubscriptions);
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
it('defers connect when default session is not yet connected', async () => {
|
|
453
|
-
// Default session is not connected initially
|
|
454
|
-
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
455
|
-
|
|
456
|
-
const result = await webinar.updatePSDataChannel();
|
|
457
|
-
|
|
458
|
-
// Should return undefined immediately (deferred)
|
|
459
|
-
assert.isUndefined(result);
|
|
460
|
-
// Should register an 'online' listener but NOT call registerAndConnect yet
|
|
461
|
-
assert.calledWith(webex.internal.llm.on, 'online', sinon.match.func);
|
|
462
|
-
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
463
|
-
// Should store the pending listener
|
|
464
|
-
assert.isNotNull(webinar._pendingOnlineListener);
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
it('does not register duplicate online listeners on repeated calls', async () => {
|
|
468
|
-
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
469
|
-
|
|
470
|
-
await webinar.updatePSDataChannel();
|
|
471
|
-
await webinar.updatePSDataChannel();
|
|
472
|
-
await webinar.updatePSDataChannel();
|
|
473
|
-
|
|
474
|
-
// Only one 'online' listener should have been registered
|
|
475
|
-
const onlineCalls = webex.internal.llm.on.args.filter(([event]) => event === 'online');
|
|
476
|
-
assert.equal(onlineCalls.length, 1, 'should register exactly one online listener');
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
it('re-invokes updatePSDataChannel when default session comes online', async () => {
|
|
480
|
-
// Default session is not connected initially
|
|
481
|
-
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
482
|
-
|
|
483
|
-
const updatePSDataChannelSpy = sinon.spy(webinar, 'updatePSDataChannel');
|
|
484
|
-
|
|
485
|
-
// First call defers
|
|
486
|
-
await webinar.updatePSDataChannel();
|
|
487
|
-
|
|
488
|
-
// Capture the 'online' listener
|
|
489
|
-
const onlineCall = webex.internal.llm.on.args.find(([event]) => event === 'online');
|
|
490
|
-
assert.isDefined(onlineCall, 'should have registered an online listener');
|
|
491
|
-
|
|
492
|
-
// Now simulate default session coming online
|
|
493
|
-
webex.internal.llm.isConnected = sinon.stub().callsFake((sessionId) => {
|
|
494
|
-
return sessionId !== LLM_PRACTICE_SESSION;
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
// Fire the captured listener
|
|
498
|
-
onlineCall[1]();
|
|
499
|
-
|
|
500
|
-
// The listener should have cleared itself, removed itself, and re-called updatePSDataChannel
|
|
501
|
-
assert.isNull(webinar._pendingOnlineListener);
|
|
502
|
-
assert.calledWith(webex.internal.llm.off, 'online', sinon.match.func);
|
|
503
|
-
assert.equal(updatePSDataChannelSpy.callCount, 2);
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
it('does not reconnect with stale data if demoted before default session comes online', async () => {
|
|
507
|
-
// Default session is not connected initially
|
|
508
|
-
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
509
|
-
|
|
510
|
-
await webinar.updatePSDataChannel();
|
|
511
|
-
|
|
512
|
-
// Capture the 'online' listener
|
|
513
|
-
const onlineCall = webex.internal.llm.on.args.find(([event]) => event === 'online');
|
|
514
|
-
assert.isDefined(onlineCall);
|
|
515
|
-
|
|
516
|
-
// Simulate demotion while waiting
|
|
517
|
-
webinar.selfIsPanelist = false;
|
|
518
|
-
|
|
519
|
-
// Now default session comes online
|
|
520
|
-
webex.internal.llm.isConnected = sinon.stub().callsFake((sessionId) => {
|
|
521
|
-
return sessionId !== LLM_PRACTICE_SESSION;
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
// Fire the listener — re-invokes updatePSDataChannel which will see isPracticeSession = false
|
|
525
|
-
onlineCall[1]();
|
|
526
|
-
|
|
527
|
-
// Should NOT have called registerAndConnect since the user is no longer eligible
|
|
528
|
-
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
it('proceeds immediately when default session is already connected', async () => {
|
|
532
|
-
// Default session already connected, practice session not
|
|
533
|
-
webex.internal.llm.isConnected = sinon.stub().callsFake((sessionId) => {
|
|
534
|
-
return sessionId !== LLM_PRACTICE_SESSION;
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
const result = await webinar.updatePSDataChannel();
|
|
538
|
-
|
|
539
|
-
// The 'online' listener is registered then immediately removed since default session is already connected
|
|
540
|
-
assert.calledWith(webex.internal.llm.on, 'online', sinon.match.func);
|
|
541
|
-
assert.calledWith(webex.internal.llm.off, 'online', sinon.match.func);
|
|
542
|
-
assert.isNull(webinar._pendingOnlineListener);
|
|
543
|
-
assert.calledOnce(webex.internal.llm.registerAndConnect);
|
|
544
|
-
assert.equal(result, 'REGISTER_AND_CONNECT_RESULT');
|
|
545
|
-
});
|
|
546
150
|
});
|
|
547
151
|
|
|
548
152
|
describe('#updateStatusByRole', () => {
|
|
153
|
+
let updateLLMConnection;
|
|
549
154
|
let updateMediaShares;
|
|
550
155
|
beforeEach(() => {
|
|
156
|
+
// @ts-ignore
|
|
157
|
+
updateLLMConnection = sinon.stub();
|
|
551
158
|
updateMediaShares = sinon.stub()
|
|
552
159
|
webinar.webex.meetings = {
|
|
553
160
|
getMeetingByType: sinon.stub().returns({
|
|
554
161
|
id: 'meeting-id',
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
shareStatus: SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE,
|
|
162
|
+
updateLLMConnection: updateLLMConnection,
|
|
163
|
+
shareStatus: 'whiteboard_share_active',
|
|
558
164
|
locusInfo: {
|
|
559
165
|
mediaShares: 'mediaShares',
|
|
560
166
|
updateMediaShares: updateMediaShares
|
|
@@ -567,20 +173,40 @@ describe('plugin-meetings', () => {
|
|
|
567
173
|
sinon.restore();
|
|
568
174
|
});
|
|
569
175
|
|
|
176
|
+
it('trigger updateLLMConnection if PS started', () => {
|
|
177
|
+
|
|
178
|
+
webinar.practiceSessionEnabled = true;
|
|
179
|
+
const roleChange = {isPromoted: true, isDemoted: false};
|
|
180
|
+
|
|
181
|
+
const result = webinar.updateStatusByRole(roleChange);
|
|
182
|
+
|
|
183
|
+
assert.calledOnce(updateLLMConnection);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('Not trigger updateLLMConnection if PS not started', () => {
|
|
187
|
+
|
|
188
|
+
webinar.practiceSessionEnabled = false;
|
|
189
|
+
const roleChange = {isPromoted: true, isDemoted: false};
|
|
190
|
+
|
|
191
|
+
const result = webinar.updateStatusByRole(roleChange);
|
|
192
|
+
|
|
193
|
+
assert.notCalled(updateLLMConnection);
|
|
194
|
+
});
|
|
195
|
+
|
|
570
196
|
it('trigger updateMediaShares if promoted', () => {
|
|
571
197
|
|
|
572
198
|
const roleChange = {isPromoted: true, isDemoted: false};
|
|
573
199
|
|
|
574
|
-
webinar.updateStatusByRole(roleChange);
|
|
200
|
+
const result = webinar.updateStatusByRole(roleChange);
|
|
575
201
|
|
|
576
|
-
assert.
|
|
202
|
+
assert.calledOnce(updateMediaShares);
|
|
577
203
|
});
|
|
578
204
|
|
|
579
205
|
it('Not trigger updateMediaShares if no role change', () => {
|
|
580
206
|
|
|
581
207
|
const roleChange = {isPromoted: false, isDemoted: false};
|
|
582
208
|
|
|
583
|
-
webinar.updateStatusByRole(roleChange);
|
|
209
|
+
const result = webinar.updateStatusByRole(roleChange);
|
|
584
210
|
|
|
585
211
|
assert.notCalled(updateMediaShares);
|
|
586
212
|
});
|
|
@@ -588,18 +214,18 @@ describe('plugin-meetings', () => {
|
|
|
588
214
|
|
|
589
215
|
const roleChange = {isPromoted: true, isDemoted: false};
|
|
590
216
|
|
|
591
|
-
webinar.updateStatusByRole(roleChange);
|
|
217
|
+
const result = webinar.updateStatusByRole(roleChange);
|
|
592
218
|
|
|
593
|
-
assert.
|
|
219
|
+
assert.calledOnce(updateMediaShares);
|
|
594
220
|
});
|
|
595
221
|
|
|
596
222
|
it('trigger updateMediaShares if is attendee with whiteboard share', () => {
|
|
597
223
|
|
|
598
224
|
const roleChange = {isPromoted: false, isDemoted: true};
|
|
599
225
|
|
|
600
|
-
webinar.updateStatusByRole(roleChange);
|
|
226
|
+
const result = webinar.updateStatusByRole(roleChange);
|
|
601
227
|
|
|
602
|
-
assert.
|
|
228
|
+
assert.calledOnce(updateMediaShares);
|
|
603
229
|
});
|
|
604
230
|
|
|
605
231
|
it('Not trigger updateMediaShares if is attendee with screen share', () => {
|
|
@@ -607,9 +233,8 @@ describe('plugin-meetings', () => {
|
|
|
607
233
|
webinar.webex.meetings = {
|
|
608
234
|
getMeetingByType: sinon.stub().returns({
|
|
609
235
|
id: 'meeting-id',
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
shareStatus: SHARE_STATUS.REMOTE_SHARE_ACTIVE,
|
|
236
|
+
updateLLMConnection: updateLLMConnection,
|
|
237
|
+
shareStatus: 'remote_share_active',
|
|
613
238
|
locusInfo: {
|
|
614
239
|
mediaShares: 'mediaShares',
|
|
615
240
|
updateMediaShares: updateMediaShares
|
|
@@ -619,18 +244,10 @@ describe('plugin-meetings', () => {
|
|
|
619
244
|
|
|
620
245
|
const roleChange = {isPromoted: false, isDemoted: true};
|
|
621
246
|
|
|
622
|
-
webinar.updateStatusByRole(roleChange);
|
|
247
|
+
const result = webinar.updateStatusByRole(roleChange);
|
|
623
248
|
|
|
624
249
|
assert.notCalled(updateMediaShares);
|
|
625
250
|
});
|
|
626
|
-
|
|
627
|
-
it('updates PS data channel based on join eligibility', () => {
|
|
628
|
-
const updatePSDataChannelStub = sinon.stub(webinar, 'updatePSDataChannel').resolves();
|
|
629
|
-
|
|
630
|
-
webinar.updateStatusByRole({isPromoted: false, isDemoted: false});
|
|
631
|
-
|
|
632
|
-
assert.calledOnceWithExactly(updatePSDataChannelStub);
|
|
633
|
-
});
|
|
634
251
|
});
|
|
635
252
|
|
|
636
253
|
describe("#setPracticeSessionState", () => {
|
|
@@ -706,14 +323,6 @@ describe('plugin-meetings', () => {
|
|
|
706
323
|
|
|
707
324
|
assert.equal(webinar.practiceSessionEnabled, false);
|
|
708
325
|
});
|
|
709
|
-
it('triggers PS data channel update using computed eligibility', () => {
|
|
710
|
-
webinar.selfIsPanelist = true;
|
|
711
|
-
const updatePSDataChannelStub = sinon.stub(webinar, 'updatePSDataChannel').resolves();
|
|
712
|
-
|
|
713
|
-
webinar.updatePracticeSessionStatus({enabled: true});
|
|
714
|
-
|
|
715
|
-
assert.calledOnceWithExactly(updatePSDataChannelStub);
|
|
716
|
-
});
|
|
717
326
|
});
|
|
718
327
|
|
|
719
328
|
describe("#startWebcast", () => {
|
|
@@ -1022,111 +631,5 @@ describe('plugin-meetings', () => {
|
|
|
1022
631
|
}
|
|
1023
632
|
});
|
|
1024
633
|
});
|
|
1025
|
-
|
|
1026
|
-
describe("#searchLargeScaleWebinarAttendees", () => {
|
|
1027
|
-
const attendeeSearchUrl = 'https://locusUrl/attendees/search';
|
|
1028
|
-
const params = {
|
|
1029
|
-
queryString: 'queryString',
|
|
1030
|
-
limit: 50,
|
|
1031
|
-
next: null,
|
|
1032
|
-
};
|
|
1033
|
-
beforeEach(() => {
|
|
1034
|
-
// @ts-ignore
|
|
1035
|
-
webinar.webex.meetings = {
|
|
1036
|
-
getMeetingByType: sinon.stub().returns({
|
|
1037
|
-
id: 'meeting-id',
|
|
1038
|
-
locusInfo: {
|
|
1039
|
-
links:{
|
|
1040
|
-
resources: {
|
|
1041
|
-
attendeeSearch: {
|
|
1042
|
-
url: attendeeSearchUrl
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
})
|
|
1048
|
-
};
|
|
1049
|
-
});
|
|
1050
|
-
|
|
1051
|
-
it('throws an error if attendeeSearchUrl is not available', async () => {
|
|
1052
|
-
webinar.webex.meetings = {
|
|
1053
|
-
getMeetingByType: sinon.stub().returns({
|
|
1054
|
-
id: 'meeting-id',
|
|
1055
|
-
locusInfo: {
|
|
1056
|
-
links:{
|
|
1057
|
-
resources: {
|
|
1058
|
-
attendeeSearch: {
|
|
1059
|
-
url: null
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
})
|
|
1065
|
-
};
|
|
1066
|
-
try {
|
|
1067
|
-
await webinar.searchLargeScaleWebinarAttendees(params);
|
|
1068
|
-
assert.fail('searchLargeScaleWebinarAttendees should throw an error');
|
|
1069
|
-
} catch (error) {
|
|
1070
|
-
assert.equal(error.message,'Meeting:webinar5k#Attendee search url is not available', 'should throw the correct error');
|
|
1071
|
-
}
|
|
1072
|
-
});
|
|
1073
|
-
|
|
1074
|
-
it('sends a GET request to search the large scale webinar attendees', async () => {
|
|
1075
|
-
const result = await webinar.searchLargeScaleWebinarAttendees(params);
|
|
1076
|
-
assert.calledOnce(webex.request);
|
|
1077
|
-
assert.calledWith(webex.request, {
|
|
1078
|
-
method: 'GET',
|
|
1079
|
-
uri: `${attendeeSearchUrl}?search_text=${encodeURIComponent(params.queryString)}&limit=50`,
|
|
1080
|
-
headers: {
|
|
1081
|
-
authorization: 'test-token',
|
|
1082
|
-
trackingId: 'webex-js-sdk_test-uuid',
|
|
1083
|
-
},
|
|
1084
|
-
});
|
|
1085
|
-
assert.equal(
|
|
1086
|
-
result,
|
|
1087
|
-
'REQUEST_RETURN_VALUE',
|
|
1088
|
-
'should return the resolved value from the request'
|
|
1089
|
-
);
|
|
1090
|
-
});
|
|
1091
|
-
|
|
1092
|
-
it('queryString is empty string', async () => {
|
|
1093
|
-
params.queryString = '';
|
|
1094
|
-
const result = await webinar.searchLargeScaleWebinarAttendees(params);
|
|
1095
|
-
assert.calledOnce(webex.request);
|
|
1096
|
-
assert.calledWith(webex.request, {
|
|
1097
|
-
method: 'GET',
|
|
1098
|
-
uri: `${attendeeSearchUrl}?limit=50`,
|
|
1099
|
-
headers: {
|
|
1100
|
-
authorization: 'test-token',
|
|
1101
|
-
trackingId: 'webex-js-sdk_test-uuid',
|
|
1102
|
-
},
|
|
1103
|
-
});
|
|
1104
|
-
assert.equal(
|
|
1105
|
-
result,
|
|
1106
|
-
'REQUEST_RETURN_VALUE',
|
|
1107
|
-
'should return the resolved value from the request'
|
|
1108
|
-
);
|
|
1109
|
-
});
|
|
1110
|
-
|
|
1111
|
-
it('handles API call failures gracefully', async () => {
|
|
1112
|
-
webex.request.rejects(new Error('API_ERROR'));
|
|
1113
|
-
const errorLogger = sinon.stub(LoggerProxy.logger, 'error');
|
|
1114
|
-
|
|
1115
|
-
try {
|
|
1116
|
-
await webinar.searchLargeScaleWebinarAttendees(params);
|
|
1117
|
-
assert.fail('searchLargeScaleWebinarAttendees should throw an error');
|
|
1118
|
-
} catch (error) {
|
|
1119
|
-
assert.equal(error.message, 'API_ERROR', 'should throw the correct error');
|
|
1120
|
-
assert.calledOnce(errorLogger);
|
|
1121
|
-
assert.calledWith(
|
|
1122
|
-
errorLogger,
|
|
1123
|
-
'Meeting:webinar5k#searchLargeScaleWebinarAttendees failed',
|
|
1124
|
-
sinon.match.instanceOf(Error)
|
|
1125
|
-
);
|
|
1126
|
-
} finally {
|
|
1127
|
-
errorLogger.restore();
|
|
1128
|
-
}
|
|
1129
|
-
});
|
|
1130
|
-
});
|
|
1131
634
|
})
|
|
1132
635
|
})
|