@webex/plugin-meetings 3.8.0-next.60 → 3.8.0-next.62
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/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/media/properties.js +94 -6
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +4 -0
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +30 -16
- package/dist/meeting/index.js.map +1 -1
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/media/properties.d.ts +15 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +4 -3
- package/src/constants.ts +1 -0
- package/src/media/properties.ts +96 -0
- package/src/meeting/in-meeting-actions.ts +8 -0
- package/src/meeting/index.ts +13 -1
- package/test/unit/spec/media/properties.ts +130 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
- package/test/unit/spec/meeting/index.js +22 -1
@@ -743,6 +743,7 @@ export declare enum SELF_POLICY {
|
|
743
743
|
SUPPORT_NETWORK_BASED_RECORD = "supportNetworkBasedRecord",
|
744
744
|
SUPPORT_PREMISE_RECORD = "supportPremiseRecord",
|
745
745
|
SUPPORT_REALTIME_CLOSE_CAPTION = "supportRealtimeCloseCaption",
|
746
|
+
SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL = "supportRealtimeCloseCaptionManual",
|
746
747
|
SUPPORT_CHAT = "supportChat",
|
747
748
|
SUPPORT_DESKTOP_SHARE_REMOTE = "supportDesktopShareRemote",
|
748
749
|
SUPPORT_DESKTOP_SHARE = "supportDesktopShare",
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { LocalCameraStream, LocalMicrophoneStream, LocalDisplayStream, LocalSystemAudioStream, RemoteStream } from '@webex/media-helpers';
|
2
|
+
import { ClientEvent } from '@webex/internal-plugin-metrics';
|
2
3
|
export type MediaDirection = {
|
3
4
|
sendAudio: boolean;
|
4
5
|
sendVideo: boolean;
|
@@ -7,6 +8,7 @@ export type MediaDirection = {
|
|
7
8
|
receiveVideo: boolean;
|
8
9
|
receiveShare: boolean;
|
9
10
|
};
|
11
|
+
export type IPVersion = ClientEvent['payload']['ipVersion'];
|
10
12
|
/**
|
11
13
|
* @class MediaProperties
|
12
14
|
*/
|
@@ -94,6 +96,18 @@ export default class MediaProperties {
|
|
94
96
|
* @returns {Object}
|
95
97
|
*/
|
96
98
|
private getTransportInfo;
|
99
|
+
/**
|
100
|
+
* Checks if the given IP address is IPv6
|
101
|
+
* @param {string} ip address to check
|
102
|
+
* @returns {boolean} true if the address is IPv6, false otherwise
|
103
|
+
*/
|
104
|
+
private isIPv6;
|
105
|
+
/** Finds out if we connected using IPv4 or IPv6
|
106
|
+
* @param {RTCPeerConnection} webrtcMediaConnection
|
107
|
+
* @param {Array<any>} allStatsReports array of RTC stats reports
|
108
|
+
* @returns {string} IPVersion
|
109
|
+
*/
|
110
|
+
private getConnectionIpVersion;
|
97
111
|
/**
|
98
112
|
* Returns the type of a connection that has been established
|
99
113
|
* It should be 'UDP' | 'TCP' | 'TURN-TLS' | 'TURN-TCP' | 'TURN-UDP' | 'unknown'
|
@@ -111,6 +125,7 @@ export default class MediaProperties {
|
|
111
125
|
*/
|
112
126
|
getCurrentConnectionInfo(): Promise<{
|
113
127
|
connectionType: string;
|
128
|
+
ipVersion?: IPVersion;
|
114
129
|
selectedCandidatePairChanges: number;
|
115
130
|
numTransports: number;
|
116
131
|
}>;
|
@@ -77,6 +77,8 @@ interface IInMeetingActions {
|
|
77
77
|
canShareDesktop?: boolean;
|
78
78
|
canShareContent?: boolean;
|
79
79
|
canTransferFile?: boolean;
|
80
|
+
canRealtimeCloseCaption?: boolean;
|
81
|
+
canRealtimeCloseCaptionManual?: boolean;
|
80
82
|
canChat?: boolean;
|
81
83
|
canDoVideo?: boolean;
|
82
84
|
canAnnotate?: boolean;
|
@@ -178,6 +180,8 @@ export default class InMeetingActions implements IInMeetingActions {
|
|
178
180
|
canShareDesktop: any;
|
179
181
|
canShareContent: any;
|
180
182
|
canTransferFile: any;
|
183
|
+
canRealtimeCloseCaption: any;
|
184
|
+
canRealtimeCloseCaptionManual: any;
|
181
185
|
canChat: any;
|
182
186
|
canDoVideo: any;
|
183
187
|
canAnnotate: any;
|
package/dist/webinar/index.js
CHANGED
package/package.json
CHANGED
@@ -43,7 +43,7 @@
|
|
43
43
|
"@webex/eslint-config-legacy": "0.0.0",
|
44
44
|
"@webex/jest-config-legacy": "0.0.0",
|
45
45
|
"@webex/legacy-tools": "0.0.0",
|
46
|
-
"@webex/plugin-meetings": "3.8.0-next.
|
46
|
+
"@webex/plugin-meetings": "3.8.0-next.62",
|
47
47
|
"@webex/plugin-rooms": "3.8.0-next.21",
|
48
48
|
"@webex/test-helper-chai": "3.8.0-next.17",
|
49
49
|
"@webex/test-helper-mocha": "3.8.0-next.17",
|
@@ -71,10 +71,11 @@
|
|
71
71
|
"@webex/internal-plugin-metrics": "3.8.0-next.17",
|
72
72
|
"@webex/internal-plugin-support": "3.8.0-next.21",
|
73
73
|
"@webex/internal-plugin-user": "3.8.0-next.17",
|
74
|
-
"@webex/internal-plugin-voicea": "3.8.0-next.
|
74
|
+
"@webex/internal-plugin-voicea": "3.8.0-next.62",
|
75
75
|
"@webex/media-helpers": "3.8.0-next.21",
|
76
76
|
"@webex/plugin-people": "3.8.0-next.19",
|
77
77
|
"@webex/plugin-rooms": "3.8.0-next.21",
|
78
|
+
"@webex/ts-sdp": "^1.8.1",
|
78
79
|
"@webex/web-capabilities": "^1.4.0",
|
79
80
|
"@webex/webex-core": "3.8.0-next.17",
|
80
81
|
"ampersand-collection": "^2.0.2",
|
@@ -92,5 +93,5 @@
|
|
92
93
|
"//": [
|
93
94
|
"TODO: upgrade jwt-decode when moving to node 18"
|
94
95
|
],
|
95
|
-
"version": "3.8.0-next.
|
96
|
+
"version": "3.8.0-next.62"
|
96
97
|
}
|
package/src/constants.ts
CHANGED
@@ -911,6 +911,7 @@ export enum SELF_POLICY {
|
|
911
911
|
SUPPORT_NETWORK_BASED_RECORD = 'supportNetworkBasedRecord',
|
912
912
|
SUPPORT_PREMISE_RECORD = 'supportPremiseRecord',
|
913
913
|
SUPPORT_REALTIME_CLOSE_CAPTION = 'supportRealtimeCloseCaption',
|
914
|
+
SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL = 'supportRealtimeCloseCaptionManual',
|
914
915
|
SUPPORT_CHAT = 'supportChat',
|
915
916
|
SUPPORT_DESKTOP_SHARE_REMOTE = 'supportDesktopShareRemote',
|
916
917
|
SUPPORT_DESKTOP_SHARE = 'supportDesktopShare',
|
package/src/media/properties.ts
CHANGED
@@ -7,6 +7,8 @@ import {
|
|
7
7
|
RemoteStream,
|
8
8
|
} from '@webex/media-helpers';
|
9
9
|
|
10
|
+
import {parse} from '@webex/ts-sdp';
|
11
|
+
import {ClientEvent} from '@webex/internal-plugin-metrics';
|
10
12
|
import {MEETINGS, QUALITY_LEVELS} from '../constants';
|
11
13
|
import LoggerProxy from '../common/logs/logger-proxy';
|
12
14
|
import MediaConnectionAwaiter from './MediaConnectionAwaiter';
|
@@ -20,6 +22,8 @@ export type MediaDirection = {
|
|
20
22
|
receiveShare: boolean;
|
21
23
|
};
|
22
24
|
|
25
|
+
export type IPVersion = ClientEvent['payload']['ipVersion'];
|
26
|
+
|
23
27
|
/**
|
24
28
|
* @class MediaProperties
|
25
29
|
*/
|
@@ -212,6 +216,91 @@ export default class MediaProperties {
|
|
212
216
|
};
|
213
217
|
}
|
214
218
|
|
219
|
+
/**
|
220
|
+
* Checks if the given IP address is IPv6
|
221
|
+
* @param {string} ip address to check
|
222
|
+
* @returns {boolean} true if the address is IPv6, false otherwise
|
223
|
+
*/
|
224
|
+
private isIPv6(ip: string): boolean {
|
225
|
+
return ip.includes(':');
|
226
|
+
}
|
227
|
+
|
228
|
+
/** Finds out if we connected using IPv4 or IPv6
|
229
|
+
* @param {RTCPeerConnection} webrtcMediaConnection
|
230
|
+
* @param {Array<any>} allStatsReports array of RTC stats reports
|
231
|
+
* @returns {string} IPVersion
|
232
|
+
*/
|
233
|
+
private getConnectionIpVersion(
|
234
|
+
webrtcMediaConnection: RTCPeerConnection,
|
235
|
+
allStatsReports: any[]
|
236
|
+
): IPVersion | undefined {
|
237
|
+
const transports = allStatsReports.filter((report) => report.type === 'transport');
|
238
|
+
|
239
|
+
let selectedCandidatePair;
|
240
|
+
|
241
|
+
if (transports.length > 0 && transports[0].selectedCandidatePairId) {
|
242
|
+
selectedCandidatePair = allStatsReports.find(
|
243
|
+
(report) =>
|
244
|
+
report.type === 'candidate-pair' && report.id === transports[0].selectedCandidatePairId
|
245
|
+
);
|
246
|
+
} else {
|
247
|
+
// Firefox doesn't have selectedCandidatePairId, but has selected property on the candidate pair
|
248
|
+
selectedCandidatePair = allStatsReports.find(
|
249
|
+
(report) => report.type === 'candidate-pair' && report.selected
|
250
|
+
);
|
251
|
+
}
|
252
|
+
|
253
|
+
if (selectedCandidatePair) {
|
254
|
+
const localCandidate = allStatsReports.find(
|
255
|
+
(report) =>
|
256
|
+
report.type === 'local-candidate' && report.id === selectedCandidatePair.localCandidateId
|
257
|
+
);
|
258
|
+
|
259
|
+
if (localCandidate) {
|
260
|
+
if (localCandidate.address) {
|
261
|
+
return this.isIPv6(localCandidate.address) ? 'IPv6' : 'IPv4';
|
262
|
+
}
|
263
|
+
|
264
|
+
try {
|
265
|
+
// safari doesn't have address field on the candidate, so we have to use the port to look up the candidate in the SDP
|
266
|
+
const localSdp = webrtcMediaConnection.localDescription.sdp;
|
267
|
+
|
268
|
+
const parsedSdp = parse(localSdp);
|
269
|
+
|
270
|
+
for (const mediaLine of parsedSdp.avMedia) {
|
271
|
+
const matchingCandidate = mediaLine.iceInfo.candidates.find(
|
272
|
+
(candidate) => candidate.port === localCandidate.port
|
273
|
+
);
|
274
|
+
if (matchingCandidate) {
|
275
|
+
return this.isIPv6(matchingCandidate.connectionAddress) ? 'IPv6' : 'IPv4';
|
276
|
+
}
|
277
|
+
}
|
278
|
+
|
279
|
+
LoggerProxy.logger.warn(
|
280
|
+
`Media:properties#getConnectionIpVersion --> failed to find local candidate in the SDP for port ${localCandidate.port}`
|
281
|
+
);
|
282
|
+
} catch (error) {
|
283
|
+
LoggerProxy.logger.warn(
|
284
|
+
`Media:properties#getConnectionIpVersion --> error while trying to find candidate in local SDP:`,
|
285
|
+
error
|
286
|
+
);
|
287
|
+
|
288
|
+
return undefined;
|
289
|
+
}
|
290
|
+
} else {
|
291
|
+
LoggerProxy.logger.warn(
|
292
|
+
`Media:properties#getConnectionIpVersion --> failed to find local candidate "${selectedCandidatePair.localCandidateId}" in getStats() results`
|
293
|
+
);
|
294
|
+
}
|
295
|
+
} else {
|
296
|
+
LoggerProxy.logger.warn(
|
297
|
+
`Media:properties#getConnectionIpVersion --> failed to find selected candidate pair in getStats() results (transports.length=${transports.length}, selectedCandidatePairId=${transports[0]?.selectedCandidatePairId})`
|
298
|
+
);
|
299
|
+
}
|
300
|
+
|
301
|
+
return undefined;
|
302
|
+
}
|
303
|
+
|
215
304
|
/**
|
216
305
|
* Returns the type of a connection that has been established
|
217
306
|
* It should be 'UDP' | 'TCP' | 'TURN-TLS' | 'TURN-TCP' | 'TURN-UDP' | 'unknown'
|
@@ -284,6 +373,7 @@ export default class MediaProperties {
|
|
284
373
|
*/
|
285
374
|
async getCurrentConnectionInfo(): Promise<{
|
286
375
|
connectionType: string;
|
376
|
+
ipVersion?: IPVersion;
|
287
377
|
selectedCandidatePairChanges: number;
|
288
378
|
numTransports: number;
|
289
379
|
}> {
|
@@ -309,10 +399,15 @@ export default class MediaProperties {
|
|
309
399
|
});
|
310
400
|
|
311
401
|
const connectionType = this.getConnectionType(allStatsReports);
|
402
|
+
const rtcPeerconnection =
|
403
|
+
this.webrtcMediaConnection.multistreamConnection?.pc.pc ||
|
404
|
+
this.webrtcMediaConnection.mediaConnection?.pc;
|
405
|
+
const ipVersion = this.getConnectionIpVersion(rtcPeerconnection, allStatsReports);
|
312
406
|
const {selectedCandidatePairChanges, numTransports} = this.getTransportInfo(allStatsReports);
|
313
407
|
|
314
408
|
return {
|
315
409
|
connectionType,
|
410
|
+
ipVersion,
|
316
411
|
selectedCandidatePairChanges,
|
317
412
|
numTransports,
|
318
413
|
};
|
@@ -323,6 +418,7 @@ export default class MediaProperties {
|
|
323
418
|
|
324
419
|
return {
|
325
420
|
connectionType: 'unknown',
|
421
|
+
ipVersion: undefined,
|
326
422
|
selectedCandidatePairChanges: -1,
|
327
423
|
numTransports: 0,
|
328
424
|
};
|
@@ -81,6 +81,8 @@ interface IInMeetingActions {
|
|
81
81
|
canShareDesktop?: boolean;
|
82
82
|
canShareContent?: boolean;
|
83
83
|
canTransferFile?: boolean;
|
84
|
+
canRealtimeCloseCaption?: boolean;
|
85
|
+
canRealtimeCloseCaptionManual?: boolean;
|
84
86
|
canChat?: boolean;
|
85
87
|
canDoVideo?: boolean;
|
86
88
|
canAnnotate?: boolean;
|
@@ -255,6 +257,10 @@ export default class InMeetingActions implements IInMeetingActions {
|
|
255
257
|
|
256
258
|
canTransferFile = null;
|
257
259
|
|
260
|
+
canRealtimeCloseCaption = null;
|
261
|
+
|
262
|
+
canRealtimeCloseCaptionManual = null;
|
263
|
+
|
258
264
|
canChat = null;
|
259
265
|
|
260
266
|
canDoVideo = null;
|
@@ -379,6 +385,8 @@ export default class InMeetingActions implements IInMeetingActions {
|
|
379
385
|
canShareDesktop: this.canShareDesktop,
|
380
386
|
canShareContent: this.canShareContent,
|
381
387
|
canTransferFile: this.canTransferFile,
|
388
|
+
canRealtimeCloseCaption: this.canRealtimeCloseCaption,
|
389
|
+
canRealtimeCloseCaptionManual: this.canRealtimeCloseCaptionManual,
|
382
390
|
canChat: this.canChat,
|
383
391
|
canDoVideo: this.canDoVideo,
|
384
392
|
canAnnotate: this.canAnnotate,
|
package/src/meeting/index.ts
CHANGED
@@ -4261,6 +4261,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
4261
4261
|
requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
|
4262
4262
|
policies: this.selfUserPolicies,
|
4263
4263
|
}),
|
4264
|
+
canRealtimeCloseCaption: ControlsOptionsUtil.hasPolicies({
|
4265
|
+
requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION],
|
4266
|
+
policies: this.selfUserPolicies,
|
4267
|
+
}),
|
4268
|
+
canRealtimeCloseCaptionManual: ControlsOptionsUtil.hasPolicies({
|
4269
|
+
requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL],
|
4270
|
+
policies: this.selfUserPolicies,
|
4271
|
+
}),
|
4264
4272
|
canChat: ControlsOptionsUtil.hasPolicies({
|
4265
4273
|
requiredPolicies: [SELF_POLICY.SUPPORT_CHAT],
|
4266
4274
|
policies: this.selfUserPolicies,
|
@@ -7772,7 +7780,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
7772
7780
|
await this.enqueueScreenShareFloorRequest();
|
7773
7781
|
}
|
7774
7782
|
|
7775
|
-
const {connectionType, selectedCandidatePairChanges, numTransports} =
|
7783
|
+
const {connectionType, ipVersion, selectedCandidatePairChanges, numTransports} =
|
7776
7784
|
await this.mediaProperties.getCurrentConnectionInfo();
|
7777
7785
|
|
7778
7786
|
const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
|
@@ -7783,6 +7791,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
7783
7791
|
correlation_id: this.correlationId,
|
7784
7792
|
locus_id: this.locusUrl.split('/').pop(),
|
7785
7793
|
connectionType,
|
7794
|
+
ipVersion,
|
7786
7795
|
selectedCandidatePairChanges,
|
7787
7796
|
numTransports,
|
7788
7797
|
isMultistream: this.isMultistream,
|
@@ -7795,6 +7804,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
7795
7804
|
// @ts-ignore
|
7796
7805
|
this.webex.internal.newMetrics.submitClientEvent({
|
7797
7806
|
name: 'client.media-engine.ready',
|
7807
|
+
payload: {
|
7808
|
+
ipVersion,
|
7809
|
+
},
|
7798
7810
|
options: {
|
7799
7811
|
meetingId: this.id,
|
7800
7812
|
},
|
@@ -2,6 +2,7 @@ import 'jsdom-global/register';
|
|
2
2
|
import {assert} from '@webex/test-helper-chai';
|
3
3
|
import sinon from 'sinon';
|
4
4
|
import {ConnectionState} from '@webex/internal-media-core';
|
5
|
+
import * as tsSdpModule from '@webex/ts-sdp';
|
5
6
|
import MediaProperties from '@webex/plugin-meetings/src/media/properties';
|
6
7
|
import {Defer} from '@webex/common';
|
7
8
|
import MediaConnectionAwaiter from '../../../../src/media/MediaConnectionAwaiter';
|
@@ -10,15 +11,21 @@ describe('MediaProperties', () => {
|
|
10
11
|
let mediaProperties;
|
11
12
|
let mockMC;
|
12
13
|
let clock;
|
14
|
+
let rtcPeerConnection;
|
13
15
|
|
14
16
|
beforeEach(() => {
|
15
17
|
clock = sinon.useFakeTimers();
|
16
18
|
|
19
|
+
rtcPeerConnection = {
|
20
|
+
localDescription: {sdp: ''},
|
21
|
+
};
|
22
|
+
|
17
23
|
mockMC = {
|
18
24
|
getStats: sinon.stub().resolves([]),
|
19
25
|
on: sinon.stub(),
|
20
26
|
off: sinon.stub(),
|
21
27
|
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
28
|
+
multistreamConnection: {pc: {pc: rtcPeerConnection}},
|
22
29
|
};
|
23
30
|
|
24
31
|
mediaProperties = new MediaProperties();
|
@@ -81,6 +88,129 @@ describe('MediaProperties', () => {
|
|
81
88
|
assert.equal(numTransports, 0);
|
82
89
|
});
|
83
90
|
|
91
|
+
describe('ipVersion', () => {
|
92
|
+
it('returns ipVersion=undefined if getStats() returns no candidate pairs', async () => {
|
93
|
+
mockMC.getStats.resolves([{type: 'something', id: '1234'}]);
|
94
|
+
const info = await mediaProperties.getCurrentConnectionInfo();
|
95
|
+
assert.equal(info.ipVersion, undefined);
|
96
|
+
});
|
97
|
+
|
98
|
+
it('returns ipVersion=undefined if getStats() returns no selected candidate pair', async () => {
|
99
|
+
mockMC.getStats.resolves([{type: 'candidate-pair', id: '1234', selected: false}]);
|
100
|
+
const info = await mediaProperties.getCurrentConnectionInfo();
|
101
|
+
assert.equal(info.ipVersion, undefined);
|
102
|
+
});
|
103
|
+
|
104
|
+
it('returns ipVersion="IPv4" if transport has selectedCandidatePairId and local candidate has IPv4 address', async () => {
|
105
|
+
mockMC.getStats.resolves([
|
106
|
+
{type: 'transport', id: 't1', selectedCandidatePairId: 'cp1'},
|
107
|
+
{type: 'candidate-pair', id: 'cp1', localCandidateId: 'lc1'},
|
108
|
+
{type: 'local-candidate', id: 'lc1', address: '192.168.1.1'},
|
109
|
+
]);
|
110
|
+
const info = await mediaProperties.getCurrentConnectionInfo();
|
111
|
+
assert.equal(info.ipVersion, 'IPv4');
|
112
|
+
});
|
113
|
+
|
114
|
+
it('returns ipVersion="IPv6" if transport has selectedCandidatePairId and local candidate has IPv6 address', async () => {
|
115
|
+
mockMC.getStats.resolves([
|
116
|
+
{type: 'transport', id: 't1', selectedCandidatePairId: 'cp1'},
|
117
|
+
{type: 'candidate-pair', id: 'cp1', localCandidateId: 'lc1'},
|
118
|
+
{type: 'local-candidate', id: 'lc1', address: 'fd8f:12e6:5e53:784f:a0ba:f8d5:b906:1acc'},
|
119
|
+
]);
|
120
|
+
const info = await mediaProperties.getCurrentConnectionInfo();
|
121
|
+
assert.equal(info.ipVersion, 'IPv6');
|
122
|
+
});
|
123
|
+
|
124
|
+
it('returns ipVersion="IPv4" if transport has no selectedCandidatePairId but finds selected candidate pair and local candidate has IPv4 address', async () => {
|
125
|
+
mockMC.getStats.resolves([
|
126
|
+
{type: 'transport', id: 't1'},
|
127
|
+
{type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
|
128
|
+
{type: 'local-candidate', id: 'lc2', address: '10.0.0.1'},
|
129
|
+
]);
|
130
|
+
const info = await mediaProperties.getCurrentConnectionInfo();
|
131
|
+
assert.equal(info.ipVersion, 'IPv4');
|
132
|
+
});
|
133
|
+
|
134
|
+
it('returns ipVersion="IPv6" if transport has no selectedCandidatePairId but finds selected candidate pair and local candidate has IPv6 address', async () => {
|
135
|
+
mockMC.getStats.resolves([
|
136
|
+
{type: 'transport', id: 't1'},
|
137
|
+
{type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
|
138
|
+
{type: 'local-candidate', id: 'lc2', address: 'fe80::1ff:fe23:4567:890a'},
|
139
|
+
]);
|
140
|
+
const info = await mediaProperties.getCurrentConnectionInfo();
|
141
|
+
assert.equal(info.ipVersion, 'IPv6');
|
142
|
+
});
|
143
|
+
|
144
|
+
describe('local candidate without address', () => {
|
145
|
+
it('return="IPv4" if candidate from SDP with matching port number has IPv4 address', async () => {
|
146
|
+
sinon.stub(tsSdpModule, 'parse').returns({
|
147
|
+
avMedia: [
|
148
|
+
{
|
149
|
+
iceInfo: {
|
150
|
+
candidates: [
|
151
|
+
{
|
152
|
+
port: 1234,
|
153
|
+
connectionAddress: '192.168.0.1',
|
154
|
+
},
|
155
|
+
],
|
156
|
+
},
|
157
|
+
},
|
158
|
+
],
|
159
|
+
});
|
160
|
+
|
161
|
+
mockMC.getStats.resolves([
|
162
|
+
{type: 'transport', id: 't1'},
|
163
|
+
{type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
|
164
|
+
{type: 'local-candidate', id: 'lc2', port: 1234},
|
165
|
+
]);
|
166
|
+
const info = await mediaProperties.getCurrentConnectionInfo();
|
167
|
+
assert.equal(info.ipVersion, 'IPv4');
|
168
|
+
|
169
|
+
assert.calledWith(tsSdpModule.parse, rtcPeerConnection.localDescription.sdp);
|
170
|
+
});
|
171
|
+
|
172
|
+
it('returns ipVersion="IPv6" if candidate from SDP with matching port number has IPv6 address', async () => {
|
173
|
+
sinon.stub(tsSdpModule, 'parse').returns({
|
174
|
+
avMedia: [
|
175
|
+
{
|
176
|
+
iceInfo: {
|
177
|
+
candidates: [
|
178
|
+
{
|
179
|
+
port: 5000,
|
180
|
+
connectionAddress: 'fe80::1ff:fe23:4567:890a',
|
181
|
+
},
|
182
|
+
],
|
183
|
+
},
|
184
|
+
},
|
185
|
+
],
|
186
|
+
});
|
187
|
+
|
188
|
+
mockMC.getStats.resolves([
|
189
|
+
{type: 'transport', id: 't1'},
|
190
|
+
{type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
|
191
|
+
{type: 'local-candidate', id: 'lc2', port: 5000},
|
192
|
+
]);
|
193
|
+
const info = await mediaProperties.getCurrentConnectionInfo();
|
194
|
+
assert.equal(info.ipVersion, 'IPv6');
|
195
|
+
|
196
|
+
assert.calledWith(tsSdpModule.parse, rtcPeerConnection.localDescription.sdp);
|
197
|
+
});
|
198
|
+
|
199
|
+
it('returns ipVersion=undefined if parsing of the SDP fails', async () => {
|
200
|
+
sinon.stub(tsSdpModule, 'parse').throws(new Error('fake error'));
|
201
|
+
|
202
|
+
mockMC.getStats.resolves([
|
203
|
+
{type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
|
204
|
+
{type: 'local-candidate', id: 'lc2', port: 5000},
|
205
|
+
]);
|
206
|
+
const info = await mediaProperties.getCurrentConnectionInfo();
|
207
|
+
assert.equal(info.ipVersion, undefined);
|
208
|
+
|
209
|
+
assert.calledWith(tsSdpModule.parse, rtcPeerConnection.localDescription.sdp);
|
210
|
+
});
|
211
|
+
});
|
212
|
+
});
|
213
|
+
|
84
214
|
describe('selectedCandidatePairChanges and numTransports', () => {
|
85
215
|
it('returns correct values when getStats() returns no transport stats at all', async () => {
|
86
216
|
mockMC.getStats.resolves([{type: 'something', id: '1234'}]);
|
@@ -76,6 +76,8 @@ describe('plugin-meetings', () => {
|
|
76
76
|
canShareDesktop: null,
|
77
77
|
canShareContent: null,
|
78
78
|
canTransferFile: null,
|
79
|
+
canRealtimeCloseCaption: null,
|
80
|
+
canRealtimeCloseCaptionManual: null,
|
79
81
|
canChat: null,
|
80
82
|
canDoVideo: null,
|
81
83
|
canAnnotate: null,
|
@@ -182,6 +184,8 @@ describe('plugin-meetings', () => {
|
|
182
184
|
'canShareDesktop',
|
183
185
|
'canShareContent',
|
184
186
|
'canTransferFile',
|
187
|
+
'canRealtimeCloseCaption',
|
188
|
+
'canRealtimeCloseCaptionManual',
|
185
189
|
'canChat',
|
186
190
|
'canDoVideo',
|
187
191
|
'canAnnotate',
|
@@ -2047,7 +2047,12 @@ describe('plugin-meetings', () => {
|
|
2047
2047
|
meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
|
2048
2048
|
meeting.mediaProperties.getCurrentConnectionInfo = sinon
|
2049
2049
|
.stub()
|
2050
|
-
.resolves({
|
2050
|
+
.resolves({
|
2051
|
+
connectionType: 'udp',
|
2052
|
+
selectedCandidatePairChanges: 2,
|
2053
|
+
numTransports: 1,
|
2054
|
+
ipVersion: 'IPv6',
|
2055
|
+
});
|
2051
2056
|
meeting.audio = muteStateStub;
|
2052
2057
|
meeting.video = muteStateStub;
|
2053
2058
|
sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
|
@@ -3059,6 +3064,9 @@ describe('plugin-meetings', () => {
|
|
3059
3064
|
});
|
3060
3065
|
assert.calledWith(webex.internal.newMetrics.submitClientEvent.thirdCall, {
|
3061
3066
|
name: 'client.media-engine.ready',
|
3067
|
+
payload: {
|
3068
|
+
ipVersion: 'IPv6',
|
3069
|
+
},
|
3062
3070
|
options: {
|
3063
3071
|
meetingId: meeting.id,
|
3064
3072
|
},
|
@@ -3115,6 +3123,7 @@ describe('plugin-meetings', () => {
|
|
3115
3123
|
locus_id: meeting.locusUrl.split('/').pop(),
|
3116
3124
|
connectionType: 'udp',
|
3117
3125
|
selectedCandidatePairChanges: 2,
|
3126
|
+
ipVersion: 'IPv6',
|
3118
3127
|
numTransports: 1,
|
3119
3128
|
isMultistream: false,
|
3120
3129
|
retriedWithTurnServer: true,
|
@@ -3268,6 +3277,7 @@ describe('plugin-meetings', () => {
|
|
3268
3277
|
locus_id: meeting.locusUrl.split('/').pop(),
|
3269
3278
|
connectionType: 'udp',
|
3270
3279
|
selectedCandidatePairChanges: 2,
|
3280
|
+
ipVersion: 'IPv6',
|
3271
3281
|
numTransports: 1,
|
3272
3282
|
isMultistream: false,
|
3273
3283
|
retriedWithTurnServer: false,
|
@@ -3443,6 +3453,7 @@ describe('plugin-meetings', () => {
|
|
3443
3453
|
correlation_id: meeting.correlationId,
|
3444
3454
|
locus_id: meeting.locusUrl.split('/').pop(),
|
3445
3455
|
connectionType: 'udp',
|
3456
|
+
ipVersion: 'IPv6',
|
3446
3457
|
selectedCandidatePairChanges: 2,
|
3447
3458
|
numTransports: 1,
|
3448
3459
|
isMultistream: false,
|
@@ -10995,6 +11006,16 @@ describe('plugin-meetings', () => {
|
|
10995
11006
|
requiredDisplayHints: [],
|
10996
11007
|
requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
|
10997
11008
|
},
|
11009
|
+
{
|
11010
|
+
actionName: 'canRealtimeCloseCaption',
|
11011
|
+
requiredDisplayHints: [],
|
11012
|
+
requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION],
|
11013
|
+
},
|
11014
|
+
{
|
11015
|
+
actionName: 'canRealtimeCloseCaptionManual',
|
11016
|
+
requiredDisplayHints: [],
|
11017
|
+
requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL],
|
11018
|
+
},
|
10998
11019
|
{
|
10999
11020
|
actionName: 'canChat',
|
11000
11021
|
requiredDisplayHints: [],
|