@webex/plugin-meetings 2.60.1-next.13 → 2.60.1-next.14
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/mediaQualityMetrics/config.d.ts +103 -99
- package/dist/mediaQualityMetrics/config.js +133 -129
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/meeting/index.js +4 -2
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.d.ts +2 -0
- package/dist/meeting/request.js +4 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meetings/index.js +19 -0
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +1 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/metrics/constants.d.ts +2 -0
- package/dist/metrics/constants.js +3 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/reachability/index.js +14 -20
- package/dist/reachability/index.js.map +1 -1
- package/dist/reconnection-manager/index.js +63 -43
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/turnDiscovery.d.ts +18 -2
- package/dist/roap/turnDiscovery.js +163 -69
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/rtcMetrics/index.d.ts +7 -0
- package/dist/rtcMetrics/index.js +38 -1
- package/dist/rtcMetrics/index.js.map +1 -1
- package/dist/statsAnalyzer/index.js +135 -23
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.d.ts +28 -4
- package/dist/statsAnalyzer/mqaUtil.js +278 -148
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +21 -21
- package/src/mediaQualityMetrics/config.ts +107 -107
- package/src/meeting/index.ts +2 -0
- package/src/meeting/request.ts +6 -0
- package/src/meetings/index.ts +22 -0
- package/src/meetings/util.ts +1 -1
- package/src/metrics/constants.ts +2 -0
- package/src/reachability/index.ts +0 -6
- package/src/reconnection-manager/index.ts +18 -7
- package/src/roap/turnDiscovery.ts +100 -24
- package/src/rtcMetrics/index.ts +43 -1
- package/src/statsAnalyzer/index.ts +158 -24
- package/src/statsAnalyzer/mqaUtil.ts +302 -154
- package/test/unit/spec/meeting/index.js +46 -0
- package/test/unit/spec/meeting/request.js +2 -0
- package/test/unit/spec/meetings/utils.js +35 -8
- package/test/unit/spec/reachability/index.ts +74 -0
- package/test/unit/spec/reconnection-manager/index.js +36 -1
- package/test/unit/spec/roap/turnDiscovery.ts +326 -76
- package/test/unit/spec/rtcMetrics/index.ts +32 -3
- package/test/unit/spec/stats-analyzer/index.js +439 -1
- package/test/utils/webex-test-users.js +12 -4
|
@@ -3853,6 +3853,7 @@ describe('plugin-meetings', () => {
|
|
|
3853
3853
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
3854
3854
|
meeting.mediaProperties.shareVideoStream = {};
|
|
3855
3855
|
meeting.mediaProperties.mediaDirection.sendShare = true;
|
|
3856
|
+
meeting.deviceUrl = 'deviceUrl.com';
|
|
3856
3857
|
meeting.state = 'JOINED';
|
|
3857
3858
|
meeting.localShareInstanceId = '1234-5678';
|
|
3858
3859
|
});
|
|
@@ -3868,6 +3869,15 @@ describe('plugin-meetings', () => {
|
|
|
3868
3869
|
await share;
|
|
3869
3870
|
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
|
3870
3871
|
|
|
3872
|
+
assert.calledWith(meeting.meetingRequest.changeMeetingFloor, {
|
|
3873
|
+
disposition: FLOOR_ACTION.GRANTED,
|
|
3874
|
+
personUrl: url1,
|
|
3875
|
+
deviceUrl: 'deviceUrl.com',
|
|
3876
|
+
uri: url1,
|
|
3877
|
+
resourceUrl: undefined,
|
|
3878
|
+
shareInstanceId: '1234-5678',
|
|
3879
|
+
});
|
|
3880
|
+
|
|
3871
3881
|
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
|
3872
3882
|
name: 'client.share.floor-grant.request',
|
|
3873
3883
|
payload: {mediaType: 'share', shareInstanceId: '1234-5678'},
|
|
@@ -3887,6 +3897,15 @@ describe('plugin-meetings', () => {
|
|
|
3887
3897
|
assert.equal(err, error);
|
|
3888
3898
|
});
|
|
3889
3899
|
|
|
3900
|
+
assert.calledWith(meeting.meetingRequest.changeMeetingFloor, {
|
|
3901
|
+
disposition: 'GRANTED',
|
|
3902
|
+
personUrl: url1,
|
|
3903
|
+
deviceUrl: 'deviceUrl.com',
|
|
3904
|
+
uri: url1,
|
|
3905
|
+
resourceUrl: undefined,
|
|
3906
|
+
shareInstanceId: '1234-5678',
|
|
3907
|
+
});
|
|
3908
|
+
|
|
3890
3909
|
assert.calledWith(getChangeMeetingFloorErrorPayloadSpy, 'forced');
|
|
3891
3910
|
|
|
3892
3911
|
// ensure the expected CA share metric is submitted
|
|
@@ -7751,11 +7770,21 @@ describe('plugin-meetings', () => {
|
|
|
7751
7770
|
meeting.locusInfo.self = {url: url2};
|
|
7752
7771
|
meeting.mediaProperties = {mediaDirection: {sendShare: true}};
|
|
7753
7772
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
7773
|
+
(meeting.deviceUrl = 'deviceUrl.com'), (meeting.localShareInstanceId = '1234-5678');
|
|
7754
7774
|
});
|
|
7755
7775
|
it('should call changeMeetingFloor()', async () => {
|
|
7756
7776
|
meeting.screenShareFloorState = 'GRANTED';
|
|
7757
7777
|
const share = meeting.releaseScreenShareFloor();
|
|
7758
7778
|
|
|
7779
|
+
assert.calledWith(meeting.meetingRequest.changeMeetingFloor, {
|
|
7780
|
+
disposition: FLOOR_ACTION.RELEASED,
|
|
7781
|
+
personUrl: url2,
|
|
7782
|
+
deviceUrl: 'deviceUrl.com',
|
|
7783
|
+
uri: url1,
|
|
7784
|
+
resourceUrl: undefined,
|
|
7785
|
+
shareInstanceId: '1234-5678',
|
|
7786
|
+
});
|
|
7787
|
+
|
|
7759
7788
|
assert.exists(share.then);
|
|
7760
7789
|
await share;
|
|
7761
7790
|
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
|
@@ -9120,6 +9149,7 @@ describe('plugin-meetings', () => {
|
|
|
9120
9149
|
meeting.locusInfo.mediaShares = [{name: 'whiteboard', url: url1}];
|
|
9121
9150
|
meeting.locusInfo.self = {url: url1};
|
|
9122
9151
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
9152
|
+
meeting.deviceUrl = 'deviceUrl.com';
|
|
9123
9153
|
});
|
|
9124
9154
|
it('should have #startWhiteboardShare', () => {
|
|
9125
9155
|
assert.exists(meeting.startWhiteboardShare);
|
|
@@ -9133,6 +9163,14 @@ describe('plugin-meetings', () => {
|
|
|
9133
9163
|
await whiteboardShare;
|
|
9134
9164
|
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
|
9135
9165
|
|
|
9166
|
+
assert.calledWith(meeting.meetingRequest.changeMeetingFloor, {
|
|
9167
|
+
disposition: FLOOR_ACTION.GRANTED,
|
|
9168
|
+
personUrl: url1,
|
|
9169
|
+
deviceUrl: 'deviceUrl.com',
|
|
9170
|
+
uri: url1,
|
|
9171
|
+
resourceUrl: {channelUrl: url2},
|
|
9172
|
+
});
|
|
9173
|
+
|
|
9136
9174
|
// ensure the CA share metric is submitted
|
|
9137
9175
|
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
|
9138
9176
|
name: 'client.share.initiated',
|
|
@@ -9149,12 +9187,20 @@ describe('plugin-meetings', () => {
|
|
|
9149
9187
|
meeting.locusInfo.mediaShares = [{name: 'whiteboard', url: url1}];
|
|
9150
9188
|
meeting.locusInfo.self = {url: url1};
|
|
9151
9189
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
9190
|
+
meeting.deviceUrl = 'deviceUrl.com';
|
|
9152
9191
|
});
|
|
9153
9192
|
it('should stop the whiteboard share', async () => {
|
|
9154
9193
|
const whiteboardShare = meeting.stopWhiteboardShare();
|
|
9155
9194
|
|
|
9156
9195
|
assert.exists(whiteboardShare.then);
|
|
9157
9196
|
await whiteboardShare;
|
|
9197
|
+
|
|
9198
|
+
assert.calledWith(meeting.meetingRequest.changeMeetingFloor, {
|
|
9199
|
+
disposition: FLOOR_ACTION.RELEASED,
|
|
9200
|
+
personUrl: url1,
|
|
9201
|
+
deviceUrl: 'deviceUrl.com',
|
|
9202
|
+
uri: url1,
|
|
9203
|
+
});
|
|
9158
9204
|
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
|
9159
9205
|
});
|
|
9160
9206
|
});
|
|
@@ -755,6 +755,7 @@ describe('plugin-meetings', () => {
|
|
|
755
755
|
deviceUrl: 'deviceUrl',
|
|
756
756
|
resourceId: 'resourceId',
|
|
757
757
|
resourceUrl: 'resourceUrl',
|
|
758
|
+
shareInstanceId: '12345',
|
|
758
759
|
uri: 'optionsUrl',
|
|
759
760
|
annotationInfo: {
|
|
760
761
|
version: '1',
|
|
@@ -768,6 +769,7 @@ describe('plugin-meetings', () => {
|
|
|
768
769
|
version: '1',
|
|
769
770
|
},
|
|
770
771
|
floor: {
|
|
772
|
+
shareInstanceId: '12345',
|
|
771
773
|
beneficiary: {
|
|
772
774
|
devices: [
|
|
773
775
|
{
|
|
@@ -162,9 +162,24 @@ describe('plugin-meetings', () => {
|
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
describe("#handleRoapMercury", () => {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
let envelope;
|
|
166
|
+
let meetingCollection;
|
|
167
|
+
let roapMessageReceived;
|
|
168
|
+
let handleTurnDiscoveryResponse;
|
|
169
|
+
let meeting;
|
|
170
|
+
|
|
171
|
+
beforeEach(() => {
|
|
172
|
+
roapMessageReceived = sinon.stub();
|
|
173
|
+
handleTurnDiscoveryResponse = sinon.stub();
|
|
174
|
+
|
|
175
|
+
meeting = {
|
|
176
|
+
id: 'meeting-id',
|
|
177
|
+
roapMessageReceived,
|
|
178
|
+
roap: {
|
|
179
|
+
turnDiscovery: {handleTurnDiscoveryResponse}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
envelope = {
|
|
168
183
|
data: {
|
|
169
184
|
message:{
|
|
170
185
|
seq: "seq",
|
|
@@ -178,13 +193,12 @@ describe('plugin-meetings', () => {
|
|
|
178
193
|
eventType: 'locus.message.roap',
|
|
179
194
|
}
|
|
180
195
|
};
|
|
181
|
-
|
|
182
|
-
getByKey: () =>
|
|
183
|
-
id: 'meeting-id',
|
|
184
|
-
roapMessageReceived
|
|
185
|
-
})
|
|
196
|
+
meetingCollection = {
|
|
197
|
+
getByKey: () => meeting
|
|
186
198
|
};
|
|
199
|
+
});
|
|
187
200
|
|
|
201
|
+
it('it sends the correct behaviour metric', () => {
|
|
188
202
|
MeetingsUtil.handleRoapMercury(envelope, meetingCollection);
|
|
189
203
|
assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ROAP_MERCURY_EVENT_RECEIVED, {
|
|
190
204
|
correlation_id: 'correlationId',
|
|
@@ -203,6 +217,19 @@ describe('plugin-meetings', () => {
|
|
|
203
217
|
})
|
|
204
218
|
|
|
205
219
|
});
|
|
220
|
+
|
|
221
|
+
it('calls handleTurnDiscoveryResponse for TURN_DISCOVERY_RESPONSE', () => {
|
|
222
|
+
envelope.data.message.messageType = 'TURN_DISCOVERY_RESPONSE';
|
|
223
|
+
delete envelope.data.message.sdps;
|
|
224
|
+
MeetingsUtil.handleRoapMercury(envelope, meetingCollection);
|
|
225
|
+
assert.calledWith(meeting.roap.turnDiscovery.handleTurnDiscoveryResponse, {
|
|
226
|
+
seq: "seq",
|
|
227
|
+
messageType: 'TURN_DISCOVERY_RESPONSE',
|
|
228
|
+
errorType: 'errorType',
|
|
229
|
+
tieBreaker: 'tieBreaker',
|
|
230
|
+
errorCause: 'errorCause',
|
|
231
|
+
}, 'from mercury')
|
|
232
|
+
});
|
|
206
233
|
})
|
|
207
234
|
});
|
|
208
235
|
|
|
@@ -128,6 +128,11 @@ describe('gatherReachability', () => {
|
|
|
128
128
|
'reachability.result',
|
|
129
129
|
JSON.stringify({old: 'results'})
|
|
130
130
|
);
|
|
131
|
+
await webex.boundedStorage.put(
|
|
132
|
+
'Reachability',
|
|
133
|
+
'reachability.joinCookie',
|
|
134
|
+
JSON.stringify({old: 'joinCookie'})
|
|
135
|
+
);
|
|
131
136
|
});
|
|
132
137
|
|
|
133
138
|
afterEach(() => {
|
|
@@ -169,6 +174,75 @@ describe('gatherReachability', () => {
|
|
|
169
174
|
assert.equal(JSON.stringify(getClustersResult.joinCookie), storedResultForJoinCookie);
|
|
170
175
|
});
|
|
171
176
|
|
|
177
|
+
it('keeps the stored reachability from previous call to gatherReachability if getClusters fails', async () => {
|
|
178
|
+
const reachability = new Reachability(webex);
|
|
179
|
+
|
|
180
|
+
const reachabilityResults = {
|
|
181
|
+
clusters: {
|
|
182
|
+
clusterId: {
|
|
183
|
+
udp: 'testUDP',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
const getClustersResult = {
|
|
188
|
+
clusters: {clusterId: 'cluster'},
|
|
189
|
+
joinCookie: {id: 'id'},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
reachability.reachabilityRequest.getClusters = sinon.stub().throws();
|
|
193
|
+
|
|
194
|
+
const result = await reachability.gatherReachability();
|
|
195
|
+
|
|
196
|
+
assert.empty(result);
|
|
197
|
+
|
|
198
|
+
const storedResultForReachabilityResult = await webex.boundedStorage.get(
|
|
199
|
+
'Reachability',
|
|
200
|
+
'reachability.result'
|
|
201
|
+
);
|
|
202
|
+
const storedResultForJoinCookie = await webex.boundedStorage.get(
|
|
203
|
+
'Reachability',
|
|
204
|
+
'reachability.joinCookie'
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
assert.equal(JSON.stringify({old: 'results'}), storedResultForReachabilityResult);
|
|
208
|
+
assert.equal(JSON.stringify({old: 'joinCookie'}), storedResultForJoinCookie);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('keeps the stored reachability from previous call to gatherReachability if performReachabilityChecks fails', async () => {
|
|
212
|
+
const reachability = new Reachability(webex);
|
|
213
|
+
|
|
214
|
+
const reachabilityResults = {
|
|
215
|
+
clusters: {
|
|
216
|
+
clusterId: {
|
|
217
|
+
udp: 'testUDP',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
const getClustersResult = {
|
|
222
|
+
clusters: {clusterId: 'cluster'},
|
|
223
|
+
joinCookie: {id: 'id'},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
|
|
227
|
+
(reachability as any).performReachabilityChecks = sinon.stub().throws();
|
|
228
|
+
|
|
229
|
+
const result = await reachability.gatherReachability();
|
|
230
|
+
|
|
231
|
+
assert.empty(result);
|
|
232
|
+
|
|
233
|
+
const storedResultForReachabilityResult = await webex.boundedStorage.get(
|
|
234
|
+
'Reachability',
|
|
235
|
+
'reachability.result'
|
|
236
|
+
);
|
|
237
|
+
const storedResultForJoinCookie = await webex.boundedStorage.get(
|
|
238
|
+
'Reachability',
|
|
239
|
+
'reachability.joinCookie'
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
assert.equal(JSON.stringify({old: 'results'}), storedResultForReachabilityResult);
|
|
243
|
+
assert.equal(JSON.stringify({old: 'joinCookie'}), storedResultForJoinCookie);
|
|
244
|
+
});
|
|
245
|
+
|
|
172
246
|
it('starts ClusterReachability on each media cluster', async () => {
|
|
173
247
|
webex.config.meetings.experimental = {enableTcpReachability: true};
|
|
174
248
|
|
|
@@ -4,6 +4,8 @@ import chaiAsPromised from 'chai-as-promised';
|
|
|
4
4
|
import sinon from 'sinon';
|
|
5
5
|
import ReconnectionManager from '@webex/plugin-meetings/src/reconnection-manager';
|
|
6
6
|
import { RECONNECTION } from '../../../../src/constants';
|
|
7
|
+
import LoggerProxy from '../../../../src/common/logs/logger-proxy';
|
|
8
|
+
import LoggerConfig from '../../../../src/common/logs/logger-config';
|
|
7
9
|
|
|
8
10
|
const {assert} = chai;
|
|
9
11
|
|
|
@@ -12,8 +14,16 @@ sinon.assert.expose(chai.assert, {prefix: ''});
|
|
|
12
14
|
|
|
13
15
|
describe('plugin-meetings', () => {
|
|
14
16
|
describe('ReconnectionManager.reconnect', () => {
|
|
17
|
+
const sandbox = sinon.createSandbox();
|
|
15
18
|
let fakeMediaConnection;
|
|
16
19
|
let fakeMeeting;
|
|
20
|
+
let loggerSpy;
|
|
21
|
+
|
|
22
|
+
before(() => {
|
|
23
|
+
LoggerConfig.set({ enable: false });
|
|
24
|
+
LoggerProxy.set();
|
|
25
|
+
loggerSpy = sandbox.spy(LoggerProxy.logger, 'info');
|
|
26
|
+
});
|
|
17
27
|
|
|
18
28
|
beforeEach(() => {
|
|
19
29
|
fakeMediaConnection = {
|
|
@@ -65,6 +75,7 @@ describe('plugin-meetings', () => {
|
|
|
65
75
|
meetings: {
|
|
66
76
|
getMeetingByType: sinon.stub().returns(true),
|
|
67
77
|
syncMeetings: sinon.stub().resolves({}),
|
|
78
|
+
startReachability: sinon.stub().resolves({}),
|
|
68
79
|
},
|
|
69
80
|
internal: {
|
|
70
81
|
newMetrics: {
|
|
@@ -75,6 +86,10 @@ describe('plugin-meetings', () => {
|
|
|
75
86
|
};
|
|
76
87
|
});
|
|
77
88
|
|
|
89
|
+
afterEach(() => {
|
|
90
|
+
sandbox.reset();
|
|
91
|
+
});
|
|
92
|
+
|
|
78
93
|
it('syncs meetings if it is not an unverified guest', async () => {
|
|
79
94
|
const rm = new ReconnectionManager(fakeMeeting);
|
|
80
95
|
|
|
@@ -94,6 +109,27 @@ describe('plugin-meetings', () => {
|
|
|
94
109
|
assert.notCalled(rm.webex.meetings.syncMeetings);
|
|
95
110
|
});
|
|
96
111
|
|
|
112
|
+
it('calls startReachability on reconnect', async () => {
|
|
113
|
+
const rm = new ReconnectionManager(fakeMeeting);
|
|
114
|
+
|
|
115
|
+
await rm.reconnect();
|
|
116
|
+
|
|
117
|
+
assert.calledOnce(rm.webex.meetings.startReachability);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('continues with reconnection attempt if startReachability throws an error', async () => {
|
|
121
|
+
const reachabilityError = new Error();
|
|
122
|
+
fakeMeeting.webex.meetings.startReachability = sinon.stub().throws(reachabilityError);
|
|
123
|
+
|
|
124
|
+
const rm = new ReconnectionManager(fakeMeeting);
|
|
125
|
+
|
|
126
|
+
await rm.reconnect();
|
|
127
|
+
|
|
128
|
+
assert.calledOnce(rm.webex.meetings.startReachability);
|
|
129
|
+
assert.calledWith(loggerSpy, 'ReconnectionManager:index#reconnect --> Reachability failed, continuing with reconnection attempt, err: ', reachabilityError);
|
|
130
|
+
assert.calledWith(loggerSpy, 'ReconnectionManager:index#executeReconnection --> Attempting to reconnect to meeting.');
|
|
131
|
+
});
|
|
132
|
+
|
|
97
133
|
it('uses correct TURN TLS information on the reconnection', async () => {
|
|
98
134
|
const rm = new ReconnectionManager(fakeMeeting);
|
|
99
135
|
|
|
@@ -142,7 +178,6 @@ describe('plugin-meetings', () => {
|
|
|
142
178
|
assert.calledOnce(fakeMeeting.mediaRequestManagers.video.commit);
|
|
143
179
|
});
|
|
144
180
|
|
|
145
|
-
|
|
146
181
|
it('sends the correct client event when reconnection fails', async () => {
|
|
147
182
|
sinon.stub(ReconnectionManager.prototype, 'executeReconnection').rejects();
|
|
148
183
|
fakeMeeting.isMultistream = true;
|