@webex/plugin-meetings 3.11.0-next.46 → 3.11.0-next.48
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/interceptors/dataChannelAuthToken.js +103 -46
- package/dist/interceptors/dataChannelAuthToken.js.map +1 -1
- package/dist/interceptors/utils.js +27 -0
- package/dist/interceptors/utils.js.map +1 -0
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +4 -6
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +2 -2
- package/dist/meeting/request.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +9 -60
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/reconnection-manager/index.js +0 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/types/interceptors/dataChannelAuthToken.d.ts +8 -0
- package/dist/types/interceptors/utils.d.ts +1 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
- package/dist/webinar/index.js +8 -2
- package/dist/webinar/index.js.map +1 -1
- package/package.json +4 -3
- package/src/interceptors/dataChannelAuthToken.ts +28 -0
- package/src/interceptors/utils.ts +16 -0
- package/src/meeting/index.ts +5 -5
- package/src/meeting/request.ts +4 -4
- package/src/multistream/mediaRequestManager.ts +3 -53
- package/src/reconnection-manager/index.ts +0 -1
- package/src/webinar/index.ts +5 -0
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +69 -0
- package/test/unit/spec/interceptors/utils.ts +75 -0
- package/test/unit/spec/meeting/index.js +6 -6
- package/test/unit/spec/meeting/request.js +18 -12
- package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
- package/test/unit/spec/reconnection-manager/index.js +4 -8
- package/test/unit/spec/webinar/index.ts +18 -0
package/src/meeting/request.ts
CHANGED
|
@@ -1159,13 +1159,13 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
1159
1159
|
method: HTTP_VERBS.GET,
|
|
1160
1160
|
uri,
|
|
1161
1161
|
}).catch((err) => {
|
|
1162
|
-
LoggerProxy.logger.
|
|
1163
|
-
`Meeting:request#fetchDatachannelToken -->
|
|
1162
|
+
LoggerProxy.logger.warn(
|
|
1163
|
+
`Meeting:request#fetchDatachannelToken --> Failed to retrieve ${
|
|
1164
1164
|
isPracticeSession ? 'practice session ' : ''
|
|
1165
|
-
}datachannel token
|
|
1165
|
+
}datachannel token: ${err?.message || err}`
|
|
1166
1166
|
);
|
|
1167
1167
|
|
|
1168
|
-
|
|
1168
|
+
return null;
|
|
1169
1169
|
});
|
|
1170
1170
|
}
|
|
1171
1171
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
RecommendedOpusBitrates,
|
|
11
11
|
NamedMediaGroup,
|
|
12
12
|
} from '@webex/internal-media-core';
|
|
13
|
-
import {cloneDeepWith, debounce
|
|
13
|
+
import {cloneDeepWith, debounce} from 'lodash';
|
|
14
14
|
|
|
15
15
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
16
16
|
|
|
@@ -94,8 +94,6 @@ export class MediaRequestManager {
|
|
|
94
94
|
|
|
95
95
|
private debouncedSourceUpdateListener: () => void;
|
|
96
96
|
|
|
97
|
-
private previousStreamRequests: Array<StreamRequest> = [];
|
|
98
|
-
|
|
99
97
|
private trimRequestsToNumOfSources: boolean;
|
|
100
98
|
private numTotalSources: number;
|
|
101
99
|
private numLiveSources: number;
|
|
@@ -161,36 +159,6 @@ export class MediaRequestManager {
|
|
|
161
159
|
}
|
|
162
160
|
}
|
|
163
161
|
|
|
164
|
-
/**
|
|
165
|
-
* Returns true if two stream requests are the same, false otherwise.
|
|
166
|
-
*
|
|
167
|
-
* @param {StreamRequest} streamRequestA - Stream request A for comparison.
|
|
168
|
-
* @param {StreamRequest} streamRequestB - Stream request B for comparison.
|
|
169
|
-
* @returns {boolean} - Whether they are equal.
|
|
170
|
-
*/
|
|
171
|
-
// eslint-disable-next-line class-methods-use-this
|
|
172
|
-
public isEqual(streamRequestA: StreamRequest, streamRequestB: StreamRequest) {
|
|
173
|
-
return (
|
|
174
|
-
JSON.stringify(streamRequestA._toJmpStreamRequest()) ===
|
|
175
|
-
JSON.stringify(streamRequestB._toJmpStreamRequest())
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Compares new stream requests to previous ones and determines
|
|
181
|
-
* if they are the same.
|
|
182
|
-
*
|
|
183
|
-
* @param {StreamRequest[]} newRequests - Array with new requests.
|
|
184
|
-
* @returns {boolean} - True if they are equal, false otherwise.
|
|
185
|
-
*/
|
|
186
|
-
private checkIsNewRequestsEqualToPrev(newRequests: StreamRequest[]) {
|
|
187
|
-
return (
|
|
188
|
-
!isEmpty(this.previousStreamRequests) &&
|
|
189
|
-
this.previousStreamRequests.length === newRequests.length &&
|
|
190
|
-
this.previousStreamRequests.every((req, idx) => this.isEqual(req, newRequests[idx]))
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
162
|
/**
|
|
195
163
|
* Returns the maxPayloadBitsPerSecond per Stream
|
|
196
164
|
*
|
|
@@ -230,15 +198,6 @@ export class MediaRequestManager {
|
|
|
230
198
|
return (mediaRequest.codecInfo.maxFs * maxFps) / 100;
|
|
231
199
|
}
|
|
232
200
|
|
|
233
|
-
/**
|
|
234
|
-
* Clears the previous stream requests.
|
|
235
|
-
*
|
|
236
|
-
* @returns {void}
|
|
237
|
-
*/
|
|
238
|
-
public clearPreviousRequests(): void {
|
|
239
|
-
this.previousStreamRequests = [];
|
|
240
|
-
}
|
|
241
|
-
|
|
242
201
|
/** Modifies the passed in clientRequests and makes sure that in total they don't ask
|
|
243
202
|
* for more streams than there are available.
|
|
244
203
|
*
|
|
@@ -372,17 +331,8 @@ export class MediaRequestManager {
|
|
|
372
331
|
}
|
|
373
332
|
});
|
|
374
333
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (!this.checkIsNewRequestsEqualToPrev(streamRequests)) {
|
|
378
|
-
this.sendMediaRequestsCallback(streamRequests);
|
|
379
|
-
this.previousStreamRequests = streamRequests;
|
|
380
|
-
LoggerProxy.logger.info(`multistream:sendRequests --> media requests sent. `);
|
|
381
|
-
} else {
|
|
382
|
-
LoggerProxy.logger.info(
|
|
383
|
-
`multistream:sendRequests --> detected duplicate WCME requests, skipping them... `
|
|
384
|
-
);
|
|
385
|
-
}
|
|
334
|
+
this.sendMediaRequestsCallback(streamRequests);
|
|
335
|
+
LoggerProxy.logger.info(`multistream:sendRequests --> media requests sent. `);
|
|
386
336
|
}
|
|
387
337
|
|
|
388
338
|
public addRequest(mediaRequest: MediaRequest, commit = true): MediaRequestId {
|
|
@@ -609,7 +609,6 @@ export default class ReconnectionManager {
|
|
|
609
609
|
if (this.meeting.isMultistream) {
|
|
610
610
|
Object.values(this.meeting.mediaRequestManagers).forEach(
|
|
611
611
|
(mediaRequestManager: MediaRequestManager) => {
|
|
612
|
-
mediaRequestManager.clearPreviousRequests();
|
|
613
612
|
mediaRequestManager.commit();
|
|
614
613
|
}
|
|
615
614
|
);
|
package/src/webinar/index.ts
CHANGED
|
@@ -177,6 +177,8 @@ const Webinar = WebexPlugin.extend({
|
|
|
177
177
|
|
|
178
178
|
const finalToken = currentToken ?? practiceSessionDatachannelToken;
|
|
179
179
|
|
|
180
|
+
const isCaptionBoxOn = this.webex.internal.voicea.getIsCaptionBoxOn();
|
|
181
|
+
|
|
180
182
|
if (!currentToken && practiceSessionDatachannelToken) {
|
|
181
183
|
// @ts-ignore
|
|
182
184
|
this.webex.internal.llm.setDatachannelToken(
|
|
@@ -219,6 +221,9 @@ const Webinar = WebexPlugin.extend({
|
|
|
219
221
|
);
|
|
220
222
|
// @ts-ignore - Fix type
|
|
221
223
|
this.webex.internal.voicea?.announce?.();
|
|
224
|
+
if (isCaptionBoxOn) {
|
|
225
|
+
this.webex.internal.voicea.updateSubchannelSubscriptions({subscribe: ['transcription']});
|
|
226
|
+
}
|
|
222
227
|
LoggerProxy.logger.info(
|
|
223
228
|
`Webinar:index#updatePSDataChannel --> enabled to receive relay events for default session for ${LLM_PRACTICE_SESSION}!`
|
|
224
229
|
);
|
|
@@ -5,6 +5,7 @@ import MockWebex from '@webex/test-helper-mock-webex';
|
|
|
5
5
|
import {WebexHttpError} from '@webex/webex-core';
|
|
6
6
|
import DataChannelAuthTokenInterceptor from '@webex/plugin-meetings/src/interceptors/dataChannelAuthToken';
|
|
7
7
|
import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
|
|
8
|
+
import * as utils from '@webex/plugin-meetings/src/interceptors/utils';
|
|
8
9
|
import {DATA_CHANNEL_AUTH_HEADER, MAX_RETRY} from '@webex/plugin-meetings/src/interceptors/constant';
|
|
9
10
|
|
|
10
11
|
describe('plugin-meetings', () => {
|
|
@@ -14,6 +15,10 @@ describe('plugin-meetings', () => {
|
|
|
14
15
|
|
|
15
16
|
beforeEach(() => {
|
|
16
17
|
clock = sinon.useFakeTimers();
|
|
18
|
+
sinon.stub(LoggerProxy, 'logger').value({
|
|
19
|
+
error: sinon.stub(),
|
|
20
|
+
warn: sinon.stub(),
|
|
21
|
+
});
|
|
17
22
|
|
|
18
23
|
webex = new MockWebex({children: {}});
|
|
19
24
|
webex.request = sinon.stub().resolves({});
|
|
@@ -25,6 +30,7 @@ describe('plugin-meetings', () => {
|
|
|
25
30
|
});
|
|
26
31
|
|
|
27
32
|
afterEach(() => {
|
|
33
|
+
sinon.restore();
|
|
28
34
|
clock.restore();
|
|
29
35
|
});
|
|
30
36
|
|
|
@@ -86,6 +92,69 @@ describe('plugin-meetings', () => {
|
|
|
86
92
|
});
|
|
87
93
|
});
|
|
88
94
|
|
|
95
|
+
describe('#onRequest', () => {
|
|
96
|
+
let isJwtTokenExpiredStub;
|
|
97
|
+
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
isJwtTokenExpiredStub = sinon.stub(utils, 'isJwtTokenExpired').returns(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('does nothing when token is missing', async () => {
|
|
103
|
+
const options = {headers: {}};
|
|
104
|
+
|
|
105
|
+
const res = await interceptor.onRequest(options);
|
|
106
|
+
|
|
107
|
+
expect(res).to.equal(options);
|
|
108
|
+
sinon.assert.notCalled(isJwtTokenExpiredStub);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('does nothing when feature is disabled', async () => {
|
|
112
|
+
interceptor._isDataChannelTokenEnabled.resolves(false);
|
|
113
|
+
|
|
114
|
+
const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'}};
|
|
115
|
+
const res = await interceptor.onRequest(options);
|
|
116
|
+
|
|
117
|
+
expect(res).to.equal(options);
|
|
118
|
+
sinon.assert.notCalled(isJwtTokenExpiredStub);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('does not refresh when token is not expired', async () => {
|
|
122
|
+
interceptor._isDataChannelTokenEnabled.resolves(true);
|
|
123
|
+
isJwtTokenExpiredStub.returns(false);
|
|
124
|
+
|
|
125
|
+
const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'}};
|
|
126
|
+
const res = await interceptor.onRequest(options);
|
|
127
|
+
|
|
128
|
+
sinon.assert.notCalled(interceptor._refreshDataChannelToken);
|
|
129
|
+
expect(res.headers[DATA_CHANNEL_AUTH_HEADER]).to.equal('old-token');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('refreshes token when expired', async () => {
|
|
133
|
+
interceptor._isDataChannelTokenEnabled.resolves(true);
|
|
134
|
+
isJwtTokenExpiredStub.returns(true);
|
|
135
|
+
|
|
136
|
+
interceptor._refreshDataChannelToken.resolves('new-token');
|
|
137
|
+
|
|
138
|
+
const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'}};
|
|
139
|
+
const res = await interceptor.onRequest(options);
|
|
140
|
+
|
|
141
|
+
sinon.assert.calledOnce(interceptor._refreshDataChannelToken);
|
|
142
|
+
expect(res.headers[DATA_CHANNEL_AUTH_HEADER]).to.equal('new-token');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('continues request when refresh fails', async () => {
|
|
146
|
+
interceptor._isDataChannelTokenEnabled.resolves(true);
|
|
147
|
+
isJwtTokenExpiredStub.returns(true);
|
|
148
|
+
|
|
149
|
+
interceptor._refreshDataChannelToken.rejects(new Error('refresh failed'));
|
|
150
|
+
|
|
151
|
+
const options = {headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'}};
|
|
152
|
+
const res = await interceptor.onRequest(options);
|
|
153
|
+
|
|
154
|
+
expect(res.headers[DATA_CHANNEL_AUTH_HEADER]).to.equal('old-token');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
89
158
|
describe('#refreshTokenAndRetryWithDelay', () => {
|
|
90
159
|
const options = {
|
|
91
160
|
headers: {[DATA_CHANNEL_AUTH_HEADER]: 'old-token'},
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import 'jsdom-global/register';
|
|
2
|
+
import {expect} from '@webex/test-helper-chai';
|
|
3
|
+
import sinon from 'sinon';
|
|
4
|
+
import {isJwtTokenExpired} from '@webex/plugin-meetings/src/interceptors/utils';
|
|
5
|
+
|
|
6
|
+
const makeJwt = (payload) =>
|
|
7
|
+
[
|
|
8
|
+
Buffer.from(JSON.stringify({alg: 'none', typ: 'JWT'})).toString('base64url'),
|
|
9
|
+
Buffer.from(JSON.stringify(payload)).toString('base64url'),
|
|
10
|
+
''
|
|
11
|
+
].join('.');
|
|
12
|
+
|
|
13
|
+
describe('plugin-meetings', () => {
|
|
14
|
+
describe('Interceptors', () => {
|
|
15
|
+
describe('utils - isJwtTokenExpired', () => {
|
|
16
|
+
let clock;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
clock = sinon.useFakeTimers();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
sinon.restore();
|
|
24
|
+
clock.restore();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns false when token has no exp', () => {
|
|
28
|
+
const token = makeJwt({}); // no exp
|
|
29
|
+
|
|
30
|
+
const result = isJwtTokenExpired(token);
|
|
31
|
+
|
|
32
|
+
expect(result).to.equal(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('returns false when token is not expired', () => {
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
const futureExp = Math.floor((now + 60 * 1000) / 1000);
|
|
38
|
+
|
|
39
|
+
const token = makeJwt({exp: futureExp});
|
|
40
|
+
|
|
41
|
+
const result = isJwtTokenExpired(token);
|
|
42
|
+
|
|
43
|
+
expect(result).to.equal(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('returns true when token is expired', () => {
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const pastExp = Math.floor((now - 60 * 1000) / 1000);
|
|
49
|
+
|
|
50
|
+
const token = makeJwt({exp: pastExp});
|
|
51
|
+
|
|
52
|
+
const result = isJwtTokenExpired(token);
|
|
53
|
+
|
|
54
|
+
expect(result).to.equal(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('returns true when token expires within EXPIRY_BUFFER', () => {
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
const expSoon = Math.floor((now + 10 * 1000) / 1000);
|
|
60
|
+
|
|
61
|
+
const token = makeJwt({exp: expSoon});
|
|
62
|
+
|
|
63
|
+
const result = isJwtTokenExpired(token);
|
|
64
|
+
|
|
65
|
+
expect(result).to.equal(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns true when token is invalid', () => {
|
|
69
|
+
const result = isJwtTokenExpired('not-a-jwt');
|
|
70
|
+
|
|
71
|
+
expect(result).to.equal(true);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -13029,7 +13029,7 @@ describe('plugin-meetings', () => {
|
|
|
13029
13029
|
'a datachannel url',
|
|
13030
13030
|
'token-123'
|
|
13031
13031
|
);
|
|
13032
|
-
assert.calledWithExactly(webex.internal.llm.setDatachannelToken, 'token-123', 'default');
|
|
13032
|
+
assert.calledWithExactly(webex.internal.llm.setDatachannelToken, 'token-123', 'llm-default-session');
|
|
13033
13033
|
});
|
|
13034
13034
|
it('prefers refreshed token over locus self token', async () => {
|
|
13035
13035
|
meeting.joinedWith = {state: 'JOINED'};
|
|
@@ -13039,7 +13039,7 @@ describe('plugin-meetings', () => {
|
|
|
13039
13039
|
self: {datachannelToken: 'locus-token'},
|
|
13040
13040
|
};
|
|
13041
13041
|
|
|
13042
|
-
webex.internal.llm.getDatachannelToken.withArgs('default').returns('refreshed-token');
|
|
13042
|
+
webex.internal.llm.getDatachannelToken.withArgs('llm-default-session').returns('refreshed-token');
|
|
13043
13043
|
|
|
13044
13044
|
await meeting.updateLLMConnection();
|
|
13045
13045
|
|
|
@@ -13072,7 +13072,7 @@ describe('plugin-meetings', () => {
|
|
|
13072
13072
|
'a datachannel url',
|
|
13073
13073
|
'token-123'
|
|
13074
13074
|
);
|
|
13075
|
-
assert.calledWithExactly(webex.internal.llm.setDatachannelToken, 'token-123', 'default');
|
|
13075
|
+
assert.calledWithExactly(webex.internal.llm.setDatachannelToken, 'token-123', 'llm-default-session');
|
|
13076
13076
|
});
|
|
13077
13077
|
|
|
13078
13078
|
describe('#clearMeetingData', () => {
|
|
@@ -14735,7 +14735,7 @@ describe('plugin-meetings', () => {
|
|
|
14735
14735
|
expect(result).to.deep.equal({
|
|
14736
14736
|
body: {
|
|
14737
14737
|
datachannelToken: 'mock-token',
|
|
14738
|
-
dataChannelTokenType: '
|
|
14738
|
+
dataChannelTokenType: 'llm-practice-session',
|
|
14739
14739
|
},
|
|
14740
14740
|
});
|
|
14741
14741
|
});
|
|
@@ -14748,7 +14748,7 @@ describe('plugin-meetings', () => {
|
|
|
14748
14748
|
|
|
14749
14749
|
const result = meeting.getDataChannelTokenType();
|
|
14750
14750
|
|
|
14751
|
-
expect(result).to.equal('
|
|
14751
|
+
expect(result).to.equal('llm-practice-session');
|
|
14752
14752
|
});
|
|
14753
14753
|
|
|
14754
14754
|
it('returns Default when not in practice session mode', () => {
|
|
@@ -14758,7 +14758,7 @@ describe('plugin-meetings', () => {
|
|
|
14758
14758
|
|
|
14759
14759
|
const result = meeting.getDataChannelTokenType();
|
|
14760
14760
|
|
|
14761
|
-
expect(result).to.equal('default');
|
|
14761
|
+
expect(result).to.equal('llm-default-session');
|
|
14762
14762
|
});
|
|
14763
14763
|
});
|
|
14764
14764
|
describe('#stopKeepAlive', () => {
|
|
@@ -924,7 +924,14 @@ describe('plugin-meetings', () => {
|
|
|
924
924
|
const locusUrl = 'https://locus.example.com/locus/api/v1/loci/123';
|
|
925
925
|
const participantId = 'participant-123';
|
|
926
926
|
|
|
927
|
+
beforeEach(() => {
|
|
928
|
+
sinon.restore();
|
|
929
|
+
locusDeltaRequestSpy = sinon.stub(meetingsRequest, 'locusDeltaRequest');
|
|
930
|
+
});
|
|
931
|
+
|
|
927
932
|
it('sends GET request to regular datachannel token endpoint', async () => {
|
|
933
|
+
locusDeltaRequestSpy.resolves({body: {}});
|
|
934
|
+
|
|
928
935
|
await meetingsRequest.fetchDatachannelToken({
|
|
929
936
|
locusUrl,
|
|
930
937
|
requestingParticipantId: participantId,
|
|
@@ -938,6 +945,8 @@ describe('plugin-meetings', () => {
|
|
|
938
945
|
});
|
|
939
946
|
|
|
940
947
|
it('sends GET request to practice session datachannel token endpoint', async () => {
|
|
948
|
+
locusDeltaRequestSpy.resolves({body: {}});
|
|
949
|
+
|
|
941
950
|
await meetingsRequest.fetchDatachannelToken({
|
|
942
951
|
locusUrl,
|
|
943
952
|
requestingParticipantId: participantId,
|
|
@@ -950,7 +959,7 @@ describe('plugin-meetings', () => {
|
|
|
950
959
|
});
|
|
951
960
|
});
|
|
952
961
|
|
|
953
|
-
it('
|
|
962
|
+
it('rejects when locusUrl or participantId is missing', async () => {
|
|
954
963
|
await assert.isRejected(
|
|
955
964
|
meetingsRequest.fetchDatachannelToken({
|
|
956
965
|
locusUrl: null,
|
|
@@ -968,18 +977,15 @@ describe('plugin-meetings', () => {
|
|
|
968
977
|
);
|
|
969
978
|
});
|
|
970
979
|
|
|
971
|
-
it('
|
|
972
|
-
|
|
973
|
-
locusDeltaRequestSpy.restore();
|
|
974
|
-
sinon.stub(meetingsRequest, 'locusDeltaRequest').rejects(error);
|
|
980
|
+
it('returns null when locusDeltaRequest fails', async () => {
|
|
981
|
+
locusDeltaRequestSpy.rejects(new Error('network error'));
|
|
975
982
|
|
|
976
|
-
await
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
);
|
|
983
|
+
const result = await meetingsRequest.fetchDatachannelToken({
|
|
984
|
+
locusUrl,
|
|
985
|
+
requestingParticipantId: participantId,
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
assert.equal(result, null);
|
|
983
989
|
});
|
|
984
990
|
});
|
|
985
991
|
});
|
|
@@ -666,8 +666,8 @@ describe('MediaRequestManager', () => {
|
|
|
666
666
|
]);
|
|
667
667
|
});
|
|
668
668
|
|
|
669
|
-
it('
|
|
670
|
-
// send some requests and commit them
|
|
669
|
+
it('clears all the requests on reset()', () => {
|
|
670
|
+
// send some requests and commit them
|
|
671
671
|
addReceiverSelectedRequest(1500, fakeReceiveSlots[0], MAX_FS_1080p, false);
|
|
672
672
|
addReceiverSelectedRequest(1501, fakeReceiveSlots[1], MAX_FS_1080p, false);
|
|
673
673
|
addActiveSpeakerRequest(
|
|
@@ -722,95 +722,12 @@ describe('MediaRequestManager', () => {
|
|
|
722
722
|
},
|
|
723
723
|
]);
|
|
724
724
|
|
|
725
|
-
// check that when calling commit()
|
|
726
|
-
// all requests are not re-sent again (avoid duplicate requests)
|
|
727
|
-
mediaRequestManager.commit();
|
|
728
|
-
|
|
729
|
-
assert.notCalled(sendMediaRequestsCallback);
|
|
730
|
-
|
|
731
|
-
// now reset everything
|
|
732
|
-
mediaRequestManager.reset();
|
|
733
|
-
|
|
734
|
-
// calling commit now should not cause any requests to be sent out
|
|
735
|
-
mediaRequestManager.commit();
|
|
736
|
-
checkMediaRequestsSent([]);
|
|
737
|
-
});
|
|
738
|
-
|
|
739
|
-
it('makes sure to call requests correctly after reset was called and another request was added', () => {
|
|
740
|
-
addReceiverSelectedRequest(1500, fakeReceiveSlots[0], MAX_FS_1080p, false);
|
|
741
|
-
|
|
742
|
-
assert.notCalled(sendMediaRequestsCallback);
|
|
743
|
-
|
|
744
|
-
mediaRequestManager.commit();
|
|
745
|
-
checkMediaRequestsSent([
|
|
746
|
-
{
|
|
747
|
-
policy: 'receiver-selected',
|
|
748
|
-
csi: 1500,
|
|
749
|
-
receiveSlot: fakeWcmeSlots[0],
|
|
750
|
-
maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
|
|
751
|
-
maxFs: MAX_FS_1080p,
|
|
752
|
-
maxMbps: MAX_MBPS_1080p,
|
|
753
|
-
},
|
|
754
|
-
]);
|
|
755
|
-
|
|
756
725
|
// now reset everything
|
|
757
726
|
mediaRequestManager.reset();
|
|
758
727
|
|
|
759
728
|
// calling commit now should not cause any requests to be sent out
|
|
760
729
|
mediaRequestManager.commit();
|
|
761
730
|
checkMediaRequestsSent([]);
|
|
762
|
-
|
|
763
|
-
//add new request
|
|
764
|
-
addReceiverSelectedRequest(1501, fakeReceiveSlots[1], MAX_FS_1080p, false);
|
|
765
|
-
|
|
766
|
-
// commit
|
|
767
|
-
mediaRequestManager.commit();
|
|
768
|
-
|
|
769
|
-
// check the new request was sent
|
|
770
|
-
checkMediaRequestsSent([
|
|
771
|
-
{
|
|
772
|
-
policy: 'receiver-selected',
|
|
773
|
-
csi: 1501,
|
|
774
|
-
receiveSlot: fakeWcmeSlots[1],
|
|
775
|
-
maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
|
|
776
|
-
maxFs: MAX_FS_1080p,
|
|
777
|
-
maxMbps: MAX_MBPS_1080p,
|
|
778
|
-
},
|
|
779
|
-
]);
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
it('can send same media request after previous requests have been cleared', () => {
|
|
783
|
-
// add a request and commit
|
|
784
|
-
addReceiverSelectedRequest(1500, fakeReceiveSlots[0], MAX_FS_1080p, false);
|
|
785
|
-
mediaRequestManager.commit();
|
|
786
|
-
checkMediaRequestsSent([
|
|
787
|
-
{
|
|
788
|
-
policy: 'receiver-selected',
|
|
789
|
-
csi: 1500,
|
|
790
|
-
maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
|
|
791
|
-
receiveSlot: fakeWcmeSlots[0],
|
|
792
|
-
maxFs: MAX_FS_1080p,
|
|
793
|
-
maxMbps: MAX_MBPS_1080p,
|
|
794
|
-
},
|
|
795
|
-
]);
|
|
796
|
-
|
|
797
|
-
// clear previous requests
|
|
798
|
-
mediaRequestManager.clearPreviousRequests();
|
|
799
|
-
|
|
800
|
-
// commit same request
|
|
801
|
-
mediaRequestManager.commit();
|
|
802
|
-
|
|
803
|
-
// check the request was sent
|
|
804
|
-
checkMediaRequestsSent([
|
|
805
|
-
{
|
|
806
|
-
policy: 'receiver-selected',
|
|
807
|
-
csi: 1500,
|
|
808
|
-
maxPayloadBitsPerSecond: MAX_PAYLOADBITSPS_1080p,
|
|
809
|
-
receiveSlot: fakeWcmeSlots[0],
|
|
810
|
-
maxFs: MAX_FS_1080p,
|
|
811
|
-
maxMbps: MAX_MBPS_1080p,
|
|
812
|
-
},
|
|
813
|
-
]);
|
|
814
731
|
});
|
|
815
732
|
|
|
816
733
|
it('re-sends media requests after degradation preferences are set', () => {
|
|
@@ -54,8 +54,8 @@ describe('plugin-meetings', () => {
|
|
|
54
54
|
webrtcMediaConnection: fakeMediaConnection,
|
|
55
55
|
},
|
|
56
56
|
mediaRequestManagers: {
|
|
57
|
-
audio: {commit: sinon.stub()
|
|
58
|
-
video: {commit: sinon.stub()
|
|
57
|
+
audio: {commit: sinon.stub()},
|
|
58
|
+
video: {commit: sinon.stub()},
|
|
59
59
|
},
|
|
60
60
|
roap: {
|
|
61
61
|
doTurnDiscovery: sinon.stub().resolves({
|
|
@@ -179,26 +179,22 @@ describe('plugin-meetings', () => {
|
|
|
179
179
|
});
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
-
it('does not
|
|
182
|
+
it('does not re-request media for non-multistream meetings', async () => {
|
|
183
183
|
fakeMeeting.isMultistream = false;
|
|
184
184
|
const rm = new ReconnectionManager(fakeMeeting);
|
|
185
185
|
|
|
186
186
|
await rm.reconnect();
|
|
187
187
|
|
|
188
|
-
assert.notCalled(fakeMeeting.mediaRequestManagers.audio.clearPreviousRequests);
|
|
189
|
-
assert.notCalled(fakeMeeting.mediaRequestManagers.video.clearPreviousRequests);
|
|
190
188
|
assert.notCalled(fakeMeeting.mediaRequestManagers.audio.commit);
|
|
191
189
|
assert.notCalled(fakeMeeting.mediaRequestManagers.video.commit);
|
|
192
190
|
});
|
|
193
191
|
|
|
194
|
-
it('does
|
|
192
|
+
it('does re-request media for multistream meetings', async () => {
|
|
195
193
|
fakeMeeting.isMultistream = true;
|
|
196
194
|
const rm = new ReconnectionManager(fakeMeeting);
|
|
197
195
|
|
|
198
196
|
await rm.reconnect();
|
|
199
197
|
|
|
200
|
-
assert.calledOnce(fakeMeeting.mediaRequestManagers.audio.clearPreviousRequests);
|
|
201
|
-
assert.calledOnce(fakeMeeting.mediaRequestManagers.video.clearPreviousRequests);
|
|
202
198
|
assert.calledOnce(fakeMeeting.mediaRequestManagers.audio.commit);
|
|
203
199
|
assert.calledOnce(fakeMeeting.mediaRequestManagers.video.commit);
|
|
204
200
|
});
|
|
@@ -233,6 +233,8 @@ describe('plugin-meetings', () => {
|
|
|
233
233
|
// Ensure connect path is eligible
|
|
234
234
|
webinar.selfIsPanelist = true;
|
|
235
235
|
webinar.practiceSessionEnabled = true;
|
|
236
|
+
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
|
|
237
|
+
webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
|
|
236
238
|
});
|
|
237
239
|
|
|
238
240
|
it('no-ops when practice session join eligibility is false', async () => {
|
|
@@ -342,6 +344,22 @@ describe('plugin-meetings', () => {
|
|
|
342
344
|
processRelayEvent
|
|
343
345
|
);
|
|
344
346
|
});
|
|
347
|
+
|
|
348
|
+
it('subscribes to transcription when caption intent is enabled', async () => {
|
|
349
|
+
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(true);
|
|
350
|
+
|
|
351
|
+
await webinar.updatePSDataChannel();
|
|
352
|
+
|
|
353
|
+
assert.calledOnceWithExactly(webex.internal.voicea.updateSubchannelSubscriptions, { subscribe: ['transcription'] });
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('does not subscribe to transcription when caption intent is disabled', async () => {
|
|
357
|
+
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
|
|
358
|
+
|
|
359
|
+
await webinar.updatePSDataChannel();
|
|
360
|
+
|
|
361
|
+
assert.notCalled(webex.internal.voicea.updateSubchannelSubscriptions);
|
|
362
|
+
});
|
|
345
363
|
});
|
|
346
364
|
|
|
347
365
|
describe('#updateStatusByRole', () => {
|