@webex/plugin-meetings 1.153.0 → 1.153.4
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/common/errors/webex-errors.js +27 -3
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/constants.js +8 -6
- package/dist/constants.js.map +1 -1
- package/dist/meeting/index.js +13 -10
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +24 -20
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/state.js +8 -15
- package/dist/meeting/state.js.map +1 -1
- package/dist/meeting/util.js +2 -1
- package/dist/meeting/util.js.map +1 -1
- package/dist/peer-connection-manager/index.js +52 -44
- package/dist/peer-connection-manager/index.js.map +1 -1
- package/dist/peer-connection-manager/util.js +28 -0
- package/dist/peer-connection-manager/util.js.map +1 -0
- package/package.json +5 -5
- package/src/common/errors/webex-errors.js +21 -2
- package/src/constants.js +4 -5
- package/src/meeting/index.js +13 -9
- package/src/meeting/request.js +10 -5
- package/src/meeting/state.js +8 -17
- package/src/meeting/util.js +2 -1
- package/src/peer-connection-manager/index.js +46 -46
- package/src/peer-connection-manager/util.js +19 -0
- package/test/unit/spec/meeting/index.js +35 -3
- package/test/unit/spec/meeting/request.js +53 -1
- package/test/unit/spec/meeting/utils.js +41 -0
- package/test/unit/spec/meetings/index.js +3 -1
- package/test/unit/spec/peerconnection-manager/index.js +69 -0
- package/test/unit/spec/peerconnection-manager/utils.js +25 -0
|
@@ -10,9 +10,8 @@ import Metrics from '../metrics';
|
|
|
10
10
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
11
11
|
import StaticConfig from '../common/config';
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
HOST,
|
|
13
|
+
COMPLETE,
|
|
14
|
+
GATHERING,
|
|
16
15
|
AUDIO,
|
|
17
16
|
SDP,
|
|
18
17
|
ICE_STATE,
|
|
@@ -22,15 +21,16 @@ import {
|
|
|
22
21
|
OFFER,
|
|
23
22
|
QUALITY_LEVELS,
|
|
24
23
|
MAX_FRAMESIZES,
|
|
25
|
-
METRICS_OPERATIONAL_MEASURES
|
|
26
|
-
IPV4_REGEX
|
|
24
|
+
METRICS_OPERATIONAL_MEASURES
|
|
27
25
|
} from '../constants';
|
|
28
26
|
import {error, eventType} from '../metrics/config';
|
|
29
27
|
import MediaError from '../common/errors/media';
|
|
30
28
|
import ParameterError from '../common/errors/parameter';
|
|
31
|
-
import {InvalidSdpError} from '../common/errors/webex-errors';
|
|
29
|
+
import {InvalidSdpError, IceGatheringFailed} from '../common/errors/webex-errors';
|
|
32
30
|
import BrowserDetection from '../common/browser-detection';
|
|
33
31
|
|
|
32
|
+
import PeerConnectionUtils from './util';
|
|
33
|
+
|
|
34
34
|
const {isBrowser} = BrowserDetection();
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -120,18 +120,10 @@ const isSdpInvalid = (sdp) => {
|
|
|
120
120
|
const parsedSdp = sdpTransform.parse(sdp);
|
|
121
121
|
|
|
122
122
|
for (const mediaLine of parsedSdp.media) {
|
|
123
|
-
if (mediaLine.candidates
|
|
123
|
+
if (!mediaLine.candidates || mediaLine.candidates?.length === 0) {
|
|
124
124
|
LoggerProxy.logger.error('PeerConnectionManager:index#isSdpInvalid --> iceCandidate: Ice candadate never completed');
|
|
125
125
|
|
|
126
|
-
return 'iceCandidate: Ice
|
|
127
|
-
}
|
|
128
|
-
// Sometimes the candidates might be there but only IPV6 we need to makes sure we have IPV4
|
|
129
|
-
const hostCandidate = mediaLine.candidates.filter((candidate) => !!(candidate.type === HOST && candidate.ip.match(IPV4_REGEX)));
|
|
130
|
-
|
|
131
|
-
if (hostCandidate.length === 0) {
|
|
132
|
-
LoggerProxy.logger.error('PeerConnectionManager:index#isSdpInvalid --> iceCandidate: no IPV4 candidate present');
|
|
133
|
-
|
|
134
|
-
return 'iceCandidate: no IPV4 candidate present';
|
|
126
|
+
return 'iceCandidate: Ice gathering never completed';
|
|
135
127
|
}
|
|
136
128
|
|
|
137
129
|
if (SDP.BAD_MEDIA_PORTS.includes(mediaLine.port)) {
|
|
@@ -191,43 +183,52 @@ pc.setContentSlides = (screenPc) => {
|
|
|
191
183
|
*/
|
|
192
184
|
pc.iceCandidate = (peerConnection, {remoteQualityLevel}) =>
|
|
193
185
|
new Promise((resolve, reject) => {
|
|
194
|
-
|
|
195
|
-
const
|
|
186
|
+
const now = Date.now();
|
|
187
|
+
const doneGatheringIceCandidate = () => {
|
|
188
|
+
const miliseconds = parseInt(Math.abs(Date.now() - now), 4);
|
|
189
|
+
|
|
196
190
|
peerConnection.sdp = limitBandwidth(peerConnection.localDescription.sdp);
|
|
197
191
|
peerConnection.sdp = setMaxFs(peerConnection.sdp, remoteQualityLevel);
|
|
192
|
+
peerConnection.sdp = PeerConnectionUtils.convertCLineToIpv4(peerConnection.sdp);
|
|
198
193
|
|
|
199
|
-
|
|
200
|
-
setTimeout(() => {
|
|
201
|
-
// peerconnection does gather ice candidate IP but in some cases due to firewall
|
|
202
|
-
// or proxy the ice candidate does not get gathered so we need to wait and then retry
|
|
203
|
-
// if still not valid then throw an error saying missing ice candidate
|
|
204
|
-
// if ice candidate still not present after retry
|
|
205
|
-
const invalidSdpPresent = isSdpInvalid(peerConnection.sdp);
|
|
194
|
+
const invalidSdpPresent = isSdpInvalid(peerConnection.sdp);
|
|
206
195
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
else {
|
|
211
|
-
LoggerProxy.logger.error('PeerConnectionManager:index#iceCandidate --> SDP not valid after waiting.');
|
|
212
|
-
reject(new InvalidSdpError(invalidSdpPresent));
|
|
213
|
-
}
|
|
214
|
-
}, RETRY_TIMEOUT);
|
|
196
|
+
if (invalidSdpPresent) {
|
|
197
|
+
LoggerProxy.logger.error('PeerConnectionManager:index#iceCandidate --> SDP not valid after waiting.');
|
|
198
|
+
reject(new InvalidSdpError(invalidSdpPresent));
|
|
215
199
|
}
|
|
216
|
-
|
|
217
|
-
|
|
200
|
+
LoggerProxy.logger.log(`PeerConnectionManager:index#iceCandidate --> Time to gather ice candidate ${miliseconds} miliseconds`);
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
resolve();
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// If ice has already been gathered
|
|
207
|
+
if (peerConnection.iceGatheringState === COMPLETE) {
|
|
208
|
+
doneGatheringIceCandidate();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
peerConnection.onIceGatheringStateChange = () => {
|
|
212
|
+
if (peerConnection.iceGatheringState === COMPLETE) {
|
|
213
|
+
doneGatheringIceCandidate(peerConnection);
|
|
214
|
+
}
|
|
215
|
+
if (peerConnection.iceGatheringState === GATHERING) {
|
|
216
|
+
LoggerProxy.logger.log('PeerConnectionManager:index#onIceGatheringStateChange --> Ice state changed to gathering');
|
|
218
217
|
}
|
|
219
|
-
}
|
|
218
|
+
};
|
|
220
219
|
|
|
221
220
|
peerConnection.onicecandidate = (evt) => {
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
peerConnection.sdp = setMaxFs(peerConnection.sdp, remoteQualityLevel);
|
|
225
|
-
|
|
226
|
-
if (evt.candidate === null && !isSdpInvalid(peerConnection.sdp)) {
|
|
227
|
-
clearTimeout(timeout);
|
|
228
|
-
resolve(peerConnection);
|
|
229
|
-
}
|
|
221
|
+
if (evt.candidate === null) {
|
|
222
|
+
doneGatheringIceCandidate(peerConnection);
|
|
230
223
|
}
|
|
224
|
+
else {
|
|
225
|
+
LoggerProxy.logger.log(`PeerConnectionManager:index#onicecandidate --> Candidate ${evt.candidate?.type} ${evt.candidate?.protocol} ${evt.candidate?.address}:${evt.candidate?.port}`);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
peerConnection.onicecandidateerror = (event) => {
|
|
230
|
+
LoggerProxy.logger.error('PeerConnectionManager:index#onicecandidateerror --> Failed to gather ice candidate.', event);
|
|
231
|
+
reject(new IceGatheringFailed());
|
|
231
232
|
};
|
|
232
233
|
});
|
|
233
234
|
|
|
@@ -402,8 +403,6 @@ pc.createOffer = (peerConnection, {
|
|
|
402
403
|
})
|
|
403
404
|
.then(() => pc.iceCandidate(peerConnection, {remoteQualityLevel}))
|
|
404
405
|
.then(() => {
|
|
405
|
-
peerConnection.sdp = limitBandwidth(peerConnection.localDescription.sdp);
|
|
406
|
-
peerConnection.sdp = setMaxFs(peerConnection.sdp, remoteQualityLevel);
|
|
407
406
|
if (!checkH264Support(peerConnection.sdp)) {
|
|
408
407
|
throw new MediaError('openH264 is downloading please Wait. Upload logs if not working on second try');
|
|
409
408
|
}
|
|
@@ -530,6 +529,7 @@ pc.createAnswer = (params, {meetingId, remoteQualityLevel}) => {
|
|
|
530
529
|
.then(() => {
|
|
531
530
|
peerConnection.sdp = limitBandwidth(peerConnection.localDescription.sdp);
|
|
532
531
|
peerConnection.sdp = setMaxFs(peerConnection.sdp, remoteQualityLevel);
|
|
532
|
+
peerConnection.sdp = PeerConnectionUtils.convertCLineToIpv4(peerConnection.sdp);
|
|
533
533
|
if (!checkH264Support(peerConnection.sdp)) {
|
|
534
534
|
throw new MediaError('openH264 is downloading please Wait. Upload logs if not working on second try');
|
|
535
535
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
const PeerConnectionUtils = {};
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Convert C line to IPv4
|
|
6
|
+
* @param {String} sdp
|
|
7
|
+
* @returns {String}
|
|
8
|
+
*/
|
|
9
|
+
PeerConnectionUtils.convertCLineToIpv4 = (sdp) => {
|
|
10
|
+
let replaceSdp = sdp;
|
|
11
|
+
|
|
12
|
+
// TODO: remove this once linus supports Ipv6 c line.currently linus rejects SDP with c line having ipv6 candidates we are
|
|
13
|
+
// mocking ipv6 to ipv4 candidates
|
|
14
|
+
// https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-299232
|
|
15
|
+
replaceSdp = replaceSdp.replace(/c=IN IP6 .*/gi, 'c=IN IP4 0.0.0.0');
|
|
16
|
+
|
|
17
|
+
return replaceSdp;
|
|
18
|
+
};
|
|
19
|
+
export default PeerConnectionUtils;
|
|
@@ -48,6 +48,7 @@ import WebExMeetingsErrors from '../../../../src/common/errors/webex-meetings-er
|
|
|
48
48
|
import ParameterError from '../../../../src/common/errors/parameter';
|
|
49
49
|
import PasswordError from '../../../../src/common/errors/password-error';
|
|
50
50
|
import CaptchaError from '../../../../src/common/errors/captcha-error';
|
|
51
|
+
import IntentToJoinError from '../../../../src/common/errors/intent-to-join';
|
|
51
52
|
import DefaultSDKConfig from '../../../../src/config';
|
|
52
53
|
import testUtils from '../../../utils/testUtils';
|
|
53
54
|
import {MeetingInfoV2CaptchaError, MeetingInfoV2PasswordError} from '../../../../src/meeting-info/meeting-info-v2';
|
|
@@ -609,6 +610,17 @@ describe('plugin-meetings', () => {
|
|
|
609
610
|
});
|
|
610
611
|
});
|
|
611
612
|
describe('#join', () => {
|
|
613
|
+
let sandbox = null;
|
|
614
|
+
|
|
615
|
+
beforeEach(() => {
|
|
616
|
+
sandbox = sinon.createSandbox();
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
afterEach(() => {
|
|
620
|
+
sandbox.restore();
|
|
621
|
+
sandbox = null;
|
|
622
|
+
});
|
|
623
|
+
|
|
612
624
|
it('should have #join', () => {
|
|
613
625
|
assert.exists(meeting.join);
|
|
614
626
|
});
|
|
@@ -618,8 +630,9 @@ describe('plugin-meetings', () => {
|
|
|
618
630
|
});
|
|
619
631
|
describe('successful', () => {
|
|
620
632
|
beforeEach(() => {
|
|
621
|
-
|
|
633
|
+
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve());
|
|
622
634
|
});
|
|
635
|
+
|
|
623
636
|
it('should join the meeting and return promise', async () => {
|
|
624
637
|
const join = meeting.join();
|
|
625
638
|
|
|
@@ -642,16 +655,17 @@ describe('plugin-meetings', () => {
|
|
|
642
655
|
});
|
|
643
656
|
|
|
644
657
|
it('should create new correlation ID when already joined', async () => {
|
|
645
|
-
meeting.
|
|
658
|
+
meeting.hasJoinedOnce = true;
|
|
646
659
|
await meeting.join();
|
|
647
660
|
sinon.assert.called(meeting.setCorrelationId);
|
|
648
661
|
});
|
|
649
662
|
});
|
|
650
663
|
describe('failure', () => {
|
|
651
664
|
beforeEach(() => {
|
|
652
|
-
|
|
665
|
+
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.reject());
|
|
653
666
|
meeting.logger.log = sinon.stub().returns(true);
|
|
654
667
|
});
|
|
668
|
+
|
|
655
669
|
describe('guest join', () => {
|
|
656
670
|
beforeEach(() => {
|
|
657
671
|
MeetingUtil.isPinOrGuest = sinon.stub().returns(true);
|
|
@@ -662,6 +676,24 @@ describe('plugin-meetings', () => {
|
|
|
662
676
|
assert.calledOnce(MeetingUtil.joinMeeting);
|
|
663
677
|
});
|
|
664
678
|
});
|
|
679
|
+
it('should succeed when called again after IntentToJoinError error', async () => {
|
|
680
|
+
let joinSucceeded = false;
|
|
681
|
+
|
|
682
|
+
try {
|
|
683
|
+
await meeting.join();
|
|
684
|
+
joinSucceeded = true;
|
|
685
|
+
}
|
|
686
|
+
catch (e) {
|
|
687
|
+
assert.instanceOf(e, IntentToJoinError);
|
|
688
|
+
}
|
|
689
|
+
assert.isFalse(joinSucceeded);
|
|
690
|
+
|
|
691
|
+
// IntentToJoinError means that client should call join() again
|
|
692
|
+
// with moderator and pin explicitly set
|
|
693
|
+
MeetingUtil.joinMeeting = sinon.stub().returns(Promise.resolve());
|
|
694
|
+
await meeting.join({pin: '1234', moderator: false});
|
|
695
|
+
assert.calledWith(MeetingUtil.joinMeeting, meeting, {moderator: false, pin: '1234'});
|
|
696
|
+
});
|
|
665
697
|
});
|
|
666
698
|
it('should fail if password is required', async () => {
|
|
667
699
|
meeting.passwordStatus = PASSWORD_STATUS.REQUIRED;
|
|
@@ -19,10 +19,18 @@ describe('plugin-meetings', () => {
|
|
|
19
19
|
regionCode: 'WEST-COAST'
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
webex.internal = {
|
|
23
|
+
services: {
|
|
24
|
+
get: sinon.mock().returns('locusUrl'),
|
|
25
|
+
waitForCatalog: sinon.mock().returns(Promise.resolve({}))
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
22
29
|
meetingsRequest = new MeetingRequest({}, {
|
|
23
30
|
parent: webex
|
|
24
31
|
});
|
|
25
32
|
|
|
33
|
+
|
|
26
34
|
meetingsRequest.request = sinon.mock().returns(Promise.resolve({}));
|
|
27
35
|
});
|
|
28
36
|
|
|
@@ -112,12 +120,15 @@ describe('plugin-meetings', () => {
|
|
|
112
120
|
const deviceUrl = 'deviceUrl';
|
|
113
121
|
const correlationId = 'random-uuid';
|
|
114
122
|
const roapMessage = 'roap-message';
|
|
123
|
+
const permissionToken = 'permission-token';
|
|
115
124
|
|
|
116
125
|
await meetingsRequest.joinMeeting({
|
|
117
126
|
locusUrl,
|
|
118
127
|
deviceUrl,
|
|
119
128
|
correlationId,
|
|
120
|
-
roapMessage
|
|
129
|
+
roapMessage,
|
|
130
|
+
permissionToken
|
|
131
|
+
|
|
121
132
|
});
|
|
122
133
|
const requestParams = meetingsRequest.request.getCall(0).args[0];
|
|
123
134
|
|
|
@@ -125,8 +136,49 @@ describe('plugin-meetings', () => {
|
|
|
125
136
|
assert.equal(requestParams.uri, `${locusUrl}/participant?alternateRedirect=true`);
|
|
126
137
|
assert.equal(requestParams.body.device.url, deviceUrl);
|
|
127
138
|
assert.equal(requestParams.body.device.countryCode, 'US');
|
|
139
|
+
assert.equal(requestParams.body.permissionToken, 'permission-token');
|
|
128
140
|
assert.equal(requestParams.body.device.regionCode, 'WEST-COAST');
|
|
129
141
|
});
|
|
142
|
+
|
|
143
|
+
it('sends /call with meetingNumber if inviteeAddress does not exist', async () => {
|
|
144
|
+
const deviceUrl = 'deviceUrl';
|
|
145
|
+
const correlationId = 'random-uuid';
|
|
146
|
+
const roapMessage = 'roap-message';
|
|
147
|
+
const meetingNumber = 'meetingNumber';
|
|
148
|
+
|
|
149
|
+
await meetingsRequest.joinMeeting({
|
|
150
|
+
deviceUrl,
|
|
151
|
+
correlationId,
|
|
152
|
+
roapMessage,
|
|
153
|
+
meetingNumber
|
|
154
|
+
});
|
|
155
|
+
const requestParams = meetingsRequest.request.getCall(0).args[0];
|
|
156
|
+
|
|
157
|
+
assert.equal(requestParams.method, 'POST');
|
|
158
|
+
assert.equal(requestParams.uri, 'locusUrl/loci/call?alternateRedirect=true');
|
|
159
|
+
assert.equal(requestParams.body.invitee.address, 'wbxmn:meetingNumber');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('sends /call with inviteeAddress over meetingNumber as preference', async () => {
|
|
163
|
+
const deviceUrl = 'deviceUrl';
|
|
164
|
+
const correlationId = 'random-uuid';
|
|
165
|
+
const roapMessage = 'roap-message';
|
|
166
|
+
const meetingNumber = 'meetingNumber';
|
|
167
|
+
const inviteeAddress = 'sipUrl';
|
|
168
|
+
|
|
169
|
+
await meetingsRequest.joinMeeting({
|
|
170
|
+
deviceUrl,
|
|
171
|
+
correlationId,
|
|
172
|
+
roapMessage,
|
|
173
|
+
meetingNumber,
|
|
174
|
+
inviteeAddress
|
|
175
|
+
});
|
|
176
|
+
const requestParams = meetingsRequest.request.getCall(0).args[0];
|
|
177
|
+
|
|
178
|
+
assert.equal(requestParams.method, 'POST');
|
|
179
|
+
assert.equal(requestParams.uri, 'locusUrl/loci/call?alternateRedirect=true');
|
|
180
|
+
assert.equal(requestParams.body.invitee.address, 'sipUrl');
|
|
181
|
+
});
|
|
130
182
|
});
|
|
131
183
|
|
|
132
184
|
describe('#pstn', () => {
|
|
@@ -4,6 +4,7 @@ import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
|
|
|
4
4
|
import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
|
|
5
5
|
import LoggerConfig
|
|
6
6
|
from '@webex/plugin-meetings/src/common/logs/logger-config';
|
|
7
|
+
import Metrics from '@webex/plugin-meetings/src/metrics/index';
|
|
7
8
|
|
|
8
9
|
describe('plugin-meetings', () => {
|
|
9
10
|
describe('Meeting utils function', () => {
|
|
@@ -11,6 +12,7 @@ describe('plugin-meetings', () => {
|
|
|
11
12
|
const meeting = {};
|
|
12
13
|
|
|
13
14
|
beforeEach(() => {
|
|
15
|
+
Metrics.postEvent = sinon.stub();
|
|
14
16
|
const logger = {
|
|
15
17
|
info: sandbox.stub(),
|
|
16
18
|
log: sandbox.stub(),
|
|
@@ -132,6 +134,45 @@ describe('plugin-meetings', () => {
|
|
|
132
134
|
});
|
|
133
135
|
});
|
|
134
136
|
});
|
|
137
|
+
|
|
138
|
+
describe('joinMeeting', () => {
|
|
139
|
+
it('#Should call `meetingRequest.joinMeeting', async () => {
|
|
140
|
+
const meeting = {meetingJoinUrl: 'meetingJoinUrl', locusUrl: 'locusUrl', meetingRequest: {joinMeeting: sinon.stub().returns(Promise.resolve({body: {}, headers: {}}))}};
|
|
141
|
+
|
|
142
|
+
MeetingUtil.parseLocusJoin = sinon.stub();
|
|
143
|
+
await MeetingUtil.joinMeeting(meeting, {});
|
|
144
|
+
|
|
145
|
+
assert.calledOnce(meeting.meetingRequest.joinMeeting);
|
|
146
|
+
const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
|
|
147
|
+
|
|
148
|
+
assert.equal(parameter.inviteeAddress, 'meetingJoinUrl');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('#Should fallback sipUrl if meetingJoinUrl does not exists', async () => {
|
|
152
|
+
const meeting = {sipUri: 'sipUri', locusUrl: 'locusUrl', meetingRequest: {joinMeeting: sinon.stub().returns(Promise.resolve({body: {}, headers: {}}))}};
|
|
153
|
+
|
|
154
|
+
MeetingUtil.parseLocusJoin = sinon.stub();
|
|
155
|
+
await MeetingUtil.joinMeeting(meeting, {});
|
|
156
|
+
|
|
157
|
+
assert.calledOnce(meeting.meetingRequest.joinMeeting);
|
|
158
|
+
const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
|
|
159
|
+
|
|
160
|
+
assert.equal(parameter.inviteeAddress, 'sipUri');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('#Should fallback to meetingNumber if meetingJoinUrl/sipUrl does not exists', async () => {
|
|
164
|
+
const meeting = {meetingNumber: 'meetingNumber', locusUrl: 'locusUrl', meetingRequest: {joinMeeting: sinon.stub().returns(Promise.resolve({body: {}, headers: {}}))}};
|
|
165
|
+
|
|
166
|
+
MeetingUtil.parseLocusJoin = sinon.stub();
|
|
167
|
+
await MeetingUtil.joinMeeting(meeting, {});
|
|
168
|
+
|
|
169
|
+
assert.calledOnce(meeting.meetingRequest.joinMeeting);
|
|
170
|
+
const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
|
|
171
|
+
|
|
172
|
+
assert.isUndefined(parameter.inviteeAddress);
|
|
173
|
+
assert.equal(parameter.meetingNumber, 'meetingNumber');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
135
176
|
});
|
|
136
177
|
});
|
|
137
178
|
|
|
@@ -468,7 +468,7 @@ skipInBrowser(describe)('plugin-meetings', () => {
|
|
|
468
468
|
});
|
|
469
469
|
describe('successful MeetingInfo.#fetchMeetingInfo', () => {
|
|
470
470
|
beforeEach(() => {
|
|
471
|
-
webex.meetings.meetingInfo.fetchMeetingInfo = sinon.stub().returns(Promise.resolve(
|
|
471
|
+
webex.meetings.meetingInfo.fetchMeetingInfo = sinon.stub().returns(Promise.resolve({body: {permissionToken: 'PT', meetingJoinUrl: 'meetingJoinUrl'}}));
|
|
472
472
|
});
|
|
473
473
|
it('creates the meeting from a successful meeting info fetch promise testing', async () => {
|
|
474
474
|
const meeting = webex.meetings.createMeeting('test', 'test');
|
|
@@ -482,6 +482,8 @@ skipInBrowser(describe)('plugin-meetings', () => {
|
|
|
482
482
|
assert.calledWith(webex.meetings.meetingInfo.fetchMeetingInfo, 'test');
|
|
483
483
|
assert.calledWith(MeetingsUtil.extractDestination, 'test', 'test');
|
|
484
484
|
assert.calledWith(MeetingsUtil.getMeetingAddedType, 'test');
|
|
485
|
+
assert.equal(meeting.permissionToken, 'PT');
|
|
486
|
+
assert.equal(meeting.meetingJoinUrl, 'meetingJoinUrl');
|
|
485
487
|
});
|
|
486
488
|
|
|
487
489
|
it('creates the meeting from a successful meeting info fetch meeting resolve testing', async () => {
|
|
@@ -3,9 +3,23 @@ import {assert} from '@webex/test-helper-chai';
|
|
|
3
3
|
import sinon from 'sinon';
|
|
4
4
|
import PeerConnectionManager from '@webex/plugin-meetings/src/peer-connection-manager/index';
|
|
5
5
|
import StaticConfig from '@webex/plugin-meetings/src/common/config';
|
|
6
|
+
import {IceGatheringFailed, InvalidSdpError} from '@webex/plugin-meetings/src/common/errors/webex-errors';
|
|
6
7
|
|
|
7
8
|
describe('Peerconnection Manager', () => {
|
|
8
9
|
describe('Methods', () => {
|
|
10
|
+
let peerConnection = null;
|
|
11
|
+
let sdp = null;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
sdp = 'v=0\r\no=- 1026633665396855335 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1 2\r\nr\na=msid-semantic: WMS\r\nm=audio 40903 UDP/TLS/RTP/SAVPF 111 63 103 104 9 0 8 106 105 13 110 112 113 126\r\nb=TIAS:64000\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=candidate:602151403 1 udp 2122262783 2401:4900:2301:c2cb:5ea9:46d5:7321:2531 47974 typ host generation 0 network-id 2 network-cost 900\r\na=candidate:675071982 1 udp 2122197247 2401:4900:33fc:314c:ad57:3e1d:befe:34b7 41234 typ host generation 0 network-id 3 network-cost 900\r\na=candidate:1835525403 1 tcp 1518283007 2401:4900:2301:c2cb:5ea9:46d5:7321:2531 9 typ host tcptype active generation 0 network-id 2 network-cost 900\r\na=candidate:1723808542 1 tcp 1518217471 2401:4900:33fc:314c:ad57:3e1d:befe:34b7 9 typ host tcptype active generation 0 network-id 3 network-cost 900\r\na=ice-ufrag:zK3G\r\na=ice-pwd:e9xgQIGnRsJvaFpvTAenr5JQ\r\na=ice-options:trickle\r\nm=video 43875 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 122 121 127\r\nb=TIAS:4000000\r\nc=IN IP4 0.0.0.0\r\na=periodic-keyframes:20\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=candidate:602151403 1 udp 2122262783 2401:4900:2301:c2cb:5ea9:46d5:7321:2531 41619 typ host generation 0 network-id 2 network-cost 900\r\na=candidate:675071982 1 udp 2122197247 2401:4900:33fc:314c:ad57:3e1d:befe:34b7 47098 typ host generation 0 network-id 3 network-cost 900\r\na=candidate:1835525403 1 tcp 1518283007 2401:4900:2301:c2cb:5ea9:46d5:7321:2531 9 typ host tcptype active generation 0 network-id 2 network-cost 900\r\na=candidate:1723808542 1 tcp 1518217471 2401:4900:33fc:314c:ad57:3e1d:befe:34b7 9 typ host tcptype active generation 0 network-id 3 network-cost 900\r\na=ice-ufrag:zK3G\r\na=ice-pwd:e9xgQIGnRsJvaFpvTAenr5JQ\r\na=ice-options:trickle\r\na=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\nm=video 49298 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 122 121 127\r\nb=TIAS:4000000\r\nc=IN IP4 0.0.0.0\r\na=periodic-keyframes:20\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=candidate:602151403 1 udp 2122262783 2401:4900:2301:c2cb:5ea9:46d5:7321:2531 37670 typ host generation 0 network-id 2 network-cost 900\r\na=candidate:675071982 1 udp 2122197247 2401:4900:33fc:314c:ad57:3e1d:befe:34b7 37790 typ host generation 0 network-id 3 network-cost 900\r\na=candidate:1835525403 1 tcp 1518283007 2401:4900:2301:c2cb:5ea9:46d5:7321:2531 9 typ host tcptype active generation 0 network-id 2 network-cost 900\r\na=candidate:1723808542 1 tcp 1518217471 2401:4900:33fc:314c:ad57:3e1d:befe:34b7 9 typ host tcptype active generation 0 network-id 3 network-cost 900\r\na=ice-ufrag:zK3G\r\na=ice-pwd:e9xgQIGnRsJvaFpvTAenr5JQ\r\na=ice-options:trickle\r\na=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f;\r\na=content:slides\r\n';
|
|
15
|
+
|
|
16
|
+
peerConnection = {
|
|
17
|
+
iceGatheringState: 'new',
|
|
18
|
+
onicecandidate: null,
|
|
19
|
+
onicecandidateerror: null,
|
|
20
|
+
localDescription: {sdp}
|
|
21
|
+
};
|
|
22
|
+
});
|
|
9
23
|
describe('setRemoteSessionDetails', () => {
|
|
10
24
|
it('change the start bitrate on remoteSDP', async () => {
|
|
11
25
|
StaticConfig.set({bandwidth: {audio: 50, video: 500, startBitrate: 2000}});
|
|
@@ -62,5 +76,60 @@ describe('Peerconnection Manager', () => {
|
|
|
62
76
|
assert.equal(result.sdp, remoteSdp);
|
|
63
77
|
});
|
|
64
78
|
});
|
|
79
|
+
|
|
80
|
+
describe('iceCandidate', () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
StaticConfig.set({bandwidth: {audio: 50, video: 500, startBitrate: 0}});
|
|
83
|
+
peerConnection.sdp = null;
|
|
84
|
+
});
|
|
85
|
+
it('ice gathering already completed', async () => {
|
|
86
|
+
peerConnection.iceGatheringState = 'complete';
|
|
87
|
+
|
|
88
|
+
await PeerConnectionManager.iceCandidate(peerConnection, {remoteQualityLevel: 'HIGH'});
|
|
89
|
+
console.error('Came her with sdp resolved');
|
|
90
|
+
assert(peerConnection.sdp.search('max-fs:8192'), true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('listen onIceCandidate,onicecandidateerror and onIceGatheringStateChange', async () => {
|
|
94
|
+
peerConnection.iceGatheringState = 'none';
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
peerConnection.onicecandidate({candidate: null});
|
|
97
|
+
}, 1000);
|
|
98
|
+
await PeerConnectionManager.iceCandidate(peerConnection, {remoteQualityLevel: 'HIGH'});
|
|
99
|
+
assert.isFunction(peerConnection.onIceGatheringStateChange);
|
|
100
|
+
assert.isFunction(peerConnection.onicecandidate);
|
|
101
|
+
assert.isFunction(peerConnection.onicecandidateerror);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('generate sdp with iceGatheringstate is `complet`', async () => {
|
|
105
|
+
peerConnection.iceGatheringState = 'none';
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
peerConnection.iceGatheringState = 'complete';
|
|
108
|
+
peerConnection.onIceGatheringStateChange();
|
|
109
|
+
}, 1000);
|
|
110
|
+
await PeerConnectionManager.iceCandidate(peerConnection, {remoteQualityLevel: 'HIGH'})
|
|
111
|
+
.then(() => {
|
|
112
|
+
assert(peerConnection.sdp.search('max-fs:8192'), true);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should not generate sdp if onicecandidateerror errors out ', async () => {
|
|
117
|
+
peerConnection.iceGatheringState = 'none';
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
peerConnection.onicecandidateerror();
|
|
120
|
+
}, 1000);
|
|
121
|
+
await assert.isRejected(PeerConnectionManager.iceCandidate(peerConnection, {remoteQualityLevel: 'HIGH'}), IceGatheringFailed);
|
|
122
|
+
assert.equal(peerConnection.sdp, null);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should throw generated SDP does not have candidates ', async () => {
|
|
126
|
+
peerConnection.iceGatheringState = 'none';
|
|
127
|
+
peerConnection.localDescription.sdp = 'v=0\r\no=- 1026633665396855335 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1 2\r\nr\na=msid-semantic: WMS\r\nm=audio 40903 UDP/TLS/RTP/SAVPF 111 63 103 104 9 0 8 106 105 13 110 112 113 126\r\nb=TIAS:64000\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:zK3G\r\na=ice-pwd:e9xgQIGnRsJvaFpvTAenr5JQ\r\na=ice-options:trickle\r\nm=video 43875 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 122 121 127\r\nb=TIAS:4000000\r\nc=IN IP4 0.0.0.0\r\na=periodic-keyframes:20\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:zK3G\r\na=ice-pwd:e9xgQIGnRsJvaFpvTAenr5JQ\r\na=ice-options:trickle\r\na=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\nm=video 49298 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 122 121 127\r\nb=TIAS:4000000\r\nc=IN IP4 0.0.0.0\r\na=periodic-keyframes:20\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:zK3G\r\na=ice-pwd:e9xgQIGnRsJvaFpvTAenr5JQ\r\na=ice-options:trickle\r\na=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f;\r\na=content:slides\r\n';
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
peerConnection.onicecandidate({candidate: null});
|
|
130
|
+
}, 1000);
|
|
131
|
+
await assert.isRejected(PeerConnectionManager.iceCandidate(peerConnection, {remoteQualityLevel: 'HIGH'}), InvalidSdpError);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
65
134
|
});
|
|
66
135
|
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
import {assert} from '@webex/test-helper-chai';
|
|
3
|
+
import PeerConnectionUtils from '@webex/plugin-meetings/src/peer-connection-manager/util';
|
|
4
|
+
|
|
5
|
+
describe('Peerconnection Manager', () => {
|
|
6
|
+
describe('Utils', () => {
|
|
7
|
+
describe('convertCLineToIpv4', () => {
|
|
8
|
+
it('changes ipv6 to ipv4 default', () => {
|
|
9
|
+
const localSdp = 'v=0\r\n' +
|
|
10
|
+
'm=video 5004 UDP/TLS/RTP/SAVPF 102 127 97 99\r\n' +
|
|
11
|
+
'c=IN IP6 2607:fb90:d27c:b314:211a:32dd:c47f:ffe\r\n' +
|
|
12
|
+
'a=rtpmap:127 H264/90000\r\n';
|
|
13
|
+
const resultSdp = 'v=0\r\n' +
|
|
14
|
+
'm=video 5004 UDP/TLS/RTP/SAVPF 102 127 97 99\r\n' +
|
|
15
|
+
'c=IN IP4 0.0.0.0\r\n' +
|
|
16
|
+
'a=rtpmap:127 H264/90000\r\n';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const temp = PeerConnectionUtils.convertCLineToIpv4(localSdp);
|
|
20
|
+
|
|
21
|
+
assert.equal(temp, resultSdp);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|