@webex/plugin-meetings 3.3.1-next.19 → 3.3.1-next.20
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/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +35 -6
- package/dist/meeting/index.js.map +1 -1
- package/dist/types/meeting/index.d.ts +1 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +3 -3
- package/src/meeting/index.ts +39 -0
- package/test/unit/spec/meeting/index.js +94 -0
|
@@ -457,6 +457,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
457
457
|
private hasMediaConnectionConnectedAtLeastOnce;
|
|
458
458
|
private joinWithMediaRetryInfo?;
|
|
459
459
|
private connectionStateHandler?;
|
|
460
|
+
private iceCandidateErrors;
|
|
460
461
|
/**
|
|
461
462
|
* @param {Object} attrs
|
|
462
463
|
* @param {Object} options
|
package/dist/webinar/index.js
CHANGED
|
@@ -62,7 +62,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
|
|
|
62
62
|
updateCanManageWebcast: function updateCanManageWebcast(canManageWebcast) {
|
|
63
63
|
this.set('canManageWebcast', canManageWebcast);
|
|
64
64
|
},
|
|
65
|
-
version: "3.3.1-next.
|
|
65
|
+
version: "3.3.1-next.20"
|
|
66
66
|
});
|
|
67
67
|
var _default = exports.default = Webinar;
|
|
68
68
|
//# sourceMappingURL=index.js.map
|
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.3.1-next.
|
|
46
|
+
"@webex/plugin-meetings": "3.3.1-next.20",
|
|
47
47
|
"@webex/plugin-rooms": "3.3.1-next.5",
|
|
48
48
|
"@webex/test-helper-chai": "3.3.1-next.4",
|
|
49
49
|
"@webex/test-helper-mocha": "3.3.1-next.4",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@webex/internal-plugin-metrics": "3.3.1-next.4",
|
|
71
71
|
"@webex/internal-plugin-support": "3.3.1-next.5",
|
|
72
72
|
"@webex/internal-plugin-user": "3.3.1-next.4",
|
|
73
|
-
"@webex/internal-plugin-voicea": "3.3.1-next.
|
|
73
|
+
"@webex/internal-plugin-voicea": "3.3.1-next.20",
|
|
74
74
|
"@webex/media-helpers": "3.3.1-next.7",
|
|
75
75
|
"@webex/plugin-people": "3.3.1-next.5",
|
|
76
76
|
"@webex/plugin-rooms": "3.3.1-next.5",
|
|
@@ -92,5 +92,5 @@
|
|
|
92
92
|
"//": [
|
|
93
93
|
"TODO: upgrade jwt-decode when moving to node 18"
|
|
94
94
|
],
|
|
95
|
-
"version": "3.3.1-next.
|
|
95
|
+
"version": "3.3.1-next.20"
|
|
96
96
|
}
|
package/src/meeting/index.ts
CHANGED
|
@@ -682,6 +682,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
682
682
|
private hasMediaConnectionConnectedAtLeastOnce: boolean;
|
|
683
683
|
private joinWithMediaRetryInfo?: {isRetry: boolean; prevJoinResponse?: any};
|
|
684
684
|
private connectionStateHandler?: ConnectionStateHandler;
|
|
685
|
+
private iceCandidateErrors: Map<string, number>;
|
|
685
686
|
|
|
686
687
|
/**
|
|
687
688
|
* @param {Object} attrs
|
|
@@ -1481,6 +1482,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1481
1482
|
* @memberof Meeting
|
|
1482
1483
|
*/
|
|
1483
1484
|
this.connectionStateHandler = undefined;
|
|
1485
|
+
|
|
1486
|
+
/**
|
|
1487
|
+
* ICE Candidates errors map
|
|
1488
|
+
* @instance
|
|
1489
|
+
* @type {Map<[number, string], number>}
|
|
1490
|
+
* @private
|
|
1491
|
+
* @memberof Meeting
|
|
1492
|
+
*/
|
|
1493
|
+
this.iceCandidateErrors = new Map();
|
|
1484
1494
|
}
|
|
1485
1495
|
|
|
1486
1496
|
/**
|
|
@@ -6010,6 +6020,32 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6010
6020
|
);
|
|
6011
6021
|
}
|
|
6012
6022
|
);
|
|
6023
|
+
|
|
6024
|
+
this.iceCandidateErrors.clear();
|
|
6025
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ICE_CANDIDATE_ERROR, (event) => {
|
|
6026
|
+
const {errorCode} = event.error;
|
|
6027
|
+
let {errorText} = event.error;
|
|
6028
|
+
|
|
6029
|
+
if (
|
|
6030
|
+
errorCode === 600 &&
|
|
6031
|
+
errorText === 'Address not associated with the desired network interface.'
|
|
6032
|
+
) {
|
|
6033
|
+
return;
|
|
6034
|
+
}
|
|
6035
|
+
|
|
6036
|
+
if (errorText.endsWith('.')) {
|
|
6037
|
+
errorText = errorText.slice(0, -1);
|
|
6038
|
+
}
|
|
6039
|
+
|
|
6040
|
+
errorText = errorText.toLowerCase();
|
|
6041
|
+
errorText = errorText.replace(/ /g, '_');
|
|
6042
|
+
|
|
6043
|
+
const error = `${errorCode}_${errorText}`;
|
|
6044
|
+
|
|
6045
|
+
const count = this.iceCandidateErrors.get(error) || 0;
|
|
6046
|
+
|
|
6047
|
+
this.iceCandidateErrors.set(error, count + 1);
|
|
6048
|
+
});
|
|
6013
6049
|
};
|
|
6014
6050
|
|
|
6015
6051
|
/**
|
|
@@ -6848,6 +6884,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6848
6884
|
const {selectedCandidatePairChanges, numTransports} =
|
|
6849
6885
|
await this.mediaProperties.getCurrentConnectionInfo();
|
|
6850
6886
|
|
|
6887
|
+
const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
|
|
6888
|
+
|
|
6851
6889
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
|
|
6852
6890
|
correlation_id: this.correlationId,
|
|
6853
6891
|
locus_id: this.locusUrl.split('/').pop(),
|
|
@@ -6877,6 +6915,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6877
6915
|
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.iceConnectionState ||
|
|
6878
6916
|
'unknown',
|
|
6879
6917
|
...reachabilityMetrics,
|
|
6918
|
+
...iceCandidateErrors,
|
|
6880
6919
|
});
|
|
6881
6920
|
|
|
6882
6921
|
await this.cleanUpOnAddMediaFailure();
|
|
@@ -2762,6 +2762,66 @@ describe('plugin-meetings', () => {
|
|
|
2762
2762
|
assert.isOk(errorThrown);
|
|
2763
2763
|
});
|
|
2764
2764
|
|
|
2765
|
+
it('should send ICE_CANDIDATE_ERROR metric if media connection fails and ice candidate errors have been gathered', async () => {
|
|
2766
|
+
let errorThrown = undefined;
|
|
2767
|
+
|
|
2768
|
+
meeting.roap.doTurnDiscovery = sinon.stub().returns({
|
|
2769
|
+
turnServerInfo: undefined,
|
|
2770
|
+
turnDiscoverySkippedReason: undefined,
|
|
2771
|
+
});
|
|
2772
|
+
meeting.meetingState = 'ACTIVE';
|
|
2773
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
|
|
2774
|
+
|
|
2775
|
+
const forceRtcMetricsSend = sinon.stub().resolves();
|
|
2776
|
+
const closeMediaConnectionStub = sinon.stub();
|
|
2777
|
+
Media.createMediaConnection = sinon.stub().returns({
|
|
2778
|
+
close: closeMediaConnectionStub,
|
|
2779
|
+
forceRtcMetricsSend,
|
|
2780
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
2781
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
2782
|
+
on: sinon.stub(),
|
|
2783
|
+
});
|
|
2784
|
+
|
|
2785
|
+
meeting.iceCandidateErrors.set('701_error', 2);
|
|
2786
|
+
meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
|
|
2787
|
+
|
|
2788
|
+
await meeting
|
|
2789
|
+
.addMedia({
|
|
2790
|
+
mediaSettings: {},
|
|
2791
|
+
})
|
|
2792
|
+
.catch((err) => {
|
|
2793
|
+
errorThrown = err;
|
|
2794
|
+
assert.instanceOf(err, AddMediaFailed);
|
|
2795
|
+
});
|
|
2796
|
+
|
|
2797
|
+
// Check that the only metric sent is ADD_MEDIA_FAILURE
|
|
2798
|
+
assert.calledOnceWithExactly(
|
|
2799
|
+
Metrics.sendBehavioralMetric,
|
|
2800
|
+
BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
|
|
2801
|
+
{
|
|
2802
|
+
correlation_id: meeting.correlationId,
|
|
2803
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
2804
|
+
reason: errorThrown.message,
|
|
2805
|
+
stack: errorThrown.stack,
|
|
2806
|
+
code: errorThrown.code,
|
|
2807
|
+
turnDiscoverySkippedReason: undefined,
|
|
2808
|
+
turnServerUsed: true,
|
|
2809
|
+
retriedWithTurnServer: false,
|
|
2810
|
+
isMultistream: false,
|
|
2811
|
+
isJoinWithMediaRetry: false,
|
|
2812
|
+
signalingState: 'unknown',
|
|
2813
|
+
connectionState: 'unknown',
|
|
2814
|
+
iceConnectionState: 'unknown',
|
|
2815
|
+
selectedCandidatePairChanges: 2,
|
|
2816
|
+
numTransports: 1,
|
|
2817
|
+
'701_error': 2,
|
|
2818
|
+
'701_turn_host_lookup_received_error': 1
|
|
2819
|
+
}
|
|
2820
|
+
);
|
|
2821
|
+
|
|
2822
|
+
assert.isOk(errorThrown);
|
|
2823
|
+
});
|
|
2824
|
+
|
|
2765
2825
|
describe('handles StatsAnalyzer events', () => {
|
|
2766
2826
|
let prevConfigValue;
|
|
2767
2827
|
let statsAnalyzerStub;
|
|
@@ -7106,6 +7166,7 @@ describe('plugin-meetings', () => {
|
|
|
7106
7166
|
assert.isFunction(eventListeners[Event.REMOTE_TRACK_ADDED]);
|
|
7107
7167
|
assert.isFunction(eventListeners[Event.PEER_CONNECTION_STATE_CHANGED]);
|
|
7108
7168
|
assert.isFunction(eventListeners[Event.ICE_CONNECTION_STATE_CHANGED]);
|
|
7169
|
+
assert.isFunction(eventListeners[Event.ICE_CANDIDATE_ERROR]);
|
|
7109
7170
|
});
|
|
7110
7171
|
|
|
7111
7172
|
it('should trigger a media:ready event when REMOTE_TRACK_ADDED is fired', () => {
|
|
@@ -7141,6 +7202,39 @@ describe('plugin-meetings', () => {
|
|
|
7141
7202
|
});
|
|
7142
7203
|
});
|
|
7143
7204
|
|
|
7205
|
+
describe('should react on a ICE_CANDIDATE_ERROR event', () => {
|
|
7206
|
+
beforeEach(() => {
|
|
7207
|
+
meeting.setupMediaConnectionListeners();
|
|
7208
|
+
|
|
7209
|
+
});
|
|
7210
|
+
|
|
7211
|
+
it('should not collect skipped ice candidates error', () => {
|
|
7212
|
+
eventListeners[Event.ICE_CANDIDATE_ERROR]({error: { errorCode: 600, errorText: 'Address not associated with the desired network interface.' }});
|
|
7213
|
+
|
|
7214
|
+
assert.equal(meeting.iceCandidateErrors.size, 0);
|
|
7215
|
+
});
|
|
7216
|
+
|
|
7217
|
+
it('should collect valid ice candidates error', () => {
|
|
7218
|
+
eventListeners[Event.ICE_CANDIDATE_ERROR]({error: { errorCode: 701, errorText: '' }});
|
|
7219
|
+
|
|
7220
|
+
assert.equal(meeting.iceCandidateErrors.size, 1);
|
|
7221
|
+
assert.equal(meeting.iceCandidateErrors.has('701_'), true);
|
|
7222
|
+
});
|
|
7223
|
+
|
|
7224
|
+
it('should increment counter if same valid ice candidates error collected', () => {
|
|
7225
|
+
eventListeners[Event.ICE_CANDIDATE_ERROR]({error: { errorCode: 701, errorText: '' }});
|
|
7226
|
+
|
|
7227
|
+
eventListeners[Event.ICE_CANDIDATE_ERROR]({error: { errorCode: 701, errorText: 'STUN host lookup received error.' }});
|
|
7228
|
+
eventListeners[Event.ICE_CANDIDATE_ERROR]({error: { errorCode: 701, errorText: 'STUN host lookup received error.' }});
|
|
7229
|
+
|
|
7230
|
+
assert.equal(meeting.iceCandidateErrors.size, 2);
|
|
7231
|
+
assert.equal(meeting.iceCandidateErrors.has('701_'), true);
|
|
7232
|
+
assert.equal(meeting.iceCandidateErrors.get('701_'), 1);
|
|
7233
|
+
assert.equal(meeting.iceCandidateErrors.has('701_stun_host_lookup_received_error'), true);
|
|
7234
|
+
assert.equal(meeting.iceCandidateErrors.get('701_stun_host_lookup_received_error'), 2);
|
|
7235
|
+
});
|
|
7236
|
+
});
|
|
7237
|
+
|
|
7144
7238
|
describe('CONNECTION_STATE_CHANGED event when state = "Connecting"', () => {
|
|
7145
7239
|
it('sends client.ice.start correctly when hasMediaConnectionConnectedAtLeastOnce = true', () => {
|
|
7146
7240
|
meeting.hasMediaConnectionConnectedAtLeastOnce = true;
|