@webex/plugin-meetings 3.12.0-next.65 → 3.12.0-next.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aiEnableRequest/index.js +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +24 -4
- package/dist/constants.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interceptors/dataChannelAuthToken.js +75 -15
- package/dist/interceptors/dataChannelAuthToken.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/interpretation.types.js +7 -0
- package/dist/interpretation/interpretation.types.js.map +1 -0
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +738 -679
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +5 -2
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/types/constants.d.ts +7 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/interpretation/interpretation.types.d.ts +10 -0
- package/dist/types/meeting/index.d.ts +2 -2
- package/dist/types/meeting/request.d.ts +1 -0
- package/dist/webinar/index.js +219 -146
- package/dist/webinar/index.js.map +1 -1
- package/package.json +3 -3
- package/src/constants.ts +8 -1
- package/src/index.ts +1 -0
- package/src/interceptors/dataChannelAuthToken.ts +88 -12
- package/src/interpretation/index.ts +2 -1
- package/src/interpretation/interpretation.types.ts +11 -0
- package/src/meeting/index.ts +111 -49
- package/src/meeting/request.ts +11 -0
- package/src/meeting/util.ts +1 -0
- package/src/webinar/index.ts +114 -16
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +196 -0
- package/test/unit/spec/meeting/index.js +139 -23
- package/test/unit/spec/meeting/request.js +12 -0
- package/test/unit/spec/webinar/index.ts +197 -14
|
@@ -7,6 +7,7 @@ import DataChannelAuthTokenInterceptor from '@webex/plugin-meetings/src/intercep
|
|
|
7
7
|
import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
|
|
8
8
|
import * as utils from '@webex/plugin-meetings/src/interceptors/utils';
|
|
9
9
|
import {DATA_CHANNEL_AUTH_HEADER, MAX_RETRY} from '@webex/plugin-meetings/src/interceptors/constant';
|
|
10
|
+
import {LOCUS_URL} from '@webex/plugin-meetings/src/constants';
|
|
10
11
|
|
|
11
12
|
describe('plugin-meetings', () => {
|
|
12
13
|
describe('Interceptors', () => {
|
|
@@ -178,6 +179,28 @@ describe('plugin-meetings', () => {
|
|
|
178
179
|
expect(result).to.equal('mock-response');
|
|
179
180
|
});
|
|
180
181
|
|
|
182
|
+
it('passes request URL to _refreshDataChannelToken', async () => {
|
|
183
|
+
const psOptions = {
|
|
184
|
+
headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'},
|
|
185
|
+
method: 'POST',
|
|
186
|
+
uri: 'https://locus.example.com/practiceSession/datachannel',
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
interceptor._refreshDataChannelToken.resolves('new-token');
|
|
190
|
+
webex.request.resolves('mock-response');
|
|
191
|
+
|
|
192
|
+
const promise = interceptor.refreshTokenAndRetryWithDelay(psOptions);
|
|
193
|
+
|
|
194
|
+
clock.tick(2000);
|
|
195
|
+
|
|
196
|
+
await promise;
|
|
197
|
+
|
|
198
|
+
sinon.assert.calledOnceWithExactly(
|
|
199
|
+
interceptor._refreshDataChannelToken,
|
|
200
|
+
psOptions.uri
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
181
204
|
it('rejects when refreshDataChannelToken fails', async () => {
|
|
182
205
|
interceptor._refreshDataChannelToken.rejects(new Error('refresh failed'));
|
|
183
206
|
|
|
@@ -205,6 +228,179 @@ describe('plugin-meetings', () => {
|
|
|
205
228
|
);
|
|
206
229
|
});
|
|
207
230
|
});
|
|
231
|
+
|
|
232
|
+
describe('refreshDataChannelToken routing (factory dispatcher)', () => {
|
|
233
|
+
let llmMock;
|
|
234
|
+
let meetingA;
|
|
235
|
+
let meetingsMock;
|
|
236
|
+
let dispatcherInterceptor;
|
|
237
|
+
|
|
238
|
+
const PS_DATACHANNEL_URL = 'https://board-a.wbx2.com/datachannel/api/v1/locus/cHJhY3RpY2Vfc2Vzc2lvbl9sb2N1cw==/registrations';
|
|
239
|
+
const DEFAULT_DATACHANNEL_URL = 'https://board-a.wbx2.com/datachannel/api/v1/locus/aHR0cHM6Ly9sb2N1cy1hLndieDIuY29t/registrations';
|
|
240
|
+
|
|
241
|
+
beforeEach(() => {
|
|
242
|
+
meetingA = {
|
|
243
|
+
id: 'meeting-a',
|
|
244
|
+
refreshDataChannelToken: sinon.stub().resolves({
|
|
245
|
+
body: {
|
|
246
|
+
datachannelToken: 'token-from-meeting-a',
|
|
247
|
+
dataChannelTokenType: 'llm-practice-session',
|
|
248
|
+
},
|
|
249
|
+
}),
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
llmMock = {
|
|
253
|
+
isDataChannelTokenEnabled: sinon.stub().resolves(true),
|
|
254
|
+
getSessionIdByDatachannelUrl: sinon.stub(),
|
|
255
|
+
getLocusUrlByDatachannelUrl: sinon.stub(),
|
|
256
|
+
getOwnerMeetingId: sinon.stub().returns(undefined),
|
|
257
|
+
refreshDataChannelToken: sinon.stub().resolves({
|
|
258
|
+
body: {
|
|
259
|
+
datachannelToken: 'token-from-llm-fallback',
|
|
260
|
+
dataChannelTokenType: 'llm-default-session',
|
|
261
|
+
},
|
|
262
|
+
}),
|
|
263
|
+
setDatachannelToken: sinon.stub(),
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
meetingsMock = {
|
|
267
|
+
getMeetingByType: sinon.stub(),
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const context = {
|
|
271
|
+
internal: {llm: llmMock},
|
|
272
|
+
meetings: meetingsMock,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
dispatcherInterceptor = Reflect.apply(DataChannelAuthTokenInterceptor.create, context, []);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('routes PS request URL to PS session handler', async () => {
|
|
279
|
+
llmMock.getSessionIdByDatachannelUrl.withArgs(PS_DATACHANNEL_URL).returns('llm-practice-session');
|
|
280
|
+
llmMock.refreshDataChannelToken
|
|
281
|
+
.withArgs('llm-practice-session')
|
|
282
|
+
.resolves({
|
|
283
|
+
body: {
|
|
284
|
+
datachannelToken: 'token-from-ps-session',
|
|
285
|
+
dataChannelTokenType: 'llm-practice-session',
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const token = await dispatcherInterceptor._refreshDataChannelToken(PS_DATACHANNEL_URL);
|
|
290
|
+
|
|
291
|
+
expect(token).to.equal('token-from-ps-session');
|
|
292
|
+
sinon.assert.calledOnceWithExactly(llmMock.refreshDataChannelToken, 'llm-practice-session');
|
|
293
|
+
sinon.assert.calledOnceWithExactly(
|
|
294
|
+
llmMock.setDatachannelToken,
|
|
295
|
+
'token-from-ps-session',
|
|
296
|
+
'llm-practice-session',
|
|
297
|
+
undefined
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('routes non-PS URL to default session handler', async () => {
|
|
302
|
+
llmMock.getSessionIdByDatachannelUrl.withArgs(DEFAULT_DATACHANNEL_URL).returns('llm-default-session');
|
|
303
|
+
llmMock.refreshDataChannelToken
|
|
304
|
+
.withArgs('llm-default-session')
|
|
305
|
+
.resolves({
|
|
306
|
+
body: {
|
|
307
|
+
datachannelToken: 'token-from-default-session',
|
|
308
|
+
dataChannelTokenType: 'llm-default-session',
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const token = await dispatcherInterceptor._refreshDataChannelToken(DEFAULT_DATACHANNEL_URL);
|
|
313
|
+
|
|
314
|
+
expect(token).to.equal('token-from-default-session');
|
|
315
|
+
sinon.assert.calledOnceWithExactly(llmMock.refreshDataChannelToken, 'llm-default-session');
|
|
316
|
+
sinon.assert.calledOnceWithExactly(
|
|
317
|
+
llmMock.setDatachannelToken,
|
|
318
|
+
'token-from-default-session',
|
|
319
|
+
'llm-default-session',
|
|
320
|
+
undefined
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('falls back to default refresh when URL does not match any session or meeting route', async () => {
|
|
325
|
+
llmMock.getSessionIdByDatachannelUrl.withArgs(PS_DATACHANNEL_URL).returns(undefined);
|
|
326
|
+
llmMock.getLocusUrlByDatachannelUrl.withArgs(PS_DATACHANNEL_URL).returns(undefined);
|
|
327
|
+
llmMock.refreshDataChannelToken.withArgs(undefined).resolves({
|
|
328
|
+
body: {
|
|
329
|
+
datachannelToken: 'token-from-default-fallback',
|
|
330
|
+
dataChannelTokenType: 'llm-default-session',
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const token = await dispatcherInterceptor._refreshDataChannelToken(PS_DATACHANNEL_URL);
|
|
335
|
+
|
|
336
|
+
expect(token).to.equal('token-from-default-fallback');
|
|
337
|
+
sinon.assert.calledOnceWithExactly(llmMock.refreshDataChannelToken, undefined);
|
|
338
|
+
sinon.assert.calledOnceWithExactly(
|
|
339
|
+
llmMock.setDatachannelToken,
|
|
340
|
+
'token-from-default-fallback',
|
|
341
|
+
'llm-default-session',
|
|
342
|
+
undefined
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('falls back to meeting lookup by locusUrl when session cannot be resolved', async () => {
|
|
347
|
+
llmMock.getSessionIdByDatachannelUrl.withArgs(PS_DATACHANNEL_URL).returns(undefined);
|
|
348
|
+
llmMock.getLocusUrlByDatachannelUrl.withArgs(PS_DATACHANNEL_URL).returns('https://locus-a.example.com');
|
|
349
|
+
meetingsMock.getMeetingByType.withArgs(LOCUS_URL, 'https://locus-a.example.com').returns(meetingA);
|
|
350
|
+
|
|
351
|
+
const token = await dispatcherInterceptor._refreshDataChannelToken(PS_DATACHANNEL_URL);
|
|
352
|
+
|
|
353
|
+
expect(token).to.equal('token-from-meeting-a');
|
|
354
|
+
sinon.assert.calledOnceWithExactly(meetingA.refreshDataChannelToken);
|
|
355
|
+
sinon.assert.notCalled(llmMock.refreshDataChannelToken);
|
|
356
|
+
sinon.assert.calledOnceWithExactly(
|
|
357
|
+
llmMock.setDatachannelToken,
|
|
358
|
+
'token-from-meeting-a',
|
|
359
|
+
'llm-practice-session',
|
|
360
|
+
'meeting-a'
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('falls back to active meeting datachannel URL lookup when session/locus routing is unavailable', async () => {
|
|
365
|
+
llmMock.getSessionIdByDatachannelUrl.withArgs(PS_DATACHANNEL_URL).returns(undefined);
|
|
366
|
+
llmMock.getLocusUrlByDatachannelUrl.withArgs(PS_DATACHANNEL_URL).returns(undefined);
|
|
367
|
+
meetingsMock.getAllMeetings = sinon.stub().returns({
|
|
368
|
+
'meeting-a': {
|
|
369
|
+
...meetingA,
|
|
370
|
+
locusInfo: {
|
|
371
|
+
info: {
|
|
372
|
+
practiceSessionDatachannelUrl: PS_DATACHANNEL_URL,
|
|
373
|
+
datachannelUrl: DEFAULT_DATACHANNEL_URL,
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const token = await dispatcherInterceptor._refreshDataChannelToken(PS_DATACHANNEL_URL);
|
|
380
|
+
|
|
381
|
+
expect(token).to.equal('token-from-meeting-a');
|
|
382
|
+
sinon.assert.calledOnceWithExactly(meetingA.refreshDataChannelToken);
|
|
383
|
+
sinon.assert.notCalled(llmMock.refreshDataChannelToken);
|
|
384
|
+
sinon.assert.calledOnceWithExactly(
|
|
385
|
+
llmMock.setDatachannelToken,
|
|
386
|
+
'token-from-meeting-a',
|
|
387
|
+
'llm-practice-session',
|
|
388
|
+
'meeting-a'
|
|
389
|
+
);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('throws when refresh returns no payload', async () => {
|
|
393
|
+
llmMock.getSessionIdByDatachannelUrl.returns('llm-default-session');
|
|
394
|
+
llmMock.refreshDataChannelToken.withArgs('llm-default-session').resolves(null);
|
|
395
|
+
|
|
396
|
+
await assert.isRejected(
|
|
397
|
+
dispatcherInterceptor._refreshDataChannelToken(
|
|
398
|
+
'https://unknown-datachannel.example.com/registrations'
|
|
399
|
+
),
|
|
400
|
+
/DataChannel token refresh returned no payload/
|
|
401
|
+
);
|
|
402
|
+
});
|
|
403
|
+
});
|
|
208
404
|
});
|
|
209
405
|
});
|
|
210
406
|
});
|
|
@@ -269,6 +269,20 @@ describe('plugin-meetings', () => {
|
|
|
269
269
|
stopReachability: sinon.stub(),
|
|
270
270
|
isSubnetReachable: sinon.stub().returns(true),
|
|
271
271
|
};
|
|
272
|
+
webex.internal.llm.resolveSessionOwnership = sinon
|
|
273
|
+
.stub()
|
|
274
|
+
.callsFake((ownerMeetingId, sessionId) => {
|
|
275
|
+
const currentOwner = webex.internal.llm.getOwnerMeetingId
|
|
276
|
+
? webex.internal.llm.getOwnerMeetingId(sessionId)
|
|
277
|
+
: undefined;
|
|
278
|
+
const canAssertOwnership = !!ownerMeetingId;
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
currentOwner,
|
|
282
|
+
canAssertOwnership,
|
|
283
|
+
isOwner: !currentOwner || !canAssertOwnership || currentOwner === ownerMeetingId,
|
|
284
|
+
};
|
|
285
|
+
});
|
|
272
286
|
webex.internal.llm.isDataChannelTokenEnabled = sinon.stub().resolves(false);
|
|
273
287
|
webex.internal.llm.on = sinon.stub();
|
|
274
288
|
webex.internal.voicea.announce = sinon.stub();
|
|
@@ -11107,7 +11121,7 @@ describe('plugin-meetings', () => {
|
|
|
11107
11121
|
);
|
|
11108
11122
|
done();
|
|
11109
11123
|
});
|
|
11110
|
-
it('listens to the self admitted guest event
|
|
11124
|
+
it('listens to the self admitted guest event and waits for token prefetch before reconnecting LLM', async () => {
|
|
11111
11125
|
meeting.stopKeepAlive = sinon.stub();
|
|
11112
11126
|
meeting.updateLLMConnection = sinon.stub();
|
|
11113
11127
|
let resolvePrefetch;
|
|
@@ -11133,7 +11147,7 @@ describe('plugin-meetings', () => {
|
|
|
11133
11147
|
'meeting:self:guestAdmitted',
|
|
11134
11148
|
{payload: test1}
|
|
11135
11149
|
);
|
|
11136
|
-
assert.
|
|
11150
|
+
assert.notCalled(meeting.updateLLMConnection);
|
|
11137
11151
|
assert.calledOnceWithExactly(meeting.rtcMetrics.sendNextMetrics);
|
|
11138
11152
|
|
|
11139
11153
|
assert.calledOnceWithExactly(
|
|
@@ -11146,6 +11160,7 @@ describe('plugin-meetings', () => {
|
|
|
11146
11160
|
|
|
11147
11161
|
resolvePrefetch(false);
|
|
11148
11162
|
await Promise.resolve();
|
|
11163
|
+
await Promise.resolve();
|
|
11149
11164
|
|
|
11150
11165
|
assert.calledOnce(meeting.updateLLMConnection);
|
|
11151
11166
|
});
|
|
@@ -13670,6 +13685,10 @@ describe('plugin-meetings', () => {
|
|
|
13670
13685
|
describe('#saveDataChannelToken', () => {
|
|
13671
13686
|
beforeEach(() => {
|
|
13672
13687
|
webex.internal.llm.setDatachannelToken = sinon.stub();
|
|
13688
|
+
webex.internal.llm.resolveSessionOwnership = sinon
|
|
13689
|
+
.stub()
|
|
13690
|
+
.returns({currentOwner: undefined, isOwner: true});
|
|
13691
|
+
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
13673
13692
|
});
|
|
13674
13693
|
|
|
13675
13694
|
it('saves datachannelToken into LLM as Default', () => {
|
|
@@ -13682,7 +13701,8 @@ describe('plugin-meetings', () => {
|
|
|
13682
13701
|
assert.calledWithExactly(
|
|
13683
13702
|
webex.internal.llm.setDatachannelToken,
|
|
13684
13703
|
'default-token',
|
|
13685
|
-
'llm-default-session'
|
|
13704
|
+
'llm-default-session',
|
|
13705
|
+
meeting.id
|
|
13686
13706
|
);
|
|
13687
13707
|
});
|
|
13688
13708
|
|
|
@@ -13696,7 +13716,8 @@ describe('plugin-meetings', () => {
|
|
|
13696
13716
|
assert.calledWithExactly(
|
|
13697
13717
|
webex.internal.llm.setDatachannelToken,
|
|
13698
13718
|
'ps-token',
|
|
13699
|
-
'llm-practice-session'
|
|
13719
|
+
'llm-practice-session',
|
|
13720
|
+
meeting.id
|
|
13700
13721
|
);
|
|
13701
13722
|
});
|
|
13702
13723
|
|
|
@@ -13714,12 +13735,14 @@ describe('plugin-meetings', () => {
|
|
|
13714
13735
|
assert.calledWithExactly(
|
|
13715
13736
|
webex.internal.llm.setDatachannelToken,
|
|
13716
13737
|
'default-token',
|
|
13717
|
-
'llm-default-session'
|
|
13738
|
+
'llm-default-session',
|
|
13739
|
+
meeting.id
|
|
13718
13740
|
);
|
|
13719
13741
|
assert.calledWithExactly(
|
|
13720
13742
|
webex.internal.llm.setDatachannelToken,
|
|
13721
13743
|
'ps-token',
|
|
13722
|
-
'llm-practice-session'
|
|
13744
|
+
'llm-practice-session',
|
|
13745
|
+
meeting.id
|
|
13723
13746
|
);
|
|
13724
13747
|
});
|
|
13725
13748
|
|
|
@@ -13740,17 +13763,42 @@ describe('plugin-meetings', () => {
|
|
|
13740
13763
|
|
|
13741
13764
|
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
13742
13765
|
});
|
|
13766
|
+
|
|
13767
|
+
it('writes token with meeting id as owner', () => {
|
|
13768
|
+
meeting.saveDataChannelToken({
|
|
13769
|
+
locus: {
|
|
13770
|
+
self: {datachannelToken: 'default-token'},
|
|
13771
|
+
},
|
|
13772
|
+
});
|
|
13773
|
+
|
|
13774
|
+
assert.calledOnceWithExactly(
|
|
13775
|
+
webex.internal.llm.setDatachannelToken,
|
|
13776
|
+
'default-token',
|
|
13777
|
+
'llm-default-session',
|
|
13778
|
+
meeting.id
|
|
13779
|
+
);
|
|
13780
|
+
});
|
|
13743
13781
|
});
|
|
13744
13782
|
|
|
13745
13783
|
describe('#clearDataChannelToken', () => {
|
|
13746
13784
|
beforeEach(() => {
|
|
13747
|
-
webex.internal.llm.
|
|
13785
|
+
webex.internal.llm.clearDatachannelToken = sinon.stub();
|
|
13748
13786
|
});
|
|
13749
13787
|
|
|
13750
|
-
it('
|
|
13788
|
+
it('delegates default and practice token clears to llm with meeting ownership id', () => {
|
|
13751
13789
|
meeting.clearDataChannelToken();
|
|
13752
13790
|
|
|
13753
|
-
assert.
|
|
13791
|
+
assert.calledWithExactly(
|
|
13792
|
+
webex.internal.llm.clearDatachannelToken,
|
|
13793
|
+
'llm-default-session',
|
|
13794
|
+
meeting.id
|
|
13795
|
+
);
|
|
13796
|
+
assert.calledWithExactly(
|
|
13797
|
+
webex.internal.llm.clearDatachannelToken,
|
|
13798
|
+
'llm-practice-session',
|
|
13799
|
+
meeting.id
|
|
13800
|
+
);
|
|
13801
|
+
assert.callCount(webex.internal.llm.clearDatachannelToken, 2);
|
|
13754
13802
|
});
|
|
13755
13803
|
});
|
|
13756
13804
|
|
|
@@ -13760,6 +13808,7 @@ describe('plugin-meetings', () => {
|
|
|
13760
13808
|
webex.internal.llm.getLocusUrl = sinon.stub();
|
|
13761
13809
|
webex.internal.llm.getDatachannelUrl = sinon.stub();
|
|
13762
13810
|
webex.internal.llm.registerAndConnect = sinon.stub().resolves('something');
|
|
13811
|
+
webex.internal.llm.setRefreshHandler = sinon.stub();
|
|
13763
13812
|
webex.internal.llm.disconnectLLM = sinon.stub().resolves();
|
|
13764
13813
|
webex.internal.llm.on = sinon.stub();
|
|
13765
13814
|
webex.internal.llm.off = sinon.stub();
|
|
@@ -13837,6 +13886,12 @@ describe('plugin-meetings', () => {
|
|
|
13837
13886
|
'a datachannel url',
|
|
13838
13887
|
undefined
|
|
13839
13888
|
);
|
|
13889
|
+
assert.calledOnceWithExactly(
|
|
13890
|
+
webex.internal.llm.setRefreshHandler,
|
|
13891
|
+
sinon.match.func,
|
|
13892
|
+
'llm-default-session',
|
|
13893
|
+
meeting.id
|
|
13894
|
+
);
|
|
13840
13895
|
assert.equal(result, 'something');
|
|
13841
13896
|
assert.calledOnceWithExactly(meeting.locusInfo.syncAllHashTreeDatasets, {onlyLLM: true});
|
|
13842
13897
|
});
|
|
@@ -13858,7 +13913,7 @@ describe('plugin-meetings', () => {
|
|
|
13858
13913
|
assert.calledWithExactly(webex.internal.llm.disconnectLLM, {
|
|
13859
13914
|
code: 3050,
|
|
13860
13915
|
reason: 'done (permanent)',
|
|
13861
|
-
});
|
|
13916
|
+
}, 'llm-default-session', meeting.id);
|
|
13862
13917
|
|
|
13863
13918
|
assert.calledWithExactly(
|
|
13864
13919
|
webex.internal.llm.registerAndConnect,
|
|
@@ -13912,7 +13967,7 @@ describe('plugin-meetings', () => {
|
|
|
13912
13967
|
assert.calledWithExactly(webex.internal.llm.disconnectLLM, {
|
|
13913
13968
|
code: 3050,
|
|
13914
13969
|
reason: 'done (permanent)',
|
|
13915
|
-
});
|
|
13970
|
+
}, 'llm-default-session', meeting.id);
|
|
13916
13971
|
|
|
13917
13972
|
assert.calledWithExactly(
|
|
13918
13973
|
webex.internal.llm.registerAndConnect,
|
|
@@ -13960,7 +14015,7 @@ describe('plugin-meetings', () => {
|
|
|
13960
14015
|
assert.calledWith(webex.internal.llm.disconnectLLM, {
|
|
13961
14016
|
code: 3050,
|
|
13962
14017
|
reason: 'done (permanent)',
|
|
13963
|
-
});
|
|
14018
|
+
}, 'llm-default-session', meeting.id);
|
|
13964
14019
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
13965
14020
|
assert.equal(result, undefined);
|
|
13966
14021
|
assert.isFalse(
|
|
@@ -14035,7 +14090,7 @@ describe('plugin-meetings', () => {
|
|
|
14035
14090
|
};
|
|
14036
14091
|
|
|
14037
14092
|
webex.internal.llm.getDatachannelToken
|
|
14038
|
-
.withArgs('llm-default-session')
|
|
14093
|
+
.withArgs('llm-default-session', meeting.id)
|
|
14039
14094
|
.returns('token-123');
|
|
14040
14095
|
|
|
14041
14096
|
await meeting.updateLLMConnection();
|
|
@@ -14157,6 +14212,33 @@ describe('plugin-meetings', () => {
|
|
|
14157
14212
|
assert.calledWith(webex.internal.llm.setOwnerMeetingId, undefined);
|
|
14158
14213
|
});
|
|
14159
14214
|
|
|
14215
|
+
it('does not clear owner tag when ownership changes during cleanup disconnect await', async () => {
|
|
14216
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
14217
|
+
webex.internal.llm.isConnected.returns(true);
|
|
14218
|
+
webex.internal.llm.getOwnerMeetingId.returns(meeting.id);
|
|
14219
|
+
webex.internal.llm.getLocusUrl.returns('a url');
|
|
14220
|
+
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
14221
|
+
webex.internal.llm.disconnectLLM.callsFake(async () => {
|
|
14222
|
+
webex.internal.llm.getOwnerMeetingId.returns('new-owner-id');
|
|
14223
|
+
throw new Error('disconnect failed');
|
|
14224
|
+
});
|
|
14225
|
+
meeting.locusInfo = {
|
|
14226
|
+
syncAllHashTreeDatasets: sinon.stub().resolves(),
|
|
14227
|
+
url: 'a different url',
|
|
14228
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
14229
|
+
self: {},
|
|
14230
|
+
};
|
|
14231
|
+
|
|
14232
|
+
try {
|
|
14233
|
+
await meeting.updateLLMConnection();
|
|
14234
|
+
} catch (e) {
|
|
14235
|
+
/* updateLLMConnection may reject when cleanup throws */
|
|
14236
|
+
}
|
|
14237
|
+
|
|
14238
|
+
assert.notCalled(webex.internal.llm.setOwnerMeetingId);
|
|
14239
|
+
assert.equal(webex.internal.llm.getOwnerMeetingId(), 'new-owner-id');
|
|
14240
|
+
});
|
|
14241
|
+
|
|
14160
14242
|
it('proceeds normally when LLM is connected and owned by this meeting with URL change', async () => {
|
|
14161
14243
|
meeting.joinedWith = {state: 'JOINED'};
|
|
14162
14244
|
webex.internal.llm.isConnected.returns(true);
|
|
@@ -14175,7 +14257,7 @@ describe('plugin-meetings', () => {
|
|
|
14175
14257
|
assert.calledOnceWithExactly(webex.internal.llm.disconnectLLM, {
|
|
14176
14258
|
code: 3050,
|
|
14177
14259
|
reason: 'done (permanent)',
|
|
14178
|
-
});
|
|
14260
|
+
}, 'llm-default-session', meeting.id);
|
|
14179
14261
|
assert.calledWithExactly(
|
|
14180
14262
|
webex.internal.llm.registerAndConnect,
|
|
14181
14263
|
'a different url',
|
|
@@ -14200,6 +14282,12 @@ describe('plugin-meetings', () => {
|
|
|
14200
14282
|
await meeting.updateLLMConnection();
|
|
14201
14283
|
|
|
14202
14284
|
assert.calledOnce(webex.internal.llm.registerAndConnect);
|
|
14285
|
+
assert.calledOnceWithExactly(
|
|
14286
|
+
webex.internal.llm.setRefreshHandler,
|
|
14287
|
+
sinon.match.func,
|
|
14288
|
+
'llm-default-session',
|
|
14289
|
+
meeting.id
|
|
14290
|
+
);
|
|
14203
14291
|
assert.calledOnceWithExactly(webex.internal.llm.setOwnerMeetingId, meeting.id);
|
|
14204
14292
|
});
|
|
14205
14293
|
|
|
@@ -14211,11 +14299,41 @@ describe('plugin-meetings', () => {
|
|
|
14211
14299
|
meeting.joinedWith = {state: 'JOINED'};
|
|
14212
14300
|
webex.internal.llm.isConnected.returns(false);
|
|
14213
14301
|
webex.internal.llm.getOwnerMeetingId.returns('stale-owner-id');
|
|
14302
|
+
webex.internal.llm.getDatachannelToken.onFirstCall().returns(undefined);
|
|
14303
|
+
webex.internal.llm.getDatachannelToken.onSecondCall().returns('recovered-token');
|
|
14214
14304
|
meeting.locusInfo = {syncAllHashTreeDatasets: sinon.stub().resolves(), url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
14215
14305
|
|
|
14216
14306
|
await meeting.updateLLMConnection();
|
|
14217
14307
|
|
|
14218
|
-
assert.
|
|
14308
|
+
assert.calledTwice(webex.internal.llm.getDatachannelToken);
|
|
14309
|
+
assert.calledWithExactly(
|
|
14310
|
+
webex.internal.llm.getDatachannelToken.firstCall,
|
|
14311
|
+
'llm-default-session',
|
|
14312
|
+
meeting.id
|
|
14313
|
+
);
|
|
14314
|
+
assert.calledWithExactly(
|
|
14315
|
+
webex.internal.llm.getDatachannelToken.secondCall,
|
|
14316
|
+
'llm-default-session'
|
|
14317
|
+
);
|
|
14318
|
+
assert.calledOnceWithExactly(
|
|
14319
|
+
webex.internal.llm.registerAndConnect,
|
|
14320
|
+
'a url',
|
|
14321
|
+
'a datachannel url',
|
|
14322
|
+
'recovered-token'
|
|
14323
|
+
);
|
|
14324
|
+
assert.calledWithExactly(
|
|
14325
|
+
webex.internal.llm.setRefreshHandler.firstCall,
|
|
14326
|
+
sinon.match.func,
|
|
14327
|
+
'llm-default-session',
|
|
14328
|
+
undefined
|
|
14329
|
+
);
|
|
14330
|
+
assert.calledTwice(webex.internal.llm.setRefreshHandler);
|
|
14331
|
+
assert.calledWithExactly(
|
|
14332
|
+
webex.internal.llm.setRefreshHandler.secondCall,
|
|
14333
|
+
sinon.match.func,
|
|
14334
|
+
'llm-default-session',
|
|
14335
|
+
meeting.id
|
|
14336
|
+
);
|
|
14219
14337
|
assert.calledOnceWithExactly(webex.internal.llm.setOwnerMeetingId, meeting.id);
|
|
14220
14338
|
});
|
|
14221
14339
|
});
|
|
@@ -14238,7 +14356,7 @@ describe('plugin-meetings', () => {
|
|
|
14238
14356
|
assert.calledOnceWithExactly(webex.internal.llm.disconnectLLM, {
|
|
14239
14357
|
code: 3050,
|
|
14240
14358
|
reason: 'done (permanent)',
|
|
14241
|
-
});
|
|
14359
|
+
}, 'llm-default-session', meeting.id);
|
|
14242
14360
|
assert.calledWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
14243
14361
|
assert.calledWithExactly(
|
|
14244
14362
|
webex.internal.llm.off,
|
|
@@ -14292,11 +14410,9 @@ describe('plugin-meetings', () => {
|
|
|
14292
14410
|
await meeting.clearMeetingData();
|
|
14293
14411
|
|
|
14294
14412
|
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
14295
|
-
//
|
|
14296
|
-
//
|
|
14297
|
-
|
|
14298
|
-
// its Data-Channel-Auth-Token.
|
|
14299
|
-
assert.notCalled(meeting.clearDataChannelToken);
|
|
14413
|
+
// clearDataChannelToken is always delegated; llm enforces
|
|
14414
|
+
// ownership and no-ops for non-owners internally.
|
|
14415
|
+
assert.calledOnce(meeting.clearDataChannelToken);
|
|
14300
14416
|
// Listeners owned by *this* Meeting instance must still be
|
|
14301
14417
|
// removed so a leaving subordinate meeting stops receiving
|
|
14302
14418
|
// relay/locus events from the shared singleton.
|
|
@@ -14322,7 +14438,7 @@ describe('plugin-meetings', () => {
|
|
|
14322
14438
|
assert.calledOnceWithExactly(webex.internal.llm.disconnectLLM, {
|
|
14323
14439
|
code: 3050,
|
|
14324
14440
|
reason: 'done (permanent)',
|
|
14325
|
-
});
|
|
14441
|
+
}, 'llm-default-session', meeting.id);
|
|
14326
14442
|
assert.calledOnce(meeting.clearDataChannelToken);
|
|
14327
14443
|
});
|
|
14328
14444
|
|
|
@@ -14334,7 +14450,7 @@ describe('plugin-meetings', () => {
|
|
|
14334
14450
|
assert.calledOnceWithExactly(webex.internal.llm.disconnectLLM, {
|
|
14335
14451
|
code: 3050,
|
|
14336
14452
|
reason: 'done (permanent)',
|
|
14337
|
-
});
|
|
14453
|
+
}, 'llm-default-session', meeting.id);
|
|
14338
14454
|
assert.calledOnce(meeting.clearDataChannelToken);
|
|
14339
14455
|
});
|
|
14340
14456
|
});
|
|
@@ -347,6 +347,18 @@ describe('plugin-meetings', () => {
|
|
|
347
347
|
'ANNOTATION_ON_SHARE_SUPPORTED',
|
|
348
348
|
]);
|
|
349
349
|
});
|
|
350
|
+
it('adds deviceCapabilities to request when simultaneous interpretation is enabled', async () => {
|
|
351
|
+
await meetingsRequest.joinMeeting({
|
|
352
|
+
enableSimultaneousInterpretation: true,
|
|
353
|
+
});
|
|
354
|
+
const requestParams = meetingsRequest.request.getCall(0).args[0];
|
|
355
|
+
assert.deepEqual(requestParams.body.deviceCapabilities, [
|
|
356
|
+
'HOST_CONTROL_SI_SUPPORTED',
|
|
357
|
+
'INTERPRETER_CONTROL_SI_SUPPORTED',
|
|
358
|
+
'SI_HANDOVER_SUPPORTED',
|
|
359
|
+
'SIGN_INTERPRETER_SUPPORTED',
|
|
360
|
+
]);
|
|
361
|
+
});
|
|
350
362
|
it('does not add deviceCapabilities to request when breakouts and live annotation are not supported', async () => {
|
|
351
363
|
await meetingsRequest.joinMeeting({});
|
|
352
364
|
|